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 */ 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('%.*?
(.*?)%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 */ 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 */ 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'), '' . $cdn_url . '');
$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 '';
}
/**
* 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[] = '' . __('Settings', 'litespeed-cache') . '';
$links[] = '' . __('Settings', 'litespeed-cache') . '';
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 '' . wp_kses_post($str) . '
' . __('Dismiss', 'litespeed-cache') . '' . '
'; } 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 '' . Base::conf_const($id) . '');
} 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'), "$val") . '$val";
$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 '' . $v . '';
}
}
}
/**
* 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 '/.htaccess') . '';
}
}
/**
* 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') . ': ' . $min . '.';
}
if ($max && $val > $max) {
$tip[] = __('Maximum value', 'litespeed-cache') . ': ' . $max . '.';
}
echo '' . $range . '';
}
/**
* 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') . ': ' . esc_textarea($v) . '.';
}
}
if ($tip) {
echo '' . implode(', ', $args) . '';
echo ' ' .
__('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'), '/mypath/mypage?aa=bb', 'mypage?aa=');
echo '^');
echo ' ' . sprintf(__('To do an exact match, add %s to the end of the URL.', 'litespeed-cache'), '$');
echo ' ' . __('One per line.', 'litespeed-cache');
echo '';
}
/**
* 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 = '' . substr($code, strlen('unfinished_queue ')) . ''
);
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('' . $args . '');
}
$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'),
'' . Utility::readable_time(substr($code, strlen('try_later ')), 3600, true) . ''
);
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 _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 '';
}
/**
* 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 '';
}
/**
* 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 = <<jquery-core 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 _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 xxxx` to `xxxx`
*
* @since 7.0
*/
public static function translate_qc_apis($html)
{
preg_match_all('/ $html_to_be_replaced) {
$link = ' 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
*/
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
*/
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
*/
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
*/
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 _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 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
*/
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" . '';
}
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 _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', '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 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 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 _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('#]+)/?>|#isU', $html, $matches, PREG_SET_ORDER);
foreach ($matches as $match) {
$debug_info = '';
if (strpos($match[0], '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
*/
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'),
'' . Utility::readable_time($is_upgrading) . ''
) . ' [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('' . $tb . '', '' . $sql . '')));
}
}
/**
* 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
*/
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'),
'' . $v . ''
);
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 _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: `xxxxxxxx2`
*
* @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), '' . $service_tag . '');
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('%s', $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 _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 '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 _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
*/
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 '';
echo 'β οΈ ' .
sprintf(
__('This setting is %1$s for certain qualifying requests due to %2$s!', 'litespeed-cache'),
'' . __('ON', 'litespeed-cache') . '',
Lang::title(Base::O_GUEST_OPTM)
);
self::learn_more('https://docs.litespeedtech.com/lscache/lscwp/general/#guest-optimization');
echo '';
}
/**
* Changes affect crawler list warning
*
* @since 4.3
* @access public
*/
public static function crawler_affected()
{
echo '';
echo 'β οΈ ' . __('This setting will regenerate crawler list and clear the disabled list!', 'litespeed-cache');
echo '';
}
/**
* 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'),
'https://quic.cloud/privacy-policy/'
);
}
/**
* 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 = " $title";
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 '';
echo 'β οΈ ' . __('This setting will edit the .htaccess file.', 'litespeed-cache');
echo ' ' .
__('Learn More', 'litespeed-cache') .
'';
echo '';
}
/**
* Notice for whitelist IPs
*
* @since 3.0
* @access public
*/
public static function notice_ips()
{
echo 'Please add/replace the following codes into the beginning of %1$s:
%2$s', 'litespeed-cache'), $file, '' ); } /** * 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 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' ), '' . __('JS Combine', 'litespeed-cache') . '',
'' . __('JS Defer', 'litespeed-cache') . ''
);
$msg .= sprintf(' %s.', __('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 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
*/
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, "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/', "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 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 ''; // 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( ' %4$s', $link, $cls, $desc, $curr_status ); } else { echo sprintf( ' %2$s', __('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'), '' . __('(no savings)', 'litespeed-cache') . ''); } else { echo __('Orig', 'litespeed-cache') . 'β'; } echo '
'; echo ''; // 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( ' %4$s', $link, $cls, $desc, $curr_status ); } else { echo sprintf( ' %3$s', __('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() . 'β'; } echo '
'; // Delete row btn if ($size_meta) { echo sprintf( '', Utility::build_url(Router::ACTION_IMG_OPTM, Img_Optm::TYPE_RESET_ROW, false, null, array('id' => $post_id)), __('Restore from backup', 'litespeed-cache') ); echo ''; } } /** * 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 ? '' : ''; if ($cfg_js_delay) { $v = str_replace(' src=', ' data-litespeed-src=', $v); } else { $v = str_replace(' src=', ' data-src=', $v); } $v = str_replace('