File manager - Edit - /home/veronikagstoette/public_html/litespeed-cache.tar
Back
composer.json 0000644 00000000633 15246276230 0007277 0 ustar 00 { "name": "litespeedtech/lscache_wp", "require-dev": { "squizlabs/php_codesniffer": "*", "phpcompatibility/php-compatibility": "*" }, "prefer-stable": true, "scripts": { "post-install-cmd": "phpcs --config-set installed_paths vendor/phpcompatibility/php-compatibility", "post-update-cmd": "phpcs --config-set installed_paths vendor/phpcompatibility/php-compatibility", "sniff-check": "phpcs" } } src/report.cls.php 0000644 00000014221 15246276230 0010146 0 ustar 00 <?php /** * The report class * * * @since 1.1.0 * @package LiteSpeed * @subpackage LiteSpeed/src * @author LiteSpeed Technologies <info@litespeedtech.com> */ namespace LiteSpeed; defined('WPINC') || exit(); class Report extends Base { const TYPE_SEND_REPORT = 'send_report'; /** * Handle all request actions from main cls * * @since 1.6.5 * @access public */ public function handler() { $type = Router::verify_type(); switch ($type) { case self::TYPE_SEND_REPORT: $this->post_env(); break; default: break; } Admin::redirect(); } /** * post env report number to ls center server * * @since 1.6.5 * @access public */ public function post_env() { $report_con = $this->generate_environment_report(); // Generate link $link = !empty($_POST['link']) ? esc_url($_POST['link']) : ''; $notes = !empty($_POST['notes']) ? esc_html($_POST['notes']) : ''; $php_info = !empty($_POST['attach_php']) ? esc_html($_POST['attach_php']) : ''; $report_php = $php_info === '1' ? $this->generate_php_report() : ''; if ($report_php) { $report_con .= "\nPHPINFO\n" . $report_php; } $data = array( 'env' => $report_con, 'link' => $link, 'notes' => $notes, ); $json = Cloud::post(Cloud::API_REPORT, $data); if (!is_array($json)) { return; } $num = !empty($json['num']) ? $json['num'] : '--'; $summary = array( 'num' => $num, 'dateline' => time(), ); self::save_summary($summary); return $num; } /** * Gathers the PHP information. * * @since 7.0 * @access public */ public function generate_php_report($flags = INFO_GENERAL | INFO_CONFIGURATION | INFO_MODULES) { // INFO_ENVIRONMENT $report = ''; ob_start(); phpinfo($flags); $report = ob_get_contents(); ob_end_clean(); preg_match('%<style type="text/css">(.*?)</style>.*?<body>(.*?)</body>%s', $report, $report); return $report[2]; } /** * Gathers the environment details and creates the report. * Will write to the environment report file. * * @since 1.0.12 * @access public */ public function generate_environment_report($options = null) { global $wp_version, $_SERVER; $frontend_htaccess = Htaccess::get_frontend_htaccess(); $backend_htaccess = Htaccess::get_backend_htaccess(); $paths = array($frontend_htaccess); if ($frontend_htaccess != $backend_htaccess) { $paths[] = $backend_htaccess; } if (is_multisite()) { $active_plugins = get_site_option('active_sitewide_plugins'); if (!empty($active_plugins)) { $active_plugins = array_keys($active_plugins); } } else { $active_plugins = get_option('active_plugins'); } if (function_exists('wp_get_theme')) { $theme_obj = wp_get_theme(); $active_theme = $theme_obj->get('Name'); } else { $active_theme = get_current_theme(); } $extras = array( 'wordpress version' => $wp_version, 'siteurl' => get_option('siteurl'), 'home' => get_option('home'), 'home_url' => home_url(), 'locale' => get_locale(), 'active theme' => $active_theme, ); $extras['active plugins'] = $active_plugins; $extras['cloud'] = Cloud::get_summary(); foreach (array('mini_html', 'pk_b64', 'sk_b64', 'cdn_dash', 'ips') as $v) { if (!empty($extras['cloud'][$v])) { unset($extras['cloud'][$v]); } } if (is_null($options)) { $options = $this->get_options(true); if (is_multisite()) { $options2 = $this->get_options(); foreach ($options2 as $k => $v) { if (isset($options[$k]) && $options[$k] !== $v) { $options['[Overwritten] ' . $k] = $v; } } } } if (!is_null($options) && is_multisite()) { $blogs = Activation::get_network_ids(); if (!empty($blogs)) { $i = 0; foreach ($blogs as $blog_id) { if (++$i > 3) { // Only log 3 subsites break; } $opts = $this->cls('Conf')->load_options($blog_id, true); if (isset($opts[self::O_CACHE])) { $options['blog ' . $blog_id . ' radio select'] = $opts[self::O_CACHE]; } } } } // Security: Remove cf key in report $secure_fields = array(self::O_CDN_CLOUDFLARE_KEY, self::O_OBJECT_PSWD); foreach ($secure_fields as $v) { if (!empty($options[$v])) { $options[$v] = str_repeat('*', strlen($options[$v])); } } $report = $this->build_environment_report($_SERVER, $options, $extras, $paths); return $report; } /** * Builds the environment report buffer with the given parameters * * @access private */ private function build_environment_report($server, $options, $extras = array(), $htaccess_paths = array()) { $server_keys = array( 'DOCUMENT_ROOT' => '', 'SERVER_SOFTWARE' => '', 'X-LSCACHE' => '', 'HTTP_X_LSCACHE' => '', ); $server_vars = array_intersect_key($server, $server_keys); $server_vars[] = 'LSWCP_TAG_PREFIX = ' . LSWCP_TAG_PREFIX; $server_vars = array_merge($server_vars, $this->cls('Base')->server_vars()); $buf = $this->_format_report_section('Server Variables', $server_vars); $buf .= $this->_format_report_section('WordPress Specific Extras', $extras); $buf .= $this->_format_report_section('LSCache Plugin Options', $options); if (empty($htaccess_paths)) { return $buf; } foreach ($htaccess_paths as $path) { if (!file_exists($path) || !is_readable($path)) { $buf .= $path . " does not exist or is not readable.\n"; continue; } $content = file_get_contents($path); if ($content === false) { $buf .= $path . " returned false for file_get_contents.\n"; continue; } $buf .= $path . " contents:\n" . $content . "\n\n"; } return $buf; } /** * Creates a part of the environment report based on a section header and an array for the section parameters. * * @since 1.0.12 * @access private */ private function _format_report_section($section_header, $section) { $tab = ' '; // four spaces if (empty($section)) { return 'No matching ' . $section_header . "\n\n"; } $buf = $section_header; foreach ($section as $k => $v) { $buf .= "\n" . $tab; if (!is_numeric($k)) { $buf .= $k . ' = '; } if (!is_string($v)) { $v = var_export($v, true); } else { $v = esc_html($v); } $buf .= $v; } return $buf . "\n\n"; } } src/img-optm.cls.php 0000644 00000200144 15246276230 0010365 0 ustar 00 <?php /** * The class to optimize image. * * @since 2.0 * @package LiteSpeed * @subpackage LiteSpeed/src * @author LiteSpeed Technologies <info@litespeedtech.com> */ namespace LiteSpeed; use WpOrg\Requests\Autoload; use WpOrg\Requests\Requests; defined('WPINC') || exit(); class Img_Optm extends Base { const LOG_TAG = '🗜️'; const CLOUD_ACTION_NEW_REQ = 'new_req'; const CLOUD_ACTION_TAKEN = 'taken'; const CLOUD_ACTION_REQUEST_DESTROY = 'imgoptm_destroy'; const CLOUD_ACTION_CLEAN = 'clean'; const TYPE_NEW_REQ = 'new_req'; const TYPE_RESCAN = 'rescan'; const TYPE_DESTROY = 'destroy'; const TYPE_RESET_COUNTER = 'reset_counter'; const TYPE_CLEAN = 'clean'; const TYPE_PULL = 'pull'; const TYPE_BATCH_SWITCH_ORI = 'batch_switch_ori'; const TYPE_BATCH_SWITCH_OPTM = 'batch_switch_optm'; const TYPE_CALC_BKUP = 'calc_bkup'; const TYPE_RESET_ROW = 'reset_row'; const TYPE_RM_BKUP = 'rm_bkup'; const STATUS_NEW = 0; // 'new'; const STATUS_RAW = 1; // 'raw'; const STATUS_REQUESTED = 3; // 'requested'; const STATUS_NOTIFIED = 6; // 'notified'; const STATUS_DUPLICATED = 8; // 'duplicated'; const STATUS_PULLED = 9; // 'pulled'; const STATUS_FAILED = -1; //'failed'; const STATUS_MISS = -3; // 'miss'; const STATUS_ERR_FETCH = -5; // 'err_fetch'; const STATUS_ERR_404 = -6; // 'err_404'; const STATUS_ERR_OPTM = -7; // 'err_optm'; const STATUS_XMETA = -8; // 'xmeta'; const STATUS_ERR = -9; // 'err'; const DB_SIZE = 'litespeed-optimize-size'; const DB_SET = 'litespeed-optimize-set'; const DB_NEED_PULL = 'need_pull'; private $wp_upload_dir; private $tmp_pid; private $tmp_type; private $tmp_path; private $_img_in_queue = array(); private $_existed_src_list = array(); private $_pids_set = array(); private $_thumbnail_set = ''; private $_table_img_optm; private $_table_img_optming; private $_cron_ran = false; private $__media; private $__data; protected $_summary; private $_format = ''; /** * Init * * @since 2.0 */ public function __construct() { Debug2::debug2('[ImgOptm] init'); $this->wp_upload_dir = wp_upload_dir(); $this->__media = $this->cls('Media'); $this->__data = $this->cls('Data'); $this->_table_img_optm = $this->__data->tb('img_optm'); $this->_table_img_optming = $this->__data->tb('img_optming'); $this->_summary = self::get_summary(); if (empty($this->_summary['next_post_id'])) { $this->_summary['next_post_id'] = 0; } if ($this->conf(Base::O_IMG_OPTM_WEBP)) { $this->_format = 'webp'; if ($this->conf(Base::O_IMG_OPTM_WEBP) == 2) { $this->_format = 'avif'; } } } /** * Gather images auto when update attachment meta * This is to optimize new uploaded images first. Stored in img_optm table. * Later normal process will auto remove these records when trying to optimize these images again * * @since 4.0 */ public function wp_update_attachment_metadata($meta_value, $post_id) { global $wpdb; self::debug2('🖌️ Auto update attachment meta [id] ' . $post_id); if (empty($meta_value['file'])) { return; } // Load gathered images if (!$this->_existed_src_list) { // To aavoid extra query when recalling this function self::debug('SELECT src from img_optm table'); if ($this->__data->tb_exist('img_optm')) { $q = "SELECT src FROM `$this->_table_img_optm` WHERE post_id = %d"; $list = $wpdb->get_results($wpdb->prepare($q, $post_id)); foreach ($list as $v) { $this->_existed_src_list[] = $post_id . '.' . $v->src; } } if ($this->__data->tb_exist('img_optming')) { $q = "SELECT src FROM `$this->_table_img_optming` WHERE post_id = %d"; $list = $wpdb->get_results($wpdb->prepare($q, $post_id)); foreach ($list as $v) { $this->_existed_src_list[] = $post_id . '.' . $v->src; } } else { $this->__data->tb_create('img_optming'); } } // Prepare images $this->tmp_pid = $post_id; $this->tmp_path = pathinfo($meta_value['file'], PATHINFO_DIRNAME) . '/'; $this->_append_img_queue($meta_value, true); if (!empty($meta_value['sizes'])) { array_map(array($this, '_append_img_queue'), $meta_value['sizes']); } if (!$this->_img_in_queue) { self::debug('auto update attachment meta 2 bypass: empty _img_in_queue'); return; } // Save to DB $this->_save_raw(); // $this->_send_request(); } /** * Auto send optm request * * @since 2.4.1 * @access public */ public static function cron_auto_request() { if (!defined('DOING_CRON')) { return false; } $instance = self::cls(); $instance->new_req(); } /** * Calculate wet run allowance * * @since 3.0 */ public function wet_limit() { $wet_limit = 1; if (!empty($this->_summary['img_taken'])) { $wet_limit = pow($this->_summary['img_taken'], 2); } if ($wet_limit == 1 && !empty($this->_summary['img_status.' . self::STATUS_ERR_OPTM])) { $wet_limit = pow($this->_summary['img_status.' . self::STATUS_ERR_OPTM], 2); } if ($wet_limit < Cloud::IMG_OPTM_DEFAULT_GROUP) { return $wet_limit; } // No limit return false; } /** * Push raw img to image optm server * * @since 1.6 * @access public */ public function new_req() { global $wpdb; // check if is running if (!empty($this->_summary['is_running']) && time() - $this->_summary['is_running'] < apply_filters('litespeed_imgoptm_new_req_interval', 3600)) { self::debug('The previous req was in 3600s.'); return; } $this->_summary['is_running'] = time(); self::save_summary(); // Check if has credit to push $err = false; $allowance = Cloud::cls()->allowance(Cloud::SVC_IMG_OPTM, $err); $wet_limit = $this->wet_limit(); self::debug("allowance_max $allowance wet_limit $wet_limit"); if ($wet_limit && $wet_limit < $allowance) { $allowance = $wet_limit; } if (!$allowance) { self::debug('❌ No credit'); Admin_Display::error(Error::msg($err)); $this->_finished_running(); return; } self::debug('preparing images to push'); $this->__data->tb_create('img_optming'); $q = "SELECT COUNT(1) FROM `$this->_table_img_optming` WHERE optm_status = %d"; $q = $wpdb->prepare($q, array(self::STATUS_REQUESTED)); $total_requested = $wpdb->get_var($q); $max_requested = $allowance * 1; if ($total_requested > $max_requested) { self::debug('❌ Too many queued images (' . $total_requested . ' > ' . $max_requested . ')'); Admin_Display::error(Error::msg('too_many_requested')); $this->_finished_running(); return; } $allowance -= $total_requested; if ($allowance < 1) { self::debug('❌ Too many requested images ' . $total_requested); Admin_Display::error(Error::msg('too_many_requested')); $this->_finished_running(); return; } // Limit maximum number of items waiting to be pulled $q = "SELECT COUNT(1) FROM `$this->_table_img_optming` WHERE optm_status = %d"; $q = $wpdb->prepare($q, array(self::STATUS_NOTIFIED)); $total_notified = $wpdb->get_var($q); if ($total_notified > 0) { self::debug('❌ Too many notified images (' . $total_notified . ')'); Admin_Display::error(Error::msg('too_many_notified')); $this->_finished_running(); return; } $q = "SELECT COUNT(1) FROM `$this->_table_img_optming` WHERE optm_status IN (%d, %d)"; $q = $wpdb->prepare($q, array(self::STATUS_NEW, self::STATUS_RAW)); $total_new = $wpdb->get_var($q); // $allowance -= $total_new; // May need to get more images $list = array(); $more = $allowance - $total_new; if ($more > 0) { $q = "SELECT b.post_id, b.meta_value FROM `$wpdb->posts` a LEFT JOIN `$wpdb->postmeta` b ON b.post_id = a.ID WHERE b.meta_key = '_wp_attachment_metadata' AND a.post_type = 'attachment' AND a.post_status = 'inherit' AND a.ID>%d AND a.post_mime_type IN ('image/jpeg', 'image/png', 'image/gif') ORDER BY a.ID LIMIT %d "; $q = $wpdb->prepare($q, array($this->_summary['next_post_id'], $more)); $list = $wpdb->get_results($q); foreach ($list as $v) { if (!$v->post_id) { continue; } $this->_summary['next_post_id'] = $v->post_id; $meta_value = $this->_parse_wp_meta_value($v); if (!$meta_value) { continue; } $meta_value['file'] = wp_normalize_path($meta_value['file']); $basedir = $this->wp_upload_dir['basedir'] . '/'; if (strpos($meta_value['file'], $basedir) === 0) { $meta_value['file'] = substr($meta_value['file'], strlen($basedir)); } $this->tmp_pid = $v->post_id; $this->tmp_path = pathinfo($meta_value['file'], PATHINFO_DIRNAME) . '/'; $this->_append_img_queue($meta_value, true); if (!empty($meta_value['sizes'])) { array_map(array($this, '_append_img_queue'), $meta_value['sizes']); } } self::save_summary(); $num_a = count($this->_img_in_queue); self::debug('Images found: ' . $num_a); $this->_filter_duplicated_src(); self::debug('Images after duplicated: ' . count($this->_img_in_queue)); $this->_filter_invalid_src(); self::debug('Images after invalid: ' . count($this->_img_in_queue)); // Check w/ legacy imgoptm table, bypass finished images $this->_filter_legacy_src(); $num_b = count($this->_img_in_queue); if ($num_b != $num_a) { self::debug('Images after filtered duplicated/invalid/legacy src: ' . $num_b); } // Save to DB $this->_save_raw(); } // Push to Cloud server $accepted_imgs = $this->_send_request($allowance); $this->_finished_running(); if (!$accepted_imgs) { return; } $placeholder1 = Admin_Display::print_plural($accepted_imgs[0], 'image'); $placeholder2 = Admin_Display::print_plural($accepted_imgs[1], 'image'); $msg = sprintf(__('Pushed %1$s to Cloud server, accepted %2$s.', 'litespeed-cache'), $placeholder1, $placeholder2); Admin_Display::success($msg); } /** * Set running to done */ private function _finished_running() { $this->_summary['is_running'] = 0; self::save_summary(); } /** * Add a new img to queue which will be pushed to request * * @since 1.6 * @access private */ private function _append_img_queue($meta_value, $is_ori_file = false) { if (empty($meta_value['file']) || empty($meta_value['width']) || empty($meta_value['height'])) { self::debug2('bypass image due to lack of file/w/h: pid ' . $this->tmp_pid, $meta_value); return; } $short_file_path = $meta_value['file']; if (!$is_ori_file) { $short_file_path = $this->tmp_path . $short_file_path; } // Check if src is gathered already or not if (in_array($this->tmp_pid . '.' . $short_file_path, $this->_existed_src_list)) { // Debug2::debug2( '[Img_Optm] bypass image due to gathered: pid ' . $this->tmp_pid . ' ' . $short_file_path ); return; } else { // Append handled images $this->_existed_src_list[] = $this->tmp_pid . '.' . $short_file_path; } // check file exists or not $_img_info = $this->__media->info($short_file_path, $this->tmp_pid); $extension = pathinfo($short_file_path, PATHINFO_EXTENSION); if (!$_img_info || !in_array($extension, array('jpg', 'jpeg', 'png', 'gif'))) { self::debug2('bypass image due to file not exist: pid ' . $this->tmp_pid . ' ' . $short_file_path); return; } // Check if optimized file exists or not $target_needed = false; if ($this->_format) { $target_file_path = $short_file_path . '.' . $this->_format; if (!$this->__media->info($target_file_path, $this->tmp_pid)) { $target_needed = true; } } if ($this->conf(self::O_IMG_OPTM_ORI)) { $target_file_path = substr($short_file_path, 0, -strlen($extension)) . 'bk.' . $extension; if (!$this->__media->info($target_file_path, $this->tmp_pid)) { $target_needed = true; } } if (!$target_needed) { self::debug2('bypass image due to optimized file exists: pid ' . $this->tmp_pid . ' ' . $short_file_path); return; } // Debug2::debug2( '[Img_Optm] adding image: pid ' . $this->tmp_pid ); $this->_img_in_queue[] = array( 'pid' => $this->tmp_pid, 'md5' => $_img_info['md5'], 'url' => $_img_info['url'], 'src' => $short_file_path, // not needed in LiteSpeed IAPI, just leave for local storage after post 'mime_type' => !empty($meta_value['mime-type']) ? $meta_value['mime-type'] : '', ); } /** * Save gathered image raw data * * @since 3.0 */ private function _save_raw() { if (empty($this->_img_in_queue)) { return; } $data = array(); $pid_list = array(); foreach ($this->_img_in_queue as $k => $v) { $_img_info = $this->__media->info($v['src'], $v['pid']); // attachment doesn't exist, delete the record if (empty($_img_info['url']) || empty($_img_info['md5'])) { unset($this->_img_in_queue[$k]); continue; } $pid_list[] = (int) $v['pid']; $data[] = $v['pid']; $data[] = self::STATUS_RAW; $data[] = $v['src']; } global $wpdb; $fields = 'post_id, optm_status, src'; $q = "INSERT INTO `$this->_table_img_optming` ( $fields ) VALUES "; // Add placeholder $q .= Utility::chunk_placeholder($data, $fields); // Store data $wpdb->query($wpdb->prepare($q, $data)); $count = count($this->_img_in_queue); self::debug('Added raw images [total] ' . $count); $this->_img_in_queue = array(); // Save thumbnail groups for future rescan index $this->_gen_thumbnail_set(); $pid_list = array_unique($pid_list); self::debug('pid list to append to postmeta', $pid_list); $pid_list = array_diff($pid_list, $this->_pids_set); $this->_pids_set = array_merge($this->_pids_set, $pid_list); $existed_meta = $wpdb->get_results("SELECT * FROM `$wpdb->postmeta` WHERE post_id IN ('" . implode("','", $pid_list) . "') AND meta_key='" . self::DB_SET . "'"); $existed_pid = array(); if ($existed_meta) { foreach ($existed_meta as $v) { $existed_pid[] = $v->post_id; } self::debug('pid list to update postmeta', $existed_pid); $wpdb->query( $wpdb->prepare("UPDATE `$wpdb->postmeta` SET meta_value=%s WHERE post_id IN ('" . implode("','", $existed_pid) . "') AND meta_key=%s", array( $this->_thumbnail_set, self::DB_SET, )) ); } # Add new meta $new_pids = $existed_pid ? array_diff($pid_list, $existed_pid) : $pid_list; if ($new_pids) { self::debug('pid list to update postmeta', $new_pids); foreach ($new_pids as $v) { self::debug('New group set info [pid] ' . $v); $q = "INSERT INTO `$wpdb->postmeta` (post_id, meta_key, meta_value) VALUES (%d, %s, %s)"; $wpdb->query($wpdb->prepare($q, array($v, self::DB_SET, $this->_thumbnail_set))); } } } /** * Generate thumbnail sets of current image group * * @since 5.4 */ private function _gen_thumbnail_set() { if ($this->_thumbnail_set) { return; } $set = array(); foreach (Media::cls()->get_image_sizes() as $size) { $curr_size = $size['width'] . 'x' . $size['height']; if (in_array($curr_size, $set)) { continue; } $set[] = $curr_size; } $this->_thumbnail_set = implode(PHP_EOL, $set); } /** * Filter duplicated src in work table and $this->_img_in_queue, then mark them as duplicated * * @since 2.0 * @access private */ private function _filter_duplicated_src() { global $wpdb; $srcpath_list = array(); $list = $wpdb->get_results("SELECT src FROM `$this->_table_img_optming`"); foreach ($list as $v) { $srcpath_list[] = $v->src; } foreach ($this->_img_in_queue as $k => $v) { if (in_array($v['src'], $srcpath_list)) { unset($this->_img_in_queue[$k]); continue; } $srcpath_list[] = $v['src']; } } /** * Filter legacy finished ones * * @since 5.4 */ private function _filter_legacy_src() { global $wpdb; if (!$this->__data->tb_exist('img_optm')) { return; } if (!$this->_img_in_queue) { return; } $finished_ids = array(); Utility::compatibility(); $post_ids = array_unique(array_column($this->_img_in_queue, 'pid')); $list = $wpdb->get_results("SELECT post_id FROM `$this->_table_img_optm` WHERE post_id in (" . implode(',', $post_ids) . ') GROUP BY post_id'); foreach ($list as $v) { $finished_ids[] = $v->post_id; } foreach ($this->_img_in_queue as $k => $v) { if (in_array($v['pid'], $finished_ids)) { self::debug('Legacy image optimized [pid] ' . $v['pid']); unset($this->_img_in_queue[$k]); continue; } } // Drop all existing legacy records $wpdb->query("DELETE FROM `$this->_table_img_optm` WHERE post_id in (" . implode(',', $post_ids) . ')'); } /** * Filter the invalid src before sending * * @since 3.0.8.3 * @access private */ private function _filter_invalid_src() { $img_in_queue_invalid = array(); foreach ($this->_img_in_queue as $k => $v) { if ($v['src']) { $extension = pathinfo($v['src'], PATHINFO_EXTENSION); } if (!$v['src'] || empty($extension) || !in_array($extension, array('jpg', 'jpeg', 'png', 'gif'))) { $img_in_queue_invalid[] = $v['id']; unset($this->_img_in_queue[$k]); continue; } } if (!$img_in_queue_invalid) { return; } $count = count($img_in_queue_invalid); $msg = sprintf(__('Cleared %1$s invalid images.', 'litespeed-cache'), $count); Admin_Display::success($msg); self::debug('Found invalid src [total] ' . $count); } /** * Push img request to Cloud server * * @since 1.6.7 * @access private */ private function _send_request($allowance) { global $wpdb; $q = "SELECT id, src, post_id FROM `$this->_table_img_optming` WHERE optm_status=%d LIMIT %d"; $q = $wpdb->prepare($q, array(self::STATUS_RAW, $allowance)); $_img_in_queue = $wpdb->get_results($q); if (!$_img_in_queue) { return; } self::debug('Load img in queue [total] ' . count($_img_in_queue)); $list = array(); foreach ($_img_in_queue as $v) { $_img_info = $this->__media->info($v->src, $v->post_id); # If record is invalid, remove from img_optming table if (empty($_img_info['url']) || empty($_img_info['md5'])) { $wpdb->query($wpdb->prepare("DELETE FROM `$this->_table_img_optming` WHERE id=%d", $v->id)); continue; } $img = array( 'id' => $v->id, 'url' => $_img_info['url'], 'md5' => $_img_info['md5'], ); // Build the needed image types for request as we now support soft reset counter if ($this->_format) { $target_file_path = $v->src . '.' . $this->_format; if ($this->__media->info($target_file_path, $v->post_id)) { $img['optm_' . $this->_format] = 0; } } if ($this->conf(self::O_IMG_OPTM_ORI)) { $extension = pathinfo($v->src, PATHINFO_EXTENSION); $target_file_path = substr($v->src, 0, -strlen($extension)) . 'bk.' . $extension; if ($this->__media->info($target_file_path, $v->post_id)) { $img['optm_ori'] = 0; } } $list[] = $img; } if (!$list) { $msg = __('No valid image found in the current request.', 'litespeed-cache'); Admin_Display::error($msg); return; } $data = array( 'action' => self::CLOUD_ACTION_NEW_REQ, 'list' => \json_encode($list), 'optm_ori' => $this->conf(self::O_IMG_OPTM_ORI) ? 1 : 0, 'optm_lossless' => $this->conf(self::O_IMG_OPTM_LOSSLESS) ? 1 : 0, 'keep_exif' => $this->conf(self::O_IMG_OPTM_EXIF) ? 1 : 0, ); if ($this->_format) { $data['optm_' . $this->_format] = 1; } // Push to Cloud server $json = Cloud::post(Cloud::SVC_IMG_OPTM, $data); if (!$json) { return; } // Check data format if (empty($json['ids'])) { self::debug('Failed to parse response data from Cloud server ', $json); $msg = __('No valid image found by Cloud server in the current request.', 'litespeed-cache'); Admin_Display::error($msg); return; } self::debug('Returned data from Cloud server count: ' . count($json['ids'])); $ids = implode(',', array_map('intval', $json['ids'])); // Update img table $q = "UPDATE `$this->_table_img_optming` SET optm_status = '" . self::STATUS_REQUESTED . "' WHERE id IN ( $ids )"; $wpdb->query($q); $this->_summary['last_requested'] = time(); self::save_summary(); return array(count($list), count($json['ids'])); } /** * Cloud server notify Client img status changed * * @access public */ public function notify_img() { // Interval validation to avoid hacking domain_key if (!empty($this->_summary['notify_ts_err']) && time() - $this->_summary['notify_ts_err'] < 3) { return Cloud::err('too_often'); } $post_data = \json_decode(file_get_contents('php://input'), true); if (is_null($post_data)) { $post_data = $_POST; } global $wpdb; $notified_data = $post_data['data']; if (empty($notified_data) || !is_array($notified_data)) { self::debug('❌ notify exit: no notified data'); return Cloud::err('no notified data'); } if (empty($post_data['server']) || (substr($post_data['server'], -11) !== '.quic.cloud' && substr($post_data['server'], -15) !== '.quicserver.com')) { self::debug('notify exit: no/wrong server'); return Cloud::err('no/wrong server'); } if (empty($post_data['status'])) { self::debug('notify missing status'); return Cloud::err('no status'); } $status = $post_data['status']; self::debug('notified status=' . $status); $last_log_pid = 0; if (empty($this->_summary['reduced'])) { $this->_summary['reduced'] = 0; } if ($status == self::STATUS_NOTIFIED) { // Notified data format: [ img_optm_id => [ id=>, src_size=>, ori=>, ori_md5=>, ori_reduced=>, webp=>, webp_md5=>, webp_reduced=> ] ] $q = "SELECT a.*, b.meta_id as b_meta_id, b.meta_value AS b_optm_info FROM `$this->_table_img_optming` a LEFT JOIN `$wpdb->postmeta` b ON b.post_id = a.post_id AND b.meta_key = %s WHERE a.id IN ( " . implode(',', array_fill(0, count($notified_data), '%d')) . ' )'; $list = $wpdb->get_results($wpdb->prepare($q, array_merge(array(self::DB_SIZE), array_keys($notified_data)))); $ls_optm_size_row_exists_postids = array(); foreach ($list as $v) { $json = $notified_data[$v->id]; // self::debug('Notified data for [id] ' . $v->id, $json); $server = !empty($json['server']) ? $json['server'] : $post_data['server']; $server_info = array( 'server' => $server, ); // Save server side ID to send taken notification after pulled $server_info['id'] = $json['id']; if (!empty($json['file_id'])) { $server_info['file_id'] = $json['file_id']; } // Optm info array $postmeta_info = array( 'ori_total' => 0, 'ori_saved' => 0, 'webp_total' => 0, 'webp_saved' => 0, 'avif_total' => 0, 'avif_saved' => 0, ); // Init postmeta_info for the first one if (!empty($v->b_meta_id)) { foreach (maybe_unserialize($v->b_optm_info) as $k2 => $v2) { $postmeta_info[$k2] += $v2; } } if (!empty($json['ori'])) { $server_info['ori_md5'] = $json['ori_md5']; $server_info['ori'] = $json['ori']; // Append meta info $postmeta_info['ori_total'] += $json['src_size']; $postmeta_info['ori_saved'] += $json['ori_reduced']; // optimized image size info in img_optm tb will be updated when pull $this->_summary['reduced'] += $json['ori_reduced']; } if (!empty($json['webp'])) { $server_info['webp_md5'] = $json['webp_md5']; $server_info['webp'] = $json['webp']; // Append meta info $postmeta_info['webp_total'] += $json['src_size']; $postmeta_info['webp_saved'] += $json['webp_reduced']; $this->_summary['reduced'] += $json['webp_reduced']; } if (!empty($json['avif'])) { $server_info['avif_md5'] = $json['avif_md5']; $server_info['avif'] = $json['avif']; // Append meta info $postmeta_info['avif_total'] += $json['src_size']; $postmeta_info['avif_saved'] += $json['avif_reduced']; $this->_summary['reduced'] += $json['avif_reduced']; } // Update status and data in working table $q = "UPDATE `$this->_table_img_optming` SET optm_status = %d, server_info = %s WHERE id = %d "; $wpdb->query($wpdb->prepare($q, array($status, \json_encode($server_info), $v->id))); // Update postmeta for optm summary $postmeta_info = serialize($postmeta_info); if (empty($v->b_meta_id) && !in_array($v->post_id, $ls_optm_size_row_exists_postids)) { self::debug('New size info [pid] ' . $v->post_id); $q = "INSERT INTO `$wpdb->postmeta` ( post_id, meta_key, meta_value ) VALUES ( %d, %s, %s )"; $wpdb->query($wpdb->prepare($q, array($v->post_id, self::DB_SIZE, $postmeta_info))); $ls_optm_size_row_exists_postids[] = $v->post_id; } else { $q = "UPDATE `$wpdb->postmeta` SET meta_value = %s WHERE meta_id = %d "; $wpdb->query($wpdb->prepare($q, array($postmeta_info, $v->b_meta_id))); } // write log $pid_log = $last_log_pid == $v->post_id ? '.' : $v->post_id; self::debug('notify_img [status] ' . $status . " \t\t[pid] " . $pid_log . " \t\t[id] " . $v->id); $last_log_pid = $v->post_id; } self::save_summary(); // Mark need_pull tag for cron self::update_option(self::DB_NEED_PULL, self::STATUS_NOTIFIED); } else { // Other errors will directly remove the working records // Delete from working table $q = "DELETE FROM `$this->_table_img_optming` WHERE id IN ( " . implode(',', array_fill(0, count($notified_data), '%d')) . ' ) '; $wpdb->query($wpdb->prepare($q, $notified_data)); } return Cloud::ok(array('count' => count($notified_data))); } /** * Cron start async req * * @since 5.5 */ public static function start_async_cron() { Task::async_call('imgoptm'); } /** * Manually start async req * * @since 5.5 */ public static function start_async() { Task::async_call('imgoptm_force'); $msg = __('Started async image optimization request', 'litespeed-cache'); Admin_Display::success($msg); } /** * Ajax req handler * * @since 5.5 */ public static function async_handler($force = false) { self::debug('------------async-------------start_async_handler'); $tag = self::get_option(self::DB_NEED_PULL); if (!$tag || $tag != self::STATUS_NOTIFIED) { self::debug('❌ no need pull [tag] ' . $tag); return; } if (defined('LITESPEED_IMG_OPTM_PULL_CRON') && !LITESPEED_IMG_OPTM_PULL_CRON) { self::debug('Cron disabled [define] LITESPEED_IMG_OPTM_PULL_CRON'); return; } self::cls()->pull($force); } /** * Calculate pull threads * * @since 5.8 * @access private */ private function _calc_pull_threads() { global $wpdb; if (defined('LITESPEED_IMG_OPTM_PULL_THREADS')) { return LITESPEED_IMG_OPTM_PULL_THREADS; } // Tune number of images per request based on number of images waiting and cloud packages $imgs_per_req = 1; // base 1, ramp up to ~50 max // Ramp up the request rate based on how many images are waiting $c = "SELECT count(id) FROM `$this->_table_img_optming` WHERE optm_status = %d"; $_c = $wpdb->prepare($c, array(self::STATUS_NOTIFIED)); $images_waiting = $wpdb->get_var($_c); if ($images_waiting && $images_waiting > 0) { $imgs_per_req = ceil($images_waiting / 1000); //ie. download 5/request if 5000 images are waiting } // Cap the request rate at 50 images per request $imgs_per_req = min(50, $imgs_per_req); self::debug('Pulling images at rate: ' . $imgs_per_req . ' Images per request.'); return $imgs_per_req; } /** * Pull optimized img * * @since 1.6 * @access public */ public function pull($manual = false) { global $wpdb; $timeoutLimit = ini_get('max_execution_time'); $endts = time() + $timeoutLimit; self::debug('' . ($manual ? 'Manually' : 'Cron') . ' pull started [timeout: ' . $timeoutLimit . 's]'); if ($this->cron_running()) { self::debug('Pull cron is running'); $msg = __('Pull Cron is running', 'litespeed-cache'); Admin_Display::note($msg); return; } $this->_summary['last_pulled'] = time(); $this->_summary['last_pulled_by_cron'] = !$manual; self::save_summary(); $imgs_per_req = $this->_calc_pull_threads(); $q = "SELECT * FROM `$this->_table_img_optming` WHERE optm_status = %d ORDER BY id LIMIT %d"; $_q = $wpdb->prepare($q, array(self::STATUS_NOTIFIED, $imgs_per_req)); $rm_ori_bkup = $this->conf(self::O_IMG_OPTM_RM_BKUP); $total_pulled_ori = 0; $total_pulled_webp = 0; $total_pulled_avif = 0; $server_list = array(); try { while ($img_rows = $wpdb->get_results($_q)) { self::debug('timeout left: ' . ($endts - time()) . 's'); if (function_exists('set_time_limit')) { $endts += 600; self::debug('Endtime extended to ' . date('Ymd H:i:s', $endts)); set_time_limit(600); // This will be no more important as we use noabort now } // Disabled as we use noabort // if ($endts - time() < 10) { // self::debug("🚨 End loop due to timeout limit reached " . $timeoutLimit . "s"); // break; // } /** * Update cron timestamp to avoid duplicated running * @since 1.6.2 */ $this->_update_cron_running(); // Run requests in parallel $requests = array(); // store each request URL for Requests::request_multiple() $imgs_by_req = array(); // store original request data so that we can reference it in the response $req_counter = 0; foreach ($img_rows as $row_img) { // request original image $server_info = \json_decode($row_img->server_info, true); if (!empty($server_info['ori'])) { $image_url = $server_info['server'] . '/' . $server_info['ori']; self::debug('Queueing pull: ' . $image_url); $requests[$req_counter] = array( 'url' => $image_url, 'type' => 'GET', ); $imgs_by_req[$req_counter++] = array( 'type' => 'ori', 'data' => $row_img, ); } // request webp image $webp_size = 0; if (!empty($server_info['webp'])) { $image_url = $server_info['server'] . '/' . $server_info['webp']; self::debug('Queueing pull WebP: ' . $image_url); $requests[$req_counter] = array( 'url' => $image_url, 'type' => 'GET', ); $imgs_by_req[$req_counter++] = array( 'type' => 'webp', 'data' => $row_img, ); } // request avif image $avif_size = 0; if (!empty($server_info['avif'])) { $image_url = $server_info['server'] . '/' . $server_info['avif']; self::debug('Queueing pull AVIF: ' . $image_url); $requests[$req_counter] = array( 'url' => $image_url, 'type' => 'GET', ); $imgs_by_req[$req_counter++] = array( 'type' => 'avif', 'data' => $row_img, ); } } self::debug('Loaded images count: ' . $req_counter); $complete_action = function ($response, $req_count) use ($imgs_by_req, $rm_ori_bkup, &$total_pulled_ori, &$total_pulled_webp, &$total_pulled_avif, &$server_list) { global $wpdb; $row_data = isset($imgs_by_req[$req_count]) ? $imgs_by_req[$req_count] : false; if (false === $row_data) { self::debug('❌ failed to pull image: Request not found in lookup variable.'); return; } $row_type = isset($row_data['type']) ? $row_data['type'] : 'ori'; $row_img = $row_data['data']; $local_file = $this->wp_upload_dir['basedir'] . '/' . $row_img->src; $server_info = \json_decode($row_img->server_info, true); if (empty($response->success)) { if (!empty($response->status_code) && 404 == $response->status_code) { $this->_step_back_image($row_img->id); $msg = __('Some optimized image file(s) has expired and was cleared.', 'litespeed-cache'); Admin_Display::error($msg); return; } else { // handle error $image_url = $server_info['server'] . '/' . $server_info[$row_type]; self::debug( '❌ failed to pull image (' . $row_type . '): ' . (!empty($response->status_code) ? $response->status_code : '') . ' [Local: ' . $row_img->src . '] / [remote: ' . $image_url . ']' ); throw new \Exception('Failed to pull image ' . (!empty($response->status_code) ? $response->status_code : '') . ' [url] ' . $image_url); return; } } // Handle wp_remote_get 404 as its success=true if (!empty($response->status_code)) { if ($response->status_code == 404) { $this->_step_back_image($row_img->id); $msg = __('Some optimized image file(s) has expired and was cleared.', 'litespeed-cache'); Admin_Display::error($msg); return; } // Note: if there is other error status code found in future, handle here } if ('webp' === $row_type) { file_put_contents($local_file . '.webp', $response->body); if (!file_exists($local_file . '.webp') || !filesize($local_file . '.webp') || md5_file($local_file . '.webp') !== $server_info['webp_md5']) { self::debug('❌ Failed to pull optimized webp img: file md5 mismatch, server md5: ' . $server_info['webp_md5']); // Delete working table $q = "DELETE FROM `$this->_table_img_optming` WHERE id = %d "; $wpdb->query($wpdb->prepare($q, $row_img->id)); $msg = __('Pulled WebP image md5 does not match the notified WebP image md5.', 'litespeed-cache'); Admin_Display::error($msg); return; } self::debug('Pulled optimized img WebP: ' . $local_file . '.webp'); $webp_size = filesize($local_file . '.webp'); /** * API for WebP * @since 2.9.5 * @since 3.0 $row_img less elements (see above one) * @see #751737 - API docs for WEBP generation */ do_action('litespeed_img_pull_webp', $row_img, $local_file . '.webp'); $total_pulled_webp++; } elseif ('avif' === $row_type) { file_put_contents($local_file . '.avif', $response->body); if (!file_exists($local_file . '.avif') || !filesize($local_file . '.avif') || md5_file($local_file . '.avif') !== $server_info['avif_md5']) { self::debug('❌ Failed to pull optimized avif img: file md5 mismatch, server md5: ' . $server_info['avif_md5']); // Delete working table $q = "DELETE FROM `$this->_table_img_optming` WHERE id = %d "; $wpdb->query($wpdb->prepare($q, $row_img->id)); $msg = __('Pulled AVIF image md5 does not match the notified AVIF image md5.', 'litespeed-cache'); Admin_Display::error($msg); return; } self::debug('Pulled optimized img AVIF: ' . $local_file . '.avif'); $avif_size = filesize($local_file . '.avif'); /** * API for AVIF * @since 7.0 */ do_action('litespeed_img_pull_avif', $row_img, $local_file . '.avif'); $total_pulled_avif++; } else { // "ori" image type file_put_contents($local_file . '.tmp', $response->body); if (!file_exists($local_file . '.tmp') || !filesize($local_file . '.tmp') || md5_file($local_file . '.tmp') !== $server_info['ori_md5']) { self::debug( '❌ Failed to pull optimized img: file md5 mismatch [url] ' . $server_info['server'] . '/' . $server_info['ori'] . ' [server_md5] ' . $server_info['ori_md5'] ); // Delete working table $q = "DELETE FROM `$this->_table_img_optming` WHERE id = %d "; $wpdb->query($wpdb->prepare($q, $row_img->id)); $msg = __('One or more pulled images does not match with the notified image md5', 'litespeed-cache'); Admin_Display::error($msg); return; } // Backup ori img if (!$rm_ori_bkup) { $extension = pathinfo($local_file, PATHINFO_EXTENSION); $bk_file = substr($local_file, 0, -strlen($extension)) . 'bk.' . $extension; file_exists($local_file) && rename($local_file, $bk_file); } // Replace ori img rename($local_file . '.tmp', $local_file); self::debug('Pulled optimized img: ' . $local_file); /** * API Hook * @since 2.9.5 * @since 3.0 $row_img has less elements now. Most useful ones are `post_id`/`src` */ do_action('litespeed_img_pull_ori', $row_img, $local_file); self::debug2('Remove _table_img_optming record [id] ' . $row_img->id); } // Delete working table $q = "DELETE FROM `$this->_table_img_optming` WHERE id = %d "; $wpdb->query($wpdb->prepare($q, $row_img->id)); // Save server_list to notify taken if (empty($server_list[$server_info['server']])) { $server_list[$server_info['server']] = array(); } $server_info_id = !empty($server_info['file_id']) ? $server_info['file_id'] : $server_info['id']; $server_list[$server_info['server']][] = $server_info_id; $total_pulled_ori++; }; $force_wp_remote_get = defined('LITESPEED_FORCE_WP_REMOTE_GET') && LITESPEED_FORCE_WP_REMOTE_GET; if (!$force_wp_remote_get && class_exists('\WpOrg\Requests\Requests') && class_exists('\WpOrg\Requests\Autoload') && version_compare(PHP_VERSION, '5.6.0', '>=')) { // Make sure Requests can load internal classes. Autoload::register(); // Run pull requests in parallel Requests::request_multiple($requests, array( 'timeout' => 60, 'connect_timeout' => 60, 'complete' => $complete_action, )); } else { foreach ($requests as $cnt => $req) { $wp_response = wp_safe_remote_get($req['url'], array('timeout' => 60)); $request_response = array( 'success' => false, 'status_code' => 0, 'body' => null, ); if (is_wp_error($wp_response)) { $error_message = $wp_response->get_error_message(); self::debug('❌ failed to pull image: ' . $error_message); } else { $request_response['success'] = true; $request_response['status_code'] = $wp_response['response']['code']; $request_response['body'] = $wp_response['body']; } self::debug('response code [code] ' . $wp_response['response']['code'] . ' [url] ' . $req['url']); $request_response = (object) $request_response; $complete_action($request_response, $cnt); } } self::debug('Current batch pull finished'); } } catch (\Exception $e) { Admin_Display::error('Image pull process failure: ' . $e->getMessage()); } // Notify IAPI images taken foreach ($server_list as $server => $img_list) { $data = array( 'action' => self::CLOUD_ACTION_TAKEN, 'list' => $img_list, 'server' => $server, ); // TODO: improve this so we do not call once per server, but just once and then filter on the server side Cloud::post(Cloud::SVC_IMG_OPTM, $data); } if (empty($this->_summary['img_taken'])) { $this->_summary['img_taken'] = 0; } $this->_summary['img_taken'] += $total_pulled_ori + $total_pulled_webp + $total_pulled_avif; self::save_summary(); // Manually running needs to roll back timestamp for next running if ($manual) { $this->_update_cron_running(true); } // $msg = sprintf(__('Pulled %d image(s)', 'litespeed-cache'), $total_pulled_ori + $total_pulled_webp); // Admin_Display::success($msg); // Check if there is still task in queue $q = "SELECT * FROM `$this->_table_img_optming` WHERE optm_status = %d LIMIT 1"; $to_be_continued = $wpdb->get_row($wpdb->prepare($q, self::STATUS_NOTIFIED)); if ($to_be_continued) { self::debug('Task in queue, to be continued...'); return; // return Router::self_redirect(Router::ACTION_IMG_OPTM, self::TYPE_PULL); } // If all pulled, update tag to done self::debug('Marked pull status to all pulled'); self::update_option(self::DB_NEED_PULL, self::STATUS_PULLED); } /** * Push image back to previous status * * @since 3.0 * @access private */ private function _step_back_image($id) { global $wpdb; self::debug('Push image back to new status [id] ' . $id); // Reset the image to gathered status $q = "UPDATE `$this->_table_img_optming` SET optm_status = %d WHERE id = %d "; $wpdb->query($wpdb->prepare($q, array(self::STATUS_RAW, $id))); } /** * Parse wp's meta value * * @since 1.6.7 * @access private */ private function _parse_wp_meta_value($v) { if (empty($v)) { self::debug('bypassed parsing meta due to null value'); return false; } if (!$v->meta_value) { self::debug('bypassed parsing meta due to no meta_value: pid ' . $v->post_id); return false; } $meta_value = @maybe_unserialize($v->meta_value); if (!is_array($meta_value)) { self::debug('bypassed parsing meta due to meta_value not json: pid ' . $v->post_id); return false; } if (empty($meta_value['file'])) { self::debug('bypassed parsing meta due to no ori file: pid ' . $v->post_id); return false; } return $meta_value; } /** * Clean up all unfinished queue locally and to Cloud server * * @since 2.1.2 * @access public */ public function clean() { global $wpdb; // Reset img_optm table's queue if ($this->__data->tb_exist('img_optming')) { // Get min post id to mark $q = "SELECT MIN(post_id) FROM `$this->_table_img_optming`"; $min_pid = $wpdb->get_var($q) - 1; if ($this->_summary['next_post_id'] > $min_pid) { $this->_summary['next_post_id'] = $min_pid; self::save_summary(); } $q = "DELETE FROM `$this->_table_img_optming`"; $wpdb->query($q); } $msg = __('Cleaned up unfinished data successfully.', 'litespeed-cache'); Admin_Display::success($msg); } /** * Reset image counter * * @since 7.0 * @access private */ private function _reset_counter() { self::debug('reset image optm counter'); $this->_summary['next_post_id'] = 0; self::save_summary(); $this->clean(); $msg = __('Reset image optimization counter successfully.', 'litespeed-cache'); Admin_Display::success($msg); } /** * Destroy all optimized images * * @since 3.0 * @access private */ private function _destroy() { global $wpdb; self::debug('executing DESTROY process'); $offset = !empty($_GET['litespeed_i']) ? $_GET['litespeed_i'] : 0; /** * Limit images each time before redirection to fix Out of memory issue. #665465 * @since 2.9.8 */ // Start deleting files $limit = apply_filters('litespeed_imgoptm_destroy_max_rows', 500); $img_q = "SELECT b.post_id, b.meta_value FROM `$wpdb->posts` a LEFT JOIN `$wpdb->postmeta` b ON b.post_id = a.ID WHERE b.meta_key = '_wp_attachment_metadata' AND a.post_type = 'attachment' AND a.post_status = 'inherit' AND a.post_mime_type IN ('image/jpeg', 'image/png', 'image/gif') ORDER BY a.ID LIMIT %d,%d "; $q = $wpdb->prepare($img_q, array($offset * $limit, $limit)); $list = $wpdb->get_results($q); $i = 0; foreach ($list as $v) { if (!$v->post_id) { continue; } $meta_value = $this->_parse_wp_meta_value($v); if (!$meta_value) { continue; } $i++; $this->tmp_pid = $v->post_id; $this->tmp_path = pathinfo($meta_value['file'], PATHINFO_DIRNAME) . '/'; $this->_destroy_optm_file($meta_value, true); if (!empty($meta_value['sizes'])) { array_map(array($this, '_destroy_optm_file'), $meta_value['sizes']); } } self::debug('batch switched images total: ' . $i); $offset++; $to_be_continued = $wpdb->get_row($wpdb->prepare($img_q, array($offset * $limit, 1))); if ($to_be_continued) { # Check if post_id is beyond next_post_id self::debug('[next_post_id] ' . $this->_summary['next_post_id'] . ' [cursor post id] ' . $to_be_continued->post_id); if ($to_be_continued->post_id <= $this->_summary['next_post_id']) { self::debug('redirecting to next'); return Router::self_redirect(Router::ACTION_IMG_OPTM, self::TYPE_DESTROY); } self::debug('🎊 Finished destroying'); } // Delete postmeta info $q = "DELETE FROM `$wpdb->postmeta` WHERE meta_key = %s"; $wpdb->query($wpdb->prepare($q, self::DB_SIZE)); $wpdb->query($wpdb->prepare($q, self::DB_SET)); // Delete img_optm table $this->__data->tb_del('img_optm'); $this->__data->tb_del('img_optming'); // Clear options table summary info self::delete_option('_summary'); self::delete_option(self::DB_NEED_PULL); $msg = __('Destroy all optimization data successfully.', 'litespeed-cache'); Admin_Display::success($msg); } /** * Destroy optm file */ private function _destroy_optm_file($meta_value, $is_ori_file = false) { $short_file_path = $meta_value['file']; if (!$is_ori_file) { $short_file_path = $this->tmp_path . $short_file_path; } self::debug('deleting ' . $short_file_path); // del webp $this->__media->info($short_file_path . '.webp', $this->tmp_pid) && $this->__media->del($short_file_path . '.webp', $this->tmp_pid); $this->__media->info($short_file_path . '.optm.webp', $this->tmp_pid) && $this->__media->del($short_file_path . '.optm.webp', $this->tmp_pid); // del avif $this->__media->info($short_file_path . '.avif', $this->tmp_pid) && $this->__media->del($short_file_path . '.avif', $this->tmp_pid); $this->__media->info($short_file_path . '.optm.avif', $this->tmp_pid) && $this->__media->del($short_file_path . '.optm.avif', $this->tmp_pid); $extension = pathinfo($short_file_path, PATHINFO_EXTENSION); $local_filename = substr($short_file_path, 0, -strlen($extension) - 1); $bk_file = $local_filename . '.bk.' . $extension; $bk_optm_file = $local_filename . '.bk.optm.' . $extension; // del optimized ori if ($this->__media->info($bk_file, $this->tmp_pid)) { self::debug('deleting optim ori'); $this->__media->del($short_file_path, $this->tmp_pid); $this->__media->rename($bk_file, $short_file_path, $this->tmp_pid); } $this->__media->info($bk_optm_file, $this->tmp_pid) && $this->__media->del($bk_optm_file, $this->tmp_pid); } /** * Rescan to find new generated images * * @since 1.6.7 * @access private */ private function _rescan() { global $wpdb; exit('tobedone'); $offset = !empty($_GET['litespeed_i']) ? $_GET['litespeed_i'] : 0; $limit = 500; self::debug('rescan images'); // Get images $q = "SELECT b.post_id, b.meta_value FROM `$wpdb->posts` a, `$wpdb->postmeta` b WHERE a.post_type = 'attachment' AND a.post_status = 'inherit' AND a.post_mime_type IN ('image/jpeg', 'image/png', 'image/gif') AND a.ID = b.post_id AND b.meta_key = '_wp_attachment_metadata' ORDER BY a.ID LIMIT %d, %d "; $list = $wpdb->get_results($wpdb->prepare($q, $offset * $limit, $limit + 1)); // last one is the seed for next batch if (!$list) { $msg = __('Rescanned successfully.', 'litespeed-cache'); Admin_Display::success($msg); self::debug('rescan bypass: no gathered image found'); return; } if (count($list) == $limit + 1) { $to_be_continued = true; array_pop($list); // last one is the seed for next round, discard here. } else { $to_be_continued = false; } // Prepare post_ids to inquery gathered images $pid_set = array(); $scanned_list = array(); foreach ($list as $v) { $meta_value = $this->_parse_wp_meta_value($v); if (!$meta_value) { continue; } $scanned_list[] = array( 'pid' => $v->post_id, 'meta' => $meta_value, ); $pid_set[] = $v->post_id; } // Build gathered images $q = "SELECT src, post_id FROM `$this->_table_img_optm` WHERE post_id IN (" . implode(',', array_fill(0, count($pid_set), '%d')) . ')'; $list = $wpdb->get_results($wpdb->prepare($q, $pid_set)); foreach ($list as $v) { $this->_existed_src_list[] = $v->post_id . '.' . $v->src; } // Find new images foreach ($scanned_list as $v) { $meta_value = $v['meta']; // Parse all child src and put them into $this->_img_in_queue, missing ones to $this->_img_in_queue_missed $this->tmp_pid = $v['pid']; $this->tmp_path = pathinfo($meta_value['file'], PATHINFO_DIRNAME) . '/'; $this->_append_img_queue($meta_value, true); if (!empty($meta_value['sizes'])) { array_map(array($this, '_append_img_queue'), $meta_value['sizes']); } } self::debug('rescanned [img] ' . count($this->_img_in_queue)); $count = count($this->_img_in_queue); if ($count > 0) { // Save to DB $this->_save_raw(); } if ($to_be_continued) { return Router::self_redirect(Router::ACTION_IMG_OPTM, self::TYPE_RESCAN); } $msg = $count ? sprintf(__('Rescanned %d images successfully.', 'litespeed-cache'), $count) : __('Rescanned successfully.', 'litespeed-cache'); Admin_Display::success($msg); } /** * Calculate bkup original images storage * * @since 2.2.6 * @access private */ private function _calc_bkup() { global $wpdb; $offset = !empty($_GET['litespeed_i']) ? $_GET['litespeed_i'] : 0; $limit = 500; if (!$offset) { $this->_summary['bk_summary'] = array( 'date' => time(), 'count' => 0, 'sum' => 0, ); } $img_q = "SELECT b.post_id, b.meta_value FROM `$wpdb->posts` a LEFT JOIN `$wpdb->postmeta` b ON b.post_id = a.ID WHERE b.meta_key = '_wp_attachment_metadata' AND a.post_type = 'attachment' AND a.post_status = 'inherit' AND a.post_mime_type IN ('image/jpeg', 'image/png', 'image/gif') ORDER BY a.ID LIMIT %d,%d "; $q = $wpdb->prepare($img_q, array($offset * $limit, $limit)); $list = $wpdb->get_results($q); foreach ($list as $v) { if (!$v->post_id) { continue; } $meta_value = $this->_parse_wp_meta_value($v); if (!$meta_value) { continue; } $this->tmp_pid = $v->post_id; $this->tmp_path = pathinfo($meta_value['file'], PATHINFO_DIRNAME) . '/'; $this->_get_bk_size($meta_value, true); if (!empty($meta_value['sizes'])) { array_map(array($this, '_get_bk_size'), $meta_value['sizes']); } } $this->_summary['bk_summary']['date'] = time(); self::save_summary(); self::debug('_calc_bkup total: ' . $this->_summary['bk_summary']['count'] . ' [size] ' . $this->_summary['bk_summary']['sum']); $offset++; $to_be_continued = $wpdb->get_row($wpdb->prepare($img_q, array($offset * $limit, 1))); if ($to_be_continued) { return Router::self_redirect(Router::ACTION_IMG_OPTM, self::TYPE_CALC_BKUP); } $msg = __('Calculated backups successfully.', 'litespeed-cache'); Admin_Display::success($msg); } /** * Calculate single size */ private function _get_bk_size($meta_value, $is_ori_file = false) { $short_file_path = $meta_value['file']; if (!$is_ori_file) { $short_file_path = $this->tmp_path . $short_file_path; } $extension = pathinfo($short_file_path, PATHINFO_EXTENSION); $local_filename = substr($short_file_path, 0, -strlen($extension) - 1); $bk_file = $local_filename . '.bk.' . $extension; $img_info = $this->__media->info($bk_file, $this->tmp_pid); if (!$img_info) { return; } $this->_summary['bk_summary']['count']++; $this->_summary['bk_summary']['sum'] += $img_info['size']; } /** * Delete bkup original images storage * * @since 2.5 * @access public */ public function rm_bkup() { global $wpdb; if (!$this->__data->tb_exist('img_optming')) { return; } $offset = !empty($_GET['litespeed_i']) ? $_GET['litespeed_i'] : 0; $limit = 500; if (empty($this->_summary['rmbk_summary'])) { $this->_summary['rmbk_summary'] = array( 'date' => time(), 'count' => 0, 'sum' => 0, ); } $img_q = "SELECT b.post_id, b.meta_value FROM `$wpdb->posts` a LEFT JOIN `$wpdb->postmeta` b ON b.post_id = a.ID WHERE b.meta_key = '_wp_attachment_metadata' AND a.post_type = 'attachment' AND a.post_status = 'inherit' AND a.post_mime_type IN ('image/jpeg', 'image/png', 'image/gif') ORDER BY a.ID LIMIT %d,%d "; $q = $wpdb->prepare($img_q, array($offset * $limit, $limit)); $list = $wpdb->get_results($q); foreach ($list as $v) { if (!$v->post_id) { continue; } $meta_value = $this->_parse_wp_meta_value($v); if (!$meta_value) { continue; } $this->tmp_pid = $v->post_id; $this->tmp_path = pathinfo($meta_value['file'], PATHINFO_DIRNAME) . '/'; $this->_del_bk_file($meta_value, true); if (!empty($meta_value['sizes'])) { array_map(array($this, '_del_bk_file'), $meta_value['sizes']); } } $this->_summary['rmbk_summary']['date'] = time(); self::save_summary(); self::debug('rm_bkup total: ' . $this->_summary['rmbk_summary']['count'] . ' [size] ' . $this->_summary['rmbk_summary']['sum']); $offset++; $to_be_continued = $wpdb->get_row($wpdb->prepare($img_q, array($offset * $limit, 1))); if ($to_be_continued) { return Router::self_redirect(Router::ACTION_IMG_OPTM, self::TYPE_RM_BKUP); } $msg = __('Removed backups successfully.', 'litespeed-cache'); Admin_Display::success($msg); } /** * Delete single file */ private function _del_bk_file($meta_value, $is_ori_file = false) { $short_file_path = $meta_value['file']; if (!$is_ori_file) { $short_file_path = $this->tmp_path . $short_file_path; } $extension = pathinfo($short_file_path, PATHINFO_EXTENSION); $local_filename = substr($short_file_path, 0, -strlen($extension) - 1); $bk_file = $local_filename . '.bk.' . $extension; $img_info = $this->__media->info($bk_file, $this->tmp_pid); if (!$img_info) { return; } $this->_summary['rmbk_summary']['count']++; $this->_summary['rmbk_summary']['sum'] += $img_info['size']; $this->__media->del($bk_file, $this->tmp_pid); } /** * Count images * * @since 1.6 * @access public */ public function img_count() { global $wpdb; $q = "SELECT count(*) FROM `$wpdb->posts` a LEFT JOIN `$wpdb->postmeta` b ON b.post_id = a.ID WHERE b.meta_key = '_wp_attachment_metadata' AND a.post_type = 'attachment' AND a.post_status = 'inherit' AND a.post_mime_type IN ('image/jpeg', 'image/png', 'image/gif') "; $groups_all = $wpdb->get_var($q); $groups_new = $wpdb->get_var($q . ' AND ID>' . (int) $this->_summary['next_post_id'] . ' ORDER BY ID'); $groups_done = $wpdb->get_var($q . ' AND ID<=' . (int) $this->_summary['next_post_id'] . ' ORDER BY ID'); $q = "SELECT b.post_id FROM `$wpdb->posts` a LEFT JOIN `$wpdb->postmeta` b ON b.post_id = a.ID WHERE b.meta_key = '_wp_attachment_metadata' AND a.post_type = 'attachment' AND a.post_status = 'inherit' AND a.post_mime_type IN ('image/jpeg', 'image/png', 'image/gif') ORDER BY a.ID DESC LIMIT 1 "; $max_id = $wpdb->get_var($q); $count_list = array( 'max_id' => $max_id, 'groups_all' => $groups_all, 'groups_new' => $groups_new, 'groups_done' => $groups_done, ); // images count from work table if ($this->__data->tb_exist('img_optming')) { $q = "SELECT COUNT(DISTINCT post_id),COUNT(*) FROM `$this->_table_img_optming` WHERE optm_status = %d"; $groups_to_check = array(self::STATUS_RAW, self::STATUS_REQUESTED, self::STATUS_NOTIFIED, self::STATUS_ERR_FETCH); foreach ($groups_to_check as $v) { $count_list['img.' . $v] = $count_list['group.' . $v] = 0; list($count_list['group.' . $v], $count_list['img.' . $v]) = $wpdb->get_row($wpdb->prepare($q, $v), ARRAY_N); } } return $count_list; } /** * Check if fetch cron is running * * @since 1.6.2 * @access public */ public function cron_running($bool_res = true) { $last_run = !empty($this->_summary['last_pull']) ? $this->_summary['last_pull'] : 0; $is_running = $last_run && time() - $last_run < 120; if ($bool_res) { return $is_running; } return array($last_run, $is_running); } /** * Update fetch cron timestamp tag * * @since 1.6.2 * @access private */ private function _update_cron_running($done = false) { $this->_summary['last_pull'] = time(); if ($done) { // Only update cron tag when its from the active running cron if ($this->_cron_ran) { // Rollback for next running $this->_summary['last_pull'] -= 120; } else { return; } } self::save_summary(); $this->_cron_ran = true; } /** * Batch switch images to ori/optm version * * @since 1.6.2 * @access public */ public function batch_switch($type) { global $wpdb; if (defined('LITESPEED_CLI') || defined('DOING_CRON')) { $offset = 0; while ($offset !== 'done') { Admin_Display::info("Starting switch to $type [offset] $offset"); $offset = $this->_batch_switch($type, $offset); } } else { $offset = !empty($_GET['litespeed_i']) ? $_GET['litespeed_i'] : 0; $newOffset = $this->_batch_switch($type, $offset); if ($newOffset !== 'done') { return Router::self_redirect(Router::ACTION_IMG_OPTM, $type); } } $msg = __('Switched images successfully.', 'litespeed-cache'); Admin_Display::success($msg); } /** * Switch images per offset */ private function _batch_switch($type, $offset) { global $wpdb; $limit = 500; $this->tmp_type = $type; $img_q = "SELECT b.post_id, b.meta_value FROM `$wpdb->posts` a LEFT JOIN `$wpdb->postmeta` b ON b.post_id = a.ID WHERE b.meta_key = '_wp_attachment_metadata' AND a.post_type = 'attachment' AND a.post_status = 'inherit' AND a.post_mime_type IN ('image/jpeg', 'image/png', 'image/gif') ORDER BY a.ID LIMIT %d,%d "; $q = $wpdb->prepare($img_q, array($offset * $limit, $limit)); $list = $wpdb->get_results($q); $i = 0; foreach ($list as $v) { if (!$v->post_id) { continue; } $meta_value = $this->_parse_wp_meta_value($v); if (!$meta_value) { continue; } $i++; $this->tmp_pid = $v->post_id; $this->tmp_path = pathinfo($meta_value['file'], PATHINFO_DIRNAME) . '/'; $this->_switch_bk_file($meta_value, true); if (!empty($meta_value['sizes'])) { array_map(array($this, '_switch_bk_file'), $meta_value['sizes']); } } self::debug('batch switched images total: ' . $i . ' [type] ' . $type); $offset++; $to_be_continued = $wpdb->get_row($wpdb->prepare($img_q, array($offset * $limit, 1))); if ($to_be_continued) { return $offset; } return 'done'; } /** * Delete single file */ private function _switch_bk_file($meta_value, $is_ori_file = false) { $short_file_path = $meta_value['file']; if (!$is_ori_file) { $short_file_path = $this->tmp_path . $short_file_path; } $extension = pathinfo($short_file_path, PATHINFO_EXTENSION); $local_filename = substr($short_file_path, 0, -strlen($extension) - 1); $bk_file = $local_filename . '.bk.' . $extension; $bk_optm_file = $local_filename . '.bk.optm.' . $extension; // self::debug('_switch_bk_file ' . $bk_file . ' [type] ' . $this->tmp_type); // switch to ori if ($this->tmp_type === self::TYPE_BATCH_SWITCH_ORI || $this->tmp_type == 'orig') { // self::debug('switch to orig ' . $bk_file); if (!$this->__media->info($bk_file, $this->tmp_pid)) { return; } $this->__media->rename($local_filename . '.' . $extension, $bk_optm_file, $this->tmp_pid); $this->__media->rename($bk_file, $local_filename . '.' . $extension, $this->tmp_pid); } // switch to optm elseif ($this->tmp_type === self::TYPE_BATCH_SWITCH_OPTM || $this->tmp_type == 'optm') { // self::debug('switch to optm ' . $bk_file); if (!$this->__media->info($bk_optm_file, $this->tmp_pid)) { return; } $this->__media->rename($local_filename . '.' . $extension, $bk_file, $this->tmp_pid); $this->__media->rename($bk_optm_file, $local_filename . '.' . $extension, $this->tmp_pid); } } /** * Switch image between original one and optimized one * * @since 1.6.2 * @access private */ private function _switch_optm_file($type) { Admin_Display::success(__('Switched to optimized file successfully.', 'litespeed-cache')); return; global $wpdb; $pid = substr($type, 4); $switch_type = substr($type, 0, 4); $q = "SELECT src,post_id FROM `$this->_table_img_optm` WHERE post_id = %d AND optm_status = %d"; $list = $wpdb->get_results($wpdb->prepare($q, array($pid, self::STATUS_PULLED))); $msg = 'Unknown Msg'; foreach ($list as $v) { // to switch webp file if ($switch_type === 'webp') { if ($this->__media->info($v->src . '.webp', $v->post_id)) { $this->__media->rename($v->src . '.webp', $v->src . '.optm.webp', $v->post_id); self::debug('Disabled WebP: ' . $v->src); $msg = __('Disabled WebP file successfully.', 'litespeed-cache'); } elseif ($this->__media->info($v->src . '.optm.webp', $v->post_id)) { $this->__media->rename($v->src . '.optm.webp', $v->src . '.webp', $v->post_id); self::debug('Enable WebP: ' . $v->src); $msg = __('Enabled WebP file successfully.', 'litespeed-cache'); } } // to switch avif file elseif ($switch_type === 'avif') { if ($this->__media->info($v->src . '.avif', $v->post_id)) { $this->__media->rename($v->src . '.avif', $v->src . '.optm.avif', $v->post_id); self::debug('Disabled AVIF: ' . $v->src); $msg = __('Disabled AVIF file successfully.', 'litespeed-cache'); } elseif ($this->__media->info($v->src . '.optm.avif', $v->post_id)) { $this->__media->rename($v->src . '.optm.avif', $v->src . '.avif', $v->post_id); self::debug('Enable AVIF: ' . $v->src); $msg = __('Enabled AVIF file successfully.', 'litespeed-cache'); } } // to switch original file else { $extension = pathinfo($v->src, PATHINFO_EXTENSION); $local_filename = substr($v->src, 0, -strlen($extension) - 1); $bk_file = $local_filename . '.bk.' . $extension; $bk_optm_file = $local_filename . '.bk.optm.' . $extension; // revert ori back if ($this->__media->info($bk_file, $v->post_id)) { $this->__media->rename($v->src, $bk_optm_file, $v->post_id); $this->__media->rename($bk_file, $v->src, $v->post_id); self::debug('Restore original img: ' . $bk_file); $msg = __('Restored original file successfully.', 'litespeed-cache'); } elseif ($this->__media->info($bk_optm_file, $v->post_id)) { $this->__media->rename($v->src, $bk_file, $v->post_id); $this->__media->rename($bk_optm_file, $v->src, $v->post_id); self::debug('Switch to optm img: ' . $v->src); $msg = __('Switched to optimized file successfully.', 'litespeed-cache'); } } } Admin_Display::success($msg); } /** * Delete one optm data and recover original file * * @since 2.4.2 * @access public */ public function reset_row($post_id) { global $wpdb; if (!$post_id) { return; } // Gathered image don't have DB_SIZE info yet // $size_meta = get_post_meta( $post_id, self::DB_SIZE, true ); // if ( ! $size_meta ) { // return; // } self::debug('_reset_row [pid] ' . $post_id); # TODO: Load image sub files $img_q = "SELECT b.post_id, b.meta_value FROM `$wpdb->postmeta` b WHERE b.post_id =%d AND b.meta_key = '_wp_attachment_metadata'"; $q = $wpdb->prepare($img_q, array($post_id)); $v = $wpdb->get_row($q); $meta_value = $this->_parse_wp_meta_value($v); if ($meta_value) { $this->tmp_pid = $v->post_id; $this->tmp_path = pathinfo($meta_value['file'], PATHINFO_DIRNAME) . '/'; $this->_destroy_optm_file($meta_value, true); if (!empty($meta_value['sizes'])) { array_map(array($this, '_destroy_optm_file'), $meta_value['sizes']); } } delete_post_meta($post_id, self::DB_SIZE); delete_post_meta($post_id, self::DB_SET); $msg = __('Reset the optimized data successfully.', 'litespeed-cache'); Admin_Display::success($msg); } /** * Show an image's optm status * * @since 1.6.5 * @access public */ public function check_img() { global $wpdb; $pid = $_POST['data']; self::debug('Check image [ID] ' . $pid); $data = array(); $data['img_count'] = $this->img_count(); $data['optm_summary'] = self::get_summary(); $data['_wp_attached_file'] = get_post_meta($pid, '_wp_attached_file', true); $data['_wp_attachment_metadata'] = get_post_meta($pid, '_wp_attachment_metadata', true); // Get img_optm data $q = "SELECT * FROM `$this->_table_img_optm` WHERE post_id = %d"; $list = $wpdb->get_results($wpdb->prepare($q, $pid)); $img_data = array(); if ($list) { foreach ($list as $v) { $img_data[] = array( 'id' => $v->id, 'optm_status' => $v->optm_status, 'src' => $v->src, 'srcpath_md5' => $v->srcpath_md5, 'src_md5' => $v->src_md5, 'server_info' => $v->server_info, ); } } $data['img_data'] = $img_data; return array('_res' => 'ok', 'data' => $data); } /** * Handle all request actions from main cls * * @since 2.0 * @access public */ public function handler() { $type = Router::verify_type(); switch ($type) { case self::TYPE_RESET_ROW: $this->reset_row(!empty($_GET['id']) ? $_GET['id'] : false); break; case self::TYPE_CALC_BKUP: $this->_calc_bkup(); break; case self::TYPE_RM_BKUP: $this->rm_bkup(); break; case self::TYPE_NEW_REQ: $this->new_req(); break; case self::TYPE_RESCAN: $this->_rescan(); break; case self::TYPE_RESET_COUNTER: $this->_reset_counter(); break; case self::TYPE_DESTROY: $this->_destroy(); break; case self::TYPE_CLEAN: $this->clean(); break; case self::TYPE_PULL: self::start_async(); break; case self::TYPE_BATCH_SWITCH_ORI: case self::TYPE_BATCH_SWITCH_OPTM: $this->batch_switch($type); break; case substr($type, 0, 4) === 'avif': case substr($type, 0, 4) === 'webp': case substr($type, 0, 4) === 'orig': $this->_switch_optm_file($type); break; default: break; } Admin::redirect(); } } src/admin-display.cls.php 0000644 00000106620 15246276230 0011373 0 ustar 00 <?php /** * The admin-panel specific functionality of the plugin. * * * @since 1.0.0 * @package LiteSpeed * @subpackage LiteSpeed/admin * @author LiteSpeed Technologies <info@litespeedtech.com> */ namespace LiteSpeed; defined('WPINC') || exit(); class Admin_Display extends Base { const LOG_TAG = '👮♀️'; const NOTICE_BLUE = 'notice notice-info'; const NOTICE_GREEN = 'notice notice-success'; const NOTICE_RED = 'notice notice-error'; const NOTICE_YELLOW = 'notice notice-warning'; const DB_MSG = 'messages'; const DB_MSG_PIN = 'msg_pin'; const PURGEBY_CAT = '0'; const PURGEBY_PID = '1'; const PURGEBY_TAG = '2'; const PURGEBY_URL = '3'; const PURGEBYOPT_SELECT = 'purgeby'; const PURGEBYOPT_LIST = 'purgebylist'; const DB_DISMISS_MSG = 'dismiss'; const RULECONFLICT_ON = 'ExpiresDefault_1'; const RULECONFLICT_DISMISSED = 'ExpiresDefault_0'; const TYPE_QC_HIDE_BANNER = 'qc_hide_banner'; const COOKIE_QC_HIDE_BANNER = 'litespeed_qc_hide_banner'; protected $messages = array(); protected $default_settings = array(); protected $_is_network_admin = false; protected $_is_multisite = false; private $_btn_i = 0; /** * Initialize the class and set its properties. * * @since 1.0.7 */ public function __construct() { // main css add_action('admin_enqueue_scripts', array($this, 'enqueue_style')); // Main js add_action('admin_enqueue_scripts', array($this, 'enqueue_scripts')); $this->_is_network_admin = is_network_admin(); $this->_is_multisite = is_multisite(); // Quick access menu if (is_multisite() && $this->_is_network_admin) { $manage = 'manage_network_options'; } else { $manage = 'manage_options'; } if (current_user_can($manage)) { if (!defined('LITESPEED_DISABLE_ALL') || !LITESPEED_DISABLE_ALL) { add_action('wp_before_admin_bar_render', array(GUI::cls(), 'backend_shortcut')); } // `admin_notices` is after `admin_enqueue_scripts` // @see wp-admin/admin-header.php add_action($this->_is_network_admin ? 'network_admin_notices' : 'admin_notices', array($this, 'display_messages')); } /** * In case this is called outside the admin page * @see https://codex.wordpress.org/Function_Reference/is_plugin_active_for_network * @since 2.0 */ if (!function_exists('is_plugin_active_for_network')) { require_once ABSPATH . '/wp-admin/includes/plugin.php'; } // add menus ( Also check for mu-plugins) if ($this->_is_network_admin && (is_plugin_active_for_network(LSCWP_BASENAME) || defined('LSCWP_MU_PLUGIN'))) { add_action('network_admin_menu', array($this, 'register_admin_menu')); } else { add_action('admin_menu', array($this, 'register_admin_menu')); } $this->cls('Metabox')->register_settings(); } /** * Show the title of one line * * @since 3.0 * @access public */ public function title($id) { echo Lang::title($id); } /** * Register the admin menu display. * * @since 1.0.0 * @access public */ public function register_admin_menu() { $capability = $this->_is_network_admin ? 'manage_network_options' : 'manage_options'; if (current_user_can($capability)) { // root menu add_menu_page('LiteSpeed Cache', 'LiteSpeed Cache', 'manage_options', 'litespeed'); // sub menus $this->_add_submenu(__('Dashboard', 'litespeed-cache'), 'litespeed', 'show_menu_dash'); !$this->_is_network_admin && $this->_add_submenu(__('Presets', 'litespeed-cache'), 'litespeed-presets', 'show_menu_presets'); $this->_add_submenu(__('General', 'litespeed-cache'), 'litespeed-general', 'show_menu_general'); $this->_add_submenu(__('Cache', 'litespeed-cache'), 'litespeed-cache', 'show_menu_cache'); !$this->_is_network_admin && $this->_add_submenu(__('CDN', 'litespeed-cache'), 'litespeed-cdn', 'show_menu_cdn'); $this->_add_submenu(__('Image Optimization', 'litespeed-cache'), 'litespeed-img_optm', 'show_img_optm'); !$this->_is_network_admin && $this->_add_submenu(__('Page Optimization', 'litespeed-cache'), 'litespeed-page_optm', 'show_page_optm'); $this->_add_submenu(__('Database', 'litespeed-cache'), 'litespeed-db_optm', 'show_db_optm'); !$this->_is_network_admin && $this->_add_submenu(__('Crawler', 'litespeed-cache'), 'litespeed-crawler', 'show_crawler'); $this->_add_submenu(__('Toolbox', 'litespeed-cache'), 'litespeed-toolbox', 'show_toolbox'); // sub menus under options add_options_page('LiteSpeed Cache', 'LiteSpeed Cache', $capability, 'litespeed-cache-options', array($this, 'show_menu_cache')); } } /** * Helper function to set up a submenu page. * * @since 1.0.4 * @access private * @param string $menu_title The title that appears on the menu. * @param string $menu_slug The slug of the page. * @param string $callback The callback to call if selected. */ private function _add_submenu($menu_title, $menu_slug, $callback) { add_submenu_page('litespeed', $menu_title, $menu_title, 'manage_options', $menu_slug, array($this, $callback)); } /** * Register the stylesheets for the admin area. * * @since 1.0.14 * @access public */ public function enqueue_style() { wp_enqueue_style(Core::PLUGIN_NAME, LSWCP_PLUGIN_URL . 'assets/css/litespeed.css', array(), Core::VER, 'all'); } /** * Register the JavaScript for the admin area. * * @since 1.0.0 * @access public */ public function enqueue_scripts() { wp_register_script(Core::PLUGIN_NAME, LSWCP_PLUGIN_URL . 'assets/js/litespeed-cache-admin.js', array(), Core::VER, false); $localize_data = array(); if (GUI::has_whm_msg()) { $ajax_url_dismiss_whm = Utility::build_url(Core::ACTION_DISMISS, GUI::TYPE_DISMISS_WHM, true); $localize_data['ajax_url_dismiss_whm'] = $ajax_url_dismiss_whm; } if (GUI::has_msg_ruleconflict()) { $ajax_url = Utility::build_url(Core::ACTION_DISMISS, GUI::TYPE_DISMISS_EXPIRESDEFAULT, true); $localize_data['ajax_url_dismiss_ruleconflict'] = $ajax_url; } $promo_tag = GUI::cls()->show_promo(true); if ($promo_tag) { $ajax_url_promo = Utility::build_url(Core::ACTION_DISMISS, GUI::TYPE_DISMISS_PROMO, true, null, array('promo_tag' => $promo_tag)); $localize_data['ajax_url_promo'] = $ajax_url_promo; } // Injection to LiteSpeed pages global $pagenow; if ($pagenow == 'admin.php' && !empty($_GET['page']) && (strpos($_GET['page'], 'litespeed-') === 0 || $_GET['page'] == 'litespeed')) { // Admin footer add_filter('admin_footer_text', array($this, 'admin_footer_text'), 1); if ($_GET['page'] == 'litespeed-crawler' || $_GET['page'] == 'litespeed-cdn') { // Babel JS type correction add_filter('script_loader_tag', array($this, 'babel_type'), 10, 3); wp_enqueue_script(Core::PLUGIN_NAME . '-lib-react', LSWCP_PLUGIN_URL . 'assets/js/react.min.js', array(), Core::VER, false); wp_enqueue_script(Core::PLUGIN_NAME . '-lib-babel', LSWCP_PLUGIN_URL . 'assets/js/babel.min.js', array(), Core::VER, false); } // Crawler Cookie Simulation if ($_GET['page'] == 'litespeed-crawler') { wp_enqueue_script(Core::PLUGIN_NAME . '-crawler', LSWCP_PLUGIN_URL . 'assets/js/component.crawler.js', array(), Core::VER, false); $localize_data['lang'] = array(); $localize_data['lang']['cookie_name'] = __('Cookie Name', 'litespeed-cache'); $localize_data['lang']['cookie_value'] = __('Cookie Values', 'litespeed-cache'); $localize_data['lang']['one_per_line'] = Doc::one_per_line(true); $localize_data['lang']['remove_cookie_simulation'] = __('Remove cookie simulation', 'litespeed-cache'); $localize_data['lang']['add_cookie_simulation_row'] = __('Add new cookie to simulate', 'litespeed-cache'); empty($localize_data['ids']) && ($localize_data['ids'] = array()); $localize_data['ids']['crawler_cookies'] = self::O_CRAWLER_COOKIES; } // CDN mapping if ($_GET['page'] == 'litespeed-cdn') { $home_url = home_url('/'); $parsed = parse_url($home_url); $home_url = str_replace($parsed['scheme'] . ':', '', $home_url); $cdn_url = 'https://cdn.' . substr($home_url, 2); wp_enqueue_script(Core::PLUGIN_NAME . '-cdn', LSWCP_PLUGIN_URL . 'assets/js/component.cdn.js', array(), Core::VER, false); $localize_data['lang'] = array(); $localize_data['lang']['cdn_mapping_url'] = Lang::title(self::CDN_MAPPING_URL); $localize_data['lang']['cdn_mapping_inc_img'] = Lang::title(self::CDN_MAPPING_INC_IMG); $localize_data['lang']['cdn_mapping_inc_css'] = Lang::title(self::CDN_MAPPING_INC_CSS); $localize_data['lang']['cdn_mapping_inc_js'] = Lang::title(self::CDN_MAPPING_INC_JS); $localize_data['lang']['cdn_mapping_filetype'] = Lang::title(self::CDN_MAPPING_FILETYPE); $localize_data['lang']['cdn_mapping_url_desc'] = sprintf(__('CDN URL to be used. For example, %s', 'litespeed-cache'), '<code>' . $cdn_url . '</code>'); $localize_data['lang']['one_per_line'] = Doc::one_per_line(true); $localize_data['lang']['cdn_mapping_remove'] = __('Remove CDN URL', 'litespeed-cache'); $localize_data['lang']['add_cdn_mapping_row'] = __('Add new CDN URL', 'litespeed-cache'); $localize_data['lang']['on'] = __('ON', 'litespeed-cache'); $localize_data['lang']['off'] = __('OFF', 'litespeed-cache'); empty($localize_data['ids']) && ($localize_data['ids'] = array()); $localize_data['ids']['cdn_mapping'] = self::O_CDN_MAPPING; } // If on Server IP setting page, append getIP link if ($_GET['page'] == 'litespeed-general') { $localize_data['ajax_url_getIP'] = function_exists('get_rest_url') ? get_rest_url(null, 'litespeed/v1/tool/check_ip') : '/'; $localize_data['nonce'] = wp_create_nonce('wp_rest'); } // Activate or deactivate a specific crawler if ($_GET['page'] == 'litespeed-crawler') { $localize_data['ajax_url_crawler_switch'] = function_exists('get_rest_url') ? get_rest_url(null, 'litespeed/v1/toggle_crawler_state') : '/'; $localize_data['nonce'] = wp_create_nonce('wp_rest'); } } if ($localize_data) { wp_localize_script(Core::PLUGIN_NAME, 'litespeed_data', $localize_data); } wp_enqueue_script(Core::PLUGIN_NAME); } /** * Babel type for crawler * * @since 3.6 */ public function babel_type($tag, $handle, $src) { if ($handle != Core::PLUGIN_NAME . '-crawler' && $handle != Core::PLUGIN_NAME . '-cdn') { return $tag; } return '<script src="' . Str::trim_quotes($src) . '" type="text/babel"></script>'; } /** * Callback that adds LiteSpeed Cache's action links. * * @since 1.0.0 * @access public * @param array $links Previously added links from other plugins. * @return array Links array with the litespeed cache one appended. */ public function add_plugin_links($links) { // $links[] = '<a href="' . admin_url('options-general.php?page=litespeed-cache') . '">' . __('Settings', 'litespeed-cache') . '</a>'; $links[] = '<a href="' . admin_url('admin.php?page=litespeed-cache') . '">' . __('Settings', 'litespeed-cache') . '</a>'; return $links; } /** * Change the admin footer text on LiteSpeed Cache admin pages. * * @since 1.0.13 * @param string $footer_text * @return string */ public function admin_footer_text($footer_text) { require_once LSCWP_DIR . 'tpl/inc/admin_footer.php'; return $footer_text; } /** * Builds the html for a single notice. * * @since 1.0.7 * @access public * @param string $color The color to use for the notice. * @param string $str The notice message. * @return string The built notice html. */ public static function build_notice($color, $str, $irremovable = false, $additional_classes = '') { $cls = $color; if ($irremovable) { $cls .= ' litespeed-irremovable'; } else { $cls .= ' is-dismissible'; } if ($additional_classes) { $cls .= ' ' . $additional_classes; } // possible translation $str = Lang::maybe_translate($str); return '<div class="litespeed_icon ' . $cls . '"><p>' . wp_kses_post($str) . '</p></div>'; } /** * Display info notice * * @since 1.6.5 * @access public */ public static function info($msg, $echo = false, $irremovable = false, $additional_classes = '') { self::add_notice(self::NOTICE_BLUE, $msg, $echo, $irremovable, $additional_classes); } /** * Display note notice * * @since 1.6.5 * @access public */ public static function note($msg, $echo = false, $irremovable = false, $additional_classes = '') { self::add_notice(self::NOTICE_YELLOW, $msg, $echo, $irremovable, $additional_classes); } /** * Display success notice * * @since 1.6 * @access public */ public static function success($msg, $echo = false, $irremovable = false, $additional_classes = '') { self::add_notice(self::NOTICE_GREEN, $msg, $echo, $irremovable, $additional_classes); } /** @deprecated 4.7 */ /** will drop in v7.5 */ public static function succeed($msg, $echo = false, $irremovable = false, $additional_classes = '') { self::success($msg, $echo, $irremovable, $additional_classes); } /** * Display error notice * * @since 1.6 * @access public */ public static function error($msg, $echo = false, $irremovable = false, $additional_classes = '') { self::add_notice(self::NOTICE_RED, $msg, $echo, $irremovable, $additional_classes); } /** * Add irremovable msg * @since 4.7 */ public static function add_unique_notice($color_mode, $msgs, $irremovable = false) { if (!is_array($msgs)) { $msgs = array($msgs); } $color_map = array( 'info' => self::NOTICE_BLUE, 'note' => self::NOTICE_YELLOW, 'success' => self::NOTICE_GREEN, 'error' => self::NOTICE_RED, ); if (empty($color_map[$color_mode])) { self::debug('Wrong admin display color mode!'); return; } $color = $color_map[$color_mode]; // Go through to make sure unique $filtered_msgs = array(); foreach ($msgs as $k => $str) { if (is_numeric($k)) { $k = md5($str); } // Use key to make it overwritable to previous same msg $filtered_msgs[$k] = $str; } self::add_notice($color, $filtered_msgs, false, $irremovable); } /** * Adds a notice to display on the admin page * * @since 1.0.7 * @access public */ public static function add_notice($color, $msg, $echo = false, $irremovable = false, $additional_classes = '') { // self::debug("add_notice msg", $msg); // Bypass adding for CLI or cron if (defined('LITESPEED_CLI') || defined('DOING_CRON')) { // WP CLI will show the info directly if (defined('WP_CLI') && WP_CLI) { if (!is_array($msg)) { $msg = array($msg); } foreach ($msg as $v) { $v = strip_tags($v); if ($color == self::NOTICE_RED) { \WP_CLI::error($v, false); } else { \WP_CLI::success($v); } } } return; } if ($echo) { echo self::build_notice($color, $msg, $irremovable, $additional_classes); return; } $msg_name = $irremovable ? self::DB_MSG_PIN : self::DB_MSG; $messages = self::get_option($msg_name, array()); if (!is_array($messages)) { $messages = array(); } if (is_array($msg)) { foreach ($msg as $k => $str) { $messages[$k] = self::build_notice($color, $str, $irremovable, $additional_classes); } } else { $messages[] = self::build_notice($color, $msg, $irremovable, $additional_classes); } $messages = array_unique($messages); self::update_option($msg_name, $messages); } /** * Display notices and errors in dashboard * * @since 1.1.0 * @access public */ public function display_messages() { if (!defined('LITESPEED_CONF_LOADED')) { $this->_in_upgrading(); } if (GUI::has_whm_msg()) { $this->show_display_installed(); } Data::cls()->check_upgrading_msg(); // If is in dev version, always check latest update Cloud::cls()->check_dev_version(); // One time msg $messages = self::get_option(self::DB_MSG, array()); $added_thickbox = false; if (is_array($messages)) { foreach ($messages as $msg) { // Added for popup links if (strpos($msg, 'TB_iframe') && !$added_thickbox) { add_thickbox(); $added_thickbox = true; } echo wp_kses_post($msg); } } if ($messages != -1) { self::update_option(self::DB_MSG, -1); } // Pinned msg $messages = self::get_option(self::DB_MSG_PIN, array()); if (is_array($messages)) { foreach ($messages as $k => $msg) { // Added for popup links if (strpos($msg, 'TB_iframe') && !$added_thickbox) { add_thickbox(); $added_thickbox = true; } // Append close btn if (substr($msg, -6) == '</div>') { $link = Utility::build_url(Core::ACTION_DISMISS, GUI::TYPE_DISMISS_PIN, false, null, array('msgid' => $k)); $msg = substr($msg, 0, -6) . '<p><a href="' . $link . '" class="button litespeed-btn-primary litespeed-btn-mini">' . __('Dismiss', 'litespeed-cache') . '</a>' . '</p></div>'; } echo wp_kses_post($msg); } } // if ( $messages != -1 ) { // self::update_option( self::DB_MSG_PIN, -1 ); // } if (empty($_GET['page']) || strpos($_GET['page'], 'litespeed') !== 0) { global $pagenow; if ($pagenow != 'plugins.php') { // && $pagenow != 'index.php' return; } } // Show disable all warning if (defined('LITESPEED_DISABLE_ALL') && LITESPEED_DISABLE_ALL) { Admin_Display::error(Error::msg('disabled_all'), true); } if (!$this->conf(self::O_NEWS)) { return; } // Show promo from cloud Cloud::cls()->show_promo(); /** * Check promo msg first * @since 2.9 */ GUI::cls()->show_promo(); // Show version news Cloud::cls()->news(); } /** * Dismiss pinned msg * * @since 3.5.2 * @access public */ public static function dismiss_pin() { if (!isset($_GET['msgid'])) { return; } $messages = self::get_option(self::DB_MSG_PIN, array()); if (!is_array($messages) || empty($messages[$_GET['msgid']])) { return; } unset($messages[$_GET['msgid']]); if (!$messages) { $messages = -1; } self::update_option(self::DB_MSG_PIN, $messages); } /** * Dismiss pinned msg by msg content * * @since 7.0 * @access public */ public static function dismiss_pin_by_content($content, $color, $irremovable) { $content = self::build_notice($color, $content, $irremovable); $messages = self::get_option(self::DB_MSG_PIN, array()); $hit = false; if ($messages != -1) { foreach ($messages as $k => $v) { if ($v == $content) { unset($messages[$k]); $hit = true; self::debug('✅ pinned msg content hit. Removed'); break; } } } if ($hit) { if (!$messages) { $messages = -1; } self::update_option(self::DB_MSG_PIN, $messages); } else { self::debug('❌ No pinned msg content hit'); } } /** * Hooked to the in_widget_form action. * Appends LiteSpeed Cache settings to the widget edit settings screen. * This will append the esi on/off selector and ttl text. * * @since 1.1.0 * @access public */ public function show_widget_edit($widget, $return, $instance) { require LSCWP_DIR . 'tpl/esi_widget_edit.php'; } /** * Displays the dashboard page. * * @since 3.0 * @access public */ public function show_menu_dash() { $this->cls('Cloud')->maybe_preview_banner(); require_once LSCWP_DIR . 'tpl/dash/entry.tpl.php'; } /** * Displays the General page. * * @since 5.3 * @access public */ public function show_menu_presets() { require_once LSCWP_DIR . 'tpl/presets/entry.tpl.php'; } /** * Displays the General page. * * @since 3.0 * @access public */ public function show_menu_general() { $this->cls('Cloud')->maybe_preview_banner(); require_once LSCWP_DIR . 'tpl/general/entry.tpl.php'; } /** * Displays the CDN page. * * @since 3.0 * @access public */ public function show_menu_cdn() { $this->cls('Cloud')->maybe_preview_banner(); require_once LSCWP_DIR . 'tpl/cdn/entry.tpl.php'; } /** * Outputs the LiteSpeed Cache settings page. * * @since 1.0.0 * @access public */ public function show_menu_cache() { if ($this->_is_network_admin) { require_once LSCWP_DIR . 'tpl/cache/entry_network.tpl.php'; } else { require_once LSCWP_DIR . 'tpl/cache/entry.tpl.php'; } } /** * Tools page * * @since 3.0 * @access public */ public function show_toolbox() { $this->cls('Cloud')->maybe_preview_banner(); require_once LSCWP_DIR . 'tpl/toolbox/entry.tpl.php'; } /** * Outputs the crawler operation page. * * @since 1.1.0 * @access public */ public function show_crawler() { $this->cls('Cloud')->maybe_preview_banner(); require_once LSCWP_DIR . 'tpl/crawler/entry.tpl.php'; } /** * Outputs the optimization operation page. * * @since 1.6 * @access public */ public function show_img_optm() { $this->cls('Cloud')->maybe_preview_banner(); require_once LSCWP_DIR . 'tpl/img_optm/entry.tpl.php'; } /** * Page optm page. * * @since 3.0 * @access public */ public function show_page_optm() { $this->cls('Cloud')->maybe_preview_banner(); require_once LSCWP_DIR . 'tpl/page_optm/entry.tpl.php'; } /** * DB optm page. * * @since 3.0 * @access public */ public function show_db_optm() { require_once LSCWP_DIR . 'tpl/db_optm/entry.tpl.php'; } /** * Outputs a notice to the admin panel when the plugin is installed * via the WHM plugin. * * @since 1.0.12 * @access public */ public function show_display_installed() { require_once LSCWP_DIR . 'tpl/inc/show_display_installed.php'; } /** * Display error cookie msg. * * @since 1.0.12 * @access public */ public static function show_error_cookie() { require_once LSCWP_DIR . 'tpl/inc/show_error_cookie.php'; } /** * Display warning if lscache is disabled * * @since 2.1 * @access public */ public function cache_disabled_warning() { include LSCWP_DIR . 'tpl/inc/check_cache_disabled.php'; } /** * Display conf data upgrading banner * * @since 2.1 * @access private */ private function _in_upgrading() { include LSCWP_DIR . 'tpl/inc/in_upgrading.php'; } /** * Output litespeed form info * * @since 3.0 * @access public */ public function form_action($action = false, $type = false, $has_upload = false) { if (!$action) { $action = Router::ACTION_SAVE_SETTINGS; } $has_upload = $has_upload ? 'enctype="multipart/form-data"' : ''; if (!defined('LITESPEED_CONF_LOADED')) { echo '<div class="litespeed-relative"'; } else { echo '<form method="post" action="' . wp_unslash($_SERVER['REQUEST_URI']) . '" class="litespeed-relative" ' . $has_upload . '>'; } echo '<input type="hidden" name="' . Router::ACTION . '" value="' . $action . '" />'; if ($type) { echo '<input type="hidden" name="' . Router::TYPE . '" value="' . $type . '" />'; } wp_nonce_field($action, Router::NONCE); } /** * Output litespeed form info END * * @since 3.0 * @access public */ public function form_end($disable_reset = false) { echo "<div class='litespeed-top20'></div>"; if (!defined('LITESPEED_CONF_LOADED')) { submit_button(__('Save Changes', 'litespeed-cache'), 'secondary litespeed-duplicate-float', 'litespeed-submit', true, array('disabled' => 'disabled')); echo '</div>'; } else { submit_button(__('Save Changes', 'litespeed-cache'), 'primary litespeed-duplicate-float', 'litespeed-submit', true, array( 'id' => 'litespeed-submit-' . $this->_btn_i++, )); echo '</form>'; } } /** * Register this setting to save * * @since 3.0 * @access public */ public function enroll($id) { echo '<input type="hidden" name="' . Admin_Settings::ENROLL . '[]" value="' . $id . '" />'; } /** * Build a textarea * * @since 1.1.0 * @access public */ public function build_textarea($id, $cols = false, $val = null) { if ($val === null) { $val = $this->conf($id, true); if (is_array($val)) { $val = implode("\n", $val); } } if (!$cols) { $cols = 80; } $rows = 5; $lines = substr_count($val, "\n") + 2; if ($lines > $rows) { $rows = $lines; } if ($rows > 40) { $rows = 40; } $this->enroll($id); echo "<textarea name='$id' rows='$rows' cols='$cols'>" . esc_textarea($val) . '</textarea>'; $this->_check_overwritten($id); } /** * Build a text input field * * @since 1.1.0 * @access public */ public function build_input($id, $cls = null, $val = null, $type = 'text', $disabled = false) { if ($val === null) { $val = $this->conf($id, true); // Mask pswds if ($this->_conf_pswd($id) && $val) { $val = str_repeat('*', strlen($val)); } } $label_id = preg_replace('/\W/', '', $id); if ($type == 'text') { $cls = "regular-text $cls"; } if ($disabled) { echo "<input type='$type' class='$cls' value='" . esc_textarea($val) . "' id='input_$label_id' disabled /> "; } else { $this->enroll($id); echo "<input type='$type' class='$cls' name='$id' value='" . esc_textarea($val) . "' id='input_$label_id' /> "; } $this->_check_overwritten($id); } /** * Build a checkbox html snippet * * @since 1.1.0 * @access public * @param string $id * @param string $title * @param bool $checked */ public function build_checkbox($id, $title, $checked = null, $value = 1) { if ($checked === null && $this->conf($id, true)) { $checked = true; } $checked = $checked ? ' checked ' : ''; $label_id = preg_replace('/\W/', '', $id); if ($value !== 1) { $label_id .= '_' . $value; } $this->enroll($id); echo "<div class='litespeed-tick'> <input type='checkbox' name='$id' id='input_checkbox_$label_id' value='$value' $checked /> <label for='input_checkbox_$label_id'>$title</label> </div>"; $this->_check_overwritten($id); } /** * Build a toggle checkbox html snippet * * @since 1.7 */ public function build_toggle($id, $checked = null, $title_on = null, $title_off = null) { if ($checked === null && $this->conf($id, true)) { $checked = true; } if ($title_on === null) { $title_on = __('ON', 'litespeed-cache'); $title_off = __('OFF', 'litespeed-cache'); } $cls = $checked ? 'primary' : 'default litespeed-toggleoff'; echo "<div class='litespeed-toggle litespeed-toggle-btn litespeed-toggle-btn-$cls' data-litespeed-toggle-on='primary' data-litespeed-toggle-off='default' data-litespeed_toggle_id='$id' > <input name='$id' type='hidden' value='$checked' /> <div class='litespeed-toggle-group'> <label class='litespeed-toggle-btn litespeed-toggle-btn-primary litespeed-toggle-on'>$title_on</label> <label class='litespeed-toggle-btn litespeed-toggle-btn-default litespeed-toggle-active litespeed-toggle-off'>$title_off</label> <span class='litespeed-toggle-handle litespeed-toggle-btn litespeed-toggle-btn-default'></span> </div> </div>"; } /** * Build a switch div html snippet * * @since 1.1.0 * @since 1.7 removed param $disable * @access public */ public function build_switch($id, $title_list = false) { $this->enroll($id); echo '<div class="litespeed-switch">'; if (!$title_list) { $title_list = array(__('OFF', 'litespeed-cache'), __('ON', 'litespeed-cache')); } foreach ($title_list as $k => $v) { $this->_build_radio($id, $k, $v); } echo '</div>'; $this->_check_overwritten($id); } /** * Build a radio input html codes and output * * @since 1.1.0 * @access private */ private function _build_radio($id, $val, $txt) { $id_attr = 'input_radio_' . preg_replace('/\W/', '', $id) . '_' . $val; $default = isset(self::$_default_options[$id]) ? self::$_default_options[$id] : self::$_default_site_options[$id]; if (!is_string($default)) { $checked = (int) $this->conf($id, true) === (int) $val ? ' checked ' : ''; } else { $checked = $this->conf($id, true) === $val ? ' checked ' : ''; } echo "<input type='radio' autocomplete='off' name='$id' id='$id_attr' value='$val' $checked /> <label for='$id_attr'>$txt</label>"; } /** * Show overwritten msg if there is a const defined * * @since 3.0 */ protected function _check_overwritten($id) { $const_val = $this->const_overwritten($id); $primary_val = $this->primary_overwritten($id); if ($const_val === null && $primary_val === null) { return; } $val = $const_val !== null ? $const_val : $primary_val; $default = isset(self::$_default_options[$id]) ? self::$_default_options[$id] : self::$_default_site_options[$id]; if (is_bool($default)) { $val = $val ? __('ON', 'litespeed-cache') : __('OFF', 'litespeed-cache'); } else { if (is_array($default)) { $val = implode("\n", $val); } $val = esc_textarea($val); } echo '<div class="litespeed-desc litespeed-warning">⚠️ '; if ($const_val !== null) { echo sprintf(__('This setting is overwritten by the PHP constant %s', 'litespeed-cache'), '<code>' . Base::conf_const($id) . '</code>'); } else { if (get_current_blog_id() != BLOG_ID_CURRENT_SITE && $this->conf(self::NETWORK_O_USE_PRIMARY)) { echo __('This setting is overwritten by the primary site setting', 'litespeed-cache'); } else { echo __('This setting is overwritten by the Network setting', 'litespeed-cache'); } } echo ', ' . sprintf(__('currently set to %s', 'litespeed-cache'), "<code>$val</code>") . '</div>'; } /** * Display seconds text and readable layout * * @since 3.0 * @access public */ public function readable_seconds() { echo __('seconds', 'litespeed-cache'); echo ' <span data-litespeed-readable=""></span>'; } /** * Display default value * * @since 1.1.1 * @access public */ public function recommended($id) { if (!$this->default_settings) { $this->default_settings = $this->load_default_vals(); } $val = $this->default_settings[$id]; if ($val) { if (is_array($val)) { $rows = 5; $cols = 30; // Flexible rows/cols $lines = count($val) + 1; $rows = min(max($lines, $rows), 40); foreach ($val as $v) { $cols = max(strlen($v), $cols); } $cols = min($cols, 150); $val = implode("\n", $val); $val = esc_textarea($val); $val = '<div class="litespeed-desc">' . __('Default value', 'litespeed-cache') . ':</div>' . "<textarea readonly rows='$rows' cols='$cols'>$val</textarea>"; } else { $val = esc_textarea($val); $val = "<code>$val</code>"; $val = __('Default value', 'litespeed-cache') . ': ' . $val; } echo $val; } } /** * Validate rewrite rules regex syntax * * @since 3.0 */ protected function _validate_syntax($id) { $val = $this->conf($id, true); if (!$val) { return; } if (!is_array($val)) { $val = array($val); } foreach ($val as $v) { if (!Utility::syntax_checker($v)) { echo '<br /><font class="litespeed-warning"> ❌ ' . __('Invalid rewrite rule', 'litespeed-cache') . ': <code>' . $v . '</code></font>'; } } } /** * Validate if the htaccess path is valid * * @since 3.0 */ protected function _validate_htaccess_path($id) { $val = $this->conf($id, true); if (!$val) { return; } if (substr($val, -10) !== '/.htaccess') { echo '<br /><font class="litespeed-warning"> ❌ ' . sprintf(__('Path must end with %s', 'litespeed-cache'), '<code>/.htaccess</code>') . '</font>'; } } /** * Check ttl instead of error when saving * * @since 3.0 */ protected function _validate_ttl($id, $min = false, $max = false, $allow_zero = false) { $val = $this->conf($id, true); if ($allow_zero && !$val) { // return; } $tip = array(); if ($min && $val < $min && (!$allow_zero || $val != 0)) { $tip[] = __('Minimum value', 'litespeed-cache') . ': <code>' . $min . '</code>.'; } if ($max && $val > $max) { $tip[] = __('Maximum value', 'litespeed-cache') . ': <code>' . $max . '</code>.'; } echo '<br />'; if ($tip) { echo '<font class="litespeed-warning"> ❌ ' . implode(' ', $tip) . '</font>'; } $range = ''; if ($allow_zero) { $range .= __('Zero, or', 'litespeed-cache') . ' '; } if ($min && $max) { $range .= $min . ' - ' . $max; } elseif ($min) { $range .= __('Larger than', 'litespeed-cache') . ' ' . $min; } elseif ($max) { $range .= __('Smaller than', 'litespeed-cache') . ' ' . $max; } echo __('Value range', 'litespeed-cache') . ': <code>' . $range . '</code>'; } /** * Check if ip is valid * * @since 3.0 */ protected function _validate_ip($id) { $val = $this->conf($id, true); if (!$val) { return; } if (!is_array($val)) { $val = array($val); } $tip = array(); foreach ($val as $v) { if (!$v) { continue; } if (!\WP_Http::is_ip_address($v)) { $tip[] = __('Invalid IP', 'litespeed-cache') . ': <code>' . esc_textarea($v) . '</code>.'; } } if ($tip) { echo '<br /><font class="litespeed-warning"> ❌ ' . implode(' ', $tip) . '</font>'; } } /** * Display API environment variable support * * @since 1.8.3 * @access protected */ protected function _api_env_var() { $args = func_get_args(); $s = '<code>' . implode('</code>, <code>', $args) . '</code>'; echo '<font class="litespeed-success"> ' . __('API', 'litespeed-cache') . ': ' . sprintf(__('Server variable(s) %s available to override this setting.', 'litespeed-cache'), $s); Doc::learn_more('https://docs.litespeedtech.com/lscache/lscwp/admin/#limiting-the-crawler'); } /** * Display URI setting example * * @since 2.6.1 * @access protected */ protected function _uri_usage_example() { echo __('The URLs will be compared to the REQUEST_URI server variable.', 'litespeed-cache'); echo ' ' . sprintf(__('For example, for %s, %s can be used here.', 'litespeed-cache'), '<code>/mypath/mypage?aa=bb</code>', '<code>mypage?aa=</code>'); echo '<br /><i>'; echo sprintf(__('To match the beginning, add %s to the beginning of the item.', 'litespeed-cache'), '<code>^</code>'); echo ' ' . sprintf(__('To do an exact match, add %s to the end of the URL.', 'litespeed-cache'), '<code>$</code>'); echo ' ' . __('One per line.', 'litespeed-cache'); echo '</i>'; } /** * Return groups string * * @since 2.0 * @access public */ public static function print_plural($num, $kind = 'group') { if ($num > 1) { switch ($kind) { case 'group': return sprintf(__('%s groups', 'litespeed-cache'), $num); case 'image': return sprintf(__('%s images', 'litespeed-cache'), $num); default: return $num; } } switch ($kind) { case 'group': return sprintf(__('%s group', 'litespeed-cache'), $num); case 'image': return sprintf(__('%s image', 'litespeed-cache'), $num); default: return $num; } } /** * Return guidance html * * @since 2.0 * @access public */ public static function guidance($title, $steps, $current_step) { if ($current_step === 'done') { $current_step = count($steps) + 1; } $percentage = ' (' . floor((($current_step - 1) * 100) / count($steps)) . '%)'; $html = '<div class="litespeed-guide">' . '<h2>' . $title . $percentage . '</h2>' . '<ol>'; foreach ($steps as $k => $v) { $step = $k + 1; if ($current_step > $step) { $html .= '<li class="litespeed-guide-done">'; } else { $html .= '<li>'; } $html .= $v . '</li>'; } $html .= '</ol></div>'; return $html; } /** * Check if has qc hide banner cookie or not * @since 7.1 */ public static function has_qc_hide_banner() { return isset($_COOKIE[self::COOKIE_QC_HIDE_BANNER]); } /** * Set qc hide banner cookie * @since 7.1 */ public static function set_qc_hide_banner() { $expire = time() + 86400 * 365; self::debug('Set qc hide banner cookie'); setcookie(self::COOKIE_QC_HIDE_BANNER, time(), $expire, COOKIEPATH, COOKIE_DOMAIN); } /** * Handle all request actions from main cls * * @since 7.1 * @access public */ public function handler() { $type = Router::verify_type(); switch ($type) { case self::TYPE_QC_HIDE_BANNER: self::set_qc_hide_banner(); break; default: break; } Admin::redirect(); } } src/base.cls.php 0000644 00000075165 15246276230 0007563 0 ustar 00 <?php /** * The base consts * * @since 3.7 */ namespace LiteSpeed; defined('WPINC') || exit(); class Base extends Root { // This is redundant since v3.0 // New conf items are `litespeed.key` const OPTION_NAME = 'litespeed-cache-conf'; const _CACHE = '_cache'; // final cache status from setting ## -------------------------------------------------- ## ## -------------- General ----------------- ## ## -------------------------------------------------- ## const _VER = '_version'; // Not set-able const HASH = 'hash'; // Not set-able const O_AUTO_UPGRADE = 'auto_upgrade'; const O_API_KEY = 'api_key'; // Deprecated since v6.4. TODO: Will drop after v6.5 const O_SERVER_IP = 'server_ip'; const O_GUEST = 'guest'; const O_GUEST_OPTM = 'guest_optm'; const O_NEWS = 'news'; const O_GUEST_UAS = 'guest_uas'; const O_GUEST_IPS = 'guest_ips'; ## -------------------------------------------------- ## ## -------------- Cache ----------------- ## ## -------------------------------------------------- ## const O_CACHE = 'cache'; const O_CACHE_PRIV = 'cache-priv'; const O_CACHE_COMMENTER = 'cache-commenter'; const O_CACHE_REST = 'cache-rest'; const O_CACHE_PAGE_LOGIN = 'cache-page_login'; const O_CACHE_FAVICON = 'cache-favicon'; // Deprecated since v6.2. TODO: Will drop after v6.5 const O_CACHE_RES = 'cache-resources'; const O_CACHE_MOBILE = 'cache-mobile'; const O_CACHE_MOBILE_RULES = 'cache-mobile_rules'; const O_CACHE_BROWSER = 'cache-browser'; const O_CACHE_EXC_USERAGENTS = 'cache-exc_useragents'; const O_CACHE_EXC_COOKIES = 'cache-exc_cookies'; const O_CACHE_EXC_QS = 'cache-exc_qs'; const O_CACHE_EXC_CAT = 'cache-exc_cat'; const O_CACHE_EXC_TAG = 'cache-exc_tag'; const O_CACHE_FORCE_URI = 'cache-force_uri'; const O_CACHE_FORCE_PUB_URI = 'cache-force_pub_uri'; const O_CACHE_PRIV_URI = 'cache-priv_uri'; const O_CACHE_EXC = 'cache-exc'; const O_CACHE_EXC_ROLES = 'cache-exc_roles'; const O_CACHE_DROP_QS = 'cache-drop_qs'; const O_CACHE_TTL_PUB = 'cache-ttl_pub'; const O_CACHE_TTL_PRIV = 'cache-ttl_priv'; const O_CACHE_TTL_FRONTPAGE = 'cache-ttl_frontpage'; const O_CACHE_TTL_FEED = 'cache-ttl_feed'; const O_CACHE_TTL_REST = 'cache-ttl_rest'; const O_CACHE_TTL_STATUS = 'cache-ttl_status'; const O_CACHE_TTL_BROWSER = 'cache-ttl_browser'; const O_CACHE_AJAX_TTL = 'cache-ajax_ttl'; const O_CACHE_LOGIN_COOKIE = 'cache-login_cookie'; const O_CACHE_VARY_COOKIES = 'cache-vary_cookies'; const O_CACHE_VARY_GROUP = 'cache-vary_group'; ## -------------------------------------------------- ## ## -------------- Purge ----------------- ## ## -------------------------------------------------- ## const O_PURGE_ON_UPGRADE = 'purge-upgrade'; const O_PURGE_STALE = 'purge-stale'; const O_PURGE_POST_ALL = 'purge-post_all'; const O_PURGE_POST_FRONTPAGE = 'purge-post_f'; const O_PURGE_POST_HOMEPAGE = 'purge-post_h'; const O_PURGE_POST_PAGES = 'purge-post_p'; const O_PURGE_POST_PAGES_WITH_RECENT_POSTS = 'purge-post_pwrp'; const O_PURGE_POST_AUTHOR = 'purge-post_a'; const O_PURGE_POST_YEAR = 'purge-post_y'; const O_PURGE_POST_MONTH = 'purge-post_m'; const O_PURGE_POST_DATE = 'purge-post_d'; const O_PURGE_POST_TERM = 'purge-post_t'; // include category|tag|tax const O_PURGE_POST_POSTTYPE = 'purge-post_pt'; const O_PURGE_TIMED_URLS = 'purge-timed_urls'; const O_PURGE_TIMED_URLS_TIME = 'purge-timed_urls_time'; const O_PURGE_HOOK_ALL = 'purge-hook_all'; ## -------------------------------------------------- ## ## -------------- ESI ----------------- ## ## -------------------------------------------------- ## const O_ESI = 'esi'; const O_ESI_CACHE_ADMBAR = 'esi-cache_admbar'; const O_ESI_CACHE_COMMFORM = 'esi-cache_commform'; const O_ESI_NONCE = 'esi-nonce'; ## -------------------------------------------------- ## ## -------------- Utilities ----------------- ## ## -------------------------------------------------- ## const O_UTIL_INSTANT_CLICK = 'util-instant_click'; const O_UTIL_NO_HTTPS_VARY = 'util-no_https_vary'; ## -------------------------------------------------- ## ## -------------- Debug ----------------- ## ## -------------------------------------------------- ## const O_DEBUG_DISABLE_ALL = 'debug-disable_all'; const O_DEBUG = 'debug'; const O_DEBUG_IPS = 'debug-ips'; const O_DEBUG_LEVEL = 'debug-level'; const O_DEBUG_FILESIZE = 'debug-filesize'; const O_DEBUG_COOKIE = 'debug-cookie'; // For backwards compatibility, will drop after v7.0 const O_DEBUG_COLLAPSE_QS = 'debug-collapse_qs'; const O_DEBUG_COLLAPS_QS = 'debug-collapse_qs'; // For backwards compatibility, will drop after v6.5 const O_DEBUG_INC = 'debug-inc'; const O_DEBUG_EXC = 'debug-exc'; const O_DEBUG_EXC_STRINGS = 'debug-exc_strings'; ## -------------------------------------------------- ## ## -------------- DB Optm ----------------- ## ## -------------------------------------------------- ## const O_DB_OPTM_REVISIONS_MAX = 'db_optm-revisions_max'; const O_DB_OPTM_REVISIONS_AGE = 'db_optm-revisions_age'; ## -------------------------------------------------- ## ## -------------- HTML Optm ----------------- ## ## -------------------------------------------------- ## const O_OPTM_CSS_MIN = 'optm-css_min'; const O_OPTM_CSS_COMB = 'optm-css_comb'; const O_OPTM_CSS_COMB_EXT_INL = 'optm-css_comb_ext_inl'; const O_OPTM_UCSS = 'optm-ucss'; const O_OPTM_UCSS_INLINE = 'optm-ucss_inline'; const O_OPTM_UCSS_SELECTOR_WHITELIST = 'optm-ucss_whitelist'; const O_OPTM_UCSS_FILE_EXC_INLINE = 'optm-ucss_file_exc_inline'; const O_OPTM_UCSS_EXC = 'optm-ucss_exc'; const O_OPTM_CSS_EXC = 'optm-css_exc'; const O_OPTM_JS_MIN = 'optm-js_min'; const O_OPTM_JS_COMB = 'optm-js_comb'; const O_OPTM_JS_COMB_EXT_INL = 'optm-js_comb_ext_inl'; const O_OPTM_JS_DELAY_INC = 'optm-js_delay_inc'; const O_OPTM_JS_EXC = 'optm-js_exc'; const O_OPTM_HTML_MIN = 'optm-html_min'; const O_OPTM_HTML_LAZY = 'optm-html_lazy'; const O_OPTM_HTML_SKIP_COMMENTS = 'optm-html_skip_comment'; const O_OPTM_QS_RM = 'optm-qs_rm'; const O_OPTM_GGFONTS_RM = 'optm-ggfonts_rm'; const O_OPTM_CSS_ASYNC = 'optm-css_async'; const O_OPTM_CCSS_PER_URL = 'optm-ccss_per_url'; const O_OPTM_CCSS_SEP_POSTTYPE = 'optm-ccss_sep_posttype'; const O_OPTM_CCSS_SEP_URI = 'optm-ccss_sep_uri'; const O_OPTM_CCSS_SELECTOR_WHITELIST = 'optm-ccss_whitelist'; const O_OPTM_CSS_ASYNC_INLINE = 'optm-css_async_inline'; const O_OPTM_CSS_FONT_DISPLAY = 'optm-css_font_display'; const O_OPTM_JS_DEFER = 'optm-js_defer'; const O_OPTM_LOCALIZE = 'optm-localize'; const O_OPTM_LOCALIZE_DOMAINS = 'optm-localize_domains'; const O_OPTM_EMOJI_RM = 'optm-emoji_rm'; const O_OPTM_NOSCRIPT_RM = 'optm-noscript_rm'; const O_OPTM_GGFONTS_ASYNC = 'optm-ggfonts_async'; const O_OPTM_EXC_ROLES = 'optm-exc_roles'; const O_OPTM_CCSS_CON = 'optm-ccss_con'; const O_OPTM_JS_DEFER_EXC = 'optm-js_defer_exc'; const O_OPTM_GM_JS_EXC = 'optm-gm_js_exc'; const O_OPTM_DNS_PREFETCH = 'optm-dns_prefetch'; const O_OPTM_DNS_PREFETCH_CTRL = 'optm-dns_prefetch_ctrl'; const O_OPTM_DNS_PRECONNECT = 'optm-dns_preconnect'; const O_OPTM_EXC = 'optm-exc'; const O_OPTM_GUEST_ONLY = 'optm-guest_only'; ## -------------------------------------------------- ## ## -------------- Object Cache ----------------- ## ## -------------------------------------------------- ## const O_OBJECT = 'object'; const O_OBJECT_KIND = 'object-kind'; const O_OBJECT_HOST = 'object-host'; const O_OBJECT_PORT = 'object-port'; const O_OBJECT_LIFE = 'object-life'; const O_OBJECT_PERSISTENT = 'object-persistent'; const O_OBJECT_ADMIN = 'object-admin'; const O_OBJECT_TRANSIENTS = 'object-transients'; const O_OBJECT_DB_ID = 'object-db_id'; const O_OBJECT_USER = 'object-user'; const O_OBJECT_PSWD = 'object-pswd'; const O_OBJECT_GLOBAL_GROUPS = 'object-global_groups'; const O_OBJECT_NON_PERSISTENT_GROUPS = 'object-non_persistent_groups'; ## -------------------------------------------------- ## ## -------------- Discussion ----------------- ## ## -------------------------------------------------- ## const O_DISCUSS_AVATAR_CACHE = 'discuss-avatar_cache'; const O_DISCUSS_AVATAR_CRON = 'discuss-avatar_cron'; const O_DISCUSS_AVATAR_CACHE_TTL = 'discuss-avatar_cache_ttl'; ## -------------------------------------------------- ## ## -------------- Media ----------------- ## ## -------------------------------------------------- ## const O_MEDIA_PRELOAD_FEATURED = 'media-preload_featured'; // Deprecated since v6.2. TODO: Will drop after v6.5 const O_MEDIA_LAZY = 'media-lazy'; const O_MEDIA_LAZY_PLACEHOLDER = 'media-lazy_placeholder'; const O_MEDIA_PLACEHOLDER_RESP = 'media-placeholder_resp'; const O_MEDIA_PLACEHOLDER_RESP_COLOR = 'media-placeholder_resp_color'; const O_MEDIA_PLACEHOLDER_RESP_SVG = 'media-placeholder_resp_svg'; const O_MEDIA_LQIP = 'media-lqip'; const O_MEDIA_LQIP_QUAL = 'media-lqip_qual'; const O_MEDIA_LQIP_MIN_W = 'media-lqip_min_w'; const O_MEDIA_LQIP_MIN_H = 'media-lqip_min_h'; const O_MEDIA_PLACEHOLDER_RESP_ASYNC = 'media-placeholder_resp_async'; const O_MEDIA_IFRAME_LAZY = 'media-iframe_lazy'; const O_MEDIA_ADD_MISSING_SIZES = 'media-add_missing_sizes'; const O_MEDIA_LAZY_EXC = 'media-lazy_exc'; const O_MEDIA_LAZY_CLS_EXC = 'media-lazy_cls_exc'; const O_MEDIA_LAZY_PARENT_CLS_EXC = 'media-lazy_parent_cls_exc'; const O_MEDIA_IFRAME_LAZY_CLS_EXC = 'media-iframe_lazy_cls_exc'; const O_MEDIA_IFRAME_LAZY_PARENT_CLS_EXC = 'media-iframe_lazy_parent_cls_exc'; const O_MEDIA_LAZY_URI_EXC = 'media-lazy_uri_exc'; const O_MEDIA_LQIP_EXC = 'media-lqip_exc'; const O_MEDIA_VPI = 'media-vpi'; const O_MEDIA_VPI_CRON = 'media-vpi_cron'; const O_IMG_OPTM_JPG_QUALITY = 'img_optm-jpg_quality'; ## -------------------------------------------------- ## ## -------------- Image Optm ----------------- ## ## -------------------------------------------------- ## const O_IMG_OPTM_AUTO = 'img_optm-auto'; const O_IMG_OPTM_CRON = 'img_optm-cron'; // @Deprecated since v7.0 TODO: remove after v7.5 const O_IMG_OPTM_ORI = 'img_optm-ori'; const O_IMG_OPTM_RM_BKUP = 'img_optm-rm_bkup'; const O_IMG_OPTM_WEBP = 'img_optm-webp'; const O_IMG_OPTM_LOSSLESS = 'img_optm-lossless'; const O_IMG_OPTM_EXIF = 'img_optm-exif'; const O_IMG_OPTM_WEBP_ATTR = 'img_optm-webp_attr'; const O_IMG_OPTM_WEBP_REPLACE_SRCSET = 'img_optm-webp_replace_srcset'; ## -------------------------------------------------- ## ## -------------- Crawler ----------------- ## ## -------------------------------------------------- ## const O_CRAWLER = 'crawler'; const O_CRAWLER_USLEEP = 'crawler-usleep'; // @Deprecated since v7.0 TODO: remove after v7.5 const O_CRAWLER_RUN_DURATION = 'crawler-run_duration'; // @Deprecated since v7.0 TODO: remove after v7.5 const O_CRAWLER_RUN_INTERVAL = 'crawler-run_interval'; // @Deprecated since v7.0 TODO: remove after v7.5 const O_CRAWLER_CRAWL_INTERVAL = 'crawler-crawl_interval'; const O_CRAWLER_THREADS = 'crawler-threads'; // @Deprecated since v7.0 TODO: remove after v7.5 const O_CRAWLER_TIMEOUT = 'crawler-timeout'; // @Deprecated since v7.0 TODO: remove after v7.5 const O_CRAWLER_LOAD_LIMIT = 'crawler-load_limit'; const O_CRAWLER_SITEMAP = 'crawler-sitemap'; const O_CRAWLER_DROP_DOMAIN = 'crawler-drop_domain'; // @Deprecated since v7.0 TODO: remove after v7.5 const O_CRAWLER_MAP_TIMEOUT = 'crawler-map_timeout'; // @Deprecated since v7.0 TODO: remove after v7.5 const O_CRAWLER_ROLES = 'crawler-roles'; const O_CRAWLER_COOKIES = 'crawler-cookies'; ## -------------------------------------------------- ## ## -------------- Misc ----------------- ## ## -------------------------------------------------- ## const O_MISC_HEARTBEAT_FRONT = 'misc-heartbeat_front'; const O_MISC_HEARTBEAT_FRONT_TTL = 'misc-heartbeat_front_ttl'; const O_MISC_HEARTBEAT_BACK = 'misc-heartbeat_back'; const O_MISC_HEARTBEAT_BACK_TTL = 'misc-heartbeat_back_ttl'; const O_MISC_HEARTBEAT_EDITOR = 'misc-heartbeat_editor'; const O_MISC_HEARTBEAT_EDITOR_TTL = 'misc-heartbeat_editor_ttl'; ## -------------------------------------------------- ## ## -------------- CDN ----------------- ## ## -------------------------------------------------- ## const O_CDN = 'cdn'; const O_CDN_ORI = 'cdn-ori'; const O_CDN_ORI_DIR = 'cdn-ori_dir'; const O_CDN_EXC = 'cdn-exc'; const O_CDN_QUIC = 'cdn-quic'; // No more a visible setting since v7 const O_CDN_CLOUDFLARE = 'cdn-cloudflare'; const O_CDN_CLOUDFLARE_EMAIL = 'cdn-cloudflare_email'; const O_CDN_CLOUDFLARE_KEY = 'cdn-cloudflare_key'; const O_CDN_CLOUDFLARE_NAME = 'cdn-cloudflare_name'; const O_CDN_CLOUDFLARE_ZONE = 'cdn-cloudflare_zone'; const O_CDN_MAPPING = 'cdn-mapping'; const O_CDN_ATTR = 'cdn-attr'; const O_QC_NAMESERVERS = 'qc-nameservers'; const O_QC_CNAME = 'qc-cname'; const NETWORK_O_USE_PRIMARY = 'use_primary_settings'; /*** Other consts ***/ const O_GUIDE = 'litespeed-guide'; // Array of each guidance tag as key, step as val //xx todo: may need to remove // Server variables const ENV_CRAWLER_USLEEP = 'CRAWLER_USLEEP'; const ENV_CRAWLER_LOAD_LIMIT = 'CRAWLER_LOAD_LIMIT'; const ENV_CRAWLER_LOAD_LIMIT_ENFORCE = 'CRAWLER_LOAD_LIMIT_ENFORCE'; const CRWL_COOKIE_NAME = 'name'; const CRWL_COOKIE_VALS = 'vals'; const CDN_MAPPING_URL = 'url'; const CDN_MAPPING_INC_IMG = 'inc_img'; const CDN_MAPPING_INC_CSS = 'inc_css'; const CDN_MAPPING_INC_JS = 'inc_js'; const CDN_MAPPING_FILETYPE = 'filetype'; const VAL_OFF = 0; const VAL_ON = 1; const VAL_ON2 = 2; /* This is for API hook usage */ const IMG_OPTM_BM_ORI = 1; // @Deprecated since v7.0 const IMG_OPTM_BM_WEBP = 2; // @Deprecated since v7.0 const IMG_OPTM_BM_LOSSLESS = 4; // @Deprecated since v7.0 const IMG_OPTM_BM_EXIF = 8; // @Deprecated since v7.0 const IMG_OPTM_BM_AVIF = 16; // @Deprecated since v7.0 /* Site related options (Will not overwrite other sites' config) */ protected static $SINGLE_SITE_OPTIONS = array( self::O_CRAWLER, self::O_CRAWLER_SITEMAP, self::O_CDN, self::O_CDN_ORI, self::O_CDN_ORI_DIR, self::O_CDN_EXC, self::O_CDN_CLOUDFLARE, self::O_CDN_CLOUDFLARE_EMAIL, self::O_CDN_CLOUDFLARE_KEY, self::O_CDN_CLOUDFLARE_NAME, self::O_CDN_CLOUDFLARE_ZONE, self::O_CDN_MAPPING, self::O_CDN_ATTR, self::O_QC_NAMESERVERS, self::O_QC_CNAME, ); protected static $_default_options = array( self::_VER => '', self::HASH => '', self::O_API_KEY => '', self::O_AUTO_UPGRADE => false, self::O_SERVER_IP => '', self::O_GUEST => false, self::O_GUEST_OPTM => false, self::O_NEWS => false, self::O_GUEST_UAS => array(), self::O_GUEST_IPS => array(), // Cache self::O_CACHE => false, self::O_CACHE_PRIV => false, self::O_CACHE_COMMENTER => false, self::O_CACHE_REST => false, self::O_CACHE_PAGE_LOGIN => false, self::O_CACHE_RES => false, self::O_CACHE_MOBILE => false, self::O_CACHE_MOBILE_RULES => array(), self::O_CACHE_BROWSER => false, self::O_CACHE_EXC_USERAGENTS => array(), self::O_CACHE_EXC_COOKIES => array(), self::O_CACHE_EXC_QS => array(), self::O_CACHE_EXC_CAT => array(), self::O_CACHE_EXC_TAG => array(), self::O_CACHE_FORCE_URI => array(), self::O_CACHE_FORCE_PUB_URI => array(), self::O_CACHE_PRIV_URI => array(), self::O_CACHE_EXC => array(), self::O_CACHE_EXC_ROLES => array(), self::O_CACHE_DROP_QS => array(), self::O_CACHE_TTL_PUB => 0, self::O_CACHE_TTL_PRIV => 0, self::O_CACHE_TTL_FRONTPAGE => 0, self::O_CACHE_TTL_FEED => 0, self::O_CACHE_TTL_REST => 0, self::O_CACHE_TTL_BROWSER => 0, self::O_CACHE_TTL_STATUS => array(), self::O_CACHE_LOGIN_COOKIE => '', self::O_CACHE_AJAX_TTL => array(), self::O_CACHE_VARY_COOKIES => array(), self::O_CACHE_VARY_GROUP => array(), // Purge self::O_PURGE_ON_UPGRADE => false, self::O_PURGE_STALE => false, self::O_PURGE_POST_ALL => false, self::O_PURGE_POST_FRONTPAGE => false, self::O_PURGE_POST_HOMEPAGE => false, self::O_PURGE_POST_PAGES => false, self::O_PURGE_POST_PAGES_WITH_RECENT_POSTS => false, self::O_PURGE_POST_AUTHOR => false, self::O_PURGE_POST_YEAR => false, self::O_PURGE_POST_MONTH => false, self::O_PURGE_POST_DATE => false, self::O_PURGE_POST_TERM => false, self::O_PURGE_POST_POSTTYPE => false, self::O_PURGE_TIMED_URLS => array(), self::O_PURGE_TIMED_URLS_TIME => '', self::O_PURGE_HOOK_ALL => array(), // ESI self::O_ESI => false, self::O_ESI_CACHE_ADMBAR => false, self::O_ESI_CACHE_COMMFORM => false, self::O_ESI_NONCE => array(), // Util self::O_UTIL_INSTANT_CLICK => false, self::O_UTIL_NO_HTTPS_VARY => false, // Debug self::O_DEBUG_DISABLE_ALL => false, self::O_DEBUG => false, self::O_DEBUG_IPS => array(), self::O_DEBUG_LEVEL => false, self::O_DEBUG_FILESIZE => 0, self::O_DEBUG_COLLAPSE_QS => false, self::O_DEBUG_INC => array(), self::O_DEBUG_EXC => array(), self::O_DEBUG_EXC_STRINGS => array(), // DB Optm self::O_DB_OPTM_REVISIONS_MAX => 0, self::O_DB_OPTM_REVISIONS_AGE => 0, // HTML Optm self::O_OPTM_CSS_MIN => false, self::O_OPTM_CSS_COMB => false, self::O_OPTM_CSS_COMB_EXT_INL => false, self::O_OPTM_UCSS => false, self::O_OPTM_UCSS_INLINE => false, self::O_OPTM_UCSS_SELECTOR_WHITELIST => array(), self::O_OPTM_UCSS_FILE_EXC_INLINE => array(), self::O_OPTM_UCSS_EXC => array(), self::O_OPTM_CSS_EXC => array(), self::O_OPTM_JS_MIN => false, self::O_OPTM_JS_COMB => false, self::O_OPTM_JS_COMB_EXT_INL => false, self::O_OPTM_JS_DELAY_INC => array(), self::O_OPTM_JS_EXC => array(), self::O_OPTM_HTML_MIN => false, self::O_OPTM_HTML_LAZY => array(), self::O_OPTM_HTML_SKIP_COMMENTS => array(), self::O_OPTM_QS_RM => false, self::O_OPTM_GGFONTS_RM => false, self::O_OPTM_CSS_ASYNC => false, self::O_OPTM_CCSS_PER_URL => false, self::O_OPTM_CCSS_SEP_POSTTYPE => array(), self::O_OPTM_CCSS_SEP_URI => array(), self::O_OPTM_CCSS_SELECTOR_WHITELIST => array(), self::O_OPTM_CSS_ASYNC_INLINE => false, self::O_OPTM_CSS_FONT_DISPLAY => false, self::O_OPTM_JS_DEFER => false, self::O_OPTM_EMOJI_RM => false, self::O_OPTM_NOSCRIPT_RM => false, self::O_OPTM_GGFONTS_ASYNC => false, self::O_OPTM_EXC_ROLES => array(), self::O_OPTM_CCSS_CON => '', self::O_OPTM_JS_DEFER_EXC => array(), self::O_OPTM_GM_JS_EXC => array(), self::O_OPTM_DNS_PREFETCH => array(), self::O_OPTM_DNS_PREFETCH_CTRL => false, self::O_OPTM_DNS_PRECONNECT => array(), self::O_OPTM_EXC => array(), self::O_OPTM_GUEST_ONLY => false, // Object self::O_OBJECT => false, self::O_OBJECT_KIND => false, self::O_OBJECT_HOST => '', self::O_OBJECT_PORT => 0, self::O_OBJECT_LIFE => 0, self::O_OBJECT_PERSISTENT => false, self::O_OBJECT_ADMIN => false, self::O_OBJECT_TRANSIENTS => false, self::O_OBJECT_DB_ID => 0, self::O_OBJECT_USER => '', self::O_OBJECT_PSWD => '', self::O_OBJECT_GLOBAL_GROUPS => array(), self::O_OBJECT_NON_PERSISTENT_GROUPS => array(), // Discuss self::O_DISCUSS_AVATAR_CACHE => false, self::O_DISCUSS_AVATAR_CRON => false, self::O_DISCUSS_AVATAR_CACHE_TTL => 0, self::O_OPTM_LOCALIZE => false, self::O_OPTM_LOCALIZE_DOMAINS => array(), // Media self::O_MEDIA_LAZY => false, self::O_MEDIA_LAZY_PLACEHOLDER => '', self::O_MEDIA_PLACEHOLDER_RESP => false, self::O_MEDIA_PLACEHOLDER_RESP_COLOR => '', self::O_MEDIA_PLACEHOLDER_RESP_SVG => '', self::O_MEDIA_LQIP => false, self::O_MEDIA_LQIP_QUAL => 0, self::O_MEDIA_LQIP_MIN_W => 0, self::O_MEDIA_LQIP_MIN_H => 0, self::O_MEDIA_PLACEHOLDER_RESP_ASYNC => false, self::O_MEDIA_IFRAME_LAZY => false, self::O_MEDIA_ADD_MISSING_SIZES => false, self::O_MEDIA_LAZY_EXC => array(), self::O_MEDIA_LAZY_CLS_EXC => array(), self::O_MEDIA_LAZY_PARENT_CLS_EXC => array(), self::O_MEDIA_IFRAME_LAZY_CLS_EXC => array(), self::O_MEDIA_IFRAME_LAZY_PARENT_CLS_EXC => array(), self::O_MEDIA_LAZY_URI_EXC => array(), self::O_MEDIA_LQIP_EXC => array(), self::O_MEDIA_VPI => false, self::O_MEDIA_VPI_CRON => false, // Image Optm self::O_IMG_OPTM_AUTO => false, self::O_IMG_OPTM_ORI => false, self::O_IMG_OPTM_RM_BKUP => false, self::O_IMG_OPTM_WEBP => false, self::O_IMG_OPTM_LOSSLESS => false, self::O_IMG_OPTM_EXIF => false, self::O_IMG_OPTM_WEBP_ATTR => array(), self::O_IMG_OPTM_WEBP_REPLACE_SRCSET => false, self::O_IMG_OPTM_JPG_QUALITY => 0, // Crawler self::O_CRAWLER => false, self::O_CRAWLER_CRAWL_INTERVAL => 0, self::O_CRAWLER_LOAD_LIMIT => 0, self::O_CRAWLER_SITEMAP => '', self::O_CRAWLER_ROLES => array(), self::O_CRAWLER_COOKIES => array(), // Misc self::O_MISC_HEARTBEAT_FRONT => false, self::O_MISC_HEARTBEAT_FRONT_TTL => 0, self::O_MISC_HEARTBEAT_BACK => false, self::O_MISC_HEARTBEAT_BACK_TTL => 0, self::O_MISC_HEARTBEAT_EDITOR => false, self::O_MISC_HEARTBEAT_EDITOR_TTL => 0, // CDN self::O_CDN => false, self::O_CDN_ORI => array(), self::O_CDN_ORI_DIR => array(), self::O_CDN_EXC => array(), self::O_CDN_QUIC => false, self::O_CDN_CLOUDFLARE => false, self::O_CDN_CLOUDFLARE_EMAIL => '', self::O_CDN_CLOUDFLARE_KEY => '', self::O_CDN_CLOUDFLARE_NAME => '', self::O_CDN_CLOUDFLARE_ZONE => '', self::O_CDN_MAPPING => array(), self::O_CDN_ATTR => array(), self::O_QC_NAMESERVERS => '', self::O_QC_CNAME => '', ); protected static $_default_site_options = array( self::_VER => '', self::O_CACHE => false, self::NETWORK_O_USE_PRIMARY => false, self::O_AUTO_UPGRADE => false, self::O_GUEST => false, self::O_CACHE_RES => false, self::O_CACHE_BROWSER => false, self::O_CACHE_MOBILE => false, self::O_CACHE_MOBILE_RULES => array(), self::O_CACHE_LOGIN_COOKIE => '', self::O_CACHE_VARY_COOKIES => array(), self::O_CACHE_EXC_COOKIES => array(), self::O_CACHE_EXC_USERAGENTS => array(), self::O_CACHE_TTL_BROWSER => 0, self::O_PURGE_ON_UPGRADE => false, self::O_OBJECT => false, self::O_OBJECT_KIND => false, self::O_OBJECT_HOST => '', self::O_OBJECT_PORT => 0, self::O_OBJECT_LIFE => 0, self::O_OBJECT_PERSISTENT => false, self::O_OBJECT_ADMIN => false, self::O_OBJECT_TRANSIENTS => false, self::O_OBJECT_DB_ID => 0, self::O_OBJECT_USER => '', self::O_OBJECT_PSWD => '', self::O_OBJECT_GLOBAL_GROUPS => array(), self::O_OBJECT_NON_PERSISTENT_GROUPS => array(), // Debug self::O_DEBUG_DISABLE_ALL => false, self::O_DEBUG => false, self::O_DEBUG_IPS => array(), self::O_DEBUG_LEVEL => false, self::O_DEBUG_FILESIZE => 0, self::O_DEBUG_COLLAPSE_QS => false, self::O_DEBUG_INC => array(), self::O_DEBUG_EXC => array(), self::O_DEBUG_EXC_STRINGS => array(), self::O_IMG_OPTM_WEBP => false, ); // NOTE: all the val of following items will be int while not bool protected static $_multi_switch_list = array( self::O_DEBUG => 2, self::O_OPTM_JS_DEFER => 2, self::O_IMG_OPTM_WEBP => 2, ); /** * Correct the option type * * TODO: add similar network func * * @since 3.0.3 */ protected function type_casting($val, $id, $is_site_conf = false) { $default_v = !$is_site_conf ? self::$_default_options[$id] : self::$_default_site_options[$id]; if (is_bool($default_v)) { if ($val === 'true') { $val = true; } if ($val === 'false') { $val = false; } $max = $this->_conf_multi_switch($id); if ($max) { $val = (int) $val; $val %= $max + 1; } else { $val = (bool) $val; } } elseif (is_array($default_v)) { // from textarea input if (!is_array($val)) { $val = Utility::sanitize_lines($val, $this->_conf_filter($id)); } } elseif (!is_string($default_v)) { $val = (int) $val; } else { // Check if the string has a limit set $val = $this->_conf_string_val($id, $val); } return $val; } /** * Load default network settings from data.ini * * @since 3.0 */ public function load_default_site_vals() { // Load network_default.json if (file_exists(LSCWP_DIR . 'data/const.network_default.json')) { $default_ini_cfg = json_decode(File::read(LSCWP_DIR . 'data/const.network_default.json'), true); foreach (self::$_default_site_options as $k => $v) { if (!array_key_exists($k, $default_ini_cfg)) { continue; } // Parse value in ini file $ini_v = $this->type_casting($default_ini_cfg[$k], $k, true); if ($ini_v == $v) { continue; } self::$_default_site_options[$k] = $ini_v; } } self::$_default_site_options[self::_VER] = Core::VER; return self::$_default_site_options; } /** * Load default values from default.json * * @since 3.0 * @access public */ public function load_default_vals() { // Load default.json if (file_exists(LSCWP_DIR . 'data/const.default.json')) { $default_ini_cfg = json_decode(File::read(LSCWP_DIR . 'data/const.default.json'), true); foreach (self::$_default_options as $k => $v) { if (!array_key_exists($k, $default_ini_cfg)) { continue; } // Parse value in ini file $ini_v = $this->type_casting($default_ini_cfg[$k], $k); // NOTE: Multiple lines value must be stored as array /** * Special handler for CDN_mapping * * format in .ini: * [cdn-mapping] * url[0] = 'https://example.com/' * inc_js[0] = true * filetype[0] = '.css * .js * .jpg' * * format out: * [0] = [ 'url' => 'https://example.com', 'inc_js' => true, 'filetype' => [ '.css', '.js', '.jpg' ] ] */ if ($k == self::O_CDN_MAPPING) { $mapping_fields = array( self::CDN_MAPPING_URL, self::CDN_MAPPING_INC_IMG, self::CDN_MAPPING_INC_CSS, self::CDN_MAPPING_INC_JS, self::CDN_MAPPING_FILETYPE, // Array ); $ini_v2 = array(); foreach ($ini_v[self::CDN_MAPPING_URL] as $k2 => $v2) { // $k2 is numeric $this_row = array(); foreach ($mapping_fields as $v3) { $this_v = !empty($ini_v[$v3][$k2]) ? $ini_v[$v3][$k2] : false; if ($v3 == self::CDN_MAPPING_URL) { $this_v = $this_v ?: ''; } if ($v3 == self::CDN_MAPPING_FILETYPE) { $this_v = $this_v ? Utility::sanitize_lines($this_v) : array(); // Note: Since v3.0 its already an array } $this_row[$v3] = $this_v; } $ini_v2[$k2] = $this_row; } $ini_v = $ini_v2; } if ($ini_v == $v) { continue; } self::$_default_options[$k] = $ini_v; } } // Load internal default vals // Setting the default bool to int is also to avoid type casting override it back to bool self::$_default_options[self::O_CACHE] = is_multisite() ? self::VAL_ON2 : self::VAL_ON; //For multi site, default is 2 (Use Network Admin Settings). For single site, default is 1 (Enabled). // Load default vals containing variables if (!self::$_default_options[self::O_CDN_ORI_DIR]) { self::$_default_options[self::O_CDN_ORI_DIR] = LSCWP_CONTENT_FOLDER . "\nwp-includes"; self::$_default_options[self::O_CDN_ORI_DIR] = explode("\n", self::$_default_options[self::O_CDN_ORI_DIR]); self::$_default_options[self::O_CDN_ORI_DIR] = array_map('trim', self::$_default_options[self::O_CDN_ORI_DIR]); } // Set security key if not initialized yet if (!self::$_default_options[self::HASH]) { self::$_default_options[self::HASH] = Str::rrand(32); } self::$_default_options[self::_VER] = Core::VER; return self::$_default_options; } /** * Format the string value * * @since 3.0 */ protected function _conf_string_val($id, $val) { return $val; } /** * If the switch setting is a triple value or not * * @since 3.0 */ protected function _conf_multi_switch($id) { if (!empty(self::$_multi_switch_list[$id])) { return self::$_multi_switch_list[$id]; } if ($id == self::O_CACHE && is_multisite()) { return self::VAL_ON2; } return false; } /** * Append a new multi switch max limit for the bool option * * @since 3.0 */ public static function set_multi_switch($id, $v) { self::$_multi_switch_list[$id] = $v; } /** * Generate const name based on $id * * @since 3.0 */ public static function conf_const($id) { return 'LITESPEED_CONF__' . strtoupper(str_replace('-', '__', $id)); } /** * Filter to be used when saving setting * * @since 3.0 */ protected function _conf_filter($id) { $filters = array( self::O_MEDIA_LAZY_EXC => 'uri', self::O_DEBUG_INC => 'relative', self::O_DEBUG_EXC => 'relative', self::O_MEDIA_LAZY_URI_EXC => 'relative', self::O_CACHE_PRIV_URI => 'relative', self::O_PURGE_TIMED_URLS => 'relative', self::O_CACHE_FORCE_URI => 'relative', self::O_CACHE_FORCE_PUB_URI => 'relative', self::O_CACHE_EXC => 'relative', // self::O_OPTM_CSS_EXC => 'uri', // Need to comment out for inline & external CSS // self::O_OPTM_JS_EXC => 'uri', self::O_OPTM_EXC => 'relative', self::O_OPTM_CCSS_SEP_URI => 'uri', // self::O_OPTM_JS_DEFER_EXC => 'uri', self::O_OPTM_DNS_PREFETCH => 'domain', self::O_CDN_ORI => 'noprotocol,trailingslash', // `Original URLs` // self::O_OPTM_LOCALIZE_DOMAINS => 'noprotocol', // `Localize Resources` // self:: => '', // self:: => '', ); if (!empty($filters[$id])) { return $filters[$id]; } return false; } /** * If the setting changes worth a purge or not * * @since 3.0 */ protected function _conf_purge($id) { $check_ids = array( self::O_MEDIA_LAZY_URI_EXC, self::O_OPTM_EXC, self::O_CACHE_PRIV_URI, self::O_PURGE_TIMED_URLS, self::O_CACHE_FORCE_URI, self::O_CACHE_FORCE_PUB_URI, self::O_CACHE_EXC, ); return in_array($id, $check_ids); } /** * If the setting changes worth a purge ALL or not * * @since 3.0 */ protected function _conf_purge_all($id) { $check_ids = array(self::O_CACHE, self::O_ESI, self::O_DEBUG_DISABLE_ALL, self::NETWORK_O_USE_PRIMARY); return in_array($id, $check_ids); } /** * If the setting is a pswd or not * * @since 3.0 */ protected function _conf_pswd($id) { $check_ids = array(self::O_CDN_CLOUDFLARE_KEY, self::O_OBJECT_PSWD); return in_array($id, $check_ids); } /** * If the setting is cron related or not * * @since 3.0 */ protected function _conf_cron($id) { $check_ids = array(self::O_OPTM_CSS_ASYNC, self::O_MEDIA_PLACEHOLDER_RESP_ASYNC, self::O_DISCUSS_AVATAR_CRON, self::O_IMG_OPTM_AUTO, self::O_CRAWLER); return in_array($id, $check_ids); } /** * If the setting changes worth a purge, return the tag * * @since 3.0 */ protected function _conf_purge_tag($id) { $check_ids = array( self::O_CACHE_PAGE_LOGIN => Tag::TYPE_LOGIN, ); if (!empty($check_ids[$id])) { return $check_ids[$id]; } return false; } /** * Generate server vars * * @since 2.4.1 */ public function server_vars() { $consts = array( 'WP_SITEURL', 'WP_HOME', 'WP_CONTENT_DIR', 'SHORTINIT', 'LSCWP_CONTENT_DIR', 'LSCWP_CONTENT_FOLDER', 'LSCWP_DIR', 'LITESPEED_TIME_OFFSET', 'LITESPEED_SERVER_TYPE', 'LITESPEED_CLI', 'LITESPEED_ALLOWED', 'LITESPEED_ON', 'LSWCP_TAG_PREFIX', 'COOKIEHASH', ); $server_vars = array(); foreach ($consts as $v) { $server_vars[$v] = defined($v) ? constant($v) : null; } return $server_vars; } } src/db-optm.cls.php 0000644 00000023513 15246276230 0010201 0 ustar 00 <?php /** * The admin optimize tool * * * @since 1.2.1 * @package LiteSpeed * @subpackage LiteSpeed/src * @author LiteSpeed Technologies <info@litespeedtech.com> */ namespace LiteSpeed; defined('WPINC') || exit(); class DB_Optm extends Root { private static $_hide_more = false; private static $TYPES = array( 'revision', 'orphaned_post_meta', 'auto_draft', 'trash_post', 'spam_comment', 'trash_comment', 'trackback-pingback', 'expired_transient', 'all_transients', 'optimize_tables', ); const TYPE_CONV_TB = 'conv_innodb'; /** * Show if there are more sites in hidden * * @since 3.0 */ public static function hide_more() { return self::$_hide_more; } /** * Clean/Optimize WP tables * * @since 1.2.1 * @access public * @param string $type The type to clean * @param bool $ignore_multisite If ignore multisite check * @return int The rows that will be affected */ public function db_count($type, $ignore_multisite = false) { if ($type === 'all') { $num = 0; foreach (self::$TYPES as $v) { $num += $this->db_count($v); } return $num; } if (!$ignore_multisite) { if (is_multisite() && is_network_admin()) { $num = 0; $blogs = Activation::get_network_ids(); foreach ($blogs as $k => $blog_id) { if ($k > 3) { self::$_hide_more = true; break; } switch_to_blog($blog_id); $num += $this->db_count($type, true); restore_current_blog(); } return $num; } } global $wpdb; switch ($type) { case 'revision': $rev_max = (int) $this->conf(Base::O_DB_OPTM_REVISIONS_MAX); $rev_age = (int) $this->conf(Base::O_DB_OPTM_REVISIONS_AGE); $sql_add = ''; if ($rev_age) { $sql_add = " and post_modified < DATE_SUB( NOW(), INTERVAL $rev_age DAY ) "; } $sql = "SELECT COUNT(*) FROM `$wpdb->posts` WHERE post_type = 'revision' $sql_add"; if (!$rev_max) { return $wpdb->get_var($sql); } // Has count limit $sql = "SELECT COUNT(*)-$rev_max FROM `$wpdb->posts` WHERE post_type = 'revision' $sql_add GROUP BY post_parent HAVING count(*)>$rev_max"; $res = $wpdb->get_results($sql, ARRAY_N); Utility::compatibility(); return array_sum(array_column($res, 0)); case 'orphaned_post_meta': return $wpdb->get_var("SELECT COUNT(*) FROM `$wpdb->postmeta` a LEFT JOIN `$wpdb->posts` b ON b.ID=a.post_id WHERE b.ID IS NULL"); case 'auto_draft': return $wpdb->get_var("SELECT COUNT(*) FROM `$wpdb->posts` WHERE post_status = 'auto-draft'"); case 'trash_post': return $wpdb->get_var("SELECT COUNT(*) FROM `$wpdb->posts` WHERE post_status = 'trash'"); case 'spam_comment': return $wpdb->get_var("SELECT COUNT(*) FROM `$wpdb->comments` WHERE comment_approved = 'spam'"); case 'trash_comment': return $wpdb->get_var("SELECT COUNT(*) FROM `$wpdb->comments` WHERE comment_approved = 'trash'"); case 'trackback-pingback': return $wpdb->get_var("SELECT COUNT(*) FROM `$wpdb->comments` WHERE comment_type = 'trackback' OR comment_type = 'pingback'"); case 'expired_transient': return $wpdb->get_var("SELECT COUNT(*) FROM `$wpdb->options` WHERE option_name LIKE '_transient_timeout%' AND option_value < " . time()); case 'all_transients': return $wpdb->get_var("SELECT COUNT(*) FROM `$wpdb->options` WHERE option_name LIKE '%_transient_%'"); case 'optimize_tables': return $wpdb->get_var("SELECT COUNT(*) FROM information_schema.tables WHERE TABLE_SCHEMA = '" . DB_NAME . "' and ENGINE <> 'InnoDB' and DATA_FREE > 0"); } return '-'; } /** * Clean/Optimize WP tables * * @since 1.2.1 * @since 3.0 changed to private * @access private */ private function _db_clean($type) { if ($type === 'all') { foreach (self::$TYPES as $v) { $this->_db_clean($v); } return __('Clean all successfully.', 'litespeed-cache'); } global $wpdb; switch ($type) { case 'revision': $rev_max = (int) $this->conf(Base::O_DB_OPTM_REVISIONS_MAX); $rev_age = (int) $this->conf(Base::O_DB_OPTM_REVISIONS_AGE); $postmeta = "`$wpdb->postmeta`"; $posts = "`$wpdb->posts`"; $sql_postmeta_join = function ($table) use ($postmeta, $posts) { return " $postmeta CROSS JOIN $table ON $posts.ID = $postmeta.post_id "; }; $sql_where = "WHERE $posts.post_type = 'revision'"; $sql_add = $rev_age ? "AND $posts.post_modified < DATE_SUB( NOW(), INTERVAL $rev_age DAY )" : ''; if (!$rev_max) { $sql_where = "$sql_where $sql_add"; $sql_postmeta = $sql_postmeta_join($posts); $wpdb->query("DELETE $postmeta FROM $sql_postmeta $sql_where"); $wpdb->query("DELETE FROM $posts $sql_where"); } else { // Has count limit $sql = " SELECT COUNT(*) - $rev_max AS del_max, post_parent FROM $posts WHERE post_type = 'revision' $sql_add GROUP BY post_parent HAVING COUNT(*) > $rev_max "; $res = $wpdb->get_results($sql); $sql_where = " $sql_where AND post_parent = %d ORDER BY ID LIMIT %d "; $sql_postmeta = $sql_postmeta_join("(SELECT ID FROM $posts $sql_where) AS $posts"); foreach ($res as $v) { $args = array($v->post_parent, $v->del_max); $sql = $wpdb->prepare("DELETE $postmeta FROM $sql_postmeta", $args); $wpdb->query($sql); $sql = $wpdb->prepare("DELETE FROM $posts $sql_where", $args); $wpdb->query($sql); } } return __('Clean post revisions successfully.', 'litespeed-cache'); case 'orphaned_post_meta': $wpdb->query("DELETE a FROM `$wpdb->postmeta` a LEFT JOIN `$wpdb->posts` b ON b.ID=a.post_id WHERE b.ID IS NULL"); return __('Clean orphaned post meta successfully.', 'litespeed-cache'); case 'auto_draft': $wpdb->query("DELETE FROM `$wpdb->posts` WHERE post_status = 'auto-draft'"); return __('Clean auto drafts successfully.', 'litespeed-cache'); case 'trash_post': $wpdb->query("DELETE FROM `$wpdb->posts` WHERE post_status = 'trash'"); return __('Clean trashed posts and pages successfully.', 'litespeed-cache'); case 'spam_comment': $wpdb->query("DELETE FROM `$wpdb->comments` WHERE comment_approved = 'spam'"); return __('Clean spam comments successfully.', 'litespeed-cache'); case 'trash_comment': $wpdb->query("DELETE FROM `$wpdb->comments` WHERE comment_approved = 'trash'"); return __('Clean trashed comments successfully.', 'litespeed-cache'); case 'trackback-pingback': $wpdb->query("DELETE FROM `$wpdb->comments` WHERE comment_type = 'trackback' OR comment_type = 'pingback'"); return __('Clean trackbacks and pingbacks successfully.', 'litespeed-cache'); case 'expired_transient': $wpdb->query("DELETE FROM `$wpdb->options` WHERE option_name LIKE '_transient_timeout%' AND option_value < " . time()); return __('Clean expired transients successfully.', 'litespeed-cache'); case 'all_transients': $wpdb->query("DELETE FROM `$wpdb->options` WHERE option_name LIKE '%\\_transient\\_%'"); return __('Clean all transients successfully.', 'litespeed-cache'); case 'optimize_tables': $sql = "SELECT table_name, DATA_FREE FROM information_schema.tables WHERE TABLE_SCHEMA = '" . DB_NAME . "' and ENGINE <> 'InnoDB' and DATA_FREE > 0"; $result = $wpdb->get_results($sql); if ($result) { foreach ($result as $row) { $wpdb->query('OPTIMIZE TABLE ' . $row->table_name); } } return __('Optimized all tables.', 'litespeed-cache'); } } /** * Get all myisam tables * * @since 3.0 * @access public */ public function list_myisam() { global $wpdb; $q = "SELECT * FROM information_schema.tables WHERE TABLE_SCHEMA = '" . DB_NAME . "' and ENGINE = 'myisam' AND TABLE_NAME LIKE '{$wpdb->prefix}%'"; return $wpdb->get_results($q); } /** * Convert tables to InnoDB * * @since 3.0 * @access private */ private function _conv_innodb() { global $wpdb; if (empty($_GET['tb'])) { Admin_Display::error('No table to convert'); return; } $tb = false; $list = $this->list_myisam(); foreach ($list as $v) { if ($v->TABLE_NAME == $_GET['tb']) { $tb = $v->TABLE_NAME; break; } } if (!$tb) { Admin_Display::error('No existing table'); return; } $q = 'ALTER TABLE ' . DB_NAME . '.' . $tb . ' ENGINE = InnoDB'; $wpdb->query($q); Debug2::debug("[DB] Converted $tb to InnoDB"); $msg = __('Converted to InnoDB successfully.', 'litespeed-cache'); Admin_Display::success($msg); } /** * Count all autoload size * * @since 3.0 * @access public */ public function autoload_summary() { global $wpdb; $autoloads = function_exists('wp_autoload_values_to_autoload') ? wp_autoload_values_to_autoload() : array('yes', 'on', 'auto-on', 'auto'); $autoloads = '("' . implode('","', $autoloads) . '")'; $summary = $wpdb->get_row("SELECT SUM(LENGTH(option_value)) AS autoload_size,COUNT(*) AS autload_entries FROM `$wpdb->options` WHERE autoload IN " . $autoloads); $summary->autoload_toplist = $wpdb->get_results( "SELECT option_name, LENGTH(option_value) AS option_value_length, autoload FROM `$wpdb->options` WHERE autoload IN " . $autoloads . ' ORDER BY option_value_length DESC LIMIT 20' ); return $summary; } /** * Handle all request actions from main cls * * @since 3.0 * @access public */ public function handler() { $type = Router::verify_type(); switch ($type) { case 'all': case in_array($type, self::$TYPES): if (is_multisite() && is_network_admin()) { $blogs = Activation::get_network_ids(); foreach ($blogs as $blog_id) { switch_to_blog($blog_id); $msg = $this->_db_clean($type); restore_current_blog(); } } else { $msg = $this->_db_clean($type); } Admin_Display::success($msg); break; case self::TYPE_CONV_TB: $this->_conv_innodb(); break; default: break; } Admin::redirect(); } } src/file.cls.php 0000644 00000024734 15246276230 0007564 0 ustar 00 <?php /** * LiteSpeed File Operator Library Class * Append/Replace content to a file * * @since 1.1.0 */ namespace LiteSpeed; defined('WPINC') || exit(); class File { const MARKER = 'LiteSpeed Operator'; /** * Detect if an URL is 404 * * @since 3.3 */ public static function is_404($url) { $response = wp_safe_remote_get($url); $code = wp_remote_retrieve_response_code($response); if ($code == 404) { return true; } return false; } /** * Delete folder * * @since 2.1 */ public static function rrmdir($dir) { $files = array_diff(scandir($dir), array('.', '..')); foreach ($files as $file) { is_dir("$dir/$file") ? self::rrmdir("$dir/$file") : unlink("$dir/$file"); } return rmdir($dir); } public static function count_lines($filename) { if (!file_exists($filename)) { return 0; } $file = new \SplFileObject($filename); $file->seek(PHP_INT_MAX); return $file->key() + 1; } /** * Read data from file * * @since 1.1.0 * @param string $filename * @param int $start_line * @param int $lines */ public static function read($filename, $start_line = null, $lines = null) { if (!file_exists($filename)) { return ''; } if (!is_readable($filename)) { return false; } if ($start_line !== null) { $res = array(); $file = new \SplFileObject($filename); $file->seek($start_line); if ($lines === null) { while (!$file->eof()) { $res[] = rtrim($file->current(), "\n"); $file->next(); } } else { for ($i = 0; $i < $lines; $i++) { if ($file->eof()) { break; } $res[] = rtrim($file->current(), "\n"); $file->next(); } } unset($file); return $res; } $content = file_get_contents($filename); $content = self::remove_zero_space($content); return $content; } /** * Append data to file * * @since 1.1.5 * @access public * @param string $filename * @param string $data * @param boolean $mkdir * @param boolean $silence Used to avoid WP's functions are used */ public static function append($filename, $data, $mkdir = false, $silence = true) { return self::save($filename, $data, $mkdir, true, $silence); } /** * Save data to file * * @since 1.1.0 * @param string $filename * @param string $data * @param boolean $mkdir * @param boolean $append If the content needs to be appended * @param boolean $silence Used to avoid WP's functions are used */ public static function save($filename, $data, $mkdir = false, $append = false, $silence = true) { if (is_null($filename)) { return $silence ? false : __('Filename is empty!', 'litespeed-cache'); } $error = false; $folder = dirname($filename); // mkdir if folder does not exist if (!file_exists($folder)) { if (!$mkdir) { return $silence ? false : sprintf(__('Folder does not exist: %s', 'litespeed-cache'), $folder); } set_error_handler('litespeed_exception_handler'); try { mkdir($folder, 0755, true); // Create robots.txt file to forbid search engine indexes if (!file_exists(LITESPEED_STATIC_DIR . '/robots.txt')) { file_put_contents(LITESPEED_STATIC_DIR . '/robots.txt', "User-agent: *\nDisallow: /\n"); } } catch (\ErrorException $ex) { return $silence ? false : sprintf(__('Can not create folder: %1$s. Error: %2$s', 'litespeed-cache'), $folder, $ex->getMessage()); } restore_error_handler(); } if (!file_exists($filename)) { if (!is_writable($folder)) { return $silence ? false : sprintf(__('Folder is not writable: %s.', 'litespeed-cache'), $folder); } set_error_handler('litespeed_exception_handler'); try { touch($filename); } catch (\ErrorException $ex) { return $silence ? false : sprintf(__('File %s is not writable.', 'litespeed-cache'), $filename); } restore_error_handler(); } elseif (!is_writable($filename)) { return $silence ? false : sprintf(__('File %s is not writable.', 'litespeed-cache'), $filename); } $data = self::remove_zero_space($data); $ret = file_put_contents($filename, $data, $append ? FILE_APPEND : LOCK_EX); if ($ret === false) { return $silence ? false : sprintf(__('Failed to write to %s.', 'litespeed-cache'), $filename); } return true; } /** * Remove Unicode zero-width space <200b><200c> * * @since 2.1.2 * @since 2.9 changed to public */ public static function remove_zero_space($content) { if (is_array($content)) { $content = array_map(__CLASS__ . '::remove_zero_space', $content); return $content; } // Remove UTF-8 BOM if present if (substr($content, 0, 3) === "\xEF\xBB\xBF") { $content = substr($content, 3); } $content = str_replace("\xe2\x80\x8b", '', $content); $content = str_replace("\xe2\x80\x8c", '', $content); $content = str_replace("\xe2\x80\x8d", '', $content); return $content; } /** * Appends an array of strings into a file (.htaccess ), placing it between * BEGIN and END markers. * * Replaces existing marked info. Retains surrounding * data. Creates file if none exists. * * @param string $filename Filename to alter. * @param string $marker The marker to alter. * @param array|string $insertion The new content to insert. * @param bool $prepend Prepend insertion if not exist. * @return bool True on write success, false on failure. */ public static function insert_with_markers($filename, $insertion = false, $marker = false, $prepend = false) { if (!$marker) { $marker = self::MARKER; } if (!$insertion) { $insertion = array(); } return self::_insert_with_markers($filename, $marker, $insertion, $prepend); //todo: capture exceptions } /** * Return wrapped block data with marker * * @param string $insertion * @param string $marker * @return string The block data */ public static function wrap_marker_data($insertion, $marker = false) { if (!$marker) { $marker = self::MARKER; } $start_marker = "# BEGIN {$marker}"; $end_marker = "# END {$marker}"; $new_data = implode("\n", array_merge(array($start_marker), $insertion, array($end_marker))); return $new_data; } /** * Touch block data from file, return with marker * * @param string $filename * @param string $marker * @return string The current block data */ public static function touch_marker_data($filename, $marker = false) { if (!$marker) { $marker = self::MARKER; } $result = self::_extract_from_markers($filename, $marker); if (!$result) { return false; } $start_marker = "# BEGIN {$marker}"; $end_marker = "# END {$marker}"; $new_data = implode("\n", array_merge(array($start_marker), $result, array($end_marker))); return $new_data; } /** * Extracts strings from between the BEGIN and END markers in the .htaccess file. * * @param string $filename * @param string $marker * @return array An array of strings from a file (.htaccess ) from between BEGIN and END markers. */ public static function extract_from_markers($filename, $marker = false) { if (!$marker) { $marker = self::MARKER; } return self::_extract_from_markers($filename, $marker); } /** * Extracts strings from between the BEGIN and END markers in the .htaccess file. * * @param string $filename * @param string $marker * @return array An array of strings from a file (.htaccess ) from between BEGIN and END markers. */ private static function _extract_from_markers($filename, $marker) { $result = array(); if (!file_exists($filename)) { return $result; } if ($markerdata = explode("\n", implode('', file($filename)))) { $state = false; foreach ($markerdata as $markerline) { if (strpos($markerline, '# END ' . $marker) !== false) { $state = false; } if ($state) { $result[] = $markerline; } if (strpos($markerline, '# BEGIN ' . $marker) !== false) { $state = true; } } } return array_map('trim', $result); } /** * Inserts an array of strings into a file (.htaccess ), placing it between BEGIN and END markers. * * Replaces existing marked info. Retains surrounding data. Creates file if none exists. * * NOTE: will throw error if failed * * @since 3.0- * @since 3.0 Throw errors if failed * @access private */ private static function _insert_with_markers($filename, $marker, $insertion, $prepend = false) { if (!file_exists($filename)) { if (!is_writable(dirname($filename))) { Error::t('W', dirname($filename)); } set_error_handler('litespeed_exception_handler'); try { touch($filename); } catch (\ErrorException $ex) { Error::t('W', $filename); } restore_error_handler(); } elseif (!is_writable($filename)) { Error::t('W', $filename); } if (!is_array($insertion)) { $insertion = explode("\n", $insertion); } $start_marker = "# BEGIN {$marker}"; $end_marker = "# END {$marker}"; $fp = fopen($filename, 'r+'); if (!$fp) { Error::t('W', $filename); } // Attempt to get a lock. If the filesystem supports locking, this will block until the lock is acquired. flock($fp, LOCK_EX); $lines = array(); while (!feof($fp)) { $lines[] = rtrim(fgets($fp), "\r\n"); } // Split out the existing file into the preceding lines, and those that appear after the marker $pre_lines = $post_lines = $existing_lines = array(); $found_marker = $found_end_marker = false; foreach ($lines as $line) { if (!$found_marker && false !== strpos($line, $start_marker)) { $found_marker = true; continue; } elseif (!$found_end_marker && false !== strpos($line, $end_marker)) { $found_end_marker = true; continue; } if (!$found_marker) { $pre_lines[] = $line; } elseif ($found_marker && $found_end_marker) { $post_lines[] = $line; } else { $existing_lines[] = $line; } } // Check to see if there was a change if ($existing_lines === $insertion) { flock($fp, LOCK_UN); fclose($fp); return true; } // Check if need to prepend data if not exist if ($prepend && !$post_lines) { // Generate the new file data $new_file_data = implode("\n", array_merge(array($start_marker), $insertion, array($end_marker), $pre_lines)); } else { // Generate the new file data $new_file_data = implode("\n", array_merge($pre_lines, array($start_marker), $insertion, array($end_marker), $post_lines)); } // Write to the start of the file, and truncate it to that length fseek($fp, 0); $bytes = fwrite($fp, $new_file_data); if ($bytes) { ftruncate($fp, ftell($fp)); } fflush($fp); flock($fp, LOCK_UN); fclose($fp); return (bool) $bytes; } } src/gui.cls.php 0000644 00000066745 15246276230 0007441 0 ustar 00 <?php /** * The frontend GUI class. * * @since 1.3 * @subpackage LiteSpeed/src * @author LiteSpeed Technologies <info@litespeedtech.com> */ namespace LiteSpeed; defined('WPINC') || exit(); class GUI extends Base { private static $_clean_counter = 0; private $_promo_true; // [ file_tag => [ days, litespeed_only ], ... ] private $_promo_list = array( 'new_version' => array(7, false), 'score' => array(14, false), // 'slack' => array( 3, false ), ); const LIB_GUEST_JS = 'assets/js/guest.min.js'; const LIB_GUEST_DOCREF_JS = 'assets/js/guest.docref.min.js'; const PHP_GUEST = 'guest.vary.php'; const TYPE_DISMISS_WHM = 'whm'; const TYPE_DISMISS_EXPIRESDEFAULT = 'ExpiresDefault'; const TYPE_DISMISS_PROMO = 'promo'; const TYPE_DISMISS_PIN = 'pin'; const WHM_MSG = 'lscwp_whm_install'; const WHM_MSG_VAL = 'whm_install'; protected $_summary; /** * Instance * * @since 1.3 */ public function __construct() { $this->_summary = self::get_summary(); } /** * Frontend Init * * @since 3.0 */ public function init() { Debug2::debug2('[GUI] init'); if (is_admin_bar_showing() && current_user_can('manage_options')) { add_action('wp_enqueue_scripts', array($this, 'frontend_enqueue_style')); add_action('admin_bar_menu', array($this, 'frontend_shortcut'), 95); } /** * Turn on instant click * @since 1.8.2 */ if ($this->conf(self::O_UTIL_INSTANT_CLICK)) { add_action('wp_enqueue_scripts', array($this, 'frontend_enqueue_style_public')); } // NOTE: this needs to be before optimizer to avoid wrapper being removed add_filter('litespeed_buffer_finalize', array($this, 'finalize'), 8); } /** * Print a loading message when redirecting CCSS/UCSS page to avoid whiteboard confusion */ public static function print_loading($counter, $type) { echo '<div style="font-size: 25px; text-align: center; padding-top: 150px; width: 100%; position: absolute;">'; echo "<img width='35' src='" . LSWCP_PLUGIN_URL . "assets/img/Litespeed.icon.svg' /> "; echo sprintf(__('%1$s %2$s files left in queue', 'litespeed-cache'), $counter, $type); echo '<p><a href="' . admin_url('admin.php?page=litespeed-page_optm') . '">' . __('Cancel', 'litespeed-cache') . '</a></p>'; echo '</div>'; } /** * Display a pie * * @since 1.6.6 */ public static function pie($percent, $width = 50, $finished_tick = false, $without_percentage = false, $append_cls = false) { $percentage = '<text x="50%" y="50%">' . $percent . ($without_percentage ? '' : '%') . '</text>'; if ($percent == 100 && $finished_tick) { $percentage = '<text x="50%" y="50%" class="litespeed-pie-done">✓</text>'; } return " <svg class='litespeed-pie $append_cls' viewbox='0 0 33.83098862 33.83098862' width='$width' height='$width' xmlns='http://www.w3.org/2000/svg'> <circle class='litespeed-pie_bg' cx='16.91549431' cy='16.91549431' r='15.91549431' /> <circle class='litespeed-pie_circle' cx='16.91549431' cy='16.91549431' r='15.91549431' stroke-dasharray='$percent,100' /> <g class='litespeed-pie_info'>$percentage</g> </svg> "; } /** * Display a tiny pie with a tooltip * * @since 3.0 */ public static function pie_tiny($percent, $width = 50, $tooltip = '', $tooltip_pos = 'up', $append_cls = false) { // formula C = 2πR $dasharray = 2 * 3.1416 * 9 * ($percent / 100); return " <button type='button' data-balloon-break data-balloon-pos='$tooltip_pos' aria-label='$tooltip' class='litespeed-btn-pie'> <svg class='litespeed-pie litespeed-pie-tiny $append_cls' viewbox='0 0 30 30' width='$width' height='$width' xmlns='http://www.w3.org/2000/svg'> <circle class='litespeed-pie_bg' cx='15' cy='15' r='9' /> <circle class='litespeed-pie_circle' cx='15' cy='15' r='9' stroke-dasharray='$dasharray,100' /> <g class='litespeed-pie_info'><text x='50%' y='50%'>i</text></g> </svg> </button> "; } /** * Get classname of PageSpeed Score * * Scale: * 90-100 (fast) * 50-89 (average) * 0-49 (slow) * * @since 2.9 * @access public */ public function get_cls_of_pagescore($score) { if ($score >= 90) { return 'success'; } if ($score >= 50) { return 'warning'; } return 'danger'; } /** * Dismiss banner * * @since 1.0 * @access public */ public static function dismiss() { $_instance = self::cls(); switch (Router::verify_type()) { case self::TYPE_DISMISS_WHM: self::dismiss_whm(); break; case self::TYPE_DISMISS_EXPIRESDEFAULT: self::update_option(Admin_Display::DB_DISMISS_MSG, Admin_Display::RULECONFLICT_DISMISSED); break; case self::TYPE_DISMISS_PIN: admin_display::dismiss_pin(); break; case self::TYPE_DISMISS_PROMO: if (empty($_GET['promo_tag'])) { break; } $promo_tag = sanitize_key($_GET['promo_tag']); if (empty($_instance->_promo_list[$promo_tag])) { break; } defined('LSCWP_LOG') && Debug2::debug('[GUI] Dismiss promo ' . $promo_tag); // Forever dismiss if (!empty($_GET['done'])) { $_instance->_summary[$promo_tag] = 'done'; } elseif (!empty($_GET['later'])) { // Delay the banner to half year later $_instance->_summary[$promo_tag] = time() + 86400 * 180; } else { // Update welcome banner to 30 days after $_instance->_summary[$promo_tag] = time() + 86400 * 30; } self::save_summary(); break; default: break; } if (Router::is_ajax()) { // All dismiss actions are considered as ajax call, so just exit exit(\json_encode(array('success' => 1))); } // Plain click link, redirect to referral url Admin::redirect(); } /** * Check if has rule conflict notice * * @since 1.1.5 * @access public * @return boolean */ public static function has_msg_ruleconflict() { $db_dismiss_msg = self::get_option(Admin_Display::DB_DISMISS_MSG); if (!$db_dismiss_msg) { self::update_option(Admin_Display::DB_DISMISS_MSG, -1); } return $db_dismiss_msg == Admin_Display::RULECONFLICT_ON; } /** * Check if has whm notice * * @since 1.1.1 * @access public * @return boolean */ public static function has_whm_msg() { $val = self::get_option(self::WHM_MSG); if (!$val) { self::dismiss_whm(); return false; } return $val == self::WHM_MSG_VAL; } /** * Delete whm msg tag * * @since 1.1.1 * @access public */ public static function dismiss_whm() { self::update_option(self::WHM_MSG, -1); } /** * Set current page a litespeed page * * @since 2.9 */ private function _is_litespeed_page() { if ( !empty($_GET['page']) && in_array($_GET['page'], array( 'litespeed-settings', 'litespeed-dash', Admin::PAGE_EDIT_HTACCESS, 'litespeed-optimization', 'litespeed-crawler', 'litespeed-import', 'litespeed-report', )) ) { return true; } return false; } /** * Display promo banner * * @since 2.1 * @access public */ public function show_promo($check_only = false) { $is_litespeed_page = $this->_is_litespeed_page(); // Bypass showing info banner if disabled all in debug if (defined('LITESPEED_DISABLE_ALL') && LITESPEED_DISABLE_ALL) { if ($is_litespeed_page && !$check_only) { include_once LSCWP_DIR . 'tpl/inc/disabled_all.php'; } return false; } if (file_exists(ABSPATH . '.litespeed_no_banner')) { defined('LSCWP_LOG') && Debug2::debug('[GUI] Bypass banners due to silence file'); return false; } foreach ($this->_promo_list as $promo_tag => $v) { list($delay_days, $litespeed_page_only) = $v; if ($litespeed_page_only && !$is_litespeed_page) { continue; } // first time check if (empty($this->_summary[$promo_tag])) { $this->_summary[$promo_tag] = time() + 86400 * $delay_days; self::save_summary(); continue; } $promo_timestamp = $this->_summary[$promo_tag]; // was ticked as done if ($promo_timestamp == 'done') { continue; } // Not reach the dateline yet if (time() < $promo_timestamp) { continue; } // try to load, if can pass, will set $this->_promo_true = true $this->_promo_true = false; include LSCWP_DIR . "tpl/banner/$promo_tag.php"; // If not defined, means it didn't pass the display workflow in tpl. if (!$this->_promo_true) { continue; } if ($check_only) { return $promo_tag; } defined('LSCWP_LOG') && Debug2::debug('[GUI] Show promo ' . $promo_tag); // Only contain one break; } return false; } /** * Load frontend public script * * @since 1.8.2 * @access public */ public function frontend_enqueue_style_public() { wp_enqueue_script(Core::PLUGIN_NAME, LSWCP_PLUGIN_URL . 'assets/js/instant_click.min.js', array(), Core::VER, true); } /** * Load frontend menu shortcut * * @since 1.3 * @access public */ public function frontend_enqueue_style() { wp_enqueue_style(Core::PLUGIN_NAME, LSWCP_PLUGIN_URL . 'assets/css/litespeed.css', array(), Core::VER, 'all'); } /** * Load frontend menu shortcut * * @since 1.3 * @access public */ public function frontend_shortcut() { global $wp_admin_bar; $wp_admin_bar->add_menu(array( 'id' => 'litespeed-menu', 'title' => '<span class="ab-icon"></span>', 'href' => get_admin_url(null, 'admin.php?page=litespeed'), 'meta' => array('tabindex' => 0, 'class' => 'litespeed-top-toolbar'), )); $wp_admin_bar->add_menu(array( 'parent' => 'litespeed-menu', 'id' => 'litespeed-purge-single', 'title' => __('Purge this page', 'litespeed-cache') . ' - LSCache', 'href' => Utility::build_url(Router::ACTION_PURGE, Purge::TYPE_PURGE_FRONT, false, true), 'meta' => array('tabindex' => '0'), )); if ($this->has_cache_folder('ucss')) { $possible_url_tag = UCSS::get_url_tag(); $append_arr = array(); if ($possible_url_tag) { $append_arr['url_tag'] = $possible_url_tag; } $wp_admin_bar->add_menu(array( 'parent' => 'litespeed-menu', 'id' => 'litespeed-purge-single-ucss', 'title' => __('Purge this page', 'litespeed-cache') . ' - UCSS', 'href' => Utility::build_url(Router::ACTION_PURGE, Purge::TYPE_PURGE_UCSS, false, true, $append_arr), 'meta' => array('tabindex' => '0'), )); } $wp_admin_bar->add_menu(array( 'parent' => 'litespeed-menu', 'id' => 'litespeed-single-action', 'title' => __('Mark this page as ', 'litespeed-cache'), 'meta' => array('tabindex' => '0'), )); if (!empty($_SERVER['REQUEST_URI'])) { $append_arr = array( Conf::TYPE_SET . '[' . self::O_CACHE_FORCE_URI . '][]' => $_SERVER['REQUEST_URI'] . '$', 'redirect' => $_SERVER['REQUEST_URI'], ); $wp_admin_bar->add_menu(array( 'parent' => 'litespeed-single-action', 'id' => 'litespeed-single-forced_cache', 'title' => __('Forced cacheable', 'litespeed-cache'), 'href' => Utility::build_url(Router::ACTION_CONF, Conf::TYPE_SET, false, true, $append_arr), )); $append_arr = array( Conf::TYPE_SET . '[' . self::O_CACHE_EXC . '][]' => $_SERVER['REQUEST_URI'] . '$', 'redirect' => $_SERVER['REQUEST_URI'], ); $wp_admin_bar->add_menu(array( 'parent' => 'litespeed-single-action', 'id' => 'litespeed-single-noncache', 'title' => __('Non cacheable', 'litespeed-cache'), 'href' => Utility::build_url(Router::ACTION_CONF, Conf::TYPE_SET, false, true, $append_arr), )); $append_arr = array( Conf::TYPE_SET . '[' . self::O_CACHE_PRIV_URI . '][]' => $_SERVER['REQUEST_URI'] . '$', 'redirect' => $_SERVER['REQUEST_URI'], ); $wp_admin_bar->add_menu(array( 'parent' => 'litespeed-single-action', 'id' => 'litespeed-single-private', 'title' => __('Private cache', 'litespeed-cache'), 'href' => Utility::build_url(Router::ACTION_CONF, Conf::TYPE_SET, false, true, $append_arr), )); $append_arr = array( Conf::TYPE_SET . '[' . self::O_OPTM_EXC . '][]' => $_SERVER['REQUEST_URI'] . '$', 'redirect' => $_SERVER['REQUEST_URI'], ); $wp_admin_bar->add_menu(array( 'parent' => 'litespeed-single-action', 'id' => 'litespeed-single-nonoptimize', 'title' => __('No optimization', 'litespeed-cache'), 'href' => Utility::build_url(Router::ACTION_CONF, Conf::TYPE_SET, false, true, $append_arr), )); } $wp_admin_bar->add_menu(array( 'parent' => 'litespeed-single-action', 'id' => 'litespeed-single-more', 'title' => __('More settings', 'litespeed-cache'), 'href' => get_admin_url(null, 'admin.php?page=litespeed-cache'), )); $wp_admin_bar->add_menu(array( 'parent' => 'litespeed-menu', 'id' => 'litespeed-purge-all', 'title' => __('Purge All', 'litespeed-cache'), 'href' => Utility::build_url(Router::ACTION_PURGE, Purge::TYPE_PURGE_ALL, false, '_ori'), 'meta' => array('tabindex' => '0'), )); $wp_admin_bar->add_menu(array( 'parent' => 'litespeed-menu', 'id' => 'litespeed-purge-all-lscache', 'title' => __('Purge All', 'litespeed-cache') . ' - ' . __('LSCache', 'litespeed-cache'), 'href' => Utility::build_url(Router::ACTION_PURGE, Purge::TYPE_PURGE_ALL_LSCACHE, false, '_ori'), 'meta' => array('tabindex' => '0'), )); $wp_admin_bar->add_menu(array( 'parent' => 'litespeed-menu', 'id' => 'litespeed-purge-cssjs', 'title' => __('Purge All', 'litespeed-cache') . ' - ' . __('CSS/JS Cache', 'litespeed-cache'), 'href' => Utility::build_url(Router::ACTION_PURGE, Purge::TYPE_PURGE_ALL_CSSJS, false, '_ori'), 'meta' => array('tabindex' => '0'), )); if ($this->conf(self::O_CDN_CLOUDFLARE)) { $wp_admin_bar->add_menu(array( 'parent' => 'litespeed-menu', 'id' => 'litespeed-purge-cloudflare', 'title' => __('Purge All', 'litespeed-cache') . ' - ' . __('Cloudflare', 'litespeed-cache'), 'href' => Utility::build_url(Router::ACTION_CDN_CLOUDFLARE, CDN\Cloudflare::TYPE_PURGE_ALL), 'meta' => array('tabindex' => '0'), )); } if (defined('LSCWP_OBJECT_CACHE')) { $wp_admin_bar->add_menu(array( 'parent' => 'litespeed-menu', 'id' => 'litespeed-purge-object', 'title' => __('Purge All', 'litespeed-cache') . ' - ' . __('Object Cache', 'litespeed-cache'), 'href' => Utility::build_url(Router::ACTION_PURGE, Purge::TYPE_PURGE_ALL_OBJECT, false, '_ori'), 'meta' => array('tabindex' => '0'), )); } if (Router::opcache_enabled()) { $wp_admin_bar->add_menu(array( 'parent' => 'litespeed-menu', 'id' => 'litespeed-purge-opcache', 'title' => __('Purge All', 'litespeed-cache') . ' - ' . __('Opcode Cache', 'litespeed-cache'), 'href' => Utility::build_url(Router::ACTION_PURGE, Purge::TYPE_PURGE_ALL_OPCACHE, false, '_ori'), 'meta' => array('tabindex' => '0'), )); } if ($this->has_cache_folder('ccss')) { $wp_admin_bar->add_menu(array( 'parent' => 'litespeed-menu', 'id' => 'litespeed-purge-ccss', 'title' => __('Purge All', 'litespeed-cache') . ' - CCSS', 'href' => Utility::build_url(Router::ACTION_PURGE, Purge::TYPE_PURGE_ALL_CCSS, false, '_ori'), 'meta' => array('tabindex' => '0'), )); } if ($this->has_cache_folder('ucss')) { $wp_admin_bar->add_menu(array( 'parent' => 'litespeed-menu', 'id' => 'litespeed-purge-ucss', 'title' => __('Purge All', 'litespeed-cache') . ' - UCSS', 'href' => Utility::build_url(Router::ACTION_PURGE, Purge::TYPE_PURGE_ALL_UCSS, false, '_ori'), )); } if ($this->has_cache_folder('localres')) { $wp_admin_bar->add_menu(array( 'parent' => 'litespeed-menu', 'id' => 'litespeed-purge-localres', 'title' => __('Purge All', 'litespeed-cache') . ' - ' . __('Localized Resources', 'litespeed-cache'), 'href' => Utility::build_url(Router::ACTION_PURGE, Purge::TYPE_PURGE_ALL_LOCALRES, false, '_ori'), 'meta' => array('tabindex' => '0'), )); } if ($this->has_cache_folder('lqip')) { $wp_admin_bar->add_menu(array( 'parent' => 'litespeed-menu', 'id' => 'litespeed-purge-placeholder', 'title' => __('Purge All', 'litespeed-cache') . ' - ' . __('LQIP Cache', 'litespeed-cache'), 'href' => Utility::build_url(Router::ACTION_PURGE, Purge::TYPE_PURGE_ALL_LQIP, false, '_ori'), 'meta' => array('tabindex' => '0'), )); } if ($this->has_cache_folder('avatar')) { $wp_admin_bar->add_menu(array( 'parent' => 'litespeed-menu', 'id' => 'litespeed-purge-avatar', 'title' => __('Purge All', 'litespeed-cache') . ' - ' . __('Gravatar Cache', 'litespeed-cache'), 'href' => Utility::build_url(Router::ACTION_PURGE, Purge::TYPE_PURGE_ALL_AVATAR, false, '_ori'), 'meta' => array('tabindex' => '0'), )); } do_action('litespeed_frontend_shortcut'); } /** * Hooked to wp_before_admin_bar_render. * Adds a link to the admin bar so users can quickly purge all. * * @access public * @global WP_Admin_Bar $wp_admin_bar * @since 1.7.2 Moved from admin_display.cls to gui.cls; Renamed from `add_quick_purge` to `backend_shortcut` */ public function backend_shortcut() { global $wp_admin_bar; // if ( defined( 'LITESPEED_ON' ) ) { $wp_admin_bar->add_menu(array( 'id' => 'litespeed-menu', 'title' => '<span class="ab-icon" title="' . __('LiteSpeed Cache Purge All', 'litespeed-cache') . ' - ' . __('LSCache', 'litespeed-cache') . '"></span>', 'href' => Utility::build_url(Router::ACTION_PURGE, Purge::TYPE_PURGE_ALL_LSCACHE), 'meta' => array('tabindex' => 0, 'class' => 'litespeed-top-toolbar'), )); // } // else { // $wp_admin_bar->add_menu( array( // 'id' => 'litespeed-menu', // 'title' => '<span class="ab-icon" title="' . __( 'LiteSpeed Cache', 'litespeed-cache' ) . '"></span>', // 'meta' => array( 'tabindex' => 0, 'class' => 'litespeed-top-toolbar' ), // ) ); // } $wp_admin_bar->add_menu(array( 'parent' => 'litespeed-menu', 'id' => 'litespeed-bar-manage', 'title' => __('Manage', 'litespeed-cache'), 'href' => 'admin.php?page=litespeed', 'meta' => array('tabindex' => '0'), )); $wp_admin_bar->add_menu(array( 'parent' => 'litespeed-menu', 'id' => 'litespeed-bar-setting', 'title' => __('Settings', 'litespeed-cache'), 'href' => 'admin.php?page=litespeed-cache', 'meta' => array('tabindex' => '0'), )); if (!is_network_admin()) { $wp_admin_bar->add_menu(array( 'parent' => 'litespeed-menu', 'id' => 'litespeed-bar-imgoptm', 'title' => __('Image Optimization', 'litespeed-cache'), 'href' => 'admin.php?page=litespeed-img_optm', 'meta' => array('tabindex' => '0'), )); } $wp_admin_bar->add_menu(array( 'parent' => 'litespeed-menu', 'id' => 'litespeed-purge-all', 'title' => __('Purge All', 'litespeed-cache'), 'href' => Utility::build_url(Router::ACTION_PURGE, Purge::TYPE_PURGE_ALL), 'meta' => array('tabindex' => '0'), )); $wp_admin_bar->add_menu(array( 'parent' => 'litespeed-menu', 'id' => 'litespeed-purge-all-lscache', 'title' => __('Purge All', 'litespeed-cache') . ' - ' . __('LSCache', 'litespeed-cache'), 'href' => Utility::build_url(Router::ACTION_PURGE, Purge::TYPE_PURGE_ALL_LSCACHE), 'meta' => array('tabindex' => '0'), )); $wp_admin_bar->add_menu(array( 'parent' => 'litespeed-menu', 'id' => 'litespeed-purge-cssjs', 'title' => __('Purge All', 'litespeed-cache') . ' - ' . __('CSS/JS Cache', 'litespeed-cache'), 'href' => Utility::build_url(Router::ACTION_PURGE, Purge::TYPE_PURGE_ALL_CSSJS), 'meta' => array('tabindex' => '0'), )); if ($this->conf(self::O_CDN_CLOUDFLARE)) { $wp_admin_bar->add_menu(array( 'parent' => 'litespeed-menu', 'id' => 'litespeed-purge-cloudflare', 'title' => __('Purge All', 'litespeed-cache') . ' - ' . __('Cloudflare', 'litespeed-cache'), 'href' => Utility::build_url(Router::ACTION_CDN_CLOUDFLARE, CDN\Cloudflare::TYPE_PURGE_ALL), 'meta' => array('tabindex' => '0'), )); } if (defined('LSCWP_OBJECT_CACHE')) { $wp_admin_bar->add_menu(array( 'parent' => 'litespeed-menu', 'id' => 'litespeed-purge-object', 'title' => __('Purge All', 'litespeed-cache') . ' - ' . __('Object Cache', 'litespeed-cache'), 'href' => Utility::build_url(Router::ACTION_PURGE, Purge::TYPE_PURGE_ALL_OBJECT), 'meta' => array('tabindex' => '0'), )); } if (Router::opcache_enabled()) { $wp_admin_bar->add_menu(array( 'parent' => 'litespeed-menu', 'id' => 'litespeed-purge-opcache', 'title' => __('Purge All', 'litespeed-cache') . ' - ' . __('Opcode Cache', 'litespeed-cache'), 'href' => Utility::build_url(Router::ACTION_PURGE, Purge::TYPE_PURGE_ALL_OPCACHE), 'meta' => array('tabindex' => '0'), )); } if ($this->has_cache_folder('ccss')) { $wp_admin_bar->add_menu(array( 'parent' => 'litespeed-menu', 'id' => 'litespeed-purge-ccss', 'title' => __('Purge All', 'litespeed-cache') . ' - CCSS', 'href' => Utility::build_url(Router::ACTION_PURGE, Purge::TYPE_PURGE_ALL_CCSS), 'meta' => array('tabindex' => '0'), )); } if ($this->has_cache_folder('ucss')) { $wp_admin_bar->add_menu(array( 'parent' => 'litespeed-menu', 'id' => 'litespeed-purge-ucss', 'title' => __('Purge All', 'litespeed-cache') . ' - UCSS', 'href' => Utility::build_url(Router::ACTION_PURGE, Purge::TYPE_PURGE_ALL_UCSS), )); } if ($this->has_cache_folder('localres')) { $wp_admin_bar->add_menu(array( 'parent' => 'litespeed-menu', 'id' => 'litespeed-purge-localres', 'title' => __('Purge All', 'litespeed-cache') . ' - ' . __('Localized Resources', 'litespeed-cache'), 'href' => Utility::build_url(Router::ACTION_PURGE, Purge::TYPE_PURGE_ALL_LOCALRES), 'meta' => array('tabindex' => '0'), )); } if ($this->has_cache_folder('lqip')) { $wp_admin_bar->add_menu(array( 'parent' => 'litespeed-menu', 'id' => 'litespeed-purge-placeholder', 'title' => __('Purge All', 'litespeed-cache') . ' - ' . __('LQIP Cache', 'litespeed-cache'), 'href' => Utility::build_url(Router::ACTION_PURGE, Purge::TYPE_PURGE_ALL_LQIP), 'meta' => array('tabindex' => '0'), )); } if ($this->has_cache_folder('avatar')) { $wp_admin_bar->add_menu(array( 'parent' => 'litespeed-menu', 'id' => 'litespeed-purge-avatar', 'title' => __('Purge All', 'litespeed-cache') . ' - ' . __('Gravatar Cache', 'litespeed-cache'), 'href' => Utility::build_url(Router::ACTION_PURGE, Purge::TYPE_PURGE_ALL_AVATAR), 'meta' => array('tabindex' => '0'), )); } do_action('litespeed_backend_shortcut'); } /** * Clear unfinished data * * @since 2.4.2 * @access public */ public static function img_optm_clean_up($unfinished_num) { return sprintf( '<a href="%1$s" class="button litespeed-btn-warning" data-balloon-pos="up" aria-label="%2$s"><span class="dashicons dashicons-editor-removeformatting"></span> %3$s</a>', Utility::build_url(Router::ACTION_IMG_OPTM, Img_Optm::TYPE_CLEAN), __('Remove all previous unfinished image optimization requests.', 'litespeed-cache'), __('Clean Up Unfinished Data', 'litespeed-cache') . ($unfinished_num ? ': ' . Admin_Display::print_plural($unfinished_num, 'image') : '') ); } /** * Generate install link * * @since 2.4.2 * @access public */ public static function plugin_install_link($title, $name, $v) { $url = wp_nonce_url(self_admin_url('update.php?action=install-plugin&plugin=' . $name), 'install-plugin_' . $name); $action = sprintf( '<a href="%1$s" class="install-now" data-slug="%2$s" data-name="%3$s" aria-label="%4$s">%5$s</a>', esc_url($url), esc_attr($name), esc_attr($title), esc_attr(sprintf(__('Install %s', 'litespeed-cache'), $title)), __('Install Now', 'litespeed-cache') ); return $action; // $msg .= " <a href='$upgrade_link' class='litespeed-btn-success' target='_blank'>" . __( 'Click here to upgrade', 'litespeed-cache' ) . '</a>'; } /** * Generate upgrade link * * @since 2.4.2 * @access public */ public static function plugin_upgrade_link($title, $name, $v) { $details_url = self_admin_url('plugin-install.php?tab=plugin-information&plugin=' . $name . '§ion=changelog&TB_iframe=true&width=600&height=800'); $file = $name . '/' . $name . '.php'; $msg = sprintf( __('<a href="%1$s" %2$s>View version %3$s details</a> or <a href="%4$s" %5$s target="_blank">update now</a>.', 'litespeed-cache'), esc_url($details_url), sprintf('class="thickbox open-plugin-details-modal" aria-label="%s"', esc_attr(sprintf(__('View %1$s version %2$s details', 'litespeed-cache'), $title, $v))), $v, wp_nonce_url(self_admin_url('update.php?action=upgrade-plugin&plugin=') . $file, 'upgrade-plugin_' . $file), sprintf('class="update-link" aria-label="%s"', esc_attr(sprintf(__('Update %s now', 'litespeed-cache'), $title))) ); return $msg; } /** * Finalize buffer by GUI class * * @since 1.6 * @access public */ public function finalize($buffer) { $buffer = $this->_clean_wrapper($buffer); // Maybe restore doc.ref if ($this->conf(Base::O_GUEST) && strpos($buffer, '<head>') !== false && defined('LITESPEED_IS_HTML')) { $buffer = $this->_enqueue_guest_docref_js($buffer); } if (defined('LITESPEED_GUEST') && LITESPEED_GUEST && strpos($buffer, '</body>') !== false && defined('LITESPEED_IS_HTML')) { $buffer = $this->_enqueue_guest_js($buffer); } return $buffer; } /** * Append guest restore doc.ref JS for organic traffic count * * @since 4.4.6 */ private function _enqueue_guest_docref_js($buffer) { $js_con = File::read(LSCWP_DIR . self::LIB_GUEST_DOCREF_JS); $buffer = preg_replace('/<head>/', '<head><script data-no-optimize="1">' . $js_con . '</script>', $buffer, 1); return $buffer; } /** * Append guest JS to update vary * * @since 4.0 */ private function _enqueue_guest_js($buffer) { $js_con = File::read(LSCWP_DIR . self::LIB_GUEST_JS); // $guest_update_url = add_query_arg( 'litespeed_guest', 1, home_url( '/' ) ); $guest_update_url = parse_url(LSWCP_PLUGIN_URL . self::PHP_GUEST, PHP_URL_PATH); $js_con = str_replace('litespeed_url', esc_url($guest_update_url), $js_con); $buffer = preg_replace('/<\/body>/', '<script data-no-optimize="1">' . $js_con . '</script></body>', $buffer, 1); return $buffer; } /** * Clean wrapper from buffer * * @since 1.4 * @since 1.6 converted to private with adding prefix _ * @access private */ private function _clean_wrapper($buffer) { if (self::$_clean_counter < 1) { Debug2::debug2('GUI bypassed by no counter'); return $buffer; } Debug2::debug2('GUI start cleaning counter ' . self::$_clean_counter); for ($i = 1; $i <= self::$_clean_counter; $i++) { // If miss beginning $start = strpos($buffer, self::clean_wrapper_begin($i)); if ($start === false) { $buffer = str_replace(self::clean_wrapper_end($i), '', $buffer); Debug2::debug2("GUI lost beginning wrapper $i"); continue; } // If miss end $end_wrapper = self::clean_wrapper_end($i); $end = strpos($buffer, $end_wrapper); if ($end === false) { $buffer = str_replace(self::clean_wrapper_begin($i), '', $buffer); Debug2::debug2("GUI lost ending wrapper $i"); continue; } // Now replace wrapped content $buffer = substr_replace($buffer, '', $start, $end - $start + strlen($end_wrapper)); Debug2::debug2("GUI cleaned wrapper $i"); } return $buffer; } /** * Display a to-be-removed html wrapper * * @since 1.4 * @access public */ public static function clean_wrapper_begin($counter = false) { if ($counter === false) { self::$_clean_counter++; $counter = self::$_clean_counter; Debug2::debug("GUI clean wrapper $counter begin"); } return '<!-- LiteSpeed To Be Removed begin ' . $counter . ' -->'; } /** * Display a to-be-removed html wrapper * * @since 1.4 * @access public */ public static function clean_wrapper_end($counter = false) { if ($counter === false) { $counter = self::$_clean_counter; Debug2::debug("GUI clean wrapper $counter end"); } return '<!-- LiteSpeed To Be Removed end ' . $counter . ' -->'; } } src/crawler.cls.php 0000644 00000121213 15246276230 0010272 0 ustar 00 <?php /** * The crawler class * * @since 1.1.0 */ namespace LiteSpeed; defined('WPINC') || exit(); class Crawler extends Root { const LOG_TAG = '🕸️'; const TYPE_REFRESH_MAP = 'refresh_map'; const TYPE_EMPTY = 'empty'; const TYPE_BLACKLIST_EMPTY = 'blacklist_empty'; const TYPE_BLACKLIST_DEL = 'blacklist_del'; const TYPE_BLACKLIST_ADD = 'blacklist_add'; const TYPE_START = 'start'; const TYPE_RESET = 'reset'; const USER_AGENT = 'lscache_walker'; const FAST_USER_AGENT = 'lscache_runner'; const CHUNKS = 10000; const STATUS_WAIT = 'W'; const STATUS_HIT = 'H'; const STATUS_MISS = 'M'; const STATUS_BLACKLIST = 'B'; const STATUS_NOCACHE = 'N'; private $_sitemeta = 'meta.data'; private $_resetfile; private $_end_reason; private $_ncpu = 1; private $_server_ip; private $_crawler_conf = array( 'cookies' => array(), 'headers' => array(), 'ua' => '', ); private $_crawlers = array(); private $_cur_threads = -1; private $_max_run_time; private $_cur_thread_time; private $_map_status_list = array( 'H' => array(), 'M' => array(), 'B' => array(), 'N' => array(), ); protected $_summary; /** * Initialize crawler, assign sitemap path * * @since 1.1.0 */ public function __construct() { if (is_multisite()) { $this->_sitemeta = 'meta' . get_current_blog_id() . '.data'; } $this->_resetfile = LITESPEED_STATIC_DIR . '/crawler/' . $this->_sitemeta . '.reset'; $this->_summary = self::get_summary(); $this->_ncpu = $this->_get_server_cpu(); $this->_server_ip = $this->conf(Base::O_SERVER_IP); self::debug('Init w/ CPU cores=' . $this->_ncpu); } /** * Try get server CPUs * @since 5.2 */ private function _get_server_cpu() { $cpuinfo_file = '/proc/cpuinfo'; $setting_open_dir = ini_get('open_basedir'); if ($setting_open_dir) { return 1; } // Server has limit try { if (!@is_file($cpuinfo_file)) { return 1; } } catch (\Exception $e) { return 1; } $cpuinfo = file_get_contents($cpuinfo_file); preg_match_all('/^processor/m', $cpuinfo, $matches); return count($matches[0]) ?: 1; } /** * Check whether the current crawler is active/runable/useable/enabled/want it to work or not * * @since 4.3 */ public function is_active($curr) { $bypass_list = self::get_option('bypass_list', array()); return !in_array($curr, $bypass_list); } /** * Toggle the current crawler's activeness state, i.e., runable/useable/enabled/want it to work or not, and return the updated state * * @since 4.3 */ public function toggle_activeness($curr) { // param type: int $bypass_list = self::get_option('bypass_list', array()); if (in_array($curr, $bypass_list)) { // when the ith opt was off / in the bypassed list, turn it on / remove it from the list unset($bypass_list[array_search($curr, $bypass_list)]); $bypass_list = array_values($bypass_list); self::update_option('bypass_list', $bypass_list); return true; } else { // when the ith opt was on / not in the bypassed list, turn it off / add it to the list $bypass_list[] = (int) $curr; self::update_option('bypass_list', $bypass_list); return false; } } /** * Clear bypassed list * * @since 4.3 * @access public */ public function clear_disabled_list() { self::update_option('bypass_list', array()); $msg = __('Crawler disabled list is cleared! All crawlers are set to active! ', 'litespeed-cache'); Admin_Display::note($msg); self::debug('All crawlers are set to active...... '); } /** * Overwrite get_summary to init elements * * @since 3.0 * @access public */ public static function get_summary($field = false) { $_default = array( 'list_size' => 0, 'last_update_time' => 0, 'curr_crawler' => 0, 'curr_crawler_beginning_time' => 0, 'last_pos' => 0, 'last_count' => 0, 'last_crawled' => 0, 'last_start_time' => 0, 'last_status' => '', 'is_running' => 0, 'end_reason' => '', 'meta_save_time' => 0, 'pos_reset_check' => 0, 'done' => 0, 'this_full_beginning_time' => 0, 'last_full_time_cost' => 0, 'last_crawler_total_cost' => 0, 'crawler_stats' => array(), // this will store all crawlers hit/miss crawl status ); wp_cache_delete('alloptions', 'options'); // ensure the summary is current $summary = parent::get_summary(); $summary = array_merge($_default, $summary); if (!$field) { return $summary; } if (array_key_exists($field, $summary)) { return $summary[$field]; } return null; } /** * Overwrite save_summary * * @since 3.0 * @access public */ public static function save_summary($data = false, $reload = false, $overwrite = false) { $instance = self::cls(); $instance->_summary['meta_save_time'] = time(); if (!$data) { $data = $instance->_summary; } parent::save_summary($data, $reload, $overwrite); File::save(LITESPEED_STATIC_DIR . '/crawler/' . $instance->_sitemeta, \json_encode($data), true); } /** * Cron start async crawling * * @since 5.5 */ public static function start_async_cron() { Task::async_call('crawler'); } /** * Manually start async crawling * * @since 5.5 */ public static function start_async() { Task::async_call('crawler_force'); $msg = __('Started async crawling', 'litespeed-cache'); Admin_Display::success($msg); } /** * Ajax crawl handler * * @since 5.5 */ public static function async_handler($manually_run = false) { self::debug('------------async-------------start_async_handler'); // check_ajax_referer('async_crawler', 'nonce'); self::start($manually_run); } /** * Proceed crawling * * @since 1.1.0 * @access public */ public static function start($manually_run = false) { if (!Router::can_crawl()) { self::debug('......crawler is NOT allowed by the server admin......'); return false; } if ($manually_run) { self::debug('......crawler manually ran......'); } self::cls()->_crawl_data($manually_run); } /** * Crawling start * * @since 1.1.0 * @access private */ private function _crawl_data($manually_run) { if (!defined('LITESPEED_LANE_HASH')) { define('LITESPEED_LANE_HASH', Str::rrand(8)); } if ($this->_check_valid_lane()) { $this->_take_over_lane(); } else { self::debug('⚠️ lane in use'); return; // if ($manually_run) { // self::debug('......crawler started (manually_rund)......'); // // Log pid to prevent from multi running // if (defined('LITESPEED_CLI')) { // // Take over lane // self::debug('⚠️⚠️⚠️ Forced take over lane (CLI)'); // $this->_take_over_lane(); // } // } } self::debug('......crawler started......'); // for the first time running if (!$this->_summary || !Data::cls()->tb_exist('crawler') || !Data::cls()->tb_exist('crawler_blacklist')) { $this->cls('Crawler_Map')->gen(); } // if finished last time, regenerate sitemap if ($this->_summary['done'] === 'touchedEnd') { // check whole crawling interval $last_finished_at = $this->_summary['last_full_time_cost'] + $this->_summary['this_full_beginning_time']; if (!$manually_run && time() - $last_finished_at < $this->conf(Base::O_CRAWLER_CRAWL_INTERVAL)) { self::debug('Cron abort: cache warmed already.'); // if not reach whole crawling interval, exit $this->Release_lane(); return; } self::debug('TouchedEnd. regenerate sitemap....'); $this->cls('Crawler_Map')->gen(); } $this->list_crawlers(); // Skip the crawlers that in bypassed list while (!$this->is_active($this->_summary['curr_crawler']) && $this->_summary['curr_crawler'] < count($this->_crawlers)) { self::debug('Skipped the Crawler #' . $this->_summary['curr_crawler'] . ' ......'); $this->_summary['curr_crawler']++; } if ($this->_summary['curr_crawler'] >= count($this->_crawlers)) { $this->_end_reason = 'end'; $this->_terminate_running(); $this->Release_lane(); return; } // In case crawlers are all done but not reload, reload it if (empty($this->_summary['curr_crawler']) || empty($this->_crawlers[$this->_summary['curr_crawler']])) { $this->_summary['curr_crawler'] = 0; $this->_summary['crawler_stats'][$this->_summary['curr_crawler']] = array(); } $res = $this->load_conf(); if (!$res) { self::debug('Load conf failed'); $this->_terminate_running(); $this->Release_lane(); return; } try { $this->_engine_start(); $this->Release_lane(); } catch (\Exception $e) { self::debug('🛑 ' . $e->getMessage()); } } /** * Load conf before running crawler * * @since 3.0 * @access private */ private function load_conf() { $this->_crawler_conf['base'] = home_url(); $current_crawler = $this->_crawlers[$this->_summary['curr_crawler']]; /** * Check cookie crawler * @since 2.8 */ foreach ($current_crawler as $k => $v) { if (strpos($k, 'cookie:') !== 0) { continue; } if ($v == '_null') { continue; } $this->_crawler_conf['cookies'][substr($k, 7)] = $v; } /** * Set WebP simulation * @since 1.9.1 */ if (!empty($current_crawler['webp'])) { $this->_crawler_conf['headers'][] = 'Accept: image/' . ($this->conf(Base::O_IMG_OPTM_WEBP) == 2 ? 'avif' : 'webp') . ',*/*'; } /** * Set mobile crawler * @since 2.8 */ if (!empty($current_crawler['mobile'])) { $this->_crawler_conf['ua'] = 'Mobile iPhone'; } /** * Limit delay to use server setting * @since 1.8.3 */ $this->_crawler_conf['run_delay'] = 500; // microseconds if (defined('LITESPEED_CRAWLER_USLEEP') && LITESPEED_CRAWLER_USLEEP > $this->_crawler_conf['run_delay']) { $this->_crawler_conf['run_delay'] = LITESPEED_CRAWLER_USLEEP; } if (!empty($_SERVER[Base::ENV_CRAWLER_USLEEP]) && $_SERVER[Base::ENV_CRAWLER_USLEEP] > $this->_crawler_conf['run_delay']) { $this->_crawler_conf['run_delay'] = $_SERVER[Base::ENV_CRAWLER_USLEEP]; } $this->_crawler_conf['run_duration'] = $this->get_crawler_duration(); $this->_crawler_conf['load_limit'] = $this->conf(Base::O_CRAWLER_LOAD_LIMIT); if (!empty($_SERVER[Base::ENV_CRAWLER_LOAD_LIMIT_ENFORCE])) { $this->_crawler_conf['load_limit'] = $_SERVER[Base::ENV_CRAWLER_LOAD_LIMIT_ENFORCE]; } elseif (!empty($_SERVER[Base::ENV_CRAWLER_LOAD_LIMIT]) && $_SERVER[Base::ENV_CRAWLER_LOAD_LIMIT] < $this->_crawler_conf['load_limit']) { $this->_crawler_conf['load_limit'] = $_SERVER[Base::ENV_CRAWLER_LOAD_LIMIT]; } if ($this->_crawler_conf['load_limit'] == 0) { self::debug('🛑 Terminated crawler due to load limit set to 0'); return false; } /** * Set role simulation * @since 1.9.1 */ if (!empty($current_crawler['uid'])) { if (!$this->_server_ip) { self::debug('🛑 Terminated crawler due to Server IP not set'); return false; } // Get role simulation vary name $vary_name = $this->cls('Vary')->get_vary_name(); $vary_val = $this->cls('Vary')->finalize_default_vary($current_crawler['uid']); $this->_crawler_conf['cookies'][$vary_name] = $vary_val; $this->_crawler_conf['cookies']['litespeed_hash'] = Router::cls()->get_hash($current_crawler['uid']); } return true; } /** * Get crawler duration allowance * * @since 7.0 */ public function get_crawler_duration() { $RUN_DURATION = defined('LITESPEED_CRAWLER_DURATION') ? LITESPEED_CRAWLER_DURATION : 900; if ($RUN_DURATION > 900) { $RUN_DURATION = 900; // reset to default value if defined in conf file is higher than 900 seconds for security enhancement } return $RUN_DURATION; } /** * Start crawler * * @since 1.1.0 * @access private */ private function _engine_start() { // check if is running // if ($this->_summary['is_running'] && time() - $this->_summary['is_running'] < $this->_crawler_conf['run_duration']) { // $this->_end_reason = 'stopped'; // self::debug('The crawler is running.'); // return; // } // check current load $this->_adjust_current_threads(); if ($this->_cur_threads == 0) { $this->_end_reason = 'stopped_highload'; self::debug('Stopped due to heavy load.'); return; } // log started time self::save_summary(array('last_start_time' => time())); // set time limit $maxTime = (int) ini_get('max_execution_time'); self::debug('ini_get max_execution_time=' . $maxTime); if ($maxTime == 0) { $maxTime = 300; // hardlimit } else { $maxTime -= 5; } if ($maxTime >= $this->_crawler_conf['run_duration']) { $maxTime = $this->_crawler_conf['run_duration']; self::debug('Use run_duration setting as max_execution_time=' . $maxTime); } elseif (ini_set('max_execution_time', $this->_crawler_conf['run_duration'] + 15) !== false) { $maxTime = $this->_crawler_conf['run_duration']; self::debug('ini_set max_execution_time=' . $maxTime); } self::debug('final max_execution_time=' . $maxTime); $this->_max_run_time = $maxTime + time(); // mark running $this->_prepare_running(); // run crawler $this->_do_running(); $this->_terminate_running(); } /** * Get server load * * @since 5.5 */ public function get_server_load() { /** * If server is windows, exit * @see https://wordpress.org/support/topic/crawler-keeps-causing-crashes/ */ if (!function_exists('sys_getloadavg')) { return -1; } $curload = sys_getloadavg(); $curload = $curload[0]; self::debug('Server load: ' . $curload); return $curload; } /** * Adjust threads dynamically * * @since 1.1.0 * @access private */ private function _adjust_current_threads() { $curload = $this->get_server_load(); if ($curload == -1) { self::debug('set threads=0 due to func sys_getloadavg not exist!'); $this->_cur_threads = 0; return; } $curload /= $this->_ncpu; // $curload = 1; $CRAWLER_THREADS = defined('LITESPEED_CRAWLER_THREADS') ? LITESPEED_CRAWLER_THREADS : 3; if ($this->_cur_threads == -1) { // init if ($curload > $this->_crawler_conf['load_limit']) { $curthreads = 0; } elseif ($curload >= $this->_crawler_conf['load_limit'] - 1) { $curthreads = 1; } else { $curthreads = intval($this->_crawler_conf['load_limit'] - $curload); if ($curthreads > $CRAWLER_THREADS) { $curthreads = $CRAWLER_THREADS; } } } else { // adjust $curthreads = $this->_cur_threads; if ($curload >= $this->_crawler_conf['load_limit'] + 1) { sleep(5); // sleep 5 secs if ($curthreads >= 1) { $curthreads--; } } elseif ($curload >= $this->_crawler_conf['load_limit']) { // if ( $curthreads > 1 ) {// if already 1, keep $curthreads--; // } } elseif ($curload + 1 < $this->_crawler_conf['load_limit']) { if ($curthreads < $CRAWLER_THREADS) { $curthreads++; } } } // $log = 'set current threads = ' . $curthreads . ' previous=' . $this->_cur_threads // . ' max_allowed=' . $CRAWLER_THREADS . ' load_limit=' . $this->_crawler_conf[ 'load_limit' ] . ' current_load=' . $curload; $this->_cur_threads = $curthreads; $this->_cur_thread_time = time(); } /** * Mark running status * * @since 1.1.0 * @access private */ private function _prepare_running() { $this->_summary['is_running'] = time(); $this->_summary['done'] = 0; // reset done status $this->_summary['last_status'] = 'prepare running'; $this->_summary['last_crawled'] = 0; // Current crawler starttime mark if ($this->_summary['last_pos'] == 0) { $this->_summary['curr_crawler_beginning_time'] = time(); } if ($this->_summary['curr_crawler'] == 0 && $this->_summary['last_pos'] == 0) { $this->_summary['this_full_beginning_time'] = time(); $this->_summary['list_size'] = $this->cls('Crawler_Map')->count_map(); } if ($this->_summary['end_reason'] == 'end' && $this->_summary['last_pos'] == 0) { $this->_summary['crawler_stats'][$this->_summary['curr_crawler']] = array(); } self::save_summary(); } /** * Take over lane * @since 6.1 */ private function _take_over_lane() { self::debug('Take over lane as lane is free: ' . $this->json_local_path() . '.pid'); file::save($this->json_local_path() . '.pid', LITESPEED_LANE_HASH); } /** * Update lane file * @since 6.1 */ private function _touch_lane() { touch($this->json_local_path() . '.pid'); } /** * Release lane file * @since 6.1 */ public function Release_lane() { $lane_file = $this->json_local_path() . '.pid'; if (!file_exists($lane_file)) { return; } self::debug('Release lane'); unlink($lane_file); } /** * Check if lane is used by other crawlers * @since 6.1 */ private function _check_valid_lane($strict_mode = false) { // Check lane hash $lane_file = $this->json_local_path() . '.pid'; if ($strict_mode) { if (!file_exists($lane_file)) { self::debug("lane file not existed, strict mode is false [file] $lane_file"); return false; } } $pid = file::read($lane_file); if ($pid && LITESPEED_LANE_HASH != $pid) { // If lane file is older than 1h, ignore if (time() - filemtime($lane_file) > 3600) { self::debug('Lane file is older than 1h, releasing lane'); $this->Release_lane(); return true; } return false; } return true; } /** * Test port for simulator * * @since 7.0 * @access private * @return bool true if success and can continue crawling, false if failed and need to stop */ private function _test_port() { if (empty($this->_crawler_conf['cookies']) || empty($this->_crawler_conf['cookies']['litespeed_hash'])) { return true; } if (!$this->_server_ip) { self::debug('❌ Server IP not set'); return false; } if (defined('LITESPEED_CRAWLER_LOCAL_PORT')) { self::debug('✅ LITESPEED_CRAWLER_LOCAL_PORT already defined'); return true; } // Don't repeat testing in 120s if (!empty($this->_summary['test_port_tts']) && time() - $this->_summary['test_port_tts'] < 120) { if (!empty($this->_summary['test_port'])) { self::debug('✅ Use tested local port: ' . $this->_summary['test_port']); define('LITESPEED_CRAWLER_LOCAL_PORT', $this->_summary['test_port']); return true; } return false; } $this->_summary['test_port_tts'] = time(); self::save_summary(); $options = $this->_get_curl_options(); $home = home_url(); File::save(LITESPEED_STATIC_DIR . '/crawler/test_port.txt', $home, true); $url = LITESPEED_STATIC_URL . '/crawler/test_port.txt'; $parsed_url = parse_url($url); if (empty($parsed_url['host'])) { self::debug('❌ Test port failed, invalid URL: ' . $url); return false; } $resolved = $parsed_url['host'] . ':443:' . $this->_server_ip; $options[CURLOPT_RESOLVE] = array($resolved); $options[CURLOPT_DNS_USE_GLOBAL_CACHE] = false; $options[CURLOPT_HEADER] = false; self::debug('Test local 443 port for ' . $resolved); $ch = curl_init(); curl_setopt_array($ch, $options); curl_setopt($ch, CURLOPT_URL, $url); $result = curl_exec($ch); $test_result = false; if (curl_errno($ch) || $result !== $home) { if (curl_errno($ch)) { self::debug('❌ Test port curl error: [errNo] ' . curl_errno($ch) . ' [err] ' . curl_error($ch)); } elseif ($result !== $home) { self::debug('❌ Test port response is wrong: ' . $result); } self::debug('❌ Test local 443 port failed, try port 80'); // Try port 80 $resolved = $parsed_url['host'] . ':80:' . $this->_server_ip; $options[CURLOPT_RESOLVE] = array($resolved); $url = str_replace('https://', 'http://', $url); if (!in_array('X-Forwarded-Proto: https', $options[CURLOPT_HTTPHEADER])) { $options[CURLOPT_HTTPHEADER][] = 'X-Forwarded-Proto: https'; } // $options[CURLOPT_HTTPHEADER][] = 'X-Forwarded-SSL: on'; $ch = curl_init(); curl_setopt_array($ch, $options); curl_setopt($ch, CURLOPT_URL, $url); $result = curl_exec($ch); if (curl_errno($ch)) { self::debug('❌ Test port curl error: [errNo] ' . curl_errno($ch) . ' [err] ' . curl_error($ch)); } elseif ($result !== $home) { self::debug('❌ Test port response is wrong: ' . $result); } else { self::debug('✅ Test local 80 port successfully'); define('LITESPEED_CRAWLER_LOCAL_PORT', 80); $this->_summary['test_port'] = 80; $test_result = true; } // self::debug('Response data: ' . $result); // $this->Release_lane(); // exit($result); } else { self::debug('✅ Tested local 443 port successfully'); define('LITESPEED_CRAWLER_LOCAL_PORT', 443); $this->_summary['test_port'] = 443; $test_result = true; } self::save_summary(); curl_close($ch); return $test_result; } /** * Run crawler * * @since 1.1.0 * @access private */ private function _do_running() { $options = $this->_get_curl_options(true); // If is role simulator and not defined local port, check port once $test_result = $this->_test_port(); if (!$test_result) { $this->_end_reason = 'port_test_failed'; self::debug('❌ Test port failed, crawler stopped.'); return; } while ($urlChunks = $this->cls('Crawler_Map')->list_map(self::CHUNKS, $this->_summary['last_pos'])) { // self::debug('$urlChunks=' . count($urlChunks) . ' $this->_cur_threads=' . $this->_cur_threads); // start crawling $urlChunks = array_chunk($urlChunks, $this->_cur_threads); // self::debug('$urlChunks after array_chunk: ' . count($urlChunks)); foreach ($urlChunks as $rows) { if (!$this->_check_valid_lane(true)) { $this->_end_reason = 'lane_invalid'; self::debug('🛑 The crawler lane is used by newer crawler.'); throw new \Exception('invalid crawler lane'); } // Update time $this->_touch_lane(); // self::debug('chunk fetching count($rows)= ' . count($rows)); // multi curl $rets = $this->_multi_request($rows, $options); // check result headers foreach ($rows as $row) { // self::debug('chunk fetching 553'); if (empty($rets[$row['id']])) { // If already in blacklist, no curl happened, no corresponding record continue; } // self::debug('chunk fetching 557'); // check response if ($rets[$row['id']]['code'] == 428) { // HTTP/1.1 428 Precondition Required (need to test) $this->_end_reason = 'crawler_disabled'; self::debug('crawler_disabled'); return; } $status = $this->_status_parse($rets[$row['id']]['header'], $rets[$row['id']]['code'], $row['url']); // B or H or M or N(nocache) self::debug('[status] ' . $this->_status2title($status) . "\t\t [url] " . $row['url']); $this->_map_status_list[$status][$row['id']] = array( 'url' => $row['url'], 'code' => $rets[$row['id']]['code'], // 201 or 200 or 404 ); if (empty($this->_summary['crawler_stats'][$this->_summary['curr_crawler']][$status])) { $this->_summary['crawler_stats'][$this->_summary['curr_crawler']][$status] = 0; } $this->_summary['crawler_stats'][$this->_summary['curr_crawler']][$status]++; } // update offset position $_time = time(); $this->_summary['last_count'] = count($rows); $this->_summary['last_pos'] += $this->_summary['last_count']; $this->_summary['last_crawled'] += $this->_summary['last_count']; $this->_summary['last_update_time'] = $_time; $this->_summary['last_status'] = 'updated position'; // self::debug("chunk fetching 604 last_pos:{$this->_summary['last_pos']} last_count:{$this->_summary['last_count']} last_crawled:{$this->_summary['last_crawled']}"); // check duration if ($this->_summary['last_update_time'] > $this->_max_run_time) { $this->_end_reason = 'stopped_maxtime'; self::debug('Terminated due to maxtime'); return; // return __('Stopped due to exceeding defined Maximum Run Time', 'litespeed-cache'); } // make sure at least each 10s save meta & map status once if ($_time - $this->_summary['meta_save_time'] > 10) { $this->_map_status_list = $this->cls('Crawler_Map')->save_map_status($this->_map_status_list, $this->_summary['curr_crawler']); self::save_summary(); } // self::debug('chunk fetching 597'); // check if need to reset pos each 5s if ($_time > $this->_summary['pos_reset_check']) { $this->_summary['pos_reset_check'] = $_time + 5; if (file_exists($this->_resetfile) && unlink($this->_resetfile)) { self::debug('Terminated due to reset file'); $this->_summary['last_pos'] = 0; $this->_summary['curr_crawler'] = 0; $this->_summary['crawler_stats'][$this->_summary['curr_crawler']] = array(); // reset done status $this->_summary['done'] = 0; $this->_summary['this_full_beginning_time'] = 0; $this->_end_reason = 'stopped_reset'; return; // return __('Stopped due to reset meta position', 'litespeed-cache'); } } // self::debug('chunk fetching 615'); // check loads if ($this->_summary['last_update_time'] - $this->_cur_thread_time > 60) { $this->_adjust_current_threads(); if ($this->_cur_threads == 0) { $this->_end_reason = 'stopped_highload'; self::debug('🛑 Terminated due to highload'); return; // return __('Stopped due to load over limit', 'litespeed-cache'); } } $this->_summary['last_status'] = 'sleeping ' . $this->_crawler_conf['run_delay'] . 'ms'; usleep($this->_crawler_conf['run_delay']); } // self::debug('chunk fetching done'); } // All URLs are done for current crawler $this->_end_reason = 'end'; $this->_summary['crawler_stats'][$this->_summary['curr_crawler']]['W'] = 0; self::debug('Crawler #' . $this->_summary['curr_crawler'] . ' touched end'); } /** * Send multi curl requests * If res=B, bypass request and won't return * * @since 1.1.0 * @access private */ private function _multi_request($rows, $options) { if (!function_exists('curl_multi_init')) { exit('curl_multi_init disabled'); } $mh = curl_multi_init(); $CRAWLER_DROP_DOMAIN = defined('LITESPEED_CRAWLER_DROP_DOMAIN') ? LITESPEED_CRAWLER_DROP_DOMAIN : false; $curls = array(); foreach ($rows as $row) { if (substr($row['res'], $this->_summary['curr_crawler'], 1) == self::STATUS_BLACKLIST) { continue; } if (substr($row['res'], $this->_summary['curr_crawler'], 1) == self::STATUS_NOCACHE) { continue; } if (!function_exists('curl_init')) { exit('curl_init disabled'); } $curls[$row['id']] = curl_init(); // Append URL $url = $row['url']; if ($CRAWLER_DROP_DOMAIN) { $url = $this->_crawler_conf['base'] . $row['url']; } // IP resolve if (!empty($this->_crawler_conf['cookies']) && !empty($this->_crawler_conf['cookies']['litespeed_hash'])) { $parsed_url = parse_url($url); // self::debug('Crawl role simulator, required to use localhost for resolve'); if (!empty($parsed_url['host'])) { $dom = $parsed_url['host']; $port = defined('LITESPEED_CRAWLER_LOCAL_PORT') ? LITESPEED_CRAWLER_LOCAL_PORT : '443'; $resolved = $dom . ':' . $port . ':' . $this->_server_ip; $options[CURLOPT_RESOLVE] = array($resolved); $options[CURLOPT_DNS_USE_GLOBAL_CACHE] = false; // $options[CURLOPT_PORT] = $port; if ($port == 80) { $url = str_replace('https://', 'http://', $url); if (!in_array('X-Forwarded-Proto: https', $options[CURLOPT_HTTPHEADER])) { $options[CURLOPT_HTTPHEADER][] = 'X-Forwarded-Proto: https'; } } self::debug('Resolved DNS for ' . $resolved); } } curl_setopt($curls[$row['id']], CURLOPT_URL, $url); self::debug('Crawling [url] ' . $url . ($url == $row['url'] ? '' : ' [ori] ' . $row['url'])); curl_setopt_array($curls[$row['id']], $options); curl_multi_add_handle($mh, $curls[$row['id']]); } // execute curl if ($curls) { do { $status = curl_multi_exec($mh, $active); if ($active) { curl_multi_select($mh); } } while ($active && $status == CURLM_OK); } // curl done $ret = array(); foreach ($rows as $row) { if (substr($row['res'], $this->_summary['curr_crawler'], 1) == self::STATUS_BLACKLIST) { continue; } if (substr($row['res'], $this->_summary['curr_crawler'], 1) == self::STATUS_NOCACHE) { continue; } // self::debug('-----debug3'); $ch = $curls[$row['id']]; // Parse header $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); $content = curl_multi_getcontent($ch); $header = substr($content, 0, $header_size); $ret[$row['id']] = array( 'header' => $header, 'code' => curl_getinfo($ch, CURLINFO_HTTP_CODE), ); // self::debug('-----debug4'); curl_multi_remove_handle($mh, $ch); curl_close($ch); } // self::debug('-----debug5'); curl_multi_close($mh); // self::debug('-----debug6'); return $ret; } /** * Translate the status to title * @since 6.0 */ private function _status2title($status) { if ($status == self::STATUS_HIT) { return '✅ Hit'; } if ($status == self::STATUS_MISS) { return '😊 Miss'; } if ($status == self::STATUS_BLACKLIST) { return '😅 Blacklisted'; } if ($status == self::STATUS_NOCACHE) { return '😅 Blacklisted'; } return '🛸 Unknown'; } /** * Check returned curl header to find if cached or not * * @since 2.0 * @access private */ private function _status_parse($header, $code, $url) { // self::debug('http status code: ' . $code . ' [headers]', $header); if ($code == 201) { return self::STATUS_HIT; } if (stripos($header, 'X-Litespeed-Cache-Control: no-cache') !== false) { // If is from DIVI, taken as miss if (defined('LITESPEED_CRAWLER_IGNORE_NONCACHEABLE') && LITESPEED_CRAWLER_IGNORE_NONCACHEABLE) { return self::STATUS_MISS; } // If blacklist is disabled if ((defined('LITESPEED_CRAWLER_DISABLE_BLOCKLIST') && LITESPEED_CRAWLER_DISABLE_BLOCKLIST) || apply_filters('litespeed_crawler_disable_blocklist', false, $url)) { return self::STATUS_MISS; } return self::STATUS_NOCACHE; // Blacklist } $_cache_headers = array('x-qc-cache', 'x-lsadc-cache', 'x-litespeed-cache'); foreach ($_cache_headers as $_header) { if (stripos($header, $_header) !== false) { if (stripos($header, $_header . ': miss') !== false) { return self::STATUS_MISS; // Miss } return self::STATUS_HIT; // Hit } } // If blacklist is disabled if ((defined('LITESPEED_CRAWLER_DISABLE_BLOCKLIST') && LITESPEED_CRAWLER_DISABLE_BLOCKLIST) || apply_filters('litespeed_crawler_disable_blocklist', false, $url)) { return self::STATUS_MISS; } return self::STATUS_BLACKLIST; // Blacklist } /** * Get curl_options * * @since 1.1.0 * @access private */ private function _get_curl_options($crawler_only = false) { $CRAWLER_TIMEOUT = defined('LITESPEED_CRAWLER_TIMEOUT') ? LITESPEED_CRAWLER_TIMEOUT : 30; $options = array( CURLOPT_RETURNTRANSFER => true, CURLOPT_HEADER => true, CURLOPT_CUSTOMREQUEST => 'GET', CURLOPT_FOLLOWLOCATION => false, CURLOPT_ENCODING => 'gzip', CURLOPT_CONNECTTIMEOUT => 10, CURLOPT_TIMEOUT => $CRAWLER_TIMEOUT, // Larger timeout to avoid incorrect blacklist addition #900171 CURLOPT_SSL_VERIFYHOST => 0, CURLOPT_SSL_VERIFYPEER => false, CURLOPT_NOBODY => false, CURLOPT_HTTPHEADER => $this->_crawler_conf['headers'], ); $options[CURLOPT_HTTPHEADER][] = 'Cache-Control: max-age=0'; /** * Try to enable http2 connection (only available since PHP7+) * @since 1.9.1 * @since 2.2.7 Commented due to cause no-cache issue * @since 2.9.1+ Fixed wrongly usage of CURL_HTTP_VERSION_1_1 const */ $options[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_1; // $options[ CURL_HTTP_VERSION_2 ] = 1; // if is walker // $options[ CURLOPT_FRESH_CONNECT ] = true; // Referer if (isset($_SERVER['HTTP_HOST']) && isset($_SERVER['REQUEST_URI'])) { $options[CURLOPT_REFERER] = 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; } // User Agent if ($crawler_only) { if (strpos($this->_crawler_conf['ua'], Crawler::FAST_USER_AGENT) !== 0) { $this->_crawler_conf['ua'] = Crawler::FAST_USER_AGENT . ' ' . $this->_crawler_conf['ua']; } } $options[CURLOPT_USERAGENT] = $this->_crawler_conf['ua']; // Cookies $cookies = array(); foreach ($this->_crawler_conf['cookies'] as $k => $v) { if (!$v) { continue; } $cookies[] = $k . '=' . urlencode($v); } if ($cookies) { $options[CURLOPT_COOKIE] = implode('; ', $cookies); } return $options; } /** * Self curl to get HTML content * * @since 3.3 */ public function self_curl($url, $ua, $uid = false, $accept = false) { // $accept not in use yet $this->_crawler_conf['base'] = home_url(); $this->_crawler_conf['ua'] = $ua; if ($accept) { $this->_crawler_conf['headers'] = array('Accept: ' . $accept); } $options = $this->_get_curl_options(); if ($uid) { $this->_crawler_conf['cookies']['litespeed_flash_hash'] = Router::cls()->get_flash_hash($uid); $parsed_url = parse_url($url); if (!empty($parsed_url['host'])) { $dom = $parsed_url['host']; $port = defined('LITESPEED_CRAWLER_LOCAL_PORT') ? LITESPEED_CRAWLER_LOCAL_PORT : '443'; $resolved = $dom . ':' . $port . ':' . $this->_server_ip; $options[CURLOPT_RESOLVE] = array($resolved); $options[CURLOPT_DNS_USE_GLOBAL_CACHE] = false; $options[CURLOPT_PORT] = $port; self::debug('Resolved DNS for ' . $resolved); } } $options[CURLOPT_HEADER] = false; $options[CURLOPT_FOLLOWLOCATION] = true; $ch = curl_init(); curl_setopt_array($ch, $options); curl_setopt($ch, CURLOPT_URL, $url); $result = curl_exec($ch); $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($code != 200) { self::debug('❌ Response code is not 200 in self_curl() [code] ' . var_export($code, true)); return false; } return $result; } /** * Terminate crawling * * @since 1.1.0 * @access private */ private function _terminate_running() { $this->_map_status_list = $this->cls('Crawler_Map')->save_map_status($this->_map_status_list, $this->_summary['curr_crawler']); if ($this->_end_reason == 'end') { // Current crawler is fully done // $end_reason = sprintf( __( 'Crawler %s reached end of sitemap file.', 'litespeed-cache' ), '#' . ( $this->_summary['curr_crawler'] + 1 ) ); $this->_summary['curr_crawler']++; // Jump to next crawler // $this->_summary[ 'crawler_stats' ][ $this->_summary[ 'curr_crawler' ] ] = array(); // reset this at next crawl time $this->_summary['last_pos'] = 0; // reset last position $this->_summary['last_crawler_total_cost'] = time() - $this->_summary['curr_crawler_beginning_time']; $count_crawlers = count($this->list_crawlers()); if ($this->_summary['curr_crawler'] >= $count_crawlers) { self::debug('_terminate_running Touched end, whole crawled. Reload crawler!'); $this->_summary['curr_crawler'] = 0; // $this->_summary[ 'crawler_stats' ][ $this->_summary[ 'curr_crawler' ] ] = array(); $this->_summary['done'] = 'touchedEnd'; // log done status $this->_summary['last_full_time_cost'] = time() - $this->_summary['this_full_beginning_time']; } } $this->_summary['last_status'] = 'stopped'; $this->_summary['is_running'] = 0; $this->_summary['end_reason'] = $this->_end_reason; self::save_summary(); } /** * List all crawlers ( tagA => [ valueA => titleA, ... ] ...) * * @since 1.9.1 * @access public */ public function list_crawlers() { if ($this->_crawlers) { return $this->_crawlers; } $crawler_factors = array(); // Add default Guest crawler $crawler_factors['uid'] = array(0 => __('Guest', 'litespeed-cache')); // WebP on/off if ($this->conf(Base::O_IMG_OPTM_WEBP)) { $crawler_factors['webp'] = array(1 => $this->cls('Media')->next_gen_image_title()); if (apply_filters('litespeed_crawler_webp', false)) { $crawler_factors['webp'][0] = ''; } } // Guest Mode on/off if ($this->conf(Base::O_GUEST)) { $vary_name = $this->cls('Vary')->get_vary_name(); $vary_val = 'guest_mode:1'; if (!defined('LSCWP_LOG')) { $vary_val = md5($this->conf(Base::HASH) . $vary_val); } $crawler_factors['cookie:' . $vary_name] = array($vary_val => '', '_null' => '<font data-balloon-pos="up" aria-label="Guest Mode">👒</font>'); } // Mobile crawler if ($this->conf(Base::O_CACHE_MOBILE)) { $crawler_factors['mobile'] = array(1 => '<font data-balloon-pos="up" aria-label="Mobile">📱</font>', 0 => ''); } // Get roles set // List all roles foreach ($this->conf(Base::O_CRAWLER_ROLES) as $v) { $role_title = ''; $udata = get_userdata($v); if (isset($udata->roles) && is_array($udata->roles)) { $tmp = array_values($udata->roles); $role_title = array_shift($tmp); } if (!$role_title) { continue; } $crawler_factors['uid'][$v] = ucfirst($role_title); } // Cookie crawler foreach ($this->conf(Base::O_CRAWLER_COOKIES) as $v) { if (empty($v['name'])) { continue; } $this_cookie_key = 'cookie:' . $v['name']; $crawler_factors[$this_cookie_key] = array(); foreach ($v['vals'] as $v2) { $crawler_factors[$this_cookie_key][$v2] = $v2 == '_null' ? '' : '<font data-balloon-pos="up" aria-label="Cookie">🍪</font>' . esc_html($v['name']) . '=' . esc_html($v2); } } // Crossing generate the crawler list $this->_crawlers = $this->_recursive_build_crawler($crawler_factors); return $this->_crawlers; } /** * Build a crawler list recursively * * @since 2.8 * @access private */ private function _recursive_build_crawler($crawler_factors, $group = array(), $i = 0) { $current_factor = array_keys($crawler_factors); $current_factor = $current_factor[$i]; $if_touch_end = $i + 1 >= count($crawler_factors); $final_list = array(); foreach ($crawler_factors[$current_factor] as $k => $v) { // Don't alter $group bcos of loop usage $item = $group; $item['title'] = !empty($group['title']) ? $group['title'] : ''; if ($v) { if ($item['title']) { $item['title'] .= ' - '; } $item['title'] .= $v; } $item[$current_factor] = $k; if ($if_touch_end) { $final_list[] = $item; } else { // Inception: next layer $final_list = array_merge($final_list, $this->_recursive_build_crawler($crawler_factors, $item, $i + 1)); } } return $final_list; } /** * Return crawler meta file local path * * @since 6.1 * @access public */ public function json_local_path() { // if (!file_exists(LITESPEED_STATIC_DIR . '/crawler/' . $this->_sitemeta)) { // return false; // } return LITESPEED_STATIC_DIR . '/crawler/' . $this->_sitemeta; } /** * Return crawler meta file * * @since 1.1.0 * @access public */ public function json_path() { if (!file_exists(LITESPEED_STATIC_DIR . '/crawler/' . $this->_sitemeta)) { return false; } return LITESPEED_STATIC_URL . '/crawler/' . $this->_sitemeta; } /** * Create reset pos file * * @since 1.1.0 * @access public */ public function reset_pos() { File::save($this->_resetfile, time(), true); self::save_summary(array('is_running' => 0)); } /** * Display status based by matching crawlers order * * @since 3.0 * @access public */ public function display_status($status_row, $reason_set) { if (!$status_row) { return ''; } $_status_list = array( '-' => 'default', self::STATUS_MISS => 'primary', self::STATUS_HIT => 'success', self::STATUS_BLACKLIST => 'danger', self::STATUS_NOCACHE => 'warning', ); $reason_set = explode(',', $reason_set); $status = ''; foreach (str_split($status_row) as $k => $v) { $reason = $reason_set[$k]; if ($reason == 'Man') { $reason = __('Manually added to blocklist', 'litespeed-cache'); } if ($reason == 'Existed') { $reason = __('Previously existed in blocklist', 'litespeed-cache'); } if ($reason) { $reason = 'data-balloon-pos="up" aria-label="' . $reason . '"'; } $status .= '<i class="litespeed-dot litespeed-bg-' . $_status_list[$v] . '" ' . $reason . '>' . ($k + 1) . '</i>'; } return $status; } /** * Output info and exit * * @since 1.1.0 * @access protected * @param string $error Error info */ protected function output($msg) { if (defined('DOING_CRON')) { echo $msg; // exit(); } else { echo "<script>alert('" . htmlspecialchars($msg) . "');</script>"; // exit; } } /** * Handle all request actions from main cls * * @since 3.0 * @access public */ public function handler() { $type = Router::verify_type(); switch ($type) { case self::TYPE_REFRESH_MAP: $this->cls('Crawler_Map')->gen(true); break; case self::TYPE_EMPTY: $this->cls('Crawler_Map')->empty_map(); break; case self::TYPE_BLACKLIST_EMPTY: $this->cls('Crawler_Map')->blacklist_empty(); break; case self::TYPE_BLACKLIST_DEL: if (!empty($_GET['id'])) { $this->cls('Crawler_Map')->blacklist_del($_GET['id']); } break; case self::TYPE_BLACKLIST_ADD: if (!empty($_GET['id'])) { $this->cls('Crawler_Map')->blacklist_add($_GET['id']); } break; case self::TYPE_START: // Handle the ajax request to proceed crawler manually by admin self::start_async(); break; case self::TYPE_RESET: $this->reset_pos(); break; default: break; } Admin::redirect(); } } src/optimize.cls.php 0000644 00000111727 15246276230 0010504 0 ustar 00 <?php /** * The optimize class. * * @since 1.2.2 */ namespace LiteSpeed; defined('WPINC') || exit(); class Optimize extends Base { const LIB_FILE_CSS_ASYNC = 'assets/js/css_async.min.js'; const LIB_FILE_WEBFONTLOADER = 'assets/js/webfontloader.min.js'; const LIB_FILE_JS_DELAY = 'assets/js/js_delay.min.js'; const ITEM_TIMESTAMP_PURGE_CSS = 'timestamp_purge_css'; private $content; private $content_ori; private $cfg_css_min; private $cfg_css_comb; private $cfg_js_min; private $cfg_js_comb; private $cfg_css_async; private $cfg_js_delay_inc = array(); private $cfg_js_defer; private $cfg_js_defer_exc = false; private $cfg_ggfonts_async; private $_conf_css_font_display; private $cfg_ggfonts_rm; private $dns_prefetch; private $dns_preconnect; private $_ggfonts_urls = array(); private $_ccss; private $_ucss = false; private $__optimizer; private $html_foot = ''; // The html info append to <body> private $html_head = ''; // The html info prepend to <body> private static $_var_i = 0; private $_var_preserve_js = array(); private $_request_url; /** * Constructor * @since 4.0 */ public function __construct() { Debug2::debug('[Optm] init'); $this->__optimizer = $this->cls('Optimizer'); } /** * Init optimizer * * @since 3.0 * @access protected */ public function init() { $this->cfg_css_async = defined('LITESPEED_GUEST_OPTM') || $this->conf(self::O_OPTM_CSS_ASYNC); if ($this->cfg_css_async) { if (!$this->cls('Cloud')->activated()) { Debug2::debug('[Optm] ❌ CCSS set to OFF due to QC not activated'); $this->cfg_css_async = false; } if ((defined('LITESPEED_GUEST_OPTM') || ($this->conf(self::O_OPTM_UCSS) && $this->conf(self::O_OPTM_CSS_COMB))) && $this->conf(self::O_OPTM_UCSS_INLINE)) { Debug2::debug('[Optm] ⚠️ CCSS set to OFF due to UCSS Inline'); $this->cfg_css_async = false; } } $this->cfg_js_defer = $this->conf(self::O_OPTM_JS_DEFER); if (defined('LITESPEED_GUEST_OPTM')) { $this->cfg_js_defer = 2; } if ($this->cfg_js_defer == 2) { add_filter( 'litespeed_optm_cssjs', function ($con, $file_type) { if ($file_type == 'js') { $con = str_replace('DOMContentLoaded', 'DOMContentLiteSpeedLoaded', $con); // $con = str_replace( 'addEventListener("load"', 'addEventListener("litespeedLoad"', $con ); } return $con; }, 20, 2 ); } // To remove emoji from WP if ($this->conf(self::O_OPTM_EMOJI_RM)) { $this->_emoji_rm(); } if ($this->conf(self::O_OPTM_QS_RM)) { add_filter('style_loader_src', array($this, 'remove_query_strings'), 999); add_filter('script_loader_src', array($this, 'remove_query_strings'), 999); } // GM JS exclude @since 4.1 if (defined('LITESPEED_GUEST_OPTM')) { $this->cfg_js_defer_exc = apply_filters('litespeed_optm_gm_js_exc', $this->conf(self::O_OPTM_GM_JS_EXC)); } else { /** * Exclude js from deferred setting * @since 1.5 */ if ($this->cfg_js_defer) { add_filter('litespeed_optm_js_defer_exc', array($this->cls('Data'), 'load_js_defer_exc')); $this->cfg_js_defer_exc = apply_filters('litespeed_optm_js_defer_exc', $this->conf(self::O_OPTM_JS_DEFER_EXC)); $this->cfg_js_delay_inc = apply_filters('litespeed_optm_js_delay_inc', $this->conf(self::O_OPTM_JS_DELAY_INC)); } } /** * Add vary filter for Role Excludes * @since 1.6 */ add_filter('litespeed_vary', array($this, 'vary_add_role_exclude')); /** * Prefetch DNS * @since 1.7.1 */ $this->_dns_prefetch_init(); /** * Preconnect * @since 5.6.1 */ $this->_dns_preconnect_init(); add_filter('litespeed_buffer_finalize', array($this, 'finalize'), 20); } /** * Exclude role from optimization filter * * @since 1.6 * @access public */ public function vary_add_role_exclude($vary) { if ($this->cls('Conf')->in_optm_exc_roles()) { $vary['role_exclude_optm'] = 1; } return $vary; } /** * Remove emoji from WP * * @since 1.4 * @since 2.9.8 Changed to private * @access private */ private function _emoji_rm() { remove_action('wp_head', 'print_emoji_detection_script', 7); remove_action('admin_print_scripts', 'print_emoji_detection_script'); remove_filter('the_content_feed', 'wp_staticize_emoji'); remove_filter('comment_text_rss', 'wp_staticize_emoji'); /** * Added for better result * @since 1.6.2.1 */ remove_action('wp_print_styles', 'print_emoji_styles'); remove_action('admin_print_styles', 'print_emoji_styles'); remove_filter('wp_mail', 'wp_staticize_emoji_for_email'); } /** * Delete file-based cache folder * * @since 2.1 * @access public */ public function rm_cache_folder($subsite_id = false) { if ($subsite_id) { file_exists(LITESPEED_STATIC_DIR . '/css/' . $subsite_id) && File::rrmdir(LITESPEED_STATIC_DIR . '/css/' . $subsite_id); file_exists(LITESPEED_STATIC_DIR . '/js/' . $subsite_id) && File::rrmdir(LITESPEED_STATIC_DIR . '/js/' . $subsite_id); return; } file_exists(LITESPEED_STATIC_DIR . '/css') && File::rrmdir(LITESPEED_STATIC_DIR . '/css'); file_exists(LITESPEED_STATIC_DIR . '/js') && File::rrmdir(LITESPEED_STATIC_DIR . '/js'); } /** * Remove QS * * @since 1.3 * @access public */ public function remove_query_strings($src) { if (strpos($src, '_litespeed_rm_qs=0') || strpos($src, '/recaptcha')) { return $src; } if (!Utility::is_internal_file($src)) { return $src; } if (strpos($src, '.js?') !== false || strpos($src, '.css?') !== false) { $src = preg_replace('/\?.*/', '', $src); } return $src; } /** * Run optimize process * NOTE: As this is after cache finalized, can NOT set any cache control anymore * * @since 1.2.2 * @access public * @return string The content that is after optimization */ public function finalize($content) { if (defined('LITESPEED_NO_PAGEOPTM')) { Debug2::debug2('[Optm] bypass: NO_PAGEOPTM const'); return $content; } if (!defined('LITESPEED_IS_HTML')) { Debug2::debug('[Optm] bypass: Not frontend HTML type'); return $content; } if (!defined('LITESPEED_GUEST_OPTM')) { if (!Control::is_cacheable()) { Debug2::debug('[Optm] bypass: Not cacheable'); return $content; } // Check if hit URI excludes add_filter('litespeed_optm_uri_exc', array($this->cls('Data'), 'load_optm_uri_exc')); $excludes = apply_filters('litespeed_optm_uri_exc', $this->conf(self::O_OPTM_EXC)); $result = Utility::str_hit_array($_SERVER['REQUEST_URI'], $excludes); if ($result) { Debug2::debug('[Optm] bypass: hit URI Excludes setting: ' . $result); return $content; } } Debug2::debug('[Optm] start'); $this->content_ori = $this->content = $content; $this->_optimize(); return $this->content; } /** * Optimize css src * * @since 1.2.2 * @access private */ private function _optimize() { global $wp; $this->_request_url = get_permalink(); // Backup, in case get_permalink() fails. if (!$this->_request_url) { $this->_request_url = home_url($wp->request); } $this->cfg_css_min = defined('LITESPEED_GUEST_OPTM') || $this->conf(self::O_OPTM_CSS_MIN); $this->cfg_css_comb = defined('LITESPEED_GUEST_OPTM') || $this->conf(self::O_OPTM_CSS_COMB); $this->cfg_js_min = defined('LITESPEED_GUEST_OPTM') || $this->conf(self::O_OPTM_JS_MIN); $this->cfg_js_comb = defined('LITESPEED_GUEST_OPTM') || $this->conf(self::O_OPTM_JS_COMB); $this->cfg_ggfonts_rm = defined('LITESPEED_GUEST_OPTM') || $this->conf(self::O_OPTM_GGFONTS_RM); $this->cfg_ggfonts_async = !defined('LITESPEED_GUEST_OPTM') && $this->conf(self::O_OPTM_GGFONTS_ASYNC); // forced rm already $this->_conf_css_font_display = !defined('LITESPEED_GUEST_OPTM') && $this->conf(self::O_OPTM_CSS_FONT_DISPLAY); if (!$this->cls('Router')->can_optm()) { Debug2::debug('[Optm] bypass: admin/feed/preview'); return; } if ($this->cfg_css_async) { $this->_ccss = $this->cls('CSS')->prepare_ccss(); if (!$this->_ccss) { Debug2::debug('[Optm] ❌ CCSS set to OFF due to CCSS not generated yet'); $this->cfg_css_async = false; } elseif (strpos($this->_ccss, '<style id="litespeed-ccss" data-error') === 0) { Debug2::debug('[Optm] ❌ CCSS set to OFF due to CCSS failed to generate'); $this->cfg_css_async = false; } } do_action('litespeed_optm'); // Parse css from content $src_list = false; if ($this->cfg_css_min || $this->cfg_css_comb || $this->cfg_ggfonts_rm || $this->cfg_css_async || $this->cfg_ggfonts_async || $this->_conf_css_font_display) { add_filter('litespeed_optimize_css_excludes', array($this->cls('Data'), 'load_css_exc')); list($src_list, $html_list) = $this->_parse_css(); } // css optimizer if ($this->cfg_css_min || $this->cfg_css_comb) { if ($src_list) { // IF combine if ($this->cfg_css_comb) { // Check if has inline UCSS enabled or not if ((defined('LITESPEED_GUEST_OPTM') || $this->conf(self::O_OPTM_UCSS)) && $this->conf(self::O_OPTM_UCSS_INLINE)) { $filename = $this->cls('UCSS')->load($this->_request_url, true); if ($filename) { $filepath_prefix = $this->_build_filepath_prefix('ucss'); $this->_ucss = File::read(LITESPEED_STATIC_DIR . $filepath_prefix . $filename); // Drop all css $this->content = str_replace($html_list, '', $this->content); } } if (!$this->_ucss) { $url = $this->_build_hash_url($src_list); if ($url) { // Handle css async load if ($this->cfg_css_async) { $this->html_head .= '<link rel="preload" data-asynced="1" data-optimized="2" as="style" onload="this.onload=null;this.rel=\'stylesheet\'" href="' . Str::trim_quotes($url) . '" />'; // todo: How to use " in attr wrapper " } else { $this->html_head .= '<link data-optimized="2" rel="stylesheet" href="' . Str::trim_quotes($url) . '" />'; // use 2 as combined } // Move all css to top $this->content = str_replace($html_list, '', $this->content); } } } // Only minify elseif ($this->cfg_css_min) { // will handle async css load inside $this->_src_queue_handler($src_list, $html_list); } // Only HTTP2 push else { foreach ($src_list as $src_info) { if (!empty($src_info['inl'])) { continue; } } } } } // Handle css lazy load if not handled async loaded yet if ($this->cfg_css_async && !$this->cfg_css_min && !$this->cfg_css_comb) { // async html $html_list_async = $this->_async_css_list($html_list, $src_list); // Replace async css $this->content = str_replace($html_list, $html_list_async, $this->content); } // Parse js from buffer as needed $src_list = false; if ($this->cfg_js_min || $this->cfg_js_comb || $this->cfg_js_defer || $this->cfg_js_delay_inc) { add_filter('litespeed_optimize_js_excludes', array($this->cls('Data'), 'load_js_exc')); list($src_list, $html_list) = $this->_parse_js(); } // js optimizer if ($src_list) { // IF combine if ($this->cfg_js_comb) { $url = $this->_build_hash_url($src_list, 'js'); if ($url) { $this->html_foot .= $this->_build_js_tag($url); // Will move all JS to bottom combined one $this->content = str_replace($html_list, '', $this->content); } } // Only minify elseif ($this->cfg_js_min) { // Will handle js defer inside $this->_src_queue_handler($src_list, $html_list, 'js'); } // Only HTTP2 push and Defer else { foreach ($src_list as $k => $src_info) { // Inline JS if (!empty($src_info['inl'])) { if ($this->cfg_js_defer) { $attrs = !empty($src_info['attrs']) ? $src_info['attrs'] : ''; $deferred = $this->_js_inline_defer($src_info['src'], $attrs); if ($deferred) { $this->content = str_replace($html_list[$k], $deferred, $this->content); } } } // JS files else { if ($this->cfg_js_defer) { $deferred = $this->_js_defer($html_list[$k], $src_info['src']); if ($deferred) { $this->content = str_replace($html_list[$k], $deferred, $this->content); } } elseif ($this->cfg_js_delay_inc) { $deferred = $this->_js_delay($html_list[$k], $src_info['src']); if ($deferred) { $this->content = str_replace($html_list[$k], $deferred, $this->content); } } } } } } // Append JS inline var for preserved ESI // Shouldn't give any optm (defer/delay) @since 4.4 if ($this->_var_preserve_js) { $this->html_head .= '<script>var ' . implode(',', $this->_var_preserve_js) . ';</script>'; Debug2::debug2('[Optm] Inline JS defer vars', $this->_var_preserve_js); } // Append async compatibility lib to head if ($this->cfg_css_async) { // Inline css async lib if ($this->conf(self::O_OPTM_CSS_ASYNC_INLINE)) { $this->html_head .= $this->_build_js_inline(File::read(LSCWP_DIR . self::LIB_FILE_CSS_ASYNC), true); } else { $css_async_lib_url = LSWCP_PLUGIN_URL . self::LIB_FILE_CSS_ASYNC; $this->html_head .= $this->_build_js_tag($css_async_lib_url, 'litespeed-css-async-lib'); // Don't exclude it from defer for now } } /** * Handle google fonts async * This will result in a JS snippet in head, so need to put it in the end to avoid being replaced by JS parser */ $this->_async_ggfonts(); /** * Font display optm * @since 3.0 */ $this->_font_optm(); // Inject JS Delay lib $this->_maybe_js_delay(); /** * HTML Lazyload */ if ($this->conf(self::O_OPTM_HTML_LAZY)) { $this->html_head = $this->cls('CSS')->prepare_html_lazy() . $this->html_head; } // Maybe prepend inline UCSS if ($this->_ucss) { $this->html_head = '<style id="litespeed-ucss">' . $this->_ucss . '</style>' . $this->html_head; } // Check if there is any critical css rules setting if ($this->cfg_css_async && $this->_ccss) { $this->html_head = $this->_ccss . $this->html_head; } // Replace html head part $this->html_head = apply_filters('litespeed_optm_html_head', $this->html_head); if ($this->html_head) { if (apply_filters('litespeed_optm_html_after_head', false)) { $this->content = str_replace('</head>', $this->html_head . '</head>', $this->content); } else { // Put header content to be after charset if (strpos($this->content, '<meta charset') !== false) { $this->content = preg_replace('#<meta charset([^>]*)>#isU', '<meta charset$1>' . $this->html_head, $this->content, 1); } else { $this->content = preg_replace('#<head([^>]*)>#isU', '<head$1>' . $this->html_head, $this->content, 1); } } } // Replace html foot part $this->html_foot = apply_filters('litespeed_optm_html_foot', $this->html_foot); if ($this->html_foot) { $this->content = str_replace('</body>', $this->html_foot . '</body>', $this->content); } // Drop noscript if enabled if ($this->conf(self::O_OPTM_NOSCRIPT_RM)) { // $this->content = preg_replace( '#<noscript>.*</noscript>#isU', '', $this->content ); } // HTML minify if (defined('LITESPEED_GUEST_OPTM') || $this->conf(self::O_OPTM_HTML_MIN)) { $this->content = $this->__optimizer->html_min($this->content); } } /** * Build a full JS tag * * @since 4.0 */ private function _build_js_tag($src) { if ($this->cfg_js_defer === 2 || Utility::str_hit_array($src, $this->cfg_js_delay_inc)) { return '<script data-optimized="1" type="litespeed/javascript" data-src="' . Str::trim_quotes($src) . '"></script>'; } if ($this->cfg_js_defer) { return '<script data-optimized="1" src="' . Str::trim_quotes($src) . '" defer></script>'; } return '<script data-optimized="1" src="' . Str::trim_quotes($src) . '"></script>'; } /** * Build a full inline JS snippet * * @since 4.0 */ private function _build_js_inline($script, $minified = false) { if ($this->cfg_js_defer) { $deferred = $this->_js_inline_defer($script, false, $minified); if ($deferred) { return $deferred; } } return '<script>' . $script . '</script>'; } /** * Load JS delay lib * * @since 4.0 */ private function _maybe_js_delay() { if ($this->cfg_js_defer !== 2 && !$this->cfg_js_delay_inc) { return; } $this->html_foot .= '<script>' . File::read(LSCWP_DIR . self::LIB_FILE_JS_DELAY) . '</script>'; } /** * Google font async * * @since 2.7.3 * @access private */ private function _async_ggfonts() { if (!$this->cfg_ggfonts_async || !$this->_ggfonts_urls) { return; } Debug2::debug2('[Optm] google fonts async found: ', $this->_ggfonts_urls); $html = '<link rel="preconnect" href="https://fonts.gstatic.com/" crossorigin />'; /** * Append fonts * * Could be multiple fonts * * <link rel='stylesheet' href='//fonts.googleapis.com/css?family=Open+Sans%3A400%2C600%2C700%2C800%2C300&ver=4.9.8' type='text/css' media='all' /> * <link rel='stylesheet' href='//fonts.googleapis.com/css?family=PT+Sans%3A400%2C700%7CPT+Sans+Narrow%3A400%7CMontserrat%3A600&subset=latin&ver=4.9.8' type='text/css' media='all' /> * -> family: PT Sans:400,700|PT Sans Narrow:400|Montserrat:600 * <link rel='stylesheet' href='https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,300,300italic,400italic,600,700,900&subset=latin%2Clatin-ext' /> */ $script = 'WebFontConfig={google:{families:['; $families = array(); foreach ($this->_ggfonts_urls as $v) { $qs = wp_specialchars_decode($v); $qs = urldecode($qs); $qs = parse_url($qs, PHP_URL_QUERY); parse_str($qs, $qs); if (empty($qs['family'])) { Debug2::debug('[Optm] ERR ggfonts failed to find family: ' . $v); continue; } $subset = empty($qs['subset']) ? '' : ':' . $qs['subset']; foreach (array_filter(explode('|', $qs['family'])) as $v2) { $families[] = Str::trim_quotes($v2 . $subset); } } $script .= '"' . implode('","', $families) . ($this->_conf_css_font_display ? '&display=swap' : '') . '"'; $script .= ']}};'; // if webfontloader lib was loaded before WebFontConfig variable, call WebFont.load $script .= 'if ( typeof WebFont === "object" && typeof WebFont.load === "function" ) { WebFont.load( WebFontConfig ); }'; $html .= $this->_build_js_inline($script); // https://cdnjs.cloudflare.com/ajax/libs/webfont/1.6.28/webfontloader.js $webfont_lib_url = LSWCP_PLUGIN_URL . self::LIB_FILE_WEBFONTLOADER; // default async, if js defer set use defer $html .= $this->_build_js_tag($webfont_lib_url); // Put this in the very beginning for preconnect $this->html_head = $html . $this->html_head; } /** * Font optm * * @since 3.0 * @access private */ private function _font_optm() { if (!$this->_conf_css_font_display || !$this->_ggfonts_urls) { return; } Debug2::debug2('[Optm] google fonts optm ', $this->_ggfonts_urls); foreach ($this->_ggfonts_urls as $v) { if (strpos($v, 'display=')) { continue; } $this->html_head = str_replace($v, $v . '&display=swap', $this->html_head); $this->html_foot = str_replace($v, $v . '&display=swap', $this->html_foot); $this->content = str_replace($v, $v . '&display=swap', $this->content); } } /** * Prefetch DNS * * @since 1.7.1 * @access private */ private function _dns_prefetch_init() { // Widely enable link DNS prefetch if (defined('LITESPEED_GUEST_OPTM') || $this->conf(self::O_OPTM_DNS_PREFETCH_CTRL)) { @header('X-DNS-Prefetch-Control: on'); } $this->dns_prefetch = $this->conf(self::O_OPTM_DNS_PREFETCH); if (!$this->dns_prefetch) { return; } if (function_exists('wp_resource_hints')) { add_filter('wp_resource_hints', array($this, 'dns_prefetch_filter'), 10, 2); } else { add_action('litespeed_optm', array($this, 'dns_prefetch_output')); } } /** * Preconnect init * * @since 5.6.1 */ private function _dns_preconnect_init() { $this->dns_preconnect = $this->conf(self::O_OPTM_DNS_PRECONNECT); if ($this->dns_preconnect) { add_action('litespeed_optm', array($this, 'dns_preconnect_output')); } } /** * Prefetch DNS hook for WP * * @since 1.7.1 * @access public */ public function dns_prefetch_filter($urls, $relation_type) { if ($relation_type !== 'dns-prefetch') { return $urls; } foreach ($this->dns_prefetch as $v) { if ($v) { $urls[] = $v; } } return $urls; } /** * Prefetch DNS * * @since 1.7.1 * @access public */ public function dns_prefetch_output() { foreach ($this->dns_prefetch as $v) { if ($v) { $this->html_head .= '<link rel="dns-prefetch" href="' . Str::trim_quotes($v) . '" />'; } } } /** * Preconnect * * @since 5.6.1 * @access public */ public function dns_preconnect_output() { foreach ($this->dns_preconnect as $v) { if ($v) { $this->html_head .= '<link rel="preconnect" href="' . Str::trim_quotes($v) . '" />'; } } } /** * Run minify with src queue list * * @since 1.2.2 * @access private */ private function _src_queue_handler($src_list, $html_list, $file_type = 'css') { $html_list_ori = $html_list; $can_webp = $this->cls('Media')->webp_support(); $tag = $file_type == 'css' ? 'link' : 'script'; foreach ($src_list as $key => $src_info) { // Minify inline CSS/JS if (!empty($src_info['inl'])) { if ($file_type == 'css') { $code = Optimizer::minify_css($src_info['src']); $can_webp && ($code = $this->cls('Media')->replace_background_webp($code)); $snippet = str_replace($src_info['src'], $code, $html_list[$key]); } else { // Inline defer JS if ($this->cfg_js_defer) { $attrs = !empty($src_info['attrs']) ? $src_info['attrs'] : ''; $snippet = $this->_js_inline_defer($src_info['src'], $attrs) ?: $html_list[$key]; } else { $code = Optimizer::minify_js($src_info['src']); $snippet = str_replace($src_info['src'], $code, $html_list[$key]); } } } // CSS/JS files else { $url = $this->_build_single_hash_url($src_info['src'], $file_type); if ($url) { $snippet = str_replace($src_info['src'], $url, $html_list[$key]); } // Handle css async load if ($file_type == 'css' && $this->cfg_css_async) { $snippet = $this->_async_css($snippet); } // Handle js defer if ($file_type === 'js' && $this->cfg_js_defer) { $snippet = $this->_js_defer($snippet, $src_info['src']) ?: $snippet; } } $snippet = str_replace("<$tag ", '<' . $tag . ' data-optimized="1" ', $snippet); $html_list[$key] = $snippet; } $this->content = str_replace($html_list_ori, $html_list, $this->content); } /** * Build a single URL mapped filename (This will not save in DB) * @since 4.0 */ private function _build_single_hash_url($src, $file_type = 'css') { $content = $this->__optimizer->load_file($src, $file_type); $is_min = $this->__optimizer->is_min($src); $content = $this->__optimizer->optm_snippet($content, $file_type, !$is_min, $src); $filepath_prefix = $this->_build_filepath_prefix($file_type); // Save to file $filename = $filepath_prefix . md5($this->remove_query_strings($src)) . '.' . $file_type; $static_file = LITESPEED_STATIC_DIR . $filename; File::save($static_file, $content, true); // QS is required as $src may contains version info $qs_hash = substr(md5($src), -5); return LITESPEED_STATIC_URL . "$filename?ver=$qs_hash"; } /** * Generate full URL path with hash for a list of src * * @since 1.2.2 * @access private */ private function _build_hash_url($src_list, $file_type = 'css') { // $url_sensitive = $this->conf( self::O_OPTM_CSS_UNIQUE ) && $file_type == 'css'; // If need to keep unique CSS per URI // Replace preserved ESI (before generating hash) if ($file_type == 'js') { foreach ($src_list as $k => $v) { if (empty($v['inl'])) { continue; } $src_list[$k]['src'] = $this->_preserve_esi($v['src']); } } $minify = $file_type === 'css' ? $this->cfg_css_min : $this->cfg_js_min; $filename_info = $this->__optimizer->serve($this->_request_url, $file_type, $minify, $src_list); if (!$filename_info) { return false; // Failed to generate } list($filename, $type) = $filename_info; // Add cache tag in case later file deleted to avoid lscache served stale non-existed files @since 4.4.1 Tag::add(Tag::TYPE_MIN . '.' . $filename); $qs_hash = substr(md5(self::get_option(self::ITEM_TIMESTAMP_PURGE_CSS)), -5); // As filename is already related to filecon md5, no need QS anymore $filepath_prefix = $this->_build_filepath_prefix($type); return LITESPEED_STATIC_URL . $filepath_prefix . $filename . '?ver=' . $qs_hash; } /** * Parse js src * * @since 1.2.2 * @access private */ private function _parse_js() { $excludes = apply_filters('litespeed_optimize_js_excludes', $this->conf(self::O_OPTM_JS_EXC)); $combine_ext_inl = $this->conf(self::O_OPTM_JS_COMB_EXT_INL); if (!apply_filters('litespeed_optm_js_comb_ext_inl', true)) { Debug2::debug2('[Optm] js_comb_ext_inl bypassed via litespeed_optm_js_comb_ext_inl filter'); $combine_ext_inl = false; } $src_list = array(); $html_list = array(); // V7 added: (?:\r\n?|\n?) to fix replacement leaving empty new line $content = preg_replace('#<!--.*-->(?:\r\n?|\n?)#sU', '', $this->content); preg_match_all('#<script([^>]*)>(.*)</script>(?:\r\n?|\n?)#isU', $content, $matches, PREG_SET_ORDER); foreach ($matches as $match) { $attrs = empty($match[1]) ? array() : Utility::parse_attr($match[1]); if (isset($attrs['data-optimized'])) { continue; } if (!empty($attrs['data-no-optimize'])) { continue; } if (!empty($attrs['data-cfasync']) && $attrs['data-cfasync'] === 'false') { continue; } if (!empty($attrs['type']) && $attrs['type'] != 'text/javascript') { continue; } // to avoid multiple replacement if (in_array($match[0], $html_list)) { continue; } $this_src_arr = array(); // JS files if (!empty($attrs['src'])) { // Exclude check $js_excluded = Utility::str_hit_array($attrs['src'], $excludes); $is_internal = Utility::is_internal_file($attrs['src']); $is_file = substr($attrs['src'], 0, 5) != 'data:'; $ext_excluded = !$combine_ext_inl && !$is_internal; if ($js_excluded || $ext_excluded || !$is_file) { // Maybe defer if ($this->cfg_js_defer) { $deferred = $this->_js_defer($match[0], $attrs['src']); if ($deferred) { $this->content = str_replace($match[0], $deferred, $this->content); } } Debug2::debug2('[Optm] _parse_js bypassed due to ' . ($js_excluded ? 'js files excluded [hit] ' . $js_excluded : 'external js')); continue; } if (strpos($attrs['src'], '/localres/') !== false) { continue; } if (strpos($attrs['src'], 'instant_click') !== false) { continue; } $this_src_arr['src'] = $attrs['src']; } // Inline JS elseif (!empty($match[2])) { // Debug2::debug( '🌹🌹🌹 ' . $match[2] . '🌹' ); // Exclude check $js_excluded = Utility::str_hit_array($match[2], $excludes); if ($js_excluded || !$combine_ext_inl) { // Maybe defer if ($this->cfg_js_defer) { $deferred = $this->_js_inline_defer($match[2], $match[1]); if ($deferred) { $this->content = str_replace($match[0], $deferred, $this->content); } } Debug2::debug2('[Optm] _parse_js bypassed due to ' . ($js_excluded ? 'js excluded [hit] ' . $js_excluded : 'inline js')); continue; } $this_src_arr['inl'] = true; $this_src_arr['src'] = $match[2]; if ($match[1]) { $this_src_arr['attrs'] = $match[1]; } } else { // Compatibility to those who changed src to data-src already Debug2::debug2('[Optm] No JS src or inline JS content'); continue; } $src_list[] = $this_src_arr; $html_list[] = $match[0]; } return array($src_list, $html_list); } /** * Inline JS defer * * @since 3.0 * @access private */ private function _js_inline_defer($con, $attrs = false, $minified = false) { if (strpos($attrs, 'data-no-defer') !== false) { Debug2::debug2('[Optm] bypass: attr api data-no-defer'); return false; } $hit = Utility::str_hit_array($con, $this->cfg_js_defer_exc); if ($hit) { Debug2::debug2('[Optm] inline js defer excluded [setting] ' . $hit); return false; } $con = trim($con); // Minify JS first if (!$minified) { // && $this->cfg_js_defer !== 2 $con = Optimizer::minify_js($con); } if (!$con) { return false; } // Check if the content contains ESI nonce or not $con = $this->_preserve_esi($con); if ($this->cfg_js_defer === 2) { // Drop type attribute from $attrs if (strpos($attrs, ' type=') !== false) { $attrs = preg_replace('# type=([\'"])([^\1]+)\1#isU', '', $attrs); } // Replace DOMContentLoaded $con = str_replace('DOMContentLoaded', 'DOMContentLiteSpeedLoaded', $con); return '<script' . $attrs . ' type="litespeed/javascript">' . $con . '</script>'; // return '<script' . $attrs . ' type="litespeed/javascript" src="data:text/javascript;base64,' . base64_encode( $con ) . '"></script>'; // return '<script' . $attrs . ' type="litespeed/javascript">' . $con . '</script>'; } return '<script' . $attrs . ' src="data:text/javascript;base64,' . base64_encode($con) . '" defer></script>'; } /** * Replace ESI to JS inline var (mainly used to avoid nonce timeout) * * @since 3.5.1 */ private function _preserve_esi($con) { $esi_placeholder_list = $this->cls('ESI')->contain_preserve_esi($con); if (!$esi_placeholder_list) { return $con; } foreach ($esi_placeholder_list as $esi_placeholder) { $js_var = '__litespeed_var_' . self::$_var_i++ . '__'; $con = str_replace($esi_placeholder, $js_var, $con); $this->_var_preserve_js[] = $js_var . '=' . $esi_placeholder; } return $con; } /** * Parse css src and remove to-be-removed css * * @since 1.2.2 * @access private * @return array All the src & related raw html list */ private function _parse_css() { $excludes = apply_filters('litespeed_optimize_css_excludes', $this->conf(self::O_OPTM_CSS_EXC)); $ucss_file_exc_inline = apply_filters('litespeed_optimize_ucss_file_exc_inline', $this->conf(self::O_OPTM_UCSS_FILE_EXC_INLINE)); $combine_ext_inl = $this->conf(self::O_OPTM_CSS_COMB_EXT_INL); if (!apply_filters('litespeed_optm_css_comb_ext_inl', true)) { Debug2::debug2('[Optm] css_comb_ext_inl bypassed via litespeed_optm_css_comb_ext_inl filter'); $combine_ext_inl = false; } $css_to_be_removed = apply_filters('litespeed_optm_css_to_be_removed', array()); $src_list = array(); $html_list = array(); // $dom = new \PHPHtmlParser\Dom; // $dom->load( $content );return $val; // $items = $dom->find( 'link' ); // V7 added: (?:\r\n?|\n?) to fix replacement leaving empty new line $content = preg_replace( array('#<!--.*-->(?:\r\n?|\n?)#sU', '#<script([^>]*)>.*</script>(?:\r\n?|\n?)#isU', '#<noscript([^>]*)>.*</noscript>(?:\r\n?|\n?)#isU'), '', $this->content ); preg_match_all('#<link ([^>]+)/?>|<style([^>]*)>([^<]+)</style>(?:\r\n?|\n?)#isU', $content, $matches, PREG_SET_ORDER); foreach ($matches as $match) { // to avoid multiple replacement if (in_array($match[0], $html_list)) { continue; } if ($exclude = Utility::str_hit_array($match[0], $excludes)) { Debug2::debug2('[Optm] _parse_css bypassed exclude ' . $exclude); continue; } $this_src_arr = array(); if (strpos($match[0], '<link') === 0) { $attrs = Utility::parse_attr($match[1]); if (empty($attrs['rel']) || $attrs['rel'] !== 'stylesheet') { continue; } if (empty($attrs['href'])) { continue; } // Check if need to remove this css if (Utility::str_hit_array($attrs['href'], $css_to_be_removed)) { Debug2::debug('[Optm] rm css snippet ' . $attrs['href']); // Delete this css snippet from orig html $this->content = str_replace($match[0], '', $this->content); continue; } // Check if need to inline this css file if ($this->conf(self::O_OPTM_UCSS) && Utility::str_hit_array($attrs['href'], $ucss_file_exc_inline)) { Debug2::debug('[Optm] ucss_file_exc_inline hit ' . $attrs['href']); // Replace this css to inline from orig html $inline_script = '<style>' . $this->__optimizer->load_file($attrs['href']) . '</style>'; $this->content = str_replace($match[0], $inline_script, $this->content); continue; } // Check Google fonts hit if (strpos($attrs['href'], 'fonts.googleapis.com') !== false) { /** * For async gg fonts, will add webfont into head, hence remove it from buffer and store the matches to use later * @since 2.7.3 * @since 3.0 For font display optm, need to parse google fonts URL too */ if (!in_array($attrs['href'], $this->_ggfonts_urls)) { $this->_ggfonts_urls[] = $attrs['href']; } if ($this->cfg_ggfonts_rm || $this->cfg_ggfonts_async) { Debug2::debug('[Optm] rm css snippet [Google fonts] ' . $attrs['href']); $this->content = str_replace($match[0], '', $this->content); continue; } } if (isset($attrs['data-optimized'])) { // $this_src_arr[ 'exc' ] = true; continue; } elseif (!empty($attrs['data-no-optimize'])) { // $this_src_arr[ 'exc' ] = true; continue; } $is_internal = Utility::is_internal_file($attrs['href']); $ext_excluded = !$combine_ext_inl && !$is_internal; if ($ext_excluded) { Debug2::debug2('[Optm] Bypassed due to external link'); // Maybe defer if ($this->cfg_css_async) { $snippet = $this->_async_css($match[0]); if ($snippet != $match[0]) { $this->content = str_replace($match[0], $snippet, $this->content); } } continue; } if (!empty($attrs['media']) && $attrs['media'] !== 'all') { $this_src_arr['media'] = $attrs['media']; } $this_src_arr['src'] = $attrs['href']; } else { // Inline style if (!$combine_ext_inl) { Debug2::debug2('[Optm] Bypassed due to inline'); continue; } $attrs = Utility::parse_attr($match[2]); if (!empty($attrs['data-no-optimize'])) { continue; } if (!empty($attrs['media']) && $attrs['media'] !== 'all') { $this_src_arr['media'] = $attrs['media']; } $this_src_arr['inl'] = true; $this_src_arr['src'] = $match[3]; } $src_list[] = $this_src_arr; $html_list[] = $match[0]; } return array($src_list, $html_list); } /** * Replace css to async loaded css * * @since 1.3 * @access private */ private function _async_css_list($html_list, $src_list) { foreach ($html_list as $k => $ori) { if (!empty($src_list[$k]['inl'])) { continue; } $html_list[$k] = $this->_async_css($ori); } return $html_list; } /** * Async CSS snippet * @since 3.5 */ private function _async_css($ori) { if (strpos($ori, 'data-asynced') !== false) { Debug2::debug2('[Optm] bypass: attr data-asynced exist'); return $ori; } if (strpos($ori, 'data-no-async') !== false) { Debug2::debug2('[Optm] bypass: attr api data-no-async'); return $ori; } // async replacement $v = str_replace('stylesheet', 'preload', $ori); $v = str_replace('<link', '<link data-asynced="1" as="style" onload="this.onload=null;this.rel=\'stylesheet\'" ', $v); // Append to noscript content if (!defined('LITESPEED_GUEST_OPTM') && !$this->conf(self::O_OPTM_NOSCRIPT_RM)) { $v .= '<noscript>' . preg_replace('/ id=\'[\w-]+\' /U', ' ', $ori) . '</noscript>'; } return $v; } /** * Defer JS snippet * * @since 3.5 */ private function _js_defer($ori, $src) { if (strpos($ori, ' async') !== false) { $ori = preg_replace('# async(?:=([\'"])(?:[^\1]+)\1)?#isU', '', $ori); } if (strpos($ori, 'defer') !== false) { return false; } if (strpos($ori, 'data-deferred') !== false) { Debug2::debug2('[Optm] bypass: attr data-deferred exist'); return false; } if (strpos($ori, 'data-no-defer') !== false) { Debug2::debug2('[Optm] bypass: attr api data-no-defer'); return false; } /** * Exclude JS from setting * @since 1.5 */ if (Utility::str_hit_array($src, $this->cfg_js_defer_exc)) { Debug2::debug('[Optm] js defer exclude ' . $src); return false; } if ($this->cfg_js_defer === 2 || Utility::str_hit_array($src, $this->cfg_js_delay_inc)) { if (strpos($ori, ' type=') !== false) { $ori = preg_replace('# type=([\'"])([^\1]+)\1#isU', '', $ori); } return str_replace(' src=', ' type="litespeed/javascript" data-src=', $ori); } return str_replace('></script>', ' defer data-deferred="1"></script>', $ori); } /** * Delay JS for included setting * * @since 5.6 */ private function _js_delay($ori, $src) { if (strpos($ori, ' async') !== false) { $ori = str_replace(' async', '', $ori); } if (strpos($ori, 'defer') !== false) { return false; } if (strpos($ori, 'data-deferred') !== false) { Debug2::debug2('[Optm] bypass: attr data-deferred exist'); return false; } if (strpos($ori, 'data-no-defer') !== false) { Debug2::debug2('[Optm] bypass: attr api data-no-defer'); return false; } if (!Utility::str_hit_array($src, $this->cfg_js_delay_inc)) { return; } if (strpos($ori, ' type=') !== false) { $ori = preg_replace('# type=([\'"])([^\1]+)\1#isU', '', $ori); } return str_replace(' src=', ' type="litespeed/javascript" data-src=', $ori); } } src/admin.cls.php 0000644 00000010704 15246276230 0007725 0 ustar 00 <?php /** * The admin-panel specific functionality of the plugin. * * * @since 1.0.0 * @package LiteSpeed_Cache * @subpackage LiteSpeed_Cache/admin * @author LiteSpeed Technologies <info@litespeedtech.com> */ namespace LiteSpeed; defined('WPINC') || exit(); class Admin extends Root { const LOG_TAG = '👮'; const PAGE_EDIT_HTACCESS = 'litespeed-edit-htaccess'; /** * Initialize the class and set its properties. * Run in hook `after_setup_theme` when is_admin() * * @since 1.0.0 */ public function __construct() { // Define LSCWP_MU_PLUGIN if is mu-plugins if (defined('WPMU_PLUGIN_DIR') && dirname(LSCWP_DIR) == WPMU_PLUGIN_DIR) { define('LSCWP_MU_PLUGIN', true); } self::debug('No cache due to Admin page'); defined('DONOTCACHEPAGE') || define('DONOTCACHEPAGE', true); // Additional litespeed assets on admin display // Also register menu $this->cls('Admin_Display'); // initialize admin actions add_action('admin_init', array($this, 'admin_init')); // add link to plugin list page add_filter('plugin_action_links_' . LSCWP_BASENAME, array($this->cls('Admin_Display'), 'add_plugin_links')); } /** * Callback that initializes the admin options for LiteSpeed Cache. * * @since 1.0.0 * @access public */ public function admin_init() { // Hook attachment upload if ($this->conf(Base::O_IMG_OPTM_AUTO)) { add_filter('wp_update_attachment_metadata', array($this, 'wp_update_attachment_metadata'), 9999, 2); } $this->_proceed_admin_action(); // Terminate if user doesn't have the access to settings if (is_network_admin()) { $capability = 'manage_network_options'; } else { $capability = 'manage_options'; } if (!current_user_can($capability)) { return; } // Save setting from admin settings page // NOTE: cli will call `validate_plugin_settings` manually. Cron activation doesn't need to validate // Add privacy policy // @since 2.2.6 if (function_exists('wp_add_privacy_policy_content')) { wp_add_privacy_policy_content(Core::NAME, Doc::privacy_policy()); } $this->cls('Media')->after_admin_init(); do_action('litspeed_after_admin_init'); if ($this->cls('Router')->esi_enabled()) { add_action('in_widget_form', array($this->cls('Admin_Display'), 'show_widget_edit'), 100, 3); add_filter('widget_update_callback', __NAMESPACE__ . '\Admin_Settings::validate_widget_save', 10, 4); } } /** * Handle attachment update * @since 4.0 */ public function wp_update_attachment_metadata($data, $post_id) { $this->cls('Img_Optm')->wp_update_attachment_metadata($data, $post_id); return $data; } /** * Run litespeed admin actions * * @since 1.1.0 */ private function _proceed_admin_action() { // handle actions switch (Router::get_action()) { case Router::ACTION_SAVE_SETTINGS: $this->cls('Admin_Settings')->save($_POST); break; // Save network settings case Router::ACTION_SAVE_SETTINGS_NETWORK: $this->cls('Admin_Settings')->network_save($_POST); break; default: break; } } /** * Clean up the input string of any extra slashes/spaces. * * @since 1.0.4 * @access public * @param string $input The input string to clean. * @return string The cleaned up input. */ public static function cleanup_text($input) { if (is_array($input)) { return array_map(__CLASS__ . '::cleanup_text', $input); } return stripslashes(trim($input)); } /** * After a LSCWP_CTRL action, need to redirect back to the same page * without the nonce and action in the query string. * * If the redirect url cannot be determined, redirects to the homepage. * * @since 1.0.12 * @access public * @global string $pagenow */ public static function redirect($url = false) { global $pagenow; if (!empty($_GET['_litespeed_ori'])) { wp_safe_redirect(wp_get_referer() ?: get_home_url()); exit(); } $qs = ''; if (!$url) { if (!empty($_GET)) { if (isset($_GET[Router::ACTION])) { unset($_GET[Router::ACTION]); } if (isset($_GET[Router::NONCE])) { unset($_GET[Router::NONCE]); } if (isset($_GET[Router::TYPE])) { unset($_GET[Router::TYPE]); } if (isset($_GET['litespeed_i'])) { unset($_GET['litespeed_i']); } if (!empty($_GET)) { $qs = '?' . http_build_query($_GET); } } if (is_network_admin()) { $url = network_admin_url($pagenow . $qs); } else { $url = admin_url($pagenow . $qs); } } wp_redirect($url); exit(); } } src/cdn/cloudflare.cls.php 0000644 00000016030 15246276230 0011517 0 ustar 00 <?php /** * The cloudflare CDN class. * * @since 2.1 * @package LiteSpeed * @subpackage LiteSpeed/src/cdn * @author LiteSpeed Technologies <info@litespeedtech.com> */ namespace LiteSpeed\CDN; use LiteSpeed\Core; use LiteSpeed\Base; use LiteSpeed\Debug2; use LiteSpeed\Router; use LiteSpeed\Admin; use LiteSpeed\Admin_Display; defined('WPINC') || exit(); class Cloudflare extends Base { const TYPE_PURGE_ALL = 'purge_all'; const TYPE_GET_DEVMODE = 'get_devmode'; const TYPE_SET_DEVMODE_ON = 'set_devmode_on'; const TYPE_SET_DEVMODE_OFF = 'set_devmode_off'; const ITEM_STATUS = 'status'; /** * Update zone&name based on latest settings * * @since 3.0 * @access public */ public function try_refresh_zone() { if (!$this->conf(self::O_CDN_CLOUDFLARE)) { return; } $zone = $this->_fetch_zone(); if ($zone) { $this->cls('Conf')->update(self::O_CDN_CLOUDFLARE_NAME, $zone['name']); $this->cls('Conf')->update(self::O_CDN_CLOUDFLARE_ZONE, $zone['id']); Debug2::debug("[Cloudflare] Get zone successfully \t\t[ID] $zone[id]"); } else { $this->cls('Conf')->update(self::O_CDN_CLOUDFLARE_ZONE, ''); Debug2::debug('[Cloudflare] ❌ Get zone failed, clean zone'); } } /** * Get Cloudflare development mode * * @since 1.7.2 * @access private */ private function _get_devmode($show_msg = true) { Debug2::debug('[Cloudflare] _get_devmode'); $zone = $this->_zone(); if (!$zone) { return; } $url = 'https://api.cloudflare.com/client/v4/zones/' . $zone . '/settings/development_mode'; $res = $this->_cloudflare_call($url, 'GET', false, $show_msg); if (!$res) { return; } Debug2::debug('[Cloudflare] _get_devmode result ', $res); // Make sure is array: #992174 $curr_status = self::get_option(self::ITEM_STATUS, array()) ?: array(); $curr_status['devmode'] = $res['value']; $curr_status['devmode_expired'] = $res['time_remaining'] + time(); // update status self::update_option(self::ITEM_STATUS, $curr_status); } /** * Set Cloudflare development mode * * @since 1.7.2 * @access private */ private function _set_devmode($type) { Debug2::debug('[Cloudflare] _set_devmode'); $zone = $this->_zone(); if (!$zone) { return; } $url = 'https://api.cloudflare.com/client/v4/zones/' . $zone . '/settings/development_mode'; $new_val = $type == self::TYPE_SET_DEVMODE_ON ? 'on' : 'off'; $data = array('value' => $new_val); $res = $this->_cloudflare_call($url, 'PATCH', $data); if (!$res) { return; } $res = $this->_get_devmode(false); if ($res) { $msg = sprintf(__('Notified Cloudflare to set development mode to %s successfully.', 'litespeed-cache'), strtoupper($new_val)); Admin_Display::success($msg); } } /** * Purge Cloudflare cache * * @since 1.7.2 * @access private */ private function _purge_all() { Debug2::debug('[Cloudflare] _purge_all'); $cf_on = $this->conf(self::O_CDN_CLOUDFLARE); if (!$cf_on) { $msg = __('Cloudflare API is set to off.', 'litespeed-cache'); Admin_Display::error($msg); return; } $zone = $this->_zone(); if (!$zone) { return; } $url = 'https://api.cloudflare.com/client/v4/zones/' . $zone . '/purge_cache'; $data = array('purge_everything' => true); $res = $this->_cloudflare_call($url, 'DELETE', $data); if ($res) { $msg = __('Notified Cloudflare to purge all successfully.', 'litespeed-cache'); Admin_Display::success($msg); } } /** * Get current Cloudflare zone from cfg * * @since 1.7.2 * @access private */ private function _zone() { $zone = $this->conf(self::O_CDN_CLOUDFLARE_ZONE); if (!$zone) { $msg = __('No available Cloudflare zone', 'litespeed-cache'); Admin_Display::error($msg); return false; } return $zone; } /** * Get Cloudflare zone settings * * @since 1.7.2 * @access private */ private function _fetch_zone() { $kw = $this->conf(self::O_CDN_CLOUDFLARE_NAME); $url = 'https://api.cloudflare.com/client/v4/zones?status=active&match=all'; // Try exact match first if ($kw && strpos($kw, '.')) { $zones = $this->_cloudflare_call($url . '&name=' . $kw, 'GET', false, false); if ($zones) { Debug2::debug('[Cloudflare] fetch_zone exact matched'); return $zones[0]; } } // Can't find, try to get default one $zones = $this->_cloudflare_call($url, 'GET', false, false); if (!$zones) { Debug2::debug('[Cloudflare] fetch_zone no zone'); return false; } if (!$kw) { Debug2::debug('[Cloudflare] fetch_zone no set name, use first one by default'); return $zones[0]; } foreach ($zones as $v) { if (strpos($v['name'], $kw) !== false) { Debug2::debug('[Cloudflare] fetch_zone matched ' . $kw . ' [name] ' . $v['name']); return $v; } } // Can't match current name, return default one Debug2::debug('[Cloudflare] fetch_zone failed match name, use first one by default'); return $zones[0]; } /** * Cloudflare API * * @since 1.7.2 * @access private */ private function _cloudflare_call($url, $method = 'GET', $data = false, $show_msg = true) { Debug2::debug("[Cloudflare] _cloudflare_call \t\t[URL] $url"); if (40 == strlen($this->conf(self::O_CDN_CLOUDFLARE_KEY))) { $headers = array( 'Content-Type' => 'application/json', 'Authorization' => 'Bearer ' . $this->conf(self::O_CDN_CLOUDFLARE_KEY), ); } else { $headers = array( 'Content-Type' => 'application/json', 'X-Auth-Email' => $this->conf(self::O_CDN_CLOUDFLARE_EMAIL), 'X-Auth-Key' => $this->conf(self::O_CDN_CLOUDFLARE_KEY), ); } $wp_args = array( 'method' => $method, 'headers' => $headers, ); if ($data) { if (is_array($data)) { $data = \json_encode($data); } $wp_args['body'] = $data; } $resp = wp_remote_request($url, $wp_args); if (is_wp_error($resp)) { Debug2::debug('[Cloudflare] error in response'); if ($show_msg) { $msg = __('Failed to communicate with Cloudflare', 'litespeed-cache'); Admin_Display::error($msg); } return false; } $result = wp_remote_retrieve_body($resp); $json = \json_decode($result, true); if ($json && $json['success'] && $json['result']) { Debug2::debug('[Cloudflare] _cloudflare_call called successfully'); if ($show_msg) { $msg = __('Communicated with Cloudflare successfully.', 'litespeed-cache'); Admin_Display::success($msg); } return $json['result']; } Debug2::debug("[Cloudflare] _cloudflare_call called failed: $result"); if ($show_msg) { $msg = __('Failed to communicate with Cloudflare', 'litespeed-cache'); Admin_Display::error($msg); } return false; } /** * Handle all request actions from main cls * * @since 1.7.2 * @access public */ public function handler() { $type = Router::verify_type(); switch ($type) { case self::TYPE_PURGE_ALL: $this->_purge_all(); break; case self::TYPE_GET_DEVMODE: $this->_get_devmode(); break; case self::TYPE_SET_DEVMODE_ON: case self::TYPE_SET_DEVMODE_OFF: $this->_set_devmode($type); break; default: break; } Admin::redirect(); } } src/cdn/quic.cls.php 0000644 00000005700 15246276230 0010342 0 ustar 00 <?php /** * The quic.cloud class. * * @since 2.4.1 * @package LiteSpeed * @subpackage LiteSpeed/src/cdn * @author LiteSpeed Technologies <info@litespeedtech.com> */ namespace LiteSpeed\CDN; use LiteSpeed\Cloud; use LiteSpeed\Base; defined('WPINC') || exit(); class Quic extends Base { const LOG_TAG = '☁️'; const TYPE_REG = 'reg'; protected $_summary; private $_force = false; public function __construct() { $this->_summary = self::get_summary(); } /** * Notify CDN new config updated * * @access public */ public function try_sync_conf($force = false) { if ($force) { $this->_force = $force; } if (!$this->conf(self::O_CDN_QUIC)) { if (!empty($this->_summary['conf_md5'])) { self::debug('❌ No QC CDN, clear conf md5!'); self::save_summary(array('conf_md5' => '')); } return false; } // Notice: Sync conf must be after `wp_loaded` hook, to get 3rd party vary injected (e.g. `woocommerce_cart_hash`). if (!did_action('wp_loaded')) { add_action('wp_loaded', array($this, 'try_sync_conf'), 999); self::debug('WP not loaded yet, delay sync to wp_loaded:999'); return; } $options = $this->get_options(); $options['_tp_cookies'] = apply_filters('litespeed_vary_cookies', array()); // Build necessary options only $options_needed = array( self::O_CACHE_DROP_QS, self::O_CACHE_EXC_COOKIES, self::O_CACHE_EXC_USERAGENTS, self::O_CACHE_LOGIN_COOKIE, self::O_CACHE_VARY_COOKIES, self::O_CACHE_MOBILE_RULES, self::O_CACHE_MOBILE, self::O_CACHE_RES, self::O_CACHE_BROWSER, self::O_CACHE_TTL_BROWSER, self::O_IMG_OPTM_WEBP, self::O_GUEST, '_tp_cookies', ); $consts_needed = array('WP_CONTENT_DIR', 'LSCWP_CONTENT_DIR', 'LSCWP_CONTENT_FOLDER', 'LSWCP_TAG_PREFIX'); $options_for_md5 = array(); foreach ($options_needed as $v) { if (isset($options[$v])) { $options_for_md5[$v] = $options[$v]; // Remove overflow multi lines fields if (is_array($options_for_md5[$v]) && count($options_for_md5[$v]) > 30) { $options_for_md5[$v] = array_slice($options_for_md5[$v], 0, 30); } } } $server_vars = $this->server_vars(); foreach ($consts_needed as $v) { if (isset($server_vars[$v])) { if (empty($options_for_md5['_server'])) { $options_for_md5['_server'] = array(); } $options_for_md5['_server'][$v] = $server_vars[$v]; } } $conf_md5 = md5(\json_encode($options_for_md5)); if (!empty($this->_summary['conf_md5'])) { if ($conf_md5 == $this->_summary['conf_md5']) { if (!$this->_force) { self::debug('Bypass sync conf to QC due to same md5', $conf_md5); return; } self::debug('!!!Force sync conf even same md5'); } else { self::debug('[conf_md5] ' . $conf_md5 . ' [existing_conf_md5] ' . $this->_summary['conf_md5']); } } self::save_summary(array('conf_md5' => $conf_md5)); self::debug('sync conf to QC'); Cloud::post(Cloud::SVC_D_SYNC_CONF, $options_for_md5); } } src/health.cls.php 0000644 00000005622 15246276230 0010105 0 ustar 00 <?php /** * The page health * * * @since 3.0 * @package LiteSpeed * @subpackage LiteSpeed/src * @author LiteSpeed Technologies <info@litespeedtech.com> */ namespace LiteSpeed; defined('WPINC') || exit(); class Health extends Base { const TYPE_SPEED = 'speed'; const TYPE_SCORE = 'score'; protected $_summary; /** * Init * * @since 3.0 */ public function __construct() { $this->_summary = self::get_summary(); } /** * Test latest speed * * @since 3.0 */ private function _ping($type) { $data = array('action' => $type); $json = Cloud::post(Cloud::SVC_HEALTH, $data, 600); if (empty($json['data']['before']) || empty($json['data']['after'])) { Debug2::debug('[Health] ❌ no data'); return false; } $this->_summary[$type . '.before'] = $json['data']['before']; $this->_summary[$type . '.after'] = $json['data']['after']; self::save_summary(); Debug2::debug('[Health] saved result'); } /** * Generate scores * * @since 3.0 */ public function scores() { $speed_before = $speed_after = $speed_improved = 0; if (!empty($this->_summary['speed.before']) && !empty($this->_summary['speed.after'])) { // Format loading time $speed_before = $this->_summary['speed.before'] / 1000; if ($speed_before < 0.01) { $speed_before = 0.01; } $speed_before = number_format($speed_before, 2); $speed_after = $this->_summary['speed.after'] / 1000; if ($speed_after < 0.01) { $speed_after = number_format($speed_after, 3); } else { $speed_after = number_format($speed_after, 2); } $speed_improved = (($this->_summary['speed.before'] - $this->_summary['speed.after']) * 100) / $this->_summary['speed.before']; if ($speed_improved > 99) { $speed_improved = number_format($speed_improved, 2); } else { $speed_improved = number_format($speed_improved); } } $score_before = $score_after = $score_improved = 0; if (!empty($this->_summary['score.before']) && !empty($this->_summary['score.after'])) { $score_before = $this->_summary['score.before']; $score_after = $this->_summary['score.after']; // Format Score $score_improved = (($score_after - $score_before) * 100) / $score_after; if ($score_improved > 99) { $score_improved = number_format($score_improved, 2); } else { $score_improved = number_format($score_improved); } } return array( 'speed_before' => $speed_before, 'speed_after' => $speed_after, 'speed_improved' => $speed_improved, 'score_before' => $score_before, 'score_after' => $score_after, 'score_improved' => $score_improved, ); } /** * Handle all request actions from main cls * * @since 3.0 * @access public */ public function handler() { $type = Router::verify_type(); switch ($type) { case self::TYPE_SPEED: case self::TYPE_SCORE: $this->_ping($type); break; default: break; } Admin::redirect(); } } src/placeholder.cls.php 0000644 00000034130 15246276230 0011116 0 ustar 00 <?php /** * The PlaceHolder class * * @since 3.0 * @package LiteSpeed * @subpackage LiteSpeed/inc * @author LiteSpeed Technologies <info@litespeedtech.com> */ namespace LiteSpeed; defined('WPINC') || exit(); class Placeholder extends Base { const TYPE_GENERATE = 'generate'; const TYPE_CLEAR_Q = 'clear_q'; private $_conf_placeholder_resp; private $_conf_placeholder_resp_svg; private $_conf_lqip; private $_conf_lqip_qual; private $_conf_lqip_min_w; private $_conf_lqip_min_h; private $_conf_placeholder_resp_color; private $_conf_placeholder_resp_async; private $_conf_ph_default; private $_placeholder_resp_dict = array(); private $_ph_queue = array(); protected $_summary; /** * Init * * @since 3.0 */ public function __construct() { $this->_conf_placeholder_resp = defined('LITESPEED_GUEST_OPTM') || $this->conf(self::O_MEDIA_PLACEHOLDER_RESP); $this->_conf_placeholder_resp_svg = $this->conf(self::O_MEDIA_PLACEHOLDER_RESP_SVG); $this->_conf_lqip = !defined('LITESPEED_GUEST_OPTM') && $this->conf(self::O_MEDIA_LQIP); $this->_conf_lqip_qual = $this->conf(self::O_MEDIA_LQIP_QUAL); $this->_conf_lqip_min_w = $this->conf(self::O_MEDIA_LQIP_MIN_W); $this->_conf_lqip_min_h = $this->conf(self::O_MEDIA_LQIP_MIN_H); $this->_conf_placeholder_resp_async = $this->conf(self::O_MEDIA_PLACEHOLDER_RESP_ASYNC); $this->_conf_placeholder_resp_color = $this->conf(self::O_MEDIA_PLACEHOLDER_RESP_COLOR); $this->_conf_ph_default = $this->conf(self::O_MEDIA_LAZY_PLACEHOLDER) ?: LITESPEED_PLACEHOLDER; $this->_summary = self::get_summary(); } /** * Init Placeholder */ public function init() { Debug2::debug2('[LQIP] init'); add_action('litspeed_after_admin_init', array($this, 'after_admin_init')); } /** * Display column in Media * * @since 3.0 * @access public */ public function after_admin_init() { if ($this->_conf_lqip) { add_filter('manage_media_columns', array($this, 'media_row_title')); add_filter('manage_media_custom_column', array($this, 'media_row_actions'), 10, 2); add_action('litespeed_media_row_lqip', array($this, 'media_row_con')); } } /** * Media Admin Menu -> LQIP col * * @since 3.0 * @access public */ public function media_row_title($posts_columns) { $posts_columns['lqip'] = __('LQIP', 'litespeed-cache'); return $posts_columns; } /** * Media Admin Menu -> LQIP Column * * @since 3.0 * @access public */ public function media_row_actions($column_name, $post_id) { if ($column_name !== 'lqip') { return; } do_action('litespeed_media_row_lqip', $post_id); } /** * Display LQIP column * * @since 3.0 * @access public */ public function media_row_con($post_id) { $meta_value = wp_get_attachment_metadata($post_id); if (empty($meta_value['file'])) { return; } $total_files = 0; // List all sizes $all_sizes = array($meta_value['file']); $size_path = pathinfo($meta_value['file'], PATHINFO_DIRNAME) . '/'; foreach ($meta_value['sizes'] as $v) { $all_sizes[] = $size_path . $v['file']; } foreach ($all_sizes as $short_path) { $lqip_folder = LITESPEED_STATIC_DIR . '/lqip/' . $short_path; if (is_dir($lqip_folder)) { Debug2::debug('[LQIP] Found folder: ' . $short_path); // List all files foreach (scandir($lqip_folder) as $v) { if ($v == '.' || $v == '..') { continue; } if ($total_files == 0) { echo '<div class="litespeed-media-lqip"><img src="' . Str::trim_quotes(File::read($lqip_folder . '/' . $v)) . '" alt="' . sprintf(__('LQIP image preview for size %s', 'litespeed-cache'), $v) . '"></div>'; } echo '<div class="litespeed-media-size"><a href="' . Str::trim_quotes(File::read($lqip_folder . '/' . $v)) . '" target="_blank">' . $v . '</a></div>'; $total_files++; } } } if ($total_files == 0) { echo '—'; } } /** * Replace image with placeholder * * @since 3.0 * @access public */ public function replace($html, $src, $size) { // Check if need to enable responsive placeholder or not $this_placeholder = $this->_placeholder($src, $size) ?: $this->_conf_ph_default; $additional_attr = ''; if ($this->_conf_lqip && $this_placeholder != $this->_conf_ph_default) { Debug2::debug2('[LQIP] Use resp LQIP [size] ' . $size); $additional_attr = ' data-placeholder-resp="' . Str::trim_quotes($size) . '"'; } $snippet = defined('LITESPEED_GUEST_OPTM') || $this->conf(self::O_OPTM_NOSCRIPT_RM) ? '' : '<noscript>' . $html . '</noscript>'; $html = str_replace(array(' src=', ' srcset=', ' sizes='), array(' data-src=', ' data-srcset=', ' data-sizes='), $html); $html = str_replace('<img ', '<img data-lazyloaded="1"' . $additional_attr . ' src="' . Str::trim_quotes($this_placeholder) . '" ', $html); $snippet = $html . $snippet; return $snippet; } /** * Generate responsive placeholder * * @since 2.5.1 * @access private */ private function _placeholder($src, $size) { // Low Quality Image Placeholders if (!$size) { Debug2::debug2('[LQIP] no size ' . $src); return false; } if (!$this->_conf_placeholder_resp) { return false; } // If use local generator if (!$this->_conf_lqip || !$this->_lqip_size_check($size)) { return $this->_generate_placeholder_locally($size); } Debug2::debug2('[LQIP] Resp LQIP process [src] ' . $src . ' [size] ' . $size); $arr_key = $size . ' ' . $src; // Check if its already in dict or not if (!empty($this->_placeholder_resp_dict[$arr_key])) { Debug2::debug2('[LQIP] already in dict'); return $this->_placeholder_resp_dict[$arr_key]; } // Need to generate the responsive placeholder $placeholder_realpath = $this->_placeholder_realpath($src, $size); // todo: give offload API if (file_exists($placeholder_realpath)) { Debug2::debug2('[LQIP] file exists'); $this->_placeholder_resp_dict[$arr_key] = File::read($placeholder_realpath); return $this->_placeholder_resp_dict[$arr_key]; } // Add to cron queue // Prevent repeated requests if (in_array($arr_key, $this->_ph_queue)) { Debug2::debug2('[LQIP] file bypass generating due to in queue'); return $this->_generate_placeholder_locally($size); } if ($hit = Utility::str_hit_array($src, $this->conf(self::O_MEDIA_LQIP_EXC))) { Debug2::debug2('[LQIP] file bypass generating due to exclude setting [hit] ' . $hit); return $this->_generate_placeholder_locally($size); } $this->_ph_queue[] = $arr_key; // Send request to generate placeholder if (!$this->_conf_placeholder_resp_async) { // If requested recently, bypass if ($this->_summary && !empty($this->_summary['curr_request']) && time() - $this->_summary['curr_request'] < 300) { Debug2::debug2('[LQIP] file bypass generating due to interval limit'); return false; } // Generate immediately $this->_placeholder_resp_dict[$arr_key] = $this->_generate_placeholder($arr_key); return $this->_placeholder_resp_dict[$arr_key]; } // Prepare default svg placeholder as tmp placeholder $tmp_placeholder = $this->_generate_placeholder_locally($size); // Store it to prepare for cron $queue = $this->load_queue('lqip'); if (in_array($arr_key, $queue)) { Debug2::debug2('[LQIP] already in queue'); return $tmp_placeholder; } if (count($queue) > 500) { Debug2::debug2('[LQIP] queue is full'); return $tmp_placeholder; } $queue[] = $arr_key; $this->save_queue('lqip', $queue); Debug2::debug('[LQIP] Added placeholder queue'); return $tmp_placeholder; } /** * Generate realpath of placeholder file * * @since 2.5.1 * @access private */ private function _placeholder_realpath($src, $size) { // Use LQIP Cloud generator, each image placeholder will be separately stored // Compatibility with WebP and AVIF $src = Utility::drop_webp($src); $filepath_prefix = $this->_build_filepath_prefix('lqip'); // External images will use cache folder directly $domain = parse_url($src, PHP_URL_HOST); if ($domain && !Utility::internal($domain)) { // todo: need to improve `util:internal()` to include `CDN::internal()` $md5 = md5($src); return LITESPEED_STATIC_DIR . $filepath_prefix . 'remote/' . substr($md5, 0, 1) . '/' . substr($md5, 1, 1) . '/' . $md5 . '.' . $size; } // Drop domain $short_path = Utility::att_short_path($src); return LITESPEED_STATIC_DIR . $filepath_prefix . $short_path . '/' . $size; } /** * Cron placeholder generation * * @since 2.5.1 * @access public */ public static function cron($continue = false) { $_instance = self::cls(); $queue = $_instance->load_queue('lqip'); if (empty($queue)) { return; } // For cron, need to check request interval too if (!$continue) { if (!empty($_instance->_summary['curr_request']) && time() - $_instance->_summary['curr_request'] < 300) { Debug2::debug('[LQIP] Last request not done'); return; } } foreach ($queue as $v) { Debug2::debug('[LQIP] cron job [size] ' . $v); $res = $_instance->_generate_placeholder($v, true); // Exit queue if out of quota if ($res === 'out_of_quota') { return; } // only request first one if (!$continue) { return; } } } /** * Generate placeholder locally * * @since 3.0 * @access private */ private function _generate_placeholder_locally($size) { Debug2::debug2('[LQIP] _generate_placeholder local [size] ' . $size); $size = explode('x', $size); $svg = str_replace(array('{width}', '{height}', '{color}'), array($size[0], $size[1], $this->_conf_placeholder_resp_color), $this->_conf_placeholder_resp_svg); return 'data:image/svg+xml;base64,' . base64_encode($svg); } /** * Send to LiteSpeed API to generate placeholder * * @since 2.5.1 * @access private */ private function _generate_placeholder($raw_size_and_src, $from_cron = false) { // Parse containing size and src info $size_and_src = explode(' ', $raw_size_and_src, 2); $size = $size_and_src[0]; if (empty($size_and_src[1])) { $this->_popup_and_save($raw_size_and_src); Debug2::debug('[LQIP] ❌ No src [raw] ' . $raw_size_and_src); return $this->_generate_placeholder_locally($size); } $src = $size_and_src[1]; $file = $this->_placeholder_realpath($src, $size); // Local generate SVG to serve ( Repeatedly doing this here to remove stored cron queue in case the setting _conf_lqip is changed ) if (!$this->_conf_lqip || !$this->_lqip_size_check($size)) { $data = $this->_generate_placeholder_locally($size); } else { $err = false; $allowance = Cloud::cls()->allowance(Cloud::SVC_LQIP, $err); if (!$allowance) { Debug2::debug('[LQIP] ❌ No credit: ' . $err); $err && Admin_Display::error(Error::msg($err)); if ($from_cron) { return 'out_of_quota'; } return $this->_generate_placeholder_locally($size); } // Generate LQIP list($width, $height) = explode('x', $size); $req_data = array( 'width' => $width, 'height' => $height, 'url' => Utility::drop_webp($src), 'quality' => $this->_conf_lqip_qual, ); // CHeck if the image is 404 first if (File::is_404($req_data['url'])) { $this->_popup_and_save($raw_size_and_src, true); $this->_append_exc($src); Debug2::debug('[LQIP] 404 before request [src] ' . $req_data['url']); return $this->_generate_placeholder_locally($size); } // Update request status $this->_summary['curr_request'] = time(); self::save_summary(); $json = Cloud::post(Cloud::SVC_LQIP, $req_data, 120); if (!is_array($json)) { return $this->_generate_placeholder_locally($size); } if (empty($json['lqip']) || strpos($json['lqip'], 'data:image/svg+xml') !== 0) { // image error, pop up the current queue $this->_popup_and_save($raw_size_and_src, true); $this->_append_exc($src); Debug2::debug('[LQIP] wrong response format', $json); return $this->_generate_placeholder_locally($size); } $data = $json['lqip']; Debug2::debug('[LQIP] _generate_placeholder LQIP'); } // Write to file File::save($file, $data, true); // Save summary data $this->_summary['last_spent'] = time() - $this->_summary['curr_request']; $this->_summary['last_request'] = $this->_summary['curr_request']; $this->_summary['curr_request'] = 0; self::save_summary(); $this->_popup_and_save($raw_size_and_src); Debug2::debug('[LQIP] saved LQIP ' . $file); return $data; } /** * Check if the size is valid to send LQIP request or not * * @since 3.0 */ private function _lqip_size_check($size) { $size = explode('x', $size); if ($size[0] >= $this->_conf_lqip_min_w || $size[1] >= $this->_conf_lqip_min_h) { return true; } Debug2::debug2('[LQIP] Size too small'); return false; } /** * Add to LQIP exclude list * * @since 3.4 */ private function _append_exc($src) { $val = $this->conf(self::O_MEDIA_LQIP_EXC); $val[] = $src; $this->cls('Conf')->update(self::O_MEDIA_LQIP_EXC, $val); Debug2::debug('[LQIP] Appended to LQIP Excludes [URL] ' . $src); } /** * Pop up the current request and save * * @since 3.0 */ private function _popup_and_save($raw_size_and_src, $append_to_exc = false) { $queue = $this->load_queue('lqip'); if (!empty($queue) && in_array($raw_size_and_src, $queue)) { unset($queue[array_search($raw_size_and_src, $queue)]); } if ($append_to_exc) { $size_and_src = explode(' ', $raw_size_and_src, 2); $this_src = $size_and_src[1]; // Append to lqip exc setting first $this->_append_exc($this_src); // Check if other queues contain this src or not if ($queue) { foreach ($queue as $k => $raw_size_and_src) { $size_and_src = explode(' ', $raw_size_and_src, 2); if (empty($size_and_src[1])) { continue; } if ($size_and_src[1] == $this_src) { unset($queue[$k]); } } } } $this->save_queue('lqip', $queue); } /** * Handle all request actions from main cls * * @since 2.5.1 * @access public */ public function handler() { $type = Router::verify_type(); switch ($type) { case self::TYPE_GENERATE: self::cron(true); break; case self::TYPE_CLEAR_Q: $this->clear_q('lqip'); break; default: break; } Admin::redirect(); } } src/api.cls.php 0000644 00000026116 15246276230 0007412 0 ustar 00 <?php /** * The plugin API class. * * @since 1.1.3 * @since 1.4 Moved into /inc * @package LiteSpeed * @subpackage LiteSpeed/inc * @author LiteSpeed Technologies <info@litespeedtech.com> */ namespace LiteSpeed; defined('WPINC') || exit(); class API extends Base { const VERSION = Core::VER; const TYPE_FEED = Tag::TYPE_FEED; const TYPE_FRONTPAGE = Tag::TYPE_FRONTPAGE; const TYPE_HOME = Tag::TYPE_HOME; const TYPE_PAGES = Tag::TYPE_PAGES; const TYPE_PAGES_WITH_RECENT_POSTS = Tag::TYPE_PAGES_WITH_RECENT_POSTS; const TYPE_HTTP = Tag::TYPE_HTTP; const TYPE_ARCHIVE_POSTTYPE = Tag::TYPE_ARCHIVE_POSTTYPE; const TYPE_ARCHIVE_TERM = Tag::TYPE_ARCHIVE_TERM; const TYPE_AUTHOR = Tag::TYPE_AUTHOR; const TYPE_ARCHIVE_DATE = Tag::TYPE_ARCHIVE_DATE; const TYPE_BLOG = Tag::TYPE_BLOG; const TYPE_LOGIN = Tag::TYPE_LOGIN; const TYPE_URL = Tag::TYPE_URL; const TYPE_ESI = Tag::TYPE_ESI; const PARAM_NAME = ESI::PARAM_NAME; const WIDGET_O_ESIENABLE = ESI::WIDGET_O_ESIENABLE; const WIDGET_O_TTL = ESI::WIDGET_O_TTL; /** * Instance * * @since 3.0 */ public function __construct() { } /** * Define hooks to be used in other plugins. * * The benefit to use hooks other than functions is no need to detach if LSCWP enabled and function existed or not anymore * * @since 3.0 */ public function init() { /** * Init */ // Action `litespeed_init` // @previous API::hook_init( $hook ) /** * Conf */ add_filter('litespeed_conf', array($this, 'conf')); // @previous API::config($id) // Action `litespeed_conf_append` // @previous API::conf_append( $name, $default ) add_action('litespeed_conf_multi_switch', __NAMESPACE__ . '\Base::set_multi_switch', 10, 2); // Action ``litespeed_conf_force` // @previous API::force_option( $k, $v ) /** * Cache Control Hooks */ // Action `litespeed_control_finalize` // @previous API::hook_control($tags) && action `litespeed_api_control` add_action('litespeed_control_set_private', __NAMESPACE__ . '\Control::set_private'); // @previous API::set_cache_private() add_action('litespeed_control_set_nocache', __NAMESPACE__ . '\Control::set_nocache'); // @previous API::set_nocache( $reason = false ) add_action('litespeed_control_set_cacheable', array($this, 'set_cacheable')); // Might needed if not call hook `wp` // @previous API::set_cacheable( $reason ) add_action('litespeed_control_force_cacheable', __NAMESPACE__ . '\Control::force_cacheable'); // Set cache status to force cacheable ( Will ignore most kinds of non-cacheable conditions ) // @previous API::set_force_cacheable( $reason ) add_action('litespeed_control_force_public', __NAMESPACE__ . '\Control::set_public_forced'); // Set cache to force public cache if cacheable ( Will ignore most kinds of non-cacheable conditions ) // @previous API::set_force_public( $reason ) add_filter('litespeed_control_cacheable', __NAMESPACE__ . '\Control::is_cacheable', 3); // Note: Read-Only. Directly append to this filter won't work. Call actions above to set cacheable or not // @previous API::not_cacheable() add_action('litespeed_control_set_ttl', __NAMESPACE__ . '\Control::set_custom_ttl', 10, 2); // @previous API::set_ttl( $val ) add_filter('litespeed_control_ttl', array($this, 'get_ttl'), 3); // @previous API::get_ttl() /** * Tag Hooks */ // Action `litespeed_tag_finalize` // @previous API::hook_tag( $hook ) add_action('litespeed_tag', __NAMESPACE__ . '\Tag::add'); // Shorter alias of `litespeed_tag_add` add_action('litespeed_tag_post', __NAMESPACE__ . '\Tag::add_post'); // Shorter alias of `litespeed_tag_add_post` add_action('litespeed_tag_widget', __NAMESPACE__ . '\Tag::add_widget'); // Shorter alias of `litespeed_tag_add_widget` add_action('litespeed_tag_private', __NAMESPACE__ . '\Tag::add_private'); // Shorter alias of `litespeed_tag_add_private` add_action('litespeed_tag_private_esi', __NAMESPACE__ . '\Tag::add_private_esi'); // Shorter alias of `litespeed_tag_add_private_esi` add_action('litespeed_tag_add', __NAMESPACE__ . '\Tag::add'); // @previous API::tag_add( $tag ) add_action('litespeed_tag_add_post', __NAMESPACE__ . '\Tag::add_post'); add_action('litespeed_tag_add_widget', __NAMESPACE__ . '\Tag::add_widget'); add_action('litespeed_tag_add_private', __NAMESPACE__ . '\Tag::add_private'); // @previous API::tag_add_private( $tags ) add_action('litespeed_tag_add_private_esi', __NAMESPACE__ . '\Tag::add_private_esi'); /** * Purge Hooks */ // Action `litespeed_purge_finalize` // @previous API::hook_purge($tags) add_action('litespeed_purge', __NAMESPACE__ . '\Purge::add'); // @previous API::purge($tags) add_action('litespeed_purge_all', __NAMESPACE__ . '\Purge::purge_all'); add_action('litespeed_purge_post', array($this, 'purge_post')); // @previous API::purge_post( $pid ) add_action('litespeed_purge_posttype', __NAMESPACE__ . '\Purge::purge_posttype'); add_action('litespeed_purge_url', array($this, 'purge_url')); add_action('litespeed_purge_widget', __NAMESPACE__ . '\Purge::purge_widget'); add_action('litespeed_purge_esi', __NAMESPACE__ . '\Purge::purge_esi'); add_action('litespeed_purge_private', __NAMESPACE__ . '\Purge::add_private'); // @previous API::purge_private( $tags ) add_action('litespeed_purge_private_esi', __NAMESPACE__ . '\Purge::add_private_esi'); add_action('litespeed_purge_private_all', __NAMESPACE__ . '\Purge::add_private_all'); // @previous API::purge_private_all() // Action `litespeed_api_purge_post` // Triggered when purge a post // @previous API::hook_purge_post($hook) // Action `litespeed_purged_all` // Triggered after purged all. add_action('litespeed_purge_all_object', __NAMESPACE__ . '\Purge::purge_all_object'); add_action('litespeed_purge_ucss', __NAMESPACE__ . '\Purge::purge_ucss'); /** * ESI */ // Action `litespeed_nonce` // @previous API::nonce_action( $action ) & API::nonce( $action = -1, $defence_for_html_filter = true ) // NOTE: only available after `init` hook add_filter('litespeed_esi_status', array($this, 'esi_enabled')); // Get ESI enable status // @previous API::esi_enabled() add_filter('litespeed_esi_url', array($this, 'sub_esi_block'), 10, 8); // Generate ESI block url // @previous API::esi_url( $block_id, $wrapper, $params = array(), $control = 'private,no-vary', $silence = false, $preserved = false, $svar = false, $inline_val = false ) // Filter `litespeed_widget_default_options` // Hook widget default settings value. Currently used in Woo 3rd // @previous API::hook_widget_default_options( $hook ) // Filter `litespeed_esi_params` // @previous API::hook_esi_param( $hook ) // Action `litespeed_tpl_normal` // @previous API::hook_tpl_not_esi($hook) && Action `litespeed_is_not_esi_template` // Action `litespeed_esi_load-$block` // @usage add_action( 'litespeed_esi_load-' . $block, $hook ) // @previous API::hook_tpl_esi($block, $hook) add_action('litespeed_esi_combine', __NAMESPACE__ . '\ESI::combine'); /** * Vary * * To modify default vary, There are two ways: Action `litespeed_vary_append` or Filter `litespeed_vary` */ add_action('litespeed_vary_ajax_force', __NAMESPACE__ . '\Vary::can_ajax_vary'); // API::force_vary() -> Action `litespeed_vary_ajax_force` // Force finalize vary even if its in an AJAX call // Filter `litespeed_vary_curr_cookies` to generate current in use vary, which will be used for response vary header. // Filter `litespeed_vary_cookies` to register the final vary cookies, which will be written to rewrite rule. (litespeed_vary_curr_cookies are always equal to or less than litespeed_vary_cookies) // Filter `litespeed_vary` // Previous API::hook_vary_finalize( $hook ) add_action('litespeed_vary_no', __NAMESPACE__ . '\Control::set_no_vary'); // API::set_cache_no_vary() -> Action `litespeed_vary_no` // Set cache status to no vary // add_filter( 'litespeed_is_mobile', __NAMESPACE__ . '\Control::is_mobile' ); // API::set_mobile() -> Filter `litespeed_is_mobile` /** * Cloud */ add_filter('litespeed_is_from_cloud', array($this, 'is_from_cloud')); // Check if current request is from QC (usually its to check REST access) // @see https://wordpress.org/support/topic/image-optimization-not-working-3/ /** * Media */ add_action('litespeed_media_reset', __NAMESPACE__ . '\Media::delete_attachment'); // Reset one media row /** * GUI */ // API::clean_wrapper_begin( $counter = false ) -> Filter `litespeed_clean_wrapper_begin` // Start a to-be-removed html wrapper add_filter('litespeed_clean_wrapper_begin', __NAMESPACE__ . '\GUI::clean_wrapper_begin'); // API::clean_wrapper_end( $counter = false ) -> Filter `litespeed_clean_wrapper_end` // End a to-be-removed html wrapper add_filter('litespeed_clean_wrapper_end', __NAMESPACE__ . '\GUI::clean_wrapper_end'); /** * Mist */ add_action('litespeed_debug', __NAMESPACE__ . '\Debug2::debug', 10, 2); // API::debug()-> Action `litespeed_debug` add_action('litespeed_debug2', __NAMESPACE__ . '\Debug2::debug2', 10, 2); // API::debug2()-> Action `litespeed_debug2` add_action('litespeed_disable_all', array($this, '_disable_all')); // API::disable_all( $reason ) -> Action `litespeed_disable_all` add_action('litspeed_after_admin_init', array($this, '_after_admin_init')); } /** * API for admin related * * @since 3.0 * @access public */ public function _after_admin_init() { /** * GUI */ add_action('litespeed_setting_enroll', array($this->cls('Admin_Display'), 'enroll'), 10, 4); // API::enroll( $id ) // Register a field in setting form to save add_action('litespeed_build_switch', array($this->cls('Admin_Display'), 'build_switch')); // API::build_switch( $id ) // Build a switch div html snippet // API::hook_setting_content( $hook, $priority = 10, $args = 1 ) -> Action `litespeed_settings_content` // API::hook_setting_tab( $hook, $priority = 10, $args = 1 ) -> Action `litespeed_settings_tab` } /** * Disable All (Note: Not for direct call, always use Hooks) * * @since 2.9.7.2 * @access public */ public function _disable_all($reason) { do_action('litespeed_debug', '[API] Disabled_all due to ' . $reason); !defined('LITESPEED_DISABLE_ALL') && define('LITESPEED_DISABLE_ALL', true); } /** * @since 3.0 */ public static function vary_append_commenter() { Vary::cls()->append_commenter(); } /** * Check if is from Cloud * * @since 4.2 */ public function is_from_cloud() { return $this->cls('Cloud')->is_from_cloud(); } public function purge_post($pid) { $this->cls('Purge')->purge_post($pid); } public function purge_url($url) { $this->cls('Purge')->purge_url($url); } public function set_cacheable($reason = false) { $this->cls('Control')->set_cacheable($reason); } public function esi_enabled() { return $this->cls('Router')->esi_enabled(); } public function get_ttl() { return $this->cls('Control')->get_ttl(); } public function sub_esi_block( $block_id, $wrapper, $params = array(), $control = 'private,no-vary', $silence = false, $preserved = false, $svar = false, $inline_param = array() ) { return $this->cls('ESI')->sub_esi_block($block_id, $wrapper, $params, $control, $silence, $preserved, $svar, $inline_param); } } src/tag.cls.php 0000644 00000021633 15246276230 0007413 0 ustar 00 <?php /** * The plugin cache-tag class for X-LiteSpeed-Tag * * @since 1.1.3 * @since 1.5 Moved into /inc */ namespace LiteSpeed; defined('WPINC') || exit(); class Tag extends Root { const TYPE_FEED = 'FD'; const TYPE_FRONTPAGE = 'F'; const TYPE_HOME = 'H'; const TYPE_PAGES = 'PGS'; const TYPE_PAGES_WITH_RECENT_POSTS = 'PGSRP'; const TYPE_HTTP = 'HTTP.'; const TYPE_POST = 'Po.'; // Post. Cannot use P, reserved for litemage. const TYPE_ARCHIVE_POSTTYPE = 'PT.'; const TYPE_ARCHIVE_TERM = 'T.'; //for is_category|is_tag|is_tax const TYPE_AUTHOR = 'A.'; const TYPE_ARCHIVE_DATE = 'D.'; const TYPE_BLOG = 'B.'; const TYPE_LOGIN = 'L'; const TYPE_URL = 'URL.'; const TYPE_WIDGET = 'W.'; const TYPE_ESI = 'ESI.'; const TYPE_REST = 'REST'; const TYPE_AJAX = 'AJAX.'; const TYPE_LIST = 'LIST'; const TYPE_MIN = 'MIN'; const TYPE_LOCALRES = 'LOCALRES'; const X_HEADER = 'X-LiteSpeed-Tag'; private static $_tags = array(); private static $_tags_priv = array('tag_priv'); public static $error_code_tags = array(403, 404, 500); /** * Initialize * * @since 4.0 */ public function init() { // register recent posts widget tag before theme renders it to make it work add_filter('widget_posts_args', array($this, 'add_widget_recent_posts')); } /** * Check if the login page is cacheable. * If not, unset the cacheable member variable. * * NOTE: This is checked separately because login page doesn't go through WP logic. * * @since 1.0.0 * @access public */ public function check_login_cacheable() { if (!$this->conf(Base::O_CACHE_PAGE_LOGIN)) { return; } if (Control::isset_notcacheable()) { return; } if (!empty($_GET)) { Control::set_nocache('has GET request'); return; } $this->cls('Control')->set_cacheable(); self::add(self::TYPE_LOGIN); // we need to send lsc-cookie manually to make it be sent to all other users when is cacheable $list = headers_list(); if (empty($list)) { return; } foreach ($list as $hdr) { if (strncasecmp($hdr, 'set-cookie:', 11) == 0) { $cookie = substr($hdr, 12); @header('lsc-cookie: ' . $cookie, false); } } } /** * Register purge tag for pages with recent posts widget * of the plugin. * * @since 1.0.15 * @access public * @param array $params [wordpress params for widget_posts_args] */ public function add_widget_recent_posts($params) { self::add(self::TYPE_PAGES_WITH_RECENT_POSTS); return $params; } /** * Adds cache tags to the list of cache tags for the current page. * * @since 1.0.5 * @access public * @param mixed $tags A string or array of cache tags to add to the current list. */ public static function add($tags) { if (!is_array($tags)) { $tags = array($tags); } Debug2::debug('💰 [Tag] Add ', $tags); self::$_tags = array_merge(self::$_tags, $tags); // Send purge header immediately $tag_header = self::cls()->output(true); @header($tag_header); } /** * Add a post id to cache tag * * @since 3.0 * @access public */ public static function add_post($pid) { self::add(self::TYPE_POST . $pid); } /** * Add a widget id to cache tag * * @since 3.0 * @access public */ public static function add_widget($id) { self::add(self::TYPE_WIDGET . $id); } /** * Add a private ESI to cache tag * * @since 3.0 * @access public */ public static function add_private_esi($tag) { self::add_private(self::TYPE_ESI . $tag); } /** * Adds private cache tags to the list of cache tags for the current page. * * @since 1.6.3 * @access public * @param mixed $tags A string or array of cache tags to add to the current list. */ public static function add_private($tags) { if (!is_array($tags)) { $tags = array($tags); } self::$_tags_priv = array_merge(self::$_tags_priv, $tags); } /** * Return tags for Admin QS * * @since 1.1.3 * @access public */ public static function output_tags() { return self::$_tags; } /** * Will get a hash of the URI. Removes query string and appends a '/' if it is missing. * * @since 1.0.12 * @access public * @param string $uri The uri to get the hash of. * @param boolean $ori Return the original url or not * @return bool|string False on input error, hash otherwise. */ public static function get_uri_tag($uri, $ori = false) { $no_qs = strtok($uri, '?'); if (empty($no_qs)) { return false; } $slashed = trailingslashit($no_qs); // If only needs uri tag if ($ori) { return $slashed; } if (defined('LSCWP_LOG')) { return self::TYPE_URL . $slashed; } return self::TYPE_URL . md5($slashed); } /** * Get the unique tag based on self url. * * @since 1.1.3 * @access public * @param boolean $ori Return the original url or not */ public static function build_uri_tag($ori = false) { return self::get_uri_tag(urldecode($_SERVER['REQUEST_URI']), $ori); } /** * Gets the cache tags to set for the page. * * This includes site wide post types (e.g. front page) as well as * any third party plugin specific cache tags. * * @since 1.0.0 * @access private * @return array The list of cache tags to set. */ private static function _build_type_tags() { $tags = array(); $tags[] = Utility::page_type(); $tags[] = self::build_uri_tag(); if (is_front_page()) { $tags[] = self::TYPE_FRONTPAGE; } elseif (is_home()) { $tags[] = self::TYPE_HOME; } global $wp_query; if (isset($wp_query)) { $queried_obj_id = get_queried_object_id(); if (is_archive()) { //An Archive is a Category, Tag, Author, Date, Custom Post Type or Custom Taxonomy based pages. if (is_category() || is_tag() || is_tax()) { $tags[] = self::TYPE_ARCHIVE_TERM . $queried_obj_id; } elseif (is_post_type_archive() && ($post_type = get_post_type())) { $tags[] = self::TYPE_ARCHIVE_POSTTYPE . $post_type; } elseif (is_author()) { $tags[] = self::TYPE_AUTHOR . $queried_obj_id; } elseif (is_date()) { global $post; if ($post && isset($post->post_date)) { $date = $post->post_date; $date = strtotime($date); if (is_day()) { $tags[] = self::TYPE_ARCHIVE_DATE . date('Ymd', $date); } elseif (is_month()) { $tags[] = self::TYPE_ARCHIVE_DATE . date('Ym', $date); } elseif (is_year()) { $tags[] = self::TYPE_ARCHIVE_DATE . date('Y', $date); } } } } elseif (is_singular()) { //$this->is_singular = $this->is_single || $this->is_page || $this->is_attachment; $tags[] = self::TYPE_POST . $queried_obj_id; if (is_page()) { $tags[] = self::TYPE_PAGES; } } elseif (is_feed()) { $tags[] = self::TYPE_FEED; } } // Check REST API if (REST::cls()->is_rest()) { $tags[] = self::TYPE_REST; $path = !empty($_SERVER['SCRIPT_URL']) ? $_SERVER['SCRIPT_URL'] : false; if ($path) { // posts collections tag if (substr($path, -6) == '/posts') { $tags[] = self::TYPE_LIST; // Not used for purge yet } // single post tag global $post; if (!empty($post->ID) && substr($path, -strlen($post->ID) - 1) === '/' . $post->ID) { $tags[] = self::TYPE_POST . $post->ID; } // pages collections & single page tag if (stripos($path, '/pages') !== false) { $tags[] = self::TYPE_PAGES; } } } // Append AJAX action tag if (Router::is_ajax() && !empty($_REQUEST['action'])) { $tags[] = self::TYPE_AJAX . $_REQUEST['action']; } return $tags; } /** * Generate all cache tags before output * * @access private * @since 1.1.3 */ private static function _finalize() { // run 3rdparty hooks to tag do_action('litespeed_tag_finalize'); // generate wp tags if (!defined('LSCACHE_IS_ESI')) { $type_tags = self::_build_type_tags(); self::$_tags = array_merge(self::$_tags, $type_tags); } if (defined('LITESPEED_GUEST') && LITESPEED_GUEST) { self::$_tags[] = 'guest'; } // append blog main tag self::$_tags[] = ''; // removed duplicates self::$_tags = array_unique(self::$_tags); } /** * Sets up the Cache Tags header. * ONLY need to run this if is cacheable * * @since 1.1.3 * @access public * @return string empty string if empty, otherwise the cache tags header. */ public function output($no_finalize = false) { if (defined('LSCACHE_NO_CACHE') && LSCACHE_NO_CACHE) { return; } if (!$no_finalize) { self::_finalize(); } $prefix_tags = array(); /** * Only append blog_id when is multisite * @since 2.9.3 */ $prefix = LSWCP_TAG_PREFIX . (is_multisite() ? get_current_blog_id() : '') . '_'; // If is_private and has private tags, append them first, then specify prefix to `public` for public tags if (Control::is_private()) { foreach (self::$_tags_priv as $priv_tag) { $prefix_tags[] = $prefix . $priv_tag; } $prefix = 'public:' . $prefix; } foreach (self::$_tags as $tag) { $prefix_tags[] = $prefix . $tag; } $hdr = self::X_HEADER . ': ' . implode(',', $prefix_tags); return $hdr; } } src/object-cache.cls.php 0000644 00000037530 15246276230 0011152 0 ustar 00 <?php /** * The object cache class * * @since 1.8 * @package LiteSpeed * @subpackage LiteSpeed/inc * @author LiteSpeed Technologies <info@litespeedtech.com> */ namespace LiteSpeed; defined('WPINC') || exit(); require_once dirname(__DIR__) . '/autoload.php'; class Object_Cache extends Root { const O_DEBUG = 'debug'; const O_OBJECT = 'object'; const O_OBJECT_KIND = 'object-kind'; const O_OBJECT_HOST = 'object-host'; const O_OBJECT_PORT = 'object-port'; const O_OBJECT_LIFE = 'object-life'; const O_OBJECT_PERSISTENT = 'object-persistent'; const O_OBJECT_ADMIN = 'object-admin'; const O_OBJECT_TRANSIENTS = 'object-transients'; const O_OBJECT_DB_ID = 'object-db_id'; const O_OBJECT_USER = 'object-user'; const O_OBJECT_PSWD = 'object-pswd'; const O_OBJECT_GLOBAL_GROUPS = 'object-global_groups'; const O_OBJECT_NON_PERSISTENT_GROUPS = 'object-non_persistent_groups'; private $_conn; private $_cfg_debug; private $_cfg_enabled; private $_cfg_method; private $_cfg_host; private $_cfg_port; private $_cfg_life; private $_cfg_persistent; private $_cfg_admin; private $_cfg_transients; private $_cfg_db; private $_cfg_user; private $_cfg_pswd; private $_default_life = 360; private $_oc_driver = 'Memcached'; // Redis or Memcached private $_global_groups = array(); private $_non_persistent_groups = array(); /** * Init * * NOTE: this class may be included without initialized core * * @since 1.8 */ public function __construct($cfg = false) { if ($cfg) { if (!is_array($cfg[Base::O_OBJECT_GLOBAL_GROUPS])) { $cfg[Base::O_OBJECT_GLOBAL_GROUPS] = explode("\n", $cfg[Base::O_OBJECT_GLOBAL_GROUPS]); } if (!is_array($cfg[Base::O_OBJECT_NON_PERSISTENT_GROUPS])) { $cfg[Base::O_OBJECT_NON_PERSISTENT_GROUPS] = explode("\n", $cfg[Base::O_OBJECT_NON_PERSISTENT_GROUPS]); } $this->_cfg_debug = $cfg[Base::O_DEBUG] ? $cfg[Base::O_DEBUG] : false; $this->_cfg_method = $cfg[Base::O_OBJECT_KIND] ? true : false; $this->_cfg_host = $cfg[Base::O_OBJECT_HOST]; $this->_cfg_port = $cfg[Base::O_OBJECT_PORT]; $this->_cfg_life = $cfg[Base::O_OBJECT_LIFE]; $this->_cfg_persistent = $cfg[Base::O_OBJECT_PERSISTENT]; $this->_cfg_admin = $cfg[Base::O_OBJECT_ADMIN]; $this->_cfg_transients = $cfg[Base::O_OBJECT_TRANSIENTS]; $this->_cfg_db = $cfg[Base::O_OBJECT_DB_ID]; $this->_cfg_user = $cfg[Base::O_OBJECT_USER]; $this->_cfg_pswd = $cfg[Base::O_OBJECT_PSWD]; $this->_global_groups = $cfg[Base::O_OBJECT_GLOBAL_GROUPS]; $this->_non_persistent_groups = $cfg[Base::O_OBJECT_NON_PERSISTENT_GROUPS]; if ($this->_cfg_method) { $this->_oc_driver = 'Redis'; } $this->_cfg_enabled = $cfg[Base::O_OBJECT] && class_exists($this->_oc_driver) && $this->_cfg_host; } // If OC is OFF, will hit here to init OC after conf initialized elseif (defined('LITESPEED_CONF_LOADED')) { $this->_cfg_debug = $this->conf(Base::O_DEBUG) ? $this->conf(Base::O_DEBUG) : false; $this->_cfg_method = $this->conf(Base::O_OBJECT_KIND) ? true : false; $this->_cfg_host = $this->conf(Base::O_OBJECT_HOST); $this->_cfg_port = $this->conf(Base::O_OBJECT_PORT); $this->_cfg_life = $this->conf(Base::O_OBJECT_LIFE); $this->_cfg_persistent = $this->conf(Base::O_OBJECT_PERSISTENT); $this->_cfg_admin = $this->conf(Base::O_OBJECT_ADMIN); $this->_cfg_transients = $this->conf(Base::O_OBJECT_TRANSIENTS); $this->_cfg_db = $this->conf(Base::O_OBJECT_DB_ID); $this->_cfg_user = $this->conf(Base::O_OBJECT_USER); $this->_cfg_pswd = $this->conf(Base::O_OBJECT_PSWD); $this->_global_groups = $this->conf(Base::O_OBJECT_GLOBAL_GROUPS); $this->_non_persistent_groups = $this->conf(Base::O_OBJECT_NON_PERSISTENT_GROUPS); if ($this->_cfg_method) { $this->_oc_driver = 'Redis'; } $this->_cfg_enabled = $this->conf(Base::O_OBJECT) && class_exists($this->_oc_driver) && $this->_cfg_host; } elseif (defined('self::CONF_FILE') && file_exists(WP_CONTENT_DIR . '/' . self::CONF_FILE)) { // Get cfg from _data_file // Use self::const to avoid loading more classes $cfg = \json_decode(file_get_contents(WP_CONTENT_DIR . '/' . self::CONF_FILE), true); if (!empty($cfg[self::O_OBJECT_HOST])) { $this->_cfg_debug = !empty($cfg[Base::O_DEBUG]) ? $cfg[Base::O_DEBUG] : false; $this->_cfg_method = !empty($cfg[self::O_OBJECT_KIND]) ? $cfg[self::O_OBJECT_KIND] : false; $this->_cfg_host = $cfg[self::O_OBJECT_HOST]; $this->_cfg_port = $cfg[self::O_OBJECT_PORT]; $this->_cfg_life = !empty($cfg[self::O_OBJECT_LIFE]) ? $cfg[self::O_OBJECT_LIFE] : $this->_default_life; $this->_cfg_persistent = !empty($cfg[self::O_OBJECT_PERSISTENT]) ? $cfg[self::O_OBJECT_PERSISTENT] : false; $this->_cfg_admin = !empty($cfg[self::O_OBJECT_ADMIN]) ? $cfg[self::O_OBJECT_ADMIN] : false; $this->_cfg_transients = !empty($cfg[self::O_OBJECT_TRANSIENTS]) ? $cfg[self::O_OBJECT_TRANSIENTS] : false; $this->_cfg_db = !empty($cfg[self::O_OBJECT_DB_ID]) ? $cfg[self::O_OBJECT_DB_ID] : 0; $this->_cfg_user = !empty($cfg[self::O_OBJECT_USER]) ? $cfg[self::O_OBJECT_USER] : ''; $this->_cfg_pswd = !empty($cfg[self::O_OBJECT_PSWD]) ? $cfg[self::O_OBJECT_PSWD] : ''; $this->_global_groups = !empty($cfg[self::O_OBJECT_GLOBAL_GROUPS]) ? $cfg[self::O_OBJECT_GLOBAL_GROUPS] : array(); $this->_non_persistent_groups = !empty($cfg[self::O_OBJECT_NON_PERSISTENT_GROUPS]) ? $cfg[self::O_OBJECT_NON_PERSISTENT_GROUPS] : array(); if ($this->_cfg_method) { $this->_oc_driver = 'Redis'; } $this->_cfg_enabled = class_exists($this->_oc_driver) && $this->_cfg_host; } else { $this->_cfg_enabled = false; } } else { $this->_cfg_enabled = false; } } /** * Add debug. * * @since 6.3 * @access private */ private function debug_oc($text, $show_error = false) { if (defined('LSCWP_LOG')) { Debug2::debug($text); return; } if (!$show_error && $this->_cfg_debug != BASE::VAL_ON2) { return; } $LITESPEED_DATA_FOLDER = defined('LITESPEED_DATA_FOLDER') ? LITESPEED_DATA_FOLDER : 'litespeed'; $LSCWP_CONTENT_DIR = defined('LSCWP_CONTENT_DIR') ? LSCWP_CONTENT_DIR : WP_CONTENT_DIR; $LITESPEED_STATIC_DIR = $LSCWP_CONTENT_DIR . '/' . $LITESPEED_DATA_FOLDER; $log_path_prefix = $LITESPEED_STATIC_DIR . '/debug/'; $log_file = $log_path_prefix . Debug2::FilePath('debug'); if (file_exists($log_path_prefix . 'index.php') && file_exists($log_file)) { error_log(gmdate('m/d/y H:i:s') . ' - OC - ' . $text . PHP_EOL, 3, $log_file); } } /** * Get `Store Transients` setting value * * @since 1.8.3 * @access public */ public function store_transients($group) { return $this->_cfg_transients && $this->_is_transients_group($group); } /** * Check if the group belongs to transients or not * * @since 1.8.3 * @access private */ private function _is_transients_group($group) { return in_array($group, array('transient', 'site-transient')); } /** * Update WP object cache file config * * @since 1.8 * @access public */ public function update_file($options) { $changed = false; // NOTE: When included in oc.php, `LSCWP_DIR` will show undefined, so this must be assigned/generated when used $_oc_ori_file = LSCWP_DIR . 'lib/object-cache.php'; $_oc_wp_file = WP_CONTENT_DIR . '/object-cache.php'; // Update cls file if (!file_exists($_oc_wp_file) || md5_file($_oc_wp_file) !== md5_file($_oc_ori_file)) { $this->debug_oc('copying object-cache.php file to ' . $_oc_wp_file); copy($_oc_ori_file, $_oc_wp_file); $changed = true; } /** * Clear object cache */ if ($changed) { $this->_reconnect($options); } } /** * Remove object cache file * * @since 1.8.2 * @access public */ public function del_file() { // NOTE: When included in oc.php, `LSCWP_DIR` will show undefined, so this must be assigned/generated when used $_oc_ori_file = LSCWP_DIR . 'lib/object-cache.php'; $_oc_wp_file = WP_CONTENT_DIR . '/object-cache.php'; if (file_exists($_oc_wp_file) && md5_file($_oc_wp_file) === md5_file($_oc_ori_file)) { $this->debug_oc('removing ' . $_oc_wp_file); unlink($_oc_wp_file); } } /** * Try to build connection * * @since 1.8 * @access public */ public function test_connection() { return $this->_connect(); } /** * Force to connect with this setting * * @since 1.8 * @access private */ private function _reconnect($cfg) { $this->debug_oc('Reconnecting'); if (isset($this->_conn)) { // error_log( 'Object: Quitting existing connection!' ); $this->debug_oc('Quitting existing connection'); $this->flush(); $this->_conn = null; $this->cls(false, true); } $cls = $this->cls(false, false, $cfg); $cls->_connect(); if (isset($cls->_conn)) { $cls->flush(); } } /** * Connect to Memcached/Redis server * * @since 1.8 * @access private */ private function _connect() { if (isset($this->_conn)) { // error_log( 'Object: _connected' ); return true; } if (!class_exists($this->_oc_driver) || !$this->_cfg_host) { return null; } if (defined('LITESPEED_OC_FAILURE')) { return false; } $this->debug_oc('Init ' . $this->_oc_driver . ' connection to ' . $this->_cfg_host . ':' . $this->_cfg_port); $failed = false; /** * Connect to Redis * * @since 1.8.1 * @see https://github.com/phpredis/phpredis/#example-1 */ if ($this->_oc_driver == 'Redis') { set_error_handler('litespeed_exception_handler'); try { $this->_conn = new \Redis(); // error_log( 'Object: _connect Redis' ); if ($this->_cfg_persistent) { if ($this->_cfg_port) { $this->_conn->pconnect($this->_cfg_host, $this->_cfg_port); } else { $this->_conn->pconnect($this->_cfg_host); } } else { if ($this->_cfg_port) { $this->_conn->connect($this->_cfg_host, $this->_cfg_port); } else { $this->_conn->connect($this->_cfg_host); } } if ($this->_cfg_pswd) { if ($this->_cfg_user) { $this->_conn->auth(array($this->_cfg_user, $this->_cfg_pswd)); } else { $this->_conn->auth($this->_cfg_pswd); } } if ($this->_cfg_db) { $this->_conn->select($this->_cfg_db); } $res = $this->_conn->ping(); if ($res != '+PONG') { $failed = true; } } catch (\Exception $e) { $this->debug_oc('Redis connect exception: ' . $e->getMessage(), true); $failed = true; } catch (\ErrorException $e) { $this->debug_oc('Redis connect error: ' . $e->getMessage(), true); $failed = true; } restore_error_handler(); } else { // Connect to Memcached if ($this->_cfg_persistent) { $this->_conn = new \Memcached($this->_get_mem_id()); // Check memcached persistent connection if ($this->_validate_mem_server()) { // error_log( 'Object: _validate_mem_server' ); $this->debug_oc('Got persistent ' . $this->_oc_driver . ' connection'); return true; } $this->debug_oc('No persistent ' . $this->_oc_driver . ' server list!'); } else { // error_log( 'Object: new memcached!' ); $this->_conn = new \Memcached(); } $this->_conn->addServer($this->_cfg_host, (int) $this->_cfg_port); /** * Add SASL auth * @since 1.8.1 * @since 2.9.6 Fixed SASL connection @see https://www.litespeedtech.com/support/wiki/doku.php/litespeed_wiki:lsmcd:new_sasl */ if ($this->_cfg_user && $this->_cfg_pswd && method_exists($this->_conn, 'setSaslAuthData')) { $this->_conn->setOption(\Memcached::OPT_BINARY_PROTOCOL, true); $this->_conn->setOption(\Memcached::OPT_COMPRESSION, false); $this->_conn->setSaslAuthData($this->_cfg_user, $this->_cfg_pswd); } // Check connection if (!$this->_validate_mem_server()) { $failed = true; } } // If failed to connect if ($failed) { $this->debug_oc('❌ Failed to connect ' . $this->_oc_driver . ' server!', true); $this->_conn = null; $this->_cfg_enabled = false; !defined('LITESPEED_OC_FAILURE') && define('LITESPEED_OC_FAILURE', true); // error_log( 'Object: false!' ); return false; } $this->debug_oc('Connected'); return true; } /** * Check if the connected memcached host is the one in cfg * * @since 1.8 * @access private */ private function _validate_mem_server() { $mem_list = $this->_conn->getStats(); if (empty($mem_list)) { return false; } foreach ($mem_list as $k => $v) { if (substr($k, 0, strlen($this->_cfg_host)) != $this->_cfg_host) { continue; } if (!empty($v['pid']) || !empty($v['curr_connections'])) { return true; } } return false; } /** * Get memcached unique id to be used for connecting * * @since 1.8 * @access private */ private function _get_mem_id() { $mem_id = 'litespeed'; if (is_multisite()) { $mem_id .= '_' . get_current_blog_id(); } return $mem_id; } /** * Get cache * * @since 1.8 * @access public */ public function get($key) { if (!$this->_cfg_enabled) { return null; } if (!$this->_can_cache()) { return null; } if (!$this->_connect()) { return null; } $res = $this->_conn->get($key); return $res; } /** * Set cache * * @since 1.8 * @access public */ public function set($key, $data, $expire) { if (!$this->_cfg_enabled) { return null; } /** * To fix the Cloud callback cached as its frontend call but the hash is generated in backend * Bug found by Stan at Jan/10/2020 */ // if ( ! $this->_can_cache() ) { // return null; // } if (!$this->_connect()) { return null; } $ttl = $expire ?: $this->_cfg_life; if ($this->_oc_driver == 'Redis') { try { $res = $this->_conn->setEx($key, $ttl, $data); } catch (\RedisException $ex) { $res = false; $msg = sprintf(__('Redis encountered a fatal error: %s (code: %d)', 'litespeed-cache'), $ex->getMessage(), $ex->getCode()); $this->debug_oc($msg); Admin_Display::error($msg); } } else { $res = $this->_conn->set($key, $data, $ttl); } return $res; } /** * Check if can cache or not * * @since 1.8 * @access private */ private function _can_cache() { if (!$this->_cfg_admin && defined('WP_ADMIN')) { return false; } return true; } /** * Delete cache * * @since 1.8 * @access public */ public function delete($key) { if (!$this->_cfg_enabled) { return null; } if (!$this->_connect()) { return null; } if ($this->_oc_driver == 'Redis') { $res = $this->_conn->del($key); } else { $res = $this->_conn->delete($key); } return (bool) $res; } /** * Clear all cache * * @since 1.8 * @access public */ public function flush() { if (!$this->_cfg_enabled) { $this->debug_oc('bypass flushing'); return null; } if (!$this->_connect()) { return null; } $this->debug_oc('flush!'); if ($this->_oc_driver == 'Redis') { $res = $this->_conn->flushDb(); } else { $res = $this->_conn->flush(); $this->_conn->resetServerList(); } return $res; } /** * Add global groups * * @since 1.8 * @access public */ public function add_global_groups($groups) { if (!is_array($groups)) { $groups = array($groups); } $this->_global_groups = array_merge($this->_global_groups, $groups); $this->_global_groups = array_unique($this->_global_groups); } /** * Check if is in global groups or not * * @since 1.8 * @access public */ public function is_global($group) { return in_array($group, $this->_global_groups); } /** * Add non persistent groups * * @since 1.8 * @access public */ public function add_non_persistent_groups($groups) { if (!is_array($groups)) { $groups = array($groups); } $this->_non_persistent_groups = array_merge($this->_non_persistent_groups, $groups); $this->_non_persistent_groups = array_unique($this->_non_persistent_groups); } /** * Check if is in non persistent groups or not * * @since 1.8 * @access public */ public function is_non_persistent($group) { return in_array($group, $this->_non_persistent_groups); } } src/cdn.cls.php 0000644 00000032274 15246276230 0007407 0 ustar 00 <?php /** * The CDN class. * * @since 1.2.3 * @since 1.5 Moved into /inc * @package LiteSpeed * @subpackage LiteSpeed/inc * @author LiteSpeed Technologies <info@litespeedtech.com> */ namespace LiteSpeed; defined('WPINC') || exit(); class CDN extends Root { const BYPASS = 'LITESPEED_BYPASS_CDN'; private $content; private $_cfg_cdn; private $_cfg_url_ori; private $_cfg_ori_dir; private $_cfg_cdn_mapping = array(); private $_cfg_cdn_exclude; private $cdn_mapping_hosts = array(); /** * Init * * @since 1.2.3 */ public function init() { Debug2::debug2('[CDN] init'); if (defined(self::BYPASS)) { Debug2::debug2('CDN bypass'); return; } if (!Router::can_cdn()) { if (!defined(self::BYPASS)) { define(self::BYPASS, true); } return; } $this->_cfg_cdn = $this->conf(Base::O_CDN); if (!$this->_cfg_cdn) { if (!defined(self::BYPASS)) { define(self::BYPASS, true); } return; } $this->_cfg_url_ori = $this->conf(Base::O_CDN_ORI); // Parse cdn mapping data to array( 'filetype' => 'url' ) $mapping_to_check = array(Base::CDN_MAPPING_INC_IMG, Base::CDN_MAPPING_INC_CSS, Base::CDN_MAPPING_INC_JS); foreach ($this->conf(Base::O_CDN_MAPPING) as $v) { if (!$v[Base::CDN_MAPPING_URL]) { continue; } $this_url = $v[Base::CDN_MAPPING_URL]; $this_host = parse_url($this_url, PHP_URL_HOST); // Check img/css/js foreach ($mapping_to_check as $to_check) { if ($v[$to_check]) { Debug2::debug2('[CDN] mapping ' . $to_check . ' -> ' . $this_url); // If filetype to url is one to many, make url be an array $this->_append_cdn_mapping($to_check, $this_url); if (!in_array($this_host, $this->cdn_mapping_hosts)) { $this->cdn_mapping_hosts[] = $this_host; } } } // Check file types if ($v[Base::CDN_MAPPING_FILETYPE]) { foreach ($v[Base::CDN_MAPPING_FILETYPE] as $v2) { $this->_cfg_cdn_mapping[Base::CDN_MAPPING_FILETYPE] = true; // If filetype to url is one to many, make url be an array $this->_append_cdn_mapping($v2, $this_url); if (!in_array($this_host, $this->cdn_mapping_hosts)) { $this->cdn_mapping_hosts[] = $this_host; } } Debug2::debug2('[CDN] mapping ' . implode(',', $v[Base::CDN_MAPPING_FILETYPE]) . ' -> ' . $this_url); } } if (!$this->_cfg_url_ori || !$this->_cfg_cdn_mapping) { if (!defined(self::BYPASS)) { define(self::BYPASS, true); } return; } $this->_cfg_ori_dir = $this->conf(Base::O_CDN_ORI_DIR); // In case user customized upload path if (defined('UPLOADS')) { $this->_cfg_ori_dir[] = UPLOADS; } // Check if need preg_replace $this->_cfg_url_ori = Utility::wildcard2regex($this->_cfg_url_ori); $this->_cfg_cdn_exclude = $this->conf(Base::O_CDN_EXC); if (!empty($this->_cfg_cdn_mapping[Base::CDN_MAPPING_INC_IMG])) { // Hook to srcset if (function_exists('wp_calculate_image_srcset')) { add_filter('wp_calculate_image_srcset', array($this, 'srcset'), 999); } // Hook to mime icon add_filter('wp_get_attachment_image_src', array($this, 'attach_img_src'), 999); add_filter('wp_get_attachment_url', array($this, 'url_img'), 999); } if (!empty($this->_cfg_cdn_mapping[Base::CDN_MAPPING_INC_CSS])) { add_filter('style_loader_src', array($this, 'url_css'), 999); } if (!empty($this->_cfg_cdn_mapping[Base::CDN_MAPPING_INC_JS])) { add_filter('script_loader_src', array($this, 'url_js'), 999); } add_filter('litespeed_buffer_finalize', array($this, 'finalize'), 30); } /** * Associate all filetypes with url * * @since 2.0 * @access private */ private function _append_cdn_mapping($filetype, $url) { // If filetype to url is one to many, make url be an array if (empty($this->_cfg_cdn_mapping[$filetype])) { $this->_cfg_cdn_mapping[$filetype] = $url; } elseif (is_array($this->_cfg_cdn_mapping[$filetype])) { // Append url to filetype $this->_cfg_cdn_mapping[$filetype][] = $url; } else { // Convert _cfg_cdn_mapping from string to array $this->_cfg_cdn_mapping[$filetype] = array($this->_cfg_cdn_mapping[$filetype], $url); } } /** * If include css/js in CDN * * @since 1.6.2.1 * @return bool true if included in CDN */ public function inc_type($type) { if ($type == 'css' && !empty($this->_cfg_cdn_mapping[Base::CDN_MAPPING_INC_CSS])) { return true; } if ($type == 'js' && !empty($this->_cfg_cdn_mapping[Base::CDN_MAPPING_INC_JS])) { return true; } return false; } /** * Run CDN process * NOTE: As this is after cache finalized, can NOT set any cache control anymore * * @since 1.2.3 * @access public * @return string The content that is after optimization */ public function finalize($content) { $this->content = $content; $this->_finalize(); return $this->content; } /** * Replace CDN url * * @since 1.2.3 * @access private */ private function _finalize() { if (defined(self::BYPASS)) { return; } Debug2::debug('CDN _finalize'); // Start replacing img src if (!empty($this->_cfg_cdn_mapping[Base::CDN_MAPPING_INC_IMG])) { $this->_replace_img(); $this->_replace_inline_css(); } if (!empty($this->_cfg_cdn_mapping[Base::CDN_MAPPING_FILETYPE])) { $this->_replace_file_types(); } } /** * Parse all file types * * @since 1.2.3 * @access private */ private function _replace_file_types() { $ele_to_check = $this->conf(Base::O_CDN_ATTR); foreach ($ele_to_check as $v) { if (!$v || strpos($v, '.') === false) { Debug2::debug2('[CDN] replace setting bypassed: no . attribute ' . $v); continue; } Debug2::debug2('[CDN] replace attribute ' . $v); $v = explode('.', $v); $attr = preg_quote($v[1], '#'); if ($v[0]) { $pattern = '#<' . preg_quote($v[0], '#') . '([^>]+)' . $attr . '=([\'"])(.+)\g{2}#iU'; } else { $pattern = '# ' . $attr . '=([\'"])(.+)\g{1}#iU'; } preg_match_all($pattern, $this->content, $matches); if (empty($matches[$v[0] ? 3 : 2])) { continue; } foreach ($matches[$v[0] ? 3 : 2] as $k2 => $url) { // Debug2::debug2( '[CDN] check ' . $url ); $postfix = '.' . pathinfo((string) parse_url($url, PHP_URL_PATH), PATHINFO_EXTENSION); if (!array_key_exists($postfix, $this->_cfg_cdn_mapping)) { // Debug2::debug2( '[CDN] non-existed postfix ' . $postfix ); continue; } Debug2::debug2('[CDN] matched file_type ' . $postfix . ' : ' . $url); if (!($url2 = $this->rewrite($url, Base::CDN_MAPPING_FILETYPE, $postfix))) { continue; } $attr = str_replace($url, $url2, $matches[0][$k2]); $this->content = str_replace($matches[0][$k2], $attr, $this->content); } } } /** * Parse all images * * @since 1.2.3 * @access private */ private function _replace_img() { preg_match_all('#<img([^>]+?)src=([\'"\\\]*)([^\'"\s\\\>]+)([\'"\\\]*)([^>]*)>#i', $this->content, $matches); foreach ($matches[3] as $k => $url) { // Check if is a DATA-URI if (strpos($url, 'data:image') !== false) { continue; } if (!($url2 = $this->rewrite($url, Base::CDN_MAPPING_INC_IMG))) { continue; } $html_snippet = sprintf('<img %1$s src=%2$s %3$s>', $matches[1][$k], $matches[2][$k] . $url2 . $matches[4][$k], $matches[5][$k]); $this->content = str_replace($matches[0][$k], $html_snippet, $this->content); } } /** * Parse and replace all inline styles containing url() * * @since 1.2.3 * @access private */ private function _replace_inline_css() { Debug2::debug2('[CDN] _replace_inline_css', $this->_cfg_cdn_mapping); /** * Excludes `\` from URL matching * @see #959152 - WordPress LSCache CDN Mapping causing malformed URLS * @see #685485 * @since 3.0 */ preg_match_all('/url\((?![\'"]?data)[\'"]?(.+?)[\'"]?\)/i', $this->content, $matches); foreach ($matches[1] as $k => $url) { $url = str_replace(array(' ', '\t', '\n', '\r', '\0', '\x0B', '"', "'", '"', '''), '', $url); // Parse file postfix $parsed_url = parse_url($url, PHP_URL_PATH); if (!$parsed_url) { continue; } $postfix = '.' . pathinfo($parsed_url, PATHINFO_EXTENSION); if (array_key_exists($postfix, $this->_cfg_cdn_mapping)) { Debug2::debug2('[CDN] matched file_type ' . $postfix . ' : ' . $url); if (!($url2 = $this->rewrite($url, Base::CDN_MAPPING_FILETYPE, $postfix))) { continue; } } elseif (in_array($postfix, array('jpg', 'jpeg', 'png', 'gif', 'svg', 'webp', 'avif'))) { if (!($url2 = $this->rewrite($url, Base::CDN_MAPPING_INC_IMG))) { continue; } } else { continue; } $attr = str_replace($matches[1][$k], $url2, $matches[0][$k]); $this->content = str_replace($matches[0][$k], $attr, $this->content); } Debug2::debug2('[CDN] _replace_inline_css done'); } /** * Hook to wp_get_attachment_image_src * * @since 1.2.3 * @since 1.7 Removed static from function * @access public * @param array $img The URL of the attachment image src, the width, the height * @return array */ public function attach_img_src($img) { if ($img && ($url = $this->rewrite($img[0], Base::CDN_MAPPING_INC_IMG))) { $img[0] = $url; } return $img; } /** * Try to rewrite one URL with CDN * * @since 1.7 * @access public */ public function url_img($url) { if ($url && ($url2 = $this->rewrite($url, Base::CDN_MAPPING_INC_IMG))) { $url = $url2; } return $url; } /** * Try to rewrite one URL with CDN * * @since 1.7 * @access public */ public function url_css($url) { if ($url && ($url2 = $this->rewrite($url, Base::CDN_MAPPING_INC_CSS))) { $url = $url2; } return $url; } /** * Try to rewrite one URL with CDN * * @since 1.7 * @access public */ public function url_js($url) { if ($url && ($url2 = $this->rewrite($url, Base::CDN_MAPPING_INC_JS))) { $url = $url2; } return $url; } /** * Hook to replace WP responsive images * * @since 1.2.3 * @since 1.7 Removed static from function * @access public * @param array $srcs * @return array */ public function srcset($srcs) { if ($srcs) { foreach ($srcs as $w => $data) { if (!($url = $this->rewrite($data['url'], Base::CDN_MAPPING_INC_IMG))) { continue; } $srcs[$w]['url'] = $url; } } return $srcs; } /** * Replace URL to CDN URL * * @since 1.2.3 * @access public * @param string $url * @return string Replaced URL */ public function rewrite($url, $mapping_kind, $postfix = false) { Debug2::debug2('[CDN] rewrite ' . $url); $url_parsed = parse_url($url); if (empty($url_parsed['path'])) { Debug2::debug2('[CDN] -rewrite bypassed: no path'); return false; } // Only images under wp-cotnent/wp-includes can be replaced $is_internal_folder = Utility::str_hit_array($url_parsed['path'], $this->_cfg_ori_dir); if (!$is_internal_folder) { Debug2::debug2('[CDN] -rewrite failed: path not match: ' . LSCWP_CONTENT_FOLDER); return false; } // Check if is external url if (!empty($url_parsed['host'])) { if (!Utility::internal($url_parsed['host']) && !$this->_is_ori_url($url)) { Debug2::debug2('[CDN] -rewrite failed: host not internal'); return false; } } $exclude = Utility::str_hit_array($url, $this->_cfg_cdn_exclude); if ($exclude) { Debug2::debug2('[CDN] -abort excludes ' . $exclude); return false; } // Fill full url before replacement if (empty($url_parsed['host'])) { $url = Utility::uri2url($url); Debug2::debug2('[CDN] -fill before rewritten: ' . $url); $url_parsed = parse_url($url); } $scheme = !empty($url_parsed['scheme']) ? $url_parsed['scheme'] . ':' : ''; if ($scheme) { // Debug2::debug2( '[CDN] -scheme from url: ' . $scheme ); } // Find the mapping url to be replaced to if (empty($this->_cfg_cdn_mapping[$mapping_kind])) { return false; } if ($mapping_kind !== Base::CDN_MAPPING_FILETYPE) { $final_url = $this->_cfg_cdn_mapping[$mapping_kind]; } else { // select from file type $final_url = $this->_cfg_cdn_mapping[$postfix]; } // If filetype to url is one to many, need to random one if (is_array($final_url)) { $final_url = $final_url[array_rand($final_url)]; } // Now lets replace CDN url foreach ($this->_cfg_url_ori as $v) { if (strpos($v, '*') !== false) { $url = preg_replace('#' . $scheme . $v . '#iU', $final_url, $url); } else { $url = str_replace($scheme . $v, $final_url, $url); } } Debug2::debug2('[CDN] -rewritten: ' . $url); return $url; } /** * Check if is original URL of CDN or not * * @since 2.1 * @access private */ private function _is_ori_url($url) { $url_parsed = parse_url($url); $scheme = !empty($url_parsed['scheme']) ? $url_parsed['scheme'] . ':' : ''; foreach ($this->_cfg_url_ori as $v) { $needle = $scheme . $v; if (strpos($v, '*') !== false) { if (preg_match('#' . $needle . '#iU', $url)) { return true; } } else { if (strpos($url, $needle) === 0) { return true; } } } return false; } /** * Check if the host is the CDN internal host * * @since 1.2.3 * */ public static function internal($host) { if (defined(self::BYPASS)) { return false; } $instance = self::cls(); return in_array($host, $instance->cdn_mapping_hosts); // todo: can add $this->_is_ori_url() check in future } } src/core.cls.php 0000644 00000047526 15246276230 0007601 0 ustar 00 <?php /** * The core plugin class. * * Note: Core doesn't allow $this->cls( 'Core' ) * * @since 1.0.0 */ namespace LiteSpeed; defined('WPINC') || exit(); class Core extends Root { const NAME = 'LiteSpeed Cache'; const PLUGIN_NAME = 'litespeed-cache'; const PLUGIN_FILE = 'litespeed-cache/litespeed-cache.php'; const VER = LSCWP_V; const ACTION_DISMISS = 'dismiss'; const ACTION_PURGE_BY = 'PURGE_BY'; const ACTION_PURGE_EMPTYCACHE = 'PURGE_EMPTYCACHE'; const ACTION_QS_PURGE = 'PURGE'; const ACTION_QS_PURGE_SINGLE = 'PURGESINGLE'; // This will be same as `ACTION_QS_PURGE` (purge single url only) const ACTION_QS_SHOW_HEADERS = 'SHOWHEADERS'; const ACTION_QS_PURGE_ALL = 'purge_all'; const ACTION_QS_PURGE_EMPTYCACHE = 'empty_all'; const ACTION_QS_NOCACHE = 'NOCACHE'; const HEADER_DEBUG = 'X-LiteSpeed-Debug'; protected static $_debug_show_header = false; private $_footer_comment = ''; /** * Define the core functionality of the plugin. * * Set the plugin name and the plugin version that can be used throughout the plugin. * Load the dependencies, define the locale, and set the hooks for the admin area and * the public-facing side of the site. * * @since 1.0.0 */ public function __construct() { !defined('LSCWP_TS_0') && define('LSCWP_TS_0', microtime(true)); $this->cls('Conf')->init(); /** * Load API hooks * @since 3.0 */ $this->cls('API')->init(); if (defined('LITESPEED_ON')) { // Load third party detection if lscache enabled. include_once LSCWP_DIR . 'thirdparty/entry.inc.php'; } if ($this->conf(Base::O_DEBUG_DISABLE_ALL)) { !defined('LITESPEED_DISABLE_ALL') && define('LITESPEED_DISABLE_ALL', true); } /** * Register plugin activate/deactivate/uninstall hooks * NOTE: this can't be moved under after_setup_theme, otherwise activation will be bypassed somehow * @since 2.7.1 Disabled admin&CLI check to make frontend able to enable cache too */ // if( is_admin() || defined( 'LITESPEED_CLI' ) ) { $plugin_file = LSCWP_DIR . 'litespeed-cache.php'; register_activation_hook($plugin_file, array(__NAMESPACE__ . '\Activation', 'register_activation')); register_deactivation_hook($plugin_file, array(__NAMESPACE__ . '\Activation', 'register_deactivation')); register_uninstall_hook($plugin_file, __NAMESPACE__ . '\Activation::uninstall_litespeed_cache'); // } if (defined('LITESPEED_ON')) { // register purge_all actions $purge_all_events = $this->conf(Base::O_PURGE_HOOK_ALL); // purge all on upgrade if ($this->conf(Base::O_PURGE_ON_UPGRADE)) { $purge_all_events[] = 'automatic_updates_complete'; $purge_all_events[] = 'upgrader_process_complete'; $purge_all_events[] = 'admin_action_do-plugin-upgrade'; } foreach ($purge_all_events as $event) { // Don't allow hook to update_option bcos purge_all will cause infinite loop of update_option if (in_array($event, array('update_option'))) { continue; } add_action($event, __NAMESPACE__ . '\Purge::purge_all'); } // add_filter( 'upgrader_pre_download', 'Purge::filter_with_purge_all' ); // Add headers to site health check for full page cache // @since 5.4 add_filter('site_status_page_cache_supported_cache_headers', function ($cache_headers) { $is_cache_hit = function ($header_value) { return false !== strpos(strtolower($header_value), 'hit'); }; $cache_headers['x-litespeed-cache'] = $is_cache_hit; $cache_headers['x-lsadc-cache'] = $is_cache_hit; $cache_headers['x-qc-cache'] = $is_cache_hit; return $cache_headers; }); } add_action('after_setup_theme', array($this, 'init')); // Check if there is a purge request in queue if (!defined('LITESPEED_CLI')) { $purge_queue = Purge::get_option(Purge::DB_QUEUE); if ($purge_queue && $purge_queue != -1) { $this->_http_header($purge_queue); Debug2::debug('[Core] Purge Queue found&sent: ' . $purge_queue); } if ($purge_queue != -1) { Purge::update_option(Purge::DB_QUEUE, -1); // Use 0 to bypass purge while still enable db update as WP's update_option will check value===false to bypass update } $purge_queue = Purge::get_option(Purge::DB_QUEUE2); if ($purge_queue && $purge_queue != -1) { $this->_http_header($purge_queue); Debug2::debug('[Core] Purge2 Queue found&sent: ' . $purge_queue); } if ($purge_queue != -1) { Purge::update_option(Purge::DB_QUEUE2, -1); } } /** * Hook internal REST * @since 2.9.4 */ $this->cls('REST'); /** * Hook wpnonce function * * Note: ESI nonce won't be available until hook after_setup_theme ESI init due to Guest Mode concern * @since v4.1 */ if ($this->cls('Router')->esi_enabled() && !function_exists('wp_create_nonce')) { Debug2::debug('[ESI] Overwrite wp_create_nonce()'); litespeed_define_nonce_func(); } } /** * The plugin initializer. * * This function checks if the cache is enabled and ready to use, then determines what actions need to be set up based on the type of user and page accessed. Output is buffered if the cache is enabled. * * NOTE: WP user doesn't init yet * * @since 1.0.0 * @access public */ public function init() { /** * Added hook before init * 3rd party preload hooks will be fired here too (e.g. Divi disable all in edit mode) * @since 1.6.6 * @since 2.6 Added filter to all config values in Conf */ do_action('litespeed_init'); add_action('wp_ajax_async_litespeed', 'LiteSpeed\Task::async_litespeed_handler'); add_action('wp_ajax_nopriv_async_litespeed', 'LiteSpeed\Task::async_litespeed_handler'); // in `after_setup_theme`, before `init` hook $this->cls('Activation')->auto_update(); if (is_admin() && !(defined('DOING_AJAX') && DOING_AJAX)) { $this->cls('Admin'); } if (defined('LITESPEED_DISABLE_ALL') && LITESPEED_DISABLE_ALL) { Debug2::debug('[Core] Bypassed due to debug disable all setting'); return; } do_action('litespeed_initing'); ob_start(array($this, 'send_headers_force')); add_action('shutdown', array($this, 'send_headers'), 0); add_action('wp_footer', array($this, 'footer_hook')); /** * Check if is non optm simulator * @since 2.9 */ if (!empty($_GET[Router::ACTION]) && $_GET[Router::ACTION] == 'before_optm' && !apply_filters('litespeed_qs_forbidden', false)) { Debug2::debug('[Core] ⛑️ bypass_optm due to QS CTRL'); !defined('LITESPEED_NO_OPTM') && define('LITESPEED_NO_OPTM', true); } /** * Register vary filter * @since 1.6.2 */ $this->cls('Control')->init(); // 1. Init vary // 2. Init cacheable status // $this->cls('Vary')->init(); // Init Purge hooks $this->cls('Purge')->init(); $this->cls('Tag')->init(); // Load hooks that may be related to users add_action('init', array($this, 'after_user_init'), 5); // Load 3rd party hooks add_action('wp_loaded', array($this, 'load_thirdparty'), 2); // test: Simulate a purge all // if (defined( 'LITESPEED_CLI' )) Purge::add('test'.date('Ymd.His')); } /** * Run hooks after user init * * @since 2.9.8 * @access public */ public function after_user_init() { $this->cls('Router')->is_role_simulation(); // Detect if is Guest mode or not also $this->cls('Vary')->after_user_init(); /** * Preload ESI functionality for ESI request uri recovery * @since 1.8.1 * @since 4.0 ESI init needs to be after Guest mode detection to bypass ESI if is under Guest mode */ $this->cls('ESI')->init(); if (!is_admin() && !defined('LITESPEED_GUEST_OPTM') && ($result = $this->cls('Conf')->in_optm_exc_roles())) { Debug2::debug('[Core] ⛑️ bypass_optm: hit Role Excludes setting: ' . $result); !defined('LITESPEED_NO_OPTM') && define('LITESPEED_NO_OPTM', true); } // Heartbeat control $this->cls('Tool')->heartbeat(); /** * Backward compatibility for v4.2- @Ruikai * TODO: Will change to hook in future versions to make it revertable */ if (defined('LITESPEED_BYPASS_OPTM') && !defined('LITESPEED_NO_OPTM')) { define('LITESPEED_NO_OPTM', LITESPEED_BYPASS_OPTM); } if (!defined('LITESPEED_NO_OPTM') || !LITESPEED_NO_OPTM) { // Check missing static files $this->cls('Router')->serve_static(); $this->cls('Media')->init(); $this->cls('Placeholder')->init(); $this->cls('Router')->can_optm() && $this->cls('Optimize')->init(); $this->cls('Localization')->init(); // Hook cdn for attachments $this->cls('CDN')->init(); // load cron tasks $this->cls('Task')->init(); } // load litespeed actions if ($action = Router::get_action()) { $this->proceed_action($action); } // Load frontend GUI if (!is_admin()) { $this->cls('GUI')->init(); } } /** * Run frontend actions * * @since 1.1.0 * @access public */ public function proceed_action($action) { $msg = false; // handle actions switch ($action) { case self::ACTION_QS_SHOW_HEADERS: self::$_debug_show_header = true; break; case self::ACTION_QS_PURGE: case self::ACTION_QS_PURGE_SINGLE: Purge::set_purge_single(); break; case self::ACTION_QS_PURGE_ALL: Purge::purge_all(); break; case self::ACTION_PURGE_EMPTYCACHE: case self::ACTION_QS_PURGE_EMPTYCACHE: define('LSWCP_EMPTYCACHE', true); // clear all sites caches Purge::purge_all(); $msg = __('Notified LiteSpeed Web Server to purge everything.', 'litespeed-cache'); break; case self::ACTION_PURGE_BY: $this->cls('Purge')->purge_list(); $msg = __('Notified LiteSpeed Web Server to purge the list.', 'litespeed-cache'); break; case self::ACTION_DISMISS: // Even its from ajax, we don't need to register wp ajax callback function but directly use our action GUI::dismiss(); break; default: $msg = $this->cls('Router')->handler($action); break; } if ($msg && !Router::is_ajax()) { Admin_Display::add_notice(Admin_Display::NOTICE_GREEN, $msg); Admin::redirect(); return; } if (Router::is_ajax()) { exit(); } } /** * Callback used to call the detect third party action. * * The detect action is used by third party plugin integration classes to determine if they should add the rest of their hooks. * * @since 1.0.5 * @access public */ public function load_thirdparty() { do_action('litespeed_load_thirdparty'); } /** * Mark wp_footer called * * @since 1.3 * @access public */ public function footer_hook() { Debug2::debug('[Core] Footer hook called'); if (!defined('LITESPEED_FOOTER_CALLED')) { define('LITESPEED_FOOTER_CALLED', true); } } /** * Trigger comment info display hook * * @since 1.3 * @access private */ private function _check_is_html($buffer = null) { if (!defined('LITESPEED_FOOTER_CALLED')) { Debug2::debug2('[Core] CHK html bypass: miss footer const'); return; } if (defined('DOING_AJAX')) { Debug2::debug2('[Core] CHK html bypass: doing ajax'); return; } if (defined('DOING_CRON')) { Debug2::debug2('[Core] CHK html bypass: doing cron'); return; } if ($_SERVER['REQUEST_METHOD'] !== 'GET') { Debug2::debug2('[Core] CHK html bypass: not get method ' . $_SERVER['REQUEST_METHOD']); return; } if ($buffer === null) { $buffer = ob_get_contents(); } // double check to make sure it is a html file if (strlen($buffer) > 300) { $buffer = substr($buffer, 0, 300); } if (strstr($buffer, '<!--') !== false) { $buffer = preg_replace('/<!--.*?-->/s', '', $buffer); } $buffer = trim($buffer); $buffer = File::remove_zero_space($buffer); $is_html = stripos($buffer, '<html') === 0 || stripos($buffer, '<!DOCTYPE') === 0; if (!$is_html) { Debug2::debug('[Core] Footer check failed: ' . ob_get_level() . '-' . substr($buffer, 0, 100)); return; } Debug2::debug('[Core] Footer check passed'); if (!defined('LITESPEED_IS_HTML')) { define('LITESPEED_IS_HTML', true); } } /** * For compatibility with those plugins have 'Bad' logic that forced all buffer output even it is NOT their buffer :( * * Usually this is called after send_headers() if following original WP process * * @since 1.1.5 * @access public * @param string $buffer * @return string */ public function send_headers_force($buffer) { $this->_check_is_html($buffer); // Hook to modify buffer before $buffer = apply_filters('litespeed_buffer_before', $buffer); /** * Media: Image lazyload && WebP * GUI: Clean wrapper mainly for esi block NOTE: this needs to be before optimizer to avoid wrapper being removed * Optimize * CDN */ if (!defined('LITESPEED_NO_OPTM') || !LITESPEED_NO_OPTM) { Debug2::debug('[Core] run hook litespeed_buffer_finalize'); $buffer = apply_filters('litespeed_buffer_finalize', $buffer); } /** * Replace ESI preserved list * @since 3.3 Replace this in the end to avoid `Inline JS Defer` or other Page Optm features encoded ESI tags wrongly, which caused LSWS can't recognize ESI */ $buffer = $this->cls('ESI')->finalize($buffer); $this->send_headers(true); // Log ESI nonce buffer empty issue if (defined('LSCACHE_IS_ESI') && strlen($buffer) == 0) { // log ref for debug purpose error_log('ESI buffer empty ' . $_SERVER['REQUEST_URI']); } // Init comment info $running_info_showing = defined('LITESPEED_IS_HTML') || defined('LSCACHE_IS_ESI'); if (defined('LSCACHE_ESI_SILENCE')) { $running_info_showing = false; Debug2::debug('[Core] ESI silence'); } /** * Silence comment for json req * @since 2.9.3 */ if (REST::cls()->is_rest() || Router::is_ajax()) { $running_info_showing = false; Debug2::debug('[Core] Silence Comment due to REST/AJAX'); } $running_info_showing = apply_filters('litespeed_comment', $running_info_showing); if ($running_info_showing) { if ($this->_footer_comment) { $buffer .= $this->_footer_comment; } } /** * If ESI req is JSON, give the content JSON format * @since 2.9.3 * @since 2.9.4 ESI req could be from internal REST call, so moved json_encode out of this cond */ if (defined('LSCACHE_IS_ESI')) { Debug2::debug('[Core] ESI Start 👇'); if (strlen($buffer) > 500) { Debug2::debug(trim(substr($buffer, 0, 500)) . '.....'); } else { Debug2::debug($buffer); } Debug2::debug('[Core] ESI End 👆'); } if (apply_filters('litespeed_is_json', false)) { if (\json_decode($buffer, true) == null) { Debug2::debug('[Core] Buffer converting to JSON'); $buffer = \json_encode($buffer); $buffer = trim($buffer, '"'); } else { Debug2::debug('[Core] JSON Buffer'); } } // Hook to modify buffer after $buffer = apply_filters('litespeed_buffer_after', $buffer); Debug2::ended(); return $buffer; } /** * Sends the headers out at the end of processing the request. * * This will send out all LiteSpeed Cache related response headers needed for the post. * * @since 1.0.5 * @access public * @param boolean $is_forced If the header is sent following our normal finalizing logic */ public function send_headers($is_forced = false) { // Make sure header output only run once if (!defined('LITESPEED_DID_' . __FUNCTION__)) { define('LITESPEED_DID_' . __FUNCTION__, true); } else { return; } // Avoid PHP warning for header sent out already if (headers_sent()) { self::debug('❌ !!! Err: Header sent out already'); return; } $this->_check_is_html(); // NOTE: cache ctrl output needs to be done first, as currently some varies are added in 3rd party hook `litespeed_api_control`. $this->cls('Control')->finalize(); $vary_header = $this->cls('Vary')->finalize(); // If is not cacheable but Admin QS is `purge` or `purgesingle`, `tag` still needs to be generated $tag_header = $this->cls('Tag')->output(); if (!$tag_header && Control::is_cacheable()) { Control::set_nocache('empty tag header'); } // NOTE: `purge` output needs to be after `tag` output as Admin QS may need to send `tag` header $purge_header = Purge::output(); // generate `control` header in the end in case control status is changed by other headers. $control_header = $this->cls('Control')->output(); // Give one more break to avoid ff crash if (!defined('LSCACHE_IS_ESI')) { $this->_footer_comment .= "\n"; } $cache_support = 'supported'; if (defined('LITESPEED_ON')) { $cache_support = Control::is_cacheable() ? 'cached' : 'uncached'; } $this->_comment( sprintf( '%1$s %2$s by LiteSpeed Cache %4$s on %3$s', defined('LSCACHE_IS_ESI') ? 'Block' : 'Page', $cache_support, date('Y-m-d H:i:s', time() + LITESPEED_TIME_OFFSET), self::VER ) ); // send Control header if (defined('LITESPEED_ON') && $control_header) { $this->_http_header($control_header); if (!Control::is_cacheable()) { $this->_http_header('Cache-Control: no-cache, no-store, must-revalidate, max-age=0'); // @ref: https://wordpress.org/support/topic/apply_filterslitespeed_control_cacheable-returns-false-for-cacheable/ } if (defined('LSCWP_LOG')) { $this->_comment($control_header); } } // send PURGE header (Always send regardless of cache setting disabled/enabled) if (defined('LITESPEED_ON') && $purge_header) { $this->_http_header($purge_header); Debug2::log_purge($purge_header); if (defined('LSCWP_LOG')) { $this->_comment($purge_header); } } // send Vary header if (defined('LITESPEED_ON') && $vary_header) { $this->_http_header($vary_header); if (defined('LSCWP_LOG')) { $this->_comment($vary_header); } } if (defined('LITESPEED_ON') && defined('LSCWP_LOG')) { $vary = $this->cls('Vary')->finalize_full_varies(); if ($vary) { $this->_comment('Full varies: ' . $vary); } } // Admin QS show header action if (self::$_debug_show_header) { $debug_header = self::HEADER_DEBUG . ': '; if ($control_header) { $debug_header .= $control_header . '; '; } if ($purge_header) { $debug_header .= $purge_header . '; '; } if ($tag_header) { $debug_header .= $tag_header . '; '; } if ($vary_header) { $debug_header .= $vary_header . '; '; } $this->_http_header($debug_header); } else { // Control header if (defined('LITESPEED_ON') && Control::is_cacheable() && $tag_header) { $this->_http_header($tag_header); if (defined('LSCWP_LOG')) { $this->_comment($tag_header); } } } // Object cache _comment if (defined('LSCWP_LOG') && defined('LSCWP_OBJECT_CACHE') && method_exists('WP_Object_Cache', 'debug')) { $this->_comment('Object Cache ' . \WP_Object_Cache::get_instance()->debug()); } if (defined('LITESPEED_GUEST') && LITESPEED_GUEST) { $this->_comment('Guest Mode'); } if (!empty($this->_footer_comment)) { self::debug('[footer comment] ' . $this->_footer_comment); } if ($is_forced) { Debug2::debug('--forced--'); } /** * If is CLI and contains Purge Header, then issue a HTTP req to Purge * @since v5.3 */ if (defined('LITESPEED_CLI')) { $purge_queue = Purge::get_option(Purge::DB_QUEUE); if (!$purge_queue || $purge_queue == -1) { $purge_queue = Purge::get_option(Purge::DB_QUEUE2); } if ($purge_queue && $purge_queue != -1) { self::debug('[Core] Purge Queue found, issue a HTTP req to purge: ' . $purge_queue); // Kick off HTTP req $url = admin_url('admin-ajax.php'); $resp = wp_safe_remote_get($url); if (is_wp_error($resp)) { $error_message = $resp->get_error_message(); self::debug('[URL]' . $url); self::debug('failed to request: ' . $error_message); } else { self::debug('HTTP req res: ' . $resp['body']); } } } } /** * Append one HTML comment * @since 5.5 */ public static function comment($data) { self::cls()->_comment($data); } private function _comment($data) { $this->_footer_comment .= "\n<!-- " . $data . ' -->'; } /** * Send HTTP header * @since 5.3 */ private function _http_header($header) { if (defined('LITESPEED_CLI')) { return; } @header($header); if (!defined('LSCWP_LOG')) { return; } Debug2::debug('💰 ' . $header); } } src/avatar.cls.php 0000644 00000014107 15246276230 0010114 0 ustar 00 <?php /** * The avatar cache class * * @since 3.0 * @package LiteSpeed * @subpackage LiteSpeed/inc * @author LiteSpeed Technologies <info@litespeedtech.com> */ namespace LiteSpeed; defined('WPINC') || exit(); class Avatar extends Base { const TYPE_GENERATE = 'generate'; private $_conf_cache_ttl; private $_tb; private $_avatar_realtime_gen_dict = array(); protected $_summary; /** * Init * * @since 1.4 */ public function __construct() { if (!$this->conf(self::O_DISCUSS_AVATAR_CACHE)) { return; } Debug2::debug2('[Avatar] init'); $this->_tb = $this->cls('Data')->tb('avatar'); $this->_conf_cache_ttl = $this->conf(self::O_DISCUSS_AVATAR_CACHE_TTL); add_filter('get_avatar_url', array($this, 'crawl_avatar')); $this->_summary = self::get_summary(); } /** * Check if need db table or not * * @since 3.0 * @access public */ public function need_db() { if ($this->conf(self::O_DISCUSS_AVATAR_CACHE)) { return true; } return false; } /** * Get gravatar URL from DB and regenerate * * @since 3.0 * @access public */ public function serve_static($md5) { global $wpdb; Debug2::debug('[Avatar] is avatar request'); if (strlen($md5) !== 32) { Debug2::debug('[Avatar] wrong md5 ' . $md5); return; } $q = "SELECT url FROM `$this->_tb` WHERE md5=%s"; $url = $wpdb->get_var($wpdb->prepare($q, $md5)); if (!$url) { Debug2::debug('[Avatar] no matched url for md5 ' . $md5); return; } $url = $this->_generate($url); wp_redirect($url); exit(); } /** * Localize gravatar * * @since 3.0 * @access public */ public function crawl_avatar($url) { if (!$url) { return $url; } // Check if its already in dict or not if (!empty($this->_avatar_realtime_gen_dict[$url])) { Debug2::debug2('[Avatar] already in dict [url] ' . $url); return $this->_avatar_realtime_gen_dict[$url]; } $realpath = $this->_realpath($url); if (file_exists($realpath) && time() - filemtime($realpath) <= $this->_conf_cache_ttl) { Debug2::debug2('[Avatar] cache file exists [url] ' . $url); return $this->_rewrite($url, filemtime($realpath)); } if (!strpos($url, 'gravatar.com')) { return $url; } // Send request if (!empty($this->_summary['curr_request']) && time() - $this->_summary['curr_request'] < 300) { Debug2::debug2('[Avatar] Bypass generating due to interval limit [url] ' . $url); return $url; } // Generate immediately $this->_avatar_realtime_gen_dict[$url] = $this->_generate($url); return $this->_avatar_realtime_gen_dict[$url]; } /** * Read last time generated info * * @since 3.0 * @access public */ public function queue_count() { global $wpdb; // If var not exists, mean table not exists // todo: not true if (!$this->_tb) { return false; } $q = "SELECT COUNT(*) FROM `$this->_tb` WHERE dateline<" . (time() - $this->_conf_cache_ttl); return $wpdb->get_var($q); } /** * Get the final URL of local avatar * * Check from db also * * @since 3.0 */ private function _rewrite($url, $time = null) { return LITESPEED_STATIC_URL . '/avatar/' . $this->_filepath($url) . ($time ? '?ver=' . $time : ''); } /** * Generate realpath of the cache file * * @since 3.0 * @access private */ private function _realpath($url) { return LITESPEED_STATIC_DIR . '/avatar/' . $this->_filepath($url); } /** * Get filepath * * @since 4.0 */ private function _filepath($url) { $filename = md5($url) . '.jpg'; if (is_multisite()) { $filename = get_current_blog_id() . '/' . $filename; } return $filename; } /** * Cron generation * * @since 3.0 * @access public */ public static function cron($force = false) { global $wpdb; $_instance = self::cls(); if (!$_instance->queue_count()) { Debug2::debug('[Avatar] no queue'); return; } // For cron, need to check request interval too if (!$force) { if (!empty($_instance->_summary['curr_request']) && time() - $_instance->_summary['curr_request'] < 300) { Debug2::debug('[Avatar] curr_request too close'); return; } } $q = "SELECT url FROM `$_instance->_tb` WHERE dateline < %d ORDER BY id DESC LIMIT %d"; $q = $wpdb->prepare($q, array(time() - $_instance->_conf_cache_ttl, apply_filters('litespeed_avatar_limit', 30))); $list = $wpdb->get_results($q); Debug2::debug('[Avatar] cron job [count] ' . count($list)); foreach ($list as $v) { Debug2::debug('[Avatar] cron job [url] ' . $v->url); $_instance->_generate($v->url); } } /** * Remote generator * * @since 3.0 * @access private */ private function _generate($url) { global $wpdb; // Record the data $file = $this->_realpath($url); // Update request status self::save_summary(array('curr_request' => time())); // Generate $this->_maybe_mk_cache_folder('avatar'); $response = wp_safe_remote_get($url, array('timeout' => 180, 'stream' => true, 'filename' => $file)); Debug2::debug('[Avatar] _generate [url] ' . $url); // Parse response data if (is_wp_error($response)) { $error_message = $response->get_error_message(); file_exists($file) && unlink($file); Debug2::debug('[Avatar] failed to get: ' . $error_message); return $url; } // Save summary data self::save_summary(array( 'last_spent' => time() - $this->_summary['curr_request'], 'last_request' => $this->_summary['curr_request'], 'curr_request' => 0, )); // Update DB $md5 = md5($url); $q = "UPDATE `$this->_tb` SET dateline=%d WHERE md5=%s"; $existed = $wpdb->query($wpdb->prepare($q, array(time(), $md5))); if (!$existed) { $q = "INSERT INTO `$this->_tb` SET url=%s, md5=%s, dateline=%d"; $wpdb->query($wpdb->prepare($q, array($url, $md5, time()))); } Debug2::debug('[Avatar] saved avatar ' . $file); return $this->_rewrite($url); } /** * Handle all request actions from main cls * * @since 3.0 * @access public */ public function handler() { $type = Router::verify_type(); switch ($type) { case self::TYPE_GENERATE: self::cron(true); break; default: break; } Admin::redirect(); } } src/import.preset.cls.php 0000644 00000012670 15246276230 0011454 0 ustar 00 <?php /** * The preset class. * * @since 5.3.0 */ namespace LiteSpeed; defined('WPINC') || exit(); class Preset extends Import { protected $_summary; const MAX_BACKUPS = 10; const TYPE_APPLY = 'apply'; const TYPE_RESTORE = 'restore'; const STANDARD_DIR = LSCWP_DIR . 'data/preset'; const BACKUP_DIR = LITESPEED_STATIC_DIR . '/auto-backup'; /** * Returns sorted backup names * * @since 5.3.0 * @access public */ public static function get_backups() { self::init_filesystem(); global $wp_filesystem; $backups = array_map( function ($path) { return self::basename($path['name']); }, $wp_filesystem->dirlist(self::BACKUP_DIR) ?: array() ); rsort($backups); return $backups; } /** * Removes extra backup files * * @since 5.3.0 * @access public */ public static function prune_backups() { $backups = self::get_backups(); global $wp_filesystem; foreach (array_slice($backups, self::MAX_BACKUPS) as $backup) { $path = self::get_backup($backup); $wp_filesystem->delete($path); Debug2::debug('[Preset] Deleted old backup from ' . $backup); } } /** * Returns a settings file's extensionless basename given its filesystem path * * @since 5.3.0 * @access public */ public static function basename($path) { return basename($path, '.data'); } /** * Returns a standard preset's path given its extensionless basename * * @since 5.3.0 * @access public */ public static function get_standard($name) { return path_join(self::STANDARD_DIR, $name . '.data'); } /** * Returns a backup's path given its extensionless basename * * @since 5.3.0 * @access public */ public static function get_backup($name) { return path_join(self::BACKUP_DIR, $name . '.data'); } /** * Initializes the global $wp_filesystem object and clears stat cache * * @since 5.3.0 */ static function init_filesystem() { require_once ABSPATH . '/wp-admin/includes/file.php'; \WP_Filesystem(); clearstatcache(); } /** * Init * * @since 5.3.0 */ public function __construct() { Debug2::debug('[Preset] Init'); $this->_summary = self::get_summary(); } /** * Applies a standard preset's settings given its extensionless basename * * @since 5.3.0 * @access public */ public function apply($preset) { $this->make_backup($preset); $path = self::get_standard($preset); $result = $this->import_file($path) ? $preset : 'error'; $this->log($result); } /** * Restores settings from the backup file with the given timestamp, then deletes the file * * @since 5.3.0 * @access public */ public function restore($timestamp) { $backups = array(); foreach (self::get_backups() as $backup) { if (preg_match('/^backup-' . $timestamp . '(-|$)/', $backup) === 1) { $backups[] = $backup; } } if (empty($backups)) { $this->log('error'); return; } $backup = $backups[0]; $path = self::get_backup($backup); if (!$this->import_file($path)) { $this->log('error'); return; } self::init_filesystem(); global $wp_filesystem; $wp_filesystem->delete($path); Debug2::debug('[Preset] Deleted most recent backup from ' . $backup); $this->log('backup'); } /** * Saves current settings as a backup file, then prunes extra backup files * * @since 5.3.0 * @access public */ public function make_backup($preset) { $backup = 'backup-' . time() . '-before-' . $preset; $data = $this->export(true); $path = self::get_backup($backup); File::save($path, $data, true); Debug2::debug('[Preset] Backup saved to ' . $backup); self::prune_backups(); } /** * Tries to import from a given settings file * * @since 5.3.0 */ function import_file($path) { $debug = function ($result, $name) { $action = $result ? 'Applied' : 'Failed to apply'; Debug2::debug('[Preset] ' . $action . ' settings from ' . $name); return $result; }; $name = self::basename($path); $contents = file_get_contents($path); if (false === $contents) { Debug2::debug('[Preset] ❌ Failed to get file contents'); return $debug(false, $name); } $parsed = array(); try { // Check if the data is v4+ if (strpos($contents, '["_version",') === 0) { $contents = explode("\n", $contents); foreach ($contents as $line) { $line = trim($line); if (empty($line)) { continue; } list($key, $value) = \json_decode($line, true); $parsed[$key] = $value; } } else { $parsed = \json_decode(base64_decode($contents), true); } } catch (\Exception $ex) { Debug2::debug('[Preset] ❌ Failed to parse serialized data'); return $debug(false, $name); } if (empty($parsed)) { Debug2::debug('[Preset] ❌ Nothing to apply'); return $debug(false, $name); } $this->cls('Conf')->update_confs($parsed); return $debug(true, $name); } /** * Updates the log * * @since 5.3.0 */ function log($preset) { $this->_summary['preset'] = $preset; $this->_summary['preset_timestamp'] = time(); self::save_summary(); } /** * Handles all request actions from main cls * * @since 5.3.0 * @access public */ public function handler() { $type = Router::verify_type(); switch ($type) { case self::TYPE_APPLY: $this->apply(!empty($_GET['preset']) ? $_GET['preset'] : false); break; case self::TYPE_RESTORE: $this->restore(!empty($_GET['timestamp']) ? $_GET['timestamp'] : false); break; default: break; } Admin::redirect(); } } src/conf.cls.php 0000644 00000042613 15246276230 0007566 0 ustar 00 <?php /** * The core plugin config class. * * This maintains all the options and settings for this plugin. * * @since 1.0.0 * @since 1.5 Moved into /inc * @package LiteSpeed * @subpackage LiteSpeed/inc * @author LiteSpeed Technologies <info@litespeedtech.com> */ namespace LiteSpeed; defined('WPINC') || exit(); class Conf extends Base { const TYPE_SET = 'set'; private $_updated_ids = array(); private $_is_primary = false; /** * Specify init logic to avoid infinite loop when calling conf.cls instance * * @since 3.0 * @access public */ public function init() { // Check if conf exists or not. If not, create them in DB (won't change version if is converting v2.9- data) // Conf may be stale, upgrade later $this->_conf_db_init(); /** * Detect if has quic.cloud set * @since 2.9.7 */ if ($this->conf(self::O_CDN_QUIC)) { !defined('LITESPEED_ALLOWED') && define('LITESPEED_ALLOWED', true); } add_action('litespeed_conf_append', array($this, 'option_append'), 10, 2); add_action('litespeed_conf_force', array($this, 'force_option'), 10, 2); $this->define_cache(); } /** * Init conf related data * * @since 3.0 * @access private */ private function _conf_db_init() { /** * Try to load options first, network sites can override this later * * NOTE: Load before run `conf_upgrade()` to avoid infinite loop when getting conf in `conf_upgrade()` */ $this->load_options(); // Check if debug is on // Init debug as early as possible if ($this->conf(Base::O_DEBUG)) { $this->cls('Debug2')->init(); } $ver = $this->conf(self::_VER); /** * Version is less than v3.0, or, is a new installation */ $ver_check_tag = ''; if (!$ver) { // Try upgrade first (network will upgrade inside too) $ver_check_tag = Data::cls()->try_upgrade_conf_3_0(); } else { defined('LSCWP_CUR_V') || define('LSCWP_CUR_V', $ver); /** * Upgrade conf */ if ($ver != Core::VER) { // Plugin version will be set inside // Site plugin upgrade & version change will do in load_site_conf $ver_check_tag = Data::cls()->conf_upgrade($ver); } } /** * Sync latest new options */ if (!$ver || $ver != Core::VER) { // Load default values $this->load_default_vals(); if (!$ver) { // New install $this->set_conf(self::$_default_options); $ver_check_tag .= ' activate' . (defined('LSCWP_REF') ? '_' . LSCWP_REF : ''); } // Init new default/missing options foreach (self::$_default_options as $k => $v) { // If the option existed, bypass updating // Bcos we may ask clients to deactivate for debug temporarily, we need to keep the current cfg in deactivation, hence we need to only try adding default cfg when activating. self::add_option($k, $v); } // Force correct version in case a rare unexpected case that `_ver` exists but empty self::update_option(Base::_VER, Core::VER); if ($ver_check_tag) { Cloud::version_check($ver_check_tag); } } /** * Network sites only * * Override conf if is network subsites and chose `Use Primary Config` */ $this->_try_load_site_options(); // Mark as conf loaded defined('LITESPEED_CONF_LOADED') || define('LITESPEED_CONF_LOADED', true); if (!$ver || $ver != Core::VER) { // Only trigger once in upgrade progress, don't run always $this->update_confs(); // Files only get corrected in activation or saving settings actions. } } /** * Load all latest options from DB * * @since 3.0 * @access public */ public function load_options($blog_id = null, $dry_run = false) { $options = array(); foreach (self::$_default_options as $k => $v) { if (!is_null($blog_id)) { $options[$k] = self::get_blog_option($blog_id, $k, $v); } else { $options[$k] = self::get_option($k, $v); } // Correct value type $options[$k] = $this->type_casting($options[$k], $k); } if ($dry_run) { return $options; } // Bypass site special settings if ($blog_id !== null) { // This is to load the primary settings ONLY // These options are the ones that can be overwritten by primary $options = array_diff_key($options, array_flip(self::$SINGLE_SITE_OPTIONS)); $this->set_primary_conf($options); } else { $this->set_conf($options); } // Append const options if (defined('LITESPEED_CONF') && LITESPEED_CONF) { foreach (self::$_default_options as $k => $v) { $const = Base::conf_const($k); if (defined($const)) { $this->set_const_conf($k, $this->type_casting(constant($const), $k)); } } } } /** * For multisite installations, the single site options need to be updated with the network wide options. * * @since 1.0.13 * @access private */ private function _try_load_site_options() { if (!$this->_if_need_site_options()) { return; } $this->_conf_site_db_init(); $this->_is_primary = get_current_blog_id() == BLOG_ID_CURRENT_SITE; // If network set to use primary setting if ($this->network_conf(self::NETWORK_O_USE_PRIMARY) && !$this->_is_primary) { // subsites or network admin // Get the primary site settings // If it's just upgraded, 2nd blog is being visited before primary blog, can just load default config (won't hurt as this could only happen shortly) $this->load_options(BLOG_ID_CURRENT_SITE); } // Overwrite single blog options with site options foreach (self::$_default_options as $k => $v) { if (!$this->has_network_conf($k)) { continue; } // $this->_options[ $k ] = $this->_network_options[ $k ]; // Special handler to `Enable Cache` option if the value is set to OFF if ($k == self::O_CACHE) { if ($this->_is_primary) { if ($this->conf($k) != $this->network_conf($k)) { if ($this->conf($k) != self::VAL_ON2) { continue; } } } else { if ($this->network_conf(self::NETWORK_O_USE_PRIMARY)) { if ($this->has_primary_conf($k) && $this->primary_conf($k) != self::VAL_ON2) { // This case will use primary_options override always continue; } } else { if ($this->conf($k) != self::VAL_ON2) { continue; } } } } // primary_options will store primary settings + network settings, OR, store the network settings for subsites $this->set_primary_conf($k, $this->network_conf($k)); } // var_dump($this->_options); } /** * Check if needs to load site_options for network sites * * @since 3.0 * @access private */ private function _if_need_site_options() { if (!is_multisite()) { return false; } // Check if needs to use site_options or not // todo: check if site settings are separate bcos it will affect .htaccess /** * In case this is called outside the admin page * @see https://codex.wordpress.org/Function_Reference/is_plugin_active_for_network * @since 2.0 */ if (!function_exists('is_plugin_active_for_network')) { require_once ABSPATH . '/wp-admin/includes/plugin.php'; } // If is not activated on network, it will not have site options if (!is_plugin_active_for_network(Core::PLUGIN_FILE)) { if ((int) $this->conf(self::O_CACHE) == self::VAL_ON2) { // Default to cache on $this->set_conf(self::_CACHE, true); } return false; } return true; } /** * Init site conf and upgrade if necessary * * @since 3.0 * @access private */ private function _conf_site_db_init() { $this->load_site_options(); $ver = $this->network_conf(self::_VER); /** * Don't upgrade or run new installations other than from backend visit * In this case, just use default conf */ if (!$ver || $ver != Core::VER) { if (!is_admin() && !defined('LITESPEED_CLI')) { $this->set_network_conf($this->load_default_site_vals()); return; } } /** * Upgrade conf */ if ($ver && $ver != Core::VER) { // Site plugin version will change inside Data::cls()->conf_site_upgrade($ver); } /** * Is a new installation */ if (!$ver || $ver != Core::VER) { // Load default values $this->load_default_site_vals(); // Init new default/missing options foreach (self::$_default_site_options as $k => $v) { // If the option existed, bypass updating self::add_site_option($k, $v); } } } /** * Get the plugin's site wide options. * * If the site wide options are not set yet, set it to default. * * @since 1.0.2 * @access public */ public function load_site_options() { if (!is_multisite()) { return null; } // Load all site options foreach (self::$_default_site_options as $k => $v) { $val = self::get_site_option($k, $v); $val = $this->type_casting($val, $k, true); $this->set_network_conf($k, $val); } } /** * Append a 3rd party option to default options * * This will not be affected by network use primary site setting. * * NOTE: If it is a multi switch option, need to call `_conf_multi_switch()` first * * @since 3.0 * @access public */ public function option_append($name, $default) { self::$_default_options[$name] = $default; $this->set_conf($name, self::get_option($name, $default)); $this->set_conf($name, $this->type_casting($this->conf($name), $name)); } /** * Force an option to a certain value * * @since 2.6 * @access public */ public function force_option($k, $v) { if (!$this->has_conf($k)) { return; } $v = $this->type_casting($v, $k); if ($this->conf($k) === $v) { return; } Debug2::debug("[Conf] ** $k forced from " . var_export($this->conf($k), true) . ' to ' . var_export($v, true)); $this->set_conf($k, $v); } /** * Define `_CACHE` const in options ( for both single and network ) * * @since 3.0 * @access public */ public function define_cache() { // Init global const cache on setting $this->set_conf(self::_CACHE, false); if ((int) $this->conf(self::O_CACHE) == self::VAL_ON || $this->conf(self::O_CDN_QUIC)) { $this->set_conf(self::_CACHE, true); } // Check network if (!$this->_if_need_site_options()) { // Set cache on $this->_define_cache_on(); return; } // If use network setting if ((int) $this->conf(self::O_CACHE) == self::VAL_ON2 && $this->network_conf(self::O_CACHE)) { $this->set_conf(self::_CACHE, true); } $this->_define_cache_on(); } /** * Define `LITESPEED_ON` * * @since 2.1 * @access private */ private function _define_cache_on() { if (!$this->conf(self::_CACHE)) { return; } defined('LITESPEED_ALLOWED') && !defined('LITESPEED_ON') && define('LITESPEED_ON', true); } /** * Get an option value * * @since 3.0 * @access public * @deprecated 4.0 Use $this->conf() instead */ public static function val($id, $ori = false) { error_log('Called deprecated function \LiteSpeed\Conf::val(). Please use API call instead.'); return self::cls()->conf($id, $ori); } /** * Save option * * @since 3.0 * @access public */ public function update_confs($the_matrix = false) { if ($the_matrix) { foreach ($the_matrix as $id => $val) { $this->update($id, $val); } } if ($this->_updated_ids) { foreach ($this->_updated_ids as $id) { // Check if need to do a purge all or not if ($this->_conf_purge_all($id)) { Purge::purge_all('conf changed [id] ' . $id); } // Check if need to purge a tag if ($tag = $this->_conf_purge_tag($id)) { Purge::add($tag); } // Update cron if ($this->_conf_cron($id)) { $this->cls('Task')->try_clean($id); } // Reset crawler bypassed list when any of the options WebP replace, guest mode, or cache mobile got changed if ($id == self::O_IMG_OPTM_WEBP || $id == self::O_GUEST || $id == self::O_CACHE_MOBILE) { $this->cls('Crawler')->clear_disabled_list(); } } } do_action('litespeed_update_confs', $the_matrix); // Update related tables $this->cls('Data')->correct_tb_existence(); // Update related files $this->cls('Activation')->update_files(); /** * CDN related actions - Cloudflare */ $this->cls('CDN\Cloudflare')->try_refresh_zone(); /** * CDN related actions - QUIC.cloud * @since 2.3 */ $this->cls('CDN\Quic')->try_sync_conf(); } /** * Save option * * Note: this is direct save, won't trigger corresponding file update or data sync. To save settings normally, always use `Conf->update_confs()` * * @since 3.0 * @access public */ public function update($id, $val) { // Bypassed this bcos $this->_options could be changed by force_option() // if ( $this->_options[ $id ] === $val ) { // return; // } if ($id == self::_VER) { return; } if ($id == self::O_SERVER_IP) { if ($val && !Utility::valid_ipv4($val)) { $msg = sprintf(__('Saving option failed. IPv4 only for %s.', 'litespeed-cache'), Lang::title(Base::O_SERVER_IP)); Admin_Display::error($msg); return; } } if (!array_key_exists($id, self::$_default_options)) { defined('LSCWP_LOG') && Debug2::debug('[Conf] Invalid option ID ' . $id); return; } if ($val && $this->_conf_pswd($id) && !preg_match('/[^\*]/', $val)) { return; } // Special handler for CDN Original URLs if ($id == self::O_CDN_ORI && !$val) { $home_url = home_url('/'); $parsed = parse_url($home_url); $home_url = str_replace($parsed['scheme'] . ':', '', $home_url); $val = $home_url; } // Validate type $val = $this->type_casting($val, $id); // Save data self::update_option($id, $val); // Handle purge if setting changed if ($this->conf($id) != $val) { $this->_updated_ids[] = $id; // Check if need to fire a purge or not (Here has to stay inside `update()` bcos need comparing old value) if ($this->_conf_purge($id)) { $diff = array_diff($val, $this->conf($id)); $diff2 = array_diff($this->conf($id), $val); $diff = array_merge($diff, $diff2); // If has difference foreach ($diff as $v) { $v = ltrim($v, '^'); $v = rtrim($v, '$'); $this->cls('Purge')->purge_url($v); } } } // Update in-memory data $this->set_conf($id, $val); } /** * Save network option * * @since 3.0 * @access public */ public function network_update($id, $val) { if (!array_key_exists($id, self::$_default_site_options)) { defined('LSCWP_LOG') && Debug2::debug('[Conf] Invalid network option ID ' . $id); return; } if ($val && $this->_conf_pswd($id) && !preg_match('/[^\*]/', $val)) { return; } // Validate type if (is_bool(self::$_default_site_options[$id])) { $max = $this->_conf_multi_switch($id); if ($max && $val > 1) { $val %= $max + 1; } else { $val = (bool) $val; } } elseif (is_array(self::$_default_site_options[$id])) { // from textarea input if (!is_array($val)) { $val = Utility::sanitize_lines($val, $this->_conf_filter($id)); } } elseif (!is_string(self::$_default_site_options[$id])) { $val = (int) $val; } else { // Check if the string has a limit set $val = $this->_conf_string_val($id, $val); } // Save data self::update_site_option($id, $val); // Handle purge if setting changed if ($this->network_conf($id) != $val) { // Check if need to do a purge all or not if ($this->_conf_purge_all($id)) { Purge::purge_all('[Conf] Network conf changed [id] ' . $id); } // Update in-memory data $this->set_network_conf($id, $val); } // No need to update cron here, Cron will register in each init if ($this->has_conf($id)) { $this->set_conf($id, $val); } } /** * Check if one user role is in exclude optimization group settings * * @since 1.6 * @access public * @param string $role The user role * @return int The set value if already set */ public function in_optm_exc_roles($role = null) { // Get user role if ($role === null) { $role = Router::get_role(); } if (!$role) { return false; } $roles = explode(',', $role); $found = array_intersect($roles, $this->conf(self::O_OPTM_EXC_ROLES)); return $found ? implode(',', $found) : false; } /** * Set one config value directly * * @since 2.9 * @access private */ private function _set_conf() { /** * NOTE: For URL Query String setting, * 1. If append lines to an array setting e.g. `cache-force_uri`, use `set[cache-force_uri][]=the_url`. * 2. If replace the array setting with one line, use `set[cache-force_uri]=the_url`. * 3. If replace the array setting with multi lines value, use 2 then 1. */ if (empty($_GET[self::TYPE_SET]) || !is_array($_GET[self::TYPE_SET])) { return; } $the_matrix = array(); foreach ($_GET[self::TYPE_SET] as $id => $v) { if (!$this->has_conf($id)) { continue; } // Append new item to array type settings if (is_array($v) && is_array($this->conf($id))) { $v = array_merge($this->conf($id), $v); Debug2::debug('[Conf] Appended to settings [' . $id . ']: ' . var_export($v, true)); } else { Debug2::debug('[Conf] Set setting [' . $id . ']: ' . var_export($v, true)); } $the_matrix[$id] = $v; } if (!$the_matrix) { return; } $this->update_confs($the_matrix); $msg = __('Changed setting successfully.', 'litespeed-cache'); Admin_Display::success($msg); // Redirect if changed frontend URL if (!empty($_GET['redirect'])) { wp_redirect($_GET['redirect']); exit(); } } /** * Handle all request actions from main cls * * @since 2.9 * @access public */ public function handler() { $type = Router::verify_type(); switch ($type) { case self::TYPE_SET: $this->_set_conf(); break; default: break; } Admin::redirect(); } } src/localization.cls.php 0000644 00000006617 15246276230 0011335 0 ustar 00 <?php /** * The localization class. * * @since 3.3 */ namespace LiteSpeed; defined('WPINC') || exit(); class Localization extends Base { const LOG_TAG = '🛍️'; /** * Init optimizer * * @since 3.0 * @access protected */ public function init() { add_filter('litespeed_buffer_finalize', array($this, 'finalize'), 23); // After page optm } /** * Localize Resources * * @since 3.3 */ public function serve_static($uri) { $url = base64_decode($uri); if (!$this->conf(self::O_OPTM_LOCALIZE)) { // wp_redirect( $url ); exit('Not supported'); } if (substr($url, -3) !== '.js') { // wp_redirect( $url ); // exit( 'Not supported ' . $uri ); } $match = false; $domains = $this->conf(self::O_OPTM_LOCALIZE_DOMAINS); foreach ($domains as $v) { if (!$v || strpos($v, '#') === 0) { continue; } $type = 'js'; $domain = $v; // Try to parse space split value if (strpos($v, ' ')) { $v = explode(' ', $v); if (!empty($v[1])) { $type = strtolower($v[0]); $domain = $v[1]; } } if (strpos($domain, 'https://') !== 0) { continue; } if ($type != 'js') { continue; } // if ( strpos( $url, $domain ) !== 0 ) { if ($url != $domain) { continue; } $match = true; break; } if (!$match) { // wp_redirect( $url ); exit('Not supported2'); } header('Content-Type: application/javascript'); // Generate $this->_maybe_mk_cache_folder('localres'); $file = $this->_realpath($url); self::debug('localize [url] ' . $url); $response = wp_safe_remote_get($url, array('timeout' => 180, 'stream' => true, 'filename' => $file)); // Parse response data if (is_wp_error($response)) { $error_message = $response->get_error_message(); file_exists($file) && unlink($file); self::debug('failed to get: ' . $error_message); wp_redirect($url); exit(); } $url = $this->_rewrite($url); wp_redirect($url); exit(); } /** * Get the final URL of local avatar * * @since 4.5 */ private function _rewrite($url) { return LITESPEED_STATIC_URL . '/localres/' . $this->_filepath($url); } /** * Generate realpath of the cache file * * @since 4.5 * @access private */ private function _realpath($url) { return LITESPEED_STATIC_DIR . '/localres/' . $this->_filepath($url); } /** * Get filepath * * @since 4.5 */ private function _filepath($url) { $filename = md5($url) . '.js'; if (is_multisite()) { $filename = get_current_blog_id() . '/' . $filename; } return $filename; } /** * Localize JS/Fonts * * @since 3.3 * @access public */ public function finalize($content) { if (is_admin()) { return $content; } if (!$this->conf(self::O_OPTM_LOCALIZE)) { return $content; } $domains = $this->conf(self::O_OPTM_LOCALIZE_DOMAINS); if (!$domains) { return $content; } foreach ($domains as $v) { if (!$v || strpos($v, '#') === 0) { continue; } $type = 'js'; $domain = $v; // Try to parse space split value if (strpos($v, ' ')) { $v = explode(' ', $v); if (!empty($v[1])) { $type = strtolower($v[0]); $domain = $v[1]; } } if (strpos($domain, 'https://') !== 0) { continue; } if ($type != 'js') { continue; } $content = str_replace($domain, LITESPEED_STATIC_URL . '/localres/' . base64_encode($domain), $content); } return $content; } } src/error.cls.php 0000644 00000015620 15246276230 0007770 0 ustar 00 <?php /** * The error class. * * @since 3.0 * @package LiteSpeed * @subpackage LiteSpeed/src * @author LiteSpeed Technologies <info@litespeedtech.com> */ namespace LiteSpeed; defined('WPINC') || exit(); class Error { private static $CODE_SET = array( 'HTA_LOGIN_COOKIE_INVALID' => 4300, // .htaccess did not find. 'HTA_DNF' => 4500, // .htaccess did not find. 'HTA_BK' => 9010, // backup 'HTA_R' => 9041, // read htaccess 'HTA_W' => 9042, // write 'HTA_GET' => 9030, // failed to get ); /** * Throw an error with msg * * @since 3.0 */ public static function t($code, $args = null) { throw new \Exception(self::msg($code, $args)); } /** * Translate an error to description * * @since 3.0 */ public static function msg($code, $args = null) { switch ($code) { case 'disabled_all': $msg = sprintf(__('The setting %s is currently enabled.', 'litespeed-cache'), '<strong>' . Lang::title(Base::O_DEBUG_DISABLE_ALL) . '</strong>') . Doc::learn_more( is_network_admin() ? network_admin_url('admin.php?page=litespeed-toolbox') : admin_url('admin.php?page=litespeed-toolbox'), __('Click here to change.', 'litespeed-cache'), true, false, true ); break; case 'qc_setup_required': $msg = sprintf(__('You will need to finish %s setup to use the online services.', 'litespeed-cache'), '<strong>QUIC.cloud</strong>') . Doc::learn_more(admin_url('admin.php?page=litespeed-general'), __('Click here to set.', 'litespeed-cache'), true, false, true); break; case 'out_of_daily_quota': $msg = __('You have used all of your daily quota for today.', 'litespeed-cache'); $msg .= ' ' . Doc::learn_more( 'https://docs.quic.cloud/billing/services/#daily-limits-on-free-quota-usage', __('Learn more or purchase additional quota.', 'litespeed-cache'), false, false, true ); break; case 'out_of_quota': $msg = __('You have used all of your quota left for current service this month.', 'litespeed-cache'); $msg .= ' ' . Doc::learn_more( 'https://docs.quic.cloud/billing/services/#daily-limits-on-free-quota-usage', __('Learn more or purchase additional quota.', 'litespeed-cache'), false, false, true ); break; case 'too_many_requested': $msg = __('You have too many requested images, please try again in a few minutes.', 'litespeed-cache'); break; case 'too_many_notified': $msg = __('You have images waiting to be pulled. Please wait for the automatic pull to complete, or pull them down manually now.', 'litespeed-cache'); break; case 'empty_list': $msg = __('The image list is empty.', 'litespeed-cache'); break; case 'lack_of_param': $msg = __('Not enough parameters. Please check if the domain key is set correctly', 'litespeed-cache'); break; case 'unfinished_queue': $msg = __('There is proceeding queue not pulled yet.', 'litespeed-cache'); break; case strpos($code, 'unfinished_queue ') === 0: $msg = sprintf( __('There is proceeding queue not pulled yet. Queue info: %s.', 'litespeed-cache'), '<code>' . substr($code, strlen('unfinished_queue ')) . '</code>' ); break; case 'err_alias': $msg = __('The site is not a valid alias on QUIC.cloud.', 'litespeed-cache'); break; case 'site_not_registered': $msg = __('The site is not registered on QUIC.cloud.', 'litespeed-cache'); break; case 'err_key': $msg = __('The domain key is not correct. Please try to sync your domain key again.', 'litespeed-cache'); break; case 'heavy_load': $msg = __('The current server is under heavy load.', 'litespeed-cache'); break; case 'redetect_node': $msg = __('Online node needs to be redetected.', 'litespeed-cache'); break; case 'err_overdraw': $msg = __('Credits are not enough to proceed the current request.', 'litespeed-cache'); break; case 'W': $msg = __('%s file not writable.', 'litespeed-cache'); break; case 'HTA_DNF': if (!is_array($args)) { $args = array('<code>' . $args . '</code>'); } $args[] = '.htaccess'; $msg = __('Could not find %1$s in %2$s.', 'litespeed-cache'); break; case 'HTA_LOGIN_COOKIE_INVALID': $msg = sprintf(__('Invalid login cookie. Please check the %s file.', 'litespeed-cache'), '.htaccess'); break; case 'HTA_BK': $msg = sprintf(__('Failed to back up %s file, aborted changes.', 'litespeed-cache'), '.htaccess'); break; case 'HTA_R': $msg = sprintf(__('%s file not readable.', 'litespeed-cache'), '.htaccess'); break; case 'HTA_W': $msg = sprintf(__('%s file not writable.', 'litespeed-cache'), '.htaccess'); break; case 'HTA_GET': $msg = sprintf(__('Failed to get %s file contents.', 'litespeed-cache'), '.htaccess'); break; case 'failed_tb_creation': $msg = __('Failed to create table %s! SQL: %s.', 'litespeed-cache'); break; case 'crawler_disabled': $msg = __('Crawler disabled by the server admin.', 'litespeed-cache'); break; case 'try_later': // QC error code $msg = __('Previous request too recent. Please try again later.', 'litespeed-cache'); break; case strpos($code, 'try_later ') === 0: $msg = sprintf( __('Previous request too recent. Please try again after %s.', 'litespeed-cache'), '<code>' . Utility::readable_time(substr($code, strlen('try_later ')), 3600, true) . '</code>' ); break; case 'waiting_for_approval': $msg = __('Your application is waiting for approval.', 'litespeed-cache'); break; case 'callback_fail_hash': $msg = __('The callback validation to your domain failed due to hash mismatch.', 'litespeed-cache'); break; case 'callback_fail': $msg = __('The callback validation to your domain failed. Please make sure there is no firewall blocking our servers.', 'litespeed-cache'); break; case substr($code, 0, 14) === 'callback_fail ': $msg = __('The callback validation to your domain failed. Please make sure there is no firewall blocking our servers. Response code: ', 'litespeed-cache') . substr($code, 14); break; case 'forbidden': $msg = __('Your domain has been forbidden from using our services due to a previous policy violation.', 'litespeed-cache'); break; case 'err_dns_active': $msg = __( 'You cannot remove this DNS zone, because it is still in use. Please update the domain\'s nameservers, then try to delete this zone again, otherwise your site will become inaccessible.', 'litespeed-cache' ); break; default: $msg = __('Unknown error', 'litespeed-cache') . ': ' . $code; break; } if ($args !== null) { $msg = is_array($args) ? vsprintf($msg, $args) : sprintf($msg, $args); } if (isset(self::$CODE_SET[$code])) { $msg = 'ERROR ' . self::$CODE_SET[$code] . ': ' . $msg; } return $msg; } } src/css.cls.php 0000644 00000036215 15246276230 0007432 0 ustar 00 <?php /** * The optimize css class. * * @since 2.3 */ namespace LiteSpeed; defined('WPINC') || exit(); class CSS extends Base { const LOG_TAG = '[CSS]'; const TYPE_GEN_CCSS = 'gen_ccss'; const TYPE_CLEAR_Q_CCSS = 'clear_q_ccss'; protected $_summary; private $_ccss_whitelist; private $_queue; /** * Init * * @since 3.0 */ public function __construct() { $this->_summary = self::get_summary(); add_filter('litespeed_ccss_whitelist', array($this->cls('Data'), 'load_ccss_whitelist')); } /** * HTML lazyload CSS * @since 4.0 */ public function prepare_html_lazy() { return '<style>' . implode(',', $this->conf(self::O_OPTM_HTML_LAZY)) . '{content-visibility:auto;contain-intrinsic-size:1px 1000px;}</style>'; } /** * Output critical css * * @since 1.3 * @access public */ public function prepare_ccss() { // Get critical css for current page // Note: need to consider mobile $rules = $this->_ccss(); if (!$rules) { return null; } $error_tag = ''; if (substr($rules, 0, 2) == '/*' && substr($rules, -2) == '*/') { Core::comment('QUIC.cloud CCSS bypassed due to generation error ❌'); $error_tag = ' data-error="failed to generate"'; } // Append default critical css $rules .= $this->conf(self::O_OPTM_CCSS_CON); return '<style id="litespeed-ccss"' . $error_tag . '>' . $rules . '</style>'; } /** * Generate CCSS url tag * * @since 4.0 */ private function _gen_ccss_file_tag($request_url) { if (is_404()) { return '404'; } if ($this->conf(self::O_OPTM_CCSS_PER_URL)) { return $request_url; } $sep_uri = $this->conf(self::O_OPTM_CCSS_SEP_URI); if ($sep_uri && ($hit = Utility::str_hit_array($request_url, $sep_uri))) { Debug2::debug('[CCSS] Separate CCSS due to separate URI setting: ' . $hit); return $request_url; } $pt = Utility::page_type(); $sep_pt = $this->conf(self::O_OPTM_CCSS_SEP_POSTTYPE); if (in_array($pt, $sep_pt)) { Debug2::debug('[CCSS] Separate CCSS due to posttype setting: ' . $pt); return $request_url; } // Per posttype return $pt; } /** * The critical css content of the current page * * @since 2.3 */ private function _ccss() { global $wp; $request_url = get_permalink(); // Backup, in case get_permalink() fails. if (!$request_url) { $request_url = home_url($wp->request); } $filepath_prefix = $this->_build_filepath_prefix('ccss'); $url_tag = $this->_gen_ccss_file_tag($request_url); $vary = $this->cls('Vary')->finalize_full_varies(); $filename = $this->cls('Data')->load_url_file($url_tag, $vary, 'ccss'); if ($filename) { $static_file = LITESPEED_STATIC_DIR . $filepath_prefix . $filename . '.css'; if (file_exists($static_file)) { Debug2::debug2('[CSS] existing ccss ' . $static_file); Core::comment('QUIC.cloud CCSS loaded ✅ ' . $filepath_prefix . $filename . '.css'); return File::read($static_file); } } $uid = get_current_user_id(); $ua = !empty($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : ''; // Store it to prepare for cron Core::comment('QUIC.cloud CCSS in queue'); $this->_queue = $this->load_queue('ccss'); if (count($this->_queue) > 500) { self::debug('CCSS Queue is full - 500'); return null; } $queue_k = (strlen($vary) > 32 ? md5($vary) : $vary) . ' ' . $url_tag; $this->_queue[$queue_k] = array( 'url' => apply_filters('litespeed_ccss_url', $request_url), 'user_agent' => substr($ua, 0, 200), 'is_mobile' => $this->_separate_mobile(), 'is_webp' => $this->cls('Media')->webp_support() ? 1 : 0, 'uid' => $uid, 'vary' => $vary, 'url_tag' => $url_tag, ); // Current UA will be used to request $this->save_queue('ccss', $this->_queue); self::debug('Added queue_ccss [url_tag] ' . $url_tag . ' [UA] ' . $ua . ' [vary] ' . $vary . ' [uid] ' . $uid); // Prepare cache tag for later purge Tag::add('CCSS.' . md5($queue_k)); // For v4.1- clean up if (isset($this->_summary['ccss_type_history']) || isset($this->_summary['ccss_history']) || isset($this->_summary['queue_ccss'])) { if (isset($this->_summary['ccss_type_history'])) { unset($this->_summary['ccss_type_history']); } if (isset($this->_summary['ccss_history'])) { unset($this->_summary['ccss_history']); } if (isset($this->_summary['queue_ccss'])) { unset($this->_summary['queue_ccss']); } self::save_summary(); } return null; } /** * Cron ccss generation * * @since 2.3 * @access private */ public static function cron_ccss($continue = false) { $_instance = self::cls(); return $_instance->_cron_handler('ccss', $continue); } /** * Handle UCSS/CCSS cron * * @since 4.2 */ private function _cron_handler($type, $continue) { $this->_queue = $this->load_queue($type); if (empty($this->_queue)) { return; } $type_tag = strtoupper($type); // For cron, need to check request interval too if (!$continue) { if (!empty($this->_summary['curr_request_' . $type]) && time() - $this->_summary['curr_request_' . $type] < 300 && !$this->conf(self::O_DEBUG)) { Debug2::debug('[' . $type_tag . '] Last request not done'); return; } } $i = 0; foreach ($this->_queue as $k => $v) { if (!empty($v['_status'])) { continue; } Debug2::debug('[' . $type_tag . '] cron job [tag] ' . $k . ' [url] ' . $v['url'] . ($v['is_mobile'] ? ' 📱 ' : '') . ' [UA] ' . $v['user_agent']); if ($type == 'ccss' && empty($v['url_tag'])) { unset($this->_queue[$k]); $this->save_queue($type, $this->_queue); Debug2::debug('[CCSS] wrong queue_ccss format'); continue; } if (!isset($v['is_webp'])) { $v['is_webp'] = false; } $i++; $res = $this->_send_req($v['url'], $k, $v['uid'], $v['user_agent'], $v['vary'], $v['url_tag'], $type, $v['is_mobile'], $v['is_webp']); if (!$res) { // Status is wrong, drop this this->_queue unset($this->_queue[$k]); $this->save_queue($type, $this->_queue); if (!$continue) { return; } if ($i > 3) { GUI::print_loading(count($this->_queue), $type_tag); return Router::self_redirect(Router::ACTION_CSS, CSS::TYPE_GEN_CCSS); } continue; } // Exit queue if out of quota or service is hot if ($res === 'out_of_quota' || $res === 'svc_hot') { return; } $this->_queue[$k]['_status'] = 'requested'; $this->save_queue($type, $this->_queue); // only request first one if (!$continue) { return; } if ($i > 3) { GUI::print_loading(count($this->_queue), $type_tag); return Router::self_redirect(Router::ACTION_CSS, CSS::TYPE_GEN_CCSS); } } } /** * Send to QC API to generate CCSS/UCSS * * @since 2.3 * @access private */ private function _send_req($request_url, $queue_k, $uid, $user_agent, $vary, $url_tag, $type, $is_mobile, $is_webp) { // Check if has credit to push or not $err = false; $allowance = $this->cls('Cloud')->allowance(Cloud::SVC_CCSS, $err); if (!$allowance) { Debug2::debug('[CCSS] ❌ No credit: ' . $err); $err && Admin_Display::error(Error::msg($err)); return 'out_of_quota'; } set_time_limit(120); // Update css request status $this->_summary['curr_request_' . $type] = time(); self::save_summary(); // Gather guest HTML to send $html = $this->prepare_html($request_url, $user_agent, $uid); if (!$html) { return false; } // Parse HTML to gather all CSS content before requesting list($css, $html) = $this->prepare_css($html, $is_webp); if (!$css) { $type_tag = strtoupper($type); Debug2::debug('[' . $type_tag . '] ❌ No combined css'); return false; } // Generate critical css $data = array( 'url' => $request_url, 'queue_k' => $queue_k, 'user_agent' => $user_agent, 'is_mobile' => $is_mobile ? 1 : 0, // todo:compatible w/ tablet 'is_webp' => $is_webp ? 1 : 0, 'html' => $html, 'css' => $css, ); if (!isset($this->_ccss_whitelist)) { $this->_ccss_whitelist = $this->_filter_whitelist(); } $data['whitelist'] = $this->_ccss_whitelist; self::debug('Generating: ', $data); $json = Cloud::post(Cloud::SVC_CCSS, $data, 30); if (!is_array($json)) { return $json; } // Old version compatibility if (empty($json['status'])) { if (!empty($json[$type])) { $this->_save_con($type, $json[$type], $queue_k, $is_mobile, $is_webp); } // Delete the row return false; } // Unknown status, remove this line if ($json['status'] != 'queued') { return false; } // Save summary data $this->_summary['last_spent_' . $type] = time() - $this->_summary['curr_request_' . $type]; $this->_summary['last_request_' . $type] = $this->_summary['curr_request_' . $type]; $this->_summary['curr_request_' . $type] = 0; self::save_summary(); return true; } /** * Save CCSS/UCSS content * * @since 4.2 */ private function _save_con($type, $css, $queue_k, $mobile, $webp) { // Add filters $css = apply_filters('litespeed_' . $type, $css, $queue_k); Debug2::debug2('[CSS] con: ' . $css); if (substr($css, 0, 2) == '/*' && substr($css, -2) == '*/') { self::debug('❌ empty ' . $type . ' [content] ' . $css); // continue; // Save the error info too } // Write to file $filecon_md5 = md5($css); $filepath_prefix = $this->_build_filepath_prefix($type); $static_file = LITESPEED_STATIC_DIR . $filepath_prefix . $filecon_md5 . '.css'; File::save($static_file, $css, true); $url_tag = $this->_queue[$queue_k]['url_tag']; $vary = $this->_queue[$queue_k]['vary']; Debug2::debug2("[CSS] Save URL to file [file] $static_file [vary] $vary"); $this->cls('Data')->save_url($url_tag, $vary, $type, $filecon_md5, dirname($static_file), $mobile, $webp); Purge::add(strtoupper($type) . '.' . md5($queue_k)); } /** * Play for fun * * @since 3.4.3 */ public function test_url($request_url) { $user_agent = $_SERVER['HTTP_USER_AGENT']; $html = $this->prepare_html($request_url, $user_agent); list($css, $html) = $this->prepare_css($html, true, true); // var_dump( $css ); // $html = <<<EOT // EOT; // $css = <<<EOT // EOT; $data = array( 'url' => $request_url, 'ccss_type' => 'test', 'user_agent' => $user_agent, 'is_mobile' => 0, 'html' => $html, 'css' => $css, 'type' => 'CCSS', ); // self::debug( 'Generating: ', $data ); $json = Cloud::post(Cloud::SVC_CCSS, $data, 180); var_dump($json); } /** * Prepare HTML from URL * * @since 3.4.3 */ public function prepare_html($request_url, $user_agent, $uid = false) { $html = $this->cls('Crawler')->self_curl(add_query_arg('LSCWP_CTRL', 'before_optm', $request_url), $user_agent, $uid); Debug2::debug2('[CSS] self_curl result....', $html); if (!$html) { return false; } $html = $this->cls('Optimizer')->html_min($html, true); // Drop <noscript>xxx</noscript> $html = preg_replace('#<noscript>.*</noscript>#isU', '', $html); return $html; } /** * Prepare CSS from HTML for CCSS generation only. UCSS will used combined CSS directly. * Prepare refined HTML for both CCSS and UCSS. * * @since 3.4.3 */ public function prepare_css($html, $is_webp = false, $dryrun = false) { $css = ''; preg_match_all('#<link ([^>]+)/?>|<style([^>]*)>([^<]+)</style>#isU', $html, $matches, PREG_SET_ORDER); foreach ($matches as $match) { $debug_info = ''; if (strpos($match[0], '<link') === 0) { $attrs = Utility::parse_attr($match[1]); if (empty($attrs['rel'])) { continue; } if ($attrs['rel'] != 'stylesheet') { if ($attrs['rel'] != 'preload' || empty($attrs['as']) || $attrs['as'] != 'style') { continue; } } if (!empty($attrs['media']) && strpos($attrs['media'], 'print') !== false) { continue; } if (empty($attrs['href'])) { continue; } // Check Google fonts hit if (strpos($attrs['href'], 'fonts.googleapis.com') !== false) { $html = str_replace($match[0], '', $html); continue; } $debug_info = $attrs['href']; // Load CSS content if (!$dryrun) { // Dryrun will not load CSS but just drop them $con = $this->cls('Optimizer')->load_file($attrs['href']); if (!$con) { continue; } } else { $con = ''; } } else { // Inline style $attrs = Utility::parse_attr($match[2]); if (!empty($attrs['media']) && strpos($attrs['media'], 'print') !== false) { continue; } Debug2::debug2('[CSS] Load inline CSS ' . substr($match[3], 0, 100) . '...', $attrs); $con = $match[3]; $debug_info = '__INLINE__'; } $con = Optimizer::minify_css($con); if ($is_webp && $this->cls('Media')->webp_support()) { $con = $this->cls('Media')->replace_background_webp($con); } if (!empty($attrs['media']) && $attrs['media'] !== 'all') { $con = '@media ' . $attrs['media'] . '{' . $con . "}\n"; } else { $con = $con . "\n"; } $con = '/* ' . $debug_info . ' */' . $con; $css .= $con; $html = str_replace($match[0], '', $html); } return array($css, $html); } /** * Filter the comment content, add quotes to selector from whitelist. Return the json * * @since 7.1 */ private function _filter_whitelist() { $whitelist = array(); $list = apply_filters('litespeed_ccss_whitelist', $this->conf(self::O_OPTM_CCSS_SELECTOR_WHITELIST)); foreach ($list as $v) { if (substr($v, 0, 2) === '//') { continue; } $whitelist[] = $v; } return $whitelist; } /** * Notify finished from server * @since 7.1 */ public function notify() { $post_data = \json_decode(file_get_contents('php://input'), true); if (is_null($post_data)) { $post_data = $_POST; } self::debug('notify() data', $post_data); $this->_queue = $this->load_queue('ccss'); list($post_data) = $this->cls('Cloud')->extract_msg($post_data, 'ccss'); $notified_data = $post_data['data']; if (empty($notified_data) || !is_array($notified_data)) { self::debug('❌ notify exit: no notified data'); return Cloud::err('no notified data'); } // Check if its in queue or not $valid_i = 0; foreach ($notified_data as $v) { if (empty($v['request_url'])) { self::debug('❌ notify bypass: no request_url', $v); continue; } if (empty($v['queue_k'])) { self::debug('❌ notify bypass: no queue_k', $v); continue; } if (empty($this->_queue[$v['queue_k']])) { self::debug('❌ notify bypass: no this queue [q_k]' . $v['queue_k']); continue; } // Save data if (!empty($v['data_ccss'])) { $is_mobile = $this->_queue[$v['queue_k']]['is_mobile']; $is_webp = $this->_queue[$v['queue_k']]['is_webp']; $this->_save_con('ccss', $v['data_ccss'], $v['queue_k'], $is_mobile, $is_webp); $valid_i++; } unset($this->_queue[$v['queue_k']]); self::debug('notify data handled, unset queue [q_k] ' . $v['queue_k']); } $this->save_queue('ccss', $this->_queue); self::debug('notified'); return Cloud::ok(array('count' => $valid_i)); } /** * Handle all request actions from main cls * * @since 2.3 * @access public */ public function handler() { $type = Router::verify_type(); switch ($type) { case self::TYPE_GEN_CCSS: self::cron_ccss(true); break; case self::TYPE_CLEAR_Q_CCSS: $this->clear_q('ccss'); break; default: break; } Admin::redirect(); } } src/router.cls.php 0000644 00000047007 15246276230 0010163 0 ustar 00 <?php /** * The core plugin router class. * * This generate the valid action. * * @since 1.1.0 * @since 1.5 Moved into /inc */ namespace LiteSpeed; defined('WPINC') || exit(); class Router extends Base { const LOG_TAG = '[Router]'; const NONCE = 'LSCWP_NONCE'; const ACTION = 'LSCWP_CTRL'; const ACTION_SAVE_SETTINGS_NETWORK = 'save-settings-network'; const ACTION_DB_OPTM = 'db_optm'; const ACTION_PLACEHOLDER = 'placeholder'; const ACTION_AVATAR = 'avatar'; const ACTION_SAVE_SETTINGS = 'save-settings'; const ACTION_CLOUD = 'cloud'; const ACTION_IMG_OPTM = 'img_optm'; const ACTION_HEALTH = 'health'; const ACTION_CRAWLER = 'crawler'; const ACTION_PURGE = 'purge'; const ACTION_CONF = 'conf'; const ACTION_ACTIVATION = 'activation'; const ACTION_CSS = 'css'; const ACTION_UCSS = 'ucss'; const ACTION_VPI = 'vpi'; const ACTION_PRESET = 'preset'; const ACTION_IMPORT = 'import'; const ACTION_REPORT = 'report'; const ACTION_DEBUG2 = 'debug2'; const ACTION_CDN_CLOUDFLARE = 'CDN\Cloudflare'; const ACTION_ADMIN_DISPLAY = 'admin_display'; // List all handlers here private static $_HANDLERS = array( self::ACTION_ADMIN_DISPLAY, self::ACTION_ACTIVATION, self::ACTION_AVATAR, self::ACTION_CDN_CLOUDFLARE, self::ACTION_CLOUD, self::ACTION_CONF, self::ACTION_CRAWLER, self::ACTION_CSS, self::ACTION_UCSS, self::ACTION_VPI, self::ACTION_DB_OPTM, self::ACTION_DEBUG2, self::ACTION_HEALTH, self::ACTION_IMG_OPTM, self::ACTION_PRESET, self::ACTION_IMPORT, self::ACTION_PLACEHOLDER, self::ACTION_PURGE, self::ACTION_REPORT, ); const TYPE = 'litespeed_type'; const ITEM_HASH = 'hash'; const ITEM_FLASH_HASH = 'flash_hash'; private static $_esi_enabled; private static $_is_ajax; private static $_is_logged_in; private static $_ip; private static $_action; private static $_is_admin_ip; private static $_frontend_path; /** * Redirect to self to continue operation * * Note: must return when use this func. CLI/Cron call won't die in this func. * * @since 3.0 * @access public */ public static function self_redirect($action, $type) { if (defined('LITESPEED_CLI') || defined('DOING_CRON')) { Admin_Display::success('To be continued'); // Show for CLI return; } // Add i to avoid browser too many redirected warning $i = !empty($_GET['litespeed_i']) ? $_GET['litespeed_i'] : 0; $i++; $link = Utility::build_url($action, $type, false, null, array('litespeed_i' => $i)); $url = html_entity_decode($link); exit("<meta http-equiv='refresh' content='0;url=$url'>"); } /** * Check if can run optimize * * @since 1.3 * @since 2.3.1 Relocated from cdn.cls * @access public */ public function can_optm() { $can = true; if (is_user_logged_in() && $this->conf(self::O_OPTM_GUEST_ONLY)) { $can = false; } elseif (is_admin()) { $can = false; } elseif (is_feed()) { $can = false; } elseif (is_preview()) { $can = false; } elseif (self::is_ajax()) { $can = false; } if (self::_is_login_page()) { Debug2::debug('[Router] Optm bypassed: login/reg page'); $can = false; } $can_final = apply_filters('litespeed_can_optm', $can); if ($can_final != $can) { Debug2::debug('[Router] Optm bypassed: filter'); } return $can_final; } /** * Check referer page to see if its from admin * * @since 2.4.2.1 * @access public */ public static function from_admin() { return !empty($_SERVER['HTTP_REFERER']) && strpos($_SERVER['HTTP_REFERER'], get_admin_url()) === 0; } /** * Check if it can use CDN replacement * * @since 1.2.3 * @since 2.3.1 Relocated from cdn.cls * @access public */ public static function can_cdn() { $can = true; if (is_admin()) { if (!self::is_ajax()) { Debug2::debug2('[Router] CDN bypassed: is not ajax call'); $can = false; } if (self::from_admin()) { Debug2::debug2('[Router] CDN bypassed: ajax call from admin'); $can = false; } } elseif (is_feed()) { $can = false; } elseif (is_preview()) { $can = false; } /** * Bypass cron to avoid deregister jq notice `Do not deregister the <code>jquery-core</code> script in the administration area.` * @since 2.7.2 */ if (defined('DOING_CRON')) { $can = false; } /** * Bypass login/reg page * @since 1.6 */ if (self::_is_login_page()) { Debug2::debug('[Router] CDN bypassed: login/reg page'); $can = false; } /** * Bypass post/page link setting * @since 2.9.8.5 */ $rest_prefix = function_exists('rest_get_url_prefix') ? rest_get_url_prefix() : apply_filters('rest_url_prefix', 'wp-json'); if ( !empty($_SERVER['REQUEST_URI']) && strpos($_SERVER['REQUEST_URI'], $rest_prefix . '/wp/v2/media') !== false && isset($_SERVER['HTTP_REFERER']) && strpos($_SERVER['HTTP_REFERER'], 'wp-admin') !== false ) { Debug2::debug('[Router] CDN bypassed: wp-json on admin page'); $can = false; } $can_final = apply_filters('litespeed_can_cdn', $can); if ($can_final != $can) { Debug2::debug('[Router] CDN bypassed: filter'); } return $can_final; } /** * Check if is login page or not * * @since 2.3.1 * @access protected */ protected static function _is_login_page() { if (in_array($GLOBALS['pagenow'], array('wp-login.php', 'wp-register.php'), true)) { return true; } return false; } /** * UCSS/Crawler role simulator * * @since 1.9.1 * @since 3.3 Renamed from `is_crawler_role_simulation` */ public function is_role_simulation() { if (is_admin()) { return; } if (empty($_COOKIE['litespeed_hash']) && empty($_COOKIE['litespeed_flash_hash'])) { return; } self::debug('🪪 starting role validation'); // Check if is from crawler // if ( empty( $_SERVER[ 'HTTP_USER_AGENT' ] ) || strpos( $_SERVER[ 'HTTP_USER_AGENT' ], Crawler::FAST_USER_AGENT ) !== 0 ) { // Debug2::debug( '[Router] user agent not match' ); // return; // } $server_ip = $this->conf(self::O_SERVER_IP); if (!$server_ip || self::get_ip() !== $server_ip) { self::debug('❌❌ Role simulate uid denied! Not localhost visit!'); Control::set_nocache('Role simulate uid denied'); return; } // Flash hash validation if (!empty($_COOKIE['litespeed_flash_hash'])) { $hash_data = self::get_option(self::ITEM_FLASH_HASH, array()); if ($hash_data && is_array($hash_data) && !empty($hash_data['hash']) && !empty($hash_data['ts']) && !empty($hash_data['uid'])) { if (time() - $hash_data['ts'] < 120 && $_COOKIE['litespeed_flash_hash'] == $hash_data['hash']) { self::debug('🪪 Role simulator flash hash matched, escalating user to be uid=' . $hash_data['uid']); self::delete_option(self::ITEM_FLASH_HASH); wp_set_current_user($hash_data['uid']); return; } } } // Hash validation if (!empty($_COOKIE['litespeed_hash'])) { $hash_data = self::get_option(self::ITEM_HASH, array()); if ($hash_data && is_array($hash_data) && !empty($hash_data['hash']) && !empty($hash_data['ts']) && !empty($hash_data['uid'])) { $RUN_DURATION = $this->cls('Crawler')->get_crawler_duration(); if (time() - $hash_data['ts'] < $RUN_DURATION && $_COOKIE['litespeed_hash'] == $hash_data['hash']) { self::debug('🪪 Role simulator hash matched, escalating user to be uid=' . $hash_data['uid']); wp_set_current_user($hash_data['uid']); return; } } } self::debug('❌ WARNING: role simulator hash not match'); } /** * Get a short ttl hash (2mins) * * @since 6.4 */ public function get_flash_hash($uid) { $hash_data = self::get_option(self::ITEM_FLASH_HASH, array()); if ($hash_data && is_array($hash_data) && !empty($hash_data['hash']) && !empty($hash_data['ts'])) { if (time() - $hash_data['ts'] < 60) { return $hash_data['hash']; } } // Check if this user has editor access or not if (user_can($uid, 'edit_posts')) { self::debug('🛑 The user with id ' . $uid . ' has editor access, which is not allowed for the role simulator.'); return ''; } $hash = Str::rrand(32); self::update_option(self::ITEM_FLASH_HASH, array('hash' => $hash, 'ts' => time(), 'uid' => $uid)); return $hash; } /** * Get a security hash * * @since 3.3 */ public function get_hash($uid) { // Check if this user has editor access or not if (user_can($uid, 'edit_posts')) { self::debug('🛑 The user with id ' . $uid . ' has editor access, which is not allowed for the role simulator.'); return ''; } // As this is called only when starting crawling, not per page, no need to reuse $hash = Str::rrand(32); self::update_option(self::ITEM_HASH, array('hash' => $hash, 'ts' => time(), 'uid' => $uid)); return $hash; } /** * Get user role * * @since 1.6.2 */ public static function get_role($uid = null) { if (defined('LITESPEED_WP_ROLE')) { return LITESPEED_WP_ROLE; } if ($uid === null) { $uid = get_current_user_id(); } $role = false; if ($uid) { $user = get_userdata($uid); if (isset($user->roles) && is_array($user->roles)) { $tmp = array_values($user->roles); $role = implode(',', $tmp); // Combine for PHP5.3 const comaptibility } } Debug2::debug('[Router] get_role: ' . $role); if (!$role) { return $role; // Guest user Debug2::debug('[Router] role: guest'); /** * Fix double login issue * The previous user init refactoring didn't fix this bcos this is in login process and the user role could change * @see https://github.com/litespeedtech/lscache_wp/commit/69e7bc71d0de5cd58961bae953380b581abdc088 * @since 2.9.8 Won't assign const if in login process */ if (substr_compare(wp_login_url(), $GLOBALS['pagenow'], -strlen($GLOBALS['pagenow'])) === 0) { return $role; } } define('LITESPEED_WP_ROLE', $role); return LITESPEED_WP_ROLE; } /** * Get frontend path * * @since 1.2.2 * @access public * @return boolean */ public static function frontend_path() { //todo: move to htaccess.cls ? if (!isset(self::$_frontend_path)) { $frontend = rtrim(ABSPATH, '/'); // /home/user/public_html/frontend // get home path failed. Trac ticket #37668 (e.g. frontend:/blog backend:/wordpress) if (!$frontend) { Debug2::debug('[Router] No ABSPATH, generating from home option'); $frontend = parse_url(get_option('home')); $frontend = !empty($frontend['path']) ? $frontend['path'] : ''; $frontend = $_SERVER['DOCUMENT_ROOT'] . $frontend; } $frontend = realpath($frontend); self::$_frontend_path = $frontend; } return self::$_frontend_path; } /** * Check if ESI is enabled or not * * @since 1.2.0 * @access public * @return boolean */ public function esi_enabled() { if (!isset(self::$_esi_enabled)) { self::$_esi_enabled = defined('LITESPEED_ON') && $this->conf(self::O_ESI); if (!empty($_REQUEST[self::ACTION])) { self::$_esi_enabled = false; } } return self::$_esi_enabled; } /** * Check if crawler is enabled on server level * * @since 1.1.1 * @access public */ public static function can_crawl() { if (isset($_SERVER['X-LSCACHE']) && strpos($_SERVER['X-LSCACHE'], 'crawler') === false) { return false; } // CLI will bypass this check as crawler library can always do the 428 check if (defined('LITESPEED_CLI')) { return true; } return true; } /** * Check action * * @since 1.1.0 * @access public * @return string */ public static function get_action() { if (!isset(self::$_action)) { self::$_action = false; self::cls()->verify_action(); if (self::$_action) { defined('LSCWP_LOG') && Debug2::debug('[Router] LSCWP_CTRL verified: ' . var_export(self::$_action, true)); } } return self::$_action; } /** * Check if is logged in * * @since 1.1.3 * @access public * @return boolean */ public static function is_logged_in() { if (!isset(self::$_is_logged_in)) { self::$_is_logged_in = is_user_logged_in(); } return self::$_is_logged_in; } /** * Check if is ajax call * * @since 1.1.0 * @access public * @return boolean */ public static function is_ajax() { if (!isset(self::$_is_ajax)) { self::$_is_ajax = defined('DOING_AJAX') && DOING_AJAX; } return self::$_is_ajax; } /** * Check if is admin ip * * @since 1.1.0 * @access public * @return boolean */ public function is_admin_ip() { if (!isset(self::$_is_admin_ip)) { $ips = $this->conf(self::O_DEBUG_IPS); self::$_is_admin_ip = $this->ip_access($ips); } return self::$_is_admin_ip; } /** * Get type value * * @since 1.6 * @access public */ public static function verify_type() { if (empty($_REQUEST[self::TYPE])) { Debug2::debug('[Router] no type', 2); return false; } Debug2::debug('[Router] parsed type: ' . $_REQUEST[self::TYPE], 2); return $_REQUEST[self::TYPE]; } /** * Check privilege and nonce for the action * * @since 1.1.0 * @access private */ private function verify_action() { if (empty($_REQUEST[Router::ACTION])) { Debug2::debug2('[Router] LSCWP_CTRL bypassed empty'); return; } $action = stripslashes($_REQUEST[Router::ACTION]); if (!$action) { return; } $_is_public_action = false; // Each action must have a valid nonce unless its from admin ip and is public action // Validate requests nonce (from admin logged in page or cli) if (!$this->verify_nonce($action)) { // check if it is from admin ip if (!$this->is_admin_ip()) { Debug2::debug('[Router] LSCWP_CTRL query string - did not match admin IP: ' . $action); return; } // check if it is public action if ( !in_array($action, array( Core::ACTION_QS_NOCACHE, Core::ACTION_QS_PURGE, Core::ACTION_QS_PURGE_SINGLE, Core::ACTION_QS_SHOW_HEADERS, Core::ACTION_QS_PURGE_ALL, Core::ACTION_QS_PURGE_EMPTYCACHE, )) ) { Debug2::debug('[Router] LSCWP_CTRL query string - did not match admin IP Actions: ' . $action); return; } if (apply_filters('litespeed_qs_forbidden', false)) { Debug2::debug('[Router] LSCWP_CTRL forbidden by hook litespeed_qs_forbidden'); return; } $_is_public_action = true; } /* Now it is a valid action, lets log and check the permission */ Debug2::debug('[Router] LSCWP_CTRL: ' . $action); // OK, as we want to do something magic, lets check if its allowed $_is_multisite = is_multisite(); $_is_network_admin = $_is_multisite && is_network_admin(); $_can_network_option = $_is_network_admin && current_user_can('manage_network_options'); $_can_option = current_user_can('manage_options'); switch ($action) { case self::ACTION_SAVE_SETTINGS_NETWORK: // Save network settings if ($_can_network_option) { self::$_action = $action; } return; case Core::ACTION_PURGE_BY: if (defined('LITESPEED_ON') && ($_can_network_option || $_can_option || self::is_ajax())) { //here may need more security self::$_action = $action; } return; case self::ACTION_DB_OPTM: if ($_can_network_option || $_can_option) { self::$_action = $action; } return; case Core::ACTION_PURGE_EMPTYCACHE: // todo: moved to purge.cls type action if ((defined('LITESPEED_ON') || $_is_network_admin) && ($_can_network_option || (!$_is_multisite && $_can_option))) { self::$_action = $action; } return; case Core::ACTION_QS_NOCACHE: case Core::ACTION_QS_PURGE: case Core::ACTION_QS_PURGE_SINGLE: case Core::ACTION_QS_SHOW_HEADERS: case Core::ACTION_QS_PURGE_ALL: case Core::ACTION_QS_PURGE_EMPTYCACHE: if (defined('LITESPEED_ON') && ($_is_public_action || self::is_ajax())) { self::$_action = $action; } return; case self::ACTION_ADMIN_DISPLAY: case self::ACTION_PLACEHOLDER: case self::ACTION_AVATAR: case self::ACTION_IMG_OPTM: case self::ACTION_CLOUD: case self::ACTION_CDN_CLOUDFLARE: case self::ACTION_CRAWLER: case self::ACTION_PRESET: case self::ACTION_IMPORT: case self::ACTION_REPORT: case self::ACTION_CSS: case self::ACTION_UCSS: case self::ACTION_VPI: case self::ACTION_CONF: case self::ACTION_ACTIVATION: case self::ACTION_HEALTH: case self::ACTION_SAVE_SETTINGS: // Save settings if ($_can_option && !$_is_network_admin) { self::$_action = $action; } return; case self::ACTION_PURGE: case self::ACTION_DEBUG2: if ($_can_network_option || $_can_option) { self::$_action = $action; } return; case Core::ACTION_DISMISS: /** * Non ajax call can dismiss too * @since 2.9 */ // if ( self::is_ajax() ) { self::$_action = $action; // } return; default: Debug2::debug('[Router] LSCWP_CTRL match failed: ' . $action); return; } } /** * Verify nonce * * @since 1.1.0 * @access public * @param string $action * @return bool */ public function verify_nonce($action) { if (!isset($_REQUEST[Router::NONCE]) || !wp_verify_nonce($_REQUEST[Router::NONCE], $action)) { return false; } else { return true; } } /** * Check if the ip is in the range * * @since 1.1.0 * @access public */ public function ip_access($ip_list) { if (!$ip_list) { return false; } if (!isset(self::$_ip)) { self::$_ip = self::get_ip(); } if (!self::$_ip) { return false; } // $uip = explode('.', $_ip); // if(empty($uip) || count($uip) != 4) Return false; // foreach($ip_list as $key => $ip) $ip_list[$key] = explode('.', trim($ip)); // foreach($ip_list as $key => $ip) { // if(count($ip) != 4) continue; // for($i = 0; $i <= 3; $i++) if($ip[$i] == '*') $ip_list[$key][$i] = $uip[$i]; // } return in_array(self::$_ip, $ip_list); } /** * Get client ip * * @since 1.1.0 * @since 1.6.5 changed to public * @access public * @return string */ public static function get_ip() { $_ip = ''; // if ( function_exists( 'apache_request_headers' ) ) { // $apache_headers = apache_request_headers(); // $_ip = ! empty( $apache_headers['True-Client-IP'] ) ? $apache_headers['True-Client-IP'] : false; // if ( ! $_ip ) { // $_ip = ! empty( $apache_headers['X-Forwarded-For'] ) ? $apache_headers['X-Forwarded-For'] : false; // $_ip = explode( ',', $_ip ); // $_ip = $_ip[ 0 ]; // } // } if (!$_ip) { $_ip = !empty($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : false; } return $_ip; } /** * Check if opcode cache is enabled * * @since 1.8.2 * @access public */ public static function opcache_enabled() { return function_exists('opcache_reset') && ini_get('opcache.enable'); } /** * Handle static files * * @since 3.0 */ public function serve_static() { if (!empty($_SERVER['SCRIPT_URI'])) { if (strpos($_SERVER['SCRIPT_URI'], LITESPEED_STATIC_URL . '/') !== 0) { return; } $path = substr($_SERVER['SCRIPT_URI'], strlen(LITESPEED_STATIC_URL . '/')); } elseif (!empty($_SERVER['REQUEST_URI'])) { $static_path = parse_url(LITESPEED_STATIC_URL, PHP_URL_PATH) . '/'; if (strpos($_SERVER['REQUEST_URI'], $static_path) !== 0) { return; } $path = substr(parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH), strlen($static_path)); } else { return; } $path = explode('/', $path, 2); if (empty($path[0]) || empty($path[1])) { return; } switch ($path[0]) { case 'avatar': $this->cls('Avatar')->serve_static($path[1]); break; case 'localres': $this->cls('Localization')->serve_static($path[1]); break; default: break; } } /** * Handle all request actions from main cls * * This is different than other handlers * * @since 3.0 * @access public */ public function handler($cls) { if (!in_array($cls, self::$_HANDLERS)) { return; } return $this->cls($cls)->handler(); } } src/vary.cls.php 0000644 00000050134 15246276230 0007617 0 ustar 00 <?php /** * The plugin vary class to manage X-LiteSpeed-Vary * * @since 1.1.3 */ namespace LiteSpeed; defined('WPINC') || exit(); class Vary extends Root { const LOG_TAG = '🔱'; const X_HEADER = 'X-LiteSpeed-Vary'; private static $_vary_name = '_lscache_vary'; // this default vary cookie is used for logged in status check private static $_can_change_vary = false; // Currently only AJAX used this /** * Adds the actions used for setting up cookies on log in/out. * * Also checks if the database matches the rewrite rule. * * @since 1.0.4 */ // public function init() // { // $this->_update_vary_name(); // } /** * Update the default vary name if changed * * @since 4.0 * @since 7.0 Moved to after_user_init to allow ESI no-vary no conflict w/ LSCACHE_VARY_COOKIE/O_CACHE_LOGIN_COOKIE */ private function _update_vary_name() { $db_cookie = $this->conf(Base::O_CACHE_LOGIN_COOKIE); // [3.0] todo: check if works in network's sites // If no vary set in rewrite rule if (!isset($_SERVER['LSCACHE_VARY_COOKIE'])) { if ($db_cookie) { // Check if is from ESI req or not. If from ESI no-vary, no need to set no-cache $something_wrong = true; if (!empty($_GET[ESI::QS_ACTION]) && !empty($_GET['_control'])) { // Have to manually build this checker bcoz ESI is not init yet. $control = explode(',', $_GET['_control']); if (in_array('no-vary', $control)) { self::debug('no-vary control existed, bypass vary_name update'); $something_wrong = false; self::$_vary_name = $db_cookie; } } if (defined('LITESPEED_CLI') || defined('DOING_CRON')) { $something_wrong = false; } if ($something_wrong) { // Display cookie error msg to admin if (is_multisite() ? is_network_admin() : is_admin()) { Admin_Display::show_error_cookie(); } Control::set_nocache('❌❌ vary cookie setting error'); } } return; } // If db setting does not exist, skip checking db value if (!$db_cookie) { return; } // beyond this point, need to make sure db vary setting is in $_SERVER env. $vary_arr = explode(',', $_SERVER['LSCACHE_VARY_COOKIE']); if (in_array($db_cookie, $vary_arr)) { self::$_vary_name = $db_cookie; return; } if (is_multisite() ? is_network_admin() : is_admin()) { Admin_Display::show_error_cookie(); } Control::set_nocache('vary cookie setting lost error'); } /** * Hooks after user init * * @since 4.0 */ public function after_user_init() { $this->_update_vary_name(); // logged in user if (Router::is_logged_in()) { // If not esi, check cache logged-in user setting if (!$this->cls('Router')->esi_enabled()) { // If cache logged-in, then init cacheable to private if ($this->conf(Base::O_CACHE_PRIV)) { add_action('wp_logout', __NAMESPACE__ . '\Purge::purge_on_logout'); $this->cls('Control')->init_cacheable(); Control::set_private('logged in user'); } // No cache for logged-in user else { Control::set_nocache('logged in user'); } } // ESI is on, can be public cache else { // Need to make sure vary is using group id $this->cls('Control')->init_cacheable(); } // register logout hook to clear login status add_action('clear_auth_cookie', array($this, 'remove_logged_in')); } else { // Only after vary init, can detect if is Guest mode or not // Here need `self::$_vary_name` to be set first. $this->_maybe_guest_mode(); // Set vary cookie for logging in user, otherwise the user will hit public with vary=0 (guest version) add_action('set_logged_in_cookie', array($this, 'add_logged_in'), 10, 4); add_action('wp_login', __NAMESPACE__ . '\Purge::purge_on_logout'); $this->cls('Control')->init_cacheable(); // Check `login page` cacheable setting because they don't go through main WP logic add_action('login_init', array($this->cls('Tag'), 'check_login_cacheable'), 5); if (!empty($_GET['litespeed_guest'])) { add_action('wp_loaded', array($this, 'update_guest_vary'), 20); } } // Add comment list ESI add_filter('comments_array', array($this, 'check_commenter')); // Set vary cookie for commenter. add_action('set_comment_cookies', array($this, 'append_commenter')); /** * Don't change for REST call because they don't carry on user info usually * @since 1.6.7 */ add_action('rest_api_init', function () { // this hook is fired in `init` hook self::debug('Rest API init disabled vary change'); add_filter('litespeed_can_change_vary', '__return_false'); }); } /** * Check if is Guest mode or not * * @since 4.0 */ private function _maybe_guest_mode() { if (defined('LITESPEED_GUEST')) { self::debug('👒👒 Guest mode ' . (LITESPEED_GUEST ? 'predefined' : 'turned off')); return; } if (!$this->conf(Base::O_GUEST)) { return; } // If vary is set, then not a guest if (self::has_vary()) { return; } // If has admin QS, then no guest if (!empty($_GET[Router::ACTION])) { return; } if (defined('DOING_AJAX')) { return; } if (defined('DOING_CRON')) { return; } // If is the request to update vary, then no guest // Don't need anymore as it is always ajax call // Still keep it in case some WP blocked the lightweight guest vary update script, WP can still update the vary if (!empty($_GET['litespeed_guest'])) { return; } /* @ref https://wordpress.org/support/topic/checkout-add-to-cart-executed-twice/ */ if (!empty($_GET['litespeed_guest_off'])) { return; } self::debug('👒👒 Guest mode'); !defined('LITESPEED_GUEST') && define('LITESPEED_GUEST', true); if ($this->conf(Base::O_GUEST_OPTM)) { !defined('LITESPEED_GUEST_OPTM') && define('LITESPEED_GUEST_OPTM', true); } } /** * Update Guest vary * * @since 4.0 * @deprecated 4.1 Use independent lightweight guest.vary.php as a replacement */ public function update_guest_vary() { // This process must not be cached !defined('LSCACHE_NO_CACHE') && define('LSCACHE_NO_CACHE', true); $_guest = new Lib\Guest(); if ($_guest->always_guest() || self::has_vary()) { // If contains vary already, don't reload to avoid infinite loop when parent page having browser cache !defined('LITESPEED_GUEST') && define('LITESPEED_GUEST', true); // Reuse this const to bypass set vary in vary finalize self::debug('🤠🤠 Guest'); echo '[]'; exit(); } self::debug('Will update guest vary in finalize'); // return json echo \json_encode(array('reload' => 'yes')); exit(); } /** * Hooked to the comments_array filter. * * Check if the user accessing the page has the commenter cookie. * * If the user does not want to cache commenters, just check if user is commenter. * Otherwise if the vary cookie is set, unset it. This is so that when the page is cached, the page will appear as if the user was a normal user. * Normal user is defined as not a logged in user and not a commenter. * * @since 1.0.4 * @access public * @global type $post * @param array $comments The current comments to output * @return array The comments to output. */ public function check_commenter($comments) { /** * Hook to bypass pending comment check for comment related plugins compatibility * @since 2.9.5 */ if (apply_filters('litespeed_vary_check_commenter_pending', true)) { $pending = false; foreach ($comments as $comment) { if (!$comment->comment_approved) { // current user has pending comment $pending = true; break; } } // No pending comments, don't need to add private cache if (!$pending) { self::debug('No pending comment'); $this->remove_commenter(); // Remove commenter prefilled info if exists, for public cache foreach ($_COOKIE as $cookie_name => $cookie_value) { if (strlen($cookie_name) >= 15 && strpos($cookie_name, 'comment_author_') === 0) { unset($_COOKIE[$cookie_name]); } } return $comments; } } // Current user/visitor has pending comments // set vary=2 for next time vary lookup $this->add_commenter(); if ($this->conf(Base::O_CACHE_COMMENTER)) { Control::set_private('existing commenter'); } else { Control::set_nocache('existing commenter'); } return $comments; } /** * Check if default vary has a value * * @since 1.1.3 * @access public */ public static function has_vary() { if (empty($_COOKIE[self::$_vary_name])) { return false; } return $_COOKIE[self::$_vary_name]; } /** * Append user status with logged in * * @since 1.1.3 * @since 1.6.2 Removed static referral * @access public */ public function add_logged_in($logged_in_cookie = false, $expire = false, $expiration = false, $uid = false) { self::debug('add_logged_in'); /** * NOTE: Run before `$this->_update_default_vary()` to make vary changeable * @since 2.2.2 */ self::can_ajax_vary(); // If the cookie is lost somehow, set it $this->_update_default_vary($uid, $expire); } /** * Remove user logged in status * * @since 1.1.3 * @since 1.6.2 Removed static referral * @access public */ public function remove_logged_in() { self::debug('remove_logged_in'); /** * NOTE: Run before `$this->_update_default_vary()` to make vary changeable * @since 2.2.2 */ self::can_ajax_vary(); // Force update vary to remove login status $this->_update_default_vary(-1); } /** * Allow vary can be changed for ajax calls * * @since 2.2.2 * @since 2.6 Changed to static * @access public */ public static function can_ajax_vary() { self::debug('_can_change_vary -> true'); self::$_can_change_vary = true; } /** * Check if can change default vary * * @since 1.6.2 * @access private */ private function can_change_vary() { // Don't change for ajax due to ajax not sending webp header if (Router::is_ajax()) { if (!self::$_can_change_vary) { self::debug('can_change_vary bypassed due to ajax call'); return false; } } /** * POST request can set vary to fix #820789 login "loop" guest cache issue * @since 1.6.5 */ if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] !== 'GET' && $_SERVER['REQUEST_METHOD'] !== 'POST') { self::debug('can_change_vary bypassed due to method not get/post'); return false; } /** * Disable vary change if is from crawler * @since 2.9.8 To enable woocommerce cart not empty warm up (@Taba) */ if (!empty($_SERVER['HTTP_USER_AGENT']) && strpos($_SERVER['HTTP_USER_AGENT'], Crawler::FAST_USER_AGENT) === 0) { self::debug('can_change_vary bypassed due to crawler'); return false; } if (!apply_filters('litespeed_can_change_vary', true)) { self::debug('can_change_vary bypassed due to litespeed_can_change_vary hook'); return false; } return true; } /** * Update default vary * * @since 1.6.2 * @since 1.6.6.1 Add ran check to make it only run once ( No run multiple times due to login process doesn't have valid uid ) * @access private */ private function _update_default_vary($uid = false, $expire = false) { // Make sure header output only run once if (!defined('LITESPEED_DID_' . __FUNCTION__)) { define('LITESPEED_DID_' . __FUNCTION__, true); } else { self::debug2('_update_default_vary bypassed due to run already'); return; } // ESI shouldn't change vary (Let main page do only) if (defined('LSCACHE_IS_ESI') && LSCACHE_IS_ESI) { self::debug2('_update_default_vary bypassed due to ESI'); return; } // If the cookie is lost somehow, set it $vary = $this->finalize_default_vary($uid); $current_vary = self::has_vary(); if ($current_vary !== $vary && $current_vary !== 'commenter' && $this->can_change_vary()) { // $_COOKIE[ self::$_vary_name ] = $vary; // not needed // save it if (!$expire) { $expire = time() + 2 * DAY_IN_SECONDS; } $this->_cookie($vary, $expire); // Control::set_nocache( 'changing default vary' . " $current_vary => $vary" ); } } /** * Get vary name * * @since 1.9.1 * @access public */ public function get_vary_name() { return self::$_vary_name; } /** * Check if one user role is in vary group settings * * @since 1.2.0 * @since 3.0 Moved here from conf.cls * @access public * @param string $role The user role * @return int The set value if already set */ public function in_vary_group($role) { $group = 0; $vary_groups = $this->conf(Base::O_CACHE_VARY_GROUP); $roles = explode(',', $role); if ($found = array_intersect($roles, array_keys($vary_groups))) { $groups = array(); foreach ($found as $curr_role) { $groups[] = $vary_groups[$curr_role]; } $group = implode(',', array_unique($groups)); } elseif (in_array('administrator', $roles)) { $group = 99; } if ($group) { self::debug2('role in vary_group [group] ' . $group); } return $group; } /** * Finalize default Vary Cookie * * Get user vary tag based on admin_bar & role * * NOTE: Login process will also call this because it does not call wp hook as normal page loading * * @since 1.6.2 * @access public */ public function finalize_default_vary($uid = false) { // Must check this to bypass vary generation for guests // Must check this to avoid Guest page's CSS/JS/CCSS/UCSS get non-guest vary filename if (defined('LITESPEED_GUEST') && LITESPEED_GUEST) { return false; } $vary = array(); if ($this->conf(Base::O_GUEST)) { $vary['guest_mode'] = 1; } if (!$uid) { $uid = get_current_user_id(); } else { self::debug('uid: ' . $uid); } // get user's group id $role = Router::get_role($uid); if ($uid > 0 && $role) { $vary['logged-in'] = 1; // parse role group from settings if ($role_group = $this->in_vary_group($role)) { $vary['role'] = $role_group; } // Get admin bar set // see @_get_admin_bar_pref() $pref = get_user_option('show_admin_bar_front', $uid); self::debug2('show_admin_bar_front: ' . $pref); $admin_bar = $pref === false || $pref === 'true'; if ($admin_bar) { $vary['admin_bar'] = 1; self::debug2('admin bar : true'); } } else { // Guest user self::debug('role id: failed, guest'); } /** * Add filter * @since 1.6 Added for Role Excludes for optimization cls * @since 1.6.2 Hooked to webp (checked in v4, no webp anymore) * @since 3.0 Used by 3rd hooks too */ $vary = apply_filters('litespeed_vary', $vary); if (!$vary) { return false; } ksort($vary); $res = array(); foreach ($vary as $key => $val) { $res[] = $key . ':' . $val; } $res = implode(';', $res); if (defined('LSCWP_LOG')) { return $res; } // Encrypt in production return md5($this->conf(Base::HASH) . $res); } /** * Get the hash of all vary related values * * @since 4.0 */ public function finalize_full_varies() { $vary = $this->_finalize_curr_vary_cookies(true); $vary .= $this->finalize_default_vary(get_current_user_id()); $vary .= $this->get_env_vary(); return $vary; } /** * Get request environment Vary * * @since 4.0 */ public function get_env_vary() { $env_vary = isset($_SERVER['LSCACHE_VARY_VALUE']) ? $_SERVER['LSCACHE_VARY_VALUE'] : false; if (!$env_vary) { $env_vary = isset($_SERVER['HTTP_X_LSCACHE_VARY_VALUE']) ? $_SERVER['HTTP_X_LSCACHE_VARY_VALUE'] : false; } return $env_vary; } /** * Append user status with commenter * * This is ONLY used when submit a comment * * @since 1.1.6 * @access public */ public function append_commenter() { $this->add_commenter(true); } /** * Correct user status with commenter * * @since 1.1.3 * @access private * @param boolean $from_redirect If the request is from redirect page or not */ private function add_commenter($from_redirect = false) { // If the cookie is lost somehow, set it if (self::has_vary() !== 'commenter') { self::debug('Add commenter'); // $_COOKIE[ self::$_vary_name ] = 'commenter'; // not needed // save it // only set commenter status for current domain path $this->_cookie('commenter', time() + apply_filters('comment_cookie_lifetime', 30000000), self::_relative_path($from_redirect)); // Control::set_nocache( 'adding commenter status' ); } } /** * Remove user commenter status * * @since 1.1.3 * @access private */ private function remove_commenter() { if (self::has_vary() === 'commenter') { self::debug('Remove commenter'); // remove logged in status from global var // unset( $_COOKIE[ self::$_vary_name ] ); // not needed // save it $this->_cookie(false, false, self::_relative_path()); // Control::set_nocache( 'removing commenter status' ); } } /** * Generate relative path for cookie * * @since 1.1.3 * @access private * @param boolean $from_redirect If the request is from redirect page or not */ private static function _relative_path($from_redirect = false) { $path = false; $tag = $from_redirect ? 'HTTP_REFERER' : 'SCRIPT_URL'; if (!empty($_SERVER[$tag])) { $path = parse_url($_SERVER[$tag]); $path = !empty($path['path']) ? $path['path'] : false; self::debug('Cookie Vary path: ' . $path); } return $path; } /** * Builds the vary header. * * NOTE: Non caccheable page can still set vary ( for logged in process ) * * Currently, this only checks post passwords and 3rd party. * * @since 1.0.13 * @access public * @global $post * @return mixed false if the user has the postpass cookie. Empty string if the post is not password protected. Vary header otherwise. */ public function finalize() { // Finalize default vary if (!defined('LITESPEED_GUEST') || !LITESPEED_GUEST) { $this->_update_default_vary(); } $tp_cookies = $this->_finalize_curr_vary_cookies(); if (!$tp_cookies) { self::debug2('no custimzed vary'); return; } self::debug('finalized 3rd party cookies', $tp_cookies); return self::X_HEADER . ': ' . implode(',', $tp_cookies); } /** * Gets vary cookies or their values unique hash that are already added for the current page. * * @since 1.0.13 * @access private * @return array List of all vary cookies currently added. */ private function _finalize_curr_vary_cookies($values_json = false) { global $post; $cookies = array(); // No need to append default vary cookie name if (!empty($post->post_password)) { $postpass_key = 'wp-postpass_' . COOKIEHASH; if ($this->_get_cookie_val($postpass_key)) { self::debug('finalize bypassed due to password protected vary '); // If user has password cookie, do not cache & ignore existing vary cookies Control::set_nocache('password protected vary'); return false; } $cookies[] = $values_json ? $this->_get_cookie_val($postpass_key) : $postpass_key; } $cookies = apply_filters('litespeed_vary_curr_cookies', $cookies); if ($cookies) { $cookies = array_filter(array_unique($cookies)); self::debug('vary cookies changed by filter litespeed_vary_curr_cookies', $cookies); } if (!$cookies) { return false; } // Format cookie name data or value data sort($cookies); // This is to maintain the cookie val orders for $values_json=true case. foreach ($cookies as $k => $v) { $cookies[$k] = $values_json ? $this->_get_cookie_val($v) : 'cookie=' . $v; } return $values_json ? \json_encode($cookies) : $cookies; } /** * Get one vary cookie value * * @since 4.0 */ private function _get_cookie_val($key) { if (!empty($_COOKIE[$key])) { return $_COOKIE[$key]; } return false; } /** * Set the vary cookie. * * If vary cookie changed, must set non cacheable. * * @since 1.0.4 * @access private * @param integer $val The value to update. * @param integer $expire Expire time. * @param boolean $path False if use wp root path as cookie path */ private function _cookie($val = false, $expire = false, $path = false) { if (!$val) { $expire = 1; } /** * Add HTTPS bypass in case clients use both HTTP and HTTPS version of site * @since 1.7 */ $is_ssl = $this->conf(Base::O_UTIL_NO_HTTPS_VARY) ? false : is_ssl(); setcookie(self::$_vary_name, $val, $expire, $path ?: COOKIEPATH, COOKIE_DOMAIN, $is_ssl, true); self::debug('set_cookie ---> [k] ' . self::$_vary_name . " [v] $val [ttl] " . ($expire - time())); } } src/str.cls.php 0000644 00000004575 15246276230 0007456 0 ustar 00 <?php /** * LiteSpeed String Operator Library Class * * @since 1.3 */ namespace LiteSpeed; defined('WPINC') || exit(); class Str { /** * Translate QC HTML links from html. Convert `<a href="{#xxx#}">xxxx</a>` to `<a href="xxx">xxxx</a>` * * @since 7.0 */ public static function translate_qc_apis($html) { preg_match_all('/<a href="{#(\w+)#}"/U', $html, $matches); if (!$matches) { return $html; } foreach ($matches[0] as $k => $html_to_be_replaced) { $link = '<a href="' . Utility::build_url(Router::ACTION_CLOUD, Cloud::TYPE_API, false, null, array('action2' => $matches[1][$k])) . '"'; $html = str_replace($html_to_be_replaced, $link, $html); } return $html; } /** * Return safe HTML * * @since 7.0 */ public static function safe_html($html) { $common_attrs = array( 'style' => array(), 'class' => array(), 'target' => array(), 'src' => array(), 'color' => array(), 'href' => array(), ); $tags = array('hr', 'h3', 'h4', 'h5', 'ul', 'li', 'br', 'strong', 'p', 'span', 'img', 'a', 'div', 'font'); $allowed_tags = array(); foreach ($tags as $tag) { $allowed_tags[$tag] = $common_attrs; } return wp_kses($html, $allowed_tags); } /** * Generate random string * * @since 1.3 * @access public * @param int $len Length of string * @param int $type 1-Number 2-LowerChar 4-UpperChar * @return string */ public static function rrand($len, $type = 7) { switch ($type) { case 0: $charlist = '012'; break; case 1: $charlist = '0123456789'; break; case 2: $charlist = 'abcdefghijklmnopqrstuvwxyz'; break; case 3: $charlist = '0123456789abcdefghijklmnopqrstuvwxyz'; break; case 4: $charlist = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; break; case 5: $charlist = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'; break; case 6: $charlist = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; break; case 7: $charlist = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; break; } $str = ''; $max = strlen($charlist) - 1; for ($i = 0; $i < $len; $i++) { $str .= $charlist[random_int(0, $max)]; } return $str; } /** * Trim double quotes from a string to be used as a preformatted src in HTML. * @since 6.5.3 */ public static function trim_quotes($string) { return str_replace('"', '', $string); } } src/tool.cls.php 0000644 00000006653 15246276230 0007622 0 ustar 00 <?php /** * The tools * * @since 3.0 * @package LiteSpeed * @subpackage LiteSpeed/inc * @author LiteSpeed Technologies <info@litespeedtech.com> */ namespace LiteSpeed; defined('WPINC') || exit(); class Tool extends Root { const LOG_TAG = '[Tool]'; /** * Get public IP * * @since 3.0 * @access public */ public function check_ip() { self::debug('✅ check_ip'); $response = wp_safe_remote_get('https://cyberpanel.sh/?ip', array( 'headers' => array( 'User-Agent' => 'curl/8.7.1', ), )); if (is_wp_error($response)) { return __('Failed to detect IP', 'litespeed-cache'); } $ip = trim($response['body']); self::debug('result [ip] ' . $ip); if (Utility::valid_ipv4($ip)) { return $ip; } return __('Failed to detect IP', 'litespeed-cache'); } /** * Heartbeat Control * * NOTE: since WP4.9, there could be a core bug that sometimes the hook is not working. * * @since 3.0 * @access public */ public function heartbeat() { add_action('wp_enqueue_scripts', array($this, 'heartbeat_frontend')); add_action('admin_enqueue_scripts', array($this, 'heartbeat_backend')); add_filter('heartbeat_settings', array($this, 'heartbeat_settings')); } /** * Heartbeat Control frontend control * * @since 3.0 * @access public */ public function heartbeat_frontend() { if (!$this->conf(Base::O_MISC_HEARTBEAT_FRONT)) { return; } if (!$this->conf(Base::O_MISC_HEARTBEAT_FRONT_TTL)) { wp_deregister_script('heartbeat'); Debug2::debug('[Tool] Deregistered frontend heartbeat'); } } /** * Heartbeat Control backend control * * @since 3.0 * @access public */ public function heartbeat_backend() { if ($this->_is_editor()) { if (!$this->conf(Base::O_MISC_HEARTBEAT_EDITOR)) { return; } if (!$this->conf(Base::O_MISC_HEARTBEAT_EDITOR_TTL)) { wp_deregister_script('heartbeat'); Debug2::debug('[Tool] Deregistered editor heartbeat'); } } else { if (!$this->conf(Base::O_MISC_HEARTBEAT_BACK)) { return; } if (!$this->conf(Base::O_MISC_HEARTBEAT_BACK_TTL)) { wp_deregister_script('heartbeat'); Debug2::debug('[Tool] Deregistered backend heartbeat'); } } } /** * Heartbeat Control settings * * @since 3.0 * @access public */ public function heartbeat_settings($settings) { // Check editor first to make frontend editor valid too if ($this->_is_editor()) { if ($this->conf(Base::O_MISC_HEARTBEAT_EDITOR)) { $settings['interval'] = $this->conf(Base::O_MISC_HEARTBEAT_EDITOR_TTL); Debug2::debug('[Tool] Heartbeat interval set to ' . $this->conf(Base::O_MISC_HEARTBEAT_EDITOR_TTL)); } } elseif (!is_admin()) { if ($this->conf(Base::O_MISC_HEARTBEAT_FRONT)) { $settings['interval'] = $this->conf(Base::O_MISC_HEARTBEAT_FRONT_TTL); Debug2::debug('[Tool] Heartbeat interval set to ' . $this->conf(Base::O_MISC_HEARTBEAT_FRONT_TTL)); } } else { if ($this->conf(Base::O_MISC_HEARTBEAT_BACK)) { $settings['interval'] = $this->conf(Base::O_MISC_HEARTBEAT_BACK_TTL); Debug2::debug('[Tool] Heartbeat interval set to ' . $this->conf(Base::O_MISC_HEARTBEAT_BACK_TTL)); } } return $settings; } /** * If is in editor * * @since 3.0 * @access public */ private function _is_editor() { $res = is_admin() && Utility::str_hit_array($_SERVER['REQUEST_URI'], array('post.php', 'post-new.php')); return apply_filters('litespeed_is_editor', $res); } } src/lang.cls.php 0000644 00000035617 15246276230 0007570 0 ustar 00 <?php /** * The language class. * * @since 3.0 * @package LiteSpeed_Cache * @subpackage LiteSpeed_Cache/inc * @author LiteSpeed Technologies <info@litespeedtech.com> */ namespace LiteSpeed; defined('WPINC') || exit(); class Lang extends Base { /** * Get image status per status bit * * @since 3.0 */ public static function img_status($status = null) { $list = array( Img_Optm::STATUS_NEW => __('Images not requested', 'litespeed-cache'), Img_Optm::STATUS_RAW => __('Images ready to request', 'litespeed-cache'), Img_Optm::STATUS_REQUESTED => __('Images requested', 'litespeed-cache'), Img_Optm::STATUS_NOTIFIED => __('Images notified to pull', 'litespeed-cache'), Img_Optm::STATUS_PULLED => __('Images optimized and pulled', 'litespeed-cache'), ); if ($status !== null) { return !empty($list[$status]) ? $list[$status] : 'N/A'; } return $list; } /** * Try translating a string * * @since 4.7 */ public static function maybe_translate($raw_string) { $map = array( 'auto_alias_failed_cdn' => __('Unable to automatically add %1$s as a Domain Alias for main %2$s domain, due to potential CDN conflict.', 'litespeed-cache') . ' ' . Doc::learn_more('https://quic.cloud/docs/cdn/dns/how-to-setup-domain-alias/', false, false, false, true), 'auto_alias_failed_uid' => __('Unable to automatically add %1$s as a Domain Alias for main %2$s domain.', 'litespeed-cache') . ' ' . __('Alias is in use by another QUIC.cloud account.', 'litespeed-cache') . ' ' . Doc::learn_more('https://quic.cloud/docs/cdn/dns/how-to-setup-domain-alias/', false, false, false, true), ); // Maybe has placeholder if (strpos($raw_string, '::')) { $replacements = explode('::', $raw_string); if (empty($map[$replacements[0]])) { return $raw_string; } $tpl = $map[$replacements[0]]; unset($replacements[0]); return vsprintf($tpl, array_values($replacements)); } // Direct translation only if (empty($map[$raw_string])) { return $raw_string; } return $map[$raw_string]; } /** * Get the title of id * * @since 3.0 * @access public */ public static function title($id) { $_lang_list = array( self::O_SERVER_IP => __('Server IP', 'litespeed-cache'), self::O_GUEST_UAS => __('Guest Mode User Agents', 'litespeed-cache'), self::O_GUEST_IPS => __('Guest Mode IPs', 'litespeed-cache'), self::O_CACHE => __('Enable Cache', 'litespeed-cache'), self::O_CACHE_BROWSER => __('Browser Cache', 'litespeed-cache'), self::O_CACHE_TTL_PUB => __('Default Public Cache TTL', 'litespeed-cache'), self::O_CACHE_TTL_PRIV => __('Default Private Cache TTL', 'litespeed-cache'), self::O_CACHE_TTL_FRONTPAGE => __('Default Front Page TTL', 'litespeed-cache'), self::O_CACHE_TTL_FEED => __('Default Feed TTL', 'litespeed-cache'), self::O_CACHE_TTL_REST => __('Default REST TTL', 'litespeed-cache'), self::O_CACHE_TTL_STATUS => __('Default HTTP Status Code Page TTL', 'litespeed-cache'), self::O_CACHE_TTL_BROWSER => __('Browser Cache TTL', 'litespeed-cache'), self::O_CACHE_AJAX_TTL => __('AJAX Cache TTL', 'litespeed-cache'), self::O_AUTO_UPGRADE => __('Automatically Upgrade', 'litespeed-cache'), self::O_GUEST => __('Guest Mode', 'litespeed-cache'), self::O_GUEST_OPTM => __('Guest Optimization', 'litespeed-cache'), self::O_NEWS => __('Notifications', 'litespeed-cache'), self::O_CACHE_PRIV => __('Cache Logged-in Users', 'litespeed-cache'), self::O_CACHE_COMMENTER => __('Cache Commenters', 'litespeed-cache'), self::O_CACHE_REST => __('Cache REST API', 'litespeed-cache'), self::O_CACHE_PAGE_LOGIN => __('Cache Login Page', 'litespeed-cache'), self::O_CACHE_RES => __('Cache PHP Resources', 'litespeed-cache'), self::O_CACHE_MOBILE => __('Cache Mobile', 'litespeed-cache'), self::O_CACHE_MOBILE_RULES => __('List of Mobile User Agents', 'litespeed-cache'), self::O_CACHE_PRIV_URI => __('Private Cached URIs', 'litespeed-cache'), self::O_CACHE_DROP_QS => __('Drop Query String', 'litespeed-cache'), self::O_OBJECT => __('Object Cache', 'litespeed-cache'), self::O_OBJECT_KIND => __('Method', 'litespeed-cache'), self::O_OBJECT_HOST => __('Host', 'litespeed-cache'), self::O_OBJECT_PORT => __('Port', 'litespeed-cache'), self::O_OBJECT_LIFE => __('Default Object Lifetime', 'litespeed-cache'), self::O_OBJECT_USER => __('Username', 'litespeed-cache'), self::O_OBJECT_PSWD => __('Password', 'litespeed-cache'), self::O_OBJECT_DB_ID => __('Redis Database ID', 'litespeed-cache'), self::O_OBJECT_GLOBAL_GROUPS => __('Global Groups', 'litespeed-cache'), self::O_OBJECT_NON_PERSISTENT_GROUPS => __('Do Not Cache Groups', 'litespeed-cache'), self::O_OBJECT_PERSISTENT => __('Persistent Connection', 'litespeed-cache'), self::O_OBJECT_ADMIN => __('Cache WP-Admin', 'litespeed-cache'), self::O_OBJECT_TRANSIENTS => __('Store Transients', 'litespeed-cache'), self::O_PURGE_ON_UPGRADE => __('Purge All On Upgrade', 'litespeed-cache'), self::O_PURGE_STALE => __('Serve Stale', 'litespeed-cache'), self::O_PURGE_TIMED_URLS => __('Scheduled Purge URLs', 'litespeed-cache'), self::O_PURGE_TIMED_URLS_TIME => __('Scheduled Purge Time', 'litespeed-cache'), self::O_CACHE_FORCE_URI => __('Force Cache URIs', 'litespeed-cache'), self::O_CACHE_FORCE_PUB_URI => __('Force Public Cache URIs', 'litespeed-cache'), self::O_CACHE_EXC => __('Do Not Cache URIs', 'litespeed-cache'), self::O_CACHE_EXC_QS => __('Do Not Cache Query Strings', 'litespeed-cache'), self::O_CACHE_EXC_CAT => __('Do Not Cache Categories', 'litespeed-cache'), self::O_CACHE_EXC_TAG => __('Do Not Cache Tags', 'litespeed-cache'), self::O_CACHE_EXC_ROLES => __('Do Not Cache Roles', 'litespeed-cache'), self::O_OPTM_CSS_MIN => __('CSS Minify', 'litespeed-cache'), self::O_OPTM_CSS_COMB => __('CSS Combine', 'litespeed-cache'), self::O_OPTM_CSS_COMB_EXT_INL => __('CSS Combine External and Inline', 'litespeed-cache'), self::O_OPTM_UCSS => __('Generate UCSS', 'litespeed-cache'), self::O_OPTM_UCSS_INLINE => __('UCSS Inline', 'litespeed-cache'), self::O_OPTM_UCSS_SELECTOR_WHITELIST => __('UCSS Selector Allowlist', 'litespeed-cache'), self::O_OPTM_UCSS_FILE_EXC_INLINE => __('UCSS File Excludes and Inline', 'litespeed-cache'), self::O_OPTM_UCSS_EXC => __('UCSS URI Excludes', 'litespeed-cache'), self::O_OPTM_JS_MIN => __('JS Minify', 'litespeed-cache'), self::O_OPTM_JS_COMB => __('JS Combine', 'litespeed-cache'), self::O_OPTM_JS_COMB_EXT_INL => __('JS Combine External and Inline', 'litespeed-cache'), self::O_OPTM_HTML_MIN => __('HTML Minify', 'litespeed-cache'), self::O_OPTM_HTML_LAZY => __('HTML Lazy Load Selectors', 'litespeed-cache'), self::O_OPTM_HTML_SKIP_COMMENTS => __('HTML Keep Comments', 'litespeed-cache'), self::O_OPTM_CSS_ASYNC => __('Load CSS Asynchronously', 'litespeed-cache'), self::O_OPTM_CCSS_PER_URL => __('CCSS Per URL', 'litespeed-cache'), self::O_OPTM_CSS_ASYNC_INLINE => __('Inline CSS Async Lib', 'litespeed-cache'), self::O_OPTM_CSS_FONT_DISPLAY => __('Font Display Optimization', 'litespeed-cache'), self::O_OPTM_JS_DEFER => __('Load JS Deferred', 'litespeed-cache'), self::O_OPTM_LOCALIZE => __('Localize Resources', 'litespeed-cache'), self::O_OPTM_LOCALIZE_DOMAINS => __('Localization Files', 'litespeed-cache'), self::O_OPTM_DNS_PREFETCH => __('DNS Prefetch', 'litespeed-cache'), self::O_OPTM_DNS_PREFETCH_CTRL => __('DNS Prefetch Control', 'litespeed-cache'), self::O_OPTM_DNS_PRECONNECT => __('DNS Preconnect', 'litespeed-cache'), self::O_OPTM_CSS_EXC => __('CSS Excludes', 'litespeed-cache'), self::O_OPTM_JS_DELAY_INC => __('JS Delayed Includes', 'litespeed-cache'), self::O_OPTM_JS_EXC => __('JS Excludes', 'litespeed-cache'), self::O_OPTM_QS_RM => __('Remove Query Strings', 'litespeed-cache'), self::O_OPTM_GGFONTS_ASYNC => __('Load Google Fonts Asynchronously', 'litespeed-cache'), self::O_OPTM_GGFONTS_RM => __('Remove Google Fonts', 'litespeed-cache'), self::O_OPTM_CCSS_CON => __('Critical CSS Rules', 'litespeed-cache'), self::O_OPTM_CCSS_SEP_POSTTYPE => __('Separate CCSS Cache Post Types', 'litespeed-cache'), self::O_OPTM_CCSS_SEP_URI => __('Separate CCSS Cache URIs', 'litespeed-cache'), self::O_OPTM_CCSS_SELECTOR_WHITELIST => __('CCSS Selector Allowlist', 'litespeed-cache'), self::O_OPTM_JS_DEFER_EXC => __('JS Deferred / Delayed Excludes', 'litespeed-cache'), self::O_OPTM_GM_JS_EXC => __('Guest Mode JS Excludes', 'litespeed-cache'), self::O_OPTM_EMOJI_RM => __('Remove WordPress Emoji', 'litespeed-cache'), self::O_OPTM_NOSCRIPT_RM => __('Remove Noscript Tags', 'litespeed-cache'), self::O_OPTM_EXC => __('URI Excludes', 'litespeed-cache'), self::O_OPTM_GUEST_ONLY => __('Optimize for Guests Only', 'litespeed-cache'), self::O_OPTM_EXC_ROLES => __('Role Excludes', 'litespeed-cache'), self::O_DISCUSS_AVATAR_CACHE => __('Gravatar Cache', 'litespeed-cache'), self::O_DISCUSS_AVATAR_CRON => __('Gravatar Cache Cron', 'litespeed-cache'), self::O_DISCUSS_AVATAR_CACHE_TTL => __('Gravatar Cache TTL', 'litespeed-cache'), self::O_MEDIA_LAZY => __('Lazy Load Images', 'litespeed-cache'), self::O_MEDIA_LAZY_EXC => __('Lazy Load Image Excludes', 'litespeed-cache'), self::O_MEDIA_LAZY_CLS_EXC => __('Lazy Load Image Class Name Excludes', 'litespeed-cache'), self::O_MEDIA_LAZY_PARENT_CLS_EXC => __('Lazy Load Image Parent Class Name Excludes', 'litespeed-cache'), self::O_MEDIA_IFRAME_LAZY_CLS_EXC => __('Lazy Load Iframe Class Name Excludes', 'litespeed-cache'), self::O_MEDIA_IFRAME_LAZY_PARENT_CLS_EXC => __('Lazy Load Iframe Parent Class Name Excludes', 'litespeed-cache'), self::O_MEDIA_LAZY_URI_EXC => __('Lazy Load URI Excludes', 'litespeed-cache'), self::O_MEDIA_LQIP_EXC => __('LQIP Excludes', 'litespeed-cache'), self::O_MEDIA_LAZY_PLACEHOLDER => __('Basic Image Placeholder', 'litespeed-cache'), self::O_MEDIA_PLACEHOLDER_RESP => __('Responsive Placeholder', 'litespeed-cache'), self::O_MEDIA_PLACEHOLDER_RESP_COLOR => __('Responsive Placeholder Color', 'litespeed-cache'), self::O_MEDIA_PLACEHOLDER_RESP_SVG => __('Responsive Placeholder SVG', 'litespeed-cache'), self::O_MEDIA_LQIP => __('LQIP Cloud Generator', 'litespeed-cache'), self::O_MEDIA_LQIP_QUAL => __('LQIP Quality', 'litespeed-cache'), self::O_MEDIA_LQIP_MIN_W => __('LQIP Minimum Dimensions', 'litespeed-cache'), // self::O_MEDIA_LQIP_MIN_H => __( 'LQIP Minimum Height', 'litespeed-cache' ), self::O_MEDIA_PLACEHOLDER_RESP_ASYNC => __('Generate LQIP In Background', 'litespeed-cache'), self::O_MEDIA_IFRAME_LAZY => __('Lazy Load Iframes', 'litespeed-cache'), self::O_MEDIA_ADD_MISSING_SIZES => __('Add Missing Sizes', 'litespeed-cache'), self::O_MEDIA_VPI => __('Viewport Images', 'litespeed-cache'), self::O_MEDIA_VPI_CRON => __('Viewport Images Cron', 'litespeed-cache'), self::O_IMG_OPTM_AUTO => __('Auto Request Cron', 'litespeed-cache'), self::O_IMG_OPTM_ORI => __('Optimize Original Images', 'litespeed-cache'), self::O_IMG_OPTM_RM_BKUP => __('Remove Original Backups', 'litespeed-cache'), self::O_IMG_OPTM_WEBP => __('Next-Gen Image Format', 'litespeed-cache'), self::O_IMG_OPTM_LOSSLESS => __('Optimize Losslessly', 'litespeed-cache'), self::O_IMG_OPTM_EXIF => __('Preserve EXIF/XMP data', 'litespeed-cache'), self::O_IMG_OPTM_WEBP_ATTR => __('WebP/AVIF Attribute To Replace', 'litespeed-cache'), self::O_IMG_OPTM_WEBP_REPLACE_SRCSET => __('WebP/AVIF For Extra srcset', 'litespeed-cache'), self::O_IMG_OPTM_JPG_QUALITY => __('WordPress Image Quality Control', 'litespeed-cache'), self::O_ESI => __('Enable ESI', 'litespeed-cache'), self::O_ESI_CACHE_ADMBAR => __('Cache Admin Bar', 'litespeed-cache'), self::O_ESI_CACHE_COMMFORM => __('Cache Comment Form', 'litespeed-cache'), self::O_ESI_NONCE => __('ESI Nonces', 'litespeed-cache'), self::O_CACHE_VARY_GROUP => __('Vary Group', 'litespeed-cache'), self::O_PURGE_HOOK_ALL => __('Purge All Hooks', 'litespeed-cache'), self::O_UTIL_NO_HTTPS_VARY => __('Improve HTTP/HTTPS Compatibility', 'litespeed-cache'), self::O_UTIL_INSTANT_CLICK => __('Instant Click', 'litespeed-cache'), self::O_CACHE_EXC_COOKIES => __('Do Not Cache Cookies', 'litespeed-cache'), self::O_CACHE_EXC_USERAGENTS => __('Do Not Cache User Agents', 'litespeed-cache'), self::O_CACHE_LOGIN_COOKIE => __('Login Cookie', 'litespeed-cache'), self::O_CACHE_VARY_COOKIES => __('Vary Cookies', 'litespeed-cache'), self::O_MISC_HEARTBEAT_FRONT => __('Frontend Heartbeat Control', 'litespeed-cache'), self::O_MISC_HEARTBEAT_FRONT_TTL => __('Frontend Heartbeat TTL', 'litespeed-cache'), self::O_MISC_HEARTBEAT_BACK => __('Backend Heartbeat Control', 'litespeed-cache'), self::O_MISC_HEARTBEAT_BACK_TTL => __('Backend Heartbeat TTL', 'litespeed-cache'), self::O_MISC_HEARTBEAT_EDITOR => __('Editor Heartbeat', 'litespeed-cache'), self::O_MISC_HEARTBEAT_EDITOR_TTL => __('Editor Heartbeat TTL', 'litespeed-cache'), self::O_CDN => __('Use CDN Mapping', 'litespeed-cache'), self::CDN_MAPPING_URL => __('CDN URL', 'litespeed-cache'), self::CDN_MAPPING_INC_IMG => __('Include Images', 'litespeed-cache'), self::CDN_MAPPING_INC_CSS => __('Include CSS', 'litespeed-cache'), self::CDN_MAPPING_INC_JS => __('Include JS', 'litespeed-cache'), self::CDN_MAPPING_FILETYPE => __('Include File Types', 'litespeed-cache'), self::O_CDN_ATTR => __('HTML Attribute To Replace', 'litespeed-cache'), self::O_CDN_ORI => __('Original URLs', 'litespeed-cache'), self::O_CDN_ORI_DIR => __('Included Directories', 'litespeed-cache'), self::O_CDN_EXC => __('Exclude Path', 'litespeed-cache'), self::O_CDN_CLOUDFLARE => __('Cloudflare API', 'litespeed-cache'), self::O_CRAWLER => __('Crawler', 'litespeed-cache'), self::O_CRAWLER_CRAWL_INTERVAL => __('Crawl Interval', 'litespeed-cache'), self::O_CRAWLER_LOAD_LIMIT => __('Server Load Limit', 'litespeed-cache'), self::O_CRAWLER_ROLES => __('Role Simulation', 'litespeed-cache'), self::O_CRAWLER_COOKIES => __('Cookie Simulation', 'litespeed-cache'), self::O_CRAWLER_SITEMAP => __('Custom Sitemap', 'litespeed-cache'), self::O_DEBUG_DISABLE_ALL => __('Disable All Features', 'litespeed-cache'), self::O_DEBUG => __('Debug Log', 'litespeed-cache'), self::O_DEBUG_IPS => __('Admin IPs', 'litespeed-cache'), self::O_DEBUG_LEVEL => __('Debug Level', 'litespeed-cache'), self::O_DEBUG_FILESIZE => __('Log File Size Limit', 'litespeed-cache'), self::O_DEBUG_COLLAPSE_QS => __('Collapse Query Strings', 'litespeed-cache'), self::O_DEBUG_INC => __('Debug URI Includes', 'litespeed-cache'), self::O_DEBUG_EXC => __('Debug URI Excludes', 'litespeed-cache'), self::O_DEBUG_EXC_STRINGS => __('Debug String Excludes', 'litespeed-cache'), self::O_DB_OPTM_REVISIONS_MAX => __('Revisions Max Number', 'litespeed-cache'), self::O_DB_OPTM_REVISIONS_AGE => __('Revisions Max Age', 'litespeed-cache'), ); if (array_key_exists($id, $_lang_list)) { return $_lang_list[$id]; } return 'N/A'; } } src/control.cls.php 0000644 00000053211 15246276230 0010315 0 ustar 00 <?php /** * The plugin cache-control class for X-Litespeed-Cache-Control * * @since 1.1.3 * @package LiteSpeed * @subpackage LiteSpeed/inc * @author LiteSpeed Technologies <info@litespeedtech.com> */ namespace LiteSpeed; defined('WPINC') || exit(); class Control extends Root { const LOG_TAG = '💵'; const BM_CACHEABLE = 1; const BM_PRIVATE = 2; const BM_SHARED = 4; const BM_NO_VARY = 8; const BM_FORCED_CACHEABLE = 32; const BM_PUBLIC_FORCED = 64; const BM_STALE = 128; const BM_NOTCACHEABLE = 256; const X_HEADER = 'X-LiteSpeed-Cache-Control'; protected static $_control = 0; protected static $_custom_ttl = 0; private $_response_header_ttls = array(); /** * Init cache control * * @since 1.6.2 */ public function init() { /** * Add vary filter for Role Excludes * @since 1.6.2 */ add_filter('litespeed_vary', array($this, 'vary_add_role_exclude')); // 301 redirect hook add_filter('wp_redirect', array($this, 'check_redirect'), 10, 2); // Load response header conf $this->_response_header_ttls = $this->conf(Base::O_CACHE_TTL_STATUS); foreach ($this->_response_header_ttls as $k => $v) { $v = explode(' ', $v); if (empty($v[0]) || empty($v[1])) { continue; } $this->_response_header_ttls[$v[0]] = $v[1]; } if ($this->conf(Base::O_PURGE_STALE)) { $this->set_stale(); } } /** * Exclude role from optimization filter * * @since 1.6.2 * @access public */ public function vary_add_role_exclude($vary) { if ($this->in_cache_exc_roles()) { $vary['role_exclude_cache'] = 1; } return $vary; } /** * Check if one user role is in exclude cache group settings * * @since 1.6.2 * @since 3.0 Moved here from conf.cls * @access public * @param string $role The user role * @return int The set value if already set */ public function in_cache_exc_roles($role = null) { // Get user role if ($role === null) { $role = Router::get_role(); } if (!$role) { return false; } $roles = explode(',', $role); $found = array_intersect($roles, $this->conf(Base::O_CACHE_EXC_ROLES)); return $found ? implode(',', $found) : false; } /** * 1. Initialize cacheable status for `wp` hook * 2. Hook error page tags for cacheable pages * * @since 1.1.3 * @access public */ public function init_cacheable() { // Hook `wp` to mark default cacheable status // NOTE: Any process that does NOT run into `wp` hook will not get cacheable by default add_action('wp', array($this, 'set_cacheable'), 5); // Hook WP REST to be cacheable if ($this->conf(Base::O_CACHE_REST)) { add_action('rest_api_init', array($this, 'set_cacheable'), 5); } // Cache resources // NOTE: If any strange resource doesn't use normal WP logic `wp_loaded` hook, rewrite rule can handle it $cache_res = $this->conf(Base::O_CACHE_RES); if ($cache_res) { $uri = esc_url($_SERVER['REQUEST_URI']); // todo: check if need esc_url() $pattern = '!' . LSCWP_CONTENT_FOLDER . Htaccess::RW_PATTERN_RES . '!'; if (preg_match($pattern, $uri)) { add_action('wp_loaded', array($this, 'set_cacheable'), 5); } } // AJAX cache $ajax_cache = $this->conf(Base::O_CACHE_AJAX_TTL); foreach ($ajax_cache as $v) { $v = explode(' ', $v); if (empty($v[0]) || empty($v[1])) { continue; } // self::debug("Initializing cacheable status for wp_ajax_nopriv_" . $v[0]); add_action( 'wp_ajax_nopriv_' . $v[0], function () use ($v) { self::set_custom_ttl($v[1]); self::force_cacheable('ajax Cache setting for action ' . $v[0]); }, 4 ); } // Check error page add_filter('status_header', array($this, 'check_error_codes'), 10, 2); } /** * Check if the page returns any error code. * * @since 1.0.13.1 * @access public * @param $status_header * @param $code * @return $error_status */ public function check_error_codes($status_header, $code) { if (array_key_exists($code, $this->_response_header_ttls)) { if (self::is_cacheable() && !$this->_response_header_ttls[$code]) { self::set_nocache('[Ctrl] TTL is set to no cache [status_header] ' . $code); } // Set TTL self::set_custom_ttl($this->_response_header_ttls[$code]); } elseif (self::is_cacheable()) { if (substr($code, 0, 1) == 4 || substr($code, 0, 1) == 5) { self::set_nocache('[Ctrl] 4xx/5xx default to no cache [status_header] ' . $code); } } // Set cache tag if (in_array($code, Tag::$error_code_tags)) { Tag::add(Tag::TYPE_HTTP . $code); } // Give the default status_header back return $status_header; } /** * Set no vary setting * * @access public * @since 1.1.3 */ public static function set_no_vary() { if (self::is_no_vary()) { return; } self::$_control |= self::BM_NO_VARY; self::debug('X Cache_control -> no-vary', 3); } /** * Get no vary setting * * @access public * @since 1.1.3 */ public static function is_no_vary() { return self::$_control & self::BM_NO_VARY; } /** * Set stale * * @access public * @since 1.1.3 */ public function set_stale() { if (self::is_stale()) { return; } self::$_control |= self::BM_STALE; self::debug('X Cache_control -> stale'); } /** * Get stale * * @access public * @since 1.1.3 */ public static function is_stale() { return self::$_control & self::BM_STALE; } /** * Set cache control to shared private * * @access public * @since 1.1.3 * @param string $reason The reason to no cache */ public static function set_shared($reason = false) { if (self::is_shared()) { return; } self::$_control |= self::BM_SHARED; self::set_private(); if (!is_string($reason)) { $reason = false; } if ($reason) { $reason = "( $reason )"; } self::debug('X Cache_control -> shared ' . $reason); } /** * Check if is shared private * * @access public * @since 1.1.3 */ public static function is_shared() { return self::$_control & self::BM_SHARED && self::is_private(); } /** * Set cache control to forced public * * @access public * @since 1.7.1 */ public static function set_public_forced($reason = false) { if (self::is_public_forced()) { return; } self::$_control |= self::BM_PUBLIC_FORCED; if (!is_string($reason)) { $reason = false; } if ($reason) { $reason = "( $reason )"; } self::debug('X Cache_control -> public forced ' . $reason); } /** * Check if is public forced * * @access public * @since 1.7.1 */ public static function is_public_forced() { return self::$_control & self::BM_PUBLIC_FORCED; } /** * Set cache control to private * * @access public * @since 1.1.3 * @param string $reason The reason to no cache */ public static function set_private($reason = false) { if (self::is_private()) { return; } self::$_control |= self::BM_PRIVATE; if (!is_string($reason)) { $reason = false; } if ($reason) { $reason = "( $reason )"; } self::debug('X Cache_control -> private ' . $reason); } /** * Check if is private * * @access public * @since 1.1.3 */ public static function is_private() { if (defined('LITESPEED_GUEST') && LITESPEED_GUEST) { // return false; } return self::$_control & self::BM_PRIVATE && !self::is_public_forced(); } /** * Initialize cacheable status in `wp` hook, if not call this, by default it will be non-cacheable * * @access public * @since 1.1.3 */ public function set_cacheable($reason = false) { self::$_control |= self::BM_CACHEABLE; if (!is_string($reason)) { $reason = false; } if ($reason) { $reason = ' [reason] ' . $reason; } self::debug('Cache_control init on' . $reason); } /** * This will disable non-cacheable BM * * @access public * @since 2.2 */ public static function force_cacheable($reason = false) { self::$_control |= self::BM_FORCED_CACHEABLE; if (!is_string($reason)) { $reason = false; } if ($reason) { $reason = ' [reason] ' . $reason; } self::debug('Forced cacheable' . $reason); } /** * Switch to nocacheable status * * @access public * @since 1.1.3 * @param string $reason The reason to no cache */ public static function set_nocache($reason = false) { self::$_control |= self::BM_NOTCACHEABLE; if (!is_string($reason)) { $reason = false; } if ($reason) { $reason = "( $reason )"; } self::debug('X Cache_control -> no Cache ' . $reason, 5); } /** * Check current notcacheable bit set * * @access public * @since 1.1.3 * @return bool True if notcacheable bit is set, otherwise false. */ public static function isset_notcacheable() { return self::$_control & self::BM_NOTCACHEABLE; } /** * Check current force cacheable bit set * * @access public * @since 2.2 */ public static function is_forced_cacheable() { return self::$_control & self::BM_FORCED_CACHEABLE; } /** * Check current cacheable status * * @access public * @since 1.1.3 * @return bool True if is still cacheable, otherwise false. */ public static function is_cacheable() { if (defined('LSCACHE_NO_CACHE') && LSCACHE_NO_CACHE) { self::debug('LSCACHE_NO_CACHE constant defined'); return false; } // Guest mode always cacheable if (defined('LITESPEED_GUEST') && LITESPEED_GUEST) { // return true; } // If its forced public cacheable if (self::is_public_forced()) { return true; } // If its forced cacheable if (self::is_forced_cacheable()) { return true; } return !self::isset_notcacheable() && self::$_control & self::BM_CACHEABLE; } /** * Set a custom TTL to use with the request if needed. * * @access public * @since 1.1.3 * @param mixed $ttl An integer or string to use as the TTL. Must be numeric. */ public static function set_custom_ttl($ttl, $reason = false) { if (is_numeric($ttl)) { self::$_custom_ttl = $ttl; self::debug('X Cache_control TTL -> ' . $ttl . ($reason ? ' [reason] ' . $ttl : '')); } } /** * Generate final TTL. * * @access public * @since 1.1.3 */ public function get_ttl() { if (self::$_custom_ttl != 0) { return self::$_custom_ttl; } // Check if is in timed url list or not $timed_urls = Utility::wildcard2regex($this->conf(Base::O_PURGE_TIMED_URLS)); $timed_urls_time = $this->conf(Base::O_PURGE_TIMED_URLS_TIME); if ($timed_urls && $timed_urls_time) { $current_url = Tag::build_uri_tag(true); // Use time limit ttl $scheduled_time = strtotime($timed_urls_time); $ttl = $scheduled_time - time(); if ($ttl < 0) { $ttl += 86400; // add one day } foreach ($timed_urls as $v) { if (strpos($v, '*') !== false) { if (preg_match('#' . $v . '#iU', $current_url)) { self::debug('X Cache_control TTL is limited to ' . $ttl . ' due to scheduled purge regex ' . $v); return $ttl; } } else { if ($v == $current_url) { self::debug('X Cache_control TTL is limited to ' . $ttl . ' due to scheduled purge rule ' . $v); return $ttl; } } } } // Private cache uses private ttl setting if (self::is_private()) { return $this->conf(Base::O_CACHE_TTL_PRIV); } if (is_front_page()) { return $this->conf(Base::O_CACHE_TTL_FRONTPAGE); } $feed_ttl = $this->conf(Base::O_CACHE_TTL_FEED); if (is_feed() && $feed_ttl > 0) { return $feed_ttl; } if ($this->cls('REST')->is_rest() || $this->cls('REST')->is_internal_rest()) { return $this->conf(Base::O_CACHE_TTL_REST); } return $this->conf(Base::O_CACHE_TTL_PUB); } /** * Check if need to set no cache status for redirection or not * * @access public * @since 1.1.3 */ public function check_redirect($location, $status) { // TODO: some env don't have SCRIPT_URI but only REQUEST_URI, need to be compatible if (!empty($_SERVER['SCRIPT_URI'])) { // dont check $status == '301' anymore self::debug('301 from ' . $_SERVER['SCRIPT_URI']); self::debug("301 to $location"); $to_check = array(PHP_URL_SCHEME, PHP_URL_HOST, PHP_URL_PATH, PHP_URL_QUERY); $is_same_redirect = true; foreach ($to_check as $v) { $url_parsed = $v == PHP_URL_QUERY ? $_SERVER['QUERY_STRING'] : parse_url($_SERVER['SCRIPT_URI'], $v); $target = parse_url($location, $v); self::debug("Compare [from] $url_parsed [to] $target"); if ($v == PHP_URL_QUERY) { $url_parsed = $url_parsed ? urldecode($url_parsed) : ''; $target = $target ? urldecode($target) : ''; if (substr($url_parsed, -1) == '&') { $url_parsed = substr($url_parsed, 0, -1); } } if ($url_parsed != $target) { $is_same_redirect = false; self::debug('301 different redirection'); break; } } if ($is_same_redirect) { self::set_nocache('301 to same url'); } } return $location; } /** * Sets up the Cache Control header. * * @since 1.1.3 * @access public * @return string empty string if empty, otherwise the cache control header. */ public function output() { $esi_hdr = ''; if (ESI::has_esi()) { $esi_hdr = ',esi=on'; } $hdr = self::X_HEADER . ': '; if (defined('DONOTCACHEPAGE') && apply_filters('litespeed_const_DONOTCACHEPAGE', DONOTCACHEPAGE)) { self::debug('❌ forced no cache [reason] DONOTCACHEPAGE const'); $hdr .= 'no-cache' . $esi_hdr; return $hdr; } // Guest mode directly return cacheable result // if ( defined( 'LITESPEED_GUEST' ) && LITESPEED_GUEST ) { // // If is POST, no cache // if ( defined( 'LSCACHE_NO_CACHE' ) && LSCACHE_NO_CACHE ) { // self::debug( "[Ctrl] ❌ forced no cache [reason] LSCACHE_NO_CACHE const" ); // $hdr .= 'no-cache'; // } // else if( $_SERVER[ 'REQUEST_METHOD' ] !== 'GET' ) { // self::debug( "[Ctrl] ❌ forced no cache [reason] req not GET" ); // $hdr .= 'no-cache'; // } // else { // $hdr .= 'public'; // $hdr .= ',max-age=' . $this->get_ttl(); // } // $hdr .= $esi_hdr; // return $hdr; // } // Fix cli `uninstall --deactivate` fatal err if (!self::is_cacheable()) { $hdr .= 'no-cache' . $esi_hdr; return $hdr; } if (self::is_shared()) { $hdr .= 'shared,private'; } elseif (self::is_private()) { $hdr .= 'private'; } else { $hdr .= 'public'; } if (self::is_no_vary()) { $hdr .= ',no-vary'; } $hdr .= ',max-age=' . $this->get_ttl() . $esi_hdr; return $hdr; } /** * Generate all `control` tags before output * * @access public * @since 1.1.3 */ public function finalize() { if (defined('LITESPEED_GUEST') && LITESPEED_GUEST) { // return; } if (is_preview()) { self::set_nocache('preview page'); return; } // Check if has metabox non-cacheable setting or not if (file_exists(LSCWP_DIR . 'src/metabox.cls.php') && $this->cls('Metabox')->setting('litespeed_no_cache')) { self::set_nocache('per post metabox setting'); return; } // Check if URI is forced public cache $excludes = $this->conf(Base::O_CACHE_FORCE_PUB_URI); $hit = Utility::str_hit_array($_SERVER['REQUEST_URI'], $excludes, true); if ($hit) { list($result, $this_ttl) = $hit; self::set_public_forced('Setting: ' . $result); self::debug('Forced public cacheable due to setting: ' . $result); if ($this_ttl) { self::set_custom_ttl($this_ttl); } } if (self::is_public_forced()) { return; } // Check if URI is forced cache $excludes = $this->conf(Base::O_CACHE_FORCE_URI); $hit = Utility::str_hit_array($_SERVER['REQUEST_URI'], $excludes, true); if ($hit) { list($result, $this_ttl) = $hit; self::force_cacheable(); self::debug('Forced cacheable due to setting: ' . $result); if ($this_ttl) { self::set_custom_ttl($this_ttl); } } // if is not cacheable, terminate check // Even no need to run 3rd party hook if (!self::is_cacheable()) { self::debug('not cacheable before ctrl finalize'); return; } // Apply 3rd party filter // NOTE: Hook always needs to run asap because some 3rd party set is_mobile in this hook do_action('litespeed_control_finalize', defined('LSCACHE_IS_ESI') ? LSCACHE_IS_ESI : false); // Pass ESI block id // if is not cacheable, terminate check if (!self::is_cacheable()) { self::debug('not cacheable after api_control'); return; } // Check litespeed setting to set cacheable status if (!$this->_setting_cacheable()) { self::set_nocache(); return; } // If user has password cookie, do not cache (moved from vary) global $post; if (!empty($post->post_password) && isset($_COOKIE['wp-postpass_' . COOKIEHASH])) { // If user has password cookie, do not cache self::set_nocache('pswd cookie'); return; } // The following check to the end is ONLY for mobile $is_mobile = apply_filters('litespeed_is_mobile', false); if (!$this->conf(Base::O_CACHE_MOBILE)) { if ($is_mobile) { self::set_nocache('mobile'); } return; } $env_vary = isset($_SERVER['LSCACHE_VARY_VALUE']) ? $_SERVER['LSCACHE_VARY_VALUE'] : false; if (!$env_vary) { $env_vary = isset($_SERVER['HTTP_X_LSCACHE_VARY_VALUE']) ? $_SERVER['HTTP_X_LSCACHE_VARY_VALUE'] : false; } if ($env_vary && strpos($env_vary, 'ismobile') !== false) { if (!wp_is_mobile() && !$is_mobile) { self::set_nocache('is not mobile'); // todo: no need to uncache, it will correct vary value in vary finalize anyways return; } } elseif (wp_is_mobile() || $is_mobile) { self::set_nocache('is mobile'); return; } } /** * Check if is mobile for filter `litespeed_is_mobile` in API * * @since 3.0 * @access public */ public static function is_mobile() { return wp_is_mobile(); } /** * Get request method w/ compatibility to X-Http-Method-Override * * @since 6.2 */ private function _get_req_method() { if (isset($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'])) { self::debug('X-Http-Method-Override -> ' . $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE']); defined('LITESPEED_X_HTTP_METHOD_OVERRIDE') || define('LITESPEED_X_HTTP_METHOD_OVERRIDE', true); return $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE']; } if (isset($_SERVER['REQUEST_METHOD'])) { return $_SERVER['REQUEST_METHOD']; } return 'unknown'; } /** * Check if a page is cacheable based on litespeed setting. * * @since 1.0.0 * @access private * @return boolean True if cacheable, false otherwise. */ private function _setting_cacheable() { // logged_in users already excluded, no hook added if (!empty($_REQUEST[Router::ACTION])) { return $this->_no_cache_for('Query String Action'); } $method = $this->_get_req_method(); if (defined('LITESPEED_X_HTTP_METHOD_OVERRIDE') && LITESPEED_X_HTTP_METHOD_OVERRIDE && $method == 'HEAD') { return $this->_no_cache_for('HEAD method from override'); } if ('GET' !== $method && 'HEAD' !== $method) { return $this->_no_cache_for('Not GET method: ' . $method); } if (is_feed() && $this->conf(Base::O_CACHE_TTL_FEED) == 0) { return $this->_no_cache_for('feed'); } if (is_trackback()) { return $this->_no_cache_for('trackback'); } if (is_search()) { return $this->_no_cache_for('search'); } // if ( !defined('WP_USE_THEMES') || !WP_USE_THEMES ) { // return $this->_no_cache_for('no theme used'); // } // Check private cache URI setting $excludes = $this->conf(Base::O_CACHE_PRIV_URI); $result = Utility::str_hit_array($_SERVER['REQUEST_URI'], $excludes); if ($result) { self::set_private('Admin cfg Private Cached URI: ' . $result); } if (!self::is_forced_cacheable()) { // Check if URI is excluded from cache $excludes = $this->cls('Data')->load_cache_nocacheable($this->conf(Base::O_CACHE_EXC)); $result = Utility::str_hit_array($_SERVER['REQUEST_URI'], $excludes); if ($result) { return $this->_no_cache_for('Admin configured URI Do not cache: ' . $result); } // Check QS excluded setting $excludes = $this->conf(Base::O_CACHE_EXC_QS); if (!empty($excludes) && ($qs = $this->_is_qs_excluded($excludes))) { return $this->_no_cache_for('Admin configured QS Do not cache: ' . $qs); } $excludes = $this->conf(Base::O_CACHE_EXC_CAT); if (!empty($excludes) && has_category($excludes)) { return $this->_no_cache_for('Admin configured Category Do not cache.'); } $excludes = $this->conf(Base::O_CACHE_EXC_TAG); if (!empty($excludes) && has_tag($excludes)) { return $this->_no_cache_for('Admin configured Tag Do not cache.'); } $excludes = $this->conf(Base::O_CACHE_EXC_COOKIES); if (!empty($excludes) && !empty($_COOKIE)) { $cookie_hit = array_intersect(array_keys($_COOKIE), $excludes); if ($cookie_hit) { return $this->_no_cache_for('Admin configured Cookie Do not cache.'); } } $excludes = $this->conf(Base::O_CACHE_EXC_USERAGENTS); if (!empty($excludes) && isset($_SERVER['HTTP_USER_AGENT'])) { $nummatches = preg_match(Utility::arr2regex($excludes), $_SERVER['HTTP_USER_AGENT']); if ($nummatches) { return $this->_no_cache_for('Admin configured User Agent Do not cache.'); } } // Check if is exclude roles ( Need to set Vary too ) if ($result = $this->in_cache_exc_roles()) { return $this->_no_cache_for('Role Excludes setting ' . $result); } } return true; } /** * Write a debug message for if a page is not cacheable. * * @since 1.0.0 * @access private * @param string $reason An explanation for why the page is not cacheable. * @return boolean Return false. */ private function _no_cache_for($reason) { self::debug('X Cache_control off - ' . $reason); return false; } /** * Check if current request has qs excluded setting * * @since 1.3 * @access private * @param array $excludes QS excludes setting * @return boolean|string False if not excluded, otherwise the hit qs list */ private function _is_qs_excluded($excludes) { if (!empty($_GET) && ($intersect = array_intersect(array_keys($_GET), $excludes))) { return implode(',', $intersect); } return false; } } src/metabox.cls.php 0000644 00000010322 15246276230 0010270 0 ustar 00 <?php /** * The class to operate post editor metabox settings * * @since 4.7 * @package Core * @subpackage Core/inc * @author LiteSpeed Technologies <info@litespeedtech.com> */ namespace LiteSpeed; defined('WPINC') || exit(); class Metabox extends Root { const LOG_TAG = '📦'; const POST_NONCE_ACTION = 'post_nonce_action'; private $_postmeta_settings; /** * Get the setting list * @since 4.7 */ public function __construct() { // Append meta box $this->_postmeta_settings = array( 'litespeed_no_cache' => __('Disable Cache', 'litespeed-cache'), 'litespeed_no_image_lazy' => __('Disable Image Lazyload', 'litespeed-cache'), 'litespeed_no_vpi' => __('Disable VPI', 'litespeed-cache'), 'litespeed_vpi_list' => __('Viewport Images', 'litespeed-cache'), 'litespeed_vpi_list_mobile' => __('Viewport Images', 'litespeed-cache') . ' - ' . __('Mobile', 'litespeed-cache'), ); } /** * Register post edit settings * @since 4.7 */ public function register_settings() { add_action('add_meta_boxes', array($this, 'add_meta_boxes')); add_action('save_post', array($this, 'save_meta_box_settings'), 15, 2); add_action('attachment_updated', array($this, 'save_meta_box_settings'), 15, 2); } /** * Register meta box * @since 4.7 */ public function add_meta_boxes($post_type) { if (apply_filters('litespeed_bypass_metabox', false, $post_type)) { return; } $post_type_obj = get_post_type_object($post_type); if (!empty($post_type_obj) && !$post_type_obj->public) { self::debug('post type public=false, bypass add_meta_boxes'); return; } add_meta_box('litespeed_meta_boxes', __('LiteSpeed Options', 'litespeed-cache'), array($this, 'meta_box_options'), $post_type, 'side', 'core'); } /** * Show meta box content * @since 4.7 */ public function meta_box_options() { require_once LSCWP_DIR . 'tpl/inc/metabox.php'; } /** * Save settings * @since 4.7 */ public function save_meta_box_settings($post_id, $post) { global $pagenow; self::debug('Maybe save post2 [post_id] ' . $post_id); if ($pagenow != 'post.php' || !$post || !is_object($post)) { return; } if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) { return; } if (!$this->cls('Router')->verify_nonce(self::POST_NONCE_ACTION)) { return; } self::debug('Saving post [post_id] ' . $post_id); foreach ($this->_postmeta_settings as $k => $v) { $val = isset($_POST[$k]) ? $_POST[$k] : false; $this->save($post_id, $k, $val); } } /** * Load setting per post * @since 4.7 */ public function setting($conf, $post_id = false) { // Check if has metabox non-cacheable setting or not if (!$post_id) { $home_id = get_option('page_for_posts'); if (is_singular()) { $post_id = get_the_ID(); } elseif ($home_id > 0 && is_home()) { $post_id = $home_id; } } if ($post_id && ($val = get_post_meta($post_id, $conf, true))) { return $val; } return null; } /** * Save a metabox value * @since 4.7 */ public function save($post_id, $name, $val, $is_append = false) { if (strpos($name, 'litespeed_vpi_list') !== false) { $val = Utility::sanitize_lines($val, 'basename,drop_webp'); } // Load existing data if has set if ($is_append) { $existing_data = $this->setting($name, $post_id); if ($existing_data) { $existing_data = Utility::sanitize_lines($existing_data, 'basename'); $val = array_unique(array_merge($val, $existing_data)); } } if ($val) { update_post_meta($post_id, $name, $val); } else { delete_post_meta($post_id, $name); } } /** * Load exclude images per post * @since 4.7 */ public function lazy_img_excludes($list) { $is_mobile = $this->_separate_mobile(); $excludes = $this->setting($is_mobile ? 'litespeed_vpi_list_mobile' : 'litespeed_vpi_list'); if ($excludes !== null) { $excludes = Utility::sanitize_lines($excludes, 'basename'); if ($excludes) { // Check if contains `data:` (invalid result, need to clear existing result) or not if (Utility::str_hit_array('data:', $excludes)) { $this->cls('VPI')->add_to_queue(); } else { return array_merge($list, $excludes); } } return $list; } $this->cls('VPI')->add_to_queue(); return $list; } } src/crawler-map.cls.php 0000644 00000035245 15246276230 0011056 0 ustar 00 <?php /** * The Crawler Sitemap Class * * @since 1.1.0 */ namespace LiteSpeed; defined('WPINC') || exit(); class Crawler_Map extends Root { const LOG_TAG = '🐞🗺️'; const BM_MISS = 1; const BM_HIT = 2; const BM_BLACKLIST = 4; private $_home_url; // Used to simplify urls private $_tb; private $_tb_blacklist; private $__data; private $_conf_map_timeout; private $_urls = array(); /** * Instantiate the class * * @since 1.1.0 */ public function __construct() { $this->_home_url = get_home_url(); $this->__data = Data::cls(); $this->_tb = $this->__data->tb('crawler'); $this->_tb_blacklist = $this->__data->tb('crawler_blacklist'); $this->_conf_map_timeout = defined('LITESPEED_CRAWLER_MAP_TIMEOUT') ? LITESPEED_CRAWLER_MAP_TIMEOUT : 180; // Specify the timeout while parsing the sitemap } /** * Save URLs crawl status into DB * * @since 3.0 * @access public */ public function save_map_status($list, $curr_crawler) { global $wpdb; Utility::compatibility(); $total_crawler = count(Crawler::cls()->list_crawlers()); $total_crawler_pos = $total_crawler - 1; // Replace current crawler's position $curr_crawler = (int) $curr_crawler; foreach ($list as $bit => $ids) { // $ids = [ id => [ url, code ], ... ] if (!$ids) { continue; } self::debug("Update map [crawler] $curr_crawler [bit] $bit [count] " . count($ids)); // Update res first, then reason $right_pos = $total_crawler_pos - $curr_crawler; $sql_res = "CONCAT( LEFT( res, $curr_crawler ), '$bit', RIGHT( res, $right_pos ) )"; $id_all = implode(',', array_map('intval', array_keys($ids))); $wpdb->query("UPDATE `$this->_tb` SET res = $sql_res WHERE id IN ( $id_all )"); // Add blacklist if ($bit == Crawler::STATUS_BLACKLIST || $bit == Crawler::STATUS_NOCACHE) { $q = "SELECT a.id, a.url FROM `$this->_tb_blacklist` a LEFT JOIN `$this->_tb` b ON b.url=a.url WHERE b.id IN ( $id_all )"; $existing = $wpdb->get_results($q, ARRAY_A); // Update current crawler status tag in existing blacklist if ($existing) { $count = $wpdb->query("UPDATE `$this->_tb_blacklist` SET res = $sql_res WHERE id IN ( " . implode(',', array_column($existing, 'id')) . ' )'); self::debug('Update blacklist [count] ' . $count); } // Append new blacklist if (count($ids) > count($existing)) { $new_urls = array_diff(array_column($ids, 'url'), array_column($existing, 'url')); self::debug('Insert into blacklist [count] ' . count($new_urls)); $q = "INSERT INTO `$this->_tb_blacklist` ( url, res, reason ) VALUES " . implode(',', array_fill(0, count($new_urls), '( %s, %s, %s )')); $data = array(); $res = array_fill(0, $total_crawler, '-'); $res[$curr_crawler] = $bit; $res = implode('', $res); $default_reason = $total_crawler > 1 ? str_repeat(',', $total_crawler - 1) : ''; // Pre-populate default reason value first, update later foreach ($new_urls as $url) { $data[] = $url; $data[] = $res; $data[] = $default_reason; } $wpdb->query($wpdb->prepare($q, $data)); } } // Update sitemap reason w/ HTTP code $reason_array = array(); foreach ($ids as $id => $v2) { $code = (int) $v2['code']; if (empty($reason_array[$code])) { $reason_array[$code] = array(); } $reason_array[$code][] = (int) $id; } foreach ($reason_array as $code => $v2) { // Complement comma if ($curr_crawler) { $code = ',' . $code; } if ($curr_crawler < $total_crawler_pos) { $code .= ','; } $count = $wpdb->query( "UPDATE `$this->_tb` SET reason=CONCAT(SUBSTRING_INDEX(reason, ',', $curr_crawler), '$code', SUBSTRING_INDEX(reason, ',', -$right_pos)) WHERE id IN (" . implode(',', $v2) . ')' ); self::debug("Update map reason [code] $code [pos] left $curr_crawler right -$right_pos [count] $count"); // Update blacklist reason if ($bit == Crawler::STATUS_BLACKLIST || $bit == Crawler::STATUS_NOCACHE) { $count = $wpdb->query( "UPDATE `$this->_tb_blacklist` a LEFT JOIN `$this->_tb` b ON b.url = a.url SET a.reason=CONCAT(SUBSTRING_INDEX(a.reason, ',', $curr_crawler), '$code', SUBSTRING_INDEX(a.reason, ',', -$right_pos)) WHERE b.id IN (" . implode(',', $v2) . ')' ); self::debug("Update blacklist [code] $code [pos] left $curr_crawler right -$right_pos [count] $count"); } } // Reset list $list[$bit] = array(); } return $list; } /** * Add one record to blacklist * NOTE: $id is sitemap table ID * * @since 3.0 * @access public */ public function blacklist_add($id) { global $wpdb; $id = (int) $id; // Build res&reason $total_crawler = count(Crawler::cls()->list_crawlers()); $res = str_repeat(Crawler::STATUS_BLACKLIST, $total_crawler); $reason = implode(',', array_fill(0, $total_crawler, 'Man')); $row = $wpdb->get_row("SELECT a.url, b.id FROM `$this->_tb` a LEFT JOIN `$this->_tb_blacklist` b ON b.url = a.url WHERE a.id = '$id'", ARRAY_A); if (!$row) { self::debug('blacklist failed to add [id] ' . $id); return; } self::debug('Add to blacklist [url] ' . $row['url']); $q = "UPDATE `$this->_tb` SET res = %s, reason = %s WHERE id = %d"; $wpdb->query($wpdb->prepare($q, array($res, $reason, $id))); if ($row['id']) { $q = "UPDATE `$this->_tb_blacklist` SET res = %s, reason = %s WHERE id = %d"; $wpdb->query($wpdb->prepare($q, array($res, $reason, $row['id']))); } else { $q = "INSERT INTO `$this->_tb_blacklist` (url, res, reason) VALUES (%s, %s, %s)"; $wpdb->query($wpdb->prepare($q, array($row['url'], $res, $reason))); } } /** * Delete one record from blacklist * * @since 3.0 * @access public */ public function blacklist_del($id) { global $wpdb; if (!$this->__data->tb_exist('crawler_blacklist')) { return; } $id = (int) $id; self::debug('blacklist delete [id] ' . $id); $sql = sprintf( "UPDATE `%s` SET res=REPLACE(REPLACE(res, '%s', '-'), '%s', '-') WHERE url=(SELECT url FROM `%s` WHERE id=%d)", $this->_tb, Crawler::STATUS_NOCACHE, Crawler::STATUS_BLACKLIST, $this->_tb_blacklist, $id ); $wpdb->query($sql); $wpdb->query("DELETE FROM `$this->_tb_blacklist` WHERE id='$id'"); } /** * Empty blacklist * * @since 3.0 * @access public */ public function blacklist_empty() { global $wpdb; if (!$this->__data->tb_exist('crawler_blacklist')) { return; } self::debug('Truncate blacklist'); $sql = sprintf("UPDATE `%s` SET res=REPLACE(REPLACE(res, '%s', '-'), '%s', '-')", $this->_tb, Crawler::STATUS_NOCACHE, Crawler::STATUS_BLACKLIST); $wpdb->query($sql); $wpdb->query("TRUNCATE `$this->_tb_blacklist`"); } /** * List blacklist * * @since 3.0 * @access public */ public function list_blacklist($limit = false, $offset = false) { global $wpdb; if (!$this->__data->tb_exist('crawler_blacklist')) { return array(); } $q = "SELECT * FROM `$this->_tb_blacklist` ORDER BY id DESC"; if ($limit !== false) { if ($offset === false) { $total = $this->count_blacklist(); $offset = Utility::pagination($total, $limit, true); } $q .= ' LIMIT %d, %d'; $q = $wpdb->prepare($q, $offset, $limit); } return $wpdb->get_results($q, ARRAY_A); } /** * Count blacklist */ public function count_blacklist() { global $wpdb; if (!$this->__data->tb_exist('crawler_blacklist')) { return false; } $q = "SELECT COUNT(*) FROM `$this->_tb_blacklist`"; return $wpdb->get_var($q); } /** * Empty sitemap * * @since 3.0 * @access public */ public function empty_map() { Data::cls()->tb_del('crawler'); $msg = __('Sitemap cleaned successfully', 'litespeed-cache'); Admin_Display::success($msg); } /** * List generated sitemap * * @since 3.0 * @access public */ public function list_map($limit, $offset = false) { global $wpdb; if (!$this->__data->tb_exist('crawler')) { return array(); } if ($offset === false) { $total = $this->count_map(); $offset = Utility::pagination($total, $limit, true); } $type = Router::verify_type(); $where = ''; if (!empty($_POST['kw'])) { $q = "SELECT * FROM `$this->_tb` WHERE url LIKE %s"; if ($type == 'hit') { $q .= " AND res LIKE '%" . Crawler::STATUS_HIT . "%'"; } if ($type == 'miss') { $q .= " AND res LIKE '%" . Crawler::STATUS_MISS . "%'"; } if ($type == 'blacklisted') { $q .= " AND res LIKE '%" . Crawler::STATUS_BLACKLIST . "%'"; } $q .= ' ORDER BY id LIMIT %d, %d'; $where = '%' . $wpdb->esc_like($_POST['kw']) . '%'; return $wpdb->get_results($wpdb->prepare($q, $where, $offset, $limit), ARRAY_A); } $q = "SELECT * FROM `$this->_tb`"; if ($type == 'hit') { $q .= " WHERE res LIKE '%" . Crawler::STATUS_HIT . "%'"; } if ($type == 'miss') { $q .= " WHERE res LIKE '%" . Crawler::STATUS_MISS . "%'"; } if ($type == 'blacklisted') { $q .= " WHERE res LIKE '%" . Crawler::STATUS_BLACKLIST . "%'"; } $q .= ' ORDER BY id LIMIT %d, %d'; // self::debug("q=$q offset=$offset, limit=$limit"); return $wpdb->get_results($wpdb->prepare($q, $offset, $limit), ARRAY_A); } /** * Count sitemap */ public function count_map() { global $wpdb; if (!$this->__data->tb_exist('crawler')) { return false; } $q = "SELECT COUNT(*) FROM `$this->_tb`"; $type = Router::verify_type(); if ($type == 'hit') { $q .= " WHERE res LIKE '%" . Crawler::STATUS_HIT . "%'"; } if ($type == 'miss') { $q .= " WHERE res LIKE '%" . Crawler::STATUS_MISS . "%'"; } if ($type == 'blacklisted') { $q .= " WHERE res LIKE '%" . Crawler::STATUS_BLACKLIST . "%'"; } return $wpdb->get_var($q); } /** * Generate sitemap * * @since 1.1.0 * @access public */ public function gen($manual = false) { $count = $this->_gen(); if (!$count) { Admin_Display::error(__('No valid sitemap parsed for crawler.', 'litespeed-cache')); return; } if (!defined('DOING_CRON') && $manual) { $msg = sprintf(__('Sitemap created successfully: %d items', 'litespeed-cache'), $count); Admin_Display::success($msg); } } /** * Generate the sitemap * * @since 1.1.0 * @access private */ private function _gen() { global $wpdb; if (!$this->__data->tb_exist('crawler')) { $this->__data->tb_create('crawler'); } if (!$this->__data->tb_exist('crawler_blacklist')) { $this->__data->tb_create('crawler_blacklist'); } // use custom sitemap if (!($sitemap = $this->conf(Base::O_CRAWLER_SITEMAP))) { return false; } $offset = strlen($this->_home_url); $sitemap = Utility::sanitize_lines($sitemap); try { foreach ($sitemap as $this_map) { $this->_parse($this_map); } } catch (\Exception $e) { self::debug('❌ failed to parse custom sitemap: ' . $e->getMessage()); } if (is_array($this->_urls) && !empty($this->_urls)) { if (defined('LITESPEED_CRAWLER_DROP_DOMAIN') && LITESPEED_CRAWLER_DROP_DOMAIN) { foreach ($this->_urls as $k => $v) { if (stripos($v, $this->_home_url) !== 0) { unset($this->_urls[$k]); continue; } $this->_urls[$k] = substr($v, $offset); } } $this->_urls = array_unique($this->_urls); } self::debug('Truncate sitemap'); $wpdb->query("TRUNCATE `$this->_tb`"); self::debug('Generate sitemap'); // Filter URLs in blacklist $blacklist = $this->list_blacklist(); $full_blacklisted = array(); $partial_blacklisted = array(); foreach ($blacklist as $v) { if (strpos($v['res'], '-') === false) { // Full blacklisted $full_blacklisted[] = $v['url']; } else { // Replace existing reason $v['reason'] = explode(',', $v['reason']); $v['reason'] = array_map(function ($element) { return $element ? 'Existed' : ''; }, $v['reason']); $v['reason'] = implode(',', $v['reason']); $partial_blacklisted[$v['url']] = array( 'res' => $v['res'], 'reason' => $v['reason'], ); } } // Drop all blacklisted URLs $this->_urls = array_diff($this->_urls, $full_blacklisted); // Default res & reason $crawler_count = count(Crawler::cls()->list_crawlers()); $default_res = str_repeat('-', $crawler_count); $default_reason = $crawler_count > 1 ? str_repeat(',', $crawler_count - 1) : ''; $data = array(); foreach ($this->_urls as $url) { $data[] = $url; $data[] = array_key_exists($url, $partial_blacklisted) ? $partial_blacklisted[$url]['res'] : $default_res; $data[] = array_key_exists($url, $partial_blacklisted) ? $partial_blacklisted[$url]['reason'] : $default_reason; } foreach (array_chunk($data, 300) as $data2) { $this->_save($data2); } // Reset crawler Crawler::cls()->reset_pos(); return count($this->_urls); } /** * Save data to table * * @since 3.0 * @access private */ private function _save($data, $fields = 'url,res,reason') { global $wpdb; if (empty($data)) { return; } $q = "INSERT INTO `$this->_tb` ( $fields ) VALUES "; // Add placeholder $q .= Utility::chunk_placeholder($data, $fields); // Store data $wpdb->query($wpdb->prepare($q, $data)); } /** * Parse custom sitemap and return urls * * @since 1.1.1 * @access private */ private function _parse($sitemap) { /** * Read via wp func to avoid allow_url_fopen = off * @since 2.2.7 */ $response = wp_safe_remote_get($sitemap, array('timeout' => $this->_conf_map_timeout, 'sslverify' => false)); if (is_wp_error($response)) { $error_message = $response->get_error_message(); self::debug('failed to read sitemap: ' . $error_message); throw new \Exception('Failed to remote read ' . $sitemap); } $xml_object = simplexml_load_string($response['body'], null, LIBXML_NOCDATA); if (!$xml_object) { if ($this->_urls) { return; } throw new \Exception('Failed to parse xml ' . $sitemap); } // start parsing $xml_array = (array) $xml_object; if (!empty($xml_array['sitemap'])) { // parse sitemap set if (is_object($xml_array['sitemap'])) { $xml_array['sitemap'] = (array) $xml_array['sitemap']; } if (!empty($xml_array['sitemap']['loc'])) { // is single sitemap $this->_parse($xml_array['sitemap']['loc']); } else { // parse multiple sitemaps foreach ($xml_array['sitemap'] as $val) { $val = (array) $val; if (!empty($val['loc'])) { $this->_parse($val['loc']); // recursive parse sitemap } } } } elseif (!empty($xml_array['url'])) { // parse url set if (is_object($xml_array['url'])) { $xml_array['url'] = (array) $xml_array['url']; } // if only 1 element if (!empty($xml_array['url']['loc'])) { $this->_urls[] = $xml_array['url']['loc']; } else { foreach ($xml_array['url'] as $val) { $val = (array) $val; if (!empty($val['loc'])) { $this->_urls[] = $val['loc']; } } } } } } src/purge.cls.php 0000644 00000074764 15246276230 0007777 0 ustar 00 <?php /** * The plugin purge class for X-LiteSpeed-Purge * * @since 1.1.3 * @since 2.2 Refactored. Changed access from public to private for most func and class variables. */ namespace LiteSpeed; defined('WPINC') || exit(); class Purge extends Base { const LOG_TAG = '🧹'; protected $_pub_purge = array(); protected $_pub_purge2 = array(); protected $_priv_purge = array(); protected $_purge_single = false; const X_HEADER = 'X-LiteSpeed-Purge'; const X_HEADER2 = 'X-LiteSpeed-Purge2'; const DB_QUEUE = 'queue'; const DB_QUEUE2 = 'queue2'; const TYPE_PURGE_ALL = 'purge_all'; const TYPE_PURGE_ALL_LSCACHE = 'purge_all_lscache'; const TYPE_PURGE_ALL_CSSJS = 'purge_all_cssjs'; const TYPE_PURGE_ALL_LOCALRES = 'purge_all_localres'; const TYPE_PURGE_ALL_CCSS = 'purge_all_ccss'; const TYPE_PURGE_ALL_UCSS = 'purge_all_ucss'; const TYPE_PURGE_ALL_LQIP = 'purge_all_lqip'; const TYPE_PURGE_ALL_AVATAR = 'purge_all_avatar'; const TYPE_PURGE_ALL_OBJECT = 'purge_all_object'; const TYPE_PURGE_ALL_OPCACHE = 'purge_all_opcache'; const TYPE_PURGE_FRONT = 'purge_front'; const TYPE_PURGE_UCSS = 'purge_ucss'; const TYPE_PURGE_FRONTPAGE = 'purge_frontpage'; const TYPE_PURGE_PAGES = 'purge_pages'; const TYPE_PURGE_ERROR = 'purge_error'; /** * Init hooks * * @since 3.0 */ public function init() { // Register purge actions. // Most used values: edit_post, save_post, delete_post, wp_trash_post, clean_post_cache, wp_update_comment_count $purge_post_events = apply_filters('litespeed_purge_post_events', array( 'delete_post', 'wp_trash_post', // 'clean_post_cache', // This will disable wc's not purge product when stock status not change setting 'wp_update_comment_count', // TODO: check if needed for non ESI )); foreach ($purge_post_events as $event) { // this will purge all related tags add_action($event, array($this, 'purge_post')); } // Purge post only when status is/was publish add_action('transition_post_status', array($this, 'purge_publish'), 10, 3); add_action('wp_update_comment_count', array($this, 'purge_feeds')); if ($this->conf(self::O_OPTM_UCSS)) { add_action('edit_post', __NAMESPACE__ . '\Purge::purge_ucss'); } } /** * Only purge publish related status post * * @since 3.0 * @access public */ public function purge_publish($new_status, $old_status, $post) { if ($new_status != 'publish' && $old_status != 'publish') { return; } $this->purge_post($post->ID); } /** * Handle all request actions from main cls * * @since 1.8 * @access public */ public function handler() { $type = Router::verify_type(); switch ($type) { case self::TYPE_PURGE_ALL: $this->_purge_all(); break; case self::TYPE_PURGE_ALL_LSCACHE: $this->_purge_all_lscache(); break; case self::TYPE_PURGE_ALL_CSSJS: $this->_purge_all_cssjs(); break; case self::TYPE_PURGE_ALL_LOCALRES: $this->_purge_all_localres(); break; case self::TYPE_PURGE_ALL_CCSS: $this->_purge_all_ccss(); break; case self::TYPE_PURGE_ALL_UCSS: $this->_purge_all_ucss(); break; case self::TYPE_PURGE_ALL_LQIP: $this->_purge_all_lqip(); break; case self::TYPE_PURGE_ALL_AVATAR: $this->_purge_all_avatar(); break; case self::TYPE_PURGE_ALL_OBJECT: $this->_purge_all_object(); break; case self::TYPE_PURGE_ALL_OPCACHE: $this->purge_all_opcache(); break; case self::TYPE_PURGE_FRONT: $this->_purge_front(); break; case self::TYPE_PURGE_UCSS: $this->_purge_ucss(); break; case self::TYPE_PURGE_FRONTPAGE: $this->_purge_frontpage(); break; case self::TYPE_PURGE_PAGES: $this->_purge_pages(); break; case strpos($type, self::TYPE_PURGE_ERROR) === 0: $this->_purge_error(substr($type, strlen(self::TYPE_PURGE_ERROR))); break; default: break; } Admin::redirect(); } /** * Shortcut to purge all lscache * * @since 1.0.0 * @access public */ public static function purge_all($reason = false) { self::cls()->_purge_all($reason); } /** * Purge all caches (lscache/op/oc) * * @since 2.2 * @access private */ private function _purge_all($reason = false) { // if ( defined( 'LITESPEED_CLI' ) ) { // // Can't send, already has output, need to save and wait for next run // self::update_option( self::DB_QUEUE, $curr_built ); // self::debug( 'CLI request, queue stored: ' . $curr_built ); // } // else { $this->_purge_all_lscache(true); $this->_purge_all_cssjs(true); $this->_purge_all_localres(true); // $this->_purge_all_ccss( true ); // $this->_purge_all_lqip( true ); $this->_purge_all_object(true); $this->purge_all_opcache(true); // } if (!is_string($reason)) { $reason = false; } if ($reason) { $reason = "( $reason )"; } self::debug('Purge all ' . $reason, 3); $msg = __('Purged all caches successfully.', 'litespeed-cache'); !defined('LITESPEED_PURGE_SILENT') && Admin_Display::success($msg); do_action('litespeed_purged_all'); } /** * Alerts LiteSpeed Web Server to purge all pages. * * For multisite installs, if this is called by a site admin (not network admin), * it will only purge all posts associated with that site. * * @since 2.2 * @access public */ private function _purge_all_lscache($silence = false) { $this->_add('*'); // Action to run after server was notified to delete LSCache entries. do_action('litespeed_purged_all_lscache'); if (!$silence) { $msg = __('Notified LiteSpeed Web Server to purge all LSCache entries.', 'litespeed-cache'); !defined('LITESPEED_PURGE_SILENT') && Admin_Display::success($msg); } } /** * Delete all critical css * * @since 2.3 * @access private */ private function _purge_all_ccss($silence = false) { do_action('litespeed_purged_all_ccss'); $this->cls('CSS')->rm_cache_folder('ccss'); $this->cls('Data')->url_file_clean('ccss'); if (!$silence) { $msg = __('Cleaned all Critical CSS files.', 'litespeed-cache'); !defined('LITESPEED_PURGE_SILENT') && Admin_Display::success($msg); } } /** * Delete all unique css * * @since 2.3 * @access private */ private function _purge_all_ucss($silence = false) { do_action('litespeed_purged_all_ucss'); $this->cls('CSS')->rm_cache_folder('ucss'); $this->cls('Data')->url_file_clean('ucss'); if (!$silence) { $msg = __('Cleaned all Unique CSS files.', 'litespeed-cache'); !defined('LITESPEED_PURGE_SILENT') && Admin_Display::success($msg); } } /** * Purge one UCSS by URL * * @since 4.5 * @access public */ public static function purge_ucss($post_id_or_url) { self::debug('Purge a single UCSS: ' . $post_id_or_url); // If is post_id, generate URL if (!preg_match('/\D/', $post_id_or_url)) { $post_id_or_url = get_permalink($post_id_or_url); } $post_id_or_url = untrailingslashit($post_id_or_url); $existing_url_files = Data::cls()->mark_as_expired($post_id_or_url, true); if ($existing_url_files) { // Add to UCSS Q self::cls('UCSS')->add_to_q($existing_url_files); } } /** * Delete all LQIP images * * @since 3.0 * @access private */ private function _purge_all_lqip($silence = false) { do_action('litespeed_purged_all_lqip'); $this->cls('Placeholder')->rm_cache_folder('lqip'); if (!$silence) { $msg = __('Cleaned all LQIP files.', 'litespeed-cache'); !defined('LITESPEED_PURGE_SILENT') && Admin_Display::success($msg); } } /** * Delete all avatar images * * @since 3.0 * @access private */ private function _purge_all_avatar($silence = false) { do_action('litespeed_purged_all_avatar'); $this->cls('Avatar')->rm_cache_folder('avatar'); if (!$silence) { $msg = __('Cleaned all Gravatar files.', 'litespeed-cache'); !defined('LITESPEED_PURGE_SILENT') && Admin_Display::success($msg); } } /** * Delete all localized JS * * @since 3.3 * @access private */ private function _purge_all_localres($silence = false) { do_action('litespeed_purged_all_localres'); $this->_add(Tag::TYPE_LOCALRES); if (!$silence) { $msg = __('Cleaned all localized resource entries.', 'litespeed-cache'); !defined('LITESPEED_PURGE_SILENT') && Admin_Display::success($msg); } } /** * Alerts LiteSpeed Web Server to purge pages. * * @since 1.2.2 * @access private */ private function _purge_all_cssjs($silence = false) { if (defined('DOING_CRON') || defined('LITESPEED_DID_send_headers')) { self::debug('❌ Bypassed cssjs delete as header sent (lscache purge after this point will fail) or doing cron'); return; } $this->_purge_all_lscache($silence); // Purge CSSJS must purge lscache too to avoid 404 do_action('litespeed_purged_all_cssjs'); Optimize::update_option(Optimize::ITEM_TIMESTAMP_PURGE_CSS, time()); $this->_add(Tag::TYPE_MIN); $this->cls('CSS')->rm_cache_folder('css'); $this->cls('CSS')->rm_cache_folder('js'); $this->cls('Data')->url_file_clean('css'); $this->cls('Data')->url_file_clean('js'); // Clear UCSS queue as it used combined CSS to generate $this->clear_q('ucss', true); if (!$silence) { $msg = __('Notified LiteSpeed Web Server to purge CSS/JS entries.', 'litespeed-cache'); !defined('LITESPEED_PURGE_SILENT') && Admin_Display::success($msg); } } /** * Purge opcode cache * * @since 1.8.2 * @access public */ public function purge_all_opcache($silence = false) { if (!Router::opcache_enabled()) { self::debug('Failed to reset opcode cache due to opcache not enabled'); if (!$silence) { $msg = __('Opcode cache is not enabled.', 'litespeed-cache'); Admin_Display::error($msg); } return false; } // Action to run after opcache purge. do_action('litespeed_purged_all_opcache'); // Purge opcode cache opcache_reset(); self::debug('Reset opcode cache'); if (!$silence) { $msg = __('Reset the entire opcode cache successfully.', 'litespeed-cache'); !defined('LITESPEED_PURGE_SILENT') && Admin_Display::success($msg); } return true; } /** * Purge object cache * * @since 3.4 * @access public */ public static function purge_all_object($silence = true) { self::cls()->_purge_all_object($silence); } /** * Purge object cache * * @since 1.8 * @access private */ private function _purge_all_object($silence = false) { if (!defined('LSCWP_OBJECT_CACHE')) { self::debug('Failed to flush object cache due to object cache not enabled'); if (!$silence) { $msg = __('Object cache is not enabled.', 'litespeed-cache'); Admin_Display::error($msg); } return false; } do_action('litespeed_purged_all_object'); $this->cls('Object_Cache')->flush(); self::debug('Flushed object cache'); if (!$silence) { $msg = __('Purge all object caches successfully.', 'litespeed-cache'); !defined('LITESPEED_PURGE_SILENT') && Admin_Display::success($msg); } return true; } /** * Adds new public purge tags to the array of purge tags for the request. * * @since 1.1.3 * @access public * @param mixed $tags Tags to add to the list. */ public static function add($tags, $purge2 = false) { self::cls()->_add($tags, $purge2); } /** * Add tags to purge * * @since 2.2 * @access private */ private function _add($tags, $purge2 = false) { if (!is_array($tags)) { $tags = array($tags); } $tags = $this->_prepend_bid($tags); if (!array_diff($tags, $purge2 ? $this->_pub_purge2 : $this->_pub_purge)) { return; } if ($purge2) { $this->_pub_purge2 = array_merge($this->_pub_purge2, $tags); $this->_pub_purge2 = array_unique($this->_pub_purge2); } else { $this->_pub_purge = array_merge($this->_pub_purge, $tags); $this->_pub_purge = array_unique($this->_pub_purge); } self::debug('added ' . implode(',', $tags) . ($purge2 ? ' [Purge2]' : ''), 8); // Send purge header immediately $curr_built = $this->_build($purge2); if (defined('LITESPEED_CLI')) { // Can't send, already has output, need to save and wait for next run self::update_option($purge2 ? self::DB_QUEUE2 : self::DB_QUEUE, $curr_built); self::debug('CLI request, queue stored: ' . $curr_built); } else { @header($curr_built); if (defined('DOING_CRON') || defined('LITESPEED_DID_send_headers') || apply_filters('litespeed_delay_purge', false)) { self::update_option($purge2 ? self::DB_QUEUE2 : self::DB_QUEUE, $curr_built); self::debug('Output existed, queue stored: ' . $curr_built); } self::debug($curr_built); } } /** * Adds new private purge tags to the array of purge tags for the request. * * @since 1.1.3 * @access public * @param mixed $tags Tags to add to the list. */ public static function add_private($tags) { self::cls()->_add_private($tags); } /** * Add private ESI tag to purge list * * @since 3.0 * @access public */ public static function add_private_esi($tag) { self::add_private(Tag::TYPE_ESI . $tag); } /** * Add private all tag to purge list * * @since 3.0 * @access public */ public static function add_private_all() { self::add_private('*'); } /** * Add tags to private purge * * @since 2.2 * @access private */ private function _add_private($tags) { if (!is_array($tags)) { $tags = array($tags); } $tags = $this->_prepend_bid($tags); if (!array_diff($tags, $this->_priv_purge)) { return; } self::debug('added [private] ' . implode(',', $tags), 3); $this->_priv_purge = array_merge($this->_priv_purge, $tags); $this->_priv_purge = array_unique($this->_priv_purge); // Send purge header immediately @header($this->_build()); } /** * Incorporate blog_id into purge tags for multisite * * @since 4.0 * @access private * @param mixed $tags Tags to add to the list. */ private function _prepend_bid($tags) { if (in_array('*', $tags)) { return array('*'); } $curr_bid = is_multisite() ? get_current_blog_id() : ''; foreach ($tags as $k => $v) { $tags[$k] = $curr_bid . '_' . $v; } return $tags; } /** * Activate `purge related tags` for Admin QS. * * @since 1.1.3 * @access public * @deprecated @7.0 Drop @v7.5 */ public static function set_purge_related() { } /** * Activate `purge single url tag` for Admin QS. * * @since 1.1.3 * @access public */ public static function set_purge_single() { self::cls()->_purge_single = true; do_action('litespeed_purged_single'); } /** * Purge frontend url * * @since 1.3 * @since 2.2 Renamed from `frontend_purge`; Access changed from public * @access private */ private function _purge_front() { if (empty($_SERVER['HTTP_REFERER'])) { exit('no referer'); } $this->purge_url($_SERVER['HTTP_REFERER']); do_action('litespeed_purged_front', $_SERVER['HTTP_REFERER']); wp_redirect($_SERVER['HTTP_REFERER']); exit(); } /** * Purge single UCSS * @since 4.7 */ private function _purge_ucss() { if (empty($_SERVER['HTTP_REFERER'])) { exit('no referer'); } $url_tag = empty($_GET['url_tag']) ? $_SERVER['HTTP_REFERER'] : $_GET['url_tag']; self::debug('Purge ucss [url_tag] ' . $url_tag); do_action('litespeed_purge_ucss', $url_tag); $this->purge_url($_SERVER['HTTP_REFERER']); wp_redirect($_SERVER['HTTP_REFERER']); exit(); } /** * Alerts LiteSpeed Web Server to purge the front page. * * @since 1.0.3 * @since 2.2 Access changed from public to private, renamed from `_purge_front` * @access private */ private function _purge_frontpage() { $this->_add(Tag::TYPE_FRONTPAGE); if (LITESPEED_SERVER_TYPE !== 'LITESPEED_SERVER_OLS') { $this->_add_private(Tag::TYPE_FRONTPAGE); } $msg = __('Notified LiteSpeed Web Server to purge the front page.', 'litespeed-cache'); !defined('LITESPEED_PURGE_SILENT') && Admin_Display::success($msg); do_action('litespeed_purged_frontpage'); } /** * Alerts LiteSpeed Web Server to purge pages. * * @since 1.0.15 * @access private */ private function _purge_pages() { $this->_add(Tag::TYPE_PAGES); $msg = __('Notified LiteSpeed Web Server to purge all pages.', 'litespeed-cache'); !defined('LITESPEED_PURGE_SILENT') && Admin_Display::success($msg); do_action('litespeed_purged_pages'); } /** * Alerts LiteSpeed Web Server to purge error pages. * * @since 1.0.14 * @access private */ private function _purge_error($type = false) { $this->_add(Tag::TYPE_HTTP); if (!$type || !in_array($type, array('403', '404', '500'))) { return; } $this->_add(Tag::TYPE_HTTP . $type); $msg = __('Notified LiteSpeed Web Server to purge error pages.', 'litespeed-cache'); !defined('LITESPEED_PURGE_SILENT') && Admin_Display::success($msg); } /** * Callback to add purge tags if admin selects to purge selected category pages. * * @since 1.0.7 * @access public */ public function purge_cat($value) { $val = trim($value); if (empty($val)) { return; } if (preg_match('/^[a-zA-Z0-9-]+$/', $val) == 0) { self::debug("$val cat invalid"); return; } $cat = get_category_by_slug($val); if ($cat == false) { self::debug("$val cat not existed/published"); return; } self::add(Tag::TYPE_ARCHIVE_TERM . $cat->term_id); !defined('LITESPEED_PURGE_SILENT') && Admin_Display::success(sprintf(__('Purge category %s', 'litespeed-cache'), $val)); // Action to run after category purge. do_action('litespeed_purged_cat', $value); } /** * Callback to add purge tags if admin selects to purge selected tag pages. * * @since 1.0.7 * @access public */ public function purge_tag($val) { $val = trim($val); if (empty($val)) { return; } if (preg_match('/^[a-zA-Z0-9-]+$/', $val) == 0) { self::debug("$val tag invalid"); return; } $term = get_term_by('slug', $val, 'post_tag'); if ($term == 0) { self::debug("$val tag not exist"); return; } self::add(Tag::TYPE_ARCHIVE_TERM . $term->term_id); !defined('LITESPEED_PURGE_SILENT') && Admin_Display::success(sprintf(__('Purge tag %s', 'litespeed-cache'), $val)); // Action to run after tag purge. do_action('litespeed_purged_tag', $val); } /** * Callback to add purge tags if admin selects to purge selected urls. * * @since 1.0.7 * @access public */ public function purge_url($url, $purge2 = false, $quite = false) { $val = trim($url); if (empty($val)) { return; } if (strpos($val, '<') !== false) { self::debug("$val url contains <"); return; } $val = Utility::make_relative($val); $hash = Tag::get_uri_tag($val); if ($hash === false) { self::debug("$val url invalid"); return; } self::add($hash, $purge2); !$quite && !defined('LITESPEED_PURGE_SILENT') && Admin_Display::success(sprintf(__('Purge url %s', 'litespeed-cache'), $val)); // Action to run after url purge. do_action('litespeed_purged_link', $url); } /** * Purge a list of pages when selected by admin. This method will look at the post arguments to determine how and what to purge. * * @since 1.0.7 * @access public */ public function purge_list() { if (!isset($_REQUEST[Admin_Display::PURGEBYOPT_SELECT]) || !isset($_REQUEST[Admin_Display::PURGEBYOPT_LIST])) { return; } $sel = $_REQUEST[Admin_Display::PURGEBYOPT_SELECT]; $list_buf = $_REQUEST[Admin_Display::PURGEBYOPT_LIST]; if (empty($list_buf)) { return; } $list_buf = str_replace(',', "\n", $list_buf); // for cli $list = explode("\n", $list_buf); switch ($sel) { case Admin_Display::PURGEBY_CAT: $cb = 'purge_cat'; break; case Admin_Display::PURGEBY_PID: $cb = 'purge_post'; break; case Admin_Display::PURGEBY_TAG: $cb = 'purge_tag'; break; case Admin_Display::PURGEBY_URL: $cb = 'purge_url'; break; default: return; } array_map(array($this, $cb), $list); // for redirection $_GET[Admin_Display::PURGEBYOPT_SELECT] = $sel; } /** * Purge ESI * * @since 3.0 * @access public */ public static function purge_esi($tag) { self::add(Tag::TYPE_ESI . $tag); do_action('litespeed_purged_esi', $tag); } /** * Purge a certain post type * * @since 3.0 * @access public */ public static function purge_posttype($post_type) { self::add(Tag::TYPE_ARCHIVE_POSTTYPE . $post_type); self::add($post_type); do_action('litespeed_purged_posttype', $post_type); } /** * Purge all related tags to a post. * * @since 1.0.0 * @access public */ public function purge_post($pid) { $pid = intval($pid); // ignore the status we don't care if (!$pid || !in_array(get_post_status($pid), array('publish', 'trash', 'private', 'draft'))) { return; } $purge_tags = $this->_get_purge_tags_by_post($pid); if (!$purge_tags) { return; } self::add($purge_tags); if ($this->conf(self::O_CACHE_REST)) { self::add(Tag::TYPE_REST); } // $this->cls( 'Control' )->set_stale(); do_action('litespeed_purged_post', $pid); } /** * Hooked to the load-widgets.php action. * Attempts to purge a single widget from cache. * If no widget id is passed in, the method will attempt to find the widget id. * * @since 1.1.3 * @access public */ public static function purge_widget($widget_id = null) { if (is_null($widget_id)) { $widget_id = $_POST['widget-id']; if (is_null($widget_id)) { return; } } self::add(Tag::TYPE_WIDGET . $widget_id); self::add_private(Tag::TYPE_WIDGET . $widget_id); do_action('litespeed_purged_widget', $widget_id); } /** * Hooked to the wp_update_comment_count action. * Purges the comment widget when the count is updated. * * @access public * @since 1.1.3 * @global type $wp_widget_factory */ public static function purge_comment_widget() { global $wp_widget_factory; if (!isset($wp_widget_factory->widgets['WP_Widget_Recent_Comments'])) { return; } $recent_comments = $wp_widget_factory->widgets['WP_Widget_Recent_Comments']; if (!is_null($recent_comments)) { self::add(Tag::TYPE_WIDGET . $recent_comments->id); self::add_private(Tag::TYPE_WIDGET . $recent_comments->id); do_action('litespeed_purged_comment_widget', $recent_comments->id); } } /** * Purges feeds on comment count update. * * @since 1.0.9 * @access public */ public function purge_feeds() { if ($this->conf(self::O_CACHE_TTL_FEED) > 0) { self::add(Tag::TYPE_FEED); } do_action('litespeed_purged_feeds'); } /** * Purges all private cache entries when the user logs out. * * @access public * @since 1.1.3 */ public static function purge_on_logout() { self::add_private_all(); do_action('litespeed_purged_on_logout'); } /** * Generate all purge tags before output * * @access private * @since 1.1.3 */ private function _finalize() { // Make sure header output only run once if (!defined('LITESPEED_DID_' . __FUNCTION__)) { define('LITESPEED_DID_' . __FUNCTION__, true); } else { return; } do_action('litespeed_purge_finalize'); // Append unique uri purge tags if Admin QS is `PURGESINGLE` or `PURGE` if ($this->_purge_single) { $tags = array(Tag::build_uri_tag()); $this->_pub_purge = array_merge($this->_pub_purge, $this->_prepend_bid($tags)); } if (!empty($this->_pub_purge)) { $this->_pub_purge = array_unique($this->_pub_purge); } if (!empty($this->_priv_purge)) { $this->_priv_purge = array_unique($this->_priv_purge); } } /** * Gathers all the purge headers. * * This will collect all site wide purge tags as well as third party plugin defined purge tags. * * @since 1.1.0 * @access public * @return string the built purge header */ public static function output() { $instance = self::cls(); $instance->_finalize(); return $instance->_build(); } /** * Build the current purge headers. * * @since 1.1.5 * @access private * @return string the built purge header */ private function _build($purge2 = false) { if ($purge2) { if (empty($this->_pub_purge2)) { return; } } else { if (empty($this->_pub_purge) && empty($this->_priv_purge)) { return; } } $purge_header = ''; // Handle purge2 @since 4.4.1 if ($purge2) { $public_tags = $this->_append_prefix($this->_pub_purge2); if (empty($public_tags)) { return; } $purge_header = self::X_HEADER2 . ': public,'; if (Control::is_stale()) { $purge_header .= 'stale,'; } $purge_header .= implode(',', $public_tags); return $purge_header; } $private_prefix = self::X_HEADER . ': private,'; if (!empty($this->_pub_purge)) { $public_tags = $this->_append_prefix($this->_pub_purge); if (empty($public_tags)) { // If this ends up empty, private will also end up empty return; } $purge_header = self::X_HEADER . ': public,'; if (Control::is_stale()) { $purge_header .= 'stale,'; } $purge_header .= implode(',', $public_tags); $private_prefix = ';private,'; } // Handle priv purge tags if (!empty($this->_priv_purge)) { $private_tags = $this->_append_prefix($this->_priv_purge, true); $purge_header .= $private_prefix . implode(',', $private_tags); } return $purge_header; } /** * Append prefix to an array of purge headers * * @since 1.1.0 * @access private */ private function _append_prefix($purge_tags, $is_private = false) { $curr_bid = is_multisite() ? get_current_blog_id() : ''; if (!in_array('*', $purge_tags)) { $tags = array(); foreach ($purge_tags as $val) { $tags[] = LSWCP_TAG_PREFIX . $val; } return $tags; } // Purge All need to check if need to reset crawler or not if (!$is_private && $this->conf(self::O_CRAWLER)) { Crawler::cls()->reset_pos(); } if ((defined('LSWCP_EMPTYCACHE') && LSWCP_EMPTYCACHE) || $is_private) { return array('*'); } if (is_multisite() && !$this->_is_subsite_purge()) { $blogs = Activation::get_network_ids(); if (empty($blogs)) { self::debug('build_purge_headers: blog list is empty'); return ''; } $tags = array(); foreach ($blogs as $blog_id) { $tags[] = LSWCP_TAG_PREFIX . $blog_id . '_'; } return $tags; } else { return array(LSWCP_TAG_PREFIX . $curr_bid . '_'); } } /** * Check if this purge belongs to a subsite purge * * @since 4.0 */ private function _is_subsite_purge() { if (!is_multisite()) { return false; } if (is_network_admin()) { return false; } if (defined('LSWCP_EMPTYCACHE') && LSWCP_EMPTYCACHE) { return false; } // Would only use multisite and network admin except is_network_admin is false for ajax calls, which is used by wordpress updates v4.6+ if (Router::is_ajax() && (check_ajax_referer('updates', false, false) || check_ajax_referer('litespeed-purgeall-network', false, false))) { return false; } return true; } /** * Gets all the purge tags correlated with the post about to be purged. * * If the purge all pages configuration is set, all pages will be purged. * * This includes site wide post types (e.g. front page) as well as any third party plugin specific post tags. * * @since 1.0.0 * @access private */ private function _get_purge_tags_by_post($post_id) { // If this is a valid post we want to purge the post, the home page and any associated tags & cats // If not, purge everything on the site. $purge_tags = array(); if ($this->conf(self::O_PURGE_POST_ALL)) { // ignore the rest if purge all return array('*'); } // now do API hook action for post purge do_action('litespeed_api_purge_post', $post_id); // post $purge_tags[] = Tag::TYPE_POST . $post_id; $post_status = get_post_status($post_id); if (function_exists('is_post_status_viewable')) { $viewable = is_post_status_viewable($post_status); if ($viewable) { $purge_tags[] = Tag::get_uri_tag(wp_make_link_relative(get_permalink($post_id))); } } // for archive of categories|tags|custom tax global $post; $original_post = $post; $post = get_post($post_id); $post_type = $post->post_type; global $wp_widget_factory; // recent_posts $recent_posts = isset($wp_widget_factory->widgets['WP_Widget_Recent_Posts']) ? $wp_widget_factory->widgets['WP_Widget_Recent_Posts'] : null; if (!is_null($recent_posts)) { $purge_tags[] = Tag::TYPE_WIDGET . $recent_posts->id; } // get adjacent posts id as related post tag if ($post_type == 'post') { $prev_post = get_previous_post(); $next_post = get_next_post(); if (!empty($prev_post->ID)) { $purge_tags[] = Tag::TYPE_POST . $prev_post->ID; self::debug('--------purge_tags prev is: ' . $prev_post->ID); } if (!empty($next_post->ID)) { $purge_tags[] = Tag::TYPE_POST . $next_post->ID; self::debug('--------purge_tags next is: ' . $next_post->ID); } } if ($this->conf(self::O_PURGE_POST_TERM)) { $taxonomies = get_object_taxonomies($post_type); //self::debug('purge by post, check tax = ' . var_export($taxonomies, true)); foreach ($taxonomies as $tax) { $terms = get_the_terms($post_id, $tax); if (!empty($terms)) { foreach ($terms as $term) { $purge_tags[] = Tag::TYPE_ARCHIVE_TERM . $term->term_id; } } } } if ($this->conf(self::O_CACHE_TTL_FEED)) { $purge_tags[] = Tag::TYPE_FEED; } // author, for author posts and feed list if ($this->conf(self::O_PURGE_POST_AUTHOR)) { $purge_tags[] = Tag::TYPE_AUTHOR . get_post_field('post_author', $post_id); } // archive and feed of post type // todo: check if type contains space if ($this->conf(self::O_PURGE_POST_POSTTYPE)) { if (get_post_type_archive_link($post_type)) { $purge_tags[] = Tag::TYPE_ARCHIVE_POSTTYPE . $post_type; $purge_tags[] = $post_type; } } if ($this->conf(self::O_PURGE_POST_FRONTPAGE)) { $purge_tags[] = Tag::TYPE_FRONTPAGE; } if ($this->conf(self::O_PURGE_POST_HOMEPAGE)) { $purge_tags[] = Tag::TYPE_HOME; } if ($this->conf(self::O_PURGE_POST_PAGES)) { $purge_tags[] = Tag::TYPE_PAGES; } if ($this->conf(self::O_PURGE_POST_PAGES_WITH_RECENT_POSTS)) { $purge_tags[] = Tag::TYPE_PAGES_WITH_RECENT_POSTS; } // if configured to have archived by date $date = $post->post_date; $date = strtotime($date); if ($this->conf(self::O_PURGE_POST_DATE)) { $purge_tags[] = Tag::TYPE_ARCHIVE_DATE . date('Ymd', $date); } if ($this->conf(self::O_PURGE_POST_MONTH)) { $purge_tags[] = Tag::TYPE_ARCHIVE_DATE . date('Ym', $date); } if ($this->conf(self::O_PURGE_POST_YEAR)) { $purge_tags[] = Tag::TYPE_ARCHIVE_DATE . date('Y', $date); } // Set back to original post as $post_id might affecting the global $post value $post = $original_post; return array_unique($purge_tags); } /** * The dummy filter for purge all * * @since 1.1.5 * @access public * @param string $val The filter value * @return string The filter value */ public static function filter_with_purge_all($val) { self::purge_all(); return $val; } } src/optimizer.cls.php 0000644 00000022625 15246276230 0010664 0 ustar 00 <?php /** * The optimize4 class. * * @since 1.9 * @package LiteSpeed * @subpackage LiteSpeed/inc * @author LiteSpeed Technologies <info@litespeedtech.com> */ namespace LiteSpeed; defined('WPINC') || exit(); class Optimizer extends Root { private $_conf_css_font_display; /** * Init optimizer * * @since 1.9 */ public function __construct() { $this->_conf_css_font_display = $this->conf(Base::O_OPTM_CSS_FONT_DISPLAY); } /** * Run HTML minify process and return final content * * @since 1.9 * @access public */ public function html_min($content, $force_inline_minify = false) { if (!apply_filters('litespeed_html_min', true)) { Debug2::debug2('[Optmer] html_min bypassed via litespeed_html_min filter'); return $content; } $options = array(); if ($force_inline_minify) { $options['jsMinifier'] = __CLASS__ . '::minify_js'; } $skip_comments = $this->conf(Base::O_OPTM_HTML_SKIP_COMMENTS); if ($skip_comments) { $options['skipComments'] = $skip_comments; } /** * Added exception capture when minify * @since 2.2.3 */ try { $obj = new Lib\HTML_MIN($content, $options); $content_final = $obj->process(); // check if content from minification is empty if ($content_final == '') { Debug2::debug('Failed to minify HTML: HTML minification resulted in empty HTML'); return $content; } if (!defined('LSCACHE_ESI_SILENCE')) { $content_final .= "\n" . '<!-- Page optimized by LiteSpeed Cache @' . date('Y-m-d H:i:s', time() + LITESPEED_TIME_OFFSET) . ' -->'; } return $content_final; } catch (\Exception $e) { Debug2::debug('******[Optmer] html_min failed: ' . $e->getMessage()); error_log('****** LiteSpeed Optimizer html_min failed: ' . $e->getMessage()); return $content; } } /** * Run minify process and save content * * @since 1.9 * @access public */ public function serve($request_url, $file_type, $minify, $src_list) { // Try Unique CSS if ($file_type == 'css') { $content = false; if (defined('LITESPEED_GUEST_OPTM') || $this->conf(Base::O_OPTM_UCSS)) { $filename = $this->cls('UCSS')->load($request_url); if ($filename) { return array($filename, 'ucss'); } } } // Before generated, don't know the contented hash filename yet, so used url hash as tmp filename $file_path_prefix = $this->_build_filepath_prefix($file_type); $url_tag = $request_url; $url_tag_for_file = md5($request_url); if (is_404()) { $url_tag_for_file = $url_tag = '404'; } elseif ($file_type == 'css' && apply_filters('litespeed_ucss_per_pagetype', false)) { $url_tag_for_file = $url_tag = Utility::page_type(); } $static_file = LITESPEED_STATIC_DIR . $file_path_prefix . $url_tag_for_file . '.' . $file_type; // Create tmp file to avoid conflict $tmp_static_file = $static_file . '.tmp'; if (file_exists($tmp_static_file) && time() - filemtime($tmp_static_file) <= 600) { // some other request is generating return false; } // File::save( $tmp_static_file, '/* ' . ( is_404() ? '404' : $request_url ) . ' */', true ); // Can't use this bcos this will get filecon md5 changed File::save($tmp_static_file, '', true); // Load content $real_files = array(); foreach ($src_list as $src_info) { $is_min = false; if (!empty($src_info['inl'])) { // Load inline $content = $src_info['src']; } else { // Load file $content = $this->load_file($src_info['src'], $file_type); if (!$content) { continue; } $is_min = $this->is_min($src_info['src']); } $content = $this->optm_snippet($content, $file_type, $minify && !$is_min, $src_info['src'], !empty($src_info['media']) ? $src_info['media'] : false); // Write to file File::save($tmp_static_file, $content, true, true); } // if CSS - run the minification on the saved file. // Will move imports to the top of file and remove extra spaces. if ($file_type == 'css') { $obj = new Lib\CSS_JS_MIN\Minify\CSS(); $file_content_combined = $obj->moveImportsToTop(File::read($tmp_static_file)); File::save($tmp_static_file, $file_content_combined); } // validate md5 $filecon_md5 = md5_file($tmp_static_file); $final_file_path = $file_path_prefix . $filecon_md5 . '.' . $file_type; $realfile = LITESPEED_STATIC_DIR . $final_file_path; if (!file_exists($realfile)) { rename($tmp_static_file, $realfile); Debug2::debug2('[Optmer] Saved static file [path] ' . $realfile); } else { unlink($tmp_static_file); } $vary = $this->cls('Vary')->finalize_full_varies(); Debug2::debug2("[Optmer] Save URL to file for [file_type] $file_type [file] $filecon_md5 [vary] $vary "); $this->cls('Data')->save_url($url_tag, $vary, $file_type, $filecon_md5, dirname($realfile)); return array($filecon_md5 . '.' . $file_type, $file_type); } /** * Load a single file * @since 4.0 */ public function optm_snippet($content, $file_type, $minify, $src, $media = false) { // CSS related features if ($file_type == 'css') { // Font optimize if ($this->_conf_css_font_display) { $content = preg_replace('#(@font\-face\s*\{)#isU', '${1}font-display:swap;', $content); } $content = preg_replace('/@charset[^;]+;\\s*/', '', $content); if ($media) { $content = '@media ' . $media . '{' . $content . "\n}"; } if ($minify) { $content = self::minify_css($content); } $content = $this->cls('CDN')->finalize($content); if ((defined('LITESPEED_GUEST_OPTM') || $this->conf(Base::O_IMG_OPTM_WEBP)) && $this->cls('Media')->webp_support()) { $content = $this->cls('Media')->replace_background_webp($content); } } else { if ($minify) { $content = self::minify_js($content); } else { $content = $this->_null_minifier($content); } $content .= "\n;"; } // Add filter $content = apply_filters('litespeed_optm_cssjs', $content, $file_type, $src); return $content; } /** * Load remote resource from cache if existed * * @since 4.7 */ private function load_cached_file($url, $file_type) { $file_path_prefix = $this->_build_filepath_prefix($file_type); $folder_name = LITESPEED_STATIC_DIR . $file_path_prefix; $to_be_deleted_folder = $folder_name . date('Ymd', strtotime('-2 days')); if (file_exists($to_be_deleted_folder)) { Debug2::debug('[Optimizer] ❌ Clearing folder [name] ' . $to_be_deleted_folder); File::rrmdir($to_be_deleted_folder); } $today_file = $folder_name . date('Ymd') . '/' . md5($url); if (file_exists($today_file)) { return File::read($today_file); } // Write file $res = wp_safe_remote_get($url); $res_code = wp_remote_retrieve_response_code($res); if (is_wp_error($res) || $res_code != 200) { Debug2::debug2('[Optimizer] ❌ Load Remote error [code] ' . $res_code); return false; } $con = wp_remote_retrieve_body($res); if (!$con) { return false; } Debug2::debug('[Optimizer] ✅ Save remote file to cache [name] ' . $today_file); File::save($today_file, $con, true); return $con; } /** * Load remote/local resource * * @since 3.5 */ public function load_file($src, $file_type = 'css') { $real_file = Utility::is_internal_file($src); $postfix = pathinfo(parse_url($src, PHP_URL_PATH), PATHINFO_EXTENSION); if (!$real_file || $postfix != $file_type) { Debug2::debug2('[CSS] Load Remote [' . $file_type . '] ' . $src); $this_url = substr($src, 0, 2) == '//' ? set_url_scheme($src) : $src; $con = $this->load_cached_file($this_url, $file_type); if ($file_type == 'css') { $dirname = dirname($this_url) . '/'; $con = Lib\UriRewriter::prepend($con, $dirname); } } else { Debug2::debug2('[CSS] Load local [' . $file_type . '] ' . $real_file[0]); $con = File::read($real_file[0]); if ($file_type == 'css') { $dirname = dirname($real_file[0]); $con = Lib\UriRewriter::rewrite($con, $dirname); } } return $con; } /** * Minify CSS * * @since 2.2.3 * @access private */ public static function minify_css($data) { try { $obj = new Lib\CSS_JS_MIN\Minify\CSS(); $obj->add($data); return $obj->minify(); } catch (\Exception $e) { Debug2::debug('******[Optmer] minify_css failed: ' . $e->getMessage()); error_log('****** LiteSpeed Optimizer minify_css failed: ' . $e->getMessage()); return $data; } } /** * Minify JS * * Added exception capture when minify * * @since 2.2.3 * @access private */ public static function minify_js($data, $js_type = '') { // For inline JS optimize, need to check if it's js type if ($js_type) { preg_match('#type=([\'"])(.+)\g{1}#isU', $js_type, $matches); if ($matches && $matches[2] != 'text/javascript') { Debug2::debug('******[Optmer] minify_js bypass due to type: ' . $matches[2]); return $data; } } try { $obj = new Lib\CSS_JS_MIN\Minify\JS(); $obj->add($data); return $obj->minify(); } catch (\Exception $e) { Debug2::debug('******[Optmer] minify_js failed: ' . $e->getMessage()); // error_log( '****** LiteSpeed Optimizer minify_js failed: ' . $e->getMessage() ); return $data; } } /** * Basic minifier * * @access private */ private function _null_minifier($content) { $content = str_replace("\r\n", "\n", $content); return trim($content); } /** * Check if the file is already min file * * @since 1.9 */ public function is_min($filename) { $basename = basename($filename); if (preg_match('/[-\.]min\.(?:[a-zA-Z]+)$/i', $basename)) { return true; } return false; } } src/debug2.cls.php 0000644 00000032126 15246276230 0010007 0 ustar 00 <?php /** * The plugin logging class. */ namespace LiteSpeed; defined('WPINC') || exit(); class Debug2 extends Root { private static $log_path; private static $log_path_prefix; private static $_prefix; const TYPE_CLEAR_LOG = 'clear_log'; const TYPE_BETA_TEST = 'beta_test'; const BETA_TEST_URL = 'beta_test_url'; const BETA_TEST_URL_WP = 'https://downloads.wordpress.org/plugin/litespeed-cache.zip'; /** * Log class Confructor * * NOTE: in this process, until last step ( define const LSCWP_LOG = true ), any usage to WP filter will not be logged to prevent infinite loop with log_filters() * * @since 1.1.2 * @access public */ public function __construct() { self::$log_path_prefix = LITESPEED_STATIC_DIR . '/debug/'; // Maybe move legacy log files $this->_maybe_init_folder(); self::$log_path = $this->path('debug'); if (!empty($_SERVER['HTTP_USER_AGENT']) && strpos($_SERVER['HTTP_USER_AGENT'], 'lscache_') === 0) { self::$log_path = $this->path('crawler'); } !defined('LSCWP_LOG_TAG') && define('LSCWP_LOG_TAG', get_current_blog_id()); if ($this->conf(Base::O_DEBUG_LEVEL)) { !defined('LSCWP_LOG_MORE') && define('LSCWP_LOG_MORE', true); } defined('LSCWP_DEBUG_EXC_STRINGS') || define('LSCWP_DEBUG_EXC_STRINGS', $this->conf(Base::O_DEBUG_EXC_STRINGS)); } /** * Try moving legacy logs into /litespeed/debug/ folder * * @since 6.5 */ private function _maybe_init_folder() { if (file_exists(self::$log_path_prefix . 'index.php')) { return; } file::save(self::$log_path_prefix . 'index.php', '<?php // Silence is golden.', true); $logs = array('debug', 'debug.purge', 'crawler'); foreach ($logs as $log) { if (file_exists(LSCWP_CONTENT_DIR . '/' . $log . '.log') && !file_exists($this->path($log))) { rename(LSCWP_CONTENT_DIR . '/' . $log . '.log', $this->path($log)); } } } /** * Generate log file path * * @since 6.5 */ public function path($type) { return self::$log_path_prefix . self::FilePath($type); } /** * Generate the fixed log filename * * @since 6.5 */ public static function FilePath($type) { if ($type == 'debug.purge') { $type = 'purge'; } $key = defined('AUTH_KEY') ? AUTH_KEY : md5(__FILE__); $rand = substr(md5(substr($key, -16)), -16); return $type . $rand . '.log'; } /** * End call of one request process * @since 4.7 * @access public */ public static function ended() { $headers = headers_list(); foreach ($headers as $key => $header) { if (stripos($header, 'Set-Cookie') === 0) { unset($headers[$key]); } } self::debug('Response headers', $headers); $elapsed_time = number_format((microtime(true) - LSCWP_TS_0) * 1000, 2); self::debug("End response\n--------------------------------------------------Duration: " . $elapsed_time . " ms------------------------------\n"); } /** * Beta test upgrade * * @since 2.9.5 * @access public */ public function beta_test($zip = false) { if (!$zip) { if (empty($_REQUEST[self::BETA_TEST_URL])) { return; } $zip = $_REQUEST[self::BETA_TEST_URL]; if ($zip !== Debug2::BETA_TEST_URL_WP) { if ($zip === 'latest') { $zip = Debug2::BETA_TEST_URL_WP; } else { // Generate zip url $zip = $this->_package_zip($zip); } } } if (!$zip) { Debug2::debug('[Debug2] ❌ No ZIP file'); return; } Debug2::debug('[Debug2] ZIP file ' . $zip); $update_plugins = get_site_transient('update_plugins'); if (!is_object($update_plugins)) { $update_plugins = new \stdClass(); } $plugin_info = new \stdClass(); $plugin_info->new_version = Core::VER; $plugin_info->slug = Core::PLUGIN_NAME; $plugin_info->plugin = Core::PLUGIN_FILE; $plugin_info->package = $zip; $plugin_info->url = 'https://wordpress.org/plugins/litespeed-cache/'; $update_plugins->response[Core::PLUGIN_FILE] = $plugin_info; set_site_transient('update_plugins', $update_plugins); // Run upgrade Activation::cls()->upgrade(); } /** * Git package refresh * * @since 2.9.5 * @access private */ private function _package_zip($commit) { $data = array( 'commit' => $commit, ); $res = Cloud::get(Cloud::API_BETA_TEST, $data); if (empty($res['zip'])) { return false; } return $res['zip']; } /** * Log Purge headers separately * * @since 2.7 * @access public */ public static function log_purge($purge_header) { // Check if debug is ON if (!defined('LSCWP_LOG') && !defined('LSCWP_LOG_BYPASS_NOTADMIN')) { return; } $purge_file = self::cls()->path('purge'); self::cls()->_init_request($purge_file); $msg = $purge_header . self::_backtrace_info(6); File::append($purge_file, self::format_message($msg)); } /** * Enable debug log * * @since 1.1.0 * @access public */ public function init() { $debug = $this->conf(Base::O_DEBUG); if ($debug == Base::VAL_ON2) { if (!$this->cls('Router')->is_admin_ip()) { defined('LSCWP_LOG_BYPASS_NOTADMIN') || define('LSCWP_LOG_BYPASS_NOTADMIN', true); return; } } /** * Check if hit URI includes/excludes * This is after LSCWP_LOG_BYPASS_NOTADMIN to make `log_purge()` still work * @since 3.0 */ $list = $this->conf(Base::O_DEBUG_INC); if ($list) { $result = Utility::str_hit_array($_SERVER['REQUEST_URI'], $list); if (!$result) { return; } } $list = $this->conf(Base::O_DEBUG_EXC); if ($list) { $result = Utility::str_hit_array($_SERVER['REQUEST_URI'], $list); if ($result) { return; } } if (!defined('LSCWP_LOG')) { // If not initialized, do it now $this->_init_request(); define('LSCWP_LOG', true); } } /** * Create the initial log messages with the request parameters. * * @since 1.0.12 * @access private */ private function _init_request($log_file = null) { if (!$log_file) { $log_file = self::$log_path; } // Check log file size $log_file_size = $this->conf(Base::O_DEBUG_FILESIZE); if (file_exists($log_file) && filesize($log_file) > $log_file_size * 1000000) { File::save($log_file, ''); } // For more than 2s's requests, add more break if (file_exists($log_file) && time() - filemtime($log_file) > 2) { File::append($log_file, "\n\n\n\n"); } if (PHP_SAPI == 'cli') { return; } $servervars = array( 'Query String' => '', 'HTTP_ACCEPT' => '', 'HTTP_USER_AGENT' => '', 'HTTP_ACCEPT_ENCODING' => '', 'HTTP_COOKIE' => '', 'REQUEST_METHOD' => '', 'SERVER_PROTOCOL' => '', 'X-LSCACHE' => '', 'LSCACHE_VARY_COOKIE' => '', 'LSCACHE_VARY_VALUE' => '', 'ESI_CONTENT_TYPE' => '', ); $server = array_merge($servervars, $_SERVER); $params = array(); if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') { $server['SERVER_PROTOCOL'] .= ' (HTTPS) '; } $param = sprintf('💓 ------%s %s %s', $server['REQUEST_METHOD'], $server['SERVER_PROTOCOL'], strtok($server['REQUEST_URI'], '?')); $qs = !empty($server['QUERY_STRING']) ? $server['QUERY_STRING'] : ''; if ($this->conf(Base::O_DEBUG_COLLAPSE_QS)) { $qs = $this->_omit_long_message($qs); if ($qs) { $param .= ' ? ' . $qs; } $params[] = $param; } else { $params[] = $param; $params[] = 'Query String: ' . $qs; } if (!empty($_SERVER['HTTP_REFERER'])) { $params[] = 'HTTP_REFERER: ' . $this->_omit_long_message($server['HTTP_REFERER']); } if (defined('LSCWP_LOG_MORE')) { $params[] = 'User Agent: ' . $this->_omit_long_message($server['HTTP_USER_AGENT']); $params[] = 'Accept: ' . $server['HTTP_ACCEPT']; $params[] = 'Accept Encoding: ' . $server['HTTP_ACCEPT_ENCODING']; } // $params[] = 'Cookie: ' . $server['HTTP_COOKIE']; if (isset($_COOKIE['_lscache_vary'])) { $params[] = 'Cookie _lscache_vary: ' . $_COOKIE['_lscache_vary']; } if (defined('LSCWP_LOG_MORE')) { $params[] = 'X-LSCACHE: ' . (!empty($server['X-LSCACHE']) ? 'true' : 'false'); } if ($server['LSCACHE_VARY_COOKIE']) { $params[] = 'LSCACHE_VARY_COOKIE: ' . $server['LSCACHE_VARY_COOKIE']; } if ($server['LSCACHE_VARY_VALUE']) { $params[] = 'LSCACHE_VARY_VALUE: ' . $server['LSCACHE_VARY_VALUE']; } if ($server['ESI_CONTENT_TYPE']) { $params[] = 'ESI_CONTENT_TYPE: ' . $server['ESI_CONTENT_TYPE']; } $request = array_map(__CLASS__ . '::format_message', $params); File::append($log_file, $request); } /** * Trim long msg to keep log neat * @since 6.3 */ private function _omit_long_message($msg) { if (strlen($msg) > 53) { $msg = substr($msg, 0, 53) . '...'; } return $msg; } /** * Formats the log message with a consistent prefix. * * @since 1.0.12 * @access private * @param string $msg The log message to write. * @return string The formatted log message. */ private static function format_message($msg) { // If call here without calling get_enabled() first, improve compatibility if (!defined('LSCWP_LOG_TAG')) { return $msg . "\n"; } if (!isset(self::$_prefix)) { // address if (PHP_SAPI == 'cli') { $addr = '=CLI='; if (isset($_SERVER['USER'])) { $addr .= $_SERVER['USER']; } elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) { $addr .= $_SERVER['HTTP_X_FORWARDED_FOR']; } } else { $addr = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : ''; $port = isset($_SERVER['REMOTE_PORT']) ? $_SERVER['REMOTE_PORT'] : ''; $addr = "$addr:$port"; } // Generate a unique string per request self::$_prefix = sprintf(' [%s %s %s] ', $addr, LSCWP_LOG_TAG, Str::rrand(3)); } list($usec, $sec) = explode(' ', microtime()); return date('m/d/y H:i:s', $sec + LITESPEED_TIME_OFFSET) . substr($usec, 1, 4) . self::$_prefix . $msg . "\n"; } /** * Direct call to log a debug message. * * @since 1.1.3 * @access public */ public static function debug($msg, $backtrace_limit = false) { if (!defined('LSCWP_LOG')) { return; } if (defined('LSCWP_DEBUG_EXC_STRINGS') && Utility::str_hit_array($msg, LSCWP_DEBUG_EXC_STRINGS)) { return; } if ($backtrace_limit !== false) { if (!is_numeric($backtrace_limit)) { $backtrace_limit = self::trim_longtext($backtrace_limit); if (is_array($backtrace_limit) && count($backtrace_limit) == 1 && !empty($backtrace_limit[0])) { $msg .= ' --- ' . $backtrace_limit[0]; } else { $msg .= ' --- ' . var_export($backtrace_limit, true); } self::push($msg); return; } self::push($msg, $backtrace_limit + 1); return; } self::push($msg); } /** * Trim long string before array dump * @since 3.3 */ public static function trim_longtext($backtrace_limit) { if (is_array($backtrace_limit)) { $backtrace_limit = array_map(__CLASS__ . '::trim_longtext', $backtrace_limit); } if (is_string($backtrace_limit) && strlen($backtrace_limit) > 500) { $backtrace_limit = substr($backtrace_limit, 0, 1000) . '...'; } return $backtrace_limit; } /** * Direct call to log an advanced debug message. * * @since 1.2.0 * @access public */ public static function debug2($msg, $backtrace_limit = false) { if (!defined('LSCWP_LOG_MORE')) { return; } self::debug($msg, $backtrace_limit); } /** * Logs a debug message. * * @since 1.1.0 * @access private * @param string $msg The debug message. * @param int $backtrace_limit Backtrace depth. */ private static function push($msg, $backtrace_limit = false) { // backtrace handler if (defined('LSCWP_LOG_MORE') && $backtrace_limit !== false) { $msg .= self::_backtrace_info($backtrace_limit); } File::append(self::$log_path, self::format_message($msg)); } /** * Backtrace info * * @since 2.7 */ private static function _backtrace_info($backtrace_limit) { $msg = ''; $trace = version_compare(PHP_VERSION, '5.4.0', '<') ? debug_backtrace() : debug_backtrace(false, $backtrace_limit + 3); for ($i = 2; $i <= $backtrace_limit + 2; $i++) { // 0st => _backtrace_info(), 1st => push() if (empty($trace[$i]['class'])) { if (empty($trace[$i]['file'])) { break; } $log = "\n" . $trace[$i]['file']; } else { if ($trace[$i]['class'] == __CLASS__) { continue; } $args = ''; if (!empty($trace[$i]['args'])) { foreach ($trace[$i]['args'] as $v) { if (is_array($v)) { $v = 'ARRAY'; } if (is_string($v) || is_numeric($v)) { $args .= $v . ','; } } $args = substr($args, 0, strlen($args) > 100 ? 100 : -1); } $log = str_replace('Core', 'LSC', $trace[$i]['class']) . $trace[$i]['type'] . $trace[$i]['function'] . '(' . $args . ')'; } if (!empty($trace[$i - 1]['line'])) { $log .= '@' . $trace[$i - 1]['line']; } $msg .= " => $log"; } return $msg; } /** * Clear log file * * @since 1.6.6 * @access private */ private function _clear_log() { $logs = array('debug', 'purge', 'crawler'); foreach ($logs as $log) { File::save($this->path($log), ''); } } /** * Handle all request actions from main cls * * @since 1.6.6 * @access public */ public function handler() { $type = Router::verify_type(); switch ($type) { case self::TYPE_CLEAR_LOG: $this->_clear_log(); break; case self::TYPE_BETA_TEST: $this->beta_test(); break; default: break; } Admin::redirect(); } } src/object.lib.php 0000644 00000103740 15246276230 0010073 0 ustar 00 <?php /** * LiteSpeed Object Cache Library * * @since 1.8 */ defined('WPINC') || exit(); /** * Handle exception */ if (!function_exists('litespeed_exception_handler')) { function litespeed_exception_handler($errno, $errstr, $errfile, $errline) { throw new \ErrorException($errstr, 0, $errno, $errfile, $errline); } } require_once __DIR__ . '/object-cache.cls.php'; /** * Sets up Object Cache Global and assigns it. * * @since 1.8 * * @global WP_Object_Cache $wp_object_cache */ function wp_cache_init() { $GLOBALS['wp_object_cache'] = WP_Object_Cache::get_instance(); } /** * Adds data to the cache, if the cache key doesn't already exist. * * @since 1.8 * * @see WP_Object_Cache::add() * @global WP_Object_Cache $wp_object_cache Object cache global instance. * * @param int|string $key The cache key to use for retrieval later. * @param mixed $data The data to add to the cache. * @param string $group Optional. The group to add the cache to. Enables the same key * to be used across groups. Default empty. * @param int $expire Optional. When the cache data should expire, in seconds. * Default 0 (no expiration). * @return bool True on success, false if cache key and group already exist. */ function wp_cache_add($key, $data, $group = '', $expire = 0) { global $wp_object_cache; return $wp_object_cache->add($key, $data, $group, (int) $expire); } /** * Adds multiple values to the cache in one call. * * @since 5.4 * * @see WP_Object_Cache::add_multiple() * @global WP_Object_Cache $wp_object_cache Object cache global instance. * * @param array $data Array of keys and values to be set. * @param string $group Optional. Where the cache contents are grouped. Default empty. * @param int $expire Optional. When to expire the cache contents, in seconds. * Default 0 (no expiration). * @return bool[] Array of return values, grouped by key. Each value is either * true on success, or false if cache key and group already exist. */ function wp_cache_add_multiple(array $data, $group = '', $expire = 0) { global $wp_object_cache; return $wp_object_cache->add_multiple($data, $group, $expire); } /** * Replaces the contents of the cache with new data. * * @since 1.8 * * @see WP_Object_Cache::replace() * @global WP_Object_Cache $wp_object_cache Object cache global instance. * * @param int|string $key The key for the cache data that should be replaced. * @param mixed $data The new data to store in the cache. * @param string $group Optional. The group for the cache data that should be replaced. * Default empty. * @param int $expire Optional. When to expire the cache contents, in seconds. * Default 0 (no expiration). * @return bool True if contents were replaced, false if original value does not exist. */ function wp_cache_replace($key, $data, $group = '', $expire = 0) { global $wp_object_cache; return $wp_object_cache->replace($key, $data, $group, (int) $expire); } /** * Saves the data to the cache. * * Differs from wp_cache_add() and wp_cache_replace() in that it will always write data. * * @since 1.8 * * @see WP_Object_Cache::set() * @global WP_Object_Cache $wp_object_cache Object cache global instance. * * @param int|string $key The cache key to use for retrieval later. * @param mixed $data The contents to store in the cache. * @param string $group Optional. Where to group the cache contents. Enables the same key * to be used across groups. Default empty. * @param int $expire Optional. When to expire the cache contents, in seconds. * Default 0 (no expiration). * @return bool True on success, false on failure. */ function wp_cache_set($key, $data, $group = '', $expire = 0) { global $wp_object_cache; return $wp_object_cache->set($key, $data, $group, (int) $expire); } /** * Sets multiple values to the cache in one call. * * @since 5.4 * * @see WP_Object_Cache::set_multiple() * @global WP_Object_Cache $wp_object_cache Object cache global instance. * * @param array $data Array of keys and values to be set. * @param string $group Optional. Where the cache contents are grouped. Default empty. * @param int $expire Optional. When to expire the cache contents, in seconds. * Default 0 (no expiration). * @return bool[] Array of return values, grouped by key. Each value is either * true on success, or false on failure. */ function wp_cache_set_multiple(array $data, $group = '', $expire = 0) { global $wp_object_cache; return $wp_object_cache->set_multiple($data, $group, $expire); } /** * Retrieves the cache contents from the cache by key and group. * * @since 1.8 * * @see WP_Object_Cache::get() * @global WP_Object_Cache $wp_object_cache Object cache global instance. * * @param int|string $key The key under which the cache contents are stored. * @param string $group Optional. Where the cache contents are grouped. Default empty. * @param bool $force Optional. Whether to force an update of the local cache * from the persistent cache. Default false. * @param bool $found Optional. Whether the key was found in the cache (passed by reference). * Disambiguates a return of false, a storable value. Default null. * @return mixed|false The cache contents on success, false on failure to retrieve contents. */ function wp_cache_get($key, $group = '', $force = false, &$found = null) { global $wp_object_cache; return $wp_object_cache->get($key, $group, $force, $found); } /** * Retrieves multiple values from the cache in one call. * * @since 5.4 * * @see WP_Object_Cache::get_multiple() * @global WP_Object_Cache $wp_object_cache Object cache global instance. * * @param array $keys Array of keys under which the cache contents are stored. * @param string $group Optional. Where the cache contents are grouped. Default empty. * @param bool $force Optional. Whether to force an update of the local cache * from the persistent cache. Default false. * @return array Array of return values, grouped by key. Each value is either * the cache contents on success, or false on failure. */ function wp_cache_get_multiple($keys, $group = '', $force = false) { global $wp_object_cache; return $wp_object_cache->get_multiple($keys, $group, $force); } /** * Removes the cache contents matching key and group. * * @since 1.8 * * @see WP_Object_Cache::delete() * @global WP_Object_Cache $wp_object_cache Object cache global instance. * * @param int|string $key What the contents in the cache are called. * @param string $group Optional. Where the cache contents are grouped. Default empty. * @return bool True on successful removal, false on failure. */ function wp_cache_delete($key, $group = '') { global $wp_object_cache; return $wp_object_cache->delete($key, $group); } /** * Deletes multiple values from the cache in one call. * * @since 5.4 * * @see WP_Object_Cache::delete_multiple() * @global WP_Object_Cache $wp_object_cache Object cache global instance. * * @param array $keys Array of keys under which the cache to deleted. * @param string $group Optional. Where the cache contents are grouped. Default empty. * @return bool[] Array of return values, grouped by key. Each value is either * true on success, or false if the contents were not deleted. */ function wp_cache_delete_multiple(array $keys, $group = '') { global $wp_object_cache; return $wp_object_cache->delete_multiple($keys, $group); } /** * Increments numeric cache item's value. * * @since 1.8 * * @see WP_Object_Cache::incr() * @global WP_Object_Cache $wp_object_cache Object cache global instance. * * @param int|string $key The key for the cache contents that should be incremented. * @param int $offset Optional. The amount by which to increment the item's value. * Default 1. * @param string $group Optional. The group the key is in. Default empty. * @return int|false The item's new value on success, false on failure. */ function wp_cache_incr($key, $offset = 1, $group = '') { global $wp_object_cache; return $wp_object_cache->incr($key, $offset, $group); } /** * Decrements numeric cache item's value. * * @since 1.8 * * @see WP_Object_Cache::decr() * @global WP_Object_Cache $wp_object_cache Object cache global instance. * * @param int|string $key The cache key to decrement. * @param int $offset Optional. The amount by which to decrement the item's value. * Default 1. * @param string $group Optional. The group the key is in. Default empty. * @return int|false The item's new value on success, false on failure. */ function wp_cache_decr($key, $offset = 1, $group = '') { global $wp_object_cache; return $wp_object_cache->decr($key, $offset, $group); } /** * Removes all cache items. * * @since 1.8 * * @see WP_Object_Cache::flush() * @global WP_Object_Cache $wp_object_cache Object cache global instance. * * @return bool True on success, false on failure. */ function wp_cache_flush() { global $wp_object_cache; return $wp_object_cache->flush(); } /** * Removes all cache items from the in-memory runtime cache. * * @since 5.4 * * @see WP_Object_Cache::flush_runtime() * * @return bool True on success, false on failure. */ function wp_cache_flush_runtime() { global $wp_object_cache; return $wp_object_cache->flush_runtime(); } /** * Removes all cache items in a group, if the object cache implementation supports it. * * Before calling this function, always check for group flushing support using the * `wp_cache_supports( 'flush_group' )` function. * * @since 5.4 * * @see WP_Object_Cache::flush_group() * @global WP_Object_Cache $wp_object_cache Object cache global instance. * * @param string $group Name of group to remove from cache. * @return bool True if group was flushed, false otherwise. */ function wp_cache_flush_group($group) { global $wp_object_cache; return $wp_object_cache->flush_group($group); } /** * Determines whether the object cache implementation supports a particular feature. * * @since 5.4 * * @param string $feature Name of the feature to check for. Possible values include: * 'add_multiple', 'set_multiple', 'get_multiple', 'delete_multiple', * 'flush_runtime', 'flush_group'. * @return bool True if the feature is supported, false otherwise. */ function wp_cache_supports($feature) { switch ($feature) { case 'add_multiple': case 'set_multiple': case 'get_multiple': case 'delete_multiple': case 'flush_runtime': return true; case 'flush_group': default: return false; } } /** * Closes the cache. * * This function has ceased to do anything since WordPress 2.5. The * functionality was removed along with the rest of the persistent cache. * * This does not mean that plugins can't implement this function when they need * to make sure that the cache is cleaned up after WordPress no longer needs it. * * @since 1.8 * * @return true Always returns true. */ function wp_cache_close() { return true; } /** * Adds a group or set of groups to the list of global groups. * * @since 1.8 * * @see WP_Object_Cache::add_global_groups() * @global WP_Object_Cache $wp_object_cache Object cache global instance. * * @param string|string[] $groups A group or an array of groups to add. */ function wp_cache_add_global_groups($groups) { global $wp_object_cache; $wp_object_cache->add_global_groups($groups); } /** * Adds a group or set of groups to the list of non-persistent groups. * * @since 1.8 * * @param string|string[] $groups A group or an array of groups to add. */ function wp_cache_add_non_persistent_groups($groups) { global $wp_object_cache; $wp_object_cache->add_non_persistent_groups($groups); } /** * Switches the internal blog ID. * * This changes the blog id used to create keys in blog specific groups. * * @since 1.8 * * @see WP_Object_Cache::switch_to_blog() * @global WP_Object_Cache $wp_object_cache Object cache global instance. * * @param int $blog_id Site ID. */ function wp_cache_switch_to_blog($blog_id) { global $wp_object_cache; $wp_object_cache->switch_to_blog($blog_id); } class WP_Object_Cache { protected static $_instance; private $_object_cache; private $_cache = array(); private $_cache_404 = array(); private $cache_total = 0; private $count_hit_incall = 0; private $count_hit = 0; private $count_miss_incall = 0; private $count_miss = 0; private $count_set = 0; protected $global_groups = array(); private $blog_prefix; private $multisite; /** * Init. * * @since 1.8 */ public function __construct() { $this->_object_cache = \LiteSpeed\Object_Cache::cls(); $this->multisite = is_multisite(); $this->blog_prefix = $this->multisite ? get_current_blog_id() . ':' : ''; /** * Fix multiple instance using same oc issue * @since 1.8.2 */ !defined('LSOC_PREFIX') && define('LSOC_PREFIX', substr(md5(__FILE__), -5)); } /** * Makes private properties readable for backward compatibility. * * @since 5.4 * @access public * * @param string $name Property to get. * @return mixed Property. */ public function __get($name) { return $this->$name; } /** * Makes private properties settable for backward compatibility. * * @since 5.4 * @access public * * @param string $name Property to set. * @param mixed $value Property value. * @return mixed Newly-set property. */ public function __set($name, $value) { return $this->$name = $value; } /** * Makes private properties checkable for backward compatibility. * * @since 5.4 * @access public * * @param string $name Property to check if set. * @return bool Whether the property is set. */ public function __isset($name) { return isset($this->$name); } /** * Makes private properties un-settable for backward compatibility. * * @since 5.4 * @access public * * @param string $name Property to unset. */ public function __unset($name) { unset($this->$name); } /** * Serves as a utility function to determine whether a key is valid. * * @since 5.4 * @access protected * * @param int|string $key Cache key to check for validity. * @return bool Whether the key is valid. */ protected function is_valid_key($key) { if (is_int($key)) { return true; } if (is_string($key) && trim($key) !== '') { return true; } $type = gettype($key); if (!function_exists('__')) { wp_load_translations_early(); } $message = is_string($key) ? __('Cache key must not be an empty string.') : /* translators: %s: The type of the given cache key. */ sprintf(__('Cache key must be integer or non-empty string, %s given.'), $type); _doing_it_wrong(sprintf('%s::%s', __CLASS__, debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2)[1]['function']), $message, '6.1.0'); return false; } /** * Get the final key. * * @since 1.8 * @access private */ private function _key($key, $group = 'default') { if (empty($group)) { $group = 'default'; } $prefix = $this->_object_cache->is_global($group) ? '' : $this->blog_prefix; return LSOC_PREFIX . $prefix . $group . '.' . $key; } /** * Output debug info. * * @since 1.8 * @access public */ public function debug() { return ' [total] ' . $this->cache_total . ' [hit_incall] ' . $this->count_hit_incall . ' [hit] ' . $this->count_hit . ' [miss_incall] ' . $this->count_miss_incall . ' [miss] ' . $this->count_miss . ' [set] ' . $this->count_set; } /** * Adds data to the cache if it doesn't already exist. * * @since 1.8 * @access public * * @uses WP_Object_Cache::_exists() Checks to see if the cache already has data. * @uses WP_Object_Cache::set() Sets the data after the checking the cache * contents existence. * * @param int|string $key What to call the contents in the cache. * @param mixed $data The contents to store in the cache. * @param string $group Optional. Where to group the cache contents. Default 'default'. * @param int $expire Optional. When to expire the cache contents, in seconds. * Default 0 (no expiration). * @return bool True on success, false if cache key and group already exist. */ public function add($key, $data, $group = 'default', $expire = 0) { if (wp_suspend_cache_addition()) { return false; } if (!$this->is_valid_key($key)) { return false; } if (empty($group)) { $group = 'default'; } $id = $this->_key($key, $group); if (array_key_exists($id, $this->_cache)) { return false; } return $this->set($key, $data, $group, (int) $expire); } /** * Adds multiple values to the cache in one call. * * @since 5.4 * @access public * * @param array $data Array of keys and values to be added. * @param string $group Optional. Where the cache contents are grouped. Default empty. * @param int $expire Optional. When to expire the cache contents, in seconds. * Default 0 (no expiration). * @return bool[] Array of return values, grouped by key. Each value is either * true on success, or false if cache key and group already exist. */ public function add_multiple(array $data, $group = '', $expire = 0) { $values = array(); foreach ($data as $key => $value) { $values[$key] = $this->add($key, $value, $group, $expire); } return $values; } /** * Replaces the contents in the cache, if contents already exist. * * @since 1.8 * @access public * * @see WP_Object_Cache::set() * * @param int|string $key What to call the contents in the cache. * @param mixed $data The contents to store in the cache. * @param string $group Optional. Where to group the cache contents. Default 'default'. * @param int $expire Optional. When to expire the cache contents, in seconds. * Default 0 (no expiration). * @return bool True if contents were replaced, false if original value does not exist. */ public function replace($key, $data, $group = 'default', $expire = 0) { if (!$this->is_valid_key($key)) { return false; } if (empty($group)) { $group = 'default'; } $id = $this->_key($key, $group); if (!array_key_exists($id, $this->_cache)) { return false; } return $this->set($key, $data, $group, (int) $expire); } /** * Sets the data contents into the cache. * * The cache contents are grouped by the $group parameter followed by the * $key. This allows for duplicate IDs in unique groups. Therefore, naming of * the group should be used with care and should follow normal function * naming guidelines outside of core WordPress usage. * * The $expire parameter is not used, because the cache will automatically * expire for each time a page is accessed and PHP finishes. The method is * more for cache plugins which use files. * * @since 1.8 * @since 5.4 Returns false if cache key is invalid. * @access public * * @param int|string $key What to call the contents in the cache. * @param mixed $data The contents to store in the cache. * @param string $group Optional. Where to group the cache contents. Default 'default'. * @param int $expire Optional. When to expire the cache contents, in seconds. * Default 0 (no expiration). * @return bool True if contents were set, false if key is invalid. */ public function set($key, $data, $group = 'default', $expire = 0) { if (!$this->is_valid_key($key)) { return false; } if (empty($group)) { $group = 'default'; } $id = $this->_key($key, $group); if (is_object($data)) { $data = clone $data; } // error_log("oc: set \t\t\t[key] " . $id ); $this->_cache[$id] = $data; if (array_key_exists($id, $this->_cache_404)) { // error_log("oc: unset404\t\t\t[key] " . $id ); unset($this->_cache_404[$id]); } if (!$this->_object_cache->is_non_persistent($group)) { $this->_object_cache->set($id, serialize(array('data' => $data)), (int) $expire); $this->count_set++; } if ($this->_object_cache->store_transients($group)) { $this->_transient_set($key, $data, $group, (int) $expire); } return true; } /** * Sets multiple values to the cache in one call. * * @since 5.4 * @access public * * @param array $data Array of key and value to be set. * @param string $group Optional. Where the cache contents are grouped. Default empty. * @param int $expire Optional. When to expire the cache contents, in seconds. * Default 0 (no expiration). * @return bool[] Array of return values, grouped by key. Each value is always true. */ public function set_multiple(array $data, $group = '', $expire = 0) { $values = array(); foreach ($data as $key => $value) { $values[$key] = $this->set($key, $value, $group, $expire); } return $values; } /** * Retrieves the cache contents, if it exists. * * The contents will be first attempted to be retrieved by searching by the * key in the cache group. If the cache is hit (success) then the contents * are returned. * * On failure, the number of cache misses will be incremented. * * @since 1.8 * @access public * * @param int|string $key The key under which the cache contents are stored. * @param string $group Optional. Where the cache contents are grouped. Default 'default'. * @param bool $force Optional. Unused. Whether to force an update of the local cache * from the persistent cache. Default false. * @param bool $found Optional. Whether the key was found in the cache (passed by reference). * Disambiguates a return of false, a storable value. Default null. * @return mixed|false The cache contents on success, false on failure to retrieve contents. */ public function get($key, $group = 'default', $force = false, &$found = null) { if (!$this->is_valid_key($key)) { return false; } if (empty($group)) { $group = 'default'; } $id = $this->_key($key, $group); // error_log(''); // error_log("oc: get \t\t\t[key] " . $id . ( $force ? "\t\t\t [forced] " : '' ) ); $found = false; $found_in_oc = false; $cache_val = false; if (array_key_exists($id, $this->_cache) && !$force) { $found = true; $cache_val = $this->_cache[$id]; $this->count_hit_incall++; } elseif (!array_key_exists($id, $this->_cache_404) && !$this->_object_cache->is_non_persistent($group)) { $v = $this->_object_cache->get($id); if ($v !== null) { $v = @maybe_unserialize($v); } // To be compatible with false val if (is_array($v) && array_key_exists('data', $v)) { $this->count_hit++; $found = true; $found_in_oc = true; $cache_val = $v['data']; } else { // Can't find key, cache it to 404 // error_log("oc: add404\t\t\t[key] " . $id ); $this->_cache_404[$id] = 1; $this->count_miss++; } } else { $this->count_miss_incall++; } if (is_object($cache_val)) { $cache_val = clone $cache_val; } // If not found but has `Store Transients` cfg on, still need to follow WP's get_transient() logic if (!$found && $this->_object_cache->store_transients($group)) { $cache_val = $this->_transient_get($key, $group); if ($cache_val) { $found = true; // $found not used for now (v1.8.3) } } if ($found_in_oc) { $this->_cache[$id] = $cache_val; } $this->cache_total++; return $cache_val; } /** * Retrieves multiple values from the cache in one call. * * @since 5.4 * @access public * * @param array $keys Array of keys under which the cache contents are stored. * @param string $group Optional. Where the cache contents are grouped. Default 'default'. * @param bool $force Optional. Whether to force an update of the local cache * from the persistent cache. Default false. * @return array Array of return values, grouped by key. Each value is either * the cache contents on success, or false on failure. */ public function get_multiple($keys, $group = 'default', $force = false) { $values = array(); foreach ($keys as $key) { $values[$key] = $this->get($key, $group, $force); } return $values; } /** * Removes the contents of the cache key in the group. * * If the cache key does not exist in the group, then nothing will happen. * * @since 1.8 * @access public * * @param int|string $key What the contents in the cache are called. * @param string $group Optional. Where the cache contents are grouped. Default 'default'. * @param bool $deprecated Optional. Unused. Default false. * @return bool True on success, false if the contents were not deleted. */ public function delete($key, $group = 'default', $deprecated = false) { if (!$this->is_valid_key($key)) { return false; } if (empty($group)) { $group = 'default'; } $id = $this->_key($key, $group); if ($this->_object_cache->store_transients($group)) { $this->_transient_del($key, $group); } if (array_key_exists($id, $this->_cache)) { unset($this->_cache[$id]); } // error_log("oc: delete \t\t\t[key] " . $id ); if ($this->_object_cache->is_non_persistent($group)) { return false; } return $this->_object_cache->delete($id); } /** * Deletes multiple values from the cache in one call. * * @since 5.4 * @access public * * @param array $keys Array of keys to be deleted. * @param string $group Optional. Where the cache contents are grouped. Default empty. * @return bool[] Array of return values, grouped by key. Each value is either * true on success, or false if the contents were not deleted. */ public function delete_multiple(array $keys, $group = '') { $values = array(); foreach ($keys as $key) { $values[$key] = $this->delete($key, $group); } return $values; } /** * Increments numeric cache item's value. * * @since 5.4 * * @param int|string $key The cache key to increment. * @param int $offset Optional. The amount by which to increment the item's value. * Default 1. * @param string $group Optional. The group the key is in. Default 'default'. * @return int|false The item's new value on success, false on failure. */ public function incr($key, $offset = 1, $group = 'default') { return $this->incr_desr($key, $offset, $group, true); } /** * Decrements numeric cache item's value. * * @since 5.4 * * @param int|string $key The cache key to decrement. * @param int $offset Optional. The amount by which to decrement the item's value. * Default 1. * @param string $group Optional. The group the key is in. Default 'default'. * @return int|false The item's new value on success, false on failure. */ public function decr($key, $offset = 1, $group = 'default') { return $this->incr_desr($key, $offset, $group, false); } /** * Increments or decrements numeric cache item's value. * * @since 1.8 * @access public */ public function incr_desr($key, $offset = 1, $group = 'default', $incr = true) { if (!$this->is_valid_key($key)) { return false; } if (empty($group)) { $group = 'default'; } $cache_val = $this->get($key, $group); if (false === $cache_val) { return false; } if (!is_numeric($cache_val)) { $cache_val = 0; } $offset = (int) $offset; if ($incr) { $cache_val += $offset; } else { $cache_val -= $offset; } if ($cache_val < 0) { $cache_val = 0; } $this->set($key, $cache_val, $group); return $cache_val; } /** * Clears the object cache of all data. * * @since 1.8 * @access public * * @return true Always returns true. */ public function flush() { $this->flush_runtime(); $this->_object_cache->flush(); return true; } /** * Removes all cache items from the in-memory runtime cache. * * @since 5.4 * @access public * * @return true Always returns true. */ public function flush_runtime() { $this->_cache = array(); $this->_cache_404 = array(); return true; } /** * Removes all cache items in a group. * * @since 5.4 * @access public * * @param string $group Name of group to remove from cache. * @return true Always returns true. */ public function flush_group($group) { // unset( $this->cache[ $group ] ); return true; } /** * Sets the list of global cache groups. * * @since 1.8 * @access public * * @param string|string[] $groups List of groups that are global. */ public function add_global_groups($groups) { $groups = (array) $groups; $this->_object_cache->add_global_groups($groups); } /** * Sets the list of non-persistent cache groups. * * @since 1.8 * @access public */ public function add_non_persistent_groups($groups) { $groups = (array) $groups; $this->_object_cache->add_non_persistent_groups($groups); } /** * Switches the internal blog ID. * * This changes the blog ID used to create keys in blog specific groups. * * @since 1.8 * @access public * * @param int $blog_id Blog ID. */ public function switch_to_blog($blog_id) { $blog_id = (int) $blog_id; $this->blog_prefix = $this->multisite ? $blog_id . ':' : ''; } /** * Get transient from wp table * * @since 1.8.3 * @access private * @see `wp-includes/option.php` function `get_transient`/`set_site_transient` */ private function _transient_get($transient, $group) { if ($group == 'transient') { /**** Ori WP func start ****/ $transient_option = '_transient_' . $transient; if (!wp_installing()) { // If option is not in alloptions, it is not autoloaded and thus has a timeout $alloptions = wp_load_alloptions(); if (!isset($alloptions[$transient_option])) { $transient_timeout = '_transient_timeout_' . $transient; $timeout = get_option($transient_timeout); if (false !== $timeout && $timeout < time()) { delete_option($transient_option); delete_option($transient_timeout); $value = false; } } } if (!isset($value)) { $value = get_option($transient_option); } /**** Ori WP func end ****/ } elseif ($group == 'site-transient') { /**** Ori WP func start ****/ $no_timeout = array('update_core', 'update_plugins', 'update_themes'); $transient_option = '_site_transient_' . $transient; if (!in_array($transient, $no_timeout)) { $transient_timeout = '_site_transient_timeout_' . $transient; $timeout = get_site_option($transient_timeout); if (false !== $timeout && $timeout < time()) { delete_site_option($transient_option); delete_site_option($transient_timeout); $value = false; } } if (!isset($value)) { $value = get_site_option($transient_option); } /**** Ori WP func end ****/ } else { $value = false; } return $value; } /** * Set transient to WP table * * @since 1.8.3 * @access private * @see `wp-includes/option.php` function `set_transient`/`set_site_transient` */ private function _transient_set($transient, $value, $group, $expiration) { if ($group == 'transient') { /**** Ori WP func start ****/ $transient_timeout = '_transient_timeout_' . $transient; $transient_option = '_transient_' . $transient; if (false === get_option($transient_option)) { $autoload = 'yes'; if ((int) $expiration) { $autoload = 'no'; add_option($transient_timeout, time() + (int) $expiration, '', 'no'); } $result = add_option($transient_option, $value, '', $autoload); } else { // If expiration is requested, but the transient has no timeout option, // delete, then re-create transient rather than update. $update = true; if ((int) $expiration) { if (false === get_option($transient_timeout)) { delete_option($transient_option); add_option($transient_timeout, time() + (int) $expiration, '', 'no'); $result = add_option($transient_option, $value, '', 'no'); $update = false; } else { update_option($transient_timeout, time() + (int) $expiration); } } if ($update) { $result = update_option($transient_option, $value); } } /**** Ori WP func end ****/ } elseif ($group == 'site-transient') { /**** Ori WP func start ****/ $transient_timeout = '_site_transient_timeout_' . $transient; $option = '_site_transient_' . $transient; if (false === get_site_option($option)) { if ((int) $expiration) { add_site_option($transient_timeout, time() + (int) $expiration); } $result = add_site_option($option, $value); } else { if ((int) $expiration) { update_site_option($transient_timeout, time() + (int) $expiration); } $result = update_site_option($option, $value); } /**** Ori WP func end ****/ } else { $result = null; } return $result; } /** * Delete transient from WP table * * @since 1.8.3 * @access private * @see `wp-includes/option.php` function `delete_transient`/`delete_site_transient` */ private function _transient_del($transient, $group) { if ($group == 'transient') { /**** Ori WP func start ****/ $option_timeout = '_transient_timeout_' . $transient; $option = '_transient_' . $transient; $result = delete_option($option); if ($result) { delete_option($option_timeout); } /**** Ori WP func end ****/ } elseif ($group == 'site-transient') { /**** Ori WP func start ****/ $option_timeout = '_site_transient_timeout_' . $transient; $option = '_site_transient_' . $transient; $result = delete_site_option($option); if ($result) { delete_site_option($option_timeout); } /**** Ori WP func end ****/ } } /** * Get the current instance object. * * @since 1.8 * @access public */ public static function get_instance() { if (!isset(self::$_instance)) { self::$_instance = new self(); } return self::$_instance; } } src/root.cls.php 0000644 00000031435 15246276230 0007624 0 ustar 00 <?php /** * The abstract instance * * @since 3.0 */ namespace LiteSpeed; defined('WPINC') || exit(); abstract class Root { const CONF_FILE = '.litespeed_conf.dat'; // Instance set private static $_instances; private static $_options = array(); private static $_const_options = array(); private static $_primary_options = array(); private static $_network_options = array(); /** * Check if need to separate ccss for mobile * * @since 4.7 * @access protected */ protected function _separate_mobile() { return (wp_is_mobile() || apply_filters('litespeed_is_mobile', false)) && $this->conf(Base::O_CACHE_MOBILE); } /** * Log an error message * * @since 7.0 */ public static function debugErr($msg, $backtrace_limit = false) { $msg = '❌ ' . $msg; self::debug($msg, $backtrace_limit); } /** * Log a debug message. * * @since 4.4 * @access public */ public static function debug($msg, $backtrace_limit = false) { if (!defined('LSCWP_LOG')) { return; } if (defined('static::LOG_TAG')) { $msg = static::LOG_TAG . ' ' . $msg; } Debug2::debug($msg, $backtrace_limit); } /** * Log an advanced debug message. * * @since 4.4 * @access public */ public static function debug2($msg, $backtrace_limit = false) { if (!defined('LSCWP_LOG_MORE')) { return; } if (defined('static::LOG_TAG')) { $msg = static::LOG_TAG . ' ' . $msg; } Debug2::debug2($msg, $backtrace_limit); } /** * Check if there is cache folder for that type * * @since 3.0 */ public function has_cache_folder($type) { $subsite_id = is_multisite() && !is_network_admin() ? get_current_blog_id() : ''; if (file_exists(LITESPEED_STATIC_DIR . '/' . $type . '/' . $subsite_id)) { return true; } return false; } /** * Maybe make the cache folder if not existed * * @since 4.4.2 */ protected function _maybe_mk_cache_folder($type) { if (!$this->has_cache_folder($type)) { $subsite_id = is_multisite() && !is_network_admin() ? get_current_blog_id() : ''; $path = LITESPEED_STATIC_DIR . '/' . $type . '/' . $subsite_id; mkdir($path, 0755, true); } } /** * Delete file-based cache folder for that type * * @since 3.0 */ public function rm_cache_folder($type) { if (!$this->has_cache_folder($type)) { return; } $subsite_id = is_multisite() && !is_network_admin() ? get_current_blog_id() : ''; File::rrmdir(LITESPEED_STATIC_DIR . '/' . $type . '/' . $subsite_id); // Clear All summary data self::save_summary(false, false, true); if ($type == 'ccss' || $type == 'ucss') { Debug2::debug('[CSS] Cleared ' . $type . ' queue'); } elseif ($type == 'avatar') { Debug2::debug('[Avatar] Cleared ' . $type . ' queue'); } elseif ($type == 'css' || $type == 'js') { return; } else { Debug2::debug('[' . strtoupper($type) . '] Cleared ' . $type . ' queue'); } } /** * Build the static filepath * * @since 4.0 */ protected function _build_filepath_prefix($type) { $filepath_prefix = '/' . $type . '/'; if (is_multisite()) { $filepath_prefix .= get_current_blog_id() . '/'; } return $filepath_prefix; } /** * Load current queues from data file * * @since 4.1 * @since 4.3 Elevated to root.cls */ public function load_queue($type) { $filepath_prefix = $this->_build_filepath_prefix($type); $static_path = LITESPEED_STATIC_DIR . $filepath_prefix . '.litespeed_conf.dat'; $queue = array(); if (file_exists($static_path)) { $queue = \json_decode(file_get_contents($static_path), true) ?: array(); } return $queue; } /** * Save current queues to data file * * @since 4.1 * @since 4.3 Elevated to root.cls */ public function save_queue($type, $list) { $filepath_prefix = $this->_build_filepath_prefix($type); $static_path = LITESPEED_STATIC_DIR . $filepath_prefix . '.litespeed_conf.dat'; $data = \json_encode($list); File::save($static_path, $data, true); } /** * Clear all waiting queues * * @since 3.4 * @since 4.3 Elevated to root.cls */ public function clear_q($type, $silent = false) { $filepath_prefix = $this->_build_filepath_prefix($type); $static_path = LITESPEED_STATIC_DIR . $filepath_prefix . '.litespeed_conf.dat'; if (file_exists($static_path)) { $silent = false; unlink($static_path); } if (!$silent) { $msg = __('All QUIC.cloud service queues have been cleared.', 'litespeed-cache'); Admin_Display::success($msg); } } /** * Load an instance or create it if not existed * @since 4.0 */ public static function cls($cls = false, $unset = false, $data = false) { if (!$cls) { $cls = self::ori_cls(); } $cls = __NAMESPACE__ . '\\' . $cls; $cls_tag = strtolower($cls); if (!isset(self::$_instances[$cls_tag])) { if ($unset) { return; } self::$_instances[$cls_tag] = new $cls($data); } else { if ($unset) { unset(self::$_instances[$cls_tag]); return; } } return self::$_instances[$cls_tag]; } /** * Set one conf or confs */ public function set_conf($id, $val = null) { if (is_array($id)) { foreach ($id as $k => $v) { $this->set_conf($k, $v); } return; } self::$_options[$id] = $val; } /** * Set one primary conf or confs */ public function set_primary_conf($id, $val = null) { if (is_array($id)) { foreach ($id as $k => $v) { $this->set_primary_conf($k, $v); } return; } self::$_primary_options[$id] = $val; } /** * Set one network conf */ public function set_network_conf($id, $val = null) { if (is_array($id)) { foreach ($id as $k => $v) { $this->set_network_conf($k, $v); } return; } self::$_network_options[$id] = $val; } /** * Set one const conf */ public function set_const_conf($id, $val) { self::$_const_options[$id] = $val; } /** * Check if is overwritten by const * * @since 3.0 */ public function const_overwritten($id) { if (!isset(self::$_const_options[$id]) || self::$_const_options[$id] == self::$_options[$id]) { return null; } return self::$_const_options[$id]; } /** * Check if is overwritten by primary site * * @since 3.2.2 */ public function primary_overwritten($id) { if (!isset(self::$_primary_options[$id]) || self::$_primary_options[$id] == self::$_options[$id]) { return null; } // Network admin settings is impossible to be overwritten by primary if (is_network_admin()) { return null; } return self::$_primary_options[$id]; } /** * Get the list of configured options for the blog. * * @since 1.0 */ public function get_options($ori = false) { if (!$ori) { return array_merge(self::$_options, self::$_primary_options, self::$_network_options, self::$_const_options); } return self::$_options; } /** * If has a conf or not */ public function has_conf($id) { return array_key_exists($id, self::$_options); } /** * If has a primary conf or not */ public function has_primary_conf($id) { return array_key_exists($id, self::$_primary_options); } /** * If has a network conf or not */ public function has_network_conf($id) { return array_key_exists($id, self::$_network_options); } /** * Get conf */ public function conf($id, $ori = false) { if (isset(self::$_options[$id])) { if (!$ori) { $val = $this->const_overwritten($id); if ($val !== null) { defined('LSCWP_LOG') && Debug2::debug('[Conf] 🏛️ const option ' . $id . '=' . var_export($val, true)); return $val; } $val = $this->primary_overwritten($id); // Network Use primary site settings if ($val !== null) { return $val; } } // Network original value will be in _network_options if (!is_network_admin() || !$this->has_network_conf($id)) { return self::$_options[$id]; } } if ($this->has_network_conf($id)) { if (!$ori) { $val = $this->const_overwritten($id); if ($val !== null) { defined('LSCWP_LOG') && Debug2::debug('[Conf] 🏛️ const option ' . $id . '=' . var_export($val, true)); return $val; } } return $this->network_conf($id); } defined('LSCWP_LOG') && Debug2::debug('[Conf] Invalid option ID ' . $id); return null; } /** * Get primary conf */ public function primary_conf($id) { return self::$_primary_options[$id]; } /** * Get network conf */ public function network_conf($id) { if (!$this->has_network_conf($id)) { return null; } return self::$_network_options[$id]; } /** * Get called class short name */ public static function ori_cls() { $cls = new \ReflectionClass(get_called_class()); $shortname = $cls->getShortName(); $namespace = str_replace(__NAMESPACE__ . '\\', '', $cls->getNamespaceName() . '\\'); if ($namespace) { // the left namespace after dropped LiteSpeed $shortname = $namespace . $shortname; } return $shortname; } /** * Generate conf name for wp_options record * * @since 3.0 */ public static function name($id) { $name = strtolower(self::ori_cls()); if ($name == 'conf2') { // For a certain 3.7rc correction, can be dropped after v4 $name = 'conf'; } return 'litespeed.' . $name . '.' . $id; } /** * Dropin with prefix for WP's get_option * * @since 3.0 */ public static function get_option($id, $default_v = false) { $v = get_option(self::name($id), $default_v); // Maybe decode array if (is_array($default_v)) { $v = self::_maybe_decode($v); } return $v; } /** * Dropin with prefix for WP's get_site_option * * @since 3.0 */ public static function get_site_option($id, $default_v = false) { $v = get_site_option(self::name($id), $default_v); // Maybe decode array if (is_array($default_v)) { $v = self::_maybe_decode($v); } return $v; } /** * Dropin with prefix for WP's get_blog_option * * @since 3.0 */ public static function get_blog_option($blog_id, $id, $default_v = false) { $v = get_blog_option($blog_id, self::name($id), $default_v); // Maybe decode array if (is_array($default_v)) { $v = self::_maybe_decode($v); } return $v; } /** * Dropin with prefix for WP's add_option * * @since 3.0 */ public static function add_option($id, $v) { add_option(self::name($id), self::_maybe_encode($v)); } /** * Dropin with prefix for WP's add_site_option * * @since 3.0 */ public static function add_site_option($id, $v) { add_site_option(self::name($id), self::_maybe_encode($v)); } /** * Dropin with prefix for WP's update_option * * @since 3.0 */ public static function update_option($id, $v) { update_option(self::name($id), self::_maybe_encode($v)); } /** * Dropin with prefix for WP's update_site_option * * @since 3.0 */ public static function update_site_option($id, $v) { update_site_option(self::name($id), self::_maybe_encode($v)); } /** * Decode an array * * @since 4.0 */ private static function _maybe_decode($v) { if (!is_array($v)) { $v2 = \json_decode($v, true); if ($v2 !== null) { $v = $v2; } } return $v; } /** * Encode an array * * @since 4.0 */ private static function _maybe_encode($v) { if (is_array($v)) { $v = \json_encode($v) ?: $v; // Non utf-8 encoded value will get failed, then used ori value } return $v; } /** * Dropin with prefix for WP's delete_option * * @since 3.0 */ public static function delete_option($id) { delete_option(self::name($id)); } /** * Dropin with prefix for WP's delete_site_option * * @since 3.0 */ public static function delete_site_option($id) { delete_site_option(self::name($id)); } /** * Read summary * * @since 3.0 * @access public */ public static function get_summary($field = false) { $summary = self::get_option('_summary', array()); if (!is_array($summary)) { $summary = array(); } if (!$field) { return $summary; } if (array_key_exists($field, $summary)) { return $summary[$field]; } return null; } /** * Save summary * * @since 3.0 * @access public */ public static function save_summary($data = false, $reload = false, $overwrite = false) { if ($reload || empty(static::cls()->_summary)) { self::reload_summary(); } $existing_summary = static::cls()->_summary; if ($overwrite || !is_array($existing_summary)) { $existing_summary = array(); } $new_summary = array_merge($existing_summary, $data ?: array()); // self::debug2('Save after Reloaded summary', $new_summary); static::cls()->_summary = $new_summary; self::update_option('_summary', $new_summary); } /** * Reload summary * @since 5.0 */ public static function reload_summary() { static::cls()->_summary = self::get_summary(); // self::debug2( 'Reloaded summary', static::cls()->_summary ); } /** * Get the current instance object. To be inherited. * * @since 3.0 */ public static function get_instance() { return static::cls(); } } src/ucss.cls.php 0000644 00000034261 15246276230 0007616 0 ustar 00 <?php /** * The ucss class. * * @since 5.1 */ namespace LiteSpeed; defined('WPINC') || exit(); class UCSS extends Base { const LOG_TAG = '[UCSS]'; const TYPE_GEN = 'gen'; const TYPE_CLEAR_Q = 'clear_q'; protected $_summary; private $_ucss_whitelist; private $_queue; /** * Init * * @since 3.0 */ public function __construct() { $this->_summary = self::get_summary(); add_filter('litespeed_ucss_whitelist', array($this->cls('Data'), 'load_ucss_whitelist')); } /** * Uniform url tag for ucss usage * @since 4.7 */ public static function get_url_tag($request_url = false) { $url_tag = $request_url; if (is_404()) { $url_tag = '404'; } elseif (apply_filters('litespeed_ucss_per_pagetype', false)) { $url_tag = Utility::page_type(); self::debug('litespeed_ucss_per_pagetype filter altered url to ' . $url_tag); } return $url_tag; } /** * Get UCSS path * * @since 4.0 */ public function load($request_url, $dry_run = false) { // Check UCSS URI excludes $ucss_exc = apply_filters('litespeed_ucss_exc', $this->conf(self::O_OPTM_UCSS_EXC)); if ($ucss_exc && ($hit = Utility::str_hit_array($request_url, $ucss_exc))) { self::debug('UCSS bypassed due to UCSS URI Exclude setting: ' . $hit); Core::comment('QUIC.cloud UCSS bypassed by setting'); return false; } $filepath_prefix = $this->_build_filepath_prefix('ucss'); $url_tag = self::get_url_tag($request_url); $vary = $this->cls('Vary')->finalize_full_varies(); $filename = $this->cls('Data')->load_url_file($url_tag, $vary, 'ucss'); if ($filename) { $static_file = LITESPEED_STATIC_DIR . $filepath_prefix . $filename . '.css'; if (file_exists($static_file)) { self::debug2('existing ucss ' . $static_file); // Check if is error comment inside only $tmp = File::read($static_file); if (substr($tmp, 0, 2) == '/*' && substr(trim($tmp), -2) == '*/') { self::debug2('existing ucss is error only: ' . $tmp); Core::comment('QUIC.cloud UCSS bypassed due to generation error ❌ ' . $filepath_prefix . $filename . '.css'); return false; } Core::comment('QUIC.cloud UCSS loaded ✅'); return $filename . '.css'; } } if ($dry_run) { return false; } Core::comment('QUIC.cloud UCSS in queue'); $uid = get_current_user_id(); $ua = $this->_get_ua(); // Store it for cron $this->_queue = $this->load_queue('ucss'); if (count($this->_queue) > 500) { self::debug('UCSS Queue is full - 500'); return false; } $queue_k = (strlen($vary) > 32 ? md5($vary) : $vary) . ' ' . $url_tag; $this->_queue[$queue_k] = array( 'url' => apply_filters('litespeed_ucss_url', $request_url), 'user_agent' => substr($ua, 0, 200), 'is_mobile' => $this->_separate_mobile(), 'is_webp' => $this->cls('Media')->webp_support() ? 1 : 0, 'uid' => $uid, 'vary' => $vary, 'url_tag' => $url_tag, ); // Current UA will be used to request $this->save_queue('ucss', $this->_queue); self::debug('Added queue_ucss [url_tag] ' . $url_tag . ' [UA] ' . $ua . ' [vary] ' . $vary . ' [uid] ' . $uid); // Prepare cache tag for later purge Tag::add('UCSS.' . md5($queue_k)); return false; } /** * Get User Agent * * @since 5.3 */ private function _get_ua() { return !empty($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : ''; } /** * Add rows to q * * @since 5.3 */ public function add_to_q($url_files) { // Store it for cron $this->_queue = $this->load_queue('ucss'); if (count($this->_queue) > 500) { self::debug('UCSS Queue is full - 500'); return false; } $ua = $this->_get_ua(); foreach ($url_files as $url_file) { $vary = $url_file['vary']; $request_url = $url_file['url']; $is_mobile = $url_file['mobile']; $is_webp = $url_file['webp']; $url_tag = self::get_url_tag($request_url); $queue_k = (strlen($vary) > 32 ? md5($vary) : $vary) . ' ' . $url_tag; $q = array( 'url' => apply_filters('litespeed_ucss_url', $request_url), 'user_agent' => substr($ua, 0, 200), 'is_mobile' => $is_mobile, 'is_webp' => $is_webp, 'uid' => false, 'vary' => $vary, 'url_tag' => $url_tag, ); // Current UA will be used to request self::debug('Added queue_ucss [url_tag] ' . $url_tag . ' [UA] ' . $ua . ' [vary] ' . $vary . ' [uid] false'); $this->_queue[$queue_k] = $q; } $this->save_queue('ucss', $this->_queue); } /** * Generate UCSS * * @since 4.0 */ public static function cron($continue = false) { $_instance = self::cls(); return $_instance->_cron_handler($continue); } /** * Handle UCSS cron * * @since 4.2 */ private function _cron_handler($continue) { $this->_queue = $this->load_queue('ucss'); if (empty($this->_queue)) { return; } // For cron, need to check request interval too if (!$continue) { if (!empty($this->_summary['curr_request']) && time() - $this->_summary['curr_request'] < 300 && !$this->conf(self::O_DEBUG)) { self::debug('Last request not done'); return; } } $i = 0; foreach ($this->_queue as $k => $v) { if (!empty($v['_status'])) { continue; } self::debug('cron job [tag] ' . $k . ' [url] ' . $v['url'] . ($v['is_mobile'] ? ' 📱 ' : '') . ' [UA] ' . $v['user_agent']); if (!isset($v['is_webp'])) { $v['is_webp'] = false; } $i++; $res = $this->_send_req($v['url'], $k, $v['uid'], $v['user_agent'], $v['vary'], $v['url_tag'], $v['is_mobile'], $v['is_webp']); if (!$res) { // Status is wrong, drop this this->_queue $this->_queue = $this->load_queue('ucss'); unset($this->_queue[$k]); $this->save_queue('ucss', $this->_queue); if (!$continue) { return; } if ($i > 3) { GUI::print_loading(count($this->_queue), 'UCSS'); return Router::self_redirect(Router::ACTION_UCSS, self::TYPE_GEN); } continue; } // Exit queue if out of quota or service is hot if ($res === 'out_of_quota' || $res === 'svc_hot') { return; } $this->_queue = $this->load_queue('ucss'); $this->_queue[$k]['_status'] = 'requested'; $this->save_queue('ucss', $this->_queue); self::debug('Saved to queue [k] ' . $k); // only request first one if (!$continue) { return; } if ($i > 3) { GUI::print_loading(count($this->_queue), 'UCSS'); return Router::self_redirect(Router::ACTION_UCSS, self::TYPE_GEN); } } } /** * Send to QC API to generate UCSS * * @since 2.3 * @access private */ private function _send_req($request_url, $queue_k, $uid, $user_agent, $vary, $url_tag, $is_mobile, $is_webp) { // Check if has credit to push or not $err = false; $allowance = $this->cls('Cloud')->allowance(Cloud::SVC_UCSS, $err); if (!$allowance) { self::debug('❌ No credit: ' . $err); $err && Admin_Display::error(Error::msg($err)); return 'out_of_quota'; } set_time_limit(120); // Update css request status $this->_summary['curr_request'] = time(); self::save_summary(); // Gather guest HTML to send $html = $this->cls('CSS')->prepare_html($request_url, $user_agent, $uid); if (!$html) { return false; } // Parse HTML to gather all CSS content before requesting $css = false; list(, $html) = $this->prepare_css($html, $is_webp, true); // Use this to drop CSS from HTML as we don't need those CSS to generate UCSS $filename = $this->cls('Data')->load_url_file($url_tag, $vary, 'css'); $filepath_prefix = $this->_build_filepath_prefix('css'); $static_file = LITESPEED_STATIC_DIR . $filepath_prefix . $filename . '.css'; self::debug('Checking combined file ' . $static_file); if (file_exists($static_file)) { $css = File::read($static_file); } if (!$css) { self::debug('❌ No combined css'); return false; } $data = array( 'url' => $request_url, 'queue_k' => $queue_k, 'user_agent' => $user_agent, 'is_mobile' => $is_mobile ? 1 : 0, // todo:compatible w/ tablet 'is_webp' => $is_webp ? 1 : 0, 'html' => $html, 'css' => $css, ); if (!isset($this->_ucss_whitelist)) { $this->_ucss_whitelist = $this->_filter_whitelist(); } $data['whitelist'] = $this->_ucss_whitelist; self::debug('Generating: ', $data); $json = Cloud::post(Cloud::SVC_UCSS, $data, 30); if (!is_array($json)) { return $json; } // Old version compatibility if (empty($json['status'])) { if (!empty($json['ucss'])) { $this->_save_con('ucss', $json['ucss'], $queue_k, $is_mobile, $is_webp); } // Delete the row return false; } // Unknown status, remove this line if ($json['status'] != 'queued') { return false; } // Save summary data $this->_summary['last_spent'] = time() - $this->_summary['curr_request']; $this->_summary['last_request'] = $this->_summary['curr_request']; $this->_summary['curr_request'] = 0; self::save_summary(); return true; } /** * Save UCSS content * * @since 4.2 */ private function _save_con($type, $css, $queue_k, $is_mobile, $is_webp) { // Add filters $css = apply_filters('litespeed_' . $type, $css, $queue_k); self::debug2('con: ', $css); if (substr($css, 0, 2) == '/*' && substr($css, -2) == '*/') { self::debug('❌ empty ' . $type . ' [content] ' . $css); // continue; // Save the error info too } // Write to file $filecon_md5 = md5($css); $filepath_prefix = $this->_build_filepath_prefix($type); $static_file = LITESPEED_STATIC_DIR . $filepath_prefix . $filecon_md5 . '.css'; File::save($static_file, $css, true); $url_tag = $this->_queue[$queue_k]['url_tag']; $vary = $this->_queue[$queue_k]['vary']; self::debug2("Save URL to file [file] $static_file [vary] $vary"); $this->cls('Data')->save_url($url_tag, $vary, $type, $filecon_md5, dirname($static_file), $is_mobile, $is_webp); Purge::add(strtoupper($type) . '.' . md5($queue_k)); } /** * Prepare CSS from HTML for CCSS generation only. UCSS will used combined CSS directly. * Prepare refined HTML for both CCSS and UCSS. * * @since 3.4.3 */ public function prepare_css($html, $is_webp = false, $dryrun = false) { $css = ''; preg_match_all('#<link ([^>]+)/?>|<style([^>]*)>([^<]+)</style>#isU', $html, $matches, PREG_SET_ORDER); foreach ($matches as $match) { $debug_info = ''; if (strpos($match[0], '<link') === 0) { $attrs = Utility::parse_attr($match[1]); if (empty($attrs['rel'])) { continue; } if ($attrs['rel'] != 'stylesheet') { if ($attrs['rel'] != 'preload' || empty($attrs['as']) || $attrs['as'] != 'style') { continue; } } if (!empty($attrs['media']) && strpos($attrs['media'], 'print') !== false) { continue; } if (empty($attrs['href'])) { continue; } // Check Google fonts hit if (strpos($attrs['href'], 'fonts.googleapis.com') !== false) { $html = str_replace($match[0], '', $html); continue; } $debug_info = $attrs['href']; // Load CSS content if (!$dryrun) { // Dryrun will not load CSS but just drop them $con = $this->cls('Optimizer')->load_file($attrs['href']); if (!$con) { continue; } } else { $con = ''; } } else { // Inline style $attrs = Utility::parse_attr($match[2]); if (!empty($attrs['media']) && strpos($attrs['media'], 'print') !== false) { continue; } Debug2::debug2('[CSS] Load inline CSS ' . substr($match[3], 0, 100) . '...', $attrs); $con = $match[3]; $debug_info = '__INLINE__'; } $con = Optimizer::minify_css($con); if ($is_webp && $this->cls('Media')->webp_support()) { $con = $this->cls('Media')->replace_background_webp($con); } if (!empty($attrs['media']) && $attrs['media'] !== 'all') { $con = '@media ' . $attrs['media'] . '{' . $con . "}\n"; } else { $con = $con . "\n"; } $con = '/* ' . $debug_info . ' */' . $con; $css .= $con; $html = str_replace($match[0], '', $html); } return array($css, $html); } /** * Filter the comment content, add quotes to selector from whitelist. Return the json * * @since 3.3 */ private function _filter_whitelist() { $whitelist = array(); $list = apply_filters('litespeed_ucss_whitelist', $this->conf(self::O_OPTM_UCSS_SELECTOR_WHITELIST)); foreach ($list as $k => $v) { if (substr($v, 0, 2) === '//') { continue; } // Wrap in quotes for selectors if (substr($v, 0, 1) !== '/' && strpos($v, '"') === false && strpos($v, "'") === false) { // $v = "'$v'"; } $whitelist[] = $v; } return $whitelist; } /** * Notify finished from server * @since 5.1 */ public function notify() { $post_data = \json_decode(file_get_contents('php://input'), true); if (is_null($post_data)) { $post_data = $_POST; } self::debug('notify() data', $post_data); $this->_queue = $this->load_queue('ucss'); list($post_data) = $this->cls('Cloud')->extract_msg($post_data, 'ucss'); $notified_data = $post_data['data']; if (empty($notified_data) || !is_array($notified_data)) { self::debug('❌ notify exit: no notified data'); return Cloud::err('no notified data'); } // Check if its in queue or not $valid_i = 0; foreach ($notified_data as $v) { if (empty($v['request_url'])) { self::debug('❌ notify bypass: no request_url', $v); continue; } if (empty($v['queue_k'])) { self::debug('❌ notify bypass: no queue_k', $v); continue; } if (empty($this->_queue[$v['queue_k']])) { self::debug('❌ notify bypass: no this queue [q_k]' . $v['queue_k']); continue; } // Save data if (!empty($v['data_ucss'])) { $is_mobile = $this->_queue[$v['queue_k']]['is_mobile']; $is_webp = $this->_queue[$v['queue_k']]['is_webp']; $this->_save_con('ucss', $v['data_ucss'], $v['queue_k'], $is_mobile, $is_webp); $valid_i++; } unset($this->_queue[$v['queue_k']]); self::debug('notify data handled, unset queue [q_k] ' . $v['queue_k']); } $this->save_queue('ucss', $this->_queue); self::debug('notified'); return Cloud::ok(array('count' => $valid_i)); } /** * Handle all request actions from main cls * * @since 2.3 * @access public */ public function handler() { $type = Router::verify_type(); switch ($type) { case self::TYPE_GEN: self::cron(true); break; case self::TYPE_CLEAR_Q: $this->clear_q('ucss'); break; default: break; } Admin::redirect(); } } src/data.cls.php 0000644 00000043164 15246276230 0007554 0 ustar 00 <?php /** * The class to store and manage litespeed db data. * * @since 1.3.1 * @package LiteSpeed * @subpackage LiteSpeed/src * @author LiteSpeed Technologies <info@litespeedtech.com> */ namespace LiteSpeed; defined('WPINC') || exit(); class Data extends Root { const LOG_TAG = '🚀'; private $_db_updater = array( '3.5.0.3' => array('litespeed_update_3_5'), '4.0' => array('litespeed_update_4'), '4.1' => array('litespeed_update_4_1'), '4.3' => array('litespeed_update_4_3'), '4.4.4-b1' => array('litespeed_update_4_4_4'), '5.3-a5' => array('litespeed_update_5_3'), '7.0-b26' => array('litespeed_update_7'), '7.0.1-b1' => array('litespeed_update_7_0_1'), ); private $_db_site_updater = array( // Example // '2.0' => array( // 'litespeed_update_site_2_0', // ), ); private $_url_file_types = array( 'css' => 1, 'js' => 2, 'ccss' => 3, 'ucss' => 4, ); const TB_IMG_OPTM = 'litespeed_img_optm'; const TB_IMG_OPTMING = 'litespeed_img_optming'; // working table const TB_AVATAR = 'litespeed_avatar'; const TB_CRAWLER = 'litespeed_crawler'; const TB_CRAWLER_BLACKLIST = 'litespeed_crawler_blacklist'; const TB_URL = 'litespeed_url'; const TB_URL_FILE = 'litespeed_url_file'; /** * Init * * @since 1.3.1 */ public function __construct() { } /** * Correct table existence * * Call when activate -> update_confs() * Call when update_confs() * * @since 3.0 * @access public */ public function correct_tb_existence() { // Gravatar if ($this->conf(Base::O_DISCUSS_AVATAR_CACHE)) { $this->tb_create('avatar'); } // Crawler if ($this->conf(Base::O_CRAWLER)) { $this->tb_create('crawler'); $this->tb_create('crawler_blacklist'); } // URL mapping $this->tb_create('url'); $this->tb_create('url_file'); // Image optm is a bit different. Only trigger creation when sending requests. Drop when destroying. } /** * Upgrade conf to latest format version from previous versions * * NOTE: Only for v3.0+ * * @since 3.0 * @access public */ public function conf_upgrade($ver) { // Skip count check if `Use Primary Site Configurations` is on // Deprecated since v3.0 as network primary site didn't override the subsites conf yet // if ( ! is_main_site() && ! empty ( $this->_site_options[ self::NETWORK_O_USE_PRIMARY ] ) ) { // return; // } if ($this->_get_upgrade_lock()) { return; } $this->_set_upgrade_lock(true); require_once LSCWP_DIR . 'src/data.upgrade.func.php'; // Init log manually if ($this->conf(Base::O_DEBUG)) { $this->cls('Debug2')->init(); } foreach ($this->_db_updater as $k => $v) { if (version_compare($ver, $k, '<')) { // run each callback foreach ($v as $v2) { self::debug("Updating [ori_v] $ver \t[to] $k \t[func] $v2"); call_user_func($v2); } } } // Reload options $this->cls('Conf')->load_options(); $this->correct_tb_existence(); // Update related files $this->cls('Activation')->update_files(); // Update version to latest Conf::delete_option(Base::_VER); Conf::add_option(Base::_VER, Core::VER); self::debug('Updated version to ' . Core::VER); $this->_set_upgrade_lock(false); !defined('LSWCP_EMPTYCACHE') && define('LSWCP_EMPTYCACHE', true); // clear all sites caches Purge::purge_all(); return 'upgrade'; } /** * Upgrade site conf to latest format version from previous versions * * NOTE: Only for v3.0+ * * @since 3.0 * @access public */ public function conf_site_upgrade($ver) { if ($this->_get_upgrade_lock()) { return; } $this->_set_upgrade_lock(true); require_once LSCWP_DIR . 'src/data.upgrade.func.php'; foreach ($this->_db_site_updater as $k => $v) { if (version_compare($ver, $k, '<')) { // run each callback foreach ($v as $v2) { self::debug("Updating site [ori_v] $ver \t[to] $k \t[func] $v2"); call_user_func($v2); } } } // Reload options $this->cls('Conf')->load_site_options(); Conf::delete_site_option(Base::_VER); Conf::add_site_option(Base::_VER, Core::VER); self::debug('Updated site_version to ' . Core::VER); $this->_set_upgrade_lock(false); !defined('LSWCP_EMPTYCACHE') && define('LSWCP_EMPTYCACHE', true); // clear all sites caches Purge::purge_all(); } /** * Check if upgrade script is running or not * * @since 3.0.1 */ private function _get_upgrade_lock() { $is_upgrading = get_option('litespeed.data.upgrading'); if (!$is_upgrading) { $this->_set_upgrade_lock(false); // set option value to existed to avoid repeated db query next time } if ($is_upgrading && time() - $is_upgrading < 3600) { return $is_upgrading; } return false; } /** * Show the upgrading banner if upgrade script is running * * @since 3.0.1 */ public function check_upgrading_msg() { $is_upgrading = $this->_get_upgrade_lock(); if (!$is_upgrading) { return; } Admin_Display::info( sprintf( __('The database has been upgrading in the background since %s. This message will disappear once upgrade is complete.', 'litespeed-cache'), '<code>' . Utility::readable_time($is_upgrading) . '</code>' ) . ' [LiteSpeed]', true ); } /** * Set lock for upgrade process * * @since 3.0.1 */ private function _set_upgrade_lock($lock) { if (!$lock) { update_option('litespeed.data.upgrading', -1); } else { update_option('litespeed.data.upgrading', time()); } } /** * Upgrade the conf to v3.0 from previous v3.0- data * * NOTE: Only for v3.0- * * @since 3.0 * @access public */ public function try_upgrade_conf_3_0() { $previous_options = get_option('litespeed-cache-conf'); if (!$previous_options) { return 'new'; } $ver = $previous_options['version']; !defined('LSCWP_CUR_V') && define('LSCWP_CUR_V', $ver); // Init log manually if ($this->conf(Base::O_DEBUG)) { $this->cls('Debug2')->init(); } self::debug('Upgrading previous settings [from] ' . $ver . ' [to] v3.0'); if ($this->_get_upgrade_lock()) { return; } $this->_set_upgrade_lock(true); require_once LSCWP_DIR . 'src/data.upgrade.func.php'; // Here inside will update the version to v3.0 litespeed_update_3_0($ver); $this->_set_upgrade_lock(false); self::debug('Upgraded to v3.0'); // Upgrade from 3.0 to latest version $ver = '3.0'; if (Core::VER != $ver) { return $this->conf_upgrade($ver); } else { // Reload options $this->cls('Conf')->load_options(); $this->correct_tb_existence(); !defined('LSWCP_EMPTYCACHE') && define('LSWCP_EMPTYCACHE', true); // clear all sites caches Purge::purge_all(); return 'upgrade'; } } /** * Get the table name * * @since 3.0 * @access public */ public function tb($tb) { global $wpdb; switch ($tb) { case 'img_optm': return $wpdb->prefix . self::TB_IMG_OPTM; break; case 'img_optming': return $wpdb->prefix . self::TB_IMG_OPTMING; break; case 'avatar': return $wpdb->prefix . self::TB_AVATAR; break; case 'crawler': return $wpdb->prefix . self::TB_CRAWLER; break; case 'crawler_blacklist': return $wpdb->prefix . self::TB_CRAWLER_BLACKLIST; break; case 'url': return $wpdb->prefix . self::TB_URL; break; case 'url_file': return $wpdb->prefix . self::TB_URL_FILE; break; default: break; } } /** * Check if one table exists or not * * @since 3.0 * @access public */ public function tb_exist($tb) { global $wpdb; return $wpdb->get_var("SHOW TABLES LIKE '" . $this->tb($tb) . "'"); } /** * Get data structure of one table * * @since 2.0 * @access private */ private function _tb_structure($tb) { return File::read(LSCWP_DIR . 'src/data_structure/' . $tb . '.sql'); } /** * Create img optm table and sync data from wp_postmeta * * @since 3.0 * @access public */ public function tb_create($tb) { global $wpdb; self::debug2('[Data] Checking table ' . $tb); // Check if table exists first if ($this->tb_exist($tb)) { self::debug2('[Data] Existed'); return; } self::debug('Creating ' . $tb); $sql = sprintf( 'CREATE TABLE IF NOT EXISTS `%1$s` (' . $this->_tb_structure($tb) . ') %2$s;', $this->tb($tb), $wpdb->get_charset_collate() // 'DEFAULT CHARSET=utf8' ); $res = $wpdb->query($sql); if ($res !== true) { self::debug('Warning! Creating table failed!', $sql); Admin_Display::error(Error::msg('failed_tb_creation', array('<code>' . $tb . '</code>', '<code>' . $sql . '</code>'))); } } /** * Drop table * * @since 3.0 * @access public */ public function tb_del($tb) { global $wpdb; if (!$this->tb_exist($tb)) { return; } self::debug('Deleting table ' . $tb); $q = 'DROP TABLE IF EXISTS ' . $this->tb($tb); $wpdb->query($q); } /** * Drop generated tables * * @since 3.0 * @access public */ public function tables_del() { $this->tb_del('avatar'); $this->tb_del('crawler'); $this->tb_del('crawler_blacklist'); $this->tb_del('url'); $this->tb_del('url_file'); // Deleting img_optm only can be done when destroy all optm images } /** * Keep table but clear all data * * @since 4.0 */ public function table_truncate($tb) { global $wpdb; $q = 'TRUNCATE TABLE ' . $this->tb($tb); $wpdb->query($q); } /** * Clean certain type of url_file * * @since 4.0 */ public function url_file_clean($file_type) { global $wpdb; if (!$this->tb_exist('url_file')) { return; } $type = $this->_url_file_types[$file_type]; $q = 'DELETE FROM ' . $this->tb('url_file') . ' WHERE `type` = %d'; $wpdb->query($wpdb->prepare($q, $type)); // Added to cleanup url table. See issue: https://wordpress.org/support/topic/wp_litespeed_url-1-1-gb-in-db-huge-big/ $wpdb->query( 'DELETE d FROM `' . $this->tb('url') . '` AS d LEFT JOIN `' . $this->tb('url_file') . '` AS f ON d.`id` = f.`url_id` WHERE f.`url_id` IS NULL' ); } /** * Generate filename based on URL, if content md5 existed, reuse existing file. * @since 4.0 */ public function save_url($request_url, $vary, $file_type, $filecon_md5, $path, $mobile = false, $webp = false) { global $wpdb; if (strlen($vary) > 32) { $vary = md5($vary); } $type = $this->_url_file_types[$file_type]; $tb_url = $this->tb('url'); $tb_url_file = $this->tb('url_file'); $q = "SELECT * FROM `$tb_url` WHERE url=%s"; $url_row = $wpdb->get_row($wpdb->prepare($q, $request_url), ARRAY_A); if (!$url_row) { $q = "INSERT INTO `$tb_url` SET url=%s"; $wpdb->query($wpdb->prepare($q, $request_url)); $url_id = $wpdb->insert_id; } else { $url_id = $url_row['id']; } $q = "SELECT * FROM `$tb_url_file` WHERE url_id=%d AND vary=%s AND type=%d AND expired=0"; $file_row = $wpdb->get_row($wpdb->prepare($q, array($url_id, $vary, $type)), ARRAY_A); // Check if has previous file or not if ($file_row && $file_row['filename'] == $filecon_md5) { return; } // If the new $filecon_md5 is marked as expired by previous records, clear those records $q = "DELETE FROM `$tb_url_file` WHERE filename = %s AND expired > 0"; $wpdb->query($wpdb->prepare($q, $filecon_md5)); // Check if there is any other record used the same filename or not $q = "SELECT id FROM `$tb_url_file` WHERE filename = %s AND expired = 0 AND id != %d LIMIT 1"; if ($file_row && $wpdb->get_var($wpdb->prepare($q, array($file_row['filename'], $file_row['id'])))) { $q = "UPDATE `$tb_url_file` SET filename=%s WHERE id=%d"; $wpdb->query($wpdb->prepare($q, array($filecon_md5, $file_row['id']))); return; } // New record needed $q = "INSERT INTO `$tb_url_file` SET url_id=%d, vary=%s, filename=%s, type=%d, mobile=%d, webp=%d, expired=0"; $wpdb->query($wpdb->prepare($q, array($url_id, $vary, $filecon_md5, $type, $mobile ? 1 : 0, $webp ? 1 : 0))); // Mark existing rows as expired if ($file_row) { $q = "UPDATE `$tb_url_file` SET expired=%d WHERE id=%d"; $expired = time() + 86400 * apply_filters('litespeed_url_file_expired_days', 20); $wpdb->query($wpdb->prepare($q, array($expired, $file_row['id']))); // Also check if has other files expired already to be deleted $q = "SELECT * FROM `$tb_url_file` WHERE url_id = %d AND expired BETWEEN 1 AND %d"; $q = $wpdb->prepare($q, array($url_id, time())); $list = $wpdb->get_results($q, ARRAY_A); if ($list) { foreach ($list as $v) { $file_to_del = $path . '/' . $v['filename'] . '.' . ($file_type == 'js' ? 'js' : 'css'); if (file_exists($file_to_del)) { // Safe to delete self::debug('Delete expired unused file: ' . $file_to_del); // Clear related lscache first to avoid cache copy of same URL w/ diff QS // Purge::add( Tag::TYPE_MIN . '.' . $file_row[ 'filename' ] . '.' . $file_type ); unlink($file_to_del); } } $q = "DELETE FROM `$tb_url_file` WHERE url_id = %d AND expired BETWEEN 1 AND %d"; $wpdb->query($wpdb->prepare($q, array($url_id, time()))); } } // Purge this URL to avoid cache copy of same URL w/ diff QS // $this->cls( 'Purge' )->purge_url( Utility::make_relative( $request_url ) ?: '/', true, true ); } /** * Load CCSS related file * @since 4.0 */ public function load_url_file($request_url, $vary, $file_type) { global $wpdb; if (strlen($vary) > 32) { $vary = md5($vary); } $type = $this->_url_file_types[$file_type]; self::debug2('load url file: ' . $request_url); $tb_url = $this->tb('url'); $q = "SELECT * FROM `$tb_url` WHERE url=%s"; $url_row = $wpdb->get_row($wpdb->prepare($q, $request_url), ARRAY_A); if (!$url_row) { return false; } $url_id = $url_row['id']; $tb_url_file = $this->tb('url_file'); $q = "SELECT * FROM `$tb_url_file` WHERE url_id=%d AND vary=%s AND type=%d AND expired=0"; $file_row = $wpdb->get_row($wpdb->prepare($q, array($url_id, $vary, $type)), ARRAY_A); if (!$file_row) { return false; } return $file_row['filename']; } /** * Mark all entries of one URL to expired * @since 4.5 */ public function mark_as_expired($request_url, $auto_q = false) { global $wpdb; $tb_url = $this->tb('url'); self::debug('Try to mark as expired: ' . $request_url); $q = "SELECT * FROM `$tb_url` WHERE url=%s"; $url_row = $wpdb->get_row($wpdb->prepare($q, $request_url), ARRAY_A); if (!$url_row) { return; } self::debug('Mark url_id=' . $url_row['id'] . ' as expired'); $tb_url_file = $this->tb('url_file'); $existing_url_files = array(); if ($auto_q) { $q = "SELECT a.*, b.url FROM `$tb_url_file` a LEFT JOIN `$tb_url` b ON b.id=a.url_id WHERE a.url_id=%d AND a.type=4 AND a.expired=0"; $q = $wpdb->prepare($q, $url_row['id']); $existing_url_files = $wpdb->get_results($q, ARRAY_A); } $q = "UPDATE `$tb_url_file` SET expired=%d WHERE url_id=%d AND type=4 AND expired=0"; $expired = time() + 86400 * apply_filters('litespeed_url_file_expired_days', 20); $wpdb->query($wpdb->prepare($q, array($expired, $url_row['id']))); return $existing_url_files; } /** * Get list from `data/css_excludes.txt` * * @since 3.6 */ public function load_css_exc($list) { $data = $this->_load_per_line('css_excludes.txt'); if ($data) { $list = array_unique(array_filter(array_merge($list, $data))); } return $list; } /** * Get list from `data/ccss_whitelist.txt` * * @since 7.1 */ public function load_ccss_whitelist($list) { $data = $this->_load_per_line('ccss_whitelist.txt'); if ($data) { $list = array_unique(array_filter(array_merge($list, $data))); } return $list; } /** * Get list from `data/ucss_whitelist.txt` * * @since 4.0 */ public function load_ucss_whitelist($list) { $data = $this->_load_per_line('ucss_whitelist.txt'); if ($data) { $list = array_unique(array_filter(array_merge($list, $data))); } return $list; } /** * Get list from `data/js_excludes.txt` * * @since 3.5 */ public function load_js_exc($list) { $data = $this->_load_per_line('js_excludes.txt'); if ($data) { $list = array_unique(array_filter(array_merge($list, $data))); } return $list; } /** * Get list from `data/js_defer_excludes.txt` * * @since 3.6 */ public function load_js_defer_exc($list) { $data = $this->_load_per_line('js_defer_excludes.txt'); if ($data) { $list = array_unique(array_filter(array_merge($list, $data))); } return $list; } /** * Get list from `data/optm_uri_exc.txt` * * @since 5.4 */ public function load_optm_uri_exc($list) { $data = $this->_load_per_line('optm_uri_exc.txt'); if ($data) { $list = array_unique(array_filter(array_merge($list, $data))); } return $list; } /** * Get list from `data/esi.nonces.txt` * * @since 3.5 */ public function load_esi_nonces($list) { $data = $this->_load_per_line('esi.nonces.txt'); if ($data) { $list = array_unique(array_filter(array_merge($list, $data))); } return $list; } /** * Get list from `data/cache_nocacheable.txt` * * @since 6.3.0.1 */ public function load_cache_nocacheable($list) { $data = $this->_load_per_line('cache_nocacheable.txt'); if ($data) { $list = array_unique(array_filter(array_merge($list, $data))); } return $list; } /** * Load file per line * * Support two kinds of comments: * 1. `# this is comment` * 2. `##this is comment` * * @since 3.5 */ private function _load_per_line($file) { $data = File::read(LSCWP_DIR . 'data/' . $file); $data = explode(PHP_EOL, $data); $list = array(); foreach ($data as $v) { // Drop two kinds of comments if (strpos($v, '##') !== false) { $v = trim(substr($v, 0, strpos($v, '##'))); } if (strpos($v, '# ') !== false) { $v = trim(substr($v, 0, strpos($v, '# '))); } if (!$v) { continue; } $list[] = $v; } return $list; } } src/admin-settings.cls.php 0000644 00000024032 15246276230 0011562 0 ustar 00 <?php /** * The admin settings handler of the plugin. * * * @since 1.1.0 * @package LiteSpeed * @subpackage LiteSpeed/src * @author LiteSpeed Technologies <info@litespeedtech.com> */ namespace LiteSpeed; defined('WPINC') || exit(); class Admin_Settings extends Base { const ENROLL = '_settings-enroll'; /** * Save settings * * Both $_POST and CLI can use this way * * Import will directly call conf.cls * * @since 3.0 * @access public */ public function save($raw_data) { Debug2::debug('[Settings] saving'); if (empty($raw_data[self::ENROLL])) { exit('No fields'); } $raw_data = Admin::cleanup_text($raw_data); // Convert data to config format $the_matrix = array(); foreach (array_unique($raw_data[self::ENROLL]) as $id) { $child = false; // Drop array format if (strpos($id, '[') !== false) { if (strpos($id, self::O_CDN_MAPPING) === 0 || strpos($id, self::O_CRAWLER_COOKIES) === 0) { // CDN child | Cookie Crawler settings $child = substr($id, strpos($id, '[') + 1, strpos($id, ']') - strpos($id, '[') - 1); $id = substr($id, 0, strpos($id, '[')); // Drop ending []; Compatible with xx[0] way from CLI } else { $id = substr($id, 0, strpos($id, '[')); // Drop ending [] } } if (!array_key_exists($id, self::$_default_options)) { continue; } // Validate $child if ($id == self::O_CDN_MAPPING) { if (!in_array($child, array(self::CDN_MAPPING_URL, self::CDN_MAPPING_INC_IMG, self::CDN_MAPPING_INC_CSS, self::CDN_MAPPING_INC_JS, self::CDN_MAPPING_FILETYPE))) { continue; } } if ($id == self::O_CRAWLER_COOKIES) { if (!in_array($child, array(self::CRWL_COOKIE_NAME, self::CRWL_COOKIE_VALS))) { continue; } } $data = false; if ($child) { $data = !empty($raw_data[$id][$child]) ? $raw_data[$id][$child] : false; // []=xxx or [0]=xxx } else { $data = !empty($raw_data[$id]) ? $raw_data[$id] : false; } /** * Sanitize the value */ if ($id == self::O_CDN_MAPPING || $id == self::O_CRAWLER_COOKIES) { // Use existing in queue data if existed (Only available when $child != false) $data2 = array_key_exists($id, $the_matrix) ? $the_matrix[$id] : (defined('WP_CLI') && WP_CLI ? $this->conf($id) : array()); } switch ($id) { case self::O_CRAWLER_ROLES: // Don't allow Editor/admin to be used in crawler role simulator $data = Utility::sanitize_lines($data); if ($data) { foreach ($data as $k => $v) { if (user_can($v, 'edit_posts')) { $msg = sprintf( __('The user with id %s has editor access, which is not allowed for the role simulator.', 'litespeed-cache'), '<code>' . $v . '</code>' ); Admin_Display::error($msg); unset($data[$k]); } } } break; case self::O_CDN_MAPPING: /** * CDN setting * * Raw data format: * cdn-mapping[url][] = 'xxx' * cdn-mapping[url][2] = 'xxx2' * cdn-mapping[inc_js][] = 1 * * Final format: * cdn-mapping[ 0 ][ url ] = 'xxx' * cdn-mapping[ 2 ][ url ] = 'xxx2' */ if ($data) { foreach ($data as $k => $v) { if ($child == self::CDN_MAPPING_FILETYPE) { $v = Utility::sanitize_lines($v); } if ($child == self::CDN_MAPPING_URL) { # If not a valid URL, turn off CDN if (strpos($v, 'https://') !== 0) { self::debug('❌ CDN mapping set to OFF due to invalid URL'); $the_matrix[self::O_CDN] = false; } $v = trailingslashit($v); } if (in_array($child, array(self::CDN_MAPPING_INC_IMG, self::CDN_MAPPING_INC_CSS, self::CDN_MAPPING_INC_JS))) { // Because these can't be auto detected in `config->update()`, need to format here $v = $v === 'false' ? 0 : (bool) $v; } if (empty($data2[$k])) { $data2[$k] = array(); } $data2[$k][$child] = $v; } } $data = $data2; break; case self::O_CRAWLER_COOKIES: /** * Cookie Crawler setting * Raw Format: * crawler-cookies[name][] = xxx * crawler-cookies[name][2] = xxx2 * crawler-cookies[vals][] = xxx * * todo: need to allow null for values * * Final format: * crawler-cookie[ 0 ][ name ] = 'xxx' * crawler-cookie[ 0 ][ vals ] = 'xxx' * crawler-cookie[ 2 ][ name ] = 'xxx2' * * empty line for `vals` use literal `_null` */ if ($data) { foreach ($data as $k => $v) { if ($child == self::CRWL_COOKIE_VALS) { $v = Utility::sanitize_lines($v); } if (empty($data2[$k])) { $data2[$k] = array(); } $data2[$k][$child] = $v; } } $data = $data2; break; case self::O_CACHE_EXC_CAT: // Cache exclude cat $data2 = array(); $data = Utility::sanitize_lines($data); foreach ($data as $v) { $cat_id = get_cat_ID($v); if (!$cat_id) { continue; } $data2[] = $cat_id; } $data = $data2; break; case self::O_CACHE_EXC_TAG: // Cache exclude tag $data2 = array(); $data = Utility::sanitize_lines($data); foreach ($data as $v) { $term = get_term_by('name', $v, 'post_tag'); if (!$term) { // todo: can show the error in admin error msg continue; } $data2[] = $term->term_id; } $data = $data2; break; default: break; } $the_matrix[$id] = $data; } // Special handler for CDN/Crawler 2d list to drop empty rows foreach ($the_matrix as $id => $data) { /** * cdn-mapping[ 0 ][ url ] = 'xxx' * cdn-mapping[ 2 ][ url ] = 'xxx2' * * crawler-cookie[ 0 ][ name ] = 'xxx' * crawler-cookie[ 0 ][ vals ] = 'xxx' * crawler-cookie[ 2 ][ name ] = 'xxx2' */ if ($id == self::O_CDN_MAPPING || $id == self::O_CRAWLER_COOKIES) { // Drop this line if all children elements are empty foreach ($data as $k => $v) { foreach ($v as $v2) { if ($v2) { continue 2; } } // If hit here, means all empty unset($the_matrix[$id][$k]); } } // Don't allow repeated cookie name if ($id == self::O_CRAWLER_COOKIES) { $existed = array(); foreach ($the_matrix[$id] as $k => $v) { if (!$v[self::CRWL_COOKIE_NAME] || in_array($v[self::CRWL_COOKIE_NAME], $existed)) { // Filter repeated or empty name unset($the_matrix[$id][$k]); continue; } $existed[] = $v[self::CRWL_COOKIE_NAME]; } } // CDN mapping allow URL values repeated // if ( $id == self::O_CDN_MAPPING ) {} // tmp fix the 3rd part woo update hook issue when enabling vary cookie if ($id == 'wc_cart_vary') { if ($data) { add_filter('litespeed_vary_cookies', function ($list) { $list[] = 'woocommerce_cart_hash'; return array_unique($list); }); } else { add_filter('litespeed_vary_cookies', function ($list) { if (in_array('woocommerce_cart_hash', $list)) { unset($list[array_search('woocommerce_cart_hash', $list)]); } return array_unique($list); }); } } } // id validation will be inside $this->cls('Conf')->update_confs($the_matrix); $msg = __('Options saved.', 'litespeed-cache'); Admin_Display::success($msg); } /** * Parses any changes made by the network admin on the network settings. * * @since 3.0 * @access public */ public function network_save($raw_data) { Debug2::debug('[Settings] network saving'); if (empty($raw_data[self::ENROLL])) { exit('No fields'); } $raw_data = Admin::cleanup_text($raw_data); foreach (array_unique($raw_data[self::ENROLL]) as $id) { // Append current field to setting save if (!array_key_exists($id, self::$_default_site_options)) { continue; } $data = !empty($raw_data[$id]) ? $raw_data[$id] : false; // id validation will be inside $this->cls('Conf')->network_update($id, $data); } // Update related files Activation::cls()->update_files(); $msg = __('Options saved.', 'litespeed-cache'); Admin_Display::success($msg); } /** * Hooked to the wp_redirect filter. * This will only hook if there was a problem when saving the widget. * * @since 1.1.3 * @access public * @param string $location The location string. * @return string the updated location string. */ public static function widget_save_err($location) { return str_replace('?message=0', '?error=0', $location); } /** * Hooked to the widget_update_callback filter. * Validate the LiteSpeed Cache settings on edit widget save. * * @since 1.1.3 * @access public * @param array $instance The new settings. * @param array $new_instance * @param array $old_instance The original settings. * @param WP_Widget $widget The widget * @return mixed Updated settings on success, false on error. */ public static function validate_widget_save($instance, $new_instance, $old_instance, $widget) { if (empty($new_instance)) { return $instance; } if (!isset($new_instance[ESI::WIDGET_O_ESIENABLE]) || !isset($new_instance[ESI::WIDGET_O_TTL])) { return $instance; } $esi = intval($new_instance[ESI::WIDGET_O_ESIENABLE]) % 3; $ttl = (int) $new_instance[ESI::WIDGET_O_TTL]; if ($ttl != 0 && $ttl < 30) { add_filter('wp_redirect', __CLASS__ . '::widget_save_err'); return false; // invalid ttl. } if (empty($instance[Conf::OPTION_NAME])) { // todo: to be removed $instance[Conf::OPTION_NAME] = array(); } $instance[Conf::OPTION_NAME][ESI::WIDGET_O_ESIENABLE] = $esi; $instance[Conf::OPTION_NAME][ESI::WIDGET_O_TTL] = $ttl; $current = !empty($old_instance[Conf::OPTION_NAME]) ? $old_instance[Conf::OPTION_NAME] : false; if (!strpos($_SERVER['HTTP_REFERER'], '/wp-admin/customize.php')) { if (!$current || $esi != $current[ESI::WIDGET_O_ESIENABLE]) { Purge::purge_all('Widget ESI_enable changed'); } elseif ($ttl != 0 && $ttl != $current[ESI::WIDGET_O_TTL]) { Purge::add(Tag::TYPE_WIDGET . $widget->id); } Purge::purge_all('Widget saved'); } return $instance; } } src/cloud.cls.php 0000644 00000150656 15246276230 0007756 0 ustar 00 <?php /** * Cloud service cls * * @since 3.0 */ namespace LiteSpeed; defined('WPINC') || exit(); class Cloud extends Base { const LOG_TAG = '❄️'; const CLOUD_SERVER = 'https://api.quic.cloud'; const CLOUD_IPS = 'https://quic.cloud/ips'; const CLOUD_SERVER_DASH = 'https://my.quic.cloud'; const CLOUD_SERVER_WP = 'https://wpapi.quic.cloud'; const SVC_D_ACTIVATE = 'd/activate'; const SVC_U_ACTIVATE = 'u/wp3/activate'; const SVC_D_ENABLE_CDN = 'd/enable_cdn'; const SVC_D_LINK = 'd/link'; const SVC_D_API = 'd/api'; const SVC_D_DASH = 'd/dash'; const SVC_D_V3UPGRADE = 'd/v3upgrade'; const SVC_U_LINK = 'u/wp3/link'; const SVC_U_ENABLE_CDN = 'u/wp3/enablecdn'; const SVC_D_STATUS_CDN_CLI = 'd/status/cdn_cli'; const SVC_D_NODES = 'd/nodes'; const SVC_D_SYNC_CONF = 'd/sync_conf'; const SVC_D_USAGE = 'd/usage'; const SVC_D_SETUP_TOKEN = 'd/get_token'; const SVC_D_DEL_CDN_DNS = 'd/del_cdn_dns'; const SVC_PAGE_OPTM = 'page_optm'; const SVC_CCSS = 'ccss'; const SVC_UCSS = 'ucss'; const SVC_VPI = 'vpi'; const SVC_LQIP = 'lqip'; const SVC_QUEUE = 'queue'; const SVC_IMG_OPTM = 'img_optm'; const SVC_HEALTH = 'health'; const SVC_CDN = 'cdn'; const IMG_OPTM_DEFAULT_GROUP = 200; const IMGOPTM_TAKEN = 'img_optm-taken'; const TTL_NODE = 3; // Days before node expired const EXPIRATION_REQ = 300; // Seconds of min interval between two unfinished requests const TTL_IPS = 3; // Days for node ip list cache const API_REPORT = 'wp/report'; const API_NEWS = 'news'; const API_VER = 'ver_check'; const API_BETA_TEST = 'beta_test'; const API_REST_ECHO = 'tool/wp_rest_echo'; const API_SERVER_KEY_SIGN = 'key_sign'; private static $CENTER_SVC_SET = array( self::SVC_D_ACTIVATE, self::SVC_U_ACTIVATE, self::SVC_D_ENABLE_CDN, self::SVC_D_LINK, self::SVC_D_NODES, self::SVC_D_SYNC_CONF, self::SVC_D_USAGE, self::SVC_D_API, self::SVC_D_V3UPGRADE, self::SVC_D_DASH, self::SVC_D_STATUS_CDN_CLI, // self::API_NEWS, self::API_REPORT, // self::API_VER, // self::API_BETA_TEST, self::SVC_D_SETUP_TOKEN, self::SVC_D_DEL_CDN_DNS, ); private static $WP_SVC_SET = array(self::API_NEWS, self::API_VER, self::API_BETA_TEST, self::API_REST_ECHO); // No api key needed for these services private static $_PUB_SVC_SET = array(self::API_NEWS, self::API_REPORT, self::API_VER, self::API_BETA_TEST, self::API_REST_ECHO, self::SVC_D_V3UPGRADE, self::SVC_D_DASH); private static $_QUEUE_SVC_SET = array(self::SVC_CCSS, self::SVC_UCSS, self::SVC_VPI); public static $SERVICES_LOAD_CHECK = array( // self::SVC_CCSS, // self::SVC_UCSS, // self::SVC_VPI, self::SVC_LQIP, self::SVC_HEALTH, ); public static $SERVICES = array( self::SVC_IMG_OPTM, self::SVC_PAGE_OPTM, self::SVC_CCSS, self::SVC_UCSS, self::SVC_VPI, self::SVC_LQIP, self::SVC_CDN, self::SVC_HEALTH, // self::SVC_QUEUE, ); const TYPE_CLEAR_PROMO = 'clear_promo'; const TYPE_REDETECT_CLOUD = 'redetect_cloud'; const TYPE_CLEAR_CLOUD = 'clear_cloud'; const TYPE_ACTIVATE = 'activate'; const TYPE_LINK = 'link'; const TYPE_ENABLE_CDN = 'enablecdn'; const TYPE_API = 'api'; const TYPE_SYNC_USAGE = 'sync_usage'; const TYPE_RESET = 'reset'; const TYPE_SYNC_STATUS = 'sync_status'; protected $_summary; /** * Init * * @since 3.0 */ public function __construct() { $this->_summary = self::get_summary(); } /** * Init QC setup preparation * * @since 7.0 */ public function init_qc_prepare() { if (empty($this->_summary['sk_b64'])) { $keypair = sodium_crypto_sign_keypair(); $pk = base64_encode(sodium_crypto_sign_publickey($keypair)); $sk = base64_encode(sodium_crypto_sign_secretkey($keypair)); $this->_summary['pk_b64'] = $pk; $this->_summary['sk_b64'] = $sk; $this->save_summary(); // ATM `qc_activated` = null return true; } return false; } /** * Init QC setup * * @since 7.0 */ public function init_qc() { $this->init_qc_prepare(); $ref = $this->_get_ref_url(); // WPAPI REST echo dryrun $req_data = array( 'wp_pk_b64' => $this->_summary['pk_b64'], ); $echobox = self::post(self::API_REST_ECHO, $req_data); if ($echobox === false) { self::debugErr('REST Echo Failed!'); $msg = __('Your WP REST API seems blocked our QUIC.cloud server calls.', 'litespeed-cache'); Admin_Display::error($msg); wp_redirect($ref); return; } self::debug('echo succeeded'); // Load separate thread echoed data from storage if (empty($echobox['wpapi_ts']) || empty($echobox['wpapi_signature_b64'])) { Admin_Display::error(__('Failed to get echo data from WPAPI', 'litespeed-cache')); wp_redirect($ref); return; } $data = array( 'wp_pk_b64' => $this->_summary['pk_b64'], 'wpapi_ts' => $echobox['wpapi_ts'], 'wpapi_signature_b64' => $echobox['wpapi_signature_b64'], ); $server_ip = $this->conf(self::O_SERVER_IP); if ($server_ip) { $data['server_ip'] = $server_ip; } // Activation redirect $param = array( 'site_url' => home_url(), 'ver' => Core::VER, 'data' => $data, 'ref' => $ref, ); wp_redirect(self::CLOUD_SERVER_DASH . '/' . self::SVC_U_ACTIVATE . '?data=' . urlencode(Utility::arr2str($param))); exit(); } /** * Decide the ref */ private function _get_ref_url($ref = false) { $link = 'admin.php?page=litespeed'; if ($ref == 'cdn') { $link = 'admin.php?page=litespeed-cdn'; } if ($ref == 'online') { $link = 'admin.php?page=litespeed-general'; } if (!empty($_GET['ref']) && $_GET['ref'] == 'cdn') { $link = 'admin.php?page=litespeed-cdn'; } if (!empty($_GET['ref']) && $_GET['ref'] == 'online') { $link = 'admin.php?page=litespeed-general'; } return get_admin_url(null, $link); } /** * Init QC setup (CLI) * * @since 7.0 */ public function init_qc_cli() { $this->init_qc_prepare(); $server_ip = $this->conf(self::O_SERVER_IP); if (!$server_ip) { self::debugErr('Server IP needs to be set first!'); $msg = sprintf( __('You need to set the %1$s first. Please use the command %2$s to set.', 'litespeed-cache'), '`' . __('Server IP', 'litespeed-cache') . '`', '`wp litespeed-option set server_ip __your_ip_value__`' ); Admin_Display::error($msg); return; } // WPAPI REST echo dryrun $req_data = array( 'wp_pk_b64' => $this->_summary['pk_b64'], ); $echobox = self::post(self::API_REST_ECHO, $req_data); if ($echobox === false) { self::debugErr('REST Echo Failed!'); $msg = __('Your WP REST API seems blocked our QUIC.cloud server calls.', 'litespeed-cache'); Admin_Display::error($msg); return; } self::debug('echo succeeded'); // Load separate thread echoed data from storage if (empty($echobox['wpapi_ts']) || empty($echobox['wpapi_signature_b64'])) { self::debug('Resp: ', $echobox); Admin_Display::error(__('Failed to get echo data from WPAPI', 'litespeed-cache')); return; } $data = array( 'wp_pk_b64' => $this->_summary['pk_b64'], 'wpapi_ts' => $echobox['wpapi_ts'], 'wpapi_signature_b64' => $echobox['wpapi_signature_b64'], 'server_ip' => $server_ip, ); $res = $this->post(self::SVC_D_ACTIVATE, $data); return $res; } /** * Init QC CDN setup (CLI) * * @since 7.0 */ public function init_qc_cdn_cli($method, $cert = false, $key = false, $cf_token = false) { if (!$this->activated()) { Admin_Display::error(__('You need to activate QC first.', 'litespeed-cache')); return; } $server_ip = $this->conf(self::O_SERVER_IP); if (!$server_ip) { self::debugErr('Server IP needs to be set first!'); $msg = sprintf( __('You need to set the %1$s first. Please use the command %2$s to set.', 'litespeed-cache'), '`' . __('Server IP', 'litespeed-cache') . '`', '`wp litespeed-option set server_ip __your_ip_value__`' ); Admin_Display::error($msg); return; } if ($cert) { if (!file_exists($cert) || !file_exists($key)) { Admin_Display::error(__('Cert or key file does not exist.', 'litespeed-cache')); return; } } $data = array( 'method' => $method, 'server_ip' => $server_ip, ); if ($cert) { $data['cert'] = File::read($cert); $data['key'] = File::read($key); } if ($cf_token) { $data['cf_token'] = $cf_token; } $res = $this->post(self::SVC_D_ENABLE_CDN, $data); return $res; } /** * Link to QC setup * * @since 7.0 */ public function link_qc() { if (!$this->activated()) { Admin_Display::error(__('You need to activate QC first.', 'litespeed-cache')); return; } $data = array( 'wp_ts' => time(), ); $data['wp_signature_b64'] = $this->_sign_b64($data['wp_ts']); // Activation redirect $param = array( 'site_url' => home_url(), 'ver' => Core::VER, 'data' => $data, 'ref' => $this->_get_ref_url(), ); wp_redirect(self::CLOUD_SERVER_DASH . '/' . self::SVC_U_LINK . '?data=' . urlencode(Utility::arr2str($param))); exit(); } /** * Show QC Account CDN status * * @since 7.0 */ public function cdn_status_cli() { if (!$this->activated()) { Admin_Display::error(__('You need to activate QC first.', 'litespeed-cache')); return; } $data = array(); $res = $this->post(self::SVC_D_STATUS_CDN_CLI, $data); return $res; } /** * Link to QC Account for CLI * * @since 7.0 */ public function link_qc_cli($email, $key) { if (!$this->activated()) { Admin_Display::error(__('You need to activate QC first.', 'litespeed-cache')); return; } $data = array( 'qc_acct_email' => $email, 'qc_acct_apikey' => $key, ); $res = $this->post(self::SVC_D_LINK, $data); return $res; } /** * API link parsed call to QC * * @since 7.0 */ public function api_link_call($action2) { if (!$this->activated()) { Admin_Display::error(__('You need to activate QC first.', 'litespeed-cache')); return; } $data = array( 'action2' => $action2, ); $res = $this->post(self::SVC_D_API, $data); self::debug('API link call result: ', $res); } /** * Enable QC CDN * * @since 7.0 */ public function enable_cdn() { if (!$this->activated()) { Admin_Display::error(__('You need to activate QC first.', 'litespeed-cache')); return; } $data = array( 'wp_ts' => time(), ); $data['wp_signature_b64'] = $this->_sign_b64($data['wp_ts']); // Activation redirect $param = array( 'site_url' => home_url(), 'ver' => Core::VER, 'data' => $data, 'ref' => $this->_get_ref_url(), ); wp_redirect(self::CLOUD_SERVER_DASH . '/' . self::SVC_U_ENABLE_CDN . '?data=' . urlencode(Utility::arr2str($param))); exit(); } /** * Encrypt data for cloud req * * @since 7.0 */ private function _sign_b64($data) { if (empty($this->_summary['sk_b64'])) { self::debugErr('No sk to sign.'); return false; } $sk = base64_decode($this->_summary['sk_b64']); if (strlen($sk) !== SODIUM_CRYPTO_SIGN_SECRETKEYBYTES) { self::debugErr('Invalid local sign sk length.'); // Reset local pk/sk unset($this->_summary['pk_b64']); unset($this->_summary['sk_b64']); $this->save_summary(); self::debug('Clear local sign pk/sk pair.'); return false; } $signature = sodium_crypto_sign_detached((string) $data, $sk); return base64_encode($signature); } /** * Load server pk from cloud * * @since 7.0 */ private function _load_server_pk($from_wpapi = false) { // Load cloud pk $server_key_url = self::CLOUD_SERVER . '/' . self::API_SERVER_KEY_SIGN; if ($from_wpapi) { $server_key_url = self::CLOUD_SERVER_WP . '/' . self::API_SERVER_KEY_SIGN; } $resp = wp_safe_remote_get($server_key_url); if (is_wp_error($resp)) { self::debugErr('Failed to load key: ' . $resp->get_error_message()); return false; } $pk = trim($resp['body']); self::debug('Loaded key from ' . $server_key_url . ': ' . $pk); $cloud_pk = base64_decode($pk); if (strlen($cloud_pk) !== SODIUM_CRYPTO_SIGN_PUBLICKEYBYTES) { self::debugErr('Invalid cloud public key length.'); return false; } $sk = base64_decode($this->_summary['sk_b64']); if (strlen($sk) !== SODIUM_CRYPTO_SIGN_SECRETKEYBYTES) { self::debugErr('Invalid local secret key length.'); // Reset local pk/sk unset($this->_summary['pk_b64']); unset($this->_summary['sk_b64']); $this->save_summary(); self::debug('Unset local pk/sk pair.'); return false; } return $cloud_pk; } /** * WPAPI echo back to notify the sealed databox * * @since 7.0 */ public function wp_rest_echo() { self::debug('Parsing echo', $_POST); if (empty($_POST['wpapi_ts']) || empty($_POST['wpapi_signature_b64'])) { return self::err('No echo data'); } $is_valid = $this->_validate_signature($_POST['wpapi_signature_b64'], $_POST['wpapi_ts'], true); if (!$is_valid) { return self::err('Data validation from WPAPI REST Echo failed'); } $diff = time() - $_POST['wpapi_ts']; if (abs($diff) > 86400) { self::debugErr('WPAPI echo data timeout [diff] ' . $diff); return self::err('Echo data expired'); } $signature_b64 = $this->_sign_b64($_POST['wpapi_ts']); self::debug('Response to echo [signature_b64] ' . $signature_b64); return self::ok(array('signature_b64' => $signature_b64)); } /** * Validate cloud data * * @since 7.0 */ private function _validate_signature($signature_b64, $data, $from_wpapi = false) { // Try validation try { $cloud_pk = $this->_load_server_pk($from_wpapi); if (!$cloud_pk) { return false; } $signature = base64_decode($signature_b64); $is_valid = sodium_crypto_sign_verify_detached($signature, $data, $cloud_pk); } catch (\SodiumException $e) { self::debugErr('Decryption failed: ' . $e->getMessage()); return false; } self::debug('Signature validation result: ' . ($is_valid ? 'true' : 'false')); return $is_valid; } /** * Finish qc activation after redirection back from QC * * @since 7.0 */ public function finish_qc_activation($ref = false) { if (empty($_GET['qc_activated']) || empty($_GET['qc_ts']) || empty($_GET['qc_signature_b64'])) { return; } $data_to_validate_signature = array( 'wp_pk_b64' => $this->_summary['pk_b64'], 'qc_ts' => $_GET['qc_ts'], ); $is_valid = $this->_validate_signature($_GET['qc_signature_b64'], implode('', $data_to_validate_signature)); if (!$is_valid) { self::debugErr('Failed to validate qc activation data'); Admin_Display::error(sprintf(__('Failed to validate %s activation data.', 'litespeed-cache'), 'QUIC.cloud')); return; } self::debug('QC activation status: ' . $_GET['qc_activated']); if (!in_array($_GET['qc_activated'], array('anonymous', 'linked', 'cdn'))) { self::debugErr('Failed to parse qc activation status'); Admin_Display::error(sprintf(__('Failed to parse %s activation status.', 'litespeed-cache'), 'QUIC.cloud')); return; } $diff = time() - $_GET['qc_ts']; if (abs($diff) > 86400) { self::debugErr('QC activation data timeout [diff] ' . $diff); Admin_Display::error(sprintf(__('%s activation data expired.', 'litespeed-cache'), 'QUIC.cloud')); return; } $main_domain = !empty($_GET['main_domain']) ? $_GET['main_domain'] : false; $this->update_qc_activation($_GET['qc_activated'], $main_domain); wp_redirect($this->_get_ref_url($ref)); } /** * Finish qc activation process * * @since 7.0 */ public function update_qc_activation($qc_activated, $main_domain = false, $quite = false) { $this->_summary['qc_activated'] = $qc_activated; if ($main_domain) { $this->_summary['main_domain'] = $main_domain; } $this->save_summary(); $msg = sprintf(__('Congratulations, %s successfully set this domain up for the anonymous online services.', 'litespeed-cache'), 'QUIC.cloud'); if ($qc_activated == 'linked') { $msg = sprintf(__('Congratulations, %s successfully set this domain up for the online services.', 'litespeed-cache'), 'QUIC.cloud'); // Sync possible partner info $this->sync_usage(); } if ($qc_activated == 'cdn') { $msg = sprintf(__('Congratulations, %s successfully set this domain up for the online services with CDN service.', 'litespeed-cache'), 'QUIC.cloud'); // Turn on CDN option $this->cls('Conf')->update_confs(array(self::O_CDN_QUIC => true)); } if (!$quite) { Admin_Display::success('🎊 ' . $msg); } $this->_clear_reset_qc_reg_msg(); $this->clear_cloud(); } /** * Load QC status for dash usage * Format to translate: `<a href="{#xxx#}" class="button button-primary">xxxx</a><a href="{#xxx#}">xxxx2</a>` * * @since 7.0 */ public function load_qc_status_for_dash($type, $force = false) { return Str::translate_qc_apis($this->_load_qc_status_for_dash($type, $force)); } private function _load_qc_status_for_dash($type, $force = false) { if ( !$force && !empty($this->_summary['mini_html']) && isset($this->_summary['mini_html'][$type]) && !empty($this->_summary['mini_html']['ttl.' . $type]) && $this->_summary['mini_html']['ttl.' . $type] > time() ) { return Str::safe_html($this->_summary['mini_html'][$type]); } // Try to update dash content $data = self::post(self::SVC_D_DASH, array('action2' => $type == 'cdn_dash_mini' ? 'cdn_dash' : $type)); if (!empty($data['qc_activated'])) { // Sync conf as changed if (empty($this->_summary['qc_activated']) || $this->_summary['qc_activated'] != $data['qc_activated']) { $msg = sprintf(__('Congratulations, %s successfully set this domain up for the online services with CDN service.', 'litespeed-cache'), 'QUIC.cloud'); Admin_Display::success('🎊 ' . $msg); $this->_clear_reset_qc_reg_msg(); // Turn on CDN option $this->cls('Conf')->update_confs(array(self::O_CDN_QUIC => true)); $this->cls('CDN\Quic')->try_sync_conf(true); } $this->_summary['qc_activated'] = $data['qc_activated']; $this->save_summary(); } // Show the info if (isset($this->_summary['mini_html'][$type])) { return Str::safe_html($this->_summary['mini_html'][$type]); } return ''; } /** * Update QC status * * @since 7.0 */ public function update_cdn_status() { if (empty($_POST['qc_activated']) || !in_array($_POST['qc_activated'], array('anonymous', 'linked', 'cdn', 'deleted'))) { return self::err('lack_of_params'); } self::debug('update_cdn_status request hash: ' . $_POST['qc_activated']); if ($_POST['qc_activated'] == 'deleted') { $this->_reset_qc_reg(); } else { $this->_summary['qc_activated'] = $_POST['qc_activated']; $this->save_summary(); } if ($_POST['qc_activated'] == 'cdn') { $msg = sprintf(__('Congratulations, %s successfully set this domain up for the online services with CDN service.', 'litespeed-cache'), 'QUIC.cloud'); Admin_Display::success('🎊 ' . $msg); $this->_clear_reset_qc_reg_msg(); // Turn on CDN option $this->cls('Conf')->update_confs(array(self::O_CDN_QUIC => true)); $this->cls('CDN\Quic')->try_sync_conf(true); } return self::ok(array('qc_activated' => $_POST['qc_activated'])); } /** * Reset QC setup * * @since 7.0 */ public function reset_qc() { unset($this->_summary['pk_b64']); unset($this->_summary['sk_b64']); unset($this->_summary['qc_activated']); if (!empty($this->_summary['partner'])) { unset($this->_summary['partner']); } $this->save_summary(); self::debug('Clear local QC activation.'); $this->clear_cloud(); Admin_Display::success(sprintf(__('Reset %s activation successfully.', 'litespeed-cache'), 'QUIC.cloud')); wp_redirect($this->_get_ref_url()); exit(); } /** * Show latest commit version always if is on dev * * @since 3.0 */ public function check_dev_version() { if (!preg_match('/[^\d\.]/', Core::VER)) { return; } $last_check = empty($this->_summary['last_request.' . self::API_VER]) ? 0 : $this->_summary['last_request.' . self::API_VER]; if (time() - $last_check > 86400) { $auto_v = self::version_check('dev'); if (!empty($auto_v['dev'])) { self::save_summary(array('version.dev' => $auto_v['dev'])); } } if (empty($this->_summary['version.dev'])) { return; } self::debug('Latest dev version ' . $this->_summary['version.dev']); if (version_compare($this->_summary['version.dev'], Core::VER, '<=')) { return; } // Show the dev banner require_once LSCWP_DIR . 'tpl/banner/new_version_dev.tpl.php'; } /** * Check latest version * * @since 2.9 * @access public */ public static function version_check($src = false) { $req_data = array( 'v' => defined('LSCWP_CUR_V') ? LSCWP_CUR_V : '', 'src' => $src, 'php' => phpversion(), ); if (defined('LITESPEED_ERR')) { $req_data['err'] = base64_encode(!is_string(LITESPEED_ERR) ? \json_encode(LITESPEED_ERR) : LITESPEED_ERR); } $data = self::post(self::API_VER, $req_data); return $data; } /** * Show latest news * * @since 3.0 */ public function news() { $this->_update_news(); if (empty($this->_summary['news.new'])) { return; } if (!empty($this->_summary['news.plugin']) && Activation::cls()->dash_notifier_is_plugin_active($this->_summary['news.plugin'])) { return; } require_once LSCWP_DIR . 'tpl/banner/cloud_news.tpl.php'; } /** * Update latest news * * @since 2.9.9.1 */ private function _update_news() { if (!empty($this->_summary['news.utime']) && time() - $this->_summary['news.utime'] < 86400 * 7) { return; } self::save_summary(array('news.utime' => time())); $data = self::get(self::API_NEWS); if (empty($data['id'])) { return; } // Save news if (!empty($this->_summary['news.id']) && $this->_summary['news.id'] == $data['id']) { return; } $this->_summary['news.id'] = $data['id']; $this->_summary['news.plugin'] = !empty($data['plugin']) ? $data['plugin'] : ''; $this->_summary['news.title'] = !empty($data['title']) ? $data['title'] : ''; $this->_summary['news.content'] = !empty($data['content']) ? $data['content'] : ''; $this->_summary['news.zip'] = !empty($data['zip']) ? $data['zip'] : ''; $this->_summary['news.new'] = 1; if ($this->_summary['news.plugin']) { $plugin_info = Activation::cls()->dash_notifier_get_plugin_info($this->_summary['news.plugin']); if ($plugin_info && !empty($plugin_info->name)) { $this->_summary['news.plugin_name'] = $plugin_info->name; } } self::save_summary(); } /** * Check if contains a package in a service or not * * @since 4.0 */ public function has_pkg($service, $pkg) { if (!empty($this->_summary['usage.' . $service]['pkgs']) && $this->_summary['usage.' . $service]['pkgs'] & $pkg) { return true; } return false; } /** * Get allowance of current service * * @since 3.0 * @access private */ public function allowance($service, &$err = false) { // Only auto sync usage at most one time per day if (empty($this->_summary['last_request.' . self::SVC_D_USAGE]) || time() - $this->_summary['last_request.' . self::SVC_D_USAGE] > 86400) { $this->sync_usage(); } if (in_array($service, array(self::SVC_CCSS, self::SVC_UCSS, self::SVC_VPI))) { // @since 4.2 $service = self::SVC_PAGE_OPTM; } if (empty($this->_summary['usage.' . $service])) { return 0; } $usage = $this->_summary['usage.' . $service]; // Image optm is always free $allowance_max = 0; if ($service == self::SVC_IMG_OPTM) { $allowance_max = self::IMG_OPTM_DEFAULT_GROUP; } $allowance = $usage['quota'] - $usage['used']; $err = 'out_of_quota'; if ($allowance > 0) { if ($allowance_max && $allowance_max < $allowance) { $allowance = $allowance_max; } // Daily limit @since 4.2 if (isset($usage['remaining_daily_quota']) && $usage['remaining_daily_quota'] >= 0 && $usage['remaining_daily_quota'] < $allowance) { $allowance = $usage['remaining_daily_quota']; if (!$allowance) { $err = 'out_of_daily_quota'; } } return $allowance; } // Check Pay As You Go balance if (empty($usage['pag_bal'])) { return $allowance_max; } if ($allowance_max && $allowance_max < $usage['pag_bal']) { return $allowance_max; } return $usage['pag_bal']; } /** * Sync Cloud usage summary data * * @since 3.0 * @access public */ public function sync_usage() { $usage = $this->_post(self::SVC_D_USAGE); if (!$usage) { return; } self::debug('sync_usage ' . \json_encode($usage)); foreach (self::$SERVICES as $v) { $this->_summary['usage.' . $v] = !empty($usage[$v]) ? $usage[$v] : false; } self::save_summary(); return $this->_summary; } /** * Clear all existing cloud nodes for future reconnect * * @since 3.0 * @access public */ public function clear_cloud() { foreach (self::$SERVICES as $service) { if (isset($this->_summary['server.' . $service])) { unset($this->_summary['server.' . $service]); } if (isset($this->_summary['server_date.' . $service])) { unset($this->_summary['server_date.' . $service]); } } self::save_summary(); self::debug('Cleared all local service node caches'); } /** * ping clouds to find the fastest node * * @since 3.0 * @access public */ public function detect_cloud($service, $force = false) { if (in_array($service, self::$CENTER_SVC_SET)) { return self::CLOUD_SERVER; } if (in_array($service, self::$WP_SVC_SET)) { return self::CLOUD_SERVER_WP; } // Check if the stored server needs to be refreshed if (!$force) { if ( !empty($this->_summary['server.' . $service]) && !empty($this->_summary['server_date.' . $service]) && $this->_summary['server_date.' . $service] > time() - 86400 * self::TTL_NODE ) { $server = $this->_summary['server.' . $service]; if (!strpos(self::CLOUD_SERVER, 'preview.') && !strpos($server, 'preview.')) { return $server; } if (strpos(self::CLOUD_SERVER, 'preview.') && strpos($server, 'preview.')) { return $server; } } } if (!$service || !in_array($service, self::$SERVICES)) { $msg = __('Cloud Error', 'litespeed-cache') . ': ' . $service; Admin_Display::error($msg); return false; } // Send request to Quic Online Service $json = $this->_post(self::SVC_D_NODES, array('svc' => $this->_maybe_queue($service))); // Check if get list correctly if (empty($json['list']) || !is_array($json['list'])) { self::debug('request cloud list failed: ', $json); if ($json) { $msg = __('Cloud Error', 'litespeed-cache') . ": [Service] $service [Info] " . \json_encode($json); Admin_Display::error($msg); } return false; } // Ping closest cloud $valid_clouds = false; if (!empty($json['list_preferred'])) { $valid_clouds = $this->_get_closest_nodes($json['list_preferred'], $service); } if (!$valid_clouds) { $valid_clouds = $this->_get_closest_nodes($json['list'], $service); } if (!$valid_clouds) { return false; } // Check server load if (in_array($service, self::$SERVICES_LOAD_CHECK)) { // TODO $valid_cloud_loads = array(); foreach ($valid_clouds as $k => $v) { $response = wp_safe_remote_get($v, array('timeout' => 5)); if (is_wp_error($response)) { $error_message = $response->get_error_message(); self::debug('failed to do load checker: ' . $error_message); continue; } $curr_load = \json_decode($response['body'], true); if (!empty($curr_load['_res']) && $curr_load['_res'] == 'ok' && isset($curr_load['load'])) { $valid_cloud_loads[$v] = $curr_load['load']; } } if (!$valid_cloud_loads) { $msg = __('Cloud Error', 'litespeed-cache') . ": [Service] $service [Info] " . __('No available Cloud Node after checked server load.', 'litespeed-cache'); Admin_Display::error($msg); return false; } self::debug('Closest nodes list after load check', $valid_cloud_loads); $qualified_list = array_keys($valid_cloud_loads, min($valid_cloud_loads)); } else { $qualified_list = $valid_clouds; } $closest = $qualified_list[array_rand($qualified_list)]; self::debug('Chose node: ' . $closest); // store data into option locally $this->_summary['server.' . $service] = $closest; $this->_summary['server_date.' . $service] = time(); self::save_summary(); return $this->_summary['server.' . $service]; } /** * Ping to choose the closest nodes * @since 7.0 */ private function _get_closest_nodes($list, $service) { $speed_list = array(); foreach ($list as $v) { // Exclude possible failed 503 nodes if (!empty($this->_summary['disabled_node']) && !empty($this->_summary['disabled_node'][$v]) && time() - $this->_summary['disabled_node'][$v] < 86400) { continue; } $speed_list[$v] = Utility::ping($v); } if (!$speed_list) { self::debug('nodes are in 503 failed nodes'); return false; } $min = min($speed_list); if ($min == 99999) { self::debug('failed to ping all clouds'); return false; } // Random pick same time range ip (230ms 250ms) $range_len = strlen($min); $range_num = substr($min, 0, 1); $valid_clouds = array(); foreach ($speed_list as $node => $speed) { if (strlen($speed) == $range_len && substr($speed, 0, 1) == $range_num) { $valid_clouds[] = $node; } // Append the lower speed ones elseif ($speed < $min * 4) { $valid_clouds[] = $node; } } if (!$valid_clouds) { $msg = __('Cloud Error', 'litespeed-cache') . ": [Service] $service [Info] " . __('No available Cloud Node.', 'litespeed-cache'); Admin_Display::error($msg); return false; } self::debug('Closest nodes list', $valid_clouds); return $valid_clouds; } /** * May need to convert to queue service */ private function _maybe_queue($service) { if (in_array($service, self::$_QUEUE_SVC_SET)) { return self::SVC_QUEUE; } return $service; } /** * Get data from QUIC cloud server * * @since 3.0 * @access public */ public static function get($service, $data = array()) { $instance = self::cls(); return $instance->_get($service, $data); } /** * Get data from QUIC cloud server * * @since 3.0 * @access private */ private function _get($service, $data = false) { $service_tag = $service; if (!empty($data['action'])) { $service_tag .= '-' . $data['action']; } $maybe_cloud = $this->_maybe_cloud($service_tag); if (!$maybe_cloud || $maybe_cloud === 'svc_hot') { return $maybe_cloud; } $server = $this->detect_cloud($service); if (!$server) { return; } $url = $server . '/' . $service; $param = array( 'site_url' => home_url(), 'main_domain' => !empty($this->_summary['main_domain']) ? $this->_summary['main_domain'] : '', 'ver' => Core::VER, ); if ($data) { $param['data'] = $data; } $url .= '?' . http_build_query($param); self::debug('getting from : ' . $url); self::save_summary(array('curr_request.' . $service_tag => time())); $response = wp_safe_remote_get($url, array( 'timeout' => 15, 'headers' => array('Accept' => 'application/json'), )); return $this->_parse_response($response, $service, $service_tag, $server); } /** * Check if is able to do cloud request or not * * @since 3.0 * @access private */ private function _maybe_cloud($service_tag) { $home_url = home_url(); if (!wp_http_validate_url($home_url)) { self::debug('wp_http_validate_url failed: ' . $home_url); return false; } // Deny if is IP if (preg_match('#^(([1-9]?\d|1\d\d|25[0-5]|2[0-4]\d)\.){3}([1-9]?\d|1\d\d|25[0-5]|2[0-4]\d)$#', Utility::parse_url_safe($home_url, PHP_URL_HOST))) { self::debug('IP home url is not allowed for cloud service.'); $msg = __('In order to use QC services, need a real domain name, cannot use an IP.', 'litespeed-cache'); Admin_Display::error($msg); return false; } /** @since 5.0 If in valid err_domains, bypass request */ if ($this->_is_err_domain($home_url)) { self::debug('home url is in err_domains, bypass request: ' . $home_url); return false; } // we don't want the `img_optm-taken` to fail at any given time if ($service_tag == self::IMGOPTM_TAKEN) { return true; } if ($service_tag == self::SVC_D_SYNC_CONF && !$this->activated()) { self::debug('Skip sync conf as QC not activated yet.'); return false; } // Check TTL if (!empty($this->_summary['ttl.' . $service_tag])) { $ttl = $this->_summary['ttl.' . $service_tag] - time(); if ($ttl > 0) { self::debug('❌ TTL limit. [srv] ' . $service_tag . ' [TTL cool down] ' . $ttl . ' seconds'); return 'svc_hot'; } } $expiration_req = self::EXPIRATION_REQ; // Limit frequent unfinished request to 5min $timestamp_tag = 'curr_request.'; if ($service_tag == self::SVC_IMG_OPTM . '-' . Img_Optm::TYPE_NEW_REQ) { $timestamp_tag = 'last_request.'; } else { // For all other requests, if is under debug mode, will always allow if ($this->conf(self::O_DEBUG)) { return true; } } if (!empty($this->_summary[$timestamp_tag . $service_tag])) { $expired = $this->_summary[$timestamp_tag . $service_tag] + $expiration_req - time(); if ($expired > 0) { self::debug("❌ try [$service_tag] after $expired seconds"); if ($service_tag !== self::API_VER) { $msg = __('Cloud Error', 'litespeed-cache') . ': ' . sprintf(__('Please try after %1$s for service %2$s.', 'litespeed-cache'), Utility::readable_time($expired, 0, true), '<code>' . $service_tag . '</code>'); Admin_Display::error(array('cloud_trylater' => $msg)); } return false; } } if (in_array($service_tag, self::$_PUB_SVC_SET)) { return true; } if (!$this->activated() && $service_tag != self::SVC_D_ACTIVATE) { Admin_Display::error(Error::msg('qc_setup_required')); return false; } return true; } /** * Check if a service tag ttl is valid or not * @since 7.1 */ public function service_hot($service_tag) { if (empty($this->_summary['ttl.' . $service_tag])) { return false; } $ttl = $this->_summary['ttl.' . $service_tag] - time(); if ($ttl <= 0) { return false; } return $ttl; } /** * Check if activated QUIC.cloud service or not * * @since 7.0 * @access public */ public function activated() { return !empty($this->_summary['sk_b64']) && !empty($this->_summary['qc_activated']); } /** * Show my.qc quick link to the domain page */ public function qc_link() { $data = array( 'site_url' => home_url(), 'ver' => LSCWP_V, 'ref' => $this->_get_ref_url(), ); return self::CLOUD_SERVER_DASH . '/u/wp3/manage?data=' . urlencode(Utility::arr2str($data)); // . (!empty($this->_summary['is_linked']) ? '?wplogin=1' : ''); } /** * Post data to QUIC.cloud server * * @since 3.0 * @access public */ public static function post($service, $data = false, $time_out = false) { $instance = self::cls(); return $instance->_post($service, $data, $time_out); } /** * Post data to cloud server * * @since 3.0 * @access private */ private function _post($service, $data = false, $time_out = false) { $service_tag = $service; if (!empty($data['action'])) { $service_tag .= '-' . $data['action']; } $maybe_cloud = $this->_maybe_cloud($service_tag); if (!$maybe_cloud || $maybe_cloud === 'svc_hot') { self::debug('Maybe cloud failed: ' . var_export($maybe_cloud, true)); return $maybe_cloud; } $server = $this->detect_cloud($service); if (!$server) { return; } $url = $server . '/' . $this->_maybe_queue($service); self::debug('posting to : ' . $url); if ($data) { $data['service_type'] = $service; // For queue distribution usage } // Encrypt service as signature // $signature_ts = time(); // $sign_data = array( // 'service_tag' => $service_tag, // 'ts' => $signature_ts, // ); // $data['signature_b64'] = $this->_sign_b64(implode('', $sign_data)); // $data['signature_ts'] = $signature_ts; self::debug('data', $data); $param = array( 'site_url' => home_url(), // Need to use home_url() as WPML case may change it for diff langs, therefore we can do auto alias 'main_domain' => !empty($this->_summary['main_domain']) ? $this->_summary['main_domain'] : '', 'wp_pk_b64' => !empty($this->_summary['pk_b64']) ? $this->_summary['pk_b64'] : '', 'ver' => Core::VER, 'data' => $data, ); self::save_summary(array('curr_request.' . $service_tag => time())); $response = wp_safe_remote_post($url, array( 'body' => $param, 'timeout' => $time_out ?: 15, 'headers' => array('Accept' => 'application/json', 'Expect' => ''), )); return $this->_parse_response($response, $service, $service_tag, $server); } /** * Parse response JSON * Mark the request successful if the response status is ok * * @since 3.0 */ private function _parse_response($response, $service, $service_tag, $server) { // If show the error or not if failed $visible_err = $service !== self::API_VER && $service !== self::API_NEWS && $service !== self::SVC_D_DASH; if (is_wp_error($response)) { $error_message = $response->get_error_message(); self::debug('failed to request: ' . $error_message); if ($visible_err) { $msg = __('Failed to request via WordPress', 'litespeed-cache') . ': ' . $error_message . " [server] $server [service] $service"; Admin_Display::error($msg); // Tmp disabled this node from reusing in 1 day if (empty($this->_summary['disabled_node'])) { $this->_summary['disabled_node'] = array(); } $this->_summary['disabled_node'][$server] = time(); self::save_summary(); // Force redetect node self::debug('Node error, redetecting node [svc] ' . $service); $this->detect_cloud($service, true); } return false; } $json = \json_decode($response['body'], true); if (!is_array($json)) { self::debugErr('failed to decode response json: ' . $response['body']); if ($visible_err) { $msg = __('Failed to request via WordPress', 'litespeed-cache') . ': ' . $response['body'] . " [server] $server [service] $service"; Admin_Display::error($msg); // Tmp disabled this node from reusing in 1 day if (empty($this->_summary['disabled_node'])) { $this->_summary['disabled_node'] = array(); } $this->_summary['disabled_node'][$server] = time(); self::save_summary(); // Force redetect node self::debugErr('Node error, redetecting node [svc] ' . $service); $this->detect_cloud($service, true); } return false; } // Check and save TTL data if (!empty($json['_ttl'])) { $ttl = intval($json['_ttl']); self::debug('Service TTL to save: ' . $ttl); if ($ttl > 0 && $ttl < 86400) { self::save_summary(array( 'ttl.' . $service_tag => $ttl + time(), )); } } if (!empty($json['_code'])) { self::debugErr('Hit err _code: ' . $json['_code']); if ($json['_code'] == 'unpulled_images') { $msg = __('Cloud server refused the current request due to unpulled images. Please pull the images first.', 'litespeed-cache'); Admin_Display::error($msg); return false; } if ($json['_code'] == 'blocklisted') { $msg = __('Your domain_key has been temporarily blocklisted to prevent abuse. You may contact support at QUIC.cloud to learn more.', 'litespeed-cache'); Admin_Display::error($msg); return false; } if ($json['_code'] == 'rate_limit') { self::debugErr('Cloud server rate limit exceeded.'); $msg = __('Cloud server refused the current request due to rate limiting. Please try again later.', 'litespeed-cache'); Admin_Display::error($msg); return false; } if ($json['_code'] == 'heavy_load' || $json['_code'] == 'redetect_node') { // Force redetect node self::debugErr('Node redetecting node [svc] ' . $service); Admin_Display::info(__('Redetected node', 'litespeed-cache') . ': ' . Error::msg($json['_code'])); $this->detect_cloud($service, true); } } if (!empty($json['_503'])) { self::debugErr('service 503 unavailable temporarily. ' . $json['_503']); $msg = __( 'We are working hard to improve your online service experience. The service will be unavailable while we work. We apologize for any inconvenience.', 'litespeed-cache' ); $msg .= ' ' . $json['_503'] . " [server] $server [service] $service"; Admin_Display::error($msg); // Force redetect node self::debugErr('Node error, redetecting node [svc] ' . $service); $this->detect_cloud($service, true); return false; } list($json, $return) = $this->extract_msg($json, $service, $server); if ($return) { return false; } self::save_summary(array( 'last_request.' . $service_tag => $this->_summary['curr_request.' . $service_tag], 'curr_request.' . $service_tag => 0, )); if ($json) { self::debug2('response ok', $json); } else { self::debug2('response ok'); } // Only successful request return Array return $json; } /** * Extract msg from json * @since 5.0 */ public function extract_msg($json, $service, $server = false, $is_callback = false) { if (!empty($json['_info'])) { self::debug('_info: ' . $json['_info']); $msg = __('Message from QUIC.cloud server', 'litespeed-cache') . ': ' . $json['_info']; $msg .= $this->_parse_link($json); Admin_Display::info($msg); unset($json['_info']); } if (!empty($json['_note'])) { self::debug('_note: ' . $json['_note']); $msg = __('Message from QUIC.cloud server', 'litespeed-cache') . ': ' . $json['_note']; $msg .= $this->_parse_link($json); Admin_Display::note($msg); unset($json['_note']); } if (!empty($json['_success'])) { self::debug('_success: ' . $json['_success']); $msg = __('Good news from QUIC.cloud server', 'litespeed-cache') . ': ' . $json['_success']; $msg .= $this->_parse_link($json); Admin_Display::success($msg); unset($json['_success']); } // Upgrade is required if (!empty($json['_err_req_v'])) { self::debug('_err_req_v: ' . $json['_err_req_v']); $msg = sprintf(__('%1$s plugin version %2$s required for this action.', 'litespeed-cache'), Core::NAME, 'v' . $json['_err_req_v'] . '+') . " [server] $server [service] $service"; // Append upgrade link $msg2 = ' ' . GUI::plugin_upgrade_link(Core::NAME, Core::PLUGIN_NAME, $json['_err_req_v']); $msg2 .= $this->_parse_link($json); Admin_Display::error($msg . $msg2); return array($json, true); } // Parse _carry_on info if (!empty($json['_carry_on'])) { self::debug('Carry_on usage', $json['_carry_on']); // Store generic info foreach (array('usage', 'promo', 'mini_html', 'partner', '_error', '_info', '_note', '_success') as $v) { if (isset($json['_carry_on'][$v])) { switch ($v) { case 'usage': $usage_svc_tag = in_array($service, array(self::SVC_CCSS, self::SVC_UCSS, self::SVC_VPI)) ? self::SVC_PAGE_OPTM : $service; $this->_summary['usage.' . $usage_svc_tag] = $json['_carry_on'][$v]; break; case 'promo': if (empty($this->_summary[$v]) || !is_array($this->_summary[$v])) { $this->_summary[$v] = array(); } $this->_summary[$v][] = $json['_carry_on'][$v]; break; case 'mini_html': foreach ($json['_carry_on'][$v] as $k2 => $v2) { if (strpos($k2, 'ttl.') === 0) { $v2 += time(); } $this->_summary[$v][$k2] = $v2; } break; case 'partner': $this->_summary[$v] = $json['_carry_on'][$v]; break; case '_error': case '_info': case '_note': case '_success': $color_mode = substr($v, 1); $msgs = $json['_carry_on'][$v]; Admin_Display::add_unique_notice($color_mode, $msgs, true); break; default: break; } } } self::save_summary(); unset($json['_carry_on']); } // Parse general error msg if (!$is_callback && (empty($json['_res']) || $json['_res'] !== 'ok')) { $json_msg = !empty($json['_msg']) ? $json['_msg'] : 'unknown'; self::debug('❌ _err: ' . $json_msg, $json); $str_translated = Error::msg($json_msg); $msg = __('Failed to communicate with QUIC.cloud server', 'litespeed-cache') . ': ' . $str_translated . " [server] $server [service] $service"; $msg .= $this->_parse_link($json); $visible_err = $service !== self::API_VER && $service !== self::API_NEWS && $service !== self::SVC_D_DASH; if ($visible_err) { Admin_Display::error($msg); } // QC may try auto alias /** @since 5.0 Store the domain as `err_domains` only for QC auto alias feature */ if ($json_msg == 'err_alias') { if (empty($this->_summary['err_domains'])) { $this->_summary['err_domains'] = array(); } $home_url = home_url(); if (!array_key_exists($home_url, $this->_summary['err_domains'])) { $this->_summary['err_domains'][$home_url] = time(); } self::save_summary(); } // Site not on QC, delete invalid domain key if ($json_msg == 'site_not_registered' || $json_msg == 'err_key') { $this->_reset_qc_reg(); } return array($json, true); } unset($json['_res']); if (!empty($json['_msg'])) { unset($json['_msg']); } return array($json, false); } /** * Clear QC linked status * @since 5.0 */ private function _reset_qc_reg() { unset($this->_summary['qc_activated']); if (!empty($this->_summary['partner'])) { unset($this->_summary['partner']); } self::save_summary(); $msg = $this->_reset_qc_reg_content(); Admin_Display::error($msg, false, true); } private function _reset_qc_reg_content() { $msg = __('Site not recognized. QUIC.cloud deactivated automatically. Please reactivate your QUIC.cloud account.', 'litespeed-cache'); $msg .= Doc::learn_more(admin_url('admin.php?page=litespeed'), __('Click here to proceed.', 'litespeed-cache'), true, false, true); $msg .= Doc::learn_more('https://docs.litespeedtech.com/lscache/lscwp/general/', false, false, false, true); return $msg; } private function _clear_reset_qc_reg_msg() { self::debug('Removed pinned reset QC reg content msg'); $msg = $this->_reset_qc_reg_content(); Admin_Display::dismiss_pin_by_content($msg, Admin_Display::NOTICE_RED, true); } /** * REST call: check if the error domain is valid call for auto alias purpose * @since 5.0 */ public function rest_err_domains() { if (empty($_POST['main_domain']) || empty($_POST['alias'])) { return self::err('lack_of_param'); } $this->extract_msg($_POST, 'Quic.cloud', false, true); if ($this->_is_err_domain($_POST['alias'])) { if ($_POST['alias'] == home_url()) { $this->_remove_domain_from_err_list($_POST['alias']); } return self::ok(); } return self::err('Not an alias req from here'); } /** * Remove a domain from err domain * @since 5.0 */ private function _remove_domain_from_err_list($url) { unset($this->_summary['err_domains'][$url]); self::save_summary(); } /** * Check if is err domain * @since 5.0 */ private function _is_err_domain($home_url) { if (empty($this->_summary['err_domains'])) { return false; } if (!array_key_exists($home_url, $this->_summary['err_domains'])) { return false; } // Auto delete if too long ago if (time() - $this->_summary['err_domains'][$home_url] > 86400 * 10) { $this->_remove_domain_from_err_list($home_url); return false; } if (time() - $this->_summary['err_domains'][$home_url] > 86400) { return false; } return true; } /** * Show promo from cloud * * @since 3.0 * @access public */ public function show_promo() { if (empty($this->_summary['promo'])) { return; } require_once LSCWP_DIR . 'tpl/banner/cloud_promo.tpl.php'; } /** * Clear promo from cloud * * @since 3.0 * @access private */ private function _clear_promo() { if (count($this->_summary['promo']) > 1) { array_shift($this->_summary['promo']); } else { $this->_summary['promo'] = array(); } self::save_summary(); } /** * Parse _links from json * * @since 1.6.5 * @since 1.6.7 Self clean the parameter * @access private */ private function _parse_link(&$json) { $msg = ''; if (!empty($json['_links'])) { foreach ($json['_links'] as $v) { $msg .= ' ' . sprintf('<a href="%s" class="%s" target="_blank">%s</a>', $v['link'], !empty($v['cls']) ? $v['cls'] : '', $v['title']); } unset($json['_links']); } return $msg; } /** * Request callback validation from Cloud * * @since 3.0 * @access public */ public function ip_validate() { if (empty($_POST['hash'])) { return self::err('lack_of_params'); } if ($_POST['hash'] != md5(substr($this->_summary['pk_b64'], 0, 4))) { self::debug('__callback IP request decryption failed'); return self::err('err_hash'); } Control::set_nocache('Cloud IP hash validation'); $resp_hash = md5(substr($this->_summary['pk_b64'], 2, 4)); self::debug('__callback IP request hash: ' . $resp_hash); return self::ok(array('hash' => $resp_hash)); } /** * Check if this visit is from cloud or not * * @since 3.0 */ public function is_from_cloud() { // return true; $check_point = time() - 86400 * self::TTL_IPS; if (empty($this->_summary['ips']) || empty($this->_summary['ips_ts']) || $this->_summary['ips_ts'] < $check_point) { self::debug('Force updating ip as ips_ts is older than ' . self::TTL_IPS . ' days'); $this->_update_ips(); } $res = $this->cls('Router')->ip_access($this->_summary['ips']); if (!$res) { self::debug('❌ Not our cloud IP'); // Auto check ip list again but need an interval limit safety. if (empty($this->_summary['ips_ts_runner']) || time() - $this->_summary['ips_ts_runner'] > 600) { self::debug('Force updating ip as ips_ts_runner is older than 10mins'); // Refresh IP list for future detection $this->_update_ips(); $res = $this->cls('Router')->ip_access($this->_summary['ips']); if (!$res) { self::debug('❌ 2nd time: Not our cloud IP'); } else { self::debug('✅ Passed Cloud IP verification'); } return $res; } } else { self::debug('✅ Passed Cloud IP verification'); } return $res; } /** * Update Cloud IP list * * @since 4.2 */ private function _update_ips() { self::debug('Load remote Cloud IP list from ' . self::CLOUD_IPS); // Prevent multiple call in a short period self::save_summary(array('ips_ts' => time(), 'ips_ts_runner' => time())); $response = wp_safe_remote_get(self::CLOUD_IPS . '?json'); if (is_wp_error($response)) { $error_message = $response->get_error_message(); self::debug('failed to get ip whitelist: ' . $error_message); throw new \Exception('Failed to fetch QUIC.cloud whitelist ' . $error_message); } $json = \json_decode($response['body'], true); self::debug('Load ips', $json); self::save_summary(array('ips' => $json)); } /** * Return succeeded response * * @since 3.0 */ public static function ok($data = array()) { $data['_res'] = 'ok'; return $data; } /** * Return error * * @since 3.0 */ public static function err($code) { self::debug("❌ Error response code: $code"); return array('_res' => 'err', '_msg' => $code); } /** * Return pong for ping to check PHP function availability * @since 6.5 */ public function ping() { $resp = array( 'v_lscwp' => Core::VER, 'v_php' => PHP_VERSION, 'v_wp' => $GLOBALS['wp_version'], 'home_url' => home_url(), ); if (!empty($_POST['funcs'])) { foreach ($_POST['funcs'] as $v) { $resp[$v] = function_exists($v) ? 'y' : 'n'; } } if (!empty($_POST['classes'])) { foreach ($_POST['classes'] as $v) { $resp[$v] = class_exists($v) ? 'y' : 'n'; } } if (!empty($_POST['consts'])) { foreach ($_POST['consts'] as $v) { $resp[$v] = defined($v) ? 'y' : 'n'; } } return self::ok($resp); } /** * Display a banner for dev env if using preview QC node. * @since 7.0 */ public function maybe_preview_banner() { if (strpos(self::CLOUD_SERVER, 'preview.')) { Admin_Display::note(__('Linked to QUIC.cloud preview environment, for testing purpose only.', 'litespeed-cache'), true, true, 'litespeed-warning-bg'); } } /** * Handle all request actions from main cls * * @since 3.0 * @access public */ public function handler() { $type = Router::verify_type(); switch ($type) { case self::TYPE_CLEAR_CLOUD: $this->clear_cloud(); break; case self::TYPE_REDETECT_CLOUD: if (!empty($_GET['svc'])) { $this->detect_cloud($_GET['svc'], true); } break; case self::TYPE_CLEAR_PROMO: $this->_clear_promo(); break; case self::TYPE_RESET: $this->reset_qc(); break; case self::TYPE_ACTIVATE: $this->init_qc(); break; case self::TYPE_LINK: $this->link_qc(); break; case self::TYPE_ENABLE_CDN: $this->enable_cdn(); break; case self::TYPE_API: if (!empty($_GET['action2'])) { $this->api_link_call($_GET['action2']); } break; case self::TYPE_SYNC_STATUS: $this->load_qc_status_for_dash('cdn_dash', true); $msg = __('Sync QUIC.cloud status successfully.', 'litespeed-cache'); Admin_Display::success($msg); break; case self::TYPE_SYNC_USAGE: $this->sync_usage(); $msg = __('Sync credit allowance with Cloud Server successfully.', 'litespeed-cache'); Admin_Display::success($msg); break; default: break; } Admin::redirect(); } } src/import.cls.php 0000644 00000010232 15246276230 0010143 0 ustar 00 <?php /** * The import/export class. * * @since 1.8.2 */ namespace LiteSpeed; defined('WPINC') || exit(); class Import extends Base { protected $_summary; const TYPE_IMPORT = 'import'; const TYPE_EXPORT = 'export'; const TYPE_RESET = 'reset'; /** * Init * * @since 1.8.2 */ public function __construct() { Debug2::debug('Import init'); $this->_summary = self::get_summary(); } /** * Export settings to file * * @since 1.8.2 * @access public */ public function export($only_data_return = false) { $raw_data = $this->get_options(true); $data = array(); foreach ($raw_data as $k => $v) { $data[] = \json_encode(array($k, $v)); } $data = implode("\n\n", $data); if ($only_data_return) { return $data; } $filename = $this->_generate_filename(); // Update log $this->_summary['export_file'] = $filename; $this->_summary['export_time'] = time(); self::save_summary(); Debug2::debug('Import: Saved to ' . $filename); @header('Content-Disposition: attachment; filename=' . $filename); echo $data; exit(); } /** * Import settings from file * * @since 1.8.2 * @access public */ public function import($file = false) { if (!$file) { if (empty($_FILES['ls_file']['name']) || substr($_FILES['ls_file']['name'], -5) != '.data' || empty($_FILES['ls_file']['tmp_name'])) { Debug2::debug('Import: Failed to import, wrong ls_file'); $msg = __('Import failed due to file error.', 'litespeed-cache'); Admin_Display::error($msg); return false; } $this->_summary['import_file'] = $_FILES['ls_file']['name']; $data = file_get_contents($_FILES['ls_file']['tmp_name']); } else { $this->_summary['import_file'] = $file; $data = file_get_contents($file); } // Update log $this->_summary['import_time'] = time(); self::save_summary(); $ori_data = array(); try { // Check if the data is v4+ or not if (strpos($data, '["_version",') === 0) { Debug2::debug('[Import] Data version: v4+'); $data = explode("\n", $data); foreach ($data as $v) { $v = trim($v); if (!$v) { continue; } list($k, $v) = \json_decode($v, true); $ori_data[$k] = $v; } } else { $ori_data = \json_decode(base64_decode($data), true); } } catch (\Exception $ex) { Debug2::debug('[Import] ❌ Failed to parse serialized data'); return false; } if (!$ori_data) { Debug2::debug('[Import] ❌ Failed to import, no data'); return false; } else { Debug2::debug('[Import] Importing data', $ori_data); } $this->cls('Conf')->update_confs($ori_data); if (!$file) { Debug2::debug('Import: Imported ' . $_FILES['ls_file']['name']); $msg = sprintf(__('Imported setting file %s successfully.', 'litespeed-cache'), $_FILES['ls_file']['name']); Admin_Display::success($msg); } else { Debug2::debug('Import: Imported ' . $file); } return true; } /** * Reset all configs to default values. * * @since 2.6.3 * @access public */ public function reset() { $options = $this->cls('Conf')->load_default_vals(); $this->cls('Conf')->update_confs($options); Debug2::debug('[Import] Reset successfully.'); $msg = __('Reset successfully.', 'litespeed-cache'); Admin_Display::success($msg); } /** * Generate the filename to export * * @since 1.8.2 * @access private */ private function _generate_filename() { // Generate filename $parsed_home = parse_url(get_home_url()); $filename = 'LSCWP_cfg-'; if (!empty($parsed_home['host'])) { $filename .= $parsed_home['host'] . '_'; } if (!empty($parsed_home['path'])) { $filename .= $parsed_home['path'] . '_'; } $filename = str_replace('/', '_', $filename); $filename .= '-' . date('Ymd_His') . '.data'; return $filename; } /** * Handle all request actions from main cls * * @since 1.8.2 * @access public */ public function handler() { $type = Router::verify_type(); switch ($type) { case self::TYPE_IMPORT: $this->import(); break; case self::TYPE_EXPORT: $this->export(); break; case self::TYPE_RESET: $this->reset(); break; default: break; } Admin::redirect(); } } src/rest.cls.php 0000644 00000016715 15246276230 0007622 0 ustar 00 <?php /** * The REST related class. * * @since 2.9.4 */ namespace LiteSpeed; defined('WPINC') || exit(); class REST extends Root { const LOG_TAG = '☎️'; private $_internal_rest_status = false; /** * Confructor of ESI * * @since 2.9.4 */ public function __construct() { // Hook to internal REST call add_filter('rest_request_before_callbacks', array($this, 'set_internal_rest_on')); add_filter('rest_request_after_callbacks', array($this, 'set_internal_rest_off')); add_action('rest_api_init', array($this, 'rest_api_init')); } /** * Register REST hooks * * @since 3.0 * @access public */ public function rest_api_init() { // Activate or deactivate a specific crawler callback register_rest_route('litespeed/v1', '/toggle_crawler_state', array( 'methods' => 'POST', 'callback' => array($this, 'toggle_crawler_state'), 'permission_callback' => function () { return current_user_can('manage_network_options') || current_user_can('manage_options'); }, )); register_rest_route('litespeed/v1', '/tool/check_ip', array( 'methods' => 'GET', 'callback' => array($this, 'check_ip'), 'permission_callback' => function () { return current_user_can('manage_network_options') || current_user_can('manage_options'); }, )); // IP callback validate register_rest_route('litespeed/v3', '/ip_validate', array( 'methods' => 'POST', 'callback' => array($this, 'ip_validate'), 'permission_callback' => array($this, 'is_from_cloud'), )); ## 1.2. WP REST Dryrun Callback register_rest_route('litespeed/v3', '/wp_rest_echo', array( 'methods' => 'POST', 'callback' => array($this, 'wp_rest_echo'), 'permission_callback' => array($this, 'is_from_cloud'), )); register_rest_route('litespeed/v3', '/ping', array( 'methods' => 'POST', 'callback' => array($this, 'ping'), 'permission_callback' => array($this, 'is_from_cloud'), )); // CDN setup callback notification register_rest_route('litespeed/v3', '/cdn_status', array( 'methods' => 'POST', 'callback' => array($this, 'cdn_status'), 'permission_callback' => array($this, 'is_from_cloud'), )); // Image optm notify_img // Need validation register_rest_route('litespeed/v1', '/notify_img', array( 'methods' => 'POST', 'callback' => array($this, 'notify_img'), 'permission_callback' => array($this, 'is_from_cloud'), )); register_rest_route('litespeed/v1', '/notify_ccss', array( 'methods' => 'POST', 'callback' => array($this, 'notify_ccss'), 'permission_callback' => array($this, 'is_from_cloud'), )); register_rest_route('litespeed/v1', '/notify_ucss', array( 'methods' => 'POST', 'callback' => array($this, 'notify_ucss'), 'permission_callback' => array($this, 'is_from_cloud'), )); register_rest_route('litespeed/v1', '/notify_vpi', array( 'methods' => 'POST', 'callback' => array($this, 'notify_vpi'), 'permission_callback' => array($this, 'is_from_cloud'), )); register_rest_route('litespeed/v3', '/err_domains', array( 'methods' => 'POST', 'callback' => array($this, 'err_domains'), 'permission_callback' => array($this, 'is_from_cloud'), )); // Image optm check_img // Need validation register_rest_route('litespeed/v1', '/check_img', array( 'methods' => 'POST', 'callback' => array($this, 'check_img'), 'permission_callback' => array($this, 'is_from_cloud'), )); } /** * Call to freeze or melt the crawler clicked * * @since 4.3 */ public function toggle_crawler_state() { if (isset($_POST['crawler_id'])) { return $this->cls('Crawler')->toggle_activeness($_POST['crawler_id']) ? 1 : 0; } } /** * Check if the request is from cloud nodes * * @since 4.2 * @since 4.4.7 As there is always token/api key validation, ip validation is redundant */ public function is_from_cloud() { // return true; return $this->cls('Cloud')->is_from_cloud(); } /** * Ping pong * * @since 3.0.4 */ public function ping() { return $this->cls('Cloud')->ping(); } /** * Launch api call * * @since 3.0 */ public function check_ip() { return Tool::cls()->check_ip(); } /** * Launch api call * * @since 3.0 */ public function ip_validate() { return $this->cls('Cloud')->ip_validate(); } /** * Launch api call * * @since 3.0 */ public function wp_rest_echo() { return $this->cls('Cloud')->wp_rest_echo(); } /** * Endpoint for QC to notify plugin of CDN status update. * * @since 7.0 */ public function cdn_status() { return $this->cls('Cloud')->update_cdn_status(); } /** * Launch api call * * @since 3.0 */ public function notify_img() { return Img_Optm::cls()->notify_img(); } /** * @since 7.1 */ public function notify_ccss() { self::debug('notify_ccss'); return CSS::cls()->notify(); } /** * @since 5.2 */ public function notify_ucss() { self::debug('notify_ucss'); return UCSS::cls()->notify(); } /** * @since 4.7 */ public function notify_vpi() { self::debug('notify_vpi'); return VPI::cls()->notify(); } /** * @since 4.7 */ public function err_domains() { self::debug('err_domains'); return $this->cls('Cloud')->rest_err_domains(); } /** * Launch api call * * @since 3.0 */ public function check_img() { return Img_Optm::cls()->check_img(); } /** * Return error * * @since 5.7.0.1 */ public static function err($code) { return array('_res' => 'err', '_msg' => $code); } /** * Set internal REST tag to ON * * @since 2.9.4 * @access public */ public function set_internal_rest_on($not_used = null) { $this->_internal_rest_status = true; Debug2::debug2('[REST] ✅ Internal REST ON [filter] rest_request_before_callbacks'); return $not_used; } /** * Set internal REST tag to OFF * * @since 2.9.4 * @access public */ public function set_internal_rest_off($not_used = null) { $this->_internal_rest_status = false; Debug2::debug2('[REST] ❎ Internal REST OFF [filter] rest_request_after_callbacks'); return $not_used; } /** * Get internal REST tag * * @since 2.9.4 * @access public */ public function is_internal_rest() { return $this->_internal_rest_status; } /** * Check if an URL or current page is REST req or not * * @since 2.9.3 * @since 2.9.4 Moved here from Utility, dropped static * @access public */ public function is_rest($url = false) { // For WP 4.4.0- compatibility if (!function_exists('rest_get_url_prefix')) { return defined('REST_REQUEST') && REST_REQUEST; } $prefix = rest_get_url_prefix(); // Case #1: After WP_REST_Request initialisation if (defined('REST_REQUEST') && REST_REQUEST) { return true; } // Case #2: Support "plain" permalink settings if (isset($_GET['rest_route']) && strpos(trim($_GET['rest_route'], '\\/'), $prefix, 0) === 0) { return true; } if (!$url) { return false; } // Case #3: URL Path begins with wp-json/ (REST prefix) Safe for subfolder installation $rest_url = wp_parse_url(site_url($prefix)); $current_url = wp_parse_url($url); // Debug2::debug( '[Util] is_rest check [base] ', $rest_url ); // Debug2::debug( '[Util] is_rest check [curr] ', $current_url ); // Debug2::debug( '[Util] is_rest check [curr2] ', wp_parse_url( add_query_arg( array( ) ) ) ); if ($current_url !== false && !empty($current_url['path']) && $rest_url !== false && !empty($rest_url['path'])) { return strpos($current_url['path'], $rest_url['path']) === 0; } return false; } } src/vpi.cls.php 0000644 00000016304 15246276230 0007435 0 ustar 00 <?php /** * The viewport image class. * * @since 4.7 */ namespace LiteSpeed; defined('WPINC') || exit(); class VPI extends Base { const LOG_TAG = '[VPI]'; const TYPE_GEN = 'gen'; const TYPE_CLEAR_Q = 'clear_q'; protected $_summary; private $_queue; /** * Init * * @since 4.7 */ public function __construct() { $this->_summary = self::get_summary(); } /** * The VPI content of the current page * * @since 4.7 */ public function add_to_queue() { $is_mobile = $this->_separate_mobile(); global $wp; $request_url = home_url($wp->request); $ua = !empty($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : ''; // Store it to prepare for cron $this->_queue = $this->load_queue('vpi'); if (count($this->_queue) > 500) { self::debug('Queue is full - 500'); return; } $home_id = get_option('page_for_posts'); if (!is_singular() && !($home_id > 0 && is_home())) { self::debug('not single post ID'); return; } $post_id = is_home() ? $home_id : get_the_ID(); $queue_k = ($is_mobile ? 'mobile' : '') . ' ' . $request_url; if (!empty($this->_queue[$queue_k])) { self::debug('queue k existed ' . $queue_k); return; } $this->_queue[$queue_k] = array( 'url' => apply_filters('litespeed_vpi_url', $request_url), 'post_id' => $post_id, 'user_agent' => substr($ua, 0, 200), 'is_mobile' => $this->_separate_mobile(), ); // Current UA will be used to request $this->save_queue('vpi', $this->_queue); self::debug('Added queue_vpi [url] ' . $queue_k . ' [UA] ' . $ua); // Prepare cache tag for later purge Tag::add('VPI.' . md5($queue_k)); return null; } /** * Notify finished from server * @since 4.7 */ public function notify() { $post_data = \json_decode(file_get_contents('php://input'), true); if (is_null($post_data)) { $post_data = $_POST; } self::debug('notify() data', $post_data); $this->_queue = $this->load_queue('vpi'); list($post_data) = $this->cls('Cloud')->extract_msg($post_data, 'vpi'); $notified_data = $post_data['data']; if (empty($notified_data) || !is_array($notified_data)) { self::debug('❌ notify exit: no notified data'); return Cloud::err('no notified data'); } // Check if its in queue or not $valid_i = 0; foreach ($notified_data as $v) { if (empty($v['request_url'])) { self::debug('❌ notify bypass: no request_url', $v); continue; } if (empty($v['queue_k'])) { self::debug('❌ notify bypass: no queue_k', $v); continue; } // $queue_k = ( $is_mobile ? 'mobile' : '' ) . ' ' . $v[ 'request_url' ]; $queue_k = $v['queue_k']; if (empty($this->_queue[$queue_k])) { self::debug('❌ notify bypass: no this queue [q_k]' . $queue_k); continue; } // Save data if (!empty($v['data_vpi'])) { $post_id = $this->_queue[$queue_k]['post_id']; $name = !empty($v['is_mobile']) ? 'litespeed_vpi_list_mobile' : 'litespeed_vpi_list'; $urldecode = is_array($v['data_vpi']) ? array_map('urldecode', $v['data_vpi']) : urldecode($v['data_vpi']); self::debug('save data_vpi', $urldecode); $this->cls('Metabox')->save($post_id, $name, $urldecode); $valid_i++; } unset($this->_queue[$queue_k]); self::debug('notify data handled, unset queue [q_k] ' . $queue_k); } $this->save_queue('vpi', $this->_queue); self::debug('notified'); return Cloud::ok(array('count' => $valid_i)); } /** * Cron * * @since 4.7 */ public static function cron($continue = false) { $_instance = self::cls(); return $_instance->_cron_handler($continue); } /** * Cron generation * * @since 4.7 */ private function _cron_handler($continue = false) { self::debug('cron start'); $this->_queue = $this->load_queue('vpi'); if (empty($this->_queue)) { return; } // For cron, need to check request interval too if (!$continue) { if (!empty($this->_summary['curr_request_vpi']) && time() - $this->_summary['curr_request_vpi'] < 300 && !$this->conf(self::O_DEBUG)) { self::debug('Last request not done'); return; } } $i = 0; foreach ($this->_queue as $k => $v) { if (!empty($v['_status'])) { continue; } self::debug('cron job [tag] ' . $k . ' [url] ' . $v['url'] . ($v['is_mobile'] ? ' 📱 ' : '') . ' [UA] ' . $v['user_agent']); $i++; $res = $this->_send_req($v['url'], $k, $v['user_agent'], $v['is_mobile']); if (!$res) { // Status is wrong, drop this this->_queue $this->_queue = $this->load_queue('vpi'); unset($this->_queue[$k]); $this->save_queue('vpi', $this->_queue); if (!$continue) { return; } // if ( $i > 3 ) { GUI::print_loading(count($this->_queue), 'VPI'); return Router::self_redirect(Router::ACTION_VPI, self::TYPE_GEN); // } continue; } // Exit queue if out of quota or service is hot if ($res === 'out_of_quota' || $res === 'svc_hot') { return; } $this->_queue = $this->load_queue('vpi'); $this->_queue[$k]['_status'] = 'requested'; $this->save_queue('vpi', $this->_queue); self::debug('Saved to queue [k] ' . $k); // only request first one if (!$continue) { return; } // if ( $i > 3 ) { GUI::print_loading(count($this->_queue), 'VPI'); return Router::self_redirect(Router::ACTION_VPI, self::TYPE_GEN); // } } } /** * Send to QC API to generate VPI * * @since 4.7 * @access private */ private function _send_req($request_url, $queue_k, $user_agent, $is_mobile) { $svc = Cloud::SVC_VPI; // Check if has credit to push or not $err = false; $allowance = $this->cls('Cloud')->allowance($svc, $err); if (!$allowance) { self::debug('❌ No credit: ' . $err); $err && Admin_Display::error(Error::msg($err)); return 'out_of_quota'; } set_time_limit(120); // Update css request status self::save_summary(array('curr_request_vpi' => time()), true); // Gather guest HTML to send $html = $this->cls('CSS')->prepare_html($request_url, $user_agent); if (!$html) { return false; } // Parse HTML to gather all CSS content before requesting $css = false; list($css, $html) = $this->cls('CSS')->prepare_css($html); if (!$css) { self::debug('❌ No css'); return false; } $data = array( 'url' => $request_url, 'queue_k' => $queue_k, 'user_agent' => $user_agent, 'is_mobile' => $is_mobile ? 1 : 0, // todo:compatible w/ tablet 'html' => $html, 'css' => $css, ); self::debug('Generating: ', $data); $json = Cloud::post($svc, $data, 30); if (!is_array($json)) { return $json; } // Unknown status, remove this line if ($json['status'] != 'queued') { return false; } // Save summary data self::reload_summary(); $this->_summary['last_spent_vpi'] = time() - $this->_summary['curr_request_vpi']; $this->_summary['last_request_vpi'] = $this->_summary['curr_request_vpi']; $this->_summary['curr_request_vpi'] = 0; self::save_summary(); return true; } /** * Handle all request actions from main cls * * @since 4.7 */ public function handler() { $type = Router::verify_type(); switch ($type) { case self::TYPE_GEN: self::cron(true); break; case self::TYPE_CLEAR_Q: $this->clear_q('vpi'); break; default: break; } Admin::redirect(); } } src/doc.cls.php 0000644 00000011353 15246276230 0007403 0 ustar 00 <?php /** * The Doc class. * * @since 2.2.7 * @package LiteSpeed * @subpackage LiteSpeed/src * @author LiteSpeed Technologies <info@litespeedtech.com> */ namespace LiteSpeed; defined('WPINC') || exit(); class Doc { // protected static $_instance; /** * Show option is actually ON by GM * * @since 5.5 * @access public */ public static function maybe_on_by_gm($id) { if (apply_filters('litespeed_conf', $id)) { return; } if (!apply_filters('litespeed_conf', Base::O_GUEST)) { return; } if (!apply_filters('litespeed_conf', Base::O_GUEST_OPTM)) { return; } echo '<font class="litespeed-warning">'; echo '⚠️ ' . sprintf( __('This setting is %1$s for certain qualifying requests due to %2$s!', 'litespeed-cache'), '<code>' . __('ON', 'litespeed-cache') . '</code>', Lang::title(Base::O_GUEST_OPTM) ); self::learn_more('https://docs.litespeedtech.com/lscache/lscwp/general/#guest-optimization'); echo '</font>'; } /** * Changes affect crawler list warning * * @since 4.3 * @access public */ public static function crawler_affected() { echo '<font class="litespeed-primary">'; echo '⚠️ ' . __('This setting will regenerate crawler list and clear the disabled list!', 'litespeed-cache'); echo '</font>'; } /** * Privacy policy * * @since 2.2.7 * @access public */ public static function privacy_policy() { return __( 'This site utilizes caching in order to facilitate a faster response time and better user experience. Caching potentially stores a duplicate copy of every web page that is on display on this site. All cache files are temporary, and are never accessed by any third party, except as necessary to obtain technical support from the cache plugin vendor. Cache files expire on a schedule set by the site administrator, but may easily be purged by the admin before their natural expiration, if necessary. We may use QUIC.cloud services to process & cache your data temporarily.', 'litespeed-cache' ) . sprintf( __('Please see %s for more details.', 'litespeed-cache'), '<a href="https://quic.cloud/privacy-policy/" target="_blank">https://quic.cloud/privacy-policy/</a>' ); } /** * Learn more link * * @since 2.4.2 * @access public */ public static function learn_more($url, $title = false, $self = false, $class = false, $return = false) { if (!$class) { $class = 'litespeed-learn-more'; } if (!$title) { $title = __('Learn More', 'litespeed-cache'); } $self = $self ? '' : "target='_blank'"; $txt = " <a href='$url' $self class='$class'>$title</a>"; if ($return) { return $txt; } echo $txt; } /** * One per line * * @since 3.0 * @access public */ public static function one_per_line($return = false) { $str = __('One per line.', 'litespeed-cache'); if ($return) { return $str; } echo $str; } /** * One per line * * @since 3.4 * @access public */ public static function full_or_partial_url($string_only = false) { if ($string_only) { echo __('Both full and partial strings can be used.', 'litespeed-cache'); } else { echo __('Both full URLs and partial strings can be used.', 'litespeed-cache'); } } /** * Notice to edit .htaccess * * @since 3.0 * @access public */ public static function notice_htaccess() { echo '<font class="litespeed-primary">'; echo '⚠️ ' . __('This setting will edit the .htaccess file.', 'litespeed-cache'); echo ' <a href="https://docs.litespeedtech.com/lscache/lscwp/toolbox/#edit-htaccess-tab" target="_blank" class="litespeed-learn-more">' . __('Learn More', 'litespeed-cache') . '</a>'; echo '</font>'; } /** * Notice for whitelist IPs * * @since 3.0 * @access public */ public static function notice_ips() { echo '<div class="litespeed-primary">'; echo '⚠️ ' . sprintf(__('For online services to work correctly, you must allowlist all %s server IPs.', 'litespeed-cache'), 'QUIC.cloud') . '<br/>'; echo ' ' . __('Before generating key, please verify all IPs on this list are allowlisted', 'litespeed-cache') . ': '; echo '<a href="' . Cloud::CLOUD_IPS . '" target="_blank">' . __('Current Online Server IPs', 'litespeed-cache') . '</a>'; echo '</div>'; } /** * Gentle reminder that web services run asynchronously * * @since 5.3.1 * @access public */ public static function queue_issues($return = false) { $str = '<div class="litespeed-desc">' . __('The queue is processed asynchronously. It may take time.', 'litespeed-cache') . self::learn_more('https://docs.litespeedtech.com/lscache/lscwp/troubleshoot/#quiccloud-queue-issues', false, false, false, true) . '</div>'; if ($return) { return $str; } echo $str; } } src/htaccess.cls.php 0000644 00000060241 15246276230 0010433 0 ustar 00 <?php /** * The htaccess rewrite rule operation class * * * @since 1.0.0 * @package LiteSpeed * @subpackage LiteSpeed/inc * @author LiteSpeed Technologies <info@litespeedtech.com> */ namespace LiteSpeed; defined('WPINC') || exit(); class Htaccess extends Root { private $frontend_htaccess = null; private $_default_frontend_htaccess = null; private $backend_htaccess = null; private $_default_backend_htaccess = null; private $theme_htaccess = null; // Not used yet private $frontend_htaccess_readable = false; private $frontend_htaccess_writable = false; private $backend_htaccess_readable = false; private $backend_htaccess_writable = false; private $theme_htaccess_readable = false; private $theme_htaccess_writable = false; private $__rewrite_on; const LS_MODULE_START = '<IfModule LiteSpeed>'; const EXPIRES_MODULE_START = '<IfModule mod_expires.c>'; const LS_MODULE_END = '</IfModule>'; const LS_MODULE_REWRITE_START = '<IfModule mod_rewrite.c>'; const REWRITE_ON = 'RewriteEngine on'; const LS_MODULE_DONOTEDIT = '## LITESPEED WP CACHE PLUGIN - Do not edit the contents of this block! ##'; const MARKER = 'LSCACHE'; const MARKER_NONLS = 'NON_LSCACHE'; const MARKER_LOGIN_COOKIE = '### marker LOGIN COOKIE'; const MARKER_ASYNC = '### marker ASYNC'; const MARKER_CRAWLER = '### marker CRAWLER'; const MARKER_MOBILE = '### marker MOBILE'; const MARKER_NOCACHE_COOKIES = '### marker NOCACHE COOKIES'; const MARKER_NOCACHE_USER_AGENTS = '### marker NOCACHE USER AGENTS'; const MARKER_CACHE_RESOURCE = '### marker CACHE RESOURCE'; const MARKER_BROWSER_CACHE = '### marker BROWSER CACHE'; const MARKER_MINIFY = '### marker MINIFY'; const MARKER_CORS = '### marker CORS'; const MARKER_WEBP = '### marker WEBP'; const MARKER_DROPQS = '### marker DROPQS'; const MARKER_START = ' start ###'; const MARKER_END = ' end ###'; const RW_PATTERN_RES = '/.*/[^/]*(responsive|css|js|dynamic|loader|fonts)\.php'; /** * Initialize the class and set its properties. * * @since 1.0.7 */ public function __construct() { $this->_path_set(); $this->_default_frontend_htaccess = $this->frontend_htaccess; $this->_default_backend_htaccess = $this->backend_htaccess; $frontend_htaccess = defined('LITESPEED_CFG_HTACCESS') ? LITESPEED_CFG_HTACCESS : false; if ($frontend_htaccess && substr($frontend_htaccess, -10) === '/.htaccess') { $this->frontend_htaccess = $frontend_htaccess; } $backend_htaccess = defined('LITESPEED_CFG_HTACCESS_BACKEND') ? LITESPEED_CFG_HTACCESS_BACKEND : false; if ($backend_htaccess && substr($backend_htaccess, -10) === '/.htaccess') { $this->backend_htaccess = $backend_htaccess; } // Filter for frontend&backend htaccess path $this->frontend_htaccess = apply_filters('litespeed_frontend_htaccess', $this->frontend_htaccess); $this->backend_htaccess = apply_filters('litespeed_backend_htaccess', $this->backend_htaccess); clearstatcache(); // frontend .htaccess privilege $test_permissions = file_exists($this->frontend_htaccess) ? $this->frontend_htaccess : dirname($this->frontend_htaccess); if (is_readable($test_permissions)) { $this->frontend_htaccess_readable = true; } if (is_writable($test_permissions)) { $this->frontend_htaccess_writable = true; } $this->__rewrite_on = array( self::REWRITE_ON, 'CacheLookup on', 'RewriteRule .* - [E=Cache-Control:no-autoflush]', 'RewriteRule ' . preg_quote(LITESPEED_DATA_FOLDER) . '/debug/.*\.log$ - [F,L]', 'RewriteRule ' . preg_quote(self::CONF_FILE) . ' - [F,L]', ); // backend .htaccess privilege if ($this->frontend_htaccess === $this->backend_htaccess) { $this->backend_htaccess_readable = $this->frontend_htaccess_readable; $this->backend_htaccess_writable = $this->frontend_htaccess_writable; } else { $test_permissions = file_exists($this->backend_htaccess) ? $this->backend_htaccess : dirname($this->backend_htaccess); if (is_readable($test_permissions)) { $this->backend_htaccess_readable = true; } if (is_writable($test_permissions)) { $this->backend_htaccess_writable = true; } } } /** * Get if htaccess file is readable * * @since 1.1.0 * @return string */ private function _readable($kind = 'frontend') { if ($kind === 'frontend') { return $this->frontend_htaccess_readable; } if ($kind === 'backend') { return $this->backend_htaccess_readable; } } /** * Get if htaccess file is writable * * @since 1.1.0 * @return string */ public function writable($kind = 'frontend') { if ($kind === 'frontend') { return $this->frontend_htaccess_writable; } if ($kind === 'backend') { return $this->backend_htaccess_writable; } } /** * Get frontend htaccess path * * @since 1.1.0 * @return string */ public static function get_frontend_htaccess($show_default = false) { if ($show_default) { return self::cls()->_default_frontend_htaccess; } return self::cls()->frontend_htaccess; } /** * Get backend htaccess path * * @since 1.1.0 * @return string */ public static function get_backend_htaccess($show_default = false) { if ($show_default) { return self::cls()->_default_backend_htaccess; } return self::cls()->backend_htaccess; } /** * Check to see if .htaccess exists starting at $start_path and going up directories until it hits DOCUMENT_ROOT. * * As dirname() strips the ending '/', paths passed in must exclude the final '/' * * @since 1.0.11 * @access private */ private function _htaccess_search($start_path) { while (!file_exists($start_path . '/.htaccess')) { if ($start_path === '/' || !$start_path) { return false; } if (!empty($_SERVER['DOCUMENT_ROOT']) && wp_normalize_path($start_path) === wp_normalize_path($_SERVER['DOCUMENT_ROOT'])) { return false; } if (dirname($start_path) === $start_path) { return false; } $start_path = dirname($start_path); } return $start_path; } /** * Set the path class variables. * * @since 1.0.11 * @access private */ private function _path_set() { $frontend = Router::frontend_path(); $frontend_htaccess_search = $this->_htaccess_search($frontend); // The existing .htaccess path to be used for frontend .htaccess $this->frontend_htaccess = ($frontend_htaccess_search ?: $frontend) . '/.htaccess'; $backend = realpath(ABSPATH); // /home/user/public_html/backend/ if ($frontend == $backend) { $this->backend_htaccess = $this->frontend_htaccess; return; } // Backend is a different path $backend_htaccess_search = $this->_htaccess_search($backend); // Found affected .htaccess if ($backend_htaccess_search) { $this->backend_htaccess = $backend_htaccess_search . '/.htaccess'; return; } // Frontend path is the parent of backend path if (stripos($backend, $frontend . '/') === 0) { // backend use frontend htaccess $this->backend_htaccess = $this->frontend_htaccess; return; } $this->backend_htaccess = $backend . '/.htaccess'; } /** * Get corresponding htaccess path * * @since 1.1.0 * @param string $kind Frontend or backend * @return string Path */ public function htaccess_path($kind = 'frontend') { switch ($kind) { case 'backend': $path = $this->backend_htaccess; break; case 'frontend': default: $path = $this->frontend_htaccess; break; } return $path; } /** * Get the content of the rules file. * * NOTE: will throw error if failed * * @since 1.0.4 * @since 2.9 Used exception for failed reading * @access public */ public function htaccess_read($kind = 'frontend') { $path = $this->htaccess_path($kind); if (!$path || !file_exists($path)) { return "\n"; } if (!$this->_readable($kind)) { Error::t('HTA_R'); } $content = File::read($path); if ($content === false) { Error::t('HTA_GET'); } // Remove ^M characters. $content = str_ireplace("\x0D", '', $content); return $content; } /** * Try to backup the .htaccess file if we didn't save one before. * * NOTE: will throw error if failed * * @since 1.0.10 * @access private */ private function _htaccess_backup($kind = 'frontend') { $path = $this->htaccess_path($kind); if (!file_exists($path)) { return; } if (file_exists($path . '.bk')) { return; } $res = copy($path, $path . '.bk'); // Failed to backup, abort if (!$res) { Error::t('HTA_BK'); } } /** * Get mobile view rule from htaccess file * * NOTE: will throw error if failed * * @since 1.1.0 */ public function current_mobile_agents() { $rules = $this->_get_rule_by(self::MARKER_MOBILE); if (!isset($rules[0])) { Error::t('HTA_DNF', self::MARKER_MOBILE); } $rule = trim($rules[0]); // 'RewriteCond %{HTTP_USER_AGENT} ' . Utility::arr2regex( $cfg[ $id ], true ) . ' [NC]'; $match = substr($rule, strlen('RewriteCond %{HTTP_USER_AGENT} '), -strlen(' [NC]')); if (!$match) { Error::t('HTA_DNF', __('Mobile Agent Rules', 'litespeed-cache')); } return $match; } /** * Parse rewrites rule from the .htaccess file. * * NOTE: will throw error if failed * * @since 1.1.0 * @access public */ public function current_login_cookie($kind = 'frontend') { $rule = $this->_get_rule_by(self::MARKER_LOGIN_COOKIE, $kind); if (!$rule) { Error::t('HTA_DNF', self::MARKER_LOGIN_COOKIE); } if (strpos($rule, 'RewriteRule .? - [E=') !== 0) { Error::t('HTA_LOGIN_COOKIE_INVALID'); } $rule_cookie = substr($rule, strlen('RewriteRule .? - [E='), -1); if (LITESPEED_SERVER_TYPE === 'LITESPEED_SERVER_OLS') { $rule_cookie = trim($rule_cookie, '"'); } // Drop `Cache-Vary:` $rule_cookie = substr($rule_cookie, strlen('Cache-Vary:')); return $rule_cookie; } /** * Get rewrite rules based on the marker * * @since 2.0 * @access private */ private function _get_rule_by($cond, $kind = 'frontend') { clearstatcache(); $path = $this->htaccess_path($kind); if (!$this->_readable($kind)) { return false; } $rules = File::extract_from_markers($path, self::MARKER); if (!in_array($cond . self::MARKER_START, $rules) || !in_array($cond . self::MARKER_END, $rules)) { return false; } $key_start = array_search($cond . self::MARKER_START, $rules); $key_end = array_search($cond . self::MARKER_END, $rules); if ($key_start === false || $key_end === false) { return false; } $results = array_slice($rules, $key_start + 1, $key_end - $key_start - 1); if (!$results) { return false; } if (count($results) == 1) { return trim($results[0]); } return array_filter($results); } /** * Generate browser cache rules * * @since 1.3 * @access private * @return array Rules set */ private function _browser_cache_rules($cfg) { /** * Add ttl setting * @since 1.6.3 */ $id = Base::O_CACHE_TTL_BROWSER; $ttl = $cfg[$id]; $rules = array( self::EXPIRES_MODULE_START, // '<FilesMatch "\.(pdf|ico|svg|xml|jpg|jpeg|png|gif|webp|ogg|mp4|webm|js|css|woff|woff2|ttf|eot)(\.gz)?$">', 'ExpiresActive on', 'ExpiresByType application/pdf A' . $ttl, 'ExpiresByType image/x-icon A' . $ttl, 'ExpiresByType image/vnd.microsoft.icon A' . $ttl, 'ExpiresByType image/svg+xml A' . $ttl, '', 'ExpiresByType image/jpg A' . $ttl, 'ExpiresByType image/jpeg A' . $ttl, 'ExpiresByType image/png A' . $ttl, 'ExpiresByType image/gif A' . $ttl, 'ExpiresByType image/webp A' . $ttl, 'ExpiresByType image/avif A' . $ttl, '', 'ExpiresByType video/ogg A' . $ttl, 'ExpiresByType audio/ogg A' . $ttl, 'ExpiresByType video/mp4 A' . $ttl, 'ExpiresByType video/webm A' . $ttl, '', 'ExpiresByType text/css A' . $ttl, 'ExpiresByType text/javascript A' . $ttl, 'ExpiresByType application/javascript A' . $ttl, 'ExpiresByType application/x-javascript A' . $ttl, '', 'ExpiresByType application/x-font-ttf A' . $ttl, 'ExpiresByType application/x-font-woff A' . $ttl, 'ExpiresByType application/font-woff A' . $ttl, 'ExpiresByType application/font-woff2 A' . $ttl, 'ExpiresByType application/vnd.ms-fontobject A' . $ttl, 'ExpiresByType font/ttf A' . $ttl, 'ExpiresByType font/otf A' . $ttl, 'ExpiresByType font/woff A' . $ttl, 'ExpiresByType font/woff2 A' . $ttl, '', // '</FilesMatch>', self::LS_MODULE_END, ); return $rules; } /** * Generate CORS rules for fonts * * @since 1.5 * @access private * @return array Rules set */ private function _cors_rules() { return array( '<FilesMatch "\.(ttf|ttc|otf|eot|woff|woff2|font\.css)$">', '<IfModule mod_headers.c>', 'Header set Access-Control-Allow-Origin "*"', '</IfModule>', '</FilesMatch>', ); } /** * Generate rewrite rules based on settings * * @since 1.3 * @access private * @param array $cfg The settings to be used for rewrite rule * @return array Rules array */ private function _generate_rules($cfg) { $new_rules = array(); $new_rules_nonls = array(); $new_rules_backend = array(); $new_rules_backend_nonls = array(); # continual crawler // $id = Base::O_CRAWLER; // if (!empty($cfg[$id])) { $new_rules[] = self::MARKER_ASYNC . self::MARKER_START; $new_rules[] = 'RewriteCond %{REQUEST_URI} /wp-admin/admin-ajax\.php'; $new_rules[] = 'RewriteCond %{QUERY_STRING} action=async_litespeed'; $new_rules[] = 'RewriteRule .* - [E=noabort:1]'; $new_rules[] = self::MARKER_ASYNC . self::MARKER_END; $new_rules[] = ''; // } // mobile agents $id = Base::O_CACHE_MOBILE_RULES; if ((!empty($cfg[Base::O_CACHE_MOBILE]) || !empty($cfg[Base::O_GUEST])) && !empty($cfg[$id])) { $new_rules[] = self::MARKER_MOBILE . self::MARKER_START; $new_rules[] = 'RewriteCond %{HTTP_USER_AGENT} ' . Utility::arr2regex($cfg[$id], true) . ' [NC]'; $new_rules[] = 'RewriteRule .* - [E=Cache-Control:vary=%{ENV:LSCACHE_VARY_VALUE}+ismobile]'; $new_rules[] = self::MARKER_MOBILE . self::MARKER_END; $new_rules[] = ''; } // nocache cookie $id = Base::O_CACHE_EXC_COOKIES; if (!empty($cfg[$id])) { $new_rules[] = self::MARKER_NOCACHE_COOKIES . self::MARKER_START; $new_rules[] = 'RewriteCond %{HTTP_COOKIE} ' . Utility::arr2regex($cfg[$id], true); $new_rules[] = 'RewriteRule .* - [E=Cache-Control:no-cache]'; $new_rules[] = self::MARKER_NOCACHE_COOKIES . self::MARKER_END; $new_rules[] = ''; } // nocache user agents $id = Base::O_CACHE_EXC_USERAGENTS; if (!empty($cfg[$id])) { $new_rules[] = self::MARKER_NOCACHE_USER_AGENTS . self::MARKER_START; $new_rules[] = 'RewriteCond %{HTTP_USER_AGENT} ' . Utility::arr2regex($cfg[$id], true) . ' [NC]'; $new_rules[] = 'RewriteRule .* - [E=Cache-Control:no-cache]'; $new_rules[] = self::MARKER_NOCACHE_USER_AGENTS . self::MARKER_END; $new_rules[] = ''; } // caching php resource TODO: consider drop $id = Base::O_CACHE_RES; if (!empty($cfg[$id])) { $new_rules[] = $new_rules_backend[] = self::MARKER_CACHE_RESOURCE . self::MARKER_START; $new_rules[] = $new_rules_backend[] = 'RewriteRule ' . LSCWP_CONTENT_FOLDER . self::RW_PATTERN_RES . ' - [E=cache-control:max-age=3600]'; $new_rules[] = $new_rules_backend[] = self::MARKER_CACHE_RESOURCE . self::MARKER_END; $new_rules[] = $new_rules_backend[] = ''; } // check login cookie $vary_cookies = $cfg[Base::O_CACHE_VARY_COOKIES]; $id = Base::O_CACHE_LOGIN_COOKIE; if (!empty($cfg[$id])) { $vary_cookies[] = $cfg[$id]; } if (LITESPEED_SERVER_TYPE === 'LITESPEED_SERVER_OLS') { // Need to keep this due to different behavior of OLS when handling response vary header @Sep/22/2018 if (defined('COOKIEHASH')) { $vary_cookies[] = ',wp-postpass_' . COOKIEHASH; } } $vary_cookies = apply_filters('litespeed_vary_cookies', $vary_cookies); // todo: test if response vary header can work in latest OLS, drop the above two lines // frontend and backend if ($vary_cookies) { $env = 'Cache-Vary:' . implode(',', $vary_cookies); // if (LITESPEED_SERVER_TYPE === 'LITESPEED_SERVER_OLS') { // } $env = '"' . $env . '"'; $new_rules[] = $new_rules_backend[] = self::MARKER_LOGIN_COOKIE . self::MARKER_START; $new_rules[] = $new_rules_backend[] = 'RewriteRule .? - [E=' . $env . ']'; $new_rules[] = $new_rules_backend[] = self::MARKER_LOGIN_COOKIE . self::MARKER_END; $new_rules[] = ''; } // CORS font rules $id = Base::O_CDN; if (!empty($cfg[$id])) { $new_rules[] = self::MARKER_CORS . self::MARKER_START; $new_rules = array_merge($new_rules, $this->_cors_rules()); //todo: network $new_rules[] = self::MARKER_CORS . self::MARKER_END; $new_rules[] = ''; } // webp support $id = Base::O_IMG_OPTM_WEBP; if (!empty($cfg[$id])) { $webP_rule = 'RewriteRule .* - [E=Cache-Control:vary=%{ENV:LSCACHE_VARY_VALUE}+webp]'; $next_gen_format = 'webp'; if ($cfg[$id] == 2) { $next_gen_format = 'avif'; } $new_rules[] = self::MARKER_WEBP . self::MARKER_START; $new_rules[] = 'RewriteCond %{HTTP_ACCEPT} "image/' . $next_gen_format . '"'; $new_rules[] = $webP_rule; $new_rules[] = 'RewriteCond %{HTTP_USER_AGENT} iPhone.*Version/(\d{2}).*Safari'; $new_rules[] = 'RewriteCond %1 >13'; $new_rules[] = $webP_rule; $new_rules[] = 'RewriteCond %{HTTP_USER_AGENT} Firefox/([0-9]+)'; $new_rules[] = 'RewriteCond %1 >=65'; $new_rules[] = $webP_rule; $new_rules[] = self::MARKER_WEBP . self::MARKER_END; $new_rules[] = ''; } // drop qs support $id = Base::O_CACHE_DROP_QS; if (!empty($cfg[$id])) { $new_rules[] = self::MARKER_DROPQS . self::MARKER_START; foreach ($cfg[$id] as $v) { $new_rules[] = 'CacheKeyModify -qs:' . $v; } $new_rules[] = self::MARKER_DROPQS . self::MARKER_END; $new_rules[] = ''; } // Browser cache $id = Base::O_CACHE_BROWSER; if (!empty($cfg[$id])) { $new_rules_nonls[] = $new_rules_backend_nonls[] = self::MARKER_BROWSER_CACHE . self::MARKER_START; $new_rules_nonls = array_merge($new_rules_nonls, $this->_browser_cache_rules($cfg)); $new_rules_backend_nonls = array_merge($new_rules_backend_nonls, $this->_browser_cache_rules($cfg)); $new_rules_nonls[] = $new_rules_backend_nonls[] = self::MARKER_BROWSER_CACHE . self::MARKER_END; $new_rules_nonls[] = ''; } // Add module wrapper for LiteSpeed rules if ($new_rules) { $new_rules = $this->_wrap_ls_module($new_rules); } if ($new_rules_backend) { $new_rules_backend = $this->_wrap_ls_module($new_rules_backend); } return array($new_rules, $new_rules_backend, $new_rules_nonls, $new_rules_backend_nonls); } /** * Add LitSpeed module wrapper with rewrite on * * @since 2.1.1 * @access private */ private function _wrap_ls_module($rules = array()) { return array_merge(array(self::LS_MODULE_START), $this->__rewrite_on, array(''), $rules, array(self::LS_MODULE_END)); } /** * Insert LitSpeed module wrapper with rewrite on * * @since 2.1.1 * @access public */ public function insert_ls_wrapper() { $rules = $this->_wrap_ls_module(); $this->_insert_wrapper($rules); } /** * wrap rules with module on info * * @since 1.1.5 * @param array $rules * @return array wrapped rules with module info */ private function _wrap_do_no_edit($rules) { // When to clear rules, don't need DONOTEDIT msg if ($rules === false || !is_array($rules)) { return $rules; } $rules = array_merge(array(self::LS_MODULE_DONOTEDIT), $rules, array(self::LS_MODULE_DONOTEDIT)); return $rules; } /** * Write to htaccess with rules * * NOTE: will throw error if failed * * @since 1.1.0 * @access private */ private function _insert_wrapper($rules = array(), $kind = false, $marker = false) { if ($kind != 'backend') { $kind = 'frontend'; } // Default marker is LiteSpeed marker `LSCACHE` if ($marker === false) { $marker = self::MARKER; } $this->_htaccess_backup($kind); File::insert_with_markers($this->htaccess_path($kind), $this->_wrap_do_no_edit($rules), $marker, true); } /** * Update rewrite rules based on setting * * NOTE: will throw error if failed * * @since 1.3 * @access public */ public function update($cfg) { list($frontend_rules, $backend_rules, $frontend_rules_nonls, $backend_rules_nonls) = $this->_generate_rules($cfg); // Check frontend content list($rules, $rules_nonls) = $this->_extract_rules(); // Check Non-LiteSpeed rules if ($this->_wrap_do_no_edit($frontend_rules_nonls) != $rules_nonls) { Debug2::debug('[Rules] Update non-ls frontend rules'); // Need to update frontend htaccess try { $this->_insert_wrapper($frontend_rules_nonls, false, self::MARKER_NONLS); } catch (\Exception $e) { $manual_guide_codes = $this->_rewrite_codes_msg($this->frontend_htaccess, $frontend_rules_nonls, self::MARKER_NONLS); Debug2::debug('[Rules] Update Failed'); throw new \Exception($manual_guide_codes); } } // Check LiteSpeed rules if ($this->_wrap_do_no_edit($frontend_rules) != $rules) { Debug2::debug('[Rules] Update frontend rules'); // Need to update frontend htaccess try { $this->_insert_wrapper($frontend_rules); } catch (\Exception $e) { Debug2::debug('[Rules] Update Failed'); $manual_guide_codes = $this->_rewrite_codes_msg($this->frontend_htaccess, $frontend_rules); throw new \Exception($manual_guide_codes); } } if ($this->frontend_htaccess !== $this->backend_htaccess) { list($rules, $rules_nonls) = $this->_extract_rules('backend'); // Check Non-LiteSpeed rules for backend if ($this->_wrap_do_no_edit($backend_rules_nonls) != $rules_nonls) { Debug2::debug('[Rules] Update non-ls backend rules'); // Need to update frontend htaccess try { $this->_insert_wrapper($backend_rules_nonls, 'backend', self::MARKER_NONLS); } catch (\Exception $e) { Debug2::debug('[Rules] Update Failed'); $manual_guide_codes = $this->_rewrite_codes_msg($this->backend_htaccess, $backend_rules_nonls, self::MARKER_NONLS); throw new \Exception($manual_guide_codes); } } // Check backend content if ($this->_wrap_do_no_edit($backend_rules) != $rules) { Debug2::debug('[Rules] Update backend rules'); // Need to update backend htaccess try { $this->_insert_wrapper($backend_rules, 'backend'); } catch (\Exception $e) { Debug2::debug('[Rules] Update Failed'); $manual_guide_codes = $this->_rewrite_codes_msg($this->backend_htaccess, $backend_rules); throw new \Exception($manual_guide_codes); } } } return true; } /** * Get existing rewrite rules * * NOTE: will throw error if failed * * @since 1.3 * @access private * @param string $kind Frontend or backend .htaccess file */ private function _extract_rules($kind = 'frontend') { clearstatcache(); $path = $this->htaccess_path($kind); if (!$this->_readable($kind)) { Error::t('E_HTA_R'); } $rules = File::extract_from_markers($path, self::MARKER); $rules_nonls = File::extract_from_markers($path, self::MARKER_NONLS); return array($rules, $rules_nonls); } /** * Output the msg with rules plain data for manual insert * * @since 1.1.5 * @param string $file * @param array $rules * @return string final msg to output */ private function _rewrite_codes_msg($file, $rules, $marker = false) { return sprintf( __('<p>Please add/replace the following codes into the beginning of %1$s:</p> %2$s', 'litespeed-cache'), $file, '<textarea style="width:100%;" rows="10" readonly>' . htmlspecialchars($this->_wrap_rules_with_marker($rules, $marker)) . '</textarea>' ); } /** * Generate rules plain data for manual insert * * @since 1.1.5 */ private function _wrap_rules_with_marker($rules, $marker = false) { // Default marker is LiteSpeed marker `LSCACHE` if ($marker === false) { $marker = self::MARKER; } $start_marker = "# BEGIN {$marker}"; $end_marker = "# END {$marker}"; $new_file_data = implode("\n", array_merge(array($start_marker), $this->_wrap_do_no_edit($rules), array($end_marker))); return $new_file_data; } /** * Clear the rules file of any changes added by the plugin specifically. * * @since 1.0.4 * @access public */ public function clear_rules() { $this->_insert_wrapper(false); // Use false to avoid do-not-edit msg // Clear non ls rules $this->_insert_wrapper(false, false, self::MARKER_NONLS); if ($this->frontend_htaccess !== $this->backend_htaccess) { $this->_insert_wrapper(false, 'backend'); $this->_insert_wrapper(false, 'backend', self::MARKER_NONLS); } } } src/data_structure/url.sql 0000644 00000000315 15246276230 0011715 0 ustar 00 `id` bigint(20) NOT NULL AUTO_INCREMENT, `url` varchar(500) NOT NULL, `cache_tags` varchar(1000) NOT NULL DEFAULT '', PRIMARY KEY (`id`), UNIQUE KEY `url` (`url`(191)), KEY `cache_tags` (`cache_tags`(191)) src/data_structure/img_optming.sql 0000644 00000000526 15246276230 0013430 0 ustar 00 `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, `post_id` bigint(20) unsigned NOT NULL DEFAULT '0', `optm_status` tinyint(4) NOT NULL DEFAULT '0', `src` varchar(1000) NOT NULL DEFAULT '', `server_info` text NOT NULL, PRIMARY KEY (`id`), KEY `post_id` (`post_id`), KEY `optm_status` (`optm_status`), KEY `src` (`src`(191)) src/data_structure/url_file.sql 0000644 00000001210 15246276230 0012707 0 ustar 00 `id` bigint(20) NOT NULL AUTO_INCREMENT, `url_id` bigint(20) NOT NULL, `vary` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT 'md5 of final vary', `filename` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT 'md5 of file content', `type` tinyint(4) NOT NULL COMMENT 'css=1,js=2,ccss=3,ucss=4', `mobile` tinyint(4) NOT NULL COMMENT 'mobile=1', `webp` tinyint(4) NOT NULL COMMENT 'webp=1', `expired` int(11) NOT NULL DEFAULT 0, PRIMARY KEY (`id`), KEY `filename` (`filename`), KEY `type` (`type`), KEY `url_id_2` (`url_id`,`vary`,`type`), KEY `filename_2` (`filename`,`expired`), KEY `url_id` (`url_id`,`expired`) src/data_structure/crawler_blacklist.sql 0000644 00000000626 15246276230 0014607 0 ustar 00 `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, `url` varchar(1000) NOT NULL DEFAULT '', `res` varchar(255) NOT NULL DEFAULT '' COMMENT '-=Not Blacklist, B=blacklist', `reason` text NOT NULL COMMENT 'Reason for blacklist, comma separated', `mtime` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(), PRIMARY KEY (`id`), KEY `url` (`url`(191)), KEY `res` (`res`) src/data_structure/crawler.sql 0000644 00000000632 15246276230 0012554 0 ustar 00 `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, `url` varchar(1000) NOT NULL DEFAULT '', `res` varchar(255) NOT NULL DEFAULT '' COMMENT '-=not crawl, H=hit, M=miss, B=blacklist', `reason` text NOT NULL COMMENT 'response code, comma separated', `mtime` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(), PRIMARY KEY (`id`), KEY `url` (`url`(191)), KEY `res` (`res`) src/data_structure/avatar.sql 0000644 00000000404 15246276230 0012370 0 ustar 00 `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, `url` varchar(1000) NOT NULL DEFAULT '', `md5` varchar(128) NOT NULL DEFAULT '', `dateline` int(11) NOT NULL DEFAULT '0', PRIMARY KEY (`id`), UNIQUE KEY `md5` (`md5`), KEY `dateline` (`dateline`) src/data_structure/img_optm.sql 0000644 00000000632 15246276230 0012730 0 ustar 00 `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, `post_id` bigint(20) unsigned NOT NULL DEFAULT '0', `optm_status` tinyint(4) NOT NULL DEFAULT '0', `src` text NOT NULL, `src_filesize` int(11) NOT NULL DEFAULT '0', `target_filesize` int(11) NOT NULL DEFAULT '0', `webp_filesize` int(11) NOT NULL DEFAULT '0', PRIMARY KEY (`id`), KEY `post_id` (`post_id`), KEY `optm_status` (`optm_status`) src/data.upgrade.func.php 0000644 00000056156 15246276230 0011361 0 ustar 00 <?php /** * Database upgrade funcs * * NOTE: whenever called this file, always call Data::get_upgrade_lock and Data::_set_upgrade_lock first. * * @since 3.0 */ defined('WPINC') || exit(); use LiteSpeed\Debug2; use LiteSpeed\Conf; use LiteSpeed\Admin_Display; use LiteSpeed\File; use LiteSpeed\Cloud; /** * Migrate v7.0- url_files URL from no trailing slash to trailing slash * @since 7.0.1 */ function litespeed_update_7_0_1() { global $wpdb; Debug2::debug('[Data] v7.0.1 upgrade started'); $tb_url = $wpdb->prefix . 'litespeed_url'; $tb_exists = $wpdb->get_var("SHOW TABLES LIKE '" . $tb_url . "'"); if (!$tb_exists) { Debug2::debug('[Data] Table `litespeed_url` not found, bypassed migration'); return; } $q = "SELECT * FROM `$tb_url` WHERE url LIKE 'https://%/'"; $q = $wpdb->prepare($q); $list = $wpdb->get_results($q, ARRAY_A); $existing_urls = array(); if ($list) { foreach ($list as $v) { $existing_urls[] = $v['url']; } } $q = "SELECT * FROM `$tb_url` WHERE url LIKE 'https://%'"; $q = $wpdb->prepare($q); $list = $wpdb->get_results($q, ARRAY_A); if (!$list) { return; } foreach ($list as $v) { if (substr($v['url'], -1) == '/') { continue; } $new_url = $v['url'] . '/'; if (in_array($new_url, $existing_urls)) { continue; } $q = "UPDATE `$tb_url` SET url = %s WHERE id = %d"; $q = $wpdb->prepare($q, $new_url, $v['id']); $wpdb->query($q); } } /** * Migrate from domain key to pk/sk for QC * @since 7.0 */ function litespeed_update_7() { Debug2::debug('[Data] v7 upgrade started'); $__cloud = Cloud::cls(); $domain_key = $__cloud->conf('api_key'); if (!$domain_key) { Debug2::debug('[Data] No domain key, bypassed migration'); return; } $new_prepared = $__cloud->init_qc_prepare(); if (!$new_prepared && $__cloud->activated()) { Debug2::debug('[Data] QC previously activated in v7, bypassed migration'); return; } $data = array( 'domain_key' => $domain_key, ); $resp = $__cloud->post(Cloud::SVC_D_V3UPGRADE, $data); if (!empty($resp['qc_activated'])) { if ($resp['qc_activated'] != 'deleted') { $cloud_summary_updates = array('qc_activated' => $resp['qc_activated']); if (!empty($resp['main_domain'])) { $cloud_summary_updates['main_domain'] = $resp['main_domain']; } Cloud::save_summary($cloud_summary_updates); Debug2::debug('[Data] Updated QC activated status to ' . $resp['qc_activated']); } } } /** * Append webp/mobile to url_file * @since 5.3 */ function litespeed_update_5_3() { global $wpdb; Debug2::debug('[Data] Upgrade url_file table'); $tb_exists = $wpdb->get_var('SHOW TABLES LIKE "' . $wpdb->prefix . 'litespeed_url_file"'); if ($tb_exists) { $q = 'ALTER TABLE `' . $wpdb->prefix . 'litespeed_url_file` ADD COLUMN `mobile` tinyint(4) NOT NULL COMMENT "mobile=1", ADD COLUMN `webp` tinyint(4) NOT NULL COMMENT "webp=1" '; $wpdb->query($q); } } /** * Add expired to url_file table * @since 4.4.4 */ function litespeed_update_4_4_4() { global $wpdb; Debug2::debug('[Data] Upgrade url_file table'); $tb_exists = $wpdb->get_var('SHOW TABLES LIKE "' . $wpdb->prefix . 'litespeed_url_file"'); if ($tb_exists) { $q = 'ALTER TABLE `' . $wpdb->prefix . 'litespeed_url_file` ADD COLUMN `expired` int(11) NOT NULL DEFAULT 0, ADD KEY `filename_2` (`filename`,`expired`), ADD KEY `url_id` (`url_id`,`expired`) '; $wpdb->query($q); } } /** * Drop cssjs table and rm cssjs folder * @since 4.3 */ function litespeed_update_4_3() { if (file_exists(LITESPEED_STATIC_DIR . '/ccsjs')) { File::rrmdir(LITESPEED_STATIC_DIR . '/ccsjs'); } } /** * Drop object cache data file * @since 4.1 */ function litespeed_update_4_1() { if (file_exists(WP_CONTENT_DIR . '/.object-cache.ini')) { unlink(WP_CONTENT_DIR . '/.object-cache.ini'); } } /** * Drop cssjs table and rm cssjs folder * @since 4.0 */ function litespeed_update_4() { global $wpdb; $tb = $wpdb->prefix . 'litespeed_cssjs'; $existed = $wpdb->get_var("SHOW TABLES LIKE '$tb'"); if (!$existed) { return; } $q = 'DROP TABLE IF EXISTS ' . $tb; $wpdb->query($q); if (file_exists(LITESPEED_STATIC_DIR . '/ccsjs')) { File::rrmdir(LITESPEED_STATIC_DIR . '/ccsjs'); } } /** * Append jQuery to JS optm exclude list for max compatibility * Turn off JS Combine and Defer * * @since 3.5.1 */ function litespeed_update_3_5() { $__conf = Conf::cls(); // Excludes jQuery foreach (array('optm-js_exc', 'optm-js_defer_exc') as $v) { $curr_setting = $__conf->conf($v); $curr_setting[] = 'jquery.js'; $curr_setting[] = 'jquery.min.js'; $__conf->update($v, $curr_setting); } // Turn off JS Combine and defer $show_msg = false; foreach (array('optm-js_comb', 'optm-js_defer', 'optm-js_inline_defer') as $v) { $curr_setting = $__conf->conf($v); if (!$curr_setting) { continue; } $show_msg = true; $__conf->update($v, false); } if ($show_msg) { $msg = sprintf( __( 'LiteSpeed Cache upgraded successfully. NOTE: Due to changes in this version, the settings %1$s and %2$s have been turned OFF. Please turn them back on manually and verify that your site layout is correct, and you have no JS errors.', 'litespeed-cache' ), '<code>' . __('JS Combine', 'litespeed-cache') . '</code>', '<code>' . __('JS Defer', 'litespeed-cache') . '</code>' ); $msg .= sprintf(' <a href="admin.php?page=litespeed-page_optm#settings_js">%s</a>.', __('Click here to settings', 'litespeed-cache')); Admin_Display::info($msg, false, true); } } /** * For version under v2.0 to v2.0+ * * @since 3.0 */ function litespeed_update_2_0($ver) { global $wpdb; // Table version only exists after all old data migrated // Last modified is v2.4.2 if (version_compare($ver, '2.4.2', '<')) { /** * Convert old data from postmeta to img_optm table * @since 2.0 */ // Migrate data from `wp_postmeta` to `wp_litespeed_img_optm` $mids_to_del = array(); $q = "SELECT * FROM $wpdb->postmeta WHERE meta_key = %s ORDER BY meta_id"; $meta_value_list = $wpdb->get_results($wpdb->prepare($q, 'litespeed-optimize-data')); if ($meta_value_list) { $max_k = count($meta_value_list) - 1; foreach ($meta_value_list as $k => $v) { $mids_to_del[] = $v->meta_id; // Delete from postmeta if (count($mids_to_del) > 100 || $k == $max_k) { $q = "DELETE FROM $wpdb->postmeta WHERE meta_id IN ( " . implode(',', array_fill(0, count($mids_to_del), '%s')) . ' ) '; $wpdb->query($wpdb->prepare($q, $mids_to_del)); $mids_to_del = array(); } } Debug2::debug('[Data] img_optm inserted records: ' . $k); } $q = "DELETE FROM $wpdb->postmeta WHERE meta_key = %s"; $rows = $wpdb->query($wpdb->prepare($q, 'litespeed-optimize-status')); Debug2::debug('[Data] img_optm delete optm_status records: ' . $rows); } /** * Add target_md5 field to table * @since 2.4.2 */ if (version_compare($ver, '2.4.2', '<') && version_compare($ver, '2.0', '>=')) { // NOTE: For new users, need to bypass this section $sql = sprintf('ALTER TABLE `%1$s` ADD `server_info` text NOT NULL, DROP COLUMN `server`', $wpdb->prefix . 'litespeed_img_optm'); $res = $wpdb->query($sql); if ($res !== true) { Debug2::debug('[Data] Warning: Alter table img_optm failed!', $sql); } else { Debug2::debug('[Data] Successfully upgraded table img_optm.'); } } // Delete img optm tb version delete_option($wpdb->prefix . 'litespeed_img_optm'); // Delete possible HTML optm data from wp_options delete_option('litespeed-cache-optimized'); // Delete HTML optm tb version delete_option($wpdb->prefix . 'litespeed_optimizer'); } /** * Move all options in litespeed-cache-conf from v3.0- to separate records * * @since 3.0 */ function litespeed_update_3_0($ver) { global $wpdb; // Upgrade v2.0- to v2.0 first if (version_compare($ver, '2.0', '<')) { litespeed_update_2_0($ver); } set_time_limit(86400); // conv items to litespeed.conf.* Debug2::debug('[Data] Conv items to litespeed.conf.*'); $data = array( 'litespeed-cache-exclude-cache-roles' => 'cache-exc_roles', 'litespeed-cache-drop_qs' => 'cache-drop_qs', 'litespeed-forced_cache_uri' => 'cache-force_uri', 'litespeed-cache_uri_priv' => 'cache-priv_uri', 'litespeed-excludes_uri' => 'cache-exc', 'litespeed-cache-vary-group' => 'cache-vary_group', 'litespeed-adv-purge_all_hooks' => 'purge-hook_all', 'litespeed-object_global_groups' => 'object-global_groups', 'litespeed-object_non_persistent_groups' => 'object-non_persistent_groups', 'litespeed-media-lazy-img-excludes' => 'media-lazy_exc', 'litespeed-media-lazy-img-cls-excludes' => 'media-lazy_cls_exc', 'litespeed-media-webp_attribute' => 'img_optm-webp_attr', 'litespeed-optm-css' => 'optm-ccss_con', 'litespeed-optm_excludes' => 'optm-exc', 'litespeed-optm-ccss-separate_posttype' => 'optm-ccss_sep_posttype', 'litespeed-optm-css-separate_uri' => 'optm-ccss_sep_uri', 'litespeed-optm-js-defer-excludes' => 'optm-js_defer_exc', 'litespeed-cache-dns_prefetch' => 'optm-dns_prefetch', 'litespeed-cache-exclude-optimization-roles' => 'optm-exc_roles', 'litespeed-log_ignore_filters' => 'debug-log_no_filters', // depreciated 'litespeed-log_ignore_part_filters' => 'debug-log_no_part_filters', // depreciated 'litespeed-cdn-ori_dir' => 'cdn-ori_dir', 'litespeed-cache-cdn_mapping' => 'cdn-mapping', 'litespeed-crawler-as-uids' => 'crawler-roles', 'litespeed-crawler-cookies' => 'crawler-cookies', ); foreach ($data as $k => $v) { $old_data = get_option($k); if ($old_data) { Debug2::debug("[Data] Convert $k"); // They must be an array if (!is_array($old_data) && $v != 'optm-ccss_con') { $old_data = explode("\n", $old_data); } if ($v == 'crawler-cookies') { $tmp = array(); $i = 0; foreach ($old_data as $k2 => $v2) { $tmp[$i]['name'] = $k2; $tmp[$i]['vals'] = explode("\n", $v2); $i++; } $old_data = $tmp; } add_option('litespeed.conf.' . $v, $old_data); } Debug2::debug("[Data] Delete $k"); delete_option($k); } // conv other items $data = array( 'litespeed-setting-mode' => 'litespeed.setting.mode', 'litespeed-media-need-pull' => 'litespeed.img_optm.need_pull', 'litespeed-env-ref' => 'litespeed.env.ref', 'litespeed-cache-cloudflare_status' => 'litespeed.cdn.cloudflare.status', ); foreach ($data as $k => $v) { $old_data = get_option($k); if ($old_data) { add_option($v, $old_data); } delete_option($k); } // Conv conf from litespeed-cache-conf child to litespeed.conf.* Debug2::debug('[Data] Conv conf from litespeed-cache-conf child to litespeed.conf.*'); $previous_options = get_option('litespeed-cache-conf'); $data = array( 'radio_select' => 'cache', 'hash' => 'hash', 'auto_upgrade' => 'auto_upgrade', 'news' => 'news', 'crawler_domain_ip' => 'server_ip', 'esi_enabled' => 'esi', 'esi_cached_admbar' => 'esi-cache_admbar', 'esi_cached_commform' => 'esi-cache_commform', 'heartbeat' => 'misc-heartbeat_front', 'cache_browser' => 'cache-browser', 'cache_browser_ttl' => 'cache-ttl_browser', 'instant_click' => 'util-instant_click', 'use_http_for_https_vary' => 'util-no_https_vary', 'purge_upgrade' => 'purge-upgrade', 'timed_urls' => 'purge-timed_urls', 'timed_urls_time' => 'purge-timed_urls_time', 'cache_priv' => 'cache-priv', 'cache_commenter' => 'cache-commenter', 'cache_rest' => 'cache-rest', 'cache_page_login' => 'cache-page_login', 'cache_favicon' => 'cache-favicon', 'cache_resources' => 'cache-resources', 'mobileview_enabled' => 'cache-mobile', 'mobileview_rules' => 'cache-mobile_rules', 'nocache_useragents' => 'cache-exc_useragents', 'nocache_cookies' => 'cache-exc_cookies', 'excludes_qs' => 'cache-exc_qs', 'excludes_cat' => 'cache-exc_cat', 'excludes_tag' => 'cache-exc_tag', 'public_ttl' => 'cache-ttl_pub', 'private_ttl' => 'cache-ttl_priv', 'front_page_ttl' => 'cache-ttl_frontpage', 'feed_ttl' => 'cache-ttl_feed', 'login_cookie' => 'cache-login_cookie', 'debug_disable_all' => 'debug-disable_all', 'debug' => 'debug', 'admin_ips' => 'debug-ips', 'debug_level' => 'debug-level', 'log_file_size' => 'debug-filesize', 'debug_cookie' => 'debug-cookie', 'collapse_qs' => 'debug-collapse_qs', // 'log_filters' => 'debug-log_filters', 'crawler_cron_active' => 'crawler', // 'crawler_include_posts' => 'crawler-inc_posts', // 'crawler_include_pages' => 'crawler-inc_pages', // 'crawler_include_cats' => 'crawler-inc_cats', // 'crawler_include_tags' => 'crawler-inc_tags', // 'crawler_excludes_cpt' => 'crawler-exc_cpt', // 'crawler_order_links' => 'crawler-order_links', 'crawler_usleep' => 'crawler-usleep', 'crawler_run_duration' => 'crawler-run_duration', 'crawler_run_interval' => 'crawler-run_interval', 'crawler_crawl_interval' => 'crawler-crawl_interval', 'crawler_threads' => 'crawler-threads', 'crawler_load_limit' => 'crawler-load_limit', 'crawler_custom_sitemap' => 'crawler-sitemap', 'cache_object' => 'object', 'cache_object_kind' => 'object-kind', 'cache_object_host' => 'object-host', 'cache_object_port' => 'object-port', 'cache_object_life' => 'object-life', 'cache_object_persistent' => 'object-persistent', 'cache_object_admin' => 'object-admin', 'cache_object_transients' => 'object-transients', 'cache_object_db_id' => 'object-db_id', 'cache_object_user' => 'object-user', 'cache_object_pswd' => 'object-psw', 'cdn' => 'cdn', 'cdn_ori' => 'cdn-ori', 'cdn_exclude' => 'cdn-exc', // 'cdn_remote_jquery' => 'cdn-remote_jq', 'cdn_quic' => 'cdn-quic', 'cdn_cloudflare' => 'cdn-cloudflare', 'cdn_cloudflare_email' => 'cdn-cloudflare_email', 'cdn_cloudflare_key' => 'cdn-cloudflare_key', 'cdn_cloudflare_name' => 'cdn-cloudflare_name', 'cdn_cloudflare_zone' => 'cdn-cloudflare_zone', 'media_img_lazy' => 'media-lazy', 'media_img_lazy_placeholder' => 'media-lazy_placeholder', 'media_placeholder_resp' => 'media-placeholder_resp', 'media_placeholder_resp_color' => 'media-placeholder_resp_color', 'media_placeholder_resp_async' => 'media-placeholder_resp_async', 'media_iframe_lazy' => 'media-iframe_lazy', // 'media_img_lazyjs_inline' => 'media-lazyjs_inline', 'media_optm_auto' => 'img_optm-auto', 'media_optm_cron' => 'img_optm-cron', 'media_optm_ori' => 'img_optm-ori', 'media_rm_ori_bkup' => 'img_optm-rm_bkup', // 'media_optm_webp' => 'img_optm-webp', 'media_webp_replace' => 'img_optm-webp', 'media_optm_lossless' => 'img_optm-lossless', 'media_optm_exif' => 'img_optm-exif', 'media_webp_replace_srcset' => 'img_optm-webp_replace_srcset', 'css_minify' => 'optm-css_min', // 'css_inline_minify' => 'optm-css_inline_min', 'css_combine' => 'optm-css_comb', // 'css_combined_priority' => 'optm-css_comb_priority', // 'css_http2' => 'optm-css_http2', 'css_exclude' => 'optm-css_exc', 'js_minify' => 'optm-js_min', // 'js_inline_minify' => 'optm-js_inline_min', 'js_combine' => 'optm-js_comb', // 'js_combined_priority' => 'optm-js_comb_priority', // 'js_http2' => 'optm-js_http2', 'js_exclude' => 'optm-js_exc', // 'optimize_ttl' => 'optm-ttl', 'html_minify' => 'optm-html_min', 'optm_qs_rm' => 'optm-qs_rm', 'optm_ggfonts_rm' => 'optm-ggfonts_rm', 'optm_css_async' => 'optm-css_async', // 'optm_ccss_gen' => 'optm-ccss_gen', // 'optm_ccss_async' => 'optm-ccss_async', 'optm_css_async_inline' => 'optm-css_async_inline', 'optm_js_defer' => 'optm-js_defer', 'optm_emoji_rm' => 'optm-emoji_rm', // 'optm_exclude_jquery' => 'optm-exc_jq', 'optm_ggfonts_async' => 'optm-ggfonts_async', // 'optm_max_size' => 'optm-max_size', // 'optm_rm_comment' => 'optm-rm_comment', ); foreach ($data as $k => $v) { if (!isset($previous_options[$k])) { continue; } // The following values must be array if (!is_array($previous_options[$k])) { if (in_array($v, array('cdn-ori', 'cache-exc_cat', 'cache-exc_tag'))) { $previous_options[$k] = explode(',', $previous_options[$k]); $previous_options[$k] = array_filter($previous_options[$k]); } elseif (in_array($v, array('cache-mobile_rules', 'cache-exc_useragents', 'cache-exc_cookies'))) { $previous_options[$k] = explode('|', str_replace('\\ ', ' ', $previous_options[$k])); $previous_options[$k] = array_filter($previous_options[$k]); } elseif ( in_array($v, array( 'purge-timed_urls', 'cache-exc_qs', 'debug-ips', // 'crawler-exc_cpt', 'cdn-exc', 'optm-css_exc', 'optm-js_exc', )) ) { $previous_options[$k] = explode("\n", $previous_options[$k]); $previous_options[$k] = array_filter($previous_options[$k]); } } // Special handler for heartbeat if ($v == 'misc-heartbeat_front') { if (!$previous_options[$k]) { add_option('litespeed.conf.misc-heartbeat_front', true); add_option('litespeed.conf.misc-heartbeat_back', true); add_option('litespeed.conf.misc-heartbeat_editor', true); add_option('litespeed.conf.misc-heartbeat_front_ttl', 0); add_option('litespeed.conf.misc-heartbeat_back_ttl', 0); add_option('litespeed.conf.misc-heartbeat_editor_ttl', 0); } continue; } add_option('litespeed.conf.' . $v, $previous_options[$k]); } // Conv purge_by_post $data = array( '-' => 'purge-post_all', 'F' => 'purge-post_f', 'H' => 'purge-post_h', 'PGS' => 'purge-post_p', 'PGSRP' => 'purge-post_pwrp', 'A' => 'purge-post_a', 'Y' => 'purge-post_y', 'M' => 'purge-post_m', 'D' => 'purge-post_d', 'T' => 'purge-post_t', 'PT' => 'purge-post_pt', ); if (isset($previous_options['purge_by_post'])) { $purge_by_post = explode('.', $previous_options['purge_by_post']); foreach ($data as $k => $v) { add_option('litespeed.conf.' . $v, in_array($k, $purge_by_post)); } } // Conv 404/403/500 TTL $ttl_status = array(); if (isset($previous_options['403_ttl'])) { $ttl_status[] = '403 ' . $previous_options['403_ttl']; } if (isset($previous_options['404_ttl'])) { $ttl_status[] = '404 ' . $previous_options['404_ttl']; } if (isset($previous_options['500_ttl'])) { $ttl_status[] = '500 ' . $previous_options['500_ttl']; } add_option('litespeed.conf.cache-ttl_status', $ttl_status); /** * Resave cdn cfg from lscfg to separate cfg when upgrade to v1.7 * * NOTE: this can be left here as `add_option` bcos it is after the item `litespeed-cache-cdn_mapping` is converted * * @since 1.7 */ if (isset($previous_options['cdn_url'])) { $cdn_mapping = array( 'url' => $previous_options['cdn_url'], 'inc_img' => $previous_options['cdn_inc_img'], 'inc_css' => $previous_options['cdn_inc_css'], 'inc_js' => $previous_options['cdn_inc_js'], 'filetype' => $previous_options['cdn_filetype'], ); add_option('litespeed.conf.cdn-mapping', array($cdn_mapping)); Debug2::debug('[Data] plugin_upgrade option adding CDN map'); } /** * Move Exclude settings to separate item * * NOTE: this can be left here as `add_option` bcos it is after the relevant items are converted * * @since 2.3 */ if (isset($previous_options['forced_cache_uri'])) { add_option('litespeed.conf.cache-force_uri', $previous_options['forced_cache_uri']); } if (isset($previous_options['cache_uri_priv'])) { add_option('litespeed.conf.cache-priv_uri', $previous_options['cache_uri_priv']); } if (isset($previous_options['optm_excludes'])) { add_option('litespeed.conf.optm-exc', $previous_options['optm_excludes']); } if (isset($previous_options['excludes_uri'])) { add_option('litespeed.conf.cache-exc', $previous_options['excludes_uri']); } // Backup stale conf Debug2::debug('[Data] Backup stale conf'); delete_option('litespeed-cache-conf'); add_option('litespeed-cache-conf.bk', $previous_options); // Upgrade site_options if is network if (is_multisite()) { $ver = get_site_option('litespeed.conf._version'); if (!$ver) { Debug2::debug('[Data] Conv multisite'); $previous_site_options = get_site_option('litespeed-cache-conf'); $data = array( 'network_enabled' => 'cache', 'use_primary_settings' => 'use_primary_settings', 'auto_upgrade' => 'auto_upgrade', 'purge_upgrade' => 'purge-upgrade', 'cache_favicon' => 'cache-favicon', 'cache_resources' => 'cache-resources', 'mobileview_enabled' => 'cache-mobile', 'mobileview_rules' => 'cache-mobile_rules', 'login_cookie' => 'cache-login_cookie', 'nocache_cookies' => 'cache-exc_cookies', 'nocache_useragents' => 'cache-exc_useragents', 'cache_object' => 'object', 'cache_object_kind' => 'object-kind', 'cache_object_host' => 'object-host', 'cache_object_port' => 'object-port', 'cache_object_life' => 'object-life', 'cache_object_persistent' => 'object-persistent', 'cache_object_admin' => 'object-admin', 'cache_object_transients' => 'object-transients', 'cache_object_db_id' => 'object-db_id', 'cache_object_user' => 'object-user', 'cache_object_pswd' => 'object-psw', 'cache_browser' => 'cache-browser', 'cache_browser_ttl' => 'cache-ttl_browser', 'media_webp_replace' => 'img_optm-webp', ); foreach ($data as $k => $v) { if (!isset($previous_site_options[$k])) { continue; } // The following values must be array if (!is_array($previous_site_options[$k])) { if (in_array($v, array('cache-mobile_rules', 'cache-exc_useragents', 'cache-exc_cookies'))) { $previous_site_options[$k] = explode('|', str_replace('\\ ', ' ', $previous_site_options[$k])); $previous_site_options[$k] = array_filter($previous_site_options[$k]); } } add_site_option('litespeed.conf.' . $v, $previous_site_options[$k]); } // These are already converted to single record in single site $data = array('object-global_groups', 'object-non_persistent_groups'); foreach ($data as $v) { $old_data = get_option($v); if ($old_data) { add_site_option('litespeed.conf.' . $v, $old_data); } } delete_site_option('litespeed-cache-conf'); add_site_option('litespeed.conf._version', '3.0'); } } // delete tables Debug2::debug('[Data] Drop litespeed_optimizer'); $q = 'DROP TABLE IF EXISTS ' . $wpdb->prefix . 'litespeed_optimizer'; $wpdb->query($q); // Update image optm table Debug2::debug('[Data] Upgrade img_optm table'); $tb_exists = $wpdb->get_var('SHOW TABLES LIKE "' . $wpdb->prefix . 'litespeed_img_optm"'); if ($tb_exists) { $status_mapping = array( 'requested' => 3, 'notified' => 6, 'pulled' => 9, 'failed' => -1, 'miss' => -3, 'err' => -9, 'err_fetch' => -5, 'err_optm' => -7, 'xmeta' => -8, ); foreach ($status_mapping as $k => $v) { $q = 'UPDATE `' . $wpdb->prefix . "litespeed_img_optm` SET optm_status='$v' WHERE optm_status='$k'"; $wpdb->query($q); } $q = 'ALTER TABLE `' . $wpdb->prefix . 'litespeed_img_optm` DROP INDEX `post_id_2`, DROP INDEX `root_id`, DROP INDEX `src_md5`, DROP INDEX `srcpath_md5`, DROP COLUMN `srcpath_md5`, DROP COLUMN `src_md5`, DROP COLUMN `root_id`, DROP COLUMN `target_saved`, DROP COLUMN `webp_saved`, DROP COLUMN `server_info`, MODIFY COLUMN `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, MODIFY COLUMN `optm_status` tinyint(4) NOT NULL DEFAULT 0, MODIFY COLUMN `src` text COLLATE utf8mb4_unicode_ci NOT NULL '; $wpdb->query($q); } delete_option('litespeed-recommended'); Debug2::debug('[Data] litespeed_update_3_0 done!'); add_option('litespeed.conf._version', '3.0'); } src/task.cls.php 0000644 00000013661 15246276230 0007604 0 ustar 00 <?php /** * The cron task class. * * @since 1.1.3 * @since 1.5 Moved into /inc */ namespace LiteSpeed; defined('WPINC') || exit(); class Task extends Root { const LOG_TAG = '⏰'; private static $_triggers = array( Base::O_IMG_OPTM_CRON => array('name' => 'litespeed_task_imgoptm_pull', 'hook' => 'LiteSpeed\Img_Optm::start_async_cron'), // always fetch immediately Base::O_OPTM_CSS_ASYNC => array('name' => 'litespeed_task_ccss', 'hook' => 'LiteSpeed\CSS::cron_ccss'), Base::O_OPTM_UCSS => array('name' => 'litespeed_task_ucss', 'hook' => 'LiteSpeed\UCSS::cron'), Base::O_MEDIA_VPI_CRON => array('name' => 'litespeed_task_vpi', 'hook' => 'LiteSpeed\VPI::cron'), Base::O_MEDIA_PLACEHOLDER_RESP_ASYNC => array('name' => 'litespeed_task_lqip', 'hook' => 'LiteSpeed\Placeholder::cron'), Base::O_DISCUSS_AVATAR_CRON => array('name' => 'litespeed_task_avatar', 'hook' => 'LiteSpeed\Avatar::cron'), Base::O_IMG_OPTM_AUTO => array('name' => 'litespeed_task_imgoptm_req', 'hook' => 'LiteSpeed\Img_Optm::cron_auto_request'), Base::O_CRAWLER => array('name' => 'litespeed_task_crawler', 'hook' => 'LiteSpeed\Crawler::start_async_cron'), // Set crawler to last one to use above results ); private static $_guest_options = array(Base::O_OPTM_CSS_ASYNC, Base::O_OPTM_UCSS, Base::O_MEDIA_VPI); const FILTER_CRAWLER = 'litespeed_crawl_filter'; const FILTER = 'litespeed_filter'; /** * Keep all tasks in cron * * @since 3.0 * @access public */ public function init() { self::debug2('Init'); add_filter('cron_schedules', array($this, 'lscache_cron_filter')); $guest_optm = $this->conf(Base::O_GUEST) && $this->conf(Base::O_GUEST_OPTM); foreach (self::$_triggers as $id => $trigger) { if ($id != Base::O_IMG_OPTM_CRON && !$this->conf($id)) { if (!$guest_optm || !in_array($id, self::$_guest_options)) { continue; } } // Special check for crawler if ($id == Base::O_CRAWLER) { if (!Router::can_crawl()) { continue; } add_filter('cron_schedules', array($this, 'lscache_cron_filter_crawler')); } if (!wp_next_scheduled($trigger['name'])) { self::debug('Cron hook register [name] ' . $trigger['name']); wp_schedule_event(time(), $id == Base::O_CRAWLER ? self::FILTER_CRAWLER : self::FILTER, $trigger['name']); } add_action($trigger['name'], $trigger['hook']); } } /** * Handle all async noabort requests * * @since 5.5 */ public static function async_litespeed_handler() { $hash_data = self::get_option('async_call-hash', array()); if (!$hash_data || !is_array($hash_data) || empty($hash_data['hash']) || empty($hash_data['ts'])) { self::debug('async_litespeed_handler no hash data', $hash_data); return; } if (time() - $hash_data['ts'] > 120 || empty($_GET['nonce']) || $_GET['nonce'] != $hash_data['hash']) { self::debug('async_litespeed_handler nonce mismatch'); return; } self::delete_option('async_call-hash'); $type = Router::verify_type(); self::debug('type=' . $type); // Don't lock up other requests while processing session_write_close(); switch ($type) { case 'crawler': Crawler::async_handler(); break; case 'crawler_force': Crawler::async_handler(true); break; case 'imgoptm': Img_Optm::async_handler(); break; case 'imgoptm_force': Img_Optm::async_handler(true); break; default: } } /** * Async caller wrapper func * * @since 5.5 */ public static function async_call($type) { $hash = Str::rrand(32); self::update_option('async_call-hash', array('hash' => $hash, 'ts' => time())); $args = array( 'timeout' => 0.01, 'blocking' => false, 'sslverify' => false, // 'cookies' => $_COOKIE, ); $qs = array( 'action' => 'async_litespeed', 'nonce' => $hash, Router::TYPE => $type, ); $url = add_query_arg($qs, admin_url('admin-ajax.php')); self::debug('async call to ' . $url); wp_safe_remote_post(esc_url_raw($url), $args); } /** * Clean all potential existing crons * * @since 3.0 * @access public */ public static function destroy() { Utility::compatibility(); array_map('wp_clear_scheduled_hook', array_column(self::$_triggers, 'name')); } /** * Try to clean the crons if disabled * * @since 3.0 * @access public */ public function try_clean($id) { // Clean v2's leftover cron ( will remove in v3.1 ) // foreach ( wp_get_ready_cron_jobs() as $hooks ) { // foreach ( $hooks as $hook => $v ) { // if ( strpos( $hook, 'litespeed_' ) === 0 && ( substr( $hook, -8 ) === '_trigger' || strpos( $hook, 'litespeed_task_' ) !== 0 ) ) { // self::debug( 'Cron clear legacy [hook] ' . $hook ); // wp_clear_scheduled_hook( $hook ); // } // } // } if ($id && !empty(self::$_triggers[$id])) { if (!$this->conf($id) || ($id == Base::O_CRAWLER && !Router::can_crawl())) { self::debug('Cron clear [id] ' . $id . ' [hook] ' . self::$_triggers[$id]['name']); wp_clear_scheduled_hook(self::$_triggers[$id]['name']); } return; } self::debug('❌ Unknown cron [id] ' . $id); } /** * Register cron interval imgoptm * * @since 1.6.1 * @access public */ public function lscache_cron_filter($schedules) { if (!array_key_exists(self::FILTER, $schedules)) { $schedules[self::FILTER] = array( 'interval' => 60, 'display' => __('Every Minute', 'litespeed-cache'), ); } return $schedules; } /** * Register cron interval * * @since 1.1.0 * @access public */ public function lscache_cron_filter_crawler($schedules) { $CRAWLER_RUN_INTERVAL = defined('LITESPEED_CRAWLER_RUN_INTERVAL') ? LITESPEED_CRAWLER_RUN_INTERVAL : 600; // $wp_schedules = wp_get_schedules(); if (!array_key_exists(self::FILTER_CRAWLER, $schedules)) { // self::debug('Crawler cron log: cron filter '.$interval.' added'); $schedules[self::FILTER_CRAWLER] = array( 'interval' => $CRAWLER_RUN_INTERVAL, 'display' => __('LiteSpeed Crawler Cron', 'litespeed-cache'), ); } return $schedules; } } src/activation.cls.php 0000644 00000035636 15246276230 0011011 0 ustar 00 <?php /** * The plugin activation class. * * @since 1.1.0 * @since 1.5 Moved into /inc * @package LiteSpeed * @subpackage LiteSpeed/inc * @author LiteSpeed Technologies <info@litespeedtech.com> */ namespace LiteSpeed; defined('WPINC') || exit(); class Activation extends Base { const TYPE_UPGRADE = 'upgrade'; const TYPE_INSTALL_3RD = 'install_3rd'; const TYPE_INSTALL_ZIP = 'install_zip'; const TYPE_DISMISS_RECOMMENDED = 'dismiss_recommended'; const NETWORK_TRANSIENT_COUNT = 'lscwp_network_count'; private static $_data_file; /** * Construct * * @since 4.1 */ public function __construct() { self::$_data_file = LSCWP_CONTENT_DIR . '/' . self::CONF_FILE; } /** * The activation hook callback. * * @since 1.0.0 * @access public */ public static function register_activation() { global $wp_version; $advanced_cache = LSCWP_CONTENT_DIR . '/advanced-cache.php'; if (version_compare($wp_version, '5.3', '<') && !file_exists($advanced_cache)) { $file_pointer = fopen($advanced_cache, 'w'); fwrite($file_pointer, "<?php\n\n// A compatibility placeholder for WordPress < v5.3\n// Created by LSCWP v6.1+"); fclose($file_pointer); } $count = 0; !defined('LSCWP_LOG_TAG') && define('LSCWP_LOG_TAG', 'Activate_' . get_current_blog_id()); /* Network file handler */ if (is_multisite()) { $count = self::get_network_count(); if ($count !== false) { $count = intval($count) + 1; set_site_transient(self::NETWORK_TRANSIENT_COUNT, $count, DAY_IN_SECONDS); } if (!is_network_admin()) { if ($count === 1) { // Only itself is activated, set .htaccess with only CacheLookUp try { Htaccess::cls()->insert_ls_wrapper(); } catch (\Exception $ex) { Admin_Display::error($ex->getMessage()); } } } } self::cls()->update_files(); if (defined('LSCWP_REF') && LSCWP_REF == 'whm') { GUI::update_option(GUI::WHM_MSG, GUI::WHM_MSG_VAL); } } /** * Uninstall plugin * @since 1.1.0 */ public static function uninstall_litespeed_cache() { Task::destroy(); // Delete options foreach (Conf::cls()->load_default_vals() as $k => $v) { Base::delete_option($k); } // Delete site options if (is_multisite()) { foreach (Conf::cls()->load_default_site_vals() as $k => $v) { Base::delete_site_option($k); } } // Delete avatar table Data::cls()->tables_del(); if (file_exists(LITESPEED_STATIC_DIR)) { File::rrmdir(LITESPEED_STATIC_DIR); } Cloud::version_check('uninstall'); // Files has been deleted when deactivated } /** * Get the blog ids for the network. Accepts function arguments. * * Will use wp_get_sites for WP versions less than 4.6 * * @since 1.0.12 * @access public * @return array The array of blog ids. */ public static function get_network_ids($args = array()) { global $wp_version; if (version_compare($wp_version, '4.6', '<')) { $blogs = wp_get_sites($args); if (!empty($blogs)) { foreach ($blogs as $key => $blog) { $blogs[$key] = $blog['blog_id']; } } } else { $args['fields'] = 'ids'; $blogs = get_sites($args); } return $blogs; } /** * Gets the count of active litespeed cache plugins on multisite. * * @since 1.0.12 * @access private */ private static function get_network_count() { $count = get_site_transient(self::NETWORK_TRANSIENT_COUNT); if ($count !== false) { return intval($count); } // need to update $default = array(); $count = 0; $sites = self::get_network_ids(array('deleted' => 0)); if (empty($sites)) { return false; } foreach ($sites as $site) { $bid = is_object($site) && property_exists($site, 'blog_id') ? $site->blog_id : $site; $plugins = get_blog_option($bid, 'active_plugins', $default); if (!empty($plugins) && in_array(LSCWP_BASENAME, $plugins, true)) { $count++; } } /** * In case this is called outside the admin page * @see https://codex.wordpress.org/Function_Reference/is_plugin_active_for_network * @since 2.0 */ if (!function_exists('is_plugin_active_for_network')) { require_once ABSPATH . '/wp-admin/includes/plugin.php'; } if (is_plugin_active_for_network(LSCWP_BASENAME)) { $count++; } return $count; } /** * Is this deactivate call the last active installation on the multisite network? * * @since 1.0.12 * @access private */ private static function is_deactivate_last() { $count = self::get_network_count(); if ($count === false) { return false; } if ($count !== 1) { // Not deactivating the last one. $count--; set_site_transient(self::NETWORK_TRANSIENT_COUNT, $count, DAY_IN_SECONDS); return false; } delete_site_transient(self::NETWORK_TRANSIENT_COUNT); return true; } /** * The deactivation hook callback. * * Initializes all clean up functionalities. * * @since 1.0.0 * @access public */ public static function register_deactivation() { Task::destroy(); !defined('LSCWP_LOG_TAG') && define('LSCWP_LOG_TAG', 'Deactivate_' . get_current_blog_id()); Purge::purge_all(); if (is_multisite()) { if (!self::is_deactivate_last()) { if (is_network_admin()) { // Still other activated subsite left, set .htaccess with only CacheLookUp try { Htaccess::cls()->insert_ls_wrapper(); } catch (\Exception $ex) { Admin_Display::error($ex->getMessage()); } } return; } } /* 1) wp-config.php; */ try { self::cls()->_manage_wp_cache_const(false); } catch (\Exception $ex) { error_log('In wp-config.php: WP_CACHE could not be set to false during deactivation!'); Admin_Display::error($ex->getMessage()); } /* 2) adv-cache.php; Dropped in v3.0.4 */ /* 3) object-cache.php; */ Object_Cache::cls()->del_file(); /* 4) .htaccess; */ try { Htaccess::cls()->clear_rules(); } catch (\Exception $ex) { Admin_Display::error($ex->getMessage()); } /* 5) .litespeed_conf.dat; */ self::_del_conf_data_file(); // delete in case it's not deleted prior to deactivation. GUI::dismiss_whm(); } /** * Manage related files based on plugin latest conf * * NOTE: Only trigger this in backend admin access for efficiency concern * * Handle files: * 1) wp-config.php; * 2) adv-cache.php; * 3) object-cache.php; * 4) .htaccess; * 5) .litespeed_conf.dat; * * @since 3.0 * @access public */ public function update_files() { Debug2::debug('🗂️ [Activation] update_files'); // Update cache setting `_CACHE` $this->cls('Conf')->define_cache(); // Site options applied already $options = $this->get_options(); /* 1) wp-config.php; */ try { $this->_manage_wp_cache_const($options[self::_CACHE]); } catch (\Exception $ex) { // Add msg to admin page or CLI Admin_Display::error($ex->getMessage()); } /* 2) adv-cache.php; Dropped in v3.0.4 */ /* 3) object-cache.php; */ if ($options[self::O_OBJECT] && (!$options[self::O_DEBUG_DISABLE_ALL] || is_multisite())) { $this->cls('Object_Cache')->update_file($options); } else { $this->cls('Object_Cache')->del_file(); // Note: because it doesn't reconnect, which caused setting page OC option changes delayed, thus may meet Connect Test Failed issue (Next refresh will correct it). Not a big deal, will keep as is. } /* 4) .htaccess; */ try { $this->cls('Htaccess')->update($options); } catch (\Exception $ex) { Admin_Display::error($ex->getMessage()); } /* 5) .litespeed_conf.dat; */ if (($options[self::O_GUEST] || $options[self::O_OBJECT]) && (!$options[self::O_DEBUG_DISABLE_ALL] || is_multisite())) { $this->_update_conf_data_file($options); } } /** * Delete data conf file * * @since 4.1 */ private static function _del_conf_data_file() { if (file_exists(self::$_data_file)) { unlink(self::$_data_file); } } /** * Update data conf file for guest mode & object cache * * @since 4.1 */ private function _update_conf_data_file($options) { $ids = array(); if ($options[self::O_OBJECT]) { $this_ids = array( self::O_DEBUG, self::O_OBJECT_KIND, self::O_OBJECT_HOST, self::O_OBJECT_PORT, self::O_OBJECT_LIFE, self::O_OBJECT_USER, self::O_OBJECT_PSWD, self::O_OBJECT_DB_ID, self::O_OBJECT_PERSISTENT, self::O_OBJECT_ADMIN, self::O_OBJECT_TRANSIENTS, self::O_OBJECT_GLOBAL_GROUPS, self::O_OBJECT_NON_PERSISTENT_GROUPS, ); $ids = array_merge($ids, $this_ids); } if ($options[self::O_GUEST]) { $this_ids = array(self::HASH, self::O_CACHE_LOGIN_COOKIE, self::O_DEBUG_IPS, self::O_UTIL_NO_HTTPS_VARY, self::O_GUEST_UAS, self::O_GUEST_IPS); $ids = array_merge($ids, $this_ids); } $data = array(); foreach ($ids as $v) { $data[$v] = $options[$v]; } $data = \json_encode($data); $old_data = File::read(self::$_data_file); if ($old_data != $data) { defined('LSCWP_LOG') && Debug2::debug('[Activation] Updating .litespeed_conf.dat'); File::save(self::$_data_file, $data); } } /** * Update the WP_CACHE variable in the wp-config.php file. * * If enabling, check if the variable is defined, and if not, define it. * Vice versa for disabling. * * @since 1.0.0 * @since 3.0 Refactored * @access private */ private function _manage_wp_cache_const($enable) { if ($enable) { if (defined('WP_CACHE') && WP_CACHE) { return false; } } elseif (!defined('WP_CACHE') || (defined('WP_CACHE') && !WP_CACHE)) { return false; } if (apply_filters('litespeed_wpconfig_readonly', false)) { throw new \Exception('wp-config file is forbidden to modify due to API hook: litespeed_wpconfig_readonly'); } /** * Follow WP's logic to locate wp-config file * @see wp-load.php */ $conf_file = ABSPATH . 'wp-config.php'; if (!file_exists($conf_file)) { $conf_file = dirname(ABSPATH) . '/wp-config.php'; } $content = File::read($conf_file); if (!$content) { throw new \Exception('wp-config file content is empty: ' . $conf_file); } // Remove the line `define('WP_CACHE', true/false);` first if (defined('WP_CACHE')) { $content = preg_replace('/define\(\s*([\'"])WP_CACHE\1\s*,\s*\w+\s*\)\s*;/sU', '', $content); } // Insert const if ($enable) { $content = preg_replace('/^<\?php/', "<?php\ndefine( 'WP_CACHE', true );", $content); } $res = File::save($conf_file, $content, false, false, false); if ($res !== true) { throw new \Exception('wp-config.php operation failed when changing `WP_CACHE` const: ' . $res); } return true; } /** * Handle auto update * * @since 2.7.2 * @access public */ public function auto_update() { if (!$this->conf(Base::O_AUTO_UPGRADE)) { return; } add_filter('auto_update_plugin', array($this, 'auto_update_hook'), 10, 2); } /** * Auto upgrade hook * * @since 3.0 * @access public */ public function auto_update_hook($update, $item) { if (!empty($item->slug) && 'litespeed-cache' === $item->slug) { $auto_v = Cloud::version_check('auto_update_plugin'); if (!empty($auto_v['latest']) && !empty($item->new_version) && $auto_v['latest'] === $item->new_version) { return true; } } return $update; // Else, use the normal API response to decide whether to update or not } /** * Upgrade LSCWP * * @since 2.9 * @access public */ public function upgrade() { $plugin = Core::PLUGIN_FILE; /** * @see wp-admin/update.php */ include_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php'; include_once ABSPATH . 'wp-admin/includes/file.php'; include_once ABSPATH . 'wp-admin/includes/misc.php'; try { ob_start(); $skin = new \WP_Ajax_Upgrader_Skin(); $upgrader = new \Plugin_Upgrader($skin); $result = $upgrader->upgrade($plugin); if (!is_plugin_active($plugin)) { // todo: upgrade should reactivate the plugin again by WP. Need to check why disabled after upgraded. activate_plugin($plugin, '', is_multisite()); } ob_end_clean(); } catch (\Exception $e) { Admin_Display::error(__('Failed to upgrade.', 'litespeed-cache')); return; } if (is_wp_error($result)) { Admin_Display::error(__('Failed to upgrade.', 'litespeed-cache')); return; } Admin_Display::success(__('Upgraded successfully.', 'litespeed-cache')); } /** * Detect if the plugin is active or not * * @since 1.0 */ public function dash_notifier_is_plugin_active($plugin) { include_once ABSPATH . 'wp-admin/includes/plugin.php'; $plugin_path = $plugin . '/' . $plugin . '.php'; return is_plugin_active($plugin_path); } /** * Detect if the plugin is installed or not * * @since 1.0 */ public function dash_notifier_is_plugin_installed($plugin) { include_once ABSPATH . 'wp-admin/includes/plugin.php'; $plugin_path = $plugin . '/' . $plugin . '.php'; $valid = validate_plugin($plugin_path); return !is_wp_error($valid); } /** * Grab a plugin info from WordPress * * @since 1.0 */ public function dash_notifier_get_plugin_info($slug) { include_once ABSPATH . 'wp-admin/includes/plugin-install.php'; $result = plugins_api('plugin_information', array('slug' => $slug)); if (is_wp_error($result)) { return false; } return $result; } /** * Install the 3rd party plugin * * @since 1.0 */ public function dash_notifier_install_3rd() { !defined('SILENCE_INSTALL') && define('SILENCE_INSTALL', true); $slug = !empty($_GET['plugin']) ? $_GET['plugin'] : false; // Check if plugin is installed already if (!$slug || $this->dash_notifier_is_plugin_active($slug)) { return; } /** * @see wp-admin/update.php */ include_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php'; include_once ABSPATH . 'wp-admin/includes/file.php'; include_once ABSPATH . 'wp-admin/includes/misc.php'; $plugin_path = $slug . '/' . $slug . '.php'; if (!$this->dash_notifier_is_plugin_installed($slug)) { $plugin_info = $this->dash_notifier_get_plugin_info($slug); if (!$plugin_info) { return; } // Try to install plugin try { ob_start(); $skin = new \Automatic_Upgrader_Skin(); $upgrader = new \Plugin_Upgrader($skin); $result = $upgrader->install($plugin_info->download_link); ob_end_clean(); } catch (\Exception $e) { return; } } if (!is_plugin_active($plugin_path)) { activate_plugin($plugin_path); } } /** * Handle all request actions from main cls * * @since 2.9 * @access public */ public function handler() { $type = Router::verify_type(); switch ($type) { case self::TYPE_UPGRADE: $this->upgrade(); break; case self::TYPE_INSTALL_3RD: $this->dash_notifier_install_3rd(); break; case self::TYPE_DISMISS_RECOMMENDED: Cloud::reload_summary(); Cloud::save_summary(array('news.new' => 0)); break; case self::TYPE_INSTALL_ZIP: Cloud::reload_summary(); $summary = Cloud::get_summary(); if (!empty($summary['news.zip'])) { Cloud::save_summary(array('news.new' => 0)); $this->cls('Debug2')->beta_test($summary['zip']); } break; default: break; } Admin::redirect(); } } src/utility.cls.php 0000644 00000051252 15246276230 0010343 0 ustar 00 <?php /** * The utility class. * * @since 1.1.5 * @since 1.5 Moved into /inc */ namespace LiteSpeed; defined('WPINC') || exit(); class Utility extends Root { private static $_internal_domains; /** * Validate regex * * @since 1.0.9 * @since 3.0 Moved here from admin-settings.cls * @access public * @return bool True for valid rules, false otherwise. */ public static function syntax_checker($rules) { return preg_match(self::arr2regex($rules), '') !== false; } /** * Combine regex array to regex rule * * @since 3.0 */ public static function arr2regex($arr, $drop_delimiter = false) { $arr = self::sanitize_lines($arr); $new_arr = array(); foreach ($arr as $v) { $new_arr[] = preg_quote($v, '#'); } $regex = implode('|', $new_arr); $regex = str_replace(' ', '\\ ', $regex); if ($drop_delimiter) { return $regex; } return '#' . $regex . '#'; } /** * Replace wildcard to regex * * @since 3.2.2 */ public static function wildcard2regex($string) { if (is_array($string)) { return array_map(__CLASS__ . '::wildcard2regex', $string); } if (strpos($string, '*') !== false) { $string = preg_quote($string, '#'); $string = str_replace('\*', '.*', $string); } return $string; } /** * Check if an URL or current page is REST req or not * * @since 2.9.3 * @deprecated 2.9.4 Moved to REST class * @access public */ public static function is_rest($url = false) { return false; } /** * Get current page type * * @since 2.9 */ public static function page_type() { global $wp_query; $page_type = 'default'; if ($wp_query->is_page) { $page_type = is_front_page() ? 'front' : 'page'; } elseif ($wp_query->is_home) { $page_type = 'home'; } elseif ($wp_query->is_single) { // $page_type = $wp_query->is_attachment ? 'attachment' : 'single'; $page_type = get_post_type(); } elseif ($wp_query->is_category) { $page_type = 'category'; } elseif ($wp_query->is_tag) { $page_type = 'tag'; } elseif ($wp_query->is_tax) { $page_type = 'tax'; // $page_type = get_queried_object()->taxonomy; } elseif ($wp_query->is_archive) { if ($wp_query->is_day) { $page_type = 'day'; } elseif ($wp_query->is_month) { $page_type = 'month'; } elseif ($wp_query->is_year) { $page_type = 'year'; } elseif ($wp_query->is_author) { $page_type = 'author'; } else { $page_type = 'archive'; } } elseif ($wp_query->is_search) { $page_type = 'search'; } elseif ($wp_query->is_404) { $page_type = '404'; } return $page_type; // if ( is_404() ) { // $page_type = '404'; // } // elseif ( is_singular() ) { // $page_type = get_post_type(); // } // elseif ( is_home() && get_option( 'show_on_front' ) == 'page' ) { // $page_type = 'home'; // } // elseif ( is_front_page() ) { // $page_type = 'front'; // } // elseif ( is_tax() ) { // $page_type = get_queried_object()->taxonomy; // } // elseif ( is_category() ) { // $page_type = 'category'; // } // elseif ( is_tag() ) { // $page_type = 'tag'; // } // return $page_type; } /** * Get ping speed * * @since 2.9 */ public static function ping($domain) { if (strpos($domain, ':')) { $domain = parse_url($domain, PHP_URL_HOST); } $starttime = microtime(true); $file = fsockopen($domain, 443, $errno, $errstr, 10); $stoptime = microtime(true); $status = 0; if (!$file) { $status = 99999; } // Site is down else { fclose($file); $status = ($stoptime - $starttime) * 1000; $status = floor($status); } Debug2::debug("[Util] ping [Domain] $domain \t[Speed] $status"); return $status; } /** * Set seconds/timestamp to readable format * * @since 1.6.5 * @access public */ public static function readable_time($seconds_or_timestamp, $timeout = 3600, $forward = false) { if (strlen($seconds_or_timestamp) == 10) { $seconds = time() - $seconds_or_timestamp; if ($seconds > $timeout) { return date('m/d/Y H:i:s', $seconds_or_timestamp + LITESPEED_TIME_OFFSET); } } else { $seconds = $seconds_or_timestamp; } $res = ''; if ($seconds > 86400) { $num = floor($seconds / 86400); $res .= $num . 'd'; $seconds %= 86400; } if ($seconds > 3600) { if ($res) { $res .= ', '; } $num = floor($seconds / 3600); $res .= $num . 'h'; $seconds %= 3600; } if ($seconds > 60) { if ($res) { $res .= ', '; } $num = floor($seconds / 60); $res .= $num . 'm'; $seconds %= 60; } if ($seconds > 0) { if ($res) { $res .= ' '; } $res .= $seconds . 's'; } if (!$res) { return $forward ? __('right now', 'litespeed-cache') : __('just now', 'litespeed-cache'); } $res = $forward ? $res : sprintf(__(' %s ago', 'litespeed-cache'), $res); return $res; } /** * Convert array to string * * @since 1.6 * @access public */ public static function arr2str($arr) { if (!is_array($arr)) { return $arr; } return base64_encode(\json_encode($arr)); } /** * Get human readable size * * @since 1.6 * @access public */ public static function real_size($filesize, $is_1000 = false) { $unit = $is_1000 ? 1000 : 1024; if ($filesize >= pow($unit, 3)) { $filesize = round(($filesize / pow($unit, 3)) * 100) / 100 . 'G'; } elseif ($filesize >= pow($unit, 2)) { $filesize = round(($filesize / pow($unit, 2)) * 100) / 100 . 'M'; } elseif ($filesize >= $unit) { $filesize = round(($filesize / $unit) * 100) / 100 . 'K'; } else { $filesize = $filesize . 'B'; } return $filesize; } /** * Parse attributes from string * * @since 1.2.2 * @since 1.4 Moved from optimize to utility * @access private * @param string $str * @return array All the attributes */ public static function parse_attr($str) { $attrs = array(); preg_match_all('#([\w-]+)=(["\'])([^\2]*)\2#isU', $str, $matches, PREG_SET_ORDER); foreach ($matches as $match) { $attrs[$match[1]] = trim($match[3]); } return $attrs; } /** * Check if an array has a string * * Support $ exact match * * @since 1.3 * @access private * @param string $needle The string to search with * @param array $haystack * @return bool|string False if not found, otherwise return the matched string in haystack. */ public static function str_hit_array($needle, $haystack, $has_ttl = false) { if (!$haystack) { return false; } /** * Safety check to avoid PHP warning * @see https://github.com/litespeedtech/lscache_wp/pull/131/commits/45fc03af308c7d6b5583d1664fad68f75fb6d017 */ if (!is_array($haystack)) { Debug2::debug('[Util] ❌ bad param in str_hit_array()!'); return false; } $hit = false; $this_ttl = 0; foreach ($haystack as $item) { if (!$item) { continue; } if ($has_ttl) { $this_ttl = 0; $item = explode(' ', $item); if (!empty($item[1])) { $this_ttl = $item[1]; } $item = $item[0]; } if (substr($item, 0, 1) === '^' && substr($item, -1) === '$') { // do exact match if (substr($item, 1, -1) === $needle) { $hit = $item; break; } } elseif (substr($item, -1) === '$') { // match end if (substr($item, 0, -1) === substr($needle, -strlen($item) + 1)) { $hit = $item; break; } } elseif (substr($item, 0, 1) === '^') { // match beginning if (substr($item, 1) === substr($needle, 0, strlen($item) - 1)) { $hit = $item; break; } } else { if (strpos($needle, $item) !== false) { $hit = $item; break; } } } if ($hit) { if ($has_ttl) { return array($hit, $this_ttl); } return $hit; } return false; } /** * Improve compatibility to PHP old versions * * @since 1.2.2 * */ public static function compatibility() { require_once LSCWP_DIR . 'lib/php-compatibility.func.php'; } /** * Convert URI to URL * * @since 1.3 * @access public * @param string $uri `xx/xx.html` or `/subfolder/xx/xx.html` * @return string http://www.example.com/subfolder/xx/xx.html */ public static function uri2url($uri) { if (substr($uri, 0, 1) === '/') { self::domain_const(); $url = LSCWP_DOMAIN . $uri; } else { $url = home_url('/') . $uri; } return $url; } /** * Convert URL to basename (filename) * * @since 4.7 */ public static function basename($url) { $url = trim($url); $uri = @parse_url($url, PHP_URL_PATH); $basename = pathinfo($uri, PATHINFO_BASENAME); return $basename; } /** * Drop .webp and .avif if existed in filename * * @since 4.7 */ public static function drop_webp($filename) { if (in_array(substr($filename, -5), array('.webp', '.avif'))) { $filename = substr($filename, 0, -5); } return $filename; } /** * Convert URL to URI * * @since 1.2.2 * @since 1.6.2.1 Added 2nd param keep_qs * @access public */ public static function url2uri($url, $keep_qs = false) { $url = trim($url); $uri = @parse_url($url, PHP_URL_PATH); $qs = @parse_url($url, PHP_URL_QUERY); if (!$keep_qs || !$qs) { return $uri; } return $uri . '?' . $qs; } /** * Get attachment relative path to upload folder * * @since 3.0 * @access public * @param string `https://aa.com/bbb/wp-content/upload/2018/08/test.jpg` or `/bbb/wp-content/upload/2018/08/test.jpg` * @return string `2018/08/test.jpg` */ public static function att_short_path($url) { if (!defined('LITESPEED_UPLOAD_PATH')) { $_wp_upload_dir = wp_upload_dir(); $upload_path = self::url2uri($_wp_upload_dir['baseurl']); define('LITESPEED_UPLOAD_PATH', $upload_path); } $local_file = self::url2uri($url); $short_path = substr($local_file, strlen(LITESPEED_UPLOAD_PATH) + 1); return $short_path; } /** * Make URL to be relative * * NOTE: for subfolder home_url, will keep subfolder part (strip nothing but scheme and host) * * @param string $url * @return string Relative URL, start with / */ public static function make_relative($url) { // replace home_url if the url is full url self::domain_const(); if (strpos($url, LSCWP_DOMAIN) === 0) { $url = substr($url, strlen(LSCWP_DOMAIN)); } return trim($url); } /** * Convert URL to domain only * * @since 1.7.1 */ public static function parse_domain($url) { $url = @parse_url($url); if (empty($url['host'])) { return ''; } if (!empty($url['scheme'])) { return $url['scheme'] . '://' . $url['host']; } return '//' . $url['host']; } /** * Drop protocol `https:` from https://example.com * * @since 3.3 */ public static function noprotocol($url) { $tmp = parse_url(trim($url)); if (!empty($tmp['scheme'])) { $url = str_replace($tmp['scheme'] . ':', '', $url); } return $url; } /** * Validate ip v4 * @since 5.5 */ public static function valid_ipv4($ip) { return filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE); } /** * Generate domain const * * This will generate http://www.example.com even there is a subfolder in home_url setting * * Conf LSCWP_DOMAIN has NO trailing / * * @since 1.3 * @access public */ public static function domain_const() { if (defined('LSCWP_DOMAIN')) { return; } self::compatibility(); $domain = http_build_url(get_home_url(), array(), HTTP_URL_STRIP_ALL); define('LSCWP_DOMAIN', $domain); } /** * Array map one textarea to sanitize the url * * @since 1.3 * @access public * @param string $content * @param bool $type String handler type * @return string|array */ public static function sanitize_lines($arr, $type = null) { $types = $type ? explode(',', $type) : array(); if (!$arr) { if ($type === 'string') { return ''; } return array(); } if (!is_array($arr)) { $arr = explode("\n", $arr); } $arr = array_map('trim', $arr); $changed = false; if (in_array('uri', $types)) { $arr = array_map(__CLASS__ . '::url2uri', $arr); $changed = true; } if (in_array('basename', $types)) { $arr = array_map(__CLASS__ . '::basename', $arr); $changed = true; } if (in_array('drop_webp', $types)) { $arr = array_map(__CLASS__ . '::drop_webp', $arr); $changed = true; } if (in_array('relative', $types)) { $arr = array_map(__CLASS__ . '::make_relative', $arr); // Remove domain $changed = true; } if (in_array('domain', $types)) { $arr = array_map(__CLASS__ . '::parse_domain', $arr); // Only keep domain $changed = true; } if (in_array('noprotocol', $types)) { $arr = array_map(__CLASS__ . '::noprotocol', $arr); // Drop protocol, `https://example.com` -> `//example.com` $changed = true; } if (in_array('trailingslash', $types)) { $arr = array_map('trailingslashit', $arr); // Append trailing slash, `https://example.com` -> `https://example.com/` $changed = true; } if ($changed) { $arr = array_map('trim', $arr); } $arr = array_unique($arr); $arr = array_filter($arr); if (in_array('string', $types)) { return implode("\n", $arr); } return $arr; } /** * Builds an url with an action and a nonce. * * Assumes user capabilities are already checked. * * @since 1.6 Changed order of 2nd&3rd param, changed 3rd param `append_str` to 2nd `type` * @access public * @return string The built url. */ public static function build_url($action, $type = false, $is_ajax = false, $page = null, $append_arr = array()) { $prefix = '?'; if ($page === '_ori') { $page = true; $append_arr['_litespeed_ori'] = 1; } if (!$is_ajax) { if ($page) { // If use admin url if ($page === true) { $page = 'admin.php'; } else { if (strpos($page, '?') !== false) { $prefix = '&'; } } $combined = $page . $prefix . Router::ACTION . '=' . $action; } else { // Current page rebuild URL $params = $_GET; if (!empty($params)) { if (isset($params[Router::ACTION])) { unset($params[Router::ACTION]); } if (isset($params['_wpnonce'])) { unset($params['_wpnonce']); } if (!empty($params)) { $prefix .= http_build_query($params) . '&'; } } global $pagenow; $combined = $pagenow . $prefix . Router::ACTION . '=' . $action; } } else { $combined = 'admin-ajax.php?action=litespeed_ajax&' . Router::ACTION . '=' . $action; } if (is_network_admin()) { $prenonce = network_admin_url($combined); } else { $prenonce = admin_url($combined); } $url = wp_nonce_url($prenonce, $action, Router::NONCE); if ($type) { // Remove potential param `type` from url $url = parse_url(htmlspecialchars_decode($url)); parse_str($url['query'], $query); $built_arr = array_merge($query, array(Router::TYPE => $type)); if ($append_arr) { $built_arr = array_merge($built_arr, $append_arr); } $url['query'] = http_build_query($built_arr); self::compatibility(); $url = http_build_url($url); $url = htmlspecialchars($url, ENT_QUOTES, 'UTF-8'); } return $url; } /** * Check if the host is the internal host * * @since 1.2.3 * */ public static function internal($host) { if (!defined('LITESPEED_FRONTEND_HOST')) { if (defined('WP_HOME')) { $home_host = WP_HOME; // Also think of `WP_SITEURL` } else { $home_host = get_option('home'); } define('LITESPEED_FRONTEND_HOST', parse_url($home_host, PHP_URL_HOST)); } if ($host === LITESPEED_FRONTEND_HOST) { return true; } /** * Filter for multiple domains * @since 2.9.4 */ if (!isset(self::$_internal_domains)) { self::$_internal_domains = apply_filters('litespeed_internal_domains', array()); } if (self::$_internal_domains) { return in_array($host, self::$_internal_domains); } return false; } /** * Check if an URL is a internal existing file * * @since 1.2.2 * @since 1.6.2 Moved here from optm.cls due to usage of media.cls * @access public * @return string|bool The real path of file OR false */ public static function is_internal_file($url, $addition_postfix = false) { if (substr($url, 0, 5) == 'data:') { Debug2::debug2('[Util] data: content not file'); return false; } $url_parsed = parse_url($url); if (isset($url_parsed['host']) && !self::internal($url_parsed['host'])) { // Check if is cdn path // Do this to avoid user hardcoded src in tpl if (!CDN::internal($url_parsed['host'])) { Debug2::debug2('[Util] external'); return false; } } if (empty($url_parsed['path'])) { return false; } // Need to replace child blog path for assets, ref: .htaccess if (is_multisite() && defined('PATH_CURRENT_SITE')) { $pattern = '#^' . PATH_CURRENT_SITE . '([_0-9a-zA-Z-]+/)(wp-(content|admin|includes))#U'; $replacement = PATH_CURRENT_SITE . '$2'; $url_parsed['path'] = preg_replace($pattern, $replacement, $url_parsed['path']); // $current_blog = (int) get_current_blog_id(); // $main_blog_id = (int) get_network()->site_id; // if ( $current_blog === $main_blog_id ) { // define( 'LITESPEED_IS_MAIN_BLOG', true ); // } // else { // define( 'LITESPEED_IS_MAIN_BLOG', false ); // } } // Parse file path /** * Trying to fix pure /.htaccess rewrite to /wordpress case * * Add `define( 'LITESPEED_WP_REALPATH', '/wordpress' );` in wp-config.php in this case * * @internal #611001 - Combine & Minify not working? * @since 1.6.3 */ if (substr($url_parsed['path'], 0, 1) === '/') { if (defined('LITESPEED_WP_REALPATH')) { $file_path_ori = $_SERVER['DOCUMENT_ROOT'] . LITESPEED_WP_REALPATH . $url_parsed['path']; } else { $file_path_ori = $_SERVER['DOCUMENT_ROOT'] . $url_parsed['path']; } } else { $file_path_ori = Router::frontend_path() . '/' . $url_parsed['path']; } /** * Added new file postfix to be check if passed in * @since 2.2.4 */ if ($addition_postfix) { $file_path_ori .= '.' . $addition_postfix; } /** * Added this filter for those plugins which overwrite the filepath * @see #101091 plugin `Hide My WordPress` * @since 2.2.3 */ $file_path_ori = apply_filters('litespeed_realpath', $file_path_ori); $file_path = realpath($file_path_ori); if (!is_file($file_path)) { Debug2::debug2('[Util] file not exist: ' . $file_path_ori); return false; } return array($file_path, filesize($file_path)); } /** * Safely parse URL for v5.3 compatibility * * @since 3.4.3 */ public static function parse_url_safe($url, $component = -1) { if (substr($url, 0, 2) == '//') { $url = 'https:' . $url; } return parse_url($url, $component); } /** * Replace url in srcset to new value * * @since 2.2.3 */ public static function srcset_replace($content, $callback) { preg_match_all('# srcset=([\'"])(.+)\g{1}#iU', $content, $matches); $srcset_ori = array(); $srcset_final = array(); foreach ($matches[2] as $k => $urls_ori) { $urls_final = explode(',', $urls_ori); $changed = false; foreach ($urls_final as $k2 => $url_info) { $url_info_arr = explode(' ', trim($url_info)); if (!($url2 = call_user_func($callback, $url_info_arr[0]))) { continue; } $changed = true; $urls_final[$k2] = str_replace($url_info_arr[0], $url2, $url_info); Debug2::debug2('[Util] - srcset replaced to ' . $url2 . (!empty($url_info_arr[1]) ? ' ' . $url_info_arr[1] : '')); } if (!$changed) { continue; } $urls_final = implode(',', $urls_final); $srcset_ori[] = $matches[0][$k]; $srcset_final[] = str_replace($urls_ori, $urls_final, $matches[0][$k]); } if ($srcset_ori) { $content = str_replace($srcset_ori, $srcset_final, $content); Debug2::debug2('[Util] - srcset replaced'); } return $content; } /** * Generate pagination * * @since 3.0 * @access public */ public static function pagination($total, $limit, $return_offset = false) { $pagenum = isset($_GET['pagenum']) ? absint($_GET['pagenum']) : 1; $offset = ($pagenum - 1) * $limit; $num_of_pages = ceil($total / $limit); if ($offset > $total) { $offset = $total - $limit; } if ($offset < 0) { $offset = 0; } if ($return_offset) { return $offset; } $page_links = paginate_links(array( 'base' => add_query_arg('pagenum', '%#%'), 'format' => '', 'prev_text' => '«', 'next_text' => '»', 'total' => $num_of_pages, 'current' => $pagenum, )); return '<div class="tablenav"><div class="tablenav-pages" style="margin: 1em 0">' . $page_links . '</div></div>'; } /** * Generate placeholder for an array to query * * @since 2.0 * @access public */ public static function chunk_placeholder($data, $fields) { $division = substr_count($fields, ',') + 1; $q = implode( ',', array_map(function ($el) { return '(' . implode(',', $el) . ')'; }, array_chunk(array_fill(0, count($data), '%s'), $division)) ); return $q; } } src/esi.cls.php 0000644 00000065701 15246276230 0007424 0 ustar 00 <?php /** * The ESI class. * * This is used to define all esi related functions. * * @since 1.1.3 * @package LiteSpeed * @subpackage LiteSpeed/src * @author LiteSpeed Technologies <info@litespeedtech.com> */ namespace LiteSpeed; defined('WPINC') || exit(); class ESI extends Root { const LOG_TAG = '⏺'; private static $has_esi = false; private static $_combine_ids = array(); private $esi_args = null; private $_esi_preserve_list = array(); private $_nonce_actions = array(-1 => ''); // val is cache control const QS_ACTION = 'lsesi'; const QS_PARAMS = 'esi'; const COMBO = '__combo'; // ESI include combine='main' handler const PARAM_ARGS = 'args'; const PARAM_ID = 'id'; const PARAM_INSTANCE = 'instance'; const PARAM_NAME = 'name'; const WIDGET_O_ESIENABLE = 'widget_esi_enable'; const WIDGET_O_TTL = 'widget_ttl'; /** * Confructor of ESI * * @since 1.2.0 * @since 4.0 Change to be after Vary init in hook 'after_setup_theme' */ public function init() { /** * Bypass ESI related funcs if disabled ESI to fix potential DIVI compatibility issue * @since 2.9.7.2 */ if (Router::is_ajax() || !$this->cls('Router')->esi_enabled()) { return; } // Guest mode, don't need to use ESI if (defined('LITESPEED_GUEST') && LITESPEED_GUEST) { return; } if (defined('LITESPEED_ESI_OFF')) { return; } // If page is not cacheable if (defined('DONOTCACHEPAGE') && apply_filters('litespeed_const_DONOTCACHEPAGE', DONOTCACHEPAGE)) { return; } // Init ESI in `after_setup_theme` hook after detected if LITESPEED_DISABLE_ALL is ON or not $this->_hooks(); /** * Overwrite wp_create_nonce func * @since 2.9.5 */ $this->_transform_nonce(); !defined('LITESPEED_ESI_INITED') && define('LITESPEED_ESI_INITED', true); } /** * Init ESI related hooks * * Load delayed by hook to give the ability to bypass by LITESPEED_DISABLE_ALL const * * @since 2.9.7.2 * @since 4.0 Changed to private from public * @access private */ private function _hooks() { add_filter('template_include', array($this, 'esi_template'), 99999); add_action('load-widgets.php', __NAMESPACE__ . '\Purge::purge_widget'); add_action('wp_update_comment_count', __NAMESPACE__ . '\Purge::purge_comment_widget'); /** * Recover REQUEST_URI * @since 1.8.1 */ if (!empty($_GET[self::QS_ACTION])) { self::debug('ESI req'); $this->_register_esi_actions(); } /** * Shortcode ESI * * To use it, just change the original shortcode as below: * old: [someshortcode aa='bb'] * new: [esi someshortcode aa='bb' cache='private,no-vary' ttl='600'] * * 1. `cache` attribute is optional, default to 'public,no-vary'. * 2. `ttl` attribute is optional, default is your public TTL setting. * 3. `_ls_silence` attribute is optional, default is false. * * @since 2.8 * @since 2.8.1 Check is_admin for Elementor compatibility #726013 */ if (!is_admin()) { add_shortcode('esi', array($this, 'shortcode')); } } /** * Take over all nonce calls and transform to ESI * * @since 2.9.5 */ private function _transform_nonce() { if (is_admin()) { return; } // Load ESI nonces in conf $nonces = $this->conf(Base::O_ESI_NONCE); add_filter('litespeed_esi_nonces', array($this->cls('Data'), 'load_esi_nonces')); if ($nonces = apply_filters('litespeed_esi_nonces', $nonces)) { foreach ($nonces as $action) { $this->nonce_action($action); } } add_action('litespeed_nonce', array($this, 'nonce_action')); } /** * Register a new nonce action to convert it to ESI * * @since 2.9.5 */ public function nonce_action($action) { // Split the Cache Control $action = explode(' ', $action); $control = !empty($action[1]) ? $action[1] : ''; $action = $action[0]; // Wildcard supported $action = Utility::wildcard2regex($action); if (array_key_exists($action, $this->_nonce_actions)) { return; } $this->_nonce_actions[$action] = $control; // Debug2::debug('[ESI] Appended nonce action to nonce list [action] ' . $action); } /** * Check if an action is registered to replace ESI * * @since 2.9.5 */ public function is_nonce_action($action) { // If GM not run yet, then ESI not init yet, then ESI nonce will not be allowed even nonce func replaced. if (!defined('LITESPEED_ESI_INITED')) { return null; } if (is_admin()) { return null; } if (defined('LITESPEED_ESI_OFF')) { return null; } foreach ($this->_nonce_actions as $k => $v) { if (strpos($k, '*') !== false) { if (preg_match('#' . $k . '#iU', $action)) { return $v; } } else { if ($k == $action) { return $v; } } } return null; } /** * Shortcode ESI * * @since 2.8 * @access public */ public function shortcode($atts) { if (empty($atts[0])) { Debug2::debug('[ESI] ===shortcode wrong format', $atts); return 'Wrong shortcode esi format'; } $cache = 'public,no-vary'; if (!empty($atts['cache'])) { $cache = $atts['cache']; unset($atts['cache']); } $silence = false; if (!empty($atts['_ls_silence'])) { $silence = true; } do_action('litespeed_esi_shortcode-' . $atts[0]); // Show ESI link return $this->sub_esi_block('esi', 'esi-shortcode', $atts, $cache, $silence); } /** * Check if the requested page has esi elements. If so, return esi on * header. * * @since 1.1.3 * @access public * @return string Esi On header if request has esi, empty string otherwise. */ public static function has_esi() { return self::$has_esi; } /** * Sets that the requested page has esi elements. * * @since 1.1.3 * @access public */ public static function set_has_esi() { self::$has_esi = true; } /** * Register all of the hooks related to the esi logic of the plugin. * Specifically when the page IS an esi page. * * @since 1.1.3 * @access private */ private function _register_esi_actions() { /** * This hook is in `init` * For any plugin need to check if page is ESI, use `LSCACHE_IS_ESI` check after `init` hook */ !defined('LSCACHE_IS_ESI') && define('LSCACHE_IS_ESI', $_GET[self::QS_ACTION]); // Reused this to ESI block ID !empty($_SERVER['ESI_REFERER']) && defined('LSCWP_LOG') && Debug2::debug('[ESI] ESI_REFERER: ' . $_SERVER['ESI_REFERER']); /** * Only when ESI's parent is not REST, replace REQUEST_URI to avoid breaking WP5 editor REST call * @since 2.9.3 */ if (!empty($_SERVER['ESI_REFERER']) && !$this->cls('REST')->is_rest($_SERVER['ESI_REFERER'])) { self::debug('overwrite REQUEST_URI to ESI_REFERER [from] ' . $_SERVER['REQUEST_URI'] . ' [to] ' . $_SERVER['ESI_REFERER']); if (!empty($_SERVER['ESI_REFERER'])) { $_SERVER['REQUEST_URI'] = $_SERVER['ESI_REFERER']; if (substr(get_option('permalink_structure'), -1) === '/' && strpos($_SERVER['ESI_REFERER'], '?') === false) { $_SERVER['REQUEST_URI'] = trailingslashit($_SERVER['ESI_REFERER']); } } # Prevent from 301 redirecting if (!empty($_SERVER['SCRIPT_URI'])) { $SCRIPT_URI = parse_url($_SERVER['SCRIPT_URI']); $SCRIPT_URI['path'] = $_SERVER['REQUEST_URI']; Utility::compatibility(); $_SERVER['SCRIPT_URI'] = http_build_url($SCRIPT_URI); } } if (!empty($_SERVER['ESI_CONTENT_TYPE']) && strpos($_SERVER['ESI_CONTENT_TYPE'], 'application/json') === 0) { add_filter('litespeed_is_json', '__return_true'); } /** * Make REST call be able to parse ESI * NOTE: Not effective due to ESI req are all to `/` yet * @since 2.9.4 */ add_action('rest_api_init', array($this, 'load_esi_block'), 101); // Register ESI blocks add_action('litespeed_esi_load-widget', array($this, 'load_widget_block')); add_action('litespeed_esi_load-admin-bar', array($this, 'load_admin_bar_block')); add_action('litespeed_esi_load-comment-form', array($this, 'load_comment_form_block')); add_action('litespeed_esi_load-nonce', array($this, 'load_nonce_block')); add_action('litespeed_esi_load-esi', array($this, 'load_esi_shortcode')); add_action('litespeed_esi_load-' . self::COMBO, array($this, 'load_combo')); } /** * Hooked to the template_include action. * Selects the esi template file when the post type is a LiteSpeed ESI page. * * @since 1.1.3 * @access public * @param string $template The template path filtered. * @return string The new template path. */ public function esi_template($template) { // Check if is an ESI request if (defined('LSCACHE_IS_ESI')) { self::debug('calling ESI template'); return LSCWP_DIR . 'tpl/esi.tpl.php'; } self::debug('calling default template'); $this->_register_not_esi_actions(); return $template; } /** * Register all of the hooks related to the esi logic of the plugin. * Specifically when the page is NOT an esi page. * * @since 1.1.3 * @access private */ private function _register_not_esi_actions() { do_action('litespeed_tpl_normal'); if (!Control::is_cacheable()) { return; } if (Router::is_ajax()) { return; } add_filter('widget_display_callback', array($this, 'sub_widget_block'), 0, 3); // Add admin_bar esi if (Router::is_logged_in()) { remove_action('wp_body_open', 'wp_admin_bar_render', 0); // Remove default Admin bar. Fix https://github.com/elementor/elementor/issues/25198 remove_action('wp_footer', 'wp_admin_bar_render', 1000); add_action('wp_footer', array($this, 'sub_admin_bar_block'), 1000); } // Add comment forum esi for logged-in user or commenter if (!Router::is_ajax() && Vary::has_vary()) { add_filter('comment_form_defaults', array($this, 'register_comment_form_actions')); } } /** * Set an ESI to be combine='sub' * * @since 3.4.2 */ public static function combine($block_id) { if (!isset($_SERVER['X-LSCACHE']) || strpos($_SERVER['X-LSCACHE'], 'combine') === false) { return; } if (in_array($block_id, self::$_combine_ids)) { return; } self::$_combine_ids[] = $block_id; } /** * Load combined ESI * * @since 3.4.2 */ public function load_combo() { Control::set_nocache('ESI combine request'); if (empty($_POST['esi_include'])) { return; } self::set_has_esi(); Debug2::debug('[ESI] 🍔 Load combo', $_POST['esi_include']); $output = ''; foreach ($_POST['esi_include'] as $url) { $qs = parse_url(htmlspecialchars_decode($url), PHP_URL_QUERY); parse_str($qs, $qs); if (empty($qs[self::QS_ACTION])) { continue; } $esi_id = $qs[self::QS_ACTION]; $esi_param = !empty($qs[self::QS_PARAMS]) ? $this->_parse_esi_param($qs[self::QS_PARAMS]) : false; $inline_param = apply_filters('litespeed_esi_inline-' . $esi_id, array(), $esi_param); // Returned array need to be [ val, control, tag ] if ($inline_param) { $output .= self::_build_inline($url, $inline_param); } } echo $output; } /** * Build a whole inline segment * * @since 3.4.2 */ private static function _build_inline($url, $inline_param) { if (!$url || empty($inline_param['val']) || empty($inline_param['control']) || empty($inline_param['tag'])) { return ''; } $url = esc_attr($url); $control = esc_attr($inline_param['control']); $tag = esc_attr($inline_param['tag']); return "<esi:inline name='$url' cache-control='" . $control . "' cache-tag='" . $tag . "'>" . $inline_param['val'] . '</esi:inline>'; } /** * Build the esi url. This method will build the html comment wrapper as well as serialize and encode the parameter array. * * The block_id parameter should contain alphanumeric and '-_' only. * * @since 1.1.3 * @access private * @param string $block_id The id to use to display the correct esi block. * @param string $wrapper The wrapper for the esi comments. * @param array $params The esi parameters. * @param string $control The cache control attribute if any. * @param bool $silence If generate wrapper comment or not * @param bool $preserved If this ESI block is used in any filter, need to temporarily convert it to a string to avoid the HTML tag being removed/filtered. * @param bool $svar If store the value in memory or not, in memory will be faster * @param array $inline_val If show the current value for current request( this can avoid multiple esi requests in first time cache generating process ) */ public function sub_esi_block( $block_id, $wrapper, $params = array(), $control = 'private,no-vary', $silence = false, $preserved = false, $svar = false, $inline_param = array() ) { if (empty($block_id) || !is_array($params) || preg_match('/[^\w-]/', $block_id)) { return false; } if (defined('LITESPEED_ESI_OFF')) { Debug2::debug('[ESI] ESI OFF so force loading [block_id] ' . $block_id); do_action('litespeed_esi_load-' . $block_id, $params); return; } if ($silence) { // Don't add comment to esi block ( original for nonce used in tag property data-nonce='esi_block' ) $params['_ls_silence'] = true; } if ($this->cls('REST')->is_rest() || $this->cls('REST')->is_internal_rest()) { $params['is_json'] = 1; } $params = apply_filters('litespeed_esi_params', $params, $block_id); $control = apply_filters('litespeed_esi_control', $control, $block_id); if (!is_array($params) || !is_string($control)) { defined('LSCWP_LOG') && Debug2::debug("[ESI] 🛑 Sub hooks returned Params: \n" . var_export($params, true) . "\ncache control: \n" . var_export($control, true)); return false; } // Build params for URL $appended_params = array( self::QS_ACTION => $block_id, ); if (!empty($control)) { $appended_params['_control'] = $control; } if ($params) { $appended_params[self::QS_PARAMS] = base64_encode(\json_encode($params)); Debug2::debug2('[ESI] param ', $params); } // Append hash $appended_params['_hash'] = $this->_gen_esi_md5($appended_params); /** * Escape potential chars * @since 2.9.4 */ $appended_params = array_map('urlencode', $appended_params); // Generate ESI URL $url = add_query_arg($appended_params, trailingslashit(wp_make_link_relative(home_url()))); $output = ''; if ($inline_param) { $output .= self::_build_inline($url, $inline_param); } $output .= "<esi:include src='$url'"; if (!empty($control)) { $control = esc_attr($control); $output .= " cache-control='$control'"; } if ($svar) { $output .= " as-var='1'"; } if (in_array($block_id, self::$_combine_ids)) { $output .= " combine='sub'"; } if ($block_id == self::COMBO && isset($_SERVER['X-LSCACHE']) && strpos($_SERVER['X-LSCACHE'], 'combine') !== false) { $output .= " combine='main'"; } $output .= ' />'; if (!$silence) { $output = "<!-- lscwp $wrapper -->$output<!-- lscwp $wrapper esi end -->"; } self::debug("💕 [BLock_ID] $block_id \t[wrapper] $wrapper \t\t[Control] $control"); self::debug2($output); self::set_has_esi(); // Convert to string to avoid html chars filter when using // Will reverse the buffer when output in self::finalize() if ($preserved) { $hash = md5($output); $this->_esi_preserve_list[$hash] = $output; self::debug("Preserved to $hash"); return $hash; } return $output; } /** * Generate ESI hash md5 * * @since 2.9.6 * @access private */ private function _gen_esi_md5($params) { $keys = array(self::QS_ACTION, '_control', self::QS_PARAMS); $str = ''; foreach ($keys as $v) { if (isset($params[$v]) && is_string($params[$v])) { $str .= $params[$v]; } } Debug2::debug2('[ESI] md5_string=' . $str); return md5($this->conf(Base::HASH) . $str); } /** * Parses the request parameters on an ESI request * * @since 1.1.3 * @access private */ private function _parse_esi_param($qs_params = false) { $req_params = false; if ($qs_params) { $req_params = $qs_params; } elseif (isset($_REQUEST[self::QS_PARAMS])) { $req_params = $_REQUEST[self::QS_PARAMS]; } if (!$req_params) { return false; } $unencrypted = base64_decode($req_params); if ($unencrypted === false) { return false; } Debug2::debug2('[ESI] params', $unencrypted); // $unencoded = urldecode($unencrypted); no need to do this as $_GET is already parsed $params = \json_decode($unencrypted, true); return $params; } /** * Select the correct esi output based on the parameters in an ESI request. * * @since 1.1.3 * @access public */ public function load_esi_block() { /** * Validate if is a legal ESI req * @since 2.9.6 */ if (empty($_GET['_hash']) || $this->_gen_esi_md5($_GET) != $_GET['_hash']) { Debug2::debug('[ESI] ❌ Failed to validate _hash'); return; } $params = $this->_parse_esi_param(); if (defined('LSCWP_LOG')) { $logInfo = '[ESI] ⭕ '; if (!empty($params[self::PARAM_NAME])) { $logInfo .= ' Name: ' . $params[self::PARAM_NAME] . ' ----- '; } $logInfo .= ' [ID] ' . LSCACHE_IS_ESI; Debug2::debug($logInfo); } if (!empty($params['_ls_silence'])) { !defined('LSCACHE_ESI_SILENCE') && define('LSCACHE_ESI_SILENCE', true); } /** * Buffer needs to be JSON format * @since 2.9.4 */ if (!empty($params['is_json'])) { add_filter('litespeed_is_json', '__return_true'); } Tag::add(rtrim(Tag::TYPE_ESI, '.')); Tag::add(Tag::TYPE_ESI . LSCACHE_IS_ESI); // Debug2::debug(var_export($params, true )); /** * Handle default cache control 'private,no-vary' for sub_esi_block() @ticket #923505 * * @since 2.2.3 */ if (!empty($_GET['_control'])) { $control = explode(',', $_GET['_control']); if (in_array('private', $control)) { Control::set_private(); } if (in_array('no-vary', $control)) { Control::set_no_vary(); } } do_action('litespeed_esi_load-' . LSCACHE_IS_ESI, $params); } // The *_sub_* functions are helpers for the sub_* functions. // The *_load_* functions are helpers for the load_* functions. /** * Loads the default options for default WordPress widgets. * * @since 1.1.3 * @access public */ public static function widget_default_options($options, $widget) { if (!is_array($options)) { return $options; } $widget_name = get_class($widget); switch ($widget_name) { case 'WP_Widget_Recent_Posts': case 'WP_Widget_Recent_Comments': $options[self::WIDGET_O_ESIENABLE] = Base::VAL_OFF; $options[self::WIDGET_O_TTL] = 86400; break; default: break; } return $options; } /** * Hooked to the widget_display_callback filter. * If the admin configured the widget to display via esi, this function * will set up the esi request and cancel the widget display. * * @since 1.1.3 * @access public * @param array $instance Parameter used to build the widget. * @param WP_Widget $widget The widget to build. * @param array $args Parameter used to build the widget. * @return mixed Return false if display through esi, instance otherwise. */ public function sub_widget_block($instance, $widget, $args) { // #210407 if (!is_array($instance)) { return $instance; } $name = get_class($widget); if (!isset($instance[Base::OPTION_NAME])) { return $instance; } $options = $instance[Base::OPTION_NAME]; if (!isset($options) || !$options[self::WIDGET_O_ESIENABLE]) { defined('LSCWP_LOG') && Debug2::debug('ESI 0 ' . $name . ': ' . (!isset($options) ? 'not set' : 'set off')); return $instance; } $esi_private = $options[self::WIDGET_O_ESIENABLE] == Base::VAL_ON2 ? 'private,' : ''; $params = array( self::PARAM_NAME => $name, self::PARAM_ID => $widget->id, self::PARAM_INSTANCE => $instance, self::PARAM_ARGS => $args, ); echo $this->sub_esi_block('widget', 'widget ' . $name, $params, $esi_private . 'no-vary'); return false; } /** * Hooked to the wp_footer action. * Sets up the ESI request for the admin bar. * * @access public * @since 1.1.3 * @global type $wp_admin_bar */ public function sub_admin_bar_block() { global $wp_admin_bar; if (!is_admin_bar_showing() || !is_object($wp_admin_bar)) { return; } // To make each admin bar ESI request different for `Edit` button different link $params = array( 'ref' => $_SERVER['REQUEST_URI'], ); echo $this->sub_esi_block('admin-bar', 'adminbar', $params); } /** * Parses the esi input parameters and generates the widget for esi display. * * @access public * @since 1.1.3 * @global $wp_widget_factory * @param array $params Input parameters needed to correctly display widget */ public function load_widget_block($params) { // global $wp_widget_factory; // $widget = $wp_widget_factory->widgets[ $params[ self::PARAM_NAME ] ]; $option = $params[self::PARAM_INSTANCE]; $option = $option[Base::OPTION_NAME]; // Since we only reach here via esi, safe to assume setting exists. $ttl = $option[self::WIDGET_O_TTL]; defined('LSCWP_LOG') && Debug2::debug('ESI widget render: name ' . $params[self::PARAM_NAME] . ', id ' . $params[self::PARAM_ID] . ', ttl ' . $ttl); if ($ttl == 0) { Control::set_nocache('ESI Widget time to live set to 0'); } else { Control::set_custom_ttl($ttl); if ($option[self::WIDGET_O_ESIENABLE] == Base::VAL_ON2) { Control::set_private(); } Control::set_no_vary(); Tag::add(Tag::TYPE_WIDGET . $params[self::PARAM_ID]); } the_widget($params[self::PARAM_NAME], $params[self::PARAM_INSTANCE], $params[self::PARAM_ARGS]); } /** * Generates the admin bar for esi display. * * @access public * @since 1.1.3 */ public function load_admin_bar_block($params) { if (!empty($params['ref'])) { $ref_qs = parse_url($params['ref'], PHP_URL_QUERY); if (!empty($ref_qs)) { parse_str($ref_qs, $ref_qs_arr); if (!empty($ref_qs_arr)) { foreach ($ref_qs_arr as $k => $v) { $_GET[$k] = $v; } } } } // Needed when permalink structure is "Plain" wp(); wp_admin_bar_render(); if (!$this->conf(Base::O_ESI_CACHE_ADMBAR)) { Control::set_nocache('build-in set to not cacheable'); } else { Control::set_private(); Control::set_no_vary(); } defined('LSCWP_LOG') && Debug2::debug('ESI: adminbar ref: ' . $_SERVER['REQUEST_URI']); } /** * Parses the esi input parameters and generates the comment form for esi display. * * @access public * @since 1.1.3 * @param array $params Input parameters needed to correctly display comment form */ public function load_comment_form_block($params) { comment_form($params[self::PARAM_ARGS], $params[self::PARAM_ID]); if (!$this->conf(Base::O_ESI_CACHE_COMMFORM)) { Control::set_nocache('build-in set to not cacheable'); } else { // by default comment form is public if (Vary::has_vary()) { Control::set_private(); Control::set_no_vary(); } } } /** * Generate nonce for certain action * * @access public * @since 2.6 */ public function load_nonce_block($params) { $action = $params['action']; Debug2::debug('[ESI] load_nonce_block [action] ' . $action); // set nonce TTL to half day Control::set_custom_ttl(43200); if (Router::is_logged_in()) { Control::set_private(); } if (function_exists('wp_create_nonce_litespeed_esi')) { echo wp_create_nonce_litespeed_esi($action); } else { echo wp_create_nonce($action); } } /** * Show original shortcode * * @access public * @since 2.8 */ public function load_esi_shortcode($params) { if (isset($params['ttl'])) { if (!$params['ttl']) { Control::set_nocache('ESI shortcode att ttl=0'); } else { Control::set_custom_ttl($params['ttl']); } unset($params['ttl']); } // Replace to original shortcode $shortcode = $params[0]; $atts_ori = array(); foreach ($params as $k => $v) { if ($k === 0) { continue; } $atts_ori[] = is_string($k) ? "$k='" . addslashes($v) . "'" : $v; } Tag::add(Tag::TYPE_ESI . "esi.$shortcode"); // Output original shortcode final content echo do_shortcode("[$shortcode " . implode(' ', $atts_ori) . ' ]'); } /** * Hooked to the comment_form_defaults filter. * Stores the default comment form settings. * If sub_comment_form_block is triggered, the output buffer is cleared and an esi block is added. The remaining comment form is also buffered and cleared. * Else there is no need to make the comment form ESI. * * @since 1.1.3 * @access public */ public function register_comment_form_actions($defaults) { $this->esi_args = $defaults; echo GUI::clean_wrapper_begin(); add_filter('comment_form_submit_button', array($this, 'sub_comment_form_btn'), 1000, 2); // To save the params passed in add_action('comment_form', array($this, 'sub_comment_form_block'), 1000); return $defaults; } /** * Store the args passed in comment_form for the ESI comment param usage in `$this->sub_comment_form_block()` * * @since 3.4 * @access public */ public function sub_comment_form_btn($unused, $args) { if (empty($args) || empty($this->esi_args)) { Debug2::debug('comment form args empty?'); return $unused; } $esi_args = array(); // compare current args with default ones foreach ($args as $k => $v) { if (!isset($this->esi_args[$k])) { $esi_args[$k] = $v; } elseif (is_array($v)) { $diff = array_diff_assoc($v, $this->esi_args[$k]); if (!empty($diff)) { $esi_args[$k] = $diff; } } elseif ($v !== $this->esi_args[$k]) { $esi_args[$k] = $v; } } $this->esi_args = $esi_args; return $unused; } /** * Hooked to the comment_form_submit_button filter. * * This method will compare the used comment form args against the default args. The difference will be passed to the esi request. * * @access public * @since 1.1.3 */ public function sub_comment_form_block($post_id) { echo GUI::clean_wrapper_end(); $params = array( self::PARAM_ID => $post_id, self::PARAM_ARGS => $this->esi_args, ); echo $this->sub_esi_block('comment-form', 'comment form', $params); echo GUI::clean_wrapper_begin(); add_action('comment_form_after', array($this, 'comment_form_sub_clean')); } /** * Hooked to the comment_form_after action. * Cleans up the remaining comment form output. * * @since 1.1.3 * @access public */ public function comment_form_sub_clean() { echo GUI::clean_wrapper_end(); } /** * Replace preserved blocks * * @since 2.6 * @access public */ public function finalize($buffer) { // Prepend combo esi block if (self::$_combine_ids) { Debug2::debug('[ESI] 🍔 Enabled combo'); $esi_block = $this->sub_esi_block(self::COMBO, '__COMBINE_MAIN__', array(), 'no-cache', true); $buffer = $esi_block . $buffer; } // Bypass if no preserved list to be replaced if (!$this->_esi_preserve_list) { return $buffer; } $keys = array_keys($this->_esi_preserve_list); Debug2::debug('[ESI] replacing preserved blocks', $keys); $buffer = str_replace($keys, $this->_esi_preserve_list, $buffer); return $buffer; } /** * Check if the content contains preserved list or not * * @since 3.3 */ public function contain_preserve_esi($content) { $hit_list = array(); foreach ($this->_esi_preserve_list as $k => $v) { if (strpos($content, '"' . $k . '"') !== false) { $hit_list[] = '"' . $k . '"'; } if (strpos($content, "'" . $k . "'") !== false) { $hit_list[] = "'" . $k . "'"; } } return $hit_list; } } src/media.cls.php 0000644 00000101321 15246276230 0007710 0 ustar 00 <?php /** * The class to operate media data. * * @since 1.4 * @since 1.5 Moved into /inc * @package Core * @subpackage Core/inc * @author LiteSpeed Technologies <info@litespeedtech.com> */ namespace LiteSpeed; defined('WPINC') || exit(); class Media extends Root { const LOG_TAG = '📺'; const LIB_FILE_IMG_LAZYLOAD = 'assets/js/lazyload.min.js'; private $content; private $_wp_upload_dir; private $_vpi_preload_list = array(); private $_format = ''; private $_sys_format = ''; /** * Init * * @since 1.4 */ public function __construct() { Debug2::debug2('[Media] init'); $this->_wp_upload_dir = wp_upload_dir(); if ($this->conf(Base::O_IMG_OPTM_WEBP)) { $this->_sys_format = 'webp'; $this->_format = 'webp'; if ($this->conf(Base::O_IMG_OPTM_WEBP) == 2) { $this->_sys_format = 'avif'; $this->_format = 'avif'; } if (!$this->_browser_support_next_gen()) { $this->_format = ''; } } } /** * Init optm features * * @since 3.0 * @access public */ public function init() { if (is_admin()) { return; } // Due to ajax call doesn't send correct accept header, have to limit webp to HTML only if ($this->webp_support()) { // Hook to srcset if (function_exists('wp_calculate_image_srcset')) { add_filter('wp_calculate_image_srcset', array($this, 'webp_srcset'), 988); } // Hook to mime icon // add_filter( 'wp_get_attachment_image_src', array( $this, 'webp_attach_img_src' ), 988 );// todo: need to check why not // add_filter( 'wp_get_attachment_url', array( $this, 'webp_url' ), 988 ); // disabled to avoid wp-admin display } if ($this->conf(Base::O_MEDIA_LAZY) && !$this->cls('Metabox')->setting('litespeed_no_image_lazy')) { self::debug('Suppress default WP lazyload'); add_filter('wp_lazy_loading_enabled', '__return_false'); } /** * Replace gravatar * @since 3.0 */ $this->cls('Avatar'); add_filter('litespeed_buffer_finalize', array($this, 'finalize'), 4); add_filter('litespeed_optm_html_head', array($this, 'finalize_head')); } /** * Add featured image to head */ public function finalize_head($content) { global $wp_query; // <link rel="preload" as="image" href="xx"> if ($this->_vpi_preload_list) { foreach ($this->_vpi_preload_list as $v) { $content .= '<link rel="preload" as="image" href="' . Str::trim_quotes($v) . '">'; } } // $featured_image_url = get_the_post_thumbnail_url(); // if ($featured_image_url) { // self::debug('Append featured image to head: ' . $featured_image_url); // if ($this->webp_support()) { // $featured_image_url = $this->replace_webp($featured_image_url) ?: $featured_image_url; // } // } // } return $content; } /** * Adjust WP default JPG quality * * @since 3.0 * @access public */ public function adjust_jpg_quality($quality) { $v = $this->conf(Base::O_IMG_OPTM_JPG_QUALITY); if ($v) { return $v; } return $quality; } /** * Register admin menu * * @since 1.6.3 * @access public */ public function after_admin_init() { /** * JPG quality control * @since 3.0 */ add_filter('jpeg_quality', array($this, 'adjust_jpg_quality')); add_filter('manage_media_columns', array($this, 'media_row_title')); add_filter('manage_media_custom_column', array($this, 'media_row_actions'), 10, 2); add_action('litespeed_media_row', array($this, 'media_row_con')); // Hook to attachment delete action add_action('delete_attachment', __CLASS__ . '::delete_attachment'); } /** * Media delete action hook * * @since 2.4.3 * @access public */ public static function delete_attachment($post_id) { // if (!Data::cls()->tb_exist('img_optm')) { // return; // } self::debug('delete_attachment [pid] ' . $post_id); Img_Optm::cls()->reset_row($post_id); } /** * Return media file info if exists * * This is for remote attachment plugins * * @since 2.9.8 * @access public */ public function info($short_file_path, $post_id) { $short_file_path = wp_normalize_path($short_file_path); $basedir = $this->_wp_upload_dir['basedir'] . '/'; if (strpos($short_file_path, $basedir) === 0) { $short_file_path = substr($short_file_path, strlen($basedir)); } $real_file = $basedir . $short_file_path; if (file_exists($real_file)) { return array( 'url' => $this->_wp_upload_dir['baseurl'] . '/' . $short_file_path, 'md5' => md5_file($real_file), 'size' => filesize($real_file), ); } /** * WP Stateless compatibility #143 https://github.com/litespeedtech/lscache_wp/issues/143 * @since 2.9.8 * @return array( 'url', 'md5', 'size' ) */ $info = apply_filters('litespeed_media_info', array(), $short_file_path, $post_id); if (!empty($info['url']) && !empty($info['md5']) && !empty($info['size'])) { return $info; } return false; } /** * Delete media file * * @since 2.9.8 * @access public */ public function del($short_file_path, $post_id) { $real_file = $this->_wp_upload_dir['basedir'] . '/' . $short_file_path; if (file_exists($real_file)) { unlink($real_file); self::debug('deleted ' . $real_file); } do_action('litespeed_media_del', $short_file_path, $post_id); } /** * Rename media file * * @since 2.9.8 * @access public */ public function rename($short_file_path, $short_file_path_new, $post_id) { // self::debug('renaming ' . $short_file_path . ' -> ' . $short_file_path_new); $real_file = $this->_wp_upload_dir['basedir'] . '/' . $short_file_path; $real_file_new = $this->_wp_upload_dir['basedir'] . '/' . $short_file_path_new; if (file_exists($real_file)) { rename($real_file, $real_file_new); self::debug('renamed ' . $real_file . ' to ' . $real_file_new); } do_action('litespeed_media_rename', $short_file_path, $short_file_path_new, $post_id); } /** * Media Admin Menu -> Image Optimization Column Title * * @since 1.6.3 * @access public */ public function media_row_title($posts_columns) { $posts_columns['imgoptm'] = __('LiteSpeed Optimization', 'litespeed-cache'); return $posts_columns; } /** * Media Admin Menu -> Image Optimization Column * * @since 1.6.2 * @access public */ public function media_row_actions($column_name, $post_id) { if ($column_name !== 'imgoptm') { return; } do_action('litespeed_media_row', $post_id); } /** * Display image optm info * * @since 3.0 */ public function media_row_con($post_id) { $att_info = wp_get_attachment_metadata($post_id); if (empty($att_info['file'])) { return; } $short_path = $att_info['file']; $size_meta = get_post_meta($post_id, Img_Optm::DB_SIZE, true); echo '<p>'; // Original image info if ($size_meta && !empty($size_meta['ori_saved'])) { $percent = ceil(($size_meta['ori_saved'] * 100) / $size_meta['ori_total']); $extension = pathinfo($short_path, PATHINFO_EXTENSION); $bk_file = substr($short_path, 0, -strlen($extension)) . 'bk.' . $extension; $bk_optm_file = substr($short_path, 0, -strlen($extension)) . 'bk.optm.' . $extension; $link = Utility::build_url(Router::ACTION_IMG_OPTM, 'orig' . $post_id); $desc = false; $cls = ''; if ($this->info($bk_file, $post_id)) { $curr_status = __('(optm)', 'litespeed-cache'); $desc = __('Currently using optimized version of file.', 'litespeed-cache') . ' ' . __('Click to switch to original (unoptimized) version.', 'litespeed-cache'); } elseif ($this->info($bk_optm_file, $post_id)) { $cls .= ' litespeed-warning'; $curr_status = __('(non-optm)', 'litespeed-cache'); $desc = __('Currently using original (unoptimized) version of file.', 'litespeed-cache') . ' ' . __('Click to switch to optimized version.', 'litespeed-cache'); } echo GUI::pie_tiny( $percent, 24, sprintf(__('Original file reduced by %1$s (%2$s)', 'litespeed-cache'), $percent . '%', Utility::real_size($size_meta['ori_saved'])), 'left' ); echo sprintf(__('Orig saved %s', 'litespeed-cache'), $percent . '%'); if ($desc) { echo sprintf( ' <a href="%1$s" class="litespeed-media-href %2$s" data-balloon-pos="left" data-balloon-break aria-label="%3$s">%4$s</a>', $link, $cls, $desc, $curr_status ); } else { echo sprintf( ' <span class="litespeed-desc" data-balloon-pos="left" data-balloon-break aria-label="%1$s">%2$s</span>', __('Using optimized version of file. ', 'litespeed-cache') . ' ' . __('No backup of original file exists.', 'litespeed-cache'), __('(optm)', 'litespeed-cache') ); } } elseif ($size_meta && $size_meta['ori_saved'] === 0) { echo GUI::pie_tiny(0, 24, __('Congratulation! Your file was already optimized', 'litespeed-cache'), 'left'); echo sprintf(__('Orig %s', 'litespeed-cache'), '<span class="litespeed-desc">' . __('(no savings)', 'litespeed-cache') . '</span>'); } else { echo __('Orig', 'litespeed-cache') . '<span class="litespeed-left10">—</span>'; } echo '</p>'; echo '<p>'; // WebP/AVIF info if ($size_meta && $this->webp_support(true) && !empty($size_meta[$this->_sys_format . '_saved'])) { $is_avif = 'avif' === $this->_sys_format; $size_meta_saved = $size_meta[$this->_sys_format . '_saved']; $size_meta_total = $size_meta[$this->_sys_format . '_total']; $percent = ceil(($size_meta_saved * 100) / $size_meta_total); $link = Utility::build_url(Router::ACTION_IMG_OPTM, $this->_sys_format . $post_id); $desc = false; $cls = ''; if ($this->info($short_path . '.' . $this->_sys_format, $post_id)) { $curr_status = __('(optm)', 'litespeed-cache'); $desc = $is_avif ? __('Currently using optimized version of AVIF file.', 'litespeed-cache') : __('Currently using optimized version of WebP file.', 'litespeed-cache'); $desc .= ' ' . __('Click to switch to original (unoptimized) version.', 'litespeed-cache'); } elseif ($this->info($short_path . '.optm.' . $this->_sys_format, $post_id)) { $cls .= ' litespeed-warning'; $curr_status = __('(non-optm)', 'litespeed-cache'); $desc = $is_avif ? __('Currently using original (unoptimized) version of AVIF file.', 'litespeed-cache') : __('Currently using original (unoptimized) version of WebP file.', 'litespeed-cache'); $desc .= ' ' . __('Click to switch to optimized version.', 'litespeed-cache'); } echo GUI::pie_tiny( $percent, 24, sprintf( $is_avif ? __('AVIF file reduced by %1$s (%2$s)', 'litespeed-cache') : __('WebP file reduced by %1$s (%2$s)', 'litespeed-cache'), $percent . '%', Utility::real_size($size_meta_saved) ), 'left' ); echo sprintf($is_avif ? __('AVIF saved %s', 'litespeed-cache') : __('WebP saved %s', 'litespeed-cache'), $percent . '%'); if ($desc) { echo sprintf( ' <a href="%1$s" class="litespeed-media-href %2$s" data-balloon-pos="left" data-balloon-break aria-label="%3$s">%4$s</a>', $link, $cls, $desc, $curr_status ); } else { echo sprintf( ' <span class="litespeed-desc" data-balloon-pos="left" data-balloon-break aria-label="%1$s %2$s">%3$s</span>', __('Using optimized version of file. ', 'litespeed-cache'), $is_avif ? __('No backup of unoptimized AVIF file exists.', 'litespeed-cache') : __('No backup of unoptimized WebP file exists.', 'litespeed-cache'), __('(optm)', 'litespeed-cache') ); } } else { echo $this->next_gen_image_title() . '<span class="litespeed-left10">—</span>'; } echo '</p>'; // Delete row btn if ($size_meta) { echo sprintf( '<div class="row-actions"><span class="delete"><a href="%1$s" class="">%2$s</a></span></div>', Utility::build_url(Router::ACTION_IMG_OPTM, Img_Optm::TYPE_RESET_ROW, false, null, array('id' => $post_id)), __('Restore from backup', 'litespeed-cache') ); echo '</div>'; } } /** * Get wp size info * * NOTE: this is not used because it has to be after admin_init * * @since 1.6.2 * @return array $sizes Data for all currently-registered image sizes. */ public function get_image_sizes() { global $_wp_additional_image_sizes; $sizes = array(); foreach (get_intermediate_image_sizes() as $_size) { if (in_array($_size, array('thumbnail', 'medium', 'medium_large', 'large'))) { $sizes[$_size]['width'] = get_option($_size . '_size_w'); $sizes[$_size]['height'] = get_option($_size . '_size_h'); $sizes[$_size]['crop'] = (bool) get_option($_size . '_crop'); } elseif (isset($_wp_additional_image_sizes[$_size])) { $sizes[$_size] = array( 'width' => $_wp_additional_image_sizes[$_size]['width'], 'height' => $_wp_additional_image_sizes[$_size]['height'], 'crop' => $_wp_additional_image_sizes[$_size]['crop'], ); } } return $sizes; } /** * Exclude role from optimization filter * * @since 1.6.2 * @access public */ public function webp_support($sys_level = false) { if ($sys_level) { return $this->_sys_format; } return $this->_format; // User level next gen support } private function _browser_support_next_gen() { if (!empty($_SERVER['HTTP_ACCEPT'])) { if (strpos($_SERVER['HTTP_ACCEPT'], 'image/' . $this->_sys_format) !== false) { return true; } } if (!empty($_SERVER['HTTP_USER_AGENT'])) { $user_agents = array('chrome-lighthouse', 'googlebot', 'page speed'); foreach ($user_agents as $user_agent) { if (stripos($_SERVER['HTTP_USER_AGENT'], $user_agent) !== false) { return true; } } if (preg_match('/iPhone OS (\d+)_/i', $_SERVER['HTTP_USER_AGENT'], $matches)) { if ($matches[1] >= 14) { return true; } } if (preg_match('/Firefox\/(\d+)/i', $_SERVER['HTTP_USER_AGENT'], $matches)) { if ($matches[1] >= 65) { return true; } } } return false; } /** * Get next gen image title * * @since 7.0 */ public function next_gen_image_title() { $next_gen_img = 'WebP'; if ($this->conf(Base::O_IMG_OPTM_WEBP) == 2) { $next_gen_img = 'AVIF'; } return $next_gen_img; } /** * Run lazy load process * NOTE: As this is after cache finalized, can NOT set any cache control anymore * * Only do for main page. Do NOT do for esi or dynamic content. * * @since 1.4 * @access public * @return string The buffer */ public function finalize($content) { if (defined('LITESPEED_NO_LAZY')) { Debug2::debug2('[Media] bypass: NO_LAZY const'); return $content; } if (!defined('LITESPEED_IS_HTML')) { Debug2::debug2('[Media] bypass: Not frontend HTML type'); return $content; } if (!Control::is_cacheable()) { self::debug('bypass: Not cacheable'); return $content; } self::debug('finalize'); $this->content = $content; $this->_finalize(); return $this->content; } /** * Run lazyload replacement for images in buffer * * @since 1.4 * @access private */ private function _finalize() { /** * Use webp for optimized images * @since 1.6.2 */ if ($this->webp_support()) { $this->content = $this->_replace_buffer_img_webp($this->content); } /** * Check if URI is excluded * @since 3.0 */ $excludes = $this->conf(Base::O_MEDIA_LAZY_URI_EXC); if (!defined('LITESPEED_GUEST_OPTM')) { $result = Utility::str_hit_array($_SERVER['REQUEST_URI'], $excludes); if ($result) { self::debug('bypass lazyload: hit URI Excludes setting: ' . $result); return; } } $cfg_lazy = (defined('LITESPEED_GUEST_OPTM') || $this->conf(Base::O_MEDIA_LAZY)) && !$this->cls('Metabox')->setting('litespeed_no_image_lazy'); $cfg_iframe_lazy = defined('LITESPEED_GUEST_OPTM') || $this->conf(Base::O_MEDIA_IFRAME_LAZY); $cfg_js_delay = defined('LITESPEED_GUEST_OPTM') || $this->conf(Base::O_OPTM_JS_DEFER) == 2; $cfg_trim_noscript = defined('LITESPEED_GUEST_OPTM') || $this->conf(Base::O_OPTM_NOSCRIPT_RM); $cfg_vpi = defined('LITESPEED_GUEST_OPTM') || $this->conf(Base::O_MEDIA_VPI); // Preload VPI if ($cfg_vpi) { $this->_parse_img_for_preload(); } if ($cfg_lazy) { if ($cfg_vpi) { add_filter('litespeed_media_lazy_img_excludes', array($this->cls('Metabox'), 'lazy_img_excludes')); } list($src_list, $html_list, $placeholder_list) = $this->_parse_img(); $html_list_ori = $html_list; } else { self::debug('lazyload disabled'); } // image lazy load if ($cfg_lazy) { $__placeholder = Placeholder::cls(); foreach ($html_list as $k => $v) { $size = $placeholder_list[$k]; $src = $src_list[$k]; $html_list[$k] = $__placeholder->replace($v, $src, $size); } } if ($cfg_lazy) { $this->content = str_replace($html_list_ori, $html_list, $this->content); } // iframe lazy load if ($cfg_iframe_lazy) { $html_list = $this->_parse_iframe(); $html_list_ori = $html_list; foreach ($html_list as $k => $v) { $snippet = $cfg_trim_noscript ? '' : '<noscript>' . $v . '</noscript>'; if ($cfg_js_delay) { $v = str_replace(' src=', ' data-litespeed-src=', $v); } else { $v = str_replace(' src=', ' data-src=', $v); } $v = str_replace('<iframe ', '<iframe data-lazyloaded="1" src="about:blank" ', $v); $snippet = $v . $snippet; $html_list[$k] = $snippet; } $this->content = str_replace($html_list_ori, $html_list, $this->content); } // Include lazyload lib js and init lazyload if ($cfg_lazy || $cfg_iframe_lazy) { $lazy_lib = '<script data-no-optimize="1">' . File::read(LSCWP_DIR . self::LIB_FILE_IMG_LAZYLOAD) . '</script>'; $this->content = str_replace('</body>', $lazy_lib . '</body>', $this->content); } } /** * Parse img src for VPI preload only * Note: Didn't reuse the _parse_img() bcoz it contains parent cls replacement and other logic which is not needed for preload * * @since 6.2 */ private function _parse_img_for_preload() { // Load VPI setting $is_mobile = $this->_separate_mobile(); $vpi_files = $this->cls('Metabox')->setting($is_mobile ? 'litespeed_vpi_list_mobile' : 'litespeed_vpi_list'); if ($vpi_files) { $vpi_files = Utility::sanitize_lines($vpi_files, 'basename'); } if (!$vpi_files) { return; } if (!$this->content) { return; } $content = preg_replace(array('#<!--.*-->#sU', '#<noscript([^>]*)>.*</noscript>#isU'), '', $this->content); if (!$content) { return; } preg_match_all('#<img\s+([^>]+)/?>#isU', $content, $matches, PREG_SET_ORDER); foreach ($matches as $match) { $attrs = Utility::parse_attr($match[1]); if (empty($attrs['src'])) { continue; } if (strpos($attrs['src'], 'base64') !== false || substr($attrs['src'], 0, 5) === 'data:') { Debug2::debug2('[Media] lazyload bypassed base64 img'); continue; } if (strpos($attrs['src'], '{') !== false) { Debug2::debug2('[Media] image src has {} ' . $attrs['src']); continue; } // If the src contains VPI filename, then preload it if (!Utility::str_hit_array($attrs['src'], $vpi_files)) { continue; } Debug2::debug2('[Media] VPI preload found and matched: ' . $attrs['src']); $this->_vpi_preload_list[] = $attrs['src']; } } /** * Parse img src * * @since 1.4 * @access private * @return array All the src & related raw html list */ private function _parse_img() { /** * Exclude list * @since 1.5 * @since 2.7.1 Changed to array */ $excludes = apply_filters('litespeed_media_lazy_img_excludes', $this->conf(Base::O_MEDIA_LAZY_EXC)); $cls_excludes = apply_filters('litespeed_media_lazy_img_cls_excludes', $this->conf(Base::O_MEDIA_LAZY_CLS_EXC)); $cls_excludes[] = 'skip-lazy'; // https://core.trac.wordpress.org/ticket/44427 $src_list = array(); $html_list = array(); $placeholder_list = array(); $content = preg_replace( array( '#<!--.*-->#sU', '#<noscript([^>]*)>.*</noscript>#isU', '#<script([^>]*)>.*</script>#isU', // Added to remove warning of file not found when image size detection is turned ON. ), '', $this->content ); /** * Exclude parent classes * @since 3.0 */ $parent_cls_exc = apply_filters('litespeed_media_lazy_img_parent_cls_excludes', $this->conf(Base::O_MEDIA_LAZY_PARENT_CLS_EXC)); if ($parent_cls_exc) { Debug2::debug2('[Media] Lazyload Class excludes', $parent_cls_exc); foreach ($parent_cls_exc as $v) { $content = preg_replace('#<(\w+) [^>]*class=(\'|")[^\'"]*' . preg_quote($v, '#') . '[^\'"]*\2[^>]*>.*</\1>#sU', '', $content); } } preg_match_all('#<img\s+([^>]+)/?>#isU', $content, $matches, PREG_SET_ORDER); foreach ($matches as $match) { $attrs = Utility::parse_attr($match[1]); if (empty($attrs['src'])) { continue; } /** * Add src validation to bypass base64 img src * @since 1.6 */ if (strpos($attrs['src'], 'base64') !== false || substr($attrs['src'], 0, 5) === 'data:') { Debug2::debug2('[Media] lazyload bypassed base64 img'); continue; } Debug2::debug2('[Media] lazyload found: ' . $attrs['src']); if ( !empty($attrs['data-no-lazy']) || !empty($attrs['data-skip-lazy']) || !empty($attrs['data-lazyloaded']) || !empty($attrs['data-src']) || !empty($attrs['data-srcset']) ) { Debug2::debug2('[Media] bypassed'); continue; } if (!empty($attrs['class']) && ($hit = Utility::str_hit_array($attrs['class'], $cls_excludes))) { Debug2::debug2('[Media] lazyload image cls excludes [hit] ' . $hit); continue; } /** * Exclude from lazyload by setting * @since 1.5 */ if ($excludes && Utility::str_hit_array($attrs['src'], $excludes)) { Debug2::debug2('[Media] lazyload image exclude ' . $attrs['src']); continue; } /** * Excldues invalid image src from buddypress avatar crop * @see https://wordpress.org/support/topic/lazy-load-breaking-buddypress-upload-avatar-feature * @since 3.0 */ if (strpos($attrs['src'], '{') !== false) { Debug2::debug2('[Media] image src has {} ' . $attrs['src']); continue; } // to avoid multiple replacement if (in_array($match[0], $html_list)) { continue; } // Add missing dimensions if (defined('LITESPEED_GUEST_OPTM') || $this->conf(Base::O_MEDIA_ADD_MISSING_SIZES)) { if (!apply_filters('litespeed_media_add_missing_sizes', true)) { Debug2::debug2('[Media] add_missing_sizes bypassed via litespeed_media_add_missing_sizes filter'); } elseif (empty($attrs['width']) || $attrs['width'] == 'auto' || empty($attrs['height']) || $attrs['height'] == 'auto') { self::debug('⚠️ Missing sizes for image [src] ' . $attrs['src']); $dimensions = $this->_detect_dimensions($attrs['src']); if ($dimensions) { $ori_width = $dimensions[0]; $ori_height = $dimensions[1]; // Calculate height based on width if (!empty($attrs['width']) && $attrs['width'] != 'auto') { $ori_height = intval(($ori_height * $attrs['width']) / $ori_width); } elseif (!empty($attrs['height']) && $attrs['height'] != 'auto') { $ori_width = intval(($ori_width * $attrs['height']) / $ori_height); } $attrs['width'] = $ori_width; $attrs['height'] = $ori_height; $new_html = preg_replace('#\s+(width|height)=(["\'])[^\2]*?\2#', '', $match[0]); $new_html = preg_replace( '#<img\s+#i', '<img width="' . Str::trim_quotes($attrs['width']) . '" height="' . Str::trim_quotes($attrs['height']) . '" ', $new_html ); self::debug('Add missing sizes ' . $attrs['width'] . 'x' . $attrs['height'] . ' to ' . $attrs['src']); $this->content = str_replace($match[0], $new_html, $this->content); $match[0] = $new_html; } } } $placeholder = false; if (!empty($attrs['width']) && $attrs['width'] != 'auto' && !empty($attrs['height']) && $attrs['height'] != 'auto') { $placeholder = intval($attrs['width']) . 'x' . intval($attrs['height']); } $src_list[] = $attrs['src']; $html_list[] = $match[0]; $placeholder_list[] = $placeholder; } return array($src_list, $html_list, $placeholder_list); } /** * Detect the original sizes * * @since 4.0 */ private function _detect_dimensions($src) { if ($pathinfo = Utility::is_internal_file($src)) { $src = $pathinfo[0]; } elseif (apply_filters('litespeed_media_ignore_remote_missing_sizes', false)) { return false; } if (substr($src, 0, 2) == '//') { $src = 'https:' . $src; } try { $sizes = getimagesize($src); } catch (\Exception $e) { return false; } if (!empty($sizes[0]) && !empty($sizes[1])) { return $sizes; } return false; } /** * Parse iframe src * * @since 1.4 * @access private * @return array All the src & related raw html list */ private function _parse_iframe() { $cls_excludes = apply_filters('litespeed_media_iframe_lazy_cls_excludes', $this->conf(Base::O_MEDIA_IFRAME_LAZY_CLS_EXC)); $cls_excludes[] = 'skip-lazy'; // https://core.trac.wordpress.org/ticket/44427 $html_list = array(); $content = preg_replace('#<!--.*-->#sU', '', $this->content); /** * Exclude parent classes * @since 3.0 */ $parent_cls_exc = apply_filters('litespeed_media_iframe_lazy_parent_cls_excludes', $this->conf(Base::O_MEDIA_IFRAME_LAZY_PARENT_CLS_EXC)); if ($parent_cls_exc) { Debug2::debug2('[Media] Iframe Lazyload Class excludes', $parent_cls_exc); foreach ($parent_cls_exc as $v) { $content = preg_replace('#<(\w+) [^>]*class=(\'|")[^\'"]*' . preg_quote($v, '#') . '[^\'"]*\2[^>]*>.*</\1>#sU', '', $content); } } preg_match_all('#<iframe \s*([^>]+)></iframe>#isU', $content, $matches, PREG_SET_ORDER); foreach ($matches as $match) { $attrs = Utility::parse_attr($match[1]); if (empty($attrs['src'])) { continue; } Debug2::debug2('[Media] found iframe: ' . $attrs['src']); if (!empty($attrs['data-no-lazy']) || !empty($attrs['data-skip-lazy']) || !empty($attrs['data-lazyloaded']) || !empty($attrs['data-src'])) { Debug2::debug2('[Media] bypassed'); continue; } if (!empty($attrs['class']) && ($hit = Utility::str_hit_array($attrs['class'], $cls_excludes))) { Debug2::debug2('[Media] iframe lazyload cls excludes [hit] ' . $hit); continue; } if (apply_filters('litespeed_iframe_lazyload_exc', false, $attrs['src'])) { Debug2::debug2('[Media] bypassed by filter'); continue; } // to avoid multiple replacement if (in_array($match[0], $html_list)) { continue; } $html_list[] = $match[0]; } return $html_list; } /** * Replace image src to webp * * @since 1.6.2 * @access private */ private function _replace_buffer_img_webp($content) { /** * Added custom element & attribute support * @since 2.2.2 */ $webp_ele_to_check = $this->conf(Base::O_IMG_OPTM_WEBP_ATTR); foreach ($webp_ele_to_check as $v) { if (!$v || strpos($v, '.') === false) { Debug2::debug2('[Media] buffer_webp no . attribute ' . $v); continue; } Debug2::debug2('[Media] buffer_webp attribute ' . $v); $v = explode('.', $v); $attr = preg_quote($v[1], '#'); if ($v[0]) { $pattern = '#<' . preg_quote($v[0], '#') . '([^>]+)' . $attr . '=([\'"])(.+)\2#iU'; } else { $pattern = '# ' . $attr . '=([\'"])(.+)\1#iU'; } preg_match_all($pattern, $content, $matches); foreach ($matches[$v[0] ? 3 : 2] as $k2 => $url) { // Check if is a DATA-URI if (strpos($url, 'data:image') !== false) { continue; } if (!($url2 = $this->replace_webp($url))) { continue; } if ($v[0]) { $html_snippet = sprintf('<' . $v[0] . '%1$s' . $v[1] . '=%2$s', $matches[1][$k2], $matches[2][$k2] . $url2 . $matches[2][$k2]); } else { $html_snippet = sprintf(' ' . $v[1] . '=%1$s', $matches[1][$k2] . $url2 . $matches[1][$k2]); } $content = str_replace($matches[0][$k2], $html_snippet, $content); } } // parse srcset // todo: should apply this to cdn too if ((defined('LITESPEED_GUEST_OPTM') || $this->conf(Base::O_IMG_OPTM_WEBP_REPLACE_SRCSET)) && $this->webp_support()) { $content = Utility::srcset_replace($content, array($this, 'replace_webp')); } // Replace background-image if ((defined('LITESPEED_GUEST_OPTM') || $this->conf(Base::O_IMG_OPTM_WEBP)) && $this->webp_support()) { $content = $this->replace_background_webp($content); } return $content; } /** * Replace background image * * @since 4.0 */ public function replace_background_webp($content) { Debug2::debug2('[Media] Start replacing background WebP/AVIF.'); // Handle Elementors data-settings json encode background-images $content = $this->replace_urls_in_json($content); // preg_match_all( '#background-image:(\s*)url\((.*)\)#iU', $content, $matches ); preg_match_all('#url\(([^)]+)\)#iU', $content, $matches); foreach ($matches[1] as $k => $url) { // Check if is a DATA-URI if (strpos($url, 'data:image') !== false) { continue; } /** * Support quotes in src `background-image: url('src')` * @since 2.9.3 */ $url = trim($url, '\'"'); // Fix Elementors Slideshow unusual background images like style="background-image: url("https://xxxx.png");" if (strpos($url, '"') === 0 && substr($url, -6) == '"') { $url = substr($url, 6, -6); } if (!($url2 = $this->replace_webp($url))) { continue; } // $html_snippet = sprintf( 'background-image:%1$surl(%2$s)', $matches[ 1 ][ $k ], $url2 ); $html_snippet = str_replace($url, $url2, $matches[0][$k]); $content = str_replace($matches[0][$k], $html_snippet, $content); } return $content; } /** * Replace images in json data settings attributes * * @since 6.2 */ public function replace_urls_in_json($content) { $pattern = '/data-settings="(.*?)"/i'; $parent_class = $this; preg_match_all($pattern, $content, $matches, PREG_SET_ORDER); foreach ($matches as $match) { // Check if the string contains HTML entities $isEncoded = preg_match('/"|<|>|&|'/', $match[1]); // Decode HTML entities in the JSON string $jsonString = html_entity_decode($match[1]); $jsonData = \json_decode($jsonString, true); if (json_last_error() === JSON_ERROR_NONE) { $did_webp_replace = false; array_walk_recursive($jsonData, function (&$item, $key) use (&$did_webp_replace, $parent_class) { if ($key == 'url') { $item_image = $parent_class->replace_webp($item); if ($item_image) { $item = $item_image; $did_webp_replace = true; } } }); if ($did_webp_replace) { // Re-encode the modified array back to a JSON string $newJsonString = \json_encode($jsonData); // Re-encode the JSON string to HTML entities only if it was originally encoded if ($isEncoded) { $newJsonString = htmlspecialchars($newJsonString, ENT_QUOTES | 0); // ENT_HTML401 is for PHPv5.4+ } // Replace the old JSON string in the content with the new, modified JSON string $content = str_replace($match[1], $newJsonString, $content); } } } return $content; } /** * Replace internal image src to webp or avif * * @since 1.6.2 * @access public */ public function replace_webp($url) { if (!$this->webp_support()) { self::debug2('No next generation format chosen in setting, bypassed'); return false; } Debug2::debug2('[Media] ' . $this->_sys_format . ' replacing: ' . substr($url, 0, 200)); if (substr($url, -5) === '.' . $this->_sys_format) { Debug2::debug2('[Media] already ' . $this->_sys_format); return false; } /** * WebP API hook * NOTE: As $url may contain query strings, WebP check will need to parse_url before appending .webp * @since 2.9.5 * @see #751737 - API docs for WebP generation */ if (apply_filters('litespeed_media_check_ori', Utility::is_internal_file($url), $url)) { // check if has webp file if (apply_filters('litespeed_media_check_webp', Utility::is_internal_file($url, $this->_sys_format), $url)) { $url .= '.' . $this->_sys_format; } else { Debug2::debug2('[Media] -no WebP or AVIF file, bypassed'); return false; } } else { Debug2::debug2('[Media] -no file, bypassed'); return false; } Debug2::debug2('[Media] - replaced to: ' . $url); return $url; } /** * Hook to wp_get_attachment_image_src * * @since 1.6.2 * @access public * @param array $img The URL of the attachment image src, the width, the height * @return array */ public function webp_attach_img_src($img) { Debug2::debug2('[Media] changing attach src: ' . $img[0]); if ($img && ($url = $this->replace_webp($img[0]))) { $img[0] = $url; } return $img; } /** * Try to replace img url * * @since 1.6.2 * @access public * @param string $url * @return string */ public function webp_url($url) { if ($url && ($url2 = $this->replace_webp($url))) { $url = $url2; } return $url; } /** * Hook to replace WP responsive images * * @since 1.6.2 * @access public * @param array $srcs * @return array */ public function webp_srcset($srcs) { if ($srcs) { foreach ($srcs as $w => $data) { if (!($url = $this->replace_webp($data['url']))) { continue; } $srcs[$w]['url'] = $url; } } return $srcs; } } security.md 0000644 00000000562 15246276230 0006747 0 ustar 00 # Security Policy ## Reporting Security Bugs We take security seriously. Please report potential vulnerabilities found in the LiteSpeed Cache plugin's source code via email to `support@litespeedtech.com` or open a ticket from your LiteSpeed Client Area. Please see [Reporting Vulnerabilities](https://www.litespeedtech.com/report-security-bugs) for more information. package.json 0000644 00000000521 15246276230 0007037 0 ustar 00 { "name": "litespeed-cache", "description": "High-performance page caching and site optimization from LiteSpeed", "license": "GPLv3", "scripts": { "format": "prettier --write . '**/*.php'", "format-check": "prettier --check . '**/*.php'" }, "devDependencies": { "@prettier/plugin-php": "^0.21.0", "prettier": "^3.0.3" } } lang/litespeed-cache.pot 0000644 00000444220 15246276230 0011245 0 ustar 00 # Copyright (C) 2025 LiteSpeed Technologies # This file is distributed under the GPLv3. msgid "" msgstr "" "Project-Id-Version: LiteSpeed Cache 7.1\n" "Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/litespeed-cache\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <LL@li.org>\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "POT-Creation-Date: 2025-04-24T13:13:02+00:00\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "X-Generator: WP-CLI 2.11.0\n" "X-Domain: litespeed-cache\n" #. Plugin Name of the plugin #: litespeed-cache.php #: tpl/banner/new_version.php:59 #: tpl/banner/new_version_dev.tpl.php:12 #: tpl/cache/more_settings_tip.tpl.php:15 #: tpl/inc/admin_footer.php:10 msgid "LiteSpeed Cache" msgstr "" #. Plugin URI of the plugin #: litespeed-cache.php msgid "https://www.litespeedtech.com/products/cache-plugins/wordpress-acceleration" msgstr "" #. Description of the plugin #: litespeed-cache.php msgid "High-performance page caching and site optimization from LiteSpeed" msgstr "" #. Author of the plugin #: litespeed-cache.php msgid "LiteSpeed Technologies" msgstr "" #. Author URI of the plugin #: litespeed-cache.php msgid "https://www.litespeedtech.com" msgstr "" #: cli/crawler.cls.php:69 #: tpl/crawler/summary.tpl.php:30 msgid "%d hours" msgstr "" #: cli/crawler.cls.php:71 #: tpl/crawler/summary.tpl.php:32 msgid "%d hour" msgstr "" #: cli/crawler.cls.php:78 #: tpl/crawler/summary.tpl.php:39 msgid "%d minutes" msgstr "" #: cli/crawler.cls.php:80 #: tpl/crawler/summary.tpl.php:41 msgid "%d minute" msgstr "" #: cli/purge.cls.php:92 msgid "Purged All!" msgstr "" #: cli/purge.cls.php:136 msgid "Purged the blog!" msgstr "" #: cli/purge.cls.php:185 msgid "Purged the url!" msgstr "" #: cli/purge.cls.php:240 msgid "Purged!" msgstr "" #: src/activation.cls.php:505 #: src/activation.cls.php:510 msgid "Failed to upgrade." msgstr "" #: src/activation.cls.php:514 msgid "Upgraded successfully." msgstr "" #: src/admin-display.cls.php:125 #: tpl/dash/entry.tpl.php:6 msgid "Dashboard" msgstr "" #: src/admin-display.cls.php:127 msgid "Presets" msgstr "" #: src/admin-display.cls.php:129 msgid "General" msgstr "" #: src/admin-display.cls.php:131 #: tpl/cache/entry.tpl.php:6 #: tpl/cache/entry_network.tpl.php:6 msgid "Cache" msgstr "" #: src/admin-display.cls.php:133 msgid "CDN" msgstr "" #: src/admin-display.cls.php:135 #: src/gui.cls.php:638 #: tpl/dash/dashboard.tpl.php:188 #: tpl/dash/network_dash.tpl.php:27 #: tpl/general/online.tpl.php:121 #: tpl/general/online.tpl.php:136 #: tpl/presets/standard.tpl.php:24 msgid "Image Optimization" msgstr "" #: src/admin-display.cls.php:137 #: tpl/dash/dashboard.tpl.php:189 #: tpl/dash/network_dash.tpl.php:28 #: tpl/general/online.tpl.php:120 #: tpl/general/online.tpl.php:135 msgid "Page Optimization" msgstr "" #: src/admin-display.cls.php:139 msgid "Database" msgstr "" #: src/admin-display.cls.php:141 #: src/lang.cls.php:251 msgid "Crawler" msgstr "" #: src/admin-display.cls.php:143 msgid "Toolbox" msgstr "" #: src/admin-display.cls.php:220 msgid "Cookie Name" msgstr "" #: src/admin-display.cls.php:221 #: tpl/crawler/settings.tpl.php:143 msgid "Cookie Values" msgstr "" #: src/admin-display.cls.php:223 msgid "Remove cookie simulation" msgstr "" #: src/admin-display.cls.php:224 msgid "Add new cookie to simulate" msgstr "" #: src/admin-display.cls.php:243 msgid "CDN URL to be used. For example, %s" msgstr "" #: src/admin-display.cls.php:245 msgid "Remove CDN URL" msgstr "" #: src/admin-display.cls.php:246 msgid "Add new CDN URL" msgstr "" #: src/admin-display.cls.php:247 #: src/admin-display.cls.php:983 #: src/admin-display.cls.php:1011 #: src/admin-display.cls.php:1062 #: src/doc.cls.php:41 #: tpl/cache/settings-cache.tpl.php:22 #: tpl/cache/settings_inc.cache_mobile.tpl.php:71 #: tpl/cdn/other.tpl.php:34 #: tpl/crawler/settings.tpl.php:113 #: tpl/page_optm/settings_css.tpl.php:201 #: tpl/page_optm/settings_media.tpl.php:165 #: tpl/toolbox/settings-debug.tpl.php:49 msgid "ON" msgstr "" #: src/admin-display.cls.php:248 #: src/admin-display.cls.php:984 #: src/admin-display.cls.php:1011 #: src/admin-display.cls.php:1062 #: tpl/cache/settings-cache.tpl.php:22 #: tpl/cache/settings_inc.object.tpl.php:213 #: tpl/cdn/other.tpl.php:39 #: tpl/img_optm/settings.media_webp.tpl.php:14 #: tpl/page_optm/settings_css.tpl.php:87 #: tpl/page_optm/settings_js.tpl.php:69 #: tpl/page_optm/settings_media.tpl.php:168 #: tpl/toolbox/settings-debug.tpl.php:49 msgid "OFF" msgstr "" #: src/admin-display.cls.php:298 #: src/gui.cls.php:629 #: tpl/crawler/entry.tpl.php:11 msgid "Settings" msgstr "" #: src/admin-display.cls.php:535 #: tpl/banner/slack.php:33 msgid "Dismiss" msgstr "" #: src/admin-display.cls.php:848 #: src/admin-display.cls.php:852 msgid "Save Changes" msgstr "" #: src/admin-display.cls.php:1073 msgid "This setting is overwritten by the PHP constant %s" msgstr "" #: src/admin-display.cls.php:1076 msgid "This setting is overwritten by the primary site setting" msgstr "" #: src/admin-display.cls.php:1078 msgid "This setting is overwritten by the Network setting" msgstr "" #: src/admin-display.cls.php:1082 msgid "currently set to %s" msgstr "" #: src/admin-display.cls.php:1093 #: tpl/cache/settings_inc.object.tpl.php:106 #: tpl/crawler/settings.tpl.php:37 #: tpl/esi_widget_edit.php:70 msgid "seconds" msgstr "" #: src/admin-display.cls.php:1125 #: src/admin-display.cls.php:1129 #: tpl/cdn/other.tpl.php:84 msgid "Default value" msgstr "" #: src/admin-display.cls.php:1154 msgid "Invalid rewrite rule" msgstr "" #: src/admin-display.cls.php:1172 msgid "Path must end with %s" msgstr "" #: src/admin-display.cls.php:1191 msgid "Minimum value" msgstr "" #: src/admin-display.cls.php:1194 msgid "Maximum value" msgstr "" #: src/admin-display.cls.php:1206 msgid "Zero, or" msgstr "" #: src/admin-display.cls.php:1212 msgid "Larger than" msgstr "" #: src/admin-display.cls.php:1214 msgid "Smaller than" msgstr "" #: src/admin-display.cls.php:1217 msgid "Value range" msgstr "" #: src/admin-display.cls.php:1243 msgid "Invalid IP" msgstr "" #: src/admin-display.cls.php:1264 #: tpl/cache/settings-esi.tpl.php:95 #: tpl/page_optm/settings_css.tpl.php:204 #: tpl/page_optm/settings_html.tpl.php:123 #: tpl/page_optm/settings_media.tpl.php:245 #: tpl/page_optm/settings_media_exc.tpl.php:26 #: tpl/page_optm/settings_tuning.tpl.php:39 #: tpl/page_optm/settings_tuning.tpl.php:59 #: tpl/page_optm/settings_tuning.tpl.php:80 #: tpl/page_optm/settings_tuning.tpl.php:101 #: tpl/page_optm/settings_tuning.tpl.php:120 #: tpl/page_optm/settings_tuning_css.tpl.php:24 #: tpl/page_optm/settings_tuning_css.tpl.php:85 #: tpl/toolbox/edit_htaccess.tpl.php:58 #: tpl/toolbox/edit_htaccess.tpl.php:76 msgid "API" msgstr "" #: src/admin-display.cls.php:1266 msgid "Server variable(s) %s available to override this setting." msgstr "" #: src/admin-display.cls.php:1279 msgid "The URLs will be compared to the REQUEST_URI server variable." msgstr "" #: src/admin-display.cls.php:1280 msgid "For example, for %s, %s can be used here." msgstr "" #: src/admin-display.cls.php:1282 msgid "To match the beginning, add %s to the beginning of the item." msgstr "" #: src/admin-display.cls.php:1283 msgid "To do an exact match, add %s to the end of the URL." msgstr "" #: src/admin-display.cls.php:1284 #: src/doc.cls.php:114 msgid "One per line." msgstr "" #: src/admin-display.cls.php:1299 msgid "%s groups" msgstr "" #: src/admin-display.cls.php:1302 msgid "%s images" msgstr "" #: src/admin-display.cls.php:1311 msgid "%s group" msgstr "" #: src/admin-display.cls.php:1314 msgid "%s image" msgstr "" #: src/admin-settings.cls.php:94 msgid "The user with id %s has editor access, which is not allowed for the role simulator." msgstr "" #: src/admin-settings.cls.php:276 #: src/admin-settings.cls.php:311 msgid "Options saved." msgstr "" #: src/cdn/cloudflare.cls.php:114 msgid "Notified Cloudflare to set development mode to %s successfully." msgstr "" #: src/cdn/cloudflare.cls.php:131 msgid "Cloudflare API is set to off." msgstr "" #: src/cdn/cloudflare.cls.php:147 msgid "Notified Cloudflare to purge all successfully." msgstr "" #: src/cdn/cloudflare.cls.php:162 msgid "No available Cloudflare zone" msgstr "" #: src/cdn/cloudflare.cls.php:254 #: src/cdn/cloudflare.cls.php:276 msgid "Failed to communicate with Cloudflare" msgstr "" #: src/cdn/cloudflare.cls.php:267 msgid "Communicated with Cloudflare successfully." msgstr "" #: src/cloud.cls.php:170 #: src/cloud.cls.php:255 msgid "Your WP REST API seems blocked our QUIC.cloud server calls." msgstr "" #: src/cloud.cls.php:180 #: src/cloud.cls.php:265 msgid "Failed to get echo data from WPAPI" msgstr "" #: src/cloud.cls.php:240 #: src/cloud.cls.php:296 msgid "You need to set the %1$s first. Please use the command %2$s to set." msgstr "" #: src/cloud.cls.php:241 #: src/cloud.cls.php:297 #: src/lang.cls.php:89 msgid "Server IP" msgstr "" #: src/cloud.cls.php:288 #: src/cloud.cls.php:335 #: src/cloud.cls.php:363 #: src/cloud.cls.php:380 #: src/cloud.cls.php:400 #: src/cloud.cls.php:419 msgid "You need to activate QC first." msgstr "" #: src/cloud.cls.php:306 msgid "Cert or key file does not exist." msgstr "" #: src/cloud.cls.php:575 msgid "Failed to validate %s activation data." msgstr "" #: src/cloud.cls.php:582 msgid "Failed to parse %s activation status." msgstr "" #: src/cloud.cls.php:589 msgid "%s activation data expired." msgstr "" #: src/cloud.cls.php:612 msgid "Congratulations, %s successfully set this domain up for the anonymous online services." msgstr "" #: src/cloud.cls.php:614 msgid "Congratulations, %s successfully set this domain up for the online services." msgstr "" #: src/cloud.cls.php:619 #: src/cloud.cls.php:659 #: src/cloud.cls.php:700 msgid "Congratulations, %s successfully set this domain up for the online services with CDN service." msgstr "" #: src/cloud.cls.php:729 msgid "Reset %s activation successfully." msgstr "" #: src/cloud.cls.php:1003 #: src/cloud.cls.php:1016 #: src/cloud.cls.php:1054 #: src/cloud.cls.php:1120 #: src/cloud.cls.php:1267 msgid "Cloud Error" msgstr "" #: src/cloud.cls.php:1054 msgid "No available Cloud Node after checked server load." msgstr "" #: src/cloud.cls.php:1120 msgid "No available Cloud Node." msgstr "" #: src/cloud.cls.php:1218 msgid "In order to use QC services, need a real domain name, cannot use an IP." msgstr "" #: src/cloud.cls.php:1269 msgid "Please try after %1$s for service %2$s." msgstr "" #: src/cloud.cls.php:1420 #: src/cloud.cls.php:1443 msgid "Failed to request via WordPress" msgstr "" #: src/cloud.cls.php:1475 msgid "Cloud server refused the current request due to unpulled images. Please pull the images first." msgstr "" #: src/cloud.cls.php:1480 msgid "Your domain_key has been temporarily blocklisted to prevent abuse. You may contact support at QUIC.cloud to learn more." msgstr "" #: src/cloud.cls.php:1487 msgid "Cloud server refused the current request due to rate limiting. Please try again later." msgstr "" #: src/cloud.cls.php:1495 msgid "Redetected node" msgstr "" #: src/cloud.cls.php:1503 msgid "We are working hard to improve your online service experience. The service will be unavailable while we work. We apologize for any inconvenience." msgstr "" #: src/cloud.cls.php:1545 #: src/cloud.cls.php:1553 msgid "Message from QUIC.cloud server" msgstr "" #: src/cloud.cls.php:1561 msgid "Good news from QUIC.cloud server" msgstr "" #: src/cloud.cls.php:1571 msgid "%1$s plugin version %2$s required for this action." msgstr "" #: src/cloud.cls.php:1638 msgid "Failed to communicate with QUIC.cloud server" msgstr "" #: src/cloud.cls.php:1692 msgid "Site not recognized. QUIC.cloud deactivated automatically. Please reactivate your QUIC.cloud account." msgstr "" #: src/cloud.cls.php:1693 msgid "Click here to proceed." msgstr "" #: src/cloud.cls.php:1961 msgid "Linked to QUIC.cloud preview environment, for testing purpose only." msgstr "" #: src/cloud.cls.php:2014 msgid "Sync QUIC.cloud status successfully." msgstr "" #: src/cloud.cls.php:2021 msgid "Sync credit allowance with Cloud Server successfully." msgstr "" #: src/conf.cls.php:523 msgid "Saving option failed. IPv4 only for %s." msgstr "" #: src/conf.cls.php:700 msgid "Changed setting successfully." msgstr "" #: src/core.cls.php:320 msgid "Notified LiteSpeed Web Server to purge everything." msgstr "" #: src/core.cls.php:325 msgid "Notified LiteSpeed Web Server to purge the list." msgstr "" #: src/crawler-map.cls.php:287 msgid "Sitemap cleaned successfully" msgstr "" #: src/crawler-map.cls.php:382 msgid "No valid sitemap parsed for crawler." msgstr "" #: src/crawler-map.cls.php:387 msgid "Sitemap created successfully: %d items" msgstr "" #: src/crawler.cls.php:148 msgid "Crawler disabled list is cleared! All crawlers are set to active! " msgstr "" #: src/crawler.cls.php:237 msgid "Started async crawling" msgstr "" #: src/crawler.cls.php:1234 msgid "Guest" msgstr "" #: src/crawler.cls.php:1404 msgid "Manually added to blocklist" msgstr "" #: src/crawler.cls.php:1407 msgid "Previously existed in blocklist" msgstr "" #: src/data.cls.php:226 msgid "The database has been upgrading in the background since %s. This message will disappear once upgrade is complete." msgstr "" #: src/data.upgrade.func.php:216 msgid "LiteSpeed Cache upgraded successfully. NOTE: Due to changes in this version, the settings %1$s and %2$s have been turned OFF. Please turn them back on manually and verify that your site layout is correct, and you have no JS errors." msgstr "" #: src/data.upgrade.func.php:220 #: src/lang.cls.php:151 msgid "JS Combine" msgstr "" #: src/data.upgrade.func.php:221 msgid "JS Defer" msgstr "" #: src/data.upgrade.func.php:223 msgid "Click here to settings" msgstr "" #: src/db-optm.cls.php:147 msgid "Clean all successfully." msgstr "" #: src/db-optm.cls.php:204 msgid "Clean post revisions successfully." msgstr "" #: src/db-optm.cls.php:208 msgid "Clean orphaned post meta successfully." msgstr "" #: src/db-optm.cls.php:212 msgid "Clean auto drafts successfully." msgstr "" #: src/db-optm.cls.php:216 msgid "Clean trashed posts and pages successfully." msgstr "" #: src/db-optm.cls.php:220 msgid "Clean spam comments successfully." msgstr "" #: src/db-optm.cls.php:224 msgid "Clean trashed comments successfully." msgstr "" #: src/db-optm.cls.php:228 msgid "Clean trackbacks and pingbacks successfully." msgstr "" #: src/db-optm.cls.php:232 msgid "Clean expired transients successfully." msgstr "" #: src/db-optm.cls.php:236 msgid "Clean all transients successfully." msgstr "" #: src/db-optm.cls.php:246 msgid "Optimized all tables." msgstr "" #: src/db-optm.cls.php:298 msgid "Converted to InnoDB successfully." msgstr "" #: src/doc.cls.php:40 msgid "This setting is %1$s for certain qualifying requests due to %2$s!" msgstr "" #: src/doc.cls.php:57 msgid "This setting will regenerate crawler list and clear the disabled list!" msgstr "" #: src/doc.cls.php:69 msgid "This site utilizes caching in order to facilitate a faster response time and better user experience. Caching potentially stores a duplicate copy of every web page that is on display on this site. All cache files are temporary, and are never accessed by any third party, except as necessary to obtain technical support from the cache plugin vendor. Cache files expire on a schedule set by the site administrator, but may easily be purged by the admin before their natural expiration, if necessary. We may use QUIC.cloud services to process & cache your data temporarily." msgstr "" #: src/doc.cls.php:74 msgid "Please see %s for more details." msgstr "" #: src/doc.cls.php:92 #: src/doc.cls.php:147 #: tpl/cdn/cf.tpl.php:127 #: tpl/dash/dashboard.tpl.php:177 #: tpl/dash/dashboard.tpl.php:786 #: tpl/general/online.tpl.php:67 #: tpl/general/online.tpl.php:79 #: tpl/general/online.tpl.php:95 #: tpl/img_optm/summary.tpl.php:49 #: tpl/inc/check_cache_disabled.php:42 msgid "Learn More" msgstr "" #: src/doc.cls.php:130 msgid "Both full and partial strings can be used." msgstr "" #: src/doc.cls.php:132 msgid "Both full URLs and partial strings can be used." msgstr "" #: src/doc.cls.php:145 msgid "This setting will edit the .htaccess file." msgstr "" #: src/doc.cls.php:161 msgid "For online services to work correctly, you must allowlist all %s server IPs." msgstr "" #: src/doc.cls.php:162 msgid "Before generating key, please verify all IPs on this list are allowlisted" msgstr "" #: src/doc.cls.php:163 msgid "Current Online Server IPs" msgstr "" #: src/doc.cls.php:177 msgid "The queue is processed asynchronously. It may take time." msgstr "" #: src/error.cls.php:47 msgid "The setting %s is currently enabled." msgstr "" #: src/error.cls.php:50 msgid "Click here to change." msgstr "" #: src/error.cls.php:59 msgid "You will need to finish %s setup to use the online services." msgstr "" #: src/error.cls.php:60 #: tpl/crawler/settings.tpl.php:105 #: tpl/crawler/settings.tpl.php:114 #: tpl/crawler/summary.tpl.php:183 msgid "Click here to set." msgstr "" #: src/error.cls.php:64 msgid "You have used all of your daily quota for today." msgstr "" #: src/error.cls.php:69 #: src/error.cls.php:82 msgid "Learn more or purchase additional quota." msgstr "" #: src/error.cls.php:77 msgid "You have used all of your quota left for current service this month." msgstr "" #: src/error.cls.php:90 msgid "You have too many requested images, please try again in a few minutes." msgstr "" #: src/error.cls.php:94 msgid "You have images waiting to be pulled. Please wait for the automatic pull to complete, or pull them down manually now." msgstr "" #: src/error.cls.php:98 msgid "The image list is empty." msgstr "" #: src/error.cls.php:102 msgid "Not enough parameters. Please check if the domain key is set correctly" msgstr "" #: src/error.cls.php:106 msgid "There is proceeding queue not pulled yet." msgstr "" #: src/error.cls.php:111 msgid "There is proceeding queue not pulled yet. Queue info: %s." msgstr "" #: src/error.cls.php:117 msgid "The site is not a valid alias on QUIC.cloud." msgstr "" #: src/error.cls.php:121 msgid "The site is not registered on QUIC.cloud." msgstr "" #: src/error.cls.php:125 msgid "The domain key is not correct. Please try to sync your domain key again." msgstr "" #: src/error.cls.php:129 msgid "The current server is under heavy load." msgstr "" #: src/error.cls.php:133 msgid "Online node needs to be redetected." msgstr "" #: src/error.cls.php:137 msgid "Credits are not enough to proceed the current request." msgstr "" #: src/error.cls.php:141 #: src/error.cls.php:165 msgid "%s file not writable." msgstr "" #: src/error.cls.php:149 msgid "Could not find %1$s in %2$s." msgstr "" #: src/error.cls.php:153 msgid "Invalid login cookie. Please check the %s file." msgstr "" #: src/error.cls.php:157 msgid "Failed to back up %s file, aborted changes." msgstr "" #: src/error.cls.php:161 msgid "%s file not readable." msgstr "" #: src/error.cls.php:169 msgid "Failed to get %s file contents." msgstr "" #: src/error.cls.php:173 msgid "Failed to create table %s! SQL: %s." msgstr "" #: src/error.cls.php:177 msgid "Crawler disabled by the server admin." msgstr "" #: src/error.cls.php:181 msgid "Previous request too recent. Please try again later." msgstr "" #: src/error.cls.php:186 msgid "Previous request too recent. Please try again after %s." msgstr "" #: src/error.cls.php:192 msgid "Your application is waiting for approval." msgstr "" #: src/error.cls.php:196 msgid "The callback validation to your domain failed due to hash mismatch." msgstr "" #: src/error.cls.php:200 msgid "The callback validation to your domain failed. Please make sure there is no firewall blocking our servers." msgstr "" #: src/error.cls.php:205 msgid "The callback validation to your domain failed. Please make sure there is no firewall blocking our servers. Response code: " msgstr "" #: src/error.cls.php:210 msgid "Your domain has been forbidden from using our services due to a previous policy violation." msgstr "" #: src/error.cls.php:214 msgid "You cannot remove this DNS zone, because it is still in use. Please update the domain's nameservers, then try to delete this zone again, otherwise your site will become inaccessible." msgstr "" #: src/error.cls.php:221 msgid "Unknown error" msgstr "" #: src/file.cls.php:138 msgid "Filename is empty!" msgstr "" #: src/file.cls.php:147 msgid "Folder does not exist: %s" msgstr "" #: src/file.cls.php:159 msgid "Can not create folder: %1$s. Error: %2$s" msgstr "" #: src/file.cls.php:167 msgid "Folder is not writable: %s." msgstr "" #: src/file.cls.php:173 #: src/file.cls.php:177 msgid "File %s is not writable." msgstr "" #: src/file.cls.php:184 msgid "Failed to write to %s." msgstr "" #: src/gui.cls.php:84 msgid "%1$s %2$s files left in queue" msgstr "" #: src/gui.cls.php:85 msgid "Cancel" msgstr "" #: src/gui.cls.php:403 #: src/gui.cls.php:418 msgid "Purge this page" msgstr "" #: src/gui.cls.php:427 msgid "Mark this page as " msgstr "" #: src/gui.cls.php:439 msgid "Forced cacheable" msgstr "" #: src/gui.cls.php:450 msgid "Non cacheable" msgstr "" #: src/gui.cls.php:461 msgid "Private cache" msgstr "" #: src/gui.cls.php:472 msgid "No optimization" msgstr "" #: src/gui.cls.php:480 msgid "More settings" msgstr "" #: src/gui.cls.php:487 #: src/gui.cls.php:495 #: src/gui.cls.php:503 #: src/gui.cls.php:512 #: src/gui.cls.php:522 #: src/gui.cls.php:532 #: src/gui.cls.php:542 #: src/gui.cls.php:552 #: src/gui.cls.php:561 #: src/gui.cls.php:571 #: src/gui.cls.php:581 #: src/gui.cls.php:647 #: src/gui.cls.php:655 #: src/gui.cls.php:663 #: src/gui.cls.php:672 #: src/gui.cls.php:682 #: src/gui.cls.php:692 #: src/gui.cls.php:702 #: src/gui.cls.php:712 #: src/gui.cls.php:721 #: src/gui.cls.php:731 #: src/gui.cls.php:741 #: tpl/page_optm/settings_media.tpl.php:131 #: tpl/toolbox/purge.tpl.php:30 #: tpl/toolbox/purge.tpl.php:36 #: tpl/toolbox/purge.tpl.php:44 #: tpl/toolbox/purge.tpl.php:53 #: tpl/toolbox/purge.tpl.php:62 #: tpl/toolbox/purge.tpl.php:71 #: tpl/toolbox/purge.tpl.php:80 #: tpl/toolbox/purge.tpl.php:89 #: tpl/toolbox/purge.tpl.php:98 #: tpl/toolbox/purge.tpl.php:107 msgid "Purge All" msgstr "" #: src/gui.cls.php:495 #: src/gui.cls.php:605 #: src/gui.cls.php:655 msgid "LSCache" msgstr "" #: src/gui.cls.php:503 #: src/gui.cls.php:663 #: tpl/toolbox/purge.tpl.php:36 msgid "CSS/JS Cache" msgstr "" #: src/gui.cls.php:512 #: src/gui.cls.php:672 #: tpl/cdn/cf.tpl.php:79 #: tpl/cdn/entry.tpl.php:9 msgid "Cloudflare" msgstr "" #: src/gui.cls.php:522 #: src/gui.cls.php:682 #: src/lang.cls.php:117 #: tpl/dash/dashboard.tpl.php:54 #: tpl/dash/dashboard.tpl.php:592 #: tpl/toolbox/purge.tpl.php:44 msgid "Object Cache" msgstr "" #: src/gui.cls.php:532 #: src/gui.cls.php:692 #: tpl/toolbox/purge.tpl.php:53 msgid "Opcode Cache" msgstr "" #: src/gui.cls.php:561 #: src/gui.cls.php:721 #: tpl/toolbox/purge.tpl.php:80 msgid "Localized Resources" msgstr "" #: src/gui.cls.php:571 #: src/gui.cls.php:731 #: tpl/page_optm/settings_media.tpl.php:131 #: tpl/toolbox/purge.tpl.php:89 msgid "LQIP Cache" msgstr "" #: src/gui.cls.php:581 #: src/gui.cls.php:741 #: src/lang.cls.php:184 #: tpl/presets/standard.tpl.php:42 #: tpl/toolbox/purge.tpl.php:98 msgid "Gravatar Cache" msgstr "" #: src/gui.cls.php:605 msgid "LiteSpeed Cache Purge All" msgstr "" #: src/gui.cls.php:621 #: tpl/db_optm/entry.tpl.php:6 msgid "Manage" msgstr "" #: src/gui.cls.php:761 #: tpl/img_optm/summary.tpl.php:149 msgid "Remove all previous unfinished image optimization requests." msgstr "" #: src/gui.cls.php:762 #: tpl/img_optm/summary.tpl.php:151 msgid "Clean Up Unfinished Data" msgstr "" #: src/gui.cls.php:781 msgid "Install %s" msgstr "" #: src/gui.cls.php:782 msgid "Install Now" msgstr "" #: src/gui.cls.php:802 msgid "<a href=\"%1$s\" %2$s>View version %3$s details</a> or <a href=\"%4$s\" %5$s target=\"_blank\">update now</a>." msgstr "" #: src/gui.cls.php:804 msgid "View %1$s version %2$s details" msgstr "" #: src/gui.cls.php:807 msgid "Update %s now" msgstr "" #: src/htaccess.cls.php:340 msgid "Mobile Agent Rules" msgstr "" #: src/htaccess.cls.php:819 msgid "<p>Please add/replace the following codes into the beginning of %1$s:</p> %2$s" msgstr "" #: src/img-optm.cls.php:348 msgid "Pushed %1$s to Cloud server, accepted %2$s." msgstr "" #: src/img-optm.cls.php:616 msgid "Cleared %1$s invalid images." msgstr "" #: src/img-optm.cls.php:674 msgid "No valid image found in the current request." msgstr "" #: src/img-optm.cls.php:699 msgid "No valid image found by Cloud server in the current request." msgstr "" #: src/img-optm.cls.php:892 msgid "Started async image optimization request" msgstr "" #: src/img-optm.cls.php:969 msgid "Pull Cron is running" msgstr "" #: src/img-optm.cls.php:1078 #: src/img-optm.cls.php:1104 msgid "Some optimized image file(s) has expired and was cleared." msgstr "" #: src/img-optm.cls.php:1121 msgid "Pulled WebP image md5 does not match the notified WebP image md5." msgstr "" #: src/img-optm.cls.php:1149 msgid "Pulled AVIF image md5 does not match the notified AVIF image md5." msgstr "" #: src/img-optm.cls.php:1183 msgid "One or more pulled images does not match with the notified image md5" msgstr "" #: src/img-optm.cls.php:1377 msgid "Cleaned up unfinished data successfully." msgstr "" #: src/img-optm.cls.php:1395 msgid "Reset image optimization counter successfully." msgstr "" #: src/img-optm.cls.php:1479 msgid "Destroy all optimization data successfully." msgstr "" #: src/img-optm.cls.php:1546 #: src/img-optm.cls.php:1608 msgid "Rescanned successfully." msgstr "" #: src/img-optm.cls.php:1608 msgid "Rescanned %d images successfully." msgstr "" #: src/img-optm.cls.php:1675 msgid "Calculated backups successfully." msgstr "" #: src/img-optm.cls.php:1769 msgid "Removed backups successfully." msgstr "" #: src/img-optm.cls.php:1921 msgid "Switched images successfully." msgstr "" #: src/img-optm.cls.php:2021 #: src/img-optm.cls.php:2081 msgid "Switched to optimized file successfully." msgstr "" #: src/img-optm.cls.php:2040 msgid "Disabled WebP file successfully." msgstr "" #: src/img-optm.cls.php:2045 msgid "Enabled WebP file successfully." msgstr "" #: src/img-optm.cls.php:2054 msgid "Disabled AVIF file successfully." msgstr "" #: src/img-optm.cls.php:2059 msgid "Enabled AVIF file successfully." msgstr "" #: src/img-optm.cls.php:2075 msgid "Restored original file successfully." msgstr "" #: src/img-optm.cls.php:2132 msgid "Reset the optimized data successfully." msgstr "" #: src/import.cls.php:81 msgid "Import failed due to file error." msgstr "" #: src/import.cls.php:134 msgid "Imported setting file %s successfully." msgstr "" #: src/import.cls.php:157 msgid "Reset successfully." msgstr "" #: src/lang.cls.php:26 msgid "Images not requested" msgstr "" #: src/lang.cls.php:27 msgid "Images ready to request" msgstr "" #: src/lang.cls.php:28 #: tpl/dash/dashboard.tpl.php:534 msgid "Images requested" msgstr "" #: src/lang.cls.php:29 #: tpl/dash/dashboard.tpl.php:544 msgid "Images notified to pull" msgstr "" #: src/lang.cls.php:30 msgid "Images optimized and pulled" msgstr "" #: src/lang.cls.php:49 msgid "Unable to automatically add %1$s as a Domain Alias for main %2$s domain, due to potential CDN conflict." msgstr "" #: src/lang.cls.php:54 msgid "Unable to automatically add %1$s as a Domain Alias for main %2$s domain." msgstr "" #: src/lang.cls.php:56 msgid "Alias is in use by another QUIC.cloud account." msgstr "" #: src/lang.cls.php:90 msgid "Guest Mode User Agents" msgstr "" #: src/lang.cls.php:91 msgid "Guest Mode IPs" msgstr "" #: src/lang.cls.php:93 msgid "Enable Cache" msgstr "" #: src/lang.cls.php:94 #: tpl/dash/dashboard.tpl.php:55 #: tpl/dash/dashboard.tpl.php:593 #: tpl/presets/standard.tpl.php:12 msgid "Browser Cache" msgstr "" #: src/lang.cls.php:95 msgid "Default Public Cache TTL" msgstr "" #: src/lang.cls.php:96 msgid "Default Private Cache TTL" msgstr "" #: src/lang.cls.php:97 msgid "Default Front Page TTL" msgstr "" #: src/lang.cls.php:98 msgid "Default Feed TTL" msgstr "" #: src/lang.cls.php:99 msgid "Default REST TTL" msgstr "" #: src/lang.cls.php:100 msgid "Default HTTP Status Code Page TTL" msgstr "" #: src/lang.cls.php:101 msgid "Browser Cache TTL" msgstr "" #: src/lang.cls.php:102 msgid "AJAX Cache TTL" msgstr "" #: src/lang.cls.php:103 msgid "Automatically Upgrade" msgstr "" #: src/lang.cls.php:104 msgid "Guest Mode" msgstr "" #: src/lang.cls.php:105 msgid "Guest Optimization" msgstr "" #: src/lang.cls.php:106 msgid "Notifications" msgstr "" #: src/lang.cls.php:107 msgid "Cache Logged-in Users" msgstr "" #: src/lang.cls.php:108 msgid "Cache Commenters" msgstr "" #: src/lang.cls.php:109 msgid "Cache REST API" msgstr "" #: src/lang.cls.php:110 msgid "Cache Login Page" msgstr "" #: src/lang.cls.php:111 msgid "Cache PHP Resources" msgstr "" #: src/lang.cls.php:112 #: tpl/cache/settings_inc.cache_mobile.tpl.php:71 msgid "Cache Mobile" msgstr "" #: src/lang.cls.php:113 #: tpl/cache/settings_inc.cache_mobile.tpl.php:71 msgid "List of Mobile User Agents" msgstr "" #: src/lang.cls.php:114 msgid "Private Cached URIs" msgstr "" #: src/lang.cls.php:115 msgid "Drop Query String" msgstr "" #: src/lang.cls.php:118 msgid "Method" msgstr "" #: src/lang.cls.php:119 msgid "Host" msgstr "" #: src/lang.cls.php:120 msgid "Port" msgstr "" #: src/lang.cls.php:121 msgid "Default Object Lifetime" msgstr "" #: src/lang.cls.php:122 msgid "Username" msgstr "" #: src/lang.cls.php:123 msgid "Password" msgstr "" #: src/lang.cls.php:124 msgid "Redis Database ID" msgstr "" #: src/lang.cls.php:125 msgid "Global Groups" msgstr "" #: src/lang.cls.php:126 msgid "Do Not Cache Groups" msgstr "" #: src/lang.cls.php:127 msgid "Persistent Connection" msgstr "" #: src/lang.cls.php:128 msgid "Cache WP-Admin" msgstr "" #: src/lang.cls.php:129 msgid "Store Transients" msgstr "" #: src/lang.cls.php:131 msgid "Purge All On Upgrade" msgstr "" #: src/lang.cls.php:132 msgid "Serve Stale" msgstr "" #: src/lang.cls.php:133 #: tpl/cache/settings-purge.tpl.php:130 msgid "Scheduled Purge URLs" msgstr "" #: src/lang.cls.php:134 #: tpl/cache/settings-purge.tpl.php:105 msgid "Scheduled Purge Time" msgstr "" #: src/lang.cls.php:135 msgid "Force Cache URIs" msgstr "" #: src/lang.cls.php:136 msgid "Force Public Cache URIs" msgstr "" #: src/lang.cls.php:137 msgid "Do Not Cache URIs" msgstr "" #: src/lang.cls.php:138 msgid "Do Not Cache Query Strings" msgstr "" #: src/lang.cls.php:139 msgid "Do Not Cache Categories" msgstr "" #: src/lang.cls.php:140 msgid "Do Not Cache Tags" msgstr "" #: src/lang.cls.php:141 msgid "Do Not Cache Roles" msgstr "" #: src/lang.cls.php:142 msgid "CSS Minify" msgstr "" #: src/lang.cls.php:143 msgid "CSS Combine" msgstr "" #: src/lang.cls.php:144 msgid "CSS Combine External and Inline" msgstr "" #: src/lang.cls.php:145 msgid "Generate UCSS" msgstr "" #: src/lang.cls.php:146 msgid "UCSS Inline" msgstr "" #: src/lang.cls.php:147 msgid "UCSS Selector Allowlist" msgstr "" #: src/lang.cls.php:148 msgid "UCSS File Excludes and Inline" msgstr "" #: src/lang.cls.php:149 msgid "UCSS URI Excludes" msgstr "" #: src/lang.cls.php:150 msgid "JS Minify" msgstr "" #: src/lang.cls.php:152 msgid "JS Combine External and Inline" msgstr "" #: src/lang.cls.php:153 msgid "HTML Minify" msgstr "" #: src/lang.cls.php:154 msgid "HTML Lazy Load Selectors" msgstr "" #: src/lang.cls.php:155 msgid "HTML Keep Comments" msgstr "" #: src/lang.cls.php:156 #: tpl/page_optm/settings_tuning_css.tpl.php:157 msgid "Load CSS Asynchronously" msgstr "" #: src/lang.cls.php:157 msgid "CCSS Per URL" msgstr "" #: src/lang.cls.php:158 msgid "Inline CSS Async Lib" msgstr "" #: src/lang.cls.php:159 #: tpl/presets/standard.tpl.php:39 msgid "Font Display Optimization" msgstr "" #: src/lang.cls.php:160 msgid "Load JS Deferred" msgstr "" #: src/lang.cls.php:161 msgid "Localize Resources" msgstr "" #: src/lang.cls.php:162 msgid "Localization Files" msgstr "" #: src/lang.cls.php:163 msgid "DNS Prefetch" msgstr "" #: src/lang.cls.php:164 msgid "DNS Prefetch Control" msgstr "" #: src/lang.cls.php:165 msgid "DNS Preconnect" msgstr "" #: src/lang.cls.php:166 msgid "CSS Excludes" msgstr "" #: src/lang.cls.php:167 msgid "JS Delayed Includes" msgstr "" #: src/lang.cls.php:168 msgid "JS Excludes" msgstr "" #: src/lang.cls.php:169 msgid "Remove Query Strings" msgstr "" #: src/lang.cls.php:170 msgid "Load Google Fonts Asynchronously" msgstr "" #: src/lang.cls.php:171 msgid "Remove Google Fonts" msgstr "" #: src/lang.cls.php:172 msgid "Critical CSS Rules" msgstr "" #: src/lang.cls.php:173 msgid "Separate CCSS Cache Post Types" msgstr "" #: src/lang.cls.php:174 msgid "Separate CCSS Cache URIs" msgstr "" #: src/lang.cls.php:175 msgid "CCSS Selector Allowlist" msgstr "" #: src/lang.cls.php:176 msgid "JS Deferred / Delayed Excludes" msgstr "" #: src/lang.cls.php:177 msgid "Guest Mode JS Excludes" msgstr "" #: src/lang.cls.php:178 #: tpl/presets/standard.tpl.php:44 msgid "Remove WordPress Emoji" msgstr "" #: src/lang.cls.php:179 #: tpl/presets/standard.tpl.php:45 msgid "Remove Noscript Tags" msgstr "" #: src/lang.cls.php:180 msgid "URI Excludes" msgstr "" #: src/lang.cls.php:181 msgid "Optimize for Guests Only" msgstr "" #: src/lang.cls.php:182 msgid "Role Excludes" msgstr "" #: src/lang.cls.php:185 msgid "Gravatar Cache Cron" msgstr "" #: src/lang.cls.php:186 msgid "Gravatar Cache TTL" msgstr "" #: src/lang.cls.php:188 msgid "Lazy Load Images" msgstr "" #: src/lang.cls.php:189 msgid "Lazy Load Image Excludes" msgstr "" #: src/lang.cls.php:190 msgid "Lazy Load Image Class Name Excludes" msgstr "" #: src/lang.cls.php:191 msgid "Lazy Load Image Parent Class Name Excludes" msgstr "" #: src/lang.cls.php:192 msgid "Lazy Load Iframe Class Name Excludes" msgstr "" #: src/lang.cls.php:193 msgid "Lazy Load Iframe Parent Class Name Excludes" msgstr "" #: src/lang.cls.php:194 msgid "Lazy Load URI Excludes" msgstr "" #: src/lang.cls.php:195 msgid "LQIP Excludes" msgstr "" #: src/lang.cls.php:196 msgid "Basic Image Placeholder" msgstr "" #: src/lang.cls.php:197 msgid "Responsive Placeholder" msgstr "" #: src/lang.cls.php:198 msgid "Responsive Placeholder Color" msgstr "" #: src/lang.cls.php:199 msgid "Responsive Placeholder SVG" msgstr "" #: src/lang.cls.php:200 msgid "LQIP Cloud Generator" msgstr "" #: src/lang.cls.php:201 msgid "LQIP Quality" msgstr "" #: src/lang.cls.php:202 msgid "LQIP Minimum Dimensions" msgstr "" #: src/lang.cls.php:204 msgid "Generate LQIP In Background" msgstr "" #: src/lang.cls.php:205 msgid "Lazy Load Iframes" msgstr "" #: src/lang.cls.php:206 msgid "Add Missing Sizes" msgstr "" #: src/lang.cls.php:207 #: src/metabox.cls.php:33 #: src/metabox.cls.php:34 #: tpl/page_optm/settings_vpi.tpl.php:15 msgid "Viewport Images" msgstr "" #: src/lang.cls.php:208 msgid "Viewport Images Cron" msgstr "" #: src/lang.cls.php:210 msgid "Auto Request Cron" msgstr "" #: src/lang.cls.php:211 msgid "Optimize Original Images" msgstr "" #: src/lang.cls.php:212 msgid "Remove Original Backups" msgstr "" #: src/lang.cls.php:213 msgid "Next-Gen Image Format" msgstr "" #: src/lang.cls.php:214 msgid "Optimize Losslessly" msgstr "" #: src/lang.cls.php:215 msgid "Preserve EXIF/XMP data" msgstr "" #: src/lang.cls.php:216 msgid "WebP/AVIF Attribute To Replace" msgstr "" #: src/lang.cls.php:217 msgid "WebP/AVIF For Extra srcset" msgstr "" #: src/lang.cls.php:218 msgid "WordPress Image Quality Control" msgstr "" #: src/lang.cls.php:219 #: tpl/esi_widget_edit.php:36 msgid "Enable ESI" msgstr "" #: src/lang.cls.php:220 msgid "Cache Admin Bar" msgstr "" #: src/lang.cls.php:221 msgid "Cache Comment Form" msgstr "" #: src/lang.cls.php:222 msgid "ESI Nonces" msgstr "" #: src/lang.cls.php:223 #: tpl/page_optm/settings_css.tpl.php:126 #: tpl/page_optm/settings_css.tpl.php:250 #: tpl/page_optm/settings_vpi.tpl.php:73 msgid "Vary Group" msgstr "" #: src/lang.cls.php:224 msgid "Purge All Hooks" msgstr "" #: src/lang.cls.php:225 msgid "Improve HTTP/HTTPS Compatibility" msgstr "" #: src/lang.cls.php:226 msgid "Instant Click" msgstr "" #: src/lang.cls.php:227 msgid "Do Not Cache Cookies" msgstr "" #: src/lang.cls.php:228 msgid "Do Not Cache User Agents" msgstr "" #: src/lang.cls.php:229 msgid "Login Cookie" msgstr "" #: src/lang.cls.php:230 msgid "Vary Cookies" msgstr "" #: src/lang.cls.php:232 msgid "Frontend Heartbeat Control" msgstr "" #: src/lang.cls.php:233 msgid "Frontend Heartbeat TTL" msgstr "" #: src/lang.cls.php:234 msgid "Backend Heartbeat Control" msgstr "" #: src/lang.cls.php:235 msgid "Backend Heartbeat TTL" msgstr "" #: src/lang.cls.php:236 msgid "Editor Heartbeat" msgstr "" #: src/lang.cls.php:237 msgid "Editor Heartbeat TTL" msgstr "" #: src/lang.cls.php:239 msgid "Use CDN Mapping" msgstr "" #: src/lang.cls.php:240 msgid "CDN URL" msgstr "" #: src/lang.cls.php:241 msgid "Include Images" msgstr "" #: src/lang.cls.php:242 msgid "Include CSS" msgstr "" #: src/lang.cls.php:243 msgid "Include JS" msgstr "" #: src/lang.cls.php:244 #: tpl/cdn/other.tpl.php:87 msgid "Include File Types" msgstr "" #: src/lang.cls.php:245 msgid "HTML Attribute To Replace" msgstr "" #: src/lang.cls.php:246 msgid "Original URLs" msgstr "" #: src/lang.cls.php:247 msgid "Included Directories" msgstr "" #: src/lang.cls.php:248 msgid "Exclude Path" msgstr "" #: src/lang.cls.php:249 msgid "Cloudflare API" msgstr "" #: src/lang.cls.php:252 msgid "Crawl Interval" msgstr "" #: src/lang.cls.php:253 msgid "Server Load Limit" msgstr "" #: src/lang.cls.php:254 msgid "Role Simulation" msgstr "" #: src/lang.cls.php:255 msgid "Cookie Simulation" msgstr "" #: src/lang.cls.php:256 msgid "Custom Sitemap" msgstr "" #: src/lang.cls.php:258 #: tpl/inc/disabled_all.php:5 msgid "Disable All Features" msgstr "" #: src/lang.cls.php:259 #: tpl/toolbox/log_viewer.tpl.php:11 msgid "Debug Log" msgstr "" #: src/lang.cls.php:260 msgid "Admin IPs" msgstr "" #: src/lang.cls.php:261 msgid "Debug Level" msgstr "" #: src/lang.cls.php:262 msgid "Log File Size Limit" msgstr "" #: src/lang.cls.php:263 msgid "Collapse Query Strings" msgstr "" #: src/lang.cls.php:264 msgid "Debug URI Includes" msgstr "" #: src/lang.cls.php:265 msgid "Debug URI Excludes" msgstr "" #: src/lang.cls.php:266 msgid "Debug String Excludes" msgstr "" #: src/lang.cls.php:268 msgid "Revisions Max Number" msgstr "" #: src/lang.cls.php:269 msgid "Revisions Max Age" msgstr "" #: src/media.cls.php:257 msgid "LiteSpeed Optimization" msgstr "" #: src/media.cls.php:308 #: src/media.cls.php:337 #: src/media.cls.php:363 #: src/media.cls.php:402 msgid "(optm)" msgstr "" #: src/media.cls.php:309 msgid "Currently using optimized version of file." msgstr "" #: src/media.cls.php:309 #: src/media.cls.php:367 msgid "Click to switch to original (unoptimized) version." msgstr "" #: src/media.cls.php:312 #: src/media.cls.php:370 msgid "(non-optm)" msgstr "" #: src/media.cls.php:313 msgid "Currently using original (unoptimized) version of file." msgstr "" #: src/media.cls.php:313 #: src/media.cls.php:374 msgid "Click to switch to optimized version." msgstr "" #: src/media.cls.php:319 msgid "Original file reduced by %1$s (%2$s)" msgstr "" #: src/media.cls.php:323 msgid "Orig saved %s" msgstr "" #: src/media.cls.php:336 #: src/media.cls.php:400 msgid "Using optimized version of file. " msgstr "" #: src/media.cls.php:336 msgid "No backup of original file exists." msgstr "" #: src/media.cls.php:341 msgid "Congratulation! Your file was already optimized" msgstr "" #: src/media.cls.php:342 msgid "Orig %s" msgstr "" #: src/media.cls.php:342 msgid "(no savings)" msgstr "" #: src/media.cls.php:344 msgid "Orig" msgstr "" #: src/media.cls.php:365 msgid "Currently using optimized version of AVIF file." msgstr "" #: src/media.cls.php:366 msgid "Currently using optimized version of WebP file." msgstr "" #: src/media.cls.php:372 msgid "Currently using original (unoptimized) version of AVIF file." msgstr "" #: src/media.cls.php:373 msgid "Currently using original (unoptimized) version of WebP file." msgstr "" #: src/media.cls.php:381 msgid "AVIF file reduced by %1$s (%2$s)" msgstr "" #: src/media.cls.php:381 msgid "WebP file reduced by %1$s (%2$s)" msgstr "" #: src/media.cls.php:387 msgid "AVIF saved %s" msgstr "" #: src/media.cls.php:387 msgid "WebP saved %s" msgstr "" #: src/media.cls.php:401 msgid "No backup of unoptimized AVIF file exists." msgstr "" #: src/media.cls.php:401 msgid "No backup of unoptimized WebP file exists." msgstr "" #: src/media.cls.php:416 msgid "Restore from backup" msgstr "" #: src/metabox.cls.php:30 msgid "Disable Cache" msgstr "" #: src/metabox.cls.php:31 msgid "Disable Image Lazyload" msgstr "" #: src/metabox.cls.php:32 msgid "Disable VPI" msgstr "" #: src/metabox.cls.php:34 msgid "Mobile" msgstr "" #: src/metabox.cls.php:63 msgid "LiteSpeed Options" msgstr "" #: src/object-cache.cls.php:498 msgid "Redis encountered a fatal error: %s (code: %d)" msgstr "" #: src/placeholder.cls.php:88 msgid "LQIP" msgstr "" #: src/placeholder.cls.php:147 msgid "LQIP image preview for size %s" msgstr "" #: src/purge.cls.php:213 msgid "Purged all caches successfully." msgstr "" #: src/purge.cls.php:236 msgid "Notified LiteSpeed Web Server to purge all LSCache entries." msgstr "" #: src/purge.cls.php:256 msgid "Cleaned all Critical CSS files." msgstr "" #: src/purge.cls.php:276 msgid "Cleaned all Unique CSS files." msgstr "" #: src/purge.cls.php:316 msgid "Cleaned all LQIP files." msgstr "" #: src/purge.cls.php:334 msgid "Cleaned all Gravatar files." msgstr "" #: src/purge.cls.php:352 msgid "Cleaned all localized resource entries." msgstr "" #: src/purge.cls.php:387 msgid "Notified LiteSpeed Web Server to purge CSS/JS entries." msgstr "" #: src/purge.cls.php:404 msgid "Opcode cache is not enabled." msgstr "" #: src/purge.cls.php:419 msgid "Reset the entire opcode cache successfully." msgstr "" #: src/purge.cls.php:449 msgid "Object cache is not enabled." msgstr "" #: src/purge.cls.php:462 msgid "Purge all object caches successfully." msgstr "" #: src/purge.cls.php:684 msgid "Notified LiteSpeed Web Server to purge the front page." msgstr "" #: src/purge.cls.php:699 msgid "Notified LiteSpeed Web Server to purge all pages." msgstr "" #: src/purge.cls.php:720 msgid "Notified LiteSpeed Web Server to purge error pages." msgstr "" #: src/purge.cls.php:748 msgid "Purge category %s" msgstr "" #: src/purge.cls.php:778 msgid "Purge tag %s" msgstr "" #: src/purge.cls.php:813 msgid "Purge url %s" msgstr "" #: src/root.cls.php:208 msgid "All QUIC.cloud service queues have been cleared." msgstr "" #: src/task.cls.php:189 msgid "Every Minute" msgstr "" #: src/task.cls.php:209 msgid "LiteSpeed Crawler Cron" msgstr "" #: src/tool.cls.php:37 #: src/tool.cls.php:48 msgid "Failed to detect IP" msgstr "" #: src/utility.cls.php:235 msgid "right now" msgstr "" #: src/utility.cls.php:235 msgid "just now" msgstr "" #: src/utility.cls.php:238 #: tpl/dash/dashboard.tpl.php:421 #: tpl/dash/dashboard.tpl.php:490 msgid " %s ago" msgstr "" #: thirdparty/litespeed-check.cls.php:74 #: thirdparty/litespeed-check.cls.php:125 msgid "Please consider disabling the following detected plugins, as they may conflict with LiteSpeed Cache:" msgstr "" #: thirdparty/woocommerce.content.tpl.php:17 msgid "WooCommerce Settings" msgstr "" #: thirdparty/woocommerce.content.tpl.php:22 #: tpl/cache/settings-advanced.tpl.php:14 #: tpl/cache/settings_inc.browser.tpl.php:12 #: tpl/toolbox/heartbeat.tpl.php:14 #: tpl/toolbox/report.tpl.php:38 msgid "NOTICE:" msgstr "" #: thirdparty/woocommerce.content.tpl.php:23 msgid "After verifying that the cache works in general, please test the cart." msgstr "" #: thirdparty/woocommerce.content.tpl.php:24 msgid "To test the cart, visit the <a %s>FAQ</a>." msgstr "" #: thirdparty/woocommerce.content.tpl.php:25 msgid "By default, the My Account, Checkout, and Cart pages are automatically excluded from caching. Misconfiguration of page associations in WooCommerce settings may cause some pages to be erroneously excluded." msgstr "" #: thirdparty/woocommerce.content.tpl.php:33 msgid "Product Update Interval" msgstr "" #: thirdparty/woocommerce.content.tpl.php:38 msgid "Purge product on changes to the quantity or stock status." msgstr "" #: thirdparty/woocommerce.content.tpl.php:38 msgid "Purge categories only when stock status changes." msgstr "" #: thirdparty/woocommerce.content.tpl.php:39 msgid "Purge product and categories only when the stock status changes." msgstr "" #: thirdparty/woocommerce.content.tpl.php:40 msgid "Purge product only when the stock status changes." msgstr "" #: thirdparty/woocommerce.content.tpl.php:40 msgid "Do not purge categories on changes to the quantity or stock status." msgstr "" #: thirdparty/woocommerce.content.tpl.php:41 msgid "Always purge both product and categories on changes to the quantity or stock status." msgstr "" #: thirdparty/woocommerce.content.tpl.php:54 msgid "Determines how changes in product quantity and product stock status affect product pages and their associated category pages." msgstr "" #: thirdparty/woocommerce.content.tpl.php:62 msgid "Vary for Mini Cart" msgstr "" #: thirdparty/woocommerce.content.tpl.php:69 msgid "Generate a separate vary cache copy for the mini cart when the cart is not empty." msgstr "" #: thirdparty/woocommerce.content.tpl.php:70 msgid "If your theme does not use JS to update the mini cart, you must enable this option to display the correct cart contents." msgstr "" #: thirdparty/woocommerce.tab.tpl.php:3 msgid "WooCommerce" msgstr "" #: tpl/banner/cloud_news.tpl.php:23 #: tpl/banner/cloud_news.tpl.php:30 msgid "Install" msgstr "" #: tpl/banner/cloud_promo.tpl.php:14 msgid "You just unlocked a promotion from QUIC.cloud!" msgstr "" #: tpl/banner/cloud_promo.tpl.php:18 msgid "Spread the love and earn %s credits to use in our QUIC.cloud online services." msgstr "" #: tpl/banner/cloud_promo.tpl.php:25 msgid "Send to twitter to get %s bonus" msgstr "" #: tpl/banner/cloud_promo.tpl.php:30 #: tpl/page_optm/settings_tuning_css.tpl.php:58 #: tpl/page_optm/settings_tuning_css.tpl.php:134 msgid "Learn more" msgstr "" #: tpl/banner/cloud_promo.tpl.php:35 msgid "Tweet preview" msgstr "" #: tpl/banner/cloud_promo.tpl.php:51 msgid "Tweet this" msgstr "" #: tpl/banner/cloud_promo.tpl.php:63 msgid "Dismiss this notice" msgstr "" #: tpl/banner/new_version.php:59 msgid "New Version Available!" msgstr "" #: tpl/banner/new_version.php:63 msgid "New release %s is available now." msgstr "" #: tpl/banner/new_version.php:71 #: tpl/banner/new_version_dev.tpl.php:24 #: tpl/toolbox/beta_test.tpl.php:59 msgid "Upgrade" msgstr "" #: tpl/banner/new_version.php:81 msgid "Turn On Auto Upgrade" msgstr "" #: tpl/banner/new_version.php:87 msgid "Maybe Later" msgstr "" #: tpl/banner/new_version.php:96 msgid "Dismiss this notice." msgstr "" #: tpl/banner/new_version_dev.tpl.php:12 msgid "New Developer Version Available!" msgstr "" #: tpl/banner/new_version_dev.tpl.php:16 msgid "New developer version %s is available now." msgstr "" #: tpl/banner/score.php:24 msgid "Thank You for Using the LiteSpeed Cache Plugin!" msgstr "" #: tpl/banner/score.php:28 #: tpl/dash/dashboard.tpl.php:360 msgid "Page Load Time" msgstr "" #: tpl/banner/score.php:34 #: tpl/banner/score.php:74 #: tpl/dash/dashboard.tpl.php:376 #: tpl/dash/dashboard.tpl.php:457 msgid "Before" msgstr "" #: tpl/banner/score.php:45 #: tpl/banner/score.php:84 #: tpl/dash/dashboard.tpl.php:385 #: tpl/dash/dashboard.tpl.php:465 msgid "After" msgstr "" #: tpl/banner/score.php:55 #: tpl/banner/score.php:94 #: tpl/dash/dashboard.tpl.php:393 #: tpl/dash/dashboard.tpl.php:473 msgid "Improved by" msgstr "" #: tpl/banner/score.php:68 #: tpl/dash/dashboard.tpl.php:437 msgid "PageSpeed Score" msgstr "" #: tpl/banner/score.php:113 msgid "Sure I'd love to review!" msgstr "" #: tpl/banner/score.php:117 msgid "I've already left a review" msgstr "" #: tpl/banner/score.php:118 msgid "Maybe later" msgstr "" #: tpl/banner/score.php:122 msgid "Created with ❤️ by LiteSpeed team." msgstr "" #: tpl/banner/score.php:124 msgid "<a %s>Support forum</a> | <a %s>Submit a ticket</a>" msgstr "" #: tpl/banner/slack.php:9 msgid "Welcome to LiteSpeed" msgstr "" #: tpl/banner/slack.php:13 msgid "Want to connect with other LiteSpeed users?" msgstr "" #: tpl/banner/slack.php:14 msgid "Join the %s community." msgstr "" #: tpl/banner/slack.php:23 msgid "Join Us on Slack" msgstr "" #: tpl/cache/entry.tpl.php:7 #: tpl/cache/settings-ttl.tpl.php:7 msgid "TTL" msgstr "" #: tpl/cache/entry.tpl.php:8 #: tpl/cache/entry_network.tpl.php:7 #: tpl/toolbox/entry.tpl.php:6 #: tpl/toolbox/purge.tpl.php:134 msgid "Purge" msgstr "" #: tpl/cache/entry.tpl.php:9 #: tpl/cache/entry_network.tpl.php:8 msgid "Excludes" msgstr "" #: tpl/cache/entry.tpl.php:10 msgid "ESI" msgstr "" #: tpl/cache/entry.tpl.php:14 #: tpl/cache/entry_network.tpl.php:9 msgid "Object" msgstr "" #: tpl/cache/entry.tpl.php:15 #: tpl/cache/entry_network.tpl.php:10 msgid "Browser" msgstr "" #: tpl/cache/entry.tpl.php:18 #: tpl/cache/entry_network.tpl.php:11 #: tpl/toolbox/settings-debug.tpl.php:84 msgid "Advanced" msgstr "" #: tpl/cache/entry.tpl.php:39 msgid "LiteSpeed Cache Settings" msgstr "" #: tpl/cache/entry_network.tpl.php:18 msgid "LiteSpeed Cache Network Cache Settings" msgstr "" #: tpl/cache/more_settings_tip.tpl.php:12 #: tpl/cache/settings-excludes.tpl.php:65 #: tpl/cache/settings-excludes.tpl.php:98 #: tpl/cdn/other.tpl.php:63 #: tpl/crawler/settings.tpl.php:72 #: tpl/crawler/settings.tpl.php:77 msgid "NOTE" msgstr "" #: tpl/cache/more_settings_tip.tpl.php:15 msgid "More settings available under %s menu" msgstr "" #: tpl/cache/network_settings-advanced.tpl.php:7 #: tpl/cache/settings-advanced.tpl.php:9 msgid "Advanced Settings" msgstr "" #: tpl/cache/network_settings-cache.tpl.php:9 #: tpl/cache/settings-cache.tpl.php:9 msgid "Cache Control Settings" msgstr "" #: tpl/cache/network_settings-cache.tpl.php:16 msgid "Network Enable Cache" msgstr "" #: tpl/cache/network_settings-cache.tpl.php:20 msgid "Enabling LiteSpeed Cache for WordPress here enables the cache for the network." msgstr "" #: tpl/cache/network_settings-cache.tpl.php:21 msgid "It is <b>STRONGLY</b> recommend that the compatibility with other plugins on a single/few sites is tested first." msgstr "" #: tpl/cache/network_settings-cache.tpl.php:22 msgid "This is to ensure compatibility prior to enabling the cache for all sites." msgstr "" #: tpl/cache/network_settings-excludes.tpl.php:7 #: tpl/cache/settings-excludes.tpl.php:9 msgid "Exclude Settings" msgstr "" #: tpl/cache/network_settings-purge.tpl.php:7 #: tpl/cache/settings-purge.tpl.php:7 msgid "Purge Settings" msgstr "" #: tpl/cache/settings-advanced.tpl.php:15 msgid "These settings are meant for ADVANCED USERS ONLY." msgstr "" #: tpl/cache/settings-advanced.tpl.php:33 msgid "Specify an AJAX action in POST/GET and the number of seconds to cache that request, separated by a space." msgstr "" #: tpl/cache/settings-advanced.tpl.php:53 msgid "Enable this option if you are using both HTTP and HTTPS in the same domain and are noticing cache irregularities." msgstr "" #: tpl/cache/settings-advanced.tpl.php:67 msgid "When a visitor hovers over a page link, preload that page. This will speed up the visit to that link." msgstr "" #: tpl/cache/settings-advanced.tpl.php:72 msgid "This will generate extra requests to the server, which will increase server load." msgstr "" #: tpl/cache/settings-cache.tpl.php:22 msgid "Use Network Admin Setting" msgstr "" #: tpl/cache/settings-cache.tpl.php:28 msgid "Please visit the <a %s>Information</a> page on how to test the cache." msgstr "" #: tpl/cache/settings-cache.tpl.php:32 #: tpl/crawler/settings.tpl.php:103 #: tpl/crawler/settings.tpl.php:112 #: tpl/crawler/summary.tpl.php:181 #: tpl/page_optm/entry.tpl.php:32 msgid "NOTICE" msgstr "" #: tpl/cache/settings-cache.tpl.php:32 msgid "When disabling the cache, all cached entries for this site will be purged." msgstr "" #: tpl/cache/settings-cache.tpl.php:35 msgid "The network admin setting can be overridden here." msgstr "" #: tpl/cache/settings-cache.tpl.php:40 msgid "With QUIC.cloud CDN enabled, you may still be seeing cache headers from your local server." msgstr "" #: tpl/cache/settings-cache.tpl.php:54 msgid "Privately cache frontend pages for logged-in users. (LSWS %s required)" msgstr "" #: tpl/cache/settings-cache.tpl.php:67 msgid "Privately cache commenters that have pending comments. Disabling this option will serve non-cacheable pages to commenters. (LSWS %s required)" msgstr "" #: tpl/cache/settings-cache.tpl.php:80 msgid "Cache requests made by WordPress REST API calls." msgstr "" #: tpl/cache/settings-cache.tpl.php:93 msgid "Disabling this option may negatively affect performance." msgstr "" #: tpl/cache/settings-cache.tpl.php:113 msgid "URI Paths containing these strings will NOT be cached as public." msgstr "" #: tpl/cache/settings-cache.tpl.php:127 msgid "Paths containing these strings will be cached regardless of no-cacheable settings." msgstr "" #: tpl/cache/settings-cache.tpl.php:129 #: tpl/cache/settings-cache.tpl.php:146 msgid "To define a custom TTL for a URI, add a space followed by the TTL value to the end of the URI." msgstr "" #: tpl/cache/settings-cache.tpl.php:130 #: tpl/cache/settings-cache.tpl.php:147 msgid "For example, %1$s defines a TTL of %2$s seconds for %3$s." msgstr "" #: tpl/cache/settings-cache.tpl.php:144 msgid "Paths containing these strings will be forced to public cached regardless of no-cacheable settings." msgstr "" #: tpl/cache/settings-esi.tpl.php:7 msgid "ESI Settings" msgstr "" #: tpl/cache/settings-esi.tpl.php:12 msgid "With ESI (Edge Side Includes), pages may be served from cache for logged-in users." msgstr "" #: tpl/cache/settings-esi.tpl.php:13 msgid "ESI allows you to designate parts of your dynamic page as separate fragments that are then assembled together to make the whole page. In other words, ESI lets you “punch holes” in a page, and then fill those holes with content that may be cached privately, cached publicly with its own TTL, or not cached at all." msgstr "" #: tpl/cache/settings-esi.tpl.php:14 msgid "WpW: Private Cache vs. Public Cache" msgstr "" #: tpl/cache/settings-esi.tpl.php:18 msgid "You can turn shortcodes into ESI blocks." msgstr "" #: tpl/cache/settings-esi.tpl.php:20 msgid "Replace %1$s with %2$s." msgstr "" #: tpl/cache/settings-esi.tpl.php:27 msgid "ESI sample for developers" msgstr "" #: tpl/cache/settings-esi.tpl.php:35 #: tpl/cdn/cf.tpl.php:83 #: tpl/crawler/summary.tpl.php:53 #: tpl/inc/check_cache_disabled.php:31 #: tpl/inc/check_if_network_disable_all.php:18 #: tpl/page_optm/settings_css.tpl.php:72 #: tpl/page_optm/settings_css.tpl.php:192 #: tpl/page_optm/settings_localization.tpl.php:11 msgid "WARNING" msgstr "" #: tpl/cache/settings-esi.tpl.php:36 msgid "These options are only available with LiteSpeed Enterprise Web Server or QUIC.cloud CDN." msgstr "" #: tpl/cache/settings-esi.tpl.php:49 msgid "Turn ON to cache public pages for logged in users, and serve the Admin Bar and Comment Form via ESI blocks. These two blocks will be uncached unless enabled below." msgstr "" #: tpl/cache/settings-esi.tpl.php:62 msgid " Cache the built-in Admin Bar ESI block." msgstr "" #: tpl/cache/settings-esi.tpl.php:75 msgid "Cache the built-in Comment Form ESI block." msgstr "" #: tpl/cache/settings-esi.tpl.php:92 msgid "The list will be merged with the predefined nonces in your local data file." msgstr "" #: tpl/cache/settings-esi.tpl.php:93 msgid "The latest data file is" msgstr "" #: tpl/cache/settings-esi.tpl.php:96 #: tpl/page_optm/settings_media_exc.tpl.php:27 #: tpl/page_optm/settings_tuning.tpl.php:40 #: tpl/page_optm/settings_tuning.tpl.php:60 #: tpl/page_optm/settings_tuning.tpl.php:81 #: tpl/page_optm/settings_tuning.tpl.php:102 #: tpl/page_optm/settings_tuning.tpl.php:121 #: tpl/page_optm/settings_tuning_css.tpl.php:25 #: tpl/page_optm/settings_tuning_css.tpl.php:86 msgid "Filter %s is supported." msgstr "" #: tpl/cache/settings-esi.tpl.php:102 msgid "The above nonces will be converted to ESI automatically." msgstr "" #: tpl/cache/settings-esi.tpl.php:104 msgid "An optional second parameter may be used to specify cache control. Use a space to separate" msgstr "" #: tpl/cache/settings-esi.tpl.php:107 #: tpl/cache/settings-purge.tpl.php:110 #: tpl/cdn/other.tpl.php:128 msgid "Wildcard %1$s supported (match zero or more characters). For example, to match %2$s and %3$s, use %4$s." msgstr "" #: tpl/cache/settings-esi.tpl.php:135 msgid "If your site contains public content that certain user roles can see but other roles cannot, you can specify a Vary Group for those user roles. For example, specifying an administrator vary group allows there to be a separate publicly-cached page tailored to administrators (with “edit” links, etc), while all other user roles see the default public page." msgstr "" #: tpl/cache/settings-excludes.tpl.php:24 msgid "Paths containing these strings will not be cached." msgstr "" #: tpl/cache/settings-excludes.tpl.php:26 #: tpl/page_optm/settings_tuning.tpl.php:62 #: tpl/page_optm/settings_tuning.tpl.php:83 #: tpl/page_optm/settings_tuning_css.tpl.php:27 #: tpl/page_optm/settings_tuning_css.tpl.php:67 #: tpl/page_optm/settings_tuning_css.tpl.php:143 msgid "Predefined list will also be combined w/ the above settings" msgstr "" #: tpl/cache/settings-excludes.tpl.php:39 msgid "Query strings containing these parameters will not be cached." msgstr "" #: tpl/cache/settings-excludes.tpl.php:40 msgid "For example, for %s, %s and %s can be used here." msgstr "" #: tpl/cache/settings-excludes.tpl.php:60 msgid "All categories are cached by default." msgstr "" #: tpl/cache/settings-excludes.tpl.php:61 #: tpl/cache/settings-excludes.tpl.php:94 #: tpl/cache/settings_inc.exclude_cookies.tpl.php:14 #: tpl/cache/settings_inc.exclude_useragent.tpl.php:14 msgid "To prevent %s from being cached, enter them here." msgstr "" #: tpl/cache/settings-excludes.tpl.php:61 msgid "categories" msgstr "" #: tpl/cache/settings-excludes.tpl.php:67 msgid "If the category name is not found, the category will be removed from the list on save." msgstr "" #: tpl/cache/settings-excludes.tpl.php:93 msgid "All tags are cached by default." msgstr "" #: tpl/cache/settings-excludes.tpl.php:94 msgid "tags" msgstr "" #: tpl/cache/settings-excludes.tpl.php:100 msgid "If the tag slug is not found, the tag will be removed from the list on save." msgstr "" #: tpl/cache/settings-excludes.tpl.php:102 msgid "To exclude %1$s, insert %2$s." msgstr "" #: tpl/cache/settings-excludes.tpl.php:129 msgid "Selected roles will be excluded from cache." msgstr "" #: tpl/cache/settings-purge.tpl.php:13 msgid "All pages" msgstr "" #: tpl/cache/settings-purge.tpl.php:14 msgid "Front page" msgstr "" #: tpl/cache/settings-purge.tpl.php:15 msgid "Home page" msgstr "" #: tpl/cache/settings-purge.tpl.php:16 msgid "Pages" msgstr "" #: tpl/cache/settings-purge.tpl.php:18 msgid "All pages with Recent Posts Widget" msgstr "" #: tpl/cache/settings-purge.tpl.php:20 msgid "Author archive" msgstr "" #: tpl/cache/settings-purge.tpl.php:21 msgid "Post type archive" msgstr "" #: tpl/cache/settings-purge.tpl.php:23 msgid "Yearly archive" msgstr "" #: tpl/cache/settings-purge.tpl.php:24 msgid "Monthly archive" msgstr "" #: tpl/cache/settings-purge.tpl.php:25 msgid "Daily archive" msgstr "" #: tpl/cache/settings-purge.tpl.php:27 msgid "Term archive (include category, tag, and tax)" msgstr "" #: tpl/cache/settings-purge.tpl.php:47 msgid "Auto Purge Rules For Publish/Update" msgstr "" #: tpl/cache/settings-purge.tpl.php:50 #: tpl/cache/settings-purge.tpl.php:89 #: tpl/cache/settings-purge.tpl.php:113 #: tpl/page_optm/settings_tuning_css.tpl.php:61 #: tpl/page_optm/settings_tuning_css.tpl.php:137 msgid "Note" msgstr "" #: tpl/cache/settings-purge.tpl.php:52 msgid "Select \"All\" if there are dynamic widgets linked to posts on pages other than the front or home pages." msgstr "" #: tpl/cache/settings-purge.tpl.php:53 msgid "Other checkboxes will be ignored." msgstr "" #: tpl/cache/settings-purge.tpl.php:54 msgid "Select only the archive types that are currently used, the others can be left unchecked." msgstr "" #: tpl/cache/settings-purge.tpl.php:72 msgid "Select which pages will be automatically purged when posts are published/updated." msgstr "" #: tpl/cache/settings-purge.tpl.php:85 msgid "If ON, the stale copy of a cached page will be shown to visitors until a new cache copy is available. Reduces the server load for following visits. If OFF, the page will be dynamically generated while visitors wait." msgstr "" #: tpl/cache/settings-purge.tpl.php:91 msgid "By design, this option may serve stale content. Do not enable this option, if that is not OK with you." msgstr "" #: tpl/cache/settings-purge.tpl.php:105 msgid "The URLs here (one per line) will be purged automatically at the time set in the option \"%s\"." msgstr "" #: tpl/cache/settings-purge.tpl.php:106 msgid "Both %1$s and %2$s are acceptable." msgstr "" #: tpl/cache/settings-purge.tpl.php:115 msgid "For URLs with wildcards, there may be a delay in initiating scheduled purge." msgstr "" #: tpl/cache/settings-purge.tpl.php:130 msgid "Specify the time to purge the \"%s\" list." msgstr "" #: tpl/cache/settings-purge.tpl.php:131 msgid "Current server time is %s." msgstr "" #: tpl/cache/settings-purge.tpl.php:153 msgid "A Purge All will be executed when WordPress runs these hooks." msgstr "" #: tpl/cache/settings-ttl.tpl.php:22 msgid "Specify how long, in seconds, public pages are cached." msgstr "" #: tpl/cache/settings-ttl.tpl.php:37 msgid "Specify how long, in seconds, private pages are cached." msgstr "" #: tpl/cache/settings-ttl.tpl.php:52 msgid "Specify how long, in seconds, the front page is cached." msgstr "" #: tpl/cache/settings-ttl.tpl.php:67 msgid "Specify how long, in seconds, feeds are cached." msgstr "" #: tpl/cache/settings-ttl.tpl.php:68 #: tpl/cache/settings-ttl.tpl.php:83 msgid "If this is set to a number less than 30, feeds will not be cached." msgstr "" #: tpl/cache/settings-ttl.tpl.php:82 msgid "Specify how long, in seconds, REST calls are cached." msgstr "" #: tpl/cache/settings-ttl.tpl.php:105 msgid "Specify an HTTP status code and the number of seconds to cache that page, separated by a space." msgstr "" #: tpl/cache/settings_inc.browser.tpl.php:6 msgid "Browser Cache Settings" msgstr "" #: tpl/cache/settings_inc.browser.tpl.php:13 msgid "OpenLiteSpeed users please check this" msgstr "" #: tpl/cache/settings_inc.browser.tpl.php:14 msgid "Setting Up Custom Headers" msgstr "" #: tpl/cache/settings_inc.browser.tpl.php:27 msgid "Browser caching stores static files locally in the user's browser. Turn on this setting to reduce repeated requests for static files." msgstr "" #: tpl/cache/settings_inc.browser.tpl.php:29 msgid "You can turn on browser caching in server admin too. <a %s>Learn more about LiteSpeed browser cache settings</a>." msgstr "" #: tpl/cache/settings_inc.browser.tpl.php:42 msgid "The amount of time, in seconds, that files will be stored in browser cache before expiring." msgstr "" #: tpl/cache/settings_inc.cache_dropquery.tpl.php:14 msgid "Ignore certain query strings when caching. (LSWS %s required)" msgstr "" #: tpl/cache/settings_inc.cache_dropquery.tpl.php:15 msgid "For example, to drop parameters beginning with %s, %s can be used here." msgstr "" #: tpl/cache/settings_inc.cache_mobile.tpl.php:18 msgid "Serve a separate cache copy for mobile visitors." msgstr "" #: tpl/cache/settings_inc.cache_mobile.tpl.php:19 msgid "Learn more about when this is needed" msgstr "" #: tpl/cache/settings_inc.cache_mobile.tpl.php:43 msgid "Htaccess did not match configuration option." msgstr "" #: tpl/cache/settings_inc.cache_mobile.tpl.php:44 msgid "Htaccess rule is: %s" msgstr "" #: tpl/cache/settings_inc.cache_mobile.tpl.php:71 msgid "If %1$s is %2$s, then %3$s must be populated!" msgstr "" #: tpl/cache/settings_inc.cache_resources.tpl.php:15 msgid "Some themes and plugins add resources via a PHP request." msgstr "" #: tpl/cache/settings_inc.cache_resources.tpl.php:16 msgid "Caching these pages may improve server performance by avoiding unnecessary PHP calls." msgstr "" #: tpl/cache/settings_inc.exclude_cookies.tpl.php:14 msgid "cookies" msgstr "" #: tpl/cache/settings_inc.exclude_useragent.tpl.php:14 msgid "user agents" msgstr "" #: tpl/cache/settings_inc.login_cookie.tpl.php:19 #: tpl/cache/settings_inc.login_cookie.tpl.php:77 msgid "SYNTAX: alphanumeric and \"_\"." msgstr "" #: tpl/cache/settings_inc.login_cookie.tpl.php:20 #: tpl/cache/settings_inc.login_cookie.tpl.php:78 msgid "No spaces and case sensitive." msgstr "" #: tpl/cache/settings_inc.login_cookie.tpl.php:21 msgid "MUST BE UNIQUE FROM OTHER WEB APPLICATIONS." msgstr "" #: tpl/cache/settings_inc.login_cookie.tpl.php:24 msgid "The default login cookie is %s." msgstr "" #: tpl/cache/settings_inc.login_cookie.tpl.php:25 msgid "The server will determine if the user is logged in based on the existence of this cookie." msgstr "" #: tpl/cache/settings_inc.login_cookie.tpl.php:26 msgid "This setting is useful for those that have multiple web applications for the same domain." msgstr "" #: tpl/cache/settings_inc.login_cookie.tpl.php:27 msgid "If every web application uses the same cookie, the server may confuse whether a user is logged in or not." msgstr "" #: tpl/cache/settings_inc.login_cookie.tpl.php:28 msgid "The cookie set here will be used for this WordPress installation." msgstr "" #: tpl/cache/settings_inc.login_cookie.tpl.php:31 msgid "Example use case:" msgstr "" #: tpl/cache/settings_inc.login_cookie.tpl.php:33 msgid "There is a WordPress installed for %s." msgstr "" #: tpl/cache/settings_inc.login_cookie.tpl.php:35 msgid "Then another WordPress is installed (NOT MULTISITE) at %s" msgstr "" #: tpl/cache/settings_inc.login_cookie.tpl.php:36 msgid "The cache needs to distinguish who is logged into which WordPress site in order to cache correctly." msgstr "" #: tpl/cache/settings_inc.login_cookie.tpl.php:43 msgid "Invalid login cookie. Invalid characters found." msgstr "" #: tpl/cache/settings_inc.login_cookie.tpl.php:57 msgid "WARNING: The .htaccess login cookie and Database login cookie do not match." msgstr "" #: tpl/cache/settings_inc.login_cookie.tpl.php:81 msgid "You can list the 3rd party vary cookies here." msgstr "" #: tpl/cache/settings_inc.object.tpl.php:6 #: tpl/general/online.tpl.php:123 msgid "Enabled" msgstr "" #: tpl/cache/settings_inc.object.tpl.php:7 #: tpl/general/online.tpl.php:125 msgid "Disabled" msgstr "" #: tpl/cache/settings_inc.object.tpl.php:14 msgid "Not Available" msgstr "" #: tpl/cache/settings_inc.object.tpl.php:17 msgid "Passed" msgstr "" #: tpl/cache/settings_inc.object.tpl.php:21 msgid "Failed" msgstr "" #: tpl/cache/settings_inc.object.tpl.php:28 msgid "Object Cache Settings" msgstr "" #: tpl/cache/settings_inc.object.tpl.php:42 msgid "Use external object cache functionality." msgstr "" #: tpl/cache/settings_inc.object.tpl.php:48 #: tpl/crawler/blacklist.tpl.php:34 #: tpl/crawler/summary.tpl.php:137 msgid "Status" msgstr "" #: tpl/cache/settings_inc.object.tpl.php:51 #: tpl/cache/settings_inc.object.tpl.php:52 msgid "%s Extension" msgstr "" #: tpl/cache/settings_inc.object.tpl.php:53 msgid "Connection Test" msgstr "" #: tpl/cache/settings_inc.object.tpl.php:79 msgid "Your %s Hostname or IP address." msgstr "" #: tpl/cache/settings_inc.object.tpl.php:80 #: tpl/cache/settings_inc.object.tpl.php:95 msgid "If you are using a %1$s socket, %2$s should be set to %3$s" msgstr "" #: tpl/cache/settings_inc.object.tpl.php:93 #: tpl/cache/settings_inc.object.tpl.php:94 msgid "Default port for %1$s is %2$s." msgstr "" #: tpl/cache/settings_inc.object.tpl.php:108 msgid "Default TTL for cached objects." msgstr "" #: tpl/cache/settings_inc.object.tpl.php:121 msgid "Only available when %s is installed." msgstr "" #: tpl/cache/settings_inc.object.tpl.php:134 msgid "Specify the password used when connecting." msgstr "" #: tpl/cache/settings_inc.object.tpl.php:147 msgid "Database to be used" msgstr "" #: tpl/cache/settings_inc.object.tpl.php:160 msgid "Groups cached at the network level." msgstr "" #: tpl/cache/settings_inc.object.tpl.php:187 msgid "Use keep-alive connections to speed up cache operations." msgstr "" #: tpl/cache/settings_inc.object.tpl.php:200 msgid "Improve wp-admin speed through caching. (May encounter expired data)" msgstr "" #: tpl/cache/settings_inc.object.tpl.php:213 msgid "Save transients in database when %1$s is %2$s." msgstr "" #: tpl/cache/settings_inc.purge_on_upgrade.tpl.php:15 msgid "When enabled, the cache will automatically purge when any plugin, theme or the WordPress core is upgraded." msgstr "" #: tpl/cdn/cf.tpl.php:11 msgid "Cloudflare Settings" msgstr "" #: tpl/cdn/cf.tpl.php:25 msgid "Use %s API functionality." msgstr "" #: tpl/cdn/cf.tpl.php:29 msgid "Global API Key / API Token" msgstr "" #: tpl/cdn/cf.tpl.php:33 msgid "Your API key / token is used to access %s APIs." msgstr "" #: tpl/cdn/cf.tpl.php:34 msgid "Get it from <a %1$s>%2$s</a>." msgstr "" #: tpl/cdn/cf.tpl.php:35 msgid "Recommended to generate the token from Cloudflare API token template \"WordPress\"." msgstr "" #: tpl/cdn/cf.tpl.php:40 msgid "Email Address" msgstr "" #: tpl/cdn/cf.tpl.php:44 msgid "Your Email address on %s." msgstr "" #: tpl/cdn/cf.tpl.php:45 msgid "Optional when API token used." msgstr "" #: tpl/cdn/cf.tpl.php:50 msgid "Domain" msgstr "" #: tpl/cdn/cf.tpl.php:58 msgid "You can just type part of the domain." msgstr "" #: tpl/cdn/cf.tpl.php:59 msgid "Once saved, it will be matched with the current list and completed automatically." msgstr "" #: tpl/cdn/cf.tpl.php:85 msgid "To enable the following functionality, turn ON Cloudflare API in CDN Settings." msgstr "" #: tpl/cdn/cf.tpl.php:90 msgid "Cloudflare Domain" msgstr "" #: tpl/cdn/cf.tpl.php:91 msgid "Cloudflare Zone" msgstr "" #: tpl/cdn/cf.tpl.php:94 msgid "Development Mode" msgstr "" #: tpl/cdn/cf.tpl.php:96 msgid "Turn ON" msgstr "" #: tpl/cdn/cf.tpl.php:99 msgid "Turn OFF" msgstr "" #: tpl/cdn/cf.tpl.php:102 msgid "Check Status" msgstr "" #: tpl/cdn/cf.tpl.php:111 msgid "Current status is %1$s since %2$s." msgstr "" #: tpl/cdn/cf.tpl.php:116 msgid "Current status is %s." msgstr "" #: tpl/cdn/cf.tpl.php:117 msgid "Development mode will be automatically turned off in %s." msgstr "" #: tpl/cdn/cf.tpl.php:125 msgid "Temporarily bypass Cloudflare cache. This allows changes to the origin server to be seen in realtime." msgstr "" #: tpl/cdn/cf.tpl.php:126 msgid "Development Mode will be turned off automatically after three hours." msgstr "" #: tpl/cdn/cf.tpl.php:132 msgid "Cloudflare Cache" msgstr "" #: tpl/cdn/cf.tpl.php:138 msgid "Purge Everything" msgstr "" #: tpl/cdn/entry.tpl.php:8 msgid "QUIC.cloud" msgstr "" #: tpl/cdn/entry.tpl.php:10 msgid "Other Static CDN" msgstr "" #: tpl/cdn/entry.tpl.php:17 msgid "LiteSpeed Cache CDN" msgstr "" #: tpl/cdn/other.tpl.php:20 msgid "CDN Settings" msgstr "" #: tpl/cdn/other.tpl.php:34 msgid "Turn this setting %1$s if you are using a traditional Content Delivery Network (CDN) or a subdomain for static content with QUIC.cloud CDN." msgstr "" #: tpl/cdn/other.tpl.php:39 msgid "NOTE: QUIC.cloud CDN and Cloudflare do not use CDN Mapping. If you are are only using QUIC.cloud or Cloudflare, leave this setting %1$s." msgstr "" #: tpl/cdn/other.tpl.php:64 msgid "To randomize CDN hostname, define multiple hostnames for the same resources." msgstr "" #: tpl/cdn/other.tpl.php:69 msgid "Serve all image files through the CDN. This will affect all attachments, HTML %s tags, and CSS %s attributes." msgstr "" #: tpl/cdn/other.tpl.php:73 msgid "Serve all CSS files through the CDN. This will affect all enqueued WP CSS files." msgstr "" #: tpl/cdn/other.tpl.php:77 msgid "Serve all JavaScript files through the CDN. This will affect all enqueued WP JavaScript files." msgstr "" #: tpl/cdn/other.tpl.php:81 msgid "Static file type links to be replaced by CDN links." msgstr "" #: tpl/cdn/other.tpl.php:83 msgid "This will affect all tags containing attributes: %s %s %s." msgstr "" #: tpl/cdn/other.tpl.php:87 msgid "If you turn any of the above settings OFF, please remove the related file types from the %s box." msgstr "" #: tpl/cdn/other.tpl.php:111 msgid "Specify which HTML element attributes will be replaced with CDN Mapping." msgstr "" #: tpl/cdn/other.tpl.php:112 #: tpl/img_optm/settings.tpl.php:118 msgid "Only attributes listed here will be replaced." msgstr "" #: tpl/cdn/other.tpl.php:113 #: tpl/img_optm/settings.tpl.php:119 msgid "Use the format %1$s or %2$s (element is optional)." msgstr "" #: tpl/cdn/other.tpl.php:127 msgid "Site URL to be served through the CDN. Beginning with %1$s. For example, %2$s." msgstr "" #: tpl/cdn/other.tpl.php:150 msgid "Only files within these directories will be pointed to the CDN." msgstr "" #: tpl/cdn/other.tpl.php:164 msgid "Paths containing these strings will not be served from the CDN." msgstr "" #: tpl/cdn/qc.tpl.php:20 #: tpl/dash/dashboard.tpl.php:818 msgid "Refresh Status" msgstr "" #: tpl/cdn/qc.tpl.php:23 msgid "QUIC.cloud CDN Status Overview" msgstr "" #: tpl/cdn/qc.tpl.php:25 msgid "Check the status of your most important settings and the health of your CDN setup here." msgstr "" #: tpl/cdn/qc.tpl.php:30 #: tpl/dash/dashboard.tpl.php:140 msgid "Accelerate, Optimize, Protect" msgstr "" #: tpl/cdn/qc.tpl.php:31 #: tpl/dash/dashboard.tpl.php:143 msgid "Speed up your WordPress site even further with <strong>QUIC.cloud Online Services and CDN</strong>." msgstr "" #: tpl/cdn/qc.tpl.php:32 #: tpl/general/online.tpl.php:46 #: tpl/general/online.tpl.php:132 msgid "Free monthly quota available." msgstr "" #: tpl/cdn/qc.tpl.php:33 #: tpl/dash/dashboard.tpl.php:150 #: tpl/general/online.tpl.php:49 #: tpl/general/online.tpl.php:105 msgid "Enable QUIC.cloud services" msgstr "" #: tpl/cdn/qc.tpl.php:35 #: tpl/dash/dashboard.tpl.php:156 #: tpl/general/online.tpl.php:19 msgid "QUIC.cloud provides CDN and online optimization services, and is not required. You may use many features of this plugin without QUIC.cloud." msgstr "" #: tpl/cdn/qc.tpl.php:36 #: tpl/dash/dashboard.tpl.php:158 msgid "Learn More about QUIC.cloud" msgstr "" #: tpl/cdn/qc.tpl.php:43 msgid "QUIC.cloud CDN is currently <strong>fully disabled</strong>." msgstr "" #: tpl/cdn/qc.tpl.php:45 msgid "QUIC.cloud CDN is <strong>not available</strong> for anonymous (unlinked) users." msgstr "" #: tpl/cdn/qc.tpl.php:49 msgid "Link & Enable QUIC.cloud CDN" msgstr "" #: tpl/cdn/qc.tpl.php:51 #: tpl/dash/dashboard.tpl.php:796 msgid "Enable QUIC.cloud CDN" msgstr "" #: tpl/cdn/qc.tpl.php:61 msgid "Content Delivery Network Service" msgstr "" #: tpl/cdn/qc.tpl.php:62 msgid "no matter where they live." msgstr "" #: tpl/cdn/qc.tpl.php:64 msgid "Best available WordPress performance, globally fast TTFB, easy setup, and <a %s>more</a>!" msgstr "" #: tpl/cdn/qc.tpl.php:78 msgid "QUIC.cloud CDN Options" msgstr "" #: tpl/cdn/qc.tpl.php:96 msgid "To manage your QUIC.cloud options, go to your hosting provider's portal." msgstr "" #: tpl/cdn/qc.tpl.php:98 msgid "To manage your QUIC.cloud options, please contact your hosting provider." msgstr "" #: tpl/cdn/qc.tpl.php:102 #: tpl/cdn/qc.tpl.php:110 msgid "To manage your QUIC.cloud options, go to QUIC.cloud Dashboard." msgstr "" #: tpl/cdn/qc.tpl.php:103 #: tpl/cdn/qc.tpl.php:106 #: tpl/dash/dashboard.tpl.php:345 #: tpl/general/online.tpl.php:140 msgid "Link to QUIC.cloud" msgstr "" #: tpl/cdn/qc.tpl.php:105 msgid "You are currently using services as an anonymous user. To manage your QUIC.cloud options, use the button below to create an account and link to the QUIC.cloud Dashboard." msgstr "" #: tpl/cdn/qc.tpl.php:108 #: tpl/cdn/qc.tpl.php:111 msgid "My QUIC.cloud Dashboard" msgstr "" #: tpl/crawler/blacklist.tpl.php:15 msgid "Are you sure to delete all existing blocklist items?" msgstr "" #: tpl/crawler/blacklist.tpl.php:16 msgid "Empty blocklist" msgstr "" #: tpl/crawler/blacklist.tpl.php:21 #: tpl/crawler/entry.tpl.php:10 msgid "Blocklist" msgstr "" #: tpl/crawler/blacklist.tpl.php:25 #: tpl/img_optm/summary.tpl.php:174 msgid "Total" msgstr "" #: tpl/crawler/blacklist.tpl.php:33 #: tpl/crawler/map.tpl.php:67 #: tpl/toolbox/purge.tpl.php:205 msgid "URL" msgstr "" #: tpl/crawler/blacklist.tpl.php:35 #: tpl/crawler/map.tpl.php:69 msgid "Operation" msgstr "" #: tpl/crawler/blacklist.tpl.php:48 msgid "Remove from Blocklist" msgstr "" #: tpl/crawler/blacklist.tpl.php:58 msgid "PHP Constant %s available to disable blocklist." msgstr "" #: tpl/crawler/blacklist.tpl.php:61 msgid "Filter %s available to disable blocklist." msgstr "" #: tpl/crawler/blacklist.tpl.php:64 msgid "Not blocklisted" msgstr "" #: tpl/crawler/blacklist.tpl.php:65 #: tpl/crawler/map.tpl.php:96 msgid "Blocklisted due to not cacheable" msgstr "" #: tpl/crawler/blacklist.tpl.php:66 #: tpl/crawler/map.tpl.php:53 #: tpl/crawler/map.tpl.php:97 #: tpl/crawler/summary.tpl.php:173 #: tpl/crawler/summary.tpl.php:208 msgid "Blocklisted" msgstr "" #: tpl/crawler/entry.tpl.php:8 msgid "Summary" msgstr "" #: tpl/crawler/entry.tpl.php:9 msgid "Map" msgstr "" #: tpl/crawler/entry.tpl.php:18 msgid "LiteSpeed Cache Crawler" msgstr "" #: tpl/crawler/map.tpl.php:19 msgid "Clean Crawler Map" msgstr "" #: tpl/crawler/map.tpl.php:23 msgid "Refresh Crawler Map" msgstr "" #: tpl/crawler/map.tpl.php:30 msgid "Generated at %s" msgstr "" #: tpl/crawler/map.tpl.php:36 msgid "Sitemap List" msgstr "" #: tpl/crawler/map.tpl.php:40 msgid "Sitemap Total" msgstr "" #: tpl/crawler/map.tpl.php:51 #: tpl/crawler/map.tpl.php:94 msgid "Cache Hit" msgstr "" #: tpl/crawler/map.tpl.php:52 #: tpl/crawler/map.tpl.php:95 msgid "Cache Miss" msgstr "" #: tpl/crawler/map.tpl.php:68 #: tpl/dash/dashboard.tpl.php:74 #: tpl/dash/dashboard.tpl.php:740 msgid "Crawler Status" msgstr "" #: tpl/crawler/map.tpl.php:83 msgid "Add to Blocklist" msgstr "" #: tpl/crawler/settings.tpl.php:11 msgid "Crawler General Settings" msgstr "" #: tpl/crawler/settings.tpl.php:25 msgid "This will enable crawler cron." msgstr "" #: tpl/crawler/settings.tpl.php:39 msgid "Specify how long in seconds before the crawler should initiate crawling the entire sitemap again." msgstr "" #: tpl/crawler/settings.tpl.php:53 msgid "The crawler will use your XML sitemap or sitemap index. Enter the full URL to your sitemap here." msgstr "" #: tpl/crawler/settings.tpl.php:67 msgid "The maximum average server load allowed while crawling. The number of crawler threads in use will be actively reduced until average server load falls under this limit. If this cannot be achieved with a single thread, the current crawler run will be terminated." msgstr "" #: tpl/crawler/settings.tpl.php:73 msgid "Server enforced value" msgstr "" #: tpl/crawler/settings.tpl.php:78 msgid "Server allowed max value" msgstr "" #: tpl/crawler/settings.tpl.php:97 msgid "To crawl the site as a logged-in user, enter the user ids to be simulated." msgstr "" #: tpl/crawler/settings.tpl.php:104 #: tpl/crawler/summary.tpl.php:182 msgid "You must set %s before using this feature." msgstr "" #: tpl/crawler/settings.tpl.php:113 msgid "You must set %1$s to %2$s before using this feature." msgstr "" #: tpl/crawler/settings.tpl.php:141 msgid "To crawl for a particular cookie, enter the cookie name, and the values you wish to crawl for. Values should be one per line. There will be one crawler created per cookie value, per simulated role." msgstr "" #: tpl/crawler/settings.tpl.php:143 msgid "Use %1$s in %2$s to indicate this cookie has not been set." msgstr "" #: tpl/crawler/summary.tpl.php:21 msgid "You need to set the %s in Settings first before using the crawler" msgstr "" #: tpl/crawler/summary.tpl.php:47 msgid "Crawler Cron" msgstr "" #: tpl/crawler/summary.tpl.php:54 msgid "The crawler feature is not enabled on the LiteSpeed server. Please consult your server admin or hosting provider." msgstr "" #: tpl/crawler/summary.tpl.php:55 msgid "See <a %s>Introduction for Enabling the Crawler</a> for detailed information." msgstr "" #: tpl/crawler/summary.tpl.php:62 msgid "Current sitemap crawl started at" msgstr "" #: tpl/crawler/summary.tpl.php:68 msgid "The next complete sitemap crawl will start at" msgstr "" #: tpl/crawler/summary.tpl.php:76 msgid "Last complete run time for all crawlers" msgstr "" #: tpl/crawler/summary.tpl.php:77 #: tpl/crawler/summary.tpl.php:84 msgid "%d seconds" msgstr "" #: tpl/crawler/summary.tpl.php:83 msgid "Run time for previous crawler" msgstr "" #: tpl/crawler/summary.tpl.php:90 #: tpl/dash/dashboard.tpl.php:87 #: tpl/dash/dashboard.tpl.php:753 msgid "Current crawler started at" msgstr "" #: tpl/crawler/summary.tpl.php:96 msgid "Current server load" msgstr "" #: tpl/crawler/summary.tpl.php:102 #: tpl/dash/dashboard.tpl.php:94 #: tpl/dash/dashboard.tpl.php:760 msgid "Last interval" msgstr "" #: tpl/crawler/summary.tpl.php:109 #: tpl/dash/dashboard.tpl.php:101 #: tpl/dash/dashboard.tpl.php:767 msgid "Ended reason" msgstr "" #: tpl/crawler/summary.tpl.php:116 msgid "<b>Last crawled:</b> %s item(s)" msgstr "" #: tpl/crawler/summary.tpl.php:121 msgid "Reset position" msgstr "" #: tpl/crawler/summary.tpl.php:124 msgid "Manually run" msgstr "" #: tpl/crawler/summary.tpl.php:135 msgid "Cron Name" msgstr "" #: tpl/crawler/summary.tpl.php:136 msgid "Run Frequency" msgstr "" #: tpl/crawler/summary.tpl.php:138 msgid "Activate" msgstr "" #: tpl/crawler/summary.tpl.php:139 msgid "Running" msgstr "" #: tpl/crawler/summary.tpl.php:170 msgid "Waiting" msgstr "" #: tpl/crawler/summary.tpl.php:171 msgid "Hit" msgstr "" #: tpl/crawler/summary.tpl.php:172 msgid "Miss" msgstr "" #: tpl/crawler/summary.tpl.php:193 msgid "running" msgstr "" #: tpl/crawler/summary.tpl.php:205 msgid "Waiting to be Crawled" msgstr "" #: tpl/crawler/summary.tpl.php:206 msgid "Already Cached" msgstr "" #: tpl/crawler/summary.tpl.php:207 msgid "Successfully Crawled" msgstr "" #: tpl/crawler/summary.tpl.php:212 msgid "Run frequency is set by the Interval Between Runs setting." msgstr "" #: tpl/crawler/summary.tpl.php:213 msgid "Crawlers cannot run concurrently." msgstr "" #: tpl/crawler/summary.tpl.php:214 msgid " If both the cron and a manual run start at similar times, the first to be started will take precedence." msgstr "" #: tpl/crawler/summary.tpl.php:215 msgid "Please see <a %s>Hooking WP-Cron Into the System Task Scheduler</a> to learn how to create the system cron task." msgstr "" #: tpl/crawler/summary.tpl.php:220 msgid "Watch Crawler Status" msgstr "" #: tpl/crawler/summary.tpl.php:227 msgid "Show crawler status" msgstr "" #: tpl/crawler/summary.tpl.php:245 msgid "No crawler meta file generated yet" msgstr "" #: tpl/dash/dashboard.tpl.php:46 #: tpl/dash/dashboard.tpl.php:584 msgid "Cache Status" msgstr "" #: tpl/dash/dashboard.tpl.php:47 #: tpl/dash/dashboard.tpl.php:75 #: tpl/dash/dashboard.tpl.php:506 #: tpl/dash/dashboard.tpl.php:585 #: tpl/dash/dashboard.tpl.php:613 #: tpl/dash/dashboard.tpl.php:645 #: tpl/dash/dashboard.tpl.php:677 #: tpl/dash/dashboard.tpl.php:709 #: tpl/dash/dashboard.tpl.php:741 #: tpl/dash/dashboard.tpl.php:788 msgid "More" msgstr "" #: tpl/dash/dashboard.tpl.php:52 #: tpl/dash/dashboard.tpl.php:590 msgid "Public Cache" msgstr "" #: tpl/dash/dashboard.tpl.php:53 #: tpl/dash/dashboard.tpl.php:591 msgid "Private Cache" msgstr "" #: tpl/dash/dashboard.tpl.php:79 #: tpl/dash/dashboard.tpl.php:745 msgid "Crawler(s)" msgstr "" #: tpl/dash/dashboard.tpl.php:82 #: tpl/dash/dashboard.tpl.php:748 msgid "Currently active crawler" msgstr "" #: tpl/dash/dashboard.tpl.php:108 #: tpl/dash/dashboard.tpl.php:774 msgid "<b>Last crawled:</b> %d item(s)" msgstr "" #: tpl/dash/dashboard.tpl.php:120 #: tpl/dash/dashboard.tpl.php:836 msgid "News" msgstr "" #: tpl/dash/dashboard.tpl.php:145 msgid "Free monthly quota available. Can also be used anonymously (no email required)." msgstr "" #: tpl/dash/dashboard.tpl.php:152 msgid "Do not show this again" msgstr "" #: tpl/dash/dashboard.tpl.php:170 msgid "QUIC.cloud Service Usage Statistics" msgstr "" #: tpl/dash/dashboard.tpl.php:172 msgid "Refresh Usage" msgstr "" #: tpl/dash/dashboard.tpl.php:173 msgid "Sync data from Cloud" msgstr "" #: tpl/dash/dashboard.tpl.php:181 msgid "The features below are provided by" msgstr "" #: tpl/dash/dashboard.tpl.php:190 #: tpl/dash/network_dash.tpl.php:29 msgid "CDN Bandwidth" msgstr "" #: tpl/dash/dashboard.tpl.php:191 #: tpl/dash/dashboard.tpl.php:676 #: tpl/dash/network_dash.tpl.php:30 msgid "Low Quality Image Placeholder" msgstr "" #: tpl/dash/dashboard.tpl.php:249 #: tpl/dash/network_dash.tpl.php:78 msgid "Fast Queue Usage" msgstr "" #: tpl/dash/dashboard.tpl.php:249 #: tpl/dash/network_dash.tpl.php:78 msgid "Usage" msgstr "" #: tpl/dash/dashboard.tpl.php:262 #: tpl/dash/network_dash.tpl.php:90 msgid "PAYG Balance" msgstr "" #: tpl/dash/dashboard.tpl.php:263 msgid "PAYG used this month" msgstr "" #: tpl/dash/dashboard.tpl.php:263 msgid "PAYG balance and usage not included in above quota calculation." msgstr "" #: tpl/dash/dashboard.tpl.php:265 #: tpl/dash/network_dash.tpl.php:93 msgid "Pay as You Go Usage Statistics" msgstr "" #: tpl/dash/dashboard.tpl.php:283 #: tpl/dash/network_dash.tpl.php:100 msgid "Total Usage" msgstr "" #: tpl/dash/dashboard.tpl.php:284 #: tpl/dash/network_dash.tpl.php:101 msgid "Total images optimized in this month" msgstr "" #: tpl/dash/dashboard.tpl.php:293 msgid "Remaining Daily Quota" msgstr "" #: tpl/dash/dashboard.tpl.php:304 msgid "Partner Benefits Provided by" msgstr "" #: tpl/dash/dashboard.tpl.php:336 msgid "Enable QUIC.cloud Services" msgstr "" #: tpl/dash/dashboard.tpl.php:341 #: tpl/general/online.tpl.php:115 msgid "Go to QUIC.cloud dashboard" msgstr "" #: tpl/dash/dashboard.tpl.php:365 msgid "Current closest Cloud server is %s. Click to redetect." msgstr "" #: tpl/dash/dashboard.tpl.php:365 #: tpl/img_optm/summary.tpl.php:44 #: tpl/page_optm/settings_css.tpl.php:106 #: tpl/page_optm/settings_css.tpl.php:230 #: tpl/page_optm/settings_media.tpl.php:182 #: tpl/page_optm/settings_vpi.tpl.php:53 msgid "Are you sure you want to redetect the closest cloud server for this service?" msgstr "" #: tpl/dash/dashboard.tpl.php:365 #: tpl/general/online.tpl.php:24 #: tpl/img_optm/summary.tpl.php:44 #: tpl/img_optm/summary.tpl.php:46 #: tpl/page_optm/settings_css.tpl.php:106 #: tpl/page_optm/settings_css.tpl.php:230 #: tpl/page_optm/settings_media.tpl.php:182 #: tpl/page_optm/settings_vpi.tpl.php:53 msgid "Redetect" msgstr "" #: tpl/dash/dashboard.tpl.php:402 msgid "You must be using one of the following products in order to measure Page Load Time:" msgstr "" #: tpl/dash/dashboard.tpl.php:421 #: tpl/dash/dashboard.tpl.php:490 #: tpl/dash/dashboard.tpl.php:636 #: tpl/dash/dashboard.tpl.php:668 #: tpl/dash/dashboard.tpl.php:700 #: tpl/dash/dashboard.tpl.php:732 msgid "Last requested" msgstr "" #: tpl/dash/dashboard.tpl.php:427 #: tpl/dash/dashboard.tpl.php:495 msgid "Refresh" msgstr "" #: tpl/dash/dashboard.tpl.php:428 msgid "Refresh page load time" msgstr "" #: tpl/dash/dashboard.tpl.php:496 msgid "Refresh page score" msgstr "" #: tpl/dash/dashboard.tpl.php:505 #: tpl/img_optm/entry.tpl.php:8 msgid "Image Optimization Summary" msgstr "" #: tpl/dash/dashboard.tpl.php:517 #: tpl/img_optm/summary.tpl.php:66 #: tpl/img_optm/summary.tpl.php:71 msgid "Send Optimization Request" msgstr "" #: tpl/dash/dashboard.tpl.php:523 #: tpl/img_optm/summary.tpl.php:277 msgid "Total Reduction" msgstr "" #: tpl/dash/dashboard.tpl.php:526 #: tpl/img_optm/summary.tpl.php:280 msgid "Images Pulled" msgstr "" #: tpl/dash/dashboard.tpl.php:554 #: tpl/img_optm/summary.tpl.php:283 msgid "Last Request" msgstr "" #: tpl/dash/dashboard.tpl.php:557 msgid "Last Pull" msgstr "" #: tpl/dash/dashboard.tpl.php:612 #: tpl/toolbox/purge.tpl.php:62 msgid "Critical CSS" msgstr "" #: tpl/dash/dashboard.tpl.php:618 #: tpl/dash/dashboard.tpl.php:650 #: tpl/dash/dashboard.tpl.php:682 #: tpl/dash/dashboard.tpl.php:714 #: tpl/page_optm/settings_css.tpl.php:97 #: tpl/page_optm/settings_css.tpl.php:221 #: tpl/page_optm/settings_media.tpl.php:176 #: tpl/page_optm/settings_vpi.tpl.php:47 msgid "Last generated" msgstr "" #: tpl/dash/dashboard.tpl.php:621 #: tpl/dash/dashboard.tpl.php:653 #: tpl/dash/dashboard.tpl.php:685 #: tpl/dash/dashboard.tpl.php:717 msgid "Time to execute previous request" msgstr "" #: tpl/dash/dashboard.tpl.php:626 #: tpl/dash/dashboard.tpl.php:658 #: tpl/dash/dashboard.tpl.php:690 #: tpl/dash/dashboard.tpl.php:722 msgid "Requests in queue" msgstr "" #: tpl/dash/dashboard.tpl.php:628 #: tpl/dash/dashboard.tpl.php:660 #: tpl/dash/dashboard.tpl.php:692 #: tpl/dash/dashboard.tpl.php:724 msgid "Force cron" msgstr "" #: tpl/dash/dashboard.tpl.php:644 #: tpl/toolbox/purge.tpl.php:71 msgid "Unique CSS" msgstr "" #: tpl/dash/dashboard.tpl.php:708 msgid "Viewport Image" msgstr "" #: tpl/dash/dashboard.tpl.php:802 msgid "Best available WordPress performance" msgstr "" #: tpl/dash/dashboard.tpl.php:805 msgid "Globally fast TTFB, easy setup, and <a %s>more</a>!" msgstr "" #: tpl/dash/dashboard.tpl.php:819 msgid "Refresh QUIC.cloud status" msgstr "" #: tpl/dash/entry.tpl.php:11 msgid "Network Dashboard" msgstr "" #: tpl/dash/entry.tpl.php:20 msgid "LiteSpeed Cache Dashboard" msgstr "" #: tpl/dash/network_dash.tpl.php:19 msgid "Usage Statistics" msgstr "" #: tpl/dash/network_dash.tpl.php:89 msgid "Pay as You Go" msgstr "" #: tpl/dash/network_dash.tpl.php:91 msgid "This Month Usage" msgstr "" #: tpl/db_optm/entry.tpl.php:10 #: tpl/db_optm/settings.tpl.php:10 msgid "DB Optimization Settings" msgstr "" #: tpl/db_optm/entry.tpl.php:17 msgid "LiteSpeed Cache Database Optimization" msgstr "" #: tpl/db_optm/manage.tpl.php:9 msgid "Clean All" msgstr "" #: tpl/db_optm/manage.tpl.php:13 msgid "Post Revisions" msgstr "" #: tpl/db_optm/manage.tpl.php:14 msgid "Clean all post revisions" msgstr "" #: tpl/db_optm/manage.tpl.php:17 msgid "Orphaned Post Meta" msgstr "" #: tpl/db_optm/manage.tpl.php:18 msgid "Clean all orphaned post meta records" msgstr "" #: tpl/db_optm/manage.tpl.php:21 msgid "Auto Drafts" msgstr "" #: tpl/db_optm/manage.tpl.php:22 msgid "Clean all auto saved drafts" msgstr "" #: tpl/db_optm/manage.tpl.php:25 msgid "Trashed Posts" msgstr "" #: tpl/db_optm/manage.tpl.php:26 msgid "Clean all trashed posts and pages" msgstr "" #: tpl/db_optm/manage.tpl.php:29 msgid "Spam Comments" msgstr "" #: tpl/db_optm/manage.tpl.php:30 msgid "Clean all spam comments" msgstr "" #: tpl/db_optm/manage.tpl.php:33 msgid "Trashed Comments" msgstr "" #: tpl/db_optm/manage.tpl.php:34 msgid "Clean all trashed comments" msgstr "" #: tpl/db_optm/manage.tpl.php:37 msgid "Trackbacks/Pingbacks" msgstr "" #: tpl/db_optm/manage.tpl.php:38 msgid "Clean all trackbacks and pingbacks" msgstr "" #: tpl/db_optm/manage.tpl.php:41 msgid "Expired Transients" msgstr "" #: tpl/db_optm/manage.tpl.php:42 msgid "Clean expired transient options" msgstr "" #: tpl/db_optm/manage.tpl.php:45 msgid "All Transients" msgstr "" #: tpl/db_optm/manage.tpl.php:46 msgid "Clean all transient options" msgstr "" #: tpl/db_optm/manage.tpl.php:49 msgid "Optimize Tables" msgstr "" #: tpl/db_optm/manage.tpl.php:50 msgid "Optimize all tables in your database" msgstr "" #: tpl/db_optm/manage.tpl.php:57 msgid "Clean revisions older than %1$s day(s), excluding %2$s latest revisions" msgstr "" #: tpl/db_optm/manage.tpl.php:78 msgid "Database Optimizer" msgstr "" #: tpl/db_optm/manage.tpl.php:105 msgid "Database Table Engine Converter" msgstr "" #: tpl/db_optm/manage.tpl.php:113 msgid "Table" msgstr "" #: tpl/db_optm/manage.tpl.php:114 msgid "Engine" msgstr "" #: tpl/db_optm/manage.tpl.php:115 msgid "Tool" msgstr "" #: tpl/db_optm/manage.tpl.php:130 msgid "Convert to InnoDB" msgstr "" #: tpl/db_optm/manage.tpl.php:138 msgid "We are good. No table uses MyISAM engine." msgstr "" #: tpl/db_optm/manage.tpl.php:160 msgid "Database Summary" msgstr "" #: tpl/db_optm/manage.tpl.php:176 msgid "Option Name" msgstr "" #: tpl/db_optm/manage.tpl.php:177 msgid "Autoload" msgstr "" #: tpl/db_optm/manage.tpl.php:178 msgid "Size" msgstr "" #: tpl/db_optm/settings.tpl.php:23 msgid "Specify the number of most recent revisions to keep when cleaning revisions." msgstr "" #: tpl/db_optm/settings.tpl.php:35 msgid "Day(s)" msgstr "" #: tpl/db_optm/settings.tpl.php:37 msgid "Revisions newer than this many days will be kept when cleaning revisions." msgstr "" #: tpl/esi_widget_edit.php:46 msgid "Public" msgstr "" #: tpl/esi_widget_edit.php:47 msgid "Private" msgstr "" #: tpl/esi_widget_edit.php:48 msgid "Disable" msgstr "" #: tpl/esi_widget_edit.php:63 msgid "Widget Cache TTL:" msgstr "" #: tpl/esi_widget_edit.php:73 msgid "Recommended value: 28800 seconds (8 hours)." msgstr "" #: tpl/esi_widget_edit.php:74 msgid "A TTL of 0 indicates do not cache." msgstr "" #: tpl/general/entry.tpl.php:8 #: tpl/general/online.tpl.php:54 msgid "Online Services" msgstr "" #: tpl/general/entry.tpl.php:9 #: tpl/general/entry.tpl.php:15 #: tpl/general/network_settings.tpl.php:9 #: tpl/general/settings.tpl.php:15 msgid "General Settings" msgstr "" #: tpl/general/entry.tpl.php:10 #: tpl/page_optm/entry.tpl.php:13 #: tpl/page_optm/entry.tpl.php:14 msgid "Tuning" msgstr "" #: tpl/general/entry.tpl.php:23 msgid "LiteSpeed Cache General Settings" msgstr "" #: tpl/general/network_settings.tpl.php:21 msgid "Use Primary Site Configuration" msgstr "" #: tpl/general/network_settings.tpl.php:25 msgid "Check this option to use the primary site's configuration for all subsites." msgstr "" #: tpl/general/network_settings.tpl.php:26 msgid "This will disable the settings page on all subsites." msgstr "" #: tpl/general/online.tpl.php:15 msgid "QUIC.cloud Online Services" msgstr "" #: tpl/general/online.tpl.php:23 msgid "Current Cloud Nodes in Service" msgstr "" #: tpl/general/online.tpl.php:24 msgid "Click to clear all nodes for further redetection." msgstr "" #: tpl/general/online.tpl.php:24 msgid "Are you sure you want to clear all cloud nodes?" msgstr "" #: tpl/general/online.tpl.php:36 msgid "No cloud services currently in use" msgstr "" #: tpl/general/online.tpl.php:44 msgid "QUIC.cloud Integration Disabled" msgstr "" #: tpl/general/online.tpl.php:45 msgid "Speed up your WordPress site even further with QUIC.cloud Online Services and CDN." msgstr "" #: tpl/general/online.tpl.php:55 msgid "QUIC.cloud's Online Services improve your site in the following ways:" msgstr "" #: tpl/general/online.tpl.php:57 msgid "<strong>Image Optimization</strong> gives you smaller image file sizes that transmit faster." msgstr "" #: tpl/general/online.tpl.php:58 msgid "<strong>Page Optimization</strong> streamlines page styles and visual elements for faster loading." msgstr "" #: tpl/general/online.tpl.php:62 msgid "QUIC.cloud's Image Optimization service does the following:" msgstr "" #: tpl/general/online.tpl.php:64 msgid "Processes your uploaded PNG and JPG images to produce smaller versions that don't sacrifice quality." msgstr "" #: tpl/general/online.tpl.php:65 msgid "Optionally creates next-generation WebP or AVIF image files." msgstr "" #: tpl/general/online.tpl.php:67 msgid "Processing for PNG, JPG, and WebP image formats is free. AVIF is available for a fee." msgstr "" #: tpl/general/online.tpl.php:70 msgid "QUIC.cloud's Page Optimization services address CSS bloat, and improve the user experience during page load, which can lead to improved page speed scores." msgstr "" #: tpl/general/online.tpl.php:72 msgid "<strong>Critical CSS (CCSS)</strong> loads visible above-the-fold content faster and with full styling." msgstr "" #: tpl/general/online.tpl.php:73 msgid "<strong>Unique CSS (UCSS)</strong> removes unused style definitions for a speedier page load overall." msgstr "" #: tpl/general/online.tpl.php:74 msgid "<strong>Low Quality Image Placeholder (LQIP)</strong> gives your imagery a more pleasing look as it lazy loads." msgstr "" #: tpl/general/online.tpl.php:75 msgid "<strong>Viewport Images (VPI)</strong> provides a well-polished fully-loaded view above the fold." msgstr "" #: tpl/general/online.tpl.php:84 msgid "Content Delivery Network" msgstr "" #: tpl/general/online.tpl.php:88 msgid "Caches your entire site, including dynamic content and <strong>ESI blocks</strong>." msgstr "" #: tpl/general/online.tpl.php:89 msgid "Delivers global coverage with a growing <strong>network of 80+ PoPs</strong>." msgstr "" #: tpl/general/online.tpl.php:90 msgid "Provides <strong>security at the CDN level</strong>, protecting your server from attack." msgstr "" #: tpl/general/online.tpl.php:91 msgid "Offers optional <strong>built-in DNS service</strong> to simplify CDN onboarding." msgstr "" #: tpl/general/online.tpl.php:100 msgid "In order to use most QUIC.cloud services, you need quota. QUIC.cloud gives you free quota every month, but if you need more, you can purchase it." msgstr "" #: tpl/general/online.tpl.php:112 msgid "QUIC.cloud Integration Enabled" msgstr "" #: tpl/general/online.tpl.php:113 msgid "Your site is connected and ready to use QUIC.cloud Online Services." msgstr "" #: tpl/general/online.tpl.php:130 msgid "QUIC.cloud Integration Enabled with limitations" msgstr "" #: tpl/general/online.tpl.php:131 msgid "Your site is connected and using QUIC.cloud Online Services as an <strong>anonymous user</strong>. The CDN function and certain features of optimization services are not available for anonymous users. Link to QUIC.cloud to use the CDN and all available Online Services features." msgstr "" #: tpl/general/online.tpl.php:137 msgid "not available for anonymous users" msgstr "" #: tpl/general/online.tpl.php:147 msgid "Are you sure you want to disconnect from QUIC.cloud? This will not remove any data from the QUIC.cloud dashboard." msgstr "" #: tpl/general/online.tpl.php:147 msgid "Disconnect from QUIC.cloud" msgstr "" #: tpl/general/online.tpl.php:148 msgid "Remove QUIC.cloud integration from this site. Note: QUIC.cloud data will be preserved so you can re-enable services at any time. If you want to fully remove your site from QUIC.cloud, delete the domain through the QUIC.cloud Dashboard first." msgstr "" #: tpl/general/settings.tpl.php:39 msgid "This option enables maximum optimization for Guest Mode visitors." msgstr "" #: tpl/general/settings.tpl.php:40 msgid "Please read all warnings before enabling this option." msgstr "" #: tpl/general/settings.tpl.php:55 msgid "Your %1s quota on %2s will still be in use." msgstr "" #: tpl/general/settings.tpl.php:63 #: tpl/general/settings.tpl.php:70 #: tpl/general/settings.tpl.php:77 #: tpl/general/settings.tpl.php:94 #: tpl/page_optm/settings_media.tpl.php:240 #: tpl/page_optm/settings_vpi.tpl.php:37 msgid "Notice" msgstr "" #: tpl/general/settings.tpl.php:63 #: tpl/page_optm/settings_media.tpl.php:240 #: tpl/page_optm/settings_vpi.tpl.php:37 msgid "%s must be turned ON for this setting to work." msgstr "" #: tpl/general/settings.tpl.php:70 msgid "You need to turn %s on to get maximum result." msgstr "" #: tpl/general/settings.tpl.php:77 msgid "You need to turn %s on and finish all WebP generation to get maximum result." msgstr "" #: tpl/general/settings.tpl.php:92 msgid "Enter this site's IP address to allow cloud services directly call IP instead of domain name. This eliminates the overhead of DNS and CDN lookups." msgstr "" #: tpl/general/settings.tpl.php:93 msgid "Your server IP" msgstr "" #: tpl/general/settings.tpl.php:93 msgid "Check my public IP from" msgstr "" #: tpl/general/settings.tpl.php:94 msgid "the auto-detected IP may not be accurate if you have an additional outgoing IP set, or you have multiple IPs configured on your server." msgstr "" #: tpl/general/settings.tpl.php:95 msgid "Please make sure this IP is the correct one for visiting your site." msgstr "" #: tpl/general/settings.tpl.php:110 msgid "Turn this option ON to show latest news automatically, including hotfixes, new releases, available beta versions, and promotions." msgstr "" #: tpl/general/settings_inc.auto_upgrade.tpl.php:15 msgid "Turn this option ON to have LiteSpeed Cache updated automatically, whenever a new version is released. If OFF, update manually as usual." msgstr "" #: tpl/general/settings_inc.guest.tpl.php:16 msgid "Guest Mode provides an always cacheable landing page for an automated guest's first time visit, and then attempts to update cache varies via AJAX." msgstr "" #: tpl/general/settings_inc.guest.tpl.php:17 msgid "This option can help to correct the cache vary for certain advanced mobile or tablet visitors." msgstr "" #: tpl/general/settings_inc.guest.tpl.php:24 msgid "Guest Mode testing result" msgstr "" #: tpl/general/settings_inc.guest.tpl.php:25 msgid "Testing" msgstr "" #: tpl/general/settings_inc.guest.tpl.php:32 msgid "Guest Mode passed testing." msgstr "" #: tpl/general/settings_inc.guest.tpl.php:35 #: tpl/general/settings_inc.guest.tpl.php:38 msgid "Guest Mode failed to test." msgstr "" #: tpl/general/settings_tuning.tpl.php:8 #: tpl/page_optm/settings_tuning.tpl.php:20 #: tpl/page_optm/settings_tuning_css.tpl.php:7 msgid "Tuning Settings" msgstr "" #: tpl/general/settings_tuning.tpl.php:29 msgid "Listed User Agents will be considered as Guest Mode visitors." msgstr "" #: tpl/general/settings_tuning.tpl.php:51 msgid "Listed IPs will be considered as Guest Mode visitors." msgstr "" #: tpl/img_optm/entry.tpl.php:9 #: tpl/img_optm/entry.tpl.php:15 #: tpl/img_optm/network_settings.tpl.php:9 #: tpl/img_optm/settings.tpl.php:11 msgid "Image Optimization Settings" msgstr "" #: tpl/img_optm/entry.tpl.php:23 msgid "LiteSpeed Cache Image Optimization" msgstr "" #: tpl/img_optm/settings.media_webp.tpl.php:17 msgid "Request WebP/AVIF versions of original images when doing optimization." msgstr "" #: tpl/img_optm/settings.media_webp.tpl.php:18 msgid "Significantly improve load time by replacing images with their optimized %s versions." msgstr "" #: tpl/img_optm/settings.media_webp.tpl.php:23 msgid "%1$s is a %2$s paid feature." msgstr "" #: tpl/img_optm/settings.media_webp.tpl.php:26 msgid "When switching formats, please %1$s or %2$s to apply this new choice to previously optimized images." msgstr "" #: tpl/img_optm/settings.media_webp.tpl.php:26 #: tpl/img_optm/summary.tpl.php:327 msgid "Destroy All Optimization Data" msgstr "" #: tpl/img_optm/settings.media_webp.tpl.php:26 #: tpl/img_optm/summary.tpl.php:318 msgid "Soft Reset Optimization Counter" msgstr "" #: tpl/img_optm/settings.tpl.php:26 msgid "Automatically request optimization via cron job." msgstr "" #: tpl/img_optm/settings.tpl.php:39 msgid "Optimize images and save backups of the originals in the same folder." msgstr "" #: tpl/img_optm/settings.tpl.php:52 msgid "Automatically remove the original image backups after fetching optimized images." msgstr "" #: tpl/img_optm/settings.tpl.php:57 #: tpl/img_optm/summary.tpl.php:204 msgid "This is irreversible." msgstr "" #: tpl/img_optm/settings.tpl.php:58 #: tpl/img_optm/summary.tpl.php:205 msgid "You will be unable to Revert Optimization once the backups are deleted!" msgstr "" #: tpl/img_optm/settings.tpl.php:72 msgid "Optimize images using lossless compression." msgstr "" #: tpl/img_optm/settings.tpl.php:73 msgid "This can improve quality but may result in larger images than lossy compression will." msgstr "" #: tpl/img_optm/settings.tpl.php:86 msgid "Preserve EXIF data (copyright, GPS, comments, keywords, etc) when optimizing." msgstr "" #: tpl/img_optm/settings.tpl.php:87 msgid "This will increase the size of optimized files." msgstr "" #: tpl/img_optm/settings.tpl.php:117 msgid "Specify which element attributes will be replaced with WebP/AVIF." msgstr "" #: tpl/img_optm/settings.tpl.php:133 msgid "Enable replacement of WebP/AVIF in %s elements that were generated outside of WordPress logic." msgstr "" #: tpl/img_optm/summary.tpl.php:44 #: tpl/page_optm/settings_css.tpl.php:106 #: tpl/page_optm/settings_css.tpl.php:230 #: tpl/page_optm/settings_media.tpl.php:182 #: tpl/page_optm/settings_vpi.tpl.php:53 msgid "Current closest Cloud server is %s. Click to redetect." msgstr "" #: tpl/img_optm/summary.tpl.php:48 msgid "Optimize images with our QUIC.cloud server" msgstr "" #: tpl/img_optm/summary.tpl.php:53 msgid "You can request a maximum of %s images at once." msgstr "" #: tpl/img_optm/summary.tpl.php:58 msgid "To make sure our server can communicate with your server without any issues and everything works fine, for the few first requests the number of image groups allowed in a single request is limited." msgstr "" #: tpl/img_optm/summary.tpl.php:59 msgid "Current limit is" msgstr "" #: tpl/img_optm/summary.tpl.php:67 #: tpl/page_optm/settings_css.tpl.php:136 #: tpl/page_optm/settings_css.tpl.php:260 #: tpl/page_optm/settings_vpi.tpl.php:82 msgid "Available after %d second(s)" msgstr "" #: tpl/img_optm/summary.tpl.php:75 msgid "Only press the button if the pull cron job is disabled." msgstr "" #: tpl/img_optm/summary.tpl.php:75 msgid "Images will be pulled automatically if the cron job is running." msgstr "" #: tpl/img_optm/summary.tpl.php:76 msgid "Pull Images" msgstr "" #: tpl/img_optm/summary.tpl.php:82 msgid "Optimization Status" msgstr "" #: tpl/img_optm/summary.tpl.php:115 msgid "After the QUIC.cloud Image Optimization server finishes optimization, it will notify your site to pull the optimized images." msgstr "" #: tpl/img_optm/summary.tpl.php:116 msgid "This process is automatic." msgstr "" #: tpl/img_optm/summary.tpl.php:131 msgid "Last pull initiated by cron at %s." msgstr "" #: tpl/img_optm/summary.tpl.php:156 msgid "Storage Optimization" msgstr "" #: tpl/img_optm/summary.tpl.php:160 msgid "A backup of each image is saved before it is optimized." msgstr "" #: tpl/img_optm/summary.tpl.php:167 msgid "Last calculated" msgstr "" #: tpl/img_optm/summary.tpl.php:171 #: tpl/img_optm/summary.tpl.php:216 msgid "Files" msgstr "" #: tpl/img_optm/summary.tpl.php:182 msgid "Calculate Original Image Storage" msgstr "" #: tpl/img_optm/summary.tpl.php:183 msgid "Calculate Backups Disk Space" msgstr "" #: tpl/img_optm/summary.tpl.php:190 msgid "Image Thumbnail Group Sizes" msgstr "" #: tpl/img_optm/summary.tpl.php:201 msgid "Delete all backups of the original images" msgstr "" #: tpl/img_optm/summary.tpl.php:213 #: tpl/page_optm/settings_localization.tpl.php:51 msgid "Last ran" msgstr "" #: tpl/img_optm/summary.tpl.php:219 msgid "Saved" msgstr "" #: tpl/img_optm/summary.tpl.php:223 msgid "Are you sure you want to remove all image backups?" msgstr "" #: tpl/img_optm/summary.tpl.php:224 msgid "Remove Original Image Backups" msgstr "" #: tpl/img_optm/summary.tpl.php:235 msgid "Image Information" msgstr "" #: tpl/img_optm/summary.tpl.php:244 msgid "Image groups total" msgstr "" #: tpl/img_optm/summary.tpl.php:249 msgid "Congratulations, all gathered!" msgstr "" #: tpl/img_optm/summary.tpl.php:252 msgid "What is a group?" msgstr "" #: tpl/img_optm/summary.tpl.php:254 msgid "What is an image group?" msgstr "" #: tpl/img_optm/summary.tpl.php:258 #: tpl/img_optm/summary.tpl.php:322 msgid "Current image post id position" msgstr "" #: tpl/img_optm/summary.tpl.php:259 msgid "Maximum image post id" msgstr "" #: tpl/img_optm/summary.tpl.php:265 msgid "Scan for any new unoptimized image thumbnail sizes and resend necessary image optimization requests." msgstr "" #: tpl/img_optm/summary.tpl.php:266 msgid "Rescan New Thumbnails" msgstr "" #: tpl/img_optm/summary.tpl.php:274 msgid "Optimization Summary" msgstr "" #: tpl/img_optm/summary.tpl.php:286 msgid "Last Pulled" msgstr "" #: tpl/img_optm/summary.tpl.php:291 msgid "Results can be checked in <a %s>Media Library</a>." msgstr "" #: tpl/img_optm/summary.tpl.php:297 msgid "Optimization Tools" msgstr "" #: tpl/img_optm/summary.tpl.php:300 msgid "You can quickly switch between using original (unoptimized versions) and optimized image files. It will affect all images on your website, both regular and webp versions if available." msgstr "" #: tpl/img_optm/summary.tpl.php:305 msgid "Use original images (unoptimized) on your site" msgstr "" #: tpl/img_optm/summary.tpl.php:306 msgid "Use Original Files" msgstr "" #: tpl/img_optm/summary.tpl.php:309 msgid "Switch back to using optimized images on your site" msgstr "" #: tpl/img_optm/summary.tpl.php:310 msgid "Use Optimized Files" msgstr "" #: tpl/img_optm/summary.tpl.php:322 msgid "This will reset the %1$s. If you changed WebP/AVIF settings and want to generate %2$s for the previously optimized images, use this action." msgstr "" #: tpl/img_optm/summary.tpl.php:326 msgid "Are you sure to destroy all optimized images?" msgstr "" #: tpl/img_optm/summary.tpl.php:331 msgid "Remove all previous image optimization requests/results, revert completed optimizations, and delete all optimization files." msgstr "" #: tpl/inc/admin_footer.php:10 msgid "Rate %s on %s" msgstr "" #: tpl/inc/admin_footer.php:13 msgid "Read LiteSpeed Documentation" msgstr "" #: tpl/inc/admin_footer.php:15 msgid "Visit LSCWP support forum" msgstr "" #: tpl/inc/admin_footer.php:17 msgid "Join LiteSpeed Slack community" msgstr "" #: tpl/inc/check_cache_disabled.php:10 msgid "To use the caching functions you must have a LiteSpeed web server or be using QUIC.cloud CDN." msgstr "" #: tpl/inc/check_cache_disabled.php:15 msgid "Please enable the LSCache Module at the server level, or ask your hosting provider." msgstr "" #: tpl/inc/check_cache_disabled.php:22 msgid "Please enable LiteSpeed Cache in the plugin settings." msgstr "" #: tpl/inc/check_cache_disabled.php:34 msgid "LSCache caching functions on this page are currently unavailable!" msgstr "" #: tpl/inc/check_if_network_disable_all.php:20 msgid "The network admin selected use primary site configs for all subsites." msgstr "" #: tpl/inc/check_if_network_disable_all.php:21 msgid "The following options are selected, but are not editable in this settings page." msgstr "" #: tpl/inc/in_upgrading.php:5 msgid "LiteSpeed cache plugin upgraded. Please refresh the page to complete the configuration data upgrade." msgstr "" #: tpl/inc/show_display_installed.php:7 msgid "LiteSpeed Cache plugin is installed!" msgstr "" #: tpl/inc/show_display_installed.php:10 msgid "This message indicates that the plugin was installed by the server admin." msgstr "" #: tpl/inc/show_display_installed.php:12 msgid "The LiteSpeed Cache plugin is used to cache pages - a simple way to improve the performance of the site." msgstr "" #: tpl/inc/show_display_installed.php:14 msgid "However, there is no way of knowing all the possible customizations that were implemented." msgstr "" #: tpl/inc/show_display_installed.php:16 msgid "For that reason, please test the site to make sure everything still functions properly." msgstr "" #: tpl/inc/show_display_installed.php:18 msgid "Examples of test cases include:" msgstr "" #: tpl/inc/show_display_installed.php:21 msgid "Visit the site while logged out." msgstr "" #: tpl/inc/show_display_installed.php:24 msgid "Create a post, make sure the front page is accurate." msgstr "" #: tpl/inc/show_display_installed.php:28 msgid "If there are any questions, the team is always happy to answer any questions on the <a %s>support forum</a>." msgstr "" #: tpl/inc/show_display_installed.php:32 msgid "If you would rather not move at litespeed, you can deactivate this plugin." msgstr "" #: tpl/inc/show_error_cookie.php:6 msgid "NOTICE: Database login cookie did not match your login cookie." msgstr "" #: tpl/inc/show_error_cookie.php:8 msgid "If the login cookie was recently changed in the settings, please log out and back in." msgstr "" #: tpl/inc/show_error_cookie.php:10 msgid "If not, please verify the setting in the <a href=\"%1$s\">Advanced tab</a>." msgstr "" #: tpl/inc/show_error_cookie.php:13 msgid "If using OpenLiteSpeed, the server must be restarted once for the changes to take effect." msgstr "" #: tpl/inc/show_rule_conflict.php:6 msgid "Unexpected cache rule %2$s found in %1$s file. This rule may cause visitors to see old versions of pages due to the browser caching HTML pages. If you are sure that HTML pages are not being browser cached, this message can be dismissed. (<a %3$s>Learn More</a>)" msgstr "" #: tpl/page_optm/entry.tpl.php:6 #: tpl/page_optm/settings_css.tpl.php:25 msgid "CSS Settings" msgstr "" #: tpl/page_optm/entry.tpl.php:7 #: tpl/page_optm/settings_js.tpl.php:9 msgid "JS Settings" msgstr "" #: tpl/page_optm/entry.tpl.php:8 #: tpl/page_optm/settings_html.tpl.php:9 msgid "HTML Settings" msgstr "" #: tpl/page_optm/entry.tpl.php:9 #: tpl/page_optm/settings_media.tpl.php:16 msgid "Media Settings" msgstr "" #: tpl/page_optm/entry.tpl.php:10 msgid "VPI" msgstr "" #: tpl/page_optm/entry.tpl.php:11 #: tpl/page_optm/settings_media_exc.tpl.php:7 msgid "Media Excludes" msgstr "" #: tpl/page_optm/entry.tpl.php:12 msgid "Localization" msgstr "" #: tpl/page_optm/entry.tpl.php:21 msgid "LiteSpeed Cache Page Optimization" msgstr "" #: tpl/page_optm/entry.tpl.php:33 msgid "Please test thoroughly when enabling any option in this list. After changing Minify/Combine settings, please do a Purge All action." msgstr "" #: tpl/page_optm/settings_css.tpl.php:41 msgid "Minify CSS files and inline CSS code." msgstr "" #: tpl/page_optm/settings_css.tpl.php:55 msgid "Combine CSS files and inline CSS code." msgstr "" #: tpl/page_optm/settings_css.tpl.php:56 #: tpl/page_optm/settings_js.tpl.php:40 msgid "How to Fix Problems Caused by CSS/JS Optimization." msgstr "" #: tpl/page_optm/settings_css.tpl.php:77 msgid "Use QUIC.cloud online service to generate unique CSS." msgstr "" #: tpl/page_optm/settings_css.tpl.php:78 msgid "This will drop the unused CSS on each page from the combined file." msgstr "" #: tpl/page_optm/settings_css.tpl.php:80 msgid "Automatic generation of unique CSS is in the background via a cron-based queue." msgstr "" #: tpl/page_optm/settings_css.tpl.php:82 msgid "Filter %s available for UCSS per page type generation." msgstr "" #: tpl/page_optm/settings_css.tpl.php:87 msgid "This option is bypassed because %1$s option is %2$s." msgstr "" #: tpl/page_optm/settings_css.tpl.php:100 #: tpl/page_optm/settings_css.tpl.php:224 msgid "Last requested cost" msgstr "" #: tpl/page_optm/settings_css.tpl.php:112 #: tpl/page_optm/settings_css.tpl.php:236 #: tpl/page_optm/settings_vpi.tpl.php:59 msgid "URL list in %s queue waiting for cron" msgstr "" #: tpl/page_optm/settings_css.tpl.php:135 #: tpl/page_optm/settings_css.tpl.php:140 #: tpl/page_optm/settings_css.tpl.php:259 #: tpl/page_optm/settings_css.tpl.php:264 #: tpl/page_optm/settings_vpi.tpl.php:81 #: tpl/page_optm/settings_vpi.tpl.php:86 msgid "Run %s Queue Manually" msgstr "" #: tpl/page_optm/settings_css.tpl.php:159 msgid "Inline UCSS to reduce the extra CSS file loading. This option will not be automatically turned on for %1$s pages. To use it on %1$s pages, please set it to ON." msgstr "" #: tpl/page_optm/settings_css.tpl.php:162 msgid "This option will automatically bypass %s option." msgstr "" #: tpl/page_optm/settings_css.tpl.php:176 msgid "Include external CSS and inline CSS in combined file when %1$s is also enabled. This option helps maintain the priorities of CSS, which should minimize potential errors caused by CSS Combine." msgstr "" #: tpl/page_optm/settings_css.tpl.php:196 msgid "Optimize CSS delivery." msgstr "" #: tpl/page_optm/settings_css.tpl.php:197 #: tpl/page_optm/settings_html.tpl.php:167 #: tpl/page_optm/settings_js.tpl.php:73 msgid "This can improve your speed score in services like Pingdom, GTmetrix and PageSpeed." msgstr "" #: tpl/page_optm/settings_css.tpl.php:198 msgid "Use QUIC.cloud online service to generate critical CSS and load remaining CSS asynchronously." msgstr "" #: tpl/page_optm/settings_css.tpl.php:200 msgid "Automatic generation of critical CSS is in the background via a cron-based queue." msgstr "" #: tpl/page_optm/settings_css.tpl.php:201 msgid "When this option is turned %s, it will also load Google Fonts asynchronously." msgstr "" #: tpl/page_optm/settings_css.tpl.php:205 msgid "Elements with attribute %s in HTML code will be excluded." msgstr "" #: tpl/page_optm/settings_css.tpl.php:211 msgid "This option is bypassed due to %s option." msgstr "" #: tpl/page_optm/settings_css.tpl.php:281 msgid "Disable this option to generate CCSS per Post Type instead of per page. This can save significant CCSS quota, however it may result in incorrect CSS styling if your site uses a page builder." msgstr "" #: tpl/page_optm/settings_css.tpl.php:294 msgid "This will inline the asynchronous CSS library to avoid render blocking." msgstr "" #: tpl/page_optm/settings_css.tpl.php:305 msgid "Default" msgstr "" #: tpl/page_optm/settings_css.tpl.php:307 msgid "Set this to append %1$s to all %2$s rules before caching CSS to specify how fonts should be displayed while being downloaded." msgstr "" #: tpl/page_optm/settings_css.tpl.php:308 msgid "%s is recommended." msgstr "" #: tpl/page_optm/settings_css.tpl.php:308 msgid "Swap" msgstr "" #: tpl/page_optm/settings_html.tpl.php:23 msgid "Minify HTML content." msgstr "" #: tpl/page_optm/settings_html.tpl.php:36 msgid "Prefetching DNS can reduce latency for visitors." msgstr "" #: tpl/page_optm/settings_html.tpl.php:37 #: tpl/page_optm/settings_html.tpl.php:68 msgid "For example" msgstr "" #: tpl/page_optm/settings_html.tpl.php:52 msgid "Automatically enable DNS prefetching for all URLs in the document, including images, CSS, JavaScript, and so forth." msgstr "" #: tpl/page_optm/settings_html.tpl.php:53 msgid "This can improve the page loading speed." msgstr "" #: tpl/page_optm/settings_html.tpl.php:67 msgid "Preconnecting speeds up future loads from a given origin." msgstr "" #: tpl/page_optm/settings_html.tpl.php:83 msgid "Delay rendering off-screen HTML elements by its selector." msgstr "" #: tpl/page_optm/settings_html.tpl.php:98 msgid "When minifying HTML do not discard comments that match a specified pattern." msgstr "" #: tpl/page_optm/settings_html.tpl.php:100 msgid "If comment to be kept is like: %s write: %s" msgstr "" #: tpl/page_optm/settings_html.tpl.php:115 msgid "Remove query strings from internal static resources." msgstr "" #: tpl/page_optm/settings_html.tpl.php:119 msgid "Google reCAPTCHA will be bypassed automatically." msgstr "" #: tpl/page_optm/settings_html.tpl.php:124 msgid "Append query string %s to the resources to bypass this action." msgstr "" #: tpl/page_optm/settings_html.tpl.php:138 msgid "Use Web Font Loader library to load Google Fonts asynchronously while leaving other CSS intact." msgstr "" #: tpl/page_optm/settings_html.tpl.php:139 msgid "This will also add a preconnect to Google Fonts to establish a connection earlier." msgstr "" #: tpl/page_optm/settings_html.tpl.php:153 msgid "Prevent Google Fonts from loading on all pages." msgstr "" #: tpl/page_optm/settings_html.tpl.php:166 msgid "Stop loading WordPress.org emoji. Browser default emoji will be displayed instead." msgstr "" #: tpl/page_optm/settings_html.tpl.php:180 msgid "This option will remove all %s tags from HTML." msgstr "" #: tpl/page_optm/settings_js.tpl.php:25 msgid "Minify JS files and inline JS codes." msgstr "" #: tpl/page_optm/settings_js.tpl.php:39 msgid "Combine all local JS files into a single file." msgstr "" #: tpl/page_optm/settings_js.tpl.php:43 #: tpl/page_optm/settings_js.tpl.php:77 msgid "This option may result in a JS error or layout issue on frontend pages with certain themes/plugins." msgstr "" #: tpl/page_optm/settings_js.tpl.php:44 msgid "JS error can be found from the developer console of browser by right clicking and choosing Inspect." msgstr "" #: tpl/page_optm/settings_js.tpl.php:58 msgid "Include external JS and inline JS in combined file when %1$s is also enabled. This option helps maintain the priorities of JS execution, which should minimize potential errors caused by JS Combine." msgstr "" #: tpl/page_optm/settings_js.tpl.php:69 msgid "Deferred" msgstr "" #: tpl/page_optm/settings_js.tpl.php:69 msgid "Delayed" msgstr "" #: tpl/page_optm/settings_js.tpl.php:71 msgid "Deferring until page is parsed or delaying till interaction can help reduce resource contention and improve performance causing a lower FID (Core Web Vitals metric)." msgstr "" #: tpl/page_optm/settings_localization.tpl.php:12 msgid "Failed to create Avatar table. Please follow <a %s>Table Creation guidance from LiteSpeed Wiki</a> to finish setup." msgstr "" #: tpl/page_optm/settings_localization.tpl.php:17 msgid "Localization Settings" msgstr "" #: tpl/page_optm/settings_localization.tpl.php:30 msgid "Store Gravatar locally." msgstr "" #: tpl/page_optm/settings_localization.tpl.php:31 msgid "Accelerates the speed by caching Gravatar (Globally Recognized Avatars)." msgstr "" #: tpl/page_optm/settings_localization.tpl.php:44 msgid "Refresh Gravatar cache by cron." msgstr "" #: tpl/page_optm/settings_localization.tpl.php:57 msgid "Avatar list in queue waiting for update" msgstr "" #: tpl/page_optm/settings_localization.tpl.php:62 #: tpl/page_optm/settings_media.tpl.php:205 msgid "Run Queue Manually" msgstr "" #: tpl/page_optm/settings_localization.tpl.php:79 msgid "Specify how long, in seconds, Gravatar files are cached." msgstr "" #: tpl/page_optm/settings_localization.tpl.php:94 msgid "Localize external resources." msgstr "" #: tpl/page_optm/settings_localization.tpl.php:98 msgid "Please thoroughly test all items in %s to ensure they function as expected." msgstr "" #: tpl/page_optm/settings_localization.tpl.php:120 msgid "Resources listed here will be copied and replaced with local URLs." msgstr "" #: tpl/page_optm/settings_localization.tpl.php:121 msgid "HTTPS sources only." msgstr "" #: tpl/page_optm/settings_localization.tpl.php:125 msgid "Comments are supported. Start a line with a %s to turn it into a comment line." msgstr "" #: tpl/page_optm/settings_localization.tpl.php:127 #: tpl/toolbox/beta_test.tpl.php:27 msgid "Example" msgstr "" #: tpl/page_optm/settings_localization.tpl.php:131 msgid "Please thoroughly test each JS file you add to ensure it functions as expected." msgstr "" #: tpl/page_optm/settings_media.tpl.php:30 msgid "Load images only when they enter the viewport." msgstr "" #: tpl/page_optm/settings_media.tpl.php:31 #: tpl/page_optm/settings_media.tpl.php:222 msgid "This can improve page loading time by reducing initial HTTP requests." msgstr "" #: tpl/page_optm/settings_media.tpl.php:35 msgid "Adding Style to Your Lazy-Loaded Images" msgstr "" #: tpl/page_optm/settings_media.tpl.php:49 msgid "Specify a base64 image to be used as a simple placeholder while images finish loading." msgstr "" #: tpl/page_optm/settings_media.tpl.php:50 msgid "This can be predefined in %2$s as well using constant %1$s, with this setting taking priority." msgstr "" #: tpl/page_optm/settings_media.tpl.php:51 msgid "By default a gray image placeholder %s will be used." msgstr "" #: tpl/page_optm/settings_media.tpl.php:52 msgid "For example, %s can be used for a transparent placeholder." msgstr "" #: tpl/page_optm/settings_media.tpl.php:66 msgid "Responsive image placeholders can help to reduce layout reshuffle when images are loaded." msgstr "" #: tpl/page_optm/settings_media.tpl.php:67 msgid "This will generate the placeholder with same dimensions as the image if it has the width and height attributes." msgstr "" #: tpl/page_optm/settings_media.tpl.php:80 msgid "Specify an SVG to be used as a placeholder when generating locally." msgstr "" #: tpl/page_optm/settings_media.tpl.php:81 msgid "It will be converted to a base64 SVG placeholder on-the-fly." msgstr "" #: tpl/page_optm/settings_media.tpl.php:82 msgid "Variables %s will be replaced with the corresponding image properties." msgstr "" #: tpl/page_optm/settings_media.tpl.php:83 msgid "Variables %s will be replaced with the configured background color." msgstr "" #: tpl/page_optm/settings_media.tpl.php:97 msgid "Specify the responsive placeholder SVG color." msgstr "" #: tpl/page_optm/settings_media.tpl.php:112 msgid "Use QUIC.cloud LQIP (Low Quality Image Placeholder) generator service for responsive image previews while loading." msgstr "" #: tpl/page_optm/settings_media.tpl.php:113 msgid "Keep this off to use plain color placeholders." msgstr "" #: tpl/page_optm/settings_media.tpl.php:127 msgid "Specify the quality when generating LQIP." msgstr "" #: tpl/page_optm/settings_media.tpl.php:128 msgid "Larger number will generate higher resolution quality placeholder, but will result in larger files which will increase page size and consume more points." msgstr "" #: tpl/page_optm/settings_media.tpl.php:131 msgid "Changes to this setting do not apply to already-generated LQIPs. To regenerate existing LQIPs, please %s first from the admin bar menu." msgstr "" #: tpl/page_optm/settings_media.tpl.php:144 msgid "pixels" msgstr "" #: tpl/page_optm/settings_media.tpl.php:146 msgid "LQIP requests will not be sent for images where both width and height are smaller than these dimensions." msgstr "" #: tpl/page_optm/settings_media.tpl.php:162 msgid "Automatically generate LQIP in the background via a cron-based queue." msgstr "" #: tpl/page_optm/settings_media.tpl.php:164 msgid "If set to %1$s, before the placeholder is localized, the %2$s configuration will be used." msgstr "" #: tpl/page_optm/settings_media.tpl.php:168 msgid "If set to %s this is done in the foreground, which may slow down page load." msgstr "" #: tpl/page_optm/settings_media.tpl.php:188 msgid "Size list in queue waiting for cron" msgstr "" #: tpl/page_optm/settings_media.tpl.php:221 msgid "Load iframes only when they enter the viewport." msgstr "" #: tpl/page_optm/settings_media.tpl.php:235 msgid "Set an explicit width and height on image elements to reduce layout shifts and improve CLS (a Core Web Vitals metric)." msgstr "" #: tpl/page_optm/settings_media.tpl.php:246 msgid "Use %1$s to bypass remote image dimension check when %2$s is ON." msgstr "" #: tpl/page_optm/settings_media.tpl.php:260 msgid "The image compression quality setting of WordPress out of 100." msgstr "" #: tpl/page_optm/settings_media_exc.tpl.php:21 msgid "Listed images will not be lazy loaded." msgstr "" #: tpl/page_optm/settings_media_exc.tpl.php:24 msgid "Useful for above-the-fold images causing CLS (a Core Web Vitals metric)." msgstr "" #: tpl/page_optm/settings_media_exc.tpl.php:28 #: tpl/page_optm/settings_tuning.tpl.php:61 #: tpl/page_optm/settings_tuning.tpl.php:82 #: tpl/page_optm/settings_tuning.tpl.php:103 #: tpl/page_optm/settings_tuning_css.tpl.php:26 msgid "Elements with attribute %s in html code will be excluded." msgstr "" #: tpl/page_optm/settings_media_exc.tpl.php:50 msgid "Images containing these class names will not be lazy loaded." msgstr "" #: tpl/page_optm/settings_media_exc.tpl.php:65 msgid "Images having these parent class names will not be lazy loaded." msgstr "" #: tpl/page_optm/settings_media_exc.tpl.php:79 msgid "Iframes containing these class names will not be lazy loaded." msgstr "" #: tpl/page_optm/settings_media_exc.tpl.php:94 msgid "Iframes having these parent class names will not be lazy loaded." msgstr "" #: tpl/page_optm/settings_media_exc.tpl.php:108 msgid "Prevent any lazy load of listed pages." msgstr "" #: tpl/page_optm/settings_media_exc.tpl.php:122 msgid "These images will not generate LQIP." msgstr "" #: tpl/page_optm/settings_tuning.tpl.php:34 msgid "Listed JS files or inline JS code will be delayed." msgstr "" #: tpl/page_optm/settings_tuning.tpl.php:54 msgid "Listed JS files or inline JS code will not be minified/combined." msgstr "" #: tpl/page_optm/settings_tuning.tpl.php:76 msgid "Listed JS files or inline JS code will not be deferred or delayed." msgstr "" #: tpl/page_optm/settings_tuning.tpl.php:97 msgid "Listed JS files or inline JS code will not be optimized by %s." msgstr "" #: tpl/page_optm/settings_tuning.tpl.php:117 msgid "Prevent any optimization of listed pages." msgstr "" #: tpl/page_optm/settings_tuning.tpl.php:135 msgid "Only optimize pages for guest (not logged in) visitors. If turned this OFF, CSS/JS/CCSS files will be doubled by each user group." msgstr "" #: tpl/page_optm/settings_tuning.tpl.php:147 msgid "Selected roles will be excluded from all optimizations." msgstr "" #: tpl/page_optm/settings_tuning_css.tpl.php:20 msgid "Listed CSS files or inline CSS code will not be minified/combined." msgstr "" #: tpl/page_optm/settings_tuning_css.tpl.php:41 msgid "Listed CSS files will be excluded from UCSS and saved to inline." msgstr "" #: tpl/page_optm/settings_tuning_css.tpl.php:56 msgid "List the CSS selectors whose styles should always be included in UCSS." msgstr "" #: tpl/page_optm/settings_tuning_css.tpl.php:59 #: tpl/page_optm/settings_tuning_css.tpl.php:135 msgid "Wildcard %s supported." msgstr "" #: tpl/page_optm/settings_tuning_css.tpl.php:63 msgid "The selector must exist in the CSS. Parent classes in the HTML will not work." msgstr "" #: tpl/page_optm/settings_tuning_css.tpl.php:81 msgid "Listed URI will not generate UCSS." msgstr "" #: tpl/page_optm/settings_tuning_css.tpl.php:88 msgid "Use %1$s to generate one single UCSS for the pages which page type is %2$s while other page types still per URL." msgstr "" #: tpl/page_optm/settings_tuning_css.tpl.php:89 msgid "Use %1$s to bypass UCSS for the pages which page type is %2$s." msgstr "" #: tpl/page_optm/settings_tuning_css.tpl.php:103 msgid "List post types where each item of that type should have its own CCSS generated." msgstr "" #: tpl/page_optm/settings_tuning_css.tpl.php:104 msgid "For example, if every Page on the site has different formatting, enter %s in the box. Separate critical CSS files will be stored for every Page on the site." msgstr "" #: tpl/page_optm/settings_tuning_css.tpl.php:118 msgid "Separate critical CSS files will be generated for paths containing these strings." msgstr "" #: tpl/page_optm/settings_tuning_css.tpl.php:132 msgid "List the CSS selectors whose styles should always be included in CCSS." msgstr "" #: tpl/page_optm/settings_tuning_css.tpl.php:139 msgid "Selectors must exist in the CSS. Parent classes in the HTML will not work." msgstr "" #: tpl/page_optm/settings_tuning_css.tpl.php:157 msgid "Specify critical CSS rules for above-the-fold content when enabling %s." msgstr "" #: tpl/page_optm/settings_vpi.tpl.php:30 msgid "When you use Lazy Load, it will delay the loading of all images on a page." msgstr "" #: tpl/page_optm/settings_vpi.tpl.php:31 msgid "The Viewport Images service detects which images appear above the fold, and excludes them from lazy load." msgstr "" #: tpl/page_optm/settings_vpi.tpl.php:32 msgid "This enables the page's initial screenful of imagery to be fully displayed without delay." msgstr "" #: tpl/page_optm/settings_vpi.tpl.php:104 msgid "Enable Viewport Images auto generation cron." msgstr "" #: tpl/presets/entry.tpl.php:6 msgid "Standard Presets" msgstr "" #: tpl/presets/entry.tpl.php:7 #: tpl/toolbox/entry.tpl.php:10 msgid "Import / Export" msgstr "" #: tpl/presets/entry.tpl.php:14 msgid "LiteSpeed Cache Configuration Presets" msgstr "" #: tpl/presets/standard.tpl.php:8 msgid "Essentials" msgstr "" #: tpl/presets/standard.tpl.php:10 msgid "Default Cache" msgstr "" #: tpl/presets/standard.tpl.php:11 msgid "Higher TTL" msgstr "" #: tpl/presets/standard.tpl.php:15 msgid "This no-risk preset is appropriate for all websites. Good for new users, simple websites, or cache-oriented development." msgstr "" #: tpl/presets/standard.tpl.php:16 msgid "A Domain Key is not required to use this preset. Only basic caching features are enabled." msgstr "" #: tpl/presets/standard.tpl.php:21 #: tpl/toolbox/settings-debug.tpl.php:84 msgid "Basic" msgstr "" #: tpl/presets/standard.tpl.php:23 msgid "Everything in Essentials, Plus" msgstr "" #: tpl/presets/standard.tpl.php:25 msgid "Mobile Cache" msgstr "" #: tpl/presets/standard.tpl.php:28 msgid "This low-risk preset introduces basic optimizations for speed and user experience. Appropriate for enthusiastic beginners." msgstr "" #: tpl/presets/standard.tpl.php:29 msgid "A Domain Key is required to use this preset. Includes optimizations known to improve site score in page speed measurement tools." msgstr "" #: tpl/presets/standard.tpl.php:34 msgid "Advanced (Recommended)" msgstr "" #: tpl/presets/standard.tpl.php:36 msgid "Everything in Basic, Plus" msgstr "" #: tpl/presets/standard.tpl.php:37 msgid "Guest Mode and Guest Optimization" msgstr "" #: tpl/presets/standard.tpl.php:38 msgid "CSS, JS and HTML Minification" msgstr "" #: tpl/presets/standard.tpl.php:40 msgid "JS Defer for both external and inline JS" msgstr "" #: tpl/presets/standard.tpl.php:41 msgid "DNS Prefetch for static files" msgstr "" #: tpl/presets/standard.tpl.php:43 msgid "Remove Query Strings from Static Files" msgstr "" #: tpl/presets/standard.tpl.php:48 msgid "This preset is good for most websites, and is unlikely to cause conflicts. Any CSS or JS conflicts may be resolved with Page Optimization > Tuning tools." msgstr "" #: tpl/presets/standard.tpl.php:49 #: tpl/presets/standard.tpl.php:64 msgid "A Domain Key is required to use this preset. Includes many optimizations known to improve page speed scores." msgstr "" #: tpl/presets/standard.tpl.php:54 msgid "Aggressive" msgstr "" #: tpl/presets/standard.tpl.php:56 msgid "Everything in Advanced, Plus" msgstr "" #: tpl/presets/standard.tpl.php:57 msgid "CSS & JS Combine" msgstr "" #: tpl/presets/standard.tpl.php:58 msgid "Asynchronous CSS Loading with Critical CSS" msgstr "" #: tpl/presets/standard.tpl.php:59 msgid "Removed Unused CSS for Users" msgstr "" #: tpl/presets/standard.tpl.php:60 msgid "Lazy Load for Iframes" msgstr "" #: tpl/presets/standard.tpl.php:63 msgid "This preset might work out of the box for some websites, but be sure to test! Some CSS or JS exclusions may be necessary in Page Optimization > Tuning." msgstr "" #: tpl/presets/standard.tpl.php:69 msgid "Extreme" msgstr "" #: tpl/presets/standard.tpl.php:71 msgid "Everything in Aggressive, Plus" msgstr "" #: tpl/presets/standard.tpl.php:72 msgid "Lazy Load for Images" msgstr "" #: tpl/presets/standard.tpl.php:73 msgid "Viewport Image Generation" msgstr "" #: tpl/presets/standard.tpl.php:74 msgid "JS Delayed" msgstr "" #: tpl/presets/standard.tpl.php:75 msgid "Inline JS added to Combine" msgstr "" #: tpl/presets/standard.tpl.php:76 msgid "Inline CSS added to Combine" msgstr "" #: tpl/presets/standard.tpl.php:79 msgid "This preset almost certainly will require testing and exclusions for some CSS, JS and Lazy Loaded images. Pay special attention to logos, or HTML-based slider images." msgstr "" #: tpl/presets/standard.tpl.php:80 msgid "A Domain Key is required to use this preset. Enables the maximum level of optimizations for improved page speed scores." msgstr "" #: tpl/presets/standard.tpl.php:87 msgid "LiteSpeed Cache Standard Presets" msgstr "" #: tpl/presets/standard.tpl.php:91 msgid "Use an official LiteSpeed-designed Preset to configure your site in one click. Try no-risk caching essentials, extreme optimization, or something in between." msgstr "" #: tpl/presets/standard.tpl.php:116 msgid "Who should use this preset?" msgstr "" #: tpl/presets/standard.tpl.php:126 msgid "This will back up your current settings and replace them with the %1$s preset settings. Do you want to continue?" msgstr "" #: tpl/presets/standard.tpl.php:128 msgid "Apply Preset" msgstr "" #: tpl/presets/standard.tpl.php:147 msgid "unknown" msgstr "" #: tpl/presets/standard.tpl.php:158 msgid "History" msgstr "" #: tpl/presets/standard.tpl.php:168 msgid "Error: Failed to apply the settings %1$s" msgstr "" #: tpl/presets/standard.tpl.php:170 msgid "Restored backup settings %1$s" msgstr "" #: tpl/presets/standard.tpl.php:173 msgid "Applied the %1$s preset %2$s" msgstr "" #: tpl/presets/standard.tpl.php:184 msgid "Backup created %1$s before applying the %2$s preset" msgstr "" #: tpl/presets/standard.tpl.php:188 msgid "This will restore the backup settings created %1$s before applying the %2$s preset. Any changes made since then will be lost. Do you want to continue?" msgstr "" #: tpl/presets/standard.tpl.php:190 msgid "Restore Settings" msgstr "" #: tpl/toolbox/beta_test.tpl.php:22 msgid "Try GitHub Version" msgstr "" #: tpl/toolbox/beta_test.tpl.php:26 msgid "Use this section to switch plugin versions. To beta test a GitHub commit, enter the commit URL in the field below." msgstr "" #: tpl/toolbox/beta_test.tpl.php:31 msgid "Use latest GitHub Dev commit" msgstr "" #: tpl/toolbox/beta_test.tpl.php:33 msgid "Use latest GitHub Master commit" msgstr "" #: tpl/toolbox/beta_test.tpl.php:35 #: tpl/toolbox/beta_test.tpl.php:51 msgid "Use latest WordPress release version" msgstr "" #: tpl/toolbox/beta_test.tpl.php:35 msgid "OR" msgstr "" #: tpl/toolbox/beta_test.tpl.php:46 msgid "Downgrade not recommended. May cause fatal error due to refactored code." msgstr "" #: tpl/toolbox/beta_test.tpl.php:50 msgid "Press the %s button to use the most recent GitHub commit. Master is for release candidate & Dev is for experimental testing." msgstr "" #: tpl/toolbox/beta_test.tpl.php:50 msgid "Use latest GitHub Dev/Master commit" msgstr "" #: tpl/toolbox/beta_test.tpl.php:51 msgid "Press the %s button to stop beta testing and go back to the current release from the WordPress Plugin Directory." msgstr "" #: tpl/toolbox/beta_test.tpl.php:56 msgid "In order to avoid an upgrade error, you must be using %1$s or later before you can upgrade to %2$s versions." msgstr "" #: tpl/toolbox/edit_htaccess.tpl.php:38 msgid "LiteSpeed Cache View .htaccess" msgstr "" #: tpl/toolbox/edit_htaccess.tpl.php:43 msgid ".htaccess Path" msgstr "" #: tpl/toolbox/edit_htaccess.tpl.php:50 msgid "Frontend .htaccess Path" msgstr "" #: tpl/toolbox/edit_htaccess.tpl.php:55 #: tpl/toolbox/edit_htaccess.tpl.php:73 msgid "Default path is" msgstr "" #: tpl/toolbox/edit_htaccess.tpl.php:59 #: tpl/toolbox/edit_htaccess.tpl.php:77 msgid "PHP Constant %s is supported." msgstr "" #: tpl/toolbox/edit_htaccess.tpl.php:60 #: tpl/toolbox/edit_htaccess.tpl.php:78 msgid "You can use this code %1$s in %2$s to specify the htaccess file path." msgstr "" #: tpl/toolbox/edit_htaccess.tpl.php:68 msgid "Backend .htaccess Path" msgstr "" #: tpl/toolbox/edit_htaccess.tpl.php:88 msgid "Current %s Contents" msgstr "" #: tpl/toolbox/entry.tpl.php:14 msgid "View .htaccess" msgstr "" #: tpl/toolbox/entry.tpl.php:18 msgid "Heartbeat" msgstr "" #: tpl/toolbox/entry.tpl.php:19 msgid "Report" msgstr "" #: tpl/toolbox/entry.tpl.php:23 #: tpl/toolbox/settings-debug.tpl.php:24 msgid "Debug Settings" msgstr "" #: tpl/toolbox/entry.tpl.php:24 msgid "Log View" msgstr "" #: tpl/toolbox/entry.tpl.php:25 msgid "Beta Test" msgstr "" #: tpl/toolbox/entry.tpl.php:32 msgid "LiteSpeed Cache Toolbox" msgstr "" #: tpl/toolbox/heartbeat.tpl.php:9 msgid "Heartbeat Control" msgstr "" #: tpl/toolbox/heartbeat.tpl.php:15 msgid "Disable WordPress interval heartbeat to reduce server load." msgstr "" #: tpl/toolbox/heartbeat.tpl.php:18 msgid "Disabling this may cause WordPress tasks triggered by AJAX to stop working." msgstr "" #: tpl/toolbox/heartbeat.tpl.php:34 msgid "Turn ON to control heartbeat on frontend." msgstr "" #: tpl/toolbox/heartbeat.tpl.php:47 #: tpl/toolbox/heartbeat.tpl.php:77 #: tpl/toolbox/heartbeat.tpl.php:107 msgid "Specify the %s heartbeat interval in seconds." msgstr "" #: tpl/toolbox/heartbeat.tpl.php:48 msgid "WordPress valid interval is %s seconds." msgstr "" #: tpl/toolbox/heartbeat.tpl.php:49 #: tpl/toolbox/heartbeat.tpl.php:79 #: tpl/toolbox/heartbeat.tpl.php:109 msgid "Set to %1$s to forbid heartbeat on %2$s." msgstr "" #: tpl/toolbox/heartbeat.tpl.php:64 msgid "Turn ON to control heartbeat on backend." msgstr "" #: tpl/toolbox/heartbeat.tpl.php:78 #: tpl/toolbox/heartbeat.tpl.php:108 msgid "WordPress valid interval is %s seconds" msgstr "" #: tpl/toolbox/heartbeat.tpl.php:94 msgid "Turn ON to control heartbeat in backend editor." msgstr "" #: tpl/toolbox/import_export.tpl.php:9 msgid "Export Settings" msgstr "" #: tpl/toolbox/import_export.tpl.php:14 msgid "Export" msgstr "" #: tpl/toolbox/import_export.tpl.php:19 msgid "Last exported" msgstr "" #: tpl/toolbox/import_export.tpl.php:24 msgid "This will export all current LiteSpeed Cache settings and save them as a file." msgstr "" #: tpl/toolbox/import_export.tpl.php:27 msgid "Import Settings" msgstr "" #: tpl/toolbox/import_export.tpl.php:35 msgid "Import" msgstr "" #: tpl/toolbox/import_export.tpl.php:41 msgid "Last imported" msgstr "" #: tpl/toolbox/import_export.tpl.php:46 msgid "This will import settings from a file and override all current LiteSpeed Cache settings." msgstr "" #: tpl/toolbox/import_export.tpl.php:49 msgid "Reset All Settings" msgstr "" #: tpl/toolbox/import_export.tpl.php:50 msgid "This will reset all settings to default settings." msgstr "" #: tpl/toolbox/import_export.tpl.php:52 msgid "Are you sure you want to reset all settings back to the default settings?" msgstr "" #: tpl/toolbox/import_export.tpl.php:53 msgid "Reset Settings" msgstr "" #: tpl/toolbox/log_viewer.tpl.php:16 msgid "Purge Log" msgstr "" #: tpl/toolbox/log_viewer.tpl.php:21 msgid "Crawler Log" msgstr "" #: tpl/toolbox/log_viewer.tpl.php:64 msgid "Clear Logs" msgstr "" #: tpl/toolbox/log_viewer.tpl.php:81 msgid "Copy Log" msgstr "" #: tpl/toolbox/log_viewer.tpl.php:114 msgid "LiteSpeed Logs" msgstr "" #: tpl/toolbox/purge.tpl.php:9 msgid "Purge Front Page" msgstr "" #: tpl/toolbox/purge.tpl.php:10 msgid "This will Purge Front Page only" msgstr "" #: tpl/toolbox/purge.tpl.php:15 msgid "Purge Pages" msgstr "" #: tpl/toolbox/purge.tpl.php:16 msgid "This will Purge Pages only" msgstr "" #: tpl/toolbox/purge.tpl.php:23 msgid "Purge %s Error" msgstr "" #: tpl/toolbox/purge.tpl.php:24 msgid "Purge %s error pages" msgstr "" #: tpl/toolbox/purge.tpl.php:31 msgid "Purge the LiteSpeed cache entries created by this plugin" msgstr "" #: tpl/toolbox/purge.tpl.php:37 msgid "This will purge all minified/combined CSS/JS entries only" msgstr "" #: tpl/toolbox/purge.tpl.php:45 msgid "Purge all the object caches" msgstr "" #: tpl/toolbox/purge.tpl.php:54 msgid "Reset the entire opcode cache" msgstr "" #: tpl/toolbox/purge.tpl.php:63 msgid "This will delete all generated critical CSS files" msgstr "" #: tpl/toolbox/purge.tpl.php:72 msgid "This will delete all generated unique CSS files" msgstr "" #: tpl/toolbox/purge.tpl.php:81 msgid "This will delete all localized resources" msgstr "" #: tpl/toolbox/purge.tpl.php:90 msgid "This will delete all generated image LQIP placeholder files" msgstr "" #: tpl/toolbox/purge.tpl.php:99 msgid "This will delete all cached Gravatar files" msgstr "" #: tpl/toolbox/purge.tpl.php:108 msgid "Purge the cache entries created by this plugin except for Critical CSS & Unique CSS & LQIP caches" msgstr "" #: tpl/toolbox/purge.tpl.php:117 msgid "Empty Entire Cache" msgstr "" #: tpl/toolbox/purge.tpl.php:118 msgid "Clears all cache entries related to this site, <i>including other web applications</i>." msgstr "" #: tpl/toolbox/purge.tpl.php:119 msgid "This action should only be used if things are cached incorrectly." msgstr "" #: tpl/toolbox/purge.tpl.php:123 msgid "This will clear EVERYTHING inside the cache." msgstr "" #: tpl/toolbox/purge.tpl.php:124 msgid "This may cause heavy load on the server." msgstr "" #: tpl/toolbox/purge.tpl.php:125 msgid "If only the WordPress site should be purged, use Purge All." msgstr "" #: tpl/toolbox/purge.tpl.php:166 msgid "Purge By..." msgstr "" #: tpl/toolbox/purge.tpl.php:168 msgid "Select below for \"Purge by\" options." msgstr "" #: tpl/toolbox/purge.tpl.php:193 msgid "Category" msgstr "" #: tpl/toolbox/purge.tpl.php:197 msgid "Post ID" msgstr "" #: tpl/toolbox/purge.tpl.php:201 msgid "Tag" msgstr "" #: tpl/toolbox/purge.tpl.php:211 msgid "Purge pages by category name - e.g. %2$s should be used for the URL %1$s." msgstr "" #: tpl/toolbox/purge.tpl.php:217 msgid "Purge pages by post ID." msgstr "" #: tpl/toolbox/purge.tpl.php:221 msgid "Purge pages by tag name - e.g. %2$s should be used for the URL %1$s." msgstr "" #: tpl/toolbox/purge.tpl.php:227 msgid "Purge pages by relative or full URL." msgstr "" #: tpl/toolbox/purge.tpl.php:229 msgid "e.g. Use %s or %s." msgstr "" #: tpl/toolbox/purge.tpl.php:243 msgid "Purge List" msgstr "" #: tpl/toolbox/report.tpl.php:30 msgid "Send to LiteSpeed" msgstr "" #: tpl/toolbox/report.tpl.php:32 msgid "Regenerate and Send a New Report" msgstr "" #: tpl/toolbox/report.tpl.php:40 msgid "To generate a passwordless link for LiteSpeed Support Team access, you must install %s." msgstr "" #: tpl/toolbox/report.tpl.php:43 msgid "Install DoLogin Security" msgstr "" #: tpl/toolbox/report.tpl.php:44 msgid "Go to plugins list" msgstr "" #: tpl/toolbox/report.tpl.php:50 msgid "LiteSpeed Report" msgstr "" #: tpl/toolbox/report.tpl.php:54 msgid "Last Report Number" msgstr "" #: tpl/toolbox/report.tpl.php:55 msgid "Last Report Date" msgstr "" #: tpl/toolbox/report.tpl.php:58 msgid "The environment report contains detailed information about the WordPress configuration." msgstr "" #: tpl/toolbox/report.tpl.php:60 msgid "If you run into any issues, please refer to the report number in your support message." msgstr "" #: tpl/toolbox/report.tpl.php:67 msgid "System Information" msgstr "" #: tpl/toolbox/report.tpl.php:79 msgid "Attach PHP info to report. Check this box to insert relevant data from %s." msgstr "" #: tpl/toolbox/report.tpl.php:91 msgid "Passwordless Link" msgstr "" #: tpl/toolbox/report.tpl.php:95 #: tpl/toolbox/report.tpl.php:97 msgid "Generate Link for Current User" msgstr "" #: tpl/toolbox/report.tpl.php:100 msgid "To grant wp-admin access to the LiteSpeed Support Team, please generate a passwordless link for the current logged-in user to be sent with the report." msgstr "" #: tpl/toolbox/report.tpl.php:102 msgid "Please do NOT share the above passwordless link with anyone." msgstr "" #: tpl/toolbox/report.tpl.php:103 msgid "Generated links may be managed under <a %s>Settings</a>." msgstr "" #: tpl/toolbox/report.tpl.php:109 msgid "Notes" msgstr "" #: tpl/toolbox/report.tpl.php:113 msgid "Optional" msgstr "" #: tpl/toolbox/report.tpl.php:114 msgid "provide more information here to assist the LiteSpeed team with debugging." msgstr "" #: tpl/toolbox/report.tpl.php:126 msgid "Send this report to LiteSpeed. Refer to this report number when posting in the WordPress support forum." msgstr "" #: tpl/toolbox/settings-debug.tpl.php:11 msgid "Debug Helpers" msgstr "" #: tpl/toolbox/settings-debug.tpl.php:15 msgid "View Site Before Optimization" msgstr "" #: tpl/toolbox/settings-debug.tpl.php:19 msgid "View Site Before Cache" msgstr "" #: tpl/toolbox/settings-debug.tpl.php:38 msgid "This will disable LSCache and all optimization features for debug purpose." msgstr "" #: tpl/toolbox/settings-debug.tpl.php:49 msgid "Admin IP Only" msgstr "" #: tpl/toolbox/settings-debug.tpl.php:51 msgid "Outputs to a series of files in the %s directory." msgstr "" #: tpl/toolbox/settings-debug.tpl.php:52 msgid "To prevent filling up the disk, this setting should be OFF when everything is working." msgstr "" #: tpl/toolbox/settings-debug.tpl.php:53 msgid "The Admin IP option will only output log messages on requests from admin IPs listed below." msgstr "" #: tpl/toolbox/settings-debug.tpl.php:66 msgid "Allows listed IPs (one per line) to perform certain actions from their browsers." msgstr "" #: tpl/toolbox/settings-debug.tpl.php:67 msgid "Your IP" msgstr "" #: tpl/toolbox/settings-debug.tpl.php:72 msgid "More information about the available commands can be found here." msgstr "" #: tpl/toolbox/settings-debug.tpl.php:86 msgid "Advanced level will log more details." msgstr "" #: tpl/toolbox/settings-debug.tpl.php:97 msgid "MB" msgstr "" #: tpl/toolbox/settings-debug.tpl.php:99 msgid "Specify the maximum size of the log file." msgstr "" #: tpl/toolbox/settings-debug.tpl.php:114 msgid "Shorten query strings in the debug log to improve readability." msgstr "" #: tpl/toolbox/settings-debug.tpl.php:127 msgid "Only log listed pages." msgstr "" #: tpl/toolbox/settings-debug.tpl.php:141 msgid "Prevent any debug log of listed pages." msgstr "" #: tpl/toolbox/settings-debug.tpl.php:155 msgid "Prevent writing log entries that include listed strings." msgstr "" error_log 0000644 00000001236 15246276230 0006472 0 ustar 00 [03-Sep-2026 07:30:31 UTC] PHP Warning: file_get_contents(/home/veronikagstoette/public_html/wp-content/.litespeed_conf.dat): Failed to open stream: No such file or directory in /home/veronikagstoette/public_html/wp-content/plugins/litespeed-cache/lib/guest.cls.php on line 34 [03-Sep-2026 07:30:31 UTC] PHP Warning: Trying to access array offset on value of type bool in /home/veronikagstoette/public_html/wp-content/plugins/litespeed-cache/lib/guest.cls.php on line 130 [03-Sep-2026 07:30:31 UTC] PHP Warning: Trying to access array offset on value of type bool in /home/veronikagstoette/public_html/wp-content/plugins/litespeed-cache/lib/guest.cls.php on line 141 autoload.php 0000644 00000006264 15246276230 0007104 0 ustar 00 <?php /** * Auto registration for LiteSpeed classes * * @since 1.1.0 */ defined('WPINC') || exit(); // Force define for object cache usage before plugin init !defined('LSCWP_DIR') && define('LSCWP_DIR', __DIR__ . '/'); // Full absolute path '/var/www/html/***/wp-content/plugins/litespeed-cache/' or MU // Load all classes instead of autoload for direct conf update purpose when upgrade to new version. // NOTE: These files need to load exactly in order $litespeed_php_files = array( // core file priority 'src/root.cls.php', 'src/base.cls.php', // main src files 'src/activation.cls.php', 'src/admin-display.cls.php', 'src/admin-settings.cls.php', 'src/admin.cls.php', 'src/api.cls.php', 'src/avatar.cls.php', 'src/cdn.cls.php', 'src/cloud.cls.php', 'src/conf.cls.php', 'src/control.cls.php', 'src/core.cls.php', 'src/crawler-map.cls.php', 'src/crawler.cls.php', 'src/css.cls.php', 'src/data.cls.php', 'src/db-optm.cls.php', 'src/debug2.cls.php', 'src/doc.cls.php', 'src/error.cls.php', 'src/esi.cls.php', 'src/file.cls.php', 'src/gui.cls.php', 'src/health.cls.php', 'src/htaccess.cls.php', 'src/img-optm.cls.php', 'src/import.cls.php', 'src/import.preset.cls.php', 'src/lang.cls.php', 'src/localization.cls.php', 'src/media.cls.php', 'src/metabox.cls.php', 'src/object-cache.cls.php', 'src/optimize.cls.php', 'src/optimizer.cls.php', 'src/placeholder.cls.php', 'src/purge.cls.php', 'src/report.cls.php', 'src/rest.cls.php', 'src/router.cls.php', 'src/str.cls.php', 'src/tag.cls.php', 'src/task.cls.php', 'src/tool.cls.php', 'src/ucss.cls.php', 'src/utility.cls.php', 'src/vary.cls.php', 'src/vpi.cls.php', // Extra CDN cls files 'src/cdn/cloudflare.cls.php', 'src/cdn/quic.cls.php', // CLI classes 'cli/crawler.cls.php', 'cli/debug.cls.php', 'cli/image.cls.php', 'cli/online.cls.php', 'cli/option.cls.php', 'cli/presets.cls.php', 'cli/purge.cls.php', // 3rd party libraries 'lib/css_js_min/pathconverter/converter.cls.php', 'lib/css_js_min/minify/exception.cls.php', 'lib/css_js_min/minify/minify.cls.php', 'lib/css_js_min/minify/css.cls.php', 'lib/css_js_min/minify/js.cls.php', 'lib/urirewriter.cls.php', 'lib/guest.cls.php', 'lib/html-min.cls.php', // 'lib/object-cache.php', // 'lib/php-compatibility.func.php', // upgrade purpose delay loaded funcs // 'src/data.upgrade.func.php', ); foreach ($litespeed_php_files as $class) { $file = LSCWP_DIR . $class; require_once $file; } if (!function_exists('litespeed_autoload')) { function litespeed_autoload($cls) { if (strpos($cls, '.') !== false) { return; } if (strpos($cls, 'LiteSpeed') !== 0) { return; } $file = explode('\\', $cls); array_shift($file); $file = implode('/', $file); $file = str_replace('_', '-', strtolower($file)); // if (strpos($file, 'lib/') === 0 || strpos($file, 'cli/') === 0 || strpos($file, 'thirdparty/') === 0) { // $file = LSCWP_DIR . $file . '.cls.php'; // } else { // $file = LSCWP_DIR . 'src/' . $file . '.cls.php'; // } if (strpos($file, 'thirdparty/') !== 0) { return; } $file = LSCWP_DIR . $file . '.cls.php'; if (file_exists($file)) { require_once $file; } } } spl_autoload_register('litespeed_autoload'); composer.lock 0000644 00000014237 15246276230 0007263 0 ustar 00 { "_readme": [ "This file locks the dependencies of your project to a known state", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], "content-hash": "21330dd959c1642a4c7dbc91aa5effef", "packages": [], "packages-dev": [ { "name": "phpcompatibility/php-compatibility", "version": "9.3.5", "source": { "type": "git", "url": "https://github.com/PHPCompatibility/PHPCompatibility.git", "reference": "9fb324479acf6f39452e0655d2429cc0d3914243" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibility/zipball/9fb324479acf6f39452e0655d2429cc0d3914243", "reference": "9fb324479acf6f39452e0655d2429cc0d3914243", "shasum": "" }, "require": { "php": ">=5.3", "squizlabs/php_codesniffer": "^2.3 || ^3.0.2" }, "conflict": { "squizlabs/php_codesniffer": "2.6.2" }, "require-dev": { "phpunit/phpunit": "~4.5 || ^5.0 || ^6.0 || ^7.0" }, "suggest": { "dealerdirect/phpcodesniffer-composer-installer": "^0.5 || This Composer plugin will sort out the PHPCS 'installed_paths' automatically.", "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues." }, "type": "phpcodesniffer-standard", "notification-url": "https://packagist.org/downloads/", "license": [ "LGPL-3.0-or-later" ], "authors": [ { "name": "Wim Godden", "homepage": "https://github.com/wimg", "role": "lead" }, { "name": "Juliette Reinders Folmer", "homepage": "https://github.com/jrfnl", "role": "lead" }, { "name": "Contributors", "homepage": "https://github.com/PHPCompatibility/PHPCompatibility/graphs/contributors" } ], "description": "A set of sniffs for PHP_CodeSniffer that checks for PHP cross-version compatibility.", "homepage": "http://techblog.wimgodden.be/tag/codesniffer/", "keywords": [ "compatibility", "phpcs", "standards" ], "support": { "issues": "https://github.com/PHPCompatibility/PHPCompatibility/issues", "source": "https://github.com/PHPCompatibility/PHPCompatibility" }, "time": "2019-12-27T09:44:58+00:00" }, { "name": "squizlabs/php_codesniffer", "version": "3.10.2", "source": { "type": "git", "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", "reference": "86e5f5dd9a840c46810ebe5ff1885581c42a3017" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/86e5f5dd9a840c46810ebe5ff1885581c42a3017", "reference": "86e5f5dd9a840c46810ebe5ff1885581c42a3017", "shasum": "" }, "require": { "ext-simplexml": "*", "ext-tokenizer": "*", "ext-xmlwriter": "*", "php": ">=5.4.0" }, "require-dev": { "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4" }, "bin": [ "bin/phpcbf", "bin/phpcs" ], "type": "library", "extra": { "branch-alias": { "dev-master": "3.x-dev" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], "authors": [ { "name": "Greg Sherwood", "role": "Former lead" }, { "name": "Juliette Reinders Folmer", "role": "Current lead" }, { "name": "Contributors", "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors" } ], "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer", "keywords": [ "phpcs", "standards", "static analysis" ], "support": { "issues": "https://github.com/PHPCSStandards/PHP_CodeSniffer/issues", "security": "https://github.com/PHPCSStandards/PHP_CodeSniffer/security/policy", "source": "https://github.com/PHPCSStandards/PHP_CodeSniffer", "wiki": "https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki" }, "funding": [ { "url": "https://github.com/PHPCSStandards", "type": "github" }, { "url": "https://github.com/jrfnl", "type": "github" }, { "url": "https://opencollective.com/php_codesniffer", "type": "open_collective" } ], "time": "2024-07-21T23:26:44+00:00" } ], "aliases": [], "minimum-stability": "stable", "stability-flags": [], "prefer-stable": true, "prefer-lowest": false, "platform": [], "platform-dev": [], "plugin-api-version": "2.6.0" } litespeed-cache.php 0000644 00000015502 15246276230 0010306 0 ustar 00 <?php /** * Plugin Name: LiteSpeed Cache * Plugin URI: https://www.litespeedtech.com/products/cache-plugins/wordpress-acceleration * Description: High-performance page caching and site optimization from LiteSpeed * Version: 7.1 * Author: LiteSpeed Technologies * Author URI: https://www.litespeedtech.com * License: GPLv3 * License URI: http://www.gnu.org/licenses/gpl.html * Text Domain: litespeed-cache * Domain Path: /lang * * Copyright (C) 2015-2025 LiteSpeed Technologies, Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ defined('WPINC') || exit(); if (defined('LSCWP_V')) { return; } !defined('LSCWP_V') && define('LSCWP_V', '7.1'); !defined('LSCWP_CONTENT_DIR') && define('LSCWP_CONTENT_DIR', WP_CONTENT_DIR); !defined('LSCWP_DIR') && define('LSCWP_DIR', __DIR__ . '/'); // Full absolute path '/var/www/html/***/wp-content/plugins/litespeed-cache/' or MU !defined('LSCWP_BASENAME') && define('LSCWP_BASENAME', 'litespeed-cache/litespeed-cache.php'); //LSCWP_BASENAME='litespeed-cache/litespeed-cache.php' /** * This needs to be before activation because admin-rules.class.php need const `LSCWP_CONTENT_FOLDER` * This also needs to be before cfg.cls init because default cdn_included_dir needs `LSCWP_CONTENT_FOLDER` * @since 5.2 Auto correct protocol for CONTENT URL */ $WP_CONTENT_URL = WP_CONTENT_URL; $home_url = home_url('/'); if (substr($WP_CONTENT_URL, 0, 5) == 'http:' && substr($home_url, 0, 5) == 'https') { $WP_CONTENT_URL = str_replace('http://', 'https://', $WP_CONTENT_URL); } !defined('LSCWP_CONTENT_FOLDER') && define('LSCWP_CONTENT_FOLDER', str_replace($home_url, '', $WP_CONTENT_URL)); // `wp-content` !defined('LSWCP_PLUGIN_URL') && define('LSWCP_PLUGIN_URL', plugin_dir_url(__FILE__)); // Full URL path '//example.com/wp-content/plugins/litespeed-cache/' /** * Static cache files consts * @since 3.0 */ !defined('LITESPEED_DATA_FOLDER') && define('LITESPEED_DATA_FOLDER', 'litespeed'); !defined('LITESPEED_STATIC_URL') && define('LITESPEED_STATIC_URL', $WP_CONTENT_URL . '/' . LITESPEED_DATA_FOLDER); // Full static cache folder URL '//example.com/wp-content/litespeed' !defined('LITESPEED_STATIC_DIR') && define('LITESPEED_STATIC_DIR', LSCWP_CONTENT_DIR . '/' . LITESPEED_DATA_FOLDER); // Full static cache folder path '/var/www/html/***/wp-content/litespeed' !defined('LITESPEED_TIME_OFFSET') && define('LITESPEED_TIME_OFFSET', get_option('gmt_offset') * 60 * 60); // Placeholder for lazyload img !defined('LITESPEED_PLACEHOLDER') && define('LITESPEED_PLACEHOLDER', 'data:image/gif;base64,R0lGODdhAQABAPAAAMPDwwAAACwAAAAAAQABAAACAkQBADs='); // Auto register LiteSpeed classes require_once LSCWP_DIR . 'autoload.php'; // Define CLI if ((defined('WP_CLI') && WP_CLI) || PHP_SAPI == 'cli') { !defined('LITESPEED_CLI') && define('LITESPEED_CLI', true); // Register CLI cmd if (method_exists('WP_CLI', 'add_command')) { WP_CLI::add_command('litespeed-option', 'LiteSpeed\CLI\Option'); WP_CLI::add_command('litespeed-purge', 'LiteSpeed\CLI\Purge'); WP_CLI::add_command('litespeed-online', 'LiteSpeed\CLI\Online'); WP_CLI::add_command('litespeed-image', 'LiteSpeed\CLI\Image'); WP_CLI::add_command('litespeed-debug', 'LiteSpeed\CLI\Debug'); WP_CLI::add_command('litespeed-presets', 'LiteSpeed\CLI\Presets'); WP_CLI::add_command('litespeed-crawler', 'LiteSpeed\CLI\Crawler'); } } // Server type if (!defined('LITESPEED_SERVER_TYPE')) { if (isset($_SERVER['HTTP_X_LSCACHE']) && $_SERVER['HTTP_X_LSCACHE']) { define('LITESPEED_SERVER_TYPE', 'LITESPEED_SERVER_ADC'); } elseif (isset($_SERVER['LSWS_EDITION']) && strpos($_SERVER['LSWS_EDITION'], 'Openlitespeed') === 0) { define('LITESPEED_SERVER_TYPE', 'LITESPEED_SERVER_OLS'); } elseif (isset($_SERVER['SERVER_SOFTWARE']) && $_SERVER['SERVER_SOFTWARE'] == 'LiteSpeed') { define('LITESPEED_SERVER_TYPE', 'LITESPEED_SERVER_ENT'); } else { define('LITESPEED_SERVER_TYPE', 'NONE'); } } // Checks if caching is allowed via server variable if (!empty($_SERVER['X-LSCACHE']) || LITESPEED_SERVER_TYPE === 'LITESPEED_SERVER_ADC' || defined('LITESPEED_CLI')) { !defined('LITESPEED_ALLOWED') && define('LITESPEED_ALLOWED', true); } // ESI const definition if (!defined('LSWCP_ESI_SUPPORT')) { define('LSWCP_ESI_SUPPORT', LITESPEED_SERVER_TYPE !== 'LITESPEED_SERVER_OLS' ? true : false); } if (!defined('LSWCP_TAG_PREFIX')) { define('LSWCP_TAG_PREFIX', substr(md5(LSCWP_DIR), -3)); } /** * Handle exception */ if (!function_exists('litespeed_exception_handler')) { function litespeed_exception_handler($errno, $errstr, $errfile, $errline) { throw new \ErrorException($errstr, 0, $errno, $errfile, $errline); } } /** * Overwrite the WP nonce funcs outside of LiteSpeed namespace * @since 3.0 */ if (!function_exists('litespeed_define_nonce_func')) { function litespeed_define_nonce_func() { /** * If the nonce is in none_actions filter, convert it to ESI */ function wp_create_nonce($action = -1) { if (!defined('LITESPEED_DISABLE_ALL') || !LITESPEED_DISABLE_ALL) { $control = \LiteSpeed\ESI::cls()->is_nonce_action($action); if ($control !== null) { $params = array( 'action' => $action, ); return \LiteSpeed\ESI::cls()->sub_esi_block('nonce', 'wp_create_nonce ' . $action, $params, $control, true, true, true); } } return wp_create_nonce_litespeed_esi($action); } /** * Ori WP wp_create_nonce */ function wp_create_nonce_litespeed_esi($action = -1) { $uid = get_current_user_id(); if (!$uid) { /** This filter is documented in wp-includes/pluggable.php */ $uid = apply_filters('nonce_user_logged_out', $uid, $action); } $token = wp_get_session_token(); $i = wp_nonce_tick(); return substr(wp_hash($i . '|' . $action . '|' . $uid . '|' . $token, 'nonce'), -12, 10); } } } /** * Begins execution of the plugin. * * @since 1.0.0 */ if (!function_exists('run_litespeed_cache')) { function run_litespeed_cache() { //Check minimum PHP requirements, which is 7.2 at the moment. if (version_compare(PHP_VERSION, '7.2.0', '<')) { return; } //Check minimum WP requirements, which is 5.3 at the moment. if (version_compare($GLOBALS['wp_version'], '5.3', '<')) { return; } \LiteSpeed\Core::cls(); } run_litespeed_cache(); } thirdparty/elementor.cls.php 0000644 00000002530 15246276230 0012230 0 ustar 00 <?php /** * The Third Party integration with the bbPress plugin. * * @since 2.9.8.8 */ namespace LiteSpeed\Thirdparty; defined('WPINC') || exit(); use LiteSpeed\Debug2; class Elementor { public static function preload() { if (!defined('ELEMENTOR_VERSION')) { return; } if (!is_admin()) { // add_action( 'init', __CLASS__ . '::disable_litespeed_esi', 4 ); // temporarily comment out this line for backward compatibility } if (isset($_GET['action']) && $_GET['action'] === 'elementor') { do_action('litespeed_disable_all', 'elementor edit mode'); } if (!empty($_SERVER['HTTP_REFERER']) && strpos($_SERVER['HTTP_REFERER'], 'action=elementor')) { if (!empty($_REQUEST['actions'])) { $json = json_decode(stripslashes($_REQUEST['actions']), true); // Debug2::debug( '3rd Elementor', $json ); if ( !empty($json['save_builder']['action']) && $json['save_builder']['action'] == 'save_builder' && !empty($json['save_builder']['data']['status']) && $json['save_builder']['data']['status'] == 'publish' ) { return; // Save post, don't disable all in case we will allow fire crawler right away after purged } } do_action('litespeed_disable_all', 'elementor edit mode in HTTP_REFERER'); } } public static function disable_litespeed_esi() { define('LITESPEED_ESI_OFF', true); } } thirdparty/caldera-forms.cls.php 0000644 00000000623 15246276230 0012756 0 ustar 00 <?php /** * The Third Party integration with Caldera Forms. * * @since 3.2.2 */ namespace LiteSpeed\Thirdparty; defined('WPINC') || exit(); class Caldera_Forms { public static function detect() { if (!defined('CFCORE_VER')) { return; } // plugins/caldera-forms/classes/render/nonce.php -> class Caldera_Forms_Render_Nonce do_action('litespeed_nonce', 'caldera_forms_front_*'); } } thirdparty/wp-polls.cls.php 0000644 00000000740 15246276230 0012014 0 ustar 00 <?php /** * The Third Party integration with the WP-Polls plugin. * * @since 1.0.7 */ namespace LiteSpeed\Thirdparty; defined('WPINC') || exit(); // todo: need test class Wp_Polls { public static function detect() { add_filter('wp_polls_display_pollvote', __CLASS__ . '::set_control'); add_filter('wp_polls_display_pollresult', __CLASS__ . '::set_control'); } public static function set_control() { do_action('litespeed_control_set_nocache', 'wp polls'); } } thirdparty/wcml.cls.php 0000644 00000001644 15246276230 0011205 0 ustar 00 <?php /** * The Third Party integration with WCML. * * @since 3.0 */ namespace LiteSpeed\Thirdparty; defined('WPINC') || exit(); class WCML { private static $_currency = ''; public static function detect() { if (!defined('WCML_VERSION')) { return; } add_filter('wcml_client_currency', __CLASS__ . '::apply_client_currency'); add_action('wcml_set_client_currency', __CLASS__ . '::set_client_currency'); } public static function set_client_currency($currency) { self::apply_client_currency($currency); do_action('litespeed_vary_ajax_force'); } public static function apply_client_currency($currency) { if ($currency !== wcml_get_woocommerce_currency_option()) { self::$_currency = $currency; add_filter('litespeed_vary', __CLASS__ . '::apply_vary'); } return $currency; } public static function apply_vary($list) { $list['wcml_currency'] = self::$_currency; return $list; } } thirdparty/woocommerce.tab.tpl.php 0000644 00000000270 15246276230 0013337 0 ustar 00 <?php defined( 'WPINC' ) || exit ; ?> <a class='litespeed-tab nav-tab' href='#woocommerce' data-litespeed-tab='woocommerce'><?php echo __( 'WooCommerce', 'litespeed-cache' ) ; ?></a> thirdparty/amp.cls.php 0000644 00000003600 15246276230 0011012 0 ustar 00 <?php /** * The Third Party integration with AMP plugin. * * @since 2.9.8.6 * @package LiteSpeed_Cache * @subpackage LiteSpeed_Cache/thirdparty * @author LiteSpeed Technologies <info@litespeedtech.com> */ namespace LiteSpeed\Thirdparty; defined('WPINC') || exit(); use LiteSpeed\API; class AMP { /** * @since 4.2 */ private static function _maybe_amp($amp_function) { if (is_admin()) { return; } if (!isset($_GET['amp']) && (!function_exists($amp_function) || !$amp_function())) { return; } do_action('litespeed_debug', '[3rd] ❌ AMP disabled page optm/lazy'); !defined('LITESPEED_NO_PAGEOPTM') && define('LITESPEED_NO_PAGEOPTM', true); !defined('LITESPEED_NO_LAZY') && define('LITESPEED_NO_LAZY', true); !defined('LITESPEED_NO_OPTM') && define('LITESPEED_NO_OPTM', true); // ! defined( 'LITESPEED_GUEST' ) && define( 'LITESPEED_GUEST', false ); } /** * ampforwp_is_amp_endpoint() from Accelerated Mobile Pages * * @since 4.2 */ public static function maybe_acc_mob_pages() { self::_maybe_amp('ampforwp_is_amp_endpoint'); } /** * Google AMP fix * * @since 4.2.0.1 */ public static function maybe_google_amp() { self::_maybe_amp('amp_is_request'); } /** * CSS async will affect AMP result and * Lazyload will inject JS library which AMP not allowed * need to force set false before load * * @since 2.9.8.6 * @access public */ public static function preload() { add_action('wp', __CLASS__ . '::maybe_acc_mob_pages'); add_action('wp', __CLASS__ . '::maybe_google_amp'); // amp_is_request() from AMP // self::maybe_amp( 'amp_is_request' ); // add_filter( 'litespeed_can_optm', '__return_false' ); // do_action( 'litespeed_conf_force', API::O_OPTM_CSS_ASYNC, false ); // do_action( 'litespeed_conf_force', API::O_MEDIA_LAZY, false ); // do_action( 'litespeed_conf_force', API::O_MEDIA_IFRAME_LAZY, false ); } } thirdparty/woocommerce.content.tpl.php 0000644 00000006712 15246276230 0014252 0 ustar 00 <?php namespace LiteSpeed\Thirdparty; defined('WPINC') || exit; use \LiteSpeed\API; use \LiteSpeed\Doc; use \LiteSpeed\Admin_Display; use \LiteSpeed\Lang; use \LiteSpeed\Base; ?> <div data-litespeed-layout='woocommerce'> <h3 class="litespeed-title-short"> <?php echo __('WooCommerce Settings', 'litespeed-cache'); ?> <?php Doc::learn_more('https://docs.litespeedtech.com/lscache/lscwp/cache/#woocommerce-tab'); ?> </h3> <div class="litespeed-callout notice notice-warning inline"> <h4><?php echo __('NOTICE:', 'litespeed-cache'); ?></h4> <p><?php echo __('After verifying that the cache works in general, please test the cart.', 'litespeed-cache'); ?></p> <p><?php echo sprintf(__('To test the cart, visit the <a %s>FAQ</a>.', 'litespeed-cache'), 'href="https://docs.litespeedtech.com/lscache/lscwp/installation/#non-cacheable-pages" target="_blank"'); ?></p> <p><?php echo __('By default, the My Account, Checkout, and Cart pages are automatically excluded from caching. Misconfiguration of page associations in WooCommerce settings may cause some pages to be erroneously excluded.', 'litespeed-cache'); ?></p> </div> <table class="wp-list-table striped litespeed-table"> <tbody> <tr> <th> <?php $id = self::O_UPDATE_INTERVAL; ?> <?php echo __('Product Update Interval', 'litespeed-cache'); ?> </th> <td> <?php $options = array( self::O_PQS_CS => __('Purge product on changes to the quantity or stock status.', 'litespeed-cache') . ' ' . __('Purge categories only when stock status changes.', 'litespeed-cache'), self::O_PS_CS => __('Purge product and categories only when the stock status changes.', 'litespeed-cache'), self::O_PS_CN => __('Purge product only when the stock status changes.', 'litespeed-cache') . ' ' . __('Do not purge categories on changes to the quantity or stock status.', 'litespeed-cache'), self::O_PQS_CQS => __('Always purge both product and categories on changes to the quantity or stock status.', 'litespeed-cache'), ); $conf = (int) apply_filters('litespeed_conf', $id); foreach ($options as $k => $v) : $checked = (int) $k === $conf ? ' checked ' : ''; ?> <?php do_action('litespeed_setting_enroll', $id); ?> <div class='litespeed-radio-row'> <input type='radio' autocomplete='off' name='<?php echo $id; ?>' id='conf_<?php echo $id; ?>_<?php echo $k; ?>' value='<?php echo $k; ?>' <?php echo $checked; ?> /> <label for='conf_<?php echo $id; ?>_<?php echo $k; ?>'><?php echo $v; ?></label> </div> <?php endforeach; ?> <div class="litespeed-desc"> <?php echo __('Determines how changes in product quantity and product stock status affect product pages and their associated category pages.', 'litespeed-cache'); ?> </div> </td> </tr> <tr> <th> <?php $id = self::O_CART_VARY; ?> <?php echo __('Vary for Mini Cart', 'litespeed-cache'); ?> </th> <td> <?php $conf = (int) apply_filters('litespeed_conf', $id); $this->cls('Admin_Display')->build_switch($id); ?> <div class="litespeed-desc"> <?php echo __('Generate a separate vary cache copy for the mini cart when the cart is not empty.', 'litespeed-cache'); ?> <?php echo __('If your theme does not use JS to update the mini cart, you must enable this option to display the correct cart contents.', 'litespeed-cache'); ?> <br /><?php Doc::notice_htaccess(); ?> </div> </td> </tr> </tbody> </table> </div>