Initial commit: Atomaste website
This commit is contained in:
@@ -0,0 +1,721 @@
|
||||
<?php
|
||||
/**
|
||||
* The plugin activation class.
|
||||
*
|
||||
* @since 1.1.0
|
||||
* @since 1.5 Moved into /inc
|
||||
* @package LiteSpeed
|
||||
* @subpackage LiteSpeed/inc
|
||||
* @author LiteSpeed Technologies <info@litespeedtech.com>
|
||||
*/
|
||||
|
||||
namespace LiteSpeed;
|
||||
|
||||
defined( 'WPINC' ) || exit();
|
||||
|
||||
/**
|
||||
* Class Activation
|
||||
*
|
||||
* Handles plugin activation, deactivation, and related file management.
|
||||
*
|
||||
* @since 1.1.0
|
||||
*/
|
||||
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';
|
||||
|
||||
/**
|
||||
* Data file path for configuration.
|
||||
*
|
||||
* @since 4.1
|
||||
* @var string
|
||||
*/
|
||||
private static $data_file;
|
||||
|
||||
/**
|
||||
* Construct
|
||||
*
|
||||
* Initializes the data file path.
|
||||
*
|
||||
* @since 4.1
|
||||
*/
|
||||
public function __construct() {
|
||||
self::$data_file = LSCWP_CONTENT_DIR . '/' . self::CONF_FILE;
|
||||
}
|
||||
|
||||
/**
|
||||
* The activation hook callback.
|
||||
*
|
||||
* Handles plugin activation tasks, including file creation and multisite setup.
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @access public
|
||||
*/
|
||||
public static function register_activation() {
|
||||
$count = 0;
|
||||
! defined( 'LSCWP_LOG_TAG' ) && define( 'LSCWP_LOG_TAG', 'Activate_' . get_current_blog_id() );
|
||||
|
||||
/* Network file handler */
|
||||
if ( is_multisite() ) {
|
||||
$count = self::get_network_count();
|
||||
if ( false !== $count ) {
|
||||
$count = (int) $count + 1;
|
||||
set_site_transient( self::NETWORK_TRANSIENT_COUNT, $count, DAY_IN_SECONDS );
|
||||
}
|
||||
|
||||
if ( ! is_network_admin() ) {
|
||||
if ( 1 === $count ) {
|
||||
// Only itself is activated, set .htaccess with only CacheLookUp
|
||||
try {
|
||||
Htaccess::cls()->insert_ls_wrapper();
|
||||
} catch ( \Exception $ex ) {
|
||||
Admin_Display::error( $ex->getMessage() );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
self::cls()->update_files();
|
||||
|
||||
if ( defined( 'LSCWP_REF' ) && 'whm' === LSCWP_REF ) {
|
||||
GUI::update_option( GUI::WHM_MSG, GUI::WHM_MSG_VAL );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Uninstall plugin
|
||||
*
|
||||
* Removes all LiteSpeed Cache settings and data.
|
||||
*
|
||||
* @since 1.1.0
|
||||
* @since 7.3 Updated to remove all settings.
|
||||
* @access public
|
||||
*/
|
||||
public static function uninstall_litespeed_cache() {
|
||||
Task::destroy();
|
||||
|
||||
if ( is_multisite() ) {
|
||||
// Save main site id
|
||||
$current_blog = get_current_blog_id();
|
||||
|
||||
// get all sites
|
||||
$sub_sites = get_sites();
|
||||
|
||||
// clear foreach site
|
||||
foreach ( $sub_sites as $sub_site ) {
|
||||
$sub_blog_id = (int) $sub_site->blog_id;
|
||||
if ( $sub_blog_id !== $current_blog ) {
|
||||
// Switch to blog
|
||||
switch_to_blog( $sub_blog_id );
|
||||
|
||||
// Delete site options
|
||||
self::delete_settings();
|
||||
|
||||
// Delete site tables
|
||||
Data::cls()->tables_del();
|
||||
}
|
||||
}
|
||||
|
||||
// Return to main site
|
||||
switch_to_blog( $current_blog );
|
||||
}
|
||||
|
||||
// Delete current blog/site
|
||||
// Delete options
|
||||
self::delete_settings();
|
||||
|
||||
// Delete site tables
|
||||
Data::cls()->tables_del();
|
||||
|
||||
if ( file_exists( LITESPEED_STATIC_DIR ) ) {
|
||||
File::rrmdir( LITESPEED_STATIC_DIR );
|
||||
}
|
||||
|
||||
Cloud::version_check( 'uninstall' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all litespeed settings.
|
||||
*
|
||||
* Deletes all LiteSpeed Cache options from the database.
|
||||
*
|
||||
* @since 7.3
|
||||
* @access private
|
||||
*/
|
||||
private static function delete_settings() {
|
||||
global $wpdb;
|
||||
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery
|
||||
$wpdb->query($wpdb->prepare("DELETE FROM `$wpdb->options` WHERE option_name LIKE %s", 'litespeed.%'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the blog ids for the network. Accepts function arguments.
|
||||
*
|
||||
* @since 1.0.12
|
||||
* @access public
|
||||
* @param array $args Arguments for get_sites().
|
||||
* @return array The array of blog ids.
|
||||
*/
|
||||
public static function get_network_ids( $args = array() ) {
|
||||
$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
|
||||
* @return int|false Number of active LSCWP or false if none.
|
||||
*/
|
||||
private static function get_network_count() {
|
||||
$count = get_site_transient( self::NETWORK_TRANSIENT_COUNT );
|
||||
if ( false !== $count ) {
|
||||
return (int) $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 ( false === $count ) {
|
||||
return false;
|
||||
}
|
||||
if ( 1 !== $count ) {
|
||||
// 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 ) {
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.PHP.DevelopmentFunctions.error_log_error_log
|
||||
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();
|
||||
|
||||
/* 6) delete option lscwp_whm_install */
|
||||
|
||||
// delete in case it's not deleted prior to deactivation.
|
||||
GUI::dismiss_whm();
|
||||
}
|
||||
|
||||
/**
|
||||
* Manage related files based on plugin latest conf
|
||||
*
|
||||
* 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( wp_kses_post( $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( wp_kses_post( $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
|
||||
*
|
||||
* Removes the .litespeed_conf.dat file.
|
||||
*
|
||||
* @since 4.1
|
||||
* @access private
|
||||
*/
|
||||
private static function del_conf_data_file() {
|
||||
global $wp_filesystem;
|
||||
|
||||
if ( ! $wp_filesystem ) {
|
||||
require_once ABSPATH . 'wp-admin/includes/file.php';
|
||||
WP_Filesystem();
|
||||
}
|
||||
|
||||
if ( $wp_filesystem->exists( self::$data_file ) ) {
|
||||
$wp_filesystem->delete( self::$data_file );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update data conf file for guest mode & object cache
|
||||
*
|
||||
* Updates the .litespeed_conf.dat file with relevant settings.
|
||||
*
|
||||
* @since 4.1
|
||||
* @access private
|
||||
* @param array $options Plugin options.
|
||||
*/
|
||||
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 = wp_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
|
||||
* @param bool $enable Whether to enable WP_CACHE.
|
||||
* @throws \Exception If wp-config.php cannot be modified.
|
||||
* @return bool True if updated, false if no change needed.
|
||||
*/
|
||||
public 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: ' . wp_kses_post( $conf_file ) );
|
||||
}
|
||||
|
||||
// Remove the line `define('WP_CACHE', true/false);` first
|
||||
if ( defined( 'WP_CACHE' ) ) {
|
||||
$content = preg_replace( '/define\(\s*([\'"])WP_CACHE\1\s*,\s*\w+\s*\)\s*;/sU', '', $content );
|
||||
}
|
||||
|
||||
// Insert const
|
||||
if ( $enable ) {
|
||||
$content = preg_replace( '/^<\?php/', "<?php\ndefine( 'WP_CACHE', true );", $content );
|
||||
}
|
||||
|
||||
$res = File::save( $conf_file, $content, false, false, false );
|
||||
|
||||
if ( true !== $res ) {
|
||||
throw new \Exception( 'wp-config.php operation failed when changing `WP_CACHE` const: ' . wp_kses_post( $res ) );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle auto update
|
||||
*
|
||||
* Enables auto-updates for the plugin if configured.
|
||||
*
|
||||
* @since 2.7.2
|
||||
* @access public
|
||||
*/
|
||||
public function auto_update() {
|
||||
if ( ! $this->conf( Base::O_AUTO_UPGRADE ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
add_filter( 'auto_update_plugin', array( $this, 'auto_update_hook' ), 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Auto upgrade hook
|
||||
*
|
||||
* Determines whether to auto-update the plugin.
|
||||
*
|
||||
* @since 3.0
|
||||
* @access public
|
||||
* @param bool $update Whether to update.
|
||||
* @param object $item Plugin data.
|
||||
* @return bool Whether to update.
|
||||
*/
|
||||
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
|
||||
*
|
||||
* Upgrades the LiteSpeed Cache plugin.
|
||||
*
|
||||
* @since 2.9
|
||||
* @access public
|
||||
*/
|
||||
public function upgrade() {
|
||||
$plugin = Core::PLUGIN_FILE;
|
||||
|
||||
/**
|
||||
* Load upgrade cls
|
||||
*
|
||||
* @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
|
||||
* @access public
|
||||
* @param string $plugin Plugin slug.
|
||||
* @return bool True if active, false otherwise.
|
||||
*/
|
||||
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
|
||||
* @access public
|
||||
* @param string $plugin Plugin slug.
|
||||
* @return bool True if installed, false otherwise.
|
||||
*/
|
||||
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
|
||||
* @access public
|
||||
* @param string $slug Plugin slug.
|
||||
* @return object|false Plugin info or false on failure.
|
||||
*/
|
||||
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
|
||||
*
|
||||
* Installs and activates a third-party plugin.
|
||||
*
|
||||
* @since 1.0
|
||||
* @access public
|
||||
*/
|
||||
public function dash_notifier_install_3rd() {
|
||||
! defined( 'SILENCE_INSTALL' ) && define( 'SILENCE_INSTALL', true );
|
||||
|
||||
// phpcs:ignore
|
||||
$slug = ! empty( $_GET['plugin'] ) ? wp_unslash( sanitize_text_field( $_GET['plugin'] ) ) : false;
|
||||
|
||||
// Check if plugin is installed already
|
||||
if ( ! $slug || $this->dash_notifier_is_plugin_active( $slug ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load upgrade cls
|
||||
*
|
||||
* @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
|
||||
*
|
||||
* Processes various activation-related actions.
|
||||
*
|
||||
* @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();
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,402 @@
|
||||
<?php
|
||||
/**
|
||||
* The admin settings handler of the plugin.
|
||||
*
|
||||
* Handles saving and validating settings from the admin UI and network admin.
|
||||
*
|
||||
* @since 1.1.0
|
||||
* @package LiteSpeed
|
||||
*/
|
||||
|
||||
namespace LiteSpeed;
|
||||
|
||||
defined( 'WPINC' ) || exit();
|
||||
|
||||
/**
|
||||
* Class Admin_Settings
|
||||
*
|
||||
* Saves, sanitizes, and validates LiteSpeed Cache settings.
|
||||
*/
|
||||
class Admin_Settings extends Base {
|
||||
const LOG_TAG = '[Settings]';
|
||||
|
||||
const ENROLL = '_settings-enroll';
|
||||
|
||||
/**
|
||||
* Save settings (single site).
|
||||
*
|
||||
* Accepts data from $_POST or WP-CLI.
|
||||
* Importers may call the Conf class directly.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param array<string,mixed> $raw_data Raw data from request/CLI.
|
||||
* @return void
|
||||
*/
|
||||
public function save( $raw_data ) {
|
||||
self::debug( 'saving' );
|
||||
|
||||
if ( empty( $raw_data[ self::ENROLL ] ) ) {
|
||||
wp_die( esc_html__( 'No fields', 'litespeed-cache' ) );
|
||||
}
|
||||
|
||||
$raw_data = Admin::cleanup_text( $raw_data );
|
||||
|
||||
// Convert data to config format.
|
||||
$the_matrix = [];
|
||||
foreach ( array_unique( $raw_data[ self::ENROLL ] ) as $id ) {
|
||||
$child = false;
|
||||
|
||||
// Drop array format.
|
||||
if ( false !== strpos( $id, '[' ) ) {
|
||||
if ( 0 === strpos( $id, self::O_CDN_MAPPING ) || 0 === strpos( $id, self::O_CRAWLER_COOKIES ) ) {
|
||||
// CDN child | Cookie Crawler settings.
|
||||
$child = substr( $id, strpos( $id, '[' ) + 1, strpos( $id, ']' ) - strpos( $id, '[' ) - 1 );
|
||||
// Drop ending []; Compatible with xx[0] way from CLI.
|
||||
$id = substr( $id, 0, strpos( $id, '[' ) );
|
||||
} else {
|
||||
// Drop ending [].
|
||||
$id = substr( $id, 0, strpos( $id, '[' ) );
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! array_key_exists( $id, self::$_default_options ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Validate $child.
|
||||
if ( self::O_CDN_MAPPING === $id ) {
|
||||
if ( ! in_array( $child, [ self::CDN_MAPPING_URL, self::CDN_MAPPING_INC_IMG, self::CDN_MAPPING_INC_CSS, self::CDN_MAPPING_INC_JS, self::CDN_MAPPING_FILETYPE ], true ) ) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ( self::O_CRAWLER_COOKIES === $id ) {
|
||||
if ( ! in_array( $child, [ self::CRWL_COOKIE_NAME, self::CRWL_COOKIE_VALS ], true ) ) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Pull value from request.
|
||||
if ( $child ) {
|
||||
// []=xxx or [0]=xxx
|
||||
$data = ! empty( $raw_data[ $id ][ $child ] ) ? $raw_data[ $id ][ $child ] : $this->type_casting(false, $id);
|
||||
} else {
|
||||
$data = ! empty( $raw_data[ $id ] ) ? $raw_data[ $id ] : $this->type_casting(false, $id);
|
||||
}
|
||||
|
||||
// Sanitize/normalize complex fields.
|
||||
if ( self::O_CDN_MAPPING === $id || self::O_CRAWLER_COOKIES === $id ) {
|
||||
// Use existing queued data if available (only when $child != false).
|
||||
$data2 = array_key_exists( $id, $the_matrix )
|
||||
? $the_matrix[ $id ]
|
||||
: ( defined( 'WP_CLI' ) && WP_CLI ? $this->conf( $id ) : [] );
|
||||
}
|
||||
|
||||
switch ( $id ) {
|
||||
// Don't allow Editor/admin to be used in crawler role simulator.
|
||||
case self::O_CRAWLER_ROLES:
|
||||
$data = Utility::sanitize_lines( $data );
|
||||
if ( $data ) {
|
||||
foreach ( $data as $k => $v ) {
|
||||
if ( user_can( $v, 'edit_posts' ) ) {
|
||||
/* translators: %s: user id in <code> tags */
|
||||
$msg = sprintf(
|
||||
esc_html__( 'The user with id %s has editor access, which is not allowed for the role simulator.', 'litespeed-cache' ),
|
||||
'<code>' . esc_html( $v ) . '</code>'
|
||||
);
|
||||
Admin_Display::error( $msg );
|
||||
unset( $data[ $k ] );
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case self::O_CDN_MAPPING:
|
||||
/**
|
||||
* CDN setting
|
||||
*
|
||||
* Raw data format:
|
||||
* cdn-mapping[url][] = 'xxx'
|
||||
* cdn-mapping[url][2] = 'xxx2'
|
||||
* cdn-mapping[inc_js][] = 1
|
||||
*
|
||||
* Final format:
|
||||
* cdn-mapping[0][url] = 'xxx'
|
||||
* cdn-mapping[2][url] = 'xxx2'
|
||||
*/
|
||||
if ( $data ) {
|
||||
foreach ( $data as $k => $v ) {
|
||||
if ( self::CDN_MAPPING_FILETYPE === $child ) {
|
||||
$v = Utility::sanitize_lines( $v );
|
||||
}
|
||||
|
||||
if ( self::CDN_MAPPING_URL === $child ) {
|
||||
// If not a valid URL, turn off CDN.
|
||||
if ( 0 !== strpos( $v, 'https://' ) ) {
|
||||
self::debug( '❌ CDN mapping set to OFF due to invalid URL' );
|
||||
$the_matrix[ self::O_CDN ] = false;
|
||||
}
|
||||
$v = trailingslashit( $v );
|
||||
}
|
||||
|
||||
if ( in_array( $child, [ self::CDN_MAPPING_INC_IMG, self::CDN_MAPPING_INC_CSS, self::CDN_MAPPING_INC_JS ], true ) ) {
|
||||
// Because these can't be auto detected in `config->update()`, need to format here.
|
||||
$v = 'false' === $v ? 0 : (bool) $v;
|
||||
}
|
||||
|
||||
if ( empty( $data2[ $k ] ) ) {
|
||||
$data2[ $k ] = [];
|
||||
}
|
||||
|
||||
$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
|
||||
*
|
||||
* Final format:
|
||||
* crawler-cookie[0][name] = 'xxx'
|
||||
* crawler-cookie[0][vals] = 'xxx'
|
||||
* crawler-cookie[2][name] = 'xxx2'
|
||||
*
|
||||
* Empty line for `vals` uses literal `_null`.
|
||||
*/
|
||||
if ( $data ) {
|
||||
foreach ( $data as $k => $v ) {
|
||||
if ( self::CRWL_COOKIE_VALS === $child ) {
|
||||
$v = Utility::sanitize_lines( $v );
|
||||
}
|
||||
|
||||
if ( empty( $data2[ $k ] ) ) {
|
||||
$data2[ $k ] = [];
|
||||
}
|
||||
|
||||
$data2[ $k ][ $child ] = $v;
|
||||
}
|
||||
}
|
||||
|
||||
$data = $data2;
|
||||
break;
|
||||
|
||||
// Cache exclude category.
|
||||
case self::O_CACHE_EXC_CAT:
|
||||
$data2 = [];
|
||||
$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;
|
||||
|
||||
// Cache exclude tag.
|
||||
case self::O_CACHE_EXC_TAG:
|
||||
$data2 = [];
|
||||
$data = Utility::sanitize_lines( $data );
|
||||
foreach ( $data as $v ) {
|
||||
$term = get_term_by( 'name', $v, 'post_tag' );
|
||||
if ( ! $term ) {
|
||||
// Could surface an admin error here if desired.
|
||||
continue;
|
||||
}
|
||||
$data2[] = $term->term_id;
|
||||
}
|
||||
$data = $data2;
|
||||
break;
|
||||
|
||||
case self::O_IMG_OPTM_SIZES_SKIPPED: // Skip image sizes
|
||||
$image_sizes = Utility::prepare_image_sizes_array();
|
||||
$saved_sizes = isset( $raw_data[$id] ) ? $raw_data[$id] : [];
|
||||
$data = array_diff( $image_sizes, $saved_sizes );
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
$the_matrix[ $id ] = $data;
|
||||
}
|
||||
|
||||
// Special handler for CDN/Crawler 2d list to drop empty rows.
|
||||
foreach ( $the_matrix as $id => $data ) {
|
||||
/**
|
||||
* Format:
|
||||
* 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 ( self::O_CDN_MAPPING === $id || self::O_CRAWLER_COOKIES === $id ) {
|
||||
// Drop row if all children are empty.
|
||||
foreach ( $data as $k => $v ) {
|
||||
foreach ( $v as $v2 ) {
|
||||
if ( $v2 ) {
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
// All empty.
|
||||
unset( $the_matrix[ $id ][ $k ] );
|
||||
}
|
||||
}
|
||||
|
||||
// Don't allow repeated cookie names.
|
||||
if ( self::O_CRAWLER_COOKIES === $id ) {
|
||||
$existed = [];
|
||||
foreach ( $the_matrix[ $id ] as $k => $v ) {
|
||||
if ( empty( $v[ self::CRWL_COOKIE_NAME ] ) || in_array( $v[ self::CRWL_COOKIE_NAME ], $existed, true ) ) {
|
||||
// Filter repeated or empty name.
|
||||
unset( $the_matrix[ $id ][ $k ] );
|
||||
continue;
|
||||
}
|
||||
|
||||
$existed[] = $v[ self::CRWL_COOKIE_NAME ];
|
||||
}
|
||||
}
|
||||
|
||||
// tmp fix the 3rd part woo update hook issue when enabling vary cookie.
|
||||
if ( 'wc_cart_vary' === $id ) {
|
||||
if ( $data ) {
|
||||
add_filter(
|
||||
'litespeed_vary_cookies',
|
||||
function ( $arr ) {
|
||||
$arr[] = 'woocommerce_cart_hash';
|
||||
return array_unique( $arr );
|
||||
}
|
||||
);
|
||||
} else {
|
||||
add_filter(
|
||||
'litespeed_vary_cookies',
|
||||
function ( $arr ) {
|
||||
$key = array_search( 'woocommerce_cart_hash', $arr, true );
|
||||
if ( false !== $key ) {
|
||||
unset( $arr[ $key ] );
|
||||
}
|
||||
return array_unique( $arr );
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
*
|
||||
* @param array<string,mixed> $raw_data Raw data from request/CLI.
|
||||
* @return void
|
||||
*/
|
||||
public function network_save( $raw_data ) {
|
||||
self::debug( 'network saving' );
|
||||
|
||||
if ( empty( $raw_data[ self::ENROLL ] ) ) {
|
||||
wp_die( esc_html__( 'No fields', 'litespeed-cache' ) );
|
||||
}
|
||||
|
||||
$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 when saving widgets fails validation.
|
||||
*
|
||||
* @since 1.1.3
|
||||
*
|
||||
* @param string $location The redirect location.
|
||||
* @return string Updated location string.
|
||||
*/
|
||||
public static function widget_save_err( $location ) {
|
||||
return str_replace( '?message=0', '?error=0', $location );
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the LiteSpeed Cache settings on widget save.
|
||||
*
|
||||
* @since 1.1.3
|
||||
*
|
||||
* @param array $instance The new settings.
|
||||
* @param array $new_instance The raw submitted settings.
|
||||
* @param array $old_instance The original settings.
|
||||
* @param \WP_Widget $widget The widget instance.
|
||||
* @return array|false 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 ], $new_instance[ ESI::WIDGET_O_TTL ] ) ) {
|
||||
return $instance;
|
||||
}
|
||||
|
||||
$esi = (int) $new_instance[ ESI::WIDGET_O_ESIENABLE ] % 3;
|
||||
$ttl = (int) $new_instance[ ESI::WIDGET_O_TTL ];
|
||||
|
||||
if ( 0 !== $ttl && $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 ] = [];
|
||||
}
|
||||
$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;
|
||||
|
||||
// Avoid unsanitized superglobal usage.
|
||||
$referrer = isset( $_SERVER['HTTP_REFERER'] ) ? esc_url_raw( wp_unslash( $_SERVER['HTTP_REFERER'] ) ) : '';
|
||||
|
||||
// Only purge when not in the Customizer.
|
||||
if ( false === strpos( $referrer, '/wp-admin/customize.php' ) ) {
|
||||
if ( ! $current || $esi !== (int) $current[ ESI::WIDGET_O_ESIENABLE ] ) {
|
||||
Purge::purge_all( 'Widget ESI_enable changed' );
|
||||
} elseif ( 0 !== $ttl && $ttl !== (int) $current[ ESI::WIDGET_O_TTL ] ) {
|
||||
Purge::add( Tag::TYPE_WIDGET . $widget->id );
|
||||
}
|
||||
|
||||
Purge::purge_all( 'Widget saved' );
|
||||
}
|
||||
|
||||
return $instance;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
<?php
|
||||
/**
|
||||
* The admin-panel specific functionality of the plugin.
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @package LiteSpeed_Cache
|
||||
*/
|
||||
|
||||
namespace LiteSpeed;
|
||||
|
||||
defined( 'WPINC' ) || exit();
|
||||
|
||||
/**
|
||||
* Class Admin
|
||||
*
|
||||
* Wires admin-side hooks, actions, and safe redirects.
|
||||
*/
|
||||
class Admin extends Root {
|
||||
|
||||
const LOG_TAG = '👮';
|
||||
|
||||
const PAGE_EDIT_HTACCESS = 'litespeed-edit-htaccess';
|
||||
|
||||
/**
|
||||
* Initialize the class and set its properties.
|
||||
* Runs in hook `after_setup_theme` when is_admin().
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function __construct() {
|
||||
// Define LSCWP_MU_PLUGIN if in mu-plugins.
|
||||
if ( defined( 'WPMU_PLUGIN_DIR' ) && dirname( LSCWP_DIR ) === WPMU_PLUGIN_DIR && ! defined( 'LSCWP_MU_PLUGIN' ) ) {
|
||||
define( 'LSCWP_MU_PLUGIN', true );
|
||||
}
|
||||
|
||||
self::debug( 'No cache due to Admin page' );
|
||||
|
||||
if ( ! defined( 'DONOTCACHEPAGE' ) ) {
|
||||
define( 'DONOTCACHEPAGE', true );
|
||||
}
|
||||
|
||||
// Additional LiteSpeed assets on admin display (also registers menus).
|
||||
$this->cls( 'Admin_Display' );
|
||||
|
||||
// Initialize admin actions.
|
||||
add_action( 'admin_init', array( $this, 'admin_init' ) );
|
||||
|
||||
// Add link to plugin list page.
|
||||
add_filter(
|
||||
'plugin_action_links_' . LSCWP_BASENAME,
|
||||
array( $this->cls( 'Admin_Display' ), 'add_plugin_links' )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback that initializes the admin options for LiteSpeed Cache.
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @return void
|
||||
*/
|
||||
public function admin_init() {
|
||||
// Hook attachment upload auto optimization.
|
||||
if ( $this->conf( Base::O_IMG_OPTM_AUTO ) ) {
|
||||
add_filter( 'wp_update_attachment_metadata', array( $this, 'wp_update_attachment_metadata' ), 9999, 2 );
|
||||
}
|
||||
|
||||
$this->_proceed_admin_action();
|
||||
|
||||
// Terminate if user doesn't have access to settings.
|
||||
$capability = is_network_admin() ? 'manage_network_options' : 'manage_options';
|
||||
if ( ! current_user_can( $capability ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Add privacy policy (since 2.2.6).
|
||||
if ( function_exists( 'wp_add_privacy_policy_content' ) ) {
|
||||
wp_add_privacy_policy_content( Core::NAME, Doc::privacy_policy() );
|
||||
}
|
||||
|
||||
$this->cls( 'Media' )->after_admin_init();
|
||||
|
||||
do_action( 'litespeed_after_admin_init' );
|
||||
|
||||
if ( $this->cls( 'Router' )->esi_enabled() ) {
|
||||
add_action( 'in_widget_form', array( $this->cls( 'Admin_Display' ), 'show_widget_edit' ), 100, 3 );
|
||||
add_filter( 'widget_update_callback', __NAMESPACE__ . '\Admin_Settings::validate_widget_save', 10, 4 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle attachment metadata update.
|
||||
*
|
||||
* @since 4.0
|
||||
*
|
||||
* @param array $data Attachment meta.
|
||||
* @param int $post_id Attachment ID.
|
||||
* @return array Filtered meta.
|
||||
*/
|
||||
public function wp_update_attachment_metadata( $data, $post_id ) {
|
||||
$this->cls( 'Img_Optm' )->wp_update_attachment_metadata( $data, $post_id );
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run LiteSpeed admin actions routed via Router.
|
||||
*
|
||||
* @since 1.1.0
|
||||
* @return void
|
||||
*/
|
||||
private function _proceed_admin_action() {
|
||||
$action = Router::get_action();
|
||||
|
||||
switch ( $action ) {
|
||||
case Router::ACTION_SAVE_SETTINGS:
|
||||
$this->cls( 'Admin_Settings' )->save( wp_unslash( $_POST ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
|
||||
break;
|
||||
|
||||
case Router::ACTION_SAVE_SETTINGS_NETWORK:
|
||||
$this->cls( 'Admin_Settings' )->network_save( wp_unslash( $_POST ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up the input (array or scalar) of any extra slashes/spaces.
|
||||
*
|
||||
* @since 1.0.4
|
||||
*
|
||||
* @param mixed $input The input value to clean.
|
||||
* @return mixed Cleaned value.
|
||||
*/
|
||||
public static function cleanup_text( $input ) {
|
||||
if ( is_array( $input ) ) {
|
||||
return array_map( __CLASS__ . '::cleanup_text', $input );
|
||||
}
|
||||
|
||||
return stripslashes(trim($input));
|
||||
}
|
||||
|
||||
/**
|
||||
* After a LSCWP_CTRL action, redirect back to same page
|
||||
* without nonce and action in the query string.
|
||||
*
|
||||
* If the redirect URL cannot be determined, redirects to the homepage.
|
||||
*
|
||||
* @since 1.0.12
|
||||
*
|
||||
* @param string|false $url Optional destination URL.
|
||||
* @return void
|
||||
*/
|
||||
public static function redirect( $url = false ) {
|
||||
global $pagenow;
|
||||
|
||||
// If originated, go back to referrer or home.
|
||||
if ( ! empty( $_GET['_litespeed_ori'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$ref = wp_get_referer();
|
||||
wp_safe_redirect( $ref ? $ref : get_home_url() );
|
||||
exit;
|
||||
}
|
||||
|
||||
if ( ! $url ) {
|
||||
$clean = [];
|
||||
|
||||
// Sanitize current query args while removing our internals.
|
||||
if ( ! empty( $_GET ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
foreach ( $_GET as $k => $v ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
if ( in_array( $k, array( Router::ACTION, Router::NONCE, Router::TYPE, 'litespeed_i' ), true ) ) {
|
||||
continue;
|
||||
}
|
||||
// Normalize to string for URL building.
|
||||
$clean[ $k ] = is_array( $v ) ? array_map( 'sanitize_text_field', wp_unslash( $v ) ) : sanitize_text_field( wp_unslash( $v ) );
|
||||
}
|
||||
}
|
||||
|
||||
$qs = '';
|
||||
if ( ! empty( $clean ) ) {
|
||||
$qs = '?' . http_build_query( $clean );
|
||||
}
|
||||
|
||||
$url = is_network_admin() ? network_admin_url( $pagenow . $qs ) : admin_url( $pagenow . $qs );
|
||||
}
|
||||
|
||||
wp_safe_redirect( $url );
|
||||
exit;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,338 @@
|
||||
<?php
|
||||
/**
|
||||
* The plugin API class.
|
||||
*
|
||||
* @since 1.1.3
|
||||
* @package LiteSpeed
|
||||
*/
|
||||
|
||||
namespace LiteSpeed;
|
||||
|
||||
defined( 'WPINC' ) || exit();
|
||||
|
||||
/**
|
||||
* Class API
|
||||
*
|
||||
* Provides API hooks and methods for LiteSpeed Cache integration.
|
||||
*
|
||||
* @since 1.1.3
|
||||
*/
|
||||
class API extends Base {
|
||||
|
||||
const VERSION = Core::VER;
|
||||
|
||||
const TYPE_FEED = Tag::TYPE_FEED;
|
||||
const TYPE_FRONTPAGE = Tag::TYPE_FRONTPAGE;
|
||||
const TYPE_HOME = Tag::TYPE_HOME;
|
||||
const TYPE_PAGES = Tag::TYPE_PAGES;
|
||||
const TYPE_PAGES_WITH_RECENT_POSTS = Tag::TYPE_PAGES_WITH_RECENT_POSTS;
|
||||
const TYPE_HTTP = Tag::TYPE_HTTP;
|
||||
const TYPE_ARCHIVE_POSTTYPE = Tag::TYPE_ARCHIVE_POSTTYPE;
|
||||
const TYPE_ARCHIVE_TERM = Tag::TYPE_ARCHIVE_TERM;
|
||||
const TYPE_AUTHOR = Tag::TYPE_AUTHOR;
|
||||
const TYPE_ARCHIVE_DATE = Tag::TYPE_ARCHIVE_DATE;
|
||||
const TYPE_BLOG = Tag::TYPE_BLOG;
|
||||
const TYPE_LOGIN = Tag::TYPE_LOGIN;
|
||||
const TYPE_URL = Tag::TYPE_URL;
|
||||
|
||||
const TYPE_ESI = Tag::TYPE_ESI;
|
||||
|
||||
const PARAM_NAME = ESI::PARAM_NAME;
|
||||
const WIDGET_O_ESIENABLE = ESI::WIDGET_O_ESIENABLE;
|
||||
const WIDGET_O_TTL = ESI::WIDGET_O_TTL;
|
||||
|
||||
/**
|
||||
* Instance
|
||||
*
|
||||
* Initializes the API class.
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
public function __construct() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Define hooks to be used in other plugins.
|
||||
*
|
||||
* The benefit to use hooks other than functions is no need to detach if LSCWP enabled and function existed or not anymore
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
public function init() {
|
||||
/**
|
||||
* Init
|
||||
*/
|
||||
// Action `litespeed_init`
|
||||
|
||||
/**
|
||||
* Conf
|
||||
*/
|
||||
add_filter( 'litespeed_conf', array( $this, 'conf' ) );
|
||||
// Action `litespeed_conf_append`
|
||||
add_action( 'litespeed_conf_multi_switch', __NAMESPACE__ . '\Base::set_multi_switch', 10, 2 );
|
||||
// Action `litespeed_conf_force`
|
||||
add_action( 'litespeed_save_conf', array( $this, 'save_conf' ) );
|
||||
|
||||
/**
|
||||
* Cache Control Hooks
|
||||
*/
|
||||
// Action `litespeed_control_finalize`
|
||||
add_action( 'litespeed_control_set_private', __NAMESPACE__ . '\Control::set_private' );
|
||||
add_action( 'litespeed_control_set_nocache', __NAMESPACE__ . '\Control::set_nocache' );
|
||||
add_action( 'litespeed_control_set_cacheable', array( $this, 'set_cacheable' ) );
|
||||
add_action( 'litespeed_control_force_cacheable', __NAMESPACE__ . '\Control::force_cacheable' );
|
||||
add_action( 'litespeed_control_force_public', __NAMESPACE__ . '\Control::set_public_forced' );
|
||||
add_filter( 'litespeed_control_cacheable', __NAMESPACE__ . '\Control::is_cacheable', 3 );
|
||||
add_action( 'litespeed_control_set_ttl', __NAMESPACE__ . '\Control::set_custom_ttl', 10, 2 );
|
||||
add_filter( 'litespeed_control_ttl', array( $this, 'get_ttl' ), 3 );
|
||||
|
||||
/**
|
||||
* Tag Hooks
|
||||
*/
|
||||
// Action `litespeed_tag_finalize`
|
||||
add_action( 'litespeed_tag', __NAMESPACE__ . '\Tag::add' );
|
||||
add_action( 'litespeed_tag_post', __NAMESPACE__ . '\Tag::add_post' );
|
||||
add_action( 'litespeed_tag_widget', __NAMESPACE__ . '\Tag::add_widget' );
|
||||
add_action( 'litespeed_tag_private', __NAMESPACE__ . '\Tag::add_private' );
|
||||
add_action( 'litespeed_tag_private_esi', __NAMESPACE__ . '\Tag::add_private_esi' );
|
||||
|
||||
add_action( 'litespeed_tag_add', __NAMESPACE__ . '\Tag::add' );
|
||||
add_action( 'litespeed_tag_add_post', __NAMESPACE__ . '\Tag::add_post' );
|
||||
add_action( 'litespeed_tag_add_widget', __NAMESPACE__ . '\Tag::add_widget' );
|
||||
add_action( 'litespeed_tag_add_private', __NAMESPACE__ . '\Tag::add_private' );
|
||||
add_action( 'litespeed_tag_add_private_esi', __NAMESPACE__ . '\Tag::add_private_esi' );
|
||||
|
||||
/**
|
||||
* Purge Hooks
|
||||
*/
|
||||
// Action `litespeed_purge_finalize`
|
||||
add_action( 'litespeed_purge', __NAMESPACE__ . '\Purge::add' );
|
||||
add_action( 'litespeed_purge_all', __NAMESPACE__ . '\Purge::purge_all' );
|
||||
add_action( 'litespeed_purge_post', array( $this, 'purge_post' ) );
|
||||
add_action( 'litespeed_purge_posttype', __NAMESPACE__ . '\Purge::purge_posttype' );
|
||||
add_action( 'litespeed_purge_url', array( $this, 'purge_url' ) );
|
||||
add_action( 'litespeed_purge_widget', __NAMESPACE__ . '\Purge::purge_widget' );
|
||||
add_action( 'litespeed_purge_esi', __NAMESPACE__ . '\Purge::purge_esi' );
|
||||
add_action( 'litespeed_purge_private', __NAMESPACE__ . '\Purge::add_private' );
|
||||
add_action( 'litespeed_purge_private_esi', __NAMESPACE__ . '\Purge::add_private_esi' );
|
||||
add_action( 'litespeed_purge_private_all', __NAMESPACE__ . '\Purge::add_private_all' );
|
||||
// Action `litespeed_api_purge_post`
|
||||
// Action `litespeed_purged_all`
|
||||
add_action( 'litespeed_purge_all_object', __NAMESPACE__ . '\Purge::purge_all_object' );
|
||||
add_action( 'litespeed_purge_ucss', __NAMESPACE__ . '\Purge::purge_ucss' );
|
||||
|
||||
/**
|
||||
* ESI
|
||||
*/
|
||||
// Action `litespeed_nonce`
|
||||
add_filter( 'litespeed_esi_status', array( $this, 'esi_enabled' ) );
|
||||
add_filter( 'litespeed_esi_url', array( $this, 'sub_esi_block' ), 10, 8 ); // Generate ESI block url
|
||||
// Filter `litespeed_widget_default_options` // Hook widget default settings value. Currently used in Woo 3rd
|
||||
// Filter `litespeed_esi_params`
|
||||
// Action `litespeed_tpl_normal`
|
||||
// Action `litespeed_esi_load-$block` // @usage add_action( 'litespeed_esi_load-' . $block, $hook )
|
||||
add_action( 'litespeed_esi_combine', __NAMESPACE__ . '\ESI::combine' );
|
||||
|
||||
/**
|
||||
* Vary
|
||||
*
|
||||
* To modify default vary, There are two ways: Action `litespeed_vary_append` or Filter `litespeed_vary`
|
||||
*/
|
||||
add_action( 'litespeed_vary_ajax_force', __NAMESPACE__ . '\Vary::can_ajax_vary' ); // Force finalize vary even if its in an AJAX call
|
||||
// Filter `litespeed_vary_curr_cookies` to generate current in use vary, which will be used for response vary header.
|
||||
// Filter `litespeed_vary_cookies` to register the final vary cookies, which will be written to rewrite rule. (litespeed_vary_curr_cookies are always equal to or less than litespeed_vary_cookies)
|
||||
// Filter `litespeed_vary`
|
||||
add_action( 'litespeed_vary_no', __NAMESPACE__ . '\Control::set_no_vary' );
|
||||
|
||||
/**
|
||||
* Cloud
|
||||
*/
|
||||
add_filter( 'litespeed_is_from_cloud', array( $this, 'is_from_cloud' ) ); // Check if current request is from QC (usually its to check REST access) // @see https://wordpress.org/support/topic/image-optimization-not-working-3/
|
||||
|
||||
/**
|
||||
* Media
|
||||
*/
|
||||
add_action( 'litespeed_media_reset', __NAMESPACE__ . '\Media::delete_attachment' );
|
||||
|
||||
/**
|
||||
* GUI
|
||||
*/
|
||||
add_filter( 'litespeed_clean_wrapper_begin', __NAMESPACE__ . '\GUI::clean_wrapper_begin' );
|
||||
add_filter( 'litespeed_clean_wrapper_end', __NAMESPACE__ . '\GUI::clean_wrapper_end' );
|
||||
|
||||
/**
|
||||
* Misc
|
||||
*/
|
||||
add_action( 'litespeed_debug', __NAMESPACE__ . '\Debug2::debug', 10, 2 );
|
||||
add_action( 'litespeed_debug2', __NAMESPACE__ . '\Debug2::debug2', 10, 2 );
|
||||
add_action( 'litespeed_disable_all', array( $this, 'disable_all' ) );
|
||||
|
||||
add_action( 'litespeed_after_admin_init', array( $this, 'after_admin_init' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* API for admin related
|
||||
*
|
||||
* Registers hooks for admin settings and UI elements.
|
||||
*
|
||||
* @since 3.0
|
||||
* @access public
|
||||
*/
|
||||
public function after_admin_init() {
|
||||
/**
|
||||
* GUI
|
||||
*/
|
||||
add_action( 'litespeed_setting_enroll', array( $this->cls( 'Admin_Display' ), 'enroll' ), 10, 4 );
|
||||
add_action( 'litespeed_build_switch', array( $this->cls( 'Admin_Display' ), 'build_switch' ) );
|
||||
// Action `litespeed_settings_content`
|
||||
// Action `litespeed_settings_tab`
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable All
|
||||
*
|
||||
* Disables all LiteSpeed Cache features with a given reason.
|
||||
*
|
||||
* @since 2.9.7.2
|
||||
* @access public
|
||||
* @param string $reason The reason for disabling all features.
|
||||
*/
|
||||
public function disable_all( $reason ) {
|
||||
do_action( 'litespeed_debug', '[API] Disabled_all due to ' . $reason );
|
||||
|
||||
! defined( 'LITESPEED_DISABLE_ALL' ) && define( 'LITESPEED_DISABLE_ALL', true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Append commenter vary
|
||||
*
|
||||
* Adds commenter vary to the cache vary cookies.
|
||||
*
|
||||
* @since 3.0
|
||||
* @access public
|
||||
*/
|
||||
public static function vary_append_commenter() {
|
||||
Vary::cls()->append_commenter();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if is from Cloud
|
||||
*
|
||||
* Checks if the current request originates from QUIC.cloud.
|
||||
*
|
||||
* @since 4.2
|
||||
* @access public
|
||||
* @return bool True if from QUIC.cloud, false otherwise.
|
||||
*/
|
||||
public function is_from_cloud() {
|
||||
return $this->cls( 'Cloud' )->is_from_cloud();
|
||||
}
|
||||
|
||||
/**
|
||||
* Purge post
|
||||
*
|
||||
* Purges the cache for a specific post.
|
||||
*
|
||||
* @since 3.0
|
||||
* @access public
|
||||
* @param int $pid Post ID to purge.
|
||||
*/
|
||||
public function purge_post( $pid ) {
|
||||
$this->cls( 'Purge' )->purge_post( $pid );
|
||||
}
|
||||
|
||||
/**
|
||||
* Purge URL
|
||||
*
|
||||
* Purges the cache for a specific URL.
|
||||
*
|
||||
* @since 3.0
|
||||
* @access public
|
||||
* @param string $url URL to purge.
|
||||
*/
|
||||
public function purge_url( $url ) {
|
||||
$this->cls( 'Purge' )->purge_url( $url );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set cacheable
|
||||
*
|
||||
* Marks the current request as cacheable.
|
||||
*
|
||||
* @since 3.0
|
||||
* @access public
|
||||
* @param string|bool $reason Optional reason for setting cacheable.
|
||||
*/
|
||||
public function set_cacheable( $reason = false ) {
|
||||
$this->cls( 'Control' )->set_cacheable( $reason );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check ESI enabled
|
||||
*
|
||||
* Returns whether ESI is enabled.
|
||||
*
|
||||
* @since 3.0
|
||||
* @access public
|
||||
* @return bool True if ESI is enabled, false otherwise.
|
||||
*/
|
||||
public function esi_enabled() {
|
||||
return $this->cls( 'Router' )->esi_enabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get TTL
|
||||
*
|
||||
* Retrieves the cache TTL (time to live).
|
||||
*
|
||||
* @since 3.0
|
||||
* @access public
|
||||
* @return int Cache TTL value.
|
||||
*/
|
||||
public function get_ttl() {
|
||||
return $this->cls( 'Control' )->get_ttl();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate ESI block URL
|
||||
*
|
||||
* Generates a URL for an ESI block.
|
||||
*
|
||||
* @since 3.0
|
||||
* @access public
|
||||
* @param string $block_id ESI block ID.
|
||||
* @param string $wrapper Wrapper identifier.
|
||||
* @param array $params Parameters for the ESI block.
|
||||
* @param string $control Cache control settings.
|
||||
* @param bool $silence Silence output flag.
|
||||
* @param bool $preserved Preserved flag.
|
||||
* @param bool $svar Server variable flag.
|
||||
* @param array $inline_param Inline parameters.
|
||||
* @return string ESI block URL.
|
||||
*/
|
||||
public function sub_esi_block(
|
||||
$block_id,
|
||||
$wrapper,
|
||||
$params = array(),
|
||||
$control = 'private,no-vary',
|
||||
$silence = false,
|
||||
$preserved = false,
|
||||
$svar = false,
|
||||
$inline_param = array()
|
||||
) {
|
||||
return $this->cls( 'ESI' )->sub_esi_block( $block_id, $wrapper, $params, $control, $silence, $preserved, $svar, $inline_param );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set and sync conf
|
||||
*
|
||||
* Updates and synchronizes configuration settings.
|
||||
*
|
||||
* @since 7.2
|
||||
* @access public
|
||||
* @param bool|array $the_matrix Configuration data to update.
|
||||
*/
|
||||
public function save_conf( $the_matrix = false ) {
|
||||
$this->cls( 'Conf' )->update_confs( $the_matrix );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,372 @@
|
||||
<?php
|
||||
/**
|
||||
* The avatar cache class.
|
||||
*
|
||||
* Caches remote (e.g., Gravatar) avatars locally and rewrites URLs
|
||||
* to serve cached copies with a TTL. Supports on-demand generation
|
||||
* during page render and batch generation via cron.
|
||||
*
|
||||
* @since 3.0
|
||||
* @package LiteSpeed
|
||||
*/
|
||||
|
||||
namespace LiteSpeed;
|
||||
|
||||
defined( 'WPINC' ) || exit();
|
||||
|
||||
/**
|
||||
* Class Avatar
|
||||
*/
|
||||
class Avatar extends Base {
|
||||
|
||||
const TYPE_GENERATE = 'generate';
|
||||
|
||||
/**
|
||||
* Avatar cache TTL (seconds).
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $_conf_cache_ttl;
|
||||
|
||||
/**
|
||||
* Avatar DB table name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $_tb;
|
||||
|
||||
/**
|
||||
* In-request map from original URL => rewritten URL to avoid duplicates.
|
||||
*
|
||||
* @var array<string,string>
|
||||
*/
|
||||
private $_avatar_realtime_gen_dict = array();
|
||||
|
||||
/**
|
||||
* Summary/status data for last requests.
|
||||
*
|
||||
* @var array<string,mixed>
|
||||
*/
|
||||
protected $_summary;
|
||||
|
||||
/**
|
||||
* Init.
|
||||
*
|
||||
* @since 1.4
|
||||
*/
|
||||
public function __construct() {
|
||||
if ( ! $this->conf( self::O_DISCUSS_AVATAR_CACHE ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
self::debug2( '[Avatar] init' );
|
||||
|
||||
$this->_tb = $this->cls( 'Data' )->tb( 'avatar' );
|
||||
|
||||
$this->_conf_cache_ttl = $this->conf( self::O_DISCUSS_AVATAR_CACHE_TTL );
|
||||
|
||||
add_filter( 'get_avatar_url', array( $this, 'crawl_avatar' ) );
|
||||
|
||||
$this->_summary = self::get_summary();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether DB table is needed.
|
||||
*
|
||||
* @since 3.0
|
||||
* @access public
|
||||
* @return bool
|
||||
*/
|
||||
public function need_db() {
|
||||
return (bool) $this->conf( self::O_DISCUSS_AVATAR_CACHE );
|
||||
}
|
||||
|
||||
/**
|
||||
* Serve static avatar by md5 (used by local static route).
|
||||
*
|
||||
* @since 3.0
|
||||
* @access public
|
||||
* @param string $md5 MD5 hash of original avatar URL.
|
||||
* @return void
|
||||
*/
|
||||
public function serve_static( $md5 ) {
|
||||
global $wpdb;
|
||||
|
||||
self::debug( '[Avatar] is avatar request' );
|
||||
|
||||
if ( strlen( $md5 ) !== 32 ) {
|
||||
self::debug( '[Avatar] wrong md5 ' . $md5 );
|
||||
return;
|
||||
}
|
||||
|
||||
$url = $wpdb->get_var( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
|
||||
$wpdb->prepare(
|
||||
'SELECT url FROM `' . $this->_tb . '` WHERE md5 = %s', // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
$md5
|
||||
)
|
||||
);
|
||||
|
||||
if ( ! $url ) {
|
||||
self::debug( '[Avatar] no matched url for md5 ' . $md5 );
|
||||
return;
|
||||
}
|
||||
|
||||
$url = $this->_generate( $url );
|
||||
|
||||
wp_safe_redirect( $url );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Localize/replace avatar URL with cached one (filter callback).
|
||||
*
|
||||
* @since 3.0
|
||||
* @access public
|
||||
* @param string $url Original avatar URL.
|
||||
* @return string Rewritten/cached avatar URL (or original).
|
||||
*/
|
||||
public function crawl_avatar( $url ) {
|
||||
if ( ! $url ) {
|
||||
return $url;
|
||||
}
|
||||
|
||||
// Check if already generated in this request.
|
||||
if ( ! empty( $this->_avatar_realtime_gen_dict[ $url ] ) ) {
|
||||
self::debug2( '[Avatar] already in dict [url] ' . $url );
|
||||
return $this->_avatar_realtime_gen_dict[ $url ];
|
||||
}
|
||||
|
||||
$realpath = $this->_realpath( $url );
|
||||
$mtime = file_exists( $realpath ) ? filemtime( $realpath ) : false;
|
||||
|
||||
if ( $mtime && time() - $mtime <= $this->_conf_cache_ttl ) {
|
||||
self::debug2( '[Avatar] cache file exists [url] ' . $url );
|
||||
return $this->_rewrite( $url, $mtime );
|
||||
}
|
||||
|
||||
// Only handle gravatar or known remote avatar providers; keep generic check for "gravatar.com".
|
||||
if ( strpos( $url, 'gravatar.com' ) === false ) {
|
||||
return $url;
|
||||
}
|
||||
|
||||
// Throttle generation.
|
||||
if ( ! empty( $this->_summary['curr_request'] ) && time() - $this->_summary['curr_request'] < 300 ) {
|
||||
self::debug2( '[Avatar] Bypass generating due to interval limit [url] ' . $url );
|
||||
return $url;
|
||||
}
|
||||
|
||||
// Generate immediately and track for this request.
|
||||
$this->_avatar_realtime_gen_dict[ $url ] = $this->_generate( $url );
|
||||
|
||||
return $this->_avatar_realtime_gen_dict[ $url ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Count queued avatars (expired ones) for cron.
|
||||
*
|
||||
* @since 3.0
|
||||
* @access public
|
||||
* @return int|false
|
||||
*/
|
||||
public function queue_count() {
|
||||
global $wpdb;
|
||||
|
||||
// If var not exists, means table not exists // todo: not true.
|
||||
if ( ! $this->_tb ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$cnt = $wpdb->get_var( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
|
||||
$wpdb->prepare(
|
||||
'SELECT COUNT(*) FROM `' . $this->_tb . '` WHERE dateline < %d', // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
time() - $this->_conf_cache_ttl
|
||||
)
|
||||
);
|
||||
|
||||
return (int) $cnt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build final local URL for cached avatar.
|
||||
*
|
||||
* @since 3.0
|
||||
* @param string $url Original URL.
|
||||
* @param int|null $time Optional filemtime for cache busting.
|
||||
* @return string Local URL.
|
||||
*/
|
||||
private function _rewrite( $url, $time = null ) {
|
||||
$qs = $time ? '?ver=' . $time : '';
|
||||
return LITESPEED_STATIC_URL . '/avatar/' . $this->_filepath( $url ) . $qs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate filesystem realpath for cache file.
|
||||
*
|
||||
* @since 3.0
|
||||
* @access private
|
||||
* @param string $url Original URL.
|
||||
* @return string Absolute filesystem path.
|
||||
*/
|
||||
private function _realpath( $url ) {
|
||||
return LITESPEED_STATIC_DIR . '/avatar/' . $this->_filepath( $url );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get relative filepath for cached avatar.
|
||||
*
|
||||
* @since 4.0
|
||||
* @param string $url Original URL.
|
||||
* @return string Relative path under avatar/ (may include blog id).
|
||||
*/
|
||||
private function _filepath( $url ) {
|
||||
$filename = md5( $url ) . '.jpg';
|
||||
if ( is_multisite() ) {
|
||||
$filename = get_current_blog_id() . '/' . $filename;
|
||||
}
|
||||
return $filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cron generation for expired avatars.
|
||||
*
|
||||
* @since 3.0
|
||||
* @access public
|
||||
* @param bool $force Bypass throttle.
|
||||
* @return void
|
||||
*/
|
||||
public static function cron( $force = false ) {
|
||||
global $wpdb;
|
||||
|
||||
$_instance = self::cls();
|
||||
if ( ! $_instance->queue_count() ) {
|
||||
self::debug( '[Avatar] no queue' );
|
||||
return;
|
||||
}
|
||||
|
||||
// For cron, need to check request interval too.
|
||||
if ( ! $force ) {
|
||||
if ( ! empty( $_instance->_summary['curr_request'] ) && time() - $_instance->_summary['curr_request'] < 300 ) {
|
||||
self::debug( '[Avatar] curr_request too close' );
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$list = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
|
||||
$wpdb->prepare(
|
||||
'SELECT url FROM `' . $_instance->_tb . '` WHERE dateline < %d ORDER BY id DESC LIMIT %d', // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
time() - $_instance->_conf_cache_ttl,
|
||||
(int) apply_filters( 'litespeed_avatar_limit', 30 )
|
||||
)
|
||||
);
|
||||
self::debug( '[Avatar] cron job [count] ' . ( $list ? count( $list ) : 0 ) );
|
||||
|
||||
if ( $list ) {
|
||||
foreach ( $list as $v ) {
|
||||
self::debug( '[Avatar] cron job [url] ' . $v->url );
|
||||
$_instance->_generate( $v->url );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Download and store the avatar locally, then update DB row.
|
||||
*
|
||||
* @since 3.0
|
||||
* @access private
|
||||
* @param string $url Original avatar URL.
|
||||
* @return string Rewritten local URL (fallback to original on failure).
|
||||
*/
|
||||
private function _generate( $url ) {
|
||||
global $wpdb;
|
||||
|
||||
$file = $this->_realpath( $url );
|
||||
|
||||
// Mark request start
|
||||
self::save_summary(
|
||||
array(
|
||||
'curr_request' => time(),
|
||||
)
|
||||
);
|
||||
|
||||
// Ensure cache directory exists
|
||||
$this->_maybe_mk_cache_folder( 'avatar' );
|
||||
|
||||
$response = wp_safe_remote_get(
|
||||
$url,
|
||||
array(
|
||||
'timeout' => 180,
|
||||
'stream' => true,
|
||||
'filename' => $file,
|
||||
)
|
||||
);
|
||||
|
||||
self::debug( '[Avatar] _generate [url] ' . $url );
|
||||
|
||||
// Parse response data
|
||||
if ( is_wp_error( $response ) ) {
|
||||
$error_message = $response->get_error_message();
|
||||
if ( file_exists( $file ) ) {
|
||||
wp_delete_file( $file );
|
||||
}
|
||||
self::debug( '[Avatar] failed to get: ' . $error_message );
|
||||
return $url;
|
||||
}
|
||||
|
||||
// Save summary data
|
||||
self::save_summary(
|
||||
array(
|
||||
'last_spent' => time() - $this->_summary['curr_request'],
|
||||
'last_request' => $this->_summary['curr_request'],
|
||||
'curr_request' => 0,
|
||||
)
|
||||
);
|
||||
|
||||
// Update/insert DB record
|
||||
$md5 = md5( $url );
|
||||
|
||||
$existed = $wpdb->query( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
|
||||
$wpdb->prepare(
|
||||
'UPDATE `' . $this->_tb . '` SET dateline = %d WHERE md5 = %s', // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
time(),
|
||||
$md5
|
||||
)
|
||||
);
|
||||
|
||||
if ( ! $existed ) {
|
||||
$wpdb->query( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
|
||||
$wpdb->prepare(
|
||||
'INSERT INTO `' . $this->_tb . '` (url, md5, dateline) VALUES (%s, %s, %d)', // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
$url,
|
||||
$md5,
|
||||
time()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
self::debug( '[Avatar] saved avatar ' . $file );
|
||||
|
||||
return $this->_rewrite( $url );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle all request actions from main cls.
|
||||
*
|
||||
* @since 3.0
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
public function handler() {
|
||||
$type = Router::verify_type();
|
||||
|
||||
switch ( $type ) {
|
||||
case self::TYPE_GENERATE:
|
||||
self::cron( true );
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
Admin::redirect();
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,621 @@
|
||||
<?php
|
||||
/**
|
||||
* CDN handling for LiteSpeed Cache.
|
||||
*
|
||||
* Rewrites eligible asset URLs to configured CDN endpoints and integrates with WordPress filters.
|
||||
*
|
||||
* @since 1.2.3
|
||||
* @package LiteSpeed
|
||||
*/
|
||||
|
||||
namespace LiteSpeed;
|
||||
|
||||
defined( 'WPINC' ) || exit();
|
||||
|
||||
/**
|
||||
* Class CDN
|
||||
*
|
||||
* Processes page content and WordPress asset URLs to map to CDN domains according to settings.
|
||||
*/
|
||||
class CDN extends Root {
|
||||
const LOG_TAG = '[CDN]';
|
||||
|
||||
const BYPASS = 'LITESPEED_BYPASS_CDN';
|
||||
|
||||
/**
|
||||
* The working HTML/content buffer being processed.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $content;
|
||||
|
||||
/**
|
||||
* Whether CDN feature is enabled.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $_cfg_cdn;
|
||||
|
||||
/**
|
||||
* List of original site URLs (may include wildcards) to be replaced.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
private $_cfg_url_ori;
|
||||
|
||||
/**
|
||||
* List of directories considered internal/original for CDN rewriting.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
private $_cfg_ori_dir;
|
||||
|
||||
/**
|
||||
* CDN mapping rules; keys include mapping kinds or file extensions, values are URL(s).
|
||||
*
|
||||
* @var array<string,string|string[]>
|
||||
*/
|
||||
private $_cfg_cdn_mapping = [];
|
||||
|
||||
/**
|
||||
* List of URL substrings/regex used to exclude items from CDN.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
private $_cfg_cdn_exclude;
|
||||
|
||||
/**
|
||||
* Hosts used by CDN mappings for quick membership checks.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
private $cdn_mapping_hosts = [];
|
||||
|
||||
/**
|
||||
* Initialize CDN integration and register filters if enabled.
|
||||
*
|
||||
* @since 1.2.3
|
||||
* @return void
|
||||
*/
|
||||
public function init() {
|
||||
self::debug2( 'init' );
|
||||
|
||||
if ( defined( self::BYPASS ) ) {
|
||||
self::debug2( 'CDN bypass' );
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! Router::can_cdn() ) {
|
||||
if ( ! defined( self::BYPASS ) ) {
|
||||
define( self::BYPASS, true );
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
$this->_cfg_cdn = $this->conf( Base::O_CDN );
|
||||
if ( ! $this->_cfg_cdn ) {
|
||||
if ( ! defined( self::BYPASS ) ) {
|
||||
define( self::BYPASS, true );
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
$this->_cfg_url_ori = $this->conf( Base::O_CDN_ORI );
|
||||
// Parse cdn mapping data to array( 'filetype' => 'url' )
|
||||
$mapping_to_check = [ Base::CDN_MAPPING_INC_IMG, Base::CDN_MAPPING_INC_CSS, Base::CDN_MAPPING_INC_JS ];
|
||||
foreach ( $this->conf( Base::O_CDN_MAPPING ) as $v ) {
|
||||
if ( ! $v[ Base::CDN_MAPPING_URL ] ) {
|
||||
continue;
|
||||
}
|
||||
$this_url = $v[ Base::CDN_MAPPING_URL ];
|
||||
$this_host = wp_parse_url( $this_url, PHP_URL_HOST );
|
||||
// Check img/css/js
|
||||
foreach ( $mapping_to_check as $to_check ) {
|
||||
if ( $v[ $to_check ] ) {
|
||||
self::debug2( 'mapping ' . $to_check . ' -> ' . $this_url );
|
||||
|
||||
// If filetype to url is one to many, make url be an array
|
||||
$this->_append_cdn_mapping( $to_check, $this_url );
|
||||
|
||||
if ( ! in_array( $this_host, $this->cdn_mapping_hosts, true ) ) {
|
||||
$this->cdn_mapping_hosts[] = $this_host;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Check file types
|
||||
if ( $v[ Base::CDN_MAPPING_FILETYPE ] ) {
|
||||
foreach ( $v[ Base::CDN_MAPPING_FILETYPE ] as $v2 ) {
|
||||
$this->_cfg_cdn_mapping[ Base::CDN_MAPPING_FILETYPE ] = true;
|
||||
|
||||
// If filetype to url is one to many, make url be an array
|
||||
$this->_append_cdn_mapping( $v2, $this_url );
|
||||
|
||||
if ( ! in_array( $this_host, $this->cdn_mapping_hosts, true ) ) {
|
||||
$this->cdn_mapping_hosts[] = $this_host;
|
||||
}
|
||||
}
|
||||
self::debug2( 'mapping ' . implode( ',', $v[ Base::CDN_MAPPING_FILETYPE ] ) . ' -> ' . $this_url );
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! $this->_cfg_url_ori || ! $this->_cfg_cdn_mapping ) {
|
||||
if ( ! defined( self::BYPASS ) ) {
|
||||
define( self::BYPASS, true );
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
$this->_cfg_ori_dir = $this->conf( Base::O_CDN_ORI_DIR );
|
||||
// In case user customized upload path
|
||||
if ( defined( 'UPLOADS' ) ) {
|
||||
$this->_cfg_ori_dir[] = UPLOADS;
|
||||
}
|
||||
|
||||
// Check if need preg_replace
|
||||
$this->_cfg_url_ori = Utility::wildcard2regex( $this->_cfg_url_ori );
|
||||
|
||||
$this->_cfg_cdn_exclude = $this->conf( Base::O_CDN_EXC );
|
||||
|
||||
if ( ! empty( $this->_cfg_cdn_mapping[ Base::CDN_MAPPING_INC_IMG ] ) ) {
|
||||
// Hook to srcset
|
||||
if ( function_exists( 'wp_calculate_image_srcset' ) ) {
|
||||
add_filter( 'wp_calculate_image_srcset', [ $this, 'srcset' ], 999 );
|
||||
}
|
||||
// Hook to mime icon
|
||||
add_filter( 'wp_get_attachment_image_src', [ $this, 'attach_img_src' ], 999 );
|
||||
add_filter( 'wp_get_attachment_url', [ $this, 'url_img' ], 999 );
|
||||
}
|
||||
|
||||
if ( ! empty( $this->_cfg_cdn_mapping[ Base::CDN_MAPPING_INC_CSS ] ) ) {
|
||||
add_filter( 'style_loader_src', [ $this, 'url_css' ], 999 );
|
||||
}
|
||||
|
||||
if ( ! empty( $this->_cfg_cdn_mapping[ Base::CDN_MAPPING_INC_JS ] ) ) {
|
||||
add_filter( 'script_loader_src', [ $this, 'url_js' ], 999 );
|
||||
}
|
||||
|
||||
add_filter( 'litespeed_buffer_finalize', [ $this, 'finalize' ], 30 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Associate all filetypes with CDN URL.
|
||||
*
|
||||
* @since 2.0
|
||||
* @access private
|
||||
*
|
||||
* @param string $filetype Mapping key (e.g., extension or mapping constant).
|
||||
* @param string $url CDN base URL to use for this mapping.
|
||||
* @return void
|
||||
*/
|
||||
private function _append_cdn_mapping( $filetype, $url ) {
|
||||
// If filetype to url is one to many, make url be an array
|
||||
if ( empty( $this->_cfg_cdn_mapping[ $filetype ] ) ) {
|
||||
$this->_cfg_cdn_mapping[ $filetype ] = $url;
|
||||
} elseif ( is_array( $this->_cfg_cdn_mapping[ $filetype ] ) ) {
|
||||
// Append url to filetype
|
||||
$this->_cfg_cdn_mapping[ $filetype ][] = $url;
|
||||
} else {
|
||||
// Convert _cfg_cdn_mapping from string to array
|
||||
$this->_cfg_cdn_mapping[ $filetype ] = [ $this->_cfg_cdn_mapping[ $filetype ], $url ];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the given type is included in CDN mappings.
|
||||
*
|
||||
* @since 1.6.2.1
|
||||
*
|
||||
* @param string $type 'css' or 'js'.
|
||||
* @return bool True if included in CDN.
|
||||
*/
|
||||
public function inc_type( $type ) {
|
||||
if ( 'css' === $type && ! empty( $this->_cfg_cdn_mapping[ Base::CDN_MAPPING_INC_CSS ] ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( 'js' === $type && ! empty( $this->_cfg_cdn_mapping[ Base::CDN_MAPPING_INC_JS ] ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run CDN processing on finalized buffer.
|
||||
* NOTE: After cache finalized, cannot change cache control.
|
||||
*
|
||||
* @since 1.2.3
|
||||
* @access public
|
||||
*
|
||||
* @param string $content The HTML/content buffer.
|
||||
* @return string The processed content.
|
||||
*/
|
||||
public function finalize( $content ) {
|
||||
$this->content = $content;
|
||||
|
||||
$this->_finalize();
|
||||
return $this->content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace eligible URLs with CDN URLs in the working buffer.
|
||||
*
|
||||
* @since 1.2.3
|
||||
* @access private
|
||||
* @return void
|
||||
*/
|
||||
private function _finalize() {
|
||||
if ( defined( self::BYPASS ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
self::debug( 'CDN _finalize' );
|
||||
|
||||
// Start replacing img src
|
||||
if ( ! empty( $this->_cfg_cdn_mapping[ Base::CDN_MAPPING_INC_IMG ] ) ) {
|
||||
$this->_replace_img();
|
||||
$this->_replace_inline_css();
|
||||
}
|
||||
|
||||
if ( ! empty( $this->_cfg_cdn_mapping[ Base::CDN_MAPPING_FILETYPE ] ) ) {
|
||||
$this->_replace_file_types();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse all file types and replace according to configured attributes.
|
||||
*
|
||||
* @since 1.2.3
|
||||
* @access private
|
||||
* @return void
|
||||
*/
|
||||
private function _replace_file_types() {
|
||||
$ele_to_check = $this->conf( Base::O_CDN_ATTR );
|
||||
|
||||
foreach ( $ele_to_check as $v ) {
|
||||
if ( ! $v || false === strpos( $v, '.' ) ) {
|
||||
self::debug2( 'replace setting bypassed: no . attribute ' . $v );
|
||||
continue;
|
||||
}
|
||||
|
||||
self::debug2( 'replace attribute ' . $v );
|
||||
|
||||
$v = explode( '.', $v );
|
||||
$attr = preg_quote( $v[1], '#' );
|
||||
if ( $v[0] ) {
|
||||
$pattern = '#<' . preg_quote( $v[0], '#' ) . '([^>]+)' . $attr . '=([\'"])(.+)\g{2}#iU';
|
||||
} else {
|
||||
$pattern = '# ' . $attr . '=([\'"])(.+)\g{1}#iU';
|
||||
}
|
||||
|
||||
preg_match_all( $pattern, $this->content, $matches );
|
||||
|
||||
if (empty($matches[$v[0] ? 3 : 2])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($matches[$v[0] ? 3 : 2] as $k2 => $url) {
|
||||
// self::debug2( 'check ' . $url );
|
||||
$postfix = '.' . pathinfo((string) wp_parse_url($url, PHP_URL_PATH), PATHINFO_EXTENSION);
|
||||
if (!array_key_exists($postfix, $this->_cfg_cdn_mapping)) {
|
||||
// self::debug2( 'non-existed postfix ' . $postfix );
|
||||
continue;
|
||||
}
|
||||
|
||||
self::debug2( 'matched file_type ' . $postfix . ' : ' . $url );
|
||||
|
||||
$url2 = $this->rewrite( $url, Base::CDN_MAPPING_FILETYPE, $postfix );
|
||||
if ( ! $url2 ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$attr_str = str_replace( $url, $url2, $matches[0][ $k2 ] );
|
||||
$this->content = str_replace( $matches[0][ $k2 ], $attr_str, $this->content );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse all images and replace their src attributes.
|
||||
*
|
||||
* @since 1.2.3
|
||||
* @access private
|
||||
* @return void
|
||||
*/
|
||||
private function _replace_img() {
|
||||
preg_match_all( '#<img([^>]+?)src=([\'"\\\]*)([^\'"\s\\\>]+)([\'"\\\]*)([^>]*)>#i', $this->content, $matches );
|
||||
foreach ( $matches[3] as $k => $url ) {
|
||||
// Check if is a DATA-URI
|
||||
if ( false !== strpos( $url, 'data:image' ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$url2 = $this->rewrite( $url, Base::CDN_MAPPING_INC_IMG );
|
||||
if ( ! $url2 ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$html_snippet = sprintf( '<img %1$s src=%2$s %3$s>', $matches[1][ $k ], $matches[2][ $k ] . $url2 . $matches[4][ $k ], $matches[5][ $k ] );
|
||||
$this->content = str_replace( $matches[0][ $k ], $html_snippet, $this->content );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse and replace all inline styles containing url().
|
||||
*
|
||||
* @since 1.2.3
|
||||
* @access private
|
||||
* @return void
|
||||
*/
|
||||
private function _replace_inline_css() {
|
||||
self::debug2( '_replace_inline_css', $this->_cfg_cdn_mapping );
|
||||
|
||||
/**
|
||||
* Excludes `\` from URL matching
|
||||
*
|
||||
* @see #959152 - WordPress LSCache CDN Mapping causing malformed URLS
|
||||
* @see #685485
|
||||
* @since 3.0
|
||||
*/
|
||||
preg_match_all( '/url\((?![\'"]?data)[\'"]?(.+?)[\'"]?\)/i', $this->content, $matches );
|
||||
foreach ( $matches[1] as $k => $url ) {
|
||||
$url = str_replace( [ ' ', '\t', '\n', '\r', '\0', '\x0B', '"', "'", '"', ''' ], '', $url );
|
||||
|
||||
// Parse file postfix
|
||||
$parsed_url = wp_parse_url( $url, PHP_URL_PATH );
|
||||
if ( ! $parsed_url ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$postfix = '.' . pathinfo( $parsed_url, PATHINFO_EXTENSION );
|
||||
if ( array_key_exists( $postfix, $this->_cfg_cdn_mapping ) ) {
|
||||
self::debug2( 'matched file_type ' . $postfix . ' : ' . $url );
|
||||
$url2 = $this->rewrite( $url, Base::CDN_MAPPING_FILETYPE, $postfix );
|
||||
if ( ! $url2 ) {
|
||||
continue;
|
||||
}
|
||||
} elseif ( in_array( $postfix, [ 'jpg', 'jpeg', 'png', 'gif', 'svg', 'webp', 'avif' ], true ) ) {
|
||||
$url2 = $this->rewrite( $url, Base::CDN_MAPPING_INC_IMG );
|
||||
if ( ! $url2 ) {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
$attr = str_replace( $matches[1][ $k ], $url2, $matches[0][ $k ] );
|
||||
$this->content = str_replace( $matches[0][ $k ], $attr, $this->content );
|
||||
}
|
||||
|
||||
self::debug2( '_replace_inline_css done' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter: wp_get_attachment_image_src.
|
||||
*
|
||||
* @since 1.2.3
|
||||
* @since 1.7 Removed static from function.
|
||||
* @access public
|
||||
*
|
||||
* @param array{0:string,1:int,2:int} $img The URL of the attachment image src, the width, the height.
|
||||
* @return array{0:string,1:int,2:int}
|
||||
*/
|
||||
public function attach_img_src( $img ) {
|
||||
if ( $img ) {
|
||||
$url = $this->rewrite( $img[0], Base::CDN_MAPPING_INC_IMG );
|
||||
if ( $url ) {
|
||||
$img[0] = $url;
|
||||
}
|
||||
}
|
||||
return $img;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to rewrite one image URL with CDN.
|
||||
*
|
||||
* @since 1.7
|
||||
* @access public
|
||||
*
|
||||
* @param string $url Original URL.
|
||||
* @return string URL after rewriting, or original if not applicable.
|
||||
*/
|
||||
public function url_img( $url ) {
|
||||
if ( $url ) {
|
||||
$url2 = $this->rewrite( $url, Base::CDN_MAPPING_INC_IMG );
|
||||
if ( $url2 ) {
|
||||
$url = $url2;
|
||||
}
|
||||
}
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to rewrite one CSS URL with CDN.
|
||||
*
|
||||
* @since 1.7
|
||||
* @access public
|
||||
*
|
||||
* @param string $url Original URL.
|
||||
* @return string URL after rewriting, or original if not applicable.
|
||||
*/
|
||||
public function url_css( $url ) {
|
||||
if ( $url ) {
|
||||
$url2 = $this->rewrite( $url, Base::CDN_MAPPING_INC_CSS );
|
||||
if ( $url2 ) {
|
||||
$url = $url2;
|
||||
}
|
||||
}
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to rewrite one JS URL with CDN.
|
||||
*
|
||||
* @since 1.7
|
||||
* @access public
|
||||
*
|
||||
* @param string $url Original URL.
|
||||
* @return string URL after rewriting, or original if not applicable.
|
||||
*/
|
||||
public function url_js( $url ) {
|
||||
if ( $url ) {
|
||||
$url2 = $this->rewrite( $url, Base::CDN_MAPPING_INC_JS );
|
||||
if ( $url2 ) {
|
||||
$url = $url2;
|
||||
}
|
||||
}
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter responsive image sources for CDN.
|
||||
*
|
||||
* @since 1.2.3
|
||||
* @since 1.7 Removed static from function.
|
||||
* @access public
|
||||
*
|
||||
* @param array<int,array{url:string}> $srcs Srcset array.
|
||||
* @return array<int,array{url:string}>
|
||||
*/
|
||||
public function srcset( $srcs ) {
|
||||
if ( $srcs ) {
|
||||
foreach ( $srcs as $w => $data ) {
|
||||
$url = $this->rewrite( $data['url'], Base::CDN_MAPPING_INC_IMG );
|
||||
if ( ! $url ) {
|
||||
continue;
|
||||
}
|
||||
$srcs[ $w ]['url'] = $url;
|
||||
}
|
||||
}
|
||||
return $srcs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace an URL with mapped CDN URL, if applicable.
|
||||
*
|
||||
* @since 1.2.3
|
||||
* @access public
|
||||
*
|
||||
* @param string $url Target URL.
|
||||
* @param string $mapping_kind Mapping kind (e.g., Base::CDN_MAPPING_INC_IMG or Base::CDN_MAPPING_FILETYPE).
|
||||
* @param string|false $postfix File extension (with dot) when mapping by file type.
|
||||
* @return string|false Replaced URL on success, false when not applicable.
|
||||
*/
|
||||
public function rewrite( $url, $mapping_kind, $postfix = false ) {
|
||||
self::debug2( 'rewrite ' . $url );
|
||||
$url_parsed = wp_parse_url( $url );
|
||||
|
||||
if ( empty( $url_parsed['path'] ) ) {
|
||||
self::debug2( '-rewrite bypassed: no path' );
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only images under wp-content/wp-includes can be replaced
|
||||
$is_internal_folder = Utility::str_hit_array( $url_parsed['path'], $this->_cfg_ori_dir );
|
||||
if ( ! $is_internal_folder ) {
|
||||
self::debug2( '-rewrite failed: path not match: ' . LSCWP_CONTENT_FOLDER );
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if is external url
|
||||
if ( ! empty( $url_parsed['host'] ) ) {
|
||||
if ( ! Utility::internal( $url_parsed['host'] ) && ! $this->_is_ori_url( $url ) ) {
|
||||
self::debug2( '-rewrite failed: host not internal' );
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$exclude = Utility::str_hit_array( $url, $this->_cfg_cdn_exclude );
|
||||
if ( $exclude ) {
|
||||
self::debug2( '-abort excludes ' . $exclude );
|
||||
return false;
|
||||
}
|
||||
|
||||
// Fill full url before replacement
|
||||
if ( empty( $url_parsed['host'] ) ) {
|
||||
$url = Utility::uri2url( $url );
|
||||
self::debug2( '-fill before rewritten: ' . $url );
|
||||
|
||||
$url_parsed = wp_parse_url( $url );
|
||||
}
|
||||
|
||||
$scheme = ! empty( $url_parsed['scheme'] ) ? $url_parsed['scheme'] . ':' : '';
|
||||
|
||||
// Find the mapping url to be replaced to
|
||||
if ( empty( $this->_cfg_cdn_mapping[ $mapping_kind ] ) ) {
|
||||
return false;
|
||||
}
|
||||
if ( Base::CDN_MAPPING_FILETYPE !== $mapping_kind ) {
|
||||
$final_url = $this->_cfg_cdn_mapping[ $mapping_kind ];
|
||||
} else {
|
||||
// select from file type
|
||||
$final_url = $this->_cfg_cdn_mapping[ $postfix ];
|
||||
if ( ! $final_url ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// If filetype to url is one to many, need to random one
|
||||
if ( is_array( $final_url ) ) {
|
||||
$final_url = $final_url[ array_rand( $final_url ) ];
|
||||
}
|
||||
|
||||
// Now lets replace CDN url
|
||||
foreach ( $this->_cfg_url_ori as $v ) {
|
||||
if ( false !== strpos( $v, '*' ) ) {
|
||||
$url = preg_replace( '#' . $scheme . $v . '#iU', $final_url, $url );
|
||||
} else {
|
||||
$url = str_replace( $scheme . $v, $final_url, $url );
|
||||
}
|
||||
}
|
||||
self::debug2( '-rewritten: ' . $url );
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given URL matches any configured "original" URLs for CDN.
|
||||
*
|
||||
* @since 2.1
|
||||
* @access private
|
||||
*
|
||||
* @param string $url URL to test.
|
||||
* @return bool True if URL is one of the originals.
|
||||
*/
|
||||
private function _is_ori_url( $url ) {
|
||||
$url_parsed = wp_parse_url( $url );
|
||||
|
||||
$scheme = ! empty( $url_parsed['scheme'] ) ? $url_parsed['scheme'] . ':' : '';
|
||||
|
||||
foreach ( $this->_cfg_url_ori as $v ) {
|
||||
$needle = $scheme . $v;
|
||||
if ( false !== strpos( $v, '*' ) ) {
|
||||
if ( preg_match( '#' . $needle . '#iU', $url ) ) {
|
||||
return true;
|
||||
}
|
||||
} elseif ( 0 === strpos( $url, $needle ) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the host is one of the CDN mapping hosts.
|
||||
*
|
||||
* @since 1.2.3
|
||||
*
|
||||
* @param string $host Hostname to check.
|
||||
* @return bool False when bypassed, otherwise true if internal CDN host.
|
||||
*/
|
||||
public static function internal( $host ) {
|
||||
if ( defined( self::BYPASS ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$instance = self::cls();
|
||||
|
||||
return in_array( $host, $instance->cdn_mapping_hosts, true ); // todo: can add $this->_is_ori_url() check in future
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,333 @@
|
||||
<?php
|
||||
/**
|
||||
* The cloudflare CDN class.
|
||||
*
|
||||
* @since 2.1
|
||||
* @package LiteSpeed
|
||||
* @subpackage LiteSpeed/src/cdn
|
||||
* @author LiteSpeed Technologies <info@litespeedtech.com>
|
||||
*/
|
||||
|
||||
namespace LiteSpeed\CDN;
|
||||
|
||||
use LiteSpeed\Base;
|
||||
use LiteSpeed\Debug2;
|
||||
use LiteSpeed\Router;
|
||||
use LiteSpeed\Admin;
|
||||
use LiteSpeed\Admin_Display;
|
||||
|
||||
defined('WPINC') || exit();
|
||||
|
||||
/**
|
||||
* Class Cloudflare
|
||||
*
|
||||
* @since 2.1
|
||||
*/
|
||||
class Cloudflare extends Base {
|
||||
|
||||
const TYPE_PURGE_ALL = 'purge_all';
|
||||
const TYPE_GET_DEVMODE = 'get_devmode';
|
||||
const TYPE_SET_DEVMODE_ON = 'set_devmode_on';
|
||||
const TYPE_SET_DEVMODE_OFF = 'set_devmode_off';
|
||||
|
||||
const ITEM_STATUS = 'status';
|
||||
|
||||
/**
|
||||
* Update zone&name based on latest settings
|
||||
*
|
||||
* @since 3.0
|
||||
* @access public
|
||||
*/
|
||||
public function try_refresh_zone() {
|
||||
if (!$this->conf(self::O_CDN_CLOUDFLARE)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$zone = $this->fetch_zone();
|
||||
if ($zone) {
|
||||
$this->cls('Conf')->update(self::O_CDN_CLOUDFLARE_NAME, $zone['name']);
|
||||
|
||||
$this->cls('Conf')->update(self::O_CDN_CLOUDFLARE_ZONE, $zone['id']);
|
||||
|
||||
Debug2::debug("[Cloudflare] Get zone successfully \t\t[ID] " . $zone['id']);
|
||||
} else {
|
||||
$this->cls('Conf')->update(self::O_CDN_CLOUDFLARE_ZONE, '');
|
||||
Debug2::debug('[Cloudflare] ❌ Get zone failed, clean zone');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Cloudflare development mode
|
||||
*
|
||||
* @since 1.7.2
|
||||
* @access private
|
||||
* @param bool $show_msg Whether to show success/error message.
|
||||
*/
|
||||
private function get_devmode( $show_msg = true ) {
|
||||
Debug2::debug('[Cloudflare] get_devmode');
|
||||
|
||||
$zone = $this->zone();
|
||||
if (!$zone) {
|
||||
return;
|
||||
}
|
||||
|
||||
$url = 'https://api.cloudflare.com/client/v4/zones/' . $zone . '/settings/development_mode';
|
||||
$res = $this->cloudflare_call($url, 'GET', false, $show_msg);
|
||||
|
||||
if (!$res) {
|
||||
return;
|
||||
}
|
||||
Debug2::debug('[Cloudflare] get_devmode result ', $res);
|
||||
|
||||
// Make sure is array: #992174
|
||||
$curr_status = self::get_option(self::ITEM_STATUS, array());
|
||||
if ( ! is_array( $curr_status ) ) {
|
||||
$curr_status = array();
|
||||
}
|
||||
$curr_status['devmode'] = $res['value'];
|
||||
$curr_status['devmode_expired'] = $res['time_remaining'] + time();
|
||||
|
||||
// update status
|
||||
self::update_option(self::ITEM_STATUS, $curr_status);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Cloudflare development mode
|
||||
*
|
||||
* @since 1.7.2
|
||||
* @access private
|
||||
* @param string $type The type of development mode to set (on/off).
|
||||
*/
|
||||
private function set_devmode( $type ) {
|
||||
Debug2::debug('[Cloudflare] set_devmode');
|
||||
|
||||
$zone = $this->zone();
|
||||
if (!$zone) {
|
||||
return;
|
||||
}
|
||||
|
||||
$url = 'https://api.cloudflare.com/client/v4/zones/' . $zone . '/settings/development_mode';
|
||||
$new_val = self::TYPE_SET_DEVMODE_ON === $type ? 'on' : 'off';
|
||||
$data = array( 'value' => $new_val );
|
||||
$res = $this->cloudflare_call($url, 'PATCH', $data);
|
||||
|
||||
if (!$res) {
|
||||
return;
|
||||
}
|
||||
|
||||
$res = $this->get_devmode(false);
|
||||
|
||||
if ($res) {
|
||||
$msg = sprintf(__('Notified Cloudflare to set development mode to %s successfully.', 'litespeed-cache'), strtoupper($new_val));
|
||||
Admin_Display::success($msg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shortcut to purge Cloudflare
|
||||
*
|
||||
* @since 7.1
|
||||
* @access public
|
||||
* @param string|bool $reason The reason for purging, or false if none.
|
||||
*/
|
||||
public static function purge_all( $reason = false ) {
|
||||
if ($reason) {
|
||||
Debug2::debug('[Cloudflare] purge call because: ' . $reason);
|
||||
}
|
||||
self::cls()->purge_all_private();
|
||||
}
|
||||
|
||||
/**
|
||||
* Purge Cloudflare cache
|
||||
*
|
||||
* @since 1.7.2
|
||||
* @access private
|
||||
*/
|
||||
private function purge_all_private() {
|
||||
Debug2::debug('[Cloudflare] purge_all_private');
|
||||
|
||||
$cf_on = $this->conf(self::O_CDN_CLOUDFLARE);
|
||||
if (!$cf_on) {
|
||||
$msg = __('Cloudflare API is set to off.', 'litespeed-cache');
|
||||
Admin_Display::error($msg);
|
||||
return;
|
||||
}
|
||||
|
||||
$zone = $this->zone();
|
||||
if (!$zone) {
|
||||
return;
|
||||
}
|
||||
|
||||
$url = 'https://api.cloudflare.com/client/v4/zones/' . $zone . '/purge_cache';
|
||||
$data = array( 'purge_everything' => true );
|
||||
|
||||
$res = $this->cloudflare_call($url, 'DELETE', $data);
|
||||
|
||||
if ($res) {
|
||||
$msg = __('Notified Cloudflare to purge all successfully.', 'litespeed-cache');
|
||||
Admin_Display::success($msg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current Cloudflare zone from cfg
|
||||
*
|
||||
* @since 1.7.2
|
||||
* @access private
|
||||
*/
|
||||
private function zone() {
|
||||
$zone = $this->conf(self::O_CDN_CLOUDFLARE_ZONE);
|
||||
if (!$zone) {
|
||||
$msg = __('No available Cloudflare zone', 'litespeed-cache');
|
||||
Admin_Display::error($msg);
|
||||
return false;
|
||||
}
|
||||
|
||||
return $zone;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Cloudflare zone settings
|
||||
*
|
||||
* @since 1.7.2
|
||||
* @access private
|
||||
*/
|
||||
private function fetch_zone() {
|
||||
$kw = $this->conf(self::O_CDN_CLOUDFLARE_NAME);
|
||||
|
||||
$url = 'https://api.cloudflare.com/client/v4/zones?status=active&match=all';
|
||||
|
||||
// Try exact match first
|
||||
if ($kw && false !== strpos($kw, '.')) {
|
||||
$zones = $this->cloudflare_call($url . '&name=' . $kw, 'GET', false, false);
|
||||
if ($zones) {
|
||||
Debug2::debug('[Cloudflare] fetch_zone exact matched');
|
||||
return $zones[0];
|
||||
}
|
||||
}
|
||||
|
||||
// Can't find, try to get default one
|
||||
$zones = $this->cloudflare_call($url, 'GET', false, false);
|
||||
|
||||
if (!$zones) {
|
||||
Debug2::debug('[Cloudflare] fetch_zone no zone');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$kw) {
|
||||
Debug2::debug('[Cloudflare] fetch_zone no set name, use first one by default');
|
||||
return $zones[0];
|
||||
}
|
||||
|
||||
foreach ($zones as $v) {
|
||||
if (false !== strpos($v['name'], $kw)) {
|
||||
Debug2::debug('[Cloudflare] fetch_zone matched ' . $kw . ' [name] ' . $v['name']);
|
||||
return $v;
|
||||
}
|
||||
}
|
||||
|
||||
// Can't match current name, return default one
|
||||
Debug2::debug('[Cloudflare] fetch_zone failed match name, use first one by default');
|
||||
return $zones[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Cloudflare API
|
||||
*
|
||||
* @since 1.7.2
|
||||
* @access private
|
||||
* @param string $url The API URL to call.
|
||||
* @param string $method The HTTP method to use (GET, POST, etc.).
|
||||
* @param array|bool $data The data to send with the request, or false if none.
|
||||
* @param bool $show_msg Whether to show success/error message.
|
||||
*/
|
||||
private function cloudflare_call( $url, $method = 'GET', $data = false, $show_msg = true ) {
|
||||
Debug2::debug("[Cloudflare] cloudflare_call \t\t[URL] $url");
|
||||
|
||||
if (strlen($this->conf(self::O_CDN_CLOUDFLARE_KEY)) === 40) {
|
||||
$headers = array(
|
||||
'Content-Type' => 'application/json',
|
||||
'Authorization' => 'Bearer ' . $this->conf(self::O_CDN_CLOUDFLARE_KEY),
|
||||
);
|
||||
} else {
|
||||
$headers = array(
|
||||
'Content-Type' => 'application/json',
|
||||
'X-Auth-Email' => $this->conf(self::O_CDN_CLOUDFLARE_EMAIL),
|
||||
'X-Auth-Key' => $this->conf(self::O_CDN_CLOUDFLARE_KEY),
|
||||
);
|
||||
}
|
||||
|
||||
$wp_args = array(
|
||||
'method' => $method,
|
||||
'headers' => $headers,
|
||||
);
|
||||
|
||||
if ($data) {
|
||||
if (is_array($data)) {
|
||||
$data = wp_json_encode($data);
|
||||
}
|
||||
$wp_args['body'] = $data;
|
||||
}
|
||||
$resp = wp_remote_request($url, $wp_args);
|
||||
if (is_wp_error($resp)) {
|
||||
Debug2::debug('[Cloudflare] error in response');
|
||||
if ($show_msg) {
|
||||
$msg = __('Failed to communicate with Cloudflare', 'litespeed-cache');
|
||||
Admin_Display::error($msg);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
$result = wp_remote_retrieve_body($resp);
|
||||
|
||||
$json = \json_decode($result, true);
|
||||
|
||||
if ($json && $json['success'] && $json['result']) {
|
||||
Debug2::debug('[Cloudflare] cloudflare_call called successfully');
|
||||
if ($show_msg) {
|
||||
$msg = __('Communicated with Cloudflare successfully.', 'litespeed-cache');
|
||||
Admin_Display::success($msg);
|
||||
}
|
||||
|
||||
return $json['result'];
|
||||
}
|
||||
|
||||
Debug2::debug("[Cloudflare] cloudflare_call called failed: $result");
|
||||
if ($show_msg) {
|
||||
$msg = __('Failed to communicate with Cloudflare', 'litespeed-cache');
|
||||
Admin_Display::error($msg);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle all request actions from main cls
|
||||
*
|
||||
* @since 1.7.2
|
||||
* @access public
|
||||
*/
|
||||
public function handler() {
|
||||
$type = Router::verify_type();
|
||||
|
||||
switch ($type) {
|
||||
case self::TYPE_PURGE_ALL:
|
||||
$this->purge_all_private();
|
||||
break;
|
||||
|
||||
case self::TYPE_GET_DEVMODE:
|
||||
$this->get_devmode();
|
||||
break;
|
||||
|
||||
case self::TYPE_SET_DEVMODE_ON:
|
||||
case self::TYPE_SET_DEVMODE_OFF:
|
||||
$this->set_devmode($type);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
Admin::redirect();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
<?php
|
||||
/**
|
||||
* The quic.cloud class.
|
||||
*
|
||||
* @since 2.4.1
|
||||
* @package LiteSpeed
|
||||
* @subpackage LiteSpeed/src/cdn
|
||||
*/
|
||||
|
||||
namespace LiteSpeed\CDN;
|
||||
|
||||
use LiteSpeed\Cloud;
|
||||
use LiteSpeed\Base;
|
||||
|
||||
defined('WPINC') || exit();
|
||||
|
||||
/**
|
||||
* Class Quic
|
||||
*
|
||||
* Handles Quic.cloud CDN integration.
|
||||
*
|
||||
* @since 2.4.1
|
||||
*/
|
||||
class Quic extends Base {
|
||||
const LOG_TAG = '☁️';
|
||||
const TYPE_REG = 'reg';
|
||||
|
||||
/**
|
||||
* Force sync flag.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $force = false;
|
||||
|
||||
/**
|
||||
* Notify CDN new config updated
|
||||
*
|
||||
* Syncs configuration with Quic.cloud CDN.
|
||||
*
|
||||
* @since 2.4.1
|
||||
* @access public
|
||||
* @param bool $force Whether to force sync.
|
||||
* @return bool|void
|
||||
*/
|
||||
public function try_sync_conf( $force = false ) {
|
||||
$cloud_summary = Cloud::get_summary();
|
||||
if ($force) {
|
||||
$this->force = $force;
|
||||
}
|
||||
|
||||
if (!$this->conf(self::O_CDN_QUIC)) {
|
||||
if (!empty($cloud_summary['conf_md5'])) {
|
||||
self::debug('❌ No QC CDN, clear conf md5!');
|
||||
Cloud::save_summary(array( 'conf_md5' => '' ));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Notice: Sync conf must be after `wp_loaded` hook, to get 3rd party vary injected (e.g. `woocommerce_cart_hash`).
|
||||
if (!did_action('wp_loaded')) {
|
||||
add_action('wp_loaded', array( $this, 'try_sync_conf' ), 999);
|
||||
self::debug('WP not loaded yet, delay sync to wp_loaded:999');
|
||||
return;
|
||||
}
|
||||
|
||||
$options = $this->get_options();
|
||||
$options['_tp_cookies'] = apply_filters('litespeed_vary_cookies', array());
|
||||
|
||||
// Build necessary options only
|
||||
$options_needed = array(
|
||||
self::O_CACHE_DROP_QS,
|
||||
self::O_CACHE_EXC_COOKIES,
|
||||
self::O_CACHE_EXC_USERAGENTS,
|
||||
self::O_CACHE_LOGIN_COOKIE,
|
||||
self::O_CACHE_VARY_COOKIES,
|
||||
self::O_CACHE_MOBILE_RULES,
|
||||
self::O_CACHE_MOBILE,
|
||||
self::O_CACHE_BROWSER,
|
||||
self::O_CACHE_TTL_BROWSER,
|
||||
self::O_IMG_OPTM_WEBP,
|
||||
self::O_GUEST,
|
||||
'_tp_cookies',
|
||||
);
|
||||
$consts_needed = array( 'LSWCP_TAG_PREFIX' );
|
||||
$options_for_md5 = array();
|
||||
foreach ($options_needed as $v) {
|
||||
if (isset($options[$v])) {
|
||||
$options_for_md5[$v] = $options[$v];
|
||||
// Remove overflow multi lines fields
|
||||
if (is_array($options_for_md5[$v]) && count($options_for_md5[$v]) > 30) {
|
||||
$options_for_md5[$v] = array_slice($options_for_md5[$v], 0, 30);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$server_vars = $this->server_vars();
|
||||
foreach ($consts_needed as $v) {
|
||||
if (isset($server_vars[$v])) {
|
||||
if (empty($options_for_md5['_server'])) {
|
||||
$options_for_md5['_server'] = array();
|
||||
}
|
||||
$options_for_md5['_server'][$v] = $server_vars[$v];
|
||||
}
|
||||
}
|
||||
|
||||
$conf_md5 = md5(wp_json_encode($options_for_md5));
|
||||
if (!empty($cloud_summary['conf_md5'])) {
|
||||
if ($conf_md5 === $cloud_summary['conf_md5']) {
|
||||
if (!$this->force) {
|
||||
self::debug('Bypass sync conf to QC due to same md5', $conf_md5);
|
||||
return;
|
||||
}
|
||||
self::debug('!!!Force sync conf even same md5');
|
||||
} else {
|
||||
self::debug('[conf_md5] ' . $conf_md5 . ' [existing_conf_md5] ' . $cloud_summary['conf_md5']);
|
||||
}
|
||||
}
|
||||
|
||||
Cloud::save_summary(array( 'conf_md5' => $conf_md5 ));
|
||||
self::debug('sync conf to QC');
|
||||
|
||||
Cloud::post(Cloud::SVC_D_SYNC_CONF, $options_for_md5);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,775 @@
|
||||
<?php
|
||||
/**
|
||||
* The core plugin config class.
|
||||
*
|
||||
* This maintains all the options and settings for this plugin.
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @package LiteSpeed
|
||||
*/
|
||||
|
||||
namespace LiteSpeed;
|
||||
|
||||
defined('WPINC') || exit();
|
||||
|
||||
/**
|
||||
* Class Conf
|
||||
*
|
||||
* Maintains all LiteSpeed plugin configuration, including CRUD for single-site
|
||||
* and multisite options, upgrade flows, and side effects like purging/cron.
|
||||
*/
|
||||
class Conf extends Base {
|
||||
|
||||
const TYPE_SET = 'set';
|
||||
|
||||
/**
|
||||
* IDs that were updated during a save cycle.
|
||||
*
|
||||
* @var array<string|int,mixed>
|
||||
*/
|
||||
private $_updated_ids = [];
|
||||
|
||||
/**
|
||||
* Whether current blog is the network primary site.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $_is_primary = false;
|
||||
|
||||
/**
|
||||
* Specify init logic to avoid infinite loop when calling conf.cls instance
|
||||
*
|
||||
* @since 3.0
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
public function init() {
|
||||
// Check if conf exists or not. If not, create them in DB (won't change version if is converting v2.9- data)
|
||||
// Conf may be stale, upgrade later
|
||||
$this->_conf_db_init();
|
||||
|
||||
/**
|
||||
* Detect if has quic.cloud set
|
||||
*
|
||||
* @since 2.9.7
|
||||
*/
|
||||
if ( $this->conf( self::O_CDN_QUIC ) ) {
|
||||
if ( ! defined( 'LITESPEED_ALLOWED' ) ) {
|
||||
define( 'LITESPEED_ALLOWED', true );
|
||||
}
|
||||
}
|
||||
|
||||
add_action( 'litespeed_conf_append', [ $this, 'option_append' ], 10, 2 );
|
||||
add_action( 'litespeed_conf_force', [ $this, 'force_option' ], 10, 2 );
|
||||
|
||||
$this->define_cache();
|
||||
}
|
||||
|
||||
/**
|
||||
* Init conf related data
|
||||
*
|
||||
* @since 3.0
|
||||
* @access private
|
||||
* @return void
|
||||
*/
|
||||
private function _conf_db_init() {
|
||||
/**
|
||||
* Try to load options first, network sites can override this later
|
||||
*
|
||||
* NOTE: Load before run `conf_upgrade()` to avoid infinite loop when getting conf in `conf_upgrade()`
|
||||
*/
|
||||
$this->load_options();
|
||||
|
||||
// Check if debug is on
|
||||
// Init debug as early as possible
|
||||
if ( $this->conf( Base::O_DEBUG ) ) {
|
||||
$this->cls( 'Debug2' )->init();
|
||||
}
|
||||
|
||||
$ver = $this->conf( self::_VER );
|
||||
|
||||
/**
|
||||
* Version is less than v3.0, or, is a new installation
|
||||
*/
|
||||
$ver_check_tag = 'new';
|
||||
if ( $ver ) {
|
||||
if ( ! defined( 'LSCWP_CUR_V' ) ) {
|
||||
define( 'LSCWP_CUR_V', $ver );
|
||||
}
|
||||
|
||||
/**
|
||||
* Upgrade conf
|
||||
*/
|
||||
if ( Core::VER !== $ver ) {
|
||||
// Plugin version will be set inside
|
||||
// Site plugin upgrade & version change will do in load_site_conf
|
||||
$ver_check_tag = Data::cls()->conf_upgrade( $ver );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync latest new options
|
||||
*/
|
||||
if ( ! $ver || Core::VER !== $ver ) {
|
||||
// Load default values
|
||||
$this->load_default_vals();
|
||||
|
||||
if ( ! $ver ) {
|
||||
// New install
|
||||
$this->set_conf( self::$_default_options );
|
||||
|
||||
$ver_check_tag .= ' activate' . ( defined( 'LSCWP_REF' ) ? '_' . constant( 'LSCWP_REF' ) : '' );
|
||||
}
|
||||
|
||||
// Init new default/missing options
|
||||
foreach ( self::$_default_options as $k => $v ) {
|
||||
// If the option existed, bypass updating
|
||||
// Bcos we may ask clients to deactivate for debug temporarily, we need to keep the current cfg in deactivation, hence we need to only try adding default cfg when activating.
|
||||
self::add_option( $k, $v );
|
||||
}
|
||||
|
||||
// Force correct version in case a rare unexpected case that `_ver` exists but empty
|
||||
self::update_option( Base::_VER, Core::VER );
|
||||
|
||||
if ( $ver_check_tag ) {
|
||||
Cloud::version_check( $ver_check_tag );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Network sites only
|
||||
*
|
||||
* Override conf if is network subsites and chose `Use Primary Config`
|
||||
*/
|
||||
$this->_try_load_site_options();
|
||||
|
||||
// Check if debug is on
|
||||
// Init debug as early as possible
|
||||
if ( $this->conf( Base::O_DEBUG ) ) {
|
||||
$this->cls( 'Debug2' )->init();
|
||||
}
|
||||
|
||||
// Mark as conf loaded
|
||||
if ( ! defined( 'LITESPEED_CONF_LOADED' ) ) {
|
||||
define( 'LITESPEED_CONF_LOADED', true );
|
||||
}
|
||||
|
||||
if ( ! $ver || Core::VER !== $ver ) {
|
||||
// Only trigger once in upgrade progress, don't run always
|
||||
$this->update_confs(); // Files only get corrected in activation or saving settings actions.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load all latest options from DB
|
||||
*
|
||||
* @since 3.0
|
||||
* @access public
|
||||
*
|
||||
* @param int|null $blog_id Blog ID to load from. Null for current.
|
||||
* @param bool $dry_run Return options instead of setting them.
|
||||
* @return array<string,mixed>|void
|
||||
*/
|
||||
public function load_options( $blog_id = null, $dry_run = false ) {
|
||||
$options = [];
|
||||
foreach ( self::$_default_options as $k => $v ) {
|
||||
if ( null !== $blog_id ) {
|
||||
$options[ $k ] = self::get_blog_option( $blog_id, $k, $v );
|
||||
} else {
|
||||
$options[ $k ] = self::get_option( $k, $v );
|
||||
}
|
||||
|
||||
// Correct value type
|
||||
$options[ $k ] = $this->type_casting( $options[ $k ], $k );
|
||||
}
|
||||
|
||||
if ( $dry_run ) {
|
||||
return $options;
|
||||
}
|
||||
|
||||
// Bypass site special settings
|
||||
if ( null !== $blog_id ) {
|
||||
// This is to load the primary settings ONLY
|
||||
// These options are the ones that can be overwritten by primary
|
||||
$options = array_diff_key( $options, array_flip( self::$single_site_options ) );
|
||||
|
||||
$this->set_primary_conf( $options );
|
||||
} else {
|
||||
$this->set_conf( $options );
|
||||
}
|
||||
|
||||
// Append const options
|
||||
if ( defined( 'LITESPEED_CONF' ) && LITESPEED_CONF ) {
|
||||
foreach ( self::$_default_options as $k => $v ) {
|
||||
$const = Base::conf_const( $k );
|
||||
if ( defined( $const ) ) {
|
||||
$this->set_const_conf( $k, $this->type_casting( constant( $const ), $k ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For multisite installations, the single site options need to be updated with the network wide options.
|
||||
*
|
||||
* @since 1.0.13
|
||||
* @access private
|
||||
* @return void
|
||||
*/
|
||||
private function _try_load_site_options() {
|
||||
if ( ! $this->_if_need_site_options() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->_conf_site_db_init();
|
||||
|
||||
$this->_is_primary = BLOG_ID_CURRENT_SITE === get_current_blog_id();
|
||||
|
||||
// If network set to use primary setting
|
||||
if ( $this->network_conf( self::NETWORK_O_USE_PRIMARY ) && ! $this->_is_primary ) {
|
||||
// subsites or network admin
|
||||
// Get the primary site settings
|
||||
// If it's just upgraded, 2nd blog is being visited before primary blog, can just load default config (won't hurt as this could only happen shortly)
|
||||
$this->load_options( BLOG_ID_CURRENT_SITE );
|
||||
}
|
||||
|
||||
// Overwrite single blog options with site options
|
||||
foreach ( self::$_default_options as $k => $v ) {
|
||||
if ( ! $this->has_network_conf( $k ) ) {
|
||||
continue;
|
||||
}
|
||||
// $this->_options[ $k ] = $this->_network_options[ $k ];
|
||||
|
||||
// Special handler to `Enable Cache` option if the value is set to OFF
|
||||
if ( self::O_CACHE === $k ) {
|
||||
if ( $this->_is_primary ) {
|
||||
if ( $this->conf( $k ) !== $this->network_conf( $k ) ) {
|
||||
if ( self::VAL_ON2 !== $this->conf( $k ) ) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} elseif ( $this->network_conf( self::NETWORK_O_USE_PRIMARY ) ) {
|
||||
if ( $this->has_primary_conf( $k ) && self::VAL_ON2 !== $this->primary_conf( $k ) ) {
|
||||
// This case will use primary_options override always
|
||||
continue;
|
||||
}
|
||||
} elseif ( self::VAL_ON2 !== $this->conf( $k ) ) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// primary_options will store primary settings + network settings, OR, store the network settings for subsites
|
||||
$this->set_primary_conf( $k, $this->network_conf( $k ) );
|
||||
}
|
||||
// var_dump($this->_options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if needs to load site_options for network sites
|
||||
*
|
||||
* @since 3.0
|
||||
* @access private
|
||||
* @return bool
|
||||
*/
|
||||
private function _if_need_site_options() {
|
||||
if ( ! is_multisite() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if needs to use site_options or not
|
||||
// todo: check if site settings are separate bcos it will affect .htaccess
|
||||
|
||||
/**
|
||||
* In case this is called outside the admin page
|
||||
*
|
||||
* @see https://codex.wordpress.org/Function_Reference/is_plugin_active_for_network
|
||||
* @since 2.0
|
||||
*/
|
||||
if ( ! function_exists( 'is_plugin_active_for_network' ) ) {
|
||||
require_once ABSPATH . '/wp-admin/includes/plugin.php';
|
||||
}
|
||||
// If is not activated on network, it will not have site options
|
||||
if ( ! is_plugin_active_for_network( Core::PLUGIN_FILE ) ) {
|
||||
if ( self::VAL_ON2 === (int) $this->conf( self::O_CACHE ) ) {
|
||||
// Default to cache on
|
||||
$this->set_conf( self::_CACHE, true );
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Init site conf and upgrade if necessary
|
||||
*
|
||||
* @since 3.0
|
||||
* @access private
|
||||
* @return void
|
||||
*/
|
||||
private function _conf_site_db_init() {
|
||||
$this->load_site_options();
|
||||
|
||||
$ver = $this->network_conf( self::_VER );
|
||||
|
||||
/**
|
||||
* Don't upgrade or run new installations other than from backend visit
|
||||
* In this case, just use default conf
|
||||
*/
|
||||
if ( ! $ver || Core::VER !== $ver ) {
|
||||
if ( ! is_admin() && ! defined( 'LITESPEED_CLI' ) ) {
|
||||
$this->set_network_conf( $this->load_default_site_vals() );
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Upgrade conf
|
||||
*/
|
||||
if ( $ver && Core::VER !== $ver ) {
|
||||
// Site plugin version will change inside
|
||||
Data::cls()->conf_site_upgrade( $ver );
|
||||
}
|
||||
|
||||
/**
|
||||
* Is a new installation
|
||||
*/
|
||||
if ( ! $ver || Core::VER !== $ver ) {
|
||||
// Load default values
|
||||
$this->load_default_site_vals();
|
||||
|
||||
// Init new default/missing options
|
||||
foreach ( self::$_default_site_options as $k => $v ) {
|
||||
// If the option existed, bypass updating
|
||||
self::add_site_option( $k, $v );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the plugin's site wide options.
|
||||
*
|
||||
* If the site wide options are not set yet, set it to default.
|
||||
*
|
||||
* @since 1.0.2
|
||||
* @access public
|
||||
* @return null|void
|
||||
*/
|
||||
public function load_site_options() {
|
||||
if ( ! is_multisite() ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Load all site options
|
||||
foreach ( self::$_default_site_options as $k => $v ) {
|
||||
$val = self::get_site_option( $k, $v );
|
||||
$val = $this->type_casting( $val, $k, true );
|
||||
$this->set_network_conf( $k, $val );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Append a 3rd party option to default options
|
||||
*
|
||||
* This will not be affected by network use primary site setting.
|
||||
*
|
||||
* NOTE: If it is a multi switch option, need to call `_conf_multi_switch()` first
|
||||
*
|
||||
* @since 3.0
|
||||
* @access public
|
||||
*
|
||||
* @param string $name Option name.
|
||||
* @param mixed $default_val Default value.
|
||||
* @return void
|
||||
*/
|
||||
public function option_append( $name, $default_val ) {
|
||||
self::$_default_options[ $name ] = $default_val;
|
||||
$this->set_conf( $name, self::get_option( $name, $default_val ) );
|
||||
$this->set_conf( $name, $this->type_casting( $this->conf( $name ), $name ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Force an option to a certain value
|
||||
*
|
||||
* @since 2.6
|
||||
* @access public
|
||||
*
|
||||
* @param string $k Option key.
|
||||
* @param mixed $v Option value.
|
||||
* @return void
|
||||
*/
|
||||
public function force_option( $k, $v ) {
|
||||
if ( ! $this->has_conf( $k ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$v = $this->type_casting( $v, $k );
|
||||
|
||||
if ( $this->conf( $k ) === $v ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_var_export
|
||||
Debug2::debug( '[Conf] ** ' . $k . ' forced from ' . var_export( $this->conf( $k ), true ) . ' to ' . var_export( $v, true ) );
|
||||
|
||||
$this->set_conf( $k, $v );
|
||||
}
|
||||
|
||||
/**
|
||||
* Define `_CACHE` const in options ( for both single and network )
|
||||
*
|
||||
* @since 3.0
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
public function define_cache() {
|
||||
// Init global const cache on setting
|
||||
$this->set_conf( self::_CACHE, false );
|
||||
if ( self::VAL_ON === (int) $this->conf( self::O_CACHE ) || $this->conf( self::O_CDN_QUIC ) ) {
|
||||
$this->set_conf( self::_CACHE, true );
|
||||
}
|
||||
|
||||
// Check network
|
||||
if ( ! $this->_if_need_site_options() ) {
|
||||
// Set cache on
|
||||
$this->_define_cache_on();
|
||||
return;
|
||||
}
|
||||
|
||||
// If use network setting
|
||||
if ( self::VAL_ON2 === (int) $this->conf( self::O_CACHE ) && $this->network_conf( self::O_CACHE ) ) {
|
||||
$this->set_conf( self::_CACHE, true );
|
||||
}
|
||||
|
||||
$this->_define_cache_on();
|
||||
}
|
||||
|
||||
/**
|
||||
* Define `LITESPEED_ON`
|
||||
*
|
||||
* @since 2.1
|
||||
* @access private
|
||||
* @return void
|
||||
*/
|
||||
private function _define_cache_on() {
|
||||
if ( ! $this->conf( self::_CACHE ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( defined( 'LITESPEED_ALLOWED' ) && ! defined( 'LITESPEED_ON' ) ) {
|
||||
define( 'LITESPEED_ON', true );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save option
|
||||
*
|
||||
* @since 3.0
|
||||
* @access public
|
||||
*
|
||||
* @param array<string,mixed> $the_matrix Option-value map.
|
||||
* @return void
|
||||
*/
|
||||
public function update_confs( $the_matrix = [] ) {
|
||||
if ( $the_matrix ) {
|
||||
foreach ( $the_matrix as $id => $val ) {
|
||||
$this->update( $id, $val );
|
||||
}
|
||||
}
|
||||
|
||||
if ( $this->_updated_ids ) {
|
||||
foreach ( $this->_updated_ids as $id ) {
|
||||
// Check if need to do a purge all or not
|
||||
if ( $this->_conf_purge_all( $id ) ) {
|
||||
Purge::purge_all( 'conf changed [id] ' . $id );
|
||||
}
|
||||
|
||||
// Check if need to purge a tag
|
||||
$tag = $this->_conf_purge_tag( $id );
|
||||
if ( $tag ) {
|
||||
Purge::add( $tag );
|
||||
}
|
||||
|
||||
// Update cron
|
||||
if ( $this->_conf_cron( $id ) ) {
|
||||
$this->cls( 'Task' )->try_clean( $id );
|
||||
}
|
||||
|
||||
// Reset crawler bypassed list when any of the options WebP replace, guest mode, or cache mobile got changed
|
||||
if ( self::O_IMG_OPTM_WEBP === $id || self::O_GUEST === $id || self::O_CACHE_MOBILE === $id ) {
|
||||
$this->cls( 'Crawler' )->clear_disabled_list();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
do_action( 'litespeed_update_confs', $the_matrix );
|
||||
|
||||
// Update related tables
|
||||
$this->cls( 'Data' )->correct_tb_existence();
|
||||
|
||||
// Update related files
|
||||
$this->cls( 'Activation' )->update_files();
|
||||
|
||||
/**
|
||||
* CDN related actions - Cloudflare
|
||||
*/
|
||||
$this->cls( 'CDN\Cloudflare' )->try_refresh_zone();
|
||||
|
||||
// If Server IP changed, must test echo
|
||||
if ( in_array( self::O_SERVER_IP, $this->_updated_ids, true ) ) {
|
||||
$this->cls( 'Cloud' )->init_qc_cli();
|
||||
}
|
||||
|
||||
// CDN related actions - QUIC.cloud
|
||||
$this->cls( 'CDN\Quic' )->try_sync_conf();
|
||||
}
|
||||
|
||||
/**
|
||||
* Save option
|
||||
*
|
||||
* Note: this is direct save, won't trigger corresponding file update or data sync. To save settings normally, always use `Conf->update_confs()`
|
||||
*
|
||||
* @since 3.0
|
||||
* @access public
|
||||
*
|
||||
* @param string $id Option ID.
|
||||
* @param mixed $val Option value.
|
||||
* @return void
|
||||
*/
|
||||
public function update( $id, $val ) {
|
||||
// Bypassed this bcos $this->_options could be changed by force_option()
|
||||
// if ( $this->_options[ $id ] === $val ) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
if ( self::_VER === $id ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( self::O_SERVER_IP === $id ) {
|
||||
if ( $val && ! Utility::valid_ipv4( $val ) ) {
|
||||
$msg = sprintf( __( 'Saving option failed. IPv4 only for %s.', 'litespeed-cache' ), Lang::title( Base::O_SERVER_IP ) );
|
||||
Admin_Display::error( $msg );
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! array_key_exists( $id, self::$_default_options ) ) {
|
||||
if ( defined( 'LSCWP_LOG' ) ) {
|
||||
Debug2::debug( '[Conf] Invalid option ID ' . $id );
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if ( $val && $this->_conf_pswd( $id ) && ! preg_match( '/[^\*]/', (string) $val ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Special handler for CDN Original URLs
|
||||
if ( self::O_CDN_ORI === $id && ! $val ) {
|
||||
$site_url = site_url( '/' );
|
||||
$parsed = wp_parse_url( $site_url );
|
||||
if ( !empty( $parsed['scheme'] ) ) {
|
||||
$site_url = str_replace( $parsed['scheme'] . ':', '', $site_url );
|
||||
}
|
||||
|
||||
$val = $site_url;
|
||||
}
|
||||
|
||||
// Validate type
|
||||
$val = $this->type_casting( $val, $id );
|
||||
|
||||
// Save data
|
||||
self::update_option( $id, $val );
|
||||
|
||||
// Handle purge if setting changed
|
||||
if ( $this->conf( $id ) !== $val ) {
|
||||
$this->_updated_ids[] = $id;
|
||||
|
||||
// Check if need to fire a purge or not (Here has to stay inside `update()` bcos need comparing old value)
|
||||
if ( $this->_conf_purge( $id ) ) {
|
||||
$old = (array) $this->conf( $id );
|
||||
$new = (array) $val;
|
||||
$diff = array_merge( array_diff( $new, $old ), array_diff( $old, $new ) );
|
||||
// If has difference
|
||||
foreach ( $diff as $v ) {
|
||||
$v = ltrim( (string) $v, '^' );
|
||||
$v = rtrim( (string) $v, '$' );
|
||||
$this->cls( 'Purge' )->purge_url( $v );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update in-memory data
|
||||
$this->set_conf( $id, $val );
|
||||
}
|
||||
|
||||
/**
|
||||
* Save network option
|
||||
*
|
||||
* @since 3.0
|
||||
* @access public
|
||||
*
|
||||
* @param string $id Option ID.
|
||||
* @param mixed $val Option value.
|
||||
* @return void
|
||||
*/
|
||||
public function network_update( $id, $val ) {
|
||||
if ( ! array_key_exists( $id, self::$_default_site_options ) ) {
|
||||
if ( defined( 'LSCWP_LOG' ) ) {
|
||||
Debug2::debug( '[Conf] Invalid network option ID ' . $id );
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if ( $val && $this->_conf_pswd( $id ) && ! preg_match( '/[^\*]/', (string) $val ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate type
|
||||
if ( is_bool( self::$_default_site_options[ $id ] ) ) {
|
||||
$max = $this->_conf_multi_switch( $id );
|
||||
if ( $max && $val > 1 ) {
|
||||
$val %= ( $max + 1 );
|
||||
} else {
|
||||
$val = (bool) $val;
|
||||
}
|
||||
} elseif ( is_array( self::$_default_site_options[ $id ] ) ) {
|
||||
// from textarea input
|
||||
if ( ! is_array( $val ) ) {
|
||||
$val = Utility::sanitize_lines( $val, $this->_conf_filter( $id ) );
|
||||
}
|
||||
} elseif ( ! is_string( self::$_default_site_options[ $id ] ) ) {
|
||||
$val = (int) $val;
|
||||
} else {
|
||||
// Check if the string has a limit set
|
||||
$val = $this->_conf_string_val( $id, $val );
|
||||
}
|
||||
|
||||
// Save data
|
||||
self::update_site_option( $id, $val );
|
||||
|
||||
// Handle purge if setting changed
|
||||
if ( $this->network_conf( $id ) !== $val ) {
|
||||
// Check if need to do a purge all or not
|
||||
if ( $this->_conf_purge_all( $id ) ) {
|
||||
Purge::purge_all( '[Conf] Network conf changed [id] ' . $id );
|
||||
}
|
||||
|
||||
// Update in-memory data
|
||||
$this->set_network_conf( $id, $val );
|
||||
}
|
||||
|
||||
// No need to update cron here, Cron will register in each init
|
||||
|
||||
if ( $this->has_conf( $id ) ) {
|
||||
$this->set_conf( $id, $val );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if one user role is in exclude optimization group settings
|
||||
*
|
||||
* @since 1.6
|
||||
* @access public
|
||||
*
|
||||
* @param string|null $role The user role.
|
||||
* @return string|false The set value if already set, otherwise false.
|
||||
*/
|
||||
public function in_optm_exc_roles( $role = null ) {
|
||||
// Get user role
|
||||
if ( null === $role ) {
|
||||
$role = Router::get_role();
|
||||
}
|
||||
|
||||
if ( ! $role ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$roles = explode( ',', $role );
|
||||
$found = array_intersect( $roles, $this->conf( self::O_OPTM_EXC_ROLES ) );
|
||||
|
||||
return $found ? implode( ',', $found ) : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set one config value directly
|
||||
*
|
||||
* @since 2.9
|
||||
* @access private
|
||||
* @return void
|
||||
*/
|
||||
private function _set_conf() {
|
||||
/**
|
||||
* NOTE: For URL Query String setting,
|
||||
* 1. If append lines to an array setting e.g. `cache-force_uri`, use `set[cache-force_uri][]=the_url`.
|
||||
* 2. If replace the array setting with one line, use `set[cache-force_uri]=the_url`.
|
||||
* 3. If replace the array setting with multi lines value, use 2 then 1.
|
||||
*/
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput
|
||||
$raw = !empty( $_GET[ self::TYPE_SET ] ) ? $_GET[ self::TYPE_SET ] : false;
|
||||
if ( !$raw || ! is_array( $raw ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Sanitize the incoming matrix.
|
||||
$the_matrix = [];
|
||||
foreach ( $raw as $id => $v ) {
|
||||
if ( ! $this->has_conf( $id ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Append new item to array type settings
|
||||
if ( is_array( $v ) && is_array( $this->conf( $id ) ) ) {
|
||||
$v = array_merge( $this->conf( $id ), $v );
|
||||
|
||||
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_var_export
|
||||
Debug2::debug( '[Conf] Appended to settings [' . $id . ']: ' . var_export( $v, true ) );
|
||||
} else {
|
||||
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_var_export
|
||||
Debug2::debug( '[Conf] Set setting [' . $id . ']: ' . var_export( $v, true ) );
|
||||
}
|
||||
|
||||
$the_matrix[ $id ] = $v;
|
||||
}
|
||||
|
||||
if ( !$the_matrix ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->update_confs( $the_matrix );
|
||||
|
||||
$msg = __( 'Changed setting successfully.', 'litespeed-cache' );
|
||||
Admin_Display::success( $msg );
|
||||
|
||||
// Redirect if changed frontend URL
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$redirect = ! empty( $_GET['redirect'] ) ? sanitize_text_field( wp_unslash( $_GET['redirect'] ) ) : '';
|
||||
if ( $redirect ) {
|
||||
wp_safe_redirect( $redirect );
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle all request actions from main cls
|
||||
*
|
||||
* @since 2.9
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
public function handler() {
|
||||
$type = Router::verify_type();
|
||||
|
||||
switch ( $type ) {
|
||||
case self::TYPE_SET:
|
||||
$this->_set_conf();
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
Admin::redirect();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,956 @@
|
||||
<?php
|
||||
/**
|
||||
* The plugin cache-control class for X-LiteSpeed-Cache-Control.
|
||||
*
|
||||
* Provides helpers for determining cacheability, emitting cache-control headers,
|
||||
* and honoring various LiteSpeed Cache configuration options.
|
||||
*
|
||||
* @package LiteSpeed
|
||||
* @since 1.1.3
|
||||
*/
|
||||
|
||||
namespace LiteSpeed;
|
||||
|
||||
defined( 'WPINC' ) || exit();
|
||||
|
||||
/**
|
||||
* Class Control
|
||||
*
|
||||
* Handles cache-control flags, TTL calculation, redirection checks,
|
||||
* role-based exclusions, and final header output.
|
||||
*/
|
||||
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';
|
||||
|
||||
/**
|
||||
* Bitmask control flags for current request.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected static $_control = 0;
|
||||
|
||||
/**
|
||||
* Custom TTL for current request (seconds).
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected static $_custom_ttl = 0;
|
||||
|
||||
/**
|
||||
* Mapping of HTTP status codes to custom TTLs.
|
||||
*
|
||||
* @var array<string,int|string>
|
||||
*/
|
||||
private $_response_header_ttls = [];
|
||||
|
||||
/**
|
||||
* Init cache control.
|
||||
*
|
||||
* @since 1.6.2
|
||||
* @return void
|
||||
*/
|
||||
public function init() {
|
||||
/**
|
||||
* Add vary filter for Role Excludes.
|
||||
*
|
||||
* @since 1.6.2
|
||||
*/
|
||||
add_filter( 'litespeed_vary', [ $this, 'vary_add_role_exclude' ] );
|
||||
|
||||
// 301 redirect hook.
|
||||
add_filter( 'wp_redirect', [ $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
|
||||
*
|
||||
* @param array<string,mixed> $vary Existing vary map.
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
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|null $role The user role.
|
||||
* @return string|false Comma-separated roles if set, otherwise false.
|
||||
*/
|
||||
public function in_cache_exc_roles( $role = null ) {
|
||||
// Get user role.
|
||||
if ( null === $role ) {
|
||||
$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
|
||||
* @return void
|
||||
*/
|
||||
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', [ $this, 'set_cacheable' ], 5 );
|
||||
|
||||
// Hook WP REST to be cacheable.
|
||||
if ( $this->conf( Base::O_CACHE_REST ) ) {
|
||||
add_action( 'rest_api_init', [ $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;
|
||||
}
|
||||
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', [ $this, 'check_error_codes' ], 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the page returns any error code.
|
||||
*
|
||||
* @since 1.0.13.1
|
||||
* @access public
|
||||
*
|
||||
* @param string $status_header Status header.
|
||||
* @param int $code HTTP status code.
|
||||
* @return string Original status header.
|
||||
*/
|
||||
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() ) {
|
||||
$first = substr( $code, 0, 1 );
|
||||
if ( '4' === $first || '5' === $first ) {
|
||||
self::set_nocache( '[Ctrl] 4xx/5xx default to no cache [status_header] ' . $code );
|
||||
}
|
||||
}
|
||||
|
||||
// Set cache tag.
|
||||
if ( in_array( $code, Tag::$error_code_tags, true ) ) {
|
||||
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
|
||||
* @return void
|
||||
*/
|
||||
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
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_no_vary() {
|
||||
return self::$_control & self::BM_NO_VARY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set stale.
|
||||
*
|
||||
* @access public
|
||||
* @since 1.1.3
|
||||
* @return void
|
||||
*/
|
||||
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
|
||||
* @return bool
|
||||
*/
|
||||
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|false $reason The reason to mark shared, or false.
|
||||
* @return void
|
||||
*/
|
||||
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
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_shared() {
|
||||
return (bool) ( self::$_control & self::BM_SHARED ) && self::is_private();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set cache control to forced public.
|
||||
*
|
||||
* @access public
|
||||
* @since 1.7.1
|
||||
*
|
||||
* @param string|false $reason Reason text or false.
|
||||
* @return void
|
||||
*/
|
||||
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
|
||||
* @return bool
|
||||
*/
|
||||
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|false $reason The reason to set private.
|
||||
* @return void
|
||||
*/
|
||||
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
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_private() {
|
||||
// if ( defined( 'LITESPEED_GUEST' ) && LITESPEED_GUEST ) {
|
||||
// return false;
|
||||
// }
|
||||
|
||||
return (bool) ( 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
|
||||
*
|
||||
* @param string|false $reason Reason text or false.
|
||||
* @return void
|
||||
*/
|
||||
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
|
||||
*
|
||||
* @param string|false $reason Reason text or false.
|
||||
* @return void
|
||||
*/
|
||||
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|false $reason The reason to no cache.
|
||||
* @return void
|
||||
*/
|
||||
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
|
||||
* @return bool
|
||||
*/
|
||||
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 it's forced public cacheable.
|
||||
if ( self::is_public_forced() ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If it's 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 int|string $ttl An integer or numeric string to use as the TTL.
|
||||
* @param string|false $reason Optional reason text.
|
||||
* @return void
|
||||
*/
|
||||
public static function set_custom_ttl( $ttl, $reason = false ) {
|
||||
if ( is_numeric( $ttl ) ) {
|
||||
self::$_custom_ttl = (int) $ttl;
|
||||
self::debug( 'X Cache_control TTL -> ' . $ttl . ( $reason ? ' [reason] ' . $ttl : '' ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate final TTL.
|
||||
*
|
||||
* @access public
|
||||
* @since 1.1.3
|
||||
* @return int
|
||||
*/
|
||||
public function get_ttl() {
|
||||
if ( 0 !== self::$_custom_ttl ) {
|
||||
return (int) 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 - current_time('timestamp'); // phpcs:ignore WordPress.DateTime.CurrentTimeTimestamp
|
||||
if ( $ttl < 0 ) {
|
||||
$ttl += 86400; // add one day
|
||||
}
|
||||
foreach ( $timed_urls as $v ) {
|
||||
if ( false !== strpos( $v, '*' ) ) {
|
||||
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;
|
||||
}
|
||||
} elseif ( $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 (int) $this->conf( Base::O_CACHE_TTL_PRIV );
|
||||
}
|
||||
|
||||
if ( is_front_page() ) {
|
||||
return (int) $this->conf( Base::O_CACHE_TTL_FRONTPAGE );
|
||||
}
|
||||
|
||||
$feed_ttl = (int) $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 (int) $this->conf( Base::O_CACHE_TTL_REST );
|
||||
}
|
||||
|
||||
return (int) $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
|
||||
*
|
||||
* @param string $location Redirect location.
|
||||
* @param int $status HTTP status.
|
||||
* @return string Redirect location.
|
||||
*/
|
||||
public function check_redirect( $location, $status ) {
|
||||
$script_uri = '';
|
||||
if ( !empty( $_SERVER['SCRIPT_URI'] ) ) {
|
||||
$script_uri = sanitize_text_field( wp_unslash( $_SERVER['SCRIPT_URI'] ) );
|
||||
} elseif ( !empty( $_SERVER['REQUEST_URI'] ) ) {
|
||||
$home = trailingslashit( home_url() );
|
||||
$script_uri = $home . ltrim( sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ), '/' );
|
||||
}
|
||||
|
||||
if ( '' !== $script_uri ) {
|
||||
self::debug( '301 from ' . $script_uri );
|
||||
self::debug( '301 to ' . $location );
|
||||
|
||||
$to_check = [ PHP_URL_SCHEME, PHP_URL_HOST, PHP_URL_PATH, PHP_URL_QUERY ];
|
||||
|
||||
$is_same_redirect = true;
|
||||
|
||||
$query_string = ! empty( $_SERVER['QUERY_STRING'] ) ? sanitize_text_field( wp_unslash( $_SERVER['QUERY_STRING'] ) ) : '';
|
||||
foreach ( $to_check as $v ) {
|
||||
$url_parsed = PHP_URL_QUERY === $v ? $query_string : wp_parse_url( $script_uri, $v );
|
||||
|
||||
$target = wp_parse_url( $location, $v );
|
||||
|
||||
self::debug( 'Compare [from] ' . $url_parsed . ' [to] ' . $target );
|
||||
|
||||
if ( PHP_URL_QUERY === $v ) {
|
||||
$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 . ': ';
|
||||
|
||||
// phpcs:ignore WordPress.NamingConventions.ValidHookName.NotLowercase
|
||||
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
|
||||
* @return void
|
||||
*/
|
||||
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 );
|
||||
$req_uri = isset( $_SERVER['REQUEST_URI'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : '';
|
||||
$hit = Utility::str_hit_array( $req_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( $req_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 ] ) ) {
|
||||
self::set_nocache( 'pswd cookie' );
|
||||
return;
|
||||
}
|
||||
|
||||
// The following check to the end is ONLY for mobile.
|
||||
$is_mobile_conf = apply_filters( 'litespeed_is_mobile', false );
|
||||
if ( ! $this->conf( Base::O_CACHE_MOBILE ) ) {
|
||||
if ( $is_mobile_conf ) {
|
||||
self::set_nocache( 'mobile' );
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
$env_vary = isset( $_SERVER['LSCACHE_VARY_VALUE'] ) ? sanitize_text_field( wp_unslash( $_SERVER['LSCACHE_VARY_VALUE'] ) ) : '';
|
||||
if ( !$env_vary && isset( $_SERVER['HTTP_X_LSCACHE_VARY_VALUE'] ) ) {
|
||||
$env_vary = sanitize_text_field( wp_unslash( $_SERVER['HTTP_X_LSCACHE_VARY_VALUE'] ) );
|
||||
}
|
||||
if ( $env_vary && false !== strpos( $env_vary, 'ismobile' ) ) {
|
||||
if ( ! wp_is_mobile() && ! $is_mobile_conf ) {
|
||||
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_conf ) {
|
||||
self::set_nocache( 'is mobile' );
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if is mobile for filter `litespeed_is_mobile` in API.
|
||||
*
|
||||
* @since 3.0
|
||||
* @access public
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_mobile() {
|
||||
return wp_is_mobile();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get request method w/ compatibility to X-Http-Method-Override.
|
||||
*
|
||||
* @since 6.2
|
||||
* @return string
|
||||
*/
|
||||
private function _get_req_method() {
|
||||
if ( isset( $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'] ) ) {
|
||||
$override = sanitize_text_field( wp_unslash( $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'] ) );
|
||||
self::debug( 'X-Http-Method-Override -> ' . $override );
|
||||
if ( ! defined( 'LITESPEED_X_HTTP_METHOD_OVERRIDE' ) ) {
|
||||
define( 'LITESPEED_X_HTTP_METHOD_OVERRIDE', true );
|
||||
}
|
||||
return $override;
|
||||
}
|
||||
if ( isset( $_SERVER['REQUEST_METHOD'] ) ) {
|
||||
return sanitize_text_field( wp_unslash( $_SERVER['REQUEST_METHOD'] ) );
|
||||
}
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a page is cacheable based on litespeed setting.
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @access private
|
||||
* @return bool True if cacheable, false otherwise.
|
||||
*/
|
||||
private function _setting_cacheable() {
|
||||
// logged_in users already excluded, no hook added.
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
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 && 'HEAD' === $method ) {
|
||||
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() && 0 === $this->conf( Base::O_CACHE_TTL_FEED ) ) {
|
||||
return $this->_no_cache_for( 'feed' );
|
||||
}
|
||||
|
||||
if ( is_trackback() ) {
|
||||
return $this->_no_cache_for( 'trackback' );
|
||||
}
|
||||
|
||||
if ( is_search() ) {
|
||||
return $this->_no_cache_for( 'search' );
|
||||
}
|
||||
|
||||
// Check private cache URI setting.
|
||||
$excludes = $this->conf( Base::O_CACHE_PRIV_URI );
|
||||
$req_uri = isset( $_SERVER['REQUEST_URI'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : '';
|
||||
$result = Utility::str_hit_array( $req_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( $req_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 );
|
||||
$qs_hit = $this->_is_qs_excluded( $excludes );
|
||||
if ( ! empty( $excludes ) && $qs_hit ) {
|
||||
return $this->_no_cache_for( 'Admin configured QS Do not cache: ' . $qs_hit );
|
||||
}
|
||||
|
||||
$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 );
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- names only, compared as keys.
|
||||
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 ), sanitize_text_field( wp_unslash( $_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 ).
|
||||
$result = $this->in_cache_exc_roles();
|
||||
if ( $result ) {
|
||||
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 bool Always 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<int,string> $excludes QS excludes setting.
|
||||
* @return bool|string False if not excluded, otherwise the hit qs list.
|
||||
*/
|
||||
private function _is_qs_excluded( $excludes ) {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
if ( ! empty( $_GET ) ) {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$keys = array_keys( $_GET );
|
||||
$intersect = array_intersect( $keys, $excludes );
|
||||
if ( $intersect ) {
|
||||
return implode( ',', $intersect );
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,738 @@
|
||||
<?php
|
||||
/**
|
||||
* The core plugin class.
|
||||
*
|
||||
* This is the main class for the LiteSpeed Cache plugin, responsible for initializing
|
||||
* the plugin's core functionality, registering hooks, and handling cache-related operations.
|
||||
*
|
||||
* Note: Core doesn't allow $this->cls( 'Core' )
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @package LiteSpeed
|
||||
*/
|
||||
|
||||
namespace LiteSpeed;
|
||||
|
||||
defined( 'WPINC' ) || exit();
|
||||
|
||||
/**
|
||||
* Class Core
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class Core extends Root {
|
||||
|
||||
const NAME = 'LiteSpeed Cache';
|
||||
const PLUGIN_NAME = 'litespeed-cache';
|
||||
const PLUGIN_FILE = 'litespeed-cache/litespeed-cache.php';
|
||||
const VER = LSCWP_V;
|
||||
|
||||
const ACTION_DISMISS = 'dismiss';
|
||||
const ACTION_PURGE_BY = 'PURGE_BY';
|
||||
const ACTION_PURGE_EMPTYCACHE = 'PURGE_EMPTYCACHE';
|
||||
const ACTION_QS_PURGE = 'PURGE';
|
||||
const ACTION_QS_PURGE_SINGLE = 'PURGESINGLE'; // This will be same as `ACTION_QS_PURGE` (purge single URL only)
|
||||
const ACTION_QS_SHOW_HEADERS = 'SHOWHEADERS';
|
||||
const ACTION_QS_PURGE_ALL = 'purge_all';
|
||||
const ACTION_QS_PURGE_EMPTYCACHE = 'empty_all';
|
||||
const ACTION_QS_NOCACHE = 'NOCACHE';
|
||||
|
||||
const HEADER_DEBUG = 'X-LiteSpeed-Debug';
|
||||
|
||||
/**
|
||||
* Whether to show debug headers.
|
||||
*
|
||||
* @var bool
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected static $debug_show_header = false;
|
||||
|
||||
/**
|
||||
* Footer comment buffer.
|
||||
*
|
||||
* @var string
|
||||
* @since 1.0.0
|
||||
*/
|
||||
private $footer_comment = '';
|
||||
|
||||
/**
|
||||
* Define the core functionality of the plugin.
|
||||
*
|
||||
* Set the plugin name and the plugin version that can be used throughout the plugin.
|
||||
* Load the dependencies, define the locale, and set the hooks for the admin area and
|
||||
* the public-facing side of the site.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function __construct() {
|
||||
! defined( 'LSCWP_TS_0' ) && define( 'LSCWP_TS_0', microtime( true ) );
|
||||
$this->cls( 'Conf' )->init();
|
||||
|
||||
/**
|
||||
* Load API hooks
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
$this->cls( 'API' )->init();
|
||||
|
||||
if ( defined( 'LITESPEED_ON' ) ) {
|
||||
// Load third party detection if lscache enabled.
|
||||
include_once LSCWP_DIR . 'thirdparty/entry.inc.php';
|
||||
}
|
||||
|
||||
|
||||
if ( $this->conf( Base::O_DEBUG_DISABLE_ALL ) || Debug2::is_tmp_disable() ) {
|
||||
! defined( 'LITESPEED_DISABLE_ALL' ) && define( 'LITESPEED_DISABLE_ALL', true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register plugin activate/deactivate/uninstall hooks
|
||||
* NOTE: this can't be moved under after_setup_theme, otherwise activation will be bypassed
|
||||
*
|
||||
* @since 2.7.1 Disabled admin&CLI check to make frontend able to enable cache too
|
||||
*/
|
||||
$plugin_file = LSCWP_DIR . 'litespeed-cache.php';
|
||||
register_activation_hook( $plugin_file, array( __NAMESPACE__ . '\Activation', 'register_activation' ) );
|
||||
register_deactivation_hook( $plugin_file, array( __NAMESPACE__ . '\Activation', 'register_deactivation' ) );
|
||||
register_uninstall_hook( $plugin_file, __NAMESPACE__ . '\Activation::uninstall_litespeed_cache' );
|
||||
|
||||
if ( defined( 'LITESPEED_ON' ) ) {
|
||||
// Register purge_all actions
|
||||
$purge_all_events = $this->conf( Base::O_PURGE_HOOK_ALL );
|
||||
|
||||
// Purge all on upgrade
|
||||
if ( $this->conf( Base::O_PURGE_ON_UPGRADE ) ) {
|
||||
$purge_all_events[] = 'automatic_updates_complete';
|
||||
$purge_all_events[] = 'upgrader_process_complete';
|
||||
$purge_all_events[] = 'admin_action_do-plugin-upgrade';
|
||||
}
|
||||
foreach ( $purge_all_events as $event ) {
|
||||
// Don't allow hook to update_option because purge_all will cause infinite loop of update_option
|
||||
if ( in_array( $event, array( 'update_option' ), true ) ) {
|
||||
continue;
|
||||
}
|
||||
add_action( $event, __NAMESPACE__ . '\Purge::purge_all' );
|
||||
}
|
||||
|
||||
// Add headers to site health check for full page cache
|
||||
// @since 5.4
|
||||
add_filter( 'site_status_page_cache_supported_cache_headers', function ( $cache_headers ) {
|
||||
$is_cache_hit = function ( $header_value ) {
|
||||
return false !== strpos( strtolower( $header_value ), 'hit' );
|
||||
};
|
||||
$cache_headers['x-litespeed-cache'] = $is_cache_hit;
|
||||
$cache_headers['x-lsadc-cache'] = $is_cache_hit;
|
||||
$cache_headers['x-qc-cache'] = $is_cache_hit;
|
||||
return $cache_headers;
|
||||
} );
|
||||
}
|
||||
|
||||
add_action( 'after_setup_theme', array( $this, 'init' ) );
|
||||
|
||||
// Check if there is a purge request in queue
|
||||
if ( ! defined( 'LITESPEED_CLI' ) ) {
|
||||
$purge_queue = Purge::get_option( Purge::DB_QUEUE );
|
||||
if ( $purge_queue && '-1' !== $purge_queue ) {
|
||||
$this->http_header( $purge_queue );
|
||||
Debug2::debug( '[Core] Purge Queue found&sent: ' . $purge_queue );
|
||||
}
|
||||
if ( '-1' !== $purge_queue ) {
|
||||
Purge::update_option( Purge::DB_QUEUE, '-1' ); // Use -1 to bypass purge while still enable db update as WP's update_option will check value===false to bypass update
|
||||
}
|
||||
|
||||
$purge_queue = Purge::get_option( Purge::DB_QUEUE2 );
|
||||
if ( $purge_queue && '-1' !== $purge_queue ) {
|
||||
$this->http_header( $purge_queue );
|
||||
Debug2::debug( '[Core] Purge2 Queue found&sent: ' . $purge_queue );
|
||||
}
|
||||
if ( '-1' !== $purge_queue ) {
|
||||
Purge::update_option( Purge::DB_QUEUE2, '-1' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook internal REST
|
||||
*
|
||||
* @since 2.9.4
|
||||
*/
|
||||
$this->cls( 'REST' );
|
||||
|
||||
/**
|
||||
* Hook wpnonce function
|
||||
*
|
||||
* Note: ESI nonce won't be available until hook after_setup_theme ESI init due to Guest Mode concern
|
||||
*
|
||||
* @since 4.1
|
||||
*/
|
||||
if ( $this->cls( 'Router' )->esi_enabled() && ! function_exists( 'wp_create_nonce' ) ) {
|
||||
Debug2::debug( '[ESI] Overwrite wp_create_nonce()' );
|
||||
litespeed_define_nonce_func();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The plugin initializer.
|
||||
*
|
||||
* This function checks if the cache is enabled and ready to use, then determines what actions need to be set up based on the type of user and page accessed. Output is buffered if the cache is enabled.
|
||||
*
|
||||
* NOTE: WP user doesn't init yet
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function init() {
|
||||
/**
|
||||
* Added hook before init
|
||||
* 3rd party preload hooks will be fired here too (e.g. Divi disable all in edit mode)
|
||||
*
|
||||
* @since 1.6.6
|
||||
* @since 2.6 Added filter to all config values in Conf
|
||||
*/
|
||||
do_action( 'litespeed_init' );
|
||||
add_action( 'wp_ajax_async_litespeed', 'LiteSpeed\Task::async_litespeed_handler' );
|
||||
add_action( 'wp_ajax_nopriv_async_litespeed', 'LiteSpeed\Task::async_litespeed_handler' );
|
||||
|
||||
// In `after_setup_theme`, before `init` hook
|
||||
$this->cls( 'Activation' )->auto_update();
|
||||
|
||||
if ( is_admin() && ! wp_doing_ajax() ) {
|
||||
$this->cls( 'Admin' );
|
||||
}
|
||||
|
||||
if ( defined( 'LITESPEED_DISABLE_ALL' ) && LITESPEED_DISABLE_ALL ) {
|
||||
Debug2::debug( '[Core] Bypassed due to debug disable all setting' );
|
||||
return;
|
||||
}
|
||||
|
||||
do_action( 'litespeed_initing' );
|
||||
|
||||
ob_start( array( $this, 'send_headers_force' ) );
|
||||
add_action( 'shutdown', array( $this, 'send_headers' ), 0 );
|
||||
add_action( 'wp_footer', array( $this, 'footer_hook' ) );
|
||||
|
||||
/**
|
||||
* Check if is non-optimization simulator
|
||||
*
|
||||
* @since 2.9
|
||||
*/
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
if ( ! empty( $_GET[ Router::ACTION ] ) && 'before_optm' === $_GET[ Router::ACTION ] && ! apply_filters( 'litespeed_qs_forbidden', false ) ) {
|
||||
Debug2::debug( '[Core] ⛑️ bypass_optm due to QS CTRL' );
|
||||
! defined( 'LITESPEED_NO_OPTM' ) && define( 'LITESPEED_NO_OPTM', true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register vary filter
|
||||
*
|
||||
* @since 1.6.2
|
||||
*/
|
||||
$this->cls( 'Control' )->init();
|
||||
|
||||
// Init Purge hooks
|
||||
$this->cls( 'Purge' )->init();
|
||||
|
||||
$this->cls( 'Tag' )->init();
|
||||
|
||||
// Load hooks that may be related to users
|
||||
add_action( 'init', array( $this, 'after_user_init' ), 5 );
|
||||
|
||||
// Load 3rd party hooks
|
||||
add_action( 'wp_loaded', array( $this, 'load_thirdparty' ), 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Run hooks after user init
|
||||
*
|
||||
* @since 2.9.8
|
||||
*/
|
||||
public function after_user_init() {
|
||||
$this->cls( 'Router' )->is_role_simulation();
|
||||
|
||||
// Detect if is Guest mode or not
|
||||
$this->cls( 'Vary' )->after_user_init();
|
||||
|
||||
// Register attachment delete hook
|
||||
$this->cls( 'Media' )->after_user_init();
|
||||
|
||||
/**
|
||||
* Preload ESI functionality for ESI request URI recovery
|
||||
*
|
||||
* @since 1.8.1
|
||||
* @since 4.0 ESI init needs to be after Guest mode detection to bypass ESI if is under Guest mode
|
||||
*/
|
||||
$this->cls( 'ESI' )->init();
|
||||
|
||||
if ( ! is_admin() && ! defined( 'LITESPEED_GUEST_OPTM' ) ) {
|
||||
$result = $this->cls( 'Conf' )->in_optm_exc_roles();
|
||||
if ( $result ) {
|
||||
Debug2::debug( '[Core] ⛑️ bypass_optm: hit Role Excludes setting: ' . $result );
|
||||
! defined( 'LITESPEED_NO_OPTM' ) && define( 'LITESPEED_NO_OPTM', true );
|
||||
}
|
||||
}
|
||||
|
||||
// Heartbeat control
|
||||
$this->cls( 'Tool' )->heartbeat();
|
||||
|
||||
if ( ! defined( 'LITESPEED_NO_OPTM' ) || ! LITESPEED_NO_OPTM ) {
|
||||
// Check missing static files
|
||||
$this->cls( 'Router' )->serve_static();
|
||||
|
||||
$this->cls( 'Media' )->init();
|
||||
|
||||
$this->cls( 'Placeholder' )->init();
|
||||
|
||||
$this->cls( 'Router' )->can_optm() && $this->cls( 'Optimize' )->init();
|
||||
|
||||
$this->cls( 'Localization' )->init();
|
||||
|
||||
// Hook CDN for attachments
|
||||
$this->cls( 'CDN' )->init();
|
||||
|
||||
// Load cron tasks
|
||||
$this->cls( 'Task' )->init();
|
||||
}
|
||||
|
||||
// Load litespeed actions
|
||||
$action = Router::get_action();
|
||||
if ( $action ) {
|
||||
$this->proceed_action( $action );
|
||||
}
|
||||
|
||||
// Load frontend GUI
|
||||
if ( ! is_admin() ) {
|
||||
$this->cls( 'GUI' )->init();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run frontend actions
|
||||
*
|
||||
* @since 1.1.0
|
||||
* @param string $action The action to proceed.
|
||||
*/
|
||||
public function proceed_action( $action ) {
|
||||
$msg = false;
|
||||
// Handle actions
|
||||
switch ( $action ) {
|
||||
case self::ACTION_QS_SHOW_HEADERS:
|
||||
self::$debug_show_header = true;
|
||||
break;
|
||||
|
||||
case self::ACTION_QS_PURGE:
|
||||
case self::ACTION_QS_PURGE_SINGLE:
|
||||
Purge::set_purge_single();
|
||||
break;
|
||||
|
||||
case self::ACTION_QS_PURGE_ALL:
|
||||
Purge::purge_all();
|
||||
break;
|
||||
|
||||
case self::ACTION_PURGE_EMPTYCACHE:
|
||||
case self::ACTION_QS_PURGE_EMPTYCACHE:
|
||||
define( 'LSWCP_EMPTYCACHE', true ); // Clear all sites caches
|
||||
Purge::purge_all();
|
||||
$msg = __( 'Notified LiteSpeed Web Server to purge everything.', 'litespeed-cache' );
|
||||
break;
|
||||
|
||||
case self::ACTION_PURGE_BY:
|
||||
$this->cls( 'Purge' )->purge_list();
|
||||
$msg = __( 'Notified LiteSpeed Web Server to purge the list.', 'litespeed-cache' );
|
||||
break;
|
||||
|
||||
case self::ACTION_DISMISS:
|
||||
GUI::dismiss();
|
||||
break;
|
||||
|
||||
default:
|
||||
$msg = $this->cls( 'Router' )->handler( $action );
|
||||
break;
|
||||
}
|
||||
if ( $msg && ! Router::is_ajax() ) {
|
||||
Admin_Display::add_notice( Admin_Display::NOTICE_GREEN, $msg );
|
||||
Admin::redirect();
|
||||
return;
|
||||
}
|
||||
|
||||
if ( Router::is_ajax() ) {
|
||||
exit();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback used to call the detect third party action.
|
||||
*
|
||||
* The detect action is used by third party plugin integration classes to determine if they should add the rest of their hooks.
|
||||
*
|
||||
* @since 1.0.5
|
||||
*/
|
||||
public function load_thirdparty() {
|
||||
do_action( 'litespeed_load_thirdparty' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark wp_footer called
|
||||
*
|
||||
* @since 1.3
|
||||
*/
|
||||
public function footer_hook() {
|
||||
Debug2::debug( '[Core] Footer hook called' );
|
||||
if ( ! defined( 'LITESPEED_FOOTER_CALLED' ) ) {
|
||||
define( 'LITESPEED_FOOTER_CALLED', true );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger comment info display hook
|
||||
*
|
||||
* @since 1.3
|
||||
* @param string|null $buffer The buffer to check.
|
||||
* @return void
|
||||
*/
|
||||
private function check_is_html( $buffer = null ) {
|
||||
if ( ! defined( 'LITESPEED_FOOTER_CALLED' ) ) {
|
||||
Debug2::debug2( '[Core] CHK html bypass: miss footer const' );
|
||||
return;
|
||||
}
|
||||
|
||||
if ( wp_doing_ajax() ) {
|
||||
Debug2::debug2( '[Core] CHK html bypass: doing ajax' );
|
||||
return;
|
||||
}
|
||||
|
||||
if ( wp_doing_cron() ) {
|
||||
Debug2::debug2( '[Core] CHK html bypass: doing cron' );
|
||||
return;
|
||||
}
|
||||
|
||||
if ( empty( $_SERVER['REQUEST_METHOD'] ) || 'GET' !== $_SERVER['REQUEST_METHOD'] ) {
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
|
||||
Debug2::debug2( '[Core] CHK html bypass: not get method ' . wp_unslash( $_SERVER['REQUEST_METHOD'] ) );
|
||||
return;
|
||||
}
|
||||
|
||||
if ( null === $buffer ) {
|
||||
$buffer = ob_get_contents();
|
||||
}
|
||||
|
||||
// Double check to make sure it is an HTML file
|
||||
if ( strlen( $buffer ) > 300 ) {
|
||||
$buffer = substr( $buffer, 0, 300 );
|
||||
}
|
||||
if ( false !== strstr( $buffer, '<!--' ) ) {
|
||||
$buffer = preg_replace( '/<!--.*?-->/s', '', $buffer );
|
||||
}
|
||||
$buffer = trim( $buffer );
|
||||
|
||||
$buffer = File::remove_zero_space( $buffer );
|
||||
|
||||
$is_html = 0 === stripos( $buffer, '<html' ) || 0 === stripos( $buffer, '<!DOCTYPE' );
|
||||
|
||||
if ( ! $is_html ) {
|
||||
Debug2::debug( '[Core] Footer check failed: ' . ob_get_level() . '-' . substr( $buffer, 0, 100 ) );
|
||||
return;
|
||||
}
|
||||
|
||||
Debug2::debug( '[Core] Footer check passed' );
|
||||
|
||||
if ( ! defined( 'LITESPEED_IS_HTML' ) ) {
|
||||
define( 'LITESPEED_IS_HTML', true );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For compatibility with plugins that have 'Bad' logic that forced all buffer output even if it is NOT their buffer.
|
||||
*
|
||||
* Usually this is called after send_headers() if following original WP process
|
||||
*
|
||||
* @since 1.1.5
|
||||
* @param string $buffer The buffer to process.
|
||||
* @return string The processed buffer.
|
||||
*/
|
||||
public function send_headers_force( $buffer ) {
|
||||
$this->check_is_html( $buffer );
|
||||
|
||||
// Hook to modify buffer before
|
||||
$buffer = apply_filters( 'litespeed_buffer_before', $buffer );
|
||||
|
||||
/**
|
||||
* Media: Image lazyload && WebP
|
||||
* GUI: Clean wrapper mainly for ESI block NOTE: this needs to be before optimizer to avoid wrapper being removed
|
||||
* Optimize
|
||||
* CDN
|
||||
*/
|
||||
if ( ! defined( 'LITESPEED_NO_OPTM' ) || ! LITESPEED_NO_OPTM ) {
|
||||
Debug2::debug( '[Core] run hook litespeed_buffer_finalize' );
|
||||
$buffer = apply_filters( 'litespeed_buffer_finalize', $buffer );
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace ESI preserved list
|
||||
*
|
||||
* @since 3.3 Replace this in the end to avoid `Inline JS Defer` or other Page Optm features encoded ESI tags wrongly, which caused LSWS can't recognize ESI
|
||||
*/
|
||||
$buffer = $this->cls( 'ESI' )->finalize( $buffer );
|
||||
|
||||
$this->send_headers( true );
|
||||
|
||||
// Log ESI nonce buffer empty issue
|
||||
if ( defined( 'LSCACHE_IS_ESI' ) && 0 === strlen( $buffer ) && ! empty( $_SERVER['REQUEST_URI'] ) ) {
|
||||
// Log ref for debug purpose
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.PHP.DevelopmentFunctions.error_log_error_log
|
||||
error_log( 'ESI buffer empty ' . wp_unslash( $_SERVER['REQUEST_URI'] ) );
|
||||
}
|
||||
|
||||
// Init comment info
|
||||
$running_info_showing = defined( 'LITESPEED_IS_HTML' ) || defined( 'LSCACHE_IS_ESI' );
|
||||
if ( defined( 'LSCACHE_ESI_SILENCE' ) ) {
|
||||
$running_info_showing = false;
|
||||
Debug2::debug( '[Core] ESI silence' );
|
||||
}
|
||||
/**
|
||||
* Silence comment for JSON request
|
||||
*
|
||||
* @since 2.9.3
|
||||
*/
|
||||
if ( REST::cls()->is_rest() || Router::is_ajax() ) {
|
||||
$running_info_showing = false;
|
||||
Debug2::debug( '[Core] Silence Comment due to REST/AJAX' );
|
||||
}
|
||||
$running_info_showing = apply_filters( 'litespeed_comment', $running_info_showing );
|
||||
if ( $running_info_showing && $this->footer_comment ) {
|
||||
$buffer .= $this->footer_comment;
|
||||
}
|
||||
|
||||
/**
|
||||
* If ESI request is JSON, give the content JSON format
|
||||
*
|
||||
* @since 2.9.3
|
||||
* @since 2.9.4 ESI request could be from internal REST call, so moved json_encode out of this condition
|
||||
*/
|
||||
if ( defined( 'LSCACHE_IS_ESI' ) ) {
|
||||
Debug2::debug( '[Core] ESI Start 👇' );
|
||||
if ( strlen( $buffer ) > 500 ) {
|
||||
Debug2::debug( trim( substr( $buffer, 0, 500 ) ) . '.....' );
|
||||
} else {
|
||||
Debug2::debug( $buffer );
|
||||
}
|
||||
Debug2::debug( '[Core] ESI End 👆' );
|
||||
}
|
||||
|
||||
if ( apply_filters( 'litespeed_is_json', false ) ) {
|
||||
if ( null === \json_decode( $buffer, true ) ) {
|
||||
Debug2::debug( '[Core] Buffer converting to JSON' );
|
||||
$buffer = wp_json_encode( $buffer );
|
||||
$buffer = trim( $buffer, '"' );
|
||||
} else {
|
||||
Debug2::debug( '[Core] JSON Buffer' );
|
||||
}
|
||||
}
|
||||
|
||||
// Hook to modify buffer after
|
||||
$buffer = apply_filters( 'litespeed_buffer_after', $buffer );
|
||||
|
||||
Debug2::ended();
|
||||
|
||||
return $buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the headers out at the end of processing the request.
|
||||
*
|
||||
* This will send out all LiteSpeed Cache related response headers needed for the post.
|
||||
*
|
||||
* @since 1.0.5
|
||||
* @param bool $is_forced If the header is sent following our normal finalizing logic.
|
||||
*/
|
||||
public function send_headers( $is_forced = false ) {
|
||||
// Make sure header output only runs once
|
||||
if ( defined( 'LITESPEED_DID_' . __FUNCTION__ ) ) {
|
||||
return;
|
||||
}
|
||||
define( 'LITESPEED_DID_' . __FUNCTION__, true );
|
||||
|
||||
// Avoid PHP warning for headers sent out already
|
||||
if ( headers_sent() ) {
|
||||
self::debug( '❌ !!! Err: Header sent out already' );
|
||||
return;
|
||||
}
|
||||
|
||||
$this->check_is_html();
|
||||
|
||||
// Cache control output needs to be done first, as some varies are added in 3rd party hook `litespeed_api_control`.
|
||||
$this->cls( 'Control' )->finalize();
|
||||
|
||||
$vary_header = $this->cls( 'Vary' )->finalize();
|
||||
|
||||
// If not cacheable but Admin QS is `purge` or `purgesingle`, `tag` still needs to be generated
|
||||
$tag_header = $this->cls( 'Tag' )->output();
|
||||
if ( ! $tag_header && Control::is_cacheable() ) {
|
||||
Control::set_nocache( 'empty tag header' );
|
||||
}
|
||||
|
||||
// `Purge` output needs to be after `tag` output as Admin QS may need to send `tag` header
|
||||
$purge_header = Purge::output();
|
||||
|
||||
// Generate `control` header in the end in case control status is changed by other headers
|
||||
$control_header = $this->cls( 'Control' )->output();
|
||||
|
||||
// Give one more break to avoid Firefox crash
|
||||
if ( ! defined( 'LSCACHE_IS_ESI' ) ) {
|
||||
$this->footer_comment .= "\n";
|
||||
}
|
||||
|
||||
$cache_support = 'supported';
|
||||
if ( defined( 'LITESPEED_ON' ) ) {
|
||||
$cache_support = Control::is_cacheable() ? 'cached' : 'uncached';
|
||||
}
|
||||
|
||||
$this->comment(
|
||||
sprintf(
|
||||
'%1$s %2$s by LiteSpeed Cache %4$s on %3$s',
|
||||
defined( 'LSCACHE_IS_ESI' ) ? 'Block' : 'Page',
|
||||
$cache_support,
|
||||
gmdate( 'Y-m-d H:i:s', time() + LITESPEED_TIME_OFFSET ),
|
||||
self::VER
|
||||
)
|
||||
);
|
||||
|
||||
// Send Control header
|
||||
if ( defined( 'LITESPEED_ON' ) && $control_header ) {
|
||||
$this->http_header( $control_header );
|
||||
if ( ! Control::is_cacheable() && !is_admin() ) {
|
||||
$ori_wp_header = wp_get_nocache_headers();
|
||||
if ( isset( $ori_wp_header['Cache-Control'] ) ) {
|
||||
$this->http_header( 'Cache-Control: ' . $ori_wp_header['Cache-Control'] ); // @ref: https://github.com/litespeedtech/lscache_wp/issues/889
|
||||
}
|
||||
}
|
||||
if ( defined( 'LSCWP_LOG' ) ) {
|
||||
$this->comment( $control_header );
|
||||
}
|
||||
}
|
||||
|
||||
// Send PURGE header (Always send regardless of cache setting disabled/enabled)
|
||||
if ( defined( 'LITESPEED_ON' ) && $purge_header ) {
|
||||
$this->http_header( $purge_header );
|
||||
Debug2::log_purge( $purge_header );
|
||||
|
||||
if ( defined( 'LSCWP_LOG' ) ) {
|
||||
$this->comment( $purge_header );
|
||||
}
|
||||
}
|
||||
|
||||
// Send Vary header
|
||||
if ( defined( 'LITESPEED_ON' ) && $vary_header ) {
|
||||
$this->http_header( $vary_header );
|
||||
if ( defined( 'LSCWP_LOG' ) ) {
|
||||
$this->comment( $vary_header );
|
||||
}
|
||||
}
|
||||
|
||||
if ( defined( 'LITESPEED_ON' ) && defined( 'LSCWP_LOG' ) ) {
|
||||
$vary = $this->cls( 'Vary' )->finalize_full_varies();
|
||||
if ( $vary ) {
|
||||
$this->comment( 'Full varies: ' . $vary );
|
||||
}
|
||||
}
|
||||
|
||||
// Admin QS show header action
|
||||
if ( self::$debug_show_header ) {
|
||||
$debug_header = self::HEADER_DEBUG . ': ';
|
||||
if ( $control_header ) {
|
||||
$debug_header .= $control_header . '; ';
|
||||
}
|
||||
if ( $purge_header ) {
|
||||
$debug_header .= $purge_header . '; ';
|
||||
}
|
||||
if ( $tag_header ) {
|
||||
$debug_header .= $tag_header . '; ';
|
||||
}
|
||||
if ( $vary_header ) {
|
||||
$debug_header .= $vary_header . '; ';
|
||||
}
|
||||
$this->http_header( $debug_header );
|
||||
} elseif ( defined( 'LITESPEED_ON' ) && Control::is_cacheable() && $tag_header ) {
|
||||
$this->http_header( $tag_header );
|
||||
if ( defined( 'LSCWP_LOG' ) ) {
|
||||
$this->comment( $tag_header );
|
||||
}
|
||||
}
|
||||
|
||||
// Object cache comment
|
||||
if ( defined( 'LSCWP_LOG' ) && defined( 'LSCWP_OBJECT_CACHE' ) && method_exists( 'WP_Object_Cache', 'debug' ) ) {
|
||||
$this->comment( 'Object Cache ' . \WP_Object_Cache::get_instance()->debug() );
|
||||
}
|
||||
|
||||
if ( defined( 'LITESPEED_GUEST' ) && LITESPEED_GUEST ) {
|
||||
$this->comment( 'Guest Mode' );
|
||||
}
|
||||
|
||||
if ( ! empty( $this->footer_comment ) ) {
|
||||
self::debug( "[footer comment]\n" . trim( $this->footer_comment ) );
|
||||
}
|
||||
|
||||
if ( $is_forced ) {
|
||||
Debug2::debug( '--forced--' );
|
||||
}
|
||||
|
||||
// If CLI and contains Purge Header, issue an HTTP request to Purge
|
||||
if ( defined( 'LITESPEED_CLI' ) ) {
|
||||
$purge_queue = Purge::get_option( Purge::DB_QUEUE );
|
||||
if ( ! $purge_queue || '-1' === $purge_queue ) {
|
||||
$purge_queue = Purge::get_option( Purge::DB_QUEUE2 );
|
||||
}
|
||||
if ( $purge_queue && '-1' !== $purge_queue ) {
|
||||
self::debug( '[Core] Purge Queue found, issue an HTTP request to purge: ' . $purge_queue );
|
||||
// Kick off HTTP request
|
||||
$url = admin_url( 'admin-ajax.php' );
|
||||
$resp = wp_safe_remote_get( $url );
|
||||
if ( is_wp_error( $resp ) ) {
|
||||
$error_message = $resp->get_error_message();
|
||||
self::debug( '[URL]' . $url );
|
||||
self::debug( 'failed to request: ' . $error_message );
|
||||
} else {
|
||||
self::debug( 'HTTP request response: ' . $resp['body'] );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Append one HTML comment
|
||||
*
|
||||
* @since 5.5
|
||||
* @param string $data The comment data.
|
||||
*/
|
||||
public static function comment( $data ) {
|
||||
self::cls()->append_comment( $data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Append one HTML comment
|
||||
*
|
||||
* @since 5.5
|
||||
* @param string $data The comment data.
|
||||
*/
|
||||
private function append_comment( $data ) {
|
||||
$this->footer_comment .= "\n<!-- " . htmlspecialchars( $data ) . ' -->';
|
||||
}
|
||||
|
||||
/**
|
||||
* Send HTTP header
|
||||
*
|
||||
* @since 5.3
|
||||
* @param string $header The header to send.
|
||||
*/
|
||||
private function http_header( $header ) {
|
||||
if ( defined( 'LITESPEED_CLI' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! headers_sent() ) {
|
||||
header( $header );
|
||||
}
|
||||
|
||||
if ( ! defined( 'LSCWP_LOG' ) ) {
|
||||
return;
|
||||
}
|
||||
Debug2::debug( '💰 ' . $header );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,667 @@
|
||||
<?php
|
||||
/**
|
||||
* The Crawler Sitemap Class.
|
||||
*
|
||||
* @package LiteSpeed
|
||||
* @since 1.1.0
|
||||
*/
|
||||
|
||||
namespace LiteSpeed;
|
||||
|
||||
defined( 'WPINC' ) || exit();
|
||||
|
||||
/**
|
||||
* Class Crawler_Map
|
||||
*
|
||||
* Maintains and persists crawler sitemap/blacklist state, parses custom sitemaps,
|
||||
* and exposes helpers to query & mutate crawler results.
|
||||
*/
|
||||
class Crawler_Map extends Root {
|
||||
|
||||
const LOG_TAG = '🐞🗺️';
|
||||
|
||||
const BM_MISS = 1;
|
||||
const BM_HIT = 2;
|
||||
const BM_BLACKLIST = 4;
|
||||
|
||||
/**
|
||||
* Site URL used to simplify URLs.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $_site_url;
|
||||
|
||||
/**
|
||||
* Main crawler table name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $_tb;
|
||||
|
||||
/**
|
||||
* Crawler blacklist table name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $_tb_blacklist;
|
||||
|
||||
/**
|
||||
* Data service instance.
|
||||
*
|
||||
* @var \LiteSpeed\Data
|
||||
*/
|
||||
private $__data;
|
||||
|
||||
/**
|
||||
* Timeout (seconds) when fetching sitemaps.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $_conf_map_timeout;
|
||||
|
||||
/**
|
||||
* Collected URLs from parsed sitemaps.
|
||||
*
|
||||
* @var array<int,string>
|
||||
*/
|
||||
private $_urls = [];
|
||||
|
||||
/**
|
||||
* Instantiate the class.
|
||||
*
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->_site_url = get_site_url();
|
||||
$this->__data = Data::cls();
|
||||
$this->_tb = $this->__data->tb( 'crawler' );
|
||||
$this->_tb_blacklist = $this->__data->tb( 'crawler_blacklist' );
|
||||
// Specify the timeout while parsing the sitemap.
|
||||
$this->_conf_map_timeout = defined( 'LITESPEED_CRAWLER_MAP_TIMEOUT' ) ? constant( 'LITESPEED_CRAWLER_MAP_TIMEOUT' ) : 180;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save URLs crawl status into DB.
|
||||
*
|
||||
* @since 3.0
|
||||
* @access public
|
||||
*
|
||||
* @param array<int,array<int,array{url:string,code:int}>> $items Map of bit => [ id => [url, code] ].
|
||||
* @param int $curr_crawler Current crawler index (0-based).
|
||||
* @return array<int,array>
|
||||
*/
|
||||
public function save_map_status( $items, $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 ( $items 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;
|
||||
$id_all = implode(',', array_map('intval', array_keys($ids)));
|
||||
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$wpdb->query("UPDATE `$this->_tb` SET res = CONCAT( LEFT( res, $curr_crawler ), '$bit', RIGHT( res, $right_pos ) ) WHERE id IN ( $id_all )");
|
||||
|
||||
// Add blacklist
|
||||
if (Crawler::STATUS_BLACKLIST === $bit || Crawler::STATUS_NOCACHE === $bit) {
|
||||
$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 )";
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared
|
||||
$existing = $wpdb->get_results($q, ARRAY_A);
|
||||
// Update current crawler status tag in existing blacklist
|
||||
if ($existing) {
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared
|
||||
$count = $wpdb->query("UPDATE `$this->_tb_blacklist` SET res = CONCAT( LEFT( res, $curr_crawler ), '$bit', RIGHT( res, $right_pos ) ) 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;
|
||||
}
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared
|
||||
$wpdb->query($wpdb->prepare($q, $data));
|
||||
}
|
||||
}
|
||||
|
||||
// Update sitemap reason w/ HTTP code.
|
||||
$reason_array = [];
|
||||
foreach ( $ids as $row_id => $row ) {
|
||||
$code = (int) $row['code'];
|
||||
if ( empty( $reason_array[ $code ] ) ) {
|
||||
$reason_array[ $code ] = [];
|
||||
}
|
||||
$reason_array[ $code ][] = (int) $row_id;
|
||||
}
|
||||
|
||||
foreach ($reason_array as $code => $v2) {
|
||||
// Complement comma
|
||||
if ($curr_crawler) {
|
||||
$code = ',' . $code;
|
||||
}
|
||||
if ($curr_crawler < $total_crawler_pos) {
|
||||
$code .= ',';
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.DB
|
||||
$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 (Crawler::STATUS_BLACKLIST === $bit || Crawler::STATUS_NOCACHE === $bit) {
|
||||
// phpcs:ignore WordPress.DB
|
||||
$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.
|
||||
$items[ $bit ] = [];
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add one record to blacklist.
|
||||
* NOTE: $id is sitemap table ID.
|
||||
*
|
||||
* @since 3.0
|
||||
* @access public
|
||||
*
|
||||
* @param int $id Sitemap row ID.
|
||||
* @return void
|
||||
*/
|
||||
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'));
|
||||
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$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";
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared
|
||||
$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";
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared
|
||||
$wpdb->query($wpdb->prepare($q, array( $res, $reason, $row['id'] )));
|
||||
} else {
|
||||
$q = "INSERT INTO `$this->_tb_blacklist` (url, res, reason) VALUES (%s, %s, %s)";
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared
|
||||
$wpdb->query($wpdb->prepare($q, array( $row['url'], $res, $reason )));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete one record from blacklist.
|
||||
*
|
||||
* @since 3.0
|
||||
* @access public
|
||||
*
|
||||
* @param int $id Blacklist row ID.
|
||||
* @return void
|
||||
*/
|
||||
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
|
||||
);
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared
|
||||
$wpdb->query($sql);
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$wpdb->query("DELETE FROM `$this->_tb_blacklist` WHERE id='$id'");
|
||||
}
|
||||
|
||||
/**
|
||||
* Empty blacklist.
|
||||
*
|
||||
* @since 3.0
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
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);
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared
|
||||
$wpdb->query($sql);
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$wpdb->query("TRUNCATE `$this->_tb_blacklist`");
|
||||
}
|
||||
|
||||
/**
|
||||
* List blacklist.
|
||||
*
|
||||
* @since 3.0
|
||||
* @access public
|
||||
*
|
||||
* @param int|false $limit Number of rows to fetch, or false for all.
|
||||
* @param int|false $offset Offset for pagination, or false to auto-calc.
|
||||
* @return array<int,array<string,mixed>>
|
||||
*/
|
||||
public function list_blacklist( $limit = false, $offset = false ) {
|
||||
global $wpdb;
|
||||
|
||||
if ( ! $this->__data->tb_exist( 'crawler_blacklist' ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$q = "SELECT * FROM `$this->_tb_blacklist` ORDER BY id DESC";
|
||||
|
||||
if ( false !== $limit ) {
|
||||
if ( false === $offset ) {
|
||||
$total = $this->count_blacklist();
|
||||
$offset = Utility::pagination($total, $limit, true);
|
||||
}
|
||||
$q .= ' LIMIT %d, %d';
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
$q = $wpdb->prepare($q, $offset, $limit);
|
||||
}
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared
|
||||
return $wpdb->get_results($q, ARRAY_A);
|
||||
}
|
||||
|
||||
/**
|
||||
* Count blacklist.
|
||||
*
|
||||
* @return int|false
|
||||
*/
|
||||
public function count_blacklist() {
|
||||
global $wpdb;
|
||||
|
||||
if ( ! $this->__data->tb_exist( 'crawler_blacklist' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$q = "SELECT COUNT(*) FROM `$this->_tb_blacklist`";
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared
|
||||
return $wpdb->get_var($q);
|
||||
}
|
||||
|
||||
/**
|
||||
* Empty sitemap.
|
||||
*
|
||||
* @since 3.0
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
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
|
||||
*
|
||||
* @param int $limit Number of rows per page.
|
||||
* @param int|bool $offset Offset for pagination, or false to auto-calc.
|
||||
* @return array<int,array<string,mixed>>
|
||||
*/
|
||||
public function list_map( $limit, $offset = false ) {
|
||||
global $wpdb;
|
||||
|
||||
if ( ! $this->__data->tb_exist( 'crawler' ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if ( false === $offset ) {
|
||||
$total = $this->count_map();
|
||||
$offset = Utility::pagination($total, $limit, true);
|
||||
}
|
||||
|
||||
$type = Router::verify_type();
|
||||
|
||||
$req_uri_like = '';
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Missing
|
||||
if ( ! empty( $_POST['kw'] ) ) {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Missing
|
||||
$kw = sanitize_text_field( wp_unslash( $_POST['kw'] ) );
|
||||
$q = "SELECT * FROM `$this->_tb` WHERE url LIKE %s";
|
||||
if ( 'hit' === $type ) {
|
||||
$q .= " AND res LIKE '%" . Crawler::STATUS_HIT . "%'";
|
||||
}
|
||||
if ( 'miss' === $type ) {
|
||||
$q .= " AND res LIKE '%" . Crawler::STATUS_MISS . "%'";
|
||||
}
|
||||
if ( 'blacklisted' === $type ) {
|
||||
$q .= " AND res LIKE '%" . Crawler::STATUS_BLACKLIST . "%'";
|
||||
}
|
||||
$q .= ' ORDER BY id LIMIT %d, %d';
|
||||
$req_uri_like = '%' . $wpdb->esc_like( $kw ) . '%';
|
||||
return $wpdb->get_results( $wpdb->prepare( $q, $req_uri_like, $offset, $limit ), ARRAY_A ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared
|
||||
}
|
||||
|
||||
$q = "SELECT * FROM `$this->_tb`";
|
||||
if ( 'hit' === $type ) {
|
||||
$q .= " WHERE res LIKE '%" . Crawler::STATUS_HIT . "%'";
|
||||
}
|
||||
if ( 'miss' === $type ) {
|
||||
$q .= " WHERE res LIKE '%" . Crawler::STATUS_MISS . "%'";
|
||||
}
|
||||
if ( 'blacklisted' === $type ) {
|
||||
$q .= " WHERE res LIKE '%" . Crawler::STATUS_BLACKLIST . "%'";
|
||||
}
|
||||
$q .= ' ORDER BY id LIMIT %d, %d';
|
||||
|
||||
return $wpdb->get_results( $wpdb->prepare( $q, $offset, $limit ), ARRAY_A ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared
|
||||
}
|
||||
|
||||
/**
|
||||
* Count sitemap.
|
||||
*
|
||||
* @return int|false
|
||||
*/
|
||||
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 ( 'hit' === $type ) {
|
||||
$q .= " WHERE res LIKE '%" . Crawler::STATUS_HIT . "%'";
|
||||
}
|
||||
if ( 'miss' === $type ) {
|
||||
$q .= " WHERE res LIKE '%" . Crawler::STATUS_MISS . "%'";
|
||||
}
|
||||
if ( 'blacklisted' === $type ) {
|
||||
$q .= " WHERE res LIKE '%" . Crawler::STATUS_BLACKLIST . "%'";
|
||||
}
|
||||
|
||||
return $wpdb->get_var( $q ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate sitemap.
|
||||
*
|
||||
* @since 1.1.0
|
||||
* @access public
|
||||
*
|
||||
* @param bool $manual Whether triggered manually from UI.
|
||||
* @return void
|
||||
*/
|
||||
public function gen( $manual = false ) {
|
||||
$count = $this->_gen();
|
||||
|
||||
if ( ! $count ) {
|
||||
Admin_Display::error( __( 'No valid sitemap parsed for crawler.', 'litespeed-cache' ) );
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! wp_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
|
||||
* @return int|false Number of URLs generated or false on failure.
|
||||
*/
|
||||
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.
|
||||
$sitemap = $this->conf( Base::O_CRAWLER_SITEMAP );
|
||||
if ( ! $sitemap ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$offset = strlen( $this->_site_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' ) && constant( 'LITESPEED_CRAWLER_DROP_DOMAIN' ) ) {
|
||||
foreach ( $this->_urls as $k => $v ) {
|
||||
if ( 0 !== stripos( $v, $this->_site_url ) ) {
|
||||
unset( $this->_urls[ $k ] );
|
||||
continue;
|
||||
}
|
||||
$this->_urls[ $k ] = substr( $v, $offset );
|
||||
}
|
||||
}
|
||||
|
||||
$this->_urls = array_values( array_unique( $this->_urls ) );
|
||||
}
|
||||
|
||||
self::debug( 'Truncate sitemap' );
|
||||
$wpdb->query( "TRUNCATE `$this->_tb`" ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery
|
||||
|
||||
self::debug( 'Generate sitemap' );
|
||||
|
||||
// Filter URLs in blacklist.
|
||||
$blacklist = $this->list_blacklist();
|
||||
|
||||
$full_blacklisted = [];
|
||||
$partial_blacklisted = [];
|
||||
foreach ( $blacklist as $v ) {
|
||||
if ( false === strpos( $v['res'], '-' ) ) {
|
||||
// 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'] ] = [
|
||||
'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 = [];
|
||||
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
|
||||
*
|
||||
* @param array<int,string> $data Flat array (url,res,reason, url,res,reason, ...).
|
||||
* @param string $fields Fields list for insert (default url,res,reason).
|
||||
* @return void
|
||||
*/
|
||||
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 ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse custom sitemap and collect urls.
|
||||
*
|
||||
* @since 1.1.1
|
||||
* @access private
|
||||
*
|
||||
* @param string $sitemap Absolute sitemap URL.
|
||||
* @return void
|
||||
* @throws \Exception If remote read or parsing fails.
|
||||
*/
|
||||
private function _parse( $sitemap ) {
|
||||
/**
|
||||
* Read via wp func to avoid allow_url_fopen = off
|
||||
*
|
||||
* @since 2.2.7
|
||||
*/
|
||||
$response = wp_safe_remote_get(
|
||||
$sitemap,
|
||||
[
|
||||
'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 ' . esc_url( $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 ' . esc_url( $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( (string) $xml_array['sitemap']['loc'] );
|
||||
} else {
|
||||
// parse multiple sitemaps.
|
||||
foreach ( (array) $xml_array['sitemap'] as $val ) {
|
||||
$val = (array) $val;
|
||||
if ( ! empty( $val['loc'] ) ) {
|
||||
$this->_parse( (string) $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[] = (string) $xml_array['url']['loc'];
|
||||
} else {
|
||||
foreach ( (array) $xml_array['url'] as $val ) {
|
||||
$val = (array) $val;
|
||||
if ( ! empty( $val['loc'] ) ) {
|
||||
$this->_urls[] = (string) $val['loc'];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,621 @@
|
||||
<?php
|
||||
// phpcs:ignoreFile
|
||||
|
||||
/**
|
||||
* The optimize css class.
|
||||
*
|
||||
* @since 2.3
|
||||
*/
|
||||
|
||||
namespace LiteSpeed;
|
||||
|
||||
defined('WPINC') || exit();
|
||||
|
||||
class CSS extends Base {
|
||||
|
||||
const LOG_TAG = '[CSS]';
|
||||
|
||||
const TYPE_GEN_CCSS = 'gen_ccss';
|
||||
const TYPE_CLEAR_Q_CCSS = 'clear_q_ccss';
|
||||
|
||||
protected $_summary;
|
||||
private $_ccss_whitelist;
|
||||
private $_queue;
|
||||
|
||||
/**
|
||||
* Init
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->_summary = self::get_summary();
|
||||
|
||||
add_filter('litespeed_ccss_whitelist', array( $this->cls('Data'), 'load_ccss_whitelist' ));
|
||||
}
|
||||
|
||||
/**
|
||||
* HTML lazyload CSS
|
||||
*
|
||||
* @since 4.0
|
||||
*/
|
||||
public function prepare_html_lazy() {
|
||||
return '<style>' . implode(',', $this->conf(self::O_OPTM_HTML_LAZY)) . '{content-visibility:auto;contain-intrinsic-size:1px 1000px;}</style>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Output critical css
|
||||
*
|
||||
* @since 1.3
|
||||
* @access public
|
||||
*/
|
||||
public function prepare_ccss() {
|
||||
// Get critical css for current page
|
||||
// Note: need to consider mobile
|
||||
$rules = $this->_ccss();
|
||||
if (!$rules) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$error_tag = '';
|
||||
if (substr($rules, 0, 2) == '/*' && substr($rules, -2) == '*/') {
|
||||
Core::comment('QUIC.cloud CCSS bypassed due to generation error ❌');
|
||||
$error_tag = ' data-error="failed to generate"';
|
||||
}
|
||||
|
||||
// Append default critical css
|
||||
$rules .= $this->conf(self::O_OPTM_CCSS_CON);
|
||||
|
||||
return '<style id="litespeed-ccss"' . $error_tag . '>' . $rules . '</style>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate CCSS url tag
|
||||
*
|
||||
* @since 4.0
|
||||
*/
|
||||
private function _gen_ccss_file_tag( $request_url ) {
|
||||
if (is_404()) {
|
||||
return '404';
|
||||
}
|
||||
|
||||
if ($this->conf(self::O_OPTM_CCSS_PER_URL)) {
|
||||
return $request_url;
|
||||
}
|
||||
|
||||
$sep_uri = $this->conf(self::O_OPTM_CCSS_SEP_URI);
|
||||
if ($sep_uri && ($hit = Utility::str_hit_array($request_url, $sep_uri))) {
|
||||
Debug2::debug('[CCSS] Separate CCSS due to separate URI setting: ' . $hit);
|
||||
return $request_url;
|
||||
}
|
||||
|
||||
$pt = Utility::page_type();
|
||||
|
||||
$sep_pt = $this->conf(self::O_OPTM_CCSS_SEP_POSTTYPE);
|
||||
if (in_array($pt, $sep_pt)) {
|
||||
Debug2::debug('[CCSS] Separate CCSS due to posttype setting: ' . $pt);
|
||||
return $request_url;
|
||||
}
|
||||
|
||||
// Per posttype
|
||||
return $pt;
|
||||
}
|
||||
|
||||
/**
|
||||
* The critical css content of the current page
|
||||
*
|
||||
* @since 2.3
|
||||
*/
|
||||
private function _ccss() {
|
||||
global $wp;
|
||||
$request_url = get_permalink();
|
||||
// Backup, in case get_permalink() fails.
|
||||
if (!$request_url) {
|
||||
$request_url = home_url($wp->request);
|
||||
}
|
||||
|
||||
$filepath_prefix = $this->_build_filepath_prefix('ccss');
|
||||
$url_tag = $this->_gen_ccss_file_tag($request_url);
|
||||
$vary = $this->cls('Vary')->finalize_full_varies();
|
||||
$filename = $this->cls('Data')->load_url_file($url_tag, $vary, 'ccss');
|
||||
if ($filename) {
|
||||
$static_file = LITESPEED_STATIC_DIR . $filepath_prefix . $filename . '.css';
|
||||
|
||||
if (file_exists($static_file)) {
|
||||
Debug2::debug2('[CSS] existing ccss ' . $static_file);
|
||||
Core::comment('QUIC.cloud CCSS loaded ✅ ' . $filepath_prefix . $filename . '.css');
|
||||
return File::read($static_file);
|
||||
}
|
||||
}
|
||||
|
||||
$uid = get_current_user_id();
|
||||
|
||||
$ua = !empty($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '';
|
||||
|
||||
// Store it to prepare for cron
|
||||
Core::comment('QUIC.cloud CCSS in queue');
|
||||
$this->_queue = $this->load_queue('ccss');
|
||||
|
||||
if (count($this->_queue) > 500) {
|
||||
self::debug('CCSS Queue is full - 500');
|
||||
return null;
|
||||
}
|
||||
|
||||
$queue_k = (strlen($vary) > 32 ? md5($vary) : $vary) . ' ' . $url_tag;
|
||||
$this->_queue[$queue_k] = array(
|
||||
'url' => apply_filters('litespeed_ccss_url', $request_url),
|
||||
'user_agent' => substr($ua, 0, 200),
|
||||
'is_mobile' => $this->_separate_mobile(),
|
||||
'is_webp' => $this->cls('Media')->webp_support() ? 1 : 0,
|
||||
'uid' => $uid,
|
||||
'vary' => $vary,
|
||||
'url_tag' => $url_tag,
|
||||
); // Current UA will be used to request
|
||||
$this->save_queue('ccss', $this->_queue);
|
||||
self::debug('Added queue_ccss [url_tag] ' . $url_tag . ' [UA] ' . $ua . ' [vary] ' . $vary . ' [uid] ' . $uid);
|
||||
|
||||
// Prepare cache tag for later purge
|
||||
Tag::add('CCSS.' . md5($queue_k));
|
||||
|
||||
// For v4.1- clean up
|
||||
if (isset($this->_summary['ccss_type_history']) || isset($this->_summary['ccss_history']) || isset($this->_summary['queue_ccss'])) {
|
||||
if (isset($this->_summary['ccss_type_history'])) {
|
||||
unset($this->_summary['ccss_type_history']);
|
||||
}
|
||||
if (isset($this->_summary['ccss_history'])) {
|
||||
unset($this->_summary['ccss_history']);
|
||||
}
|
||||
if (isset($this->_summary['queue_ccss'])) {
|
||||
unset($this->_summary['queue_ccss']);
|
||||
}
|
||||
self::save_summary();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cron ccss generation
|
||||
*
|
||||
* @since 2.3
|
||||
* @access private
|
||||
*/
|
||||
public static function cron_ccss( $continue = false ) {
|
||||
$_instance = self::cls();
|
||||
return $_instance->_cron_handler('ccss', $continue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle UCSS/CCSS cron
|
||||
*
|
||||
* @since 4.2
|
||||
*/
|
||||
private function _cron_handler( $type, $continue ) {
|
||||
$this->_queue = $this->load_queue($type);
|
||||
|
||||
if (empty($this->_queue)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$type_tag = strtoupper($type);
|
||||
|
||||
// For cron, need to check request interval too
|
||||
if (!$continue) {
|
||||
if (!empty($this->_summary['curr_request_' . $type]) && time() - $this->_summary['curr_request_' . $type] < 300 && !$this->conf(self::O_DEBUG)) {
|
||||
Debug2::debug('[' . $type_tag . '] Last request not done');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$i = 0;
|
||||
foreach ($this->_queue as $k => $v) {
|
||||
if (!empty($v['_status'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Debug2::debug('[' . $type_tag . '] cron job [tag] ' . $k . ' [url] ' . $v['url'] . ($v['is_mobile'] ? ' 📱 ' : '') . ' [UA] ' . $v['user_agent']);
|
||||
|
||||
if ($type == 'ccss' && empty($v['url_tag'])) {
|
||||
unset($this->_queue[$k]);
|
||||
$this->save_queue($type, $this->_queue);
|
||||
Debug2::debug('[CCSS] wrong queue_ccss format');
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isset($v['is_webp'])) {
|
||||
$v['is_webp'] = false;
|
||||
}
|
||||
|
||||
++$i;
|
||||
$res = $this->_send_req($v['url'], $k, $v['uid'], $v['user_agent'], $v['vary'], $v['url_tag'], $type, $v['is_mobile'], $v['is_webp']);
|
||||
if (!$res) {
|
||||
// Status is wrong, drop this this->_queue
|
||||
unset($this->_queue[$k]);
|
||||
$this->save_queue($type, $this->_queue);
|
||||
|
||||
if (!$continue) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($i > 3) {
|
||||
GUI::print_loading(count($this->_queue), $type_tag);
|
||||
return Router::self_redirect(Router::ACTION_CSS, self::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, self::TYPE_GEN_CCSS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send to QC API to generate CCSS/UCSS
|
||||
*
|
||||
* @since 2.3
|
||||
* @access private
|
||||
*/
|
||||
private function _send_req( $request_url, $queue_k, $uid, $user_agent, $vary, $url_tag, $type, $is_mobile, $is_webp ) {
|
||||
// Check if has credit to push or not
|
||||
$err = false;
|
||||
$allowance = $this->cls('Cloud')->allowance(Cloud::SVC_CCSS, $err);
|
||||
if (!$allowance) {
|
||||
Debug2::debug('[CCSS] ❌ No credit: ' . $err);
|
||||
$err && Admin_Display::error(Error::msg($err));
|
||||
return 'out_of_quota';
|
||||
}
|
||||
|
||||
set_time_limit(120);
|
||||
|
||||
// Update css request status
|
||||
$this->_summary['curr_request_' . $type] = time();
|
||||
self::save_summary();
|
||||
|
||||
// Gather guest HTML to send
|
||||
$html = $this->prepare_html($request_url, $user_agent, $uid);
|
||||
|
||||
if (!$html) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Parse HTML to gather all CSS content before requesting
|
||||
list($css, $html) = $this->prepare_css($html, $is_webp);
|
||||
|
||||
if (!$css) {
|
||||
$type_tag = strtoupper($type);
|
||||
Debug2::debug('[' . $type_tag . '] ❌ No combined css');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Generate critical css
|
||||
$data = array(
|
||||
'url' => $request_url,
|
||||
'queue_k' => $queue_k,
|
||||
'user_agent' => $user_agent,
|
||||
'is_mobile' => $is_mobile ? 1 : 0, // todo:compatible w/ tablet
|
||||
'is_webp' => $is_webp ? 1 : 0,
|
||||
'html' => $html,
|
||||
'css' => $css,
|
||||
);
|
||||
if (!isset($this->_ccss_whitelist)) {
|
||||
$this->_ccss_whitelist = $this->_filter_whitelist();
|
||||
}
|
||||
$data['whitelist'] = $this->_ccss_whitelist;
|
||||
|
||||
self::debug('Generating: ', $data);
|
||||
|
||||
$json = Cloud::post(Cloud::SVC_CCSS, $data, 30);
|
||||
if (!is_array($json)) {
|
||||
return $json;
|
||||
}
|
||||
|
||||
// Old version compatibility
|
||||
if (empty($json['status'])) {
|
||||
if (!empty($json[$type])) {
|
||||
$this->_save_con($type, $json[$type], $queue_k, $is_mobile, $is_webp);
|
||||
}
|
||||
|
||||
// Delete the row
|
||||
return false;
|
||||
}
|
||||
|
||||
// Unknown status, remove this line
|
||||
if ($json['status'] != 'queued') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Save summary data
|
||||
$this->_summary['last_spent_' . $type] = time() - $this->_summary['curr_request_' . $type];
|
||||
$this->_summary['last_request_' . $type] = $this->_summary['curr_request_' . $type];
|
||||
$this->_summary['curr_request_' . $type] = 0;
|
||||
self::save_summary();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save CCSS/UCSS content
|
||||
*
|
||||
* @since 4.2
|
||||
*/
|
||||
private function _save_con( $type, $css, $queue_k, $mobile, $webp ) {
|
||||
// Add filters
|
||||
$css = apply_filters('litespeed_' . $type, $css, $queue_k);
|
||||
Debug2::debug2('[CSS] con: ' . $css);
|
||||
|
||||
if (substr($css, 0, 2) == '/*' && substr($css, -2) == '*/') {
|
||||
self::debug('❌ empty ' . $type . ' [content] ' . $css);
|
||||
// continue; // Save the error info too
|
||||
}
|
||||
|
||||
// Write to file
|
||||
$filecon_md5 = md5($css);
|
||||
|
||||
$filepath_prefix = $this->_build_filepath_prefix($type);
|
||||
$static_file = LITESPEED_STATIC_DIR . $filepath_prefix . $filecon_md5 . '.css';
|
||||
|
||||
File::save($static_file, $css, true);
|
||||
|
||||
$url_tag = $this->_queue[$queue_k]['url_tag'];
|
||||
$vary = $this->_queue[$queue_k]['vary'];
|
||||
Debug2::debug2("[CSS] Save URL to file [file] $static_file [vary] $vary");
|
||||
|
||||
$this->cls('Data')->save_url($url_tag, $vary, $type, $filecon_md5, dirname($static_file), $mobile, $webp);
|
||||
|
||||
Purge::add(strtoupper($type) . '.' . md5($queue_k));
|
||||
}
|
||||
|
||||
/**
|
||||
* Play for fun
|
||||
*
|
||||
* @since 3.4.3
|
||||
*/
|
||||
public function test_url( $request_url ) {
|
||||
$user_agent = $_SERVER['HTTP_USER_AGENT'];
|
||||
$html = $this->prepare_html($request_url, $user_agent);
|
||||
list($css, $html) = $this->prepare_css($html, true, true);
|
||||
// var_dump( $css );
|
||||
// $html = <<<EOT
|
||||
|
||||
// EOT;
|
||||
|
||||
// $css = <<<EOT
|
||||
|
||||
// EOT;
|
||||
$data = array(
|
||||
'url' => $request_url,
|
||||
'ccss_type' => 'test',
|
||||
'user_agent' => $user_agent,
|
||||
'is_mobile' => 0,
|
||||
'html' => $html,
|
||||
'css' => $css,
|
||||
'type' => 'CCSS',
|
||||
);
|
||||
|
||||
// self::debug( 'Generating: ', $data );
|
||||
|
||||
$json = Cloud::post(Cloud::SVC_CCSS, $data, 180);
|
||||
|
||||
var_dump($json);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare HTML from URL
|
||||
*
|
||||
* @since 3.4.3
|
||||
*/
|
||||
public function prepare_html( $request_url, $user_agent, $uid = false ) {
|
||||
$html = $this->cls('Crawler')->self_curl(add_query_arg('LSCWP_CTRL', 'before_optm', $request_url), $user_agent, $uid);
|
||||
Debug2::debug2('[CSS] self_curl result....', $html);
|
||||
|
||||
if (!$html) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$html = $this->cls('Optimizer')->html_min($html, true);
|
||||
// Drop <noscript>xxx</noscript>
|
||||
$html = preg_replace('#<noscript>.*</noscript>#isU', '', $html);
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare CSS from HTML for CCSS generation only. UCSS will used combined CSS directly.
|
||||
* Prepare refined HTML for both CCSS and UCSS.
|
||||
*
|
||||
* @since 3.4.3
|
||||
*/
|
||||
public function prepare_css( $html, $is_webp = false, $dryrun = false ) {
|
||||
$css = '';
|
||||
preg_match_all('#<link ([^>]+)/?>|<style([^>]*)>([^<]+)</style>#isU', $html, $matches, PREG_SET_ORDER);
|
||||
foreach ($matches as $match) {
|
||||
$debug_info = '';
|
||||
if (strpos($match[0], '<link') === 0) {
|
||||
$attrs = Utility::parse_attr($match[1]);
|
||||
|
||||
if (empty($attrs['rel'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($attrs['rel'] != 'stylesheet') {
|
||||
if ($attrs['rel'] != 'preload' || empty($attrs['as']) || $attrs['as'] != 'style') {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($attrs['media']) && strpos($attrs['media'], 'print') !== false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (empty($attrs['href'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check Google fonts hit
|
||||
if (strpos($attrs['href'], 'fonts.googleapis.com') !== false) {
|
||||
$html = str_replace($match[0], '', $html);
|
||||
continue;
|
||||
}
|
||||
|
||||
$debug_info = $attrs['href'];
|
||||
|
||||
// Load CSS content
|
||||
if (!$dryrun) {
|
||||
// Dryrun will not load CSS but just drop them
|
||||
$con = $this->cls('Optimizer')->load_file($attrs['href']);
|
||||
if (!$con) {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
$con = '';
|
||||
}
|
||||
} else {
|
||||
// Inline style
|
||||
$attrs = Utility::parse_attr($match[2]);
|
||||
|
||||
if (!empty($attrs['media']) && strpos($attrs['media'], 'print') !== false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Debug2::debug2('[CSS] Load inline CSS ' . substr($match[3], 0, 100) . '...', $attrs);
|
||||
$con = $match[3];
|
||||
|
||||
$debug_info = '__INLINE__';
|
||||
}
|
||||
|
||||
$con = Optimizer::minify_css($con);
|
||||
if ($is_webp && $this->cls('Media')->webp_support()) {
|
||||
$con = $this->cls('Media')->replace_background_webp($con);
|
||||
}
|
||||
|
||||
if (!empty($attrs['media']) && $attrs['media'] !== 'all') {
|
||||
$con = '@media ' . $attrs['media'] . '{' . $con . "}\n";
|
||||
} else {
|
||||
$con = $con . "\n";
|
||||
}
|
||||
|
||||
$con = '/* ' . $debug_info . ' */' . $con;
|
||||
$css .= $con;
|
||||
|
||||
$html = str_replace($match[0], '', $html);
|
||||
}
|
||||
|
||||
return array( $css, $html );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter the comment content, add quotes to selector from whitelist. Return the json
|
||||
*
|
||||
* @since 7.1
|
||||
*/
|
||||
private function _filter_whitelist() {
|
||||
$whitelist = array();
|
||||
$list = apply_filters('litespeed_ccss_whitelist', $this->conf(self::O_OPTM_CCSS_SELECTOR_WHITELIST));
|
||||
foreach ($list as $v) {
|
||||
if (substr($v, 0, 2) === '//') {
|
||||
continue;
|
||||
}
|
||||
$whitelist[] = $v;
|
||||
}
|
||||
|
||||
return $whitelist;
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify finished from server
|
||||
*
|
||||
* @since 7.1
|
||||
*/
|
||||
public function notify() {
|
||||
$post_data = \json_decode(file_get_contents('php://input'), true);
|
||||
if (is_null($post_data)) {
|
||||
$post_data = $_POST;
|
||||
}
|
||||
self::debug('notify() data', $post_data);
|
||||
|
||||
$this->_queue = $this->load_queue('ccss');
|
||||
|
||||
list($post_data) = $this->cls('Cloud')->extract_msg($post_data, 'ccss');
|
||||
|
||||
$notified_data = $post_data['data'];
|
||||
if (empty($notified_data) || !is_array($notified_data)) {
|
||||
self::debug('❌ notify exit: no notified data');
|
||||
return Cloud::err('no notified data');
|
||||
}
|
||||
|
||||
// Check if its in queue or not
|
||||
$valid_i = 0;
|
||||
foreach ($notified_data as $v) {
|
||||
if (empty($v['request_url'])) {
|
||||
self::debug('❌ notify bypass: no request_url', $v);
|
||||
continue;
|
||||
}
|
||||
if (empty($v['queue_k'])) {
|
||||
self::debug('❌ notify bypass: no queue_k', $v);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (empty($this->_queue[$v['queue_k']])) {
|
||||
self::debug('❌ notify bypass: no this queue [q_k]' . $v['queue_k']);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Save data
|
||||
if (!empty($v['data_ccss'])) {
|
||||
$is_mobile = $this->_queue[$v['queue_k']]['is_mobile'];
|
||||
$is_webp = $this->_queue[$v['queue_k']]['is_webp'];
|
||||
$this->_save_con('ccss', $v['data_ccss'], $v['queue_k'], $is_mobile, $is_webp);
|
||||
|
||||
++$valid_i;
|
||||
}
|
||||
|
||||
unset($this->_queue[$v['queue_k']]);
|
||||
self::debug('notify data handled, unset queue [q_k] ' . $v['queue_k']);
|
||||
}
|
||||
$this->save_queue('ccss', $this->_queue);
|
||||
|
||||
self::debug('notified');
|
||||
|
||||
return Cloud::ok(array( 'count' => $valid_i ));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle all request actions from main cls
|
||||
*
|
||||
* @since 2.3
|
||||
* @access public
|
||||
*/
|
||||
public function handler() {
|
||||
$type = Router::verify_type();
|
||||
|
||||
switch ($type) {
|
||||
case self::TYPE_GEN_CCSS:
|
||||
self::cron_ccss(true);
|
||||
break;
|
||||
|
||||
case self::TYPE_CLEAR_Q_CCSS:
|
||||
$this->clear_q('ccss');
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
Admin::redirect();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,704 @@
|
||||
<?php
|
||||
// phpcs:ignoreFile
|
||||
|
||||
/**
|
||||
* The class to store and manage litespeed db data.
|
||||
*
|
||||
* @since 1.3.1
|
||||
* @package LiteSpeed
|
||||
*/
|
||||
|
||||
namespace LiteSpeed;
|
||||
|
||||
defined('WPINC') || exit();
|
||||
|
||||
class Data extends Root {
|
||||
|
||||
const LOG_TAG = '🚀';
|
||||
|
||||
private $_db_updater = array(
|
||||
'5.3-a5' => array( 'litespeed_update_5_3' ),
|
||||
'7.0-b26' => array( 'litespeed_update_7' ),
|
||||
'7.0.1-b1' => array( 'litespeed_update_7_0_1' ),
|
||||
);
|
||||
|
||||
private $_db_site_updater = array(
|
||||
// Example
|
||||
// '2.0' => array(
|
||||
// 'litespeed_update_site_2_0',
|
||||
// ),
|
||||
);
|
||||
|
||||
private $_url_file_types = array(
|
||||
'css' => 1,
|
||||
'js' => 2,
|
||||
'ccss' => 3,
|
||||
'ucss' => 4,
|
||||
);
|
||||
|
||||
const TB_IMG_OPTM = 'litespeed_img_optm';
|
||||
const TB_IMG_OPTMING = 'litespeed_img_optming'; // working table
|
||||
const TB_AVATAR = 'litespeed_avatar';
|
||||
const TB_CRAWLER = 'litespeed_crawler';
|
||||
const TB_CRAWLER_BLACKLIST = 'litespeed_crawler_blacklist';
|
||||
const TB_URL = 'litespeed_url';
|
||||
const TB_URL_FILE = 'litespeed_url_file';
|
||||
|
||||
/**
|
||||
* Init
|
||||
*
|
||||
* @since 1.3.1
|
||||
*/
|
||||
public function __construct() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct table existence
|
||||
*
|
||||
* Call when activate -> update_confs()
|
||||
* Call when update_confs()
|
||||
*
|
||||
* @since 3.0
|
||||
* @access public
|
||||
*/
|
||||
public function correct_tb_existence() {
|
||||
// Gravatar
|
||||
if ($this->conf(Base::O_DISCUSS_AVATAR_CACHE)) {
|
||||
$this->tb_create('avatar');
|
||||
}
|
||||
|
||||
// Crawler
|
||||
if ($this->conf(Base::O_CRAWLER)) {
|
||||
$this->tb_create('crawler');
|
||||
$this->tb_create('crawler_blacklist');
|
||||
}
|
||||
|
||||
// URL mapping
|
||||
$this->tb_create('url');
|
||||
$this->tb_create('url_file');
|
||||
|
||||
// Image optm is a bit different. Only trigger creation when sending requests. Drop when destroying.
|
||||
}
|
||||
|
||||
/**
|
||||
* Upgrade conf to latest format version from previous versions
|
||||
*
|
||||
* NOTE: Only for v3.0+
|
||||
*
|
||||
* @since 3.0
|
||||
* @access public
|
||||
*/
|
||||
public function conf_upgrade( $ver ) {
|
||||
// Skip count check if `Use Primary Site Configurations` is on
|
||||
// Deprecated since v3.0 as network primary site didn't override the subsites conf yet
|
||||
// if ( ! is_main_site() && ! empty ( $this->_site_options[ self::NETWORK_O_USE_PRIMARY ] ) ) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
if ($this->_get_upgrade_lock()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->_set_upgrade_lock(true);
|
||||
|
||||
require_once LSCWP_DIR . 'src/data.upgrade.func.php';
|
||||
|
||||
// Init log manually
|
||||
if ($this->conf(Base::O_DEBUG)) {
|
||||
$this->cls('Debug2')->init();
|
||||
}
|
||||
|
||||
foreach ($this->_db_updater as $k => $v) {
|
||||
if (version_compare($ver, $k, '<')) {
|
||||
// run each callback
|
||||
foreach ($v as $v2) {
|
||||
self::debug("Updating [ori_v] $ver \t[to] $k \t[func] $v2");
|
||||
call_user_func($v2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reload options
|
||||
$this->cls('Conf')->load_options();
|
||||
|
||||
$this->correct_tb_existence();
|
||||
|
||||
// Update related files
|
||||
$this->cls('Activation')->update_files();
|
||||
|
||||
// Update version to latest
|
||||
Conf::delete_option(Base::_VER);
|
||||
Conf::add_option(Base::_VER, Core::VER);
|
||||
|
||||
self::debug('Updated version to ' . Core::VER);
|
||||
|
||||
$this->_set_upgrade_lock(false);
|
||||
|
||||
!defined('LSWCP_EMPTYCACHE') && define('LSWCP_EMPTYCACHE', true); // clear all sites caches
|
||||
Purge::purge_all();
|
||||
|
||||
return 'upgrade';
|
||||
}
|
||||
|
||||
/**
|
||||
* Upgrade site conf to latest format version from previous versions
|
||||
*
|
||||
* NOTE: Only for v3.0+
|
||||
*
|
||||
* @since 3.0
|
||||
* @access public
|
||||
*/
|
||||
public function conf_site_upgrade( $ver ) {
|
||||
if ($this->_get_upgrade_lock()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->_set_upgrade_lock(true);
|
||||
|
||||
require_once LSCWP_DIR . 'src/data.upgrade.func.php';
|
||||
|
||||
foreach ($this->_db_site_updater as $k => $v) {
|
||||
if (version_compare($ver, $k, '<')) {
|
||||
// run each callback
|
||||
foreach ($v as $v2) {
|
||||
self::debug("Updating site [ori_v] $ver \t[to] $k \t[func] $v2");
|
||||
call_user_func($v2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reload options
|
||||
$this->cls('Conf')->load_site_options();
|
||||
|
||||
Conf::delete_site_option(Base::_VER);
|
||||
Conf::add_site_option(Base::_VER, Core::VER);
|
||||
|
||||
self::debug('Updated site_version to ' . Core::VER);
|
||||
|
||||
$this->_set_upgrade_lock(false);
|
||||
|
||||
!defined('LSWCP_EMPTYCACHE') && define('LSWCP_EMPTYCACHE', true); // clear all sites caches
|
||||
Purge::purge_all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if upgrade script is running or not
|
||||
*
|
||||
* @since 3.0.1
|
||||
*/
|
||||
private function _get_upgrade_lock() {
|
||||
$is_upgrading = get_option('litespeed.data.upgrading');
|
||||
if (!$is_upgrading) {
|
||||
$this->_set_upgrade_lock(false); // set option value to existed to avoid repeated db query next time
|
||||
}
|
||||
if ($is_upgrading && time() - $is_upgrading < 3600) {
|
||||
return $is_upgrading;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the upgrading banner if upgrade script is running
|
||||
*
|
||||
* @since 3.0.1
|
||||
*/
|
||||
public function check_upgrading_msg() {
|
||||
$is_upgrading = $this->_get_upgrade_lock();
|
||||
if (!$is_upgrading) {
|
||||
return;
|
||||
}
|
||||
|
||||
Admin_Display::info(
|
||||
sprintf(
|
||||
__('The database has been upgrading in the background since %s. This message will disappear once upgrade is complete.', 'litespeed-cache'),
|
||||
'<code>' . Utility::readable_time($is_upgrading) . '</code>'
|
||||
) . ' [LiteSpeed]',
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set lock for upgrade process
|
||||
*
|
||||
* @since 3.0.1
|
||||
*/
|
||||
private function _set_upgrade_lock( $lock ) {
|
||||
if (!$lock) {
|
||||
update_option('litespeed.data.upgrading', -1);
|
||||
} else {
|
||||
update_option('litespeed.data.upgrading', time());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
$save_state = $wpdb->suppress_errors;
|
||||
$wpdb->suppress_errors(true);
|
||||
$describe = $wpdb->get_var('DESCRIBE `' . $this->tb($tb) . '`');
|
||||
$wpdb->suppress_errors($save_state);
|
||||
|
||||
return $describe !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get data structure of one table
|
||||
*
|
||||
* @since 2.0
|
||||
* @access private
|
||||
*/
|
||||
private function _tb_structure( $tb ) {
|
||||
return File::read(LSCWP_DIR . 'src/data_structure/' . $tb . '.sql');
|
||||
}
|
||||
|
||||
/**
|
||||
* Create img optm table and sync data from wp_postmeta
|
||||
*
|
||||
* @since 3.0
|
||||
* @access public
|
||||
*/
|
||||
public function tb_create( $tb ) {
|
||||
global $wpdb;
|
||||
|
||||
self::debug2('[Data] Checking table ' . $tb);
|
||||
|
||||
// Check if table exists first
|
||||
if ($this->tb_exist($tb)) {
|
||||
self::debug2('[Data] Existed');
|
||||
return;
|
||||
}
|
||||
|
||||
self::debug('Creating ' . $tb);
|
||||
|
||||
$sql = sprintf(
|
||||
'CREATE TABLE IF NOT EXISTS `%1$s` (' . $this->_tb_structure($tb) . ') %2$s;',
|
||||
$this->tb($tb),
|
||||
$wpdb->get_charset_collate() // 'DEFAULT CHARSET=utf8'
|
||||
);
|
||||
|
||||
$res = $wpdb->query($sql);
|
||||
if ($res !== true) {
|
||||
self::debug('Warning! Creating table failed!', $sql);
|
||||
Admin_Display::error(Error::msg('failed_tb_creation', array( '<code>' . $tb . '</code>', '<code>' . $sql . '</code>' )));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Drop table
|
||||
*
|
||||
* @since 3.0
|
||||
* @access public
|
||||
*/
|
||||
public function tb_del( $tb ) {
|
||||
global $wpdb;
|
||||
|
||||
if (!$this->tb_exist($tb)) {
|
||||
return;
|
||||
}
|
||||
|
||||
self::debug('Deleting table ' . $tb);
|
||||
|
||||
$q = 'DROP TABLE IF EXISTS ' . $this->tb($tb);
|
||||
$wpdb->query($q);
|
||||
}
|
||||
|
||||
/**
|
||||
* Drop generated tables
|
||||
*
|
||||
* @since 3.0
|
||||
* @access public
|
||||
*/
|
||||
public function tables_del() {
|
||||
$this->tb_del('avatar');
|
||||
$this->tb_del('crawler');
|
||||
$this->tb_del('crawler_blacklist');
|
||||
$this->tb_del('url');
|
||||
$this->tb_del('url_file');
|
||||
|
||||
// Deleting img_optm only can be done when destroy all optm images
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep table but clear all data
|
||||
*
|
||||
* @since 4.0
|
||||
*/
|
||||
public function table_truncate( $tb ) {
|
||||
global $wpdb;
|
||||
$q = 'TRUNCATE TABLE ' . $this->tb($tb);
|
||||
$wpdb->query($q);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean certain type of url_file
|
||||
*
|
||||
* @since 4.0
|
||||
*/
|
||||
public function url_file_clean( $file_type ) {
|
||||
global $wpdb;
|
||||
|
||||
if (!$this->tb_exist('url_file')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$type = $this->_url_file_types[$file_type];
|
||||
$q = 'DELETE FROM ' . $this->tb('url_file') . ' WHERE `type` = %d';
|
||||
$wpdb->query($wpdb->prepare($q, $type));
|
||||
|
||||
// Added to cleanup url table. See issue: https://wordpress.org/support/topic/wp_litespeed_url-1-1-gb-in-db-huge-big/
|
||||
$wpdb->query(
|
||||
'DELETE d
|
||||
FROM `' .
|
||||
$this->tb('url') .
|
||||
'` AS d
|
||||
LEFT JOIN `' .
|
||||
$this->tb('url_file') .
|
||||
'` AS f ON d.`id` = f.`url_id`
|
||||
WHERE f.`url_id` IS NULL'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate filename based on URL, if content md5 existed, reuse existing file.
|
||||
*
|
||||
* @since 4.0
|
||||
*/
|
||||
public function save_url( $request_url, $vary, $file_type, $filecon_md5, $path, $mobile = false, $webp = false ) {
|
||||
global $wpdb;
|
||||
|
||||
if (strlen($vary) > 32) {
|
||||
$vary = md5($vary);
|
||||
}
|
||||
|
||||
$type = $this->_url_file_types[$file_type];
|
||||
|
||||
$tb_url = $this->tb('url');
|
||||
$tb_url_file = $this->tb('url_file');
|
||||
$q = "SELECT * FROM `$tb_url` WHERE url=%s";
|
||||
$url_row = $wpdb->get_row($wpdb->prepare($q, $request_url), ARRAY_A);
|
||||
if (!$url_row) {
|
||||
$q = "INSERT INTO `$tb_url` SET url=%s";
|
||||
$wpdb->query($wpdb->prepare($q, $request_url));
|
||||
$url_id = $wpdb->insert_id;
|
||||
} else {
|
||||
$url_id = $url_row['id'];
|
||||
}
|
||||
|
||||
$q = "SELECT * FROM `$tb_url_file` WHERE url_id=%d AND vary=%s AND type=%d AND expired=0";
|
||||
$file_row = $wpdb->get_row($wpdb->prepare($q, array( $url_id, $vary, $type )), ARRAY_A);
|
||||
|
||||
// Check if has previous file or not
|
||||
if ($file_row && $file_row['filename'] == $filecon_md5) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the new $filecon_md5 is marked as expired by previous records, clear those records
|
||||
$q = "DELETE FROM `$tb_url_file` WHERE filename = %s AND expired > 0";
|
||||
$wpdb->query($wpdb->prepare($q, $filecon_md5));
|
||||
|
||||
// Check if there is any other record used the same filename or not
|
||||
$q = "SELECT id FROM `$tb_url_file` WHERE filename = %s AND expired = 0 AND id != %d LIMIT 1";
|
||||
if ($file_row && $wpdb->get_var($wpdb->prepare($q, array( $file_row['filename'], $file_row['id'] )))) {
|
||||
$q = "UPDATE `$tb_url_file` SET filename=%s WHERE id=%d";
|
||||
$wpdb->query($wpdb->prepare($q, array( $filecon_md5, $file_row['id'] )));
|
||||
return;
|
||||
}
|
||||
|
||||
// New record needed
|
||||
$q = "INSERT INTO `$tb_url_file` SET url_id=%d, vary=%s, filename=%s, type=%d, mobile=%d, webp=%d, expired=0";
|
||||
$wpdb->query($wpdb->prepare($q, array( $url_id, $vary, $filecon_md5, $type, $mobile ? 1 : 0, $webp ? 1 : 0 )));
|
||||
|
||||
// Mark existing rows as expired
|
||||
if ($file_row) {
|
||||
$q = "UPDATE `$tb_url_file` SET expired=%d WHERE id=%d";
|
||||
$expired = time() + 86400 * apply_filters('litespeed_url_file_expired_days', 20);
|
||||
$wpdb->query($wpdb->prepare($q, array( $expired, $file_row['id'] )));
|
||||
|
||||
// Also check if has other files expired already to be deleted
|
||||
$q = "SELECT * FROM `$tb_url_file` WHERE url_id = %d AND expired BETWEEN 1 AND %d";
|
||||
$q = $wpdb->prepare($q, array( $url_id, time() ));
|
||||
$list = $wpdb->get_results($q, ARRAY_A);
|
||||
if ($list) {
|
||||
foreach ($list as $v) {
|
||||
$file_to_del = $path . '/' . $v['filename'] . '.' . ($file_type == 'js' ? 'js' : 'css');
|
||||
if (file_exists($file_to_del)) {
|
||||
// Safe to delete
|
||||
self::debug('Delete expired unused file: ' . $file_to_del);
|
||||
|
||||
// Clear related lscache first to avoid cache copy of same URL w/ diff QS
|
||||
// Purge::add( Tag::TYPE_MIN . '.' . $file_row[ 'filename' ] . '.' . $file_type );
|
||||
|
||||
unlink($file_to_del);
|
||||
}
|
||||
}
|
||||
$q = "DELETE FROM `$tb_url_file` WHERE url_id = %d AND expired BETWEEN 1 AND %d";
|
||||
$wpdb->query($wpdb->prepare($q, array( $url_id, time() )));
|
||||
}
|
||||
}
|
||||
|
||||
// Purge this URL to avoid cache copy of same URL w/ diff QS
|
||||
// $this->cls( 'Purge' )->purge_url( Utility::make_relative( $request_url ) ?: '/', true, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Load CCSS related file
|
||||
*
|
||||
* @since 4.0
|
||||
*/
|
||||
public function load_url_file( $request_url, $vary, $file_type ) {
|
||||
global $wpdb;
|
||||
|
||||
if (strlen($vary) > 32) {
|
||||
$vary = md5($vary);
|
||||
}
|
||||
|
||||
$type = $this->_url_file_types[$file_type];
|
||||
|
||||
self::debug2('load url file: ' . $request_url);
|
||||
|
||||
$tb_url = $this->tb('url');
|
||||
$q = "SELECT * FROM `$tb_url` WHERE url=%s";
|
||||
$url_row = $wpdb->get_row($wpdb->prepare($q, $request_url), ARRAY_A);
|
||||
if (!$url_row) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$url_id = $url_row['id'];
|
||||
|
||||
$tb_url_file = $this->tb('url_file');
|
||||
$q = "SELECT * FROM `$tb_url_file` WHERE url_id=%d AND vary=%s AND type=%d AND expired=0";
|
||||
$file_row = $wpdb->get_row($wpdb->prepare($q, array( $url_id, $vary, $type )), ARRAY_A);
|
||||
if (!$file_row) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $file_row['filename'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark all entries of one URL to expired
|
||||
*
|
||||
* @since 4.5
|
||||
*/
|
||||
public function mark_as_expired( $request_url, $auto_q = false ) {
|
||||
global $wpdb;
|
||||
$tb_url = $this->tb('url');
|
||||
|
||||
self::debug('Try to mark as expired: ' . $request_url);
|
||||
$q = "SELECT * FROM `$tb_url` WHERE url=%s";
|
||||
$url_row = $wpdb->get_row($wpdb->prepare($q, $request_url), ARRAY_A);
|
||||
if (!$url_row) {
|
||||
return;
|
||||
}
|
||||
|
||||
self::debug('Mark url_id=' . $url_row['id'] . ' as expired');
|
||||
|
||||
$tb_url_file = $this->tb('url_file');
|
||||
|
||||
$existing_url_files = array();
|
||||
if ($auto_q) {
|
||||
$q = "SELECT a.*, b.url FROM `$tb_url_file` a LEFT JOIN `$tb_url` b ON b.id=a.url_id WHERE a.url_id=%d AND a.type=4 AND a.expired=0";
|
||||
$q = $wpdb->prepare($q, $url_row['id']);
|
||||
$existing_url_files = $wpdb->get_results($q, ARRAY_A);
|
||||
}
|
||||
$q = "UPDATE `$tb_url_file` SET expired=%d WHERE url_id=%d AND type=4 AND expired=0";
|
||||
$expired = time() + 86400 * apply_filters('litespeed_url_file_expired_days', 20);
|
||||
$wpdb->query($wpdb->prepare($q, array( $expired, $url_row['id'] )));
|
||||
|
||||
return $existing_url_files;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list from `data/css_excludes.txt`
|
||||
*
|
||||
* @since 3.6
|
||||
*/
|
||||
public function load_css_exc( $list ) {
|
||||
$data = $this->_load_per_line('css_excludes.txt');
|
||||
if ($data) {
|
||||
$list = array_unique(array_filter(array_merge($list, $data)));
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list from `data/ccss_whitelist.txt`
|
||||
*
|
||||
* @since 7.1
|
||||
*/
|
||||
public function load_ccss_whitelist( $list ) {
|
||||
$data = $this->_load_per_line('ccss_whitelist.txt');
|
||||
if ($data) {
|
||||
$list = array_unique(array_filter(array_merge($list, $data)));
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list from `data/ucss_whitelist.txt`
|
||||
*
|
||||
* @since 4.0
|
||||
*/
|
||||
public function load_ucss_whitelist( $list ) {
|
||||
$data = $this->_load_per_line('ucss_whitelist.txt');
|
||||
if ($data) {
|
||||
$list = array_unique(array_filter(array_merge($list, $data)));
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list from `data/js_excludes.txt`
|
||||
*
|
||||
* @since 3.5
|
||||
*/
|
||||
public function load_js_exc( $list ) {
|
||||
$data = $this->_load_per_line('js_excludes.txt');
|
||||
if ($data) {
|
||||
$list = array_unique(array_filter(array_merge($list, $data)));
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list from `data/js_defer_excludes.txt`
|
||||
*
|
||||
* @since 3.6
|
||||
*/
|
||||
public function load_js_defer_exc( $list ) {
|
||||
$data = $this->_load_per_line('js_defer_excludes.txt');
|
||||
if ($data) {
|
||||
$list = array_unique(array_filter(array_merge($list, $data)));
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list from `data/optm_uri_exc.txt`
|
||||
*
|
||||
* @since 5.4
|
||||
*/
|
||||
public function load_optm_uri_exc( $list ) {
|
||||
$data = $this->_load_per_line('optm_uri_exc.txt');
|
||||
if ($data) {
|
||||
$list = array_unique(array_filter(array_merge($list, $data)));
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list from `data/esi.nonces.txt`
|
||||
*
|
||||
* @since 3.5
|
||||
*/
|
||||
public function load_esi_nonces( $list ) {
|
||||
$data = $this->_load_per_line('esi.nonces.txt');
|
||||
if ($data) {
|
||||
$list = array_unique(array_filter(array_merge($list, $data)));
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list from `data/cache_nocacheable.txt`
|
||||
*
|
||||
* @since 6.3.0.1
|
||||
*/
|
||||
public function load_cache_nocacheable( $list ) {
|
||||
$data = $this->_load_per_line('cache_nocacheable.txt');
|
||||
if ($data) {
|
||||
$list = array_unique(array_filter(array_merge($list, $data)));
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load file per line
|
||||
*
|
||||
* Support two kinds of comments:
|
||||
* 1. `# this is comment`
|
||||
* 2. `##this is comment`
|
||||
*
|
||||
* @since 3.5
|
||||
*/
|
||||
private function _load_per_line( $file ) {
|
||||
$data = File::read(LSCWP_DIR . 'data/' . $file);
|
||||
$data = explode(PHP_EOL, $data);
|
||||
$list = array();
|
||||
foreach ($data as $v) {
|
||||
// Drop two kinds of comments
|
||||
if (strpos($v, '##') !== false) {
|
||||
$v = trim(substr($v, 0, strpos($v, '##')));
|
||||
}
|
||||
if (strpos($v, '# ') !== false) {
|
||||
$v = trim(substr($v, 0, strpos($v, '# ')));
|
||||
}
|
||||
|
||||
if (!$v) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$list[] = $v;
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
<?php
|
||||
// phpcs:ignoreFile
|
||||
|
||||
/**
|
||||
* Database upgrade funcs
|
||||
*
|
||||
* NOTE: whenever called this file, always call Data::get_upgrade_lock and Data::_set_upgrade_lock first.
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
defined('WPINC') || exit();
|
||||
|
||||
use LiteSpeed\Debug2;
|
||||
use LiteSpeed\Cloud;
|
||||
|
||||
/**
|
||||
* Table existence check function
|
||||
*
|
||||
* @since 7.2
|
||||
*/
|
||||
function litespeed_table_exists( $table_name ) {
|
||||
global $wpdb;
|
||||
$save_state = $wpdb->suppress_errors;
|
||||
$wpdb->suppress_errors(true);
|
||||
$tb_exists = $wpdb->get_var('DESCRIBE `' . $table_name . '`');
|
||||
$wpdb->suppress_errors($save_state);
|
||||
|
||||
return $tb_exists !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate v7.0- url_files URL from no trailing slash to trailing slash
|
||||
*
|
||||
* @since 7.0.1
|
||||
*/
|
||||
function litespeed_update_7_0_1() {
|
||||
global $wpdb;
|
||||
Debug2::debug('[Data] v7.0.1 upgrade started');
|
||||
|
||||
$tb_url = $wpdb->prefix . 'litespeed_url';
|
||||
if (!litespeed_table_exists($tb_url)) {
|
||||
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 = $wpdb->prefix . 'litespeed_url_file';
|
||||
if (litespeed_table_exists($tb)) {
|
||||
$q =
|
||||
'ALTER TABLE `' .
|
||||
$tb .
|
||||
'`
|
||||
ADD COLUMN `mobile` tinyint(4) NOT NULL COMMENT "mobile=1",
|
||||
ADD COLUMN `webp` tinyint(4) NOT NULL COMMENT "webp=1"
|
||||
';
|
||||
$wpdb->query($q);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
`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`)
|
||||
@@ -0,0 +1,8 @@
|
||||
`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`)
|
||||
@@ -0,0 +1,8 @@
|
||||
`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`)
|
||||
@@ -0,0 +1,10 @@
|
||||
`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`)
|
||||
@@ -0,0 +1,9 @@
|
||||
`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))
|
||||
@@ -0,0 +1,6 @@
|
||||
`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))
|
||||
@@ -0,0 +1,14 @@
|
||||
`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`)
|
||||
@@ -0,0 +1,368 @@
|
||||
<?php
|
||||
// phpcs:ignoreFile
|
||||
|
||||
/**
|
||||
* The admin optimize tool
|
||||
*
|
||||
* @since 1.2.1
|
||||
* @package LiteSpeed
|
||||
*/
|
||||
|
||||
namespace LiteSpeed;
|
||||
|
||||
defined('WPINC') || exit();
|
||||
|
||||
class DB_Optm extends Root {
|
||||
|
||||
private static $_hide_more = false;
|
||||
|
||||
private static $TYPES = array(
|
||||
'revision',
|
||||
'orphaned_post_meta',
|
||||
'auto_draft',
|
||||
'trash_post',
|
||||
'spam_comment',
|
||||
'trash_comment',
|
||||
'trackback-pingback',
|
||||
'expired_transient',
|
||||
'all_transients',
|
||||
'optimize_tables',
|
||||
);
|
||||
const TYPE_CONV_TB = 'conv_innodb';
|
||||
|
||||
/**
|
||||
* Show if there are more sites in hidden
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
public static function hide_more() {
|
||||
return self::$_hide_more;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean/Optimize WP tables
|
||||
*
|
||||
* @since 1.2.1
|
||||
* @access public
|
||||
* @param string $type The type to clean
|
||||
* @param bool $ignore_multisite If ignore multisite check
|
||||
* @return int The rows that will be affected
|
||||
*/
|
||||
public function db_count( $type, $ignore_multisite = false ) {
|
||||
if ($type === 'all') {
|
||||
$num = 0;
|
||||
foreach (self::$TYPES as $v) {
|
||||
$num += $this->db_count($v);
|
||||
}
|
||||
return $num;
|
||||
}
|
||||
|
||||
if (!$ignore_multisite) {
|
||||
if (is_multisite() && is_network_admin()) {
|
||||
$num = 0;
|
||||
$blogs = Activation::get_network_ids();
|
||||
foreach ($blogs as $k => $blog_id) {
|
||||
if ($k > 3) {
|
||||
self::$_hide_more = true;
|
||||
break;
|
||||
}
|
||||
|
||||
switch_to_blog($blog_id);
|
||||
$num += $this->db_count($type, true);
|
||||
restore_current_blog();
|
||||
}
|
||||
return $num;
|
||||
}
|
||||
}
|
||||
|
||||
global $wpdb;
|
||||
|
||||
switch ($type) {
|
||||
case 'revision':
|
||||
$rev_max = (int) $this->conf(Base::O_DB_OPTM_REVISIONS_MAX);
|
||||
$rev_age = (int) $this->conf(Base::O_DB_OPTM_REVISIONS_AGE);
|
||||
$sql_add = '';
|
||||
if ($rev_age) {
|
||||
$sql_add = " and post_modified < DATE_SUB( NOW(), INTERVAL $rev_age DAY ) ";
|
||||
}
|
||||
$sql = "SELECT COUNT(*) FROM `$wpdb->posts` WHERE post_type = 'revision' $sql_add";
|
||||
if (!$rev_max) {
|
||||
return $wpdb->get_var($sql);
|
||||
}
|
||||
// Has count limit
|
||||
$sql = "SELECT COUNT(*)-$rev_max FROM `$wpdb->posts` WHERE post_type = 'revision' $sql_add GROUP BY post_parent HAVING count(*)>$rev_max";
|
||||
$res = $wpdb->get_results($sql, ARRAY_N);
|
||||
|
||||
Utility::compatibility();
|
||||
return array_sum(array_column($res, 0));
|
||||
|
||||
case 'orphaned_post_meta':
|
||||
return $wpdb->get_var("SELECT COUNT(*) FROM `$wpdb->postmeta` a LEFT JOIN `$wpdb->posts` b ON b.ID=a.post_id WHERE b.ID IS NULL");
|
||||
|
||||
case 'auto_draft':
|
||||
return $wpdb->get_var("SELECT COUNT(*) FROM `$wpdb->posts` WHERE post_status = 'auto-draft'");
|
||||
|
||||
case 'trash_post':
|
||||
return $wpdb->get_var("SELECT COUNT(*) FROM `$wpdb->posts` WHERE post_status = 'trash'");
|
||||
|
||||
case 'spam_comment':
|
||||
return $wpdb->get_var("SELECT COUNT(*) FROM `$wpdb->comments` WHERE comment_approved = 'spam'");
|
||||
|
||||
case 'trash_comment':
|
||||
return $wpdb->get_var("SELECT COUNT(*) FROM `$wpdb->comments` WHERE comment_approved = 'trash'");
|
||||
|
||||
case 'trackback-pingback':
|
||||
return $wpdb->get_var("SELECT COUNT(*) FROM `$wpdb->comments` WHERE comment_type = 'trackback' OR comment_type = 'pingback'");
|
||||
|
||||
case 'expired_transient':
|
||||
return $wpdb->get_var("SELECT COUNT(*) FROM `$wpdb->options` WHERE option_name LIKE '_transient_timeout%' AND option_value < " . time());
|
||||
|
||||
case 'all_transients':
|
||||
return $wpdb->get_var("SELECT COUNT(*) FROM `$wpdb->options` WHERE option_name LIKE '%_transient_%'");
|
||||
|
||||
case 'optimize_tables':
|
||||
return $wpdb->get_var("SELECT COUNT(*) FROM information_schema.tables WHERE TABLE_SCHEMA = '" . DB_NAME . "' and ENGINE <> 'InnoDB' and DATA_FREE > 0");
|
||||
}
|
||||
|
||||
return '-';
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean/Optimize WP tables
|
||||
*
|
||||
* @since 1.2.1
|
||||
* @since 3.0 changed to private
|
||||
* @access private
|
||||
*/
|
||||
private function _db_clean( $type ) {
|
||||
if ($type === 'all') {
|
||||
foreach (self::$TYPES as $v) {
|
||||
$this->_db_clean($v);
|
||||
}
|
||||
return __('Clean all successfully.', 'litespeed-cache');
|
||||
}
|
||||
|
||||
global $wpdb;
|
||||
switch ($type) {
|
||||
case 'revision':
|
||||
$rev_max = (int) $this->conf(Base::O_DB_OPTM_REVISIONS_MAX);
|
||||
$rev_age = (int) $this->conf(Base::O_DB_OPTM_REVISIONS_AGE);
|
||||
|
||||
$postmeta = "`$wpdb->postmeta`";
|
||||
$posts = "`$wpdb->posts`";
|
||||
|
||||
$sql_postmeta_join = function ( $table ) use ( $postmeta, $posts ) {
|
||||
return "
|
||||
$postmeta
|
||||
CROSS JOIN $table
|
||||
ON $posts.ID = $postmeta.post_id
|
||||
";
|
||||
};
|
||||
|
||||
$sql_where = "WHERE $posts.post_type = 'revision'";
|
||||
|
||||
$sql_add = $rev_age ? "AND $posts.post_modified < DATE_SUB( NOW(), INTERVAL $rev_age DAY )" : '';
|
||||
|
||||
if (!$rev_max) {
|
||||
$sql_where = "$sql_where $sql_add";
|
||||
$sql_postmeta = $sql_postmeta_join($posts);
|
||||
$wpdb->query("DELETE $postmeta FROM $sql_postmeta $sql_where");
|
||||
$wpdb->query("DELETE FROM $posts $sql_where");
|
||||
} else {
|
||||
// Has count limit
|
||||
$sql = "
|
||||
SELECT COUNT(*) - $rev_max
|
||||
AS del_max, post_parent
|
||||
FROM $posts
|
||||
WHERE post_type = 'revision'
|
||||
$sql_add
|
||||
GROUP BY post_parent
|
||||
HAVING COUNT(*) > $rev_max
|
||||
";
|
||||
$res = $wpdb->get_results($sql);
|
||||
$sql_where = "
|
||||
$sql_where
|
||||
AND post_parent = %d
|
||||
ORDER BY ID
|
||||
LIMIT %d
|
||||
";
|
||||
$sql_postmeta = $sql_postmeta_join("(SELECT ID FROM $posts $sql_where) AS $posts");
|
||||
foreach ($res as $v) {
|
||||
$args = array( $v->post_parent, $v->del_max );
|
||||
$sql = $wpdb->prepare("DELETE $postmeta FROM $sql_postmeta", $args);
|
||||
$wpdb->query($sql);
|
||||
$sql = $wpdb->prepare("DELETE FROM $posts $sql_where", $args);
|
||||
$wpdb->query($sql);
|
||||
}
|
||||
}
|
||||
|
||||
return __('Clean post revisions successfully.', 'litespeed-cache');
|
||||
|
||||
case 'orphaned_post_meta':
|
||||
$wpdb->query("DELETE a FROM `$wpdb->postmeta` a LEFT JOIN `$wpdb->posts` b ON b.ID=a.post_id WHERE b.ID IS NULL");
|
||||
return __('Clean orphaned post meta successfully.', 'litespeed-cache');
|
||||
|
||||
case 'auto_draft':
|
||||
$wpdb->query("DELETE FROM `$wpdb->posts` WHERE post_status = 'auto-draft'");
|
||||
return __('Clean auto drafts successfully.', 'litespeed-cache');
|
||||
|
||||
case 'trash_post':
|
||||
$wpdb->query("DELETE FROM `$wpdb->posts` WHERE post_status = 'trash'");
|
||||
return __('Clean trashed posts and pages successfully.', 'litespeed-cache');
|
||||
|
||||
case 'spam_comment':
|
||||
$wpdb->query("DELETE FROM `$wpdb->comments` WHERE comment_approved = 'spam'");
|
||||
return __('Clean spam comments successfully.', 'litespeed-cache');
|
||||
|
||||
case 'trash_comment':
|
||||
$wpdb->query("DELETE FROM `$wpdb->comments` WHERE comment_approved = 'trash'");
|
||||
return __('Clean trashed comments successfully.', 'litespeed-cache');
|
||||
|
||||
case 'trackback-pingback':
|
||||
$wpdb->query("DELETE FROM `$wpdb->comments` WHERE comment_type = 'trackback' OR comment_type = 'pingback'");
|
||||
return __('Clean trackbacks and pingbacks successfully.', 'litespeed-cache');
|
||||
|
||||
case 'expired_transient':
|
||||
$wpdb->query("DELETE FROM `$wpdb->options` WHERE option_name LIKE '_transient_timeout%' AND option_value < " . time());
|
||||
return __('Clean expired transients successfully.', 'litespeed-cache');
|
||||
|
||||
case 'all_transients':
|
||||
$wpdb->query("DELETE FROM `$wpdb->options` WHERE option_name LIKE '%\\_transient\\_%'");
|
||||
return __('Clean all transients successfully.', 'litespeed-cache');
|
||||
|
||||
case 'optimize_tables':
|
||||
$sql = "SELECT table_name, DATA_FREE FROM information_schema.tables WHERE TABLE_SCHEMA = '" . DB_NAME . "' and ENGINE <> 'InnoDB' and DATA_FREE > 0";
|
||||
$result = $wpdb->get_results($sql);
|
||||
if ($result) {
|
||||
foreach ($result as $row) {
|
||||
$wpdb->query('OPTIMIZE TABLE ' . $row->table_name);
|
||||
}
|
||||
}
|
||||
return __('Optimized all tables.', 'litespeed-cache');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all myisam tables
|
||||
*
|
||||
* @since 3.0
|
||||
* @access public
|
||||
*/
|
||||
public function list_myisam() {
|
||||
global $wpdb;
|
||||
$q = "SELECT TABLE_NAME as table_name, ENGINE as engine FROM information_schema.tables WHERE TABLE_SCHEMA = '" . DB_NAME . "' and ENGINE = 'myisam' AND TABLE_NAME LIKE '{$wpdb->prefix}%'";
|
||||
return $wpdb->get_results($q);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert tables to InnoDB
|
||||
*
|
||||
* @since 3.0
|
||||
* @access private
|
||||
*/
|
||||
private function _conv_innodb() {
|
||||
global $wpdb;
|
||||
|
||||
if (empty($_GET['tb'])) {
|
||||
Admin_Display::error('No table to convert');
|
||||
return;
|
||||
}
|
||||
|
||||
$tb = false;
|
||||
|
||||
$list = $this->list_myisam();
|
||||
foreach ($list as $v) {
|
||||
if ($v->table_name == $_GET['tb']) {
|
||||
$tb = $v->table_name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$tb) {
|
||||
Admin_Display::error('No existing table');
|
||||
return;
|
||||
}
|
||||
|
||||
$q = 'ALTER TABLE ' . DB_NAME . '.' . $tb . ' ENGINE = InnoDB';
|
||||
$wpdb->query($q);
|
||||
|
||||
Debug2::debug("[DB] Converted $tb to InnoDB");
|
||||
|
||||
$msg = __('Converted to InnoDB successfully.', 'litespeed-cache');
|
||||
Admin_Display::success($msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Count all autoload size
|
||||
*
|
||||
* @since 3.0
|
||||
* @access public
|
||||
*/
|
||||
public function autoload_summary() {
|
||||
global $wpdb;
|
||||
|
||||
$autoloads = function_exists('wp_autoload_values_to_autoload') ? wp_autoload_values_to_autoload() : array( 'yes', 'on', 'auto-on', 'auto' );
|
||||
$autoloads = '("' . implode('","', $autoloads) . '")';
|
||||
|
||||
$summary = $wpdb->get_row("SELECT SUM(LENGTH(option_value)) AS autoload_size,COUNT(*) AS autload_entries FROM `$wpdb->options` WHERE autoload IN " . $autoloads);
|
||||
|
||||
$summary->autoload_toplist = $wpdb->get_results(
|
||||
"SELECT option_name, LENGTH(option_value) AS option_value_length, autoload FROM `$wpdb->options` WHERE autoload IN " .
|
||||
$autoloads .
|
||||
' ORDER BY option_value_length DESC LIMIT 20'
|
||||
);
|
||||
|
||||
return $summary;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle all request actions from main cls
|
||||
*
|
||||
* @since 3.0
|
||||
* @access public
|
||||
*/
|
||||
public function handler() {
|
||||
$type = Router::verify_type();
|
||||
|
||||
switch ($type) {
|
||||
case 'all':
|
||||
case in_array($type, self::$TYPES):
|
||||
if (is_multisite() && is_network_admin()) {
|
||||
$blogs = Activation::get_network_ids();
|
||||
foreach ($blogs as $blog_id) {
|
||||
switch_to_blog($blog_id);
|
||||
$msg = $this->_db_clean($type);
|
||||
restore_current_blog();
|
||||
}
|
||||
} else {
|
||||
$msg = $this->_db_clean($type);
|
||||
}
|
||||
Admin_Display::success($msg);
|
||||
break;
|
||||
|
||||
case self::TYPE_CONV_TB:
|
||||
$this->_conv_innodb();
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
Admin::redirect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean DB
|
||||
*
|
||||
* @since 7.0
|
||||
* @access public
|
||||
*/
|
||||
public function handler_clean_db_cli($args)
|
||||
{
|
||||
if (defined('WP_CLI') && constant('WP_CLI')) {
|
||||
return $this->_db_clean($args);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,593 @@
|
||||
<?php
|
||||
// phpcs:ignoreFile
|
||||
|
||||
/**
|
||||
* The plugin logging class.
|
||||
*/
|
||||
|
||||
namespace LiteSpeed;
|
||||
|
||||
defined('WPINC') || exit();
|
||||
|
||||
class Debug2 extends Root {
|
||||
|
||||
private static $log_path;
|
||||
private static $log_path_prefix;
|
||||
private static $_prefix;
|
||||
|
||||
const TYPE_CLEAR_LOG = 'clear_log';
|
||||
const TYPE_BETA_TEST = 'beta_test';
|
||||
|
||||
const BETA_TEST_URL = 'beta_test_url';
|
||||
|
||||
const BETA_TEST_URL_WP = 'https://downloads.wordpress.org/plugin/litespeed-cache.zip';
|
||||
|
||||
/**
|
||||
* Log class Confructor
|
||||
*
|
||||
* NOTE: in this process, until last step ( define const LSCWP_LOG = true ), any usage to WP filter will not be logged to prevent infinite loop with log_filters()
|
||||
*
|
||||
* @since 1.1.2
|
||||
* @access public
|
||||
*/
|
||||
public function __construct() {
|
||||
self::$log_path_prefix = LITESPEED_STATIC_DIR . '/debug/';
|
||||
// Maybe move legacy log files
|
||||
$this->_maybe_init_folder();
|
||||
|
||||
self::$log_path = $this->path('debug');
|
||||
if (!empty($_SERVER['HTTP_USER_AGENT']) && strpos($_SERVER['HTTP_USER_AGENT'], 'lscache_') === 0) {
|
||||
self::$log_path = $this->path('crawler');
|
||||
}
|
||||
|
||||
!defined('LSCWP_LOG_TAG') && define('LSCWP_LOG_TAG', get_current_blog_id());
|
||||
|
||||
if ($this->conf(Base::O_DEBUG_LEVEL)) {
|
||||
!defined('LSCWP_LOG_MORE') && define('LSCWP_LOG_MORE', true);
|
||||
}
|
||||
|
||||
defined('LSCWP_DEBUG_EXC_STRINGS') || define('LSCWP_DEBUG_EXC_STRINGS', $this->conf(Base::O_DEBUG_EXC_STRINGS));
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable all functionalities for a time period.
|
||||
*
|
||||
* @since 7.4
|
||||
* @access public
|
||||
* @param integer $time How long should we disable LSC functionalities.
|
||||
*/
|
||||
public static function tmp_disable( $time = 86400 ) {
|
||||
$conf = Conf::cls();
|
||||
$disabled = self::cls()->conf( Base::DEBUG_TMP_DISABLE );
|
||||
|
||||
if ( 0 === $disabled ) {
|
||||
$conf->update_confs( array( Base::DEBUG_TMP_DISABLE => time() + $time ) );
|
||||
self::debug2( 'LiteSpeed Cache temporary disabled.' );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$conf->update_confs( array( Base::DEBUG_TMP_DISABLE => 0 ) );
|
||||
self::debug2( 'LiteSpeed Cache reactivated.' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if Disable All is active. Disable if time is reached.
|
||||
*
|
||||
* @since 7.4
|
||||
* @access public
|
||||
*/
|
||||
public static function is_tmp_disable() {
|
||||
$disabled_time = self::cls()->conf( Base::DEBUG_TMP_DISABLE );
|
||||
|
||||
if ( 0 === $disabled_time ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( time() - $disabled_time < 0 ){
|
||||
return true;
|
||||
}
|
||||
|
||||
Conf::cls()->update_confs( array( Base::DEBUG_TMP_DISABLE => 0 ) );
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try moving legacy logs into /litespeed/debug/ folder
|
||||
*
|
||||
* @since 6.5
|
||||
*/
|
||||
private function _maybe_init_folder() {
|
||||
if (file_exists(self::$log_path_prefix . 'index.php')) {
|
||||
return;
|
||||
}
|
||||
File::save(self::$log_path_prefix . 'index.php', '<?php // Silence is golden.', true);
|
||||
|
||||
$logs = array( 'debug', 'debug.purge', 'crawler' );
|
||||
foreach ($logs as $log) {
|
||||
if (file_exists(LSCWP_CONTENT_DIR . '/' . $log . '.log') && !file_exists($this->path($log))) {
|
||||
rename(LSCWP_CONTENT_DIR . '/' . $log . '.log', $this->path($log));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate log file path
|
||||
*
|
||||
* @since 6.5
|
||||
*/
|
||||
public function path( $type ) {
|
||||
return self::$log_path_prefix . self::FilePath($type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the fixed log filename
|
||||
*
|
||||
* @since 6.5
|
||||
*/
|
||||
public static function FilePath( $type ) {
|
||||
if ($type == 'debug.purge') {
|
||||
$type = 'purge';
|
||||
}
|
||||
$key = defined('AUTH_KEY') ? AUTH_KEY : md5(__FILE__);
|
||||
$rand = substr(md5(substr($key, -16)), -16);
|
||||
return $type . $rand . '.log';
|
||||
}
|
||||
|
||||
/**
|
||||
* End call of one request process
|
||||
*
|
||||
* @since 4.7
|
||||
* @access public
|
||||
*/
|
||||
public static function ended() {
|
||||
$headers = headers_list();
|
||||
foreach ($headers as $key => $header) {
|
||||
if (stripos($header, 'Set-Cookie') === 0) {
|
||||
unset($headers[$key]);
|
||||
}
|
||||
}
|
||||
self::debug('Response headers', $headers);
|
||||
|
||||
$elapsed_time = number_format((microtime(true) - LSCWP_TS_0) * 1000, 2);
|
||||
self::debug("End response\n--------------------------------------------------Duration: " . $elapsed_time . " ms------------------------------\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Beta test upgrade
|
||||
*
|
||||
* @since 2.9.5
|
||||
* @access public
|
||||
*/
|
||||
public function beta_test( $zip = false ) {
|
||||
if (!$zip) {
|
||||
if (empty($_REQUEST[self::BETA_TEST_URL])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$zip = $_REQUEST[self::BETA_TEST_URL];
|
||||
if ($zip !== self::BETA_TEST_URL_WP) {
|
||||
if ($zip === 'latest') {
|
||||
$zip = self::BETA_TEST_URL_WP;
|
||||
} else {
|
||||
// Generate zip url
|
||||
$zip = $this->_package_zip($zip);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$zip) {
|
||||
self::debug('[Debug2] ❌ No ZIP file');
|
||||
return;
|
||||
}
|
||||
|
||||
self::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() {
|
||||
if (defined('LSCWP_LOG')) return;
|
||||
|
||||
$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 = 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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
<?php
|
||||
// phpcs:ignoreFile
|
||||
|
||||
/**
|
||||
* The Doc class.
|
||||
*
|
||||
* @since 2.2.7
|
||||
* @package LiteSpeed
|
||||
*/
|
||||
|
||||
namespace LiteSpeed;
|
||||
|
||||
defined('WPINC') || exit();
|
||||
|
||||
class Doc {
|
||||
|
||||
// protected static $_instance;
|
||||
|
||||
/**
|
||||
* Show option is actually ON by GM
|
||||
*
|
||||
* @since 5.5
|
||||
* @access public
|
||||
*/
|
||||
public static function maybe_on_by_gm( $id ) {
|
||||
if (apply_filters('litespeed_conf', $id)) {
|
||||
return;
|
||||
}
|
||||
if (!apply_filters('litespeed_conf', Base::O_GUEST)) {
|
||||
return;
|
||||
}
|
||||
if (!apply_filters('litespeed_conf', Base::O_GUEST_OPTM)) {
|
||||
return;
|
||||
}
|
||||
echo '<font class="litespeed-warning">';
|
||||
echo '⚠️ ' .
|
||||
sprintf(
|
||||
__('This setting is %1$s for certain qualifying requests due to %2$s!', 'litespeed-cache'),
|
||||
'<code>' . __('ON', 'litespeed-cache') . '</code>',
|
||||
Lang::title(Base::O_GUEST_OPTM)
|
||||
);
|
||||
self::learn_more('https://docs.litespeedtech.com/lscache/lscwp/general/#guest-optimization');
|
||||
echo '</font>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes affect crawler list warning
|
||||
*
|
||||
* @since 4.3
|
||||
* @access public
|
||||
*/
|
||||
public static function crawler_affected() {
|
||||
echo '<font class="litespeed-primary">';
|
||||
echo '⚠️ ' . __('This setting will regenerate crawler list and clear the disabled list!', 'litespeed-cache');
|
||||
echo '</font>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Privacy policy
|
||||
*
|
||||
* @since 2.2.7
|
||||
* @access public
|
||||
*/
|
||||
public static function privacy_policy() {
|
||||
return __(
|
||||
'This site utilizes caching in order to facilitate a faster response time and better user experience. Caching potentially stores a duplicate copy of every web page that is on display on this site. All cache files are temporary, and are never accessed by any third party, except as necessary to obtain technical support from the cache plugin vendor. Cache files expire on a schedule set by the site administrator, but may easily be purged by the admin before their natural expiration, if necessary. We may use QUIC.cloud services to process & cache your data temporarily.',
|
||||
'litespeed-cache'
|
||||
) .
|
||||
sprintf(
|
||||
__('Please see %s for more details.', 'litespeed-cache'),
|
||||
'<a href="https://quic.cloud/privacy-policy/" target="_blank">https://quic.cloud/privacy-policy/</a>'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Learn more link
|
||||
*
|
||||
* @since 2.4.2
|
||||
* @access public
|
||||
*/
|
||||
public static function learn_more( $url, $title = false, $self = false, $class = false, $return = false ) {
|
||||
if (!$class) {
|
||||
$class = 'litespeed-learn-more';
|
||||
}
|
||||
|
||||
if (!$title) {
|
||||
$title = __('Learn More', 'litespeed-cache');
|
||||
}
|
||||
|
||||
$self = $self ? '' : "target='_blank'";
|
||||
|
||||
$txt = " <a href='$url' $self class='$class'>$title</a>";
|
||||
|
||||
if ($return) {
|
||||
return $txt;
|
||||
}
|
||||
|
||||
echo $txt;
|
||||
}
|
||||
|
||||
/**
|
||||
* One per line
|
||||
*
|
||||
* @since 3.0
|
||||
* @access public
|
||||
*/
|
||||
public static function one_per_line( $return = false ) {
|
||||
$str = __('One per line.', 'litespeed-cache');
|
||||
if ($return) {
|
||||
return $str;
|
||||
}
|
||||
echo $str;
|
||||
}
|
||||
|
||||
/**
|
||||
* One per line
|
||||
*
|
||||
* @since 3.4
|
||||
* @access public
|
||||
*/
|
||||
public static function full_or_partial_url( $string_only = false ) {
|
||||
if ($string_only) {
|
||||
echo __('Both full and partial strings can be used.', 'litespeed-cache');
|
||||
} else {
|
||||
echo __('Both full URLs and partial strings can be used.', 'litespeed-cache');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notice to edit .htaccess
|
||||
*
|
||||
* @since 3.0
|
||||
* @access public
|
||||
*/
|
||||
public static function notice_htaccess() {
|
||||
echo '<font class="litespeed-primary">';
|
||||
echo '⚠️ ' . __('This setting will edit the .htaccess file.', 'litespeed-cache');
|
||||
echo ' <a href="https://docs.litespeedtech.com/lscache/lscwp/toolbox/#edit-htaccess-tab" target="_blank" class="litespeed-learn-more">' .
|
||||
__('Learn More', 'litespeed-cache') .
|
||||
'</a>';
|
||||
echo '</font>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gentle reminder that web services run asynchronously
|
||||
*
|
||||
* @since 5.3.1
|
||||
* @access public
|
||||
*/
|
||||
public static function queue_issues( $return = false ) {
|
||||
$str =
|
||||
'<div class="litespeed-desc">' .
|
||||
__('The queue is processed asynchronously. It may take time.', 'litespeed-cache') .
|
||||
self::learn_more('https://docs.litespeedtech.com/lscache/lscwp/troubleshoot/#quiccloud-queue-issues', false, false, false, true) .
|
||||
'</div>';
|
||||
if ($return) {
|
||||
return $str;
|
||||
}
|
||||
echo $str;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,253 @@
|
||||
<?php
|
||||
// phpcs:ignoreFile
|
||||
/**
|
||||
* The error class.
|
||||
*
|
||||
* @since 3.0
|
||||
* @package LiteSpeed
|
||||
*/
|
||||
|
||||
namespace LiteSpeed;
|
||||
|
||||
defined( 'WPINC' ) || exit();
|
||||
|
||||
/**
|
||||
* Class Error
|
||||
*
|
||||
* Handles error message translation and throwing for LiteSpeed Cache.
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
class Error {
|
||||
|
||||
/**
|
||||
* Error code mappings to numeric values.
|
||||
*
|
||||
* @since 3.0
|
||||
* @var array
|
||||
*/
|
||||
private static $CODE_SET = array(
|
||||
'HTA_LOGIN_COOKIE_INVALID' => 4300, // .htaccess did not find.
|
||||
'HTA_DNF' => 4500, // .htaccess did not find.
|
||||
'HTA_BK' => 9010, // backup
|
||||
'HTA_R' => 9041, // read htaccess
|
||||
'HTA_W' => 9042, // write
|
||||
'HTA_GET' => 9030, // failed to get
|
||||
);
|
||||
|
||||
/**
|
||||
* Throw an error with message
|
||||
*
|
||||
* Throws an exception with the translated error message.
|
||||
*
|
||||
* @since 3.0
|
||||
* @access public
|
||||
* @param string $code Error code.
|
||||
* @param mixed $args Optional arguments for message formatting.
|
||||
* @throws \Exception Always throws an exception with the error message.
|
||||
*/
|
||||
public static function t( $code, $args = null ) {
|
||||
throw new \Exception( wp_kses_post( self::msg( $code, $args ) ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate an error to description
|
||||
*
|
||||
* Converts error codes to human-readable messages.
|
||||
*
|
||||
* @since 3.0
|
||||
* @access public
|
||||
* @param string $code Error code.
|
||||
* @param mixed $args Optional arguments for message formatting.
|
||||
* @return string Translated error message.
|
||||
*/
|
||||
public static function msg( $code, $args = null ) {
|
||||
switch ( $code ) {
|
||||
case 'qc_setup_required':
|
||||
$msg =
|
||||
sprintf(
|
||||
__( 'You will need to finish %s setup to use the online services.', 'litespeed-cache' ),
|
||||
'<strong>QUIC.cloud</strong>'
|
||||
) .
|
||||
Doc::learn_more(
|
||||
admin_url( 'admin.php?page=litespeed-general' ),
|
||||
__( 'Click here to set.', 'litespeed-cache' ),
|
||||
true,
|
||||
false,
|
||||
true
|
||||
);
|
||||
break;
|
||||
|
||||
case 'out_of_daily_quota':
|
||||
$msg = __( 'You have used all of your daily quota for today.', 'litespeed-cache' );
|
||||
$msg .=
|
||||
' ' .
|
||||
Doc::learn_more(
|
||||
'https://docs.quic.cloud/billing/services/#daily-limits-on-free-quota-usage',
|
||||
__( 'Learn more or purchase additional quota.', 'litespeed-cache' ),
|
||||
false,
|
||||
false,
|
||||
true
|
||||
);
|
||||
break;
|
||||
|
||||
case 'out_of_quota':
|
||||
$msg = __( 'You have used all of your quota left for current service this month.', 'litespeed-cache' );
|
||||
$msg .=
|
||||
' ' .
|
||||
Doc::learn_more(
|
||||
'https://docs.quic.cloud/billing/services/#daily-limits-on-free-quota-usage',
|
||||
__( 'Learn more or purchase additional quota.', 'litespeed-cache' ),
|
||||
false,
|
||||
false,
|
||||
true
|
||||
);
|
||||
break;
|
||||
|
||||
case 'too_many_requested':
|
||||
$msg = __( 'You have too many requested images, please try again in a few minutes.', 'litespeed-cache' );
|
||||
break;
|
||||
|
||||
case 'too_many_notified':
|
||||
$msg = __( 'You have images waiting to be pulled. Please wait for the automatic pull to complete, or pull them down manually now.', 'litespeed-cache' );
|
||||
break;
|
||||
|
||||
case 'empty_list':
|
||||
$msg = __( 'The image list is empty.', 'litespeed-cache' );
|
||||
break;
|
||||
|
||||
case 'lack_of_param':
|
||||
$msg = __( 'Not enough parameters. Please check if the QUIC.cloud connection is set correctly', 'litespeed-cache' );
|
||||
break;
|
||||
|
||||
case 'unfinished_queue':
|
||||
$msg = __( 'There is proceeding queue not pulled yet.', 'litespeed-cache' );
|
||||
break;
|
||||
|
||||
case 0 === strpos( $code, 'unfinished_queue ' ):
|
||||
$msg = sprintf(
|
||||
__( 'There is proceeding queue not pulled yet. Queue info: %s.', 'litespeed-cache' ),
|
||||
'<code>' . substr( $code, strlen( 'unfinished_queue ' ) ) . '</code>'
|
||||
);
|
||||
break;
|
||||
|
||||
case 'err_alias':
|
||||
$msg = __( 'The site is not a valid alias on QUIC.cloud.', 'litespeed-cache' );
|
||||
break;
|
||||
|
||||
case 'site_not_registered':
|
||||
$msg = __( 'The site is not registered on QUIC.cloud.', 'litespeed-cache' );
|
||||
break;
|
||||
|
||||
case 'err_key':
|
||||
$msg = __( 'The QUIC.cloud connection is not correct. Please try to sync your QUIC.cloud connection again.', 'litespeed-cache' );
|
||||
break;
|
||||
|
||||
case 'heavy_load':
|
||||
$msg = __( 'The current server is under heavy load.', 'litespeed-cache' );
|
||||
break;
|
||||
|
||||
case 'redetect_node':
|
||||
$msg = __( 'Online node needs to be redetected.', 'litespeed-cache' );
|
||||
break;
|
||||
|
||||
case 'err_overdraw':
|
||||
$msg = __( 'Credits are not enough to proceed the current request.', 'litespeed-cache' );
|
||||
break;
|
||||
|
||||
case 'W':
|
||||
$msg = __( '%s file not writable.', 'litespeed-cache' );
|
||||
break;
|
||||
|
||||
case 'HTA_DNF':
|
||||
if ( ! is_array( $args ) ) {
|
||||
$args = array( '<code>' . $args . '</code>' );
|
||||
}
|
||||
$args[] = '.htaccess';
|
||||
$msg = __( 'Could not find %1$s in %2$s.', 'litespeed-cache' );
|
||||
break;
|
||||
|
||||
case 'HTA_LOGIN_COOKIE_INVALID':
|
||||
$msg = sprintf( __( 'Invalid login cookie. Please check the %s file.', 'litespeed-cache' ), '.htaccess' );
|
||||
break;
|
||||
|
||||
case 'HTA_BK':
|
||||
$msg = sprintf( __( 'Failed to back up %s file, aborted changes.', 'litespeed-cache' ), '.htaccess' );
|
||||
break;
|
||||
|
||||
case 'HTA_R':
|
||||
$msg = sprintf( __( '%s file not readable.', 'litespeed-cache' ), '.htaccess' );
|
||||
break;
|
||||
|
||||
case 'HTA_W':
|
||||
$msg = sprintf( __( '%s file not writable.', 'litespeed-cache' ), '.htaccess' );
|
||||
break;
|
||||
|
||||
case 'HTA_GET':
|
||||
$msg = sprintf( __( 'Failed to get %s file contents.', 'litespeed-cache' ), '.htaccess' );
|
||||
break;
|
||||
|
||||
case 'failed_tb_creation':
|
||||
$msg = __( 'Failed to create table %1$s! SQL: %2$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 0 === strpos( $code, 'try_later ' ):
|
||||
$msg = sprintf(
|
||||
__( 'Previous request too recent. Please try again after %s.', 'litespeed-cache' ),
|
||||
'<code>' . Utility::readable_time( substr( $code, strlen( 'try_later ' ) ), 3600, true ) . '</code>'
|
||||
);
|
||||
break;
|
||||
|
||||
case 'waiting_for_approval':
|
||||
$msg = __( 'Your application is waiting for approval.', 'litespeed-cache' );
|
||||
break;
|
||||
|
||||
case 'callback_fail_hash':
|
||||
$msg = __( 'The callback validation to your domain failed due to hash mismatch.', 'litespeed-cache' );
|
||||
break;
|
||||
|
||||
case 'callback_fail':
|
||||
$msg = __( 'The callback validation to your domain failed. Please make sure there is no firewall blocking our servers.', 'litespeed-cache' );
|
||||
break;
|
||||
|
||||
case substr( $code, 0, 14 ) === 'callback_fail ':
|
||||
$msg =
|
||||
__( 'The callback validation to your domain failed. Please make sure there is no firewall blocking our servers. Response code: ', 'litespeed-cache' ) .
|
||||
substr( $code, 14 );
|
||||
break;
|
||||
|
||||
case 'forbidden':
|
||||
$msg = __( 'Your domain has been forbidden from using our services due to a previous policy violation.', 'litespeed-cache' );
|
||||
break;
|
||||
|
||||
case 'err_dns_active':
|
||||
$msg = __(
|
||||
'You cannot remove this DNS zone, because it is still in use. Please update the domain\'s nameservers, then try to delete this zone again, otherwise your site will become inaccessible.',
|
||||
'litespeed-cache'
|
||||
);
|
||||
break;
|
||||
|
||||
default:
|
||||
$msg = __( 'Unknown error', 'litespeed-cache' ) . ': ' . $code;
|
||||
break;
|
||||
}
|
||||
|
||||
if ( null !== $args ) {
|
||||
$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;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,420 @@
|
||||
<?php
|
||||
// phpcs:ignoreFile
|
||||
|
||||
/**
|
||||
* LiteSpeed File Operator Library Class
|
||||
* Append/Replace content to a file
|
||||
*
|
||||
* @since 1.1.0
|
||||
*/
|
||||
|
||||
namespace LiteSpeed;
|
||||
|
||||
defined('WPINC') || exit();
|
||||
|
||||
class File {
|
||||
|
||||
const MARKER = 'LiteSpeed Operator';
|
||||
|
||||
/**
|
||||
* Detect if an URL is 404
|
||||
*
|
||||
* @since 3.3
|
||||
*/
|
||||
public static function is_404( $url ) {
|
||||
$response = wp_safe_remote_get($url);
|
||||
$code = wp_remote_retrieve_response_code($response);
|
||||
if ($code == 404) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete folder
|
||||
*
|
||||
* @since 2.1
|
||||
*/
|
||||
public static function rrmdir( $dir ) {
|
||||
$files = array_diff(scandir($dir), array( '.', '..' ));
|
||||
|
||||
foreach ($files as $file) {
|
||||
is_dir("$dir/$file") ? self::rrmdir("$dir/$file") : unlink("$dir/$file");
|
||||
}
|
||||
|
||||
return rmdir($dir);
|
||||
}
|
||||
|
||||
public static function count_lines( $filename ) {
|
||||
if (!file_exists($filename)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$file = new \SplFileObject($filename);
|
||||
$file->seek(PHP_INT_MAX);
|
||||
return $file->key() + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read data from file
|
||||
*
|
||||
* @since 1.1.0
|
||||
* @param string $filename
|
||||
* @param int $start_line
|
||||
* @param int $lines
|
||||
*/
|
||||
public static function read( $filename, $start_line = null, $lines = null ) {
|
||||
if (!file_exists($filename)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (!is_readable($filename)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($start_line !== null) {
|
||||
$res = array();
|
||||
$file = new \SplFileObject($filename);
|
||||
$file->seek($start_line);
|
||||
|
||||
if ($lines === null) {
|
||||
while (!$file->eof()) {
|
||||
$res[] = rtrim($file->current(), "\n");
|
||||
$file->next();
|
||||
}
|
||||
} else {
|
||||
for ($i = 0; $i < $lines; $i++) {
|
||||
if ($file->eof()) {
|
||||
break;
|
||||
}
|
||||
$res[] = rtrim($file->current(), "\n");
|
||||
$file->next();
|
||||
}
|
||||
}
|
||||
|
||||
unset($file);
|
||||
return $res;
|
||||
}
|
||||
|
||||
$content = file_get_contents($filename);
|
||||
|
||||
$content = self::remove_zero_space($content);
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Append data to file
|
||||
*
|
||||
* @since 1.1.5
|
||||
* @access public
|
||||
* @param string $filename
|
||||
* @param string $data
|
||||
* @param boolean $mkdir
|
||||
* @param boolean $silence Used to avoid WP's functions are used
|
||||
*/
|
||||
public static function append( $filename, $data, $mkdir = false, $silence = true ) {
|
||||
return self::save($filename, $data, $mkdir, true, $silence);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save data to file
|
||||
*
|
||||
* @since 1.1.0
|
||||
* @param string $filename
|
||||
* @param string $data
|
||||
* @param boolean $mkdir
|
||||
* @param boolean $append If the content needs to be appended
|
||||
* @param boolean $silence Used to avoid WP's functions are used
|
||||
*/
|
||||
public static function save( $filename, $data, $mkdir = false, $append = false, $silence = true ) {
|
||||
if (is_null($filename)) {
|
||||
return $silence ? false : __('Filename is empty!', 'litespeed-cache');
|
||||
}
|
||||
|
||||
$error = false;
|
||||
$folder = dirname($filename);
|
||||
|
||||
// mkdir if folder does not exist
|
||||
if (!file_exists($folder)) {
|
||||
if (!$mkdir) {
|
||||
return $silence ? false : sprintf(__('Folder does not exist: %s', 'litespeed-cache'), $folder);
|
||||
}
|
||||
|
||||
set_error_handler('litespeed_exception_handler');
|
||||
|
||||
try {
|
||||
mkdir($folder, 0755, true);
|
||||
// Create robots.txt file to forbid search engine indexes
|
||||
if (!file_exists(LITESPEED_STATIC_DIR . '/robots.txt')) {
|
||||
file_put_contents(LITESPEED_STATIC_DIR . '/robots.txt', "User-agent: *\nDisallow: /\n");
|
||||
}
|
||||
} catch (\ErrorException $ex) {
|
||||
return $silence ? false : sprintf(__('Can not create folder: %1$s. Error: %2$s', 'litespeed-cache'), $folder, $ex->getMessage());
|
||||
}
|
||||
|
||||
restore_error_handler();
|
||||
}
|
||||
|
||||
if (!file_exists($filename)) {
|
||||
if (!is_writable($folder)) {
|
||||
return $silence ? false : sprintf(__('Folder is not writable: %s.', 'litespeed-cache'), $folder);
|
||||
}
|
||||
set_error_handler('litespeed_exception_handler');
|
||||
try {
|
||||
touch($filename);
|
||||
} catch (\ErrorException $ex) {
|
||||
return $silence ? false : sprintf(__('File %s is not writable.', 'litespeed-cache'), $filename);
|
||||
}
|
||||
restore_error_handler();
|
||||
} elseif (!is_writable($filename)) {
|
||||
return $silence ? false : sprintf(__('File %s is not writable.', 'litespeed-cache'), $filename);
|
||||
}
|
||||
|
||||
$data = self::remove_zero_space($data);
|
||||
|
||||
$ret = file_put_contents($filename, $data, $append ? FILE_APPEND : LOCK_EX);
|
||||
if ($ret === false) {
|
||||
return $silence ? false : sprintf(__('Failed to write to %s.', 'litespeed-cache'), $filename);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove Unicode zero-width space <200b><200c>
|
||||
*
|
||||
* @since 2.1.2
|
||||
* @since 2.9 changed to public
|
||||
*/
|
||||
public static function remove_zero_space( $content ) {
|
||||
if (is_array($content)) {
|
||||
$content = array_map(__CLASS__ . '::remove_zero_space', $content);
|
||||
return $content;
|
||||
}
|
||||
|
||||
// Remove UTF-8 BOM if present
|
||||
if (substr($content, 0, 3) === "\xEF\xBB\xBF") {
|
||||
$content = substr($content, 3);
|
||||
}
|
||||
|
||||
$content = str_replace("\xe2\x80\x8b", '', $content);
|
||||
$content = str_replace("\xe2\x80\x8c", '', $content);
|
||||
$content = str_replace("\xe2\x80\x8d", '', $content);
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends an array of strings into a file (.htaccess ), placing it between
|
||||
* BEGIN and END markers.
|
||||
*
|
||||
* Replaces existing marked info. Retains surrounding
|
||||
* data. Creates file if none exists.
|
||||
*
|
||||
* @param string $filename Filename to alter.
|
||||
* @param string $marker The marker to alter.
|
||||
* @param array|string|false $insertion The new content to insert.
|
||||
* @param bool $prepend Prepend insertion if not exist.
|
||||
* @return bool True on write success, false on failure.
|
||||
*/
|
||||
public static function insert_with_markers( $filename, $insertion = false, $marker = false, $prepend = false ) {
|
||||
if (!$marker) {
|
||||
$marker = self::MARKER;
|
||||
}
|
||||
|
||||
if (!$insertion) {
|
||||
$insertion = array();
|
||||
}
|
||||
|
||||
return self::_insert_with_markers($filename, $marker, $insertion, $prepend); // todo: capture exceptions
|
||||
}
|
||||
|
||||
/**
|
||||
* Return wrapped block data with marker
|
||||
*
|
||||
* @param string $insertion
|
||||
* @param string $marker
|
||||
* @return string The block data
|
||||
*/
|
||||
public static function wrap_marker_data( $insertion, $marker = false ) {
|
||||
if (!$marker) {
|
||||
$marker = self::MARKER;
|
||||
}
|
||||
$start_marker = "# BEGIN {$marker}";
|
||||
$end_marker = "# END {$marker}";
|
||||
|
||||
$new_data = implode("\n", array_merge(array( $start_marker ), $insertion, array( $end_marker )));
|
||||
return $new_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Touch block data from file, return with marker
|
||||
*
|
||||
* @param string $filename
|
||||
* @param string $marker
|
||||
* @return string The current block data
|
||||
*/
|
||||
public static function touch_marker_data( $filename, $marker = false ) {
|
||||
if (!$marker) {
|
||||
$marker = self::MARKER;
|
||||
}
|
||||
|
||||
$result = self::_extract_from_markers($filename, $marker);
|
||||
|
||||
if (!$result) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$start_marker = "# BEGIN {$marker}";
|
||||
$end_marker = "# END {$marker}";
|
||||
$new_data = implode("\n", array_merge(array( $start_marker ), $result, array( $end_marker )));
|
||||
return $new_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts strings from between the BEGIN and END markers in the .htaccess file.
|
||||
*
|
||||
* @param string $filename
|
||||
* @param string $marker
|
||||
* @return array An array of strings from a file (.htaccess ) from between BEGIN and END markers.
|
||||
*/
|
||||
public static function extract_from_markers( $filename, $marker = false ) {
|
||||
if (!$marker) {
|
||||
$marker = self::MARKER;
|
||||
}
|
||||
return self::_extract_from_markers($filename, $marker);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts strings from between the BEGIN and END markers in the .htaccess file.
|
||||
*
|
||||
* @param string $filename
|
||||
* @param string $marker
|
||||
* @return array An array of strings from a file (.htaccess ) from between BEGIN and END markers.
|
||||
*/
|
||||
private static function _extract_from_markers( $filename, $marker ) {
|
||||
$result = array();
|
||||
|
||||
if (!file_exists($filename)) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
if ($markerdata = explode("\n", implode('', file($filename)))) {
|
||||
$state = false;
|
||||
foreach ($markerdata as $markerline) {
|
||||
if (strpos($markerline, '# END ' . $marker) !== false) {
|
||||
$state = false;
|
||||
}
|
||||
if ($state) {
|
||||
$result[] = $markerline;
|
||||
}
|
||||
if (strpos($markerline, '# BEGIN ' . $marker) !== false) {
|
||||
$state = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return array_map('trim', $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts an array of strings into a file (.htaccess ), placing it between BEGIN and END markers.
|
||||
*
|
||||
* Replaces existing marked info. Retains surrounding data. Creates file if none exists.
|
||||
*
|
||||
* NOTE: will throw error if failed
|
||||
*
|
||||
* @since 3.0-
|
||||
* @since 3.0 Throw errors if failed
|
||||
* @access private
|
||||
*/
|
||||
private static function _insert_with_markers( $filename, $marker, $insertion, $prepend = false ) {
|
||||
if (!file_exists($filename)) {
|
||||
if (!is_writable(dirname($filename))) {
|
||||
Error::t('W', dirname($filename));
|
||||
}
|
||||
|
||||
set_error_handler('litespeed_exception_handler');
|
||||
try {
|
||||
touch($filename);
|
||||
} catch (\ErrorException $ex) {
|
||||
Error::t('W', $filename);
|
||||
}
|
||||
restore_error_handler();
|
||||
} elseif (!is_writable($filename)) {
|
||||
Error::t('W', $filename);
|
||||
}
|
||||
|
||||
if (!is_array($insertion)) {
|
||||
$insertion = explode("\n", $insertion);
|
||||
}
|
||||
|
||||
$start_marker = "# BEGIN {$marker}";
|
||||
$end_marker = "# END {$marker}";
|
||||
|
||||
$fp = fopen($filename, 'r+');
|
||||
if (!$fp) {
|
||||
Error::t('W', $filename);
|
||||
}
|
||||
|
||||
// Attempt to get a lock. If the filesystem supports locking, this will block until the lock is acquired.
|
||||
flock($fp, LOCK_EX);
|
||||
|
||||
$lines = array();
|
||||
while (!feof($fp)) {
|
||||
$lines[] = rtrim(fgets($fp), "\r\n");
|
||||
}
|
||||
|
||||
// Split out the existing file into the preceding lines, and those that appear after the marker
|
||||
$pre_lines = $post_lines = $existing_lines = array();
|
||||
$found_marker = $found_end_marker = false;
|
||||
foreach ($lines as $line) {
|
||||
if (!$found_marker && false !== strpos($line, $start_marker)) {
|
||||
$found_marker = true;
|
||||
continue;
|
||||
} elseif (!$found_end_marker && false !== strpos($line, $end_marker)) {
|
||||
$found_end_marker = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$found_marker) {
|
||||
$pre_lines[] = $line;
|
||||
} elseif ($found_marker && $found_end_marker) {
|
||||
$post_lines[] = $line;
|
||||
} else {
|
||||
$existing_lines[] = $line;
|
||||
}
|
||||
}
|
||||
|
||||
// Check to see if there was a change
|
||||
if ($existing_lines === $insertion) {
|
||||
flock($fp, LOCK_UN);
|
||||
fclose($fp);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if need to prepend data if not exist
|
||||
if ($prepend && !$post_lines) {
|
||||
// Generate the new file data
|
||||
$new_file_data = implode("\n", array_merge(array( $start_marker ), $insertion, array( $end_marker ), $pre_lines));
|
||||
} else {
|
||||
// Generate the new file data
|
||||
$new_file_data = implode("\n", array_merge($pre_lines, array( $start_marker ), $insertion, array( $end_marker ), $post_lines));
|
||||
}
|
||||
|
||||
// Write to the start of the file, and truncate it to that length
|
||||
fseek($fp, 0);
|
||||
$bytes = fwrite($fp, $new_file_data);
|
||||
if ($bytes) {
|
||||
ftruncate($fp, ftell($fp));
|
||||
}
|
||||
fflush($fp);
|
||||
flock($fp, LOCK_UN);
|
||||
fclose($fp);
|
||||
|
||||
return (bool) $bytes;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,127 @@
|
||||
<?php
|
||||
// phpcs:ignoreFile
|
||||
/**
|
||||
* The page health
|
||||
*
|
||||
* @since 3.0
|
||||
* @package LiteSpeed
|
||||
*/
|
||||
namespace LiteSpeed;
|
||||
|
||||
defined('WPINC') || exit();
|
||||
|
||||
class Health extends Base {
|
||||
|
||||
const TYPE_SPEED = 'speed';
|
||||
const TYPE_SCORE = 'score';
|
||||
|
||||
protected $_summary;
|
||||
|
||||
/**
|
||||
* Init
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->_summary = self::get_summary();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test latest speed
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
private function _ping( $type ) {
|
||||
$data = array( 'action' => $type );
|
||||
|
||||
$json = Cloud::post(Cloud::SVC_HEALTH, $data, 600);
|
||||
|
||||
if (empty($json['data']['before']) || empty($json['data']['after'])) {
|
||||
Debug2::debug('[Health] ❌ no data');
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->_summary[$type . '.before'] = $json['data']['before'];
|
||||
$this->_summary[$type . '.after'] = $json['data']['after'];
|
||||
|
||||
self::save_summary();
|
||||
|
||||
Debug2::debug('[Health] saved result');
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate scores
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
public function scores() {
|
||||
$speed_before = $speed_after = $speed_improved = 0;
|
||||
if (!empty($this->_summary['speed.before']) && !empty($this->_summary['speed.after'])) {
|
||||
// Format loading time
|
||||
$speed_before = $this->_summary['speed.before'] / 1000;
|
||||
if ($speed_before < 0.01) {
|
||||
$speed_before = 0.01;
|
||||
}
|
||||
$speed_before = number_format($speed_before, 2);
|
||||
|
||||
$speed_after = $this->_summary['speed.after'] / 1000;
|
||||
if ($speed_after < 0.01) {
|
||||
$speed_after = number_format($speed_after, 3);
|
||||
} else {
|
||||
$speed_after = number_format($speed_after, 2);
|
||||
}
|
||||
|
||||
$speed_improved = (($this->_summary['speed.before'] - $this->_summary['speed.after']) * 100) / $this->_summary['speed.before'];
|
||||
if ($speed_improved > 99) {
|
||||
$speed_improved = number_format($speed_improved, 2);
|
||||
} else {
|
||||
$speed_improved = number_format($speed_improved);
|
||||
}
|
||||
}
|
||||
|
||||
$score_before = $score_after = $score_improved = 0;
|
||||
if (!empty($this->_summary['score.before']) && !empty($this->_summary['score.after'])) {
|
||||
$score_before = $this->_summary['score.before'];
|
||||
$score_after = $this->_summary['score.after'];
|
||||
|
||||
// Format Score
|
||||
$score_improved = (($score_after - $score_before) * 100) / $score_after;
|
||||
if ($score_improved > 99) {
|
||||
$score_improved = number_format($score_improved, 2);
|
||||
} else {
|
||||
$score_improved = number_format($score_improved);
|
||||
}
|
||||
}
|
||||
|
||||
return array(
|
||||
'speed_before' => $speed_before,
|
||||
'speed_after' => $speed_after,
|
||||
'speed_improved' => $speed_improved,
|
||||
'score_before' => $score_before,
|
||||
'score_after' => $score_after,
|
||||
'score_improved' => $score_improved,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle all request actions from main cls
|
||||
*
|
||||
* @since 3.0
|
||||
* @access public
|
||||
*/
|
||||
public function handler() {
|
||||
$type = Router::verify_type();
|
||||
|
||||
switch ($type) {
|
||||
case self::TYPE_SPEED:
|
||||
case self::TYPE_SCORE:
|
||||
$this->_ping($type);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
Admin::redirect();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,824 @@
|
||||
<?php
|
||||
// phpcs:ignoreFile
|
||||
|
||||
/**
|
||||
* The htaccess rewrite rule operation class
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @package LiteSpeed
|
||||
*/
|
||||
|
||||
namespace LiteSpeed;
|
||||
|
||||
defined('WPINC') || exit();
|
||||
|
||||
class Htaccess extends Root {
|
||||
|
||||
private $frontend_htaccess = null;
|
||||
private $_default_frontend_htaccess = null;
|
||||
private $backend_htaccess = null;
|
||||
private $_default_backend_htaccess = null;
|
||||
private $theme_htaccess = null; // Not used yet
|
||||
private $frontend_htaccess_readable = false;
|
||||
private $frontend_htaccess_writable = false;
|
||||
private $backend_htaccess_readable = false;
|
||||
private $backend_htaccess_writable = false;
|
||||
private $theme_htaccess_readable = false;
|
||||
private $theme_htaccess_writable = false;
|
||||
private $__rewrite_on;
|
||||
|
||||
const LS_MODULE_START = '<IfModule LiteSpeed>';
|
||||
const EXPIRES_MODULE_START = '<IfModule mod_expires.c>';
|
||||
const LS_MODULE_END = '</IfModule>';
|
||||
const LS_MODULE_REWRITE_START = '<IfModule mod_rewrite.c>';
|
||||
const REWRITE_ON = 'RewriteEngine on';
|
||||
const LS_MODULE_DONOTEDIT = '## LITESPEED WP CACHE PLUGIN - Do not edit the contents of this block! ##';
|
||||
const MARKER = 'LSCACHE';
|
||||
const MARKER_NONLS = 'NON_LSCACHE';
|
||||
const MARKER_LOGIN_COOKIE = '### marker LOGIN COOKIE';
|
||||
const MARKER_ASYNC = '### marker ASYNC';
|
||||
const MARKER_CRAWLER = '### marker CRAWLER';
|
||||
const MARKER_MOBILE = '### marker MOBILE';
|
||||
const MARKER_NOCACHE_COOKIES = '### marker NOCACHE COOKIES';
|
||||
const MARKER_NOCACHE_USER_AGENTS = '### marker NOCACHE USER AGENTS';
|
||||
const MARKER_CACHE_RESOURCE = '### marker CACHE RESOURCE';
|
||||
const MARKER_BROWSER_CACHE = '### marker BROWSER CACHE';
|
||||
const MARKER_MINIFY = '### marker MINIFY';
|
||||
const MARKER_CORS = '### marker CORS';
|
||||
const MARKER_WEBP = '### marker WEBP';
|
||||
const MARKER_DROPQS = '### marker DROPQS';
|
||||
const MARKER_START = ' start ###';
|
||||
const MARKER_END = ' end ###';
|
||||
|
||||
/**
|
||||
* Initialize the class and set its properties.
|
||||
*
|
||||
* @since 1.0.7
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->_path_set();
|
||||
$this->_default_frontend_htaccess = $this->frontend_htaccess;
|
||||
$this->_default_backend_htaccess = $this->backend_htaccess;
|
||||
|
||||
$frontend_htaccess = defined('LITESPEED_CFG_HTACCESS') ? constant('LITESPEED_CFG_HTACCESS') : false;
|
||||
if ($frontend_htaccess && substr($frontend_htaccess, -10) === '/.htaccess') {
|
||||
$this->frontend_htaccess = $frontend_htaccess;
|
||||
}
|
||||
$backend_htaccess = defined('LITESPEED_CFG_HTACCESS_BACKEND') ? constant('LITESPEED_CFG_HTACCESS_BACKEND') : false;
|
||||
if ($backend_htaccess && substr($backend_htaccess, -10) === '/.htaccess') {
|
||||
$this->backend_htaccess = $backend_htaccess;
|
||||
}
|
||||
|
||||
// Filter for frontend&backend htaccess path
|
||||
$this->frontend_htaccess = apply_filters('litespeed_frontend_htaccess', $this->frontend_htaccess);
|
||||
$this->backend_htaccess = apply_filters('litespeed_backend_htaccess', $this->backend_htaccess);
|
||||
|
||||
clearstatcache();
|
||||
|
||||
// frontend .htaccess privilege
|
||||
$test_permissions = file_exists($this->frontend_htaccess) ? $this->frontend_htaccess : dirname($this->frontend_htaccess);
|
||||
if (is_readable($test_permissions)) {
|
||||
$this->frontend_htaccess_readable = true;
|
||||
}
|
||||
if (is_writable($test_permissions)) {
|
||||
$this->frontend_htaccess_writable = true;
|
||||
}
|
||||
|
||||
$this->__rewrite_on = array(
|
||||
self::REWRITE_ON,
|
||||
'CacheLookup on',
|
||||
'RewriteRule .* - [E=Cache-Control:no-autoflush]',
|
||||
'RewriteRule ' . preg_quote(LITESPEED_DATA_FOLDER) . '/debug/.*\.log$ - [F,L]',
|
||||
'RewriteRule ' . preg_quote(self::CONF_FILE) . ' - [F,L]',
|
||||
);
|
||||
|
||||
// backend .htaccess privilege
|
||||
if ($this->frontend_htaccess === $this->backend_htaccess) {
|
||||
$this->backend_htaccess_readable = $this->frontend_htaccess_readable;
|
||||
$this->backend_htaccess_writable = $this->frontend_htaccess_writable;
|
||||
} else {
|
||||
$test_permissions = file_exists($this->backend_htaccess) ? $this->backend_htaccess : dirname($this->backend_htaccess);
|
||||
if (is_readable($test_permissions)) {
|
||||
$this->backend_htaccess_readable = true;
|
||||
}
|
||||
if (is_writable($test_permissions)) {
|
||||
$this->backend_htaccess_writable = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get if htaccess file is readable
|
||||
*
|
||||
* @since 1.1.0
|
||||
* @return string
|
||||
*/
|
||||
private function _readable( $kind = 'frontend' ) {
|
||||
if ($kind === 'frontend') {
|
||||
return $this->frontend_htaccess_readable;
|
||||
}
|
||||
if ($kind === 'backend') {
|
||||
return $this->backend_htaccess_readable;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get if htaccess file is writable
|
||||
*
|
||||
* @since 1.1.0
|
||||
* @return string
|
||||
*/
|
||||
public function writable( $kind = 'frontend' ) {
|
||||
if ($kind === 'frontend') {
|
||||
return $this->frontend_htaccess_writable;
|
||||
}
|
||||
if ($kind === 'backend') {
|
||||
return $this->backend_htaccess_writable;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get frontend htaccess path
|
||||
*
|
||||
* @since 1.1.0
|
||||
* @return string
|
||||
*/
|
||||
public static function get_frontend_htaccess( $show_default = false ) {
|
||||
if ($show_default) {
|
||||
return self::cls()->_default_frontend_htaccess;
|
||||
}
|
||||
return self::cls()->frontend_htaccess;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get backend htaccess path
|
||||
*
|
||||
* @since 1.1.0
|
||||
* @return string
|
||||
*/
|
||||
public static function get_backend_htaccess( $show_default = false ) {
|
||||
if ($show_default) {
|
||||
return self::cls()->_default_backend_htaccess;
|
||||
}
|
||||
return self::cls()->backend_htaccess;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check to see if .htaccess exists starting at $start_path and going up directories until it hits DOCUMENT_ROOT.
|
||||
*
|
||||
* As dirname() strips the ending '/', paths passed in must exclude the final '/'
|
||||
*
|
||||
* @since 1.0.11
|
||||
* @access private
|
||||
*/
|
||||
private function _htaccess_search( $start_path ) {
|
||||
while (!file_exists($start_path . '/.htaccess')) {
|
||||
if ($start_path === '/' || !$start_path) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!empty($_SERVER['DOCUMENT_ROOT']) && wp_normalize_path($start_path) === wp_normalize_path($_SERVER['DOCUMENT_ROOT'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (dirname($start_path) === $start_path) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$start_path = dirname($start_path);
|
||||
}
|
||||
|
||||
return $start_path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the path class variables.
|
||||
*
|
||||
* @since 1.0.11
|
||||
* @access private
|
||||
*/
|
||||
private function _path_set() {
|
||||
$frontend = Router::frontend_path();
|
||||
$frontend_htaccess_search = $this->_htaccess_search($frontend); // The existing .htaccess path to be used for frontend .htaccess
|
||||
$this->frontend_htaccess = ($frontend_htaccess_search ?: $frontend) . '/.htaccess';
|
||||
|
||||
$backend = realpath(ABSPATH); // /home/user/public_html/backend/
|
||||
if ($frontend == $backend) {
|
||||
$this->backend_htaccess = $this->frontend_htaccess;
|
||||
return;
|
||||
}
|
||||
|
||||
// Backend is a different path
|
||||
$backend_htaccess_search = $this->_htaccess_search($backend);
|
||||
// Found affected .htaccess
|
||||
if ($backend_htaccess_search) {
|
||||
$this->backend_htaccess = $backend_htaccess_search . '/.htaccess';
|
||||
return;
|
||||
}
|
||||
|
||||
// Frontend path is the parent of backend path
|
||||
if (stripos($backend, $frontend . '/') === 0) {
|
||||
// backend use frontend htaccess
|
||||
$this->backend_htaccess = $this->frontend_htaccess;
|
||||
return;
|
||||
}
|
||||
|
||||
$this->backend_htaccess = $backend . '/.htaccess';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get corresponding htaccess path
|
||||
*
|
||||
* @since 1.1.0
|
||||
* @param string $kind Frontend or backend
|
||||
* @return string Path
|
||||
*/
|
||||
public function htaccess_path( $kind = 'frontend' ) {
|
||||
switch ($kind) {
|
||||
case 'backend':
|
||||
$path = $this->backend_htaccess;
|
||||
break;
|
||||
|
||||
case 'frontend':
|
||||
default:
|
||||
$path = $this->frontend_htaccess;
|
||||
break;
|
||||
}
|
||||
return $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the content of the rules file.
|
||||
*
|
||||
* NOTE: will throw error if failed
|
||||
*
|
||||
* @since 1.0.4
|
||||
* @since 2.9 Used exception for failed reading
|
||||
* @access public
|
||||
*/
|
||||
public function htaccess_read( $kind = 'frontend' ) {
|
||||
$path = $this->htaccess_path($kind);
|
||||
|
||||
if (!$path || !file_exists($path)) {
|
||||
return "\n";
|
||||
}
|
||||
|
||||
if (!$this->_readable($kind)) {
|
||||
Error::t('HTA_R');
|
||||
}
|
||||
|
||||
$content = File::read($path);
|
||||
if ($content === false) {
|
||||
Error::t('HTA_GET');
|
||||
}
|
||||
|
||||
// Remove ^M characters.
|
||||
$content = str_ireplace("\x0D", '', $content);
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to backup the .htaccess file if we didn't save one before.
|
||||
*
|
||||
* NOTE: will throw error if failed
|
||||
*
|
||||
* @since 1.0.10
|
||||
* @access private
|
||||
*/
|
||||
private function _htaccess_backup( $kind = 'frontend' ) {
|
||||
$path = $this->htaccess_path($kind);
|
||||
|
||||
if (!file_exists($path)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (file_exists($path . '.bk')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$res = copy($path, $path . '.bk');
|
||||
|
||||
// Failed to backup, abort
|
||||
if (!$res) {
|
||||
Error::t('HTA_BK');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get mobile view rule from htaccess file
|
||||
*
|
||||
* NOTE: will throw error if failed
|
||||
*
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public function current_mobile_agents() {
|
||||
$rules = $this->_get_rule_by(self::MARKER_MOBILE);
|
||||
if (!isset($rules[0])) {
|
||||
Error::t('HTA_DNF', self::MARKER_MOBILE);
|
||||
}
|
||||
|
||||
$rule = trim($rules[0]);
|
||||
// 'RewriteCond %{HTTP_USER_AGENT} ' . Utility::arr2regex( $cfg[ $id ], true ) . ' [NC]';
|
||||
$match = substr($rule, strlen('RewriteCond %{HTTP_USER_AGENT} '), -strlen(' [NC]'));
|
||||
|
||||
if (!$match) {
|
||||
Error::t('HTA_DNF', __('Mobile Agent Rules', 'litespeed-cache'));
|
||||
}
|
||||
|
||||
return $match;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse rewrites rule from the .htaccess file.
|
||||
*
|
||||
* NOTE: will throw error if failed
|
||||
*
|
||||
* @since 1.1.0
|
||||
* @access public
|
||||
*/
|
||||
public function current_login_cookie( $kind = 'frontend' ) {
|
||||
$rule = $this->_get_rule_by(self::MARKER_LOGIN_COOKIE, $kind);
|
||||
|
||||
if (!$rule) {
|
||||
Error::t('HTA_DNF', self::MARKER_LOGIN_COOKIE);
|
||||
}
|
||||
|
||||
if (strpos($rule, 'RewriteRule .? - [E=') !== 0) {
|
||||
Error::t('HTA_LOGIN_COOKIE_INVALID');
|
||||
}
|
||||
|
||||
$rule_cookie = substr($rule, strlen('RewriteRule .? - [E='), -1);
|
||||
|
||||
if (LITESPEED_SERVER_TYPE === 'LITESPEED_SERVER_OLS') {
|
||||
$rule_cookie = trim($rule_cookie, '"');
|
||||
}
|
||||
|
||||
// Drop `Cache-Vary:`
|
||||
$rule_cookie = substr($rule_cookie, strlen('Cache-Vary:'));
|
||||
|
||||
return $rule_cookie;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get rewrite rules based on the marker
|
||||
*
|
||||
* @since 2.0
|
||||
* @access private
|
||||
*/
|
||||
private function _get_rule_by( $cond, $kind = 'frontend' ) {
|
||||
clearstatcache();
|
||||
$path = $this->htaccess_path($kind);
|
||||
if (!$this->_readable($kind)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$rules = File::extract_from_markers($path, self::MARKER);
|
||||
if (!in_array($cond . self::MARKER_START, $rules) || !in_array($cond . self::MARKER_END, $rules)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$key_start = array_search($cond . self::MARKER_START, $rules);
|
||||
$key_end = array_search($cond . self::MARKER_END, $rules);
|
||||
if ($key_start === false || $key_end === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$results = array_slice($rules, $key_start + 1, $key_end - $key_start - 1);
|
||||
if (!$results) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (count($results) == 1) {
|
||||
return trim($results[0]);
|
||||
}
|
||||
|
||||
return array_filter($results);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate browser cache rules
|
||||
*
|
||||
* @since 1.3
|
||||
* @access private
|
||||
* @return array Rules set
|
||||
*/
|
||||
private function _browser_cache_rules( $cfg ) {
|
||||
/**
|
||||
* Add ttl setting
|
||||
*
|
||||
* @since 1.6.3
|
||||
*/
|
||||
$id = Base::O_CACHE_TTL_BROWSER;
|
||||
$ttl = $cfg[$id];
|
||||
$rules = array(
|
||||
self::EXPIRES_MODULE_START,
|
||||
// '<FilesMatch "\.(pdf|ico|svg|xml|jpg|jpeg|png|gif|webp|ogg|mp4|webm|js|css|woff|woff2|ttf|eot)(\.gz)?$">',
|
||||
'ExpiresActive on',
|
||||
'ExpiresByType application/pdf A' . $ttl,
|
||||
'ExpiresByType image/x-icon A' . $ttl,
|
||||
'ExpiresByType image/vnd.microsoft.icon A' . $ttl,
|
||||
'ExpiresByType image/svg+xml A' . $ttl,
|
||||
'',
|
||||
'ExpiresByType image/jpg A' . $ttl,
|
||||
'ExpiresByType image/jpeg A' . $ttl,
|
||||
'ExpiresByType image/png A' . $ttl,
|
||||
'ExpiresByType image/gif A' . $ttl,
|
||||
'ExpiresByType image/webp A' . $ttl,
|
||||
'ExpiresByType image/avif A' . $ttl,
|
||||
'',
|
||||
'ExpiresByType video/ogg A' . $ttl,
|
||||
'ExpiresByType audio/ogg A' . $ttl,
|
||||
'ExpiresByType video/mp4 A' . $ttl,
|
||||
'ExpiresByType video/webm A' . $ttl,
|
||||
'',
|
||||
'ExpiresByType text/css A' . $ttl,
|
||||
'ExpiresByType text/javascript A' . $ttl,
|
||||
'ExpiresByType application/javascript A' . $ttl,
|
||||
'ExpiresByType application/x-javascript A' . $ttl,
|
||||
'',
|
||||
'ExpiresByType application/x-font-ttf A' . $ttl,
|
||||
'ExpiresByType application/x-font-woff A' . $ttl,
|
||||
'ExpiresByType application/font-woff A' . $ttl,
|
||||
'ExpiresByType application/font-woff2 A' . $ttl,
|
||||
'ExpiresByType application/vnd.ms-fontobject A' . $ttl,
|
||||
'ExpiresByType font/ttf A' . $ttl,
|
||||
'ExpiresByType font/otf A' . $ttl,
|
||||
'ExpiresByType font/woff A' . $ttl,
|
||||
'ExpiresByType font/woff2 A' . $ttl,
|
||||
'',
|
||||
// '</FilesMatch>',
|
||||
self::LS_MODULE_END,
|
||||
);
|
||||
return $rules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate CORS rules for fonts
|
||||
*
|
||||
* @since 1.5
|
||||
* @access private
|
||||
* @return array Rules set
|
||||
*/
|
||||
private function _cors_rules() {
|
||||
return array(
|
||||
'<FilesMatch "\.(ttf|ttc|otf|eot|woff|woff2|font\.css)$">',
|
||||
'<IfModule mod_headers.c>',
|
||||
'Header set Access-Control-Allow-Origin "*"',
|
||||
'</IfModule>',
|
||||
'</FilesMatch>',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate rewrite rules based on settings
|
||||
*
|
||||
* @since 1.3
|
||||
* @access private
|
||||
* @param array $cfg The settings to be used for rewrite rule
|
||||
* @return array Rules array
|
||||
*/
|
||||
private function _generate_rules( $cfg ) {
|
||||
$new_rules = array();
|
||||
$new_rules_nonls = array();
|
||||
$new_rules_backend = array();
|
||||
$new_rules_backend_nonls = array();
|
||||
|
||||
// continual crawler
|
||||
// $id = Base::O_CRAWLER;
|
||||
// if (!empty($cfg[$id])) {
|
||||
$new_rules[] = self::MARKER_ASYNC . self::MARKER_START;
|
||||
$new_rules[] = 'RewriteCond %{REQUEST_URI} /wp-admin/admin-ajax\.php';
|
||||
$new_rules[] = 'RewriteCond %{QUERY_STRING} action=async_litespeed';
|
||||
$new_rules[] = 'RewriteRule .* - [E=noabort:1]';
|
||||
$new_rules[] = self::MARKER_ASYNC . self::MARKER_END;
|
||||
$new_rules[] = '';
|
||||
// }
|
||||
|
||||
// mobile agents
|
||||
$id = Base::O_CACHE_MOBILE_RULES;
|
||||
if ((!empty($cfg[Base::O_CACHE_MOBILE]) || !empty($cfg[Base::O_GUEST])) && !empty($cfg[$id])) {
|
||||
$new_rules[] = self::MARKER_MOBILE . self::MARKER_START;
|
||||
$new_rules[] = 'RewriteCond %{HTTP_USER_AGENT} ' . Utility::arr2regex($cfg[$id], true) . ' [NC]';
|
||||
$new_rules[] = 'RewriteRule .* - [E=Cache-Control:vary=%{ENV:LSCACHE_VARY_VALUE}+ismobile]';
|
||||
$new_rules[] = self::MARKER_MOBILE . self::MARKER_END;
|
||||
$new_rules[] = '';
|
||||
}
|
||||
|
||||
// nocache cookie
|
||||
$id = Base::O_CACHE_EXC_COOKIES;
|
||||
if (!empty($cfg[$id])) {
|
||||
$new_rules[] = self::MARKER_NOCACHE_COOKIES . self::MARKER_START;
|
||||
$new_rules[] = 'RewriteCond %{HTTP_COOKIE} ' . Utility::arr2regex($cfg[$id], true);
|
||||
$new_rules[] = 'RewriteRule .* - [E=Cache-Control:no-cache]';
|
||||
$new_rules[] = self::MARKER_NOCACHE_COOKIES . self::MARKER_END;
|
||||
$new_rules[] = '';
|
||||
}
|
||||
|
||||
// nocache user agents
|
||||
$id = Base::O_CACHE_EXC_USERAGENTS;
|
||||
if (!empty($cfg[$id])) {
|
||||
$new_rules[] = self::MARKER_NOCACHE_USER_AGENTS . self::MARKER_START;
|
||||
$new_rules[] = 'RewriteCond %{HTTP_USER_AGENT} ' . Utility::arr2regex($cfg[$id], true) . ' [NC]';
|
||||
$new_rules[] = 'RewriteRule .* - [E=Cache-Control:no-cache]';
|
||||
$new_rules[] = self::MARKER_NOCACHE_USER_AGENTS . self::MARKER_END;
|
||||
$new_rules[] = '';
|
||||
}
|
||||
|
||||
// check login cookie
|
||||
$vary_cookies = $cfg[Base::O_CACHE_VARY_COOKIES];
|
||||
$id = Base::O_CACHE_LOGIN_COOKIE;
|
||||
if (!empty($cfg[$id])) {
|
||||
$vary_cookies[] = $cfg[$id];
|
||||
}
|
||||
if (LITESPEED_SERVER_TYPE === 'LITESPEED_SERVER_OLS') {
|
||||
// Need to keep this due to different behavior of OLS when handling response vary header @Sep/22/2018
|
||||
if (defined('COOKIEHASH')) {
|
||||
$vary_cookies[] = ',wp-postpass_' . COOKIEHASH;
|
||||
}
|
||||
}
|
||||
$vary_cookies = apply_filters('litespeed_vary_cookies', $vary_cookies); // todo: test if response vary header can work in latest OLS, drop the above two lines
|
||||
// frontend and backend
|
||||
if ($vary_cookies) {
|
||||
$env = 'Cache-Vary:' . implode(',', $vary_cookies);
|
||||
// if (LITESPEED_SERVER_TYPE === 'LITESPEED_SERVER_OLS') {
|
||||
// }
|
||||
$env = '"' . $env . '"';
|
||||
$new_rules[] = $new_rules_backend[] = self::MARKER_LOGIN_COOKIE . self::MARKER_START;
|
||||
$new_rules[] = $new_rules_backend[] = 'RewriteRule .? - [E=' . $env . ']';
|
||||
$new_rules[] = $new_rules_backend[] = self::MARKER_LOGIN_COOKIE . self::MARKER_END;
|
||||
$new_rules[] = '';
|
||||
}
|
||||
|
||||
// CORS font rules
|
||||
$id = Base::O_CDN;
|
||||
if (!empty($cfg[$id])) {
|
||||
$new_rules[] = self::MARKER_CORS . self::MARKER_START;
|
||||
$new_rules = array_merge($new_rules, $this->_cors_rules()); // todo: network
|
||||
$new_rules[] = self::MARKER_CORS . self::MARKER_END;
|
||||
$new_rules[] = '';
|
||||
}
|
||||
|
||||
// webp support
|
||||
$id = Base::O_IMG_OPTM_WEBP;
|
||||
if (!empty($cfg[$id])) {
|
||||
$next_gen_format = 'webp';
|
||||
if ($cfg[$id] == 2) {
|
||||
$next_gen_format = 'avif';
|
||||
}
|
||||
$new_rules[] = self::MARKER_WEBP . self::MARKER_START;
|
||||
// Check for WebP support via HTTP_ACCEPT
|
||||
$new_rules[] = 'RewriteCond %{HTTP_ACCEPT} image/' . $next_gen_format . ' [OR]';
|
||||
|
||||
// Check for iPhone browsers (version > 13)
|
||||
$new_rules[] = 'RewriteCond %{HTTP_USER_AGENT} iPhone\ OS\ (1[4-9]|[2-9][0-9]) [OR]';
|
||||
|
||||
// Check for Firefox (version >= 65)
|
||||
$new_rules[] = 'RewriteCond %{HTTP_USER_AGENT} Firefox/([6-9][0-9]|[1-9][0-9]{2,})';
|
||||
|
||||
// Add vary
|
||||
$new_rules[] = 'RewriteRule .* - [E=Cache-Control:vary=%{ENV:LSCACHE_VARY_VALUE}+webp]';
|
||||
$new_rules[] = self::MARKER_WEBP . self::MARKER_END;
|
||||
$new_rules[] = '';
|
||||
}
|
||||
|
||||
// drop qs support
|
||||
$id = Base::O_CACHE_DROP_QS;
|
||||
if (!empty($cfg[$id])) {
|
||||
$new_rules[] = self::MARKER_DROPQS . self::MARKER_START;
|
||||
foreach ($cfg[$id] as $v) {
|
||||
$new_rules[] = 'CacheKeyModify -qs:' . $v;
|
||||
}
|
||||
$new_rules[] = self::MARKER_DROPQS . self::MARKER_END;
|
||||
$new_rules[] = '';
|
||||
}
|
||||
|
||||
// Browser cache
|
||||
$id = Base::O_CACHE_BROWSER;
|
||||
if (!empty($cfg[$id])) {
|
||||
$new_rules_nonls[] = $new_rules_backend_nonls[] = self::MARKER_BROWSER_CACHE . self::MARKER_START;
|
||||
$new_rules_nonls = array_merge($new_rules_nonls, $this->_browser_cache_rules($cfg));
|
||||
$new_rules_backend_nonls = array_merge($new_rules_backend_nonls, $this->_browser_cache_rules($cfg));
|
||||
$new_rules_nonls[] = $new_rules_backend_nonls[] = self::MARKER_BROWSER_CACHE . self::MARKER_END;
|
||||
$new_rules_nonls[] = '';
|
||||
}
|
||||
|
||||
// Add module wrapper for LiteSpeed rules
|
||||
if ($new_rules) {
|
||||
$new_rules = $this->_wrap_ls_module($new_rules);
|
||||
}
|
||||
|
||||
if ($new_rules_backend) {
|
||||
$new_rules_backend = $this->_wrap_ls_module($new_rules_backend);
|
||||
}
|
||||
|
||||
return array( $new_rules, $new_rules_backend, $new_rules_nonls, $new_rules_backend_nonls );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add LitSpeed module wrapper with rewrite on
|
||||
*
|
||||
* @since 2.1.1
|
||||
* @access private
|
||||
*/
|
||||
private function _wrap_ls_module( $rules = array() ) {
|
||||
return array_merge(array( self::LS_MODULE_START ), $this->__rewrite_on, array( '' ), $rules, array( self::LS_MODULE_END ));
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert LitSpeed module wrapper with rewrite on
|
||||
*
|
||||
* @since 2.1.1
|
||||
* @access public
|
||||
*/
|
||||
public function insert_ls_wrapper() {
|
||||
$rules = $this->_wrap_ls_module();
|
||||
$this->_insert_wrapper($rules);
|
||||
}
|
||||
|
||||
/**
|
||||
* wrap rules with module on info
|
||||
*
|
||||
* @since 1.1.5
|
||||
* @param array $rules
|
||||
* @return array wrapped rules with module info
|
||||
*/
|
||||
private function _wrap_do_no_edit( $rules ) {
|
||||
// When to clear rules, don't need DONOTEDIT msg
|
||||
if ($rules === false || !is_array($rules)) {
|
||||
return $rules;
|
||||
}
|
||||
|
||||
$rules = array_merge(array( self::LS_MODULE_DONOTEDIT ), $rules, array( self::LS_MODULE_DONOTEDIT ));
|
||||
|
||||
return $rules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write to htaccess with rules
|
||||
*
|
||||
* NOTE: will throw error if failed
|
||||
*
|
||||
* @since 1.1.0
|
||||
* @access private
|
||||
*/
|
||||
private function _insert_wrapper( $rules = array(), $kind = false, $marker = false ) {
|
||||
if ($kind != 'backend') {
|
||||
$kind = 'frontend';
|
||||
}
|
||||
|
||||
// Default marker is LiteSpeed marker `LSCACHE`
|
||||
if ($marker === false) {
|
||||
$marker = self::MARKER;
|
||||
}
|
||||
|
||||
$this->_htaccess_backup($kind);
|
||||
|
||||
File::insert_with_markers($this->htaccess_path($kind), $this->_wrap_do_no_edit($rules), $marker, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update rewrite rules based on setting
|
||||
*
|
||||
* NOTE: will throw error if failed
|
||||
*
|
||||
* @since 1.3
|
||||
* @access public
|
||||
*/
|
||||
public function update( $cfg ) {
|
||||
list($frontend_rules, $backend_rules, $frontend_rules_nonls, $backend_rules_nonls) = $this->_generate_rules($cfg);
|
||||
|
||||
// Check frontend content
|
||||
list($rules, $rules_nonls) = $this->_extract_rules();
|
||||
|
||||
// Check Non-LiteSpeed rules
|
||||
if ($this->_wrap_do_no_edit($frontend_rules_nonls) != $rules_nonls) {
|
||||
Debug2::debug('[Rules] Update non-ls frontend rules');
|
||||
// Need to update frontend htaccess
|
||||
try {
|
||||
$this->_insert_wrapper($frontend_rules_nonls, false, self::MARKER_NONLS);
|
||||
} catch (\Exception $e) {
|
||||
$manual_guide_codes = $this->_rewrite_codes_msg($this->frontend_htaccess, $frontend_rules_nonls, self::MARKER_NONLS);
|
||||
Debug2::debug('[Rules] Update Failed');
|
||||
throw new \Exception($manual_guide_codes);
|
||||
}
|
||||
}
|
||||
|
||||
// Check LiteSpeed rules
|
||||
if ($this->_wrap_do_no_edit($frontend_rules) != $rules) {
|
||||
Debug2::debug('[Rules] Update frontend rules');
|
||||
// Need to update frontend htaccess
|
||||
try {
|
||||
$this->_insert_wrapper($frontend_rules);
|
||||
} catch (\Exception $e) {
|
||||
Debug2::debug('[Rules] Update Failed');
|
||||
$manual_guide_codes = $this->_rewrite_codes_msg($this->frontend_htaccess, $frontend_rules);
|
||||
throw new \Exception($manual_guide_codes);
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->frontend_htaccess !== $this->backend_htaccess) {
|
||||
list($rules, $rules_nonls) = $this->_extract_rules('backend');
|
||||
|
||||
// Check Non-LiteSpeed rules for backend
|
||||
if ($this->_wrap_do_no_edit($backend_rules_nonls) != $rules_nonls) {
|
||||
Debug2::debug('[Rules] Update non-ls backend rules');
|
||||
// Need to update frontend htaccess
|
||||
try {
|
||||
$this->_insert_wrapper($backend_rules_nonls, 'backend', self::MARKER_NONLS);
|
||||
} catch (\Exception $e) {
|
||||
Debug2::debug('[Rules] Update Failed');
|
||||
$manual_guide_codes = $this->_rewrite_codes_msg($this->backend_htaccess, $backend_rules_nonls, self::MARKER_NONLS);
|
||||
throw new \Exception($manual_guide_codes);
|
||||
}
|
||||
}
|
||||
|
||||
// Check backend content
|
||||
if ($this->_wrap_do_no_edit($backend_rules) != $rules) {
|
||||
Debug2::debug('[Rules] Update backend rules');
|
||||
// Need to update backend htaccess
|
||||
try {
|
||||
$this->_insert_wrapper($backend_rules, 'backend');
|
||||
} catch (\Exception $e) {
|
||||
Debug2::debug('[Rules] Update Failed');
|
||||
$manual_guide_codes = $this->_rewrite_codes_msg($this->backend_htaccess, $backend_rules);
|
||||
throw new \Exception($manual_guide_codes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get existing rewrite rules
|
||||
*
|
||||
* NOTE: will throw error if failed
|
||||
*
|
||||
* @since 1.3
|
||||
* @access private
|
||||
* @param string $kind Frontend or backend .htaccess file
|
||||
*/
|
||||
private function _extract_rules( $kind = 'frontend' ) {
|
||||
clearstatcache();
|
||||
$path = $this->htaccess_path($kind);
|
||||
if (!$this->_readable($kind)) {
|
||||
Error::t('E_HTA_R');
|
||||
}
|
||||
|
||||
$rules = File::extract_from_markers($path, self::MARKER);
|
||||
$rules_nonls = File::extract_from_markers($path, self::MARKER_NONLS);
|
||||
|
||||
return array( $rules, $rules_nonls );
|
||||
}
|
||||
|
||||
/**
|
||||
* Output the msg with rules plain data for manual insert
|
||||
*
|
||||
* @since 1.1.5
|
||||
* @param string $file
|
||||
* @param array $rules
|
||||
* @return string final msg to output
|
||||
*/
|
||||
private function _rewrite_codes_msg( $file, $rules, $marker = false ) {
|
||||
return sprintf(
|
||||
__('<p>Please add/replace the following codes into the beginning of %1$s:</p> %2$s', 'litespeed-cache'),
|
||||
$file,
|
||||
'<textarea style="width:100%;" rows="10" readonly>' . htmlspecialchars($this->_wrap_rules_with_marker($rules, $marker)) . '</textarea>'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate rules plain data for manual insert
|
||||
*
|
||||
* @since 1.1.5
|
||||
*/
|
||||
private function _wrap_rules_with_marker( $rules, $marker = false ) {
|
||||
// Default marker is LiteSpeed marker `LSCACHE`
|
||||
if ($marker === false) {
|
||||
$marker = self::MARKER;
|
||||
}
|
||||
|
||||
$start_marker = "# BEGIN {$marker}";
|
||||
$end_marker = "# END {$marker}";
|
||||
$new_file_data = implode("\n", array_merge(array( $start_marker ), $this->_wrap_do_no_edit($rules), array( $end_marker )));
|
||||
|
||||
return $new_file_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the rules file of any changes added by the plugin specifically.
|
||||
*
|
||||
* @since 1.0.4
|
||||
* @access public
|
||||
*/
|
||||
public function clear_rules() {
|
||||
$this->_insert_wrapper(false); // Use false to avoid do-not-edit msg
|
||||
// Clear non ls rules
|
||||
$this->_insert_wrapper(false, false, self::MARKER_NONLS);
|
||||
|
||||
if ($this->frontend_htaccess !== $this->backend_htaccess) {
|
||||
$this->_insert_wrapper(false, 'backend');
|
||||
$this->_insert_wrapper(false, 'backend', self::MARKER_NONLS);
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,213 @@
|
||||
<?php
|
||||
// phpcs:ignoreFile
|
||||
|
||||
/**
|
||||
* The import/export class.
|
||||
*
|
||||
* @since 1.8.2
|
||||
*/
|
||||
|
||||
namespace LiteSpeed;
|
||||
|
||||
defined('WPINC') || exit();
|
||||
|
||||
class Import extends Base {
|
||||
|
||||
protected $_summary;
|
||||
|
||||
const TYPE_IMPORT = 'import';
|
||||
const TYPE_EXPORT = 'export';
|
||||
const TYPE_RESET = 'reset';
|
||||
|
||||
/**
|
||||
* Init
|
||||
*
|
||||
* @since 1.8.2
|
||||
*/
|
||||
public function __construct() {
|
||||
Debug2::debug('Import init');
|
||||
|
||||
$this->_summary = self::get_summary();
|
||||
}
|
||||
|
||||
/**
|
||||
* Export settings to file
|
||||
*
|
||||
* @since 1.8.2
|
||||
* @since 7.3 added download content type
|
||||
* @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-Type: application/octet-stream');
|
||||
@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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,267 @@
|
||||
<?php
|
||||
// phpcs:ignoreFile
|
||||
/**
|
||||
* The preset class.
|
||||
*
|
||||
* @since 5.3.0
|
||||
*/
|
||||
namespace LiteSpeed;
|
||||
|
||||
defined('WPINC') || exit();
|
||||
|
||||
class Preset extends Import {
|
||||
|
||||
protected $_summary;
|
||||
|
||||
const MAX_BACKUPS = 10;
|
||||
|
||||
const TYPE_APPLY = 'apply';
|
||||
const TYPE_RESTORE = 'restore';
|
||||
|
||||
const STANDARD_DIR = LSCWP_DIR . 'data/preset';
|
||||
const BACKUP_DIR = LITESPEED_STATIC_DIR . '/auto-backup';
|
||||
|
||||
/**
|
||||
* Returns sorted backup names
|
||||
*
|
||||
* @since 5.3.0
|
||||
* @access public
|
||||
*/
|
||||
public static function get_backups() {
|
||||
self::init_filesystem();
|
||||
global $wp_filesystem;
|
||||
|
||||
$backups = array_map(
|
||||
function ( $path ) {
|
||||
return self::basename($path['name']);
|
||||
},
|
||||
$wp_filesystem->dirlist(self::BACKUP_DIR) ?: array()
|
||||
);
|
||||
rsort($backups);
|
||||
|
||||
return $backups;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes extra backup files
|
||||
*
|
||||
* @since 5.3.0
|
||||
* @access public
|
||||
*/
|
||||
public static function prune_backups() {
|
||||
$backups = self::get_backups();
|
||||
global $wp_filesystem;
|
||||
|
||||
foreach (array_slice($backups, self::MAX_BACKUPS) as $backup) {
|
||||
$path = self::get_backup($backup);
|
||||
$wp_filesystem->delete($path);
|
||||
Debug2::debug('[Preset] Deleted old backup from ' . $backup);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a settings file's extensionless basename given its filesystem path
|
||||
*
|
||||
* @since 5.3.0
|
||||
* @access public
|
||||
*/
|
||||
public static function basename( $path ) {
|
||||
return basename($path, '.data');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a standard preset's path given its extensionless basename
|
||||
*
|
||||
* @since 5.3.0
|
||||
* @access public
|
||||
*/
|
||||
public static function get_standard( $name ) {
|
||||
return path_join(self::STANDARD_DIR, $name . '.data');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a backup's path given its extensionless basename
|
||||
*
|
||||
* @since 5.3.0
|
||||
* @access public
|
||||
*/
|
||||
public static function get_backup( $name ) {
|
||||
return path_join(self::BACKUP_DIR, $name . '.data');
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the global $wp_filesystem object and clears stat cache
|
||||
*
|
||||
* @since 5.3.0
|
||||
*/
|
||||
static function init_filesystem() {
|
||||
require_once ABSPATH . '/wp-admin/includes/file.php';
|
||||
\WP_Filesystem();
|
||||
clearstatcache();
|
||||
}
|
||||
|
||||
/**
|
||||
* Init
|
||||
*
|
||||
* @since 5.3.0
|
||||
*/
|
||||
public function __construct() {
|
||||
Debug2::debug('[Preset] Init');
|
||||
$this->_summary = self::get_summary();
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies a standard preset's settings given its extensionless basename
|
||||
*
|
||||
* @since 5.3.0
|
||||
* @access public
|
||||
*/
|
||||
public function apply( $preset ) {
|
||||
$this->make_backup($preset);
|
||||
|
||||
$path = self::get_standard($preset);
|
||||
$result = $this->import_file($path) ? $preset : 'error';
|
||||
|
||||
$this->log($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Restores settings from the backup file with the given timestamp, then deletes the file
|
||||
*
|
||||
* @since 5.3.0
|
||||
* @access public
|
||||
*/
|
||||
public function restore( $timestamp ) {
|
||||
$backups = array();
|
||||
foreach (self::get_backups() as $backup) {
|
||||
if (preg_match('/^backup-' . $timestamp . '(-|$)/', $backup) === 1) {
|
||||
$backups[] = $backup;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($backups)) {
|
||||
$this->log('error');
|
||||
return;
|
||||
}
|
||||
|
||||
$backup = $backups[0];
|
||||
$path = self::get_backup($backup);
|
||||
|
||||
if (!$this->import_file($path)) {
|
||||
$this->log('error');
|
||||
return;
|
||||
}
|
||||
|
||||
self::init_filesystem();
|
||||
global $wp_filesystem;
|
||||
|
||||
$wp_filesystem->delete($path);
|
||||
Debug2::debug('[Preset] Deleted most recent backup from ' . $backup);
|
||||
|
||||
$this->log('backup');
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves current settings as a backup file, then prunes extra backup files
|
||||
*
|
||||
* @since 5.3.0
|
||||
* @access public
|
||||
*/
|
||||
public function make_backup( $preset ) {
|
||||
$backup = 'backup-' . time() . '-before-' . $preset;
|
||||
$data = $this->export(true);
|
||||
|
||||
$path = self::get_backup($backup);
|
||||
File::save($path, $data, true);
|
||||
Debug2::debug('[Preset] Backup saved to ' . $backup);
|
||||
|
||||
self::prune_backups();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to import from a given settings file
|
||||
*
|
||||
* @since 5.3.0
|
||||
*/
|
||||
function import_file( $path ) {
|
||||
$debug = function ( $result, $name ) {
|
||||
$action = $result ? 'Applied' : 'Failed to apply';
|
||||
Debug2::debug('[Preset] ' . $action . ' settings from ' . $name);
|
||||
return $result;
|
||||
};
|
||||
|
||||
$name = self::basename($path);
|
||||
$contents = file_get_contents($path);
|
||||
|
||||
if (false === $contents) {
|
||||
Debug2::debug('[Preset] ❌ Failed to get file contents');
|
||||
return $debug(false, $name);
|
||||
}
|
||||
|
||||
$parsed = array();
|
||||
try {
|
||||
// Check if the data is v4+
|
||||
if (strpos($contents, '["_version",') === 0) {
|
||||
$contents = explode("\n", $contents);
|
||||
foreach ($contents as $line) {
|
||||
$line = trim($line);
|
||||
if (empty($line)) {
|
||||
continue;
|
||||
}
|
||||
list($key, $value) = \json_decode($line, true);
|
||||
$parsed[$key] = $value;
|
||||
}
|
||||
} else {
|
||||
$parsed = \json_decode(base64_decode($contents), true);
|
||||
}
|
||||
} catch (\Exception $ex) {
|
||||
Debug2::debug('[Preset] ❌ Failed to parse serialized data');
|
||||
return $debug(false, $name);
|
||||
}
|
||||
|
||||
if (empty($parsed)) {
|
||||
Debug2::debug('[Preset] ❌ Nothing to apply');
|
||||
return $debug(false, $name);
|
||||
}
|
||||
|
||||
$this->cls('Conf')->update_confs($parsed);
|
||||
|
||||
return $debug(true, $name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the log
|
||||
*
|
||||
* @since 5.3.0
|
||||
*/
|
||||
function log( $preset ) {
|
||||
$this->_summary['preset'] = $preset;
|
||||
$this->_summary['preset_timestamp'] = time();
|
||||
self::save_summary();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles all request actions from main cls
|
||||
*
|
||||
* @since 5.3.0
|
||||
* @access public
|
||||
*/
|
||||
public function handler() {
|
||||
$type = Router::verify_type();
|
||||
|
||||
switch ($type) {
|
||||
case self::TYPE_APPLY:
|
||||
$this->apply(!empty($_GET['preset']) ? $_GET['preset'] : false);
|
||||
break;
|
||||
|
||||
case self::TYPE_RESTORE:
|
||||
$this->restore(!empty($_GET['timestamp']) ? $_GET['timestamp'] : false);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
Admin::redirect();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,278 @@
|
||||
<?php
|
||||
// phpcs:ignoreFile
|
||||
|
||||
/**
|
||||
* The language class.
|
||||
*
|
||||
* @since 3.0
|
||||
* @package LiteSpeed_Cache
|
||||
*/
|
||||
|
||||
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_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 Inline Excluded Files', '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_MEDIA_AUTO_RESCALE_ORI => __('Auto Rescale Original Images', '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_SIZES_SKIPPED => __('Optimize Image Sizes', '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_CDN_CLOUDFLARE_CLEAR => __('Clear Cloudflare cache', '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'),
|
||||
|
||||
self::O_OPTIMAX => __('OptimaX', 'litespeed-cache'),
|
||||
);
|
||||
|
||||
if (array_key_exists($id, $_lang_list)) {
|
||||
return $_lang_list[$id];
|
||||
}
|
||||
|
||||
return 'N/A';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
<?php
|
||||
// phpcs:ignoreFile
|
||||
|
||||
/**
|
||||
* The localization class.
|
||||
*
|
||||
* @since 3.3
|
||||
*/
|
||||
|
||||
namespace LiteSpeed;
|
||||
|
||||
defined('WPINC') || exit();
|
||||
|
||||
class Localization extends Base {
|
||||
|
||||
const LOG_TAG = '🛍️';
|
||||
|
||||
/**
|
||||
* Init optimizer
|
||||
*
|
||||
* @since 3.0
|
||||
* @access protected
|
||||
*/
|
||||
public function init() {
|
||||
add_filter('litespeed_buffer_finalize', array( $this, 'finalize' ), 23); // After page optm
|
||||
}
|
||||
|
||||
/**
|
||||
* Localize Resources
|
||||
*
|
||||
* @since 3.3
|
||||
*/
|
||||
public function serve_static( $uri ) {
|
||||
$url = base64_decode($uri);
|
||||
|
||||
if (!$this->conf(self::O_OPTM_LOCALIZE)) {
|
||||
// wp_redirect( $url );
|
||||
exit('Not supported');
|
||||
}
|
||||
|
||||
if (substr($url, -3) !== '.js') {
|
||||
// wp_redirect( $url );
|
||||
// exit( 'Not supported ' . $uri );
|
||||
}
|
||||
|
||||
$match = false;
|
||||
$domains = $this->conf(self::O_OPTM_LOCALIZE_DOMAINS);
|
||||
foreach ($domains as $v) {
|
||||
if (!$v || strpos($v, '#') === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$type = 'js';
|
||||
$domain = $v;
|
||||
// Try to parse space split value
|
||||
if (strpos($v, ' ')) {
|
||||
$v = explode(' ', $v);
|
||||
if (!empty($v[1])) {
|
||||
$type = strtolower($v[0]);
|
||||
$domain = $v[1];
|
||||
}
|
||||
}
|
||||
|
||||
if (strpos($domain, 'https://') !== 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($type != 'js') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// if ( strpos( $url, $domain ) !== 0 ) {
|
||||
if ($url != $domain) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$match = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!$match) {
|
||||
// wp_redirect( $url );
|
||||
exit('Not supported2');
|
||||
}
|
||||
|
||||
header('Content-Type: application/javascript');
|
||||
|
||||
// Generate
|
||||
$this->_maybe_mk_cache_folder('localres');
|
||||
|
||||
$file = $this->_realpath($url);
|
||||
|
||||
self::debug('localize [url] ' . $url);
|
||||
$response = wp_safe_remote_get($url, array(
|
||||
'timeout' => 180,
|
||||
'stream' => true,
|
||||
'filename' => $file,
|
||||
));
|
||||
|
||||
// Parse response data
|
||||
if (is_wp_error($response)) {
|
||||
$error_message = $response->get_error_message();
|
||||
file_exists($file) && unlink($file);
|
||||
self::debug('failed to get: ' . $error_message);
|
||||
wp_redirect($url);
|
||||
exit();
|
||||
}
|
||||
|
||||
$url = $this->_rewrite($url);
|
||||
|
||||
wp_redirect($url);
|
||||
exit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the final URL of local avatar
|
||||
*
|
||||
* @since 4.5
|
||||
*/
|
||||
private function _rewrite( $url ) {
|
||||
return LITESPEED_STATIC_URL . '/localres/' . $this->_filepath($url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate realpath of the cache file
|
||||
*
|
||||
* @since 4.5
|
||||
* @access private
|
||||
*/
|
||||
private function _realpath( $url ) {
|
||||
return LITESPEED_STATIC_DIR . '/localres/' . $this->_filepath($url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get filepath
|
||||
*
|
||||
* @since 4.5
|
||||
*/
|
||||
private function _filepath( $url ) {
|
||||
$filename = md5($url) . '.js';
|
||||
if (is_multisite()) {
|
||||
$filename = get_current_blog_id() . '/' . $filename;
|
||||
}
|
||||
return $filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* Localize JS/Fonts
|
||||
*
|
||||
* @since 3.3
|
||||
* @access public
|
||||
*/
|
||||
public function finalize( $content ) {
|
||||
if (is_admin()) {
|
||||
return $content;
|
||||
}
|
||||
|
||||
if (!$this->conf(self::O_OPTM_LOCALIZE)) {
|
||||
return $content;
|
||||
}
|
||||
|
||||
$domains = $this->conf(self::O_OPTM_LOCALIZE_DOMAINS);
|
||||
if (!$domains) {
|
||||
return $content;
|
||||
}
|
||||
|
||||
foreach ($domains as $v) {
|
||||
if (!$v || strpos($v, '#') === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$type = 'js';
|
||||
$domain = $v;
|
||||
// Try to parse space split value
|
||||
if (strpos($v, ' ')) {
|
||||
$v = explode(' ', $v);
|
||||
if (!empty($v[1])) {
|
||||
$type = strtolower($v[0]);
|
||||
$domain = $v[1];
|
||||
}
|
||||
}
|
||||
|
||||
if (strpos($domain, 'https://') !== 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($type != 'js') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$content = str_replace($domain, LITESPEED_STATIC_URL . '/localres/' . base64_encode($domain), $content);
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,213 @@
|
||||
<?php
|
||||
/**
|
||||
* The class to operate post editor metabox settings.
|
||||
*
|
||||
* @since 4.7
|
||||
* @package LiteSpeed
|
||||
*/
|
||||
|
||||
namespace LiteSpeed;
|
||||
|
||||
defined( 'WPINC' ) || exit();
|
||||
|
||||
/**
|
||||
* Class Metabox
|
||||
*
|
||||
* Registers and handles LiteSpeed options shown in the post/page edit screen.
|
||||
*/
|
||||
class Metabox extends Root {
|
||||
|
||||
const LOG_TAG = '📦';
|
||||
|
||||
const POST_NONCE_ACTION = 'post_nonce_action';
|
||||
|
||||
/**
|
||||
* Map of metabox settings keys to labels.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $_postmeta_settings;
|
||||
|
||||
/**
|
||||
* Init 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
|
||||
* @return void
|
||||
*/
|
||||
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
|
||||
*
|
||||
* @param string $post_type Current post type.
|
||||
* @return void
|
||||
*/
|
||||
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', array( $this, 'meta_box_options' ), $post_type, 'side', 'core' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Show meta box content.
|
||||
*
|
||||
* @since 4.7
|
||||
* @return void
|
||||
*/
|
||||
public function meta_box_options() {
|
||||
require_once LSCWP_DIR . 'tpl/inc/metabox.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Save settings.
|
||||
*
|
||||
* @since 4.7
|
||||
*
|
||||
* @param int $post_id Post ID.
|
||||
* @param \WP_Post $post Post object.
|
||||
* @return void
|
||||
*/
|
||||
public function save_meta_box_settings( $post_id, $post ) {
|
||||
global $pagenow;
|
||||
|
||||
self::debug( 'Maybe save post2 [post_id] ' . $post_id );
|
||||
|
||||
if ( 'post.php' !== $pagenow || ! $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) {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput
|
||||
$val = isset($_POST[$k]) ? $_POST[$k] : false;
|
||||
$this->save($post_id, $k, $val);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load setting per post.
|
||||
*
|
||||
* @since 4.7
|
||||
*
|
||||
* @param string $conf Meta key to load.
|
||||
* @param int|bool $post_id Optional specific post ID, defaults to current query object.
|
||||
* @return mixed|null Meta value or null when not set.
|
||||
*/
|
||||
public function setting( $conf, $post_id = false ) {
|
||||
// Check if has metabox non-cacheable setting or not.
|
||||
if ( ! $post_id ) {
|
||||
$home_id = (int) get_option( 'page_for_posts' );
|
||||
if ( is_singular() ) {
|
||||
$post_id = get_the_ID();
|
||||
} elseif ( $home_id > 0 && is_home() ) {
|
||||
$post_id = $home_id;
|
||||
}
|
||||
}
|
||||
|
||||
$val = $post_id ? get_post_meta( $post_id, $conf, true ) : null;
|
||||
if ( $val ) {
|
||||
return $val;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a metabox value.
|
||||
*
|
||||
* @since 4.7
|
||||
*
|
||||
* @param int $post_id Post ID.
|
||||
* @param string $name Meta key name.
|
||||
* @param string|bool $val Value to save.
|
||||
* @param bool $is_append If true, append to existing list values.
|
||||
* @return void
|
||||
*/
|
||||
public function save( $post_id, $name, $val, $is_append = false ) {
|
||||
if ( false !== strpos( $name, VPI::POST_META ) ) {
|
||||
$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
|
||||
*
|
||||
* @param array $exclude_list Current exclude list.
|
||||
* @return array Modified exclude list.
|
||||
*/
|
||||
public function lazy_img_excludes( $exclude_list ) {
|
||||
$is_mobile = $this->_separate_mobile();
|
||||
$excludes = $this->setting( $is_mobile ? VPI::POST_META_MOBILE : VPI::POST_META );
|
||||
if ( null !== $excludes ) {
|
||||
$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( $exclude_list, $excludes );
|
||||
}
|
||||
}
|
||||
|
||||
return $exclude_list;
|
||||
}
|
||||
|
||||
$this->cls( 'VPI' )->add_to_queue();
|
||||
|
||||
return $exclude_list;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,981 @@
|
||||
<?php
|
||||
/**
|
||||
* WP Object Cache wrapper for LiteSpeed Cache.
|
||||
*
|
||||
* Provides a drop-in-compatible object cache implementation that proxies to
|
||||
* LiteSpeed's persistent cache while keeping a local runtime cache.
|
||||
*
|
||||
* @package LiteSpeed
|
||||
* @since 1.8
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WP_Object_Cache
|
||||
*
|
||||
* Implements the WordPress object cache for LiteSpeed Cache.
|
||||
*
|
||||
* @since 1.8
|
||||
* @package LiteSpeed
|
||||
*/
|
||||
class WP_Object_Cache {
|
||||
|
||||
/**
|
||||
* Singleton instance
|
||||
*
|
||||
* @since 1.8
|
||||
* @access protected
|
||||
* @var WP_Object_Cache|null
|
||||
*/
|
||||
protected static $_instance;
|
||||
|
||||
/**
|
||||
* Object cache instance
|
||||
*
|
||||
* @since 1.8
|
||||
* @access private
|
||||
* @var \LiteSpeed\Object_Cache
|
||||
*/
|
||||
private $_object_cache;
|
||||
|
||||
/**
|
||||
* Cache storage
|
||||
*
|
||||
* @since 1.8
|
||||
* @access private
|
||||
* @var array
|
||||
*/
|
||||
private $_cache = array();
|
||||
|
||||
/**
|
||||
* Cache for 404 keys
|
||||
*
|
||||
* @since 1.8
|
||||
* @access private
|
||||
* @var array
|
||||
*/
|
||||
private $_cache_404 = array();
|
||||
|
||||
/**
|
||||
* Total cache operations
|
||||
*
|
||||
* @since 1.8
|
||||
* @access private
|
||||
* @var int
|
||||
*/
|
||||
private $cache_total = 0;
|
||||
|
||||
/**
|
||||
* Cache hits within call
|
||||
*
|
||||
* @since 1.8
|
||||
* @access private
|
||||
* @var int
|
||||
*/
|
||||
private $count_hit_incall = 0;
|
||||
|
||||
/**
|
||||
* Cache hits
|
||||
*
|
||||
* @since 1.8
|
||||
* @access private
|
||||
* @var int
|
||||
*/
|
||||
private $count_hit = 0;
|
||||
|
||||
/**
|
||||
* Cache misses within call
|
||||
*
|
||||
* @since 1.8
|
||||
* @access private
|
||||
* @var int
|
||||
*/
|
||||
private $count_miss_incall = 0;
|
||||
|
||||
/**
|
||||
* Cache misses
|
||||
*
|
||||
* @since 1.8
|
||||
* @access private
|
||||
* @var int
|
||||
*/
|
||||
private $count_miss = 0;
|
||||
|
||||
/**
|
||||
* Cache set operations
|
||||
*
|
||||
* @since 1.8
|
||||
* @access private
|
||||
* @var int
|
||||
*/
|
||||
private $count_set = 0;
|
||||
|
||||
/**
|
||||
* Global cache groups
|
||||
*
|
||||
* @since 1.8
|
||||
* @access protected
|
||||
* @var array
|
||||
*/
|
||||
protected $global_groups = array();
|
||||
|
||||
/**
|
||||
* Blog prefix for cache keys
|
||||
*
|
||||
* @since 1.8
|
||||
* @access private
|
||||
* @var string
|
||||
*/
|
||||
private $blog_prefix;
|
||||
|
||||
/**
|
||||
* Multisite flag
|
||||
*
|
||||
* @since 1.8
|
||||
* @access private
|
||||
* @var bool
|
||||
*/
|
||||
private $multisite;
|
||||
|
||||
/**
|
||||
* Init.
|
||||
*
|
||||
* Initializes the object cache with LiteSpeed settings.
|
||||
*
|
||||
* @since 1.8
|
||||
* @access public
|
||||
*/
|
||||
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
|
||||
*/
|
||||
if ( ! 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 ) {
|
||||
$this->$name = $value;
|
||||
|
||||
return $this->$name;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.' )
|
||||
: sprintf(
|
||||
/* translators: %s: The type of the given cache key. */
|
||||
__( 'Cache key must be integer or non-empty string, %s given.' ),
|
||||
$type
|
||||
);
|
||||
|
||||
_doing_it_wrong(
|
||||
esc_html( __METHOD__ ),
|
||||
esc_html( $message ),
|
||||
'6.1.0'
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the final key.
|
||||
*
|
||||
* Generates a unique cache key based on group and prefix.
|
||||
*
|
||||
* @since 1.8
|
||||
* @access private
|
||||
* @param int|string $key Cache key.
|
||||
* @param string $group Optional. Cache group. Default 'default'.
|
||||
* @return string The final cache key.
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* Returns cache statistics for debugging purposes.
|
||||
*
|
||||
* @since 1.8
|
||||
* @access public
|
||||
* @return string Cache statistics.
|
||||
*/
|
||||
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
|
||||
* @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 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;
|
||||
}
|
||||
|
||||
$this->_cache[ $id ] = $data;
|
||||
|
||||
if ( array_key_exists( $id, $this->_cache_404 ) ) {
|
||||
unset( $this->_cache_404[ $id ] );
|
||||
}
|
||||
|
||||
if ( ! $this->_object_cache->is_non_persistent( $group ) ) {
|
||||
// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize
|
||||
$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 );
|
||||
|
||||
$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 ( null !== $v ) {
|
||||
$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.
|
||||
$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;
|
||||
}
|
||||
}
|
||||
|
||||
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'.
|
||||
* @return bool True on success, false if the contents were not deleted.
|
||||
*/
|
||||
public function delete( $key, $group = 'default' ) {
|
||||
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 ] );
|
||||
}
|
||||
|
||||
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
|
||||
* @access public
|
||||
*
|
||||
* @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
|
||||
* @access public
|
||||
*
|
||||
* @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
|
||||
*
|
||||
* @param int|string $key The cache key to increment or decrement.
|
||||
* @param int $offset The amount by which to adjust the item's value.
|
||||
* @param string $group Optional. The group the key is in. Default 'default'.
|
||||
* @param bool $incr True to increment, false to decrement.
|
||||
* @return int|false The item's new value on success, false on failure.
|
||||
*/
|
||||
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
|
||||
*
|
||||
* @return true Always returns true.
|
||||
*/
|
||||
public function flush_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
|
||||
*
|
||||
* @param string|string[] $groups A group or an array of groups to add.
|
||||
*/
|
||||
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
|
||||
*
|
||||
* Retrieves transient data from WordPress options table.
|
||||
*
|
||||
* @since 1.8.3
|
||||
* @access private
|
||||
* @see `wp-includes/option.php` function `get_transient`/`set_site_transient`
|
||||
*
|
||||
* @param string $transient Transient name.
|
||||
* @param string $group Transient group ('transient' or 'site-transient').
|
||||
* @return mixed Transient value or false if not found.
|
||||
*/
|
||||
private function _transient_get( $transient, $group ) {
|
||||
if ( 'transient' === $group ) {
|
||||
/**** 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 ( 'site-transient' === $group ) {
|
||||
/**** Ori WP func start */
|
||||
$no_timeout = array( 'update_core', 'update_plugins', 'update_themes' );
|
||||
$transient_option = '_site_transient_' . $transient;
|
||||
if ( ! in_array( $transient, $no_timeout, true ) ) {
|
||||
$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
|
||||
*
|
||||
* Stores transient data in WordPress options table.
|
||||
*
|
||||
* @since 1.8.3
|
||||
* @access private
|
||||
* @see `wp-includes/option.php` function `set_transient`/`set_site_transient`
|
||||
*
|
||||
* @param string $transient Transient name.
|
||||
* @param mixed $value Transient value.
|
||||
* @param string $group Transient group ('transient' or 'site-transient').
|
||||
* @param int $expiration Time until expiration in seconds.
|
||||
* @return bool True on success, false on failure.
|
||||
*/
|
||||
private function _transient_set( $transient, $value, $group, $expiration ) {
|
||||
if ( 'transient' === $group ) {
|
||||
/**** 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 ( 'site-transient' === $group ) {
|
||||
/**** 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
|
||||
*
|
||||
* Removes transient data from WordPress options table.
|
||||
*
|
||||
* @since 1.8.3
|
||||
* @access private
|
||||
* @see `wp-includes/option.php` function `delete_transient`/`delete_site_transient`
|
||||
*
|
||||
* @param string $transient Transient name.
|
||||
* @param string $group Transient group ('transient' or 'site-transient').
|
||||
*/
|
||||
private function _transient_del( $transient, $group ) {
|
||||
if ( 'transient' === $group ) {
|
||||
/**** 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 ( 'site-transient' === $group ) {
|
||||
/**** 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
|
||||
*
|
||||
* @return WP_Object_Cache The current instance.
|
||||
*/
|
||||
public static function get_instance() {
|
||||
if ( ! isset( self::$_instance ) ) {
|
||||
self::$_instance = new self();
|
||||
}
|
||||
|
||||
return self::$_instance;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,858 @@
|
||||
<?php
|
||||
/**
|
||||
* The object cache class.
|
||||
*
|
||||
* @since 1.8
|
||||
* @package LiteSpeed
|
||||
*/
|
||||
|
||||
namespace LiteSpeed;
|
||||
|
||||
defined( 'WPINC' ) || exit();
|
||||
|
||||
require_once dirname( __DIR__ ) . '/autoload.php';
|
||||
|
||||
/**
|
||||
* Object cache handler using Redis or Memcached.
|
||||
*
|
||||
* NOTE: this class may be included without initialized core.
|
||||
*
|
||||
* @since 1.8
|
||||
*/
|
||||
class Object_Cache extends Root {
|
||||
const LOG_TAG = '[Object_Cache]';
|
||||
|
||||
/**
|
||||
* Debug option key.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const O_DEBUG = 'debug';
|
||||
|
||||
/**
|
||||
* Object cache enable key.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const O_OBJECT = 'object';
|
||||
|
||||
/**
|
||||
* Object kind (Redis/Memcached).
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const O_OBJECT_KIND = 'object-kind';
|
||||
|
||||
/**
|
||||
* Object host.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const O_OBJECT_HOST = 'object-host';
|
||||
|
||||
/**
|
||||
* Object port.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const O_OBJECT_PORT = 'object-port';
|
||||
|
||||
/**
|
||||
* Object life/TTL.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const O_OBJECT_LIFE = 'object-life';
|
||||
|
||||
/**
|
||||
* Persistent connection flag.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const O_OBJECT_PERSISTENT = 'object-persistent';
|
||||
|
||||
/**
|
||||
* Admin cache flag.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const O_OBJECT_ADMIN = 'object-admin';
|
||||
|
||||
/**
|
||||
* Transients store flag.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const O_OBJECT_TRANSIENTS = 'object-transients';
|
||||
|
||||
/**
|
||||
* DB index for Redis.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const O_OBJECT_DB_ID = 'object-db_id';
|
||||
|
||||
/**
|
||||
* Username for auth.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const O_OBJECT_USER = 'object-user';
|
||||
|
||||
/**
|
||||
* Password for auth.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const O_OBJECT_PSWD = 'object-pswd';
|
||||
|
||||
/**
|
||||
* Global groups list.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const O_OBJECT_GLOBAL_GROUPS = 'object-global_groups';
|
||||
|
||||
/**
|
||||
* Non-persistent groups list.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const O_OBJECT_NON_PERSISTENT_GROUPS = 'object-non_persistent_groups';
|
||||
|
||||
/**
|
||||
* Connection instance.
|
||||
*
|
||||
* @var \Redis|\Memcached|null
|
||||
*/
|
||||
private $_conn;
|
||||
|
||||
/**
|
||||
* Debug config.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $_cfg_debug;
|
||||
|
||||
/**
|
||||
* Whether OC is enabled.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $_cfg_enabled;
|
||||
|
||||
/**
|
||||
* True => Redis, false => Memcached.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $_cfg_method;
|
||||
|
||||
/**
|
||||
* Host name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $_cfg_host;
|
||||
|
||||
/**
|
||||
* Port number.
|
||||
*
|
||||
* @var int|string
|
||||
*/
|
||||
private $_cfg_port;
|
||||
|
||||
/**
|
||||
* TTL in seconds.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $_cfg_life;
|
||||
|
||||
/**
|
||||
* Use persistent connection.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $_cfg_persistent;
|
||||
|
||||
/**
|
||||
* Cache admin pages.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $_cfg_admin;
|
||||
|
||||
/**
|
||||
* Store transients.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $_cfg_transients;
|
||||
|
||||
/**
|
||||
* Redis DB index.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $_cfg_db;
|
||||
|
||||
/**
|
||||
* Auth username.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $_cfg_user;
|
||||
|
||||
/**
|
||||
* Auth password.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $_cfg_pswd;
|
||||
|
||||
/**
|
||||
* Default TTL in seconds.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $_default_life = 360;
|
||||
|
||||
/**
|
||||
* 'Redis' or 'Memcached'.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $_oc_driver = 'Memcached'; // Redis or Memcached.
|
||||
|
||||
/**
|
||||
* Global groups.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $_global_groups = array();
|
||||
|
||||
/**
|
||||
* Non-persistent groups.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $_non_persistent_groups = array();
|
||||
|
||||
/**
|
||||
* Init.
|
||||
*
|
||||
* NOTE: this class may be included without initialized core.
|
||||
*
|
||||
* @since 1.8
|
||||
*
|
||||
* @param array|false $cfg Optional configuration to bootstrap without core.
|
||||
*/
|
||||
public function __construct( $cfg = false ) {
|
||||
if ( $cfg ) {
|
||||
if ( ! is_array( $cfg[ Base::O_OBJECT_GLOBAL_GROUPS ] ) ) {
|
||||
$cfg[ Base::O_OBJECT_GLOBAL_GROUPS ] = explode( "\n", $cfg[ Base::O_OBJECT_GLOBAL_GROUPS ] );
|
||||
}
|
||||
if ( ! is_array( $cfg[ Base::O_OBJECT_NON_PERSISTENT_GROUPS ] ) ) {
|
||||
$cfg[ Base::O_OBJECT_NON_PERSISTENT_GROUPS ] = explode( "\n", $cfg[ Base::O_OBJECT_NON_PERSISTENT_GROUPS ] );
|
||||
}
|
||||
$this->_cfg_debug = $cfg[ Base::O_DEBUG ] ? $cfg[ Base::O_DEBUG ] : false;
|
||||
$this->_cfg_method = $cfg[ Base::O_OBJECT_KIND ] ? true : false;
|
||||
$this->_cfg_host = $cfg[ Base::O_OBJECT_HOST ];
|
||||
$this->_cfg_port = $cfg[ Base::O_OBJECT_PORT ];
|
||||
$this->_cfg_life = $cfg[ Base::O_OBJECT_LIFE ];
|
||||
$this->_cfg_persistent = $cfg[ Base::O_OBJECT_PERSISTENT ];
|
||||
$this->_cfg_admin = $cfg[ Base::O_OBJECT_ADMIN ];
|
||||
$this->_cfg_transients = $cfg[ Base::O_OBJECT_TRANSIENTS ];
|
||||
$this->_cfg_db = $cfg[ Base::O_OBJECT_DB_ID ];
|
||||
$this->_cfg_user = $cfg[ Base::O_OBJECT_USER ];
|
||||
$this->_cfg_pswd = $cfg[ Base::O_OBJECT_PSWD ];
|
||||
$this->_global_groups = $cfg[ Base::O_OBJECT_GLOBAL_GROUPS ];
|
||||
$this->_non_persistent_groups = $cfg[ Base::O_OBJECT_NON_PERSISTENT_GROUPS ];
|
||||
|
||||
if ( $this->_cfg_method ) {
|
||||
$this->_oc_driver = 'Redis';
|
||||
}
|
||||
$this->_cfg_enabled = $cfg[ Base::O_OBJECT ] && class_exists( $this->_oc_driver ) && $this->_cfg_host;
|
||||
} elseif ( defined( 'LITESPEED_CONF_LOADED' ) ) { // If OC is OFF, will hit here to init OC after conf initialized
|
||||
$this->_cfg_debug = $this->conf( Base::O_DEBUG ) ? $this->conf( Base::O_DEBUG ) : false;
|
||||
$this->_cfg_method = $this->conf( Base::O_OBJECT_KIND ) ? true : false;
|
||||
$this->_cfg_host = $this->conf( Base::O_OBJECT_HOST );
|
||||
$this->_cfg_port = $this->conf( Base::O_OBJECT_PORT );
|
||||
$this->_cfg_life = $this->conf( Base::O_OBJECT_LIFE );
|
||||
$this->_cfg_persistent = $this->conf( Base::O_OBJECT_PERSISTENT );
|
||||
$this->_cfg_admin = $this->conf( Base::O_OBJECT_ADMIN );
|
||||
$this->_cfg_transients = $this->conf( Base::O_OBJECT_TRANSIENTS );
|
||||
$this->_cfg_db = $this->conf( Base::O_OBJECT_DB_ID );
|
||||
$this->_cfg_user = $this->conf( Base::O_OBJECT_USER );
|
||||
$this->_cfg_pswd = $this->conf( Base::O_OBJECT_PSWD );
|
||||
$this->_global_groups = $this->conf( Base::O_OBJECT_GLOBAL_GROUPS );
|
||||
$this->_non_persistent_groups = $this->conf( Base::O_OBJECT_NON_PERSISTENT_GROUPS );
|
||||
|
||||
if ( $this->_cfg_method ) {
|
||||
$this->_oc_driver = 'Redis';
|
||||
}
|
||||
$this->_cfg_enabled = $this->conf( Base::O_OBJECT ) && class_exists( $this->_oc_driver ) && $this->_cfg_host;
|
||||
} elseif ( defined( 'self::CONF_FILE' ) && file_exists( WP_CONTENT_DIR . '/' . self::CONF_FILE ) ) {
|
||||
// Get cfg from _data_file.
|
||||
// Use self::const to avoid loading more classes.
|
||||
$cfg = \json_decode( file_get_contents( WP_CONTENT_DIR . '/' . self::CONF_FILE ), true );
|
||||
if ( ! empty( $cfg[ self::O_OBJECT_HOST ] ) ) {
|
||||
$this->_cfg_debug = ! empty( $cfg[ Base::O_DEBUG ] ) ? $cfg[ Base::O_DEBUG ] : false;
|
||||
$this->_cfg_method = ! empty( $cfg[ self::O_OBJECT_KIND ] ) ? $cfg[ self::O_OBJECT_KIND ] : false;
|
||||
$this->_cfg_host = $cfg[ self::O_OBJECT_HOST ];
|
||||
$this->_cfg_port = $cfg[ self::O_OBJECT_PORT ];
|
||||
$this->_cfg_life = ! empty( $cfg[ self::O_OBJECT_LIFE ] ) ? $cfg[ self::O_OBJECT_LIFE ] : $this->_default_life;
|
||||
$this->_cfg_persistent = ! empty( $cfg[ self::O_OBJECT_PERSISTENT ] ) ? $cfg[ self::O_OBJECT_PERSISTENT ] : false;
|
||||
$this->_cfg_admin = ! empty( $cfg[ self::O_OBJECT_ADMIN ] ) ? $cfg[ self::O_OBJECT_ADMIN ] : false;
|
||||
$this->_cfg_transients = ! empty( $cfg[ self::O_OBJECT_TRANSIENTS ] ) ? $cfg[ self::O_OBJECT_TRANSIENTS ] : false;
|
||||
$this->_cfg_db = ! empty( $cfg[ self::O_OBJECT_DB_ID ] ) ? $cfg[ self::O_OBJECT_DB_ID ] : 0;
|
||||
$this->_cfg_user = ! empty( $cfg[ self::O_OBJECT_USER ] ) ? $cfg[ self::O_OBJECT_USER ] : '';
|
||||
$this->_cfg_pswd = ! empty( $cfg[ self::O_OBJECT_PSWD ] ) ? $cfg[ self::O_OBJECT_PSWD ] : '';
|
||||
$this->_global_groups = ! empty( $cfg[ self::O_OBJECT_GLOBAL_GROUPS ] ) ? $cfg[ self::O_OBJECT_GLOBAL_GROUPS ] : array();
|
||||
$this->_non_persistent_groups = ! empty( $cfg[ self::O_OBJECT_NON_PERSISTENT_GROUPS ] ) ? $cfg[ self::O_OBJECT_NON_PERSISTENT_GROUPS ] : array();
|
||||
|
||||
if ( $this->_cfg_method ) {
|
||||
$this->_oc_driver = 'Redis';
|
||||
}
|
||||
$this->_cfg_enabled = class_exists( $this->_oc_driver ) && $this->_cfg_host;
|
||||
} else {
|
||||
$this->_cfg_enabled = false;
|
||||
}
|
||||
} else {
|
||||
$this->_cfg_enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add debug.
|
||||
*
|
||||
* @since 6.3
|
||||
* @access private
|
||||
*
|
||||
* @param string $text Log text.
|
||||
* @return void
|
||||
*/
|
||||
private function debug_oc( $text ) {
|
||||
if ( defined( 'LSCWP_LOG' ) ) {
|
||||
self::debug( $text );
|
||||
return;
|
||||
}
|
||||
|
||||
if ( Base::VAL_ON2 !== $this->_cfg_debug ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$litespeed_data_folder = defined( 'LITESPEED_DATA_FOLDER' ) ? LITESPEED_DATA_FOLDER : 'litespeed';
|
||||
$lscwp_content_dir = defined( 'LSCWP_CONTENT_DIR' ) ? LSCWP_CONTENT_DIR : WP_CONTENT_DIR;
|
||||
$litespeed_static_dir = $lscwp_content_dir . '/' . $litespeed_data_folder;
|
||||
$log_path_prefix = $litespeed_static_dir . '/debug/';
|
||||
$log_file = $log_path_prefix . Debug2::FilePath( 'debug' );
|
||||
|
||||
if ( file_exists( $log_path_prefix . 'index.php' ) && file_exists( $log_file ) ) {
|
||||
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
|
||||
error_log(gmdate('m/d/y H:i:s') . ' - OC - ' . $text . PHP_EOL, 3, $log_file);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get `Store Transients` setting value.
|
||||
*
|
||||
* @since 1.8.3
|
||||
* @access public
|
||||
*
|
||||
* @param string $group Group name.
|
||||
* @return bool
|
||||
*/
|
||||
public function store_transients( $group ) {
|
||||
return $this->_cfg_transients && $this->_is_transients_group( $group );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the group belongs to transients or not.
|
||||
*
|
||||
* @since 1.8.3
|
||||
* @access private
|
||||
*
|
||||
* @param string $group Group name.
|
||||
* @return bool
|
||||
*/
|
||||
private function _is_transients_group( $group ) {
|
||||
return in_array( $group, array( 'transient', 'site-transient' ), true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Update WP object cache file config.
|
||||
*
|
||||
* @since 1.8
|
||||
* @access public
|
||||
*
|
||||
* @param array $options Options to apply after update.
|
||||
* @return void
|
||||
*/
|
||||
public function update_file( $options ) {
|
||||
$changed = false;
|
||||
|
||||
// NOTE: When included in oc.php, `LSCWP_DIR` will show undefined, so this must be assigned/generated when used.
|
||||
$_oc_ori_file = LSCWP_DIR . 'lib/object-cache.php';
|
||||
$_oc_wp_file = WP_CONTENT_DIR . '/object-cache.php';
|
||||
|
||||
// Update cls file.
|
||||
if ( ! file_exists( $_oc_wp_file ) || md5_file( $_oc_wp_file ) !== md5_file( $_oc_ori_file ) ) {
|
||||
$this->debug_oc( 'copying object-cache.php file to ' . $_oc_wp_file );
|
||||
copy( $_oc_ori_file, $_oc_wp_file );
|
||||
$changed = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear object cache.
|
||||
*/
|
||||
if ( $changed ) {
|
||||
$this->_reconnect( $options );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove object cache file.
|
||||
*
|
||||
* @since 1.8.2
|
||||
* @access public
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function del_file() {
|
||||
// NOTE: When included in oc.php, `LSCWP_DIR` will show undefined, so this must be assigned/generated when used.
|
||||
$_oc_ori_file = LSCWP_DIR . 'lib/object-cache.php';
|
||||
$_oc_wp_file = WP_CONTENT_DIR . '/object-cache.php';
|
||||
|
||||
if ( file_exists( $_oc_wp_file ) && md5_file( $_oc_wp_file ) === md5_file( $_oc_ori_file ) ) {
|
||||
$this->debug_oc( 'removing ' . $_oc_wp_file );
|
||||
wp_delete_file( $_oc_wp_file );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to build connection.
|
||||
*
|
||||
* @since 1.8
|
||||
* @access public
|
||||
*
|
||||
* @return bool|null False on failure, true on success, null if unsupported.
|
||||
*/
|
||||
public function test_connection() {
|
||||
return $this->_connect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Force to connect with this setting.
|
||||
*
|
||||
* @since 1.8
|
||||
* @access private
|
||||
*
|
||||
* @param array $cfg Reconnect configuration.
|
||||
* @return void
|
||||
*/
|
||||
private function _reconnect( $cfg ) {
|
||||
$this->debug_oc( 'Reconnecting' );
|
||||
if ( isset( $this->_conn ) ) {
|
||||
// error_log( 'Object: Quitting existing connection!' );
|
||||
$this->debug_oc( 'Quitting existing connection' );
|
||||
$this->flush();
|
||||
$this->_conn = null;
|
||||
$this->cls( false, true );
|
||||
}
|
||||
|
||||
$cls = $this->cls( false, false, $cfg );
|
||||
$cls->_connect();
|
||||
if ( isset( $cls->_conn ) ) {
|
||||
$cls->flush();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to Memcached/Redis server.
|
||||
*
|
||||
* @since 1.8
|
||||
* @access private
|
||||
*
|
||||
* @return bool|null False on failure, true on success, null if driver missing.
|
||||
*/
|
||||
private function _connect() {
|
||||
if ( isset( $this->_conn ) ) {
|
||||
// error_log( 'Object: _connected' );
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( ! class_exists( $this->_oc_driver ) || ! $this->_cfg_host ) {
|
||||
$this->debug_oc( '_oc_driver cls non existed or _cfg_host missed: ' . $this->_oc_driver . ' [_cfg_host] ' . $this->_cfg_host . ':' . $this->_cfg_port );
|
||||
return null;
|
||||
}
|
||||
|
||||
if ( defined( 'LITESPEED_OC_FAILURE' ) ) {
|
||||
$this->debug_oc( 'LITESPEED_OC_FAILURE const defined' );
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->debug_oc( 'Init ' . $this->_oc_driver . ' connection to ' . $this->_cfg_host . ':' . $this->_cfg_port );
|
||||
|
||||
$failed = false;
|
||||
|
||||
/**
|
||||
* Connect to Redis.
|
||||
*
|
||||
* @since 1.8.1
|
||||
* @see https://github.com/phpredis/phpredis/#example-1
|
||||
*/
|
||||
if ( 'Redis' === $this->_oc_driver ) {
|
||||
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_set_error_handler
|
||||
set_error_handler( 'litespeed_exception_handler' );
|
||||
try {
|
||||
$this->_conn = new \Redis();
|
||||
// error_log( 'Object: _connect Redis' );
|
||||
|
||||
if ( $this->_cfg_persistent ) {
|
||||
if ( $this->_cfg_port ) {
|
||||
$this->_conn->pconnect( $this->_cfg_host, $this->_cfg_port );
|
||||
} else {
|
||||
$this->_conn->pconnect( $this->_cfg_host );
|
||||
}
|
||||
} elseif ( $this->_cfg_port ) {
|
||||
$this->_conn->connect( $this->_cfg_host, $this->_cfg_port );
|
||||
} else {
|
||||
$this->_conn->connect( $this->_cfg_host );
|
||||
}
|
||||
|
||||
if ( $this->_cfg_pswd ) {
|
||||
if ( $this->_cfg_user ) {
|
||||
$this->_conn->auth( array( $this->_cfg_user, $this->_cfg_pswd ) );
|
||||
} else {
|
||||
$this->_conn->auth( $this->_cfg_pswd );
|
||||
}
|
||||
}
|
||||
|
||||
if (defined('Redis::OPT_REPLY_LITERAL')) {
|
||||
$this->debug_oc( 'Redis set OPT_REPLY_LITERAL' );
|
||||
$this->_conn->setOption(\Redis::OPT_REPLY_LITERAL, true);
|
||||
}
|
||||
|
||||
if ( $this->_cfg_db ) {
|
||||
$this->_conn->select( $this->_cfg_db );
|
||||
}
|
||||
|
||||
$res = $this->_conn->rawCommand('PING');
|
||||
|
||||
if ( 'PONG' !== $res ) {
|
||||
$this->debug_oc( 'Redis resp is wrong: ' . $res );
|
||||
$failed = true;
|
||||
}
|
||||
} catch ( \Exception $e ) {
|
||||
$this->debug_oc( 'Redis connect exception: ' . $e->getMessage() );
|
||||
$failed = true;
|
||||
} catch ( \ErrorException $e ) {
|
||||
$this->debug_oc( 'Redis connect error: ' . $e->getMessage() );
|
||||
$failed = true;
|
||||
}
|
||||
restore_error_handler();
|
||||
} else {
|
||||
// Connect to Memcached.
|
||||
if ( $this->_cfg_persistent ) {
|
||||
$this->_conn = new \Memcached( $this->_get_mem_id() );
|
||||
|
||||
// Check memcached persistent connection.
|
||||
if ( $this->_validate_mem_server() ) {
|
||||
// error_log( 'Object: _validate_mem_server' );
|
||||
$this->debug_oc( 'Got persistent ' . $this->_oc_driver . ' connection' );
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->debug_oc( 'No persistent ' . $this->_oc_driver . ' server list!' );
|
||||
} else {
|
||||
// error_log( 'Object: new memcached!' );
|
||||
$this->_conn = new \Memcached();
|
||||
}
|
||||
|
||||
$this->_conn->addServer( $this->_cfg_host, (int) $this->_cfg_port );
|
||||
|
||||
/**
|
||||
* Add SASL auth.
|
||||
*
|
||||
* @since 1.8.1
|
||||
* @since 2.9.6 Fixed SASL connection @see https://www.litespeedtech.com/support/wiki/doku.php/litespeed_wiki:lsmcd:new_sasl
|
||||
*/
|
||||
if ( $this->_cfg_user && $this->_cfg_pswd && method_exists( $this->_conn, 'setSaslAuthData' ) ) {
|
||||
$this->_conn->setOption( \Memcached::OPT_BINARY_PROTOCOL, true );
|
||||
$this->_conn->setOption( \Memcached::OPT_COMPRESSION, false );
|
||||
$this->_conn->setSaslAuthData( $this->_cfg_user, $this->_cfg_pswd );
|
||||
}
|
||||
|
||||
// Check connection.
|
||||
if ( ! $this->_validate_mem_server() ) {
|
||||
$failed = true;
|
||||
}
|
||||
}
|
||||
|
||||
// If failed to connect.
|
||||
if ( $failed ) {
|
||||
$this->debug_oc( '❌ Failed to connect ' . $this->_oc_driver . ' server!' );
|
||||
$this->_conn = null;
|
||||
$this->_cfg_enabled = false;
|
||||
! defined( 'LITESPEED_OC_FAILURE' ) && define( 'LITESPEED_OC_FAILURE', true );
|
||||
// error_log( 'Object: false!' );
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->debug_oc( '✅ Connected to ' . $this->_oc_driver . ' server.' );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the connected memcached host is the one in cfg.
|
||||
*
|
||||
* @since 1.8
|
||||
* @access private
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function _validate_mem_server() {
|
||||
$mem_list = $this->_conn->getStats();
|
||||
if ( empty( $mem_list ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ( $mem_list as $k => $v ) {
|
||||
if ( substr( $k, 0, strlen( $this->_cfg_host ) ) !== $this->_cfg_host ) {
|
||||
continue;
|
||||
}
|
||||
if ( ! empty( $v['pid'] ) || ! empty( $v['curr_connections'] ) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get memcached unique id to be used for connecting.
|
||||
*
|
||||
* @since 1.8
|
||||
* @access private
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function _get_mem_id() {
|
||||
$mem_id = 'litespeed';
|
||||
if ( is_multisite() ) {
|
||||
$mem_id .= '_' . get_current_blog_id();
|
||||
}
|
||||
|
||||
return $mem_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cache.
|
||||
*
|
||||
* @since 1.8
|
||||
* @access public
|
||||
*
|
||||
* @param string $key Cache key.
|
||||
* @return mixed|null
|
||||
*/
|
||||
public function get( $key ) {
|
||||
if ( ! $this->_cfg_enabled ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ( ! $this->_can_cache() ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ( ! $this->_connect() ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$res = $this->_conn->get( $key );
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set cache.
|
||||
*
|
||||
* @since 1.8
|
||||
* @access public
|
||||
*
|
||||
* @param string $key Cache key.
|
||||
* @param mixed $data Data to store.
|
||||
* @param int $expire TTL seconds.
|
||||
* @return bool|null
|
||||
*/
|
||||
public function set( $key, $data, $expire ) {
|
||||
if ( ! $this->_cfg_enabled ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* To fix the Cloud callback cached as its frontend call but the hash is generated in backend
|
||||
* Bug found by Stan at Jan/10/2020
|
||||
*/
|
||||
// if ( ! $this->_can_cache() ) {
|
||||
// return null;
|
||||
// }
|
||||
|
||||
if ( ! $this->_connect() ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$ttl = $expire ? $expire : $this->_cfg_life;
|
||||
|
||||
if ( 'Redis' === $this->_oc_driver ) {
|
||||
try {
|
||||
$res = $this->_conn->setEx( $key, $ttl, $data );
|
||||
} catch ( \RedisException $ex ) {
|
||||
$res = false;
|
||||
$msg = sprintf( __( 'Redis encountered a fatal error: %1$s (code: %2$d)', 'litespeed-cache' ), $ex->getMessage(), $ex->getCode() );
|
||||
$this->debug_oc( $msg );
|
||||
Admin_Display::error( $msg );
|
||||
}
|
||||
} else {
|
||||
$res = $this->_conn->set( $key, $data, $ttl );
|
||||
}
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if can cache or not.
|
||||
*
|
||||
* @since 1.8
|
||||
* @access private
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function _can_cache() {
|
||||
if ( ! $this->_cfg_admin && defined( 'WP_ADMIN' ) ) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete cache.
|
||||
*
|
||||
* @since 1.8
|
||||
* @access public
|
||||
*
|
||||
* @param string $key Cache key.
|
||||
* @return bool|null
|
||||
*/
|
||||
public function delete( $key ) {
|
||||
if ( ! $this->_cfg_enabled ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ( ! $this->_connect() ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ( 'Redis' === $this->_oc_driver ) {
|
||||
$res = $this->_conn->del( $key );
|
||||
} else {
|
||||
$res = $this->_conn->delete( $key );
|
||||
}
|
||||
|
||||
return (bool) $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all cache.
|
||||
*
|
||||
* @since 1.8
|
||||
* @access public
|
||||
*
|
||||
* @return bool|null
|
||||
*/
|
||||
public function flush() {
|
||||
if ( ! $this->_cfg_enabled ) {
|
||||
$this->debug_oc( 'bypass flushing' );
|
||||
return null;
|
||||
}
|
||||
|
||||
if ( ! $this->_connect() ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$this->debug_oc( 'flush!' );
|
||||
|
||||
if ( 'Redis' === $this->_oc_driver ) {
|
||||
$res = $this->_conn->flushDb();
|
||||
} else {
|
||||
$res = $this->_conn->flush();
|
||||
$this->_conn->resetServerList();
|
||||
}
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add global groups.
|
||||
*
|
||||
* @since 1.8
|
||||
* @access public
|
||||
*
|
||||
* @param string|string[] $groups Group(s) to add.
|
||||
* @return void
|
||||
*/
|
||||
public function add_global_groups( $groups ) {
|
||||
if ( ! is_array( $groups ) ) {
|
||||
$groups = array( $groups );
|
||||
}
|
||||
|
||||
$this->_global_groups = array_merge( $this->_global_groups, $groups );
|
||||
$this->_global_groups = array_unique( $this->_global_groups );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if is in global groups or not.
|
||||
*
|
||||
* @since 1.8
|
||||
* @access public
|
||||
*
|
||||
* @param string $group Group name.
|
||||
* @return bool
|
||||
*/
|
||||
public function is_global( $group ) {
|
||||
return in_array( $group, $this->_global_groups, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add non persistent groups.
|
||||
*
|
||||
* @since 1.8
|
||||
* @access public
|
||||
*
|
||||
* @param string|string[] $groups Group(s) to add.
|
||||
* @return void
|
||||
*/
|
||||
public function add_non_persistent_groups( $groups ) {
|
||||
if ( ! is_array( $groups ) ) {
|
||||
$groups = array( $groups );
|
||||
}
|
||||
|
||||
$this->_non_persistent_groups = array_merge( $this->_non_persistent_groups, $groups );
|
||||
$this->_non_persistent_groups = array_unique( $this->_non_persistent_groups );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if is in non persistent groups or not.
|
||||
*
|
||||
* @since 1.8
|
||||
* @access public
|
||||
*
|
||||
* @param string $group Group name.
|
||||
* @return bool
|
||||
*/
|
||||
public function is_non_persistent( $group ) {
|
||||
return in_array( $group, $this->_non_persistent_groups, true );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,418 @@
|
||||
<?php
|
||||
/**
|
||||
* LiteSpeed Object Cache Library
|
||||
*
|
||||
* @since 1.8
|
||||
* @package LiteSpeed
|
||||
*/
|
||||
|
||||
defined( 'WPINC' ) || exit();
|
||||
|
||||
if (!function_exists('litespeed_exception_handler')) {
|
||||
/**
|
||||
* Handle exception
|
||||
*
|
||||
* Converts PHP errors into exceptions for better error handling.
|
||||
*
|
||||
* @since 1.8
|
||||
* @access public
|
||||
* @param int $errno Error level.
|
||||
* @param string $errstr Error message.
|
||||
* @param string $errfile File where the error occurred.
|
||||
* @param int $errline Line number where the error occurred.
|
||||
* @throws \ErrorException Error msg.
|
||||
*/
|
||||
function litespeed_exception_handler( $errno, $errstr, $errfile, $errline ) {
|
||||
throw new \ErrorException( esc_html( $errstr ), 0, esc_html( $errno ), esc_html( $errfile ), esc_html( $errline ) );
|
||||
}
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/object-cache.cls.php';
|
||||
require_once __DIR__ . '/object-cache-wp.cls.php';
|
||||
|
||||
/**
|
||||
* Sets up Object Cache Global and assigns it.
|
||||
*
|
||||
* Initializes the global object cache instance.
|
||||
*
|
||||
* @since 1.8
|
||||
* @access public
|
||||
* @global WP_Object_Cache $wp_object_cache Object cache global instance.
|
||||
*/
|
||||
function wp_cache_init() {
|
||||
// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
|
||||
$GLOBALS['wp_object_cache'] = WP_Object_Cache::get_instance();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds data to the cache, if the cache key doesn't already exist.
|
||||
*
|
||||
* @since 1.8
|
||||
* @access public
|
||||
* @see WP_Object_Cache::add()
|
||||
* @global WP_Object_Cache $wp_object_cache Object cache global instance.
|
||||
*
|
||||
* @param int|string $key The cache key to use for retrieval later.
|
||||
* @param mixed $data The data to add to the cache.
|
||||
* @param string $group Optional. The group to add the cache to. Enables the same key
|
||||
* to be used across groups. Default empty.
|
||||
* @param int $expire Optional. When the cache data should expire, in seconds.
|
||||
* Default 0 (no expiration).
|
||||
* @return bool True on success, false if cache key and group already exist.
|
||||
*/
|
||||
function wp_cache_add( $key, $data, $group = '', $expire = 0 ) {
|
||||
global $wp_object_cache;
|
||||
|
||||
return $wp_object_cache->add( $key, $data, $group, (int) $expire );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds multiple values to the cache in one call.
|
||||
*
|
||||
* @since 5.4
|
||||
* @access public
|
||||
* @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
|
||||
* @access public
|
||||
* @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
|
||||
* @access public
|
||||
* @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
|
||||
* @access public
|
||||
* @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
|
||||
* @access public
|
||||
* @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
|
||||
* @access public
|
||||
* @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
|
||||
* @access public
|
||||
* @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
|
||||
* @access public
|
||||
* @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
|
||||
* @access public
|
||||
* @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
|
||||
* @access public
|
||||
* @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
|
||||
* @access public
|
||||
* @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
|
||||
* @access public
|
||||
* @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
|
||||
* @access public
|
||||
* @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
|
||||
* @access public
|
||||
*
|
||||
* @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
|
||||
* @access public
|
||||
*
|
||||
* @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
|
||||
* @access public
|
||||
* @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
|
||||
* @access public
|
||||
*
|
||||
* @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
|
||||
* @access public
|
||||
* @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 );
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,352 @@
|
||||
<?php
|
||||
// phpcs:ignoreFile
|
||||
|
||||
/**
|
||||
* The optimize4 class.
|
||||
*
|
||||
* @since 1.9
|
||||
* @package LiteSpeed
|
||||
*/
|
||||
|
||||
namespace LiteSpeed;
|
||||
|
||||
defined('WPINC') || exit();
|
||||
|
||||
class Optimizer extends Root {
|
||||
|
||||
private $_conf_css_font_display;
|
||||
|
||||
/**
|
||||
* Init optimizer
|
||||
*
|
||||
* @since 1.9
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->_conf_css_font_display = $this->conf(Base::O_OPTM_CSS_FONT_DISPLAY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run HTML minify process and return final content
|
||||
*
|
||||
* @since 1.9
|
||||
* @access public
|
||||
*/
|
||||
public function html_min( $content, $force_inline_minify = false ) {
|
||||
if (!apply_filters('litespeed_html_min', true)) {
|
||||
Debug2::debug2('[Optmer] html_min bypassed via litespeed_html_min filter');
|
||||
return $content;
|
||||
}
|
||||
|
||||
$options = array();
|
||||
|
||||
if ($force_inline_minify) {
|
||||
$options['jsMinifier'] = __CLASS__ . '::minify_js';
|
||||
}
|
||||
|
||||
$skip_comments = $this->conf(Base::O_OPTM_HTML_SKIP_COMMENTS);
|
||||
if ($skip_comments) {
|
||||
$options['skipComments'] = $skip_comments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Added exception capture when minify
|
||||
*
|
||||
* @since 2.2.3
|
||||
*/
|
||||
try {
|
||||
$obj = new Lib\HTML_MIN($content, $options);
|
||||
$content_final = $obj->process();
|
||||
// check if content from minification is empty
|
||||
if ($content_final == '') {
|
||||
Debug2::debug('Failed to minify HTML: HTML minification resulted in empty HTML');
|
||||
return $content;
|
||||
}
|
||||
if (!defined('LSCACHE_ESI_SILENCE')) {
|
||||
$content_final .= "\n" . '<!-- Page optimized by LiteSpeed Cache @' . date('Y-m-d H:i:s', time() + LITESPEED_TIME_OFFSET) . ' -->';
|
||||
}
|
||||
return $content_final;
|
||||
} catch (\Exception $e) {
|
||||
Debug2::debug('******[Optmer] html_min failed: ' . $e->getMessage());
|
||||
error_log('****** LiteSpeed Optimizer html_min failed: ' . $e->getMessage());
|
||||
return $content;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run minify process and save content
|
||||
*
|
||||
* @since 1.9
|
||||
* @access public
|
||||
*/
|
||||
public function serve( $request_url, $file_type, $minify, $src_list ) {
|
||||
// Try Unique CSS
|
||||
if ($file_type == 'css') {
|
||||
$content = false;
|
||||
if (defined('LITESPEED_GUEST_OPTM') || $this->conf(Base::O_OPTM_UCSS)) {
|
||||
$filename = $this->cls('UCSS')->load($request_url);
|
||||
|
||||
if ($filename) {
|
||||
return array( $filename, 'ucss' );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Before generated, don't know the contented hash filename yet, so used url hash as tmp filename
|
||||
$file_path_prefix = $this->_build_filepath_prefix($file_type);
|
||||
|
||||
$url_tag = $request_url;
|
||||
$url_tag_for_file = md5($request_url);
|
||||
if (is_404()) {
|
||||
$url_tag_for_file = $url_tag = '404';
|
||||
} elseif ($file_type == 'css' && apply_filters('litespeed_ucss_per_pagetype', false)) {
|
||||
$url_tag_for_file = $url_tag = Utility::page_type();
|
||||
}
|
||||
|
||||
$static_file = LITESPEED_STATIC_DIR . $file_path_prefix . $url_tag_for_file . '.' . $file_type;
|
||||
|
||||
// Create tmp file to avoid conflict
|
||||
$tmp_static_file = $static_file . '.tmp';
|
||||
if (file_exists($tmp_static_file) && time() - filemtime($tmp_static_file) <= 600) {
|
||||
// some other request is generating
|
||||
return false;
|
||||
}
|
||||
// File::save( $tmp_static_file, '/* ' . ( is_404() ? '404' : $request_url ) . ' */', true ); // Can't use this bcos this will get filecon md5 changed
|
||||
File::save($tmp_static_file, '', true);
|
||||
|
||||
// Load content
|
||||
$real_files = array();
|
||||
foreach ($src_list as $src_info) {
|
||||
$is_min = false;
|
||||
if (!empty($src_info['inl'])) {
|
||||
// Load inline
|
||||
$content = $src_info['src'];
|
||||
} else {
|
||||
// Load file
|
||||
$content = $this->load_file($src_info['src'], $file_type);
|
||||
|
||||
if (!$content) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$is_min = $this->is_min($src_info['src']);
|
||||
}
|
||||
$content = $this->optm_snippet($content, $file_type, $minify && !$is_min, $src_info['src'], !empty($src_info['media']) ? $src_info['media'] : false);
|
||||
// Write to file
|
||||
File::save($tmp_static_file, $content, true, true);
|
||||
}
|
||||
|
||||
// if CSS - run the minification on the saved file.
|
||||
// Will move imports to the top of file and remove extra spaces.
|
||||
if ($file_type == 'css') {
|
||||
$obj = new Lib\CSS_JS_MIN\Minify\CSS();
|
||||
$file_content_combined = $obj->moveImportsToTop(File::read($tmp_static_file));
|
||||
|
||||
File::save($tmp_static_file, $file_content_combined);
|
||||
}
|
||||
|
||||
// validate md5
|
||||
$filecon_md5 = md5_file($tmp_static_file);
|
||||
|
||||
$final_file_path = $file_path_prefix . $filecon_md5 . '.' . $file_type;
|
||||
$realfile = LITESPEED_STATIC_DIR . $final_file_path;
|
||||
if (!file_exists($realfile)) {
|
||||
rename($tmp_static_file, $realfile);
|
||||
Debug2::debug2('[Optmer] Saved static file [path] ' . $realfile);
|
||||
} else {
|
||||
unlink($tmp_static_file);
|
||||
}
|
||||
|
||||
$vary = $this->cls('Vary')->finalize_full_varies();
|
||||
Debug2::debug2("[Optmer] Save URL to file for [file_type] $file_type [file] $filecon_md5 [vary] $vary ");
|
||||
$this->cls('Data')->save_url($url_tag, $vary, $file_type, $filecon_md5, dirname($realfile));
|
||||
|
||||
return array( $filecon_md5 . '.' . $file_type, $file_type );
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a single file
|
||||
*
|
||||
* @since 4.0
|
||||
*/
|
||||
public function optm_snippet( $content, $file_type, $minify, $src, $media = false ) {
|
||||
// CSS related features
|
||||
if ($file_type == 'css') {
|
||||
// Font optimize
|
||||
if ($this->_conf_css_font_display) {
|
||||
$content = preg_replace('#(@font\-face\s*\{)#isU', '${1}font-display:swap;', $content);
|
||||
}
|
||||
|
||||
$content = preg_replace('/@charset[^;]+;\\s*/', '', $content);
|
||||
|
||||
if ($media) {
|
||||
$content = '@media ' . $media . '{' . $content . "\n}";
|
||||
}
|
||||
|
||||
if ($minify) {
|
||||
$content = self::minify_css($content);
|
||||
}
|
||||
|
||||
$content = $this->cls('CDN')->finalize($content);
|
||||
|
||||
if ((defined('LITESPEED_GUEST_OPTM') || $this->conf(Base::O_IMG_OPTM_WEBP)) && $this->cls('Media')->webp_support()) {
|
||||
$content = $this->cls('Media')->replace_background_webp($content);
|
||||
}
|
||||
} else {
|
||||
if ($minify) {
|
||||
$content = self::minify_js($content);
|
||||
} else {
|
||||
$content = $this->_null_minifier($content);
|
||||
}
|
||||
|
||||
$content .= "\n;";
|
||||
}
|
||||
|
||||
// Add filter
|
||||
$content = apply_filters('litespeed_optm_cssjs', $content, $file_type, $src);
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load remote resource from cache if existed
|
||||
*
|
||||
* @since 4.7
|
||||
*/
|
||||
private function load_cached_file( $url, $file_type ) {
|
||||
$file_path_prefix = $this->_build_filepath_prefix($file_type);
|
||||
$folder_name = LITESPEED_STATIC_DIR . $file_path_prefix;
|
||||
$to_be_deleted_folder = $folder_name . date('Ymd', strtotime('-2 days'));
|
||||
if (file_exists($to_be_deleted_folder)) {
|
||||
Debug2::debug('[Optimizer] ❌ Clearing folder [name] ' . $to_be_deleted_folder);
|
||||
File::rrmdir($to_be_deleted_folder);
|
||||
}
|
||||
|
||||
$today_file = $folder_name . date('Ymd') . '/' . md5($url);
|
||||
if (file_exists($today_file)) {
|
||||
return File::read($today_file);
|
||||
}
|
||||
|
||||
// Write file
|
||||
$res = wp_safe_remote_get($url);
|
||||
$res_code = wp_remote_retrieve_response_code($res);
|
||||
if (is_wp_error($res) || $res_code != 200) {
|
||||
Debug2::debug2('[Optimizer] ❌ Load Remote error [code] ' . $res_code);
|
||||
return false;
|
||||
}
|
||||
$con = wp_remote_retrieve_body($res);
|
||||
if (!$con) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Debug2::debug('[Optimizer] ✅ Save remote file to cache [name] ' . $today_file);
|
||||
File::save($today_file, $con, true);
|
||||
|
||||
return $con;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load remote/local resource
|
||||
*
|
||||
* @since 3.5
|
||||
*/
|
||||
public function load_file( $src, $file_type = 'css' ) {
|
||||
$real_file = Utility::is_internal_file($src);
|
||||
$postfix = pathinfo(parse_url($src, PHP_URL_PATH), PATHINFO_EXTENSION);
|
||||
if (!$real_file || $postfix != $file_type) {
|
||||
Debug2::debug2('[CSS] Load Remote [' . $file_type . '] ' . $src);
|
||||
$this_url = substr($src, 0, 2) == '//' ? set_url_scheme($src) : $src;
|
||||
$con = $this->load_cached_file($this_url, $file_type);
|
||||
|
||||
if ($file_type == 'css') {
|
||||
$dirname = dirname($this_url) . '/';
|
||||
|
||||
$con = Lib\UriRewriter::prepend($con, $dirname);
|
||||
}
|
||||
} else {
|
||||
Debug2::debug2('[CSS] Load local [' . $file_type . '] ' . $real_file[0]);
|
||||
$con = File::read($real_file[0]);
|
||||
|
||||
if ($file_type == 'css') {
|
||||
$dirname = dirname($real_file[0]);
|
||||
|
||||
$con = Lib\UriRewriter::rewrite($con, $dirname);
|
||||
}
|
||||
}
|
||||
|
||||
return $con;
|
||||
}
|
||||
|
||||
/**
|
||||
* Minify CSS
|
||||
*
|
||||
* @since 2.2.3
|
||||
* @access private
|
||||
*/
|
||||
public static function minify_css( $data ) {
|
||||
try {
|
||||
$obj = new Lib\CSS_JS_MIN\Minify\CSS();
|
||||
$obj->add($data);
|
||||
|
||||
return $obj->minify();
|
||||
} catch (\Exception $e) {
|
||||
Debug2::debug('******[Optmer] minify_css failed: ' . $e->getMessage());
|
||||
error_log('****** LiteSpeed Optimizer minify_css failed: ' . $e->getMessage());
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Minify JS
|
||||
*
|
||||
* Added exception capture when minify
|
||||
*
|
||||
* @since 2.2.3
|
||||
* @access private
|
||||
*/
|
||||
public static function minify_js( $data, $js_type = '' ) {
|
||||
// For inline JS optimize, need to check if it's js type
|
||||
if ($js_type) {
|
||||
preg_match('#type=([\'"])(.+)\g{1}#isU', $js_type, $matches);
|
||||
if ($matches && $matches[2] != 'text/javascript') {
|
||||
Debug2::debug('******[Optmer] minify_js bypass due to type: ' . $matches[2]);
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$obj = new Lib\CSS_JS_MIN\Minify\JS();
|
||||
$obj->add($data);
|
||||
|
||||
return $obj->minify();
|
||||
} catch (\Exception $e) {
|
||||
Debug2::debug('******[Optmer] minify_js failed: ' . $e->getMessage());
|
||||
// error_log( '****** LiteSpeed Optimizer minify_js failed: ' . $e->getMessage() );
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Basic minifier
|
||||
*
|
||||
* @access private
|
||||
*/
|
||||
private function _null_minifier( $content ) {
|
||||
$content = str_replace("\r\n", "\n", $content);
|
||||
|
||||
return trim($content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the file is already min file
|
||||
*
|
||||
* @since 1.9
|
||||
*/
|
||||
public function is_min( $filename ) {
|
||||
$basename = basename($filename);
|
||||
if (preg_match('/[-\.]min\.(?:[a-zA-Z]+)$/i', $basename)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,540 @@
|
||||
<?php
|
||||
// phpcs:ignoreFile
|
||||
|
||||
/**
|
||||
* The PlaceHolder class
|
||||
*
|
||||
* @since 3.0
|
||||
* @package LiteSpeed
|
||||
*/
|
||||
|
||||
namespace LiteSpeed;
|
||||
|
||||
defined('WPINC') || exit();
|
||||
|
||||
class Placeholder extends Base {
|
||||
|
||||
const TYPE_GENERATE = 'generate';
|
||||
const TYPE_CLEAR_Q = 'clear_q';
|
||||
|
||||
private $_conf_placeholder_resp;
|
||||
private $_conf_placeholder_resp_svg;
|
||||
private $_conf_lqip;
|
||||
private $_conf_lqip_qual;
|
||||
private $_conf_lqip_min_w;
|
||||
private $_conf_lqip_min_h;
|
||||
private $_conf_placeholder_resp_color;
|
||||
private $_conf_placeholder_resp_async;
|
||||
private $_conf_ph_default;
|
||||
private $_placeholder_resp_dict = array();
|
||||
private $_ph_queue = array();
|
||||
|
||||
protected $_summary;
|
||||
|
||||
/**
|
||||
* Init
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->_conf_placeholder_resp = defined('LITESPEED_GUEST_OPTM') || $this->conf(self::O_MEDIA_PLACEHOLDER_RESP);
|
||||
$this->_conf_placeholder_resp_svg = $this->conf(self::O_MEDIA_PLACEHOLDER_RESP_SVG);
|
||||
$this->_conf_lqip = !defined('LITESPEED_GUEST_OPTM') && $this->conf(self::O_MEDIA_LQIP);
|
||||
$this->_conf_lqip_qual = $this->conf(self::O_MEDIA_LQIP_QUAL);
|
||||
$this->_conf_lqip_min_w = $this->conf(self::O_MEDIA_LQIP_MIN_W);
|
||||
$this->_conf_lqip_min_h = $this->conf(self::O_MEDIA_LQIP_MIN_H);
|
||||
$this->_conf_placeholder_resp_async = $this->conf(self::O_MEDIA_PLACEHOLDER_RESP_ASYNC);
|
||||
$this->_conf_placeholder_resp_color = $this->conf(self::O_MEDIA_PLACEHOLDER_RESP_COLOR);
|
||||
$this->_conf_ph_default = $this->conf(self::O_MEDIA_LAZY_PLACEHOLDER) ?: LITESPEED_PLACEHOLDER;
|
||||
|
||||
$this->_summary = self::get_summary();
|
||||
}
|
||||
|
||||
/**
|
||||
* Init Placeholder
|
||||
*/
|
||||
public function init() {
|
||||
Debug2::debug2('[LQIP] init');
|
||||
|
||||
add_action('litespeed_after_admin_init', array( $this, 'after_admin_init' ));
|
||||
}
|
||||
|
||||
/**
|
||||
* Display column in Media
|
||||
*
|
||||
* @since 3.0
|
||||
* @access public
|
||||
*/
|
||||
public function after_admin_init() {
|
||||
if ($this->_conf_lqip) {
|
||||
add_filter('manage_media_columns', array( $this, 'media_row_title' ));
|
||||
add_filter('manage_media_custom_column', array( $this, 'media_row_actions' ), 10, 2);
|
||||
add_action('litespeed_media_row_lqip', array( $this, 'media_row_con' ));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Media Admin Menu -> LQIP col
|
||||
*
|
||||
* @since 3.0
|
||||
* @access public
|
||||
*/
|
||||
public function media_row_title( $posts_columns ) {
|
||||
$posts_columns['lqip'] = __('LQIP', 'litespeed-cache');
|
||||
|
||||
return $posts_columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Media Admin Menu -> LQIP Column
|
||||
*
|
||||
* @since 3.0
|
||||
* @access public
|
||||
*/
|
||||
public function media_row_actions( $column_name, $post_id ) {
|
||||
if ($column_name !== 'lqip') {
|
||||
return;
|
||||
}
|
||||
|
||||
do_action('litespeed_media_row_lqip', $post_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display LQIP column
|
||||
*
|
||||
* @since 3.0
|
||||
* @access public
|
||||
*/
|
||||
public function media_row_con( $post_id ) {
|
||||
$meta_value = wp_get_attachment_metadata($post_id);
|
||||
|
||||
if (empty($meta_value['file'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$total_files = 0;
|
||||
|
||||
// List all sizes
|
||||
$all_sizes = array( $meta_value['file'] );
|
||||
$size_path = pathinfo($meta_value['file'], PATHINFO_DIRNAME) . '/';
|
||||
foreach ($meta_value['sizes'] as $v) {
|
||||
$all_sizes[] = $size_path . $v['file'];
|
||||
}
|
||||
|
||||
foreach ($all_sizes as $short_path) {
|
||||
$lqip_folder = LITESPEED_STATIC_DIR . '/lqip/' . $short_path;
|
||||
|
||||
if (is_dir($lqip_folder)) {
|
||||
Debug2::debug('[LQIP] Found folder: ' . $short_path);
|
||||
|
||||
// List all files
|
||||
foreach (scandir($lqip_folder) as $v) {
|
||||
if ($v == '.' || $v == '..') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($total_files == 0) {
|
||||
echo '<div class="litespeed-media-lqip"><img src="' .
|
||||
Str::trim_quotes(File::read($lqip_folder . '/' . $v)) .
|
||||
'" alt="' .
|
||||
sprintf(__('LQIP image preview for size %s', 'litespeed-cache'), $v) .
|
||||
'"></div>';
|
||||
}
|
||||
|
||||
echo '<div class="litespeed-media-size"><a href="' . Str::trim_quotes(File::read($lqip_folder . '/' . $v)) . '" target="_blank">' . $v . '</a></div>';
|
||||
|
||||
++$total_files;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($total_files == 0) {
|
||||
echo '—';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace image with placeholder
|
||||
*
|
||||
* @since 3.0
|
||||
* @access public
|
||||
*/
|
||||
public function replace( $html, $src, $size ) {
|
||||
// Check if need to enable responsive placeholder or not
|
||||
$this_placeholder = $this->_placeholder($src, $size) ?: $this->_conf_ph_default;
|
||||
|
||||
$additional_attr = '';
|
||||
if ($this->_conf_lqip && $this_placeholder != $this->_conf_ph_default) {
|
||||
Debug2::debug2('[LQIP] Use resp LQIP [size] ' . $size);
|
||||
$additional_attr = ' data-placeholder-resp="' . Str::trim_quotes($size) . '"';
|
||||
}
|
||||
|
||||
$snippet = defined('LITESPEED_GUEST_OPTM') || $this->conf(self::O_OPTM_NOSCRIPT_RM) ? '' : '<noscript>' . $html . '</noscript>';
|
||||
$html = str_replace(array( ' src=', ' srcset=', ' sizes=' ), array( ' data-src=', ' data-srcset=', ' data-sizes=' ), $html);
|
||||
$html = str_replace('<img ', '<img data-lazyloaded="1"' . $additional_attr . ' src="' . Str::trim_quotes($this_placeholder) . '" ', $html);
|
||||
$snippet = $html . $snippet;
|
||||
|
||||
return $snippet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate responsive placeholder
|
||||
*
|
||||
* @since 2.5.1
|
||||
* @access private
|
||||
*/
|
||||
private function _placeholder( $src, $size ) {
|
||||
// Low Quality Image Placeholders
|
||||
if (!$size) {
|
||||
Debug2::debug2('[LQIP] no size ' . $src);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$this->_conf_placeholder_resp) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If use local generator
|
||||
if (!$this->_conf_lqip || !$this->_lqip_size_check($size)) {
|
||||
return $this->_generate_placeholder_locally($size);
|
||||
}
|
||||
|
||||
Debug2::debug2('[LQIP] Resp LQIP process [src] ' . $src . ' [size] ' . $size);
|
||||
|
||||
$arr_key = $size . ' ' . $src;
|
||||
|
||||
// Check if its already in dict or not
|
||||
if (!empty($this->_placeholder_resp_dict[$arr_key])) {
|
||||
Debug2::debug2('[LQIP] already in dict');
|
||||
|
||||
return $this->_placeholder_resp_dict[$arr_key];
|
||||
}
|
||||
|
||||
// Need to generate the responsive placeholder
|
||||
$placeholder_realpath = $this->_placeholder_realpath($src, $size); // todo: give offload API
|
||||
if (file_exists($placeholder_realpath)) {
|
||||
Debug2::debug2('[LQIP] file exists');
|
||||
$this->_placeholder_resp_dict[$arr_key] = File::read($placeholder_realpath);
|
||||
|
||||
return $this->_placeholder_resp_dict[$arr_key];
|
||||
}
|
||||
|
||||
// Add to cron queue
|
||||
|
||||
// Prevent repeated requests
|
||||
if (in_array($arr_key, $this->_ph_queue)) {
|
||||
Debug2::debug2('[LQIP] file bypass generating due to in queue');
|
||||
return $this->_generate_placeholder_locally($size);
|
||||
}
|
||||
|
||||
if ($hit = Utility::str_hit_array($src, $this->conf(self::O_MEDIA_LQIP_EXC))) {
|
||||
Debug2::debug2('[LQIP] file bypass generating due to exclude setting [hit] ' . $hit);
|
||||
return $this->_generate_placeholder_locally($size);
|
||||
}
|
||||
|
||||
$this->_ph_queue[] = $arr_key;
|
||||
|
||||
// Send request to generate placeholder
|
||||
if (!$this->_conf_placeholder_resp_async) {
|
||||
// If requested recently, bypass
|
||||
if ($this->_summary && !empty($this->_summary['curr_request']) && time() - $this->_summary['curr_request'] < 300) {
|
||||
Debug2::debug2('[LQIP] file bypass generating due to interval limit');
|
||||
return false;
|
||||
}
|
||||
// Generate immediately
|
||||
$this->_placeholder_resp_dict[$arr_key] = $this->_generate_placeholder($arr_key);
|
||||
|
||||
return $this->_placeholder_resp_dict[$arr_key];
|
||||
}
|
||||
|
||||
// Prepare default svg placeholder as tmp placeholder
|
||||
$tmp_placeholder = $this->_generate_placeholder_locally($size);
|
||||
|
||||
// Store it to prepare for cron
|
||||
$queue = $this->load_queue('lqip');
|
||||
if (in_array($arr_key, $queue)) {
|
||||
Debug2::debug2('[LQIP] already in queue');
|
||||
|
||||
return $tmp_placeholder;
|
||||
}
|
||||
|
||||
if (count($queue) > 500) {
|
||||
Debug2::debug2('[LQIP] queue is full');
|
||||
|
||||
return $tmp_placeholder;
|
||||
}
|
||||
|
||||
$queue[] = $arr_key;
|
||||
$this->save_queue('lqip', $queue);
|
||||
Debug2::debug('[LQIP] Added placeholder queue');
|
||||
|
||||
return $tmp_placeholder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate realpath of placeholder file
|
||||
*
|
||||
* @since 2.5.1
|
||||
* @access private
|
||||
*/
|
||||
private function _placeholder_realpath( $src, $size ) {
|
||||
// Use LQIP Cloud generator, each image placeholder will be separately stored
|
||||
|
||||
// Compatibility with WebP and AVIF
|
||||
$src = Utility::drop_webp($src);
|
||||
|
||||
$filepath_prefix = $this->_build_filepath_prefix('lqip');
|
||||
|
||||
// External images will use cache folder directly
|
||||
$domain = parse_url($src, PHP_URL_HOST);
|
||||
if ($domain && !Utility::internal($domain)) {
|
||||
// todo: need to improve `util:internal()` to include `CDN::internal()`
|
||||
$md5 = md5($src);
|
||||
|
||||
return LITESPEED_STATIC_DIR . $filepath_prefix . 'remote/' . substr($md5, 0, 1) . '/' . substr($md5, 1, 1) . '/' . $md5 . '.' . $size;
|
||||
}
|
||||
|
||||
// Drop domain
|
||||
$short_path = Utility::att_short_path($src);
|
||||
|
||||
return LITESPEED_STATIC_DIR . $filepath_prefix . $short_path . '/' . $size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cron placeholder generation
|
||||
*
|
||||
* @since 2.5.1
|
||||
* @access public
|
||||
*/
|
||||
public static function cron( $continue = false ) {
|
||||
$_instance = self::cls();
|
||||
|
||||
$queue = $_instance->load_queue('lqip');
|
||||
|
||||
if (empty($queue)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// For cron, need to check request interval too
|
||||
if (!$continue) {
|
||||
if (!empty($_instance->_summary['curr_request']) && time() - $_instance->_summary['curr_request'] < 300) {
|
||||
Debug2::debug('[LQIP] Last request not done');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($queue as $v) {
|
||||
Debug2::debug('[LQIP] cron job [size] ' . $v);
|
||||
|
||||
$res = $_instance->_generate_placeholder($v, true);
|
||||
|
||||
// Exit queue if out of quota
|
||||
if ($res === 'out_of_quota') {
|
||||
return;
|
||||
}
|
||||
|
||||
// only request first one
|
||||
if (!$continue) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate placeholder locally
|
||||
*
|
||||
* @since 3.0
|
||||
* @access private
|
||||
*/
|
||||
private function _generate_placeholder_locally( $size ) {
|
||||
Debug2::debug2('[LQIP] _generate_placeholder local [size] ' . $size);
|
||||
|
||||
$size = explode('x', $size);
|
||||
|
||||
$svg = str_replace(array( '{width}', '{height}', '{color}' ), array( $size[0], $size[1], $this->_conf_placeholder_resp_color ), $this->_conf_placeholder_resp_svg);
|
||||
|
||||
return 'data:image/svg+xml;base64,' . base64_encode($svg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send to LiteSpeed API to generate placeholder
|
||||
*
|
||||
* @since 2.5.1
|
||||
* @access private
|
||||
*/
|
||||
private function _generate_placeholder( $raw_size_and_src, $from_cron = false ) {
|
||||
// Parse containing size and src info
|
||||
$size_and_src = explode(' ', $raw_size_and_src, 2);
|
||||
$size = $size_and_src[0];
|
||||
|
||||
if (empty($size_and_src[1])) {
|
||||
$this->_popup_and_save($raw_size_and_src);
|
||||
Debug2::debug('[LQIP] ❌ No src [raw] ' . $raw_size_and_src);
|
||||
return $this->_generate_placeholder_locally($size);
|
||||
}
|
||||
|
||||
$src = $size_and_src[1];
|
||||
|
||||
$file = $this->_placeholder_realpath($src, $size);
|
||||
|
||||
// Local generate SVG to serve ( Repeatedly doing this here to remove stored cron queue in case the setting _conf_lqip is changed )
|
||||
if (!$this->_conf_lqip || !$this->_lqip_size_check($size)) {
|
||||
$data = $this->_generate_placeholder_locally($size);
|
||||
} else {
|
||||
$err = false;
|
||||
$allowance = Cloud::cls()->allowance(Cloud::SVC_LQIP, $err);
|
||||
if (!$allowance) {
|
||||
Debug2::debug('[LQIP] ❌ No credit: ' . $err);
|
||||
$err && Admin_Display::error(Error::msg($err));
|
||||
|
||||
if ($from_cron) {
|
||||
return 'out_of_quota';
|
||||
}
|
||||
|
||||
return $this->_generate_placeholder_locally($size);
|
||||
}
|
||||
|
||||
// Generate LQIP
|
||||
list($width, $height) = explode('x', $size);
|
||||
$req_data = array(
|
||||
'width' => $width,
|
||||
'height' => $height,
|
||||
'url' => Utility::drop_webp($src),
|
||||
'quality' => $this->_conf_lqip_qual,
|
||||
);
|
||||
|
||||
// CHeck if the image is 404 first
|
||||
if (File::is_404($req_data['url'])) {
|
||||
$this->_popup_and_save($raw_size_and_src, true);
|
||||
$this->_append_exc($src);
|
||||
Debug2::debug('[LQIP] 404 before request [src] ' . $req_data['url']);
|
||||
return $this->_generate_placeholder_locally($size);
|
||||
}
|
||||
|
||||
// Update request status
|
||||
$this->_summary['curr_request'] = time();
|
||||
self::save_summary();
|
||||
|
||||
$json = Cloud::post(Cloud::SVC_LQIP, $req_data, 120);
|
||||
if (!is_array($json)) {
|
||||
return $this->_generate_placeholder_locally($size);
|
||||
}
|
||||
|
||||
if (empty($json['lqip']) || strpos($json['lqip'], 'data:image/svg+xml') !== 0) {
|
||||
// image error, pop up the current queue
|
||||
$this->_popup_and_save($raw_size_and_src, true);
|
||||
$this->_append_exc($src);
|
||||
Debug2::debug('[LQIP] wrong response format', $json);
|
||||
|
||||
return $this->_generate_placeholder_locally($size);
|
||||
}
|
||||
|
||||
$data = $json['lqip'];
|
||||
|
||||
Debug2::debug('[LQIP] _generate_placeholder LQIP');
|
||||
}
|
||||
|
||||
// Write to file
|
||||
File::save($file, $data, true);
|
||||
|
||||
// Save summary data
|
||||
$this->_summary['last_spent'] = time() - $this->_summary['curr_request'];
|
||||
$this->_summary['last_request'] = $this->_summary['curr_request'];
|
||||
$this->_summary['curr_request'] = 0;
|
||||
self::save_summary();
|
||||
$this->_popup_and_save($raw_size_and_src);
|
||||
|
||||
Debug2::debug('[LQIP] saved LQIP ' . $file);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the size is valid to send LQIP request or not
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
private function _lqip_size_check( $size ) {
|
||||
$size = explode('x', $size);
|
||||
if ($size[0] >= $this->_conf_lqip_min_w || $size[1] >= $this->_conf_lqip_min_h) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Debug2::debug2('[LQIP] Size too small');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add to LQIP exclude list
|
||||
*
|
||||
* @since 3.4
|
||||
*/
|
||||
private function _append_exc( $src ) {
|
||||
$val = $this->conf(self::O_MEDIA_LQIP_EXC);
|
||||
$val[] = $src;
|
||||
$this->cls('Conf')->update(self::O_MEDIA_LQIP_EXC, $val);
|
||||
Debug2::debug('[LQIP] Appended to LQIP Excludes [URL] ' . $src);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pop up the current request and save
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
private function _popup_and_save( $raw_size_and_src, $append_to_exc = false ) {
|
||||
$queue = $this->load_queue('lqip');
|
||||
if (!empty($queue) && in_array($raw_size_and_src, $queue)) {
|
||||
unset($queue[array_search($raw_size_and_src, $queue)]);
|
||||
}
|
||||
|
||||
if ($append_to_exc) {
|
||||
$size_and_src = explode(' ', $raw_size_and_src, 2);
|
||||
$this_src = $size_and_src[1];
|
||||
|
||||
// Append to lqip exc setting first
|
||||
$this->_append_exc($this_src);
|
||||
|
||||
// Check if other queues contain this src or not
|
||||
if ($queue) {
|
||||
foreach ($queue as $k => $raw_size_and_src) {
|
||||
$size_and_src = explode(' ', $raw_size_and_src, 2);
|
||||
if (empty($size_and_src[1])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($size_and_src[1] == $this_src) {
|
||||
unset($queue[$k]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->save_queue('lqip', $queue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle all request actions from main cls
|
||||
*
|
||||
* @since 2.5.1
|
||||
* @access public
|
||||
*/
|
||||
public function handler() {
|
||||
$type = Router::verify_type();
|
||||
|
||||
switch ($type) {
|
||||
case self::TYPE_GENERATE:
|
||||
self::cron(true);
|
||||
break;
|
||||
|
||||
case self::TYPE_CLEAR_Q:
|
||||
$this->clear_q('lqip');
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
Admin::redirect();
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,268 @@
|
||||
<?php
|
||||
// phpcs:ignoreFile
|
||||
|
||||
/**
|
||||
* The report class
|
||||
*
|
||||
* @since 1.1.0
|
||||
* @package LiteSpeed
|
||||
*/
|
||||
|
||||
namespace LiteSpeed;
|
||||
|
||||
defined('WPINC') || exit();
|
||||
|
||||
class Report extends Base {
|
||||
|
||||
const TYPE_SEND_REPORT = 'send_report';
|
||||
|
||||
/**
|
||||
* Handle all request actions from main cls
|
||||
*
|
||||
* @since 1.6.5
|
||||
* @access public
|
||||
*/
|
||||
public function handler() {
|
||||
$type = Router::verify_type();
|
||||
|
||||
switch ($type) {
|
||||
case self::TYPE_SEND_REPORT:
|
||||
$this->post_env();
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
Admin::redirect();
|
||||
}
|
||||
|
||||
/**
|
||||
* post env report number to ls center server
|
||||
*
|
||||
* @since 1.6.5
|
||||
* @access public
|
||||
*/
|
||||
public function post_env() {
|
||||
$report_con = $this->generate_environment_report();
|
||||
|
||||
// Generate link
|
||||
$link = !empty($_POST['link']) ? esc_url($_POST['link']) : '';
|
||||
|
||||
$notes = !empty($_POST['notes']) ? esc_html($_POST['notes']) : '';
|
||||
|
||||
$php_info = !empty($_POST['attach_php']) ? esc_html($_POST['attach_php']) : '';
|
||||
$report_php = $php_info === '1' ? $this->generate_php_report() : '';
|
||||
|
||||
if ($report_php) {
|
||||
$report_con .= "\nPHPINFO\n" . $report_php;
|
||||
}
|
||||
|
||||
$data = array(
|
||||
'env' => $report_con,
|
||||
'link' => $link,
|
||||
'notes' => $notes,
|
||||
);
|
||||
|
||||
$json = Cloud::post(Cloud::API_REPORT, $data);
|
||||
if (!is_array($json)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$num = !empty($json['num']) ? $json['num'] : '--';
|
||||
$summary = array(
|
||||
'num' => $num,
|
||||
'dateline' => time(),
|
||||
);
|
||||
|
||||
self::save_summary($summary);
|
||||
|
||||
return $num;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gathers the PHP information.
|
||||
*
|
||||
* @since 7.0
|
||||
* @access public
|
||||
*/
|
||||
public function generate_php_report( $flags = INFO_GENERAL | INFO_CONFIGURATION | INFO_MODULES ) {
|
||||
// INFO_ENVIRONMENT
|
||||
$report = '';
|
||||
|
||||
ob_start();
|
||||
phpinfo($flags);
|
||||
$report = ob_get_contents();
|
||||
ob_end_clean();
|
||||
|
||||
preg_match('%<style type="text/css">(.*?)</style>.*?<body>(.*?)</body>%s', $report, $report);
|
||||
|
||||
return $report[2];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gathers the environment details and creates the report.
|
||||
* Will write to the environment report file.
|
||||
*
|
||||
* @since 1.0.12
|
||||
* @access public
|
||||
*/
|
||||
public function generate_environment_report( $options = null ) {
|
||||
global $wp_version, $_SERVER;
|
||||
$frontend_htaccess = Htaccess::get_frontend_htaccess();
|
||||
$backend_htaccess = Htaccess::get_backend_htaccess();
|
||||
$paths = array( $frontend_htaccess );
|
||||
if ($frontend_htaccess != $backend_htaccess) {
|
||||
$paths[] = $backend_htaccess;
|
||||
}
|
||||
|
||||
if (is_multisite()) {
|
||||
$active_plugins = get_site_option('active_sitewide_plugins');
|
||||
if (!empty($active_plugins)) {
|
||||
$active_plugins = array_keys($active_plugins);
|
||||
}
|
||||
} else {
|
||||
$active_plugins = get_option('active_plugins');
|
||||
}
|
||||
|
||||
if (function_exists('wp_get_theme')) {
|
||||
$theme_obj = wp_get_theme();
|
||||
$active_theme = $theme_obj->get('Name');
|
||||
} else {
|
||||
$active_theme = get_current_theme();
|
||||
}
|
||||
|
||||
$extras = array(
|
||||
'wordpress version' => $wp_version,
|
||||
'siteurl' => get_option('siteurl'),
|
||||
'home' => get_option('home'),
|
||||
'home_url' => home_url(),
|
||||
'locale' => get_locale(),
|
||||
'active theme' => $active_theme,
|
||||
);
|
||||
|
||||
$extras['active plugins'] = $active_plugins;
|
||||
$extras['cloud'] = Cloud::get_summary();
|
||||
foreach (array( 'mini_html', 'pk_b64', 'sk_b64', 'cdn_dash', 'ips' ) as $v) {
|
||||
if (!empty($extras['cloud'][$v])) {
|
||||
unset($extras['cloud'][$v]);
|
||||
}
|
||||
}
|
||||
|
||||
if (is_null($options)) {
|
||||
$options = $this->get_options(true);
|
||||
|
||||
if (is_multisite()) {
|
||||
$options2 = $this->get_options();
|
||||
foreach ($options2 as $k => $v) {
|
||||
if (isset($options[$k]) && $options[$k] !== $v) {
|
||||
$options['[Overwritten] ' . $k] = $v;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_null($options) && is_multisite()) {
|
||||
$blogs = Activation::get_network_ids();
|
||||
if (!empty($blogs)) {
|
||||
$i = 0;
|
||||
foreach ($blogs as $blog_id) {
|
||||
if (++$i > 3) {
|
||||
// Only log 3 subsites
|
||||
break;
|
||||
}
|
||||
$opts = $this->cls('Conf')->load_options($blog_id, true);
|
||||
if (isset($opts[self::O_CACHE])) {
|
||||
$options['blog ' . $blog_id . ' radio select'] = $opts[self::O_CACHE];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Security: Remove cf key in report
|
||||
$secure_fields = array( self::O_CDN_CLOUDFLARE_KEY, self::O_OBJECT_PSWD );
|
||||
foreach ($secure_fields as $v) {
|
||||
if (!empty($options[$v])) {
|
||||
$options[$v] = str_repeat('*', strlen($options[$v]));
|
||||
}
|
||||
}
|
||||
|
||||
$report = $this->build_environment_report($_SERVER, $options, $extras, $paths);
|
||||
return $report;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the environment report buffer with the given parameters
|
||||
*
|
||||
* @access private
|
||||
*/
|
||||
private function build_environment_report( $server, $options, $extras = array(), $htaccess_paths = array() ) {
|
||||
$server_keys = array(
|
||||
'DOCUMENT_ROOT' => '',
|
||||
'SERVER_SOFTWARE' => '',
|
||||
'X-LSCACHE' => '',
|
||||
'HTTP_X_LSCACHE' => '',
|
||||
);
|
||||
$server_vars = array_intersect_key($server, $server_keys);
|
||||
$server_vars[] = 'LSWCP_TAG_PREFIX = ' . LSWCP_TAG_PREFIX;
|
||||
|
||||
$server_vars = array_merge($server_vars, $this->cls('Base')->server_vars());
|
||||
|
||||
$buf = $this->_format_report_section('Server Variables', $server_vars);
|
||||
|
||||
$buf .= $this->_format_report_section('WordPress Specific Extras', $extras);
|
||||
|
||||
$buf .= $this->_format_report_section('LSCache Plugin Options', $options);
|
||||
|
||||
if (empty($htaccess_paths)) {
|
||||
return $buf;
|
||||
}
|
||||
|
||||
foreach ($htaccess_paths as $path) {
|
||||
if (!file_exists($path) || !is_readable($path)) {
|
||||
$buf .= $path . " does not exist or is not readable.\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
$content = file_get_contents($path);
|
||||
if ($content === false) {
|
||||
$buf .= $path . " returned false for file_get_contents.\n";
|
||||
continue;
|
||||
}
|
||||
$buf .= $path . " contents:\n" . $content . "\n\n";
|
||||
}
|
||||
return $buf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a part of the environment report based on a section header and an array for the section parameters.
|
||||
*
|
||||
* @since 1.0.12
|
||||
* @access private
|
||||
*/
|
||||
private function _format_report_section( $section_header, $section ) {
|
||||
$tab = ' '; // four spaces
|
||||
|
||||
if (empty($section)) {
|
||||
return 'No matching ' . $section_header . "\n\n";
|
||||
}
|
||||
$buf = $section_header;
|
||||
|
||||
foreach ($section as $k => $v) {
|
||||
$buf .= "\n" . $tab;
|
||||
|
||||
if (!is_numeric($k)) {
|
||||
$buf .= $k . ' = ';
|
||||
}
|
||||
|
||||
if (!is_string($v)) {
|
||||
$v = var_export($v, true);
|
||||
} else {
|
||||
$v = esc_html($v);
|
||||
}
|
||||
|
||||
$buf .= $v;
|
||||
}
|
||||
return $buf . "\n\n";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,367 @@
|
||||
<?php
|
||||
/**
|
||||
* REST endpoints and helpers for LiteSpeed.
|
||||
*
|
||||
* @since 2.9.4
|
||||
* @package LiteSpeed
|
||||
*/
|
||||
|
||||
namespace LiteSpeed;
|
||||
|
||||
defined( 'WPINC' ) || exit();
|
||||
|
||||
/**
|
||||
* Class REST
|
||||
*
|
||||
* Registers plugin REST endpoints and exposes helpers for REST detection.
|
||||
*/
|
||||
class REST extends Root {
|
||||
|
||||
const LOG_TAG = '☎️';
|
||||
|
||||
/**
|
||||
* Whether current request is an internal REST call.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $_internal_rest_status = false;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @since 2.9.4
|
||||
*/
|
||||
public function __construct() {
|
||||
// Hook to internal REST call.
|
||||
add_filter( 'rest_request_before_callbacks', [ $this, 'set_internal_rest_on' ] );
|
||||
add_filter( 'rest_request_after_callbacks', [ $this, 'set_internal_rest_off' ] );
|
||||
|
||||
add_action( 'rest_api_init', [ $this, 'rest_api_init' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register REST routes.
|
||||
*
|
||||
* @since 3.0
|
||||
* @return void
|
||||
*/
|
||||
public function rest_api_init() {
|
||||
// Activate or deactivate a specific crawler callback
|
||||
register_rest_route( 'litespeed/v1', '/toggle_crawler_state', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [ $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', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [ $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', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [ $this, 'ip_validate' ],
|
||||
'permission_callback' => [ $this, 'is_from_cloud' ],
|
||||
] );
|
||||
|
||||
// 1.2. WP REST Dryrun Callback
|
||||
register_rest_route( 'litespeed/v3', '/wp_rest_echo', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [ $this, 'wp_rest_echo' ],
|
||||
'permission_callback' => [ $this, 'is_from_cloud' ],
|
||||
] );
|
||||
register_rest_route( 'litespeed/v3', '/ping', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [ $this, 'ping' ],
|
||||
'permission_callback' => [ $this, 'is_from_cloud' ],
|
||||
] );
|
||||
|
||||
// CDN setup callback notification
|
||||
register_rest_route( 'litespeed/v3', '/cdn_status', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [ $this, 'cdn_status' ],
|
||||
'permission_callback' => [ $this, 'is_from_cloud' ],
|
||||
] );
|
||||
|
||||
// Image optm notify_img
|
||||
// Need validation
|
||||
register_rest_route( 'litespeed/v1', '/notify_img', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [ $this, 'notify_img' ],
|
||||
'permission_callback' => [ $this, 'is_from_cloud' ],
|
||||
] );
|
||||
|
||||
register_rest_route( 'litespeed/v1', '/notify_ccss', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [ $this, 'notify_ccss' ],
|
||||
'permission_callback' => [ $this, 'is_from_cloud' ],
|
||||
] );
|
||||
|
||||
register_rest_route( 'litespeed/v1', '/notify_ucss', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [ $this, 'notify_ucss' ],
|
||||
'permission_callback' => [ $this, 'is_from_cloud' ],
|
||||
] );
|
||||
|
||||
register_rest_route( 'litespeed/v1', '/notify_vpi', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [ $this, 'notify_vpi' ],
|
||||
'permission_callback' => [ $this, 'is_from_cloud' ],
|
||||
] );
|
||||
|
||||
register_rest_route( 'litespeed/v3', '/err_domains', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [ $this, 'err_domains' ],
|
||||
'permission_callback' => [ $this, 'is_from_cloud' ],
|
||||
] );
|
||||
|
||||
// Image optm check_img
|
||||
// Need validation
|
||||
register_rest_route( 'litespeed/v1', '/check_img', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [ $this, 'check_img' ],
|
||||
'permission_callback' => [ $this, 'is_from_cloud' ],
|
||||
] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Call to freeze or melt the crawler clicked
|
||||
*
|
||||
* @since 4.3
|
||||
*/
|
||||
public function toggle_crawler_state() {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Missing -- REST API nonce verified by WordPress
|
||||
$crawler_id = isset( $_POST['crawler_id'] ) ? sanitize_text_field( wp_unslash( $_POST['crawler_id'] ) ) : '';
|
||||
|
||||
if ( $crawler_id ) {
|
||||
return $this->cls( 'Crawler' )->toggle_activeness( $crawler_id ) ? 1 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the request is from cloud nodes.
|
||||
*
|
||||
* @since 4.2
|
||||
* @since 4.4.7 Token/API key validation makes IP validation redundant.
|
||||
* @return bool
|
||||
*/
|
||||
public function is_from_cloud() {
|
||||
return $this->cls( 'Cloud' )->is_from_cloud();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ping pong.
|
||||
*
|
||||
* @since 3.0.4
|
||||
* @return mixed
|
||||
*/
|
||||
public function ping() {
|
||||
return $this->cls( 'Cloud' )->ping();
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch IP check.
|
||||
*
|
||||
* @since 3.0
|
||||
* @return mixed
|
||||
*/
|
||||
public function check_ip() {
|
||||
return Tool::cls()->check_ip();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate IPs from cloud.
|
||||
*
|
||||
* @since 3.0
|
||||
* @return mixed
|
||||
*/
|
||||
public function ip_validate() {
|
||||
return $this->cls( 'Cloud' )->ip_validate();
|
||||
}
|
||||
|
||||
/**
|
||||
* REST echo helper.
|
||||
*
|
||||
* @since 3.0
|
||||
* @return mixed
|
||||
*/
|
||||
public function wp_rest_echo() {
|
||||
return $this->cls( 'Cloud' )->wp_rest_echo();
|
||||
}
|
||||
|
||||
/**
|
||||
* Endpoint to notify plugin of CDN status updates.
|
||||
*
|
||||
* @since 7.0
|
||||
* @return mixed
|
||||
*/
|
||||
public function cdn_status() {
|
||||
return $this->cls( 'Cloud' )->update_cdn_status();
|
||||
}
|
||||
|
||||
/**
|
||||
* Image optimization notification.
|
||||
*
|
||||
* @since 3.0
|
||||
* @return mixed
|
||||
*/
|
||||
public function notify_img() {
|
||||
return Img_Optm::cls()->notify_img();
|
||||
}
|
||||
|
||||
/**
|
||||
* Critical CSS notification.
|
||||
*
|
||||
* @since 7.1
|
||||
* @return mixed
|
||||
*/
|
||||
public function notify_ccss() {
|
||||
self::debug( 'notify_ccss' );
|
||||
return CSS::cls()->notify();
|
||||
}
|
||||
|
||||
/**
|
||||
* Unique CSS notification.
|
||||
*
|
||||
* @since 5.2
|
||||
* @return mixed
|
||||
*/
|
||||
public function notify_ucss() {
|
||||
self::debug( 'notify_ucss' );
|
||||
return UCSS::cls()->notify();
|
||||
}
|
||||
|
||||
/**
|
||||
* Viewport Images notification.
|
||||
*
|
||||
* @since 4.7
|
||||
* @return mixed
|
||||
*/
|
||||
public function notify_vpi() {
|
||||
self::debug( 'notify_vpi' );
|
||||
return VPI::cls()->notify();
|
||||
}
|
||||
|
||||
/**
|
||||
* Error domain report from cloud.
|
||||
*
|
||||
* @since 4.7
|
||||
* @return mixed
|
||||
*/
|
||||
public function err_domains() {
|
||||
self::debug( 'err_domains' );
|
||||
return $this->cls( 'Cloud' )->rest_err_domains();
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch image check.
|
||||
*
|
||||
* @since 3.0
|
||||
* @return mixed
|
||||
*/
|
||||
public function check_img() {
|
||||
return Img_Optm::cls()->check_img();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a standardized error payload.
|
||||
*
|
||||
* @since 5.7.0.1
|
||||
* @param string|int $code Error code.
|
||||
* @return array
|
||||
*/
|
||||
public static function err( $code ) {
|
||||
return [
|
||||
'_res' => 'err',
|
||||
'_msg' => $code,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set internal REST tag to ON.
|
||||
*
|
||||
* @since 2.9.4
|
||||
* @param mixed $not_used Passthrough value from the filter.
|
||||
* @return mixed
|
||||
*/
|
||||
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
|
||||
* @param mixed $not_used Passthrough value from the filter.
|
||||
* @return mixed
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether current request is an internal REST call.
|
||||
*
|
||||
* @since 2.9.4
|
||||
* @return bool
|
||||
*/
|
||||
public function is_internal_rest() {
|
||||
return $this->_internal_rest_status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a URL or current page is a REST request.
|
||||
*
|
||||
* @since 2.9.3
|
||||
* @since 2.9.4 Moved here from Utility, dropped static.
|
||||
* @param string|false $url URL to check; when false checks current request.
|
||||
* @return bool
|
||||
*/
|
||||
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 initialization.
|
||||
if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Case #2: Support "plain" permalink settings.
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$route = isset( $_GET['rest_route'] ) ? sanitize_text_field( wp_unslash( $_GET['rest_route'] ) ) : '';
|
||||
|
||||
if ( $route && 0 === strpos( trim( $route, '\\/' ), $prefix, 0 ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( !$url ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Case #3: URL path begins with wp-json/ (REST prefix) – safe for subfolder installs.
|
||||
$rest_url = wp_parse_url( site_url( $prefix ) );
|
||||
$current_url = wp_parse_url( $url );
|
||||
|
||||
if ( false !== $current_url && ! empty( $current_url['path'] ) && false !== $rest_url && ! empty( $rest_url['path'] ) ) {
|
||||
return 0 === strpos( $current_url['path'], $rest_url['path'] );
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,669 @@
|
||||
<?php
|
||||
// phpcs:ignoreFile
|
||||
|
||||
/**
|
||||
* The abstract instance
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
|
||||
namespace LiteSpeed;
|
||||
|
||||
defined('WPINC') || exit();
|
||||
|
||||
abstract class Root {
|
||||
|
||||
const CONF_FILE = '.litespeed_conf.dat';
|
||||
// Instance set
|
||||
private static $_instances;
|
||||
|
||||
private static $_options = array();
|
||||
private static $_const_options = array();
|
||||
private static $_primary_options = array();
|
||||
private static $_network_options = array();
|
||||
|
||||
/**
|
||||
* Check if need to separate ccss for mobile
|
||||
*
|
||||
* @since 4.7
|
||||
* @access protected
|
||||
*/
|
||||
protected function _separate_mobile() {
|
||||
return (wp_is_mobile() || apply_filters('litespeed_is_mobile', false)) && $this->conf(Base::O_CACHE_MOBILE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log an error message
|
||||
*
|
||||
* @since 7.0
|
||||
*/
|
||||
public static function debugErr( $msg, $backtrace_limit = false ) {
|
||||
$msg = '❌ ' . $msg;
|
||||
self::debug($msg, $backtrace_limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log a debug message.
|
||||
*
|
||||
* @since 4.4
|
||||
* @access public
|
||||
*/
|
||||
public static function debug( $msg, $backtrace_limit = false ) {
|
||||
if (!defined('LSCWP_LOG')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (defined('static::LOG_TAG')) {
|
||||
$msg = static::LOG_TAG . ' ' . $msg;
|
||||
}
|
||||
|
||||
Debug2::debug($msg, $backtrace_limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log an advanced debug message.
|
||||
*
|
||||
* @since 4.4
|
||||
* @access public
|
||||
*/
|
||||
public static function debug2( $msg, $backtrace_limit = false ) {
|
||||
if (!defined('LSCWP_LOG_MORE')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (defined('static::LOG_TAG')) {
|
||||
$msg = static::LOG_TAG . ' ' . $msg;
|
||||
}
|
||||
Debug2::debug2($msg, $backtrace_limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if there is cache folder for that type
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
public function has_cache_folder( $type ) {
|
||||
$subsite_id = is_multisite() && !is_network_admin() ? get_current_blog_id() : '';
|
||||
|
||||
if (file_exists(LITESPEED_STATIC_DIR . '/' . $type . '/' . $subsite_id)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maybe make the cache folder if not existed
|
||||
*
|
||||
* @since 4.4.2
|
||||
*/
|
||||
protected function _maybe_mk_cache_folder( $type ) {
|
||||
if (!$this->has_cache_folder($type)) {
|
||||
$subsite_id = is_multisite() && !is_network_admin() ? get_current_blog_id() : '';
|
||||
$path = LITESPEED_STATIC_DIR . '/' . $type . '/' . $subsite_id;
|
||||
mkdir($path, 0755, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete file-based cache folder for that type
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
public function rm_cache_folder( $type ) {
|
||||
if (!$this->has_cache_folder($type)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$subsite_id = is_multisite() && !is_network_admin() ? get_current_blog_id() : '';
|
||||
|
||||
File::rrmdir(LITESPEED_STATIC_DIR . '/' . $type . '/' . $subsite_id);
|
||||
|
||||
// Clear All summary data
|
||||
self::save_summary(false, false, true);
|
||||
|
||||
if ($type == 'ccss' || $type == 'ucss') {
|
||||
Debug2::debug('[CSS] Cleared ' . $type . ' queue');
|
||||
} elseif ($type == 'avatar') {
|
||||
Debug2::debug('[Avatar] Cleared ' . $type . ' queue');
|
||||
} elseif ($type == 'css' || $type == 'js') {
|
||||
return;
|
||||
} else {
|
||||
Debug2::debug('[' . strtoupper($type) . '] Cleared ' . $type . ' queue');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the static filepath
|
||||
*
|
||||
* @since 4.0
|
||||
*/
|
||||
protected function _build_filepath_prefix( $type ) {
|
||||
$filepath_prefix = '/' . $type . '/';
|
||||
if (is_multisite()) {
|
||||
$filepath_prefix .= get_current_blog_id() . '/';
|
||||
}
|
||||
|
||||
return $filepath_prefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load current queues from data file
|
||||
*
|
||||
* @since 4.1
|
||||
* @since 4.3 Elevated to root.cls
|
||||
*/
|
||||
public function load_queue( $type ) {
|
||||
$filepath_prefix = $this->_build_filepath_prefix($type);
|
||||
$static_path = LITESPEED_STATIC_DIR . $filepath_prefix . '.litespeed_conf.dat';
|
||||
|
||||
$queue = array();
|
||||
if (file_exists($static_path)) {
|
||||
$queue = \json_decode(file_get_contents($static_path), true) ?: array();
|
||||
}
|
||||
|
||||
return $queue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save current queues to data file
|
||||
*
|
||||
* @since 4.1
|
||||
* @since 4.3 Elevated to root.cls
|
||||
*/
|
||||
public function save_queue( $type, $list ) {
|
||||
$filepath_prefix = $this->_build_filepath_prefix($type);
|
||||
$static_path = LITESPEED_STATIC_DIR . $filepath_prefix . '.litespeed_conf.dat';
|
||||
|
||||
$data = \json_encode($list);
|
||||
|
||||
File::save($static_path, $data, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all waiting queues
|
||||
*
|
||||
* @since 3.4
|
||||
* @since 4.3 Elevated to root.cls
|
||||
*/
|
||||
public function clear_q( $type, $silent = false ) {
|
||||
$filepath_prefix = $this->_build_filepath_prefix($type);
|
||||
$static_path = LITESPEED_STATIC_DIR . $filepath_prefix . '.litespeed_conf.dat';
|
||||
|
||||
if (file_exists($static_path)) {
|
||||
$silent = false;
|
||||
unlink($static_path);
|
||||
}
|
||||
|
||||
if (!$silent) {
|
||||
$msg = __('All QUIC.cloud service queues have been cleared.', 'litespeed-cache');
|
||||
Admin_Display::success($msg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load an instance or create it if not existed
|
||||
*
|
||||
* @since 4.0
|
||||
*/
|
||||
public static function cls( $cls = false, $unset = false, $data = false ) {
|
||||
if (!$cls) {
|
||||
$cls = self::ori_cls();
|
||||
}
|
||||
$cls = __NAMESPACE__ . '\\' . $cls;
|
||||
|
||||
$cls_tag = strtolower($cls);
|
||||
|
||||
if (!isset(self::$_instances[$cls_tag])) {
|
||||
if ($unset) {
|
||||
return;
|
||||
}
|
||||
|
||||
self::$_instances[$cls_tag] = new $cls($data);
|
||||
} elseif ($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];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if is overwritten by code filter
|
||||
*
|
||||
* @since 7.4
|
||||
*/
|
||||
public function filter_overwritten( $id ) {
|
||||
$cls_admin_display = Admin_Display::$settings_filters;
|
||||
// Check if filter name is set.
|
||||
if(!isset($cls_admin_display[$id]) || !isset($cls_admin_display[$id]['filter']) || is_array($cls_admin_display[$id]['filter']) ){
|
||||
return null;
|
||||
}
|
||||
|
||||
$val_setting = $this->conf($id, true);
|
||||
// if setting not found
|
||||
if( null === $val_setting ){
|
||||
$val_setting = '';
|
||||
}
|
||||
|
||||
$val_filter = apply_filters($cls_admin_display[$id]['filter'], $val_setting );
|
||||
|
||||
if ($val_setting === $val_filter) {
|
||||
// If the value is the same, return null.
|
||||
return null;
|
||||
}
|
||||
|
||||
return $val_filter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if is overwritten by $SERVER variable
|
||||
*
|
||||
* @since 7.4
|
||||
*/
|
||||
public function server_overwritten( $id ) {
|
||||
$cls_admin_display = Admin_Display::$settings_filters;
|
||||
if(!isset($cls_admin_display[$id]['filter'])){
|
||||
return null;
|
||||
}
|
||||
|
||||
if(!is_array($cls_admin_display[$id]['filter'])) {
|
||||
$cls_admin_display[$id]['filter'] = array( $cls_admin_display[$id]['filter'] );
|
||||
}
|
||||
|
||||
foreach( $cls_admin_display[$id]['filter'] as $variable ){
|
||||
if(isset($_SERVER[$variable])) {
|
||||
return [ $variable , $_SERVER[$variable] ] ;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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());
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,833 @@
|
||||
<?php
|
||||
// phpcs:ignoreFile
|
||||
|
||||
/**
|
||||
* The core plugin router class.
|
||||
*
|
||||
* This generate the valid action.
|
||||
*
|
||||
* @since 1.1.0
|
||||
*/
|
||||
|
||||
namespace LiteSpeed;
|
||||
|
||||
defined('WPINC') || exit();
|
||||
|
||||
class Router extends Base {
|
||||
|
||||
const LOG_TAG = '[Router]';
|
||||
|
||||
const NONCE = 'LSCWP_NONCE';
|
||||
const ACTION = 'LSCWP_CTRL';
|
||||
|
||||
const ACTION_SAVE_SETTINGS_NETWORK = 'save-settings-network';
|
||||
const ACTION_DB_OPTM = 'db_optm';
|
||||
const ACTION_PLACEHOLDER = 'placeholder';
|
||||
const ACTION_AVATAR = 'avatar';
|
||||
const ACTION_SAVE_SETTINGS = 'save-settings';
|
||||
const ACTION_CLOUD = 'cloud';
|
||||
const ACTION_IMG_OPTM = 'img_optm';
|
||||
const ACTION_HEALTH = 'health';
|
||||
const ACTION_CRAWLER = 'crawler';
|
||||
const ACTION_PURGE = 'purge';
|
||||
const ACTION_CONF = 'conf';
|
||||
const ACTION_ACTIVATION = 'activation';
|
||||
const ACTION_CSS = 'css';
|
||||
const ACTION_UCSS = 'ucss';
|
||||
const ACTION_VPI = 'vpi';
|
||||
const ACTION_PRESET = 'preset';
|
||||
const ACTION_IMPORT = 'import';
|
||||
const ACTION_REPORT = 'report';
|
||||
const ACTION_DEBUG2 = 'debug2';
|
||||
const ACTION_CDN_CLOUDFLARE = 'CDN\Cloudflare';
|
||||
const ACTION_ADMIN_DISPLAY = 'admin_display';
|
||||
const ACTION_TMP_DISABLE = 'tmp_disable';
|
||||
|
||||
// List all handlers here
|
||||
private static $_HANDLERS = array(
|
||||
self::ACTION_ADMIN_DISPLAY,
|
||||
self::ACTION_ACTIVATION,
|
||||
self::ACTION_AVATAR,
|
||||
self::ACTION_CDN_CLOUDFLARE,
|
||||
self::ACTION_CLOUD,
|
||||
self::ACTION_CONF,
|
||||
self::ACTION_CRAWLER,
|
||||
self::ACTION_CSS,
|
||||
self::ACTION_UCSS,
|
||||
self::ACTION_VPI,
|
||||
self::ACTION_DB_OPTM,
|
||||
self::ACTION_DEBUG2,
|
||||
self::ACTION_HEALTH,
|
||||
self::ACTION_IMG_OPTM,
|
||||
self::ACTION_PRESET,
|
||||
self::ACTION_IMPORT,
|
||||
self::ACTION_PLACEHOLDER,
|
||||
self::ACTION_PURGE,
|
||||
self::ACTION_REPORT,
|
||||
);
|
||||
|
||||
const TYPE = 'litespeed_type';
|
||||
|
||||
const ITEM_HASH = 'hash';
|
||||
const ITEM_FLASH_HASH = 'flash_hash';
|
||||
|
||||
private static $_esi_enabled;
|
||||
private static $_is_ajax;
|
||||
private static $_is_logged_in;
|
||||
private static $_ip;
|
||||
private static $_action;
|
||||
private static $_is_admin_ip;
|
||||
private static $_frontend_path;
|
||||
|
||||
/**
|
||||
* Redirect to self to continue operation
|
||||
*
|
||||
* Note: must return when use this func. CLI/Cron call won't die in this func.
|
||||
*
|
||||
* @since 3.0
|
||||
* @access public
|
||||
*/
|
||||
public static function self_redirect( $action, $type ) {
|
||||
if (defined('LITESPEED_CLI') || wp_doing_cron()) {
|
||||
Admin_Display::success('To be continued'); // Show for CLI
|
||||
return;
|
||||
}
|
||||
|
||||
// Add i to avoid browser too many redirected warning
|
||||
$i = !empty($_GET['litespeed_i']) ? $_GET['litespeed_i'] : 0;
|
||||
++$i;
|
||||
|
||||
$link = Utility::build_url($action, $type, false, null, array( 'litespeed_i' => $i ));
|
||||
|
||||
$url = html_entity_decode($link);
|
||||
exit("<meta http-equiv='refresh' content='0;url=$url'>");
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if can run optimize
|
||||
*
|
||||
* @since 1.3
|
||||
* @since 2.3.1 Relocated from cdn.cls
|
||||
* @access public
|
||||
*/
|
||||
public function can_optm() {
|
||||
$can = true;
|
||||
|
||||
if (is_user_logged_in() && $this->conf(self::O_OPTM_GUEST_ONLY)) {
|
||||
$can = false;
|
||||
} elseif (is_admin()) {
|
||||
$can = false;
|
||||
} elseif (is_feed()) {
|
||||
$can = false;
|
||||
} elseif (is_preview()) {
|
||||
$can = false;
|
||||
} elseif (self::is_ajax()) {
|
||||
$can = false;
|
||||
}
|
||||
|
||||
if (self::_is_login_page()) {
|
||||
Debug2::debug('[Router] Optm bypassed: login/reg page');
|
||||
$can = false;
|
||||
}
|
||||
|
||||
$can_final = apply_filters('litespeed_can_optm', $can);
|
||||
|
||||
if ($can_final != $can) {
|
||||
Debug2::debug('[Router] Optm bypassed: filter');
|
||||
}
|
||||
|
||||
return $can_final;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check referer page to see if its from admin
|
||||
*
|
||||
* @since 2.4.2.1
|
||||
* @access public
|
||||
*/
|
||||
public static function from_admin() {
|
||||
return !empty($_SERVER['HTTP_REFERER']) && strpos($_SERVER['HTTP_REFERER'], get_admin_url()) === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if it can use CDN replacement
|
||||
*
|
||||
* @since 1.2.3
|
||||
* @since 2.3.1 Relocated from cdn.cls
|
||||
* @access public
|
||||
*/
|
||||
public static function can_cdn() {
|
||||
$can = true;
|
||||
|
||||
if (is_admin()) {
|
||||
if (!self::is_ajax()) {
|
||||
Debug2::debug2('[Router] CDN bypassed: is not ajax call');
|
||||
$can = false;
|
||||
}
|
||||
|
||||
if (self::from_admin()) {
|
||||
Debug2::debug2('[Router] CDN bypassed: ajax call from admin');
|
||||
$can = false;
|
||||
}
|
||||
} elseif (is_feed()) {
|
||||
$can = false;
|
||||
} elseif (is_preview()) {
|
||||
$can = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bypass cron to avoid deregister jq notice `Do not deregister the <code>jquery-core</code> script in the administration area.`
|
||||
*
|
||||
* @since 2.7.2
|
||||
*/
|
||||
if (wp_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 compatibility
|
||||
}
|
||||
}
|
||||
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 = wp_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[self::ACTION])) {
|
||||
Debug2::debug2('[Router] LSCWP_CTRL bypassed empty');
|
||||
return;
|
||||
}
|
||||
|
||||
$action = stripslashes($_REQUEST[self::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_TMP_DISABLE: // Disable LSC for 24H
|
||||
Debug2::tmp_disable();
|
||||
Admin::redirect("?page=litespeed-toolbox#settings-debug");
|
||||
return;
|
||||
|
||||
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[self::NONCE]) || !wp_verify_nonce($_REQUEST[self::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');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if opcode cache is restricted and file that is requesting.
|
||||
* https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.restrict-api
|
||||
*
|
||||
* @since 7.3
|
||||
* @access public
|
||||
*/
|
||||
public static function opcache_restricted($file)
|
||||
{
|
||||
$restrict_value = ini_get('opcache.restrict_api');
|
||||
if ($restrict_value) {
|
||||
if ( !$file || false === strpos($restrict_value, $file) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
<?php
|
||||
// phpcs:ignoreFile
|
||||
/**
|
||||
* LiteSpeed String Operator Library Class
|
||||
*
|
||||
* @since 1.3
|
||||
* @package LiteSpeed
|
||||
*/
|
||||
|
||||
namespace LiteSpeed;
|
||||
|
||||
defined( 'WPINC' ) || exit();
|
||||
|
||||
/**
|
||||
* Class Str
|
||||
*
|
||||
* Provides string manipulation utilities for LiteSpeed Cache.
|
||||
*
|
||||
* @since 1.3
|
||||
*/
|
||||
class Str {
|
||||
|
||||
/**
|
||||
* Translate QC HTML links from html.
|
||||
*
|
||||
* Converts `<a href="{#xxx#}">xxxx</a>` to `<a href="xxx">xxxx</a>`.
|
||||
*
|
||||
* @since 7.0
|
||||
* @access public
|
||||
* @param string $html The HTML string to process.
|
||||
* @return string The processed HTML string.
|
||||
*/
|
||||
public static function translate_qc_apis( $html ) {
|
||||
preg_match_all( '/<a href="{#(\w+)#}"/U', $html, $matches );
|
||||
if ( ! $matches ) {
|
||||
return $html;
|
||||
}
|
||||
|
||||
foreach ( $matches[0] as $k => $html_to_be_replaced ) {
|
||||
$link = '<a href="' . Utility::build_url( Router::ACTION_CLOUD, Cloud::TYPE_API, false, null, array( 'action2' => $matches[1][ $k ] ) ) . '"';
|
||||
$html = str_replace( $html_to_be_replaced, $link, $html );
|
||||
}
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return safe HTML
|
||||
*
|
||||
* Sanitizes HTML to allow only specific tags and attributes.
|
||||
*
|
||||
* @since 7.0
|
||||
* @access public
|
||||
* @param string $html The HTML string to sanitize.
|
||||
* @return string The sanitized HTML string.
|
||||
*/
|
||||
public static function safe_html( $html ) {
|
||||
$common_attrs = array(
|
||||
'style' => array(),
|
||||
'class' => array(),
|
||||
'target' => array(),
|
||||
'src' => array(),
|
||||
'color' => array(),
|
||||
'href' => array(),
|
||||
);
|
||||
$tags = array( 'hr', 'h3', 'h4', 'h5', 'ul', 'li', 'br', 'strong', 'p', 'span', 'img', 'a', 'div', 'font' );
|
||||
$allowed_tags = array();
|
||||
foreach ( $tags as $tag ) {
|
||||
$allowed_tags[ $tag ] = $common_attrs;
|
||||
}
|
||||
|
||||
return wp_kses( $html, $allowed_tags );
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate random string
|
||||
*
|
||||
* Creates a random string of specified length and character type.
|
||||
*
|
||||
* @since 1.3
|
||||
* @access public
|
||||
* @param int $len Length of string.
|
||||
* @param int $type Character type: 1-Number, 2-LowerChar, 4-UpperChar, 7-All.
|
||||
* @return string Randomly generated 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
|
||||
*
|
||||
* Removes double quotes from a string for use as a preformatted src in HTML.
|
||||
*
|
||||
* @since 6.5.3
|
||||
* @access public
|
||||
* @param string $text The string to process.
|
||||
* @return string The string with double quotes removed.
|
||||
*/
|
||||
public static function trim_quotes( $text ) {
|
||||
return str_replace( '"', '', $text );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,379 @@
|
||||
<?php
|
||||
// phpcs:ignoreFile
|
||||
|
||||
/**
|
||||
* The plugin cache-tag class for X-LiteSpeed-Tag
|
||||
*
|
||||
* @since 1.1.3
|
||||
*/
|
||||
|
||||
namespace LiteSpeed;
|
||||
|
||||
defined('WPINC') || exit();
|
||||
|
||||
class Tag extends Root {
|
||||
|
||||
const TYPE_FEED = 'FD';
|
||||
const TYPE_FRONTPAGE = 'F';
|
||||
const TYPE_HOME = 'H';
|
||||
const TYPE_PAGES = 'PGS';
|
||||
const TYPE_PAGES_WITH_RECENT_POSTS = 'PGSRP';
|
||||
const TYPE_HTTP = 'HTTP.';
|
||||
const TYPE_POST = 'Po.'; // Post. Cannot use P, reserved for litemage.
|
||||
const TYPE_ARCHIVE_POSTTYPE = 'PT.';
|
||||
const TYPE_ARCHIVE_TERM = 'T.'; // for is_category|is_tag|is_tax
|
||||
const TYPE_AUTHOR = 'A.';
|
||||
const TYPE_ARCHIVE_DATE = 'D.';
|
||||
const TYPE_BLOG = 'B.';
|
||||
const TYPE_LOGIN = 'L';
|
||||
const TYPE_URL = 'URL.';
|
||||
const TYPE_WIDGET = 'W.';
|
||||
const TYPE_ESI = 'ESI.';
|
||||
const TYPE_REST = 'REST';
|
||||
const TYPE_AJAX = 'AJAX.';
|
||||
const TYPE_LIST = 'LIST';
|
||||
const TYPE_MIN = 'MIN';
|
||||
const TYPE_LOCALRES = 'LOCALRES';
|
||||
|
||||
const X_HEADER = 'X-LiteSpeed-Tag';
|
||||
|
||||
private static $_tags = array();
|
||||
private static $_tags_priv = array( 'tag_priv' );
|
||||
public static $error_code_tags = array( 403, 404, 500 );
|
||||
|
||||
/**
|
||||
* Initialize
|
||||
*
|
||||
* @since 4.0
|
||||
*/
|
||||
public function init() {
|
||||
// register recent posts widget tag before theme renders it to make it work
|
||||
add_filter('widget_posts_args', array( $this, 'add_widget_recent_posts' ));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the login page is cacheable.
|
||||
* If not, unset the cacheable member variable.
|
||||
*
|
||||
* NOTE: This is checked separately because login page doesn't go through WP logic.
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @access public
|
||||
*/
|
||||
public function check_login_cacheable() {
|
||||
if (!$this->conf(Base::O_CACHE_PAGE_LOGIN)) {
|
||||
return;
|
||||
}
|
||||
if (Control::isset_notcacheable()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!empty($_GET)) {
|
||||
Control::set_nocache('has GET request');
|
||||
return;
|
||||
}
|
||||
|
||||
$this->cls('Control')->set_cacheable();
|
||||
|
||||
self::add(self::TYPE_LOGIN);
|
||||
|
||||
// we need to send lsc-cookie manually to make it be sent to all other users when is cacheable
|
||||
$list = headers_list();
|
||||
if (empty($list)) {
|
||||
return;
|
||||
}
|
||||
foreach ($list as $hdr) {
|
||||
if (strncasecmp($hdr, 'set-cookie:', 11) == 0) {
|
||||
$cookie = substr($hdr, 12);
|
||||
@header('lsc-cookie: ' . $cookie, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register purge tag for pages with recent posts widget
|
||||
* of the plugin.
|
||||
*
|
||||
* @since 1.0.15
|
||||
* @access public
|
||||
* @param array $params [WordPress params for widget_posts_args]
|
||||
*/
|
||||
public function add_widget_recent_posts( $params ) {
|
||||
self::add(self::TYPE_PAGES_WITH_RECENT_POSTS);
|
||||
return $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds cache tags to the list of cache tags for the current page.
|
||||
*
|
||||
* @since 1.0.5
|
||||
* @access public
|
||||
* @param mixed $tags A string or array of cache tags to add to the current list.
|
||||
*/
|
||||
public static function add( $tags ) {
|
||||
if (!is_array($tags)) {
|
||||
$tags = array( $tags );
|
||||
}
|
||||
|
||||
Debug2::debug('💰 [Tag] Add ', $tags);
|
||||
|
||||
self::$_tags = array_merge(self::$_tags, $tags);
|
||||
|
||||
// Send purge header immediately
|
||||
$tag_header = self::cls()->output(true);
|
||||
@header($tag_header);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a post id to cache tag
|
||||
*
|
||||
* @since 3.0
|
||||
* @access public
|
||||
*/
|
||||
public static function add_post( $pid ) {
|
||||
self::add(self::TYPE_POST . $pid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a widget id to cache tag
|
||||
*
|
||||
* @since 3.0
|
||||
* @access public
|
||||
*/
|
||||
public static function add_widget( $id ) {
|
||||
self::add(self::TYPE_WIDGET . $id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a private ESI to cache tag
|
||||
*
|
||||
* @since 3.0
|
||||
* @access public
|
||||
*/
|
||||
public static function add_private_esi( $tag ) {
|
||||
self::add_private(self::TYPE_ESI . $tag);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds private cache tags to the list of cache tags for the current page.
|
||||
*
|
||||
* @since 1.6.3
|
||||
* @access public
|
||||
* @param mixed $tags A string or array of cache tags to add to the current list.
|
||||
*/
|
||||
public static function add_private( $tags ) {
|
||||
if (!is_array($tags)) {
|
||||
$tags = array( $tags );
|
||||
}
|
||||
|
||||
self::$_tags_priv = array_merge(self::$_tags_priv, $tags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return tags for Admin QS
|
||||
*
|
||||
* @since 1.1.3
|
||||
* @access public
|
||||
*/
|
||||
public static function output_tags() {
|
||||
return self::$_tags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Will get a hash of the URI. Removes query string and appends a '/' if it is missing.
|
||||
*
|
||||
* @since 1.0.12
|
||||
* @access public
|
||||
* @param string $uri The uri to get the hash of.
|
||||
* @param boolean $ori Return the original url or not
|
||||
* @return bool|string False on input error, hash otherwise.
|
||||
*/
|
||||
public static function get_uri_tag( $uri, $ori = false ) {
|
||||
$no_qs = strtok($uri, '?');
|
||||
if (empty($no_qs)) {
|
||||
return false;
|
||||
}
|
||||
$slashed = trailingslashit($no_qs);
|
||||
|
||||
// If only needs uri tag
|
||||
if ($ori) {
|
||||
return $slashed;
|
||||
}
|
||||
|
||||
if (defined('LSCWP_LOG')) {
|
||||
return self::TYPE_URL . $slashed;
|
||||
}
|
||||
return self::TYPE_URL . md5($slashed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the unique tag based on self url.
|
||||
*
|
||||
* @since 1.1.3
|
||||
* @access public
|
||||
* @param boolean $ori Return the original url or not
|
||||
*/
|
||||
public static function build_uri_tag( $ori = false ) {
|
||||
return self::get_uri_tag(urldecode($_SERVER['REQUEST_URI']), $ori);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the cache tags to set for the page.
|
||||
*
|
||||
* This includes site wide post types (e.g. front page) as well as
|
||||
* any third party plugin specific cache tags.
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @access private
|
||||
* @return array The list of cache tags to set.
|
||||
*/
|
||||
private static function _build_type_tags() {
|
||||
$tags = array();
|
||||
|
||||
$tags[] = Utility::page_type();
|
||||
|
||||
$tags[] = self::build_uri_tag();
|
||||
|
||||
if (is_front_page()) {
|
||||
$tags[] = self::TYPE_FRONTPAGE;
|
||||
} elseif (is_home()) {
|
||||
$tags[] = self::TYPE_HOME;
|
||||
}
|
||||
|
||||
global $wp_query;
|
||||
if (isset($wp_query)) {
|
||||
$queried_obj_id = get_queried_object_id();
|
||||
if (is_archive()) {
|
||||
// An Archive is a Category, Tag, Author, Date, Custom Post Type or Custom Taxonomy based pages.
|
||||
if (is_category() || is_tag() || is_tax()) {
|
||||
$tags[] = self::TYPE_ARCHIVE_TERM . $queried_obj_id;
|
||||
} elseif (is_post_type_archive() && ($post_type = get_post_type())) {
|
||||
$tags[] = self::TYPE_ARCHIVE_POSTTYPE . $post_type;
|
||||
} elseif (is_author()) {
|
||||
$tags[] = self::TYPE_AUTHOR . $queried_obj_id;
|
||||
} elseif (is_date()) {
|
||||
global $post;
|
||||
|
||||
if ($post && isset($post->post_date)) {
|
||||
$date = $post->post_date;
|
||||
$date = strtotime($date);
|
||||
if (is_day()) {
|
||||
$tags[] = self::TYPE_ARCHIVE_DATE . date('Ymd', $date);
|
||||
} elseif (is_month()) {
|
||||
$tags[] = self::TYPE_ARCHIVE_DATE . date('Ym', $date);
|
||||
} elseif (is_year()) {
|
||||
$tags[] = self::TYPE_ARCHIVE_DATE . date('Y', $date);
|
||||
}
|
||||
}
|
||||
}
|
||||
} elseif (is_singular()) {
|
||||
// $this->is_singular = $this->is_single || $this->is_page || $this->is_attachment;
|
||||
$tags[] = self::TYPE_POST . $queried_obj_id;
|
||||
|
||||
if (is_page()) {
|
||||
$tags[] = self::TYPE_PAGES;
|
||||
}
|
||||
} elseif (is_feed()) {
|
||||
$tags[] = self::TYPE_FEED;
|
||||
}
|
||||
}
|
||||
|
||||
// Check REST API
|
||||
if (REST::cls()->is_rest()) {
|
||||
$tags[] = self::TYPE_REST;
|
||||
|
||||
$path = !empty($_SERVER['SCRIPT_URL']) ? $_SERVER['SCRIPT_URL'] : false;
|
||||
if ($path) {
|
||||
// posts collections tag
|
||||
if (substr($path, -6) == '/posts') {
|
||||
$tags[] = self::TYPE_LIST; // Not used for purge yet
|
||||
}
|
||||
|
||||
// single post tag
|
||||
global $post;
|
||||
if (!empty($post->ID) && substr($path, -strlen($post->ID) - 1) === '/' . $post->ID) {
|
||||
$tags[] = self::TYPE_POST . $post->ID;
|
||||
}
|
||||
|
||||
// pages collections & single page tag
|
||||
if (stripos($path, '/pages') !== false) {
|
||||
$tags[] = self::TYPE_PAGES;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Append AJAX action tag
|
||||
if (Router::is_ajax() && !empty($_REQUEST['action'])) {
|
||||
$tags[] = self::TYPE_AJAX . $_REQUEST['action'];
|
||||
}
|
||||
|
||||
return $tags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate all cache tags before output
|
||||
*
|
||||
* @access private
|
||||
* @since 1.1.3
|
||||
*/
|
||||
private static function _finalize() {
|
||||
// run 3rdparty hooks to tag
|
||||
do_action('litespeed_tag_finalize');
|
||||
// generate wp tags
|
||||
if (!defined('LSCACHE_IS_ESI')) {
|
||||
$type_tags = self::_build_type_tags();
|
||||
self::$_tags = array_merge(self::$_tags, $type_tags);
|
||||
}
|
||||
|
||||
if (defined('LITESPEED_GUEST') && LITESPEED_GUEST) {
|
||||
self::$_tags[] = 'guest';
|
||||
}
|
||||
|
||||
// append blog main tag
|
||||
self::$_tags[] = '';
|
||||
// removed duplicates
|
||||
self::$_tags = array_unique(self::$_tags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the Cache Tags header.
|
||||
* ONLY need to run this if is cacheable
|
||||
*
|
||||
* @since 1.1.3
|
||||
* @access public
|
||||
* @return string empty string if empty, otherwise the cache tags header.
|
||||
*/
|
||||
public function output( $no_finalize = false ) {
|
||||
if (defined('LSCACHE_NO_CACHE') && LSCACHE_NO_CACHE) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$no_finalize) {
|
||||
self::_finalize();
|
||||
}
|
||||
|
||||
$prefix_tags = array();
|
||||
/**
|
||||
* Only append blog_id when is multisite
|
||||
*
|
||||
* @since 2.9.3
|
||||
*/
|
||||
$prefix = LSWCP_TAG_PREFIX . (is_multisite() ? get_current_blog_id() : '') . '_';
|
||||
|
||||
// If is_private and has private tags, append them first, then specify prefix to `public` for public tags
|
||||
if (Control::is_private()) {
|
||||
foreach (self::$_tags_priv as $priv_tag) {
|
||||
$prefix_tags[] = $prefix . $priv_tag;
|
||||
}
|
||||
$prefix = 'public:' . $prefix;
|
||||
}
|
||||
|
||||
foreach (self::$_tags as $tag) {
|
||||
$prefix_tags[] = $prefix . $tag;
|
||||
}
|
||||
|
||||
$hdr = self::X_HEADER . ': ' . implode(',', $prefix_tags);
|
||||
|
||||
return $hdr;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,238 @@
|
||||
<?php
|
||||
// phpcs:ignoreFile
|
||||
|
||||
/**
|
||||
* The cron task class.
|
||||
*
|
||||
* @since 1.1.3
|
||||
*/
|
||||
|
||||
namespace LiteSpeed;
|
||||
|
||||
defined('WPINC') || exit();
|
||||
|
||||
class Task extends Root {
|
||||
|
||||
const LOG_TAG = '⏰';
|
||||
private static $_triggers = array(
|
||||
Base::O_IMG_OPTM_CRON => array(
|
||||
'name' => 'litespeed_task_imgoptm_pull',
|
||||
'hook' => 'LiteSpeed\Img_Optm::start_async_cron',
|
||||
), // always fetch immediately
|
||||
Base::O_OPTM_CSS_ASYNC => array(
|
||||
'name' => 'litespeed_task_ccss',
|
||||
'hook' => 'LiteSpeed\CSS::cron_ccss',
|
||||
),
|
||||
Base::O_OPTM_UCSS => array(
|
||||
'name' => 'litespeed_task_ucss',
|
||||
'hook' => 'LiteSpeed\UCSS::cron',
|
||||
),
|
||||
Base::O_MEDIA_VPI_CRON => array(
|
||||
'name' => 'litespeed_task_vpi',
|
||||
'hook' => 'LiteSpeed\VPI::cron',
|
||||
),
|
||||
Base::O_MEDIA_PLACEHOLDER_RESP_ASYNC => array(
|
||||
'name' => 'litespeed_task_lqip',
|
||||
'hook' => 'LiteSpeed\Placeholder::cron',
|
||||
),
|
||||
Base::O_DISCUSS_AVATAR_CRON => array(
|
||||
'name' => 'litespeed_task_avatar',
|
||||
'hook' => 'LiteSpeed\Avatar::cron',
|
||||
),
|
||||
Base::O_IMG_OPTM_AUTO => array(
|
||||
'name' => 'litespeed_task_imgoptm_req',
|
||||
'hook' => 'LiteSpeed\Img_Optm::cron_auto_request',
|
||||
),
|
||||
Base::O_CRAWLER => array(
|
||||
'name' => 'litespeed_task_crawler',
|
||||
'hook' => 'LiteSpeed\Crawler::start_async_cron',
|
||||
), // Set crawler to last one to use above results
|
||||
);
|
||||
|
||||
private static $_guest_options = array( Base::O_OPTM_CSS_ASYNC, Base::O_OPTM_UCSS, Base::O_MEDIA_VPI );
|
||||
|
||||
const FILTER_CRAWLER = 'litespeed_crawl_filter';
|
||||
const FILTER = 'litespeed_filter';
|
||||
|
||||
/**
|
||||
* Keep all tasks in cron
|
||||
*
|
||||
* @since 3.0
|
||||
* @access public
|
||||
*/
|
||||
public function init() {
|
||||
self::debug2('Init');
|
||||
add_filter('cron_schedules', array( $this, 'lscache_cron_filter' ));
|
||||
|
||||
$guest_optm = $this->conf(Base::O_GUEST) && $this->conf(Base::O_GUEST_OPTM);
|
||||
|
||||
foreach (self::$_triggers as $id => $trigger) {
|
||||
if ($id == Base::O_IMG_OPTM_CRON) {
|
||||
if (!Img_Optm::need_pull()) {
|
||||
continue;
|
||||
}
|
||||
} elseif (!$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') ? constant('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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
<?php
|
||||
// phpcs:ignoreFile
|
||||
/**
|
||||
* The tools
|
||||
*
|
||||
* @since 3.0
|
||||
* @package LiteSpeed
|
||||
*/
|
||||
|
||||
namespace LiteSpeed;
|
||||
|
||||
defined( 'WPINC' ) || exit();
|
||||
|
||||
/**
|
||||
* Class Tool
|
||||
*
|
||||
* Provides utility functions for LiteSpeed Cache, including IP detection and heartbeat control.
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
class Tool extends Root {
|
||||
|
||||
const LOG_TAG = '[Tool]';
|
||||
|
||||
/**
|
||||
* Get public IP
|
||||
*
|
||||
* Retrieves the public IP address of the server.
|
||||
*
|
||||
* @since 3.0
|
||||
* @access public
|
||||
* @return string The public IP address or an error message.
|
||||
*/
|
||||
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 esc_html__( 'Failed to detect IP', 'litespeed-cache' );
|
||||
}
|
||||
|
||||
$ip = trim( $response['body'] );
|
||||
|
||||
self::debug( 'result [ip] ' . $ip );
|
||||
|
||||
if ( Utility::valid_ipv4( $ip ) ) {
|
||||
return $ip;
|
||||
}
|
||||
|
||||
return esc_html__( 'Failed to detect IP', 'litespeed-cache' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Heartbeat Control
|
||||
*
|
||||
* Configures WordPress heartbeat settings for frontend, backend, and editor.
|
||||
*
|
||||
* @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
|
||||
*
|
||||
* Manages heartbeat settings for the frontend.
|
||||
*
|
||||
* @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
|
||||
*
|
||||
* Manages heartbeat settings for the backend and editor.
|
||||
*
|
||||
* @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
|
||||
*
|
||||
* Adjusts heartbeat interval settings based on configuration.
|
||||
*
|
||||
* @since 3.0
|
||||
* @access public
|
||||
* @param array $settings Existing heartbeat settings.
|
||||
* @return array Modified heartbeat settings.
|
||||
*/
|
||||
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 ) );
|
||||
}
|
||||
} elseif ( $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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if in editor
|
||||
*
|
||||
* Determines if the current request is within the WordPress editor.
|
||||
*
|
||||
* @since 3.0
|
||||
* @access public
|
||||
* @return bool True if in editor, false otherwise.
|
||||
*/
|
||||
public function is_editor() {
|
||||
$request_uri = isset( $_SERVER['REQUEST_URI'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : '';
|
||||
$res = is_admin() && Utility::str_hit_array( $request_uri, array( 'post.php', 'post-new.php' ) );
|
||||
|
||||
return apply_filters( 'litespeed_is_editor', $res );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,571 @@
|
||||
<?php
|
||||
// phpcs:ignoreFile
|
||||
|
||||
/**
|
||||
* The ucss class.
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
|
||||
namespace LiteSpeed;
|
||||
|
||||
defined('WPINC') || exit();
|
||||
|
||||
class UCSS extends Base {
|
||||
|
||||
const LOG_TAG = '[UCSS]';
|
||||
|
||||
const TYPE_GEN = 'gen';
|
||||
const TYPE_CLEAR_Q = 'clear_q';
|
||||
|
||||
protected $_summary;
|
||||
private $_ucss_whitelist;
|
||||
private $_queue;
|
||||
|
||||
/**
|
||||
* Init
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->_summary = self::get_summary();
|
||||
|
||||
add_filter('litespeed_ucss_whitelist', array( $this->cls('Data'), 'load_ucss_whitelist' ));
|
||||
}
|
||||
|
||||
/**
|
||||
* Uniform url tag for ucss usage
|
||||
*
|
||||
* @since 4.7
|
||||
*/
|
||||
public static function get_url_tag( $request_url = false ) {
|
||||
$url_tag = $request_url;
|
||||
if (is_404()) {
|
||||
$url_tag = '404';
|
||||
} elseif (apply_filters('litespeed_ucss_per_pagetype', false)) {
|
||||
$url_tag = Utility::page_type();
|
||||
self::debug('litespeed_ucss_per_pagetype filter altered url to ' . $url_tag);
|
||||
}
|
||||
|
||||
return $url_tag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get UCSS path
|
||||
*
|
||||
* @since 4.0
|
||||
*/
|
||||
public function load( $request_url, $dry_run = false ) {
|
||||
// Check UCSS URI excludes
|
||||
$ucss_exc = apply_filters('litespeed_ucss_exc', $this->conf(self::O_OPTM_UCSS_EXC));
|
||||
if ($ucss_exc && ($hit = Utility::str_hit_array($request_url, $ucss_exc))) {
|
||||
self::debug('UCSS bypassed due to UCSS URI Exclude setting: ' . $hit);
|
||||
Core::comment('QUIC.cloud UCSS bypassed by setting');
|
||||
return false;
|
||||
}
|
||||
|
||||
$filepath_prefix = $this->_build_filepath_prefix('ucss');
|
||||
|
||||
$url_tag = self::get_url_tag($request_url);
|
||||
|
||||
$vary = $this->cls('Vary')->finalize_full_varies();
|
||||
$filename = $this->cls('Data')->load_url_file($url_tag, $vary, 'ucss');
|
||||
if ($filename) {
|
||||
$static_file = LITESPEED_STATIC_DIR . $filepath_prefix . $filename . '.css';
|
||||
|
||||
if (file_exists($static_file)) {
|
||||
self::debug2('existing ucss ' . $static_file);
|
||||
// Check if is error comment inside only
|
||||
$tmp = File::read($static_file);
|
||||
if (substr($tmp, 0, 2) == '/*' && substr(trim($tmp), -2) == '*/') {
|
||||
self::debug2('existing ucss is error only: ' . $tmp);
|
||||
Core::comment('QUIC.cloud UCSS bypassed due to generation error ❌ ' . $filepath_prefix . $filename . '.css');
|
||||
return false;
|
||||
}
|
||||
|
||||
Core::comment('QUIC.cloud UCSS loaded ✅ ' . $filepath_prefix . $filename . '.css' );
|
||||
|
||||
return $filename . '.css';
|
||||
}
|
||||
}
|
||||
|
||||
if ($dry_run) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Core::comment('QUIC.cloud UCSS in queue');
|
||||
|
||||
$uid = get_current_user_id();
|
||||
|
||||
$ua = $this->_get_ua();
|
||||
|
||||
// Store it for cron
|
||||
$this->_queue = $this->load_queue('ucss');
|
||||
|
||||
if (count($this->_queue) > 500) {
|
||||
self::debug('UCSS Queue is full - 500');
|
||||
return false;
|
||||
}
|
||||
|
||||
$queue_k = (strlen($vary) > 32 ? md5($vary) : $vary) . ' ' . $url_tag;
|
||||
$this->_queue[$queue_k] = array(
|
||||
'url' => apply_filters('litespeed_ucss_url', $request_url),
|
||||
'user_agent' => substr($ua, 0, 200),
|
||||
'is_mobile' => $this->_separate_mobile(),
|
||||
'is_webp' => $this->cls('Media')->webp_support() ? 1 : 0,
|
||||
'uid' => $uid,
|
||||
'vary' => $vary,
|
||||
'url_tag' => $url_tag,
|
||||
); // Current UA will be used to request
|
||||
$this->save_queue('ucss', $this->_queue);
|
||||
self::debug('Added queue_ucss [url_tag] ' . $url_tag . ' [UA] ' . $ua . ' [vary] ' . $vary . ' [uid] ' . $uid);
|
||||
|
||||
// Prepare cache tag for later purge
|
||||
Tag::add('UCSS.' . md5($queue_k));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get User Agent
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
private function _get_ua() {
|
||||
return !empty($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Add rows to q
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
public function add_to_q( $url_files ) {
|
||||
// Store it for cron
|
||||
$this->_queue = $this->load_queue('ucss');
|
||||
|
||||
if (count($this->_queue) > 500) {
|
||||
self::debug('UCSS Queue is full - 500');
|
||||
return false;
|
||||
}
|
||||
|
||||
$ua = $this->_get_ua();
|
||||
foreach ($url_files as $url_file) {
|
||||
$vary = $url_file['vary'];
|
||||
$request_url = $url_file['url'];
|
||||
$is_mobile = $url_file['mobile'];
|
||||
$is_webp = $url_file['webp'];
|
||||
$url_tag = self::get_url_tag($request_url);
|
||||
|
||||
$queue_k = (strlen($vary) > 32 ? md5($vary) : $vary) . ' ' . $url_tag;
|
||||
$q = array(
|
||||
'url' => apply_filters('litespeed_ucss_url', $request_url),
|
||||
'user_agent' => substr($ua, 0, 200),
|
||||
'is_mobile' => $is_mobile,
|
||||
'is_webp' => $is_webp,
|
||||
'uid' => false,
|
||||
'vary' => $vary,
|
||||
'url_tag' => $url_tag,
|
||||
); // Current UA will be used to request
|
||||
|
||||
self::debug('Added queue_ucss [url_tag] ' . $url_tag . ' [UA] ' . $ua . ' [vary] ' . $vary . ' [uid] false');
|
||||
$this->_queue[$queue_k] = $q;
|
||||
}
|
||||
$this->save_queue('ucss', $this->_queue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate UCSS
|
||||
*
|
||||
* @since 4.0
|
||||
*/
|
||||
public static function cron( $continue = false ) {
|
||||
$_instance = self::cls();
|
||||
return $_instance->_cron_handler($continue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle UCSS cron
|
||||
*
|
||||
* @since 4.2
|
||||
*/
|
||||
private function _cron_handler( $continue ) {
|
||||
$this->_queue = $this->load_queue('ucss');
|
||||
|
||||
if (empty($this->_queue)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// For cron, need to check request interval too
|
||||
if (!$continue) {
|
||||
if (!empty($this->_summary['curr_request']) && time() - $this->_summary['curr_request'] < 300 && !$this->conf(self::O_DEBUG)) {
|
||||
self::debug('Last request not done');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$i = 0;
|
||||
foreach ($this->_queue as $k => $v) {
|
||||
if (!empty($v['_status'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
self::debug('cron job [tag] ' . $k . ' [url] ' . $v['url'] . ($v['is_mobile'] ? ' 📱 ' : '') . ' [UA] ' . $v['user_agent']);
|
||||
|
||||
if (!isset($v['is_webp'])) {
|
||||
$v['is_webp'] = false;
|
||||
}
|
||||
|
||||
++$i;
|
||||
$res = $this->_send_req($v['url'], $k, $v['uid'], $v['user_agent'], $v['vary'], $v['url_tag'], $v['is_mobile'], $v['is_webp']);
|
||||
if (!$res) {
|
||||
// Status is wrong, drop this this->_queue
|
||||
$this->_queue = $this->load_queue('ucss');
|
||||
unset($this->_queue[$k]);
|
||||
$this->save_queue('ucss', $this->_queue);
|
||||
|
||||
if (!$continue) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($i > 3) {
|
||||
GUI::print_loading(count($this->_queue), 'UCSS');
|
||||
return Router::self_redirect(Router::ACTION_UCSS, self::TYPE_GEN);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Exit queue if out of quota or service is hot
|
||||
if ($res === 'out_of_quota' || $res === 'svc_hot') {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->_queue = $this->load_queue('ucss');
|
||||
$this->_queue[$k]['_status'] = 'requested';
|
||||
$this->save_queue('ucss', $this->_queue);
|
||||
self::debug('Saved to queue [k] ' . $k);
|
||||
|
||||
// only request first one
|
||||
if (!$continue) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($i > 3) {
|
||||
GUI::print_loading(count($this->_queue), 'UCSS');
|
||||
return Router::self_redirect(Router::ACTION_UCSS, self::TYPE_GEN);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send to QC API to generate UCSS
|
||||
*
|
||||
* @since 2.3
|
||||
* @access private
|
||||
*/
|
||||
private function _send_req( $request_url, $queue_k, $uid, $user_agent, $vary, $url_tag, $is_mobile, $is_webp ) {
|
||||
// Check if has credit to push or not
|
||||
$err = false;
|
||||
$allowance = $this->cls('Cloud')->allowance(Cloud::SVC_UCSS, $err);
|
||||
if (!$allowance) {
|
||||
self::debug('❌ No credit: ' . $err);
|
||||
$err && Admin_Display::error(Error::msg($err));
|
||||
return 'out_of_quota';
|
||||
}
|
||||
|
||||
set_time_limit(120);
|
||||
|
||||
// Update css request status
|
||||
$this->_summary['curr_request'] = time();
|
||||
self::save_summary();
|
||||
|
||||
// Gather guest HTML to send
|
||||
$html = $this->cls('CSS')->prepare_html($request_url, $user_agent, $uid);
|
||||
|
||||
if (!$html) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Parse HTML to gather all CSS content before requesting
|
||||
$css = false;
|
||||
list(, $html) = $this->prepare_css($html, $is_webp, true); // Use this to drop CSS from HTML as we don't need those CSS to generate UCSS
|
||||
$filename = $this->cls('Data')->load_url_file($url_tag, $vary, 'css');
|
||||
$filepath_prefix = $this->_build_filepath_prefix('css');
|
||||
$static_file = LITESPEED_STATIC_DIR . $filepath_prefix . $filename . '.css';
|
||||
self::debug('Checking combined file ' . $static_file);
|
||||
if (file_exists($static_file)) {
|
||||
$css = File::read($static_file);
|
||||
}
|
||||
|
||||
if (!$css) {
|
||||
self::debug('❌ No combined css');
|
||||
return false;
|
||||
}
|
||||
|
||||
$data = array(
|
||||
'url' => $request_url,
|
||||
'queue_k' => $queue_k,
|
||||
'user_agent' => $user_agent,
|
||||
'is_mobile' => $is_mobile ? 1 : 0, // todo:compatible w/ tablet
|
||||
'is_webp' => $is_webp ? 1 : 0,
|
||||
'html' => $html,
|
||||
'css' => $css,
|
||||
);
|
||||
if (!isset($this->_ucss_whitelist)) {
|
||||
$this->_ucss_whitelist = $this->_filter_whitelist();
|
||||
}
|
||||
$data['whitelist'] = $this->_ucss_whitelist;
|
||||
|
||||
self::debug('Generating: ', $data);
|
||||
|
||||
$json = Cloud::post(Cloud::SVC_UCSS, $data, 30);
|
||||
if (!is_array($json)) {
|
||||
return $json;
|
||||
}
|
||||
|
||||
// Old version compatibility
|
||||
if (empty($json['status'])) {
|
||||
if (!empty($json['ucss'])) {
|
||||
$this->_save_con('ucss', $json['ucss'], $queue_k, $is_mobile, $is_webp);
|
||||
}
|
||||
|
||||
// Delete the row
|
||||
return false;
|
||||
}
|
||||
|
||||
// Unknown status, remove this line
|
||||
if ($json['status'] != 'queued') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Save summary data
|
||||
$this->_summary['last_spent'] = time() - $this->_summary['curr_request'];
|
||||
$this->_summary['last_request'] = $this->_summary['curr_request'];
|
||||
$this->_summary['curr_request'] = 0;
|
||||
self::save_summary();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save UCSS content
|
||||
*
|
||||
* @since 4.2
|
||||
*/
|
||||
private function _save_con( $type, $css, $queue_k, $is_mobile, $is_webp ) {
|
||||
// Add filters
|
||||
$css = apply_filters('litespeed_' . $type, $css, $queue_k);
|
||||
self::debug2('con: ', $css);
|
||||
|
||||
if (substr($css, 0, 2) == '/*' && substr($css, -2) == '*/') {
|
||||
self::debug('❌ empty ' . $type . ' [content] ' . $css);
|
||||
// continue; // Save the error info too
|
||||
}
|
||||
|
||||
// Write to file
|
||||
$filecon_md5 = md5($css);
|
||||
|
||||
$filepath_prefix = $this->_build_filepath_prefix($type);
|
||||
$static_file = LITESPEED_STATIC_DIR . $filepath_prefix . $filecon_md5 . '.css';
|
||||
|
||||
File::save($static_file, $css, true);
|
||||
|
||||
$url_tag = $this->_queue[$queue_k]['url_tag'];
|
||||
$vary = $this->_queue[$queue_k]['vary'];
|
||||
self::debug2("Save URL to file [file] $static_file [vary] $vary");
|
||||
|
||||
$this->cls('Data')->save_url($url_tag, $vary, $type, $filecon_md5, dirname($static_file), $is_mobile, $is_webp);
|
||||
|
||||
Purge::add(strtoupper($type) . '.' . md5($queue_k));
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare CSS from HTML for CCSS generation only. UCSS will used combined CSS directly.
|
||||
* Prepare refined HTML for both CCSS and UCSS.
|
||||
*
|
||||
* @since 3.4.3
|
||||
*/
|
||||
public function prepare_css( $html, $is_webp = false, $dryrun = false ) {
|
||||
$css = '';
|
||||
preg_match_all('#<link ([^>]+)/?>|<style([^>]*)>([^<]+)</style>#isU', $html, $matches, PREG_SET_ORDER);
|
||||
foreach ($matches as $match) {
|
||||
$debug_info = '';
|
||||
if (strpos($match[0], '<link') === 0) {
|
||||
$attrs = Utility::parse_attr($match[1]);
|
||||
|
||||
if (empty($attrs['rel'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($attrs['rel'] != 'stylesheet') {
|
||||
if ($attrs['rel'] != 'preload' || empty($attrs['as']) || $attrs['as'] != 'style') {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($attrs['media']) && strpos($attrs['media'], 'print') !== false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (empty($attrs['href'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check Google fonts hit
|
||||
if (strpos($attrs['href'], 'fonts.googleapis.com') !== false) {
|
||||
$html = str_replace($match[0], '', $html);
|
||||
continue;
|
||||
}
|
||||
|
||||
$debug_info = $attrs['href'];
|
||||
|
||||
// Load CSS content
|
||||
if (!$dryrun) {
|
||||
// Dryrun will not load CSS but just drop them
|
||||
$con = $this->cls('Optimizer')->load_file($attrs['href']);
|
||||
if (!$con) {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
$con = '';
|
||||
}
|
||||
} else {
|
||||
// Inline style
|
||||
$attrs = Utility::parse_attr($match[2]);
|
||||
|
||||
if (!empty($attrs['media']) && strpos($attrs['media'], 'print') !== false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Debug2::debug2('[CSS] Load inline CSS ' . substr($match[3], 0, 100) . '...', $attrs);
|
||||
$con = $match[3];
|
||||
|
||||
$debug_info = '__INLINE__';
|
||||
}
|
||||
|
||||
$con = Optimizer::minify_css($con);
|
||||
if ($is_webp && $this->cls('Media')->webp_support()) {
|
||||
$con = $this->cls('Media')->replace_background_webp($con);
|
||||
}
|
||||
|
||||
if (!empty($attrs['media']) && $attrs['media'] !== 'all') {
|
||||
$con = '@media ' . $attrs['media'] . '{' . $con . "}\n";
|
||||
} else {
|
||||
$con = $con . "\n";
|
||||
}
|
||||
|
||||
$con = '/* ' . $debug_info . ' */' . $con;
|
||||
$css .= $con;
|
||||
|
||||
$html = str_replace($match[0], '', $html);
|
||||
}
|
||||
|
||||
return array( $css, $html );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter the comment content, add quotes to selector from whitelist. Return the json
|
||||
*
|
||||
* @since 3.3
|
||||
*/
|
||||
private function _filter_whitelist() {
|
||||
$whitelist = array();
|
||||
$list = apply_filters('litespeed_ucss_whitelist', $this->conf(self::O_OPTM_UCSS_SELECTOR_WHITELIST));
|
||||
foreach ($list as $k => $v) {
|
||||
if (substr($v, 0, 2) === '//') {
|
||||
continue;
|
||||
}
|
||||
// Wrap in quotes for selectors
|
||||
if (substr($v, 0, 1) !== '/' && strpos($v, '"') === false && strpos($v, "'") === false) {
|
||||
// $v = "'$v'";
|
||||
}
|
||||
$whitelist[] = $v;
|
||||
}
|
||||
|
||||
return $whitelist;
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify finished from server
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
public function notify() {
|
||||
$post_data = \json_decode(file_get_contents('php://input'), true);
|
||||
if (is_null($post_data)) {
|
||||
$post_data = $_POST;
|
||||
}
|
||||
self::debug('notify() data', $post_data);
|
||||
|
||||
$this->_queue = $this->load_queue('ucss');
|
||||
|
||||
list($post_data) = $this->cls('Cloud')->extract_msg($post_data, 'ucss');
|
||||
|
||||
$notified_data = $post_data['data'];
|
||||
if (empty($notified_data) || !is_array($notified_data)) {
|
||||
self::debug('❌ notify exit: no notified data');
|
||||
return Cloud::err('no notified data');
|
||||
}
|
||||
|
||||
// Check if its in queue or not
|
||||
$valid_i = 0;
|
||||
foreach ($notified_data as $v) {
|
||||
if (empty($v['request_url'])) {
|
||||
self::debug('❌ notify bypass: no request_url', $v);
|
||||
continue;
|
||||
}
|
||||
if (empty($v['queue_k'])) {
|
||||
self::debug('❌ notify bypass: no queue_k', $v);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (empty($this->_queue[$v['queue_k']])) {
|
||||
self::debug('❌ notify bypass: no this queue [q_k]' . $v['queue_k']);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Save data
|
||||
if (!empty($v['data_ucss'])) {
|
||||
$is_mobile = $this->_queue[$v['queue_k']]['is_mobile'];
|
||||
$is_webp = $this->_queue[$v['queue_k']]['is_webp'];
|
||||
$this->_save_con('ucss', $v['data_ucss'], $v['queue_k'], $is_mobile, $is_webp);
|
||||
|
||||
++$valid_i;
|
||||
}
|
||||
|
||||
unset($this->_queue[$v['queue_k']]);
|
||||
self::debug('notify data handled, unset queue [q_k] ' . $v['queue_k']);
|
||||
}
|
||||
$this->save_queue('ucss', $this->_queue);
|
||||
|
||||
self::debug('notified');
|
||||
|
||||
return Cloud::ok(array( 'count' => $valid_i ));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle all request actions from main cls
|
||||
*
|
||||
* @since 2.3
|
||||
* @access public
|
||||
*/
|
||||
public function handler() {
|
||||
$type = Router::verify_type();
|
||||
|
||||
switch ($type) {
|
||||
case self::TYPE_GEN:
|
||||
self::cron(true);
|
||||
break;
|
||||
|
||||
case self::TYPE_CLEAR_Q:
|
||||
$this->clear_q('ucss');
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
Admin::redirect();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,958 @@
|
||||
<?php
|
||||
// phpcs:ignoreFile
|
||||
|
||||
/**
|
||||
* The utility class.
|
||||
*
|
||||
* @since 1.1.5
|
||||
*/
|
||||
|
||||
namespace LiteSpeed;
|
||||
|
||||
defined('WPINC') || exit();
|
||||
|
||||
class Utility extends Root {
|
||||
|
||||
private static $_internal_domains;
|
||||
|
||||
/**
|
||||
* Validate regex
|
||||
*
|
||||
* @since 1.0.9
|
||||
* @since 3.0 Moved here from admin-settings.cls
|
||||
* @access public
|
||||
* @return bool True for valid rules, false otherwise.
|
||||
*/
|
||||
public static function syntax_checker( $rules ) {
|
||||
return preg_match(self::arr2regex($rules), '') !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Combine regex array to regex rule
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
public static function arr2regex( $arr, $drop_delimiter = false ) {
|
||||
$arr = self::sanitize_lines($arr);
|
||||
|
||||
$new_arr = array();
|
||||
foreach ($arr as $v) {
|
||||
$new_arr[] = preg_quote($v, '#');
|
||||
}
|
||||
|
||||
$regex = implode('|', $new_arr);
|
||||
$regex = str_replace(' ', '\\ ', $regex);
|
||||
|
||||
if ($drop_delimiter) {
|
||||
return $regex;
|
||||
}
|
||||
|
||||
return '#' . $regex . '#';
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace wildcard to regex
|
||||
*
|
||||
* @since 3.2.2
|
||||
*/
|
||||
public static function wildcard2regex( $string ) {
|
||||
if (is_array($string)) {
|
||||
return array_map(__CLASS__ . '::wildcard2regex', $string);
|
||||
}
|
||||
|
||||
if (strpos($string, '*') !== false) {
|
||||
$string = preg_quote($string, '#');
|
||||
$string = str_replace('\*', '.*', $string);
|
||||
}
|
||||
|
||||
return $string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an URL or current page is REST req or not
|
||||
*
|
||||
* @since 2.9.3
|
||||
* @deprecated 2.9.4 Moved to REST class
|
||||
* @access public
|
||||
*/
|
||||
public static function is_rest( $url = false ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current page type
|
||||
*
|
||||
* @since 2.9
|
||||
*/
|
||||
public static function page_type() {
|
||||
global $wp_query;
|
||||
$page_type = 'default';
|
||||
|
||||
if ($wp_query->is_page) {
|
||||
$page_type = is_front_page() ? 'front' : 'page';
|
||||
} elseif ($wp_query->is_home) {
|
||||
$page_type = 'home';
|
||||
} elseif ($wp_query->is_single) {
|
||||
// $page_type = $wp_query->is_attachment ? 'attachment' : 'single';
|
||||
$page_type = get_post_type();
|
||||
} elseif ($wp_query->is_category) {
|
||||
$page_type = 'category';
|
||||
} elseif ($wp_query->is_tag) {
|
||||
$page_type = 'tag';
|
||||
} elseif ($wp_query->is_tax) {
|
||||
$page_type = 'tax';
|
||||
// $page_type = get_queried_object()->taxonomy;
|
||||
} elseif ($wp_query->is_archive) {
|
||||
if ($wp_query->is_day) {
|
||||
$page_type = 'day';
|
||||
} elseif ($wp_query->is_month) {
|
||||
$page_type = 'month';
|
||||
} elseif ($wp_query->is_year) {
|
||||
$page_type = 'year';
|
||||
} elseif ($wp_query->is_author) {
|
||||
$page_type = 'author';
|
||||
} else {
|
||||
$page_type = 'archive';
|
||||
}
|
||||
} elseif ($wp_query->is_search) {
|
||||
$page_type = 'search';
|
||||
} elseif ($wp_query->is_404) {
|
||||
$page_type = '404';
|
||||
}
|
||||
|
||||
return $page_type;
|
||||
|
||||
// if ( is_404() ) {
|
||||
// $page_type = '404';
|
||||
// }
|
||||
// elseif ( is_singular() ) {
|
||||
// $page_type = get_post_type();
|
||||
// }
|
||||
// elseif ( is_home() && get_option( 'show_on_front' ) == 'page' ) {
|
||||
// $page_type = 'home';
|
||||
// }
|
||||
// elseif ( is_front_page() ) {
|
||||
// $page_type = 'front';
|
||||
// }
|
||||
// elseif ( is_tax() ) {
|
||||
// $page_type = get_queried_object()->taxonomy;
|
||||
// }
|
||||
// elseif ( is_category() ) {
|
||||
// $page_type = 'category';
|
||||
// }
|
||||
// elseif ( is_tag() ) {
|
||||
// $page_type = 'tag';
|
||||
// }
|
||||
|
||||
// return $page_type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get ping speed
|
||||
*
|
||||
* @since 2.9
|
||||
*/
|
||||
public static function ping( $domain ) {
|
||||
if (strpos($domain, ':')) {
|
||||
$domain = parse_url($domain, PHP_URL_HOST);
|
||||
}
|
||||
$starttime = microtime(true);
|
||||
$file = fsockopen($domain, 443, $errno, $errstr, 10);
|
||||
$stoptime = microtime(true);
|
||||
$status = 0;
|
||||
|
||||
if (!$file) {
|
||||
$status = 99999;
|
||||
}
|
||||
// Site is down
|
||||
else {
|
||||
fclose($file);
|
||||
$status = ($stoptime - $starttime) * 1000;
|
||||
$status = floor($status);
|
||||
}
|
||||
|
||||
Debug2::debug("[Util] ping [Domain] $domain \t[Speed] $status");
|
||||
|
||||
return $status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set seconds/timestamp to readable format
|
||||
*
|
||||
* @since 1.6.5
|
||||
* @access public
|
||||
*/
|
||||
public static function readable_time( $seconds_or_timestamp, $timeout = 3600, $forward = false ) {
|
||||
if (strlen($seconds_or_timestamp) == 10) {
|
||||
$seconds = time() - $seconds_or_timestamp;
|
||||
if ($seconds > $timeout) {
|
||||
return date('m/d/Y H:i:s', $seconds_or_timestamp + LITESPEED_TIME_OFFSET);
|
||||
}
|
||||
} else {
|
||||
$seconds = $seconds_or_timestamp;
|
||||
}
|
||||
|
||||
$res = '';
|
||||
if ($seconds > 86400) {
|
||||
$num = floor($seconds / 86400);
|
||||
$res .= $num . 'd';
|
||||
$seconds %= 86400;
|
||||
}
|
||||
|
||||
if ($seconds > 3600) {
|
||||
if ($res) {
|
||||
$res .= ', ';
|
||||
}
|
||||
$num = floor($seconds / 3600);
|
||||
$res .= $num . 'h';
|
||||
$seconds %= 3600;
|
||||
}
|
||||
|
||||
if ($seconds > 60) {
|
||||
if ($res) {
|
||||
$res .= ', ';
|
||||
}
|
||||
$num = floor($seconds / 60);
|
||||
$res .= $num . 'm';
|
||||
$seconds %= 60;
|
||||
}
|
||||
|
||||
if ($seconds > 0) {
|
||||
if ($res) {
|
||||
$res .= ' ';
|
||||
}
|
||||
$res .= $seconds . 's';
|
||||
}
|
||||
|
||||
if (!$res) {
|
||||
return $forward ? __('right now', 'litespeed-cache') : __('just now', 'litespeed-cache');
|
||||
}
|
||||
|
||||
$res = $forward ? $res : sprintf(__(' %s ago', 'litespeed-cache'), $res);
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert array to string
|
||||
*
|
||||
* @since 1.6
|
||||
* @access public
|
||||
*/
|
||||
public static function arr2str( $arr ) {
|
||||
if (!is_array($arr)) {
|
||||
return $arr;
|
||||
}
|
||||
|
||||
return base64_encode(\json_encode($arr));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get human readable size
|
||||
*
|
||||
* @since 1.6
|
||||
* @access public
|
||||
*/
|
||||
public static function real_size( $filesize, $is_1000 = false ) {
|
||||
$unit = $is_1000 ? 1000 : 1024;
|
||||
|
||||
if ($filesize >= pow($unit, 3)) {
|
||||
$filesize = round(($filesize / pow($unit, 3)) * 100) / 100 . 'G';
|
||||
} elseif ($filesize >= pow($unit, 2)) {
|
||||
$filesize = round(($filesize / pow($unit, 2)) * 100) / 100 . 'M';
|
||||
} elseif ($filesize >= $unit) {
|
||||
$filesize = round(($filesize / $unit) * 100) / 100 . 'K';
|
||||
} else {
|
||||
$filesize = $filesize . 'B';
|
||||
}
|
||||
return $filesize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse attributes from string
|
||||
*
|
||||
* @since 1.2.2
|
||||
* @since 1.4 Moved from optimize to utility
|
||||
* @access private
|
||||
* @param string $str
|
||||
* @return array All the attributes
|
||||
*/
|
||||
public static function parse_attr( $str ) {
|
||||
$attrs = array();
|
||||
preg_match_all('#([\w-]+)=(["\'])([^\2]*)\2#isU', $str, $matches, PREG_SET_ORDER);
|
||||
foreach ($matches as $match) {
|
||||
$attrs[$match[1]] = trim($match[3]);
|
||||
}
|
||||
return $attrs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an array has a string
|
||||
*
|
||||
* Support $ exact match
|
||||
*
|
||||
* @since 1.3
|
||||
* @access private
|
||||
* @param string $needle The string to search with
|
||||
* @param array $haystack
|
||||
* @return bool|string False if not found, otherwise return the matched string in haystack.
|
||||
*/
|
||||
public static function str_hit_array( $needle, $haystack, $has_ttl = false ) {
|
||||
if (!$haystack) {
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* Safety check to avoid PHP warning
|
||||
*
|
||||
* @see https://github.com/litespeedtech/lscache_wp/pull/131/commits/45fc03af308c7d6b5583d1664fad68f75fb6d017
|
||||
*/
|
||||
if (!is_array($haystack)) {
|
||||
Debug2::debug('[Util] ❌ bad param in str_hit_array()!');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$hit = false;
|
||||
$this_ttl = 0;
|
||||
foreach ($haystack as $item) {
|
||||
if (!$item) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($has_ttl) {
|
||||
$this_ttl = 0;
|
||||
$item = explode(' ', $item);
|
||||
if (!empty($item[1])) {
|
||||
$this_ttl = $item[1];
|
||||
}
|
||||
$item = $item[0];
|
||||
}
|
||||
|
||||
if (substr($item, 0, 1) === '^' && substr($item, -1) === '$') {
|
||||
// do exact match
|
||||
if (substr($item, 1, -1) === $needle) {
|
||||
$hit = $item;
|
||||
break;
|
||||
}
|
||||
} elseif (substr($item, -1) === '$') {
|
||||
// match end
|
||||
if (substr($item, 0, -1) === substr($needle, -strlen($item) + 1)) {
|
||||
$hit = $item;
|
||||
break;
|
||||
}
|
||||
} elseif (substr($item, 0, 1) === '^') {
|
||||
// match beginning
|
||||
if (substr($item, 1) === substr($needle, 0, strlen($item) - 1)) {
|
||||
$hit = $item;
|
||||
break;
|
||||
}
|
||||
} elseif (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 $url `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 array|string $arr
|
||||
* @param string|null $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(), $unescape = false ) {
|
||||
$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';
|
||||
} elseif (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');
|
||||
}
|
||||
|
||||
if ($unescape) {
|
||||
$url = wp_specialchars_decode($url);
|
||||
}
|
||||
|
||||
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 = constant('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'] . constant('LITESPEED_WP_REALPATH') . $url_parsed['path'];
|
||||
} else {
|
||||
$file_path_ori = $_SERVER['DOCUMENT_ROOT'] . $url_parsed['path'];
|
||||
}
|
||||
} else {
|
||||
$file_path_ori = Router::frontend_path() . '/' . $url_parsed['path'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Added new file postfix to be check if passed in
|
||||
*
|
||||
* @since 2.2.4
|
||||
*/
|
||||
if ($addition_postfix) {
|
||||
$file_path_ori .= '.' . $addition_postfix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Added this filter for those plugins which overwrite the filepath
|
||||
*
|
||||
* @see #101091 plugin `Hide My WordPress`
|
||||
* @since 2.2.3
|
||||
*/
|
||||
$file_path_ori = apply_filters('litespeed_realpath', $file_path_ori);
|
||||
|
||||
$file_path = realpath($file_path_ori);
|
||||
if (!is_file($file_path)) {
|
||||
Debug2::debug2('[Util] file not exist: ' . $file_path_ori);
|
||||
return false;
|
||||
}
|
||||
|
||||
return array( $file_path, filesize($file_path) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Safely parse URL for v5.3 compatibility
|
||||
*
|
||||
* @since 3.4.3
|
||||
*/
|
||||
public static function parse_url_safe( $url, $component = -1 ) {
|
||||
if (substr($url, 0, 2) == '//') {
|
||||
$url = 'https:' . $url;
|
||||
}
|
||||
|
||||
return parse_url($url, $component);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace url in srcset to new value
|
||||
*
|
||||
* @since 2.2.3
|
||||
*/
|
||||
public static function srcset_replace( $content, $callback ) {
|
||||
preg_match_all('# srcset=([\'"])(.+)\g{1}#iU', $content, $matches);
|
||||
$srcset_ori = array();
|
||||
$srcset_final = array();
|
||||
foreach ($matches[2] as $k => $urls_ori) {
|
||||
$urls_final = explode(',', $urls_ori);
|
||||
|
||||
$changed = false;
|
||||
|
||||
foreach ($urls_final as $k2 => $url_info) {
|
||||
$url_info_arr = explode(' ', trim($url_info));
|
||||
|
||||
if (!($url2 = call_user_func($callback, $url_info_arr[0]))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$changed = true;
|
||||
|
||||
$urls_final[$k2] = str_replace($url_info_arr[0], $url2, $url_info);
|
||||
|
||||
Debug2::debug2('[Util] - srcset replaced to ' . $url2 . (!empty($url_info_arr[1]) ? ' ' . $url_info_arr[1] : ''));
|
||||
}
|
||||
|
||||
if (!$changed) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$urls_final = implode(',', $urls_final);
|
||||
|
||||
$srcset_ori[] = $matches[0][$k];
|
||||
|
||||
$srcset_final[] = str_replace($urls_ori, $urls_final, $matches[0][$k]);
|
||||
}
|
||||
|
||||
if ($srcset_ori) {
|
||||
$content = str_replace($srcset_ori, $srcset_final, $content);
|
||||
Debug2::debug2('[Util] - srcset replaced');
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate pagination
|
||||
*
|
||||
* @since 3.0
|
||||
* @access public
|
||||
*/
|
||||
public static function pagination( $total, $limit, $return_offset = false ) {
|
||||
$pagenum = isset($_GET['pagenum']) ? absint($_GET['pagenum']) : 1;
|
||||
|
||||
$offset = ($pagenum - 1) * $limit;
|
||||
$num_of_pages = ceil($total / $limit);
|
||||
|
||||
if ($offset > $total) {
|
||||
$offset = $total - $limit;
|
||||
}
|
||||
|
||||
if ($offset < 0) {
|
||||
$offset = 0;
|
||||
}
|
||||
|
||||
if ($return_offset) {
|
||||
return $offset;
|
||||
}
|
||||
|
||||
$page_links = paginate_links(array(
|
||||
'base' => add_query_arg('pagenum', '%#%'),
|
||||
'format' => '',
|
||||
'prev_text' => '«',
|
||||
'next_text' => '»',
|
||||
'total' => $num_of_pages,
|
||||
'current' => $pagenum,
|
||||
));
|
||||
|
||||
return '<div class="tablenav"><div class="tablenav-pages" style="margin: 1em 0">' . $page_links . '</div></div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate placeholder for an array to query
|
||||
*
|
||||
* @since 2.0
|
||||
* @access public
|
||||
*/
|
||||
public static function chunk_placeholder( $data, $fields ) {
|
||||
$division = substr_count($fields, ',') + 1;
|
||||
|
||||
$q = implode(
|
||||
',',
|
||||
array_map(function ( $el ) {
|
||||
return '(' . implode(',', $el) . ')';
|
||||
}, array_chunk(array_fill(0, count($data), '%s'), $division))
|
||||
);
|
||||
|
||||
return $q;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare image sizes for optimization.
|
||||
*
|
||||
* @since 7.5
|
||||
* @access public
|
||||
*/
|
||||
public static function prepare_image_sizes_array( $detailed = false ) {
|
||||
$image_sizes = wp_get_registered_image_subsizes();
|
||||
$sizes = [];
|
||||
|
||||
foreach ( $image_sizes as $current_size_name => $current_size ) {
|
||||
if( empty( $current_size['width'] ) && empty( $current_size['height'] ) ) continue;
|
||||
|
||||
if( !$detailed ) {
|
||||
$sizes[] = $current_size_name;
|
||||
}
|
||||
else{
|
||||
$label = $current_size['width'] . 'x' . $current_size['height'];
|
||||
if( $current_size_name !== $label ){
|
||||
$label = ucfirst( $current_size_name ) . ' ( ' . $label . ' )';
|
||||
}
|
||||
|
||||
$sizes[] = [
|
||||
"label" => $label,
|
||||
"file_size" => $current_size_name,
|
||||
"width" => $current_size['width'],
|
||||
"height" => $current_size['height'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $sizes;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,773 @@
|
||||
<?php
|
||||
// phpcs:ignoreFile
|
||||
|
||||
/**
|
||||
* The plugin vary class to manage X-LiteSpeed-Vary
|
||||
*
|
||||
* @since 1.1.3
|
||||
*/
|
||||
|
||||
namespace LiteSpeed;
|
||||
|
||||
defined('WPINC') || exit();
|
||||
|
||||
class Vary extends Root {
|
||||
|
||||
const LOG_TAG = '🔱';
|
||||
const X_HEADER = 'X-LiteSpeed-Vary';
|
||||
|
||||
private static $_vary_name = '_lscache_vary'; // this default vary cookie is used for logged in status check
|
||||
private static $_can_change_vary = false; // Currently only AJAX used this
|
||||
|
||||
/**
|
||||
* Adds the actions used for setting up cookies on log in/out.
|
||||
*
|
||||
* Also checks if the database matches the rewrite rule.
|
||||
*
|
||||
* @since 1.0.4
|
||||
*/
|
||||
// public function init()
|
||||
// {
|
||||
// $this->_update_vary_name();
|
||||
// }
|
||||
|
||||
/**
|
||||
* Update the default vary name if changed
|
||||
*
|
||||
* @since 4.0
|
||||
* @since 7.0 Moved to after_user_init to allow ESI no-vary no conflict w/ LSCACHE_VARY_COOKIE/O_CACHE_LOGIN_COOKIE
|
||||
*/
|
||||
private function _update_vary_name() {
|
||||
$db_cookie = $this->conf(Base::O_CACHE_LOGIN_COOKIE); // [3.0] todo: check if works in network's sites
|
||||
// If no vary set in rewrite rule
|
||||
if (!isset($_SERVER['LSCACHE_VARY_COOKIE'])) {
|
||||
if ($db_cookie) {
|
||||
// Check if is from ESI req or not. If from ESI no-vary, no need to set no-cache
|
||||
$something_wrong = true;
|
||||
if (!empty($_GET[ESI::QS_ACTION]) && !empty($_GET['_control'])) {
|
||||
// Have to manually build this checker bcoz ESI is not init yet.
|
||||
$control = explode(',', $_GET['_control']);
|
||||
if (in_array('no-vary', $control)) {
|
||||
self::debug('no-vary control existed, bypass vary_name update');
|
||||
$something_wrong = false;
|
||||
self::$_vary_name = $db_cookie;
|
||||
}
|
||||
}
|
||||
|
||||
if (defined('LITESPEED_CLI') || wp_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) && !is_admin()) {
|
||||
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
|
||||
elseif (!is_admin()) {
|
||||
// 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 (wp_doing_ajax()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (wp_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 int|false $val The value to update.
|
||||
* @param int $expire Expire time.
|
||||
* @param bool $path False if use wp root path as cookie path
|
||||
*/
|
||||
private function _cookie( $val = false, $expire = 0, $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()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,381 @@
|
||||
<?php
|
||||
/**
|
||||
* The viewport image (VPI) class.
|
||||
*
|
||||
* Handles discovering above-the-fold images for posts/pages and stores the
|
||||
* viewport image list per post (desktop & mobile). Coordinates with the
|
||||
* remote service via queue + cron + webhook notify.
|
||||
*
|
||||
* @since 4.7
|
||||
* @package LiteSpeed
|
||||
*/
|
||||
|
||||
namespace LiteSpeed;
|
||||
|
||||
defined( 'WPINC' ) || exit();
|
||||
|
||||
/**
|
||||
* Generate and manage ViewPort Images (VPI) for pages.
|
||||
*/
|
||||
class VPI extends Base {
|
||||
|
||||
/**
|
||||
* Log tag for debug output.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const LOG_TAG = '[VPI]';
|
||||
|
||||
/**
|
||||
* Action types.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const TYPE_GEN = 'gen';
|
||||
const TYPE_CLEAR_Q = 'clear_q';
|
||||
|
||||
/**
|
||||
* VPI Desktop Meta name.
|
||||
*
|
||||
* @since 7.6
|
||||
* @var string
|
||||
*/
|
||||
const POST_META = 'litespeed_vpi_list';
|
||||
/**
|
||||
* VPI Mobile Meta name.
|
||||
*
|
||||
* @since 7.6
|
||||
* @var string
|
||||
*/
|
||||
const POST_META_MOBILE = 'litespeed_vpi_list_mobile';
|
||||
|
||||
/**
|
||||
* Summary values persisted between requests (timings, last runs, etc).
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_summary;
|
||||
|
||||
/**
|
||||
* In-memory working queue for VPI jobs.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $_queue;
|
||||
|
||||
/**
|
||||
* Init.
|
||||
*
|
||||
* @since 4.7
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->_summary = self::get_summary();
|
||||
}
|
||||
|
||||
/**
|
||||
* Queue the current page for VPI generation.
|
||||
*
|
||||
* @since 4.7
|
||||
* @return void
|
||||
*/
|
||||
public function add_to_queue() {
|
||||
$is_mobile = $this->_separate_mobile();
|
||||
|
||||
global $wp;
|
||||
$request_url = home_url( $wp->request );
|
||||
|
||||
if ( ! apply_filters( 'litespeed_vpi_should_queue', true, $request_url ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Sanitize user agent coming from the server superglobal.
|
||||
$ua = ! empty( $_SERVER['HTTP_USER_AGENT'] )
|
||||
? sanitize_text_field( wp_unslash( $_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 = (int) 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 ] = [
|
||||
'url' => apply_filters( 'litespeed_vpi_url', $request_url ),
|
||||
'post_id' => $post_id,
|
||||
'user_agent' => substr( $ua, 0, 200 ),
|
||||
'is_mobile' => $is_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 ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle finish notifications from remote service.
|
||||
*
|
||||
* Expects JSON body; falls back to $_POST for legacy callers.
|
||||
*
|
||||
* @since 4.7
|
||||
* @return array Response object for the cloud layer.
|
||||
*/
|
||||
public function notify() {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Missing
|
||||
$post_data = \json_decode( file_get_contents( 'php://input' ), true );
|
||||
if ( is_null( $post_data ) ) {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Missing
|
||||
$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 it's 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 = $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 = (int) $this->_queue[ $queue_k ]['post_id'];
|
||||
$name = ! empty( $v['is_mobile'] ) ? self::POST_META_MOBILE : self::POST_META;
|
||||
$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( [ 'count' => $valid_i ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Cron entry point.
|
||||
*
|
||||
* @since 4.7
|
||||
*
|
||||
* @param bool $do_continue Continue processing multiple queue items within one cron tick.
|
||||
* @return mixed Result of the handler.
|
||||
*/
|
||||
public static function cron( $do_continue = false ) {
|
||||
$_instance = self::cls();
|
||||
return $_instance->_cron_handler( $do_continue );
|
||||
}
|
||||
|
||||
/**
|
||||
* Cron queue processor.
|
||||
*
|
||||
* @since 4.7
|
||||
*
|
||||
* @param bool $do_continue Continue processing multiple queue items within one cron tick.
|
||||
* @return void
|
||||
*/
|
||||
private function _cron_handler( $do_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 ( ! $do_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 item from queue.
|
||||
$this->_queue = $this->load_queue( 'vpi' );
|
||||
unset( $this->_queue[ $k ] );
|
||||
$this->save_queue( 'vpi', $this->_queue );
|
||||
|
||||
if ( ! $do_continue ) {
|
||||
return;
|
||||
}
|
||||
|
||||
GUI::print_loading( count( $this->_queue ), 'VPI' );
|
||||
Router::self_redirect( Router::ACTION_VPI, self::TYPE_GEN );
|
||||
return;
|
||||
}
|
||||
|
||||
// Exit queue if out of quota or service is hot.
|
||||
if ( 'out_of_quota' === $res || 'svc_hot' === $res ) {
|
||||
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 not continuing.
|
||||
if ( ! $do_continue ) {
|
||||
return;
|
||||
}
|
||||
|
||||
GUI::print_loading( count( $this->_queue ), 'VPI' );
|
||||
Router::self_redirect( Router::ACTION_VPI, self::TYPE_GEN );
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send request to QUIC.cloud API to generate VPI.
|
||||
*
|
||||
* @since 4.7
|
||||
* @access private
|
||||
*
|
||||
* @param string $request_url The URL to analyze for VPI.
|
||||
* @param string $queue_k Queue key for this job.
|
||||
* @param string $user_agent Sanitized User-Agent string (<=200 chars).
|
||||
* @param bool $is_mobile Whether the job is for mobile viewport.
|
||||
* @return bool|string True on queued successfully, 'out_of_quota'/'svc_hot' on throttling, or false on error.
|
||||
*/
|
||||
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 request status.
|
||||
self::save_summary( [ '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 CSS content before requesting.
|
||||
$css = false;
|
||||
list( $css, $html ) = $this->cls( 'CSS' )->prepare_css( $html );
|
||||
|
||||
if ( ! $css ) {
|
||||
self::debug( '❌ No css' );
|
||||
return false;
|
||||
}
|
||||
|
||||
$data = [
|
||||
'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 ( 'queued' !== $json['status'] ) {
|
||||
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 controller.
|
||||
*
|
||||
* @since 4.7
|
||||
* @return void
|
||||
*/
|
||||
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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user