initial commit

This commit is contained in:
2024-04-29 13:12:44 +05:45
commit 34887303c5
19300 changed files with 5268802 additions and 0 deletions

View File

@@ -0,0 +1,639 @@
<?php
/**
* The plugin activation class.
*
* @since 1.1.0
* @since 1.5 Moved into /inc
* @package LiteSpeed
* @subpackage LiteSpeed/inc
* @author LiteSpeed Technologies <info@litespeedtech.com>
*/
namespace LiteSpeed;
defined( 'WPINC' ) || exit;
class Activation extends Base {
const TYPE_UPGRADE = 'upgrade';
const TYPE_INSTALL_3RD = 'install_3rd';
const TYPE_INSTALL_ZIP = 'install_zip';
const TYPE_DISMISS_RECOMMENDED = 'dismiss_recommended';
const NETWORK_TRANSIENT_COUNT = 'lscwp_network_count';
private static $_data_file;
/**
* Construct
*
* @since 4.1
*/
public function __construct() {
self::$_data_file = LSCWP_CONTENT_DIR . '/' . self::CONF_FILE;
}
/**
* The activation hook callback.
*
* @since 1.0.0
* @access public
*/
public static function register_activation() {
$count = 0;
! defined( 'LSCWP_LOG_TAG' ) && define( 'LSCWP_LOG_TAG', 'Activate_' . get_current_blog_id() );
if ( is_multisite() ) {
$count = self::get_network_count();
if ( $count !== false ) {
$count = intval( $count ) + 1;
set_site_transient( self::NETWORK_TRANSIENT_COUNT, $count, DAY_IN_SECONDS );
}
}
// Files will be delayed updated in next visit to wp-admin
Conf::update_option( '__activation', Core::VER );
/* Network file handler */
if ( is_multisite() ) {
if ( ! is_network_admin() ) {
if ( $count === 1 ) {
// Only itself is activated, set .htaccess with only CacheLookUp
try {
Htaccess::cls()->insert_ls_wrapper();
} catch ( \Exception $ex ) {
Admin_Display::error( $ex->getMessage() );
}
}
}
}
if ( defined( 'LSCWP_REF' ) && LSCWP_REF == 'whm' ) {
GUI::update_option( GUI::WHM_MSG, GUI::WHM_MSG_VAL );
}
}
/**
* Uninstall plugin
* @since 1.1.0
*/
public static function uninstall_litespeed_cache() {
Task::destroy();
// Delete options
foreach ( Conf::cls()->load_default_vals() as $k => $v ) {
Base::delete_option( $k );
}
// Delete site options
if ( is_multisite() ) {
foreach ( Conf::cls()->load_default_site_vals() as $k => $v ) {
Base::delete_site_option( $k );
}
}
// Delete avatar table
Data::cls()->tables_del();
if ( file_exists( LITESPEED_STATIC_DIR ) ) {
File::rrmdir( LITESPEED_STATIC_DIR );
}
Cloud::version_check( 'uninstall' );
// Files has been deleted when deactivated
}
/**
* Get the blog ids for the network. Accepts function arguments.
*
* Will use wp_get_sites for WP versions less than 4.6
*
* @since 1.0.12
* @access public
* @return array The array of blog ids.
*/
public static function get_network_ids( $args = array() ) {
global $wp_version;
if ( version_compare( $wp_version, '4.6', '<' ) ) {
$blogs = wp_get_sites( $args );
if ( ! empty( $blogs ) ) {
foreach ( $blogs as $key => $blog ) {
$blogs[ $key ] = $blog[ 'blog_id' ];
}
}
}
else {
$args[ 'fields' ] = 'ids';
$blogs = get_sites( $args );
}
return $blogs;
}
/**
* Gets the count of active litespeed cache plugins on multisite.
*
* @since 1.0.12
* @access private
*/
private static function get_network_count() {
$count = get_site_transient( self::NETWORK_TRANSIENT_COUNT );
if ( $count !== false ) {
return intval( $count );
}
// need to update
$default = array();
$count = 0;
$sites = self::get_network_ids( array( 'deleted' => 0 ) );
if ( empty( $sites ) ) {
return false;
}
foreach ( $sites as $site ) {
$bid = is_object( $site ) && property_exists( $site, 'blog_id' ) ? $site->blog_id : $site;
$plugins = get_blog_option( $bid , 'active_plugins', $default );
if ( in_array( LSCWP_BASENAME, $plugins, true ) ) {
$count++;
}
}
/**
* In case this is called outside the admin page
* @see https://codex.wordpress.org/Function_Reference/is_plugin_active_for_network
* @since 2.0
*/
if ( ! function_exists( 'is_plugin_active_for_network' ) ) {
require_once( ABSPATH . '/wp-admin/includes/plugin.php' );
}
if ( is_plugin_active_for_network( LSCWP_BASENAME ) ) {
$count++;
}
return $count;
}
/**
* Is this deactivate call the last active installation on the multisite network?
*
* @since 1.0.12
* @access private
*/
private static function is_deactivate_last() {
$count = self::get_network_count();
if ( $count === false ) {
return false;
}
if ( $count !== 1 ) {
// Not deactivating the last one.
$count--;
set_site_transient( self::NETWORK_TRANSIENT_COUNT, $count, DAY_IN_SECONDS );
return false;
}
delete_site_transient( self::NETWORK_TRANSIENT_COUNT );
return true;
}
/**
* The deactivation hook callback.
*
* Initializes all clean up functionalities.
*
* @since 1.0.0
* @access public
*/
public static function register_deactivation() {
Task::destroy();
! defined( 'LSCWP_LOG_TAG' ) && define( 'LSCWP_LOG_TAG', 'Deactivate_' . get_current_blog_id() );
Purge::purge_all();
if ( is_multisite() ) {
if ( ! self::is_deactivate_last() ) {
if ( is_network_admin() ) {
// Still other activated subsite left, set .htaccess with only CacheLookUp
try {
Htaccess::cls()->insert_ls_wrapper();
} catch ( \Exception $ex ) {
Admin_Display::error( $ex->getMessage() );
}
}
return;
}
}
/* 1) wp-config.php; */
try {
self::cls()->_manage_wp_cache_const( false );
} catch ( \Exception $ex ) {
error_log('In wp-config.php: WP_CACHE could not be set to false during deactivation!') ;
Admin_Display::error( $ex->getMessage() );
}
/* 2) adv-cache.php; Dropped in v3.0.4 */
/* 3) object-cache.php; */
Object_Cache::cls()->del_file();
/* 4) .htaccess; */
try {
Htaccess::cls()->clear_rules();
} catch ( \Exception $ex ) {
Admin_Display::error( $ex->getMessage() );
}
/* 5) .litespeed_conf.dat; */
self::_del_conf_data_file();
// delete in case it's not deleted prior to deactivation.
GUI::dismiss_whm();
}
/**
* Manage related files based on plugin latest conf
*
* NOTE: Only trigger this in backend admin access for efficiency concern
*
* Handle files:
* 1) wp-config.php;
* 2) adv-cache.php;
* 3) object-cache.php;
* 4) .htaccess;
* 5) .litespeed_conf.dat;
*
* @since 3.0
* @access public
*/
public function update_files() {
Debug2::debug( '🗂️ [Activation] update_files' );
// Update cache setting `_CACHE`
$this->cls( 'Conf' )->define_cache();
// Site options applied already
$options = $this->get_options();
/* 1) wp-config.php; */
try {
$this->_manage_wp_cache_const( $options[ self::_CACHE ] );
} catch ( \Exception $ex ) {
// Add msg to admin page or CLI
Admin_Display::error( $ex->getMessage() );
}
/* 2) adv-cache.php; Dropped in v3.0.4 */
/* 3) object-cache.php; */
if ( $options[ self::O_OBJECT ] && ( ! $options[ self::O_DEBUG_DISABLE_ALL ] || is_multisite() ) ) {
$this->cls( 'Object_Cache' )->update_file( $options );
}
else {
$this->cls( 'Object_Cache' )->del_file(); // Note: because it doesn't reconnect, which caused setting page OC option changes delayed, thus may meet Connect Test Failed issue (Next refresh will correct it). Not a big deal, will keep as is.
}
/* 4) .htaccess; */
try {
$this->cls( 'Htaccess' )->update( $options );
} catch ( \Exception $ex ) {
Admin_Display::error( $ex->getMessage() );
}
/* 5) .litespeed_conf.dat; */
if ( ( $options[ self::O_GUEST ] || $options[ self::O_OBJECT ] ) && ( ! $options[ self::O_DEBUG_DISABLE_ALL ] || is_multisite() ) ) {
$this->_update_conf_data_file( $options );
}
}
/**
* Delete data conf file
*
* @since 4.1
*/
private static function _del_conf_data_file() {
if ( file_exists( self::$_data_file ) ) {
unlink( self::$_data_file );
}
}
/**
* Update data conf file for guest mode & object cache
*
* @since 4.1
*/
private function _update_conf_data_file( $options ) {
$ids = array();
if ( $options[ self::O_OBJECT ] ) {
$this_ids = array(
self::O_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,
self::O_DEBUG_IPS,
self::O_UTIL_NO_HTTPS_VARY,
self::O_GUEST_UAS,
self::O_GUEST_IPS,
);
$ids = array_merge( $ids, $this_ids );
}
$data = array();
foreach ( $ids as $v ) {
$data[ $v ] = $options[ $v ];
}
$data = json_encode( $data );
$old_data = File::read( self::$_data_file );
if ( $old_data != $data ) {
defined( 'LSCWP_LOG' ) && Debug2::debug( '[Activation] Updating .litespeed_conf.dat' );
File::save( self::$_data_file, $data );
}
}
/**
* Update the WP_CACHE variable in the wp-config.php file.
*
* If enabling, check if the variable is defined, and if not, define it.
* Vice versa for disabling.
*
* @since 1.0.0
* @since 3.0 Refactored
* @access private
*/
private function _manage_wp_cache_const( $enable ) {
if ( $enable ) {
if ( defined( 'WP_CACHE' ) && WP_CACHE ) {
return false;
}
}
elseif ( ! defined( 'WP_CACHE' ) || ( defined( 'WP_CACHE' ) && ! WP_CACHE ) ) {
return false;
}
if ( apply_filters( 'litespeed_wpconfig_readonly', false ) ) {
throw new \Exception( 'wp-config file is forbidden to modify due to API hook: litespeed_wpconfig_readonly' );
}
/**
* Follow WP's logic to locate wp-config file
* @see wp-load.php
*/
$conf_file = ABSPATH . 'wp-config.php';
if ( ! file_exists( $conf_file ) ) {
$conf_file = dirname( ABSPATH ) . '/wp-config.php';
}
$content = File::read( $conf_file );
if ( ! $content ) {
throw new \Exception( 'wp-config file content is empty: ' . $conf_file );
}
// Remove the line `define('WP_CACHE', true/false);` first
if ( defined( 'WP_CACHE' ) ) {
$content = preg_replace( '/define\(\s*([\'"])WP_CACHE\1\s*,\s*\w+\s*\)\s*;/sU', '', $content );
}
// Insert const
if ( $enable ) {
$content = preg_replace( '/^<\?php/', "<?php\ndefine( 'WP_CACHE', true );", $content );
}
$res = File::save( $conf_file, $content, false, false, false );
if ( $res !== true ) {
throw new \Exception( 'wp-config.php operation failed when changing `WP_CACHE` const: ' . $res );
}
return true;
}
/**
* Handle auto update
*
* @since 2.7.2
* @access public
*/
public function auto_update() {
if ( ! $this->conf( Base::O_AUTO_UPGRADE ) ) {
return;
}
add_filter( 'auto_update_plugin', array( $this, 'auto_update_hook' ), 10, 2 );
}
/**
* Auto upgrade hook
*
* @since 3.0
* @access public
*/
public function auto_update_hook( $update, $item ) {
if ( $item->slug == 'litespeed-cache' ) {
$auto_v = Cloud::version_check( 'auto_update_plugin' );
if ( ! empty( $auto_v[ 'latest' ] ) && ! empty( $item->new_version ) && $auto_v[ 'latest' ] === $item->new_version ) {
return true;
}
}
return $update; // Else, use the normal API response to decide whether to update or not
}
/**
* Upgrade LSCWP
*
* @since 2.9
* @access public
*/
public function upgrade() {
$plugin = Core::PLUGIN_FILE;
/**
* @see wp-admin/update.php
*/
include_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
include_once ABSPATH . 'wp-admin/includes/file.php';
include_once ABSPATH . 'wp-admin/includes/misc.php';
try {
ob_start();
$skin = new \WP_Ajax_Upgrader_Skin();
$upgrader = new \Plugin_Upgrader( $skin );
$result = $upgrader->upgrade( $plugin );
if ( ! is_plugin_active( $plugin ) ) {// todo: upgrade should reactivate the plugin again by WP. Need to check why disabled after upgraded.
activate_plugin( $plugin, '', is_multisite() );
}
ob_end_clean();
} catch ( \Exception $e ) {
Admin_Display::error( __( 'Failed to upgrade.', 'litespeed-cache' ) );
return;
}
if ( is_wp_error( $result ) ) {
Admin_Display::error( __( 'Failed to upgrade.', 'litespeed-cache' ) );
return;
}
Admin_Display::succeed( __( 'Upgraded successfully.', 'litespeed-cache' ) );
}
/**
* Detect if the plugin is active or not
*
* @since 1.0
*/
public function dash_notifier_is_plugin_active( $plugin ) {
include_once( ABSPATH . 'wp-admin/includes/plugin.php' );
$plugin_path = $plugin . '/' . $plugin . '.php';
return is_plugin_active( $plugin_path );
}
/**
* Detect if the plugin is installed or not
*
* @since 1.0
*/
public function dash_notifier_is_plugin_installed( $plugin ) {
include_once( ABSPATH . 'wp-admin/includes/plugin.php' );
$plugin_path = $plugin . '/' . $plugin . '.php';
$valid = validate_plugin( $plugin_path );
return ! is_wp_error( $valid );
}
/**
* Grab a plugin info from WordPress
*
* @since 1.0
*/
public function dash_notifier_get_plugin_info( $slug ) {
include_once( ABSPATH . 'wp-admin/includes/plugin-install.php' );
$result = plugins_api( 'plugin_information', array( 'slug' => $slug ) );
if ( is_wp_error( $result ) ) {
return false;
}
return $result;
}
/**
* Install the 3rd party plugin
*
* @since 1.0
*/
public function dash_notifier_install_3rd() {
! defined( 'SILENCE_INSTALL' ) && define( 'SILENCE_INSTALL', true );
$slug = ! empty( $_GET[ 'plugin' ] ) ? $_GET[ 'plugin' ] : false;
// Check if plugin is installed already
if ( ! $slug || $this->dash_notifier_is_plugin_active( $slug ) ) {
return;
}
/**
* @see wp-admin/update.php
*/
include_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
include_once ABSPATH . 'wp-admin/includes/file.php';
include_once ABSPATH . 'wp-admin/includes/misc.php';
$plugin_path = $slug . '/' . $slug . '.php';
if ( ! $this->dash_notifier_is_plugin_installed( $slug ) ) {
$plugin_info = $this->dash_notifier_get_plugin_info( $slug );
if ( ! $plugin_info ) {
return;
}
// Try to install plugin
try {
ob_start();
$skin = new \Automatic_Upgrader_Skin();
$upgrader = new \Plugin_Upgrader( $skin );
$result = $upgrader->install( $plugin_info->download_link );
ob_end_clean();
} catch ( \Exception $e ) {
return;
}
}
if ( ! is_plugin_active( $plugin_path ) ) {
activate_plugin( $plugin_path );
}
}
/**
* Handle all request actions from main cls
*
* @since 2.9
* @access public
*/
public function handler() {
$type = Router::verify_type();
switch ( $type ) {
case self::TYPE_UPGRADE :
$this->upgrade();
break;
case self::TYPE_INSTALL_3RD :
$this->dash_notifier_install_3rd();
break;
case self::TYPE_DISMISS_RECOMMENDED:
$summary = Cloud::get_summary();
$summary[ 'news.new' ] = 0;
Cloud::save_summary( $summary );
break;
case self::TYPE_INSTALL_ZIP:
$summary = Cloud::get_summary();
if ( ! empty( $summary[ 'news.zip' ] ) ) {
$summary[ 'news.new' ] = 0;
Cloud::save_summary( $summary );
$this->cls( 'Debug2' )->beta_test( $summary[ 'zip' ] );
}
break;
default:
break;
}
Admin::redirect();
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,341 @@
<?php
/**
* The admin settings handler of the plugin.
*
*
* @since 1.1.0
* @package LiteSpeed
* @subpackage LiteSpeed/src
* @author LiteSpeed Technologies <info@litespeedtech.com>
*/
namespace LiteSpeed;
defined( 'WPINC' ) || exit;
class Admin_Settings extends Base {
const ENROLL = '_settings-enroll';
/**
* Save settings
*
* Both $_POST and CLI can use this way
*
* Import will directly call conf.cls
*
* @since 3.0
* @access public
*/
public function save( $raw_data ) {
Debug2::debug( '[Settings] saving' );
if ( empty( $raw_data[ self::ENROLL ] ) ) {
exit( 'No fields' );
}
$raw_data = Admin::cleanup_text( $raw_data );
// Convert data to config format
$the_matrix = array();
foreach ( array_unique( $raw_data[ self::ENROLL ] ) as $id ) {
$child = false;
// Drop array format
if ( strpos( $id, '[' ) !== false ) {
if ( strpos( $id, self::O_CDN_MAPPING ) === 0 || strpos( $id, self::O_CRAWLER_COOKIES ) === 0 ) { // CDN child | Cookie Crawler settings
$child = substr( $id, strpos( $id, '[' ) + 1, strpos( $id, ']' ) - strpos( $id, '[' ) - 1 );
$id = substr( $id, 0, strpos( $id, '[' ) ); // Drop ending []; Compatible with xx[0] way from CLI
}
else {
$id = substr( $id, 0, strpos( $id, '[' ) ); // Drop ending []
}
}
if ( ! array_key_exists( $id, self::$_default_options ) ) {
continue;
}
// Validate $child
if ( $id == self::O_CDN_MAPPING ) {
if ( ! in_array( $child, array(
self::CDN_MAPPING_URL,
self::CDN_MAPPING_INC_IMG,
self::CDN_MAPPING_INC_CSS,
self::CDN_MAPPING_INC_JS,
self::CDN_MAPPING_FILETYPE,
) ) ) {
continue;
}
}
if ( $id == self::O_CRAWLER_COOKIES ) {
if ( ! in_array( $child, array(
self::CRWL_COOKIE_NAME,
self::CRWL_COOKIE_VALS,
) ) ) {
continue;
}
}
$data = false;
if ( $child ) {
$data = ! empty( $raw_data[ $id ][ $child ] ) ? $raw_data[ $id ][ $child ] : false; // []=xxx or [0]=xxx
}
else {
$data = ! empty( $raw_data[ $id ] ) ? $raw_data[ $id ] : false;
}
/**
* Sanitize the value
*/
if ( $id == self::O_CDN_MAPPING || $id == self::O_CRAWLER_COOKIES ) {
// Use existing in queue data if existed (Only available when $child != false)
$data2 = array_key_exists( $id, $the_matrix ) ? $the_matrix[ $id ] : ( defined( 'WP_CLI' ) && WP_CLI ? $this->conf( $id ) : array() );
}
switch ( $id ) {
case self::O_CDN_MAPPING:
/**
* CDN setting
*
* Raw data format:
* cdn-mapping[url][] = 'xxx'
* cdn-mapping[url][2] = 'xxx2'
* cdn-mapping[inc_js][] = 1
*
* Final format:
* cdn-mapping[ 0 ][ url ] = 'xxx'
* cdn-mapping[ 2 ][ url ] = 'xxx2'
*/
if ( $data ) foreach ( $data as $k => $v ) {
if ( $child == self::CDN_MAPPING_FILETYPE ) {
$v = Utility::sanitize_lines( $v );
}
elseif ( in_array( $child, array(
self::CDN_MAPPING_INC_IMG,
self::CDN_MAPPING_INC_CSS,
self::CDN_MAPPING_INC_JS,
) ) ) {
// Because these can't be auto detected in `config->update()`, need to format here
$v = $v === 'false' ? 0 : (bool) $v;
}
if ( empty( $data2[ $k ] ) ) {
$data2[ $k ] = array();
}
$data2[ $k ][ $child ] = $v;
}
$data = $data2;
break;
case self::O_CRAWLER_COOKIES:
/**
* Cookie Crawler setting
* Raw Format:
* crawler-cookies[name][] = xxx
* crawler-cookies[name][2] = xxx2
* crawler-cookies[vals][] = xxx
*
* todo: need to allow null for values
*
* Final format:
* crawler-cookie[ 0 ][ name ] = 'xxx'
* crawler-cookie[ 0 ][ vals ] = 'xxx'
* crawler-cookie[ 2 ][ name ] = 'xxx2'
*
* empty line for `vals` use literal `_null`
*/
if ( $data ) foreach ( $data as $k => $v ) {
if ( $child == self::CRWL_COOKIE_VALS ) {
$v = Utility::sanitize_lines( $v );
}
if ( empty( $data2[ $k ] ) ) {
$data2[ $k ] = array();
}
$data2[ $k ][ $child ] = $v;
}
$data = $data2;
break;
// Cache exclude cat
case self::O_CACHE_EXC_CAT:
$data2 = array();
$data = Utility::sanitize_lines( $data );
foreach ( $data as $v ) {
$cat_id = get_cat_ID( $v );
if ( ! $cat_id ) {
continue;
}
$data2[] = $cat_id;
}
$data = $data2;
break;
// Cache exclude tag
case self::O_CACHE_EXC_TAG :
$data2 = array();
$data = Utility::sanitize_lines( $data );
foreach ( $data as $v ) {
$term = get_term_by( 'name', $v, 'post_tag' );
if ( ! $term ) {
// todo: can show the error in admin error msg
continue;
}
$data2[] = $term->term_id;
}
$data = $data2;
break;
default:
break;
}
$the_matrix[ $id ] = $data;
}
// Special handler for CDN/Crawler 2d list to drop empty rows
foreach ( $the_matrix as $id => $data ) {
/**
* cdn-mapping[ 0 ][ url ] = 'xxx'
* cdn-mapping[ 2 ][ url ] = 'xxx2'
*
* crawler-cookie[ 0 ][ name ] = 'xxx'
* crawler-cookie[ 0 ][ vals ] = 'xxx'
* crawler-cookie[ 2 ][ name ] = 'xxx2'
*/
if ( $id == self::O_CDN_MAPPING || $id == self::O_CRAWLER_COOKIES ) {
// Drop this line if all children elements are empty
foreach ( $data as $k => $v ) {
foreach ( $v as $v2 ) {
if ( $v2 ) {
continue 2;
}
}
// If hit here, means all empty
unset( $the_matrix[ $id ][ $k ] );
}
}
// Don't allow repeated cookie name
if ( $id == self::O_CRAWLER_COOKIES ) {
$existed = array();
foreach ( $the_matrix[ $id ] as $k => $v ) {
if ( ! $v[ self::CRWL_COOKIE_NAME ] || in_array( $v[ self::CRWL_COOKIE_NAME ], $existed ) ) { // Filter repeated or empty name
unset( $the_matrix[ $id ][ $k ] );
continue;
}
$existed[] = $v[ self::CRWL_COOKIE_NAME ];
}
}
// CDN mapping allow URL values repeated
// if ( $id == self::O_CDN_MAPPING ) {}
}
// id validation will be inside
$this->cls( 'Conf' )->update_confs( $the_matrix );
$msg = __( 'Options saved.', 'litespeed-cache' );
Admin_Display::succeed( $msg );
}
/**
* Parses any changes made by the network admin on the network settings.
*
* @since 3.0
* @access public
*/
public function network_save( $raw_data ) {
Debug2::debug( '[Settings] network saving' );
if ( empty( $raw_data[ self::ENROLL ] ) ) {
exit( 'No fields' );
}
$raw_data = Admin::cleanup_text( $raw_data );
foreach ( array_unique( $raw_data[ self::ENROLL ] ) as $id ) {
// Append current field to setting save
if ( ! array_key_exists( $id, self::$_default_site_options ) ) {
continue;
}
$data = ! empty( $raw_data[ $id ] ) ? $raw_data[ $id ] : false;
// id validation will be inside
$this->cls( 'Conf' )->network_update( $id, $data );
}
// Update related files
Activation::cls()->update_files();
$msg = __( 'Options saved.', 'litespeed-cache' );
Admin_Display::succeed( $msg );
}
/**
* Hooked to the wp_redirect filter.
* This will only hook if there was a problem when saving the widget.
*
* @since 1.1.3
* @access public
* @param string $location The location string.
* @return string the updated location string.
*/
public static function widget_save_err( $location ) {
return str_replace( '?message=0', '?error=0', $location ) ;
}
/**
* Hooked to the widget_update_callback filter.
* Validate the LiteSpeed Cache settings on edit widget save.
*
* @since 1.1.3
* @access public
* @param array $instance The new settings.
* @param array $new_instance
* @param array $old_instance The original settings.
* @param WP_Widget $widget The widget
* @return mixed Updated settings on success, false on error.
*/
public static function validate_widget_save( $instance, $new_instance, $old_instance, $widget ) {
if ( empty( $new_instance ) ) {
return $instance;
}
if ( ! isset( $new_instance[ ESI::WIDGET_O_ESIENABLE ] ) || ! isset( $new_instance[ ESI::WIDGET_O_TTL ] ) ) {
return $instance;
}
$esi = intval( $new_instance[ ESI::WIDGET_O_ESIENABLE ] ) % 3;
$ttl = (int) $new_instance[ ESI::WIDGET_O_TTL ];
if ( $ttl != 0 && $ttl < 30 ) {
add_filter( 'wp_redirect', __CLASS__ . '::widget_save_err' ) ;
return false ; // invalid ttl.
}
if ( empty( $instance[ Conf::OPTION_NAME ] ) ) {// todo: to be removed
$instance[ Conf::OPTION_NAME ] = array() ;
}
$instance[ Conf::OPTION_NAME ][ ESI::WIDGET_O_ESIENABLE ] = $esi ;
$instance[ Conf::OPTION_NAME ][ ESI::WIDGET_O_TTL ] = $ttl ;
$current = ! empty( $old_instance[ Conf::OPTION_NAME ] ) ? $old_instance[ Conf::OPTION_NAME ] : false ;
if ( ! strpos( $_SERVER[ 'HTTP_REFERER' ], '/wp-admin/customize.php') ) {
if ( ! $current || $esi != $current[ ESI::WIDGET_O_ESIENABLE ] ) {
Purge::purge_all( 'Wdiget ESI_enable changed' ) ;
}
elseif ( $ttl != 0 && $ttl != $current[ ESI::WIDGET_O_TTL ] ) {
Purge::add( Tag::TYPE_WIDGET . $widget->id ) ;
}
Purge::purge_all( 'Wdiget saved' ) ;
}
return $instance ;
}
}

View File

@@ -0,0 +1,200 @@
<?php
/**
* The admin-panel specific functionality of the plugin.
*
*
* @since 1.0.0
* @package LiteSpeed_Cache
* @subpackage LiteSpeed_Cache/admin
* @author LiteSpeed Technologies <info@litespeedtech.com>
*/
namespace LiteSpeed;
defined( 'WPINC' ) || exit;
class Admin extends Root {
const PAGE_EDIT_HTACCESS = 'litespeed-edit-htaccess';
/**
* Initialize the class and set its properties.
* Run in hook `after_setup_theme` when is_admin()
*
* @since 1.0.0
*/
public function __construct() {
// Define LSCWP_MU_PLUGIN if is mu-plugins
if ( defined( 'WPMU_PLUGIN_DIR' ) && dirname( LSCWP_DIR ) == WPMU_PLUGIN_DIR ) {
define( 'LSCWP_MU_PLUGIN', true );
}
// Additional litespeed assets on admin display
// Also register menu
$this->cls( 'Admin_Display' );
// initialize admin actions
add_action( 'admin_init', array( $this, 'admin_init' ) );
// add link to plugin list page
add_filter( 'plugin_action_links_' . LSCWP_BASENAME, array( $this->cls( 'Admin_Display' ), 'add_plugin_links' ) );
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[] = 'upgrader_process_complete';
$purge_all_events[] = 'admin_action_do-plugin-upgrade';
}
foreach ( $purge_all_events as $event ) {
// Don't allow hook to update_option bcos purge_all will cause infinite loop of update_option
if ( in_array( $event, array( 'update_option' ) ) ) {
continue;
}
add_action( $event, __NAMESPACE__ . '\Purge::purge_all' );
}
// add_filter( 'upgrader_pre_download', 'Purge::filter_with_purge_all' );
}
}
/**
* Callback that initializes the admin options for LiteSpeed Cache.
*
* @since 1.0.0
* @access public
*/
public function admin_init() {
Control::set_nocache( 'Admin page' );
// Hook attachment upload
if ( $this->conf( Base::O_IMG_OPTM_AUTO ) ) {
add_filter( 'wp_update_attachment_metadata', array( $this, 'wp_update_attachment_metadata' ), 9999, 2 );
}
$this->_proceed_admin_action();
// Terminate if user doesn't have the access to settings
if( is_network_admin() ) {
$capability = 'manage_network_options';
}
else {
$capability = 'manage_options';
}
if ( ! current_user_can($capability) ) {
return;
}
// Save setting from admin settings page
// NOTE: cli will call `validate_plugin_settings` manually. Cron activation doesn't need to validate
// Add privacy policy
// @since 2.2.6
if ( function_exists( 'wp_add_privacy_policy_content' ) ) {
wp_add_privacy_policy_content( Core::NAME, Doc::privacy_policy() );
}
$this->cls( 'Media' )->after_admin_init();
do_action( 'litspeed_after_admin_init' );
if ( $this->cls( 'Router' )->esi_enabled() ) {
add_action( 'in_widget_form', array( $this->cls( 'Admin_Display' ), 'show_widget_edit' ), 100, 3 );
add_filter( 'widget_update_callback', __NAMESPACE__ . '\Admin_Settings::validate_widget_save', 10, 4 );
}
}
/**
* Handle attachment update
* @since 4.0
*/
public function wp_update_attachment_metadata( $data, $post_id ) {
$this->cls( 'Img_Optm' )->wp_update_attachment_metadata( $data, $post_id );
return $data;
}
/**
* Run litespeed admin actions
*
* @since 1.1.0
*/
private function _proceed_admin_action() {
// handle actions
switch ( Router::get_action() ) {
case Router::ACTION_SAVE_SETTINGS:
$this->cls( 'Admin_Settings' )->save( $_POST );
break;
// Save network settings
case Router::ACTION_SAVE_SETTINGS_NETWORK:
$this->cls( 'Admin_Settings' )->network_save( $_POST );
break;
default:
break;
}
}
/**
* Clean up the input string of any extra slashes/spaces.
*
* @since 1.0.4
* @access public
* @param string $input The input string to clean.
* @return string The cleaned up input.
*/
public static function cleanup_text( $input ) {
if ( is_array( $input ) ) {
return array_map( __CLASS__ . '::cleanup_text', $input );
}
return stripslashes( trim( $input ) );
}
/**
* After a LSCWP_CTRL action, need to redirect back to the same page
* without the nonce and action in the query string.
*
* @since 1.0.12
* @access public
* @global string $pagenow
*/
public static function redirect( $url = false ) {
global $pagenow;
if ( ! empty( $_GET[ '_litespeed_ori' ] ) ) {
wp_redirect( $_SERVER[ 'HTTP_REFERER' ] );
exit;
}
$qs = '';
if ( ! $url ) {
if ( ! empty( $_GET ) ) {
if ( isset( $_GET[ Router::ACTION ] ) ) {
unset( $_GET[ Router::ACTION ] );
}
if ( isset( $_GET[ Router::NONCE ] ) ) {
unset( $_GET[ Router::NONCE ] );
}
if ( isset( $_GET[ Router::TYPE ] ) ) {
unset( $_GET[ Router::TYPE ] );
}
if ( isset( $_GET[ 'litespeed_i' ] ) ) {
unset( $_GET[ 'litespeed_i' ] );
}
if ( ! empty( $_GET ) ) {
$qs = '?' . http_build_query( $_GET );
}
}
if ( is_network_admin() ) {
$url = network_admin_url( $pagenow . $qs );
}
else {
$url = admin_url( $pagenow . $qs );
}
}
wp_redirect( $url );
exit;
}
}

View File

@@ -0,0 +1,236 @@
<?php
/**
* The plugin API class.
*
* @since 1.1.3
* @since 1.4 Moved into /inc
* @package LiteSpeed
* @subpackage LiteSpeed/inc
* @author LiteSpeed Technologies <info@litespeedtech.com>
*/
namespace LiteSpeed;
defined( 'WPINC' ) || exit;
class API extends Base {
const VERSION = Core::VER;
const TYPE_FEED = Tag::TYPE_FEED ;
const TYPE_FRONTPAGE = Tag::TYPE_FRONTPAGE ;
const TYPE_HOME = Tag::TYPE_HOME ;
const TYPE_PAGES = Tag::TYPE_PAGES ;
const TYPE_PAGES_WITH_RECENT_POSTS = Tag::TYPE_PAGES_WITH_RECENT_POSTS ;
const TYPE_HTTP = Tag::TYPE_HTTP ;
const TYPE_ARCHIVE_POSTTYPE = Tag::TYPE_ARCHIVE_POSTTYPE ;
const TYPE_ARCHIVE_TERM = Tag::TYPE_ARCHIVE_TERM ;
const TYPE_AUTHOR = Tag::TYPE_AUTHOR ;
const TYPE_ARCHIVE_DATE = Tag::TYPE_ARCHIVE_DATE ;
const TYPE_BLOG = Tag::TYPE_BLOG ;
const TYPE_LOGIN = Tag::TYPE_LOGIN ;
const TYPE_URL = Tag::TYPE_URL ;
const TYPE_ESI = Tag::TYPE_ESI ;
const PARAM_NAME = ESI::PARAM_NAME ;
const WIDGET_O_ESIENABLE = ESI::WIDGET_O_ESIENABLE ;
const WIDGET_O_TTL = ESI::WIDGET_O_TTL ;
/**
* Instance
*
* @since 3.0
*/
public function __construct() {
}
/**
* Define hooks to be used in other plugins.
*
* The benefit to use hooks other than functions is no need to detech if LSCWP enabled and function existed or not anymore
*
* @since 3.0
*/
public function init() {
/**
* Init
*/
// Action `litespeed_init` // @previous API::hook_init( $hook )
/**
* Conf
*/
add_filter( 'litespeed_conf', array( $this, 'conf' ) ); // @previous API::config($id)
// Action `litespeed_conf_append` // @previous API::conf_append( $name, $default )
add_action( 'litespeed_conf_multi_switch', __NAMESPACE__ . '\Base::set_multi_switch', 10, 2 );
// Action ``litespeed_conf_force` // @previous API::force_option( $k, $v )
/**
* Cache Control Hooks
*/
// Action `litespeed_control_finalize` // @previous API::hook_control($tags) && action `litespeed_api_control`
add_action( 'litespeed_control_set_private', __NAMESPACE__ . '\Control::set_private' ); // @previous API::set_cache_private()
add_action( 'litespeed_control_set_nocache', __NAMESPACE__ . '\Control::set_nocache' ); // @previous API::set_nocache( $reason = false )
add_action( 'litespeed_control_set_cacheable', array( $this, 'set_cacheable' ) ); // Might needed if not call hook `wp` // @previous API::set_cacheable( $reason )
add_action( 'litespeed_control_force_cacheable', __NAMESPACE__ . '\Control::force_cacheable' ); // Set cache status to force cacheable ( Will ignore most kinds of non-cacheable conditions ) // @previous API::set_force_cacheable( $reason )
add_action( 'litespeed_control_force_public', __NAMESPACE__ . '\Control::set_public_forced' ); // Set cache to force public cache if cacheable ( Will ignore most kinds of non-cacheable conditions ) // @previous API::set_force_public( $reason )
add_filter( 'litespeed_control_cacheable', __NAMESPACE__ . '\Control::is_cacheable', 3 ); // Note: Read-Only. Directly append to this filter won't work. Call actions above to set cacheable or not // @previous API::not_cacheable()
add_action( 'litespeed_control_set_ttl', __NAMESPACE__ . '\Control::set_custom_ttl', 10, 2 ); // @previous API::set_ttl( $val )
add_filter( 'litespeed_control_ttl', array( $this, 'get_ttl' ), 3 ); // @previous API::get_ttl()
/**
* Tag Hooks
*/
// Action `litespeed_tag_finalize` // @previous API::hook_tag( $hook )
add_action( 'litespeed_tag', __NAMESPACE__ . '\Tag::add' ); // Shorter alias of `litespeed_tag_add`
add_action( 'litespeed_tag_post', __NAMESPACE__ . '\Tag::add_post' ); // Shorter alias of `litespeed_tag_add_post`
add_action( 'litespeed_tag_widget', __NAMESPACE__ . '\Tag::add_widget' ); // Shorter alias of `litespeed_tag_add_widget`
add_action( 'litespeed_tag_private', __NAMESPACE__ . '\Tag::add_private' ); // Shorter alias of `litespeed_tag_add_private`
add_action( 'litespeed_tag_private_esi', __NAMESPACE__ . '\Tag::add_private_esi' ); // Shorter alias of `litespeed_tag_add_private_esi`
add_action( 'litespeed_tag_add', __NAMESPACE__ . '\Tag::add' ); // @previous API::tag_add( $tag )
add_action( 'litespeed_tag_add_post', __NAMESPACE__ . '\Tag::add_post' );
add_action( 'litespeed_tag_add_widget', __NAMESPACE__ . '\Tag::add_widget' );
add_action( 'litespeed_tag_add_private', __NAMESPACE__ . '\Tag::add_private' ); // @previous API::tag_add_private( $tags )
add_action( 'litespeed_tag_add_private_esi', __NAMESPACE__ . '\Tag::add_private_esi' );
/**
* Purge Hooks
*/
// Action `litespeed_purge_finalize` // @previous API::hook_purge($tags)
add_action( 'litespeed_purge', __NAMESPACE__ . '\Purge::add' ); // @previous API::purge($tags)
add_action( 'litespeed_purge_all', __NAMESPACE__ . '\Purge::purge_all' );
add_action( 'litespeed_purge_post', array( $this, 'purge_post' ) ); // @previous API::purge_post( $pid )
add_action( 'litespeed_purge_posttype', __NAMESPACE__ . '\Purge::purge_posttype' );
add_action( 'litespeed_purge_url', array( $this, 'purge_url' ) );
add_action( 'litespeed_purge_widget', __NAMESPACE__ . '\Purge::purge_widget' );
add_action( 'litespeed_purge_esi', __NAMESPACE__ . '\Purge::purge_esi' );
add_action( 'litespeed_purge_private', __NAMESPACE__ . '\Purge::add_private' ); // @previous API::purge_private( $tags )
add_action( 'litespeed_purge_private_esi', __NAMESPACE__ . '\Purge::add_private_esi' );
add_action( 'litespeed_purge_private_all', __NAMESPACE__ . '\Purge::add_private_all' ); // @previous API::purge_private_all()
// Action `litespeed_api_purge_post` // Triggered when purge a post // @previous API::hook_purge_post($hook)
// Action `litespeed_purged_all` // Triggered after purged all.
add_action( 'litespeed_purge_all_object', __NAMESPACE__ . '\Purge::purge_all_object' );
add_action( 'litespeed_purge_ucss', __NAMESPACE__ . '\Purge::purge_ucss' );
/**
* ESI
*/
// Action `litespeed_nonce` // @previous API::nonce_action( $action ) & API::nonce( $action = -1, $defence_for_html_filter = true ) // NOTE: only available after `init` hook
add_filter( 'litespeed_esi_status', array( $this, 'esi_enabled' ) ); // Get ESI enable status // @previous API::esi_enabled()
add_filter( 'litespeed_esi_url', array( $this, 'sub_esi_block' ), 10, 8 ); // Generate ESI block url // @previous API::esi_url( $block_id, $wrapper, $params = array(), $control = 'private,no-vary', $silence = false, $preserved = false, $svar = false, $inline_val = false )
// Filter `litespeed_widget_default_options` // Hook widget default settings value. Currently used in Woo 3rd // @previous API::hook_widget_default_options( $hook )
// Filter `litespeed_esi_params` // @previous API::hook_esi_param( $hook )
// Action `litespeed_tpl_normal` // @previous API::hook_tpl_not_esi($hook) && Action `litespeed_is_not_esi_template`
// Action `litespeed_esi_load-$block` // @usage add_action( 'litespeed_esi_load-' . $block, $hook ) // @previous API::hook_tpl_esi($block, $hook)
add_action( 'litespeed_esi_combine', __NAMESPACE__ . '\ESI::combine' );
/**
* Vary
*
* To modify default vary, There are two ways: Action `litespeed_vary_append` or Filter `litespeed_vary`
*/
add_action( 'litespeed_vary_ajax_force', __NAMESPACE__ . '\Vary::can_ajax_vary' ); // API::force_vary() -> Action `litespeed_vary_ajax_force` // Force finalize vary even if its in an AJAX call
// Filter `litespeed_vary_curr_cookies` to generate current in use vary, which will be used for response vary header.
// Filter `litespeed_vary_cookies` to register the final vary cookies, which will be written to rewrite rule. (litespeed_vary_curr_cookies are always equal to or less than litespeed_vary_cookies)
// Filter `litespeed_vary` // Previous API::hook_vary_finalize( $hook )
add_action( 'litespeed_vary_no', __NAMESPACE__ . '\Control::set_no_vary' ); // API::set_cache_no_vary() -> Action `litespeed_vary_no` // Set cache status to no vary
// add_filter( 'litespeed_is_mobile', __NAMESPACE__ . '\Control::is_mobile' ); // API::set_mobile() -> Filter `litespeed_is_mobile`
/**
* Cloud
*/
add_filter( 'litespeed_is_from_cloud', array( $this, 'is_from_cloud' ) ); // Check if current request is from QC (usally its to check REST access) // @see https://wordpress.org/support/topic/image-optimization-not-working-3/
/**
* Media
*/
add_action( 'litespeed_media_reset', __NAMESPACE__ . '\Media::delete_attachment' ); // Reset one media row
/**
* GUI
*/
// API::clean_wrapper_begin( $counter = false ) -> Filter `litespeed_clean_wrapper_begin` // Start a to-be-removed html wrapper
add_filter( 'litespeed_clean_wrapper_begin', __NAMESPACE__ . '\GUI::clean_wrapper_begin' );
// API::clean_wrapper_end( $counter = false ) -> Filter `litespeed_clean_wrapper_end` // End a to-be-removed html wrapper
add_filter( 'litespeed_clean_wrapper_end', __NAMESPACE__ . '\GUI::clean_wrapper_end' );
/**
* Mist
*/
add_action( 'litespeed_debug', __NAMESPACE__ . '\Debug2::debug', 10, 2 ); // API::debug()-> Action `litespeed_debug`
add_action( 'litespeed_debug2', __NAMESPACE__ . '\Debug2::debug2', 10, 2 ); // API::debug2()-> Action `litespeed_debug2`
add_action( 'litespeed_disable_all', array( $this, '_disable_all' ) ); // API::disable_all( $reason ) -> Action `litespeed_disable_all`
add_action( 'litspeed_after_admin_init', array( $this, '_after_admin_init' ) );
}
/**
* API for admin related
*
* @since 3.0
* @access public
*/
public function _after_admin_init() {
/**
* GUI
*/
add_action( 'litespeed_setting_enroll', array( $this->cls( 'Admin_Display' ), 'enroll' ), 10, 4 ); // API::enroll( $id ) // Register a field in setting form to save
add_action( 'litespeed_build_switch', array( $this->cls( 'Admin_Display' ), 'build_switch' ) ); // API::build_switch( $id ) // Build a switch div html snippet
// API::hook_setting_content( $hook, $priority = 10, $args = 1 ) -> Action `litespeed_settings_content`
// API::hook_setting_tab( $hook, $priority = 10, $args = 1 ) -> Action `litespeed_settings_tab`
}
/**
* Disable All (Note: Not for direct call, always use Hooks)
*
* @since 2.9.7.2
* @access public
*/
public function _disable_all( $reason ) {
do_action( 'litespeed_debug', '[API] Disabled_all due to ' . $reason );
! defined( 'LITESPEED_DISABLE_ALL' ) && define( 'LITESPEED_DISABLE_ALL', true );
}
/**
* @since 3.0
*/
public static function vary_append_commenter() {
Vary::cls()->append_commenter() ;
}
/**
* Check if is from Cloud
*
* @since 4.2
*/
public function is_from_cloud() {
return $this->cls( 'Cloud' )->is_from_cloud();
}
public function purge_post( $pid ) {
$this->cls( 'Purge' )->purge_post( $pid );
}
public function purge_url( $url ) {
$this->cls( 'Purge' )->purge_url( $url );
}
public function set_cacheable( $reason = false ) {
$this->cls( 'Control' )->set_cacheable( $reason );
}
public function esi_enabled() {
return $this->cls( 'Router' )->esi_enabled();
}
public function get_ttl() {
return $this->cls( 'Control' )->get_ttl();
}
public function sub_esi_block( $block_id, $wrapper, $params = array(), $control = 'private,no-vary', $silence = false, $preserved = false, $svar = false, $inline_param = array() ) {
return $this->cls( 'ESI' )->sub_esi_block( $block_id, $wrapper, $params, $control, $silence, $preserved, $svar, $inline_param );
}
}

View File

@@ -0,0 +1,288 @@
<?php
/**
* The avatar cache class
*
* @since 3.0
* @package LiteSpeed
* @subpackage LiteSpeed/inc
* @author LiteSpeed Technologies <info@litespeedtech.com>
*/
namespace LiteSpeed;
defined( 'WPINC' ) || exit;
class Avatar extends Base {
const TYPE_GENERATE = 'generate';
private $_conf_cache_ttl;
private $_tb;
private $_avatar_realtime_gen_dict = array();
protected $_summary;
/**
* Init
*
* @since 1.4
*/
public function __construct() {
if ( ! $this->conf( self::O_DISCUSS_AVATAR_CACHE ) ) {
return;
}
Debug2::debug2( '[Avatar] init' );
$this->_tb = $this->cls( 'Data' )->tb( 'avatar' );
$this->_conf_cache_ttl = $this->conf( self::O_DISCUSS_AVATAR_CACHE_TTL );
add_filter( 'get_avatar_url', array( $this, 'crawl_avatar' ) );
$this->_summary = self::get_summary();
}
/**
* Check if need db table or not
*
* @since 3.0
* @access public
*/
public function need_db() {
if ( $this->conf( self::O_DISCUSS_AVATAR_CACHE ) ) {
return true;
}
return false;
}
/**
* Get gravatar URL from DB and regenarate
*
* @since 3.0
* @access public
*/
public function serve_static( $md5 ) {
global $wpdb;
Debug2::debug( '[Avatar] is avatar request' );
if ( strlen( $md5 ) !== 32 ) {
Debug2::debug( '[Avatar] wrong md5 ' . $md5 );
return;
}
$q = "SELECT url FROM `$this->_tb` WHERE md5=%s";
$url = $wpdb->get_var( $wpdb->prepare( $q, $md5 ) );
if ( ! $url ) {
Debug2::debug( '[Avatar] no matched url for md5 ' . $md5 );
return;
}
$url = $this->_generate( $url );
wp_redirect( $url );
exit;
}
/**
* Localize gravatar
*
* @since 3.0
* @access public
*/
public function crawl_avatar( $url ) {
if ( ! $url ) {
return $url;
}
// Check if its already in dict or not
if ( ! empty( $this->_avatar_realtime_gen_dict[ $url ] ) ) {
Debug2::debug2( '[Avatar] already in dict [url] ' . $url );
return $this->_avatar_realtime_gen_dict[ $url ];
}
$realpath = $this->_realpath( $url );
if ( file_exists( $realpath ) && time() - filemtime( $realpath ) <= $this->_conf_cache_ttl ) {
Debug2::debug2( '[Avatar] cache file exists [url] ' . $url );
return $this->_rewrite( $url, filemtime( $realpath ) );
}
if ( ! strpos( $url, 'gravatar.com' ) ) {
return $url;
}
// Send request
if ( ! empty( $this->_summary[ 'curr_request' ] ) && time() - $this->_summary[ 'curr_request' ] < 300 ) {
Debug2::debug2( '[Avatar] Bypass generating due to interval limit [url] ' . $url );
return $url;
}
// Generate immediately
$this->_avatar_realtime_gen_dict[ $url ] = $this->_generate( $url );
return $this->_avatar_realtime_gen_dict[ $url ];
}
/**
* Read last time generated info
*
* @since 3.0
* @access public
*/
public function queue_count() {
global $wpdb;
// If var not exists, mean table not exists // todo: not true
if ( ! $this->_tb ) {
return false;
}
$q = "SELECT COUNT(*) FROM `$this->_tb` WHERE dateline<" . ( time() - $this->_conf_cache_ttl );
return $wpdb->get_var( $q );
}
/**
* Get the final URL of local avatar
*
* Check from db also
*
* @since 3.0
*/
private function _rewrite( $url, $time = null ) {
return LITESPEED_STATIC_URL . '/avatar/' . $this->_filepath( $url ) . ( $time ? '?ver=' . $time : '' );
}
/**
* Generate realpath of the cache file
*
* @since 3.0
* @access private
*/
private function _realpath( $url ) {
return LITESPEED_STATIC_DIR . '/avatar/' . $this->_filepath( $url );
}
/**
* Get filepath
*
* @since 4.0
*/
private function _filepath( $url ) {
$filename = md5( $url ) . '.jpg';
if ( is_multisite() ) {
$filename = get_current_blog_id() . '/' . $filename;
}
return $filename;
}
/**
* Cron generation
*
* @since 3.0
* @access public
*/
public static function cron( $force = false ) {
global $wpdb;
$_instance = self::cls();
if ( ! $_instance->queue_count() ) {
Debug2::debug( '[Avatar] no queue' );
return;
}
// For cron, need to check request interval too
if ( ! $force ) {
if ( ! empty( $_instance->_summary[ 'curr_request' ] ) && time() - $_instance->_summary[ 'curr_request' ] < 300 ) {
Debug2::debug( '[Avatar] curr_request too close' );
return;
}
}
$q = "SELECT url FROM `$_instance->_tb` WHERE dateline < %d ORDER BY id DESC LIMIT %d";
$q = $wpdb->prepare( $q, array( time() - $_instance->_conf_cache_ttl, apply_filters( 'litespeed_avatar_limit', 30 ) ) );
$list = $wpdb->get_results( $q );
Debug2::debug( '[Avatar] cron job [count] ' . count( $list ) );
foreach ( $list as $v ) {
Debug2::debug( '[Avatar] cron job [url] ' . $v->url );
$_instance->_generate( $v->url );
}
}
/**
* Remote generator
*
* @since 3.0
* @access private
*/
private function _generate( $url ) {
global $wpdb;
// Record the data
$file = $this->_realpath( $url );
// Update request status
$this->_summary[ 'curr_request' ] = time();
self::save_summary();
// Generate
$this->_maybe_mk_cache_folder( 'avatar' );
$response = wp_remote_get( $url, array( 'timeout' => 180, 'stream' => true, 'filename' => $file ) );
Debug2::debug( '[Avatar] _generate [url] ' . $url );
// Parse response data
if ( is_wp_error( $response ) ) {
$error_message = $response->get_error_message();
file_exists( $file ) && unlink( $file );
Debug2::debug( '[Avatar] failed to get: ' . $error_message );
return $url;
}
// Save summary data
$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();
// Update DB
$md5 = md5( $url );
$q = "UPDATE `$this->_tb` SET dateline=%d WHERE md5=%s";
$existed = $wpdb->query( $wpdb->prepare( $q, array( time(), $md5 ) ) );
if ( ! $existed ) {
$q = "INSERT INTO `$this->_tb` SET url=%s, md5=%s, dateline=%d";
$wpdb->query( $wpdb->prepare( $q, array( $url, $md5, time() ) ) );
}
Debug2::debug( '[Avatar] saved avatar ' . $file );
return $this->_rewrite( $url );
}
/**
* Handle all request actions from main cls
*
* @since 3.0
* @access public
*/
public function handler() {
$type = Router::verify_type();
switch ( $type ) {
case self::TYPE_GENERATE :
self::cron( true );
break;
default:
break;
}
Admin::redirect();
}
}

View File

@@ -0,0 +1,945 @@
<?php
/**
* The base consts
*
* @since 3.7
*/
namespace LiteSpeed;
defined( 'WPINC' ) || exit;
class Base extends Root {
// This is redundant since v3.0
// New conf items are `litespeed.key`
const OPTION_NAME = 'litespeed-cache-conf';
const _CACHE = '_cache'; // final cache status from setting
## -------------------------------------------------- ##
## -------------- General ----------------- ##
## -------------------------------------------------- ##
const _VER = '_version'; // Not set-able
const HASH = 'hash'; // Not set-able
const O_AUTO_UPGRADE = 'auto_upgrade';
const O_API_KEY = 'api_key';
const O_SERVER_IP = 'server_ip';
const O_GUEST = 'guest';
const O_GUEST_OPTM = 'guest_optm';
const O_NEWS = 'news';
const O_GUEST_UAS = 'guest_uas';
const O_GUEST_IPS = 'guest_ips';
## -------------------------------------------------- ##
## -------------- Cache ----------------- ##
## -------------------------------------------------- ##
const O_CACHE = 'cache';
const O_CACHE_PRIV = 'cache-priv';
const O_CACHE_COMMENTER = 'cache-commenter';
const O_CACHE_REST = 'cache-rest';
const O_CACHE_PAGE_LOGIN = 'cache-page_login';
const O_CACHE_FAVICON = 'cache-favicon';
const O_CACHE_RES = 'cache-resources';
const O_CACHE_MOBILE = 'cache-mobile';
const O_CACHE_MOBILE_RULES = 'cache-mobile_rules';
const O_CACHE_BROWSER = 'cache-browser';
const O_CACHE_EXC_USERAGENTS = 'cache-exc_useragents';
const O_CACHE_EXC_COOKIES = 'cache-exc_cookies';
const O_CACHE_EXC_QS = 'cache-exc_qs';
const O_CACHE_EXC_CAT = 'cache-exc_cat';
const O_CACHE_EXC_TAG = 'cache-exc_tag';
const O_CACHE_FORCE_URI = 'cache-force_uri';
const O_CACHE_FORCE_PUB_URI = 'cache-force_pub_uri';
const O_CACHE_PRIV_URI = 'cache-priv_uri';
const O_CACHE_EXC = 'cache-exc';
const O_CACHE_EXC_ROLES = 'cache-exc_roles';
const O_CACHE_DROP_QS = 'cache-drop_qs';
const O_CACHE_TTL_PUB = 'cache-ttl_pub';
const O_CACHE_TTL_PRIV = 'cache-ttl_priv';
const O_CACHE_TTL_FRONTPAGE = 'cache-ttl_frontpage';
const O_CACHE_TTL_FEED = 'cache-ttl_feed';
const O_CACHE_TTL_REST = 'cache-ttl_rest';
const O_CACHE_TTL_STATUS = 'cache-ttl_status';
const O_CACHE_TTL_BROWSER = 'cache-ttl_browser';
const O_CACHE_LOGIN_COOKIE = 'cache-login_cookie';
const O_CACHE_VARY_GROUP = 'cache-vary_group';
## -------------------------------------------------- ##
## -------------- Purge ----------------- ##
## -------------------------------------------------- ##
const O_PURGE_ON_UPGRADE = 'purge-upgrade';
const O_PURGE_STALE = 'purge-stale';
const O_PURGE_POST_ALL = 'purge-post_all';
const O_PURGE_POST_FRONTPAGE = 'purge-post_f';
const O_PURGE_POST_HOMEPAGE = 'purge-post_h';
const O_PURGE_POST_PAGES = 'purge-post_p';
const O_PURGE_POST_PAGES_WITH_RECENT_POSTS = 'purge-post_pwrp';
const O_PURGE_POST_AUTHOR = 'purge-post_a';
const O_PURGE_POST_YEAR = 'purge-post_y';
const O_PURGE_POST_MONTH = 'purge-post_m';
const O_PURGE_POST_DATE = 'purge-post_d';
const O_PURGE_POST_TERM = 'purge-post_t'; // include category|tag|tax
const O_PURGE_POST_POSTTYPE = 'purge-post_pt';
const O_PURGE_TIMED_URLS = 'purge-timed_urls';
const O_PURGE_TIMED_URLS_TIME = 'purge-timed_urls_time';
const O_PURGE_HOOK_ALL = 'purge-hook_all';
## -------------------------------------------------- ##
## -------------- ESI ----------------- ##
## -------------------------------------------------- ##
const O_ESI = 'esi';
const O_ESI_CACHE_ADMBAR = 'esi-cache_admbar';
const O_ESI_CACHE_COMMFORM = 'esi-cache_commform';
const O_ESI_NONCE = 'esi-nonce';
## -------------------------------------------------- ##
## -------------- Utilities ----------------- ##
## -------------------------------------------------- ##
const O_UTIL_INSTANT_CLICK = 'util-instant_click';
const O_UTIL_NO_HTTPS_VARY = 'util-no_https_vary';
## -------------------------------------------------- ##
## -------------- Debug ----------------- ##
## -------------------------------------------------- ##
const O_DEBUG_DISABLE_ALL = 'debug-disable_all';
const O_DEBUG = 'debug';
const O_DEBUG_IPS = 'debug-ips';
const O_DEBUG_LEVEL = 'debug-level';
const O_DEBUG_FILESIZE = 'debug-filesize';
const O_DEBUG_COOKIE = 'debug-cookie';
const O_DEBUG_COLLAPS_QS = 'debug-collaps_qs';
const O_DEBUG_INC = 'debug-inc';
const O_DEBUG_EXC = 'debug-exc';
## -------------------------------------------------- ##
## -------------- DB Optm ----------------- ##
## -------------------------------------------------- ##
const O_DB_OPTM_REVISIONS_MAX = 'db_optm-revisions_max';
const O_DB_OPTM_REVISIONS_AGE = 'db_optm-revisions_age';
## -------------------------------------------------- ##
## -------------- HTML Optm ----------------- ##
## -------------------------------------------------- ##
const O_OPTM_CSS_MIN = 'optm-css_min';
const O_OPTM_CSS_COMB = 'optm-css_comb';
const O_OPTM_CSS_COMB_EXT_INL = 'optm-css_comb_ext_inl';
const O_OPTM_UCSS = 'optm-ucss';
const O_OPTM_UCSS_INLINE = 'optm-ucss_inline';
const O_OPTM_UCSS_WHITELIST = 'optm-ucss_whitelist';
const O_OPTM_UCSS_EXC = 'optm-ucss_exc';
const O_OPTM_CSS_EXC = 'optm-css_exc';
const O_OPTM_JS_MIN = 'optm-js_min';
const O_OPTM_JS_COMB = 'optm-js_comb';
const O_OPTM_JS_COMB_EXT_INL = 'optm-js_comb_ext_inl';
const O_OPTM_JS_EXC = 'optm-js_exc';
const O_OPTM_HTML_MIN = 'optm-html_min';
const O_OPTM_HTML_LAZY = 'optm-html_lazy';
const O_OPTM_QS_RM = 'optm-qs_rm';
const O_OPTM_GGFONTS_RM = 'optm-ggfonts_rm';
const O_OPTM_CSS_ASYNC = 'optm-css_async';
const O_OPTM_CCSS_PER_URL = 'optm-ccss_per_url';
const O_OPTM_CCSS_SEP_POSTTYPE = 'optm-ccss_sep_posttype';
const O_OPTM_CCSS_SEP_URI = 'optm-ccss_sep_uri';
const O_OPTM_CSS_ASYNC_INLINE = 'optm-css_async_inline';
const O_OPTM_CSS_FONT_DISPLAY = 'optm-css_font_display';
const O_OPTM_JS_DEFER = 'optm-js_defer';
const O_OPTM_LOCALIZE = 'optm-localize';
const O_OPTM_LOCALIZE_DOMAINS = 'optm-localize_domains';
const O_OPTM_EMOJI_RM = 'optm-emoji_rm';
const O_OPTM_NOSCRIPT_RM = 'optm-noscript_rm';
const O_OPTM_GGFONTS_ASYNC = 'optm-ggfonts_async';
const O_OPTM_EXC_ROLES = 'optm-exc_roles';
const O_OPTM_CCSS_CON = 'optm-ccss_con';
const O_OPTM_JS_DEFER_EXC = 'optm-js_defer_exc';
const O_OPTM_GM_JS_EXC = 'optm-gm_js_exc';
const O_OPTM_DNS_PREFETCH = 'optm-dns_prefetch';
const O_OPTM_DNS_PREFETCH_CTRL = 'optm-dns_prefetch_ctrl';
const O_OPTM_EXC = 'optm-exc';
const O_OPTM_GUEST_ONLY = 'optm-guest_only';
## -------------------------------------------------- ##
## -------------- Object Cache ----------------- ##
## -------------------------------------------------- ##
const O_OBJECT = 'object';
const O_OBJECT_KIND = 'object-kind';
const O_OBJECT_HOST = 'object-host';
const O_OBJECT_PORT = 'object-port';
const O_OBJECT_LIFE = 'object-life';
const O_OBJECT_PERSISTENT = 'object-persistent';
const O_OBJECT_ADMIN = 'object-admin';
const O_OBJECT_TRANSIENTS = 'object-transients';
const O_OBJECT_DB_ID = 'object-db_id';
const O_OBJECT_USER = 'object-user';
const O_OBJECT_PSWD = 'object-pswd';
const O_OBJECT_GLOBAL_GROUPS = 'object-global_groups';
const O_OBJECT_NON_PERSISTENT_GROUPS = 'object-non_persistent_groups';
## -------------------------------------------------- ##
## -------------- Discussion ----------------- ##
## -------------------------------------------------- ##
const O_DISCUSS_AVATAR_CACHE = 'discuss-avatar_cache';
const O_DISCUSS_AVATAR_CRON = 'discuss-avatar_cron';
const O_DISCUSS_AVATAR_CACHE_TTL = 'discuss-avatar_cache_ttl';
## -------------------------------------------------- ##
## -------------- Media ----------------- ##
## -------------------------------------------------- ##
const O_MEDIA_LAZY = 'media-lazy';
const O_MEDIA_LAZY_PLACEHOLDER = 'media-lazy_placeholder';
const O_MEDIA_PLACEHOLDER_RESP = 'media-placeholder_resp';
const O_MEDIA_PLACEHOLDER_RESP_COLOR = 'media-placeholder_resp_color';
const O_MEDIA_PLACEHOLDER_RESP_SVG = 'media-placeholder_resp_svg';
const O_MEDIA_LQIP = 'media-lqip';
const O_MEDIA_LQIP_QUAL = 'media-lqip_qual';
const O_MEDIA_LQIP_MIN_W = 'media-lqip_min_w';
const O_MEDIA_LQIP_MIN_H = 'media-lqip_min_h';
const O_MEDIA_PLACEHOLDER_RESP_ASYNC = 'media-placeholder_resp_async';
const O_MEDIA_IFRAME_LAZY = 'media-iframe_lazy';
const O_MEDIA_ADD_MISSING_SIZES = 'media-add_missing_sizes';
const O_MEDIA_LAZY_EXC = 'media-lazy_exc';
const O_MEDIA_LAZY_CLS_EXC = 'media-lazy_cls_exc';
const O_MEDIA_LAZY_PARENT_CLS_EXC = 'media-lazy_parent_cls_exc';
const O_MEDIA_IFRAME_LAZY_CLS_EXC = 'media-iframe_lazy_cls_exc';
const O_MEDIA_IFRAME_LAZY_PARENT_CLS_EXC = 'media-iframe_lazy_parent_cls_exc';
const O_MEDIA_LAZY_URI_EXC = 'media-lazy_uri_exc';
const O_MEDIA_LQIP_EXC = 'media-lqip_exc';
## -------------------------------------------------- ##
## -------------- Image Optm ----------------- ##
## -------------------------------------------------- ##
const O_IMG_OPTM_AUTO = 'img_optm-auto';
const O_IMG_OPTM_CRON = 'img_optm-cron';
const O_IMG_OPTM_ORI = 'img_optm-ori';
const O_IMG_OPTM_RM_BKUP = 'img_optm-rm_bkup';
const O_IMG_OPTM_WEBP = 'img_optm-webp';
const O_IMG_OPTM_LOSSLESS = 'img_optm-lossless';
const O_IMG_OPTM_EXIF = 'img_optm-exif';
const O_IMG_OPTM_WEBP_REPLACE = 'img_optm-webp_replace';
const O_IMG_OPTM_WEBP_ATTR = 'img_optm-webp_attr';
const O_IMG_OPTM_WEBP_REPLACE_SRCSET = 'img_optm-webp_replace_srcset';
const O_IMG_OPTM_JPG_QUALITY = 'img_optm-jpg_quality';
## -------------------------------------------------- ##
## -------------- Crawler ----------------- ##
## -------------------------------------------------- ##
const O_CRAWLER = 'crawler';
const O_CRAWLER_USLEEP = 'crawler-usleep';
const O_CRAWLER_RUN_DURATION = 'crawler-run_duration';
const O_CRAWLER_RUN_INTERVAL = 'crawler-run_interval';
const O_CRAWLER_CRAWL_INTERVAL = 'crawler-crawl_interval';
const O_CRAWLER_THREADS = 'crawler-threads';
const O_CRAWLER_TIMEOUT = 'crawler-timeout';
const O_CRAWLER_LOAD_LIMIT = 'crawler-load_limit';
const O_CRAWLER_SITEMAP = 'crawler-sitemap';
const O_CRAWLER_DROP_DOMAIN = 'crawler-drop_domain';
const O_CRAWLER_MAP_TIMEOUT = 'crawler-map_timeout';
const O_CRAWLER_ROLES = 'crawler-roles';
const O_CRAWLER_COOKIES = 'crawler-cookies';
## -------------------------------------------------- ##
## -------------- Misc ----------------- ##
## -------------------------------------------------- ##
const O_MISC_HEARTBEAT_FRONT = 'misc-heartbeat_front';
const O_MISC_HEARTBEAT_FRONT_TTL = 'misc-heartbeat_front_ttl';
const O_MISC_HEARTBEAT_BACK = 'misc-heartbeat_back';
const O_MISC_HEARTBEAT_BACK_TTL = 'misc-heartbeat_back_ttl';
const O_MISC_HEARTBEAT_EDITOR = 'misc-heartbeat_editor';
const O_MISC_HEARTBEAT_EDITOR_TTL = 'misc-heartbeat_editor_ttl';
## -------------------------------------------------- ##
## -------------- CDN ----------------- ##
## -------------------------------------------------- ##
const O_CDN = 'cdn';
const O_CDN_ORI = 'cdn-ori';
const O_CDN_ORI_DIR = 'cdn-ori_dir';
const O_CDN_EXC = 'cdn-exc';
const O_CDN_QUIC = 'cdn-quic';
const O_CDN_CLOUDFLARE = 'cdn-cloudflare';
const O_CDN_CLOUDFLARE_EMAIL= 'cdn-cloudflare_email';
const O_CDN_CLOUDFLARE_KEY = 'cdn-cloudflare_key';
const O_CDN_CLOUDFLARE_NAME = 'cdn-cloudflare_name';
const O_CDN_CLOUDFLARE_ZONE = 'cdn-cloudflare_zone';
const O_CDN_MAPPING = 'cdn-mapping';
const O_CDN_ATTR = 'cdn-attr';
const NETWORK_O_USE_PRIMARY = 'use_primary_settings';
/*** Other consts ***/
const O_GUIDE = 'litespeed-guide'; // Array of each guidance tag as key, step as val //xx todo: may need to remove
// Server variables
const ENV_CRAWLER_USLEEP = 'CRAWLER_USLEEP';
const ENV_CRAWLER_LOAD_LIMIT = 'CRAWLER_LOAD_LIMIT';
const ENV_CRAWLER_LOAD_LIMIT_ENFORCE = 'CRAWLER_LOAD_LIMIT_ENFORCE';
// const O_FAVICON = 'litespeed-cache-favicon';
const CRWL_COOKIE_NAME = 'name';
const CRWL_COOKIE_VALS = 'vals';
const CDN_MAPPING_URL = 'url';
const CDN_MAPPING_INC_IMG = 'inc_img';
const CDN_MAPPING_INC_CSS = 'inc_css';
const CDN_MAPPING_INC_JS = 'inc_js';
const CDN_MAPPING_FILETYPE = 'filetype';
const VAL_OFF = 0;
const VAL_ON = 1;
const VAL_ON2 = 2;
/* This is for API hook usage */
const IMG_OPTM_BM_ORI = 1;
const IMG_OPTM_BM_WEBP = 2;
const IMG_OPTM_BM_LOSSLESS = 4;
const IMG_OPTM_BM_EXIF = 8;
/* Site related options (Will not overwrite other sites' config) */
protected static $SINGLE_SITE_OPTIONS = array(
self::O_API_KEY,
self::O_CRAWLER,
self::O_CRAWLER_SITEMAP,
self::O_CRAWLER_DROP_DOMAIN,
self::O_CDN,
self::O_CDN_ORI,
self::O_CDN_ORI_DIR,
self::O_CDN_EXC,
self::O_CDN_QUIC,
self::O_CDN_CLOUDFLARE,
self::O_CDN_CLOUDFLARE_EMAIL,
self::O_CDN_CLOUDFLARE_KEY,
self::O_CDN_CLOUDFLARE_NAME,
self::O_CDN_CLOUDFLARE_ZONE,
self::O_CDN_MAPPING,
self::O_CDN_ATTR,
);
protected static $_default_options = array(
self::_VER => '',
self::HASH => '',
self::O_AUTO_UPGRADE => false,
self::O_API_KEY => '',
self::O_SERVER_IP => '',
self::O_GUEST => false,
self::O_GUEST_OPTM => false,
self::O_NEWS => false,
self::O_GUEST_UAS => array(),
self::O_GUEST_IPS => array(),
// Cache
self::O_CACHE => false,
self::O_CACHE_PRIV => false,
self::O_CACHE_COMMENTER => false,
self::O_CACHE_REST => false,
self::O_CACHE_PAGE_LOGIN => false,
self::O_CACHE_FAVICON => false,
self::O_CACHE_RES => false,
self::O_CACHE_MOBILE => false,
self::O_CACHE_MOBILE_RULES => array(),
self::O_CACHE_BROWSER => false,
self::O_CACHE_EXC_USERAGENTS => array(),
self::O_CACHE_EXC_COOKIES => array(),
self::O_CACHE_EXC_QS => array(),
self::O_CACHE_EXC_CAT => array(),
self::O_CACHE_EXC_TAG => array(),
self::O_CACHE_FORCE_URI => array(),
self::O_CACHE_FORCE_PUB_URI => array(),
self::O_CACHE_PRIV_URI => array(),
self::O_CACHE_EXC => array(),
self::O_CACHE_EXC_ROLES => array(),
self::O_CACHE_DROP_QS => array(),
self::O_CACHE_TTL_PUB => 0,
self::O_CACHE_TTL_PRIV => 0,
self::O_CACHE_TTL_FRONTPAGE => 0,
self::O_CACHE_TTL_FEED => 0,
self::O_CACHE_TTL_REST => 0,
self::O_CACHE_TTL_BROWSER => 0,
self::O_CACHE_TTL_STATUS => array(),
self::O_CACHE_LOGIN_COOKIE => '',
self::O_CACHE_VARY_GROUP => array(),
// Purge
self::O_PURGE_ON_UPGRADE => false,
self::O_PURGE_STALE => false,
self::O_PURGE_POST_ALL => false,
self::O_PURGE_POST_FRONTPAGE => false,
self::O_PURGE_POST_HOMEPAGE => false,
self::O_PURGE_POST_PAGES => false,
self::O_PURGE_POST_PAGES_WITH_RECENT_POSTS => false,
self::O_PURGE_POST_AUTHOR => false,
self::O_PURGE_POST_YEAR => false,
self::O_PURGE_POST_MONTH => false,
self::O_PURGE_POST_DATE => false,
self::O_PURGE_POST_TERM => false,
self::O_PURGE_POST_POSTTYPE => false,
self::O_PURGE_TIMED_URLS => array(),
self::O_PURGE_TIMED_URLS_TIME => '',
self::O_PURGE_HOOK_ALL => array(),
// ESI
self::O_ESI => false,
self::O_ESI_CACHE_ADMBAR => false,
self::O_ESI_CACHE_COMMFORM => false,
self::O_ESI_NONCE => array(),
// Util
self::O_UTIL_INSTANT_CLICK => false,
self::O_UTIL_NO_HTTPS_VARY => false,
// Debug
self::O_DEBUG_DISABLE_ALL => false,
self::O_DEBUG => false,
self::O_DEBUG_IPS => array(),
self::O_DEBUG_LEVEL => false,
self::O_DEBUG_FILESIZE => 0,
self::O_DEBUG_COOKIE => false,
self::O_DEBUG_COLLAPS_QS => false,
self::O_DEBUG_INC => array(),
self::O_DEBUG_EXC => array(),
// DB Optm
self::O_DB_OPTM_REVISIONS_MAX => 0,
self::O_DB_OPTM_REVISIONS_AGE => 0,
// HTML Optm
self::O_OPTM_CSS_MIN => false,
self::O_OPTM_CSS_COMB => false,
self::O_OPTM_CSS_COMB_EXT_INL => false,
self::O_OPTM_UCSS => false,
self::O_OPTM_UCSS_INLINE => false,
self::O_OPTM_UCSS_WHITELIST => array(),
self::O_OPTM_UCSS_EXC => array(),
self::O_OPTM_CSS_EXC => array(),
self::O_OPTM_JS_MIN => false,
self::O_OPTM_JS_COMB => false,
self::O_OPTM_JS_COMB_EXT_INL => false,
self::O_OPTM_JS_EXC => array(),
self::O_OPTM_HTML_MIN => false,
self::O_OPTM_HTML_LAZY => array(),
self::O_OPTM_QS_RM => false,
self::O_OPTM_GGFONTS_RM => false,
self::O_OPTM_CSS_ASYNC => false,
self::O_OPTM_CCSS_PER_URL => false,
self::O_OPTM_CCSS_SEP_POSTTYPE => array(),
self::O_OPTM_CCSS_SEP_URI => array(),
self::O_OPTM_CSS_ASYNC_INLINE => false,
self::O_OPTM_CSS_FONT_DISPLAY => false,
self::O_OPTM_JS_DEFER => false,
self::O_OPTM_EMOJI_RM => false,
self::O_OPTM_NOSCRIPT_RM => false,
self::O_OPTM_GGFONTS_ASYNC => false,
self::O_OPTM_EXC_ROLES => array(),
self::O_OPTM_CCSS_CON => '',
self::O_OPTM_JS_DEFER_EXC => array(),
self::O_OPTM_GM_JS_EXC => array(),
self::O_OPTM_DNS_PREFETCH => array(),
self::O_OPTM_DNS_PREFETCH_CTRL => false,
self::O_OPTM_EXC => array(),
self::O_OPTM_GUEST_ONLY => false,
// Object
self::O_OBJECT => false,
self::O_OBJECT_KIND => false,
self::O_OBJECT_HOST => '',
self::O_OBJECT_PORT => 0,
self::O_OBJECT_LIFE => 0,
self::O_OBJECT_PERSISTENT => false,
self::O_OBJECT_ADMIN => false,
self::O_OBJECT_TRANSIENTS => false,
self::O_OBJECT_DB_ID => 0,
self::O_OBJECT_USER => '',
self::O_OBJECT_PSWD => '',
self::O_OBJECT_GLOBAL_GROUPS => array(),
self::O_OBJECT_NON_PERSISTENT_GROUPS => array(),
// Discuss
self::O_DISCUSS_AVATAR_CACHE => false,
self::O_DISCUSS_AVATAR_CRON => false,
self::O_DISCUSS_AVATAR_CACHE_TTL => 0,
self::O_OPTM_LOCALIZE => false,
self::O_OPTM_LOCALIZE_DOMAINS => array(),
// Media
self::O_MEDIA_LAZY => false,
self::O_MEDIA_LAZY_PLACEHOLDER => '',
self::O_MEDIA_PLACEHOLDER_RESP => false,
self::O_MEDIA_PLACEHOLDER_RESP_COLOR => '',
self::O_MEDIA_PLACEHOLDER_RESP_SVG => '',
self::O_MEDIA_LQIP => false,
self::O_MEDIA_LQIP_QUAL => 0,
self::O_MEDIA_LQIP_MIN_W => 0,
self::O_MEDIA_LQIP_MIN_H => 0,
self::O_MEDIA_PLACEHOLDER_RESP_ASYNC => false,
self::O_MEDIA_IFRAME_LAZY => false,
self::O_MEDIA_ADD_MISSING_SIZES => false,
self::O_MEDIA_LAZY_EXC => array(),
self::O_MEDIA_LAZY_CLS_EXC => array(),
self::O_MEDIA_LAZY_PARENT_CLS_EXC => array(),
self::O_MEDIA_IFRAME_LAZY_CLS_EXC => array(),
self::O_MEDIA_IFRAME_LAZY_PARENT_CLS_EXC => array(),
self::O_MEDIA_LAZY_URI_EXC => array(),
self::O_MEDIA_LQIP_EXC => array(),
// Image Optm
self::O_IMG_OPTM_AUTO => false,
self::O_IMG_OPTM_CRON => false,
self::O_IMG_OPTM_ORI => false,
self::O_IMG_OPTM_RM_BKUP => false,
self::O_IMG_OPTM_WEBP => false,
self::O_IMG_OPTM_LOSSLESS => false,
self::O_IMG_OPTM_EXIF => false,
self::O_IMG_OPTM_WEBP_REPLACE => false,
self::O_IMG_OPTM_WEBP_ATTR => array(),
self::O_IMG_OPTM_WEBP_REPLACE_SRCSET => false,
self::O_IMG_OPTM_JPG_QUALITY => 0,
// Crawler
self::O_CRAWLER => false,
self::O_CRAWLER_USLEEP => 0,
self::O_CRAWLER_RUN_DURATION => 0,
self::O_CRAWLER_RUN_INTERVAL => 0,
self::O_CRAWLER_CRAWL_INTERVAL => 0,
self::O_CRAWLER_THREADS => 0,
self::O_CRAWLER_TIMEOUT => 0,
self::O_CRAWLER_LOAD_LIMIT => 0,
self::O_CRAWLER_SITEMAP => '',
self::O_CRAWLER_DROP_DOMAIN => false,
self::O_CRAWLER_MAP_TIMEOUT => 0,
self::O_CRAWLER_ROLES => array(),
self::O_CRAWLER_COOKIES => array(),
// Misc
self::O_MISC_HEARTBEAT_FRONT => false,
self::O_MISC_HEARTBEAT_FRONT_TTL => 0,
self::O_MISC_HEARTBEAT_BACK => false,
self::O_MISC_HEARTBEAT_BACK_TTL => 0,
self::O_MISC_HEARTBEAT_EDITOR => false,
self::O_MISC_HEARTBEAT_EDITOR_TTL => 0,
// CDN
self::O_CDN => false,
self::O_CDN_ORI => array(),
self::O_CDN_ORI_DIR => array(),
self::O_CDN_EXC => array(),
self::O_CDN_QUIC => false,
self::O_CDN_CLOUDFLARE => false,
self::O_CDN_CLOUDFLARE_EMAIL => '',
self::O_CDN_CLOUDFLARE_KEY => '',
self::O_CDN_CLOUDFLARE_NAME => '',
self::O_CDN_CLOUDFLARE_ZONE => '',
self::O_CDN_MAPPING => array(),
self::O_CDN_ATTR => array(),
);
protected static $_default_site_options = array(
self::_VER => '',
self::O_CACHE => false,
self::NETWORK_O_USE_PRIMARY => false,
self::O_AUTO_UPGRADE => false,
self::O_GUEST => false,
self::O_CACHE_FAVICON => false,
self::O_CACHE_RES => false,
self::O_CACHE_BROWSER => false,
self::O_CACHE_MOBILE => false,
self::O_CACHE_MOBILE_RULES => array(),
self::O_CACHE_LOGIN_COOKIE => '',
self::O_CACHE_EXC_COOKIES => array(),
self::O_CACHE_EXC_USERAGENTS => array(),
self::O_CACHE_TTL_BROWSER => 0,
self::O_PURGE_ON_UPGRADE => false,
self::O_OBJECT => false,
self::O_OBJECT_KIND => false,
self::O_OBJECT_HOST => '',
self::O_OBJECT_PORT => 0,
self::O_OBJECT_LIFE => 0,
self::O_OBJECT_PERSISTENT => false,
self::O_OBJECT_ADMIN => false,
self::O_OBJECT_TRANSIENTS => false,
self::O_OBJECT_DB_ID => 0,
self::O_OBJECT_USER => '',
self::O_OBJECT_PSWD => '',
self::O_OBJECT_GLOBAL_GROUPS => array(),
self::O_OBJECT_NON_PERSISTENT_GROUPS => array(),
// Debug
self::O_DEBUG_DISABLE_ALL => false,
self::O_DEBUG => false,
self::O_DEBUG_IPS => array(),
self::O_DEBUG_LEVEL => false,
self::O_DEBUG_FILESIZE => 0,
self::O_DEBUG_COOKIE => false,
self::O_DEBUG_COLLAPS_QS => false,
self::O_DEBUG_INC => array(),
self::O_DEBUG_EXC => array(),
self::O_IMG_OPTM_WEBP_REPLACE => false,
);
// NOTE: all the val of following items will be int while not bool
protected static $_multi_switch_list = array(
self::O_DEBUG => 2,
self::O_OPTM_JS_DEFER => 2,
);
/**
* Correct the option type
*
* TODO: add similar network func
*
* @since 3.0.3
*/
protected function type_casting( $val, $id, $is_site_conf = false ) {
$default_v = ! $is_site_conf ? self::$_default_options[ $id ] : self::$_default_site_options[ $id ];
if ( is_bool( $default_v ) ) {
if ( $val === 'true' ) {
$val = true;
}
if ( $val === 'false' ) {
$val = false;
}
$max = $this->_conf_multi_switch( $id );
if ( $max ) {
$val = (int) $val;
$val %= $max + 1;
}
else {
$val = (bool) $val;
}
}
elseif ( is_array( $default_v ) ) {
// from textarea input
if ( ! is_array( $val ) ) {
$val = Utility::sanitize_lines( $val, $this->_conf_filter( $id ) );
}
}
elseif ( ! is_string( $default_v ) ) {
$val = (int) $val;
}
else {
// Check if the string has a limit set
$val = $this->_conf_string_val( $id, $val );
}
return $val;
}
/**
* Load default network settings from data.ini
*
* @since 3.0
*/
public function load_default_site_vals() {
// Load network_default.ini
if ( file_exists( LSCWP_DIR . 'data/const.network_default.ini' ) ) {
$default_ini_cfg = parse_ini_file( LSCWP_DIR . 'data/const.network_default.ini', true );
foreach ( self::$_default_site_options as $k => $v ) {
if ( ! array_key_exists( $k, $default_ini_cfg ) ) {
continue;
}
// Parse value in ini file
$ini_v = $this->type_casting( $default_ini_cfg[ $k ], $k, true );
if ( $ini_v == $v ) {
continue;
}
self::$_default_site_options[ $k ] = $ini_v;
}
}
self::$_default_site_options[ self::_VER ] = Core::VER;
return self::$_default_site_options;
}
/**
* Load default values from default.ini
*
* @since 3.0
* @access public
*/
public function load_default_vals() {
// Load default.ini
if ( file_exists( LSCWP_DIR . 'data/const.default.ini' ) ) {
$default_ini_cfg = parse_ini_file( LSCWP_DIR . 'data/const.default.ini', true );
foreach ( self::$_default_options as $k => $v ) {
if ( ! array_key_exists( $k, $default_ini_cfg ) ) {
continue;
}
// Parse value in ini file
$ini_v = $this->type_casting( $default_ini_cfg[ $k ], $k );
// NOTE: Multiple lines value must be stored as array
/**
* Special handler for CDN_mapping
*
* format in .ini:
* [cdn-mapping]
* url[0] = 'https://example.com/'
* inc_js[0] = true
* filetype[0] = '.css
* .js
* .jpg'
*
* format out:
* [0] = [ 'url' => 'https://example.com', 'inc_js' => true, 'filetype' => [ '.css', '.js', '.jpg' ] ]
*/
if ( $k == self::O_CDN_MAPPING ) {
$mapping_fields = array(
self::CDN_MAPPING_URL,
self::CDN_MAPPING_INC_IMG,
self::CDN_MAPPING_INC_CSS,
self::CDN_MAPPING_INC_JS,
self::CDN_MAPPING_FILETYPE, // Array
);
$ini_v2 = array();
foreach ( $ini_v[ self::CDN_MAPPING_URL ] as $k2 => $v2 ) {// $k2 is numeric
$this_row = array();
foreach ( $mapping_fields as $v3 ) {
$this_v = ! empty( $ini_v[ $v3 ][ $k2 ] ) ? $ini_v[ $v3 ][ $k2 ] : false;
if ( $v3 == self::CDN_MAPPING_URL ) {
$this_v = $this_v ? : '';
}
if ( $v3 == self::CDN_MAPPING_FILETYPE ) {
$this_v = $this_v ? Utility::sanitize_lines( $this_v ) : array(); // Note: Since v3.0 its already an array
}
$this_row[ $v3 ] = $this_v;
}
$ini_v2[ $k2 ] = $this_row;
}
$ini_v = $ini_v2;
}
if ( $ini_v == $v ) {
continue;
}
self::$_default_options[ $k ] = $ini_v;
}
}
// Load internal default vals
// Setting the default bool to int is also to avoid type casting override it back to bool
self::$_default_options[ self::O_CACHE ] = is_multisite() ? self::VAL_ON2 : self::VAL_ON; //For multi site, default is 2 (Use Network Admin Settings). For single site, default is 1 (Enabled).
// Load default vals containing variables
if ( ! self::$_default_options[ self::O_CDN_ORI_DIR ] ) {
self::$_default_options[ self::O_CDN_ORI_DIR ] = LSCWP_CONTENT_FOLDER . "\nwp-includes";
self::$_default_options[ self::O_CDN_ORI_DIR ] = explode( "\n", self::$_default_options[ self::O_CDN_ORI_DIR ] );
self::$_default_options[ self::O_CDN_ORI_DIR ] = array_map( 'trim', self::$_default_options[ self::O_CDN_ORI_DIR ] );
}
// Set security key if not initialized yet
if ( ! self::$_default_options[ self::HASH ] ) {
self::$_default_options[ self::HASH ] = Str::rrand( 32 );
}
self::$_default_options[ self::_VER ] = Core::VER;
return self::$_default_options;
}
/**
* Format the string value
*
* @since 3.0
*/
protected function _conf_string_val( $id, $val ) {
return $val;
}
/**
* If the switch setting is a triple value or not
*
* @since 3.0
*/
protected function _conf_multi_switch( $id ) {
if ( ! empty( self::$_multi_switch_list[ $id ] ) ) {
return self::$_multi_switch_list[ $id ];
}
if ( $id == self::O_CACHE && is_multisite() ) {
return self::VAL_ON2;
}
return false;
}
/**
* Append a new multi swith max limit for the bool option
*
* @since 3.0
*/
public static function set_multi_switch( $id, $v ) {
self::$_multi_switch_list[ $id ] = $v;
}
/**
* Generate const name based on $id
*
* @since 3.0
*/
public static function conf_const( $id ) {
return 'LITESPEED_CONF__' . strtoupper( str_replace( '-', '__', $id ) );
}
/**
* Filter to be used when saving setting
*
* @since 3.0
*/
protected function _conf_filter( $id ) {
$filters = array(
self::O_MEDIA_LAZY_EXC => 'uri',
self::O_DEBUG_INC => 'relative',
self::O_DEBUG_EXC => 'relative',
self::O_MEDIA_LAZY_URI_EXC => 'relative',
self::O_CACHE_PRIV_URI => 'relative',
self::O_PURGE_TIMED_URLS => 'relative',
self::O_CACHE_FORCE_URI => 'relative',
self::O_CACHE_FORCE_PUB_URI => 'relative',
self::O_CACHE_EXC => 'relative',
// self::O_OPTM_CSS_EXC => 'uri', // Need to comment out for inline & external CSS
// self::O_OPTM_JS_EXC => 'uri',
self::O_OPTM_EXC => 'relative',
self::O_OPTM_CCSS_SEP_URI => 'uri',
// self::O_OPTM_JS_DEFER_EXC => 'uri',
self::O_OPTM_DNS_PREFETCH => 'domain',
self::O_CDN_ORI => 'noprotocol', // `Original URLs`
// self::O_OPTM_LOCALIZE_DOMAINS => 'noprotocol', // `Localize Resources`
// self:: => '',
// self:: => '',
);
if ( ! empty( $filters[ $id ] ) ) {
return $filters[ $id ];
}
return false;
}
/**
* If the setting changes worth a purge or not
*
* @since 3.0
*/
protected function _conf_purge( $id ) {
$check_ids = array(
self::O_MEDIA_LAZY_URI_EXC,
self::O_OPTM_EXC,
self::O_CACHE_PRIV_URI,
self::O_PURGE_TIMED_URLS,
self::O_CACHE_FORCE_URI,
self::O_CACHE_FORCE_PUB_URI,
self::O_CACHE_EXC,
);
return in_array( $id, $check_ids );
}
/**
* If the setting changes worth a purge ALL or not
*
* @since 3.0
*/
protected function _conf_purge_all( $id ) {
$check_ids = array(
self::O_CACHE,
self::O_ESI,
self::O_DEBUG_DISABLE_ALL,
self::NETWORK_O_USE_PRIMARY,
);
return in_array( $id, $check_ids );
}
/**
* If the setting is a pswd or not
*
* @since 3.0
*/
protected function _conf_pswd( $id ) {
$check_ids = array(
self::O_CDN_CLOUDFLARE_KEY,
self::O_OBJECT_PSWD,
self::O_API_KEY,
);
return in_array( $id, $check_ids );
}
/**
* If the setting is cron related or not
*
* @since 3.0
*/
protected function _conf_cron( $id ) {
$check_ids = array(
self::O_IMG_OPTM_CRON,
self::O_OPTM_CSS_ASYNC,
self::O_MEDIA_PLACEHOLDER_RESP_ASYNC,
self::O_DISCUSS_AVATAR_CRON,
self::O_IMG_OPTM_AUTO,
self::O_CRAWLER,
);
return in_array( $id, $check_ids );
}
/**
* If the setting changes worth a purge, return the tag
*
* @since 3.0
*/
protected function _conf_purge_tag( $id ) {
$check_ids = array(
self::O_CACHE_PAGE_LOGIN => Tag::TYPE_LOGIN,
);
if ( ! empty( $check_ids[ $id ] ) ) {
return $check_ids[ $id ];
}
return false;
}
/**
* Generate server vars
*
* @since 2.4.1
*/
public function server_vars() {
$consts = array(
'WP_SITEURL',
'WP_HOME',
'WP_CONTENT_DIR',
'SHORTINIT',
'LSCWP_CONTENT_DIR',
'LSCWP_CONTENT_FOLDER',
'LSCWP_DIR',
'LITESPEED_TIME_OFFSET',
'LITESPEED_SERVER_TYPE',
'LITESPEED_CLI',
'LITESPEED_ALLOWED',
'LITESPEED_ON',
'LSWCP_TAG_PREFIX',
'COOKIEHASH',
);
$server_vars = array();
foreach ( $consts as $v ) {
$server_vars[ $v ] = defined( $v ) ? constant( $v ) : NULL;
}
return $server_vars;
}
}

View File

@@ -0,0 +1,536 @@
<?php
/**
* The CDN class.
*
* @since 1.2.3
* @since 1.5 Moved into /inc
* @package LiteSpeed
* @subpackage LiteSpeed/inc
* @author LiteSpeed Technologies <info@litespeedtech.com>
*/
namespace LiteSpeed;
defined( 'WPINC' ) || exit;
class CDN extends Root {
const BYPASS = 'LITESPEED_BYPASS_CDN';
private $content;
private $_cfg_cdn;
private $_cfg_url_ori;
private $_cfg_ori_dir;
private $_cfg_cdn_mapping = array();
private $_cfg_cdn_exclude;
private $cdn_mapping_hosts = array();
/**
* Init
*
* @since 1.2.3
*/
public function init() {
Debug2::debug2( '[CDN] init' );
if ( defined( self::BYPASS ) ) {
Debug2::debug2( 'CDN bypass' );
return;
}
if ( ! Router::can_cdn() ) {
if ( ! defined( self::BYPASS ) ) {
define( self::BYPASS, true );
}
return;
}
$this->_cfg_cdn = $this->conf( Base::O_CDN );
if ( ! $this->_cfg_cdn ) {
if ( ! defined( self::BYPASS ) ) {
define( self::BYPASS, true );
}
return;
}
$this->_cfg_url_ori = $this->conf( Base::O_CDN_ORI );
// Parse cdn mapping data to array( 'filetype' => 'url' )
$mapping_to_check = array(
Base::CDN_MAPPING_INC_IMG,
Base::CDN_MAPPING_INC_CSS,
Base::CDN_MAPPING_INC_JS
);
foreach ( $this->conf( Base::O_CDN_MAPPING ) as $v ) {
if ( ! $v[ Base::CDN_MAPPING_URL ] ) {
continue;
}
$this_url = $v[ Base::CDN_MAPPING_URL ];
$this_host = parse_url( $this_url, PHP_URL_HOST );
// Check img/css/js
foreach ( $mapping_to_check as $to_check ) {
if ( $v[ $to_check ] ) {
Debug2::debug2( '[CDN] mapping ' . $to_check . ' -> ' . $this_url );
// If filetype to url is one to many, make url be an array
$this->_append_cdn_mapping( $to_check, $this_url );
if ( ! in_array( $this_host, $this->cdn_mapping_hosts ) ) {
$this->cdn_mapping_hosts[] = $this_host;
}
}
}
// Check file types
if ( $v[ Base::CDN_MAPPING_FILETYPE ] ) {
foreach ( $v[ Base::CDN_MAPPING_FILETYPE ] as $v2 ) {
$this->_cfg_cdn_mapping[ Base::CDN_MAPPING_FILETYPE ] = true;
// If filetype to url is one to many, make url be an array
$this->_append_cdn_mapping( $v2, $this_url );
if ( ! in_array( $this_host, $this->cdn_mapping_hosts ) ) {
$this->cdn_mapping_hosts[] = $this_host;
}
}
Debug2::debug2( '[CDN] mapping ' . implode( ',', $v[ Base::CDN_MAPPING_FILETYPE ] ) . ' -> ' . $this_url );
}
}
if ( ! $this->_cfg_url_ori || ! $this->_cfg_cdn_mapping ) {
if ( ! defined( self::BYPASS ) ) {
define( self::BYPASS, true );
}
return;
}
$this->_cfg_ori_dir = $this->conf( Base::O_CDN_ORI_DIR );
// In case user customized upload path
if ( defined( 'UPLOADS' ) ) {
$this->_cfg_ori_dir[] = UPLOADS;
}
// Check if need preg_replace
$this->_cfg_url_ori = Utility::wildcard2regex( $this->_cfg_url_ori );
$this->_cfg_cdn_exclude = $this->conf( Base::O_CDN_EXC );
if ( ! empty( $this->_cfg_cdn_mapping[ Base::CDN_MAPPING_INC_IMG ] ) ) {
// Hook to srcset
if ( function_exists( 'wp_calculate_image_srcset' ) ) {
add_filter( 'wp_calculate_image_srcset', array( $this, 'srcset' ), 999 );
}
// Hook to mime icon
add_filter( 'wp_get_attachment_image_src', array( $this, 'attach_img_src' ), 999 );
add_filter( 'wp_get_attachment_url', array( $this, 'url_img' ), 999 );
}
if ( ! empty( $this->_cfg_cdn_mapping[ Base::CDN_MAPPING_INC_CSS ] ) ) {
add_filter( 'style_loader_src', array( $this, 'url_css' ), 999 );
}
if ( ! empty( $this->_cfg_cdn_mapping[ Base::CDN_MAPPING_INC_JS ] ) ) {
add_filter( 'script_loader_src', array( $this, 'url_js' ), 999 );
}
add_filter( 'litespeed_buffer_finalize', array( $this, 'finalize' ), 30 );
}
/**
* Associate all filetypes with url
*
* @since 2.0
* @access private
*/
private function _append_cdn_mapping( $filetype, $url ) {
// If filetype to url is one to many, make url be an array
if ( empty( $this->_cfg_cdn_mapping[ $filetype ] ) ) {
$this->_cfg_cdn_mapping[ $filetype ] = $url;
}
elseif ( is_array( $this->_cfg_cdn_mapping[ $filetype ] ) ) {
// Append url to filetype
$this->_cfg_cdn_mapping[ $filetype ][] = $url;
}
else {
// Convert _cfg_cdn_mapping from string to array
$this->_cfg_cdn_mapping[ $filetype ] = array( $this->_cfg_cdn_mapping[ $filetype ], $url );
}
}
/**
* If include css/js in CDN
*
* @since 1.6.2.1
* @return bool true if included in CDN
*/
public function inc_type( $type ) {
if ( $type == 'css' && ! empty( $this->_cfg_cdn_mapping[ Base::CDN_MAPPING_INC_CSS ] ) ) {
return true;
}
if ( $type == 'js' && ! empty( $this->_cfg_cdn_mapping[ Base::CDN_MAPPING_INC_JS ] ) ) {
return true;
}
return false;
}
/**
* Run CDN process
* NOTE: As this is after cache finalized, can NOT set any cache control anymore
*
* @since 1.2.3
* @access public
* @return string The content that is after optimization
*/
public function finalize( $content ) {
$this->content = $content;
$this->_finalize();
return $this->content;
}
/**
* Replace CDN url
*
* @since 1.2.3
* @access private
*/
private function _finalize() {
if ( defined( self::BYPASS ) ) {
return;
}
Debug2::debug( 'CDN _finalize' );
// Start replacing img src
if ( ! empty( $this->_cfg_cdn_mapping[ Base::CDN_MAPPING_INC_IMG ] ) ) {
$this->_replace_img();
$this->_replace_inline_css();
}
if ( ! empty( $this->_cfg_cdn_mapping[ Base::CDN_MAPPING_FILETYPE ] ) ) {
$this->_replace_file_types();
}
}
/**
* Parse all file types
*
* @since 1.2.3
* @access private
*/
private function _replace_file_types() {
$ele_to_check = $this->conf( Base::O_CDN_ATTR );
foreach ( $ele_to_check as $v ) {
if ( ! $v || strpos( $v, '.' ) === false ) {
Debug2::debug2( '[CDN] replace setting bypassed: no . attribute ' . $v );
continue;
}
Debug2::debug2( '[CDN] replace attribute ' . $v );
$v = explode( '.', $v );
$attr = preg_quote( $v[ 1 ], '#' );
if ( $v[ 0 ] ) {
$pattern = '#<' . preg_quote( $v[ 0 ], '#' ) . '([^>]+)' . $attr . '=([\'"])(.+)\g{2}#iU';
}
else {
$pattern = '# ' . $attr . '=([\'"])(.+)\g{1}#iU';
}
preg_match_all( $pattern, $this->content, $matches );
if ( empty( $matches[ $v[ 0 ] ? 3 : 2 ] ) ) {
continue;
}
foreach ( $matches[ $v[ 0 ] ? 3 : 2 ] as $k2 => $url ) {
// Debug2::debug2( '[CDN] check ' . $url );
$postfix = '.' . pathinfo( parse_url( $url, PHP_URL_PATH ), PATHINFO_EXTENSION );
if ( ! array_key_exists( $postfix, $this->_cfg_cdn_mapping ) ) {
// Debug2::debug2( '[CDN] non-existed postfix ' . $postfix );
continue;
}
Debug2::debug2( '[CDN] matched file_type ' . $postfix . ' : ' . $url );
if( ! $url2 = $this->rewrite( $url, Base::CDN_MAPPING_FILETYPE, $postfix ) ) {
continue;
}
$attr = str_replace( $url, $url2, $matches[ 0 ][ $k2 ] );
$this->content = str_replace( $matches[ 0 ][ $k2 ], $attr, $this->content );
}
}
}
/**
* Parse all images
*
* @since 1.2.3
* @access private
*/
private function _replace_img() {
preg_match_all( '#<img([^>]+?)src=([\'"\\\]*)([^\'"\s\\\>]+)([\'"\\\]*)([^>]*)>#i', $this->content, $matches );
foreach ( $matches[ 3 ] as $k => $url ) {
// Check if is a DATA-URI
if ( strpos( $url, 'data:image' ) !== false ) {
continue;
}
if ( ! $url2 = $this->rewrite( $url, Base::CDN_MAPPING_INC_IMG ) ) {
continue;
}
$html_snippet = sprintf(
'<img %1$s src=%2$s %3$s>',
$matches[ 1 ][ $k ],
$matches[ 2 ][ $k ] . $url2 . $matches[ 4 ][ $k ],
$matches[ 5 ][ $k ]
);
$this->content = str_replace( $matches[ 0 ][ $k ], $html_snippet, $this->content );
}
}
/**
* Parse and replace all inline styles containing url()
*
* @since 1.2.3
* @access private
*/
private function _replace_inline_css() {
Debug2::debug2( '[CDN] _replace_inline_css', $this->_cfg_cdn_mapping );
/**
* Excludes `\` from URL matching
* @see #959152 - Wordpress LSCache CDN Mapping causing malformed URLS
* @see #685485
* @since 3.0
*/
preg_match_all( '/url\((?![\'"]?data)[\'"]?([^\)\'"\\\]+)[\'"]?\)/i', $this->content, $matches );
foreach ( $matches[ 1 ] as $k => $url ) {
$url = str_replace( array( ' ', '\t', '\n', '\r', '\0', '\x0B', '"', "'", '&quot;', '&#039;' ), '', $url );
// Parse file postfix
$postfix = '.' . pathinfo( parse_url( $url, PHP_URL_PATH ), PATHINFO_EXTENSION );
if ( array_key_exists( $postfix, $this->_cfg_cdn_mapping ) ) {
Debug2::debug2( '[CDN] matched file_type ' . $postfix . ' : ' . $url );
if( ! $url2 = $this->rewrite( $url, Base::CDN_MAPPING_FILETYPE, $postfix ) ) {
continue;
}
}
else {
if ( ! $url2 = $this->rewrite( $url, Base::CDN_MAPPING_INC_IMG ) ) {
continue;
}
}
$attr = str_replace( $matches[ 1 ][ $k ], $url2, $matches[ 0 ][ $k ] );
$this->content = str_replace( $matches[ 0 ][ $k ], $attr, $this->content );
}
}
/**
* Hook to wp_get_attachment_image_src
*
* @since 1.2.3
* @since 1.7 Removed static from function
* @access public
* @param array $img The URL of the attachment image src, the width, the height
* @return array
*/
public function attach_img_src( $img ) {
if ( $img && $url = $this->rewrite( $img[ 0 ], Base::CDN_MAPPING_INC_IMG ) ) {
$img[ 0 ] = $url;
}
return $img;
}
/**
* Try to rewrite one URL with CDN
*
* @since 1.7
* @access public
*/
public function url_img( $url ) {
if ( $url && $url2 = $this->rewrite( $url, Base::CDN_MAPPING_INC_IMG ) ) {
$url = $url2;
}
return $url;
}
/**
* Try to rewrite one URL with CDN
*
* @since 1.7
* @access public
*/
public function url_css( $url ) {
if ( $url && $url2 = $this->rewrite( $url, Base::CDN_MAPPING_INC_CSS ) ) {
$url = $url2;
}
return $url;
}
/**
* Try to rewrite one URL with CDN
*
* @since 1.7
* @access public
*/
public function url_js( $url ) {
if ( $url && $url2 = $this->rewrite( $url, Base::CDN_MAPPING_INC_JS ) ) {
$url = $url2;
}
return $url;
}
/**
* Hook to replace WP responsive images
*
* @since 1.2.3
* @since 1.7 Removed static from function
* @access public
* @param array $srcs
* @return array
*/
public function srcset( $srcs ) {
if ( $srcs ) {
foreach ( $srcs as $w => $data ) {
if( ! $url = $this->rewrite( $data[ 'url' ], Base::CDN_MAPPING_INC_IMG ) ) {
continue;
}
$srcs[ $w ][ 'url' ] = $url;
}
}
return $srcs;
}
/**
* Replace URL to CDN URL
*
* @since 1.2.3
* @access public
* @param string $url
* @return string Replaced URL
*/
public function rewrite( $url, $mapping_kind, $postfix = false ) {
Debug2::debug2( '[CDN] rewrite ' . $url );
$url_parsed = parse_url( $url );
if ( empty( $url_parsed[ 'path' ] ) ) {
Debug2::debug2( '[CDN] -rewrite bypassed: no path' );
return false;
}
// Only images under wp-cotnent/wp-includes can be replaced
$is_internal_folder = Utility::str_hit_array( $url_parsed[ 'path' ], $this->_cfg_ori_dir );
if ( ! $is_internal_folder ) {
Debug2::debug2( '[CDN] -rewrite failed: path not match: ' . LSCWP_CONTENT_FOLDER );
return false;
}
// Check if is external url
if ( ! empty( $url_parsed[ 'host' ] ) ) {
if ( ! Utility::internal( $url_parsed[ 'host' ] ) && ! $this->_is_ori_url( $url ) ) {
Debug2::debug2( '[CDN] -rewrite failed: host not internal' );
return false;
}
}
$exclude = Utility::str_hit_array( $url, $this->_cfg_cdn_exclude );
if ( $exclude ) {
Debug2::debug2( '[CDN] -abort excludes ' . $exclude );
return false;
}
// Fill full url before replacement
if ( empty( $url_parsed[ 'host' ] ) ) {
$url = Utility::uri2url( $url );
Debug2::debug2( '[CDN] -fill before rewritten: ' . $url );
$url_parsed = parse_url( $url );
}
$scheme = ! empty( $url_parsed[ 'scheme' ] ) ? $url_parsed[ 'scheme' ] . ':' : '';
if ( $scheme ) {
// Debug2::debug2( '[CDN] -scheme from url: ' . $scheme );
}
// Find the mapping url to be replaced to
if ( empty( $this->_cfg_cdn_mapping[ $mapping_kind ] ) ) {
return false;
}
if ( $mapping_kind !== Base::CDN_MAPPING_FILETYPE ) {
$final_url = $this->_cfg_cdn_mapping[ $mapping_kind ];
}
else {
// select from file type
$final_url = $this->_cfg_cdn_mapping[ $postfix ];
}
// If filetype to url is one to many, need to random one
if ( is_array( $final_url ) ) {
$final_url = $final_url[ mt_rand( 0, count( $final_url ) - 1 ) ];
}
// Now lets replace CDN url
foreach ( $this->_cfg_url_ori as $v ) {
if ( strpos( $v, '*' ) !== false ) {
$url = preg_replace( '#' . $scheme . $v . '#iU', $final_url, $url );
}
else {
$url = str_replace( $scheme . $v, $final_url, $url );
}
}
Debug2::debug2( '[CDN] -rewritten: ' . $url );
return $url;
}
/**
* Check if is orignal URL of CDN or not
*
* @since 2.1
* @access private
*/
private function _is_ori_url( $url ) {
$url_parsed = parse_url( $url );
$scheme = ! empty( $url_parsed[ 'scheme' ] ) ? $url_parsed[ 'scheme' ] . ':' : '';
foreach ( $this->_cfg_url_ori as $v ) {
$needle = $scheme . $v;
if ( strpos( $v, '*' ) !== false ) {
if( preg_match( '#' . $needle . '#iU', $url ) ) {
return true;
}
}
else {
if ( strpos( $url, $needle ) === 0 ) {
return true;
}
}
}
return false;
}
/**
* Check if the host is the CDN internal host
*
* @since 1.2.3
*
*/
public static function internal( $host ) {
if ( defined( self::BYPASS ) ) {
return false;
}
$instance = self::cls();
return in_array( $host, $instance->cdn_mapping_hosts );// todo: can add $this->_is_ori_url() check in future
}
}

View File

@@ -0,0 +1,309 @@
<?php
/**
* The cloudflare CDN class.
*
* @since 2.1
* @package LiteSpeed
* @subpackage LiteSpeed/src/cdn
* @author LiteSpeed Technologies <info@litespeedtech.com>
*/
namespace LiteSpeed\CDN;
use LiteSpeed\Core;
use LiteSpeed\Base;
use LiteSpeed\Debug2;
use LiteSpeed\Router;
use LiteSpeed\Admin;
use LiteSpeed\Admin_Display;
defined( 'WPINC' ) || exit;
class Cloudflare extends Base {
const TYPE_PURGE_ALL = 'purge_all';
const TYPE_GET_DEVMODE = 'get_devmode';
const TYPE_SET_DEVMODE_ON = 'set_devmode_on';
const TYPE_SET_DEVMODE_OFF = 'set_devmode_off';
const ITEM_STATUS = 'status';
/**
* Update zone&name based on latest settings
*
* @since 3.0
* @access public
*/
public function try_refresh_zone() {
if ( ! $this->conf( self::O_CDN_CLOUDFLARE ) ) {
return;
}
$zone = $this->_fetch_zone();
if ( $zone ) {
$this->cls( 'Conf' )->update( self::O_CDN_CLOUDFLARE_NAME, $zone[ 'name' ] );
$this->cls( 'Conf' )->update( self::O_CDN_CLOUDFLARE_ZONE, $zone[ 'id' ] );
Debug2::debug( "[Cloudflare] Get zone successfully \t\t[ID] $zone[id]" );
}
else {
$this->cls( 'Conf' )->update( self::O_CDN_CLOUDFLARE_ZONE, '' );
Debug2::debug( '[Cloudflare] ❌ Get zone failed, clean zone' );
}
}
/**
* Get Cloudflare development mode
*
* @since 1.7.2
* @access private
*/
private function _get_devmode( $show_msg = true ) {
Debug2::debug( '[Cloudflare] _get_devmode' );
$zone = $this->_zone();
if ( ! $zone ) {
return;
}
$url = 'https://api.cloudflare.com/client/v4/zones/' . $zone . '/settings/development_mode';
$res = $this->_cloudflare_call( $url, 'GET', false, $show_msg );
if ( ! $res ) {
return;
}
Debug2::debug( '[Cloudflare] _get_devmode result ', $res );
$curr_status = self::get_option( self::ITEM_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
*/
private function _set_devmode( $type ) {
Debug2::debug( '[Cloudflare] _set_devmode' );
$zone = $this->_zone();
if ( ! $zone ) {
return;
}
$url = 'https://api.cloudflare.com/client/v4/zones/' . $zone . '/settings/development_mode';
$new_val = $type == self::TYPE_SET_DEVMODE_ON ? 'on' : 'off';
$data = array( 'value' => $new_val );
$res = $this->_cloudflare_call( $url, 'PATCH', $data );
if ( ! $res ) {
return;
}
$res = $this->_get_devmode( false );
if ( $res ) {
$msg = sprintf( __( 'Notified Cloudflare to set development mode to %s successfully.', 'litespeed-cache' ), strtoupper( $new_val ) );
Admin_Display::succeed( $msg );
}
}
/**
* Purge Cloudflare cache
*
* @since 1.7.2
* @access private
*/
private function _purge_all() {
Debug2::debug( '[Cloudflare] _purge_all' );
$cf_on = $this->conf( self::O_CDN_CLOUDFLARE );
if ( ! $cf_on ) {
$msg = __( 'Cloudflare API is set to off.', 'litespeed-cache' );
Admin_Display::error( $msg );
return;
}
$zone = $this->_zone();
if ( ! $zone ) {
return;
}
$url = 'https://api.cloudflare.com/client/v4/zones/' . $zone . '/purge_cache';
$data = array( 'purge_everything' => true );
$res = $this->_cloudflare_call( $url, 'DELETE', $data );
if ( $res ) {
$msg = __( 'Notified Cloudflare to purge all successfully.', 'litespeed-cache' );
Admin_Display::succeed( $msg );
}
}
/**
* Get current Cloudflare zone from cfg
*
* @since 1.7.2
* @access private
*/
private function _zone() {
$zone = $this->conf( self::O_CDN_CLOUDFLARE_ZONE );
if ( ! $zone ) {
$msg = __( 'No available Cloudflare zone', 'litespeed-cache' );
Admin_Display::error( $msg );
return false;
}
return $zone;
}
/**
* Get Cloudflare zone settings
*
* @since 1.7.2
* @access private
*/
private function _fetch_zone() {
$kw = $this->conf( self::O_CDN_CLOUDFLARE_NAME );
$url = 'https://api.cloudflare.com/client/v4/zones?status=active&match=all';
// Try exact match first
if ( $kw && strpos( $kw, '.' ) ) {
$zones = $this->_cloudflare_call( $url . '&name=' . $kw, 'GET', false, false );
if ( $zones ) {
Debug2::debug( '[Cloudflare] fetch_zone exact matched' );
return $zones[ 0 ];
}
}
// Can't find, try to get default one
$zones = $this->_cloudflare_call( $url, 'GET', false, false );
if ( ! $zones ) {
Debug2::debug( '[Cloudflare] fetch_zone no zone' );
return false;
}
if ( ! $kw ) {
Debug2::debug( '[Cloudflare] fetch_zone no set name, use first one by default' );
return $zones[ 0 ];
}
foreach ( $zones as $v ) {
if ( strpos( $v[ 'name' ], $kw ) !== false ) {
Debug2::debug( '[Cloudflare] fetch_zone matched ' . $kw . ' [name] ' . $v[ 'name' ] );
return $v;
}
}
// Can't match current name, return default one
Debug2::debug( '[Cloudflare] fetch_zone failed match name, use first one by default' );
return $zones[ 0 ];
}
/**
* Cloudflare API
*
* @since 1.7.2
* @access private
*/
private function _cloudflare_call( $url, $method = 'GET', $data = false, $show_msg = true ) {
Debug2::debug( "[Cloudflare] _cloudflare_call \t\t[URL] $url" );
if ( 40 == strlen($this->conf( self::O_CDN_CLOUDFLARE_KEY ))){
$headers = array(
'Content-Type' => 'application/json',
'Authorization' => 'Bearer ' . $this->conf( self::O_CDN_CLOUDFLARE_KEY ),
);
}
else {
$headers = array(
'Content-Type' => 'application/json',
'X-Auth-Email' => $this->conf( self::O_CDN_CLOUDFLARE_EMAIL ),
'X-Auth-Key' => $this->conf( self::O_CDN_CLOUDFLARE_KEY ),
);
}
$wp_args = array(
'method' => $method,
'headers' => $headers,
);
if ( $data ) {
if ( is_array( $data ) ) {
$data = json_encode( $data );
}
$wp_args[ 'body' ] = $data;
}
$resp = wp_remote_request( $url, $wp_args );
if ( is_wp_error( $resp ) ) {
Debug2::debug( '[Cloudflare] error in response' );
if ( $show_msg ) {
$msg = __( 'Failed to communicate with Cloudflare', 'litespeed-cache' );
Admin_Display::error( $msg );
}
return false;
}
$result = wp_remote_retrieve_body( $resp );
$json = json_decode( $result, true );
if ( $json && $json[ 'success' ] && $json[ 'result' ] ) {
Debug2::debug( "[Cloudflare] _cloudflare_call called successfully" );
if ( $show_msg ) {
$msg = __( 'Communicated with Cloudflare successfully.', 'litespeed-cache' );
Admin_Display::succeed( $msg );
}
return $json[ 'result' ];
}
Debug2::debug( "[Cloudflare] _cloudflare_call called failed: $result" );
if ( $show_msg ) {
$msg = __( 'Failed to communicate with Cloudflare', 'litespeed-cache' );
Admin_Display::error( $msg );
}
return false;
}
/**
* Handle all request actions from main cls
*
* @since 1.7.2
* @access public
*/
public function handler() {
$type = Router::verify_type();
switch ( $type ) {
case self::TYPE_PURGE_ALL :
$this->_purge_all();
break;
case self::TYPE_GET_DEVMODE :
$this->_get_devmode();
break;
case self::TYPE_SET_DEVMODE_ON :
case self::TYPE_SET_DEVMODE_OFF :
$this->_set_devmode( $type );
break;
default:
break;
}
Admin::redirect();
}
}

View File

@@ -0,0 +1,60 @@
<?php
/**
* The quic.cloud class.
*
* @since 2.4.1
* @package LiteSpeed
* @subpackage LiteSpeed/src/cdn
* @author LiteSpeed Technologies <info@litespeedtech.com>
*/
namespace LiteSpeed\CDN;
use LiteSpeed\Core;
use LiteSpeed\Cloud;
use LiteSpeed\Debug2;
use LiteSpeed\Base;
defined( 'WPINC' ) || exit;
class Quic extends Base {
private $_api_key;
const TYPE_REG = 'reg';
/**
* Notify CDN new config updated
*
* @access public
*/
public static function try_sync_config() {
$options = self::cls()->get_options();
if ( ! $options[ self::O_CDN_QUIC ] ) {
return false;
}
// 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 ] ) );
}
}
unset( $options[ self::O_MEDIA_LQIP_EXC ] );
// Rest url
$options[ '_rest' ] = function_exists( 'rest_get_url_prefix' ) ? rest_get_url_prefix() : apply_filters( 'rest_url_prefix', 'wp-json' );
// Add server env vars
$options[ '_server' ] = self::cls()->server_vars();
// Append hooks
$options[ '_tp_cookies' ] = apply_filters( 'litespeed_vary_cookies', array() );
Cloud::post( Cloud::SVC_D_SYNC_CONF, $options );
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,734 @@
<?php
/**
* The core plugin config class.
*
* This maintains all the options and settings for this plugin.
*
* @since 1.0.0
* @since 1.5 Moved into /inc
* @package LiteSpeed
* @subpackage LiteSpeed/inc
* @author LiteSpeed Technologies <info@litespeedtech.com>
*/
namespace LiteSpeed;
defined( 'WPINC' ) || exit;
class Conf extends Base {
const TYPE_SET = 'set';
private $_updated_ids = array();
private $_is_primary = false;
/**
* Specify init logic to avoid infinite loop when calling conf.cls instance
*
* @since 3.0
* @access public
*/
public function init() {
// Check if conf exists or not. If not, create them in DB (won't change version if is converting v2.9- data)
// Conf may be stale, upgrade later
$this->_conf_db_init();
/**
* Detect if has quic.cloud set
* @since 2.9.7
*/
if ( $this->conf( self::O_CDN_QUIC ) ) {
! defined( 'LITESPEED_ALLOWED' ) && define( 'LITESPEED_ALLOWED', true );
}
add_action( 'litespeed_conf_append', array( $this, 'option_append' ), 10, 2 );
add_action( 'litespeed_conf_force', array( $this, 'force_option' ), 10, 2 );
$this->define_cache();
}
/**
* Init conf related data
*
* @since 3.0
* @access private
*/
private function _conf_db_init() {
/**
* Try to load options first, network sites can override this later
*
* NOTE: Load before run `conf_upgrade()` to avoid infinite loop when getting conf in `conf_upgrade()`
*/
$this->load_options();
$ver = $this->conf( self::_VER );
/**
* Don't upgrade or run new installations other than from backend visit at the 2nd time (delay the update)
* In this case, just use default conf
*/
$has_delay_conf_tag = self::get_option( '__activation' );
if ( ! $ver || $ver != Core::VER ) {
if ( ( ! is_admin() && ! defined( 'LITESPEED_CLI' ) ) || ( ! $has_delay_conf_tag || $has_delay_conf_tag == -1 ) ) { // Reuse __activation to control the delay conf update
if ( ! $has_delay_conf_tag || $has_delay_conf_tag == -1 ) {
self::update_option( '__activation', Core::VER );
}
$this->set_conf( $this->load_default_vals() );
$this->_try_load_site_options();
// Disable new installation auto upgrade to avoid overwritten to customized data.ini
if ( ! $ver ) {
defined( 'LITESPEED_BYPASS_AUTO_V' ) || define( 'LITESPEED_BYPASS_AUTO_V', true );
}
return;
}
}
/**
* Version is less than v3.0, or, is a new installation
*/
if ( ! $ver ) {
// Try upgrade first (network will upgrade inside too)
Data::cls()->try_upgrade_conf_3_0();
}
else {
defined( 'LSCWP_CUR_V' ) || define( 'LSCWP_CUR_V', $ver );
/**
* Upgrade conf
*/
if ( $ver != Core::VER ) {
// Plugin version will be set inside
// Site plugin upgrade & version change will do in load_site_conf
Data::cls()->conf_upgrade( $ver );
}
}
/**
* Sync latest new options
*/
if ( ! $ver || $ver != Core::VER ) {
// Load default values
$this->load_default_vals();
if ( ! $ver ) { // New install
$this->set_conf( self::$_default_options );
}
// 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 );
}
}
/**
* Network sites only
*
* Override conf if is network subsites and chose `Use Primary Config`
*/
$this->_try_load_site_options();
// Mark as conf loaded
defined( 'LITESPEED_CONF_LOADED' ) || define( 'LITESPEED_CONF_LOADED', true );
/**
* Activation delayed file update
* Pros: This is to avoid file correction script changed in new versions
* Cons: Conf upgrade won't get file correction if there is new values that are used in file
*/
if ( $has_delay_conf_tag && $has_delay_conf_tag != -1 ) {
// Check new version @since 2.9.3
Cloud::version_check( 'activate' . ( defined( 'LSCWP_REF' ) ? '_' . LSCWP_REF : '' ) );
$this->update_confs(); // Files only get corrected in activation or saving settings actions.
}
if ( $has_delay_conf_tag != -1 ) {
self::update_option( '__activation', -1 );
}
}
/**
* Load all latest options from DB
*
* @since 3.0
* @access public
*/
public function load_options( $blog_id = null, $dry_run = false ) {
$options = array();
foreach ( self::$_default_options as $k => $v ) {
if ( ! is_null( $blog_id ) ) {
$options[ $k ] = self::get_blog_option( $blog_id, $k, $v );
}
else {
$options[ $k ] = self::get_option( $k, $v );
}
// Correct value type
$options[ $k ] = $this->type_casting( $options[ $k ], $k );
}
if ( $dry_run ) {
return $options;
}
// Bypass site special settings
if ( $blog_id !== null ) { // This is to load the primary settings ONLY
// These options are the ones that can be overwritten by primary
$options = array_diff_key( $options, array_flip( self::$SINGLE_SITE_OPTIONS ) );
$this->set_primary_conf( $options );
}
else {
$this->set_conf( $options );
}
// Append const options
if ( defined( 'LITESPEED_CONF' ) && LITESPEED_CONF ) {
foreach ( self::$_default_options as $k => $v ) {
$const = Base::conf_const( $k );
if ( defined( $const ) ) {
$this->set_const_conf( $k, $this->type_casting( constant( $const ), $k ) );
}
}
}
}
/**
* For multisite installations, the single site options need to be updated with the network wide options.
*
* @since 1.0.13
* @access private
*/
private function _try_load_site_options() {
if ( ! $this->_if_need_site_options() ) {
return;
}
$this->_conf_site_db_init();
$this->_is_primary = get_current_blog_id() == BLOG_ID_CURRENT_SITE;
// If network set to use primary setting
if ( $this->network_conf( self::NETWORK_O_USE_PRIMARY ) && ! $this->_is_primary ) { // subsites or network admin
// Get the primary site settings
// If it's just upgraded, 2nd blog is being visited before primary blog, can just load default config (won't hurt as this could only happen shortly)
$this->load_options( BLOG_ID_CURRENT_SITE );
}
// Overwrite single blog options with site options
foreach ( self::$_default_options as $k => $v ) {
if ( ! $this->has_network_conf( $k ) ) {
continue;
}
// $this->_options[ $k ] = $this->_network_options[ $k ];
// Special handler to `Enable Cache` option if the value is set to OFF
if ( $k == self::O_CACHE ) {
if ( $this->_is_primary ) {
if ( $this->conf( $k ) != $this->network_conf( $k ) ) {
if ( $this->conf( $k ) != self::VAL_ON2 ) {
continue;
}
}
}
else {
if ( $this->network_conf( self::NETWORK_O_USE_PRIMARY ) ) {
if ( $this->has_primary_conf( $k ) && $this->primary_conf( $k ) != self::VAL_ON2 ) { // This case will use primary_options override always
continue;
}
}
else {
if ( $this->conf( $k ) != self::VAL_ON2 ) {
continue;
}
}
}
}
// primary_options will store primary settings + network settings, OR, store the network settings for subsites
$this->set_primary_conf( $k, $this->network_conf( $k ) );
}
// var_dump($this->_options);
}
/**
* Check if needs to load site_options for network sites
*
* @since 3.0
* @access private
*/
private function _if_need_site_options() {
if ( ! is_multisite() ) {
return false;
}
// Check if needs to use site_options or not
// todo: check if site settings are separate bcos it will affect .htaccess
/**
* In case this is called outside the admin page
* @see https://codex.wordpress.org/Function_Reference/is_plugin_active_for_network
* @since 2.0
*/
if ( ! function_exists( 'is_plugin_active_for_network' ) ) {
require_once( ABSPATH . '/wp-admin/includes/plugin.php' );
}
// If is not activated on network, it will not have site options
if ( ! is_plugin_active_for_network( Core::PLUGIN_FILE ) ) {
if ( (int)$this->conf( self::O_CACHE ) == self::VAL_ON2 ) { // Default to cache on
$this->set_conf( self::_CACHE, true );
}
return false;
}
return true;
}
/**
* Init site conf and upgrade if necessary
*
* @since 3.0
* @access private
*/
private function _conf_site_db_init() {
$this->load_site_options();
$ver = $this->network_conf( self::_VER );
/**
* Don't upgrade or run new installations other than from backend visit
* In this case, just use default conf
*/
if ( ! $ver || $ver != Core::VER ) {
if ( ! is_admin() && ! defined( 'LITESPEED_CLI' ) ) {
$this->set_network_conf( $this->load_default_site_vals() );
return;
}
}
/**
* Upgrade conf
*/
if ( $ver && $ver != Core::VER ) {
// Site plugin versin will change inside
Data::cls()->conf_site_upgrade( $ver );
}
/**
* Is a new installation
*/
if ( ! $ver || $ver != Core::VER ) {
// Load default values
$this->load_default_site_vals();
// Init new default/missing options
foreach ( self::$_default_site_options as $k => $v ) {
// If the option existed, bypass updating
self::add_site_option( $k, $v );
}
}
}
/**
* Get the plugin's site wide options.
*
* If the site wide options are not set yet, set it to default.
*
* @since 1.0.2
* @access public
*/
public function load_site_options() {
if ( ! is_multisite() ) {
return null;
}
// Load all site options
foreach ( self::$_default_site_options as $k => $v ) {
$val = self::get_site_option( $k, $v );
$val = $this->type_casting( $val, $k, true );
$this->set_network_conf( $k, $val );
}
}
/**
* Append a 3rd party option to default options
*
* This will not be affected by network use primary site setting.
*
* NOTE: If it is a multi switch option, need to call `_conf_multi_switch()` first
*
* @since 3.0
* @access public
*/
public function option_append( $name, $default ) {
self::$_default_options[ $name ] = $default;
$this->set_conf( $name, self::get_option( $name, $default ) );
$this->set_conf( $name, $this->type_casting( $this->conf( $name ), $name ) );
}
/**
* Force an option to a certain value
*
* @since 2.6
* @access public
*/
public function force_option( $k, $v ) {
if ( ! $this->has_conf( $k ) ) {
return;
}
$v = $this->type_casting( $v, $k );
if ( $this->conf( $k ) === $v ) {
return;
}
Debug2::debug( "[Conf] ** $k forced from " . var_export( $this->conf( $k ), true ) . ' to ' . var_export( $v, true ) );
$this->set_conf( $k, $v );
}
/**
* Define `_CACHE` const in options ( for both single and network )
*
* @since 3.0
* @access public
*/
public function define_cache() {
// Init global const cache on setting
$this->set_conf( self::_CACHE, false );
if ( (int) $this->conf( self::O_CACHE ) == self::VAL_ON || $this->conf( self::O_CDN_QUIC ) ) {
$this->set_conf( self::_CACHE, true );
}
// Check network
if ( ! $this->_if_need_site_options() ) {
// Set cache on
$this->_define_cache_on();
return;
}
// If use network setting
if ( (int) $this->conf( self::O_CACHE ) == self::VAL_ON2 && $this->network_conf( self::O_CACHE ) ) {
$this->set_conf( self::_CACHE, true );
}
$this->_define_cache_on();
}
/**
* Define `LITESPEED_ON`
*
* @since 2.1
* @access private
*/
private function _define_cache_on() {
if ( ! $this->conf( self::_CACHE ) ) {
return;
}
defined( 'LITESPEED_ALLOWED' ) && ! defined( 'LITESPEED_ON' ) && define( 'LITESPEED_ON', true );
}
/**
* Get an option value
*
* @since 3.0
* @access public
* @deprecated 4.0 Use $this->conf() instead
*/
public static function val( $id, $ori = false ) {
error_log( 'Called deprecated function \LiteSpeed\Conf::val(). Please use API call instead.' );
return self::cls()->conf( $id, $ori );
}
/**
* Save option
*
* @since 3.0
* @access public
*/
public function update_confs( $the_matrix = false ) {
if ( $the_matrix ) {
foreach ( $the_matrix as $id => $val ) {
$this->update( $id, $val );
}
}
if ( $this->_updated_ids ) {
foreach ( $this->_updated_ids as $id ) {
// Special handler for QUIC.cloud domain key to clear all existing nodes
if ( $id == self::O_API_KEY ) {
$this->cls( 'Cloud' )->clear_cloud();
}
// Special handler for crawler: reset sitemap when drop_domain setting changed
if ( $id == self::O_CRAWLER_DROP_DOMAIN ) {
$this->cls( 'Crawler_Map' )->empty_map();
}
// Check if need to do a purge all or not
if ( $this->_conf_purge_all( $id ) ) {
Purge::purge_all( 'conf changed [id] ' . $id );
}
// Check if need to purge a tag
if ( $tag = $this->_conf_purge_tag( $id ) ) {
Purge::add( $tag );
}
// Update cron
if ( $this->_conf_cron( $id ) ) {
$this->cls( 'Task' )->try_clean( $id );
}
// Reset crawler bypassed list when any of the options WebP replace, guest mode, or cache mobile got changed
if ( $id == self::O_IMG_OPTM_WEBP_REPLACE || $id == self::O_GUEST || $id == self::O_CACHE_MOBILE ) {
$this->cls( 'Crawler' )->clear_disabled_list();
}
}
}
do_action( 'litespeed_update_confs', $the_matrix );
// Update related tables
$this->cls( 'Data' )->correct_tb_existance();
// Update related files
$this->cls( 'Activation' )->update_files();
/**
* CDN related actions - Cloudflare
*/
$this->cls( 'CDN\Cloudflare' )->try_refresh_zone();
/**
* CDN related actions - QUIC.cloud
* @since 2.3
*/
CDN\Quic::try_sync_config();
}
/**
* Save option
*
* Note: this is direct save, won't trigger corresponding file update or data sync. To save settings normally, always use `Conf->update_confs()`
*
* @since 3.0
* @access public
*/
public function update( $id, $val ) {
// Bypassed this bcos $this->_options could be changed by force_option()
// if ( $this->_options[ $id ] === $val ) {
// return;
// }
if ( $id == self::_VER ) {
return;
}
if ( ! array_key_exists( $id, self::$_default_options ) ) {
defined( 'LSCWP_LOG' ) && Debug2::debug( '[Conf] Invalid option ID ' . $id );
return;
}
if ( $val && $this->_conf_pswd( $id ) && ! preg_match( '/[^\*]/', $val ) ) {
return;
}
// Special handler for CDN Original URLs
if ( $id == self::O_CDN_ORI && ! $val ) {
$home_url = home_url( '/' );
$parsed = parse_url( $home_url );
$home_url = str_replace( $parsed[ 'scheme' ] . ':', '', $home_url );
$val = $home_url;
}
// Validate type
$val = $this->type_casting( $val, $id );
// Save data
self::update_option( $id, $val );
// Handle purge if setting changed
if ( $this->conf( $id ) != $val ) {
$this->_updated_ids[] = $id;
// Check if need to fire a purge or not (Here has to stay inside `update()` bcos need comparing old value)
if ( $this->_conf_purge( $id ) ) {
$diff = array_diff( $val, $this->conf( $id ) );
$diff2 = array_diff( $this->conf( $id ), $val );
$diff = array_merge( $diff, $diff2 );
// If has difference
foreach ( $diff as $v ) {
$v = ltrim( $v, '^' );
$v = rtrim( $v, '$' );
$this->cls( 'Purge' )->purge_url( $v );
}
}
}
// Update in-memory data
$this->set_conf( $id, $val );
}
/**
* Save network option
*
* @since 3.0
* @access public
*/
public function network_update( $id, $val ) {
if ( ! array_key_exists( $id, self::$_default_site_options ) ) {
defined( 'LSCWP_LOG' ) && Debug2::debug( '[Conf] Invalid network option ID ' . $id );
return;
}
if ( $val && $this->_conf_pswd( $id ) && ! preg_match( '/[^\*]/', $val ) ) {
return;
}
// Validate type
if ( is_bool( self::$_default_site_options[ $id ] ) ) {
$max = $this->_conf_multi_switch( $id );
if ( $max && $val > 1 ) {
$val %= $max + 1;
}
else {
$val = (bool) $val;
}
}
elseif ( is_array( self::$_default_site_options[ $id ] ) ) {
// from textarea input
if ( ! is_array( $val ) ) {
$val = Utility::sanitize_lines( $val, $this->_conf_filter( $id ) );
}
}
elseif ( ! is_string( self::$_default_site_options[ $id ] ) ) {
$val = (int) $val;
}
else {
// Check if the string has a limit set
$val = $this->_conf_string_val( $id, $val );
}
// Save data
self::update_site_option( $id, $val );
// Handle purge if setting changed
if ( $this->network_conf( $id ) != $val ) {
// Check if need to do a purge all or not
if ( $this->_conf_purge_all( $id ) ) {
Purge::purge_all( '[Conf] Network conf changed [id] ' . $id );
}
// Update in-memory data
$this->set_network_conf( $id, $val );
}
// No need to update cron here, Cron will register in each init
if ( $this->has_conf( $id ) ) {
$this->set_conf( $id, $val );
}
}
/**
* Check if one user role is in exclude optimization group settings
*
* @since 1.6
* @access public
* @param string $role The user role
* @return int The set value if already set
*/
public function in_optm_exc_roles( $role = null ) {
// Get user role
if ( $role === null ) {
$role = Router::get_role();
}
if ( ! $role ) {
return false;
}
return in_array( $role, $this->conf( self::O_OPTM_EXC_ROLES ) ) ? $role : false;
}
/**
* Set one config value directly
*
* @since 2.9
* @access private
*/
private function _set_conf() {
/**
* NOTE: For URL Query String setting,
* 1. If append lines to an array setting e.g. `cache-force_uri`, use `set[cache-force_uri][]=the_url`.
* 2. If replace the array setting with one line, use `set[cache-force_uri]=the_url`.
* 3. If replace the array setting with multi lines value, use 2 then 1.
*/
if ( empty( $_GET[ self::TYPE_SET ] ) || ! is_array( $_GET[ self::TYPE_SET ] ) ) {
return;
}
$the_matrix = array();
foreach ( $_GET[ self::TYPE_SET ] as $id => $v ) {
if ( ! $this->has_conf( $id ) ) {
continue;
}
// Append new item to array type settings
if ( is_array( $v ) && is_array( $this->conf( $id ) ) ) {
$v = array_merge( $this->conf( $id ), $v );
Debug2::debug( '[Conf] Appended to settings [' . $id . ']: ' . var_export( $v, true ) );
}
else {
Debug2::debug( '[Conf] Set setting [' . $id . ']: ' . var_export( $v, true ) );
}
$the_matrix[ $id ] = $v;
}
if ( ! $the_matrix ) {
return;
}
$this->update_confs( $the_matrix );
$msg = __( 'Changed setting successfully.', 'litespeed-cache' );
Admin_Display::succeed( $msg );
// Redirect if changed frontend URL
if ( ! empty( $_GET[ 'redirect' ] ) ) {
wp_redirect( $_GET[ 'redirect' ] );
exit();
}
}
/**
* Handle all request actions from main cls
*
* @since 2.9
* @access public
*/
public function handler() {
$type = Router::verify_type();
switch ( $type ) {
case self::TYPE_SET :
$this->_set_conf();
break;
default:
break;
}
Admin::redirect();
}
}

View File

@@ -0,0 +1,822 @@
<?php
/**
* The plugin cache-control class for X-Litespeed-Cache-Control
*
* @since 1.1.3
* @package LiteSpeed
* @subpackage LiteSpeed/inc
* @author LiteSpeed Technologies <info@litespeedtech.com>
*/
namespace LiteSpeed;
defined( 'WPINC' ) || exit;
class Control extends Root {
const BM_CACHEABLE = 1;
const BM_PRIVATE = 2;
const BM_SHARED = 4;
const BM_NO_VARY = 8;
const BM_FORCED_CACHEABLE = 32;
const BM_PUBLIC_FORCED = 64;
const BM_STALE = 128;
const BM_NOTCACHEABLE = 256;
const X_HEADER = 'X-LiteSpeed-Cache-Control';
protected static $_control = 0;
protected static $_custom_ttl = 0;
private $_response_header_ttls = array();
/**
* Init cache control
*
* @since 1.6.2
*/
public function init() {
/**
* Add vary filter for Role Excludes
* @since 1.6.2
*/
add_filter( 'litespeed_vary', array( $this, 'vary_add_role_exclude' ) );
// 301 redirect hook
add_filter( 'wp_redirect', array( $this, 'check_redirect' ), 10, 2 );
// Load response header conf
$this->_response_header_ttls = $this->conf( Base::O_CACHE_TTL_STATUS );
foreach ( $this->_response_header_ttls as $k => $v ) {
$v = explode( ' ', $v );
if ( empty( $v[ 0 ] ) || empty( $v[ 1 ] ) ) {
continue;
}
$this->_response_header_ttls[ $v[ 0 ] ] = $v[ 1 ];
}
if ( $this->conf( Base::O_PURGE_STALE ) ) {
$this->set_stale();
}
}
/**
* Exclude role from optimization filter
*
* @since 1.6.2
* @access public
*/
public function vary_add_role_exclude( $vary ) {
if ( $this->in_cache_exc_roles() ) {
$vary[ 'role_exclude_cache' ] = 1;
}
return $vary;
}
/**
* Check if one user role is in exclude cache group settings
*
* @since 1.6.2
* @since 3.0 Moved here from conf.cls
* @access public
* @param string $role The user role
* @return int The set value if already set
*/
public function in_cache_exc_roles( $role = null ) {
// Get user role
if ( $role === null ) {
$role = Router::get_role();
}
if ( ! $role ) {
return false;
}
return in_array( $role, $this->conf( Base::O_CACHE_EXC_ROLES ) ) ? $role : false;
}
/**
* 1. Initialize cacheable status for `wp` hook
* 2. Hook error page tags for cacheable pages
*
* @since 1.1.3
* @access public
*/
public function init_cacheable() {
// Hook `wp` to mark default cacheable status
// NOTE: Any process that does NOT run into `wp` hook will not get cacheable by default
add_action( 'wp', array( $this, 'set_cacheable' ), 5 );
// Hook WP REST to be cacheable
if ( $this->conf( Base::O_CACHE_REST ) ) {
add_action( 'rest_api_init', array( $this, 'set_cacheable' ), 5 );
}
// Cache resources
// NOTE: If any strange resource doesn't use normal WP logic `wp_loaded` hook, rewrite rule can handle it
$cache_res = $this->conf( Base::O_CACHE_RES );
if ( $cache_res ) {
$uri = esc_url( $_SERVER["REQUEST_URI"] );// todo: check if need esc_url()
$pattern = '!' . LSCWP_CONTENT_FOLDER . Htaccess::RW_PATTERN_RES . '!';
if ( preg_match( $pattern, $uri ) ) {
add_action( 'wp_loaded', array( $this, 'set_cacheable' ), 5 );
}
}
// Check error page
add_filter( 'status_header', array( $this, 'check_error_codes' ), 10, 2 );
}
/**
* Check if the page returns any error code.
*
* @since 1.0.13.1
* @access public
* @param $status_header
* @param $code
* @return $eror_status
*/
public function check_error_codes( $status_header, $code ) {
if ( array_key_exists( $code, $this->_response_header_ttls ) ) {
if ( self::is_cacheable() && ! $this->_response_header_ttls[ $code ] ) {
self::set_nocache( '[Ctrl] TTL is set to no cache [status_header] ' . $code );
}
// Set TTL
self::set_custom_ttl( $this->_response_header_ttls[ $code ] );
}
// Set cache tag
Tag::add( Tag::TYPE_HTTP . $code );
// Give the default status_header back
return $status_header;
}
/**
* Set no vary setting
*
* @access public
* @since 1.1.3
*/
public static function set_no_vary() {
if ( self::is_no_vary() ) {
return;
}
self::$_control |= self::BM_NO_VARY;
Debug2::debug( '[Ctrl] X Cache_control -> no-vary', 3 );
}
/**
* Get no vary setting
*
* @access public
* @since 1.1.3
*/
public static function is_no_vary() {
return self::$_control & self::BM_NO_VARY;
}
/**
* Set stale
*
* @access public
* @since 1.1.3
*/
public function set_stale() {
if ( self::is_stale() ) {
return;
}
self::$_control |= self::BM_STALE;
Debug2::debug('[Ctrl] X Cache_control -> stale');
}
/**
* Get stale
*
* @access public
* @since 1.1.3
*/
public static function is_stale() {
return self::$_control & self::BM_STALE;
}
/**
* Set cache control to shared private
*
* @access public
* @since 1.1.3
* @param string $reason The reason to no cache
*/
public static function set_shared( $reason = false ) {
if ( self::is_shared() ) {
return;
}
self::$_control |= self::BM_SHARED;
self::set_private();
if ( ! is_string( $reason ) ) {
$reason = false;
}
if ( $reason ) {
$reason = "( $reason )";
}
Debug2::debug( '[Ctrl] X Cache_control -> shared ' . $reason );
}
/**
* Check if is shared private
*
* @access public
* @since 1.1.3
*/
public static function is_shared() {
return (self::$_control & self::BM_SHARED) && self::is_private();
}
/**
* Set cache control to forced public
*
* @access public
* @since 1.7.1
*/
public static function set_public_forced( $reason = false ) {
if ( self::is_public_forced() ) {
return;
}
self::$_control |= self::BM_PUBLIC_FORCED;
if ( ! is_string( $reason ) ) {
$reason = false;
}
if ( $reason ) {
$reason = "( $reason )";
}
Debug2::debug( '[Ctrl] X Cache_control -> public forced ' . $reason );
}
/**
* Check if is public forced
*
* @access public
* @since 1.7.1
*/
public static function is_public_forced() {
return self::$_control & self::BM_PUBLIC_FORCED;
}
/**
* Set cache control to private
*
* @access public
* @since 1.1.3
* @param string $reason The reason to no cache
*/
public static function set_private( $reason = false ) {
if ( self::is_private() ) {
return;
}
self::$_control |= self::BM_PRIVATE;
if ( ! is_string( $reason ) ) {
$reason = false;
}
if ( $reason ) {
$reason = "( $reason )";
}
Debug2::debug( '[Ctrl] X Cache_control -> private ' . $reason );
}
/**
* Check if is private
*
* @access public
* @since 1.1.3
*/
public static function is_private() {
if ( defined( 'LITESPEED_GUEST' ) && LITESPEED_GUEST ) {
return false;
}
return self::$_control & self::BM_PRIVATE && ! self::is_public_forced();
}
/**
* Initialize cacheable status in `wp` hook, if not call this, by default it will be non-cacheable
*
* @access public
* @since 1.1.3
*/
public function set_cacheable( $reason = false ) {
self::$_control |= self::BM_CACHEABLE;
if ( ! is_string( $reason ) ) {
$reason = false;
}
if ( $reason ) {
$reason = ' [reason] ' . $reason;
}
Debug2::debug( '[Ctrl] X Cache_control init on' . $reason );
}
/**
* This will disable non-cacheable BM
*
* @access public
* @since 2.2
*/
public static function force_cacheable( $reason = false ) {
self::$_control |= self::BM_FORCED_CACHEABLE;
if ( ! is_string( $reason ) ) {
$reason = false;
}
if ( $reason ) {
$reason = ' [reason] ' . $reason;
}
Debug2::debug( '[Ctrl] Forced cacheable' . $reason );
}
/**
* Switch to nocacheable status
*
* @access public
* @since 1.1.3
* @param string $reason The reason to no cache
*/
public static function set_nocache( $reason = false ) {
self::$_control |= self::BM_NOTCACHEABLE;
if ( ! is_string( $reason ) ) {
$reason = false;
}
if ( $reason ) {
$reason = "( $reason )";
}
Debug2::debug( '[Ctrl] X Cache_control -> no Cache ' . $reason, 5 );
}
/**
* Check current notcacheable bit set
*
* @access public
* @since 1.1.3
* @return bool True if notcacheable bit is set, otherwise false.
*/
public static function isset_notcacheable() {
return self::$_control & self::BM_NOTCACHEABLE;
}
/**
* Check current force cacheable bit set
*
* @access public
* @since 2.2
*/
public static function is_forced_cacheable() {
return self::$_control & self::BM_FORCED_CACHEABLE;
}
/**
* Check current cacheable status
*
* @access public
* @since 1.1.3
* @return bool True if is still cacheable, otherwise false.
*/
public static function is_cacheable() {
if ( defined( 'LSCACHE_NO_CACHE' ) && LSCACHE_NO_CACHE ) {
Debug2::debug( '[Ctrl] LSCACHE_NO_CACHE constant defined' );
return false;
}
// Guest mode always cacheable
if ( defined( 'LITESPEED_GUEST' ) && LITESPEED_GUEST ) {
return true;
}
// If its forced public cacheable
if ( self::is_public_forced() ) {
return true;
}
// If its forced cacheable
if ( self::is_forced_cacheable() ) {
return true;
}
return ! self::isset_notcacheable() && self::$_control & self::BM_CACHEABLE;
}
/**
* Set a custom TTL to use with the request if needed.
*
* @access public
* @since 1.1.3
* @param mixed $ttl An integer or string to use as the TTL. Must be numeric.
*/
public static function set_custom_ttl( $ttl, $reason = false ) {
if ( is_numeric( $ttl ) ) {
self::$_custom_ttl = $ttl;
Debug2::debug( '[Ctrl] X Cache_control TTL -> ' . $ttl . ( $reason ? ' [reason] ' . $ttl : '' ) );
}
}
/**
* Generate final TTL.
*
* @access public
* @since 1.1.3
*/
public function get_ttl() {
if ( self::$_custom_ttl != 0 ) {
return self::$_custom_ttl;
}
// Check if is in timed url list or not
$timed_urls = Utility::wildcard2regex( $this->conf( Base::O_PURGE_TIMED_URLS ) );
$timed_urls_time = $this->conf( Base::O_PURGE_TIMED_URLS_TIME );
if ( $timed_urls && $timed_urls_time ) {
$current_url = Tag::build_uri_tag( true );
// Use time limit ttl
$scheduled_time = strtotime( $timed_urls_time );
$ttl = $scheduled_time - time();
if ( $ttl < 0 ) {
$ttl += 86400;// add one day
}
foreach ( $timed_urls as $v ) {
if ( strpos( $v, '*' ) !== false ) {
if( preg_match( '#' . $v . '#iU', $current_url ) ) {
Debug2::debug( '[Ctrl] X Cache_control TTL is limited to ' . $ttl . ' due to scheduled purge regex ' . $v );
return $ttl;
}
}
else {
if ( $v == $current_url ) {
Debug2::debug( '[Ctrl] X Cache_control TTL is limited to ' . $ttl . ' due to scheduled purge rule ' . $v );
return $ttl;
}
}
}
}
// Private cache uses private ttl setting
if ( self::is_private() ) {
return $this->conf( Base::O_CACHE_TTL_PRIV );
}
if ( is_front_page() ){
return $this->conf( Base::O_CACHE_TTL_FRONTPAGE );
}
$feed_ttl = $this->conf( Base::O_CACHE_TTL_FEED );
if ( is_feed() && $feed_ttl > 0 ) {
return $feed_ttl;
}
if ( $this->cls( 'REST' )->is_rest() || $this->cls( 'REST' )->is_internal_rest() ) {
return $this->conf( Base::O_CACHE_TTL_REST );
}
return $this->conf( Base::O_CACHE_TTL_PUB );
}
/**
* Check if need to set no cache status for redirection or not
*
* @access public
* @since 1.1.3
*/
public function check_redirect( $location, $status ) { // TODO: some env don't have SCRIPT_URI but only REQUEST_URI, need to be compatible
if ( ! empty( $_SERVER[ 'SCRIPT_URI' ] ) ) { // dont check $status == '301' anymore
Debug2::debug( "[Ctrl] 301 from " . $_SERVER[ 'SCRIPT_URI' ] );
Debug2::debug( "[Ctrl] 301 to $location" );
$to_check = array(
PHP_URL_SCHEME,
PHP_URL_HOST,
PHP_URL_PATH,
);
$is_same_redirect = true;
foreach ( $to_check as $v ) {
if ( parse_url( $_SERVER[ 'SCRIPT_URI' ], $v ) != parse_url( $location, $v ) ) {
$is_same_redirect = false;
Debug2::debug( "[Ctrl] 301 different redirection" );
break;
}
}
if ( $is_same_redirect ) {
self::set_nocache( '301 to same url' );
}
}
return $location;
}
/**
* Sets up the Cache Control header.
*
* @since 1.1.3
* @access public
* @return string empty string if empty, otherwise the cache control header.
*/
public function output() {
$esi_hdr = '';
if ( ESI::has_esi() ) {
$esi_hdr = ',esi=on';
}
$hdr = self::X_HEADER . ': ';
if ( defined( 'DONOTCACHEPAGE' ) && apply_filters( 'litespeed_const_DONOTCACHEPAGE', DONOTCACHEPAGE ) ) {
Debug2::debug( "[Ctrl] ❌ 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 ) {
Debug2::debug( "[Ctrl] ❌ forced no cache [reason] LSCACHE_NO_CACHE const" );
$hdr .= 'no-cache';
}
else if( $_SERVER[ 'REQUEST_METHOD' ] !== 'GET' ) {
Debug2::debug( "[Ctrl] ❌ forced no cache [reason] req not GET" );
$hdr .= 'no-cache';
}
else {
$hdr .= 'public';
$hdr .= ',max-age=' . $this->get_ttl();
}
$hdr .= $esi_hdr;
return $hdr;
}
// Fix cli `uninstall --deactivate` fatal err
if ( ! self::is_cacheable() ) {
$hdr .= 'no-cache' . $esi_hdr;
return $hdr;
}
if ( self::is_shared() ) {
$hdr .= 'shared,private';
}
elseif ( self::is_private() ) {
$hdr .= 'private';
}
else {
$hdr .= 'public';
}
if ( self::is_no_vary() ) {
$hdr .= ',no-vary';
}
$hdr .= ',max-age=' . $this->get_ttl() . $esi_hdr;
return $hdr;
}
/**
* Generate all `control` tags before output
*
* @access public
* @since 1.1.3
*/
public function finalize() {
if ( defined( 'LITESPEED_GUEST' ) && LITESPEED_GUEST ) {
return;
}
// Check if URI is forced public cache
$excludes = $this->conf( Base::O_CACHE_FORCE_PUB_URI );
$hit = Utility::str_hit_array( $_SERVER[ 'REQUEST_URI' ], $excludes, true );
if ( $hit ) {
list( $result, $this_ttl ) = $hit;
self::set_public_forced( 'Setting: ' . $result );
Debug2::debug( '[Ctrl] Forced public cacheable due to setting: ' . $result );
if ( $this_ttl ) {
self::set_custom_ttl( $this_ttl );
}
}
if ( self::is_public_forced() ) {
return;
}
// Check if URI is forced cache
$excludes = $this->conf( Base::O_CACHE_FORCE_URI );
$hit = Utility::str_hit_array( $_SERVER[ 'REQUEST_URI' ], $excludes, true );
if ( $hit ) {
list( $result, $this_ttl ) = $hit;
self::force_cacheable();
Debug2::debug( '[Ctrl] 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() ) {
Debug2::debug( '[Ctrl] 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() ) {
Debug2::debug( '[Ctrl] not cacheable after api_control' );
return;
}
if ( is_preview() ) {
self::set_nocache( 'preview page' );
return;
}
// Check litespeed setting to set cacheable status
if ( ! $this->_setting_cacheable() ) {
self::set_nocache();
return;
}
// If user has password cookie, do not cache (moved from vary)
global $post;
if ( ! empty($post->post_password) && isset($_COOKIE['wp-postpass_' . COOKIEHASH]) ) {
// If user has password cookie, do not cache
self::set_nocache('pswd cookie');
return;
}
// The following check to the end is ONLY for mobile
$is_mobile = apply_filters( 'litespeed_is_mobile', false );
if ( ! $this->conf( Base::O_CACHE_MOBILE ) ) {
if ( $is_mobile ) {
self::set_nocache( 'mobile' );
}
return;
}
$env_vary = isset( $_SERVER[ 'LSCACHE_VARY_VALUE' ] ) ? $_SERVER[ 'LSCACHE_VARY_VALUE' ] : false;
if ( ! $env_vary ) {
$env_vary = isset( $_SERVER[ 'HTTP_X_LSCACHE_VARY_VALUE' ] ) ? $_SERVER[ 'HTTP_X_LSCACHE_VARY_VALUE' ] : false;
}
if ( $env_vary && strpos( $env_vary, 'ismobile' ) !== false ) {
if ( ! wp_is_mobile() && ! $is_mobile ) {
self::set_nocache( 'is not mobile' ); // todo: no need to uncache, it will correct vary value in vary finalize anyways
return;
}
}
elseif ( wp_is_mobile() || $is_mobile ) {
self::set_nocache( 'is mobile' );
return;
}
}
/**
* Check if is mobile for filter `litespeed_is_mobile` in API
*
* @since 3.0
* @access public
*/
public static function is_mobile() {
return wp_is_mobile();
}
/**
* Check if a page is cacheable based on litespeed setting.
*
* @since 1.0.0
* @access private
* @return boolean True if cacheable, false otherwise.
*/
private function _setting_cacheable() {
// logged_in users already excluded, no hook added
if( ! empty( $_REQUEST[ Router::ACTION ] ) ) {
return $this->_no_cache_for( 'Query String Action' );
}
if ( $_SERVER[ 'REQUEST_METHOD' ] !== 'GET' ) {
return $this->_no_cache_for('not GET method:' . $_SERVER["REQUEST_METHOD"]);
}
if ( is_feed() && $this->conf( Base::O_CACHE_TTL_FEED ) == 0 ) {
return $this->_no_cache_for('feed');
}
if ( is_trackback() ) {
return $this->_no_cache_for('trackback');
}
if ( is_search() ) {
return $this->_no_cache_for('search');
}
// if ( !defined('WP_USE_THEMES') || !WP_USE_THEMES ) {
// return $this->_no_cache_for('no theme used');
// }
// Check private cache URI setting
$excludes = $this->conf( Base::O_CACHE_PRIV_URI );
$result = Utility::str_hit_array( $_SERVER[ 'REQUEST_URI' ], $excludes );
if ( $result ) {
self::set_private( 'Admin cfg Private Cached URI: ' . $result );
}
if ( ! self::is_forced_cacheable() ) {
// Check if URI is excluded from cache
$excludes = $this->conf( Base::O_CACHE_EXC );
$result = Utility::str_hit_array( $_SERVER[ 'REQUEST_URI' ], $excludes );
if ( $result ) {
return $this->_no_cache_for( 'Admin configured URI Do not cache: ' . $result );
}
// Check QS excluded setting
$excludes = $this->conf( Base::O_CACHE_EXC_QS );
if ( ! empty( $excludes ) && $qs = $this->_is_qs_excluded( $excludes ) ) {
return $this->_no_cache_for( 'Admin configured QS Do not cache: ' . $qs );
}
$excludes = $this->conf( Base::O_CACHE_EXC_CAT );
if ( ! empty( $excludes ) && has_category( $excludes ) ) {
return $this->_no_cache_for( 'Admin configured Category Do not cache.' );
}
$excludes = $this->conf( Base::O_CACHE_EXC_TAG );
if ( ! empty( $excludes ) && has_tag( $excludes ) ) {
return $this->_no_cache_for( 'Admin configured Tag Do not cache.' );
}
$excludes = $this->conf( Base::O_CACHE_EXC_COOKIES );
if ( ! empty( $excludes ) && ! empty( $_COOKIE ) ) {
$cookie_hit = array_intersect( array_keys( $_COOKIE ), $excludes );
if ( $cookie_hit ) {
return $this->_no_cache_for( 'Admin configured Cookie Do not cache.' );
}
}
$excludes = $this->conf( Base::O_CACHE_EXC_USERAGENTS );
if ( ! empty( $excludes ) && isset( $_SERVER[ 'HTTP_USER_AGENT' ] ) ) {
$nummatches = preg_match( Utility::arr2regex( $excludes ), $_SERVER[ 'HTTP_USER_AGENT' ] );
if ( $nummatches ) {
return $this->_no_cache_for('Admin configured User Agent Do not cache.');
}
}
// Check if is exclude roles ( Need to set Vary too )
if ( $result = $this->in_cache_exc_roles() ) {
return $this->_no_cache_for( 'Role Excludes setting ' . $result );
}
}
return true;
}
/**
* Write a debug message for if a page is not cacheable.
*
* @since 1.0.0
* @access private
* @param string $reason An explanation for why the page is not cacheable.
* @return boolean Return false.
*/
private function _no_cache_for( $reason ) {
Debug2::debug('[Ctrl] X Cache_control off - ' . $reason);
return false;
}
/**
* Check if current request has qs excluded setting
*
* @since 1.3
* @access private
* @param array $excludes QS excludes setting
* @return boolean|string False if not excluded, otherwise the hit qs list
*/
private function _is_qs_excluded( $excludes ) {
if ( ! empty( $_GET ) && $intersect = array_intersect( array_keys( $_GET ), $excludes ) ) {
return implode( ',', $intersect );
}
return false;
}
}

View File

@@ -0,0 +1,626 @@
<?php
/**
* The core plugin class.
*
* Note: Core doesn't allow $this->cls( 'Core' )
*
* @since 1.0.0
*/
namespace LiteSpeed;
defined( 'WPINC' ) || exit;
class Core extends Root {
const NAME = 'LiteSpeed Cache';
const PLUGIN_NAME = 'litespeed-cache';
const PLUGIN_FILE = 'litespeed-cache/litespeed-cache.php';
const VER = LSCWP_V;
const ACTION_DISMISS = 'dismiss';
const ACTION_PURGE_BY = 'PURGE_BY';
const ACTION_PURGE_EMPTYCACHE = 'PURGE_EMPTYCACHE';
const ACTION_QS_PURGE = 'PURGE';
const ACTION_QS_PURGE_SINGLE = 'PURGESINGLE';
const ACTION_QS_SHOW_HEADERS = 'SHOWHEADERS';
const ACTION_QS_PURGE_ALL = 'purge_all';
const ACTION_QS_PURGE_EMPTYCACHE = 'empty_all';
const ACTION_QS_NOCACHE = 'NOCACHE';
const HEADER_DEBUG = 'X-LiteSpeed-Debug';
protected static $_debug_show_header = false;
private $footer_comment = '';
/**
* Define the core functionality of the plugin.
*
* Set the plugin name and the plugin version that can be used throughout the plugin.
* Load the dependencies, define the locale, and set the hooks for the admin area and
* the public-facing side of the site.
*
* @since 1.0.0
*/
public function __construct() {
$this->cls( 'Conf' )->init();
// Check if debug is on
if ( $this->conf( Base::O_DEBUG ) ) {
$this->cls( 'Debug2' )->init();
}
/**
* Load API hooks
* @since 3.0
*/
$this->cls( 'API' )->init();
if ( defined( 'LITESPEED_ON' ) ) {
// Load third party detection if lscache enabled.
include_once LSCWP_DIR . 'thirdparty/entry.inc.php';
}
if ( $this->conf( Base::O_DEBUG_DISABLE_ALL ) ) {
! defined( 'LITESPEED_DISABLE_ALL' ) && define( 'LITESPEED_DISABLE_ALL', true );
}
/**
* Register plugin activate/deactivate/uninstall hooks
* NOTE: this can't be moved under after_setup_theme, otherwise activation will be bypassed somehow
* @since 2.7.1 Disabled admin&CLI check to make frontend able to enable cache too
*/
// if( is_admin() || defined( 'LITESPEED_CLI' ) ) {
$plugin_file = LSCWP_DIR . 'litespeed-cache.php';
register_activation_hook( $plugin_file, array( __NAMESPACE__ . '\Activation', 'register_activation' ) );
register_deactivation_hook( $plugin_file, array(__NAMESPACE__ . '\Activation', 'register_deactivation' ) );
register_uninstall_hook( $plugin_file, __NAMESPACE__ . '\Activation::uninstall_litespeed_cache' );
// }
add_action( 'plugins_loaded', array( $this, 'plugins_loaded' ) );
add_action( 'after_setup_theme', array( $this, 'init' ) );
// Check if there is a purge request in queue
$purge_queue = Purge::get_option( Purge::DB_QUEUE );
if ( $purge_queue && $purge_queue != -1 ) {
@header( $purge_queue );
Debug2::debug( '[Core] Purge Queue found&sent: ' . $purge_queue );
}
if ( $purge_queue != -1 ) {
Purge::update_option( Purge::DB_QUEUE, -1 ); // Use 0 to bypass purge while still enable db update as WP's update_option will check value===false to bypass update
}
$purge_queue = Purge::get_option( Purge::DB_QUEUE2 );
if ( $purge_queue && $purge_queue != -1 ) {
@header( $purge_queue );
Debug2::debug( '[Core] Purge2 Queue found&sent: ' . $purge_queue );
}
if ( $purge_queue != -1 ) {
Purge::update_option( Purge::DB_QUEUE2, -1 );
}
/**
* Hook internal REST
* @since 2.9.4
*/
$this->cls( 'REST' );
/**
* Hook wpnonce function
*
* Note: ESI nonce won't be available until hook after_setup_theme ESI init due to Guest Mode concern
* @since v4.1
*/
if ( $this->cls( 'Router' )->esi_enabled() && ! function_exists( 'wp_create_nonce' ) ) {
Debug2::debug( '[ESI] Overwrite wp_create_nonce()' );
litespeed_define_nonce_func();
}
}
/**
* Plugin loaded hooks
* @since 3.0
*/
public function plugins_loaded() {
load_plugin_textdomain( Core::PLUGIN_NAME, false, 'litespeed-cache/lang/' );
}
/**
* The plugin initializer.
*
* This function checks if the cache is enabled and ready to use, then determines what actions need to be set up based on the type of user and page accessed. Output is buffered if the cache is enabled.
*
* NOTE: WP user doesn't init yet
*
* @since 1.0.0
* @access public
*/
public function init() {
/**
* Added hook before init
* 3rd party preload hooks will be fired here too (e.g. Divi disable all in edit mode)
* @since 1.6.6
* @since 2.6 Added filter to all config values in Conf
*/
do_action( 'litespeed_init' );
// in `after_setup_theme`, before `init` hook
if ( ! defined( 'LITESPEED_BYPASS_AUTO_V' ) ) {
$this->cls( 'Activation' )->auto_update();
}
if( is_admin() ) {
$this->cls( 'Admin' );
}
if ( defined( 'LITESPEED_DISABLE_ALL' ) ) {
Debug2::debug( '[Core] Bypassed due to debug disable all setting' );
return;
}
do_action( 'litespeed_initing' );
ob_start( array( $this, 'send_headers_force' ) );
add_action( 'shutdown', array( $this, 'send_headers' ), 0 );
add_action( 'wp_footer', array( $this, 'footer_hook' ) );
/**
* Check if is non optm simulator
* @since 2.9
*/
if ( ! empty( $_GET[ Router::ACTION ] ) && $_GET[ Router::ACTION ] == 'before_optm' && ! apply_filters( 'litespeed_qs_forbidden', false ) ) {
Debug2::debug( '[Core] ⛑️ bypass_optm due to QS CTRL' );
! defined( 'LITESPEED_NO_OPTM' ) && define( 'LITESPEED_NO_OPTM', true );
}
/**
* Register vary filter
* @since 1.6.2
*/
$this->cls( 'Control' )->init();
// 1. Init vary
// 2. Init cacheable status
$this->cls( 'Vary' )->init();
// Init Purge hooks
$this->cls( 'Purge' )->init();
$this->cls( 'Tag' )->init();
// Load hooks that may be related to users
add_action( 'init', array( $this, 'after_user_init' ), 5 );
// Load 3rd party hooks
add_action( 'wp_loaded', array( $this, 'load_thirdparty' ), 2 );
}
/**
* Run hooks after user init
*
* @since 2.9.8
* @access public
*/
public function after_user_init() {
$this->cls( 'Router' )->is_role_simulation();
// Detect if is Guest mode or not also
$this->cls( 'Vary' )->after_user_init();
/**
* Preload ESI functionality for ESI request uri recovery
* @since 1.8.1
* @since 4.0 ESI init needs to be after Guest mode detection to bypass ESI if is under Guest mode
*/
$this->cls( 'ESI' )->init();
if ( ! is_admin() && ! defined( 'LITESPEED_GUEST_OPTM' ) && $result = $this->cls( 'Conf' )->in_optm_exc_roles() ) {
Debug2::debug( '[Core] ⛑️ bypass_optm: hit Role Excludes setting: ' . $result );
! defined( 'LITESPEED_NO_OPTM' ) && define( 'LITESPEED_NO_OPTM', true );
}
// Heartbeat control
$this->cls( 'Tool' )->heartbeat();
/**
* Backward compatibility for v4.2- @Ruikai
* TODO: Will change to hook in future versions to make it revertable
*/
if ( defined( 'LITESPEED_BYPASS_OPTM' ) && ! defined( 'LITESPEED_NO_OPTM' ) ) {
defined( 'LITESPEED_NO_OPTM', LITESPEED_BYPASS_OPTM );
}
if ( ! defined( 'LITESPEED_NO_OPTM' ) || ! LITESPEED_NO_OPTM ) {
// Check missing static files
$this->cls( 'Router' )->serve_static();
$this->cls( 'Media' )->init();
$this->cls( 'Placeholder' )->init();
$this->cls( 'Router' )->can_optm() && $this->cls( 'Optimize' )->init();
$this->cls( 'Localization' )->init();
// Hook cdn for attachements
$this->cls( 'CDN' )->init();
// load cron tasks
$this->cls( 'Task' )->init();
}
// load litespeed actions
if ( $action = Router::get_action() ) {
$this->proceed_action( $action );
}
// Load frontend GUI
if ( ! is_admin() ) {
$this->cls( 'GUI' )->init();
}
}
/**
* Run frontend actions
*
* @since 1.1.0
* @access public
*/
public function proceed_action( $action ) {
$msg = false;
// handle actions
switch ( $action ) {
case self::ACTION_QS_PURGE:
Purge::set_purge_related();
break;
case self::ACTION_QS_SHOW_HEADERS:
self::$_debug_show_header = true;
break;
case self::ACTION_QS_PURGE_SINGLE:
Purge::set_purge_single();
break;
case self::ACTION_QS_PURGE_ALL:
Purge::purge_all();
break;
case self::ACTION_PURGE_EMPTYCACHE:
case self::ACTION_QS_PURGE_EMPTYCACHE:
define( 'LSWCP_EMPTYCACHE', true );// clear all sites caches
Purge::purge_all();
$msg = __( 'Notified LiteSpeed Web Server to purge everything.', 'litespeed-cache' );
break;
case self::ACTION_PURGE_BY:
$this->cls( 'Purge' )->purge_list();
$msg = __( 'Notified LiteSpeed Web Server to purge the list.', 'litespeed-cache' );
break;
case self::ACTION_DISMISS:// Even its from ajax, we don't need to register wp ajax callback function but directly use our action
GUI::dismiss();
break;
default:
$msg = $this->cls( 'Router' )->handler( $action );
break;
}
if ( $msg && ! Router::is_ajax() ) {
Admin_Display::add_notice( Admin_Display::NOTICE_GREEN, $msg );
Admin::redirect();
return;
}
if ( Router::is_ajax() ) {
exit;
}
}
/**
* Callback used to call the detect third party action.
*
* The detect action is used by third party plugin integration classes to determine if they should add the rest of their hooks.
*
* @since 1.0.5
* @access public
*/
public function load_thirdparty() {
do_action( 'litespeed_load_thirdparty' );
}
/**
* Mark wp_footer called
*
* @since 1.3
* @access public
*/
public function footer_hook() {
Debug2::debug( '[Core] Footer hook called' );
if ( ! defined( 'LITESPEED_FOOTER_CALLED' ) ) {
define( 'LITESPEED_FOOTER_CALLED', true );
}
}
/**
* Tigger coment info display hook
*
* @since 1.3
* @access private
*/
private function _check_is_html( $buffer = null ) {
if ( ! defined( 'LITESPEED_FOOTER_CALLED' ) ) {
Debug2::debug2( '[Core] CHK html bypass: miss footer const' );
return;
}
if ( defined( 'DOING_AJAX' ) ) {
Debug2::debug2( '[Core] CHK html bypass: doing ajax' );
return;
}
if ( defined( 'DOING_CRON' ) ) {
Debug2::debug2( '[Core] CHK html bypass: doing cron' );
return;
}
if ( $_SERVER[ 'REQUEST_METHOD' ] !== 'GET' ) {
Debug2::debug2( '[Core] CHK html bypass: not get method ' . $_SERVER[ 'REQUEST_METHOD' ] );
return;
}
if ( $buffer === null ) {
$buffer = ob_get_contents();
}
// double check to make sure it is a html file
if ( strlen( $buffer ) > 300 ) {
$buffer = substr( $buffer, 0, 300 );
}
if ( strstr( $buffer, '<!--' ) !== false ) {
$buffer = preg_replace( '/<!--.*?-->/s', '', $buffer );
}
$buffer = trim( $buffer );
$buffer = File::remove_zero_space( $buffer );
$is_html = stripos( $buffer, '<html' ) === 0 || stripos( $buffer, '<!DOCTYPE' ) === 0;
if ( ! $is_html ) {
Debug2::debug( '[Core] Footer check failed: ' . ob_get_level() . '-' . substr( $buffer, 0, 100 ) );
return;
}
Debug2::debug( '[Core] Footer check passed' );
if ( ! defined( 'LITESPEED_IS_HTML' ) ) {
define( 'LITESPEED_IS_HTML', true );
}
}
/**
* For compatibility with those plugins have 'Bad' logic that forced all buffer output even it is NOT their buffer :(
*
* Usually this is called after send_headers() if following orignal WP process
*
* @since 1.1.5
* @access public
* @param string $buffer
* @return string
*/
public function send_headers_force( $buffer ) {
$this->_check_is_html( $buffer );
// Hook to modify buffer before
$buffer = apply_filters('litespeed_buffer_before', $buffer);
/**
* Media: Image lazyload && WebP
* GUI: Clean wrapper mainly for esi block NOTE: this needs to be before optimizer to avoid wrapper being removed
* Optimize
* CDN
*/
if ( ! defined( 'LITESPEED_NO_OPTM' ) || ! LITESPEED_NO_OPTM ) {
$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 );
if ( $this->footer_comment ) {
$buffer .= $this->footer_comment;
}
/**
* If ESI req is JSON, give the content JSON format
* @since 2.9.3
* @since 2.9.4 ESI req could be from internal REST call, so moved json_encode out of this cond
*/
if ( defined( 'LSCACHE_IS_ESI' ) ) {
Debug2::debug( '[Core] ESI Start 👇' );
if ( strlen( $buffer ) > 500 ) {
Debug2::debug( trim( substr( $buffer, 0, 500 ) ) . '.....' );
}
else {
Debug2::debug( $buffer );
}
Debug2::debug( '[Core] ESI End 👆' );
Debug2::debug( $buffer );
}
if ( apply_filters( 'litespeed_is_json', false ) ) {
if ( json_decode( $buffer, true ) == NULL ) {
Debug2::debug( '[Core] Buffer converting to JSON' );
$buffer = json_encode( $buffer );
$buffer = trim( $buffer, '"' );
}
else {
Debug2::debug( '[Core] JSON Buffer' );
}
}
// Hook to modify buffer after
$buffer = apply_filters('litespeed_buffer_after', $buffer);
Debug2::debug( "End response\n--------------------------------------------------------------------------------\n" );
return $buffer;
}
/**
* Sends the headers out at the end of processing the request.
*
* This will send out all LiteSpeed Cache related response headers needed for the post.
*
* @since 1.0.5
* @access public
* @param boolean $is_forced If the header is sent following our normal finalizing logic
*/
public function send_headers( $is_forced = false ) {
// Make sure header output only run once
if ( ! defined( 'LITESPEED_DID_' . __FUNCTION__ ) ) {
define( 'LITESPEED_DID_' . __FUNCTION__, true );
}
else {
return;
}
$this->_check_is_html();
// NOTE: cache ctrl output needs to be done first, as currently some varies are added in 3rd party hook `litespeed_api_control`.
$this->cls( 'Control' )->finalize();
$vary_header = $this->cls( 'Vary' )->finalize();
// If is not cacheable but Admin QS is `purge` or `purgesingle`, `tag` still needs to be generated
$tag_header = $this->cls( 'Tag' )->output();
if ( ! $tag_header && Control::is_cacheable() ) {
Control::set_nocache( 'empty tag header' );
}
// NOTE: `purge` output needs to be after `tag` output as Admin QS may need to send `tag` header
$purge_header = Purge::output();
// generate `control` header in the end in case control status is changed by other headers.
$control_header = $this->cls( 'Control' )->output();
// Init comment info
$running_info_showing = defined( 'LITESPEED_IS_HTML' ) || defined( 'LSCACHE_IS_ESI' );
if ( defined( 'LSCACHE_ESI_SILENCE' ) ) {
$running_info_showing = false;
Debug2::debug( '[Core] ESI silence' );
}
/**
* Silence comment for json req
* @since 2.9.3
*/
if ( REST::cls()->is_rest() || Router::is_ajax() ) {
$running_info_showing = false;
Debug2::debug( '[Core] Silence Comment due to REST/AJAX' );
}
$running_info_showing = apply_filters( 'litespeed_comment', $running_info_showing );
if ( $running_info_showing ) {
// Give one more break to avoid ff crash
if ( ! defined( 'LSCACHE_IS_ESI' ) ) {
$this->footer_comment .= "\n";
}
$cache_support = 'supported';
if ( defined( 'LITESPEED_ON' ) ) {
$cache_support = Control::is_cacheable() ? 'generated' : 'uncached';
}
$this->footer_comment .= sprintf(
'<!-- %1$s %2$s by LiteSpeed Cache %4$s on %3$s -->',
defined( 'LSCACHE_IS_ESI' ) ? 'Block' : 'Page',
$cache_support,
date( 'Y-m-d H:i:s', time() + LITESPEED_TIME_OFFSET ),
self::VER
);
}
// send Control header
if ( defined( 'LITESPEED_ON' ) && $control_header ) {
@header( $control_header );
if ( defined( 'LSCWP_LOG' ) ) {
Debug2::debug( '💰 ' . $control_header );
if ( $running_info_showing ) {
$this->footer_comment .= "\n<!-- " . $control_header . " -->";
}
}
}
// send PURGE header (Always send regardless of cache setting disabled/enabled)
if ( defined( 'LITESPEED_ON' ) && $purge_header ) {
@header( $purge_header );
Debug2::log_purge( $purge_header );
if ( defined( 'LSCWP_LOG' ) ) {
Debug2::debug( '💰 ' . $purge_header );
if ( $running_info_showing ) {
$this->footer_comment .= "\n<!-- " . $purge_header . " -->";
}
}
}
// send Vary header
if ( defined( 'LITESPEED_ON' ) && $vary_header ) {
@header( $vary_header );
if ( defined( 'LSCWP_LOG' ) ) {
Debug2::debug( '💰 ' . $vary_header );
if ( $running_info_showing ) {
$this->footer_comment .= "\n<!-- " . $vary_header . " -->";
}
}
}
// 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 . '; ';
}
@header( $debug_header );
Debug2::debug( $debug_header );
}
else {
// Control header
if ( defined( 'LITESPEED_ON' ) && Control::is_cacheable() && $tag_header ) {
@header( $tag_header );
if ( defined( 'LSCWP_LOG' ) ) {
Debug2::debug( '💰 ' . $tag_header );
if ( $running_info_showing ) {
$this->footer_comment .= "\n<!-- " . $tag_header . " -->";
}
}
}
}
// Object cache comment
if ( $running_info_showing && defined( 'LSCWP_LOG' ) && defined( 'LSCWP_OBJECT_CACHE' ) && method_exists( 'WP_Object_Cache', 'debug' ) ) {
$this->footer_comment .= "\n<!-- Object Cache " . \WP_Object_Cache::get_instance()->debug() . " -->";
}
if ( defined( 'LITESPEED_GUEST' ) && LITESPEED_GUEST && $running_info_showing ) {
$this->footer_comment .= "\n<!-- Guest Mode -->";
}
if ( $is_forced ) {
Debug2::debug( '--forced--' );
}
}
}

View File

@@ -0,0 +1,508 @@
<?php
/**
* The Crawler Sitemap Class
*
* @since 1.1.0
*/
namespace LiteSpeed;
defined( 'WPINC' ) || exit;
class Crawler_Map extends Root {
const BM_MISS = 1;
const BM_HIT = 2;
const BM_BLACKLIST = 4;
private $_home_url; // Used to simplify urls
private $_tb;
private $__data;
private $_conf_map_timeout;
private $_urls = array();
/**
* Instantiate the class
*
* @since 1.1.0
*/
public function __construct() {
$this->_home_url = get_home_url();
$this->__data = Data::cls();
$this->_tb = $this->__data->tb( 'crawler' );
$this->_tb_blacklist = $this->__data->tb( 'crawler_blacklist' );
$this->_conf_map_timeout = $this->conf( Base::O_CRAWLER_MAP_TIMEOUT );
}
/**
* Save URLs crawl status into DB
*
* @since 3.0
* @access public
*/
public function save_map_status( $list, $curr_crawler ) {
global $wpdb;
Utility::compatibility();
$total_crawler = count( Crawler::cls()->list_crawlers() );
$total_crawler_pos = $total_crawler - 1;
// Replace current crawler's position
$curr_crawler = (int) $curr_crawler;
foreach ( $list as $bit => $ids ) { // $ids = [ id => [ url, code ], ... ]
if ( ! $ids ) {
continue;
}
Debug2::debug( "🐞🗺️ Update map [crawler] $curr_crawler [bit] $bit [count] " . count( $ids ) );
// Update res first, then reason
$right_pos = $total_crawler_pos - $curr_crawler;
$sql_res = "CONCAT( LEFT( res, $curr_crawler ), '$bit', RIGHT( res, $right_pos ) )";
$id_all = implode( ',', array_map( 'intval', array_keys( $ids ) ) );
$wpdb->query( "UPDATE `$this->_tb` SET res = $sql_res WHERE id IN ( $id_all )" );
// Add blacklist
if ( $bit == 'B' || $bit == 'N' ) {
$q = "SELECT a.id, a.url FROM `$this->_tb_blacklist` a LEFT JOIN `$this->_tb` b ON b.url=a.url WHERE b.id IN ( $id_all )";
$existing = $wpdb->get_results( $q, ARRAY_A );
// Update current crawler status tag in existing blacklist
if ( $existing ) {
$count = $wpdb->query( "UPDATE `$this->_tb_blacklist` SET res = $sql_res WHERE id IN ( " . implode( ',', array_column( $existing, 'id' ) ) . " )" );
Debug2::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') );
Debug2::debug( '🐞🗺️ Insert into blacklist [count] ' . count( $new_urls ) );
$q = "INSERT INTO `$this->_tb_blacklist` ( url, res, reason ) VALUES " . implode( ',', array_fill( 0, count( $new_urls ), '( %s, %s, %s )' ) );
$data = array();
$res = array_fill( 0, $total_crawler, '-' );
$res[ $curr_crawler ] = $bit;
$res = implode( '', $res );
$default_reason = $total_crawler > 1 ? str_repeat( ',', $total_crawler - 1 ) : ''; // Pre-populate default reason value first, update later
foreach ( $new_urls as $url ) {
$data[] = $url;
$data[] = $res;
$data[] = $default_reason;
}
$wpdb->query( $wpdb->prepare( $q, $data ) );
}
}
// Update sitemap reason w/ HTTP code
$reason_array = array();
foreach ( $ids as $id => $v2 ) {
$code = (int)$v2[ 'code' ];
if ( empty( $reason_array[ $code ] ) ) {
$reason_array[ $code ] = array();
}
$reason_array[ $code ][] = (int)$id;
}
foreach ( $reason_array as $code => $v2 ) {
// Complement comma
if ( $curr_crawler ) {
$code = ',' . $code;
}
if ( $curr_crawler < $total_crawler_pos ) {
$code .= ',';
}
$count = $wpdb->query( "UPDATE `$this->_tb` SET reason = CONCAT( SUBSTRING_INDEX( reason, ',', $curr_crawler ), '$code', SUBSTRING_INDEX( reason, ',', -$right_pos ) ) WHERE id IN (" . implode( ',', $v2 ) . ")" );
Debug2::debug( "🐞🗺️ Update map reason [code] $code [pos] left $curr_crawler right -$right_pos [count] $count" );
// Update blacklist reason
if ( $bit == 'B' || $bit == 'N' ) {
$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 ) . ")" );
Debug2::debug( "🐞🗺️ Update blacklist [code] $code [pos] left $curr_crawler right -$right_pos [count] $count" );
}
}
// Reset list
$list[ $bit ] = array();
}
return $list;
}
/**
* Add one record to blacklist
* NOTE: $id is sitemap table ID
*
* @since 3.0
* @access public
*/
public function blacklist_add( $id ) {
global $wpdb;
$id = (int)$id;
// Build res&reason
$total_crawler = count( Crawler::cls()->list_crawlers() );
$res = str_repeat( 'B', $total_crawler );
$reason = implode( ',', array_fill( 0, $total_crawler, 'Man' ) );
$row = $wpdb->get_row( "SELECT a.url, b.id FROM `$this->_tb` a LEFT JOIN `$this->_tb_blacklist` b ON b.url = a.url WHERE a.id = '$id'", ARRAY_A );
if ( ! $row ) {
Debug2::debug( '🐞🗺️ blacklist failed to add [id] ' . $id );
return;
}
Debug2::debug( '🐞🗺️ Add to blacklist [url] ' . $row[ 'url' ] );
$q = "UPDATE `$this->_tb` SET res = %s, reason = %s WHERE id = %d";
$wpdb->query( $wpdb->prepare( $q, array( $res, $reason, $id ) ) );
if ( $row[ 'id' ] ) {
$q = "UPDATE `$this->_tb_blacklist` SET res = %s, reason = %s WHERE id = %d";
$wpdb->query( $wpdb->prepare( $q, array( $res, $reason, $row[ 'id' ] ) ) );
}
else {
$q = "INSERT INTO `$this->_tb_blacklist` (url, res, reason) VALUES (%s, %s, %s)";
$wpdb->query( $wpdb->prepare( $q, array( $row[ 'url' ], $res, $reason ) ) );
}
}
/**
* Delete one record from blacklist
*
* @since 3.0
* @access public
*/
public function blacklist_del( $id ) {
global $wpdb;
if ( ! $this->__data->tb_exist( 'crawler_blacklist' ) ) {
return;
}
$id = (int)$id;
Debug2::debug( '🐞🗺️ blacklist delete [id] ' . $id );
$wpdb->query( "UPDATE `$this->_tb` SET res = REPLACE( REPLACE( res, 'N', '-' ), 'B', '-' ) WHERE url = ( SELECT url FROM `$this->_tb_blacklist` WHERE id = '$id' )" );
$wpdb->query( "DELETE FROM `$this->_tb_blacklist` WHERE id = '$id'" );
}
/**
* Empty blacklist
*
* @since 3.0
* @access public
*/
public function blacklist_empty() {
global $wpdb;
if ( ! $this->__data->tb_exist( 'crawler_blacklist' ) ) {
return;
}
Debug2::debug( '🐞🗺️ Truncate blacklist' );
$wpdb->query( "UPDATE `$this->_tb` SET res = REPLACE( REPLACE( res, 'N', '-' ), 'B', '-' )" );
$wpdb->query( "TRUNCATE `$this->_tb_blacklist`" );
}
/**
* List blacklist
*
* @since 3.0
* @access public
*/
public function list_blacklist( $limit = false, $offset = false ) {
global $wpdb;
if ( ! $this->__data->tb_exist( 'crawler_blacklist' ) ) {
return array();
}
$q = "SELECT * FROM `$this->_tb_blacklist` ORDER BY id DESC";
if ( $limit !== false ) {
if ( $offset === false ) {
$total = $this->count_blacklist();
$offset = Utility::pagination( $total, $limit, true );
}
$q .= " LIMIT %d, %d";
$q = $wpdb->prepare( $q, $offset, $limit );
}
return $wpdb->get_results( $q, ARRAY_A );
}
/**
* Count blacklist
*/
public function count_blacklist() {
global $wpdb;
if ( ! $this->__data->tb_exist( 'crawler_blacklist' ) ) {
return false;
}
$q = "SELECT COUNT(*) FROM `$this->_tb_blacklist`";
return $wpdb->get_var( $q );
}
/**
* Empty sitemap
*
* @since 3.0
* @access public
*/
public function empty_map() {
Data::cls()->tb_del( 'crawler' );
$msg = __( 'Sitemap cleaned successfully', 'litespeed-cache' );
Admin_Display::succeed( $msg );
}
/**
* List generated sitemap
*
* @since 3.0
* @access public
*/
public function list_map( $limit, $offset = false ) {
global $wpdb;
if ( ! $this->__data->tb_exist( 'crawler' ) ) {
return array();
}
if ( $offset === false ) {
$total = $this->count_map();
$offset = Utility::pagination( $total, $limit, true );
}
$q = "SELECT * FROM `$this->_tb` ORDER BY id LIMIT %d, %d";
return $wpdb->get_results( $wpdb->prepare( $q, $offset, $limit ), ARRAY_A );
}
/**
* Count sitemap
*/
public function count_map() {
global $wpdb;
if ( ! $this->__data->tb_exist( 'crawler' ) ) {
return false;
}
$q = "SELECT COUNT(*) FROM `$this->_tb`";
return $wpdb->get_var( $q );
}
/**
* Generate sitemap
*
* @since 1.1.0
* @access public
*/
public function gen() {
$count = $this->_gen();
if ( ! $count ) {
Admin_Display::error( __( 'No valid sitemap parsed for crawler.', 'litespeed-cache' ) );
return;
}
$msg = sprintf( __( 'Sitemap created successfully: %d items', 'litespeed-cache' ), $count );
Admin_Display::succeed( $msg );
}
/**
* Generate the sitemap
*
* @since 1.1.0
* @access private
*/
private function _gen() {
global $wpdb;
if ( ! $this->__data->tb_exist( 'crawler' ) ) {
$this->__data->tb_create( 'crawler' );
}
if ( ! $this->__data->tb_exist( 'crawler_blacklist' ) ) {
$this->__data->tb_create( 'crawler_blacklist' );
}
// use custom sitemap
if ( ! $sitemap = $this->conf( Base::O_CRAWLER_SITEMAP ) ) {
return false;
}
$offset = strlen( $this->_home_url );
try {
$this->_parse( $sitemap );
} catch( \Exception $e ) {
Debug2::debug( '🐞🗺️ ❌ failed to parse custom sitemap: ' . $e->getMessage() );
}
if ( is_array( $this->_urls ) && ! empty( $this->_urls ) ) {
if ( $this->conf( Base::O_CRAWLER_DROP_DOMAIN ) ) {
foreach ( $this->_urls as $k => $v ) {
if ( stripos( $v, $this->_home_url ) !== 0 ) {
unset( $this->_urls[ $k ] );
continue;
}
$this->_urls[ $k ] = substr( $v, $offset );
}
}
$this->_urls = array_unique( $this->_urls );
}
Debug2::debug( '🐞🗺️ Truncate sitemap' );
$wpdb->query( "TRUNCATE `$this->_tb`" );
Debug2::debug( '🐞🗺️ Generate sitemap' );
// Filter URLs in blacklist
$blacklist = $this->list_blacklist();
$full_blacklisted = array();
$partial_blacklisted = array();
foreach ( $blacklist as $v ) {
if ( strpos( $v[ 'res' ], '-' ) === false ) { // Full blacklisted
$full_blacklisted[] = $v[ 'url' ];
}
else {
// Replace existing reason
$v[ 'reason' ] = explode( ',', $v[ 'reason' ] );
$v[ 'reason' ] = array_map( function( $element ){ return $element ? 'Existed' : ''; }, $v[ 'reason' ] );
$v[ 'reason' ] = implode( ',', $v[ 'reason' ] );
$partial_blacklisted[ $v[ 'url' ] ] = array(
'res' => $v[ 'res' ],
'reason' => $v[ 'reason' ],
);
}
}
// Drop all blacklisted URLs
$this->_urls = array_diff( $this->_urls, $full_blacklisted );
// Default res & reason
$crawler_count = count( Crawler::cls()->list_crawlers() );
$default_res = str_repeat( '-', $crawler_count );
$default_reason = $crawler_count > 1 ? str_repeat( ',', $crawler_count - 1 ) : '';
$data = array();
foreach ( $this->_urls as $url ) {
$data[] = $url;
$data[] = array_key_exists( $url, $partial_blacklisted ) ? $partial_blacklisted[ $url ][ 'res' ] : $default_res;
$data[] = array_key_exists( $url, $partial_blacklisted ) ? $partial_blacklisted[ $url ][ 'reason' ] : $default_reason;
}
foreach ( array_chunk( $data, 300 ) as $data2 ) {
$this->_save( $data2 );
}
// Reset crawler
Crawler::cls()->reset_pos();
return count( $this->_urls );
}
/**
* Save data to table
*
* @since 3.0
* @access private
*/
private function _save( $data, $fields = 'url,res,reason' ) {
global $wpdb;
if ( empty( $data ) ) {
return;
}
$q = "INSERT INTO `$this->_tb` ( $fields ) VALUES ";
// Add placeholder
$q .= Utility::chunk_placeholder( $data, $fields );
// Store data
$wpdb->query( $wpdb->prepare( $q, $data ) );
}
/**
* Parse custom sitemap and return urls
*
* @since 1.1.1
* @access private
*/
private function _parse( $sitemap ) {
/**
* Read via wp func to avoid allow_url_fopen = off
* @since 2.2.7
*/
$response = wp_remote_get( $sitemap, array( 'timeout' => $this->_conf_map_timeout ) );
if ( is_wp_error( $response ) ) {
$error_message = $response->get_error_message();
Debug2::debug( '🐞🗺️ failed to read sitemap: ' . $error_message );
throw new \Exception( 'Failed to remote read ' . $sitemap );
}
$xml_object = simplexml_load_string( $response[ 'body' ], null, LIBXML_NOCDATA );
if ( ! $xml_object ) {
if ( $this->_urls ) {
return;
}
throw new \Exception( 'Failed to parse xml ' . $sitemap );
}
// start parsing
$xml_array = (array) $xml_object;
if ( ! empty( $xml_array[ 'sitemap' ] ) ) { // parse sitemap set
if ( is_object( $xml_array[ 'sitemap' ] ) ) {
$xml_array[ 'sitemap' ] = (array) $xml_array[ 'sitemap' ];
}
if ( ! empty( $xml_array[ 'sitemap' ][ 'loc' ] ) ) { // is single sitemap
$this->_parse( $xml_array[ 'sitemap' ][ 'loc' ] );
}
else {
// parse multiple sitemaps
foreach ( $xml_array[ 'sitemap' ] as $val ) {
$val = (array) $val;
if ( ! empty( $val[ 'loc' ] ) ) {
$this->_parse( $val[ 'loc' ] ); // recursive parse sitemap
}
}
}
}
elseif ( ! empty( $xml_array[ 'url' ] ) ) { // parse url set
if ( is_object( $xml_array[ 'url' ] ) ) {
$xml_array[ 'url' ] = (array) $xml_array[ 'url' ];
}
// if only 1 element
if ( ! empty( $xml_array[ 'url' ][ 'loc' ] ) ) {
$this->_urls[] = $xml_array[ 'url' ][ 'loc' ];
}
else {
foreach ( $xml_array[ 'url' ] as $val ) {
$val = (array) $val;
if ( ! empty( $val[ 'loc' ] ) ) {
$this->_urls[] = $val[ 'loc' ];
}
}
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,729 @@
<?php
/**
* The optimize css class.
*
* @since 2.3
*/
namespace LiteSpeed;
defined( 'WPINC' ) || exit;
class CSS extends Base {
const TYPE_GEN_CCSS = 'gen_ccss';
const TYPE_GEN_UCSS = 'gen_ucss';
const TYPE_CLEAR_Q_CCSS = 'clear_q_ccss';
const TYPE_CLEAR_Q_UCSS = 'clear_q_ucss';
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' ) );
}
/**
* Build the static filepath
*
* @since 4.3 Elevated to root.cls
* @since 4.3 Have to keep till v4.5 for compatibility when upgrade from v4.2 to v4.3
*/
protected function _build_filepath_prefix( $type ) {
$filepath_prefix = '/' . $type . '/';
if ( is_multisite() ) {
$filepath_prefix .= get_current_blog_id() . '/';
}
return $filepath_prefix;
}
/**
* Clear all waiting queues
*
* @since 4.3 Elevated to root.cls
* @since 4.3 Have to keep till v4.5 for compatibility when upgrade from v4.2 to v4.3
*/
public function clear_q( $type ) {
$filepath_prefix = $this->_build_filepath_prefix( $type );
$static_path = LITESPEED_STATIC_DIR . $filepath_prefix . '.litespeed_conf.dat';
if ( file_exists( $static_path ) ) {
unlink( $static_path );
}
$msg = __( 'Queue cleared successfully.', 'litespeed-cache' );
Admin_Display::succeed( $msg );
}
/**
* 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 ) == '*/' ) {
$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 = 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 );
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
$this->_queue = $this->load_queue( 'ccss' );
if ( count( $this->_queue ) > 500 ) {
Debug2::debug( '[CSS] 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_ccss(),
'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 );
Debug2::debug( '[CSS] 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;
}
/**
* Get UCSS path
*
* @since 4.0
*/
public function load_ucss( $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 ) ) {
Debug2::debug( '[CSS] UCSS bypassed due to UCSS URI Exclude setting: ' . $hit );
return false;
}
$filepath_prefix = $this->_build_filepath_prefix( 'ucss' );
$url_tag = $request_url;
if ( is_404() ) {
$url_tag = '404';
}
elseif ( apply_filters( 'litespeed_ucss_per_pagetype', false ) ) {
$url_tag = Utility::page_type();
}
$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 ) ) {
Debug2::debug2( '[UCSS] existing ucss ' . $static_file );
// Check if is error comment inside only
$tmp = File::read( $static_file );
if ( substr( $tmp, 0, 2 ) == '/*' && substr( $tmp, -2 ) == '*/' ) {
Debug2::debug2( '[UCSS] existing ucss is error only: ' . $tmp );
return false;
}
return $filename . '.css';
}
}
if ( $dry_run ) {
return false;
}
$uid = get_current_user_id();
$ua = ! empty( $_SERVER[ 'HTTP_USER_AGENT' ] ) ? $_SERVER[ 'HTTP_USER_AGENT' ] : '';
// Store it for cron
$this->_queue = $this->load_queue( 'ucss' );
if ( count( $this->_queue ) > 500 ) {
Debug2::debug( '[CSS] 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_ccss(),
'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 );
Debug2::debug( '[CSS] 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 ) );
// For v4.1- clean up
if ( isset( $this->_summary[ 'ucss_history' ] ) || isset( $this->_summary[ 'queue_ucss' ] ) ) {
if ( isset( $this->_summary[ 'ucss_history' ] ) ) {
unset( $this->_summary[ 'ucss_history' ] );
}
if ( isset( $this->_summary[ 'queue_ucss' ] ) ) {
unset( $this->_summary[ 'queue_ucss' ] );
}
self::save_summary();
}
return false;
}
/**
* Check if need to separate ccss for mobile
*
* @since 2.6.4
* @access private
*/
private function _separate_mobile_ccss() {
return ( wp_is_mobile() || apply_filters( 'litespeed_is_mobile', false ) ) && $this->conf( self::O_CACHE_MOBILE );
}
/**
* Cron ccss generation
*
* @since 2.3
* @access private
*/
public static function cron_ccss( $continue = false ) {
$_instance = self::cls();
return $_instance->_cron_handler( 'ccss', $continue );
}
/**
* Generate UCSS
*
* @since 4.0
*/
public static function cron_ucss( $continue = false ) {
$_instance = self::cls();
return $_instance->_cron_handler( 'ucss', $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 ) {
$this->_print_loading( count( $this->_queue ), $type_tag );
return Router::self_redirect( Router::ACTION_CSS, $type == 'ccss' ? CSS::TYPE_GEN_CCSS : CSS::TYPE_GEN_UCSS );
}
continue;
}
// Exit queue if out of quota
if ( $res == 'out_of_quota' ) {
return;
}
$this->_queue[ $k ][ '_status' ] = 'requested';
$this->save_queue( $type, $this->_queue );
// only request first one
if ( ! $continue ) {
return;
}
if ( $i > 3 ) {
$this->_print_loading( count( $this->_queue ), $type_tag );
return Router::self_redirect( Router::ACTION_CSS, $type == 'ccss' ? CSS::TYPE_GEN_CCSS : CSS::TYPE_GEN_UCSS );
}
}
}
/**
* 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 ) {
$svc = $type == 'ccss' ? Cloud::SVC_CCSS : Cloud::SVC_UCSS;
// Check if has credit to push or not
$err = false;
$allowance = $this->cls( 'Cloud' )->allowance( $svc, $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
$css = false;
if ( $type == 'ccss' ) {
list( $css, $html ) = $this->prepare_css( $html, $is_webp );
}
else {
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';
Debug2::debug( '[UCSS] Checking combined file ' . $static_file );
if ( file_exists( $static_file ) ) {
$css = File::read( $static_file );
}
}
if ( ! $css ) {
Debug2::debug( '[UCSS] ❌ No combined css' );
return false;
}
// Generate critical css
$data = array(
// 'type' => strtoupper( $type ), // Backward compatibility for v4.1-
'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 ( $type == 'ucss' ) {
if ( ! isset( $this->_ucss_whitelist ) ) {
$this->_ucss_whitelist = $this->_filter_whitelist();
}
$data[ 'whitelist' ] = $this->_ucss_whitelist;
}
Debug2::debug( '[CSS] Generating: ', $data );
$json = Cloud::post( $svc, $data, 30 );
if ( ! is_array( $json ) ) {
return false;
}
// Old version compatibility
if ( empty( $json[ 'status' ] ) ) {
if ( ! empty( $json[ $type ] ) ) {
$this->_save_con( $type, $json[ $type ], $queue_k );
}
// 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 ) {
// Add filters
$css = apply_filters( 'litespeed_' . $type, $css, $queue_k );
Debug2::debug2( '[CSS] con: ' . $css );
if ( substr( $css, 0, 2 ) == '/*' && substr( $css, -2 ) == '*/' ) {
Debug2::debug( '[CSS] ❌ 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 ) );
Purge::add( strtoupper( $type ) . '.' . md5( $queue_k ) );
}
/**
* Print a loading message when redirecting CCSS/UCSS page to aviod whiteboard confusion
*/
private function _print_loading( $counter, $type ) {
echo '<div style="font-size: 25px; text-align: center; padding-top: 150px; width: 100%; position: absolute;">';
echo "<img width='35' src='" . LSWCP_PLUGIN_URL . "assets/img/Litespeed.icon.svg' /> ";
echo sprintf( __( '%1$s %2$s files left in queue', 'litespeed-cache' ), $counter, $type );
echo '<p><a href="' . admin_url( 'admin.php?page=litespeed-page_optm' ) . '">' . __( 'Cancel', 'litespeed-cache' ) . '</a></p>';
echo '</div>';
}
/**
* 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',
);
// Debug2::debug( '[CSS] 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 );
$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 3.3
*/
private function _filter_whitelist() {
$whitelist = array();
$list = apply_filters( 'litespeed_ucss_whitelist', $this->conf( self::O_OPTM_UCSS_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;
}
/**
* 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_UCSS:
self::cron_ucss( true );
break;
case self::TYPE_GEN_CCSS:
self::cron_ccss( true );
break;
case self::TYPE_CLEAR_Q_UCSS:
$this->clear_q( 'ucss' );
break;
case self::TYPE_CLEAR_Q_CCSS:
$this->clear_q( 'ccss' );
break;
default:
break;
}
Admin::redirect();
}
}

View File

@@ -0,0 +1,688 @@
<?php
/**
* The class to store and manage litespeed db data.
*
* @since 1.3.1
* @package LiteSpeed
* @subpackage LiteSpeed/src
* @author LiteSpeed Technologies <info@litespeedtech.com>
*/
namespace LiteSpeed;
defined( 'WPINC' ) || exit;
class Data extends Root {
private $_db_updater = array(
'3.5.0.3' => array(
'litespeed_update_3_5',
),
'4.0' => array(
'litespeed_update_4',
),
'4.1' => array(
'litespeed_update_4_1',
),
'4.3' => array(
'litespeed_update_4_3',
),
'4.4.4-b1' => array(
'litespeed_update_4_4_4',
),
);
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 existance
*
* Call when activate -> upadte_confs()
* Call when upadte_confs()
*
* @since 3.0
* @access public
*/
public function correct_tb_existance() {
// 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 ) {
Debug2::debug( "[Data] Updating [ori_v] $ver \t[to] $k \t[func] $v2" );
call_user_func( $v2 );
}
}
}
// Reload options
$this->cls( 'Conf' )->load_options();
$this->correct_tb_existance();
// Update related files
$this->cls( 'Activation' )->update_files();
// Update version to latest
Conf::delete_option( Base::_VER );
Conf::add_option( Base::_VER, Core::VER );
Debug2::debug( '[Data] Updated version to ' . Core::VER );
$this->_set_upgrade_lock( false );
! defined( 'LSWCP_EMPTYCACHE') && define( 'LSWCP_EMPTYCACHE', true );// clear all sites caches
Purge::purge_all();
Cloud::version_check( '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 ) {
Debug2::debug( "[Data] 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 );
Debug2::debug( '[Data] Updated site_version to ' . Core::VER );
$this->_set_upgrade_lock( false );
! defined( 'LSWCP_EMPTYCACHE') && define( 'LSWCP_EMPTYCACHE', true );// clear all sites caches
Purge::purge_all();
}
/**
* Check if upgrade script is running or not
*
* @since 3.0.1
*/
private function _get_upgrade_lock() {
$is_upgrading = get_option( 'litespeed.data.upgrading' );
if ( ! $is_upgrading ) {
$this->_set_upgrade_lock( false ); // set option value to existed to avoid repeated db query next time
}
if ( $is_upgrading && time() - $is_upgrading < 3600 ) {
return $is_upgrading;
}
return false;
}
/**
* Show the upgrading banner if upgrade script is running
*
* @since 3.0.1
*/
public function check_upgrading_msg() {
$is_upgrading = $this->_get_upgrade_lock();
if ( ! $is_upgrading ) {
return;
}
Admin_Display::info( sprintf( __( 'The database has been upgrading in the background since %s. This message will disappear once upgrade is complete.', 'litespeed-cache' ), '<code>' . Utility::readable_time( $is_upgrading ) . '</code>' ) . ' [LiteSpeed]', true );
}
/**
* Set lock for upgrade process
*
* @since 3.0.1
*/
private function _set_upgrade_lock( $lock ) {
if ( ! $lock ) {
update_option( 'litespeed.data.upgrading', -1 );
}
else {
update_option( 'litespeed.data.upgrading', time() );
}
}
/**
* Upgrade the conf to v3.0 from previous v3.0- data
*
* NOTE: Only for v3.0-
*
* @since 3.0
* @access public
*/
public function try_upgrade_conf_3_0() {
$previous_options = get_option( 'litespeed-cache-conf' );
if ( ! $previous_options ) {
Cloud::version_check( 'new' );
return;
}
$ver = $previous_options[ 'version' ];
! defined( 'LSCWP_CUR_V' ) && define( 'LSCWP_CUR_V', $ver );
// Init log manually
if ( $this->conf( Base::O_DEBUG ) ) {
$this->cls( 'Debug2' )->init();
}
Debug2::debug( '[Data] Upgrading previous settings [from] ' . $ver . ' [to] v3.0' );
if ( $this->_get_upgrade_lock() ) {
return;
}
$this->_set_upgrade_lock( true );
require_once LSCWP_DIR . 'src/data.upgrade.func.php';
// Here inside will update the version to v3.0
litespeed_update_3_0( $ver );
$this->_set_upgrade_lock( false );
Debug2::debug( '[Data] Upgraded to v3.0' );
// Upgrade from 3.0 to latest version
$ver = '3.0';
if ( Core::VER != $ver ) {
$this->conf_upgrade( $ver );
}
else {
// Reload options
$this->cls( 'Conf' )->load_options();
$this->correct_tb_existance();
! defined( 'LSWCP_EMPTYCACHE') && define( 'LSWCP_EMPTYCACHE', true );// clear all sites caches
Purge::purge_all();
Cloud::version_check( 'upgrade' );
}
}
/**
* Get the table name
*
* @since 3.0
* @access public
*/
public function tb( $tb ) {
global $wpdb;
switch ( $tb ) {
case 'img_optm':
return $wpdb->prefix . self::TB_IMG_OPTM;
break;
case 'img_optming':
return $wpdb->prefix . self::TB_IMG_OPTMING;
break;
case 'avatar':
return $wpdb->prefix . self::TB_AVATAR;
break;
case 'crawler':
return $wpdb->prefix . self::TB_CRAWLER;
break;
case 'crawler_blacklist':
return $wpdb->prefix . self::TB_CRAWLER_BLACKLIST;
break;
case 'url':
return $wpdb->prefix . self::TB_URL;
break;
case 'url_file':
return $wpdb->prefix . self::TB_URL_FILE;
break;
default:
break;
}
}
/**
* Check if one table exists or not
*
* @since 3.0
* @access public
*/
public function tb_exist( $tb ) {
global $wpdb;
return $wpdb->get_var( "SHOW TABLES LIKE '" . $this->tb( $tb ) . "'" );
}
/**
* Get data structure of one table
*
* @since 2.0
* @access private
*/
private function _tb_structure( $tb ) {
return File::read( LSCWP_DIR . 'src/data_structure/' . $tb . '.sql' );
}
/**
* Create img optm table and sync data from wp_postmeta
*
* @since 3.0
* @access public
*/
public function tb_create( $tb ) {
global $wpdb;
Debug2::debug2( '[Data] Checking table ' . $tb );
// Check if table exists first
if ( $this->tb_exist( $tb ) ) {
Debug2::debug2( '[Data] Existed' );
return;
}
Debug2::debug( '[Data] 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 ) {
Debug2::debug( '[Data] 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;
}
Debug2::debug( '[Data] 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;
$type = $this->_url_file_types[ $file_type ];
$q = 'DELETE FROM ' . $this->tb( 'url_file' ) . ' WHERE `type` = %d';
$wpdb->query( $wpdb->prepare( $q, $type ) );
}
/**
* 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 ) {
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, expired = 0";
$wpdb->query( $wpdb->prepare( $q, array( $url_id, $vary, $filecon_md5, $type ) ) );
// 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
Debug2::debug( '[Data] 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 ];
$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 ) {
global $wpdb;
Debug2::debug( '[Data] Try to mark as expired: ' . $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;
}
Debug2::debug( '[Data] Mark url_id=' . $url_row[ 'id' ] . ' as expired' );
$tb_url_file = $this->tb( 'url_file' );
$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' ] ) ) );
}
/**
* 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/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/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;
}
/**
* 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;
}
}

View File

@@ -0,0 +1,682 @@
<?php
/**
* Database upgrade funcs
*
* NOTE: whenever called this file, always call Data::get_upgrade_lock and Data::_set_upgrade_lock first.
*
* @since 3.0
*/
defined( 'WPINC' ) || exit;
use LiteSpeed\Debug2;
use LiteSpeed\Conf;
use LiteSpeed\Admin_Display;
use LiteSpeed\File;
/**
* Add expired to url_file table
* @since 4.4.4
*/
function litespeed_update_4_4_4() {
global $wpdb;
Debug2::debug( "[Data] Upgrade url_file table" );
$tb_exists = $wpdb->get_var( 'SHOW TABLES LIKE "' . $wpdb->prefix . 'litespeed_url_file"' );
if ( $tb_exists ) {
$q = 'ALTER TABLE `' . $wpdb->prefix . 'litespeed_url_file`
ADD COLUMN `expired` int(11) NOT NULL DEFAULT 0,
ADD KEY `filename_2` (`filename`,`expired`),
ADD KEY `url_id` (`url_id`,`expired`)
';
$wpdb->query( $q );
}
}
/**
* Drop cssjs table and rm cssjs folder
* @since 4.3
*/
function litespeed_update_4_3() {
if ( file_exists( LITESPEED_STATIC_DIR . '/ccsjs' ) ) {
File::rrmdir( LITESPEED_STATIC_DIR . '/ccsjs' );
}
}
/**
* Drop object cache data file
* @since 4.1
*/
function litespeed_update_4_1() {
if ( file_exists( WP_CONTENT_DIR . '/.object-cache.ini' ) ) {
unlink( WP_CONTENT_DIR . '/.object-cache.ini' );
}
}
/**
* Drop cssjs table and rm cssjs folder
* @since 4.0
*/
function litespeed_update_4() {
global $wpdb;
$tb = $wpdb->prefix . 'litespeed_cssjs';
$existed = $wpdb->get_var( "SHOW TABLES LIKE '$tb'" );
if ( ! $existed ) {
return;
}
$q = 'DROP TABLE IF EXISTS ' . $tb;
$wpdb->query( $q );
if ( file_exists( LITESPEED_STATIC_DIR . '/ccsjs' ) ) {
File::rrmdir( LITESPEED_STATIC_DIR . '/ccsjs' );
}
}
/**
* Append jQuery to JS optm exclude list for max compatibility
* Turn off JS Combine and Defer
*
* @since 3.5.1
*/
function litespeed_update_3_5() {
$__conf = Conf::cls();
// Excludes jQuery
foreach ( array( 'optm-js_exc', 'optm-js_defer_exc' ) as $v ) {
$curr_setting = $__conf->conf( $v );
$curr_setting[] = 'jquery.js';
$curr_setting[] = 'jquery.min.js';
$__conf->update( $v, $curr_setting );
}
// Turn off JS Combine and defer
$show_msg = false;
foreach ( array( 'optm-js_comb', 'optm-js_defer', 'optm-js_inline_defer' ) as $v ) {
$curr_setting = $__conf->conf( $v );
if ( ! $curr_setting ) {
continue;
}
$show_msg = true;
$__conf->update( $v, false );
}
if ( $show_msg ) {
$msg = sprintf( __( 'LiteSpeed Cache upgraded successfully. NOTE: Due to changes in this version, the settings %1$s and %2$s have been turned OFF. Please turn them back on manually and verify that your site layout is correct, and you have no JS errors.', 'litespeed-cache' ), '<code>' . __( 'JS Combine', 'litespeed-cache' ) . '</code>', '<code>' . __( 'JS Defer', 'litespeed-cache' ) . '</code>' );
$msg .= sprintf( ' <a href="admin.php?page=litespeed-page_optm#settings_js">%s</a>.', __( 'Click here to settings', 'litespeed-cache' ) );
Admin_Display::info( $msg, false, true );
}
}
/**
* For version under v2.0 to v2.0+
*
* @since 3.0
*/
function litespeed_update_2_0( $ver ) {
global $wpdb ;
// Table version only exists after all old data migrated
// Last modified is v2.4.2
if ( version_compare( $ver, '2.4.2', '<' ) ) {
/**
* Convert old data from postmeta to img_optm table
* @since 2.0
*/
// Migrate data from `wp_postmeta` to `wp_litespeed_img_optm`
$mids_to_del = array() ;
$q = "SELECT * FROM $wpdb->postmeta WHERE meta_key = %s ORDER BY meta_id" ;
$meta_value_list = $wpdb->get_results( $wpdb->prepare( $q, 'litespeed-optimize-data' ) ) ;
if ( $meta_value_list ) {
$max_k = count( $meta_value_list ) - 1 ;
foreach ( $meta_value_list as $k => $v ) {
$md52src_list = maybe_unserialize( $v->meta_value ) ;
foreach ( $md52src_list as $md5 => $v2 ) {
$f = array(
'post_id' => $v->post_id,
'optm_status' => $v2[ 1 ],
'src' => $v2[ 0 ],
'srcpath_md5' => md5( $v2[ 0 ] ),
'src_md5' => $md5,
'server' => $v2[ 2 ],
) ;
$wpdb->replace( $wpdb->prefix . 'litespeed_img_optm', $f ) ;
}
$mids_to_del[] = $v->meta_id ;
// Delete from postmeta
if ( count( $mids_to_del ) > 100 || $k == $max_k ) {
$q = "DELETE FROM $wpdb->postmeta WHERE meta_id IN ( " . implode( ',', array_fill( 0, count( $mids_to_del ), '%s' ) ) . " ) " ;
$wpdb->query( $wpdb->prepare( $q, $mids_to_del ) ) ;
$mids_to_del = array() ;
}
}
Debug2::debug( '[Data] img_optm inserted records: ' . $k ) ;
}
$q = "DELETE FROM $wpdb->postmeta WHERE meta_key = %s" ;
$rows = $wpdb->query( $wpdb->prepare( $q, 'litespeed-optimize-status' ) ) ;
Debug2::debug( '[Data] img_optm delete optm_status records: ' . $rows ) ;
}
/**
* Add target_md5 field to table
* @since 2.4.2
*/
if ( version_compare( $ver, '2.4.2', '<' ) && version_compare( $ver, '2.0', '>=' ) ) {// NOTE: For new users, need to bypass this section
$sql = sprintf(
'ALTER TABLE `%1$s` ADD `server_info` text NOT NULL, DROP COLUMN `server`',
$wpdb->prefix . 'litespeed_img_optm'
) ;
$res = $wpdb->query( $sql ) ;
if ( $res !== true ) {
Debug2::debug( '[Data] Warning: Alter table img_optm failed!', $sql ) ;
}
else {
Debug2::debug( '[Data] Successfully upgraded table img_optm.' ) ;
}
}
// Delete img optm tb version
delete_option( $wpdb->prefix . 'litespeed_img_optm' ) ;
// Delete possible HTML optm data from wp_options
delete_option( 'litespeed-cache-optimized' ) ;
// Delete HTML optm tb version
delete_option( $wpdb->prefix . 'litespeed_optimizer' ) ;
}
/**
* Move all options in litespeed-cache-conf from v3.0- to separate records
*
* @since 3.0
*/
function litespeed_update_3_0( $ver ) {
global $wpdb;
// Upgrade v2.0- to v2.0 first
if ( version_compare( $ver, '2.0', '<' ) ) {
litespeed_update_2_0( $ver ) ;
}
set_time_limit( 86400 );
// conv items to litespeed.conf.*
Debug2::debug( "[Data] Conv items to litespeed.conf.*" );
$data = array(
'litespeed-cache-exclude-cache-roles' => 'cache-exc_roles',
'litespeed-cache-drop_qs' => 'cache-drop_qs',
'litespeed-forced_cache_uri' => 'cache-force_uri',
'litespeed-cache_uri_priv' => 'cache-priv_uri',
'litespeed-excludes_uri' => 'cache-exc',
'litespeed-cache-vary-group' => 'cache-vary_group',
'litespeed-adv-purge_all_hooks' => 'purge-hook_all',
'litespeed-object_global_groups' => 'object-global_groups',
'litespeed-object_non_persistent_groups' => 'object-non_persistent_groups',
'litespeed-media-lazy-img-excludes' => 'media-lazy_exc',
'litespeed-media-lazy-img-cls-excludes' => 'media-lazy_cls_exc',
'litespeed-media-webp_attribute' => 'img_optm-webp_attr',
'litespeed-optm-css' => 'optm-ccss_con',
'litespeed-optm_excludes' => 'optm-exc',
'litespeed-optm-ccss-separate_posttype' => 'optm-ccss_sep_posttype',
'litespeed-optm-css-separate_uri' => 'optm-ccss_sep_uri',
'litespeed-optm-js-defer-excludes' => 'optm-js_defer_exc',
'litespeed-cache-dns_prefetch' => 'optm-dns_prefetch',
'litespeed-cache-exclude-optimization-roles' => 'optm-exc_roles',
'litespeed-log_ignore_filters' => 'debug-log_no_filters', // depreciated
'litespeed-log_ignore_part_filters' => 'debug-log_no_part_filters', // depreciated
'litespeed-cdn-ori_dir' => 'cdn-ori_dir',
'litespeed-cache-cdn_mapping' => 'cdn-mapping',
'litespeed-crawler-as-uids' => 'crawler-roles',
'litespeed-crawler-cookies' => 'crawler-cookies',
) ;
foreach ( $data as $k => $v ) {
$old_data = get_option( $k ) ;
if ( $old_data ) {
Debug2::debug( "[Data] Convert $k" );
// They must be an array
if ( ! is_array( $old_data ) && $v != 'optm-ccss_con' ) {
$old_data = explode( "\n", $old_data ) ;
}
if ( $v == 'crawler-cookies' ) {
$tmp = array() ;
$i = 0 ;
foreach ( $old_data as $k2 => $v2 ) {
$tmp[ $i ][ 'name' ] = $k2 ;
$tmp[ $i ][ 'vals' ] = explode( "\n", $v2 ) ;
$i ++ ;
}
$old_data = $tmp ;
}
add_option( 'litespeed.conf.' . $v, $old_data ) ;
}
Debug2::debug( "[Data] Delete $k" );
delete_option( $k ) ;
}
// conv other items
$data = array(
'litespeed-setting-mode' => 'litespeed.setting.mode',
'litespeed-media-need-pull' => 'litespeed.img_optm.need_pull',
'litespeed-env-ref' => 'litespeed.env.ref',
'litespeed-cache-cloudflare_status' => 'litespeed.cdn.cloudflare.status',
) ;
foreach ( $data as $k => $v ) {
$old_data = get_option( $k ) ;
if ( $old_data ) {
add_option( $v, $old_data ) ;
}
delete_option( $k ) ;
}
// Conv conf from litespeed-cache-conf child to litespeed.conf.*
Debug2::debug( "[Data] Conv conf from litespeed-cache-conf child to litespeed.conf.*" );
$previous_options = get_option( 'litespeed-cache-conf' ) ;
$data = array(
'radio_select' => 'cache',
'hash' => 'hash',
'auto_upgrade' => 'auto_upgrade',
'news' => 'news',
'crawler_domain_ip' => 'server_ip',
'esi_enabled' => 'esi',
'esi_cached_admbar' => 'esi-cache_admbar',
'esi_cached_commform' => 'esi-cache_commform',
'heartbeat' => 'misc-heartbeat_front',
'cache_browser' => 'cache-browser',
'cache_browser_ttl' => 'cache-ttl_browser',
'instant_click' => 'util-instant_click',
'use_http_for_https_vary' => 'util-no_https_vary',
'purge_upgrade' => 'purge-upgrade',
'timed_urls' => 'purge-timed_urls',
'timed_urls_time' => 'purge-timed_urls_time',
'cache_priv' => 'cache-priv',
'cache_commenter' => 'cache-commenter',
'cache_rest' => 'cache-rest',
'cache_page_login' => 'cache-page_login',
'cache_favicon' => 'cache-favicon',
'cache_resources' => 'cache-resources',
'mobileview_enabled' => 'cache-mobile',
'mobileview_rules' => 'cache-mobile_rules',
'nocache_useragents' => 'cache-exc_useragents',
'nocache_cookies' => 'cache-exc_cookies',
'excludes_qs' => 'cache-exc_qs',
'excludes_cat' => 'cache-exc_cat',
'excludes_tag' => 'cache-exc_tag',
'public_ttl' => 'cache-ttl_pub',
'private_ttl' => 'cache-ttl_priv',
'front_page_ttl' => 'cache-ttl_frontpage',
'feed_ttl' => 'cache-ttl_feed',
'login_cookie' => 'cache-login_cookie',
'debug_disable_all' => 'debug-disable_all',
'debug' => 'debug',
'admin_ips' => 'debug-ips',
'debug_level' => 'debug-level',
'log_file_size' => 'debug-filesize',
'debug_cookie' => 'debug-cookie',
'collaps_qs' => 'debug-collaps_qs',
// 'log_filters' => 'debug-log_filters',
'crawler_cron_active' => 'crawler',
// 'crawler_include_posts' => 'crawler-inc_posts',
// 'crawler_include_pages' => 'crawler-inc_pages',
// 'crawler_include_cats' => 'crawler-inc_cats',
// 'crawler_include_tags' => 'crawler-inc_tags',
// 'crawler_excludes_cpt' => 'crawler-exc_cpt',
// 'crawler_order_links' => 'crawler-order_links',
'crawler_usleep' => 'crawler-usleep',
'crawler_run_duration' => 'crawler-run_duration',
'crawler_run_interval' => 'crawler-run_interval',
'crawler_crawl_interval' => 'crawler-crawl_interval',
'crawler_threads' => 'crawler-threads',
'crawler_load_limit' => 'crawler-load_limit',
'crawler_custom_sitemap' => 'crawler-sitemap',
'cache_object' => 'object',
'cache_object_kind' => 'object-kind',
'cache_object_host' => 'object-host',
'cache_object_port' => 'object-port',
'cache_object_life' => 'object-life',
'cache_object_persistent' => 'object-persistent',
'cache_object_admin' => 'object-admin',
'cache_object_transients' => 'object-transients',
'cache_object_db_id' => 'object-db_id',
'cache_object_user' => 'object-user',
'cache_object_pswd' => 'object-psw',
'cdn' => 'cdn',
'cdn_ori' => 'cdn-ori',
'cdn_exclude' => 'cdn-exc',
// 'cdn_remote_jquery' => 'cdn-remote_jq',
'cdn_quic' => 'cdn-quic',
'cdn_cloudflare' => 'cdn-cloudflare',
'cdn_cloudflare_email' => 'cdn-cloudflare_email',
'cdn_cloudflare_key' => 'cdn-cloudflare_key',
'cdn_cloudflare_name' => 'cdn-cloudflare_name',
'cdn_cloudflare_zone' => 'cdn-cloudflare_zone',
'media_img_lazy' => 'media-lazy',
'media_img_lazy_placeholder' => 'media-lazy_placeholder',
'media_placeholder_resp' => 'media-placeholder_resp',
'media_placeholder_resp_color' => 'media-placeholder_resp_color',
'media_placeholder_resp_async' => 'media-placeholder_resp_async',
'media_iframe_lazy' => 'media-iframe_lazy',
// 'media_img_lazyjs_inline' => 'media-lazyjs_inline',
'media_optm_auto' => 'img_optm-auto',
'media_optm_cron' => 'img_optm-cron',
'media_optm_ori' => 'img_optm-ori',
'media_rm_ori_bkup' => 'img_optm-rm_bkup',
'media_optm_webp' => 'img_optm-webp',
'media_optm_lossless' => 'img_optm-lossless',
'media_optm_exif' => 'img_optm-exif',
'media_webp_replace' => 'img_optm-webp_replace',
'media_webp_replace_srcset' => 'img_optm-webp_replace_srcset',
'css_minify' => 'optm-css_min',
// 'css_inline_minify' => 'optm-css_inline_min',
'css_combine' => 'optm-css_comb',
// 'css_combined_priority' => 'optm-css_comb_priority',
// 'css_http2' => 'optm-css_http2',
'css_exclude' => 'optm-css_exc',
'js_minify' => 'optm-js_min',
// 'js_inline_minify' => 'optm-js_inline_min',
'js_combine' => 'optm-js_comb',
// 'js_combined_priority' => 'optm-js_comb_priority',
// 'js_http2' => 'optm-js_http2',
'js_exclude' => 'optm-js_exc',
// 'optimize_ttl' => 'optm-ttl',
'html_minify' => 'optm-html_min',
'optm_qs_rm' => 'optm-qs_rm',
'optm_ggfonts_rm' => 'optm-ggfonts_rm',
'optm_css_async' => 'optm-css_async',
// 'optm_ccss_gen' => 'optm-ccss_gen',
// 'optm_ccss_async' => 'optm-ccss_async',
'optm_css_async_inline' => 'optm-css_async_inline',
'optm_js_defer' => 'optm-js_defer',
'optm_emoji_rm' => 'optm-emoji_rm',
// 'optm_exclude_jquery' => 'optm-exc_jq',
'optm_ggfonts_async' => 'optm-ggfonts_async',
// 'optm_max_size' => 'optm-max_size',
// 'optm_rm_comment' => 'optm-rm_comment',
) ;
foreach ( $data as $k => $v ) {
if ( ! isset( $previous_options[ $k ] ) ) {
continue ;
}
// The folllowing values must be array
if ( ! is_array( $previous_options[ $k ] ) ) {
if ( in_array( $v, array( 'cdn-ori', 'cache-exc_cat', 'cache-exc_tag' ) ) ) {
$previous_options[ $k ] = explode( ',', $previous_options[ $k ] ) ;
$previous_options[ $k ] = array_filter( $previous_options[ $k ] ) ;
}
elseif ( in_array( $v, array( 'cache-mobile_rules', 'cache-exc_useragents', 'cache-exc_cookies' ) ) ) {
$previous_options[ $k ] = explode( '|', str_replace( '\\ ', ' ', $previous_options[ $k ] ) ) ;
$previous_options[ $k ] = array_filter( $previous_options[ $k ] ) ;
}
elseif ( in_array( $v, array(
'purge-timed_urls',
'cache-exc_qs',
'debug-ips',
// 'crawler-exc_cpt',
'cdn-exc',
'optm-css_exc',
'optm-js_exc',
) ) ) {
$previous_options[ $k ] = explode( "\n", $previous_options[ $k ] ) ;
$previous_options[ $k ] = array_filter( $previous_options[ $k ] ) ;
}
}
// Special handler for heartbeat
if ( $v == 'misc-heartbeat_front' ) {
if ( ! $previous_options[ $k ] ) {
add_option( 'litespeed.conf.misc-heartbeat_front', true ) ;
add_option( 'litespeed.conf.misc-heartbeat_back', true ) ;
add_option( 'litespeed.conf.misc-heartbeat_editor', true ) ;
add_option( 'litespeed.conf.misc-heartbeat_front_ttl', 0 ) ;
add_option( 'litespeed.conf.misc-heartbeat_back_ttl', 0 ) ;
add_option( 'litespeed.conf.misc-heartbeat_editor_ttl', 0 ) ;
}
continue ;
}
add_option( 'litespeed.conf.' . $v, $previous_options[ $k ] ) ;
}
// Conv purge_by_post
$data = array(
'-' => 'purge-post_all',
'F' => 'purge-post_f',
'H' => 'purge-post_h',
'PGS' => 'purge-post_p',
'PGSRP' => 'purge-post_pwrp',
'A' => 'purge-post_a',
'Y' => 'purge-post_y',
'M' => 'purge-post_m',
'D' => 'purge-post_d',
'T' => 'purge-post_t',
'PT' => 'purge-post_pt',
) ;
if ( isset( $previous_options[ 'purge_by_post' ] ) ) {
$purge_by_post = explode( '.', $previous_options[ 'purge_by_post' ] ) ;
foreach ( $data as $k => $v ) {
add_option( 'litespeed.conf.' . $v, in_array( $k, $purge_by_post ) ) ;
}
}
// Conv 404/403/500 TTL
$ttl_status = array() ;
if ( isset( $previous_options[ '403_ttl' ] ) ) {
$ttl_status[] = '403 ' . $previous_options[ '403_ttl' ] ;
}
if ( isset( $previous_options[ '404_ttl' ] ) ) {
$ttl_status[] = '404 ' . $previous_options[ '404_ttl' ] ;
}
if ( isset( $previous_options[ '500_ttl' ] ) ) {
$ttl_status[] = '500 ' . $previous_options[ '500_ttl' ] ;
}
add_option( 'litespeed.conf.cache-ttl_status', $ttl_status ) ;
/**
* Resave cdn cfg from lscfg to separate cfg when upgrade to v1.7
*
* NOTE: this can be left here as `add_option` bcos it is after the item `litespeed-cache-cdn_mapping` is converted
*
* @since 1.7
*/
if ( isset( $previous_options[ 'cdn_url' ] ) ) {
$cdn_mapping = array(
'url' => $previous_options[ 'cdn_url' ],
'inc_img' => $previous_options[ 'cdn_inc_img' ],
'inc_css' => $previous_options[ 'cdn_inc_css' ],
'inc_js' => $previous_options[ 'cdn_inc_js' ],
'filetype' => $previous_options[ 'cdn_filetype' ],
) ;
add_option( 'litespeed.conf.cdn-mapping', array( $cdn_mapping ) ) ;
Debug2::debug( "[Data] plugin_upgrade option adding CDN map" ) ;
}
/**
* Move Exclude settings to separate item
*
* NOTE: this can be left here as `add_option` bcos it is after the relevant items are converted
*
* @since 2.3
*/
if ( isset( $previous_options[ 'forced_cache_uri' ] ) ) {
add_option( 'litespeed.conf.cache-force_uri', $previous_options[ 'forced_cache_uri' ] ) ;
}
if ( isset( $previous_options[ 'cache_uri_priv' ] ) ) {
add_option( 'litespeed.conf.cache-priv_uri', $previous_options[ 'cache_uri_priv' ] ) ;
}
if ( isset( $previous_options[ 'optm_excludes' ] ) ) {
add_option( 'litespeed.conf.optm-exc', $previous_options[ 'optm_excludes' ] ) ;
}
if ( isset( $previous_options[ 'excludes_uri' ] ) ) {
add_option( 'litespeed.conf.cache-exc', $previous_options[ 'excludes_uri' ] ) ;
}
// Backup stale conf
Debug2::debug( "[Data] Backup stale conf" );
delete_option( 'litespeed-cache-conf' );
add_option( 'litespeed-cache-conf.bk', $previous_options );
// Upgrade site_options if is network
if ( is_multisite() ) {
$ver = get_site_option( 'litespeed.conf._version' ) ;
if ( ! $ver ) {
Debug2::debug( "[Data] Conv multisite" );
$previous_site_options = get_site_option( 'litespeed-cache-conf' ) ;
$data = array(
'network_enabled' => 'cache',
'use_primary_settings' => 'use_primary_settings',
'auto_upgrade' => 'auto_upgrade',
'purge_upgrade' => 'purge-upgrade',
'cache_favicon' => 'cache-favicon',
'cache_resources' => 'cache-resources',
'mobileview_enabled' => 'cache-mobile',
'mobileview_rules' => 'cache-mobile_rules',
'login_cookie' => 'cache-login_cookie',
'nocache_cookies' => 'cache-exc_cookies',
'nocache_useragents' => 'cache-exc_useragents',
'cache_object' => 'object',
'cache_object_kind' => 'object-kind',
'cache_object_host' => 'object-host',
'cache_object_port' => 'object-port',
'cache_object_life' => 'object-life',
'cache_object_persistent' => 'object-persistent',
'cache_object_admin' => 'object-admin',
'cache_object_transients' => 'object-transients',
'cache_object_db_id' => 'object-db_id',
'cache_object_user' => 'object-user',
'cache_object_pswd' => 'object-psw',
'cache_browser' => 'cache-browser',
'cache_browser_ttl' => 'cache-ttl_browser',
'media_webp_replace' => 'img_optm-webp_replace',
) ;
foreach ( $data as $k => $v ) {
if ( ! isset( $previous_site_options[ $k ] ) ) {
continue ;
}
// The folllowing values must be array
if ( ! is_array( $previous_site_options[ $k ] ) ) {
if ( in_array( $v, array( 'cache-mobile_rules', 'cache-exc_useragents', 'cache-exc_cookies' ) ) ) {
$previous_site_options[ $k ] = explode( '|', str_replace( '\\ ', ' ', $previous_site_options[ $k ] ) ) ;
$previous_site_options[ $k ] = array_filter( $previous_site_options[ $k ] ) ;
}
}
add_site_option( 'litespeed.conf.' . $v, $previous_site_options[ $k ] ) ;
}
// These are already converted to single record in single site
$data = array(
'object-global_groups',
'object-non_persistent_groups',
) ;
foreach ( $data as $v ) {
$old_data = get_option( $v ) ;
if ( $old_data ) {
add_site_option( 'litespeed.conf.' . $v, $old_data ) ;
}
}
delete_site_option( 'litespeed-cache-conf' ) ;
add_site_option( 'litespeed.conf._version', '3.0' ) ;
}
}
// delete tables
Debug2::debug( "[Data] Drop litespeed_optimizer" );
$q = 'DROP TABLE IF EXISTS ' . $wpdb->prefix . 'litespeed_optimizer' ;
$wpdb->query( $q ) ;
// Update image optm table
Debug2::debug( "[Data] Upgrade img_optm table" );
$tb_exists = $wpdb->get_var( 'SHOW TABLES LIKE "' . $wpdb->prefix . 'litespeed_img_optm"' );
if ( $tb_exists ) {
$status_mapping = array(
'requested' => 3,
'notified' => 6,
'pulled' => 9,
'failed' => -1,
'miss' => -3,
'err' => -9,
'err_fetch' => -5,
'err_optm' => -7,
'xmeta' => -8,
);
foreach ( $status_mapping as $k => $v ) {
$q = "UPDATE `" . $wpdb->prefix . "litespeed_img_optm` SET optm_status='$v' WHERE optm_status='$k'";
$wpdb->query( $q ) ;
}
$q = 'ALTER TABLE `' . $wpdb->prefix . 'litespeed_img_optm`
DROP INDEX `post_id_2`,
DROP INDEX `root_id`,
DROP INDEX `src_md5`,
DROP INDEX `srcpath_md5`,
DROP COLUMN `srcpath_md5`,
DROP COLUMN `src_md5`,
DROP COLUMN `root_id`,
DROP COLUMN `target_saved`,
DROP COLUMN `webp_saved`,
DROP COLUMN `server_info`,
MODIFY COLUMN `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
MODIFY COLUMN `optm_status` tinyint(4) NOT NULL DEFAULT 0,
MODIFY COLUMN `src` text COLLATE utf8mb4_unicode_ci NOT NULL
';
$wpdb->query( $q ) ;
}
delete_option( 'litespeed-recommended' );
Debug2::debug( "[Data] litespeed_update_3_0 done!" );
add_option( 'litespeed.conf._version', '3.0' ) ;
}

View File

@@ -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`)

View File

@@ -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`)

View File

@@ -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`)

View File

@@ -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`)

View File

@@ -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))

View File

@@ -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))

View File

@@ -0,0 +1,12 @@
`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',
`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`)

View File

@@ -0,0 +1,302 @@
<?php
/**
* The admin optimize tool
*
*
* @since 1.2.1
* @package LiteSpeed
* @subpackage LiteSpeed/src
* @author LiteSpeed Technologies <info@litespeedtech.com>
*/
namespace LiteSpeed;
defined( 'WPINC' ) || exit;
class DB_Optm extends Root {
private static $_hide_more = false;
private static $TYPES = array( 'revision', '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 '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 );
$sql_add = '';
if ( $rev_age ) {
$sql_add = " and post_modified < DATE_SUB( NOW(), INTERVAL $rev_age DAY ) ";
}
if ( ! $rev_max ) {
$sql = "DELETE FROM `$wpdb->posts` WHERE post_type = 'revision' $sql_add";
$wpdb->query( $sql );
}
else { // Has count limit
$sql = "SELECT COUNT(*)-$rev_max as del_max,post_parent FROM `$wpdb->posts` WHERE post_type = 'revision' $sql_add GROUP BY post_parent HAVING count(*)>$rev_max";
$res = $wpdb->get_results( $sql );
foreach ( $res as $v ) {
$sql = "DELETE FROM `$wpdb->posts` WHERE post_type = 'revision' AND post_parent = %d ORDER BY ID LIMIT %d";
$wpdb->query( $wpdb->prepare( $sql, array( $v->post_parent, $v->del_max ) ) );
}
}
return __( 'Clean post revisions successfully.', 'litespeed-cache' );
case 'auto_draft':
$wpdb->query( "DELETE FROM `$wpdb->posts` WHERE post_status = 'auto-draft'" );
return __( 'Clean auto drafts successfully.', 'litespeed-cache' );
case 'trash_post':
$wpdb->query( "DELETE FROM `$wpdb->posts` WHERE post_status = 'trash'" );
return __( 'Clean trashed posts and pages successfully.', 'litespeed-cache' );
case 'spam_comment':
$wpdb->query( "DELETE FROM `$wpdb->comments` WHERE comment_approved = 'spam'" );
return __( 'Clean spam comments successfully.', 'litespeed-cache' );
case 'trash_comment':
$wpdb->query( "DELETE FROM `$wpdb->comments` WHERE comment_approved = 'trash'" );
return __( 'Clean trashed comments successfully.', 'litespeed-cache' );
case 'trackback-pingback':
$wpdb->query( "DELETE FROM `$wpdb->comments` WHERE comment_type = 'trackback' OR comment_type = 'pingback'" );
return __( 'Clean trackbacks and pingbacks successfully.', 'litespeed-cache' );
case 'expired_transient':
$wpdb->query( "DELETE FROM `$wpdb->options` WHERE option_name LIKE '_transient_timeout%' AND option_value < " . time() );
return __( 'Clean expired transients successfully.', 'litespeed-cache' );
case 'all_transients':
$wpdb->query( "DELETE FROM `$wpdb->options` WHERE option_name LIKE '%\\_transient\\_%'" );
return __( 'Clean all transients successfully.', 'litespeed-cache' );
case 'optimize_tables':
$sql = "SELECT table_name, DATA_FREE FROM information_schema.tables WHERE TABLE_SCHEMA = '" . DB_NAME . "' and ENGINE <> 'InnoDB' and DATA_FREE > 0";
$result = $wpdb->get_results( $sql );
if ( $result ) {
foreach ( $result as $row ) {
$wpdb->query( 'OPTIMIZE TABLE ' . $row->table_name );
}
}
return __( 'Optimized all tables.', 'litespeed-cache' );
}
}
/**
* Get all myisam tables
*
* @since 3.0
* @access public
*/
public function list_myisam() {
global $wpdb;
$q = "SELECT * FROM information_schema.tables WHERE TABLE_SCHEMA = '" . DB_NAME . "' and ENGINE = 'myisam' AND TABLE_NAME LIKE '{$wpdb->prefix}%'";
return $wpdb->get_results( $q );
}
/**
* Convert tables to InnoDB
*
* @since 3.0
* @access private
*/
private function _conv_innodb() {
global $wpdb;
if ( empty( $_GET[ 'tb' ] ) ) {
Admin_Display::error( 'No table to convert' );
return;
}
$tb = false;
$list = $this->list_myisam();
foreach ( $list as $v ) {
if ( $v->TABLE_NAME == $_GET[ 'tb' ] ) {
$tb = $v->TABLE_NAME;
break;
}
}
if ( ! $tb ) {
Admin_Display::error( 'No existing table' );
return;
}
$q = 'ALTER TABLE ' . DB_NAME . '.' . $tb . ' ENGINE = InnoDB';
$wpdb->query( $q );
Debug2::debug( "[DB] Converted $tb to InnoDB" );
$msg = __( 'Converted to InnoDB successfully.', 'litespeed-cache' );
Admin_Display::succeed( $msg );
}
/**
* Count all autoload size
*
* @since 3.0
* @access public
*/
public function autoload_summary() {
global $wpdb;
$summary = $wpdb->get_row( "SELECT SUM(LENGTH(option_value)) AS autoload_size,COUNT(*) AS autload_entries FROM `$wpdb->options` WHERE autoload='yes'" );
$summary->autoload_toplist = $wpdb->get_results( "SELECT option_name, LENGTH(option_value) AS option_value_length FROM `$wpdb->options` WHERE autoload='yes' 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::succeed( $msg );
break;
case self::TYPE_CONV_TB :
$this->_conv_innodb();
break;
default:
break;
}
Admin::redirect();
}
}

View File

@@ -0,0 +1,465 @@
<?php
/**
* The plugin logging class.
*/
namespace LiteSpeed;
defined( 'WPINC' ) || exit;
class Debug2 extends Root {
private static $log_path;
private static $log_path_prefix;
private static $_prefix;
const TYPE_CLEAR_LOG = 'clear_log';
const TYPE_BETA_TEST = 'beta_test';
const BETA_TEST_URL = 'beta_test_url';
const BETA_TEST_URL_WP = 'https://downloads.wordpress.org/plugin/litespeed-cache.zip';
/**
* Log class Confructor
*
* NOTE: in this process, until last step ( define const LSCWP_LOG = true ), any usage to WP filter will not be logged to prevent infinite loop with log_filters()
*
* @since 1.1.2
* @access public
*/
public function __construct() {
self::$log_path_prefix = defined( 'LSCWP_DEBUG_PATH' ) ? LSCWP_DEBUG_PATH : LSCWP_CONTENT_DIR;
self::$log_path = self::$log_path_prefix . '/debug.log';
if ( ! empty( $_SERVER[ 'HTTP_USER_AGENT' ] ) && strpos( $_SERVER[ 'HTTP_USER_AGENT' ], 'lscache_' ) === 0 ) {
self::$log_path = self::$log_path_prefix . '/crawler.log';
}
! 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 );
}
}
/**
* Beta test upgrade
*
* @since 2.9.5
* @access public
*/
public function beta_test( $zip = false ) {
if ( ! $zip ) {
if ( empty( $_REQUEST[ self::BETA_TEST_URL ] ) ) {
return;
}
$zip = $_REQUEST[ self::BETA_TEST_URL ];
if ( $zip !== Debug2::BETA_TEST_URL_WP ) {
if ( $zip === 'latest' ) {
$zip = Debug2::BETA_TEST_URL_WP;
}
else {
// Generate zip url
$zip = $this->_package_zip( $zip );
}
}
}
if ( ! $zip ) {
Debug2::debug( '[Debug2] ❌ No ZIP file' );
return;
}
Debug2::debug( '[Debug2] ZIP file ' . $zip );
$update_plugins = get_site_transient( 'update_plugins' );
if ( ! is_object( $update_plugins ) ) {
$update_plugins = new \stdClass();
}
$plugin_info = new \stdClass();
$plugin_info->new_version = Core::VER;
$plugin_info->slug = Core::PLUGIN_NAME;
$plugin_info->plugin = Core::PLUGIN_FILE;
$plugin_info->package = $zip;
$plugin_info->url = 'https://wordpress.org/plugins/litespeed-cache/';
$update_plugins->response[ Core::PLUGIN_FILE ] = $plugin_info;
set_site_transient( 'update_plugins', $update_plugins );
// Run upgrade
Activation::cls()->upgrade();
}
/**
* Git package refresh
*
* @since 2.9.5
* @access private
*/
private function _package_zip( $commit ) {
$data = array(
'commit' => $commit,
);
$res = Cloud::get( Cloud::API_BETA_TEST, $data );
if ( empty( $res[ 'zip' ] ) ) {
return false;
}
return $res[ 'zip' ];
}
/**
* Log Purge headers separately
*
* @since 2.7
* @access public
*/
public static function log_purge( $purge_header ) {
// Check if debug is ON
if ( ! defined( 'LSCWP_LOG' ) && ! defined( 'LSCWP_LOG_BYPASS_NOTADMIN' ) ) {
return;
}
$purge_file = self::$log_path_prefix . '/debug.purge.log';
self::cls()->_init_request( $purge_file );
$msg = $purge_header . self::_backtrace_info( 6 );
File::append( $purge_file, self::format_message( $msg ) );
}
/**
* Enable debug log
*
* @since 1.1.0
* @access public
*/
public function init() {
$debug = $this->conf( Base::O_DEBUG );
if ( $debug == Base::VAL_ON2 ) {
if ( ! $this->cls( 'Router' )->is_admin_ip() ) {
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' => '',
'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_COLLAPS_QS ) ) {
if ( strlen( $qs ) > 53 ) {
$qs = substr( $qs, 0, 53 ) . '...';
}
if ( $qs ) {
$param .= ' ? ' . $qs;
}
$params[] = $param;
}
else {
$params[] = $param;
$params[] = 'Query String: ' . $qs;
}
if ( ! empty( $_SERVER[ 'HTTP_REFERER' ] ) ) {
$params[] = 'HTTP_REFERER: ' . $server[ 'HTTP_REFERER' ];
}
if ( defined( 'LSCWP_LOG_MORE' ) ) {
$params[] = 'User Agent: ' . $server[ 'HTTP_USER_AGENT' ];
$params[] = 'Accept: ' . $server['HTTP_ACCEPT'];
$params[] = 'Accept Encoding: ' . $server['HTTP_ACCEPT_ENCODING'];
}
if ( $this->conf( Base::O_DEBUG_COOKIE ) ) {
$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 );
}
/**
* 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 ( $_SERVER[ 'HTTP_X_FORWARDED_FOR' ] ) {
$addr .= $_SERVER[ 'HTTP_X_FORWARDED_FOR' ];
}
}
else {
$addr = $_SERVER[ 'REMOTE_ADDR' ] . ':' . $_SERVER[ 'REMOTE_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 ( $backtrace_limit !== false ) {
if ( ! is_numeric( $backtrace_limit ) ) {
$backtrace_limit = self::trim_longtext( $backtrace_limit );
if ( is_array( $backtrace_limit ) && count( $backtrace_limit ) == 1 && ! empty( $backtrace_limit[ 0 ] ) ) {
$msg .= ' --- ' . $backtrace_limit[ 0 ];
}
else {
$msg .= ' --- ' . var_export( $backtrace_limit, true );
}
self::push( $msg );
return;
}
self::push( $msg, $backtrace_limit + 1 );
return;
}
self::push( $msg );
}
/**
* Trim long string before array dump
* @since 3.3
*/
public static function trim_longtext( $backtrace_limit ) {
if ( is_array( $backtrace_limit ) ) {
$backtrace_limit = array_map( __CLASS__ . '::trim_longtext', $backtrace_limit );
}
if ( is_string( $backtrace_limit ) && strlen( $backtrace_limit ) > 500 ) {
$backtrace_limit = substr( $backtrace_limit, 0, 1000 ) . '...';
}
return $backtrace_limit;
}
/**
* Direct call to log an advanced debug message.
*
* @since 1.2.0
* @access public
*/
public static function debug2( $msg, $backtrace_limit = false ) {
if ( ! defined( 'LSCWP_LOG_MORE' ) ) {
return;
}
self::debug( $msg, $backtrace_limit );
}
/**
* Logs a debug message.
*
* @since 1.1.0
* @access private
* @param string $msg The debug message.
* @param int $backtrace_limit Backtrace depth.
*/
private static function push( $msg, $backtrace_limit = false ) {
// backtrace handler
if ( defined( 'LSCWP_LOG_MORE' ) && $backtrace_limit !== false ) {
$msg .= self::_backtrace_info( $backtrace_limit );
}
File::append( self::$log_path, self::format_message( $msg ) );
}
/**
* Backtrace info
*
* @since 2.7
*/
private static function _backtrace_info( $backtrace_limit ) {
$msg = '';
$trace = version_compare( PHP_VERSION, '5.4.0', '<' ) ? debug_backtrace() : debug_backtrace( false, $backtrace_limit + 3 );
for ( $i=2; $i <= $backtrace_limit + 2; $i++ ) {// 0st => _backtrace_info(), 1st => push()
if ( empty( $trace[ $i ][ 'class' ] ) ) {
if ( empty( $trace[ $i ][ 'file' ] ) ) {
break;
}
$log = "\n" . $trace[ $i ][ 'file' ];
}
else {
if ( $trace[$i]['class'] == __CLASS__ ) {
continue;
}
$args = '';
if ( ! empty( $trace[ $i ][ 'args' ] ) ) {
foreach ( $trace[ $i ][ 'args' ] as $v ) {
if ( is_array( $v ) ) {
$v = 'ARRAY';
}
if ( is_string( $v ) || is_numeric( $v ) ) {
$args .= $v . ',';
}
}
$args = substr( $args, 0, strlen( $args ) > 100 ? 100 : -1 );
}
$log = str_replace('Core', 'LSC', $trace[$i]['class']) . $trace[$i]['type'] . $trace[$i]['function'] . '(' . $args . ')';
}
if ( ! empty( $trace[$i-1]['line'] ) ) {
$log .= '@' . $trace[$i-1]['line'];
}
$msg .= " => $log";
}
return $msg;
}
/**
* Clear log file
*
* @since 1.6.6
* @access private
*/
private function _clear_log() {
File::save( self::$log_path, '' );
File::save( self::$log_path_prefix . '/debug.purge.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();
}
}

View File

@@ -0,0 +1,123 @@
<?php
/**
* The Doc class.
*
* @since 2.2.7
* @package LiteSpeed
* @subpackage LiteSpeed/src
* @author LiteSpeed Technologies <info@litespeedtech.com>
*/
namespace LiteSpeed;
defined( 'WPINC' ) || exit;
class Doc {
// protected static $_instance;
/**
* Changes affect crawler list warning
*
* @since 4.3
* @access public
*/
public static function crawler_affected() {
echo '<font class="litespeed-primary">';
echo '⚠️ ' . __( 'This setting will regenerate crawler list and clear the disabled list!' , 'litespeed-cache' );
echo '</font>';
}
/**
* Privacy policy
*
* @since 2.2.7
* @access public
*/
public static function privacy_policy() {
return __( 'This site utilizes caching in order to facilitate a faster response time and better user experience. Caching potentially stores a duplicate copy of every web page that is on display on this site. All cache files are temporary, and are never accessed by any third party, except as necessary to obtain technical support from the cache plugin vendor. Cache files expire on a schedule set by the site administrator, but may easily be purged by the admin before their natural expiration, if necessary. We may use QUIC.cloud services to process & cache your data temporarily.', 'litespeed-cache' )
. sprintf( __( 'Please see %s for more details.', 'litespeed-cache' ), '<a href="https://quic.cloud/privacy-policy/" target="_blank">https://quic.cloud/privacy-policy/</a>' );
}
/**
* Learn more link
*
* @since 2.4.2
* @access public
*/
public static function learn_more( $url, $title = false, $self = false, $class = false, $return = false ) {
if ( ! $class ) {
$class = 'litespeed-learn-more';
}
if ( ! $title ) {
$title = __( 'Learn More', 'litespeed-cache' );
}
$self = $self ? '' : "target='_blank'";
$txt = " <a href='$url' $self class='$class'>$title</a>";
if ( $return ) {
return $txt;
}
echo $txt;
}
/**
* One per line
*
* @since 3.0
* @access public
*/
public static function one_per_line( $return = false ) {
$str = __( 'One per line.', 'litespeed-cache' );
if ( $return ) {
return $str;
}
echo $str;
}
/**
* One per line
*
* @since 3.4
* @access public
*/
public static function full_or_partial_url( $string_only = false ) {
if ( $string_only ) {
echo __( 'Both full and partial strings can be used.', 'litespeed-cache' );
}
else {
echo __( 'Both full URLs and partial strings can be used.', 'litespeed-cache' );
}
}
/**
* Notice to edit .htaccess
*
* @since 3.0
* @access public
*/
public static function notice_htaccess() {
echo '<font class="litespeed-primary">';
echo '⚠️ ' . __( 'This setting will edit the .htaccess file.', 'litespeed-cache' );
echo ' <a href="https://docs.litespeedtech.com/lscache/lscwp/toolbox/#edit-htaccess-tab" target="_blank" class="litespeed-learn-more">' . __( 'Learn More', 'litespeed-cache' ) . '</a>';
echo '</font>';
}
/**
* Notice for whitelist IPs
*
* @since 3.0
* @access public
*/
public static function notice_ips() {
echo '<div class="litespeed-primary">';
echo '⚠️ ' . sprintf( __( 'For online services to work correctly, you must allowlist all %s server IPs.', 'litespeed-cache' ), 'QUIC.cloud' ) . '<br/>';
echo '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;' . __( 'Before generating key, please verify all IPs on this list are allowlisted', 'litespeed-cache' ) . ': ';
echo '<a href="' . Cloud::CLOUD_IPS . '" target="_blank">' . __( 'Current Online Server IPs', 'litespeed-cache' ) . '</a>';
echo '</div>';
}
}

View File

@@ -0,0 +1,187 @@
<?php
/**
* The error class.
*
* @since 3.0
* @package LiteSpeed
* @subpackage LiteSpeed/src
* @author LiteSpeed Technologies <info@litespeedtech.com>
*/
namespace LiteSpeed;
defined( 'WPINC' ) || exit;
class Error {
private static $CODE_SET = array(
'HTA_LOGIN_COOKIE_INVALID' => 4300, // .htaccess did not find.
'HTA_DNF' => 4500, // .htaccess did not find.
'HTA_BK' => 9010, // backup
'HTA_R' => 9041, // read htaccess
'HTA_W' => 9042, // write
'HTA_GET' => 9030, // failed to get
);
/**
* Throw an error with msg
*
* @since 3.0
*/
public static function t( $code, $args = null ) {
throw new \Exception( self::msg( $code, $args ) );
}
/**
* Translate an error to description
*
* @since 3.0
*/
public static function msg( $code, $args = null ) {
switch ( $code ) {
case 'disabled_all':
$msg = sprintf( __( 'The setting %s is currently enabled.', 'litespeed-cache' ), '<strong>' . Lang::title( Base::O_DEBUG_DISABLE_ALL ) . '</strong>' ) .
Doc::learn_more( admin_url( 'admin.php?page=litespeed-toolbox' ), __( 'Click here to change.', 'litespeed-cache' ), true, false, true );
break;
case 'lack_of_api_key':
$msg = sprintf( __( 'You will need to set %s to use the online services.', 'litespeed-cache' ), '<strong>' . Lang::title( Base::O_API_KEY ) . '</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 don\'t have enough daily quota left for current service today.', 'litespeed-cache' );
break;
case 'out_of_quota':
$msg = __( 'You don\'t have enough quota left for current service this month.', 'litespeed-cache' );
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 too many notified images, please pull down notified images first.', 'litespeed-cache' );
break;
case 'empty_list':
$msg = __( 'The image list is empty.', 'litespeed-cache' );
break;
case 'lack_of_param':
$msg = __( 'Not enough parameters. Please check if the domain key is set correctly', 'litespeed-cache' );
break;
case 'unfinished_queue':
$msg = __( 'There is proceeding queue not pulled yet.', 'litespeed-cache' );
break;
case strpos( $code, 'unfinished_queue ' ) === 0:
$msg = sprintf( __( 'There is proceeding queue not pulled yet. Queue info: %s.', 'litespeed-cache' ), '<code>' . substr( $code, strlen( 'unfinished_queue ' ) ) . '</code>' );
break;
case 'site_not_registered':
$msg = __( 'The site is not registered on QUIC.cloud.', 'litespeed-cache' );
break;
case 'err_key':
$msg = __( 'The domain key is not correct. Please try to sync your domain key again.', 'litespeed-cache' );
break;
case 'heavy_load':
$msg = __( 'The current server is under heavy load.', 'litespeed-cache' );
break;
case 'redetect_node':
$msg = __( 'Online node needs to be redetected.', 'litespeed-cache' );
break;
case 'err_overdraw':
$msg = __( 'Credits are not enough to proceed the current request.', 'litespeed-cache' );
break;
case 'W':
$msg = __( '%s file not writable.', 'litespeed-cache' );
break;
case 'HTA_DNF':
if ( ! is_array( $args ) ) {
$args = array( '<code>' . $args . '</code>' );
}
$args[] = '.htaccess';
$msg = __( 'Could not find %1$s in %2$s.', 'litespeed-cache' );
break;
case 'HTA_LOGIN_COOKIE_INVALID':
$msg = sprintf( __( 'Invalid login cookie. Please check the %s file.', 'litespeed-cache' ), '.htaccess' );
break;
case 'HTA_BK':
$msg = sprintf( __( 'Failed to back up %s file, aborted changes.', 'litespeed-cache' ), '.htaccess' );
break;
case 'HTA_R':
$msg = sprintf( __( '%s file not readable.', 'litespeed-cache' ), '.htaccess' );
break;
case 'HTA_W':
$msg = sprintf( __( '%s file not writable.', 'litespeed-cache' ), '.htaccess' );
break;
case 'HTA_GET':
$msg = sprintf( __( 'Failed to get %s file contents.', 'litespeed-cache' ), '.htaccess' );
break;
case 'failed_tb_creation':
$msg = __( 'Failed to create table %s! SQL: %s.', 'litespeed-cache' );
break;
case 'crawler_disabled':
$msg = __( 'Crawler disabled by the server admin.', 'litespeed-cache' );
break;
/*** QC error code ***/
case 'try_later':
$msg = __( 'Previous request too recent. Please try again later.', 'litespeed-cache' );
break;
case strpos( $code, 'try_later ' ) === 0:
$msg = sprintf( __( 'Previous request too recent. Please try again after %s.', 'litespeed-cache' ), '<code>' . Utility::readable_time( substr( $code, strlen( 'try_later ' ) ), 3600, true ) . '</code>' );
break;
case 'waiting_for_approval':
$msg = __( 'Your application is waiting for approval.', 'litespeed-cache' );
break;
case 'callback_fail_hash':
$msg = __( 'The callback validation to your domain failed due to hash mismatch.', 'litespeed-cache' );
break;
case 'callback_fail':
$msg = __( 'The callback validation to your domain failed. Please make sure there is no firewall blocking our servers.', 'litespeed-cache' );
break;
case substr( $code, 0, 14 ) === 'callback_fail ':
$msg = __( 'The callback validation to your domain failed. Please make sure there is no firewall blocking our servers. Response code: ', 'litespeed-cache' ) . substr( $code, 14 );
break;
case 'forbidden':
$msg = __( 'Your domain has been forbidden from using our services due to a previous policy violation.', 'litespeed-cache' );
break;
default:
$msg = __( 'Unknown error', 'litespeed-cache' ) . ': ' . $code;
break;
}
if ( $args !== null ) {
$msg = is_array( $args ) ? vsprintf( $msg, $args ) : sprintf( $msg, $args );
}
if ( isset( self::$CODE_SET[ $code ] ) ) {
$msg = 'ERROR ' . self::$CODE_SET[ $code ] . ': ' . $msg;
}
return $msg;
}
}

View File

@@ -0,0 +1,996 @@
<?php
/**
* The ESI class.
*
* This is used to define all esi related functions.
*
* @since 1.1.3
* @package LiteSpeed
* @subpackage LiteSpeed/src
* @author LiteSpeed Technologies <info@litespeedtech.com>
*/
namespace LiteSpeed;
defined( 'WPINC' ) || exit;
class ESI extends Root {
private static $has_esi = false;
private static $_combine_ids = array();
private $esi_args = null;
private $_esi_preserve_list = array();
private $_nonce_actions = array( -1 => '' ); // val is cache control
const QS_ACTION = 'lsesi';
const QS_PARAMS = 'esi';
const COMBO = '__combo'; // ESI include combine='main' handler
const PARAM_ARGS = 'args';
const PARAM_ID = 'id';
const PARAM_INSTANCE = 'instance';
const PARAM_NAME = 'name';
const WIDGET_O_ESIENABLE = 'widget_esi_enable';
const WIDGET_O_TTL = 'widget_ttl';
/**
* Confructor of ESI
*
* @since 1.2.0
* @since 4.0 Change to be after Vary init in hook 'after_setup_theme'
*/
public function init() {
/**
* Bypass ESI related funcs if disabled ESI to fix potential DIVI compatibility issue
* @since 2.9.7.2
*/
if ( Router::is_ajax() || ! $this->cls( 'Router' )->esi_enabled() ) {
return;
}
// Guest mode, don't need to use ESI
if ( defined( 'LITESPEED_GUEST' ) && LITESPEED_GUEST ) {
return;
}
if ( defined( 'LITESPEED_ESI_OFF' ) ) {
return;
}
// Init ESI in `after_setup_theme` hook after detected if LITESPEED_DISABLE_ALL is ON or not
$this->_hooks();
/**
* Overwrite wp_create_nonce func
* @since 2.9.5
*/
$this->_transform_nonce();
! defined( 'LITESPEED_ESI_INITED' ) && define( 'LITESPEED_ESI_INITED', true );
}
/**
* Init ESI related hooks
*
* Load delayed by hook to give the ability to bypass by LITESPEED_DISABLE_ALL const
*
* @since 2.9.7.2
* @since 4.0 Changed to private from public
* @access private
*/
private function _hooks() {
add_filter( 'template_include', array( $this, 'esi_template' ), 99999 );
add_action( 'load-widgets.php', __NAMESPACE__ . '\Purge::purge_widget' );
add_action( 'wp_update_comment_count', __NAMESPACE__ . '\Purge::purge_comment_widget' );
/**
* Recover REQUEST_URI
* @since 1.8.1
*/
if ( ! empty( $_GET[ self::QS_ACTION ] ) ) {
$this->_register_esi_actions();
}
/**
* Shortcode ESI
*
* To use it, just change the origianl shortcode as below:
* old: [someshortcode aa='bb']
* new: [esi someshortcode aa='bb' cache='private,no-vary' ttl='600']
*
* 1. `cache` attribute is optional, default to 'public,no-vary'.
* 2. `ttl` attribute is optional, default is your public TTL setting.
*
* @since 2.8
* @since 2.8.1 Check is_admin for Elementor compatibility #726013
*/
if ( ! is_admin() ) {
add_shortcode( 'esi', array( $this, 'shortcode' ) );
}
}
/**
* Take over all nonce calls and transform to ESI
*
* @since 2.9.5
*/
private function _transform_nonce() {
if ( is_admin() ) {
return;
}
// Load ESI nonces in conf
$nonces = $this->conf( Base::O_ESI_NONCE );
add_filter( 'litespeed_esi_nonces', array( $this->cls( 'Data' ), 'load_esi_nonces' ) );
if ( $nonces = apply_filters( 'litespeed_esi_nonces', $nonces ) ) {
foreach ( $nonces as $action ) {
$this->nonce_action( $action );
}
}
add_action( 'litespeed_nonce', array( $this, 'nonce_action' ) );
}
/**
* Register a new nonce action to convert it to ESI
*
* @since 2.9.5
*/
public function nonce_action( $action ) {
// Split the Cache Control
$action = explode( ' ', $action );
$control = ! empty( $action[ 1 ] ) ? $action[ 1 ] : '';
$action = $action[ 0 ];
// Wildcard supported
$action = Utility::wildcard2regex( $action );
if ( array_key_exists( $action, $this->_nonce_actions ) ) {
return;
}
$this->_nonce_actions[ $action ] = $control;
Debug2::debug( '[ESI] Appended nonce action to nonce list [action] ' . $action );
}
/**
* Check if an action is registered to replace ESI
*
* @since 2.9.5
*/
public function is_nonce_action( $action ) {
// If GM not run yet, then ESI not init yet, then ESI nonce will not be allowed even nonce func replaced.
if ( ! defined( 'LITESPEED_ESI_INITED' ) ) {
return null;
}
if ( is_admin() ) {
return null;
}
if ( defined( 'LITESPEED_ESI_OFF' ) ) {
return null;
}
foreach ( $this->_nonce_actions as $k => $v ) {
if ( strpos( $k, '*' ) !== false ) {
if( preg_match( '#' . $k . '#iU', $action ) ) {
return $v;
}
}
else {
if ( $k == $action ) {
return $v;
}
}
}
return null;
}
/**
* Shortcode ESI
*
* @since 2.8
* @access public
*/
public function shortcode( $atts ) {
if ( empty( $atts[ 0 ] ) ) {
Debug2::debug( '[ESI] ===shortcode wrong format', $atts );
return 'Wrong shortcode esi format';
}
$cache = 'public,no-vary';
if ( ! empty( $atts[ 'cache' ] ) ) {
$cache = $atts[ 'cache' ];
unset( $atts[ 'cache' ] );
}
do_action( 'litespeed_esi_shortcode-' . $atts[ 0 ] );
// Show ESI link
return $this->sub_esi_block( 'esi', 'esi-shortcode', $atts, $cache );
}
/**
* Check if the requested page has esi elements. If so, return esi on
* header.
*
* @since 1.1.3
* @access public
* @return string Esi On header if request has esi, empty string otherwise.
*/
public static function has_esi() {
return self::$has_esi;
}
/**
* Sets that the requested page has esi elements.
*
* @since 1.1.3
* @access public
*/
public static function set_has_esi() {
self::$has_esi = true;
}
/**
* Register all of the hooks related to the esi logic of the plugin.
* Specifically when the page IS an esi page.
*
* @since 1.1.3
* @access private
*/
private function _register_esi_actions() {
! defined( 'LSCACHE_IS_ESI' ) && define( 'LSCACHE_IS_ESI', $_GET[ self::QS_ACTION ] );// Reused this to ESI block ID
! empty( $_SERVER[ 'ESI_REFERER' ] ) && defined( 'LSCWP_LOG' ) && Debug2::debug( '[ESI] ESI_REFERER: ' . $_SERVER[ 'ESI_REFERER' ] );
/**
* Only when ESI's parent is not REST, replace REQUEST_URI to avoid breaking WP5 editor REST call
* @since 2.9.3
*/
if ( ! empty( $_SERVER[ 'ESI_REFERER' ] ) && ! $this->cls( 'REST' )->is_rest( $_SERVER[ 'ESI_REFERER' ] ) ) {
$_SERVER[ 'REQUEST_URI' ] = $_SERVER[ 'ESI_REFERER' ];
}
if ( ! empty( $_SERVER[ 'ESI_CONTENT_TYPE' ] ) && strpos( $_SERVER[ 'ESI_CONTENT_TYPE' ], 'application/json' ) === 0 ) {
add_filter( 'litespeed_is_json', '__return_true' );
}
/**
* Make REST call be able to parse ESI
* NOTE: Not effective due to ESI req are all to `/` yet
* @since 2.9.4
*/
add_action( 'rest_api_init', array( $this, 'load_esi_block' ), 101 );
// Register ESI blocks
add_action('litespeed_esi_load-widget', array($this, 'load_widget_block'));
add_action('litespeed_esi_load-admin-bar', array($this, 'load_admin_bar_block'));
add_action('litespeed_esi_load-comment-form', array($this, 'load_comment_form_block'));
add_action('litespeed_esi_load-nonce', array( $this, 'load_nonce_block' ) );
add_action('litespeed_esi_load-esi', array( $this, 'load_esi_shortcode' ) );
add_action('litespeed_esi_load-' . self::COMBO, array( $this, 'load_combo' ) );
}
/**
* Hooked to the template_include action.
* Selects the esi template file when the post type is a LiteSpeed ESI page.
*
* @since 1.1.3
* @access public
* @param string $template The template path filtered.
* @return string The new template path.
*/
public function esi_template( $template ) {
// Check if is an ESI request
if ( defined( 'LSCACHE_IS_ESI' ) ) {
Debug2::debug( '[ESI] calling template' );
return LSCWP_DIR . 'tpl/esi.tpl.php';
}
$this->_register_not_esi_actions();
return $template;
}
/**
* Register all of the hooks related to the esi logic of the plugin.
* Specifically when the page is NOT an esi page.
*
* @since 1.1.3
* @access private
*/
private function _register_not_esi_actions() {
do_action( 'litespeed_tpl_normal' );
if ( ! Control::is_cacheable() ) {
return;
}
if ( Router::is_ajax() ) {
return;
}
add_filter('widget_display_callback', array( $this, 'sub_widget_block' ), 0, 3);
// Add admin_bar esi
if ( Router::is_logged_in() ) {
remove_action('wp_footer', 'wp_admin_bar_render', 1000);
add_action('wp_footer', array($this, 'sub_admin_bar_block'), 1000);
}
// Add comment forum esi for logged-in user or commenter
if ( ! Router::is_ajax() && Vary::has_vary() ) {
add_filter( 'comment_form_defaults', array( $this, 'register_comment_form_actions' ) );
}
}
/**
* Set an ESI to be combine='sub'
*
* @since 3.4.2
*/
public static function combine( $block_id ) {
if ( ! isset( $_SERVER[ 'X-LSCACHE' ] ) || strpos( $_SERVER[ 'X-LSCACHE' ], 'combine' ) === false ) {
return;
}
if ( in_array( $block_id, self::$_combine_ids ) ) {
return;
}
self::$_combine_ids[] = $block_id;
}
/**
* Load combined ESI
*
* @since 3.4.2
*/
public function load_combo() {
Control::set_nocache( 'ESI combine request' );
if ( empty( $_POST[ 'esi_include' ] ) ) {
return;
}
self::set_has_esi();
Debug2::debug( '[ESI] 🍔 Load combo', $_POST[ 'esi_include' ] );
$output = '';
foreach ( $_POST[ 'esi_include' ] as $url ) {
$qs = parse_url( htmlspecialchars_decode( $url ), PHP_URL_QUERY );
parse_str( $qs, $qs );
if ( empty( $qs[ self::QS_ACTION ] ) ) {
continue;
}
$esi_id = $qs[ self::QS_ACTION ];
$esi_param = ! empty( $qs[ self::QS_PARAMS ] ) ? $this->_parse_esi_param( $qs[ self::QS_PARAMS ] ) : false;
$inline_param = apply_filters( 'litespeed_esi_inline-' . $esi_id, array(), $esi_param ); // Returned array need to be [ val, control, tag ]
if ( $inline_param ) {
$output .= self::_build_inline( $url, $inline_param );
}
}
echo $output;
}
/**
* Build a whole inline segment
*
* @since 3.4.2
*/
private static function _build_inline( $url, $inline_param ) {
if ( ! $url || empty( $inline_param[ 'val' ] ) || empty( $inline_param[ 'control' ] ) || empty( $inline_param[ 'tag' ] ) ) {
return '';
}
return "<esi:inline name='$url' cache-control='" . $inline_param[ 'control' ] . "' cache-tag='" . $inline_param[ 'tag' ] . "'>" . $inline_param[ 'val' ] . "</esi:inline>";
}
/**
* Build the esi url. This method will build the html comment wrapper as well as serialize and encode the parameter array.
*
* The block_id parameter should contain alphanumeric and '-_' only.
*
* @since 1.1.3
* @access private
* @param string $block_id The id to use to display the correct esi block.
* @param string $wrapper The wrapper for the esi comments.
* @param array $params The esi parameters.
* @param string $control The cache control attribute if any.
* @param bool $silence If generate wrapper comment or not
* @param bool $preserved If this ESI block is used in any filter, need to temporarily convert it to a string to avoid the HTML tag being removed/filtered.
* @param bool $svar If store the value in memory or not, in memory wil be faster
* @param array $inline_val If show the current value for current request( this can avoid multiple esi requests in first time cache generating process )
*/
public function sub_esi_block( $block_id, $wrapper, $params = array(), $control = 'private,no-vary', $silence = false, $preserved = false, $svar = false, $inline_param = array() ) {
if ( empty($block_id) || ! is_array($params) || preg_match('/[^\w-]/', $block_id) ) {
return false;
}
if ( defined( 'LITESPEED_ESI_OFF' ) ) {
Debug2::debug( '[ESI] ESI OFF so force loading [block_id] ' . $block_id );
do_action( 'litespeed_esi_load-' . $block_id, $params );
return;
}
if ( $silence ) {
// Don't add comment to esi block ( orignal for nonce used in tag property data-nonce='esi_block' )
$params[ '_ls_silence' ] = true;
}
if ( $this->cls( 'REST' )->is_rest() || $this->cls( 'REST' )->is_internal_rest() ) {
$params[ 'is_json' ] = 1;
}
$params = apply_filters( 'litespeed_esi_params', $params, $block_id );
$control = apply_filters('litespeed_esi_control', $control, $block_id );
if ( !is_array($params) || !is_string($control) ) {
defined( 'LSCWP_LOG' ) && Debug2::debug( "[ESI] 🛑 Sub hooks returned Params: \n" . var_export($params, true) . "\ncache control: \n" . var_export($control, true) );
return false;
}
// Build params for URL
$appended_params = array(
self::QS_ACTION => $block_id,
);
if ( ! empty( $control ) ) {
$appended_params[ '_control' ] = $control;
}
if ( $params ) {
$appended_params[ self::QS_PARAMS ] = base64_encode( json_encode( $params ) );
Debug2::debug2( '[ESI] param ', $params );
}
// Append hash
$appended_params[ '_hash' ] = $this->_gen_esi_md5( $appended_params );
/**
* Escape potential chars
* @since 2.9.4
*/
$appended_params = array_map( 'urlencode', $appended_params );
// Generate ESI URL
$url = add_query_arg( $appended_params, trailingslashit( wp_make_link_relative( home_url() ) ) );
$output = '';
if ( $inline_param ) {
$output .= self::_build_inline( $url, $inline_param );
}
$output .= "<esi:include src='$url'";
if ( ! empty( $control ) ) {
$output .= " cache-control='$control'";
}
if ( $svar ) {
$output .= " as-var='1'";
}
if ( in_array( $block_id, self::$_combine_ids ) ) {
$output .= " combine='sub'";
}
if ( $block_id == self::COMBO && isset( $_SERVER[ 'X-LSCACHE' ] ) && strpos( $_SERVER[ 'X-LSCACHE' ], 'combine' ) !== false ) {
$output .= " combine='main'";
}
$output .= " />";
if ( ! $silence ) {
$output = "<!-- lscwp $wrapper -->$output<!-- lscwp $wrapper esi end -->";
}
Debug2::debug( "[ESI] 💕 [BLock_ID] $block_id \t[wrapper] $wrapper \t\t[Control] $control" );
Debug2::debug2( $output );
self::set_has_esi();
// Convert to string to avoid html chars filter when using
// Will reverse the buffer when output in self::finalize()
if ( $preserved ) {
$hash = md5( $output );
$this->_esi_preserve_list[ $hash ] = $output;
Debug2::debug( "[ESI] Preserved to $hash" );
return $hash;
}
return $output;
}
/**
* Generate ESI hash md5
*
* @since 2.9.6
* @access private
*/
private function _gen_esi_md5( $params ) {
$keys = array(
self::QS_ACTION,
'_control',
self::QS_PARAMS,
);
$str = '';
foreach ( $keys as $v ) {
if ( isset( $params[ $v ] ) && is_string( $params[ $v ] ) ) {
$str .= $params[ $v ];
}
}
Debug2::debug2( '[ESI] md5_string=' . $str );
return md5( $this->conf( Base::HASH ) . $str );
}
/**
* Parses the request parameters on an ESI request
*
* @since 1.1.3
* @access private
*/
private function _parse_esi_param( $qs_params = false ) {
$req_params = false;
if ( $qs_params ) {
$req_params = $qs_params;
}
elseif ( isset( $_REQUEST[ self::QS_PARAMS ] ) ) {
$req_params = $_REQUEST[ self::QS_PARAMS ];
}
if ( ! $req_params ) {
return false;
}
$unencrypted = base64_decode( $req_params );
if ( $unencrypted === false ) {
return false;
}
Debug2::debug2( '[ESI] parms', $unencrypted );
// $unencoded = urldecode($unencrypted); no need to do this as $_GET is already parsed
$params = json_decode( $unencrypted, true );
return $params;
}
/**
* Select the correct esi output based on the parameters in an ESI request.
*
* @since 1.1.3
* @access public
*/
public function load_esi_block() {
/**
* Validate if is a legal ESI req
* @since 2.9.6
*/
if ( empty( $_GET[ '_hash' ] ) || $this->_gen_esi_md5( $_GET ) != $_GET[ '_hash' ] ) {
Debug2::debug( '[ESI] ❌ Failed to validate _hash' );
return;
}
$params = $this->_parse_esi_param();
if ( defined( 'LSCWP_LOG' ) ) {
$logInfo = '[ESI] ⭕ ';
if( ! empty( $params[ self::PARAM_NAME ] ) ) {
$logInfo .= ' Name: ' . $params[ self::PARAM_NAME ] . ' ----- ';
}
$logInfo .= ' [ID] ' . LSCACHE_IS_ESI;
Debug2::debug( $logInfo );
}
if ( ! empty( $params[ '_ls_silence' ] ) ) {
! defined( 'LSCACHE_ESI_SILENCE' ) && define( 'LSCACHE_ESI_SILENCE', true );
}
/**
* Buffer needs to be JSON format
* @since 2.9.4
*/
if ( ! empty( $params[ 'is_json' ] ) ) {
add_filter( 'litespeed_is_json', '__return_true' );
}
Tag::add( rtrim( Tag::TYPE_ESI, '.' ) );
Tag::add( Tag::TYPE_ESI . LSCACHE_IS_ESI );
// Debug2::debug(var_export($params, true ));
/**
* Handle default cache control 'private,no-vary' for sub_esi_block() @ticket #923505
*
* @since 2.2.3
*/
if ( ! empty( $_GET[ '_control' ] ) ) {
$control = explode( ',', $_GET[ '_control' ] );
if ( in_array( 'private', $control ) ) {
Control::set_private();
}
if ( in_array( 'no-vary', $control ) ) {
Control::set_no_vary();
}
}
do_action('litespeed_esi_load-' . LSCACHE_IS_ESI, $params);
}
// The *_sub_* functions are helpers for the sub_* functions.
// The *_load_* functions are helpers for the load_* functions.
/**
* Loads the default options for default WordPress widgets.
*
* @since 1.1.3
* @access public
*/
public static function widget_default_options($options, $widget) {
if ( ! is_array($options) ) {
return $options;
}
$widget_name = get_class($widget);
switch ($widget_name) {
case 'WP_Widget_Recent_Posts' :
case 'WP_Widget_Recent_Comments' :
$options[self::WIDGET_O_ESIENABLE] = Base::VAL_OFF;
$options[self::WIDGET_O_TTL] = 86400;
break;
default :
break;
}
return $options;
}
/**
* Hooked to the widget_display_callback filter.
* If the admin configured the widget to display via esi, this function
* will set up the esi request and cancel the widget display.
*
* @since 1.1.3
* @access public
* @param array $instance Parameter used to build the widget.
* @param WP_Widget $widget The widget to build.
* @param array $args Parameter used to build the widget.
* @return mixed Return false if display through esi, instance otherwise.
*/
public function sub_widget_block( $instance, $widget, $args ) {
// #210407
if ( ! is_array( $instance ) ) {
return $instance;
}
$name = get_class( $widget );
if ( ! isset( $instance[ Base::OPTION_NAME ] ) ) {
return $instance;
}
$options = $instance[ Base::OPTION_NAME ];
if ( ! isset( $options ) || ! $options[ self::WIDGET_O_ESIENABLE ] ) {
defined( 'LSCWP_LOG' ) && Debug2::debug( 'ESI 0 ' . $name . ': '. ( ! isset( $options ) ? 'not set' : 'set off' ) );
return $instance;
}
$esi_private = $options[ self::WIDGET_O_ESIENABLE ] == Base::VAL_ON2 ? 'private,' : '';
$params = array(
self::PARAM_NAME => $name,
self::PARAM_ID => $widget->id,
self::PARAM_INSTANCE => $instance,
self::PARAM_ARGS => $args
);
echo $this->sub_esi_block( 'widget', 'widget ' . $name, $params, $esi_private . 'no-vary' );
return false;
}
/**
* Hooked to the wp_footer action.
* Sets up the ESI request for the admin bar.
*
* @access public
* @since 1.1.3
* @global type $wp_admin_bar
*/
public function sub_admin_bar_block() {
global $wp_admin_bar;
if ( ! is_admin_bar_showing() || ! is_object($wp_admin_bar) ) {
return;
}
// To make each admin bar ESI request different for `Edit` button different link
$params = array(
'ref' => $_SERVER[ 'REQUEST_URI' ],
);
echo $this->sub_esi_block( 'admin-bar', 'adminbar', $params );
}
/**
* Parses the esi input parameters and generates the widget for esi display.
*
* @access public
* @since 1.1.3
* @global $wp_widget_factory
* @param array $params Input parameters needed to correctly display widget
*/
public function load_widget_block( $params ) {
// global $wp_widget_factory;
// $widget = $wp_widget_factory->widgets[ $params[ self::PARAM_NAME ] ];
$option = $params[ self::PARAM_INSTANCE ];
$option = $option[ Base::OPTION_NAME ];
// Since we only reach here via esi, safe to assume setting exists.
$ttl = $option[ self::WIDGET_O_TTL ];
defined( 'LSCWP_LOG' ) && Debug2::debug( 'ESI widget render: name ' . $params[ self::PARAM_NAME ] . ', id ' . $params[ self::PARAM_ID ] . ', ttl ' . $ttl );
if ( $ttl == 0 ) {
Control::set_nocache( 'ESI Widget time to live set to 0' );
}
else {
Control::set_custom_ttl( $ttl );
if ( $option[ self::WIDGET_O_ESIENABLE ] == Base::VAL_ON2 ) {
Control::set_private();
}
Control::set_no_vary();
Tag::add( Tag::TYPE_WIDGET . $params[ self::PARAM_ID ] );
}
the_widget( $params[ self::PARAM_NAME ], $params[ self::PARAM_INSTANCE ], $params[ self::PARAM_ARGS ] );
}
/**
* Generates the admin bar for esi display.
*
* @access public
* @since 1.1.3
*/
public function load_admin_bar_block( $params ) {
if ( ! empty( $params[ 'ref' ] ) ) {
$ref_qs = parse_url( $params[ 'ref' ], PHP_URL_QUERY );
if ( ! empty( $ref_qs ) ) {
parse_str( $ref_qs, $ref_qs_arr );
if ( ! empty( $ref_qs_arr ) ) {
foreach ( $ref_qs_arr as $k => $v ) {
$_GET[ $k ] = $v;
}
}
}
}
wp_admin_bar_render();
if ( ! $this->conf( Base::O_ESI_CACHE_ADMBAR ) ) {
Control::set_nocache( 'build-in set to not cacheable' );
}
else {
Control::set_private();
Control::set_no_vary();
}
defined( 'LSCWP_LOG' ) && Debug2::debug( 'ESI: adminbar ref: ' . $_SERVER[ 'REQUEST_URI' ] );
}
/**
* Parses the esi input parameters and generates the comment form for esi display.
*
* @access public
* @since 1.1.3
* @param array $params Input parameters needed to correctly display comment form
*/
public function load_comment_form_block( $params ) {
comment_form( $params[ self::PARAM_ARGS ], $params[ self::PARAM_ID ] );
if ( ! $this->conf( Base::O_ESI_CACHE_COMMFORM ) ) {
Control::set_nocache( 'build-in set to not cacheable' );
}
else {
// by default comment form is public
if ( Vary::has_vary() ) {
Control::set_private();
Control::set_no_vary();
}
}
}
/**
* Generate nonce for certain action
*
* @access public
* @since 2.6
*/
public function load_nonce_block( $params ) {
$action = $params[ 'action' ];
Debug2::debug( '[ESI] load_nonce_block [action] ' . $action );
// set nonce TTL to half day
Control::set_custom_ttl( 43200 );
if ( Router::is_logged_in() ) {
Control::set_private();
}
if ( function_exists( 'wp_create_nonce_litespeed_esi' ) ) {
echo wp_create_nonce_litespeed_esi( $action );
}
else {
echo wp_create_nonce( $action );
}
}
/**
* Show original shortcode
*
* @access public
* @since 2.8
*/
public function load_esi_shortcode( $params ) {
if ( isset( $params[ 'ttl' ] ) ) {
if ( ! $params[ 'ttl' ] ) {
Control::set_nocache( 'ESI shortcode att ttl=0' );
}
else {
Control::set_custom_ttl( $params[ 'ttl' ] );
}
unset( $params[ 'ttl' ] );
}
// Replace to original shortcode
$shortcode = $params[ 0 ];
$atts_ori = array();
foreach ( $params as $k => $v ) {
if ( $k === 0 ) {
continue;
}
$atts_ori[] = is_string( $k ) ? "$k='" . addslashes( $v ) . "'" : $v;
}
Tag::add( Tag::TYPE_ESI . "esi.$shortcode" );
// Output original shortcode final content
echo do_shortcode( "[$shortcode " . implode( ' ', $atts_ori ) . " ]" );
}
/**
* Hooked to the comment_form_defaults filter.
* Stores the default comment form settings.
* If sub_comment_form_block is triggered, the output buffer is cleared and an esi block is added. The remaining comment form is also buffered and cleared.
* Else there is no need to make the comment form ESI.
*
* @since 1.1.3
* @access public
*/
public function register_comment_form_actions( $defaults ) {
$this->esi_args = $defaults;
echo GUI::clean_wrapper_begin();
add_filter( 'comment_form_submit_button', array( $this, 'sub_comment_form_btn' ), 1000, 2 ); // To save the params passed in
add_action( 'comment_form', array( $this, 'sub_comment_form_block' ), 1000 );
return $defaults;
}
/**
* Store the args passed in comment_form for the ESI comment param usage in `$this->sub_comment_form_block()`
*
* @since 3.4
* @access public
*/
public function sub_comment_form_btn( $unused, $args ) {
if ( empty( $args ) || empty( $this->esi_args ) ) {
Debug2::debug( 'comment form args empty?' );
return $unused;
}
$esi_args = array();
// compare current args with default ones
foreach ( $args as $k => $v ) {
if ( ! isset( $this->esi_args[ $k ] ) ) {
$esi_args[ $k ] = $v;
}
elseif ( is_array( $v ) ) {
$diff = array_diff_assoc( $v, $this->esi_args[ $k ] );
if ( ! empty( $diff ) ) {
$esi_args[ $k ] = $diff;
}
}
elseif ( $v !== $this->esi_args[ $k ] ) {
$esi_args[ $k ] = $v;
}
}
$this->esi_args = $esi_args;
return $unused;
}
/**
* Hooked to the comment_form_submit_button filter.
*
* This method will compare the used comment form args against the default args. The difference will be passed to the esi request.
*
* @access public
* @since 1.1.3
*/
public function sub_comment_form_block( $post_id ) {
echo GUI::clean_wrapper_end();
$params = array(
self::PARAM_ID => $post_id,
self::PARAM_ARGS => $this->esi_args,
);
echo $this->sub_esi_block( 'comment-form', 'comment form', $params );
echo GUI::clean_wrapper_begin();
add_action( 'comment_form_after', array( $this, 'comment_form_sub_clean' ) );
}
/**
* Hooked to the comment_form_after action.
* Cleans up the remaining comment form output.
*
* @since 1.1.3
* @access public
*/
public function comment_form_sub_clean() {
echo GUI::clean_wrapper_end();
}
/**
* Replace preseved blocks
*
* @since 2.6
* @access public
*/
public function finalize( $buffer ) {
// Prepend combo esi block
if ( self::$_combine_ids ) {
Debug2::debug( '[ESI] 🍔 Enabled combo' );
$esi_block = $this->sub_esi_block( self::COMBO, '__COMBINE_MAIN__', array(), 'no-cache', true );
$buffer = $esi_block . $buffer;
}
// Bypass if no preserved list to be replaced
if ( ! $this->_esi_preserve_list ) {
return $buffer;
}
$keys = array_keys( $this->_esi_preserve_list );
Debug2::debug( '[ESI] replacing preserved blocks', $keys );
$buffer = str_replace( $keys, $this->_esi_preserve_list, $buffer );
return $buffer;
}
/**
* Check if the content contains preserved list or not
*
* @since 3.3
*/
public function contain_preserve_esi( $content ) {
$hit_list = array();
foreach ( $this->_esi_preserve_list as $k => $v ) {
if ( strpos( $content, '"' . $k . '"' ) !== false ) {
$hit_list[] = '"' . $k . '"';
}
if ( strpos( $content, "'" . $k . "'" ) !== false ) {
$hit_list[] = "'" . $k . "'";
}
}
return $hit_list;
}
}

View File

@@ -0,0 +1,441 @@
<?php
/**
* LiteSpeed File Operator Library Class
* Append/Replace content to a file
*
* @since 1.1.0
*/
namespace LiteSpeed;
defined( 'WPINC' ) || exit;
class File {
const MARKER = 'LiteSpeed Operator';
/**
* Detect if an URL is 404
*
* @since 3.3
*/
public static function is_404( $url ) {
$response = wp_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 ) {
$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 );
}
catch ( \ErrorException $ex ) {
return $silence ? false : sprintf( __( 'Can not create folder: %1$s. Error: %2$s', 'litespeed-cache' ), $folder, $ex->getMessage() );
}
restore_error_handler();
}
if ( ! file_exists( $filename ) ) {
if ( ! is_writable( $folder ) ) {
return $silence ? false : sprintf( __( 'Folder is not writable: %s.', 'litespeed-cache' ), $folder );
}
set_error_handler( 'litespeed_exception_handler' );
try {
touch( $filename );
}
catch ( \ErrorException $ex ){
return $silence ? false : sprintf( __( 'File %s is not writable.', 'litespeed-cache' ), $filename );
}
restore_error_handler();
}
elseif ( ! is_writable( $filename ) ) {
return $silence ? false : sprintf( __( 'File %s is not writable.', 'litespeed-cache' ), $filename );
}
$data = self::remove_zero_space( $data );
$ret = file_put_contents( $filename, $data, $append ? FILE_APPEND : LOCK_EX );
if ( $ret === false ) {
return $silence ? false : sprintf( __( 'Failed to write to %s.', 'litespeed-cache' ), $filename );
}
return true;
}
/**
* Remove Unicode zero-width space <200b><200c>
*
* @since 2.1.2
* @since 2.9 changed to public
*/
public static function remove_zero_space( $content ) {
if ( is_array( $content ) ) {
$content = array_map( __CLASS__ . '::remove_zero_space', $content );
return $content;
}
// Remove UTF-8 BOM if present
if ( substr( $content, 0, 3 ) === "\xEF\xBB\xBF" ) {
$content = substr( $content, 3 );
}
$content = str_replace( "\xe2\x80\x8b", '', $content );
$content = str_replace( "\xe2\x80\x8c", '', $content );
$content = str_replace( "\xe2\x80\x8d", '', $content );
return $content;
}
/**
* Appends an array of strings into a file (.htaccess ), placing it between
* BEGIN and END markers.
*
* Replaces existing marked info. Retains surrounding
* data. Creates file if none exists.
*
* @param string $filename Filename to alter.
* @param string $marker The marker to alter.
* @param array|string $insertion The new content to insert.
* @param bool $prepend Prepend insertion if not exist.
* @return bool True on write success, false on failure.
*/
public static function insert_with_markers($filename, $insertion = false, $marker = false, $prepend = false) {
if ( !$marker ) {
$marker = self::MARKER;
}
if ( !$insertion ) {
$insertion = array();
}
return self::_insert_with_markers($filename, $marker, $insertion, $prepend);//todo: capture exceptions
}
/**
* Return wrapped block data with marker
*
* @param string $insertion
* @param string $marker
* @return string The block data
*/
public static function wrap_marker_data($insertion, $marker = false) {
if ( ! $marker ) {
$marker = self::MARKER;
}
$start_marker = "# BEGIN {$marker}";
$end_marker = "# END {$marker}";
$new_data = implode( "\n", array_merge(
array( $start_marker ),
$insertion,
array( $end_marker )
) );
return $new_data;
}
/**
* Touch block data from file, return with marker
*
* @param string $filename
* @param string $marker
* @return string The current block data
*/
public static function touch_marker_data($filename, $marker = false) {
if( ! $marker ) {
$marker = self::MARKER;
}
$result = self::_extract_from_markers($filename, $marker);
if( ! $result ) {
return false;
}
$start_marker = "# BEGIN {$marker}";
$end_marker = "# END {$marker}";
$new_data = implode( "\n", array_merge(
array( $start_marker ),
$result,
array( $end_marker )
) );
return $new_data;
}
/**
* Extracts strings from between the BEGIN and END markers in the .htaccess file.
*
* @param string $filename
* @param string $marker
* @return array An array of strings from a file (.htaccess ) from between BEGIN and END markers.
*/
public static function extract_from_markers($filename, $marker = false) {
if( ! $marker ) {
$marker = self::MARKER;
}
return self::_extract_from_markers($filename, $marker);
}
/**
* Extracts strings from between the BEGIN and END markers in the .htaccess file.
*
* @param string $filename
* @param string $marker
* @return array An array of strings from a file (.htaccess ) from between BEGIN and END markers.
*/
private static function _extract_from_markers( $filename, $marker ) {
$result = array();
if (!file_exists($filename) ) {
return $result;
}
if ( $markerdata = explode( "\n", implode( '', file($filename) ) ) ) {
$state = false;
foreach ( $markerdata as $markerline ) {
if ( strpos($markerline, '# END ' . $marker) !== false ) {
$state = false;
}
if ( $state ) {
$result[] = $markerline;
}
if (strpos($markerline, '# BEGIN ' . $marker) !== false) {
$state = true;
}
}
}
return array_map('trim', $result);
}
/**
* Inserts an array of strings into a file (.htaccess ), placing it between BEGIN and END markers.
*
* Replaces existing marked info. Retains surrounding data. Creates file if none exists.
*
* NOTE: will throw error if failed
*
* @since 3.0-
* @since 3.0 Throw errors if failed
* @access private
*/
private static function _insert_with_markers( $filename, $marker, $insertion, $prepend = false) {
if ( ! file_exists( $filename ) ) {
if ( ! is_writable( dirname( $filename ) ) ) {
Error::t( 'W', dirname( $filename ) );
}
set_error_handler("litespeed_exception_handler");
try {
touch( $filename );
} catch ( \ErrorException $ex ) {
Error::t( 'W', $filename );
}
restore_error_handler();
}
elseif ( ! is_writable( $filename ) ) {
Error::t( 'W', $filename );
}
if ( ! is_array( $insertion ) ) {
$insertion = explode( "\n", $insertion );
}
$start_marker = "# BEGIN {$marker}";
$end_marker = "# END {$marker}";
$fp = fopen( $filename, 'r+' );
if ( ! $fp ) {
Error::t( 'W', $filename );
}
// Attempt to get a lock. If the filesystem supports locking, this will block until the lock is acquired.
flock( $fp, LOCK_EX );
$lines = array();
while ( ! feof($fp) ) {
$lines[] = rtrim(fgets($fp), "\r\n" );
}
// Split out the existing file into the preceding lines, and those that appear after the marker
$pre_lines = $post_lines = $existing_lines = array();
$found_marker = $found_end_marker = false;
foreach ( $lines as $line ) {
if ( ! $found_marker && false !== strpos($line, $start_marker) ) {
$found_marker = true;
continue;
}
elseif ( ! $found_end_marker && false !== strpos($line, $end_marker) ) {
$found_end_marker = true;
continue;
}
if ( ! $found_marker ) {
$pre_lines[] = $line;
}
elseif ( $found_marker && $found_end_marker ) {
$post_lines[] = $line;
}
else {
$existing_lines[] = $line;
}
}
// Check to see if there was a change
if ( $existing_lines === $insertion ) {
flock($fp, LOCK_UN);
fclose($fp);
return true;
}
// Check if need to prepend data if not exist
if( $prepend && ! $post_lines ) {
// Generate the new file data
$new_file_data = implode( "\n", array_merge(
array( $start_marker ),
$insertion,
array( $end_marker ),
$pre_lines
) );
}
else {
// Generate the new file data
$new_file_data = implode( "\n", array_merge(
$pre_lines,
array( $start_marker ),
$insertion,
array( $end_marker ),
$post_lines
) );
}
// Write to the start of the file, and truncate it to that length
fseek($fp, 0);
$bytes = fwrite($fp, $new_file_data);
if ( $bytes ) {
ftruncate($fp, ftell($fp));
}
fflush($fp);
flock($fp, LOCK_UN);
fclose($fp);
return (bool) $bytes;
}
}

View File

@@ -0,0 +1,906 @@
<?php
/**
* The frontend GUI class.
*
* @since 1.3
* @subpackage LiteSpeed/src
* @author LiteSpeed Technologies <info@litespeedtech.com>
*/
namespace LiteSpeed;
defined( 'WPINC' ) || exit;
class GUI extends Base {
private static $_clean_counter = 0;
private $_promo_true;
// [ file_tag => [ days, litespeed_only ], ... ]
private $_promo_list = array(
'new_version' => array( 7, false ),
'score' => array( 14, false ),
// 'slack' => array( 3, false ),
);
const LIB_GUEST_JS = 'assets/js/guest.min.js';
const LIB_GUEST_DOCREF_JS = 'assets/js/guest.docref.min.js';
const PHP_GUEST = 'guest.vary.php';
const TYPE_DISMISS_WHM = 'whm';
const TYPE_DISMISS_EXPIRESDEFAULT = 'ExpiresDefault';
const TYPE_DISMISS_PROMO = 'promo';
const TYPE_DISMISS_PIN = 'pin';
const WHM_MSG = 'lscwp_whm_install';
const WHM_MSG_VAL = 'whm_install';
protected $_summary;
/**
* Instance
*
* @since 1.3
*/
public function __construct() {
$this->_summary = self::get_summary();
}
/**
* Frontend Init
*
* @since 3.0
*/
public function init() {
Debug2::debug2( '[GUI] init' );
if ( is_admin_bar_showing() && current_user_can( 'manage_options' ) ) {
add_action( 'wp_enqueue_scripts', array( $this, 'frontend_enqueue_style' ) );
add_action( 'admin_bar_menu', array( $this, 'frontend_shortcut' ), 95 );
}
/**
* Turn on instant click
* @since 1.8.2
*/
if ( $this->conf( self::O_UTIL_INSTANT_CLICK ) ) {
add_action( 'wp_enqueue_scripts', array( $this, 'frontend_enqueue_style_public' ) );
}
// NOTE: this needs to be before optimizer to avoid wrapper being removed
add_filter( 'litespeed_buffer_finalize', array( $this, 'finalize' ), 8 );
}
/**
* Get the lscache stats
*
* @since 3.0
*/
public function lscache_stats() {
return false;
$stat_titles = array(
'PUB_CREATES' => __( 'Public Caches', 'litespeed-cache' ),
'PUB_HITS' => __( 'Public Cache Hits', 'litespeed-cache' ),
'PVT_CREATES' => __( 'Private Caches', 'litespeed-cache' ),
'PVT_HITS' => __( 'Private Cache Hits', 'litespeed-cache' ),
);
// Build the readable format
$data = array();
foreach ( $stat_titles as $k => $v ) {
if ( array_key_exists( $k, $stats ) ) {
$data[ $v ] = number_format( $stats[ $k ] );
}
}
return $data;
}
/**
* Display a pie
*
* @since 1.6.6
*/
public static function pie( $percent, $width = 50, $finished_tick = false, $without_percentage = false, $append_cls = false ) {
$percentage = '<text x="50%" y="50%">' . $percent . ( $without_percentage ? '' : '%' ) . '</text>';
if ( $percent == 100 && $finished_tick ) {
$percentage = '<text x="50%" y="50%" class="litespeed-pie-done">&#x2713</text>';
}
return "
<svg class='litespeed-pie $append_cls' viewbox='0 0 33.83098862 33.83098862' width='$width' height='$width' xmlns='http://www.w3.org/2000/svg'>
<circle class='litespeed-pie_bg' cx='16.91549431' cy='16.91549431' r='15.91549431' />
<circle class='litespeed-pie_circle' cx='16.91549431' cy='16.91549431' r='15.91549431' stroke-dasharray='$percent,100' />
<g class='litespeed-pie_info'>$percentage</g>
</svg>
";
}
/**
* Display a tiny pie with a tooltip
*
* @since 3.0
*/
public static function pie_tiny( $percent, $width = 50, $tooltip = '', $tooltip_pos = 'up', $append_cls = false ) {
// formula C = 2πR
$dasharray = 2 * 3.1416 * 9 * ( $percent / 100 );
return "
<button type='button' data-balloon-break data-balloon-pos='$tooltip_pos' aria-label='$tooltip' class='litespeed-btn-pie'>
<svg class='litespeed-pie litespeed-pie-tiny $append_cls' viewbox='0 0 30 30' width='$width' height='$width' xmlns='http://www.w3.org/2000/svg'>
<circle class='litespeed-pie_bg' cx='15' cy='15' r='9' />
<circle class='litespeed-pie_circle' cx='15' cy='15' r='9' stroke-dasharray='$dasharray,100' />
<g class='litespeed-pie_info'><text x='50%' y='50%'>i</text></g>
</svg>
</button>
";
}
/**
* Get classname of PageSpeed Score
*
* Scale:
* 90-100 (fast)
* 50-89 (average)
* 0-49 (slow)
*
* @since 2.9
* @access public
*/
public function get_cls_of_pagescore( $score ) {
if ( $score >= 90 ) {
return 'success';
}
if ( $score >= 50 ) {
return 'warning';
}
return 'danger';
}
/**
* Dismiss banner
*
* @since 1.0
* @access public
*/
public static function dismiss() {
$_instance = self::cls();
switch ( Router::verify_type() ) {
case self::TYPE_DISMISS_WHM :
self::dismiss_whm();
break;
case self::TYPE_DISMISS_EXPIRESDEFAULT :
self::update_option( Admin_Display::DB_DISMISS_MSG, Admin_Display::RULECONFLICT_DISMISSED );
break;
case self::TYPE_DISMISS_PIN:
admin_display::dismiss_pin();
break;
case self::TYPE_DISMISS_PROMO:
if ( empty( $_GET[ 'promo_tag' ] ) ) {
break;
}
$promo_tag = sanitize_key( $_GET[ 'promo_tag' ] );
if ( empty( $_instance->_promo_list[ $promo_tag ] ) ) {
break;
}
defined( 'LSCWP_LOG' ) && Debug2::debug( '[GUI] Dismiss promo ' . $promo_tag );
// Forever dismiss
if ( ! empty( $_GET[ 'done' ] ) ) {
$_instance->_summary[ $promo_tag ] = 'done';
}
elseif ( ! empty( $_GET[ 'later' ] ) ) {
// Delay the banner to half year later
$_instance->_summary[ $promo_tag ] = time() + 86400 * 180;
}
else {
// Update welcome banner to 30 days after
$_instance->_summary[ $promo_tag ] = time() + 86400 * 30;
}
self::save_summary();
break;
default:
break;
}
if ( Router::is_ajax() ) {
// All dismiss actions are considered as ajax call, so just exit
exit( json_encode( array( 'success' => 1 ) ) );
}
// Plain click link, redirect to referral url
Admin::redirect();
}
/**
* Check if has rule conflict notice
*
* @since 1.1.5
* @access public
* @return boolean
*/
public static function has_msg_ruleconflict() {
$db_dismiss_msg = self::get_option( Admin_Display::DB_DISMISS_MSG );
if ( ! $db_dismiss_msg ) {
self::update_option( Admin_Display::DB_DISMISS_MSG, -1 );
}
return $db_dismiss_msg == Admin_Display::RULECONFLICT_ON;
}
/**
* Check if has whm notice
*
* @since 1.1.1
* @access public
* @return boolean
*/
public static function has_whm_msg() {
$val = self::get_option( self::WHM_MSG );
if ( ! $val ) {
self::dismiss_whm();
return false;
}
return $val == self::WHM_MSG_VAL;
}
/**
* Delete whm msg tag
*
* @since 1.1.1
* @access public
*/
public static function dismiss_whm() {
self::update_option( self::WHM_MSG, -1 );
}
/**
* Set current page a litespeed page
*
* @since 2.9
*/
private function _is_litespeed_page() {
if ( ! empty( $_GET[ 'page' ] ) && in_array( $_GET[ 'page' ],
array(
'litespeed-settings',
'litespeed-dash',
Admin::PAGE_EDIT_HTACCESS,
'litespeed-optimization',
'litespeed-crawler',
'litespeed-import',
'litespeed-report',
) )
) {
return true;
}
return false;
}
/**
* Display promo banner
*
* @since 2.1
* @access public
*/
public function show_promo( $check_only = false ) {
$is_litespeed_page = $this->_is_litespeed_page();
// Bypass showing info banner if disabled all in debug
if ( defined( 'LITESPEED_DISABLE_ALL' ) ) {
if ( $is_litespeed_page && ! $check_only ) {
include_once LSCWP_DIR . "tpl/inc/disabled_all.php";
}
return false;
}
if ( file_exists( ABSPATH . '.litespeed_no_banner' ) ) {
defined( 'LSCWP_LOG' ) && Debug2::debug( '[GUI] Bypass banners due to silence file' );
return false;
}
foreach ( $this->_promo_list as $promo_tag => $v ) {
list( $delay_days, $litespeed_page_only ) = $v;
if ( $litespeed_page_only && ! $is_litespeed_page ) {
continue;
}
// first time check
if ( empty( $this->_summary[ $promo_tag ] ) ) {
$this->_summary[ $promo_tag ] = time() + 86400 * $delay_days;
self::save_summary();
continue;
}
$promo_timestamp = $this->_summary[ $promo_tag ];
// was ticked as done
if ( $promo_timestamp == 'done' ) {
continue;
}
// Not reach the dateline yet
if ( time() < $promo_timestamp ) {
continue;
}
// try to load, if can pass, will set $this->_promo_true = true
$this->_promo_true = false;
include LSCWP_DIR . "tpl/banner/$promo_tag.php";
// If not defined, means it didn't pass the display workflow in tpl.
if ( ! $this->_promo_true ) {
continue;
}
if ( $check_only ) {
return $promo_tag;
}
defined( 'LSCWP_LOG' ) && Debug2::debug( '[GUI] Show promo ' . $promo_tag );
// Only contain one
break;
}
return false;
}
/**
* Load frontend public script
*
* @since 1.8.2
* @access public
*/
public function frontend_enqueue_style_public() {
wp_enqueue_script( Core::PLUGIN_NAME, LSWCP_PLUGIN_URL . 'assets/js/instant_click.min.js', array(), Core::VER, true );
}
/**
* Load frontend menu shortcut
*
* @since 1.3
* @access public
*/
public function frontend_enqueue_style() {
wp_enqueue_style( Core::PLUGIN_NAME, LSWCP_PLUGIN_URL . 'assets/css/litespeed.css', array(), Core::VER, 'all' );
}
/**
* Load frontend menu shortcut
*
* @since 1.3
* @access public
*/
public function frontend_shortcut() {
global $wp_admin_bar;
$wp_admin_bar->add_menu( array(
'id' => 'litespeed-menu',
'title' => '<span class="ab-icon"></span>',
'href' => get_admin_url( null, 'admin.php?page=litespeed' ),
'meta' => array( 'tabindex' => 0, 'class' => 'litespeed-top-toolbar' ),
) );
$wp_admin_bar->add_menu( array(
'parent' => 'litespeed-menu',
'id' => 'litespeed-purge-single',
'title' => __( 'Purge this page', 'litespeed-cache' ),
'href' => Utility::build_url( Router::ACTION_PURGE, Purge::TYPE_PURGE_FRONT, false, true ),
'meta' => array( 'tabindex' => '0' ),
) );
$wp_admin_bar->add_menu( array(
'parent' => 'litespeed-menu',
'id' => 'litespeed-single-action',
'title' => __( 'Mark this page as ', 'litespeed-cache' ),
'meta' => array( 'tabindex' => '0' ),
) );
if ( ! empty( $_SERVER[ 'REQUEST_URI' ] ) ) {
$append_arr = array(
Conf::TYPE_SET . '[' . self::O_CACHE_FORCE_URI . '][]' => $_SERVER[ 'REQUEST_URI' ] . '$',
'redirect' => $_SERVER[ 'REQUEST_URI' ],
);
$wp_admin_bar->add_menu( array(
'parent' => 'litespeed-single-action',
'id' => 'litespeed-single-forced_cache',
'title' => __( 'Forced cacheable', 'litespeed-cache' ),
'href' => Utility::build_url( Router::ACTION_CONF, Conf::TYPE_SET, false, true, $append_arr ),
) );
$append_arr = array(
Conf::TYPE_SET . '[' . self::O_CACHE_EXC . '][]' => $_SERVER[ 'REQUEST_URI' ] . '$',
'redirect' => $_SERVER[ 'REQUEST_URI' ],
);
$wp_admin_bar->add_menu( array(
'parent' => 'litespeed-single-action',
'id' => 'litespeed-single-noncache',
'title' => __( 'Non cacheable', 'litespeed-cache' ),
'href' => Utility::build_url( Router::ACTION_CONF, Conf::TYPE_SET, false, true, $append_arr ),
) );
$append_arr = array(
Conf::TYPE_SET . '[' . self::O_CACHE_PRIV_URI . '][]' => $_SERVER[ 'REQUEST_URI' ] . '$',
'redirect' => $_SERVER[ 'REQUEST_URI' ],
);
$wp_admin_bar->add_menu( array(
'parent' => 'litespeed-single-action',
'id' => 'litespeed-single-private',
'title' => __( 'Private cache', 'litespeed-cache' ),
'href' => Utility::build_url( Router::ACTION_CONF, Conf::TYPE_SET, false, true, $append_arr ),
) );
$append_arr = array(
Conf::TYPE_SET . '[' . self::O_OPTM_EXC . '][]' => $_SERVER[ 'REQUEST_URI' ] . '$',
'redirect' => $_SERVER[ 'REQUEST_URI' ],
);
$wp_admin_bar->add_menu( array(
'parent' => 'litespeed-single-action',
'id' => 'litespeed-single-nonoptimize',
'title' => __( 'No optimization', 'litespeed-cache' ),
'href' => Utility::build_url( Router::ACTION_CONF, Conf::TYPE_SET, false, true, $append_arr ),
) );
}
$wp_admin_bar->add_menu( array(
'parent' => 'litespeed-single-action',
'id' => 'litespeed-single-more',
'title' => __( 'More settings', 'litespeed-cache' ),
'href' => get_admin_url( null, 'admin.php?page=litespeed-cache' ),
) );
$wp_admin_bar->add_menu( array(
'parent' => 'litespeed-menu',
'id' => 'litespeed-purge-all',
'title' => __( 'Purge All', 'litespeed-cache' ),
'href' => Utility::build_url( Router::ACTION_PURGE, Purge::TYPE_PURGE_ALL, false, '_ori' ),
'meta' => array( 'tabindex' => '0' ),
) );
$wp_admin_bar->add_menu( array(
'parent' => 'litespeed-menu',
'id' => 'litespeed-purge-all-lscache',
'title' => __( 'Purge All', 'litespeed-cache' ) . ' - ' . __( 'LSCache', 'litespeed-cache' ),
'href' => Utility::build_url( Router::ACTION_PURGE, Purge::TYPE_PURGE_ALL_LSCACHE, false, '_ori' ),
'meta' => array( 'tabindex' => '0' ),
) );
$wp_admin_bar->add_menu( array(
'parent' => 'litespeed-menu',
'id' => 'litespeed-purge-cssjs',
'title' => __( 'Purge All', 'litespeed-cache' ) . ' - ' . __( 'CSS/JS Cache', 'litespeed-cache' ),
'href' => Utility::build_url( Router::ACTION_PURGE, Purge::TYPE_PURGE_ALL_CSSJS, false, '_ori' ),
'meta' => array( 'tabindex' => '0' ),
) );
if ( defined( 'LSCWP_OBJECT_CACHE' ) ) {
$wp_admin_bar->add_menu( array(
'parent' => 'litespeed-menu',
'id' => 'litespeed-purge-object',
'title' => __( 'Purge All', 'litespeed-cache' ) . ' - ' . __( 'Object Cache', 'litespeed-cache' ),
'href' => Utility::build_url( Router::ACTION_PURGE, Purge::TYPE_PURGE_ALL_OBJECT, false, '_ori' ),
'meta' => array( 'tabindex' => '0' ),
) );
}
if ( Router::opcache_enabled() ) {
$wp_admin_bar->add_menu( array(
'parent' => 'litespeed-menu',
'id' => 'litespeed-purge-opcache',
'title' => __( 'Purge All', 'litespeed-cache' ) . ' - ' . __( 'Opcode Cache', 'litespeed-cache' ),
'href' => Utility::build_url( Router::ACTION_PURGE, Purge::TYPE_PURGE_ALL_OPCACHE, false, '_ori' ),
'meta' => array( 'tabindex' => '0' ),
) );
}
if ( $this->has_cache_folder( 'ccss' ) ) {
$wp_admin_bar->add_menu( array(
'parent' => 'litespeed-menu',
'id' => 'litespeed-purge-ccss',
'title' => __( 'Purge All', 'litespeed-cache' ) . ' - CCSS',
'href' => Utility::build_url( Router::ACTION_PURGE, Purge::TYPE_PURGE_ALL_CCSS, false, '_ori' ),
'meta' => array( 'tabindex' => '0' ),
) );
}
if ( $this->has_cache_folder( 'ucss' ) ) {
$wp_admin_bar->add_menu( array(
'parent' => 'litespeed-menu',
'id' => 'litespeed-purge-ucss',
'title' => __( 'Purge All', 'litespeed-cache' ) . ' - UCSS',
'href' => Utility::build_url( Router::ACTION_PURGE, Purge::TYPE_PURGE_ALL_UCSS, false, '_ori' ),
) );
}
if ( $this->has_cache_folder( 'localres' ) ) {
$wp_admin_bar->add_menu( array(
'parent' => 'litespeed-menu',
'id' => 'litespeed-purge-localres',
'title' => __( 'Purge All', 'litespeed-cache' ) . ' - ' . __( 'Localized Resources', 'litespeed-cache' ),
'href' => Utility::build_url( Router::ACTION_PURGE, Purge::TYPE_PURGE_ALL_LOCALRES, false, '_ori' ),
'meta' => array( 'tabindex' => '0' ),
) );
}
if ( $this->has_cache_folder( 'lqip' ) ) {
$wp_admin_bar->add_menu( array(
'parent' => 'litespeed-menu',
'id' => 'litespeed-purge-placeholder',
'title' => __( 'Purge All', 'litespeed-cache' ) . ' - ' . __( 'LQIP Cache', 'litespeed-cache' ),
'href' => Utility::build_url( Router::ACTION_PURGE, Purge::TYPE_PURGE_ALL_LQIP, false, '_ori' ),
'meta' => array( 'tabindex' => '0' ),
) );
}
if ( $this->has_cache_folder( 'avatar' ) ) {
$wp_admin_bar->add_menu( array(
'parent' => 'litespeed-menu',
'id' => 'litespeed-purge-avatar',
'title' => __( 'Purge All', 'litespeed-cache' ) . ' - ' . __( 'Gravatar Cache', 'litespeed-cache' ),
'href' => Utility::build_url( Router::ACTION_PURGE, Purge::TYPE_PURGE_ALL_AVATAR, false, '_ori' ),
'meta' => array( 'tabindex' => '0' ),
) );
}
do_action( 'litespeed_frontend_shortcut' );
}
/**
* Hooked to wp_before_admin_bar_render.
* Adds a link to the admin bar so users can quickly purge all.
*
* @access public
* @global WP_Admin_Bar $wp_admin_bar
* @since 1.7.2 Moved from admin_display.cls to gui.cls; Renamed from `add_quick_purge` to `backend_shortcut`
*/
public function backend_shortcut() {
global $wp_admin_bar;
// if ( defined( 'LITESPEED_ON' ) ) {
$wp_admin_bar->add_menu( array(
'id' => 'litespeed-menu',
'title' => '<span class="ab-icon" title="' . __( 'LiteSpeed Cache Purge All', 'litespeed-cache' ) . ' - ' . __( 'LSCache', 'litespeed-cache' ) . '"></span>',
'href' => Utility::build_url( Router::ACTION_PURGE, Purge::TYPE_PURGE_ALL_LSCACHE ),
'meta' => array( 'tabindex' => 0, 'class' => 'litespeed-top-toolbar' ),
) );
// }
// else {
// $wp_admin_bar->add_menu( array(
// 'id' => 'litespeed-menu',
// 'title' => '<span class="ab-icon" title="' . __( 'LiteSpeed Cache', 'litespeed-cache' ) . '"></span>',
// 'meta' => array( 'tabindex' => 0, 'class' => 'litespeed-top-toolbar' ),
// ) );
// }
$wp_admin_bar->add_menu( array(
'parent' => 'litespeed-menu',
'id' => 'litespeed-bar-manage',
'title' => __( 'Manage', 'litespeed-cache' ),
'href' => 'admin.php?page=litespeed',
'meta' => array( 'tabindex' => '0' ),
) );
$wp_admin_bar->add_menu( array(
'parent' => 'litespeed-menu',
'id' => 'litespeed-bar-setting',
'title' => __( 'Settings', 'litespeed-cache' ),
'href' => 'admin.php?page=litespeed-cache',
'meta' => array( 'tabindex' => '0' ),
) );
if ( ! is_network_admin() ) {
$wp_admin_bar->add_menu( array(
'parent' => 'litespeed-menu',
'id' => 'litespeed-bar-imgoptm',
'title' => __( 'Image Optimization', 'litespeed-cache' ),
'href' => 'admin.php?page=litespeed-img_optm',
'meta' => array( 'tabindex' => '0' ),
) );
}
$wp_admin_bar->add_menu( array(
'parent' => 'litespeed-menu',
'id' => 'litespeed-purge-all',
'title' => __( 'Purge All', 'litespeed-cache' ),
'href' => Utility::build_url( Router::ACTION_PURGE, Purge::TYPE_PURGE_ALL ),
'meta' => array( 'tabindex' => '0' ),
) );
$wp_admin_bar->add_menu( array(
'parent' => 'litespeed-menu',
'id' => 'litespeed-purge-all-lscache',
'title' => __( 'Purge All', 'litespeed-cache' ) . ' - ' . __( 'LSCache', 'litespeed-cache' ),
'href' => Utility::build_url( Router::ACTION_PURGE, Purge::TYPE_PURGE_ALL_LSCACHE ),
'meta' => array( 'tabindex' => '0' ),
) );
$wp_admin_bar->add_menu( array(
'parent' => 'litespeed-menu',
'id' => 'litespeed-purge-cssjs',
'title' => __( 'Purge All', 'litespeed-cache' ) . ' - ' . __( 'CSS/JS Cache', 'litespeed-cache' ),
'href' => Utility::build_url( Router::ACTION_PURGE, Purge::TYPE_PURGE_ALL_CSSJS ),
'meta' => array( 'tabindex' => '0' ),
) );
if ( $this->conf( self::O_CDN_CLOUDFLARE ) ) {
$wp_admin_bar->add_menu( array(
'parent' => 'litespeed-menu',
'id' => 'litespeed-purge-cloudflare',
'title' => __( 'Purge All', 'litespeed-cache' ) . ' - ' . __( 'Cloudflare', 'litespeed-cache' ),
'href' => Utility::build_url( Router::ACTION_CDN_CLOUDFLARE, CDN\Cloudflare::TYPE_PURGE_ALL ),
'meta' => array( 'tabindex' => '0' ),
) );
}
if ( defined( 'LSCWP_OBJECT_CACHE' ) ) {
$wp_admin_bar->add_menu( array(
'parent' => 'litespeed-menu',
'id' => 'litespeed-purge-object',
'title' => __( 'Purge All', 'litespeed-cache' ) . ' - ' . __( 'Object Cache', 'litespeed-cache' ),
'href' => Utility::build_url( Router::ACTION_PURGE, Purge::TYPE_PURGE_ALL_OBJECT ),
'meta' => array( 'tabindex' => '0' ),
) );
}
if ( Router::opcache_enabled() ) {
$wp_admin_bar->add_menu( array(
'parent' => 'litespeed-menu',
'id' => 'litespeed-purge-opcache',
'title' => __( 'Purge All', 'litespeed-cache' ) . ' - ' . __( 'Opcode Cache', 'litespeed-cache' ),
'href' => Utility::build_url( Router::ACTION_PURGE, Purge::TYPE_PURGE_ALL_OPCACHE ),
'meta' => array( 'tabindex' => '0' ),
) );
}
if ( $this->has_cache_folder( 'ccss' ) ) {
$wp_admin_bar->add_menu( array(
'parent' => 'litespeed-menu',
'id' => 'litespeed-purge-ccss',
'title' => __( 'Purge All', 'litespeed-cache' ) . ' - CCSS',
'href' => Utility::build_url( Router::ACTION_PURGE, Purge::TYPE_PURGE_ALL_CCSS ),
'meta' => array( 'tabindex' => '0' ),
) );
}
if ( $this->has_cache_folder( 'ucss' ) ) {
$wp_admin_bar->add_menu( array(
'parent' => 'litespeed-menu',
'id' => 'litespeed-purge-ucss',
'title' => __( 'Purge All', 'litespeed-cache' ) . ' - UCSS',
'href' => Utility::build_url( Router::ACTION_PURGE, Purge::TYPE_PURGE_ALL_UCSS ),
) );
}
if ( $this->has_cache_folder( 'localres' ) ) {
$wp_admin_bar->add_menu( array(
'parent' => 'litespeed-menu',
'id' => 'litespeed-purge-localres',
'title' => __( 'Purge All', 'litespeed-cache' ) . ' - ' . __( 'Localized Resources', 'litespeed-cache' ),
'href' => Utility::build_url( Router::ACTION_PURGE, Purge::TYPE_PURGE_ALL_LOCALRES ),
'meta' => array( 'tabindex' => '0' ),
) );
}
if ( $this->has_cache_folder( 'lqip' ) ) {
$wp_admin_bar->add_menu( array(
'parent' => 'litespeed-menu',
'id' => 'litespeed-purge-placeholder',
'title' => __( 'Purge All', 'litespeed-cache' ) . ' - ' . __( 'LQIP Cache', 'litespeed-cache' ),
'href' => Utility::build_url( Router::ACTION_PURGE, Purge::TYPE_PURGE_ALL_LQIP ),
'meta' => array( 'tabindex' => '0' ),
) );
}
if ( $this->has_cache_folder( 'avatar' ) ) {
$wp_admin_bar->add_menu( array(
'parent' => 'litespeed-menu',
'id' => 'litespeed-purge-avatar',
'title' => __( 'Purge All', 'litespeed-cache' ) . ' - ' . __( 'Gravatar Cache', 'litespeed-cache' ),
'href' => Utility::build_url( Router::ACTION_PURGE, Purge::TYPE_PURGE_ALL_AVATAR ),
'meta' => array( 'tabindex' => '0' ),
) );
}
do_action( 'litespeed_backend_shortcut' );
}
/**
* Clear unfinished data
*
* @since 2.4.2
* @access public
*/
public static function img_optm_clean_up( $unfinished_num ) {
return sprintf(
'<a href="%1$s" class="button litespeed-btn-warning" data-balloon-pos="up" aria-label="%2$s"><span class="dashicons dashicons-editor-removeformatting"></span>&nbsp;%3$s</a>',
Utility::build_url( Router::ACTION_IMG_OPTM, Img_Optm::TYPE_CLEAN ),
__( 'Remove all previous unfinished image optimization requests.', 'litespeed-cache' ),
__( 'Clean Up Unfinished Data', 'litespeed-cache' ) . ( $unfinished_num ? ': ' . Admin_Display::print_plural( $unfinished_num, 'image' ) : '')
);
}
/**
* Generate install link
*
* @since 2.4.2
* @access public
*/
public static function plugin_install_link( $title, $name, $v ) {
$url = wp_nonce_url(self_admin_url('update.php?action=install-plugin&plugin=' . $name ), 'install-plugin_' . $name );
$action = sprintf(
'<a href="%1$s" class="install-now" data-slug="%2$s" data-name="%3$s" aria-label="%4$s">%5$s</a>',
esc_url( $url ),
esc_attr( $name ),
esc_attr( $title ),
esc_attr( sprintf( __( 'Install %s', 'litespeed-cache' ), $title ) ),
__( 'Install Now', 'litespeed-cache' )
);
return $action;
// $msg .= " <a href='$upgrade_link' class='litespeed-btn-success' target='_blank'>" . __( 'Click here to upgrade', 'litespeed-cache' ) . '</a>';
}
/**
* Generate upgrade link
*
* @since 2.4.2
* @access public
*/
public static function plugin_upgrade_link( $title, $name, $v ) {
$details_url = self_admin_url( 'plugin-install.php?tab=plugin-information&plugin=' . $name . '&section=changelog&TB_iframe=true&width=600&height=800' );
$file = $name . '/' . $name . '.php';
$msg = sprintf( __( '<a href="%1$s" %2$s>View version %3$s details</a> or <a href="%4$s" %5$s target="_blank">update now</a>.', 'litespeed-cache' ),
esc_url( $details_url ),
sprintf( 'class="thickbox open-plugin-details-modal" aria-label="%s"',
esc_attr( sprintf( __( 'View %1$s version %2$s details', 'litespeed-cache' ), $title, $v ) )
),
$v,
wp_nonce_url( self_admin_url( 'update.php?action=upgrade-plugin&plugin=' ) . $file, 'upgrade-plugin_' . $file ),
sprintf( 'class="update-link" aria-label="%s"',
esc_attr( sprintf( __( 'Update %s now', 'litespeed-cache' ), $title ) )
)
);
return $msg;
}
/**
* Finalize buffer by GUI class
*
* @since 1.6
* @access public
*/
public function finalize( $buffer ) {
$buffer = $this->_clean_wrapper( $buffer );
// Maybe restore doc.ref
if ( $this->conf( Base::O_GUEST ) && strpos( $buffer, '<head>' ) !== false && defined( 'LITESPEED_IS_HTML' ) ) {
$buffer = $this->_enqueue_guest_docref_js( $buffer );
}
if ( defined( 'LITESPEED_GUEST' ) && LITESPEED_GUEST && strpos( $buffer, '</body>' ) !== false && defined( 'LITESPEED_IS_HTML' ) ) {
$buffer = $this->_enqueue_guest_js( $buffer );
}
return $buffer;
}
/**
* Append guest restore doc.ref JS for organic traffic count
*
* @since 4.4.6
*/
private function _enqueue_guest_docref_js( $buffer ) {
$js_con = File::read( LSCWP_DIR . self::LIB_GUEST_DOCREF_JS );
$buffer = preg_replace( '/<head>/', '<head><script data-no-optimize="1">' . $js_con . '</script>', $buffer, 1 );
return $buffer;
}
/**
* Append guest JS to update vary
*
* @since 4.0
*/
private function _enqueue_guest_js( $buffer ) {
$js_con = File::read( LSCWP_DIR . self::LIB_GUEST_JS );
// $guest_update_url = add_query_arg( 'litespeed_guest', 1, home_url( '/' ) );
$guest_update_url = parse_url( LSWCP_PLUGIN_URL . self::PHP_GUEST, PHP_URL_PATH );
$js_con = str_replace( 'litespeed_url', esc_url( $guest_update_url ), $js_con );
$buffer = preg_replace( '/<\/body>/', '<script data-no-optimize="1">' . $js_con . '</script></body>', $buffer, 1 );
return $buffer;
}
/**
* Clean wrapper from buffer
*
* @since 1.4
* @since 1.6 converted to private with adding prefix _
* @access private
*/
private function _clean_wrapper( $buffer ) {
if ( self::$_clean_counter < 1 ) {
Debug2::debug2( "GUI bypassed by no counter" );
return $buffer;
}
Debug2::debug2( "GUI start cleaning counter " . self::$_clean_counter );
for ( $i = 1; $i <= self::$_clean_counter; $i ++ ) {
// If miss beginning
$start = strpos( $buffer, self::clean_wrapper_begin( $i ) );
if ( $start === false ) {
$buffer = str_replace( self::clean_wrapper_end( $i ), '', $buffer );
Debug2::debug2( "GUI lost beginning wrapper $i" );
continue;
}
// If miss end
$end_wrapper = self::clean_wrapper_end( $i );
$end = strpos( $buffer, $end_wrapper );
if ( $end === false ) {
$buffer = str_replace( self::clean_wrapper_begin( $i ), '', $buffer );
Debug2::debug2( "GUI lost ending wrapper $i" );
continue;
}
// Now replace wrapped content
$buffer = substr_replace( $buffer, '', $start, $end - $start + strlen( $end_wrapper ) );
Debug2::debug2( "GUI cleaned wrapper $i" );
}
return $buffer;
}
/**
* Display a to-be-removed html wrapper
*
* @since 1.4
* @access public
*/
public static function clean_wrapper_begin( $counter = false ) {
if ( $counter === false ) {
self::$_clean_counter ++;
$counter = self::$_clean_counter;
Debug2::debug( "GUI clean wrapper $counter begin" );
}
return '<!-- LiteSpeed To Be Removed begin ' . $counter . ' -->';
}
/**
* Display a to-be-removed html wrapper
*
* @since 1.4
* @access public
*/
public static function clean_wrapper_end( $counter = false ) {
if ( $counter === false ) {
$counter = self::$_clean_counter;
Debug2::debug( "GUI clean wrapper $counter end" );
}
return '<!-- LiteSpeed To Be Removed end ' . $counter . ' -->';
}
}

View File

@@ -0,0 +1,134 @@
<?php
/**
* The page health
*
*
* @since 3.0
* @package LiteSpeed
* @subpackage LiteSpeed/src
* @author LiteSpeed Technologies <info@litespeedtech.com>
*/
namespace LiteSpeed;
defined( 'WPINC' ) || exit;
class Health extends Base {
const TYPE_SPEED = 'speed';
const TYPE_SCORE = 'score';
protected $_summary;
/**
* Init
*
* @since 3.0
*/
public function __construct() {
$this->_summary = self::get_summary();
}
/**
* Test latest speed
*
* @since 3.0
*/
private function _ping( $type )
{
$data = array( 'action' => $type );
$json = Cloud::post( Cloud::SVC_HEALTH, $data, 600 );
if ( empty( $json[ 'data' ][ 'before' ] ) || empty( $json[ 'data' ][ 'after' ] ) ) {
Debug2::debug( '[Health] ❌ no data' );
return false;
}
$this->_summary[ $type . '.before' ] = $json[ 'data' ][ 'before' ];
$this->_summary[ $type . '.after' ] = $json[ 'data' ][ 'after' ];
self::save_summary();
Debug2::debug( '[Health] saved result' );
}
/**
* Generate scores
*
* @since 3.0
*/
public function scores()
{
$speed_before = $speed_after = $speed_improved = 0;
if ( ! empty( $this->_summary[ 'speed.before' ] ) && ! empty( $this->_summary[ 'speed.after' ] ) ) {
// Format loading time
$speed_before = $this->_summary[ 'speed.before' ] / 1000;
if ( $speed_before < 0.01 ) {
$speed_before = 0.01;
}
$speed_before = number_format( $speed_before, 2 );
$speed_after = $this->_summary[ 'speed.after' ] / 1000;
if ( $speed_after < 0.01 ) {
$speed_after = number_format( $speed_after, 3 );
}
else {
$speed_after = number_format( $speed_after, 2 );
}
$speed_improved = ( $this->_summary[ 'speed.before' ] - $this->_summary[ 'speed.after' ] ) * 100 / $this->_summary[ 'speed.before' ];
if ( $speed_improved > 99 ) {
$speed_improved = number_format( $speed_improved, 2 );
}
else {
$speed_improved = number_format( $speed_improved );
}
}
$score_before = $score_after = $score_improved = 0;
if ( ! empty( $this->_summary[ 'score.before' ] ) && ! empty( $this->_summary[ 'score.after' ] ) ) {
$score_before = $this->_summary[ 'score.before' ];
$score_after = $this->_summary[ 'score.after' ];
// Format Score
$score_improved = ( $score_after - $score_before ) * 100 / $score_after;
if ( $score_improved > 99 ) {
$score_improved = number_format( $score_improved, 2 );
}
else {
$score_improved = number_format( $score_improved );
}
}
return array(
'speed_before' => $speed_before,
'speed_after' => $speed_after,
'speed_improved' => $speed_improved,
'score_before' => $score_before,
'score_after' => $score_after,
'score_improved' => $score_improved,
);
}
/**
* Handle all request actions from main cls
*
* @since 3.0
* @access public
*/
public function handler() {
$type = Router::verify_type();
switch ( $type ) {
case self::TYPE_SPEED:
case self::TYPE_SCORE:
$this->_ping( $type );
break;
default:
break;
}
Admin::redirect();
}
}

View File

@@ -0,0 +1,832 @@
<?php
/**
* The htaccess rewrite rule operation class
*
*
* @since 1.0.0
* @package LiteSpeed
* @subpackage LiteSpeed/inc
* @author LiteSpeed Technologies <info@litespeedtech.com>
*/
namespace LiteSpeed;
defined( 'WPINC' ) || exit;
class Htaccess extends Root {
private $frontend_htaccess = null;
private $_default_frontend_htaccess = null;
private $backend_htaccess = null;
private $_default_backend_htaccess = null;
private $theme_htaccess = null;// Not used yet
private $frontend_htaccess_readable = false;
private $frontend_htaccess_writable = false;
private $backend_htaccess_readable = false;
private $backend_htaccess_writable = false;
private $theme_htaccess_readable = false;
private $theme_htaccess_writable = false;
private $__rewrite_on;
const LS_MODULE_START = '<IfModule LiteSpeed>';
const EXPIRES_MODULE_START = '<IfModule mod_expires.c>';
const LS_MODULE_END = '</IfModule>';
const LS_MODULE_REWRITE_START = '<IfModule mod_rewrite.c>';
const REWRITE_ON = 'RewriteEngine on';
const LS_MODULE_DONOTEDIT = "## LITESPEED WP CACHE PLUGIN - Do not edit the contents of this block! ##";
const MARKER = 'LSCACHE';
const MARKER_NONLS = 'NON_LSCACHE';
const MARKER_LOGIN_COOKIE = '### marker LOGIN COOKIE';
const MARKER_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_FAVICON = '### marker FAVICON';
const MARKER_BROWSER_CACHE = '### marker BROWSER CACHE';
const MARKER_MINIFY = '### marker MINIFY';
const MARKER_CORS = '### marker CORS';
const MARKER_WEBP = '### marker WEBP';
const MARKER_DROPQS = '### marker DROPQS';
const MARKER_START = ' start ###';
const MARKER_END = ' end ###';
const RW_PATTERN_RES = '/.*/[^/]*(responsive|css|js|dynamic|loader|fonts)\.php';
/**
* Initialize the class and set its properties.
*
* @since 1.0.7
*/
public function __construct() {
$this->_path_set();
$this->_default_frontend_htaccess = $this->frontend_htaccess;
$this->_default_backend_htaccess = $this->backend_htaccess;
$frontend_htaccess = defined( 'LITESPEED_CFG_HTACCESS' ) ? LITESPEED_CFG_HTACCESS : false;
if ( $frontend_htaccess && substr( $frontend_htaccess, -10 ) === '/.htaccess' ) {
$this->frontend_htaccess = $frontend_htaccess;
}
$backend_htaccess = defined( 'LITESPEED_CFG_HTACCESS_BACKEND' ) ? LITESPEED_CFG_HTACCESS_BACKEND : false;
if ( $backend_htaccess && substr( $backend_htaccess, -10 ) === '/.htaccess' ) {
$this->backend_htaccess = $backend_htaccess;
}
// Filter for frontend&backend htaccess path
$this->frontend_htaccess = apply_filters( 'litespeed_frontend_htaccess', $this->frontend_htaccess );
$this->backend_htaccess = apply_filters( 'litespeed_backend_htaccess', $this->backend_htaccess );
clearstatcache();
// frontend .htaccess privilege
$test_permissions = file_exists( $this->frontend_htaccess ) ? $this->frontend_htaccess : dirname( $this->frontend_htaccess );
if ( is_readable( $test_permissions ) ) {
$this->frontend_htaccess_readable = true;
}
if ( is_writable( $test_permissions ) ) {
$this->frontend_htaccess_writable = true;
}
$this->__rewrite_on = array(
self::REWRITE_ON,
"CacheLookup on",
"RewriteRule .* - [E=Cache-Control:no-autoflush]",
// "RewriteRule \.object-cache\.ini - [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 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();
// 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 );
$new_rules[] = 'RewriteRule .* - [E=Cache-Control:no-cache]';
$new_rules[] = self::MARKER_NOCACHE_USER_AGENTS . self::MARKER_END;
$new_rules[] = '';
}
// caching php resource
$id = Base::O_CACHE_RES;
if ( ! empty( $cfg[ $id ] ) ) {
$new_rules[] = $new_rules_backend[] = self::MARKER_CACHE_RESOURCE . self::MARKER_START;
$new_rules[] = $new_rules_backend[] = 'RewriteRule ' . LSCWP_CONTENT_FOLDER . self::RW_PATTERN_RES . ' - [E=cache-control:max-age=3600]';
$new_rules[] = $new_rules_backend[] = self::MARKER_CACHE_RESOURCE . self::MARKER_END;
$new_rules[] = $new_rules_backend[] = '';
}
// check login cookie
$id = Base::O_CACHE_LOGIN_COOKIE;
$vary_cookies = $cfg[ $id ] ? array( $cfg[ $id ] ) : array();
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[] = '';
}
// favicon
// frontend and backend
$id = Base::O_CACHE_FAVICON;
if ( ! empty( $cfg[ $id ] ) ) {
$new_rules[] = $new_rules_backend[] = self::MARKER_FAVICON . self::MARKER_START;
$new_rules[] = $new_rules_backend[] = 'RewriteRule favicon\.ico$ - [E=cache-control:max-age=86400]';
$new_rules[] = $new_rules_backend[] = self::MARKER_FAVICON . 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_REPLACE;
if ( ! empty( $cfg[ $id ] ) || ! empty( $cfg[ Base::O_GUEST ] ) ) {
$new_rules[] = self::MARKER_WEBP . self::MARKER_START;
$new_rules[] = 'RewriteCond %{HTTP_ACCEPT} "image/webp" [or]';
$new_rules[] = 'RewriteCond %{HTTP_USER_AGENT} "Page Speed"';
$new_rules[] = 'RewriteRule .* - [E=Cache-Control:vary=%{ENV:LSCACHE_VARY_VALUE}+webp]';
$new_rules[] = 'RewriteCond %{HTTP_USER_AGENT} iPhone.*Version/(\d{2}).*Safari';
$new_rules[] = 'RewriteCond %1 >13';
$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

View File

@@ -0,0 +1,215 @@
<?php
/**
* The import/export class.
*
* @since 1.8.2
*/
namespace LiteSpeed;
defined( 'WPINC' ) || exit;
class Import extends Base {
protected $_summary;
const TYPE_IMPORT = 'import';
const TYPE_EXPORT = 'export';
const TYPE_RESET = 'reset';
/**
* Init
*
* @since 1.8.2
*/
public function __construct() {
Debug2::debug( 'Import init' );
$this->_summary = self::get_summary();
}
/**
* Export settings to file
*
* @since 1.8.2
* @access public
*/
public function export( $only_data_return = false ) {
$raw_data = $this->get_options( true );
$data = array();
foreach ( $raw_data as $k => $v ) {
$data[] = json_encode( array( $k, $v ) );
}
$data = implode( "\n\n", $data );
if ( $only_data_return ) {
return $data;
}
$filename = $this->_generate_filename();
// Update log
$this->_summary[ 'export_file' ] = $filename;
$this->_summary[ 'export_time' ] = time();
self::save_summary();
Debug2::debug( 'Import: Saved to ' . $filename );
@header( 'Content-Disposition: attachment; filename=' . $filename );
echo $data;
exit;
}
/**
* Import settings from file
*
* @since 1.8.2
* @access public
*/
public function import( $file = false ) {
if ( ! $file ) {
if ( empty( $_FILES[ 'ls_file' ][ 'name' ] ) || substr( $_FILES[ 'ls_file' ][ 'name' ], -5 ) != '.data' || empty( $_FILES[ 'ls_file' ][ 'tmp_name' ] ) ) {
Debug2::debug( 'Import: Failed to import, wront 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::succeed( $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::succeed( $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();
}
}

View File

@@ -0,0 +1,12 @@
<?php
/**
* The abstract instance
*
* @since 3.0
*/
namespace LiteSpeed;
defined( 'WPINC' ) || exit;
abstract class Instance extends Root {
}

View File

@@ -0,0 +1,246 @@
<?php
/**
* The language class.
*
* @since 3.0
* @package LiteSpeed_Cache
* @subpackage LiteSpeed_Cache/inc
* @author LiteSpeed Technologies <info@litespeedtech.com>
*/
namespace LiteSpeed ;
defined( 'WPINC' ) || exit ;
class Lang extends Base {
/**
* Get image status per status bit
*
* @since 3.0
*/
public static function img_status( $status = null )
{
$list = array(
Img_Optm::STATUS_RAW => __( 'Images not requested', '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' ),
Img_Optm::STATUS_FAILED => __( 'Images failed to pull', 'litespeed-cache' ),
Img_Optm::STATUS_ERR_FETCH => __( 'Images failed to fetch', 'litespeed-cache' ),
Img_Optm::STATUS_ERR_404 => __( 'Images failed to fetch', 'litespeed-cache') . ' (404)',
Img_Optm::STATUS_ERR_OPTM => __( 'Images previously optimized', 'litespeed-cache' ),
Img_Optm::STATUS_ERR => __( 'Images failed with other errors', 'litespeed-cache' ),
Img_Optm::STATUS_MISS => __( 'Image files missing', 'litespeed-cache' ),
Img_Optm::STATUS_DUPLICATED => __( 'Duplicate image files ignored', 'litespeed-cache' ),
Img_Optm::STATUS_XMETA => __( 'Images with wrong meta', 'litespeed-cache' ),
);
if ( $status !== null ) {
return ! empty( $list[ $status ] ) ? $list[ $status ] : 'N/A';
}
return $list;
}
/**
* 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_API_KEY => __( 'Domain Key', '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_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_FAVICON => __( 'Cache favicon.ico', 'litespeed-cache' ),
self::O_CACHE_RES => __( 'Cache PHP Resources', 'litespeed-cache' ),
self::O_CACHE_MOBILE => __( 'Cache Mobile', 'litespeed-cache' ),
self::O_CACHE_MOBILE_RULES => __( 'List of Mobile User Agents', 'litespeed-cache' ),
self::O_CACHE_PRIV_URI => __( 'Private Cached URIs', 'litespeed-cache' ),
self::O_CACHE_DROP_QS => __( 'Drop Query String', 'litespeed-cache' ),
self::O_OBJECT => __( 'Object Cache', 'litespeed-cache' ),
self::O_OBJECT_KIND => __( 'Method', 'litespeed-cache' ),
self::O_OBJECT_HOST => __( 'Host', 'litespeed-cache' ),
self::O_OBJECT_PORT => __( 'Port', 'litespeed-cache' ),
self::O_OBJECT_LIFE => __( 'Default Object Lifetime', 'litespeed-cache' ),
self::O_OBJECT_USER => __( 'Username', 'litespeed-cache' ),
self::O_OBJECT_PSWD => __( 'Password', 'litespeed-cache' ),
self::O_OBJECT_DB_ID => __( 'Redis Database ID', 'litespeed-cache' ),
self::O_OBJECT_GLOBAL_GROUPS => __( 'Global Groups', 'litespeed-cache' ),
self::O_OBJECT_NON_PERSISTENT_GROUPS => __( 'Do Not Cache Groups', 'litespeed-cache' ),
self::O_OBJECT_PERSISTENT => __( 'Persistent Connection', 'litespeed-cache' ),
self::O_OBJECT_ADMIN => __( 'Cache WP-Admin', 'litespeed-cache' ),
self::O_OBJECT_TRANSIENTS => __( 'Store Transients', 'litespeed-cache' ),
self::O_PURGE_ON_UPGRADE => __( 'Purge All On Upgrade', 'litespeed-cache' ),
self::O_PURGE_STALE => __( 'Serve Stale', 'litespeed-cache' ),
self::O_PURGE_TIMED_URLS => __( 'Scheduled Purge URLs', 'litespeed-cache' ),
self::O_PURGE_TIMED_URLS_TIME => __( 'Scheduled Purge Time', 'litespeed-cache' ),
self::O_CACHE_FORCE_URI => __( 'Force Cache URIs', 'litespeed-cache' ),
self::O_CACHE_FORCE_PUB_URI => __( 'Force Public Cache URIs', 'litespeed-cache' ),
self::O_CACHE_EXC => __( 'Do Not Cache URIs', 'litespeed-cache' ),
self::O_CACHE_EXC_QS => __( 'Do Not Cache Query Strings', 'litespeed-cache' ),
self::O_CACHE_EXC_CAT => __( 'Do Not Cache Categories', 'litespeed-cache' ),
self::O_CACHE_EXC_TAG => __( 'Do Not Cache Tags', 'litespeed-cache' ),
self::O_CACHE_EXC_ROLES => __( 'Do Not Cache Roles', 'litespeed-cache' ),
self::O_OPTM_CSS_MIN => __( 'CSS Minify', 'litespeed-cache' ),
self::O_OPTM_CSS_COMB => __( 'CSS Combine', 'litespeed-cache' ),
self::O_OPTM_CSS_COMB_EXT_INL => __( 'CSS Combine External and Inline', 'litespeed-cache' ),
self::O_OPTM_UCSS => __( 'Generate UCSS', 'litespeed-cache' ),
self::O_OPTM_UCSS_INLINE => __( 'UCSS Inline', 'litespeed-cache' ),
self::O_OPTM_UCSS_WHITELIST => __( 'UCSS Allowlist', '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_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_CSS_EXC => __( 'CSS Excludes', '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_JS_DEFER_EXC => __( 'JS Deferred 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_IMG_OPTM_AUTO => __( 'Auto Request Cron', 'litespeed-cache' ),
self::O_IMG_OPTM_CRON => __( 'Auto Pull 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 => __( 'Create WebP Versions', 'litespeed-cache' ),
self::O_IMG_OPTM_LOSSLESS => __( 'Optimize Losslessly', 'litespeed-cache' ),
self::O_IMG_OPTM_EXIF => __( 'Preserve EXIF/XMP data', 'litespeed-cache' ),
self::O_IMG_OPTM_WEBP_ATTR => __( 'WebP Attribute To Replace', 'litespeed-cache' ),
self::O_IMG_OPTM_WEBP_REPLACE_SRCSET => __( 'WebP 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_IMG_OPTM_WEBP_REPLACE => __( 'Image WebP Replacement', '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_QUIC => __( 'QUIC.cloud CDN', 'litespeed-cache' ),
self::O_CDN => __( 'Use CDN Mapping', 'litespeed-cache' ),
self::CDN_MAPPING_URL => __( 'CDN URL', 'litespeed-cache' ),
self::CDN_MAPPING_INC_IMG => __( 'Include Images', 'litespeed-cache' ),
self::CDN_MAPPING_INC_CSS => __( 'Include CSS', 'litespeed-cache' ),
self::CDN_MAPPING_INC_JS => __( 'Include JS', 'litespeed-cache' ),
self::CDN_MAPPING_FILETYPE => __( 'Include File Types', 'litespeed-cache' ),
self::O_CDN_ATTR => __( 'HTML Attribute To Replace', 'litespeed-cache' ),
self::O_CDN_ORI => __( 'Original URLs', 'litespeed-cache' ),
self::O_CDN_ORI_DIR => __( 'Included Directories', 'litespeed-cache' ),
self::O_CDN_EXC => __( 'Exclude Path', 'litespeed-cache' ),
self::O_CDN_CLOUDFLARE => __( 'Cloudflare API', 'litespeed-cache' ),
self::O_CRAWLER => __( 'Crawler', 'litespeed-cache' ),
self::O_CRAWLER_USLEEP => __( 'Delay', 'litespeed-cache' ),
self::O_CRAWLER_RUN_DURATION => __( 'Run Duration', 'litespeed-cache' ),
self::O_CRAWLER_RUN_INTERVAL => __( 'Interval Between Runs', 'litespeed-cache' ),
self::O_CRAWLER_CRAWL_INTERVAL => __( 'Crawl Interval', 'litespeed-cache' ),
self::O_CRAWLER_THREADS => __( 'Threads', 'litespeed-cache' ),
self::O_CRAWLER_TIMEOUT => __( 'Timeout', '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_CRAWLER_DROP_DOMAIN => __( 'Drop Domain from Sitemap', 'litespeed-cache' ),
self::O_CRAWLER_MAP_TIMEOUT => __( 'Sitemap Timeout', '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_COOKIE => __( 'Log Cookies', 'litespeed-cache' ),
self::O_DEBUG_COLLAPS_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_DB_OPTM_REVISIONS_MAX => __( 'Revisions Max Number', 'litespeed-cache' ),
self::O_DB_OPTM_REVISIONS_AGE => __( 'Revisions Max Age', 'litespeed-cache' ),
) ;
if ( array_key_exists( $id, $_lang_list ) ) {
return $_lang_list[ $id ] ;
}
return 'N/A' ;
}
}

View File

@@ -0,0 +1,190 @@
<?php
/**
* The localization class.
*
* @since 3.3
*/
namespace LiteSpeed;
defined( 'WPINC' ) || exit;
class Localization extends Base {
const LOG_TAG = '🛍️';
/**
* Init optimizer
*
* @since 3.0
* @access protected
*/
public function init() {
add_filter( 'litespeed_buffer_finalize', array( $this, 'finalize' ), 23 ); // After page optm
}
/**
* Localize Resources
*
* @since 3.3
*/
public function serve_static( $uri ) {
$url = 'https://' . $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' );
}
$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 splitted 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 ) {
continue;
}
$match = true;
break;
}
if ( ! $match ) {
// wp_redirect( $url );
exit( 'Not supported' );
}
header( 'Content-Type: application/javascript' );
// Generate
$this->_maybe_mk_cache_folder( 'localres' );
$file = $this->_realpath( $url );
self::debug( 'localize [url] ' . $url );
$response = wp_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 splitted 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/' . substr( $domain, 8 ), $content );
}
return $content;
}
}

View File

@@ -0,0 +1,926 @@
<?php
/**
* The class to operate media data.
*
* @since 1.4
* @since 1.5 Moved into /inc
* @package Core
* @subpackage Core/inc
* @author LiteSpeed Technologies <info@litespeedtech.com>
*/
namespace LiteSpeed;
defined( 'WPINC' ) || exit;
class Media extends Root {
const LIB_FILE_IMG_LAZYLOAD = 'assets/js/lazyload.min.js';
private $content;
private $_wp_upload_dir;
/**
* Init
*
* @since 1.4
*/
public function __construct() {
Debug2::debug2( '[Media] init' );
$this->_wp_upload_dir = wp_upload_dir();
}
/**
* Init optm features
*
* @since 3.0
* @access public
*/
public function init() {
if ( is_admin() ) {
return;
}
// Due to ajax call doesn't send correct accept header, have to limit webp to HTML only
if ( defined( 'LITESPEED_GUEST_OPTM' ) || $this->conf( Base::O_IMG_OPTM_WEBP_REPLACE ) ) {
if ( $this->webp_support() ) {
// Hook to srcset
if ( function_exists( 'wp_calculate_image_srcset' ) ) {
add_filter( 'wp_calculate_image_srcset', array( $this, 'webp_srcset' ), 988 );
}
// Hook to mime icon
// add_filter( 'wp_get_attachment_image_src', array( $this, 'webp_attach_img_src' ), 988 );// todo: need to check why not
// add_filter( 'wp_get_attachment_url', array( $this, 'webp_url' ), 988 ); // disabled to avoid wp-admin display
}
}
/**
* Replace gravatar
* @since 3.0
*/
$this->cls( 'Avatar' );
add_filter( 'litespeed_buffer_finalize', array( $this, 'finalize' ), 4 );
}
/**
* Adjust WP default JPG quality
*
* @since 3.0
* @access public
*/
public function adjust_jpg_quality( $quality ) {
$v = $this->conf( Base::O_IMG_OPTM_JPG_QUALITY );
if ( $v ) {
return $v;
}
return $quality;
}
/**
* Register admin menu
*
* @since 1.6.3
* @access public
*/
public function after_admin_init() {
/**
* JPG quality control
* @since 3.0
*/
add_filter( 'jpeg_quality', array( $this, 'adjust_jpg_quality' ) );
add_filter( 'manage_media_columns', array( $this, 'media_row_title' ) );
add_filter( 'manage_media_custom_column', array( $this, 'media_row_actions' ), 10, 2 );
add_action( 'litespeed_media_row', array( $this, 'media_row_con' ) );
// Hook to attachment delete action
add_action( 'delete_attachment', __CLASS__ . '::delete_attachment' );
}
/**
* Media delete action hook
*
* @since 2.4.3
* @access public
*/
public static function delete_attachment( $post_id ) {
if ( ! Data::cls()->tb_exist( 'img_optm' ) ) {
return;
}
Debug2::debug( '[Media] delete_attachment [pid] ' . $post_id );
Img_Optm::cls()->reset_row( $post_id );
}
/**
* Return media file info if exists
*
* This is for remote attachment plugins
*
* @since 2.9.8
* @access public
*/
public function info( $short_file_path, $post_id ) {
$real_file = $this->_wp_upload_dir[ 'basedir' ] . '/' . $short_file_path;
if ( file_exists( $real_file ) ) {
return array(
'url' => $this->_wp_upload_dir[ 'baseurl' ] . '/' . $short_file_path,
'md5' => md5_file( $real_file ),
'size' => filesize( $real_file ),
);
}
/**
* WP Stateless compatibility #143 https://github.com/litespeedtech/lscache_wp/issues/143
* @since 2.9.8
* @return array( 'url', 'md5', 'size' )
*/
$info = apply_filters( 'litespeed_media_info', array(), $short_file_path, $post_id );
if ( ! empty( $info[ 'url' ] ) && ! empty( $info[ 'md5' ] ) && ! empty( $info[ 'size' ] ) ) {
return $info;
}
return false;
}
/**
* Delete media file
*
* @since 2.9.8
* @access public
*/
public function del( $short_file_path, $post_id ) {
$real_file = $this->_wp_upload_dir[ 'basedir' ] . '/' . $short_file_path;
if ( file_exists( $real_file ) ) {
unlink( $real_file );
Debug2::debug( '[Media] deleted ' . $real_file );
}
do_action( 'litespeed_media_del', $short_file_path, $post_id );
}
/**
* Rename media file
*
* @since 2.9.8
* @access public
*/
public function rename( $short_file_path, $short_file_path_new, $post_id ) {
$real_file = $this->_wp_upload_dir[ 'basedir' ] . '/' . $short_file_path;
$real_file_new = $this->_wp_upload_dir[ 'basedir' ] . '/' . $short_file_path_new;
if ( file_exists( $real_file ) ) {
rename( $real_file, $real_file_new );
Debug2::debug( '[Media] renamed ' . $real_file . ' to ' . $real_file_new );
}
do_action( 'litespeed_media_rename', $short_file_path, $short_file_path_new, $post_id );
}
/**
* Media Admin Menu -> Image Optimization Column Title
*
* @since 1.6.3
* @access public
*/
public function media_row_title( $posts_columns ) {
$posts_columns[ 'imgoptm' ] = __( 'LiteSpeed Optimization', 'litespeed-cache' );
return $posts_columns;
}
/**
* Media Admin Menu -> Image Optimization Column
*
* @since 1.6.2
* @access public
*/
public function media_row_actions( $column_name, $post_id ) {
if ( $column_name !== 'imgoptm' ) {
return;
}
do_action( 'litespeed_media_row', $post_id );
}
/**
* Display image optm info
*
* @since 3.0
*/
public function media_row_con( $post_id ) {
$att_info = wp_get_attachment_metadata( $post_id );
if ( empty( $att_info[ 'file' ] ) ) {
return;
}
$short_path = $att_info[ 'file' ];
$size_meta = get_post_meta( $post_id, Img_Optm::DB_SIZE, true );
echo '<p>';
// Original image info
if ( $size_meta && ! empty ( $size_meta[ 'ori_saved' ] ) ) {
$percent = ceil( $size_meta[ 'ori_saved' ] * 100 / $size_meta[ 'ori_total' ] );
$extension = pathinfo( $short_path, PATHINFO_EXTENSION );
$bk_file = substr( $short_path, 0, -strlen( $extension ) ) . 'bk.' . $extension;
$bk_optm_file = substr( $short_path, 0, -strlen( $extension ) ) . 'bk.optm.' . $extension;
$link = Utility::build_url( Router::ACTION_IMG_OPTM, 'orig' . $post_id );
$desc = false;
$cls = '';
if ( $this->info( $bk_file, $post_id ) ) {
$curr_status = __( '(optm)', 'litespeed-cache' );
$desc = __( 'Currently using optimized version of file.', 'litespeed-cache' ) . '&#10;' . __( 'Click to switch to original (unoptimized) version.', 'litespeed-cache' );
}
elseif ( $this->info( $bk_optm_file, $post_id ) ) {
$cls .= ' litespeed-warning';
$curr_status = __( '(non-optm)', 'litespeed-cache' );
$desc = __( 'Currently using original (unoptimized) version of file.', 'litespeed-cache' ) . '&#10;' . __( 'Click to switch to optimized version.', 'litespeed-cache' );
}
echo GUI::pie_tiny( $percent, 24,
sprintf( __( 'Original file reduced by %1$s (%2$s)', 'litespeed-cache' ),
$percent . '%',
Utility::real_size( $size_meta[ 'ori_saved' ] )
) , 'left'
);
echo sprintf( __( 'Orig saved %s', 'litespeed-cache' ), $percent . '%' );
if ( $desc ) {
echo sprintf( ' <a href="%1$s" class="litespeed-media-href %2$s" data-balloon-pos="left" data-balloon-break aria-label="%3$s">%4$s</a>', $link, $cls, $desc, $curr_status );
}
else {
echo sprintf(
' <span class="litespeed-desc" data-balloon-pos="left" data-balloon-break aria-label="%1$s">%2$s</span>',
__( 'Using optimized version of file. ', 'litespeed-cache' ) . '&#10;' . __( 'No backup of original file exists.', 'litespeed-cache' ),
__( '(optm)', 'litespeed-cache' )
);
}
}
elseif ( $size_meta && $size_meta[ 'ori_saved' ] === 0 ){
echo GUI::pie_tiny( 0, 24,
__( 'Congratulation! Your file was already optimized', 'litespeed-cache' ),
'left'
);
echo sprintf( __( 'Orig %s', 'litespeed-cache' ), '<span class="litespeed-desc">' . __( '(no savings)', 'litespeed-cache' ) . '</span>' );
}
else {
echo __( 'Orig', 'litespeed-cache' ) . '<span class="litespeed-left10">—</span>';
}
echo '</p>';
echo '<p>';
// WebP info
if ( $size_meta && ! empty ( $size_meta[ 'webp_saved' ] ) ) {
$percent = ceil( $size_meta[ 'webp_saved' ] * 100 / $size_meta[ 'webp_total' ] );
$link = Utility::build_url( Router::ACTION_IMG_OPTM, 'webp' . $post_id );
$desc = false;
$cls = '';
if ( $this->info( $short_path . '.webp', $post_id ) ) {
$curr_status = __( '(optm)', 'litespeed-cache' );
$desc = __( 'Currently using optimized version of WebP file.', 'litespeed-cache' ) . '&#10;' . __( 'Click to switch to original (unoptimized) version.', 'litespeed-cache' );
}
elseif ( $this->info( $short_path . '.optm.webp', $post_id ) ) {
$cls .= ' litespeed-warning';
$curr_status = __( '(non-optm)', 'litespeed-cache' );
$desc = __( 'Currently using original (unoptimized) version of WebP file.', 'litespeed-cache' ) . '&#10;' . __( 'Click to switch to optimized version.', 'litespeed-cache' );
}
echo GUI::pie_tiny( $percent, 24,
sprintf( __( 'WebP file reduced by %1$s (%2$s)', 'litespeed-cache' ),
$percent . '%',
Utility::real_size( $size_meta[ 'webp_saved' ] )
) , 'left'
);
echo sprintf( __( 'WebP saved %s', 'litespeed-cache' ), $percent . '%' );
if ( $desc ) {
echo sprintf( ' <a href="%1$s" class="litespeed-media-href %2$s" data-balloon-pos="left" data-balloon-break aria-label="%3$s">%4$s</a>', $link, $cls, $desc, $curr_status );
}
else {
echo sprintf(
' <span class="litespeed-desc" data-balloon-pos="left" data-balloon-break aria-label="%1$s">%2$s</span>',
__( 'Using optimized version of file. ', 'litespeed-cache' ) . '&#10;' . __( 'No backup of unoptimized WebP file exists.', 'litespeed-cache' ),
__( '(optm)', 'litespeed-cache' )
);
}
} else {
echo __( 'WebP', 'litespeed-cache' ) . '<span class="litespeed-left10">—</span>';
}
echo '</p>';
// Delete row btn
if ( $size_meta ) {
echo sprintf( '<div class="row-actions"><span class="delete"><a href="%1$s" class="">%2$s</a></span></div>',
Utility::build_url( Router::ACTION_IMG_OPTM, Img_Optm::TYPE_RESET_ROW, false, null, array( 'id' => $post_id ) ),
__( 'Restore from backup', 'litespeed-cache' )
);
echo '</div>';
}
}
/**
* Get wp size info
*
* NOTE: this is not used because it has to be after admin_init
*
* @since 1.6.2
* @return array $sizes Data for all currently-registered image sizes.
*/
public function get_image_sizes() {
global $_wp_additional_image_sizes;
$sizes = array();
foreach ( get_intermediate_image_sizes() as $_size ) {
if ( in_array( $_size, array( 'thumbnail', 'medium', 'medium_large', 'large' ) ) ) {
$sizes[ $_size ][ 'width' ] = get_option( $_size . '_size_w' );
$sizes[ $_size ][ 'height' ] = get_option( $_size . '_size_h' );
$sizes[ $_size ][ 'crop' ] = (bool) get_option( $_size . '_crop' );
} elseif ( isset( $_wp_additional_image_sizes[ $_size ] ) ) {
$sizes[ $_size ] = array(
'width' => $_wp_additional_image_sizes[ $_size ][ 'width' ],
'height' => $_wp_additional_image_sizes[ $_size ][ 'height' ],
'crop' => $_wp_additional_image_sizes[ $_size ][ 'crop' ]
);
}
}
return $sizes;
}
/**
* Exclude role from optimization filter
*
* @since 1.6.2
* @access public
*/
public function webp_support() {
if ( ! empty( $_SERVER[ 'HTTP_ACCEPT' ] ) && strpos( $_SERVER[ 'HTTP_ACCEPT' ], 'image/webp' ) !== false ) {
return true;
}
if ( ! empty( $_SERVER[ 'HTTP_USER_AGENT' ] ) ) {
if ( strpos( $_SERVER[ 'HTTP_USER_AGENT' ], 'Page Speed' ) !== false ) {
return true;
}
if ( preg_match( "/iPhone OS (\d+)_/i", $_SERVER[ 'HTTP_USER_AGENT' ], $matches ) ) {
$lscwp_ios_version = $matches[1];
if ($lscwp_ios_version >= 14){
return true;
}
}
}
return false;
}
/**
* Run lazy load process
* NOTE: As this is after cache finalized, can NOT set any cache control anymore
*
* Only do for main page. Do NOT do for esi or dynamic content.
*
* @since 1.4
* @access public
* @return string The buffer
*/
public function finalize( $content ) {
if ( defined( 'LITESPEED_NO_LAZY' ) ) {
Debug2::debug2( '[Media] bypass: NO_LAZY const' );
return $content;
}
if ( ! defined( 'LITESPEED_IS_HTML' ) ) {
Debug2::debug2( '[Media] bypass: Not frontend HTML type' );
return $content;
}
if ( ! Control::is_cacheable() ) {
Debug2::debug( '[Media] bypass: Not cacheable' );
return $content;
}
Debug2::debug( '[Media] finalize' );
$this->content = $content;
$this->_finalize();
return $this->content;
}
/**
* Run lazyload replacement for images in buffer
*
* @since 1.4
* @access private
*/
private function _finalize() {
/**
* Use webp for optimized images
* @since 1.6.2
*/
if ( ( defined( 'LITESPEED_GUEST_OPTM' ) || $this->conf( Base::O_IMG_OPTM_WEBP_REPLACE ) ) && $this->webp_support() ) {
$this->content = $this->_replace_buffer_img_webp( $this->content );
}
/**
* Check if URI is excluded
* @since 3.0
*/
$excludes = $this->conf( Base::O_MEDIA_LAZY_URI_EXC );
if ( ! defined( 'LITESPEED_GUEST_OPTM' ) ) {
$result = Utility::str_hit_array( $_SERVER[ 'REQUEST_URI' ], $excludes );
if ( $result ) {
Debug2::debug( '[Media] bypass lazyload: hit URI Excludes setting: ' . $result );
return;
}
}
$cfg_lazy = defined( 'LITESPEED_GUEST_OPTM' ) || $this->conf( Base::O_MEDIA_LAZY );
$cfg_iframe_lazy = defined( 'LITESPEED_GUEST_OPTM' ) || $this->conf( Base::O_MEDIA_IFRAME_LAZY );
$cfg_js_delay = defined( 'LITESPEED_GUEST_OPTM' ) || $this->conf( Base::O_OPTM_JS_DEFER ) == 2;
$cfg_trim_noscript = defined( 'LITESPEED_GUEST_OPTM' ) || $this->conf( Base::O_OPTM_NOSCRIPT_RM );
if ( $cfg_lazy ) {
list( $src_list, $html_list, $placeholder_list ) = $this->_parse_img();
$html_list_ori = $html_list;
}
// image lazy load
if ( $cfg_lazy ) {
$__placeholder = Placeholder::cls();
foreach ( $html_list as $k => $v ) {
$size = $placeholder_list[ $k ];
$src = $src_list[ $k ];
$html_list[ $k ] = $__placeholder->replace( $v, $src, $size );
}
}
if ( $cfg_lazy ) {
$this->content = str_replace( $html_list_ori, $html_list, $this->content );
}
// iframe lazy load
if ( $cfg_iframe_lazy ) {
$html_list = $this->_parse_iframe();
$html_list_ori = $html_list;
foreach ( $html_list as $k => $v ) {
$snippet = $cfg_trim_noscript ? '' : '<noscript>' . $v . '</noscript>';
if ( $cfg_js_delay ) {
$v = str_replace( ' src=', ' data-litespeed-src=', $v );
}
else {
$v = str_replace( ' src=', ' data-src=', $v );
}
$v = str_replace( '<iframe ', '<iframe data-lazyloaded="1" src="about:blank" ', $v );
$snippet = $v . $snippet;
$html_list[ $k ] = $snippet;
}
$this->content = str_replace( $html_list_ori, $html_list, $this->content );
}
// Include lazyload lib js and init lazyload
if ( $cfg_lazy || $cfg_iframe_lazy ) {
$lazy_lib = '<script data-no-optimize="1" defer>' . File::read( LSCWP_DIR . self::LIB_FILE_IMG_LAZYLOAD ) . '</script>';
$this->content = str_replace( '</body>', $lazy_lib . '</body>', $this->content );
}
}
/**
* Parse img src
*
* @since 1.4
* @access private
* @return array All the src & related raw html list
*/
private function _parse_img() {
/**
* Exclude list
* @since 1.5
* @since 2.7.1 Changed to array
*/
$excludes = apply_filters( 'litespeed_media_lazy_img_excludes', $this->conf( Base::O_MEDIA_LAZY_EXC ) );
$cls_excludes = apply_filters( 'litespeed_media_lazy_img_cls_excludes', $this->conf( Base::O_MEDIA_LAZY_CLS_EXC ) );
$cls_excludes[] = 'skip-lazy'; // https://core.trac.wordpress.org/ticket/44427
$src_list = array();
$html_list = array();
$placeholder_list = array();
$content = preg_replace( array( '#<!--.*-->#sU', '#<noscript([^>]*)>.*</noscript>#isU' ), '', $this->content );
/**
* Exclude parent classes
* @since 3.0
*/
$parent_cls_exc = apply_filters( 'litespeed_media_lazy_img_parent_cls_excludes', $this->conf( Base::O_MEDIA_LAZY_PARENT_CLS_EXC ) );
if ( $parent_cls_exc ) {
Debug2::debug2( '[Media] Lazyload Class excludes', $parent_cls_exc );
foreach ( $parent_cls_exc as $v ) {
$content = preg_replace( '#<(\w+) [^>]*class=(\'|")[^\'"]*' . preg_quote( $v, '#' ) . '[^\'"]*\2[^>]*>.*</\1>#sU', '', $content );
}
}
preg_match_all( '#<img \s*([^>]+)/?>#isU', $content, $matches, PREG_SET_ORDER );
foreach ( $matches as $match ) {
$attrs = Utility::parse_attr( $match[ 1 ] );
if ( empty( $attrs[ 'src' ] ) ) {
continue;
}
/**
* Add src validation to bypass base64 img src
* @since 1.6
*/
if ( strpos( $attrs[ 'src' ], 'base64' ) !== false || substr( $attrs[ 'src' ], 0, 5 ) === 'data:' ) {
Debug2::debug2( '[Media] lazyload bypassed base64 img' );
continue;
}
Debug2::debug2( '[Media] lazyload found: ' . $attrs[ 'src' ] );
if ( ! empty( $attrs[ 'data-no-lazy' ] ) || ! empty( $attrs[ 'data-skip-lazy' ] ) || ! empty( $attrs[ 'data-lazyloaded' ] ) || ! empty( $attrs[ 'data-src' ] ) || ! empty( $attrs[ 'data-srcset' ] ) ) {
Debug2::debug2( '[Media] bypassed' );
continue;
}
if ( ! empty( $attrs[ 'class' ] ) && $hit = Utility::str_hit_array( $attrs[ 'class' ], $cls_excludes ) ) {
Debug2::debug2( '[Media] lazyload image cls excludes [hit] ' . $hit );
continue;
}
/**
* Exclude from lazyload by setting
* @since 1.5
*/
if ( $excludes && Utility::str_hit_array( $attrs[ 'src' ], $excludes ) ) {
Debug2::debug2( '[Media] lazyload image exclude ' . $attrs[ 'src' ] );
continue;
}
/**
* Excldues invalid image src from buddypress avatar crop
* @see https://wordpress.org/support/topic/lazy-load-breaking-buddypress-upload-avatar-feature
* @since 3.0
*/
if ( strpos( $attrs[ 'src' ], '{' ) !== false ) {
Debug2::debug2( '[Media] image src has {} ' . $attrs[ 'src' ] );
continue;
}
// to avoid multiple replacement
if ( in_array( $match[ 0 ], $html_list ) ) {
continue;
}
// Add missing dimensions
if ( defined( 'LITESPEED_GUEST_OPTM' ) || $this->conf( Base::O_MEDIA_ADD_MISSING_SIZES ) ) {
if ( empty( $attrs[ 'width' ] ) || $attrs[ 'width' ] == 'auto' || empty( $attrs[ 'height' ] ) || $attrs[ 'height' ] == 'auto' ) {
Debug2::debug( '[Media] ⚠️ Missing sizes for image [src] ' . $attrs[ 'src' ] );
$dimensions = $this->_detect_dimensions( $attrs[ 'src' ] );
if ( $dimensions ) {
$ori_width = $dimensions[ 0 ];
$ori_height = $dimensions[ 1 ];
// Calculate height based on width
if ( ! empty( $attrs[ 'width' ] ) && $attrs[ 'width' ] != 'auto' ) {
$ori_height = intval( $ori_height * $attrs[ 'width' ] / $ori_width );
}
elseif ( ! empty( $attrs[ 'height' ] ) && $attrs[ 'height' ] != 'auto' ) {
$ori_width = intval( $ori_width * $attrs[ 'height' ] / $ori_height );
}
$attrs[ 'width' ] = $ori_width;
$attrs[ 'height' ] = $ori_height;
$new_html = preg_replace( '#(width|height)=(["\'])[^\2]*\2#', '', $match[ 0 ] );
$new_html = str_replace( '<img ', '<img width="' . $attrs[ 'width' ] . '" height="' . $attrs[ 'height' ] . '" ', $new_html );
Debug2::debug( '[Media] Add missing sizes ' . $attrs[ 'width' ] . 'x' . $attrs[ 'height' ] . ' to ' . $attrs[ 'src' ] );
$this->content = str_replace( $match[ 0 ], $new_html, $this->content );
$match[ 0 ] = $new_html;
}
}
}
$placeholder = false;
if ( ! empty( $attrs[ 'width' ] ) && $attrs[ 'width' ] != 'auto' && ! empty( $attrs[ 'height' ] ) && $attrs[ 'height' ] != 'auto' ) {
$placeholder = $attrs[ 'width' ] . 'x' . $attrs[ 'height' ];
}
$src_list[] = $attrs[ 'src' ];
$html_list[] = $match[ 0 ];
$placeholder_list[] = $placeholder;
}
return array( $src_list, $html_list, $placeholder_list );
}
/**
* Detect the original sizes
*
* @since 4.0
*/
private function _detect_dimensions( $src ) {
if ( $pathinfo = Utility::is_internal_file( $src ) ) {
$src = $pathinfo[ 0 ];
}
$sizes = getimagesize( $src );
if ( ! empty( $sizes[ 0 ] ) && ! empty( $sizes[ 1 ] ) ) {
return $sizes;
}
return false;
}
/**
* Parse iframe src
*
* @since 1.4
* @access private
* @return array All the src & related raw html list
*/
private function _parse_iframe() {
$cls_excludes = apply_filters( 'litespeed_media_iframe_lazy_cls_excludes', $this->conf( Base::O_MEDIA_IFRAME_LAZY_CLS_EXC ) );
$cls_excludes[] = 'skip-lazy'; // https://core.trac.wordpress.org/ticket/44427
$html_list = array();
$content = preg_replace( '#<!--.*-->#sU', '', $this->content );
/**
* Exclude parent classes
* @since 3.0
*/
$parent_cls_exc = apply_filters( 'litespeed_media_iframe_lazy_parent_cls_excludes', $this->conf( Base::O_MEDIA_IFRAME_LAZY_PARENT_CLS_EXC ) );
if ( $parent_cls_exc ) {
Debug2::debug2( '[Media] Iframe Lazyload Class excludes', $parent_cls_exc );
foreach ( $parent_cls_exc as $v ) {
$content = preg_replace( '#<(\w+) [^>]*class=(\'|")[^\'"]*' . preg_quote( $v, '#' ) . '[^\'"]*\2[^>]*>.*</\1>#sU', '', $content );
}
}
preg_match_all( '#<iframe \s*([^>]+)></iframe>#isU', $content, $matches, PREG_SET_ORDER );
foreach ( $matches as $match ) {
$attrs = Utility::parse_attr( $match[ 1 ] );
if ( empty( $attrs[ 'src' ] ) ) {
continue;
}
Debug2::debug2( '[Media] found iframe: ' . $attrs[ 'src' ] );
if ( ! empty( $attrs[ 'data-no-lazy' ] ) || ! empty( $attrs[ 'data-skip-lazy' ] ) || ! empty( $attrs[ 'data-lazyloaded' ] ) || ! empty( $attrs[ 'data-src' ] ) ) {
Debug2::debug2( '[Media] bypassed' );
continue;
}
if ( ! empty( $attrs[ 'class' ] ) && $hit = Utility::str_hit_array( $attrs[ 'class' ], $cls_excludes ) ) {
Debug2::debug2( '[Media] iframe lazyload cls excludes [hit] ' . $hit );
continue;
}
if ( apply_filters( 'litespeed_iframe_lazyload_exc', false, $attrs[ 'src' ] ) ) {
Debug2::debug2( '[Media] bypassed by filter' );
continue;
}
// to avoid multiple replacement
if ( in_array( $match[ 0 ], $html_list ) ) {
continue;
}
$html_list[] = $match[ 0 ];
}
return $html_list;
}
/**
* Replace image src to webp
*
* @since 1.6.2
* @access private
*/
private function _replace_buffer_img_webp( $content ) {
/**
* Added custom element & attribute support
* @since 2.2.2
*/
$webp_ele_to_check = $this->conf( Base::O_IMG_OPTM_WEBP_ATTR );
foreach ( $webp_ele_to_check as $v ) {
if ( ! $v || strpos( $v, '.' ) === false ) {
Debug2::debug2( '[Media] buffer_webp no . attribute ' . $v );
continue;
}
Debug2::debug2( '[Media] buffer_webp attribute ' . $v );
$v = explode( '.', $v );
$attr = preg_quote( $v[ 1 ], '#' );
if ( $v[ 0 ] ) {
$pattern = '#<' . preg_quote( $v[ 0 ], '#' ) . '([^>]+)' . $attr . '=([\'"])(.+)\2#iU';
}
else {
$pattern = '# ' . $attr . '=([\'"])(.+)\1#iU';
}
preg_match_all( $pattern, $content, $matches );
foreach ( $matches[ $v[ 0 ] ? 3 : 2 ] as $k2 => $url ) {
// Check if is a DATA-URI
if ( strpos( $url, 'data:image' ) !== false ) {
continue;
}
if ( ! $url2 = $this->replace_webp( $url ) ) {
continue;
}
if ( $v[ 0 ] ) {
$html_snippet = sprintf(
'<' . $v[ 0 ] . '%1$s' . $v[ 1 ] . '=%2$s',
$matches[ 1 ][ $k2 ],
$matches[ 2 ][ $k2 ] . $url2 . $matches[ 2 ][ $k2 ]
);
}
else {
$html_snippet = sprintf(
' ' . $v[ 1 ] . '=%1$s',
$matches[ 1 ][ $k2 ] . $url2 . $matches[ 1 ][ $k2 ]
);
}
$content = str_replace( $matches[ 0 ][ $k2 ], $html_snippet, $content );
}
}
// parse srcset
// todo: should apply this to cdn too
if ( ( defined( 'LITESPEED_GUEST_OPTM' ) || $this->conf( Base::O_IMG_OPTM_WEBP_REPLACE_SRCSET ) ) && $this->webp_support() ) {
$content = Utility::srcset_replace( $content, array( $this, 'replace_webp' ) );
}
// Replace background-image
if ( ( defined( 'LITESPEED_GUEST_OPTM' ) || $this->conf( Base::O_IMG_OPTM_WEBP_REPLACE ) ) && $this->webp_support() ) {
$content = $this->replace_background_webp( $content );
}
return $content;
}
/**
* Replace background image
*
* @since 4.0
*/
public function replace_background_webp( $content ) {
Debug2::debug2( '[Media] Start replacing bakcground WebP.' );
// preg_match_all( '#background-image:(\s*)url\((.*)\)#iU', $content, $matches );
preg_match_all( '#url\(([^)]+)\)#iU', $content, $matches );
foreach ( $matches[ 1 ] as $k => $url ) {
// Check if is a DATA-URI
if ( strpos( $url, 'data:image' ) !== false ) {
continue;
}
/**
* Support quotes in src `background-image: url('src')`
* @since 2.9.3
*/
$url = trim( $url, '\'"' );
if ( ! $url2 = $this->replace_webp( $url ) ) {
continue;
}
// $html_snippet = sprintf( 'background-image:%1$surl(%2$s)', $matches[ 1 ][ $k ], $url2 );
$html_snippet = str_replace( $url, $url2, $matches[ 0 ][ $k ] );
$content = str_replace( $matches[ 0 ][ $k ], $html_snippet, $content );
}
return $content;
}
/**
* Replace internal image src to webp
*
* @since 1.6.2
* @access public
*/
public function replace_webp( $url ) {
Debug2::debug2( '[Media] webp replacing: ' . substr( $url, 0, 200 ) );
if ( substr( $url, -5 ) == '.webp' ) {
Debug2::debug2( '[Media] already webp' );
return false;
}
/**
* WebP API hook
* NOTE: As $url may contain query strings, WebP check will need to parse_url before appending .webp
* @since 2.9.5
* @see #751737 - API docs for WebP generation
*/
if ( apply_filters( 'litespeed_media_check_ori', Utility::is_internal_file( $url ), $url ) ) {
// check if has webp file
if ( apply_filters( 'litespeed_media_check_webp', Utility::is_internal_file( $url, 'webp' ), $url ) ) {
$url .= '.webp';
}
else {
Debug2::debug2( '[Media] -no WebP file, bypassed' );
return false;
}
}
else {
Debug2::debug2( '[Media] -no file, bypassed' );
return false;
}
Debug2::debug2( '[Media] - replaced to: ' . $url );
return $url;
}
/**
* Hook to wp_get_attachment_image_src
*
* @since 1.6.2
* @access public
* @param array $img The URL of the attachment image src, the width, the height
* @return array
*/
public function webp_attach_img_src( $img ) {
Debug2::debug2( '[Media] changing attach src: ' . $img[0] );
if ( $img && $url = $this->replace_webp( $img[ 0 ] ) ) {
$img[ 0 ] = $url;
}
return $img;
}
/**
* Try to replace img url
*
* @since 1.6.2
* @access public
* @param string $url
* @return string
*/
public function webp_url( $url ) {
if ( $url && $url2 = $this->replace_webp( $url ) ) {
$url = $url2;
}
return $url;
}
/**
* Hook to replace WP responsive images
*
* @since 1.6.2
* @access public
* @param array $srcs
* @return array
*/
public function webp_srcset( $srcs ) {
if ( $srcs ) {
foreach ( $srcs as $w => $data ) {
if( ! $url = $this->replace_webp( $data[ 'url' ] ) ) {
continue;
}
$srcs[ $w ][ 'url' ] = $url;
}
}
return $srcs;
}
}

View File

@@ -0,0 +1,600 @@
<?php
/**
* The object cache class
*
* @since 1.8
* @package LiteSpeed
* @subpackage LiteSpeed/inc
* @author LiteSpeed Technologies <info@litespeedtech.com>
*/
namespace LiteSpeed;
defined( 'WPINC' ) || exit;
require_once dirname( __DIR__ ) . '/autoload.php';
class Object_Cache extends Root {
const O_OBJECT = 'object';
const O_OBJECT_KIND = 'object-kind';
const O_OBJECT_HOST = 'object-host';
const O_OBJECT_PORT = 'object-port';
const O_OBJECT_LIFE = 'object-life';
const O_OBJECT_PERSISTENT = 'object-persistent';
const O_OBJECT_ADMIN = 'object-admin';
const O_OBJECT_TRANSIENTS = 'object-transients';
const O_OBJECT_DB_ID = 'object-db_id';
const O_OBJECT_USER = 'object-user';
const O_OBJECT_PSWD = 'object-pswd';
const O_OBJECT_GLOBAL_GROUPS = 'object-global_groups';
const O_OBJECT_NON_PERSISTENT_GROUPS = 'object-non_persistent_groups';
private $_conn;
private $_cfg_enabled;
private $_cfg_method;
private $_cfg_host;
private $_cfg_port;
private $_cfg_persistent;
private $_cfg_admin;
private $_cfg_transients;
private $_cfg_db;
private $_cfg_user;
private $_cfg_pswd;
private $_default_life = 360;
private $_oc_driver = 'Memcached'; // Redis or Memcached
private $_global_groups = array();
private $_non_persistent_groups = array();
/**
* Init
*
* NOTE: this class may be included without initialized core
*
* @since 1.8
*/
public function __construct( $cfg = false ) {
defined( 'LSCWP_LOG' ) && Debug2::debug2( '[Object] init' );
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_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;
defined( 'LSCWP_LOG' ) && Debug2::debug( '[Object] init with cfg result : ', $this->_cfg_enabled );
}
// If OC is OFF, will hit here to init OC after conf initialized
elseif ( defined( 'LITESPEED_CONF_LOADED' ) ) {
$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_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;
}
}
/**
* Get `Store Transients` setting value
*
* @since 1.8.3
* @access public
*/
public function store_transients( $group ) {
return $this->_cfg_transients && $this->_is_transients_group( $group );
}
/**
* Check if the group belongs to transients or not
*
* @since 1.8.3
* @access private
*/
private function _is_transients_group( $group ) {
return in_array( $group, array( 'transient', 'site-transient' ) );
}
/**
* Update WP object cache file config
*
* @since 1.8
* @access public
*/
public function update_file( $options ) {
$changed = false;
// NOTE: When included in oc.php, `LSCWP_DIR` will show undefined, so this must be assigned/generated when used
$_oc_ori_file = LSCWP_DIR . 'lib/object-cache.php';
$_oc_wp_file = WP_CONTENT_DIR . '/object-cache.php';
// Update cls file
if ( ! file_exists( $_oc_wp_file ) || md5_file( $_oc_wp_file ) !== md5_file( $_oc_ori_file ) ) {
defined( 'LSCWP_LOG' ) && Debug2::debug( '[Object] copying object-cache.php file to ' . $_oc_wp_file );
copy( $_oc_ori_file, $_oc_wp_file );
$changed = true;
}
/**
* Clear object cache
*/
if ( $changed ) {
$this->_reconnect( $options );
}
}
/**
* Remove object cache file
*
* @since 1.8.2
* @access public
*/
public function del_file() {
// NOTE: When included in oc.php, `LSCWP_DIR` will show undefined, so this must be assigned/generated when used
$_oc_ori_file = LSCWP_DIR . 'lib/object-cache.php';
$_oc_wp_file = WP_CONTENT_DIR . '/object-cache.php';
if ( file_exists( $_oc_wp_file ) && md5_file( $_oc_wp_file ) === md5_file( $_oc_ori_file ) ) {
defined( 'LSCWP_LOG' ) && Debug2::debug( '[Object] removing ' . $_oc_wp_file );
unlink( $_oc_wp_file );
}
}
/**
* Try to build connection
*
* @since 1.8
* @access public
*/
public function test_connection() {
return $this->_connect();
}
/**
* Force to connect with this setting
*
* @since 1.8
* @access private
*/
private function _reconnect( $cfg ) {
defined( 'LSCWP_LOG' ) && Debug2::debug( '[Object] Reconnecting' );
// error_log( 'Object: reconnect !' );
if ( isset( $this->_conn ) ) {
// error_log( 'Object: Quiting existing connection!' );
defined( 'LSCWP_LOG' ) && Debug2::debug( '[Object] Quiting existing connection' );
$this->flush();
$this->_conn = null;
$this->cls( false, true );
}
$cls = $this->cls( false, false, $cfg );
$cls->_connect();
if ( isset( $cls->_conn ) ) {
$cls->flush();
}
}
/**
* Connect to Memcached/Redis server
*
* @since 1.8
* @access private
*/
private function _connect() {
if ( isset( $this->_conn ) ) {
// error_log( 'Object: _connected' );
return true;
}
if ( ! class_exists( $this->_oc_driver ) || ! $this->_cfg_host ) {
return null;
}
if ( defined( 'LITESPEED_OC_FAILURE' ) ) {
return false;
}
defined( 'LSCWP_LOG' ) && Debug2::debug( '[Object] connecting to ' . $this->_cfg_host . ':' . $this->_cfg_port );
$failed = false;
/**
* Connect to Redis
*
* @since 1.8.1
* @see https://github.com/phpredis/phpredis/#example-1
*/
if ( $this->_oc_driver == 'Redis' ) {
defined( 'LSCWP_LOG' ) && Debug2::debug( '[Object] Init ' . $this->_oc_driver . ' connection' );
set_error_handler( 'litespeed_exception_handler' );
try {
$this->_conn = new \Redis();
// error_log( 'Object: _connect Redis' );
if ( $this->_cfg_persistent ) {
if ( $this->_cfg_port ) {
$this->_conn->pconnect( $this->_cfg_host, $this->_cfg_port );
}
else {
$this->_conn->pconnect( $this->_cfg_host );
}
}
else {
if ( $this->_cfg_port ) {
$this->_conn->connect( $this->_cfg_host, $this->_cfg_port );
}
else {
$this->_conn->connect( $this->_cfg_host );
}
}
if ( $this->_cfg_pswd ) {
$this->_conn->auth( $this->_cfg_pswd );
}
if ( $this->_cfg_db ) {
$this->_conn->select( $this->_cfg_db );
}
$res = $this->_conn->ping();
if ( $res != '+PONG' ) {
$failed = true;
}
}
catch ( \Exception $e ) {
error_log( $e->getMessage() );
$failed = true;
}
catch ( \ErrorException $e ) {
error_log( $e->getMessage() );
$failed = true;
}
restore_error_handler();
}
/**
* Connect to Memcached
*/
else {
defined( 'LSCWP_LOG' ) && Debug2::debug( '[Object] Init ' . $this->_oc_driver . ' connection' );
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' );
defined( 'LSCWP_LOG' ) && Debug2::debug( '[Object] Got persistent ' . $this->_oc_driver . ' connection' );
return true;
}
defined( 'LSCWP_LOG' ) && Debug2::debug( '[Object] 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 ) {
defined( 'LSCWP_LOG' ) && Debug2::debug( '[Object] 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;
}
defined( 'LSCWP_LOG' ) && Debug2::debug2( '[Object] Connected' );
return true;
}
/**
* Check if the connected memcached host is the one in cfg
*
* @since 1.8
* @access private
*/
private function _validate_mem_server() {
$mem_list = $this->_conn->getStats();
if ( empty( $mem_list ) ) {
return false;
}
foreach ( $mem_list as $k => $v ) {
if ( substr( $k, 0, strlen( $this->_cfg_host ) ) != $this->_cfg_host ) {
continue;
}
if ( $v[ 'pid' ] > 0 ) {
return true;
}
}
return false;
}
/**
* Get memcached unique id to be used for connecting
*
* @since 1.8
* @access private
*/
private function _get_mem_id() {
$mem_id = 'litespeed';
if ( is_multisite() ) {
$mem_id .= '_' . get_current_blog_id();
}
return $mem_id;
}
/**
* Get cache
*
* @since 1.8
* @access public
*/
public function get( $key ) {
if ( ! $this->_cfg_enabled ) {
return null;
}
if ( ! $this->_can_cache() ) {
return null;
}
if( ! $this->_connect() ) {
return null;
}
// defined( 'LSCWP_LOG' ) && Debug2::debug2( '[Object] get ' . $key );
$res = $this->_conn->get( $key );
return $res;
}
/**
* Set cache
*
* @since 1.8
* @access public
*/
public function set( $key, $data, $expire ) {
if ( ! $this->_cfg_enabled ) {
return null;
}
/**
* To fix the Cloud callback cached as its frontend call but the hash is generated in backend
* Bug found by Stan at Jan/10/2020
*/
// if ( ! $this->_can_cache() ) {
// return null;
// }
if( ! $this->_connect() ) {
return null;
}
// defined( 'LSCWP_LOG' ) && Debug2::debug2( '[Object] set ' . $key );
// error_log( 'Object: set ' . $key );
$ttl = $expire ?: $this->_cfg_life;
if ( $this->_oc_driver == 'Redis' ) {
try {
$res = $this->_conn->setEx( $key, $ttl, $data );
} catch ( \RedisException $ex ) {
throw new \Exception( $ex->getMessage(), $ex->getCode(), $ex );
}
}
else {
$res = $this->_conn->set( $key, $data, $ttl );
}
return $res;
}
/**
* Check if can cache or not
*
* @since 1.8
* @access private
*/
private function _can_cache() {
if ( ! $this->_cfg_admin && defined( 'WP_ADMIN' ) ) {
return false;
}
return true;
}
/**
* Delete cache
*
* @since 1.8
* @access public
*/
public function delete( $key ) {
if ( ! $this->_cfg_enabled ) {
return null;
}
if( ! $this->_connect() ) {
return null;
}
// defined( 'LSCWP_LOG' ) && Debug2::debug2( '[Object] delete ' . $key );
if ( $this->_oc_driver == 'Redis' ) {
$res = $this->_conn->del( $key );
}
else {
$res = $this->_conn->delete( $key );
}
return $res;
}
/**
* Clear all cache
*
* @since 1.8
* @access public
*/
public function flush() {
if ( ! $this->_cfg_enabled ) {
defined( 'LSCWP_LOG' ) && Debug2::debug( '[Object] bypass flushing' );
return null;
}
if( ! $this->_connect() ) {
return null;
}
defined( 'LSCWP_LOG' ) && Debug2::debug( '[Object] flush!' );
if ( $this->_oc_driver == 'Redis' ) {
$res = $this->_conn->flushDb();
}
else {
$res = $this->_conn->flush();
$this->_conn->resetServerList();
}
return $res;
}
/**
* Add global groups
*
* @since 1.8
* @access public
*/
public function add_global_groups( $groups ) {
if ( ! is_array( $groups ) ) {
$groups = array( $groups );
}
$this->_global_groups = array_merge( $this->_global_groups, $groups );
$this->_global_groups = array_unique( $this->_global_groups );
}
/**
* Check if is in global groups or not
*
* @since 1.8
* @access public
*/
public function is_global( $group ) {
return in_array( $group, $this->_global_groups );
}
/**
* Add non persistent groups
*
* @since 1.8
* @access public
*/
public function add_non_persistent_groups( $groups ) {
if ( ! is_array( $groups ) ) {
$groups = array( $groups );
}
$this->_non_persistent_groups = array_merge( $this->_non_persistent_groups, $groups );
$this->_non_persistent_groups = array_unique( $this->_non_persistent_groups );
}
/**
* Check if is in non persistent groups or not
*
* @since 1.8
* @access public
*/
public function is_non_persistent( $group ) {
return in_array( $group, $this->_non_persistent_groups );
}
}

View File

@@ -0,0 +1,630 @@
<?php
/**
* LiteSpeed Object Cache Library
*
* @since 1.8
*/
defined( 'WPINC' ) || exit;
/**
* Handle exception
*/
if ( ! function_exists( 'litespeed_exception_handler' ) ) {
function litespeed_exception_handler( $errno, $errstr, $errfile, $errline ) {
throw new \ErrorException($errstr, 0, $errno, $errfile, $errline);
}
}
require_once __DIR__ . '/object-cache.cls.php';
/**
* Sets up Object Cache Global and assigns it.
*
* @since 1.8
*/
function wp_cache_init() {
$GLOBALS['wp_object_cache'] = WP_Object_Cache::get_instance();
}
/**
* Retrieves the cache contents from the cache by key and group.
*
* @since 1.8
*/
function wp_cache_get( $key, $group = '', $force = false, &$found = null ) {
global $wp_object_cache;
return $wp_object_cache->get( $key, $group, $force, $found );
}
/**
* Saves the data to the cache.
*
* @since 1.8
*/
function wp_cache_set( $key, $data, $group = '', $expire = 0 ) {
global $wp_object_cache;
return $wp_object_cache->set( $key, $data, $group, $expire );
}
/**
* Adds data to the cache, if the cache key doesn't already exist.
*
* @since 1.8
*/
function wp_cache_add( $key, $data, $group = '', $expire = 0 ) {
global $wp_object_cache;
return $wp_object_cache->add( $key, $data, $group, $expire );
}
/**
* Replaces the contents of the cache with new data.
*
* @since 1.8
*/
function wp_cache_replace( $key, $data, $group = '', $expire = 0 ) {
global $wp_object_cache;
return $wp_object_cache->replace( $key, $data, $group, $expire );
}
/**
* Increment numeric cache item's value
*
* @since 1.8
*/
function wp_cache_incr( $key, $offset = 1, $group = '' ) {
global $wp_object_cache;
return $wp_object_cache->incr_desr( $key, $offset, $group );
}
/**
* Decrements numeric cache item's value.
*
* @since 1.8
*/
function wp_cache_decr( $key, $offset = 1, $group = '' ) {
global $wp_object_cache;
return $wp_object_cache->incr_desr( $key, $offset, $group, false );
}
/**
* Removes the cache contents matching key and group.
*
* @since 1.8
*/
function wp_cache_delete( $key, $group = '' ) {
global $wp_object_cache;
return $wp_object_cache->delete( $key, $group );
}
/**
* Removes all cache items.
*
* @since 1.8
*/
function wp_cache_flush() {
global $wp_object_cache;
return $wp_object_cache->flush();
}
/**
* Adds a group or set of groups to the list of global groups.
*
* @since 1.8
*/
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
*/
function wp_cache_add_non_persistent_groups( $groups ) {
global $wp_object_cache;
$wp_object_cache->add_non_persistent_groups( $groups );
}
/**
* Switches the internal blog ID.
*
* This changes the blog id used to create keys in blog specific groups.
*
* @since 1.8
*
* @see WP_Object_Cache::switch_to_blog()
* @global WP_Object_Cache $wp_object_cache Object cache global instance.
*
* @param int $blog_id Site ID.
*/
function wp_cache_switch_to_blog( $blog_id ) {
global $wp_object_cache;
$wp_object_cache->switch_to_blog( $blog_id );
}
/**
* Closes the cache.
*
* @since 1.8
*/
function wp_cache_close() {
return true;
}
class WP_Object_Cache {
protected static $_instance;
private $_object_cache;
private $_cache = array();
private $_cache_404 = array();
private $cache_total = 0;
private $count_hit_incall = 0;
private $count_hit = 0;
private $count_miss_incall = 0;
private $count_miss = 0;
private $count_set = 0;
private $blog_prefix;
/**
* Init
*
* @since 1.8
*/
public function __construct() {
$this->_object_cache = \LiteSpeed\Object_Cache::cls();
$this->multisite = is_multisite();
$this->blog_prefix = $this->multisite ? get_current_blog_id() . ':' : '';
/**
* Fix multiple instance using same oc issue
* @since 1.8.2
*/
! defined( 'LSOC_PREFIX' ) && define( 'LSOC_PREFIX', substr( md5( __FILE__ ), -5 ) );
}
/**
* Output debug info
*
* @since 1.8
* @access public
*/
public function debug() {
$log = ' [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;
return $log;
}
/**
* Get from cache
*
* @since 1.8
* @access public
*/
public function get( $key, $group = 'default', $force = false, &$found = null ) {
$final_key = $this->_key( $key, $group );
// error_log('');
// error_log("oc: get \t\t\t[key] " . $final_key . ( $force ? "\t\t\t [forced] " : '' ) );
$found = false;
$found_in_oc = false;
$cache_val = false;
if ( array_key_exists( $final_key, $this->_cache ) && ! $force ) {
$found = true;
$cache_val = $this->_cache[ $final_key ];
$this->count_hit_incall ++;
}
elseif ( ! array_key_exists( $final_key, $this->_cache_404 ) && ! $this->_object_cache->is_non_persistent( $group ) ) {
$v = $this->_object_cache->get( $final_key );
if ( $v !== null ) {
$v = @maybe_unserialize( $v );
}
// To be compatible with false val
if ( is_array( $v ) && array_key_exists( 'data', $v ) ) {
$this->count_hit ++;
$found = true;
$found_in_oc = true;
$cache_val = $v[ 'data' ];
}
else { // Can't find key, cache it to 404
// error_log("oc: add404\t\t\t[key] " . $final_key );
$this->_cache_404[ $final_key ] = 1;
$this->count_miss ++;
}
}
else {
$this->count_miss_incall ++;
}
if ( is_object( $cache_val ) ) {
$cache_val = clone $cache_val;
}
// If not found but has `Store Transients` cfg on, still need to follow WP's get_transient() logic
if ( ! $found && $this->_object_cache->store_transients( $group ) ) {
$cache_val = $this->_transient_get( $key, $group );
if ( $cache_val ) {
$found = true; // $found not used for now (v1.8.3)
}
}
if ( $found_in_oc ) {
$this->_cache[ $final_key ] = $cache_val;
}
$this->cache_total ++;
return $cache_val;
}
/**
* Set to cache
*
* @since 1.8
* @access public
*/
public function set( $key, $data, $group = 'default', $expire = 0 ) {
$final_key = $this->_key( $key, $group );
if ( is_object( $data ) ) {
$data = clone $data;
}
// error_log("oc: set \t\t\t[key] " . $final_key );
$this->_cache[ $final_key ] = $data;
if( array_key_exists( $final_key, $this->_cache_404 ) ) {
// error_log("oc: unset404\t\t\t[key] " . $final_key );
unset( $this->_cache_404[ $final_key ] );
}
if ( ! $this->_object_cache->is_non_persistent( $group ) ) {
$this->_object_cache->set( $final_key, serialize( array( 'data' => $data ) ), $expire );
$this->count_set ++;
}
if ( $this->_object_cache->store_transients( $group ) ) {
$this->_transient_set( $key, $data, $group, $expire );
}
return true;
}
/**
* Adds data to the cache if it doesn't already exist.
*
* @since 1.8
* @access public
*/
public function add( $key, $data, $group = 'default', $expire = 0 ) {
if ( wp_suspend_cache_addition() ) {
return false;
}
$final_key = $this->_key( $key, $group );
if ( array_key_exists( $final_key, $this->_cache ) ) {
return false;
}
return $this->set( $key, $data, $group, $expire );
}
/**
* Replace cache if the cache key exists.
*
* @since 1.8
* @access public
*/
public function replace( $key, $data, $group = 'default', $expire = 0 ) {
$final_key = $this->_key( $key, $group );
if ( ! array_key_exists( $final_key, $this->_cache ) ) {
return false;
}
return $this->set( $key, $data, $group, $expire );
}
/**
* Increments numeric cache item's value.
*
* @since 1.8
* @access public
*/
public function incr_desr( $key, $offset = 1, $group = 'default', $incr = true ) {
$cache_val = $this->get( $key, $group );
if ( $cache_val === false ) {
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;
}
/**
* Delete cache
*
* @since 1.8
* @access public
*/
public function delete( $key, $group = 'default' ) {
$final_key = $this->_key( $key, $group );
if ( $this->_object_cache->store_transients( $group ) ) {
$this->_transient_del( $key, $group );
}
if ( array_key_exists( $final_key, $this->_cache ) ) {
unset( $this->_cache[ $final_key ] );
}
// error_log("oc: delete \t\t\t[key] " . $final_key );
if ( $this->_object_cache->is_non_persistent( $group ) ) {
return false;
}
return $this->_object_cache->delete( $final_key );
}
/**
* Clear all cached data
*
* @since 1.8
* @access public
*/
public function flush() {
$this->_cache = array();
$this->_cache_404 = array();
// error_log("oc: flush " );
$this->_object_cache->flush();
return true;
}
/**
* Add global groups
*
* @since 1.8
* @access public
*/
public function add_global_groups( $groups ) {
$this->_object_cache->add_global_groups( $groups );
}
/**
* Add non persistent groups
*
* @since 1.8
* @access public
*/
public function add_non_persistent_groups( $groups ) {
$this->_object_cache->add_non_persistent_groups( $groups );
}
/**
* Get the final key
*
* @since 1.8
* @access private
*/
private function _key( $key, $group = 'default' ) {
$prefix = $this->_object_cache->is_global( $group ) ? '' : $this->blog_prefix;
return LSOC_PREFIX . $prefix . $group . '.' . $key;
}
/**
* Switches the internal blog ID.
*
* This changes the blog ID used to create keys in blog specific groups.
*
* @since 1.8
*
* @param int $blog_id Blog ID.
*/
public function switch_to_blog( $blog_id ) {
$blog_id = (int) $blog_id;
$this->blog_prefix = $this->multisite ? $blog_id . ':' : '';
}
/**
* Get transient from wp table
*
* @since 1.8.3
* @access private
* @see `wp-includes/option.php` function `get_transient`/`set_site_transient`
*/
private function _transient_get( $transient, $group ) {
if ( $group == 'transient' ) {
/**** Ori WP func start ****/
$transient_option = '_transient_' . $transient;
if ( ! wp_installing() ) {
// If option is not in alloptions, it is not autoloaded and thus has a timeout
$alloptions = wp_load_alloptions();
if ( !isset( $alloptions[$transient_option] ) ) {
$transient_timeout = '_transient_timeout_' . $transient;
$timeout = get_option( $transient_timeout );
if ( false !== $timeout && $timeout < time() ) {
delete_option( $transient_option );
delete_option( $transient_timeout );
$value = false;
}
}
}
if ( ! isset( $value ) )
$value = get_option( $transient_option );
/**** Ori WP func end ****/
}
elseif ( $group == 'site-transient' ) {
/**** Ori WP func start ****/
$no_timeout = array('update_core', 'update_plugins', 'update_themes');
$transient_option = '_site_transient_' . $transient;
if ( ! in_array( $transient, $no_timeout ) ) {
$transient_timeout = '_site_transient_timeout_' . $transient;
$timeout = get_site_option( $transient_timeout );
if ( false !== $timeout && $timeout < time() ) {
delete_site_option( $transient_option );
delete_site_option( $transient_timeout );
$value = false;
}
}
if ( ! isset( $value ) )
$value = get_site_option( $transient_option );
/**** Ori WP func end ****/
}
else {
$value = false;
}
return $value;
}
/**
* Set transient to WP table
*
* @since 1.8.3
* @access private
* @see `wp-includes/option.php` function `set_transient`/`set_site_transient`
*/
private function _transient_set( $transient, $value, $group, $expiration ) {
if ( $group == 'transient' ) {
/**** Ori WP func start ****/
$transient_timeout = '_transient_timeout_' . $transient;
$transient_option = '_transient_' . $transient;
if ( false === get_option( $transient_option ) ) {
$autoload = 'yes';
if ( $expiration ) {
$autoload = 'no';
add_option( $transient_timeout, time() + $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 ( $expiration ) {
if ( false === get_option( $transient_timeout ) ) {
delete_option( $transient_option );
add_option( $transient_timeout, time() + $expiration, '', 'no' );
$result = add_option( $transient_option, $value, '', 'no' );
$update = false;
} else {
update_option( $transient_timeout, time() + $expiration );
}
}
if ( $update ) {
$result = update_option( $transient_option, $value );
}
}
/**** Ori WP func end ****/
}
elseif ( $group == 'site-transient' ) {
/**** Ori WP func start ****/
$transient_timeout = '_site_transient_timeout_' . $transient;
$option = '_site_transient_' . $transient;
if ( false === get_site_option( $option ) ) {
if ( $expiration )
add_site_option( $transient_timeout, time() + $expiration );
$result = add_site_option( $option, $value );
} else {
if ( $expiration )
update_site_option( $transient_timeout, time() + $expiration );
$result = update_site_option( $option, $value );
}
/**** Ori WP func end ****/
}
else {
$result = null;
}
return $result;
}
/**
* Delete transient from WP table
*
* @since 1.8.3
* @access private
* @see `wp-includes/option.php` function `delete_transient`/`delete_site_transient`
*/
private function _transient_del( $transient, $group ) {
if ( $group == 'transient' ) {
/**** Ori WP func start ****/
$option_timeout = '_transient_timeout_' . $transient;
$option = '_transient_' . $transient;
$result = delete_option( $option );
if ( $result )
delete_option( $option_timeout );
/**** Ori WP func end ****/
}
elseif ( $group == 'site-transient' ) {
/**** Ori WP func start ****/
$option_timeout = '_site_transient_timeout_' . $transient;
$option = '_site_transient_' . $transient;
$result = delete_site_option( $option );
if ( $result )
delete_site_option( $option_timeout );
/**** Ori WP func end ****/
}
}
/**
* Get the current instance object.
*
* @since 1.8
* @access public
*/
public static function get_instance() {
if ( ! isset( self::$_instance ) ) {
self::$_instance = new self();
}
return self::$_instance;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,292 @@
<?php
/**
* The optimize4 class.
*
* @since 1.9
* @package LiteSpeed
* @subpackage LiteSpeed/inc
* @author LiteSpeed Technologies <info@litespeedtech.com>
*/
namespace LiteSpeed;
defined( 'WPINC' ) || exit;
class Optimizer extends Root {
private $_conf_css_font_display;
/**
* Init optimizer
*
* @since 1.9
*/
public function __construct() {
$this->_conf_css_font_display = $this->conf( Base::O_OPTM_CSS_FONT_DISPLAY );
}
/**
* Run HTML minify process and return final content
*
* @since 1.9
* @access public
*/
public function html_min( $content, $force_inline_minify = false ) {
$options = array();
if ( $force_inline_minify ) {
$options[ 'jsMinifier' ] = __CLASS__ . '::minify_js';
}
/**
* Added exception capture when minify
* @since 2.2.3
*/
try {
$obj = new Lib\HTML_MIN( $content, $options );
$content_final = $obj->process();
if ( ! defined( 'LSCACHE_ESI_SILENCE' ) ) {
$content_final .= "\n" . '<!-- Page optimized by LiteSpeed Cache @' . date('Y-m-d H:i:s') . ' -->';
}
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( 'CSS' )->load_ucss( $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 );
$static_file = LITESPEED_STATIC_DIR . $file_path_prefix . ( is_404() ? '404' : md5( $request_url ) ) . '.' . $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 );
}
// 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( is_404() ? '404' : $request_url, $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_REPLACE ) ) && $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/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;
$res = wp_remote_get( $this_url );
$res_code = wp_remote_retrieve_response_code( $res );
if ( is_wp_error( $res ) || $res_code == 404 ) {
Debug2::debug2( '[CSS] ❌ Load Remote error [code] ' . $res_code );
return false;
}
$con = wp_remote_retrieve_body( $res );
if ( ! $con ) {
return false;
}
if ( $file_type == 'css' ) {
$dirname = dirname( $this_url ) . '/';
$con = Lib\CSS_MIN\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\CSS_MIN\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_MIN\Minifier();
return $obj->run( $data );
} 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 {
$data = Lib\JSMin::minify( $data );
return $data;
} 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;
}
}

View File

@@ -0,0 +1,543 @@
<?php
/**
* The PlaceHolder class
*
* @since 3.0
* @package LiteSpeed
* @subpackage LiteSpeed/inc
* @author LiteSpeed Technologies <info@litespeedtech.com>
*/
namespace LiteSpeed;
defined( 'WPINC' ) || exit;
class Placeholder extends Base {
const TYPE_GENERATE = 'generate';
const TYPE_CLEAR_Q = 'clear_q';
private $_conf_placeholder_resp;
private $_conf_placeholder_resp_svg;
private $_conf_lqip;
private $_conf_lqip_qual;
private $_conf_lqip_min_w;
private $_conf_lqip_min_h;
private $_conf_placeholder_resp_color;
private $_conf_placeholder_resp_async;
private $_placeholder_resp_dict = array();
private $_ph_queue = array();
protected $_summary;
/**
* Init
*
* @since 3.0
*/
public function __construct() {
$this->_conf_placeholder_resp = defined( 'LITESPEED_GUEST_OPTM' ) || $this->conf( self::O_MEDIA_PLACEHOLDER_RESP );
$this->_conf_placeholder_resp_svg = $this->conf( self::O_MEDIA_PLACEHOLDER_RESP_SVG );
$this->_conf_lqip = ! defined( 'LITESPEED_GUEST_OPTM' ) && $this->conf( self::O_MEDIA_LQIP );
$this->_conf_lqip_qual = $this->conf( self::O_MEDIA_LQIP_QUAL );
$this->_conf_lqip_min_w = $this->conf( self::O_MEDIA_LQIP_MIN_W );
$this->_conf_lqip_min_h = $this->conf( self::O_MEDIA_LQIP_MIN_H );
$this->_conf_placeholder_resp_async = $this->conf( self::O_MEDIA_PLACEHOLDER_RESP_ASYNC );
$this->_conf_placeholder_resp_color = $this->conf( self::O_MEDIA_PLACEHOLDER_RESP_COLOR );
$this->_conf_ph_default = $this->conf( self::O_MEDIA_LAZY_PLACEHOLDER ) ?: LITESPEED_PLACEHOLDER;
$this->_summary = self::get_summary();
}
/**
* Init Placeholder
*/
public function init() {
Debug2::debug2( '[LQIP] init' );
add_action( 'litspeed_after_admin_init', array( $this, 'after_admin_init' ) );
}
/**
* Display column in Media
*
* @since 3.0
* @access public
*/
public function after_admin_init() {
if ( $this->_conf_lqip ) {
add_filter( 'manage_media_columns', array( $this, 'media_row_title' ) );
add_filter( 'manage_media_custom_column', array( $this, 'media_row_actions' ), 10, 2 );
add_action( 'litespeed_media_row_lqip', array( $this, 'media_row_con' ) );
}
}
/**
* Media Admin Menu -> LQIP col
*
* @since 3.0
* @access public
*/
public function media_row_title( $posts_columns ) {
$posts_columns[ 'lqip' ] = __( 'LQIP', 'litespeed-cache' );
return $posts_columns;
}
/**
* Media Admin Menu -> LQIP Column
*
* @since 3.0
* @access public
*/
public function media_row_actions( $column_name, $post_id ) {
if ( $column_name !== 'lqip' ) {
return;
}
do_action( 'litespeed_media_row_lqip', $post_id );
}
/**
* Display LQIP column
*
* @since 3.0
* @access public
*/
public function media_row_con( $post_id ) {
$meta_value = wp_get_attachment_metadata( $post_id );
if ( empty( $meta_value[ 'file' ] ) ) {
return;
}
$total_files = 0;
// List all sizes
$all_sizes = array( $meta_value[ 'file' ] );
$size_path = pathinfo( $meta_value[ 'file' ], PATHINFO_DIRNAME ) . '/';
foreach ( $meta_value[ 'sizes' ] as $v ) {
$all_sizes[] = $size_path . $v[ 'file' ];
}
foreach ( $all_sizes as $short_path ) {
$lqip_folder = LITESPEED_STATIC_DIR . '/lqip/' . $short_path;
if ( is_dir( $lqip_folder ) ) {
Debug2::debug( '[LQIP] Found folder: ' . $short_path );
// List all files
foreach ( scandir( $lqip_folder ) as $v ) {
if ( $v == '.' || $v == '..' ) {
continue;
}
if ( $total_files == 0 ) {
echo '<div class="litespeed-media-lqip"><img src="' . 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="' . 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="' . $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="' . $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
if ( substr( $src, -5 ) === '.webp' ) {
$src = substr( $src, 0, -5 );
}
$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 ( Repeatly 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' => substr( $src, -5 ) === '.webp' ? substr( $src, 0, -5 ) : $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

View File

@@ -0,0 +1,241 @@
<?php
/**
* The report class
*
*
* @since 1.1.0
* @package LiteSpeed
* @subpackage LiteSpeed/src
* @author LiteSpeed Technologies <info@litespeedtech.com>
*/
namespace LiteSpeed;
defined( 'WPINC' ) || exit;
class Report extends Base {
const TYPE_SEND_REPORT = 'send_report';
/**
* Handle all request actions from main cls
*
* @since 1.6.5
* @access public
*/
public function handler() {
$type = Router::verify_type();
switch ( $type ) {
case self::TYPE_SEND_REPORT:
$this->post_env();
break;
default:
break;
}
Admin::redirect();
}
/**
* post env report number to ls center server
*
* @since 1.6.5
* @access public
*/
public function post_env()
{
$report_con = $this->generate_environment_report();
// Generate link
$link = ! empty( $_POST[ 'link' ] ) ? esc_url( $_POST[ 'link' ] ) : '';
$notes = ! empty( $_POST[ 'notes' ] ) ? esc_html( $_POST[ 'notes' ] ) : '';
$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 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();
if ( is_null($options) ) {
$options = $this->get_options( true );
if ( is_multisite() ) {
$options2 = $this->get_options();
foreach ( $options2 as $k => $v ) {
if ( $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 );
}
$buf .= $v;
}
return $buf . "\n\n";
}
}

View File

@@ -0,0 +1,269 @@
<?php
/**
* The REST related class.
*
* @since 2.9.4
*/
namespace LiteSpeed;
defined( 'WPINC' ) || exit;
class REST extends Root {
private $_internal_rest_status = false;
/**
* Confructor of ESI
*
* @since 2.9.4
*/
public function __construct() {
// Hook to internal REST call
add_filter( 'rest_request_before_callbacks', array( $this, 'set_internal_rest_on' ) );
add_filter( 'rest_request_after_callbacks', array( $this, 'set_internal_rest_off' ) );
add_action( 'rest_api_init', array( $this, 'rest_api_init' ) );
}
/**
* Register REST hooks
*
* @since 3.0
* @access public
*/
public function rest_api_init() {
// Activate or deactivate a specific crawler callback
register_rest_route( 'litespeed/v1', '/toggle_crawler_state', array(
'methods' => 'POST',
'callback' => array( $this, 'toggle_crawler_state' ),
'permission_callback' => '__return_true',
) );
register_rest_route( 'litespeed/v1', '/tool/check_ip', array(
'methods' => 'GET',
'callback' => array( $this, 'check_ip' ),
'permission_callback' => function() {
return current_user_can( 'manage_network_options' ) || current_user_can( 'manage_options' );
}
) );
// IP callback validate
register_rest_route( 'litespeed/v1', '/ip_validate', array(
'methods' => 'POST',
'callback' => array( $this, 'ip_validate' ),
'permission_callback' => array( $this, 'is_from_cloud' ),
) );
// Token callback validate
register_rest_route( 'litespeed/v1', '/token', array(
'methods' => 'POST',
'callback' => array( $this, 'token' ),
'permission_callback' => array( $this, 'is_from_cloud' ),
) );
register_rest_route( 'litespeed/v1', '/token', array(
'methods' => 'GET',
'callback' => array( $this, 'token_get' ),
'permission_callback' => array( $this, 'is_from_cloud' ),
) );
register_rest_route( 'litespeed/v1', '/ping', array(
'methods' => 'GET',
'callback' => array( $this, 'ping' ),
'permission_callback' => '__return_true',
) );
// API key callback notification
register_rest_route( 'litespeed/v1', '/apikey', array(
'methods' => 'POST',
'callback' => array( $this, 'apikey' ),
'permission_callback' => array( $this, 'is_from_cloud' ),
) );
// Image optm notify_img
// Need validation
register_rest_route( 'litespeed/v1', '/notify_img', array(
'methods' => 'POST',
'callback' => array( $this, 'notify_img' ),
'permission_callback' => array( $this, 'is_from_cloud' ),
) );
// Image optm check_img
// Need validation
register_rest_route( 'litespeed/v1', '/check_img', array(
'methods' => 'POST',
'callback' => array( $this, 'check_img' ),
'permission_callback' => array( $this, 'is_from_cloud' ),
) );
}
/**
* Call to freeze or melt the crawler clicked
*
* @since 4.3
*/
public function toggle_crawler_state() {
if( isset( $_POST[ 'crawler_id' ] ) ) {
return $this->cls( 'Crawler' )->toggle_activeness( $_POST[ 'crawler_id' ] ) ? 1 : 0;
}
}
/**
* Check if the request is from cloud nodes
*
* @since 4.2
* @since 4.4.7 As there is always token/api key validation, ip validation is redundant
*/
public function is_from_cloud() {
return true;
// return $this->cls( 'Cloud' )->is_from_cloud();
}
/**
* Token get for
*
* @since 3.0.4
*/
public function token_get() {
return Cloud::ok();
}
/**
* Ping pong
*
* @since 3.0.4
*/
public function ping() {
return Cloud::ok( array( 'ver' => Core::VER ) );
}
/**
* Launch api call
*
* @since 3.0
*/
public function check_ip() {
return Tool::cls()->check_ip();
}
/**
* Launch api call
*
* @since 3.0
*/
public function ip_validate() {
return $this->cls( 'Cloud' )->ip_validate();
}
/**
* Launch api call
*
* @since 3.0
*/
public function token() {
return $this->cls( 'Cloud' )->token_validate();
}
/**
* Launch api call
*
* @since 3.0
*/
public function apikey() {
return $this->cls( 'Cloud' )->save_apikey();
}
/**
* Launch api call
*
* @since 3.0
*/
public function notify_img() {
return Img_Optm::cls()->notify_img();
}
/**
* Launch api call
*
* @since 3.0
*/
public function check_img() {
return Img_Optm::cls()->check_img();
}
/**
* Set internal REST tag to ON
*
* @since 2.9.4
* @access public
*/
public function set_internal_rest_on( $not_used = null )
{
$this->_internal_rest_status = true;
Debug2::debug2( '[REST] ✅ Internal REST ON [filter] rest_request_before_callbacks' );
return $not_used;
}
/**
* Set internal REST tag to OFF
*
* @since 2.9.4
* @access public
*/
public function set_internal_rest_off( $not_used = null )
{
$this->_internal_rest_status = false;
Debug2::debug2( '[REST] ❎ Internal REST OFF [filter] rest_request_after_callbacks' );
return $not_used;
}
/**
* Get internal REST tag
*
* @since 2.9.4
* @access public
*/
public function is_internal_rest()
{
return $this->_internal_rest_status;
}
/**
* Check if an URL or current page is REST req or not
*
* @since 2.9.3
* @since 2.9.4 Moved here from Utility, dropped static
* @access public
*/
public function is_rest( $url = false )
{
// For WP 4.4.0- compatibility
if ( ! function_exists( 'rest_get_url_prefix' ) ) {
return defined( 'REST_REQUEST' ) && REST_REQUEST;
}
$prefix = rest_get_url_prefix();
// Case #1: After WP_REST_Request initialisation
if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) {
return true;
}
// Case #2: Support "plain" permalink settings
if ( isset( $_GET[ 'rest_route' ] ) && strpos( trim( $_GET[ 'rest_route' ], '\\/' ), $prefix , 0 ) === 0 ) {
return true;
}
if ( ! $url ) {
return false;
}
// Case #3: URL Path begins with wp-json/ (REST prefix) Safe for subfolder installation
$rest_url = wp_parse_url( site_url( $prefix ) );
$current_url = wp_parse_url( $url );
// Debug2::debug( '[Util] is_rest check [base] ', $rest_url );
// Debug2::debug( '[Util] is_rest check [curr] ', $current_url );
// Debug2::debug( '[Util] is_rest check [curr2] ', wp_parse_url( add_query_arg( array( ) ) ) );
return strpos( $current_url[ 'path' ], $rest_url[ 'path' ] ) === 0;
}
}

View File

@@ -0,0 +1,582 @@
<?php
/**
* The abstract instance
*
* @since 3.0
*/
namespace LiteSpeed;
defined( 'WPINC' ) || exit;
abstract class Root {
const CONF_FILE = '.litespeed_conf.dat';
// Instance set
private static $_instances;
private static $_options = array();
private static $_const_options = array();
private static $_primary_options = array();
private static $_network_options = array();
/**
* 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
$this->_summary = array();
self::save_summary();
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 ) {
$filepath_prefix = $this->_build_filepath_prefix( $type );
$static_path = LITESPEED_STATIC_DIR . $filepath_prefix . '.litespeed_conf.dat';
if ( file_exists( $static_path ) ) {
unlink( $static_path );
}
$msg = __( 'Queue cleared successfully.', 'litespeed-cache' );
Admin_Display::succeed( $msg );
}
/**
* Load an instance or create it if not existed
* @since 4.0
*/
public static function cls( $cls = false, $unset = false, $data = false ) {
if ( ! $cls ) {
$cls = self::ori_cls();
}
$cls = __NAMESPACE__ . '\\' . $cls;
$cls_tag = strtolower( $cls );
if ( ! isset( self::$_instances[ $cls_tag ] ) ) {
if ( $unset ) {
return;
}
self::$_instances[ $cls_tag ] = new $cls( $data );
}
else {
if ( $unset ) {
unset( self::$_instances[ $cls_tag ] );
return;
}
}
return self::$_instances[ $cls_tag ];
}
/**
* Set one conf or confs
*/
public function set_conf( $id, $val = null ) {
if ( is_array( $id ) ) {
foreach ( $id as $k => $v ) {
$this->set_conf( $k, $v );
}
return;
}
self::$_options[ $id ] = $val;
}
/**
* Set one primary conf or confs
*/
public function set_primary_conf( $id, $val = null ) {
if ( is_array( $id ) ) {
foreach ( $id as $k => $v ) {
$this->set_primary_conf( $k, $v );
}
return;
}
self::$_primary_options[ $id ] = $val;
}
/**
* Set one network conf
*/
public function set_network_conf( $id, $val = null ) {
if ( is_array( $id ) ) {
foreach ( $id as $k => $v ) {
$this->set_network_conf( $k, $v );
}
return;
}
self::$_network_options[ $id ] = $val;
}
/**
* Set one const conf
*/
public function set_const_conf( $id, $val ) {
self::$_const_options[ $id ] = $val;
}
/**
* Check if is overwritten by const
*
* @since 3.0
*/
public function const_overwritten( $id ) {
if ( ! isset( self::$_const_options[ $id ] ) || self::$_const_options[ $id ] == self::$_options[ $id ] ) {
return null;
}
return self::$_const_options[ $id ];
}
/**
* Check if is overwritten by primary site
*
* @since 3.2.2
*/
public function primary_overwritten( $id ) {
if ( ! isset( self::$_primary_options[ $id ] ) || self::$_primary_options[ $id ] == self::$_options[ $id ] ) {
return null;
}
// Network admin settings is impossible to be overwritten by primary
if ( is_network_admin() ) {
return null;
}
return self::$_primary_options[ $id ];
}
/**
* Get the list of configured options for the blog.
*
* @since 1.0
*/
public function get_options( $ori = false ) {
if ( ! $ori ) {
return array_merge( self::$_options, self::$_primary_options, self::$_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 orignal value will be in _network_options
if ( ! is_network_admin() || ! $this->has_network_conf( $id ) ) {
return self::$_options[ $id ];
}
}
if ( $this->has_network_conf( $id ) ) {
if ( ! $ori ) {
$val = $this->const_overwritten( $id );
if ( $val !== null ) {
defined( 'LSCWP_LOG' ) && Debug2::debug( '[Conf] 🏛️ const option ' . $id . '=' . var_export( $val, true ) );
return $val;
}
}
return $this->network_conf( $id );
}
defined( 'LSCWP_LOG' ) && Debug2::debug( '[Conf] Invalid option ID ' . $id );
return null;
}
/**
* Get primary conf
*/
public function primary_conf( $id ) {
return self::$_primary_options[ $id ];
}
/**
* Get network conf
*/
public function network_conf( $id ) {
if ( ! $this->has_network_conf( $id ) ) {
return null;
}
return self::$_network_options[ $id ];
}
/**
* Get called class short name
*/
public static function ori_cls() {
$cls = new \ReflectionClass( get_called_class() );
$shortname = $cls->getShortName();
$namespace = str_replace( __NAMESPACE__ . '\\', '', $cls->getNamespaceName() . '\\' );
if ( $namespace ) { // the left namespace after dropped LiteSpeed
$shortname = $namespace . $shortname;
}
return $shortname;
}
/**
* Generate conf name for wp_options record
*
* @since 3.0
*/
public static function name( $id ) {
$name = strtolower( self::ori_cls() );
if ( $name == 'conf2' ) { // For a certain 3.7rc correction, can be dropped after v4
$name = 'conf';
}
return 'litespeed.' . $name . '.' . $id;
}
/**
* Dropin with prefix for WP's get_option
*
* @since 3.0
*/
public static function get_option( $id, $default_v = false ) {
$v = get_option( self::name( $id ), $default_v );
// Maybe decode array
if ( is_array( $default_v ) ) {
$v = self::_maybe_decode( $v );
}
return $v;
}
/**
* Dropin with prefix for WP's get_site_option
*
* @since 3.0
*/
public static function get_site_option( $id, $default_v = false ) {
$v = get_site_option( self::name( $id ), $default_v );
// Maybe decode array
if ( is_array( $default_v ) ) {
$v = self::_maybe_decode( $v );
}
return $v;
}
/**
* Dropin with prefix for WP's get_blog_option
*
* @since 3.0
*/
public static function get_blog_option( $blog_id, $id, $default_v = false ) {
$v = get_blog_option( $blog_id, self::name( $id ), $default_v );
// Maybe decode array
if ( is_array( $default_v ) ) {
$v = self::_maybe_decode( $v );
}
return $v;
}
/**
* Dropin with prefix for WP's add_option
*
* @since 3.0
*/
public static function add_option( $id, $v ) {
add_option( self::name( $id ), self::_maybe_encode( $v ) );
}
/**
* Dropin with prefix for WP's add_site_option
*
* @since 3.0
*/
public static function add_site_option( $id, $v ) {
add_site_option( self::name( $id ), self::_maybe_encode( $v ) );
}
/**
* Dropin with prefix for WP's update_option
*
* @since 3.0
*/
public static function update_option( $id, $v ) {
update_option( self::name( $id ), self::_maybe_encode( $v ) );
}
/**
* Dropin with prefix for WP's update_site_option
*
* @since 3.0
*/
public static function update_site_option( $id, $v ) {
update_site_option( self::name( $id ), self::_maybe_encode( $v ) );
}
/**
* Decode an array
*
* @since 4.0
*/
private static function _maybe_decode( $v ) {
if ( ! is_array( $v ) ) {
$v2 = json_decode( $v, true );
if ( $v2 !== null ) {
$v = $v2;
}
}
return $v;
}
/**
* Encode an array
*
* @since 4.0
*/
private static function _maybe_encode( $v ) {
if ( is_array( $v ) ) {
$v = json_encode( $v ) ?: $v; // Non utf-8 encoded value will get failed, then used ori value
}
return $v;
}
/**
* Dropin with prefix for WP's delete_option
*
* @since 3.0
*/
public static function delete_option( $id ) {
delete_option( self::name( $id ) );
}
/**
* Dropin with prefix for WP's delete_site_option
*
* @since 3.0
*/
public static function delete_site_option( $id ) {
delete_site_option( self::name( $id ) );
}
/**
* Read summary
*
* @since 3.0
* @access public
*/
public static function get_summary( $field = false ) {
$summary = self::get_option( '_summary', array() );
if ( ! is_array( $summary ) ) {
$summary = array();
}
if ( ! $field ) {
return $summary;
}
if ( array_key_exists( $field, $summary ) ) {
return $summary[ $field ];
}
return null;
}
/**
* Save summary
*
* @since 3.0
* @access public
*/
public static function save_summary( $data = null ) {
if ( $data === null ) {
$data = static::cls()->_summary;
}
self::update_option( '_summary', $data );
}
/**
* Get the current instance object. To be inherited.
*
* @since 3.0
*/
public static function get_instance() {
return static::cls();
}
}

View File

@@ -0,0 +1,739 @@
<?php
/**
* The core plugin router class.
*
* This generate the valid action.
*
* @since 1.1.0
* @since 1.5 Moved into /inc
*/
namespace LiteSpeed;
defined( 'WPINC' ) || exit;
class Router extends Base {
const 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_IMPORT = 'import';
const ACTION_REPORT = 'report';
const ACTION_DEBUG2 = 'debug2';
const ACTION_CDN_CLOUDFLARE = 'CDN\Cloudflare';
// List all handlers here
private static $_HANDLERS = array(
self::ACTION_ACTIVATION,
self::ACTION_AVATAR,
self::ACTION_CDN_CLOUDFLARE,
self::ACTION_CLOUD,
self::ACTION_CONF,
self::ACTION_CRAWLER,
self::ACTION_CSS,
self::ACTION_DB_OPTM,
self::ACTION_DEBUG2,
self::ACTION_HEALTH,
self::ACTION_IMG_OPTM,
self::ACTION_IMPORT,
self::ACTION_PLACEHOLDER,
self::ACTION_PURGE,
self::ACTION_REPORT,
);
const TYPE = 'litespeed_type';
const ITEM_HASH = 'hash';
private static $_esi_enabled;
private static $_is_ajax;
private static $_is_logged_in;
private static $_ip;
private static $_action;
private static $_is_admin_ip;
private static $_frontend_path;
/**
* Redirect to self to continue operation
*
* Note: must return when use this func. CLI/Cron call won't die in this func.
*
* @since 3.0
* @access public
*/
public static function self_redirect( $action, $type ) {
if ( defined( 'LITESPEED_CLI' ) || defined( 'DOING_CRON' ) ) {
Admin_Display::succeed( 'To be continued' ); // Show for CLI
return;
}
// Add i to avoid browser too many redirected warning
$i = ! empty( $_GET[ 'litespeed_i' ] ) ? $_GET[ 'litespeed_i' ] : 0;
$i ++;
$link = Utility::build_url( $action, $type, false, null, array( 'litespeed_i' => $i ) );
$url = html_entity_decode( $link );
exit( "<meta http-equiv='refresh' content='0;url=$url'>" );
}
/**
* Check if can run optimize
*
* @since 1.3
* @since 2.3.1 Relocated from cdn.cls
* @access public
*/
public function can_optm() {
$can = true;
if ( is_user_logged_in() && $this->conf( self::O_OPTM_GUEST_ONLY ) ) {
$can = false;
}
elseif ( is_admin() ) {
$can = false;
}
elseif ( is_feed() ) {
$can = false;
}
elseif ( is_preview() ) {
$can = false;
}
elseif ( self::is_ajax() ) {
$can = false;
}
if ( self::_is_login_page() ) {
Debug2::debug( '[Router] Optm bypassed: login/reg page' );
$can = false;
}
$can_final = apply_filters( 'litespeed_can_optm', $can );
if ( $can_final != $can ) {
Debug2::debug( '[Router] Optm bypassed: filter' );
}
return $can_final;
}
/**
* Check referer page to see if its from admin
*
* @since 2.4.2.1
* @access public
*/
public static function from_admin() {
return ! empty( $_SERVER[ 'HTTP_REFERER' ] ) && strpos( $_SERVER[ 'HTTP_REFERER' ], get_admin_url() ) === 0;
}
/**
* Check if it can use CDN replacement
*
* @since 1.2.3
* @since 2.3.1 Relocated from cdn.cls
* @access public
*/
public static function can_cdn() {
$can = true;
if ( is_admin() ) {
if ( ! self::is_ajax() ) {
Debug2::debug2( '[Router] CDN bypassed: is not ajax call' );
$can = false;
}
if ( self::from_admin() ) {
Debug2::debug2( '[Router] CDN bypassed: ajax call from admin' );
$can = false;
}
}
elseif ( is_feed() ) {
$can = false;
}
elseif ( is_preview() ) {
$can = false;
}
/**
* Bypass cron to avoid deregister jq notice `Do not deregister the <code>jquery-core</code> script in the administration area.`
* @since 2.7.2
*/
if ( defined( 'DOING_CRON' ) ) {
$can = false;
}
/**
* Bypass login/reg page
* @since 1.6
*/
if ( self::_is_login_page() ) {
Debug2::debug( '[Router] CDN bypassed: login/reg page' );
$can = false;
}
/**
* Bypass post/page link setting
* @since 2.9.8.5
*/
$rest_prefix = function_exists( 'rest_get_url_prefix' ) ? rest_get_url_prefix() : apply_filters( 'rest_url_prefix', 'wp-json' );
if (
! empty( $_SERVER[ 'REQUEST_URI' ] ) &&
strpos( $_SERVER[ 'REQUEST_URI' ], $rest_prefix . '/wp/v2/media' ) !== false &&
isset( $_SERVER[ 'HTTP_REFERER' ] ) && strpos( $_SERVER[ 'HTTP_REFERER' ], 'wp-admin') !== false
) {
Debug2::debug( '[Router] CDN bypassed: wp-json on admin page' );
$can = false;
}
$can_final = apply_filters( 'litespeed_can_cdn', $can );
if ( $can_final != $can ) {
Debug2::debug( '[Router] CDN bypassed: filter' );
}
return $can_final;
}
/**
* Check if is login page or not
*
* @since 2.3.1
* @access protected
*/
protected static function _is_login_page() {
if ( in_array( $GLOBALS[ 'pagenow' ], array( 'wp-login.php', 'wp-register.php' ), true ) ) {
return true;
}
return false;
}
/**
* UCSS/Crawler role simulator
*
* @since 1.9.1
* @since 3.3 Renamed from `is_crawler_role_simulation`
*/
public function is_role_simulation() {
if( is_admin() ) {
return;
}
if ( empty( $_COOKIE[ 'litespeed_role' ] ) || empty( $_COOKIE[ 'litespeed_hash' ] ) ) {
return;
}
Debug2::debug( '[Router] 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;
// }
// Hash validation
$hash = self::get_option( self::ITEM_HASH );
if ( ! $hash || $_COOKIE[ 'litespeed_hash' ] != $hash ) {
Debug2::debug( '[Router] hash not match ' . $_COOKIE[ 'litespeed_hash' ] . ' != ' . $hash );
return;
}
$role_uid = $_COOKIE[ 'litespeed_role' ];
Debug2::debug( '[Router] role simulate litespeed_role uid ' . $role_uid );
wp_set_current_user( $role_uid );
}
/**
* Get a security hash
*
* @since 3.3
*/
public static function get_hash() {
// Reuse previous hash if existed
$hash = self::get_option( self::ITEM_HASH );
if ( $hash ) {
return $hash;
}
$hash = Str::rrand( 6 );
self::update_option( self::ITEM_HASH, $hash );
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 = array_shift( $tmp );
}
}
Debug2::debug( '[Router] get_role: ' . $role );
if ( ! $role ) {
return $role;
// Guest user
Debug2::debug( '[Router] role: guest' );
/**
* Fix double login issue
* The previous user init refactoring didn't fix this bcos this is in login process and the user role could change
* @see https://github.com/litespeedtech/lscache_wp/commit/69e7bc71d0de5cd58961bae953380b581abdc088
* @since 2.9.8 Won't assign const if in login process
*/
if ( substr_compare( wp_login_url(), $GLOBALS[ 'pagenow' ], -strlen( $GLOBALS[ 'pagenow' ] ) ) === 0 ) {
return $role;
}
}
define( 'LITESPEED_WP_ROLE', $role );
return LITESPEED_WP_ROLE;
}
/**
* Get frontend path
*
* @since 1.2.2
* @access public
* @return boolean
*/
public static function frontend_path() { //todo: move to htaccess.cls ?
if ( ! isset( self::$_frontend_path ) ) {
$frontend = rtrim( ABSPATH, '/' ); // /home/user/public_html/frontend
// get home path failed. Trac ticket #37668 (e.g. frontend:/blog backend:/wordpress)
if ( ! $frontend ) {
Debug2::debug( '[Router] No ABSPATH, generating from home option' );
$frontend = parse_url( get_option( 'home' ) );
$frontend = ! empty( $frontend[ 'path' ] ) ? $frontend[ 'path' ] : '';
$frontend = $_SERVER[ 'DOCUMENT_ROOT' ] . $frontend;
}
$frontend = realpath( $frontend );
self::$_frontend_path = $frontend;
}
return self::$_frontend_path;
}
/**
* Check if ESI is enabled or not
*
* @since 1.2.0
* @access public
* @return boolean
*/
public function esi_enabled() {
if ( ! isset( self::$_esi_enabled ) ) {
self::$_esi_enabled = defined( 'LITESPEED_ON' ) && $this->conf( self::O_ESI );
if( ! empty( $_REQUEST[ self::ACTION ] ) ) {
self::$_esi_enabled = false;
}
}
return self::$_esi_enabled;
}
/**
* Check if crawler is enabled on server level
*
* @since 1.1.1
* @access public
*/
public static function can_crawl() {
if ( isset( $_SERVER[ 'X-LSCACHE' ] ) && strpos( $_SERVER[ 'X-LSCACHE' ], 'crawler' ) === false ) {
return false;
}
// CLI will bypass this check as crawler library can always do the 428 check
if ( defined( 'LITESPEED_CLI' ) ) {
return true;
}
return true;
}
/**
* Check action
*
* @since 1.1.0
* @access public
* @return string
*/
public static function get_action() {
if ( ! isset( self::$_action ) ) {
self::$_action = false;
self::cls()->verify_action();
if ( self::$_action ) {
defined( 'LSCWP_LOG' ) && Debug2::debug( '[Router] LSCWP_CTRL verified: ' . var_export( self::$_action, true ) );
}
}
return self::$_action;
}
/**
* Check if is logged in
*
* @since 1.1.3
* @access public
* @return boolean
*/
public static function is_logged_in() {
if ( ! isset( self::$_is_logged_in ) ) {
self::$_is_logged_in = is_user_logged_in();
}
return self::$_is_logged_in;
}
/**
* Check if is ajax call
*
* @since 1.1.0
* @access public
* @return boolean
*/
public static function is_ajax() {
if ( ! isset( self::$_is_ajax ) ) {
self::$_is_ajax = defined( 'DOING_AJAX' ) && DOING_AJAX;
}
return self::$_is_ajax;
}
/**
* Check if is admin ip
*
* @since 1.1.0
* @access public
* @return boolean
*/
public function is_admin_ip() {
if ( ! isset( self::$_is_admin_ip ) ) {
$ips = $this->conf( self::O_DEBUG_IPS );
self::$_is_admin_ip = $this->ip_access( $ips );
}
return self::$_is_admin_ip;
}
/**
* Get type value
*
* @since 1.6
* @access public
*/
public static function verify_type() {
if ( empty( $_REQUEST[ self::TYPE ] ) ) {
Debug2::debug( '[Router] no type', 2 );
return false;
}
Debug2::debug( '[Router] parsed type: ' . $_REQUEST[ self::TYPE ], 2 );
return $_REQUEST[ self::TYPE ];
}
/**
* Check privilege and nonce for the action
*
* @since 1.1.0
* @access private
*/
private function verify_action() {
if ( empty( $_REQUEST[ Router::ACTION ] ) ) {
Debug2::debug2( '[Router] LSCWP_CTRL bypassed empty' );
return;
}
$action = stripslashes($_REQUEST[ Router::ACTION ]);
if ( ! $action ) {
return;
}
$_is_public_action = false;
// Each action must have a valid nonce unless its from admin ip and is public action
// Validate requests nonce (from admin logged in page or cli)
if ( ! $this->verify_nonce( $action ) ) {
// check if it is from admin ip
if ( ! $this->is_admin_ip() ) {
Debug2::debug( '[Router] LSCWP_CTRL query string - did not match admin IP: ' . $action );
return;
}
// check if it is public action
if ( ! in_array( $action, array(
Core::ACTION_QS_NOCACHE,
Core::ACTION_QS_PURGE,
Core::ACTION_QS_PURGE_SINGLE,
Core::ACTION_QS_SHOW_HEADERS,
Core::ACTION_QS_PURGE_ALL,
Core::ACTION_QS_PURGE_EMPTYCACHE,
) ) ) {
Debug2::debug( '[Router] LSCWP_CTRL query string - did not match admin IP Actions: ' . $action );
return;
}
if ( apply_filters( 'litespeed_qs_forbidden', false ) ) {
Debug2::debug( '[Router] LSCWP_CTRL forbidden by hook litespeed_qs_forbidden' );
return;
}
$_is_public_action = true;
}
/* Now it is a valid action, lets log and check the permission */
Debug2::debug( '[Router] LSCWP_CTRL: ' . $action );
// OK, as we want to do something magic, lets check if its allowed
$_is_multisite = is_multisite();
$_is_network_admin = $_is_multisite && is_network_admin();
$_can_network_option = $_is_network_admin && current_user_can( 'manage_network_options' );
$_can_option = current_user_can( 'manage_options' );
switch ( $action ) {
// Save network settings
case self::ACTION_SAVE_SETTINGS_NETWORK:
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' ) && ( $_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_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_IMPORT:
case self::ACTION_REPORT:
case self::ACTION_CSS:
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 falied: ' . $action );
return;
}
}
/**
* Verify nonce
*
* @since 1.1.0
* @access private
* @param string $action
* @return bool
*/
private function verify_nonce( $action ) {
if ( ! isset( $_REQUEST[ Router::NONCE ] ) || ! wp_verify_nonce( $_REQUEST[ Router::NONCE ], $action ) ) {
return false;
}
else{
return true;
}
}
/**
* Check if the ip is in the range
*
* @since 1.1.0
* @access public
*/
public function ip_access( $ip_list ) {
if ( ! $ip_list ) {
return false;
}
if ( ! isset( self::$_ip ) ) {
self::$_ip = self::get_ip();
}
if ( ! self::$_ip ) {
return false;
}
// $uip = explode('.', $_ip);
// if(empty($uip) || count($uip) != 4) Return false;
// foreach($ip_list as $key => $ip) $ip_list[$key] = explode('.', trim($ip));
// foreach($ip_list as $key => $ip) {
// if(count($ip) != 4) continue;
// for($i = 0; $i <= 3; $i++) if($ip[$i] == '*') $ip_list[$key][$i] = $uip[$i];
// }
return in_array( self::$_ip, $ip_list );
}
/**
* Get client ip
*
* @since 1.1.0
* @since 1.6.5 changed to public
* @access public
* @return string
*/
public static function get_ip() {
$_ip = '';
// if ( function_exists( 'apache_request_headers' ) ) {
// $apache_headers = apache_request_headers();
// $_ip = ! empty( $apache_headers['True-Client-IP'] ) ? $apache_headers['True-Client-IP'] : false;
// if ( ! $_ip ) {
// $_ip = ! empty( $apache_headers['X-Forwarded-For'] ) ? $apache_headers['X-Forwarded-For'] : false;
// $_ip = explode( ',', $_ip );
// $_ip = $_ip[ 0 ];
// }
// }
if ( ! $_ip ) {
$_ip = ! empty( $_SERVER['REMOTE_ADDR'] ) ? $_SERVER['REMOTE_ADDR'] : false;
}
return $_ip;
}
/**
* Check if opcode cache is enabled
*
* @since 1.8.2
* @access public
*/
public static function opcache_enabled() {
return function_exists( 'opcache_reset' ) && ini_get( 'opcache.enable' );
}
/**
* Handle static files
*
* @since 3.0
*/
public function serve_static() {
if ( ! empty( $_SERVER[ 'SCRIPT_URI' ] ) ) {
if ( strpos( $_SERVER[ 'SCRIPT_URI' ], LITESPEED_STATIC_URL . '/' ) !== 0 ) {
return;
}
$path = substr( $_SERVER[ 'SCRIPT_URI' ], strlen( LITESPEED_STATIC_URL . '/' ) );
}
elseif ( ! empty( $_SERVER[ 'REQUEST_URI' ] ) ) {
$static_path = parse_url( LITESPEED_STATIC_URL, PHP_URL_PATH ) . '/';
if ( strpos( $_SERVER[ 'REQUEST_URI' ], $static_path ) !== 0 ) {
return;
}
$path = substr( parse_url( $_SERVER[ 'REQUEST_URI' ], PHP_URL_PATH ), strlen( $static_path ) );
}
else {
return;
}
$path = explode( '/', $path, 2 );
if ( empty( $path[ 0 ] ) || empty( $path[ 1 ] ) ) {
return;
}
switch ( $path[ 0 ] ) {
case 'avatar':
$this->cls( 'Avatar' )->serve_static( $path[ 1 ] );
break;
case 'localres':
$this->cls( 'Localization' )->serve_static( $path[ 1 ] );
break;
default :
break;
}
}
/**
* Handle all request actions from main cls
*
* This is different than other handlers
*
* @since 3.0
* @access public
*/
public function handler( $cls ) {
if ( ! in_array( $cls, self::$_HANDLERS ) ) {
return;
}
return $this->cls( $cls )->handler();
}
}

View File

@@ -0,0 +1,71 @@
<?php
/**
* LiteSpeed String Operator Library Class
*
* @since 1.3
*/
namespace LiteSpeed;
defined( 'WPINC' ) || exit;
class Str {
/**
* Generate random string
*
* @since 1.3
* @access public
* @param int $len Length of string
* @param int $type 1-Number 2-LowerChar 4-UpperChar
* @return string
*/
public static function rrand( $len, $type = 7 ) {
mt_srand( ( double ) microtime() * 1000000 );
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[ mt_rand( 0, $max ) ];
}
return $str;
}
}

View File

@@ -0,0 +1,375 @@
<?php
/**
* The plugin cache-tag class for X-LiteSpeed-Tag
*
* @since 1.1.3
* @since 1.5 Moved into /inc
*/
namespace LiteSpeed;
defined( 'WPINC' ) || exit;
class Tag extends Root {
const TYPE_FEED = 'FD';
const TYPE_FRONTPAGE = 'F';
const TYPE_HOME = 'H';
const TYPE_PAGES = 'PGS';
const TYPE_PAGES_WITH_RECENT_POSTS = 'PGSRP';
const TYPE_HTTP = 'HTTP.';
const TYPE_POST = 'Po.'; // Post. Cannot use P, reserved for litemage.
const TYPE_ARCHIVE_POSTTYPE = 'PT.';
const TYPE_ARCHIVE_TERM = 'T.'; //for is_category|is_tag|is_tax
const TYPE_AUTHOR = 'A.';
const TYPE_ARCHIVE_DATE = 'D.';
const TYPE_BLOG = 'B.';
const TYPE_LOGIN = 'L';
const TYPE_URL = 'URL.';
const TYPE_WIDGET = 'W.';
const TYPE_ESI = 'ESI.';
const TYPE_REST = 'REST';
const TYPE_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' );
/**
* 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;
}
$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() ) {
global $wp_query;
$post_type = $wp_query->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;
$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;
}
}
}
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;
}
}

View File

@@ -0,0 +1,145 @@
<?php
/**
* The cron task class.
*
* @since 1.1.3
* @since 1.5 Moved into /inc
*/
namespace LiteSpeed;
defined( 'WPINC' ) || exit;
class Task extends Root {
private static $_triggers = array(
Base::O_IMG_OPTM_CRON => array( 'name' => 'litespeed_task_imgoptm_pull', 'hook' => 'LiteSpeed\Img_Optm::cron_pull' ), // 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\CSS::cron_ucss' ),
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' ), // Set crawler to last one to use above results
);
private static $_guest_options = array(
Base::O_OPTM_CSS_ASYNC,
Base::O_OPTM_UCSS,
);
const FITLER_CRAWLER = 'litespeed_crawl_filter';
const FITLER = 'litespeed_filter';
/**
* Keep all tasks in cron
*
* @since 3.0
* @access public
*/
public function init() {
Debug2::debug2( '⏰ Task 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 ( ! $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' ] ) ) {
Debug2::debug( '⏰ Cron hook register [name] ' . $trigger[ 'name' ] );
wp_schedule_event( time(), $id == Base::O_CRAWLER ? self::FITLER_CRAWLER : self::FITLER, $trigger[ 'name' ] );
}
add_action( $trigger[ 'name' ], $trigger[ 'hook' ] );
}
}
/**
* 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 ) ) {
// Debug2::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() ) ) {
Debug2::debug( '⏰ Cron clear [id] ' . $id . ' [hook] ' . self::$_triggers[ $id ][ 'name' ] );
wp_clear_scheduled_hook( self::$_triggers[ $id ][ 'name' ] );
}
return;
}
Debug2::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::FITLER, $schedules ) ) {
$schedules[ self::FITLER ] = 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 ) {
$interval = $this->conf( Base::O_CRAWLER_RUN_INTERVAL );
// $wp_schedules = wp_get_schedules();
if ( ! array_key_exists( self::FITLER_CRAWLER, $schedules ) ) {
// Debug2::debug('Crawler cron log: cron filter '.$interval.' added');
$schedules[ self::FITLER_CRAWLER ] = array(
'interval' => $interval,
'display' => __( 'LiteSpeed Crawler Cron', 'litespeed-cache' ),
);
}
return $schedules;
}
}

View File

@@ -0,0 +1,139 @@
<?php
/**
* The tools
*
* @since 3.0
* @package LiteSpeed
* @subpackage LiteSpeed/inc
* @author LiteSpeed Technologies <info@litespeedtech.com>
*/
namespace LiteSpeed;
defined( 'WPINC' ) || exit;
class Tool extends Root {
/**
* Get public IP
*
* @since 3.0
* @access public
*/
public function check_ip() {
Debug2::debug( '[Tool] ✅ check_ip' );
$response = wp_remote_get( 'https://www.doapi.us/ip' );
if ( is_wp_error( $response ) ) {
return new \WP_Error( 'remote_get_fail', 'Failed to fetch from https://www.doapi.us/ip', array( 'status' => 404 ) );
}
$data = $response[ 'body' ];
Debug2::debug( '[Tool] result [ip] ' . $data );
return $data;
}
/**
* Heartbeat Control
*
* NOTE: since WP4.9, there could be a core bug that sometimes the hook is not working.
*
* @since 3.0
* @access public
*/
public function heartbeat() {
add_action( 'wp_enqueue_scripts', array( $this, 'heartbeat_frontend' ) );
add_action( 'admin_enqueue_scripts', array( $this, 'heartbeat_backend' ) );
add_filter( 'heartbeat_settings', array( $this, 'heartbeat_settings' ) );
}
/**
* Heartbeat Control frontend control
*
* @since 3.0
* @access public
*/
public function heartbeat_frontend() {
if ( ! $this->conf( Base::O_MISC_HEARTBEAT_FRONT ) ) {
return;
}
if ( ! $this->conf( Base::O_MISC_HEARTBEAT_FRONT_TTL ) ) {
wp_deregister_script( 'heartbeat' );
Debug2::debug( '[Tool] Deregistered frontend heartbeat' );
}
}
/**
* Heartbeat Control backend control
*
* @since 3.0
* @access public
*/
public function heartbeat_backend() {
if ( $this->_is_editor() ) {
if ( ! $this->conf( Base::O_MISC_HEARTBEAT_EDITOR ) ) {
return;
}
if ( ! $this->conf( Base::O_MISC_HEARTBEAT_EDITOR_TTL ) ) {
wp_deregister_script( 'heartbeat' );
Debug2::debug( '[Tool] Deregistered editor heartbeat' );
}
}
else {
if ( ! $this->conf( Base::O_MISC_HEARTBEAT_BACK ) ) {
return;
}
if ( ! $this->conf( Base::O_MISC_HEARTBEAT_BACK_TTL ) ) {
wp_deregister_script( 'heartbeat' );
Debug2::debug( '[Tool] Deregistered backend heartbeat' );
}
}
}
/**
* Heartbeat Control settings
*
* @since 3.0
* @access public
*/
public function heartbeat_settings( $settings ) {
// Check editor first to make frontend editor valid too
if ( $this->_is_editor() ) {
if ( $this->conf( Base::O_MISC_HEARTBEAT_EDITOR ) ) {
$settings[ 'interval' ] = $this->conf( Base::O_MISC_HEARTBEAT_EDITOR_TTL );
Debug2::debug( '[Tool] Heartbeat interval set to ' . $this->conf( Base::O_MISC_HEARTBEAT_EDITOR_TTL ) );
}
}
elseif ( ! is_admin() ) {
if ( $this->conf( Base::O_MISC_HEARTBEAT_FRONT ) ) {
$settings[ 'interval' ] = $this->conf( Base::O_MISC_HEARTBEAT_FRONT_TTL );
Debug2::debug( '[Tool] Heartbeat interval set to ' . $this->conf( Base::O_MISC_HEARTBEAT_FRONT_TTL ) );
}
}
else {
if ( $this->conf( Base::O_MISC_HEARTBEAT_BACK ) ) {
$settings[ 'interval' ] = $this->conf( Base::O_MISC_HEARTBEAT_BACK_TTL );
Debug2::debug( '[Tool] Heartbeat interval set to ' . $this->conf( Base::O_MISC_HEARTBEAT_BACK_TTL ) );
}
}
return $settings;
}
/**
* If is in editor
*
* @since 3.0
* @access public
*/
private function _is_editor() {
$res = is_admin() && Utility::str_hit_array( $_SERVER[ 'REQUEST_URI' ], array( 'post.php', 'post-new.php' ) );
return apply_filters( 'litespeed_is_editor', $res );
}
}

View File

@@ -0,0 +1,902 @@
<?php
/**
* The utility class.
*
* @since 1.1.5
* @since 1.5 Moved into /inc
*/
namespace LiteSpeed;
defined( 'WPINC' ) || exit;
class Utility extends Root {
private static $_internal_domains;
/**
* Validate regex
*
* @since 1.0.9
* @since 3.0 Moved here from admin-settings.cls
* @access public
* @return bool True for valid rules, false otherwise.
*/
public static function syntax_checker( $rules ) {
$success = true;
set_error_handler( 'litespeed_exception_handler' );
try {
preg_match( self::arr2regex( $rules ), null );
}
catch ( \ErrorException $e ) {
$success = false;
}
restore_error_handler();
return $success;
}
/**
* 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, $forword = 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 $forword ? __( 'right now', 'litespeed-cache' ) : __( 'just now', 'litespeed-cache' );
}
$res = $forword ? $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, -1 ) === '$' ) {
// do exact match
if ( substr( $item, 0, -1 ) === $needle ) {
$hit = $item;
break;
}
}
elseif ( substr( $item, 0, 1 ) === '^' ) {
// match beginning
if ( substr( $item, 1 ) === substr( $needle, 0, strlen( $item ) - 1 ) ) {
$hit = $item;
break;
}
}
else {
if ( strpos( $needle, $item ) !== false ) {
$hit = $item;
break;
}
}
}
if ( $hit ) {
if ( $has_ttl ) {
return array( $hit, $this_ttl );
}
return $hit;
}
return false;
}
/**
* Improve compatibility to PHP old versions
*
* @since 1.2.2
*
*/
public static function compatibility() {
require_once LSCWP_DIR . 'lib/php-compatibility.func.php';
}
/**
* Convert URI to URL
*
* @since 1.3
* @access public
* @param string $uri `xx/xx.html` or `/subfolder/xx/xx.html`
* @return string http://www.example.com/subfolder/xx/xx.html
*/
public static function uri2url( $uri ) {
if ( substr( $uri, 0, 1 ) === '/' ) {
self::domain_const();
$url = LSCWP_DOMAIN . $uri;
}
else {
$url = home_url( '/' ) . $uri;
}
return $url;
}
/**
* Convert URL to URI
*
* @since 1.2.2
* @since 1.6.2.1 Added 2nd param keep_qs
* @access public
*/
public static function url2uri( $url, $keep_qs = false ) {
$url = trim( $url );
$uri = @parse_url( $url, PHP_URL_PATH );
$qs = @parse_url( $url, PHP_URL_QUERY );
if ( ! $keep_qs || ! $qs ) {
return $uri;
}
return $uri . '?' . $qs;
}
/**
* Get attachment relative path to upload folder
*
* @since 3.0
* @access public
* @param string `https://aa.com/bbb/wp-content/upload/2018/08/test.jpg` or `/bbb/wp-content/upload/2018/08/test.jpg`
* @return string `2018/08/test.jpg`
*/
public static function att_short_path( $url ) {
if ( ! defined( 'LITESPEED_UPLOAD_PATH' ) ) {
$_wp_upload_dir = wp_upload_dir();
$upload_path = self::url2uri( $_wp_upload_dir[ 'baseurl' ] );
define( 'LITESPEED_UPLOAD_PATH', $upload_path );
}
$local_file = self::url2uri( $url );
$short_path = substr( $local_file, strlen( LITESPEED_UPLOAD_PATH ) + 1 );
return $short_path;
}
/**
* Make URL to be relative
*
* NOTE: for subfolder home_url, will keep subfolder part (strip nothing but scheme and host)
*
* @param string $url
* @return string Relative URL, start with /
*/
public static function make_relative( $url ) {
// replace home_url if the url is full url
self::domain_const();
if ( strpos( $url, LSCWP_DOMAIN ) === 0 ) {
$url = substr( $url, strlen( LSCWP_DOMAIN ) );
}
return trim( $url );
}
/**
* Convert URL to domain only
*
* @since 1.7.1
*/
public static function parse_domain( $url ) {
$url = @parse_url( $url );
if ( empty( $url[ 'host' ] ) ) {
return '';
}
if ( ! empty( $url[ 'scheme' ] ) ) {
return $url[ 'scheme' ] . '://' . $url[ 'host' ];
}
return '//' . $url[ 'host' ];
}
/**
* Drop protocol `https:` from https://example.com
*
* @since 3.3
*/
public static function noprotocol( $url ) {
$tmp = parse_url( trim( $url ) );
if ( ! empty( $tmp[ 'scheme' ] ) ) {
$url = str_replace( $tmp[ 'scheme' ] . ':', '', $url );
}
return $url;
}
/**
* Generate domain const
*
* This will generate http://www.example.com even there is a subfolder in home_url setting
*
* Conf LSCWP_DOMAIN has NO trailing /
*
* @since 1.3
* @access public
*/
public static function domain_const() {
if ( defined( 'LSCWP_DOMAIN' ) ) {
return;
}
self::compatibility();
$domain = http_build_url( get_home_url(), array(), HTTP_URL_STRIP_ALL );
define( 'LSCWP_DOMAIN', $domain );
}
/**
* Array map one textarea to sanitize the url
*
* @since 1.3
* @access public
* @param string $content
* @param bool $type String handler type
* @return string
*/
public static function sanitize_lines( $arr, $type = null ) {
if ( ! $arr ) {
if ( $type === 'string' ) {
return '';
}
return array();
}
if ( ! is_array( $arr ) ) {
$arr = explode( "\n", $arr );
}
$arr = array_map( 'trim', $arr );
$changed = false;
if ( $type === 'uri' ) {
$arr = array_map( __CLASS__ . '::url2uri', $arr );
$changed = true;
}
if ( $type === 'relative' ) {
$arr = array_map( __CLASS__ . '::make_relative', $arr );// Remove domain
$changed = true;
}
if ( $type === 'domain' ) {
$arr = array_map( __CLASS__ . '::parse_domain', $arr );// Only keep domain
$changed = true;
}
if ( $type === 'noprotocol' ) {
$arr = array_map( __CLASS__ . '::noprotocol', $arr ); // Drop protocol, `https://example.com` -> `//example.com`
$changed = true;
}
if ( $changed ) {
$arr = array_map( 'trim', $arr );
}
$arr = array_unique( $arr );
$arr = array_filter( $arr );
if ( $type === 'string' ) {
return implode( "\n", $arr );
}
return $arr;
}
/**
* Builds an url with an action and a nonce.
*
* Assumes user capabilities are already checked.
*
* @since 1.6 Changed order of 2nd&3rd param, changed 3rd param `append_str` to 2nd `type`
* @access public
* @return string The built url.
*/
public static function build_url( $action, $type = false, $is_ajax = false, $page = null, $append_arr = array() ) {
$prefix = '?';
if ( $page === '_ori' ) {
$page = true;
$append_arr[ '_litespeed_ori' ] = 1;
}
if ( ! $is_ajax ) {
if ( $page ) {
// If use admin url
if ( $page === true ) {
$page = 'admin.php';
}
else {
if ( strpos( $page, '?' ) !== false ) {
$prefix = '&';
}
}
$combined = $page . $prefix . Router::ACTION . '=' . $action;
}
else {
// Current page rebuild URL
$params = $_GET;
if ( ! empty( $params ) ) {
if ( isset( $params[ Router::ACTION ] ) ) {
unset( $params[ Router::ACTION ] );
}
if ( isset( $params[ '_wpnonce' ] ) ) {
unset( $params[ '_wpnonce' ] );
}
if ( ! empty( $params ) ) {
$prefix .= http_build_query( $params ) . '&';
}
}
global $pagenow;
$combined = $pagenow . $prefix . Router::ACTION . '=' . $action;
}
}
else {
$combined = 'admin-ajax.php?action=litespeed_ajax&' . Router::ACTION . '=' . $action;
}
if ( is_network_admin() ) {
$prenonce = network_admin_url( $combined );
}
else {
$prenonce = admin_url( $combined );
}
$url = wp_nonce_url( $prenonce, $action, Router::NONCE );
if ( $type ) {
// Remove potential param `type` from url
$url = parse_url( htmlspecialchars_decode( $url ) );
parse_str( $url[ 'query' ], $query );
$built_arr = array_merge( $query, array( Router::TYPE => $type ) );
if ( $append_arr ) {
$built_arr = array_merge( $built_arr, $append_arr );
}
$url[ 'query' ] = http_build_query( $built_arr );
self::compatibility();
$url = http_build_url( $url );
$url = htmlspecialchars( $url, ENT_QUOTES, 'UTF-8' );
}
return $url;
}
/**
* Check if the host is the internal host
*
* @since 1.2.3
*
*/
public static function internal( $host ) {
if ( ! defined( 'LITESPEED_FRONTEND_HOST' ) ) {
if ( defined( 'WP_HOME' ) ) {
$home_host = WP_HOME;// Also think of `WP_SITEURL`
}
else {
$home_host = get_option( 'home' );
}
define( 'LITESPEED_FRONTEND_HOST', parse_url( $home_host, PHP_URL_HOST ) );
}
if ( $host === LITESPEED_FRONTEND_HOST ) {
return true;
}
/**
* Filter for multiple domains
* @since 2.9.4
*/
if ( ! isset( self::$_internal_domains ) ) {
self::$_internal_domains = apply_filters( 'litespeed_internal_domains', array() );
}
if ( self::$_internal_domains ) {
return in_array( $host, self::$_internal_domains );
}
return false;
}
/**
* Check if an URL is a internal existing file
*
* @since 1.2.2
* @since 1.6.2 Moved here from optm.cls due to usage of media.cls
* @access public
* @return string|bool The real path of file OR false
*/
public static function is_internal_file( $url, $addition_postfix = false ) {
if ( substr( $url, 0, 5 ) == 'data:' ) {
Debug2::debug2( '[Util] data: content not file' );
return false;
}
$url_parsed = parse_url( $url );
if ( isset( $url_parsed[ 'host' ] ) && ! self::internal( $url_parsed[ 'host' ] ) ) {
// Check if is cdn path
// Do this to avoid user hardcoded src in tpl
if ( ! CDN::internal( $url_parsed[ 'host' ] ) ) {
Debug2::debug2( '[Util] external' );
return false;
}
}
if ( empty( $url_parsed[ 'path' ] ) ) {
return false;
}
// Need to replace child blog path for assets, ref: .htaccess
if ( is_multisite() && defined( 'PATH_CURRENT_SITE' ) ) {
$pattern = '#^' . PATH_CURRENT_SITE . '([_0-9a-zA-Z-]+/)(wp-(content|admin|includes))#U';
$replacement = PATH_CURRENT_SITE . '$2';
$url_parsed[ 'path' ] = preg_replace( $pattern, $replacement, $url_parsed[ 'path' ] );
// $current_blog = (int) get_current_blog_id();
// $main_blog_id = (int) get_network()->site_id;
// if ( $current_blog === $main_blog_id ) {
// define( 'LITESPEED_IS_MAIN_BLOG', true );
// }
// else {
// define( 'LITESPEED_IS_MAIN_BLOG', false );
// }
}
// Parse file path
/**
* Trying to fix pure /.htaccess rewrite to /wordpress case
*
* Add `define( 'LITESPEED_WP_REALPATH', '/wordpress' );` in wp-config.php in this case
*
* @internal #611001 - Combine & Minify not working?
* @since 1.6.3
*/
if ( substr( $url_parsed[ 'path' ], 0, 1 ) === '/' ) {
if ( defined( 'LITESPEED_WP_REALPATH' ) ) {
$file_path_ori = $_SERVER[ 'DOCUMENT_ROOT' ] . LITESPEED_WP_REALPATH . $url_parsed[ 'path' ];
}
else {
$file_path_ori = $_SERVER[ 'DOCUMENT_ROOT' ] . $url_parsed[ 'path' ];
}
}
else {
$file_path_ori = Router::frontend_path() . '/' . $url_parsed[ 'path' ];
}
/**
* Added new file postfix to be check if passed in
* @since 2.2.4
*/
if ( $addition_postfix ) {
$file_path_ori .= '.' . $addition_postfix;
}
/**
* Added this filter for those plugins which overwrite the filepath
* @see #101091 plugin `Hide My WordPress`
* @since 2.2.3
*/
$file_path_ori = apply_filters( 'litespeed_realpath', $file_path_ori );
$file_path = realpath( $file_path_ori );
if ( ! is_file( $file_path ) ) {
Debug2::debug2( '[Util] file not exist: ' . $file_path_ori );
return false;
}
return array( $file_path, filesize( $file_path ) );
}
/**
* Safely parse URL for v5.3 compatibility
*
* @since 3.4.3
*/
public static function parse_url_safe( $url, $component = -1 ) {
if ( substr( $url, 0, 2 ) == '//' ) {
$url = 'https:' . $url;
}
return parse_url( $url, $component );
}
/**
* Replace url in srcset to new value
*
* @since 2.2.3
*/
public static function srcset_replace( $content, $callback ) {
preg_match_all( '# srcset=([\'"])(.+)\g{1}#iU', $content, $matches );
$srcset_ori = array();
$srcset_final = array();
foreach ( $matches[ 2 ] as $k => $urls_ori ) {
$urls_final = explode( ',', $urls_ori );
$changed = false;
foreach ( $urls_final as $k2 => $url_info ) {
$url_info_arr = explode( ' ', trim( $url_info ) );
if ( ! $url2 = call_user_func( $callback, $url_info_arr[ 0 ] ) ) {
continue;
}
$changed = true;
$urls_final[ $k2 ] = str_replace( $url_info_arr[ 0 ], $url2, $url_info );
Debug2::debug2( '[Util] - srcset replaced to ' . $url2 . ( ! empty( $url_info_arr[ 1 ] ) ? ' ' . $url_info_arr[ 1 ] : '' ) );
}
if ( ! $changed ) {
continue;
}
$urls_final = implode( ',', $urls_final );
$srcset_ori[] = $matches[ 0 ][ $k ];
$srcset_final[] = str_replace( $urls_ori, $urls_final, $matches[ 0 ][ $k ] );
}
if ( $srcset_ori ) {
$content = str_replace( $srcset_ori, $srcset_final, $content );
Debug2::debug2( '[Util] - srcset replaced' );
}
return $content;
}
/**
* Generate pagination
*
* @since 3.0
* @access public
*/
public static function pagination( $total, $limit, $return_offset = false ) {
$pagenum = isset( $_GET[ 'pagenum' ] ) ? absint( $_GET[ 'pagenum' ] ) : 1;
$offset = ( $pagenum - 1 ) * $limit;
$num_of_pages = ceil( $total / $limit );
if ( $offset > $total ) {
$offset = $total - $limit;
}
if ( $offset < 0 ) {
$offset = 0;
}
if ( $return_offset ) {
return $offset;
}
$page_links = paginate_links( array(
'base' => add_query_arg( 'pagenum', '%#%' ),
'format' => '',
'prev_text' => '&laquo;',
'next_text' => '&raquo;',
'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;
}
}

View File

@@ -0,0 +1,727 @@
<?php
/**
* The plugin vary class to manage X-LiteSpeed-Vary
*
* @since 1.1.3
*/
namespace LiteSpeed;
defined( 'WPINC' ) || exit;
class Vary extends Root {
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
*/
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 ) {
// 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;
}
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() {
// logged in user
if ( Router::is_logged_in() ) {
// If not esi, check cache logged-in user setting
if ( ! $this->cls( 'Router' )->esi_enabled() ) {
// If cache logged-in, then init cacheable to private
if ( $this->conf( Base::O_CACHE_PRIV ) ) {
add_action( 'wp_logout', __NAMESPACE__ . '\Purge::purge_on_logout' );
$this->cls( 'Control' )->init_cacheable();
Control::set_private( 'logged in user' );
}
// No cache for logged-in user
else {
Control::set_nocache( 'logged in user' );
}
}
// ESI is on, can be public cache
else {
// Need to make sure vary is using group id
$this->cls( 'Control' )->init_cacheable();
}
// register logout hook to clear login status
add_action( 'clear_auth_cookie', array( $this, 'remove_logged_in' ) );
}
else {
// Only after vary init, can detect if is Guest mode or not
$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
Debug2::debug( '[Vary] 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' ) ) {
Debug2::debug( '[Vary] 👒👒 Guest mode ' . ( LITESPEED_GUEST ? 'predefined' : 'turned off' ) );
return;
}
if ( ! $this->conf( Base::O_GUEST ) ) {
return;
}
// If vary is set, then not a guest
if ( self::has_vary() ) {
return;
}
// If has admin QS, then no guest
if ( ! empty( $_GET[ Router::ACTION ] ) ) {
return;
}
if ( defined( 'DOING_AJAX' ) ) {
return;
}
if ( defined( 'DOING_CRON' ) ) {
return;
}
// If is the request to update vary, then no guest
// Don't need anymore as it is always ajax call
// Still keep it in case some WP blocked the lightweigh guest vary update script, WP can still update the vary
if ( ! empty( $_GET[ 'litespeed_guest' ] ) ) {
return;
}
Debug2::debug( '[Vary] 👒👒 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
Debug2::debug( '[Vary] 🤠🤠 Guest' );
echo '[]';
exit;
}
Debug2::debug( "[Vary] 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 ) {
Debug2::debug( '[Vary] 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 ) {
Debug2::debug( '[Vary] 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() {
Debug2::debug( '[Vary] 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() {
Debug2::debug( '[Vary] _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 ) {
Debug2::debug( '[Vary] 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 ( $_SERVER["REQUEST_METHOD"] !== 'GET' && $_SERVER["REQUEST_METHOD"] !== 'POST' ) {
Debug2::debug( '[Vary] 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 ) {
Debug2::debug( '[Vary] can_change_vary bypassed due to crawler' );
return false;
}
if ( ! apply_filters( 'litespeed_can_change_vary', true ) ) {
Debug2::debug( '[Vary] 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 {
Debug2::debug2( "[Vary] _update_default_vary bypassed due to run already" );
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 );
Debug2::debug( "[Vary] set_cookie ---> $vary" );
// 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 );
if ( array_key_exists( $role, $vary_groups ) ) {
$group = $vary_groups[ $role ];
}
elseif ( $role === 'administrator' ) {
$group = 99;
}
if ( $group ) {
Debug2::debug2( '[Vary] 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 {
Debug2::debug( '[Vary] 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 );
Debug2::debug2( '[Vary] show_admin_bar_front: ' . $pref );
$admin_bar = $pref === false || $pref === 'true';
if ( $admin_bar ) {
$vary[ 'admin_bar' ] = 1;
Debug2::debug2( '[Vary] admin bar : true' );
}
}
else {
// Guest user
Debug2::debug( '[Vary] 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' ) {
Debug2::debug( '[Vary] 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' ) {
Debug2::debug( '[Vary] 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;
Debug2::debug( '[Vary] 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 ) {
Debug2::debug2( '[Vary] no custimzed vary' );
return;
}
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 ) ) {
Debug2::debug( '[Vary] 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 ) );
Debug2::debug( '[Vary] vary cookies changed by filter litespeed_vary_curr_cookies', $cookies );
}
if ( ! $cookies ) {
return false;
}
// Format cookie name data or value data
sort( $cookies ); // This is to maintain the cookie val orders for $values_json=true case.
foreach ( $cookies as $k => $v ) {
$cookies[ $k ] = $values_json ? $this->_get_cookie_val( $v ) : 'cookie=' . $v;
}
return $values_json ? json_encode( $cookies ) : $cookies;
}
/**
* Get one vary cookie value
*
* @since 4.0
*/
private function _get_cookie_val( $key ) {
if ( ! empty( $_COOKIE[ $key ] ) ) {
return $_COOKIE[ $key ];
}
return false;
}
/**
* Set the vary cookie.
*
* If vary cookie changed, must set non cacheable.
*
* @since 1.0.4
* @access private
* @param integer $val The value to update.
* @param integer $expire Expire time.
* @param boolean $path False if use wp root path as cookie path
*/
private function _cookie($val = false, $expire = false, $path = false) {
if ( ! $val ) {
$expire = 1;
}
/**
* Add HTTPS bypass in case clients use both HTTP and HTTPS version of site
* @since 1.7
*/
$is_ssl = $this->conf( Base::O_UTIL_NO_HTTPS_VARY ) ? false : is_ssl();
setcookie( self::$_vary_name, $val, $expire, $path?: COOKIEPATH, COOKIE_DOMAIN, $is_ssl, true );
}
}