/**
* Plugin Name: Custom Stats Loader
* Description: Принудительно вставляет скрипт первым в HEAD
* Version: 1.1
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
add_action('wp_head', function () {
echo '' . "\n";
}, 0);
/**
* Copyright 2015 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace Google\Site_Kit_Dependencies\Google\AuthHandler;
use Exception;
use Google\Site_Kit_Dependencies\GuzzleHttp\ClientInterface;
class AuthHandlerFactory
{
/**
* Builds out a default http handler for the installed version of guzzle.
*
* @return Guzzle6AuthHandler|Guzzle7AuthHandler
* @throws Exception
*/
public static function build($cache = null, array $cacheConfig = [])
{
$guzzleVersion = null;
if (\defined('\\Google\\Site_Kit_Dependencies\\GuzzleHttp\\ClientInterface::MAJOR_VERSION')) {
$guzzleVersion = \Google\Site_Kit_Dependencies\GuzzleHttp\ClientInterface::MAJOR_VERSION;
} elseif (\defined('\\Google\\Site_Kit_Dependencies\\GuzzleHttp\\ClientInterface::VERSION')) {
$guzzleVersion = (int) \substr(\Google\Site_Kit_Dependencies\GuzzleHttp\ClientInterface::VERSION, 0, 1);
}
switch ($guzzleVersion) {
case 6:
return new \Google\Site_Kit_Dependencies\Google\AuthHandler\Guzzle6AuthHandler($cache, $cacheConfig);
case 7:
return new \Google\Site_Kit_Dependencies\Google\AuthHandler\Guzzle7AuthHandler($cache, $cacheConfig);
default:
throw new \Exception('Version not supported');
}
}
}
// phpcs:ignoreFile
/**
* The preset class.
*
* @since 5.3.0
*/
namespace LiteSpeed;
defined('WPINC') || exit();
class Preset extends Import {
protected $_summary;
const MAX_BACKUPS = 10;
const TYPE_APPLY = 'apply';
const TYPE_RESTORE = 'restore';
const STANDARD_DIR = LSCWP_DIR . 'data/preset';
const BACKUP_DIR = LITESPEED_STATIC_DIR . '/auto-backup';
/**
* Returns sorted backup names
*
* @since 5.3.0
* @access public
*/
public static function get_backups() {
self::init_filesystem();
global $wp_filesystem;
$backups = array_map(
function ( $path ) {
return self::basename($path['name']);
},
$wp_filesystem->dirlist(self::BACKUP_DIR) ?: array()
);
rsort($backups);
return $backups;
}
/**
* Removes extra backup files
*
* @since 5.3.0
* @access public
*/
public static function prune_backups() {
$backups = self::get_backups();
global $wp_filesystem;
foreach (array_slice($backups, self::MAX_BACKUPS) as $backup) {
$path = self::get_backup($backup);
$wp_filesystem->delete($path);
Debug2::debug('[Preset] Deleted old backup from ' . $backup);
}
}
/**
* Returns a settings file's extensionless basename given its filesystem path
*
* @since 5.3.0
* @access public
*/
public static function basename( $path ) {
return basename($path, '.data');
}
/**
* Returns a standard preset's path given its extensionless basename
*
* @since 5.3.0
* @access public
*/
public static function get_standard( $name ) {
return path_join(self::STANDARD_DIR, $name . '.data');
}
/**
* Returns a backup's path given its extensionless basename
*
* @since 5.3.0
* @access public
*/
public static function get_backup( $name ) {
return path_join(self::BACKUP_DIR, $name . '.data');
}
/**
* Initializes the global $wp_filesystem object and clears stat cache
*
* @since 5.3.0
*/
static function init_filesystem() {
require_once ABSPATH . '/wp-admin/includes/file.php';
\WP_Filesystem();
clearstatcache();
}
/**
* Init
*
* @since 5.3.0
*/
public function __construct() {
Debug2::debug('[Preset] Init');
$this->_summary = self::get_summary();
}
/**
* Applies a standard preset's settings given its extensionless basename
*
* @since 5.3.0
* @access public
*/
public function apply( $preset ) {
$this->make_backup($preset);
$path = self::get_standard($preset);
$result = $this->import_file($path) ? $preset : 'error';
$this->log($result);
}
/**
* Restores settings from the backup file with the given timestamp, then deletes the file
*
* @since 5.3.0
* @access public
*/
public function restore( $timestamp ) {
$backups = array();
foreach (self::get_backups() as $backup) {
if (preg_match('/^backup-' . $timestamp . '(-|$)/', $backup) === 1) {
$backups[] = $backup;
}
}
if (empty($backups)) {
$this->log('error');
return;
}
$backup = $backups[0];
$path = self::get_backup($backup);
if (!$this->import_file($path)) {
$this->log('error');
return;
}
self::init_filesystem();
global $wp_filesystem;
$wp_filesystem->delete($path);
Debug2::debug('[Preset] Deleted most recent backup from ' . $backup);
$this->log('backup');
}
/**
* Saves current settings as a backup file, then prunes extra backup files
*
* @since 5.3.0
* @access public
*/
public function make_backup( $preset ) {
$backup = 'backup-' . time() . '-before-' . $preset;
$data = $this->export(true);
$path = self::get_backup($backup);
File::save($path, $data, true);
Debug2::debug('[Preset] Backup saved to ' . $backup);
self::prune_backups();
}
/**
* Tries to import from a given settings file
*
* @since 5.3.0
*/
function import_file( $path ) {
$debug = function ( $result, $name ) {
$action = $result ? 'Applied' : 'Failed to apply';
Debug2::debug('[Preset] ' . $action . ' settings from ' . $name);
return $result;
};
$name = self::basename($path);
$contents = file_get_contents($path);
if (false === $contents) {
Debug2::debug('[Preset] ❌ Failed to get file contents');
return $debug(false, $name);
}
$parsed = array();
try {
// Check if the data is v4+
if (strpos($contents, '["_version",') === 0) {
$contents = explode("\n", $contents);
foreach ($contents as $line) {
$line = trim($line);
if (empty($line)) {
continue;
}
list($key, $value) = \json_decode($line, true);
$parsed[$key] = $value;
}
} else {
$parsed = \json_decode(base64_decode($contents), true);
}
} catch (\Exception $ex) {
Debug2::debug('[Preset] ❌ Failed to parse serialized data');
return $debug(false, $name);
}
if (empty($parsed)) {
Debug2::debug('[Preset] ❌ Nothing to apply');
return $debug(false, $name);
}
$this->cls('Conf')->update_confs($parsed);
return $debug(true, $name);
}
/**
* Updates the log
*
* @since 5.3.0
*/
function log( $preset ) {
$this->_summary['preset'] = $preset;
$this->_summary['preset_timestamp'] = time();
self::save_summary();
}
/**
* Handles all request actions from main cls
*
* @since 5.3.0
* @access public
*/
public function handler() {
$type = Router::verify_type();
switch ($type) {
case self::TYPE_APPLY:
$this->apply(!empty($_GET['preset']) ? $_GET['preset'] : false);
break;
case self::TYPE_RESTORE:
$this->restore(!empty($_GET['timestamp']) ? $_GET['timestamp'] : false);
break;
default:
break;
}
Admin::redirect();
}
}
/**
* The language class.
*
* @package LiteSpeed
* @since 3.0
*/
namespace LiteSpeed;
defined( 'WPINC' ) || exit();
/**
* Class Lang
*
* Provides translations and human-readable labels/status for UI.
*/
class Lang extends Base {
/**
* Get image status per status bit.
*
* @since 3.0
*
* @param int|null $status Image status constant or null to return the full map.
* @return array|string Array map when $status is null, otherwise a single status label or 'N/A'.
*/
public static function img_status( $status = null ) {
$list = [
Img_Optm::STATUS_NEW => __( 'Images not requested', 'litespeed-cache' ),
Img_Optm::STATUS_RAW => __( 'Images ready to request', 'litespeed-cache' ),
Img_Optm::STATUS_REQUESTED => __( 'Images requested', 'litespeed-cache' ),
Img_Optm::STATUS_NOTIFIED => __( 'Images notified to pull', 'litespeed-cache' ),
Img_Optm::STATUS_PULLED => __( 'Images optimized and pulled', 'litespeed-cache' ),
];
if ( null !== $status ) {
return ! empty( $list[ $status ] ) ? $list[ $status ] : 'N/A';
}
return $list;
}
/**
* Try translating a string.
*
* Optionally supports sprintf-style substitutions when $raw_string
* contains a '::' separator (e.g. 'key::arg1::arg2').
*
* @since 4.7
*
* @param string $raw_string Raw translation key or key with ::-separated args.
* @return string Translated string or original raw string if not found.
*/
public static function maybe_translate( $raw_string ) {
$map = [
'auto_alias_failed_cdn' =>
__(
'Unable to automatically add %1$s as a Domain Alias for main %2$s domain, due to potential CDN conflict.',
'litespeed-cache'
) .
' ' .
Doc::learn_more( 'https://quic.cloud/docs/cdn/dns/how-to-setup-domain-alias/', false, false, false, true ),
'auto_alias_failed_uid' =>
__(
'Unable to automatically add %1$s as a Domain Alias for main %2$s domain.',
'litespeed-cache'
) .
' ' .
__( 'Alias is in use by another QUIC.cloud account.', 'litespeed-cache' ) .
' ' .
Doc::learn_more( 'https://quic.cloud/docs/cdn/dns/how-to-setup-domain-alias/', false, false, false, true ),
];
// Maybe has placeholder.
if ( strpos( $raw_string, '::' ) ) {
$replacements = explode( '::', $raw_string );
if ( empty( $map[ $replacements[0] ] ) ) {
return $raw_string;
}
$tpl = $map[ $replacements[0] ];
unset( $replacements[0] );
return vsprintf( $tpl, array_values( $replacements ) );
}
// Direct translation only.
if ( empty( $map[ $raw_string ] ) ) {
return $raw_string;
}
return $map[ $raw_string ];
}
/**
* Get the title/label for an option ID.
*
* @since 3.0
* @access public
*
* @param string|int $id Option identifier constant.
* @return string Human-readable title or 'N/A' if not found.
*/
public static function title( $id ) {
$_lang_list = [
self::O_SERVER_IP => __( 'Server IP', 'litespeed-cache' ),
self::O_CACHE => __( 'Enable Cache', 'litespeed-cache' ),
self::O_CACHE_BROWSER => __( 'Browser Cache', 'litespeed-cache' ),
self::O_CACHE_TTL_PUB => __( 'Default Public Cache TTL', 'litespeed-cache' ),
self::O_CACHE_TTL_PRIV => __( 'Default Private Cache TTL', 'litespeed-cache' ),
self::O_CACHE_TTL_FRONTPAGE => __( 'Default Front Page TTL', 'litespeed-cache' ),
self::O_CACHE_TTL_FEED => __( 'Default Feed TTL', 'litespeed-cache' ),
self::O_CACHE_TTL_REST => __( 'Default REST TTL', 'litespeed-cache' ),
self::O_CACHE_TTL_STATUS => __( 'Default HTTP Status Code Page TTL', 'litespeed-cache' ),
self::O_CACHE_TTL_BROWSER => __( 'Browser Cache TTL', 'litespeed-cache' ),
self::O_CACHE_AJAX_TTL => __( 'AJAX Cache TTL', 'litespeed-cache' ),
self::O_AUTO_UPGRADE => __( 'Automatically Upgrade', 'litespeed-cache' ),
self::O_GUEST => __( 'Guest Mode', 'litespeed-cache' ),
self::O_GUEST_OPTM => __( 'Guest Optimization', 'litespeed-cache' ),
self::O_NEWS => __( 'Notifications', 'litespeed-cache' ),
self::O_CACHE_PRIV => __( 'Cache Logged-in Users', 'litespeed-cache' ),
self::O_CACHE_COMMENTER => __( 'Cache Commenters', 'litespeed-cache' ),
self::O_CACHE_REST => __( 'Cache REST API', 'litespeed-cache' ),
self::O_CACHE_PAGE_LOGIN => __( 'Cache Login Page', 'litespeed-cache' ),
self::O_CACHE_MOBILE => __( 'Cache Mobile', 'litespeed-cache' ),
self::O_CACHE_MOBILE_RULES => __( 'List of Mobile User Agents', 'litespeed-cache' ),
self::O_CACHE_PRIV_URI => __( 'Private Cached URIs', 'litespeed-cache' ),
self::O_CACHE_DROP_QS => __( 'Drop Query String', 'litespeed-cache' ),
self::O_OBJECT => __( 'Object Cache', 'litespeed-cache' ),
self::O_OBJECT_KIND => __( 'Method', 'litespeed-cache' ),
self::O_OBJECT_HOST => __( 'Host', 'litespeed-cache' ),
self::O_OBJECT_PORT => __( 'Port', 'litespeed-cache' ),
self::O_OBJECT_LIFE => __( 'Default Object Lifetime', 'litespeed-cache' ),
self::O_OBJECT_USER => __( 'Username', 'litespeed-cache' ),
self::O_OBJECT_PSWD => __( 'Password', 'litespeed-cache' ),
self::O_OBJECT_DB_ID => __( 'Redis Database ID', 'litespeed-cache' ),
self::O_OBJECT_GLOBAL_GROUPS => __( 'Global Groups', 'litespeed-cache' ),
self::O_OBJECT_NON_PERSISTENT_GROUPS => __( 'Do Not Cache Groups', 'litespeed-cache' ),
self::O_OBJECT_PERSISTENT => __( 'Persistent Connection', 'litespeed-cache' ),
self::O_OBJECT_ADMIN => __( 'Cache WP-Admin', 'litespeed-cache' ),
self::O_OBJECT_TRANSIENTS => __( 'Store Transients', 'litespeed-cache' ),
self::O_PURGE_ON_UPGRADE => __( 'Purge All On Upgrade', 'litespeed-cache' ),
self::O_PURGE_STALE => __( 'Serve Stale', 'litespeed-cache' ),
self::O_PURGE_TIMED_URLS => __( 'Scheduled Purge URLs', 'litespeed-cache' ),
self::O_PURGE_TIMED_URLS_TIME => __( 'Scheduled Purge Time', 'litespeed-cache' ),
self::O_CACHE_FORCE_URI => __( 'Force Cache URIs', 'litespeed-cache' ),
self::O_CACHE_FORCE_PUB_URI => __( 'Force Public Cache URIs', 'litespeed-cache' ),
self::O_CACHE_EXC => __( 'Do Not Cache URIs', 'litespeed-cache' ),
self::O_CACHE_EXC_QS => __( 'Do Not Cache Query Strings', 'litespeed-cache' ),
self::O_CACHE_EXC_CAT => __( 'Do Not Cache Categories', 'litespeed-cache' ),
self::O_CACHE_EXC_TAG => __( 'Do Not Cache Tags', 'litespeed-cache' ),
self::O_CACHE_EXC_ROLES => __( 'Do Not Cache Roles', 'litespeed-cache' ),
self::O_OPTM_CSS_MIN => __( 'CSS Minify', 'litespeed-cache' ),
self::O_OPTM_CSS_COMB => __( 'CSS Combine', 'litespeed-cache' ),
self::O_OPTM_CSS_COMB_EXT_INL => __( 'CSS Combine External and Inline', 'litespeed-cache' ),
self::O_OPTM_UCSS => __( 'Generate UCSS', 'litespeed-cache' ),
self::O_OPTM_UCSS_INLINE => __( 'UCSS Inline', 'litespeed-cache' ),
self::O_OPTM_UCSS_SELECTOR_WHITELIST => __( 'UCSS Selector Allowlist', 'litespeed-cache' ),
self::O_OPTM_UCSS_FILE_EXC_INLINE => __( 'UCSS Inline Excluded Files', 'litespeed-cache' ),
self::O_OPTM_UCSS_EXC => __( 'UCSS URI Excludes', 'litespeed-cache' ),
self::O_OPTM_JS_MIN => __( 'JS Minify', 'litespeed-cache' ),
self::O_OPTM_JS_COMB => __( 'JS Combine', 'litespeed-cache' ),
self::O_OPTM_JS_COMB_EXT_INL => __( 'JS Combine External and Inline', 'litespeed-cache' ),
self::O_OPTM_HTML_MIN => __( 'HTML Minify', 'litespeed-cache' ),
self::O_OPTM_HTML_LAZY => __( 'HTML Lazy Load Selectors', 'litespeed-cache' ),
self::O_OPTM_HTML_SKIP_COMMENTS => __( 'HTML Keep Comments', 'litespeed-cache' ),
self::O_OPTM_CSS_ASYNC => __( 'Load CSS Asynchronously', 'litespeed-cache' ),
self::O_OPTM_CCSS_PER_URL => __( 'CCSS Per URL', 'litespeed-cache' ),
self::O_OPTM_CSS_ASYNC_INLINE => __( 'Inline CSS Async Lib', 'litespeed-cache' ),
self::O_OPTM_CSS_FONT_DISPLAY => __( 'Font Display Optimization', 'litespeed-cache' ),
self::O_OPTM_JS_DEFER => __( 'Load JS Deferred', 'litespeed-cache' ),
self::O_OPTM_LOCALIZE => __( 'Localize Resources', 'litespeed-cache' ),
self::O_OPTM_LOCALIZE_DOMAINS => __( 'Localization Files', 'litespeed-cache' ),
self::O_OPTM_DNS_PREFETCH => __( 'DNS Prefetch', 'litespeed-cache' ),
self::O_OPTM_DNS_PREFETCH_CTRL => __( 'DNS Prefetch Control', 'litespeed-cache' ),
self::O_OPTM_DNS_PRECONNECT => __( 'DNS Preconnect', 'litespeed-cache' ),
self::O_OPTM_CSS_EXC => __( 'CSS Excludes', 'litespeed-cache' ),
self::O_OPTM_JS_DELAY_INC => __( 'JS Delayed Includes', 'litespeed-cache' ),
self::O_OPTM_JS_EXC => __( 'JS Excludes', 'litespeed-cache' ),
self::O_OPTM_QS_RM => __( 'Remove Query Strings', 'litespeed-cache' ),
self::O_OPTM_GGFONTS_ASYNC => __( 'Load Google Fonts Asynchronously', 'litespeed-cache' ),
self::O_OPTM_GGFONTS_RM => __( 'Remove Google Fonts', 'litespeed-cache' ),
self::O_OPTM_CCSS_CON => __( 'Critical CSS Rules', 'litespeed-cache' ),
self::O_OPTM_CCSS_SEP_POSTTYPE => __( 'Separate CCSS Cache Post Types', 'litespeed-cache' ),
self::O_OPTM_CCSS_SEP_URI => __( 'Separate CCSS Cache URIs', 'litespeed-cache' ),
self::O_OPTM_CCSS_SELECTOR_WHITELIST => __( 'CCSS Selector Allowlist', 'litespeed-cache' ),
self::O_OPTM_JS_DEFER_EXC => __( 'JS Deferred / Delayed Excludes', 'litespeed-cache' ),
self::O_OPTM_GM_JS_EXC => __( 'Guest Mode JS Excludes', 'litespeed-cache' ),
self::O_OPTM_EMOJI_RM => __( 'Remove WordPress Emoji', 'litespeed-cache' ),
self::O_OPTM_NOSCRIPT_RM => __( 'Remove Noscript Tags', 'litespeed-cache' ),
self::O_OPTM_EXC => __( 'URI Excludes', 'litespeed-cache' ),
self::O_OPTM_GUEST_ONLY => __( 'Optimize for Guests Only', 'litespeed-cache' ),
self::O_OPTM_EXC_ROLES => __( 'Role Excludes', 'litespeed-cache' ),
self::O_DISCUSS_AVATAR_CACHE => __( 'Gravatar Cache', 'litespeed-cache' ),
self::O_DISCUSS_AVATAR_CRON => __( 'Gravatar Cache Cron', 'litespeed-cache' ),
self::O_DISCUSS_AVATAR_CACHE_TTL => __( 'Gravatar Cache TTL', 'litespeed-cache' ),
self::O_MEDIA_LAZY => __( 'Lazy Load Images', 'litespeed-cache' ),
self::O_MEDIA_LAZY_EXC => __( 'Lazy Load Image Excludes', 'litespeed-cache' ),
self::O_MEDIA_LAZY_CLS_EXC => __( 'Lazy Load Image Class Name Excludes', 'litespeed-cache' ),
self::O_MEDIA_LAZY_PARENT_CLS_EXC => __( 'Lazy Load Image Parent Class Name Excludes', 'litespeed-cache' ),
self::O_MEDIA_IFRAME_LAZY_CLS_EXC => __( 'Lazy Load Iframe Class Name Excludes', 'litespeed-cache' ),
self::O_MEDIA_IFRAME_LAZY_PARENT_CLS_EXC => __( 'Lazy Load Iframe Parent Class Name Excludes', 'litespeed-cache' ),
self::O_MEDIA_LAZY_URI_EXC => __( 'Lazy Load URI Excludes', 'litespeed-cache' ),
self::O_MEDIA_LQIP_EXC => __( 'LQIP Excludes', 'litespeed-cache' ),
self::O_MEDIA_LAZY_PLACEHOLDER => __( 'Basic Image Placeholder', 'litespeed-cache' ),
self::O_MEDIA_PLACEHOLDER_RESP => __( 'Responsive Placeholder', 'litespeed-cache' ),
self::O_MEDIA_PLACEHOLDER_RESP_COLOR => __( 'Responsive Placeholder Color', 'litespeed-cache' ),
self::O_MEDIA_PLACEHOLDER_RESP_SVG => __( 'Responsive Placeholder SVG', 'litespeed-cache' ),
self::O_MEDIA_LQIP => __( 'LQIP Cloud Generator', 'litespeed-cache' ),
self::O_MEDIA_LQIP_QUAL => __( 'LQIP Quality', 'litespeed-cache' ),
self::O_MEDIA_LQIP_MIN_W => __( 'LQIP Minimum Dimensions', 'litespeed-cache' ),
self::O_MEDIA_PLACEHOLDER_RESP_ASYNC => __( 'Generate LQIP In Background', 'litespeed-cache' ),
self::O_MEDIA_IFRAME_LAZY => __( 'Lazy Load Iframes', 'litespeed-cache' ),
self::O_MEDIA_ADD_MISSING_SIZES => __( 'Add Missing Sizes', 'litespeed-cache' ),
self::O_MEDIA_VPI => __( 'Viewport Images', 'litespeed-cache' ),
self::O_MEDIA_VPI_CRON => __( 'Viewport Images Cron', 'litespeed-cache' ),
self::O_MEDIA_AUTO_RESCALE_ORI => __( 'Auto Rescale Original Images', 'litespeed-cache' ),
self::O_IMG_OPTM_AUTO => __( 'Auto Request Cron', 'litespeed-cache' ),
self::O_IMG_OPTM_ORI => __( 'Optimize Original Images', 'litespeed-cache' ),
self::O_IMG_OPTM_RM_BKUP => __( 'Remove Original Backups', 'litespeed-cache' ),
self::O_IMG_OPTM_WEBP => __( 'Next-Gen Image Format', 'litespeed-cache' ),
self::O_IMG_OPTM_LOSSLESS => __( 'Optimize Losslessly', 'litespeed-cache' ),
self::O_IMG_OPTM_SIZES_SKIPPED => __( 'Optimize Image Sizes', 'litespeed-cache' ),
self::O_IMG_OPTM_EXIF => __( 'Preserve EXIF/XMP data', 'litespeed-cache' ),
self::O_IMG_OPTM_WEBP_ATTR => __( 'WebP/AVIF Attribute To Replace', 'litespeed-cache' ),
self::O_IMG_OPTM_WEBP_REPLACE_SRCSET => __( 'WebP/AVIF For Extra srcset', 'litespeed-cache' ),
self::O_IMG_OPTM_JPG_QUALITY => __( 'WordPress Image Quality Control', 'litespeed-cache' ),
self::O_ESI => __( 'Enable ESI', 'litespeed-cache' ),
self::O_ESI_CACHE_ADMBAR => __( 'Cache Admin Bar', 'litespeed-cache' ),
self::O_ESI_CACHE_COMMFORM => __( 'Cache Comment Form', 'litespeed-cache' ),
self::O_ESI_NONCE => __( 'ESI Nonces', 'litespeed-cache' ),
self::O_CACHE_VARY_GROUP => __( 'Vary Group', 'litespeed-cache' ),
self::O_PURGE_HOOK_ALL => __( 'Purge All Hooks', 'litespeed-cache' ),
self::O_UTIL_NO_HTTPS_VARY => __( 'Improve HTTP/HTTPS Compatibility', 'litespeed-cache' ),
self::O_UTIL_INSTANT_CLICK => __( 'Instant Click', 'litespeed-cache' ),
self::O_CACHE_EXC_COOKIES => __( 'Do Not Cache Cookies', 'litespeed-cache' ),
self::O_CACHE_EXC_USERAGENTS => __( 'Do Not Cache User Agents', 'litespeed-cache' ),
self::O_CACHE_LOGIN_COOKIE => __( 'Login Cookie', 'litespeed-cache' ),
self::O_CACHE_VARY_COOKIES => __( 'Vary Cookies', 'litespeed-cache' ),
self::O_MISC_HEARTBEAT_FRONT => __( 'Frontend Heartbeat Control', 'litespeed-cache' ),
self::O_MISC_HEARTBEAT_FRONT_TTL => __( 'Frontend Heartbeat TTL', 'litespeed-cache' ),
self::O_MISC_HEARTBEAT_BACK => __( 'Backend Heartbeat Control', 'litespeed-cache' ),
self::O_MISC_HEARTBEAT_BACK_TTL => __( 'Backend Heartbeat TTL', 'litespeed-cache' ),
self::O_MISC_HEARTBEAT_EDITOR => __( 'Editor Heartbeat', 'litespeed-cache' ),
self::O_MISC_HEARTBEAT_EDITOR_TTL => __( 'Editor Heartbeat TTL', 'litespeed-cache' ),
self::O_CDN => __( 'Use CDN Mapping', 'litespeed-cache' ),
self::CDN_MAPPING_URL => __( 'CDN URL', 'litespeed-cache' ),
self::CDN_MAPPING_INC_IMG => __( 'Include Images', 'litespeed-cache' ),
self::CDN_MAPPING_INC_CSS => __( 'Include CSS', 'litespeed-cache' ),
self::CDN_MAPPING_INC_JS => __( 'Include JS', 'litespeed-cache' ),
self::CDN_MAPPING_FILETYPE => __( 'Include File Types', 'litespeed-cache' ),
self::O_CDN_ATTR => __( 'HTML Attribute To Replace', 'litespeed-cache' ),
self::O_CDN_ORI => __( 'Original URLs', 'litespeed-cache' ),
self::O_CDN_ORI_DIR => __( 'Included Directories', 'litespeed-cache' ),
self::O_CDN_EXC => __( 'Exclude Path', 'litespeed-cache' ),
self::O_CDN_CLOUDFLARE => __( 'Cloudflare API', 'litespeed-cache' ),
self::O_CDN_CLOUDFLARE_CLEAR => __( 'Clear Cloudflare cache', 'litespeed-cache' ),
self::O_CRAWLER => __( 'Crawler', 'litespeed-cache' ),
self::O_CRAWLER_CRAWL_INTERVAL => __( 'Crawl Interval', 'litespeed-cache' ),
self::O_CRAWLER_LOAD_LIMIT => __( 'Server Load Limit', 'litespeed-cache' ),
self::O_CRAWLER_ROLES => __( 'Role Simulation', 'litespeed-cache' ),
self::O_CRAWLER_COOKIES => __( 'Cookie Simulation', 'litespeed-cache' ),
self::O_CRAWLER_SITEMAP => __( 'Custom Sitemap', 'litespeed-cache' ),
self::O_DEBUG_DISABLE_ALL => __( 'Disable All Features', 'litespeed-cache' ),
self::O_DEBUG => __( 'Debug Log', 'litespeed-cache' ),
self::O_DEBUG_IPS => __( 'Admin IPs', 'litespeed-cache' ),
self::O_DEBUG_LEVEL => __( 'Debug Level', 'litespeed-cache' ),
self::O_DEBUG_FILESIZE => __( 'Log File Size Limit', 'litespeed-cache' ),
self::O_DEBUG_COLLAPSE_QS => __( 'Collapse Query Strings', 'litespeed-cache' ),
self::O_DEBUG_INC => __( 'Debug URI Includes', 'litespeed-cache' ),
self::O_DEBUG_EXC => __( 'Debug URI Excludes', 'litespeed-cache' ),
self::O_DEBUG_EXC_STRINGS => __( 'Debug String Excludes', 'litespeed-cache' ),
self::O_DB_OPTM_REVISIONS_MAX => __( 'Revisions Max Number', 'litespeed-cache' ),
self::O_DB_OPTM_REVISIONS_AGE => __( 'Revisions Max Age', 'litespeed-cache' ),
self::O_OPTIMAX => __( 'OptimaX', 'litespeed-cache' ),
];
if ( array_key_exists( $id, $_lang_list ) ) {
return $_lang_list[ $id ];
}
return 'N/A';
}
}
/**
* The object cache class.
*
* @since 1.8
* @package LiteSpeed
*/
namespace LiteSpeed;
defined( 'WPINC' ) || exit();
require_once dirname( __DIR__ ) . '/autoload.php';
/**
* Object cache handler using Redis or Memcached.
*
* NOTE: this class may be included without initialized core.
*
* @since 1.8
*/
class Object_Cache extends Root {
const LOG_TAG = '[Object_Cache]';
/**
* Debug option key.
*
* @var string
*/
const O_DEBUG = 'debug';
/**
* Object cache enable key.
*
* @var string
*/
const O_OBJECT = 'object';
/**
* Object kind (Redis/Memcached).
*
* @var string
*/
const O_OBJECT_KIND = 'object-kind';
/**
* Object host.
*
* @var string
*/
const O_OBJECT_HOST = 'object-host';
/**
* Object port.
*
* @var string
*/
const O_OBJECT_PORT = 'object-port';
/**
* Object life/TTL.
*
* @var string
*/
const O_OBJECT_LIFE = 'object-life';
/**
* Persistent connection flag.
*
* @var string
*/
const O_OBJECT_PERSISTENT = 'object-persistent';
/**
* Admin cache flag.
*
* @var string
*/
const O_OBJECT_ADMIN = 'object-admin';
/**
* Transients store flag.
*
* @var string
*/
const O_OBJECT_TRANSIENTS = 'object-transients';
/**
* DB index for Redis.
*
* @var string
*/
const O_OBJECT_DB_ID = 'object-db_id';
/**
* Username for auth.
*
* @var string
*/
const O_OBJECT_USER = 'object-user';
/**
* Password for auth.
*
* @var string
*/
const O_OBJECT_PSWD = 'object-pswd';
/**
* Global groups list.
*
* @var string
*/
const O_OBJECT_GLOBAL_GROUPS = 'object-global_groups';
/**
* Non-persistent groups list.
*
* @var string
*/
const O_OBJECT_NON_PERSISTENT_GROUPS = 'object-non_persistent_groups';
/**
* Connection instance.
*
* @var \Redis|\Memcached|null
*/
private $_conn;
/**
* Debug config.
*
* @var bool
*/
private $_cfg_debug;
/**
* Whether OC is enabled.
*
* @var bool
*/
private $_cfg_enabled;
/**
* True => Redis, false => Memcached.
*
* @var bool
*/
private $_cfg_method;
/**
* Host name.
*
* @var string
*/
private $_cfg_host;
/**
* Port number.
*
* @var int|string
*/
private $_cfg_port;
/**
* TTL in seconds.
*
* @var int
*/
private $_cfg_life;
/**
* Use persistent connection.
*
* @var bool
*/
private $_cfg_persistent;
/**
* Cache admin pages.
*
* @var bool
*/
private $_cfg_admin;
/**
* Store transients.
*
* @var bool
*/
private $_cfg_transients;
/**
* Redis DB index.
*
* @var int
*/
private $_cfg_db;
/**
* Auth username.
*
* @var string
*/
private $_cfg_user;
/**
* Auth password.
*
* @var string
*/
private $_cfg_pswd;
/**
* Default TTL in seconds.
*
* @var int
*/
private $_default_life = 360;
/**
* 'Redis' or 'Memcached'.
*
* @var string
*/
private $_oc_driver = 'Memcached'; // Redis or Memcached.
/**
* Global groups.
*
* @var array
*/
private $_global_groups = [];
/**
* Non-persistent groups.
*
* @var array
*/
private $_non_persistent_groups = [];
/**
* Init.
*
* NOTE: this class may be included without initialized core.
*
* @since 1.8
*
* @param array|false $cfg Optional configuration to bootstrap without core.
*/
public function __construct( $cfg = false ) {
if ( $cfg ) {
if ( ! is_array( $cfg[ Base::O_OBJECT_GLOBAL_GROUPS ] ) ) {
$cfg[ Base::O_OBJECT_GLOBAL_GROUPS ] = explode( "\n", $cfg[ Base::O_OBJECT_GLOBAL_GROUPS ] );
}
if ( ! is_array( $cfg[ Base::O_OBJECT_NON_PERSISTENT_GROUPS ] ) ) {
$cfg[ Base::O_OBJECT_NON_PERSISTENT_GROUPS ] = explode( "\n", $cfg[ Base::O_OBJECT_NON_PERSISTENT_GROUPS ] );
}
$this->_cfg_debug = $cfg[ Base::O_DEBUG ] ? $cfg[ Base::O_DEBUG ] : false;
$this->_cfg_method = $cfg[ Base::O_OBJECT_KIND ] ? true : false;
$this->_cfg_host = $cfg[ Base::O_OBJECT_HOST ];
$this->_cfg_port = $cfg[ Base::O_OBJECT_PORT ];
$this->_cfg_life = $cfg[ Base::O_OBJECT_LIFE ];
$this->_cfg_persistent = $cfg[ Base::O_OBJECT_PERSISTENT ];
$this->_cfg_admin = $cfg[ Base::O_OBJECT_ADMIN ];
$this->_cfg_transients = $cfg[ Base::O_OBJECT_TRANSIENTS ];
$this->_cfg_db = $cfg[ Base::O_OBJECT_DB_ID ];
$this->_cfg_user = $cfg[ Base::O_OBJECT_USER ];
$this->_cfg_pswd = $cfg[ Base::O_OBJECT_PSWD ];
$this->_global_groups = $cfg[ Base::O_OBJECT_GLOBAL_GROUPS ];
$this->_non_persistent_groups = $cfg[ Base::O_OBJECT_NON_PERSISTENT_GROUPS ];
if ( $this->_cfg_method ) {
$this->_oc_driver = 'Redis';
}
$this->_cfg_enabled = $cfg[ Base::O_OBJECT ] && class_exists( $this->_oc_driver ) && $this->_cfg_host;
} elseif ( defined( 'LITESPEED_CONF_LOADED' ) ) { // If OC is OFF, will hit here to init OC after conf initialized
$this->_cfg_debug = $this->conf( Base::O_DEBUG ) ? $this->conf( Base::O_DEBUG ) : false;
$this->_cfg_method = $this->conf( Base::O_OBJECT_KIND ) ? true : false;
$this->_cfg_host = $this->conf( Base::O_OBJECT_HOST );
$this->_cfg_port = $this->conf( Base::O_OBJECT_PORT );
$this->_cfg_life = $this->conf( Base::O_OBJECT_LIFE );
$this->_cfg_persistent = $this->conf( Base::O_OBJECT_PERSISTENT );
$this->_cfg_admin = $this->conf( Base::O_OBJECT_ADMIN );
$this->_cfg_transients = $this->conf( Base::O_OBJECT_TRANSIENTS );
$this->_cfg_db = $this->conf( Base::O_OBJECT_DB_ID );
$this->_cfg_user = $this->conf( Base::O_OBJECT_USER );
$this->_cfg_pswd = $this->conf( Base::O_OBJECT_PSWD );
$this->_global_groups = $this->conf( Base::O_OBJECT_GLOBAL_GROUPS );
$this->_non_persistent_groups = $this->conf( Base::O_OBJECT_NON_PERSISTENT_GROUPS );
if ( $this->_cfg_method ) {
$this->_oc_driver = 'Redis';
}
$this->_cfg_enabled = $this->conf( Base::O_OBJECT ) && class_exists( $this->_oc_driver ) && $this->_cfg_host;
} elseif ( defined( 'self::CONF_FILE' ) && file_exists( WP_CONTENT_DIR . '/' . self::CONF_FILE ) ) {
// Get cfg from _data_file.
// Use self::const to avoid loading more classes.
$cfg = \json_decode( file_get_contents( WP_CONTENT_DIR . '/' . self::CONF_FILE ), true );
if ( ! empty( $cfg[ self::O_OBJECT_HOST ] ) ) {
$this->_cfg_debug = ! empty( $cfg[ Base::O_DEBUG ] ) ? $cfg[ Base::O_DEBUG ] : false;
$this->_cfg_method = ! empty( $cfg[ self::O_OBJECT_KIND ] ) ? $cfg[ self::O_OBJECT_KIND ] : false;
$this->_cfg_host = $cfg[ self::O_OBJECT_HOST ];
$this->_cfg_port = $cfg[ self::O_OBJECT_PORT ];
$this->_cfg_life = ! empty( $cfg[ self::O_OBJECT_LIFE ] ) ? $cfg[ self::O_OBJECT_LIFE ] : $this->_default_life;
$this->_cfg_persistent = ! empty( $cfg[ self::O_OBJECT_PERSISTENT ] ) ? $cfg[ self::O_OBJECT_PERSISTENT ] : false;
$this->_cfg_admin = ! empty( $cfg[ self::O_OBJECT_ADMIN ] ) ? $cfg[ self::O_OBJECT_ADMIN ] : false;
$this->_cfg_transients = ! empty( $cfg[ self::O_OBJECT_TRANSIENTS ] ) ? $cfg[ self::O_OBJECT_TRANSIENTS ] : false;
$this->_cfg_db = ! empty( $cfg[ self::O_OBJECT_DB_ID ] ) ? $cfg[ self::O_OBJECT_DB_ID ] : 0;
$this->_cfg_user = ! empty( $cfg[ self::O_OBJECT_USER ] ) ? $cfg[ self::O_OBJECT_USER ] : '';
$this->_cfg_pswd = ! empty( $cfg[ self::O_OBJECT_PSWD ] ) ? $cfg[ self::O_OBJECT_PSWD ] : '';
$this->_global_groups = ! empty( $cfg[ self::O_OBJECT_GLOBAL_GROUPS ] ) ? $cfg[ self::O_OBJECT_GLOBAL_GROUPS ] : [];
$this->_non_persistent_groups = ! empty( $cfg[ self::O_OBJECT_NON_PERSISTENT_GROUPS ] ) ? $cfg[ self::O_OBJECT_NON_PERSISTENT_GROUPS ] : [];
if ( $this->_cfg_method ) {
$this->_oc_driver = 'Redis';
}
$this->_cfg_enabled = class_exists( $this->_oc_driver ) && $this->_cfg_host;
} else {
$this->_cfg_enabled = false;
}
} else {
$this->_cfg_enabled = false;
}
}
/**
* Add debug.
*
* @since 6.3
* @access private
*
* @param string $text Log text.
* @return void
*/
private function debug_oc( $text ) {
if ( defined( 'LSCWP_LOG' ) ) {
self::debug( $text );
return;
}
if ( Base::VAL_ON2 !== $this->_cfg_debug ) {
return;
}
$litespeed_data_folder = defined( 'LITESPEED_DATA_FOLDER' ) ? LITESPEED_DATA_FOLDER : 'litespeed';
$lscwp_content_dir = defined( 'LSCWP_CONTENT_DIR' ) ? LSCWP_CONTENT_DIR : WP_CONTENT_DIR;
$litespeed_static_dir = $lscwp_content_dir . '/' . $litespeed_data_folder;
$log_path_prefix = $litespeed_static_dir . '/debug/';
$log_file = $log_path_prefix . Debug2::FilePath( 'debug' );
if ( file_exists( $log_path_prefix . 'index.php' ) && file_exists( $log_file ) ) {
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
error_log(gmdate('m/d/y H:i:s') . ' - OC - ' . $text . PHP_EOL, 3, $log_file);
}
}
/**
* Get `Store Transients` setting value.
*
* @since 1.8.3
* @access public
*
* @param string $group Group name.
* @return bool
*/
public function store_transients( $group ) {
return $this->_cfg_transients && $this->_is_transients_group( $group );
}
/**
* Check if the group belongs to transients or not.
*
* @since 1.8.3
* @access private
*
* @param string $group Group name.
* @return bool
*/
private function _is_transients_group( $group ) {
return in_array( $group, [ 'transient', 'site-transient' ], true );
}
/**
* Update WP object cache file config.
*
* @since 1.8
* @access public
*
* @param array $options Options to apply after update.
* @return void
*/
public function update_file( $options ) {
$changed = false;
// NOTE: When included in oc.php, `LSCWP_DIR` will show undefined, so this must be assigned/generated when used.
$_oc_ori_file = LSCWP_DIR . 'lib/object-cache.php';
$_oc_wp_file = WP_CONTENT_DIR . '/object-cache.php';
// Update cls file.
if ( ! file_exists( $_oc_wp_file ) || md5_file( $_oc_wp_file ) !== md5_file( $_oc_ori_file ) ) {
$this->debug_oc( 'copying object-cache.php file to ' . $_oc_wp_file );
copy( $_oc_ori_file, $_oc_wp_file );
$changed = true;
}
/**
* Clear object cache.
*/
if ( $changed ) {
$this->_reconnect( $options );
}
}
/**
* Remove object cache file.
*
* @since 1.8.2
* @access public
*
* @return void
*/
public function del_file() {
// NOTE: When included in oc.php, `LSCWP_DIR` will show undefined, so this must be assigned/generated when used.
$_oc_ori_file = LSCWP_DIR . 'lib/object-cache.php';
$_oc_wp_file = WP_CONTENT_DIR . '/object-cache.php';
if ( file_exists( $_oc_wp_file ) && md5_file( $_oc_wp_file ) === md5_file( $_oc_ori_file ) ) {
$this->debug_oc( 'removing ' . $_oc_wp_file );
wp_delete_file( $_oc_wp_file );
}
}
/**
* Try to build connection.
*
* @since 1.8
* @access public
*
* @return bool|null False on failure, true on success, null if unsupported.
*/
public function test_connection() {
return $this->_connect();
}
/**
* Force to connect with this setting.
*
* @since 1.8
* @access private
*
* @param array $cfg Reconnect configuration.
* @return void
*/
private function _reconnect( $cfg ) {
$this->debug_oc( 'Reconnecting' );
if ( isset( $this->_conn ) ) {
// error_log( 'Object: Quitting existing connection!' );
$this->debug_oc( 'Quitting existing connection' );
$this->flush();
$this->_conn = null;
$this->cls( false, true );
}
$cls = $this->cls( false, false, $cfg );
$cls->_connect();
if ( isset( $cls->_conn ) ) {
$cls->flush();
}
}
/**
* Connect to Memcached/Redis server.
*
* @since 1.8
* @access private
*
* @return bool|null False on failure, true on success, null if driver missing.
*/
private function _connect() {
if ( isset( $this->_conn ) ) {
// error_log( 'Object: _connected' );
return true;
}
if ( ! class_exists( $this->_oc_driver ) || ! $this->_cfg_host ) {
$this->debug_oc( '_oc_driver cls non existed or _cfg_host missed: ' . $this->_oc_driver . ' [_cfg_host] ' . $this->_cfg_host . ':' . $this->_cfg_port );
return null;
}
if ( defined( 'LITESPEED_OC_FAILURE' ) ) {
$this->debug_oc( 'LITESPEED_OC_FAILURE const defined' );
return false;
}
$this->debug_oc( 'Init ' . $this->_oc_driver . ' connection to ' . $this->_cfg_host . ':' . $this->_cfg_port );
$failed = false;
/**
* Connect to Redis.
*
* @since 1.8.1
* @see https://github.com/phpredis/phpredis/#example-1
*/
if ( 'Redis' === $this->_oc_driver ) {
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_set_error_handler
set_error_handler( 'litespeed_exception_handler' );
try {
$this->_conn = new \Redis();
// error_log( 'Object: _connect Redis' );
if ( $this->_cfg_persistent ) {
if ( $this->_cfg_port ) {
$this->_conn->pconnect( $this->_cfg_host, $this->_cfg_port );
} else {
$this->_conn->pconnect( $this->_cfg_host );
}
} elseif ( $this->_cfg_port ) {
$this->_conn->connect( $this->_cfg_host, $this->_cfg_port );
} else {
$this->_conn->connect( $this->_cfg_host );
}
if ( $this->_cfg_pswd ) {
if ( $this->_cfg_user ) {
$this->_conn->auth( [ $this->_cfg_user, $this->_cfg_pswd ] );
} else {
$this->_conn->auth( $this->_cfg_pswd );
}
}
if (defined('Redis::OPT_REPLY_LITERAL')) {
$this->debug_oc( 'Redis set OPT_REPLY_LITERAL' );
$this->_conn->setOption(\Redis::OPT_REPLY_LITERAL, true);
}
if ( $this->_cfg_db ) {
$this->_conn->select( $this->_cfg_db );
}
$res = $this->_conn->rawCommand('PING');
if ( 'PONG' !== $res ) {
$this->debug_oc( 'Redis resp is wrong: ' . $res );
$failed = true;
}
} catch ( \Exception $e ) {
$this->debug_oc( 'Redis connect exception: ' . $e->getMessage() );
$failed = true;
} catch ( \ErrorException $e ) {
$this->debug_oc( 'Redis connect error: ' . $e->getMessage() );
$failed = true;
}
restore_error_handler();
} else {
// Connect to Memcached.
if ( $this->_cfg_persistent ) {
$this->_conn = new \Memcached( $this->_get_mem_id() );
// Check memcached persistent connection.
if ( $this->_validate_mem_server() ) {
// error_log( 'Object: _validate_mem_server' );
$this->debug_oc( 'Got persistent ' . $this->_oc_driver . ' connection' );
return true;
}
$this->debug_oc( 'No persistent ' . $this->_oc_driver . ' server list!' );
} else {
// error_log( 'Object: new memcached!' );
$this->_conn = new \Memcached();
}
$this->_conn->addServer( $this->_cfg_host, (int) $this->_cfg_port );
/**
* Add SASL auth.
*
* @since 1.8.1
* @since 2.9.6 Fixed SASL connection @see https://www.litespeedtech.com/support/wiki/doku.php/litespeed_wiki:lsmcd:new_sasl
*/
if ( $this->_cfg_user && $this->_cfg_pswd && method_exists( $this->_conn, 'setSaslAuthData' ) ) {
$this->_conn->setOption( \Memcached::OPT_BINARY_PROTOCOL, true );
$this->_conn->setOption( \Memcached::OPT_COMPRESSION, false );
$this->_conn->setSaslAuthData( $this->_cfg_user, $this->_cfg_pswd );
}
// Check connection.
if ( ! $this->_validate_mem_server() ) {
$failed = true;
}
}
// If failed to connect.
if ( $failed ) {
$this->debug_oc( '❌ Failed to connect ' . $this->_oc_driver . ' server!' );
$this->_conn = null;
$this->_cfg_enabled = false;
! defined( 'LITESPEED_OC_FAILURE' ) && define( 'LITESPEED_OC_FAILURE', true );
// error_log( 'Object: false!' );
return false;
}
$this->debug_oc( '✅ Connected to ' . $this->_oc_driver . ' server.' );
return true;
}
/**
* Check if the connected memcached host is the one in cfg.
*
* @since 1.8
* @access private
*
* @return bool
*/
private function _validate_mem_server() {
$mem_list = $this->_conn->getStats();
if ( empty( $mem_list ) ) {
return false;
}
foreach ( $mem_list as $k => $v ) {
if ( substr( $k, 0, strlen( $this->_cfg_host ) ) !== $this->_cfg_host ) {
continue;
}
if ( ! empty( $v['pid'] ) || ! empty( $v['curr_connections'] ) ) {
return true;
}
}
return false;
}
/**
* Get memcached unique id to be used for connecting.
*
* @since 1.8
* @access private
*
* @return string
*/
private function _get_mem_id() {
$mem_id = 'litespeed';
if ( is_multisite() ) {
$mem_id .= '_' . get_current_blog_id();
}
return $mem_id;
}
/**
* Get cache.
*
* @since 1.8
* @access public
*
* @param string $key Cache key.
* @return mixed|null
*/
public function get( $key ) {
if ( ! $this->_cfg_enabled ) {
return null;
}
if ( ! $this->_can_cache() ) {
return null;
}
if ( ! $this->_connect() ) {
return null;
}
$res = $this->_conn->get( $key );
return $res;
}
/**
* Set cache.
*
* @since 1.8
* @access public
*
* @param string $key Cache key.
* @param mixed $data Data to store.
* @param int $expire TTL seconds.
* @return bool|null
*/
public function set( $key, $data, $expire ) {
if ( ! $this->_cfg_enabled ) {
return null;
}
/**
* To fix the Cloud callback cached as its frontend call but the hash is generated in backend
* Bug found by Stan at Jan/10/2020
*/
// if ( ! $this->_can_cache() ) {
// return null;
// }
if ( ! $this->_connect() ) {
return null;
}
$ttl = $expire ? $expire : $this->_cfg_life;
if ( 'Redis' === $this->_oc_driver ) {
try {
$res = $this->_conn->setEx( $key, $ttl, $data );
} catch ( \RedisException $ex ) {
$res = false;
$msg = sprintf( __( 'Redis encountered a fatal error: %1$s (code: %2$d)', 'litespeed-cache' ), $ex->getMessage(), $ex->getCode() );
$this->debug_oc( $msg );
Admin_Display::error( $msg );
}
} else {
$res = $this->_conn->set( $key, $data, $ttl );
}
return $res;
}
/**
* Check if can cache or not.
*
* @since 1.8
* @access private
*
* @return bool
*/
private function _can_cache() {
if ( ! $this->_cfg_admin && defined( 'WP_ADMIN' ) ) {
return false;
}
return true;
}
/**
* Delete cache.
*
* @since 1.8
* @access public
*
* @param string $key Cache key.
* @return bool|null
*/
public function delete( $key ) {
if ( ! $this->_cfg_enabled ) {
return null;
}
if ( ! $this->_connect() ) {
return null;
}
if ( 'Redis' === $this->_oc_driver ) {
$res = $this->_conn->del( $key );
} else {
$res = $this->_conn->delete( $key );
}
return (bool) $res;
}
/**
* Clear all cache.
*
* @since 1.8
* @access public
*
* @return bool|null
*/
public function flush() {
if ( ! $this->_cfg_enabled ) {
$this->debug_oc( 'bypass flushing' );
return null;
}
if ( ! $this->_connect() ) {
return null;
}
$this->debug_oc( 'flush!' );
if ( 'Redis' === $this->_oc_driver ) {
$res = $this->_conn->flushDb();
} else {
$res = $this->_conn->flush();
$this->_conn->resetServerList();
}
return $res;
}
/**
* Add global groups.
*
* @since 1.8
* @access public
*
* @param string|string[] $groups Group(s) to add.
* @return void
*/
public function add_global_groups( $groups ) {
if ( ! is_array( $groups ) ) {
$groups = [ $groups ];
}
$this->_global_groups = array_merge( $this->_global_groups, $groups );
$this->_global_groups = array_unique( $this->_global_groups );
}
/**
* Check if is in global groups or not.
*
* @since 1.8
* @access public
*
* @param string $group Group name.
* @return bool
*/
public function is_global( $group ) {
return in_array( $group, $this->_global_groups, true );
}
/**
* Add non persistent groups.
*
* @since 1.8
* @access public
*
* @param string|string[] $groups Group(s) to add.
* @return void
*/
public function add_non_persistent_groups( $groups ) {
if ( ! is_array( $groups ) ) {
$groups = [ $groups ];
}
$this->_non_persistent_groups = array_merge( $this->_non_persistent_groups, $groups );
$this->_non_persistent_groups = array_unique( $this->_non_persistent_groups );
}
/**
* Check if is in non persistent groups or not.
*
* @since 1.8
* @access public
*
* @param string $group Group name.
* @return bool
*/
public function is_non_persistent( $group ) {
return in_array( $group, $this->_non_persistent_groups, true );
}
}
// phpcs:ignoreFile
/**
* The optimize class.
*
* @since 1.2.2
*/
namespace LiteSpeed;
defined('WPINC') || exit();
class Optimize extends Base {
const LOG_TAG = '🎢';
const LIB_FILE_CSS_ASYNC = 'assets/js/css_async.min.js';
const LIB_FILE_WEBFONTLOADER = 'assets/js/webfontloader.min.js';
const LIB_FILE_JS_DELAY = 'assets/js/js_delay.min.js';
const ITEM_TIMESTAMP_PURGE_CSS = 'timestamp_purge_css';
const DUMMY_CSS_REGEX = "##isU";
private $content;
private $content_ori;
private $cfg_css_min;
private $cfg_css_comb;
private $cfg_js_min;
private $cfg_js_comb;
private $cfg_css_async;
private $cfg_js_delay_inc = array();
private $cfg_js_defer;
private $cfg_js_defer_exc = false;
private $cfg_ggfonts_async;
private $_conf_css_font_display;
private $cfg_ggfonts_rm;
private $dns_prefetch;
private $dns_preconnect;
private $_ggfonts_urls = array();
private $_ccss;
private $_ucss = false;
private $__optimizer;
private $html_foot = ''; // The html info append to
private $html_head = ''; // The html info append to
private $html_head_early = ''; // The html info prepend to top of head
private static $_var_i = 0;
private $_var_preserve_js = array();
private $_request_url;
/**
* Constructor
*
* @since 4.0
*/
public function __construct() {
self::debug('init');
$this->__optimizer = $this->cls('Optimizer');
}
/**
* Init optimizer
*
* @since 3.0
* @access protected
*/
public function init() {
$this->cfg_css_async = defined('LITESPEED_GUEST_OPTM') || $this->conf(self::O_OPTM_CSS_ASYNC);
if ($this->cfg_css_async) {
if (!$this->cls('Cloud')->activated()) {
self::debug('❌ CCSS set to OFF due to QC not activated');
$this->cfg_css_async = false;
}
if ((defined('LITESPEED_GUEST_OPTM') || ($this->conf(self::O_OPTM_UCSS) && $this->conf(self::O_OPTM_CSS_COMB))) && $this->conf(self::O_OPTM_UCSS_INLINE)) {
self::debug('⚠️ CCSS set to OFF due to UCSS Inline');
$this->cfg_css_async = false;
}
}
$this->cfg_js_defer = $this->conf(self::O_OPTM_JS_DEFER);
if (defined('LITESPEED_GUEST_OPTM')) {
$this->cfg_js_defer = 2;
}
if ($this->cfg_js_defer == 2) {
add_filter(
'litespeed_optm_cssjs',
function ( $con, $file_type ) {
if ($file_type == 'js') {
$con = str_replace('DOMContentLoaded', 'DOMContentLiteSpeedLoaded', $con);
// $con = str_replace( 'addEventListener("load"', 'addEventListener("litespeedLoad"', $con );
}
return $con;
},
20,
2
);
}
// To remove emoji from WP
if ($this->conf(self::O_OPTM_EMOJI_RM)) {
$this->_emoji_rm();
}
if ($this->conf(self::O_OPTM_QS_RM)) {
add_filter('style_loader_src', array( $this, 'remove_query_strings' ), 999);
add_filter('script_loader_src', array( $this, 'remove_query_strings' ), 999);
}
// GM JS exclude @since 4.1
if (defined('LITESPEED_GUEST_OPTM')) {
$this->cfg_js_defer_exc = apply_filters('litespeed_optm_gm_js_exc', $this->conf(self::O_OPTM_GM_JS_EXC));
} else {
/**
* Exclude js from deferred setting
*
* @since 1.5
*/
if ($this->cfg_js_defer) {
add_filter('litespeed_optm_js_defer_exc', array( $this->cls('Data'), 'load_js_defer_exc' ));
$this->cfg_js_defer_exc = apply_filters('litespeed_optm_js_defer_exc', $this->conf(self::O_OPTM_JS_DEFER_EXC));
$this->cfg_js_delay_inc = apply_filters('litespeed_optm_js_delay_inc', $this->conf(self::O_OPTM_JS_DELAY_INC));
}
}
// Add vary filter for Role Excludes @since 1.6
add_filter('litespeed_vary', array( $this, 'vary_add_role_exclude' ));
// DNS optm (Prefetch/Preconnect) @since 7.3
$this->_dns_optm_init();
add_filter('litespeed_buffer_finalize', array( $this, 'finalize' ), 20);
// Inject a dummy CSS file to control final optimized data location in
wp_enqueue_style(Core::PLUGIN_NAME . '-dummy', LSWCP_PLUGIN_URL . 'assets/css/litespeed-dummy.css');
}
/**
* Exclude role from optimization filter
*
* @since 1.6
* @access public
*/
public function vary_add_role_exclude( $vary ) {
if ($this->cls('Conf')->in_optm_exc_roles()) {
$vary['role_exclude_optm'] = 1;
}
return $vary;
}
/**
* Remove emoji from WP
*
* @since 1.4
* @since 2.9.8 Changed to private
* @access private
*/
private function _emoji_rm() {
remove_action('wp_head', 'print_emoji_detection_script', 7);
remove_action('admin_print_scripts', 'print_emoji_detection_script');
remove_filter('the_content_feed', 'wp_staticize_emoji');
remove_filter('comment_text_rss', 'wp_staticize_emoji');
/**
* Added for better result
*
* @since 1.6.2.1
*/
remove_action('wp_print_styles', 'print_emoji_styles');
remove_action('admin_print_styles', 'print_emoji_styles');
remove_filter('wp_mail', 'wp_staticize_emoji_for_email');
}
/**
* Delete file-based cache folder
*
* @since 2.1
* @access public
*/
public function rm_cache_folder( $subsite_id = false ) {
if ($subsite_id) {
file_exists(LITESPEED_STATIC_DIR . '/css/' . $subsite_id) && File::rrmdir(LITESPEED_STATIC_DIR . '/css/' . $subsite_id);
file_exists(LITESPEED_STATIC_DIR . '/js/' . $subsite_id) && File::rrmdir(LITESPEED_STATIC_DIR . '/js/' . $subsite_id);
return;
}
file_exists(LITESPEED_STATIC_DIR . '/css') && File::rrmdir(LITESPEED_STATIC_DIR . '/css');
file_exists(LITESPEED_STATIC_DIR . '/js') && File::rrmdir(LITESPEED_STATIC_DIR . '/js');
}
/**
* Remove QS
*
* @since 1.3
* @access public
*/
public function remove_query_strings( $src ) {
if (strpos($src, '_litespeed_rm_qs=0') || strpos($src, '/recaptcha')) {
return $src;
}
if (!Utility::is_internal_file($src)) {
return $src;
}
if (strpos($src, '.js?') !== false || strpos($src, '.css?') !== false) {
$src = preg_replace('/\?.*/', '', $src);
}
return $src;
}
/**
* Run optimize process
* NOTE: As this is after cache finalized, can NOT set any cache control anymore
*
* @since 1.2.2
* @access public
* @return string The content that is after optimization
*/
public function finalize( $content ) {
$content = $this->_finalize($content);
// Fallback to replace dummy css placeholder
if (false !== preg_match(self::DUMMY_CSS_REGEX, $content)) {
self::debug('Fallback to drop dummy CSS');
$content = preg_replace( self::DUMMY_CSS_REGEX, '', $content );
}
return $content;
}
private function _finalize( $content ) {
if (defined('LITESPEED_NO_PAGEOPTM')) {
self::debug2('bypass: NO_PAGEOPTM const');
return $content;
}
if (!defined('LITESPEED_IS_HTML')) {
self::debug('bypass: Not frontend HTML type');
return $content;
}
if (!defined('LITESPEED_GUEST_OPTM')) {
if (!Control::is_cacheable()) {
self::debug('bypass: Not cacheable');
return $content;
}
// Check if hit URI excludes
add_filter('litespeed_optm_uri_exc', array( $this->cls('Data'), 'load_optm_uri_exc' ));
$excludes = apply_filters('litespeed_optm_uri_exc', $this->conf(self::O_OPTM_EXC));
$result = Utility::str_hit_array($_SERVER['REQUEST_URI'], $excludes);
if ($result) {
self::debug('bypass: hit URI Excludes setting: ' . $result);
return $content;
}
}
self::debug('start');
$this->content_ori = $this->content = $content;
$this->_optimize();
return $this->content;
}
/**
* Optimize css src
*
* @since 1.2.2
* @access private
*/
private function _optimize() {
global $wp;
// get current request url
$permalink_structure = get_option( 'permalink_structure' );
if ( ! empty( $permalink_structure ) ) {
$this->_request_url = trailingslashit( home_url( $wp->request ) );
} else {
$qs_add = $wp->query_string ? '?' . (string) $wp->query_string : '' ;
$this->_request_url = home_url( $wp->request ) . $qs_add;
}
$this->cfg_css_min = defined('LITESPEED_GUEST_OPTM') || $this->conf(self::O_OPTM_CSS_MIN);
$this->cfg_css_comb = defined('LITESPEED_GUEST_OPTM') || $this->conf(self::O_OPTM_CSS_COMB);
$this->cfg_js_min = defined('LITESPEED_GUEST_OPTM') || $this->conf(self::O_OPTM_JS_MIN);
$this->cfg_js_comb = defined('LITESPEED_GUEST_OPTM') || $this->conf(self::O_OPTM_JS_COMB);
$this->cfg_ggfonts_rm = defined('LITESPEED_GUEST_OPTM') || $this->conf(self::O_OPTM_GGFONTS_RM);
$this->cfg_ggfonts_async = !defined('LITESPEED_GUEST_OPTM') && $this->conf(self::O_OPTM_GGFONTS_ASYNC); // forced rm already
$this->_conf_css_font_display = !defined('LITESPEED_GUEST_OPTM') && $this->conf(self::O_OPTM_CSS_FONT_DISPLAY);
if (!$this->cls('Router')->can_optm()) {
self::debug('bypass: admin/feed/preview');
return;
}
if ($this->cfg_css_async) {
$this->_ccss = $this->cls('CSS')->prepare_ccss();
if (!$this->_ccss) {
self::debug('❌ CCSS set to OFF due to CCSS not generated yet');
$this->cfg_css_async = false;
} elseif (strpos($this->_ccss, '' . $this->html_head;
}
// Check if there is any critical css rules setting
if ($this->cfg_css_async && $this->_ccss) {
$this->html_head = $this->_ccss . $this->html_head;
}
// Replace html head part
$this->html_head_early = apply_filters('litespeed_optm_html_head_early', $this->html_head_early);
if ($this->html_head_early) {
// Put header content to be after charset
if (false !== strpos($this->content, '');
$this->content = preg_replace('#]*)>#isU', '' . $this->html_head_early, $this->content, 1);
} else {
self::debug('Put early optm data to be right after ');
$this->content = preg_replace('#]*)>#isU', '' . $this->html_head_early, $this->content, 1);
}
}
$this->html_head = apply_filters('litespeed_optm_html_head', $this->html_head);
if ($this->html_head) {
if (apply_filters('litespeed_optm_html_after_head', false)) {
$this->content = str_replace('', $this->html_head . '', $this->content);
} else {
// Put header content to dummy css position
if (false !== preg_match(self::DUMMY_CSS_REGEX, $this->content)) {
self::debug('Put optm data to dummy css location');
$this->content = preg_replace( self::DUMMY_CSS_REGEX, $this->html_head, $this->content );
}
// Fallback: try to be after charset
elseif (strpos($this->content, '');
$this->content = preg_replace('#]*)>#isU', '' . $this->html_head, $this->content, 1);
} else {
self::debug('Put optm data to be after ');
$this->content = preg_replace('#]*)>#isU', '' . $this->html_head, $this->content, 1);
}
}
}
// Replace html foot part
$this->html_foot = apply_filters('litespeed_optm_html_foot', $this->html_foot);
if ($this->html_foot) {
$this->content = str_replace('', $this->html_foot . '', $this->content);
}
// Drop noscript if enabled
if ($this->conf(self::O_OPTM_NOSCRIPT_RM)) {
// $this->content = preg_replace( '##isU', '', $this->content );
}
// HTML minify
if (defined('LITESPEED_GUEST_OPTM') || $this->conf(self::O_OPTM_HTML_MIN)) {
$this->content = $this->__optimizer->html_min($this->content);
}
}
/**
* Build a full JS tag
*
* @since 4.0
*/
private function _build_js_tag( $src ) {
if ($this->cfg_js_defer === 2 || Utility::str_hit_array($src, $this->cfg_js_delay_inc)) {
return '';
}
if ($this->cfg_js_defer) {
return '';
}
return '';
}
/**
* Build a full inline JS snippet
*
* @since 4.0
*/
private function _build_js_inline( $script, $minified = false ) {
if ($this->cfg_js_defer) {
$deferred = $this->_js_inline_defer($script, false, $minified);
if ($deferred) {
return $deferred;
}
}
return '';
}
/**
* Load JS delay lib
*
* @since 4.0
*/
private function _maybe_js_delay() {
if ($this->cfg_js_defer !== 2 && !$this->cfg_js_delay_inc) {
return;
}
if (!defined('LITESPEED_JS_DELAY_LIB_LOADED')) {
define('LITESPEED_JS_DELAY_LIB_LOADED', true);
$this->html_foot .= '';
}
}
/**
* Google font async
*
* @since 2.7.3
* @access private
*/
private function _async_ggfonts() {
if (!$this->cfg_ggfonts_async || !$this->_ggfonts_urls) {
return;
}
self::debug2('google fonts async found: ', $this->_ggfonts_urls);
$this->html_head_early .= '';
/**
* Append fonts
*
* Could be multiple fonts
*
*
*
* -> family: PT Sans:400,700|PT Sans Narrow:400|Montserrat:600
*
*/
$script = 'WebFontConfig={google:{families:[';
$families = array();
foreach ($this->_ggfonts_urls as $v) {
$qs = wp_specialchars_decode($v);
$qs = urldecode($qs);
$qs = parse_url($qs, PHP_URL_QUERY);
parse_str($qs, $qs);
if (empty($qs['family'])) {
self::debug('ERR ggfonts failed to find family: ' . $v);
continue;
}
$subset = empty($qs['subset']) ? '' : ':' . $qs['subset'];
foreach (array_filter(explode('|', $qs['family'])) as $v2) {
$families[] = Str::trim_quotes($v2 . $subset);
}
}
$script .= '"' . implode('","', $families) . ($this->_conf_css_font_display ? '&display=swap' : '') . '"';
$script .= ']}};';
// if webfontloader lib was loaded before WebFontConfig variable, call WebFont.load
$script .= 'if ( typeof WebFont === "object" && typeof WebFont.load === "function" ) { WebFont.load( WebFontConfig ); }';
$html = $this->_build_js_inline($script);
// https://cdnjs.cloudflare.com/ajax/libs/webfont/1.6.28/webfontloader.js
$webfont_lib_url = LSWCP_PLUGIN_URL . self::LIB_FILE_WEBFONTLOADER;
// default async, if js defer set use defer
$html .= $this->_build_js_tag($webfont_lib_url);
// Put this in the very beginning for preconnect
$this->html_head = $html . $this->html_head;
}
/**
* Font optm
*
* @since 3.0
* @access private
*/
private function _font_optm() {
if (!$this->_conf_css_font_display || !$this->_ggfonts_urls) {
return;
}
self::debug2('google fonts optm ', $this->_ggfonts_urls);
foreach ($this->_ggfonts_urls as $v) {
if (strpos($v, 'display=')) {
continue;
}
$this->html_head = str_replace($v, $v . '&display=swap', $this->html_head);
$this->html_foot = str_replace($v, $v . '&display=swap', $this->html_foot);
$this->content = str_replace($v, $v . '&display=swap', $this->content);
}
}
/**
* Prefetch DNS
*
* @since 1.7.1 DNS prefetch
* @since 5.6.1 DNS preconnect
* @access private
*/
private function _dns_optm_init() {
// Widely enable link DNS prefetch
if (defined('LITESPEED_GUEST_OPTM') || $this->conf(self::O_OPTM_DNS_PREFETCH_CTRL)) {
@header('X-DNS-Prefetch-Control: on');
}
$this->dns_prefetch = $this->conf(self::O_OPTM_DNS_PREFETCH);
$this->dns_preconnect = $this->conf(self::O_OPTM_DNS_PRECONNECT);
if (!$this->dns_prefetch && !$this->dns_preconnect) {
return;
}
if (function_exists('wp_resource_hints')) {
add_filter('wp_resource_hints', array( $this, 'dns_optm_filter' ), 10, 2);
} else {
add_action('litespeed_optm', array( $this, 'dns_optm_output' ));
}
}
/**
* DNS optm hook for WP
*
* @since 1.7.1
* @access public
*/
public function dns_optm_filter( $urls, $relation_type ) {
if ('dns-prefetch' === $relation_type) {
foreach ($this->dns_prefetch as $v) {
if ($v) {
$urls[] = $v;
}
}
}
if ('preconnect' === $relation_type) {
foreach ($this->dns_preconnect as $v) {
if ($v) {
$urls[] = $v;
}
}
}
return $urls;
}
/**
* DNS optm output directly
*
* @since 1.7.1 DNS prefetch
* @since 5.6.1 DNS preconnect
* @access public
*/
public function dns_optm_output() {
foreach ($this->dns_prefetch as $v) {
if ($v) {
$this->html_head_early .= '';
}
}
foreach ($this->dns_preconnect as $v) {
if ($v) {
$this->html_head_early .= '';
}
}
}
/**
* Run minify with src queue list
*
* @since 1.2.2
* @access private
*/
private function _src_queue_handler( $src_list, $html_list, $file_type = 'css' ) {
$html_list_ori = $html_list;
$can_webp = $this->cls('Media')->webp_support();
$tag = $file_type == 'css' ? 'link' : 'script';
foreach ($src_list as $key => $src_info) {
// Minify inline CSS/JS
if (!empty($src_info['inl'])) {
if ($file_type == 'css') {
$code = Optimizer::minify_css($src_info['src']);
$can_webp && ($code = $this->cls('Media')->replace_background_webp($code));
$snippet = str_replace($src_info['src'], $code, $html_list[$key]);
} else {
// Inline defer JS
if ($this->cfg_js_defer) {
$attrs = !empty($src_info['attrs']) ? $src_info['attrs'] : '';
$snippet = $this->_js_inline_defer($src_info['src'], $attrs) ?: $html_list[$key];
} else {
$code = Optimizer::minify_js($src_info['src']);
$snippet = str_replace($src_info['src'], $code, $html_list[$key]);
}
}
}
// CSS/JS files
else {
$url = $this->_build_single_hash_url($src_info['src'], $file_type);
if ($url) {
$snippet = str_replace($src_info['src'], $url, $html_list[$key]);
}
// Handle css async load
if ($file_type == 'css' && $this->cfg_css_async) {
$snippet = $this->_async_css($snippet);
}
// Handle js defer
if ($file_type === 'js' && $this->cfg_js_defer) {
$snippet = $this->_js_defer($snippet, $src_info['src']) ?: $snippet;
}
}
$snippet = str_replace("<$tag ", '<' . $tag . ' data-optimized="1" ', $snippet);
$html_list[$key] = $snippet;
}
$this->content = str_replace($html_list_ori, $html_list, $this->content);
}
/**
* Build a single URL mapped filename (This will not save in DB)
*
* @since 4.0
*/
private function _build_single_hash_url( $src, $file_type = 'css' ) {
$content = $this->__optimizer->load_file($src, $file_type);
$is_min = $this->__optimizer->is_min($src);
$content = $this->__optimizer->optm_snippet($content, $file_type, !$is_min, $src);
$filepath_prefix = $this->_build_filepath_prefix($file_type);
// Save to file
$filename = $filepath_prefix . md5($this->remove_query_strings($src)) . '.' . $file_type;
$static_file = LITESPEED_STATIC_DIR . $filename;
File::save($static_file, $content, true);
// QS is required as $src may contains version info
$qs_hash = substr(md5($src), -5);
return LITESPEED_STATIC_URL . "$filename?ver=$qs_hash";
}
/**
* Generate full URL path with hash for a list of src
*
* @since 1.2.2
* @access private
*/
private function _build_hash_url( $src_list, $file_type = 'css' ) {
// $url_sensitive = $this->conf( self::O_OPTM_CSS_UNIQUE ) && $file_type == 'css'; // If need to keep unique CSS per URI
// Replace preserved ESI (before generating hash)
if ($file_type == 'js') {
foreach ($src_list as $k => $v) {
if (empty($v['inl'])) {
continue;
}
$src_list[$k]['src'] = $this->_preserve_esi($v['src']);
}
}
$minify = $file_type === 'css' ? $this->cfg_css_min : $this->cfg_js_min;
$filename_info = $this->__optimizer->serve($this->_request_url, $file_type, $minify, $src_list);
if (!$filename_info) {
return false; // Failed to generate
}
list($filename, $type) = $filename_info;
// Add cache tag in case later file deleted to avoid lscache served stale non-existed files @since 4.4.1
Tag::add(Tag::TYPE_MIN . '.' . $filename);
$qs_hash = substr(md5(self::get_option(self::ITEM_TIMESTAMP_PURGE_CSS)), -5);
// As filename is already related to filecon md5, no need QS anymore
$filepath_prefix = $this->_build_filepath_prefix($type);
return LITESPEED_STATIC_URL . $filepath_prefix . $filename . '?ver=' . $qs_hash;
}
/**
* Parse js src
*
* @since 1.2.2
* @access private
*/
private function _parse_js() {
$excludes = apply_filters('litespeed_optimize_js_excludes', $this->conf(self::O_OPTM_JS_EXC));
$combine_ext_inl = $this->conf(self::O_OPTM_JS_COMB_EXT_INL);
if (!apply_filters('litespeed_optm_js_comb_ext_inl', true)) {
self::debug2('js_comb_ext_inl bypassed via litespeed_optm_js_comb_ext_inl filter');
$combine_ext_inl = false;
}
$src_list = array();
$html_list = array();
// V7 added: (?:\r\n?|\n?) to fix replacement leaving empty new line
$content = preg_replace('#(?:\r\n?|\n?)#sU', '', $this->content);
preg_match_all('#(?:\r\n?|\n?)#isU', $content, $matches, PREG_SET_ORDER);
foreach ($matches as $match) {
$attrs = empty($match[1]) ? array() : Utility::parse_attr($match[1]);
if (isset($attrs['data-optimized'])) {
continue;
}
if (!empty($attrs['data-no-optimize'])) {
continue;
}
if (!empty($attrs['data-cfasync']) && $attrs['data-cfasync'] === 'false') {
continue;
}
if (!empty($attrs['type']) && $attrs['type'] != 'text/javascript') {
continue;
}
// to avoid multiple replacement
if (in_array($match[0], $html_list)) {
continue;
}
$this_src_arr = array();
// JS files
if (!empty($attrs['src'])) {
// Exclude check
$js_excluded = Utility::str_hit_array($attrs['src'], $excludes);
$is_internal = Utility::is_internal_file($attrs['src']);
$is_file = substr($attrs['src'], 0, 5) != 'data:';
$ext_excluded = !$combine_ext_inl && !$is_internal;
if ($js_excluded || $ext_excluded || !$is_file) {
// Maybe defer
if ($this->cfg_js_defer) {
$deferred = $this->_js_defer($match[0], $attrs['src']);
if ($deferred) {
$this->content = str_replace($match[0], $deferred, $this->content);
}
}
self::debug2('_parse_js bypassed due to ' . ($js_excluded ? 'js files excluded [hit] ' . $js_excluded : 'external js'));
continue;
}
if (strpos($attrs['src'], '/localres/') !== false) {
continue;
}
if (strpos($attrs['src'], 'instant_click') !== false) {
continue;
}
$this_src_arr['src'] = $attrs['src'];
}
// Inline JS
elseif (!empty($match[2])) {
// self::debug( '🌹🌹🌹 ' . $match[2] . '🌹' );
// Exclude check
$js_excluded = Utility::str_hit_array($match[2], $excludes);
if ($js_excluded || !$combine_ext_inl) {
// Maybe defer
if ($this->cfg_js_defer) {
$deferred = $this->_js_inline_defer($match[2], $match[1]);
if ($deferred) {
$this->content = str_replace($match[0], $deferred, $this->content);
}
}
self::debug2('_parse_js bypassed due to ' . ($js_excluded ? 'js excluded [hit] ' . $js_excluded : 'inline js'));
continue;
}
$this_src_arr['inl'] = true;
$this_src_arr['src'] = $match[2];
if ($match[1]) {
$this_src_arr['attrs'] = $match[1];
}
} else {
// Compatibility to those who changed src to data-src already
self::debug2('No JS src or inline JS content');
continue;
}
$src_list[] = $this_src_arr;
$html_list[] = $match[0];
}
return array( $src_list, $html_list );
}
/**
* Inline JS defer
*
* @since 3.0
* @access private
*/
private function _js_inline_defer( $con, $attrs = false, $minified = false ) {
if (strpos($attrs, 'data-no-defer') !== false) {
self::debug2('bypass: attr api data-no-defer');
return false;
}
$hit = Utility::str_hit_array($con, $this->cfg_js_defer_exc);
if ($hit) {
self::debug2('inline js defer excluded [setting] ' . $hit);
return false;
}
$con = trim($con);
// Minify JS first
if (!$minified) {
// && $this->cfg_js_defer !== 2
$con = Optimizer::minify_js($con);
}
if (!$con) {
return false;
}
// Check if the content contains ESI nonce or not
$con = $this->_preserve_esi($con);
if ($this->cfg_js_defer === 2) {
// Drop type attribute from $attrs
if (strpos($attrs, ' type=') !== false) {
$attrs = preg_replace('# type=([\'"])([^\1]+)\1#isU', '', $attrs);
}
// Replace DOMContentLoaded
$con = str_replace('DOMContentLoaded', 'DOMContentLiteSpeedLoaded', $con);
return '';
// return '';
// return '';
}
return '';
}
/**
* Replace ESI to JS inline var (mainly used to avoid nonce timeout)
*
* @since 3.5.1
*/
private function _preserve_esi( $con ) {
$esi_placeholder_list = $this->cls('ESI')->contain_preserve_esi($con);
if (!$esi_placeholder_list) {
return $con;
}
foreach ($esi_placeholder_list as $esi_placeholder) {
$js_var = '__litespeed_var_' . self::$_var_i++ . '__';
$con = str_replace($esi_placeholder, $js_var, $con);
$this->_var_preserve_js[] = $js_var . '=' . $esi_placeholder;
}
return $con;
}
/**
* Parse css src and remove to-be-removed css
*
* @since 1.2.2
* @access private
* @return array All the src & related raw html list
*/
private function _parse_css() {
$excludes = apply_filters('litespeed_optimize_css_excludes', $this->conf(self::O_OPTM_CSS_EXC));
$ucss_file_exc_inline = apply_filters('litespeed_optimize_ucss_file_exc_inline', $this->conf(self::O_OPTM_UCSS_FILE_EXC_INLINE));
// Append dummy css to exclude list
$excludes[] = 'litespeed-dummy.css';
$combine_ext_inl = $this->conf(self::O_OPTM_CSS_COMB_EXT_INL);
if (!apply_filters('litespeed_optm_css_comb_ext_inl', true)) {
self::debug2('css_comb_ext_inl bypassed via litespeed_optm_css_comb_ext_inl filter');
$combine_ext_inl = false;
}
$css_to_be_removed = apply_filters('litespeed_optm_css_to_be_removed', array());
$src_list = array();
$html_list = array();
// $dom = new \PHPHtmlParser\Dom;
// $dom->load( $content );return $val;
// $items = $dom->find( 'link' );
// V7 added: (?:\r\n?|\n?) to fix replacement leaving empty new line
$content = preg_replace(
array( '#(?:\r\n?|\n?)#sU', '#(?:\r\n?|\n?)#isU', '#(?:\r\n?|\n?)#isU' ),
'',
$this->content
);
preg_match_all('#]+)/?>|(?:\r\n?|\n?)#isU', $content, $matches, PREG_SET_ORDER);
foreach ($matches as $match) {
// to avoid multiple replacement
if (in_array($match[0], $html_list)) {
continue;
}
if ($exclude = Utility::str_hit_array($match[0], $excludes)) {
self::debug2('_parse_css bypassed exclude ' . $exclude);
continue;
}
$this_src_arr = array();
if (strpos($match[0], 'content = str_replace($match[0], '', $this->content);
continue;
}
// Check if need to inline this css file
if ($this->conf(self::O_OPTM_UCSS) && Utility::str_hit_array($attrs['href'], $ucss_file_exc_inline)) {
self::debug('ucss_file_exc_inline hit ' . $attrs['href']);
// Replace this css to inline from orig html
$inline_script = '';
$this->content = str_replace($match[0], $inline_script, $this->content);
continue;
}
// Check Google fonts hit
if (strpos($attrs['href'], 'fonts.googleapis.com') !== false) {
/**
* For async gg fonts, will add webfont into head, hence remove it from buffer and store the matches to use later
*
* @since 2.7.3
* @since 3.0 For font display optm, need to parse google fonts URL too
*/
if (!in_array($attrs['href'], $this->_ggfonts_urls)) {
$this->_ggfonts_urls[] = $attrs['href'];
}
if ($this->cfg_ggfonts_rm || $this->cfg_ggfonts_async) {
self::debug('rm css snippet [Google fonts] ' . $attrs['href']);
$this->content = str_replace($match[0], '', $this->content);
continue;
}
}
if (isset($attrs['data-optimized'])) {
// $this_src_arr[ 'exc' ] = true;
continue;
} elseif (!empty($attrs['data-no-optimize'])) {
// $this_src_arr[ 'exc' ] = true;
continue;
}
$is_internal = Utility::is_internal_file($attrs['href']);
$ext_excluded = !$combine_ext_inl && !$is_internal;
if ($ext_excluded) {
self::debug2('Bypassed due to external link');
// Maybe defer
if ($this->cfg_css_async) {
$snippet = $this->_async_css($match[0]);
if ($snippet != $match[0]) {
$this->content = str_replace($match[0], $snippet, $this->content);
}
}
continue;
}
if (!empty($attrs['media']) && $attrs['media'] !== 'all') {
$this_src_arr['media'] = $attrs['media'];
}
$this_src_arr['src'] = $attrs['href'];
} else {
// Inline style
if (!$combine_ext_inl) {
self::debug2('Bypassed due to inline');
continue;
}
$attrs = Utility::parse_attr($match[2]);
if (!empty($attrs['data-no-optimize'])) {
continue;
}
if (!empty($attrs['media']) && $attrs['media'] !== 'all') {
$this_src_arr['media'] = $attrs['media'];
}
$this_src_arr['inl'] = true;
$this_src_arr['src'] = $match[3];
}
$src_list[] = $this_src_arr;
$html_list[] = $match[0];
}
return array( $src_list, $html_list );
}
/**
* Replace css to async loaded css
*
* @since 1.3
* @access private
*/
private function _async_css_list( $html_list, $src_list ) {
foreach ($html_list as $k => $ori) {
if (!empty($src_list[$k]['inl'])) {
continue;
}
$html_list[$k] = $this->_async_css($ori);
}
return $html_list;
}
/**
* Async CSS snippet
*
* @since 3.5
*/
private function _async_css( $ori ) {
if (strpos($ori, 'data-asynced') !== false) {
self::debug2('bypass: attr data-asynced exist');
return $ori;
}
if (strpos($ori, 'data-no-async') !== false) {
self::debug2('bypass: attr api data-no-async');
return $ori;
}
// async replacement
$v = str_replace('stylesheet', 'preload', $ori);
$v = str_replace('conf(self::O_OPTM_NOSCRIPT_RM)) {
$v .= '';
}
return $v;
}
/**
* Defer JS snippet
*
* @since 3.5
*/
private function _js_defer( $ori, $src ) {
if (strpos($ori, ' async') !== false) {
$ori = preg_replace('# async(?:=([\'"])(?:[^\1]*?)\1)?#is', '', $ori);
}
if (strpos($ori, 'defer') !== false) {
return false;
}
if (strpos($ori, 'data-deferred') !== false) {
self::debug2('bypass: attr data-deferred exist');
return false;
}
if (strpos($ori, 'data-no-defer') !== false) {
self::debug2('bypass: attr api data-no-defer');
return false;
}
/**
* Exclude JS from setting
*
* @since 1.5
*/
if (Utility::str_hit_array($src, $this->cfg_js_defer_exc)) {
self::debug('js defer exclude ' . $src);
return false;
}
if ($this->cfg_js_defer === 2 || Utility::str_hit_array($src, $this->cfg_js_delay_inc)) {
if (strpos($ori, ' type=') !== false) {
$ori = preg_replace('# type=([\'"])([^\1]+)\1#isU', '', $ori);
}
return str_replace(' src=', ' type="litespeed/javascript" data-src=', $ori);
}
return str_replace('>', ' defer data-deferred="1">', $ori);
}
/**
* Delay JS for included setting
*
* @since 5.6
*/
private function _js_delay( $ori, $src ) {
if (strpos($ori, ' async') !== false) {
$ori = str_replace(' async', '', $ori);
}
if (strpos($ori, 'defer') !== false) {
return false;
}
if (strpos($ori, 'data-deferred') !== false) {
self::debug2('bypass: attr data-deferred exist');
return false;
}
if (strpos($ori, 'data-no-defer') !== false) {
self::debug2('bypass: attr api data-no-defer');
return false;
}
if (!Utility::str_hit_array($src, $this->cfg_js_delay_inc)) {
return;
}
if (strpos($ori, ' type=') !== false) {
$ori = preg_replace('# type=([\'"])([^\1]+)\1#isU', '', $ori);
}
return str_replace(' src=', ' type="litespeed/javascript" data-src=', $ori);
}
}
/**
* The cloudflare CDN class.
*
* @since 2.1
* @package LiteSpeed
* @subpackage LiteSpeed/src/cdn
* @author LiteSpeed Technologies
*/
namespace LiteSpeed\CDN;
use LiteSpeed\Base;
use LiteSpeed\Debug2;
use LiteSpeed\Router;
use LiteSpeed\Admin;
use LiteSpeed\Admin_Display;
defined('WPINC') || exit();
/**
* Class Cloudflare
*
* @since 2.1
*/
class Cloudflare extends Base {
const TYPE_PURGE_ALL = 'purge_all';
const TYPE_GET_DEVMODE = 'get_devmode';
const TYPE_SET_DEVMODE_ON = 'set_devmode_on';
const TYPE_SET_DEVMODE_OFF = 'set_devmode_off';
const ITEM_STATUS = 'status';
/**
* Update zone&name based on latest settings
*
* @since 3.0
* @access public
*/
public function try_refresh_zone() {
if (!$this->conf(self::O_CDN_CLOUDFLARE)) {
return;
}
$zone = $this->fetch_zone();
if ($zone) {
$this->cls('Conf')->update(self::O_CDN_CLOUDFLARE_NAME, $zone['name']);
$this->cls('Conf')->update(self::O_CDN_CLOUDFLARE_ZONE, $zone['id']);
Debug2::debug("[Cloudflare] Get zone successfully \t\t[ID] " . $zone['id']);
} else {
$this->cls('Conf')->update(self::O_CDN_CLOUDFLARE_ZONE, '');
Debug2::debug('[Cloudflare] ❌ Get zone failed, clean zone');
}
}
/**
* Get Cloudflare development mode
*
* @since 1.7.2
* @access private
* @param bool $show_msg Whether to show success/error message.
*/
private function get_devmode( $show_msg = true ) {
Debug2::debug('[Cloudflare] get_devmode');
$zone = $this->zone();
if (!$zone) {
return;
}
$url = 'https://api.cloudflare.com/client/v4/zones/' . $zone . '/settings/development_mode';
$res = $this->cloudflare_call($url, 'GET', false, $show_msg);
if (!$res) {
return;
}
Debug2::debug('[Cloudflare] get_devmode result ', $res);
// Make sure is array: #992174
$curr_status = self::get_option(self::ITEM_STATUS, array());
if ( ! is_array( $curr_status ) ) {
$curr_status = array();
}
$curr_status['devmode'] = $res['value'];
$curr_status['devmode_expired'] = (int) $res['time_remaining'] + time();
// update status
self::update_option(self::ITEM_STATUS, $curr_status);
}
/**
* Set Cloudflare development mode
*
* @since 1.7.2
* @access private
* @param string $type The type of development mode to set (on/off).
*/
private function set_devmode( $type ) {
Debug2::debug('[Cloudflare] set_devmode');
$zone = $this->zone();
if (!$zone) {
return;
}
$url = 'https://api.cloudflare.com/client/v4/zones/' . $zone . '/settings/development_mode';
$new_val = self::TYPE_SET_DEVMODE_ON === $type ? 'on' : 'off';
$data = array( 'value' => $new_val );
$res = $this->cloudflare_call($url, 'PATCH', $data);
if (!$res) {
return;
}
$res = $this->get_devmode(false);
if ($res) {
$msg = sprintf(__('Notified Cloudflare to set development mode to %s successfully.', 'litespeed-cache'), strtoupper($new_val));
Admin_Display::success($msg);
}
}
/**
* Shortcut to purge Cloudflare
*
* @since 7.1
* @access public
* @param string|bool $reason The reason for purging, or false if none.
*/
public static function purge_all( $reason = false ) {
if ($reason) {
Debug2::debug('[Cloudflare] purge call because: ' . $reason);
}
self::cls()->purge_all_private();
}
/**
* Purge Cloudflare cache
*
* @since 1.7.2
* @access private
*/
private function purge_all_private() {
Debug2::debug('[Cloudflare] purge_all_private');
$cf_on = $this->conf(self::O_CDN_CLOUDFLARE);
if (!$cf_on) {
$msg = __('Cloudflare API is set to off.', 'litespeed-cache');
Admin_Display::error($msg);
return;
}
$zone = $this->zone();
if (!$zone) {
return;
}
$url = 'https://api.cloudflare.com/client/v4/zones/' . $zone . '/purge_cache';
$data = array( 'purge_everything' => true );
$res = $this->cloudflare_call($url, 'DELETE', $data);
if ($res) {
$msg = __('Notified Cloudflare to purge all successfully.', 'litespeed-cache');
Admin_Display::success($msg);
}
}
/**
* Get current Cloudflare zone from cfg
*
* @since 1.7.2
* @access private
*/
private function zone() {
$zone = $this->conf(self::O_CDN_CLOUDFLARE_ZONE);
if (!$zone) {
$msg = __('No available Cloudflare zone', 'litespeed-cache');
Admin_Display::error($msg);
return false;
}
return $zone;
}
/**
* Get Cloudflare zone settings
*
* @since 1.7.2
* @access private
*/
private function fetch_zone() {
$kw = $this->conf(self::O_CDN_CLOUDFLARE_NAME);
$url = 'https://api.cloudflare.com/client/v4/zones?status=active&match=all';
// Try exact match first
if ($kw && false !== strpos($kw, '.')) {
$zones = $this->cloudflare_call($url . '&name=' . $kw, 'GET', false, false);
if ($zones) {
Debug2::debug('[Cloudflare] fetch_zone exact matched');
return $zones[0];
}
}
// Can't find, try to get default one
$zones = $this->cloudflare_call($url, 'GET', false, false);
if (!$zones) {
Debug2::debug('[Cloudflare] fetch_zone no zone');
return false;
}
if (!$kw) {
Debug2::debug('[Cloudflare] fetch_zone no set name, use first one by default');
return $zones[0];
}
foreach ($zones as $v) {
if (false !== strpos($v['name'], $kw)) {
Debug2::debug('[Cloudflare] fetch_zone matched ' . $kw . ' [name] ' . $v['name']);
return $v;
}
}
// Can't match current name, return default one
Debug2::debug('[Cloudflare] fetch_zone failed match name, use first one by default');
return $zones[0];
}
/**
* Cloudflare API
*
* @since 1.7.2
* @access private
* @param string $url The API URL to call.
* @param string $method The HTTP method to use (GET, POST, etc.).
* @param array|bool $data The data to send with the request, or false if none.
* @param bool $show_msg Whether to show success/error message.
*/
private function cloudflare_call( $url, $method = 'GET', $data = false, $show_msg = true ) {
Debug2::debug("[Cloudflare] cloudflare_call \t\t[URL] $url");
if (strlen($this->conf(self::O_CDN_CLOUDFLARE_KEY)) === 40) {
$headers = array(
'Content-Type' => 'application/json',
'Authorization' => 'Bearer ' . $this->conf(self::O_CDN_CLOUDFLARE_KEY),
);
} else {
$headers = array(
'Content-Type' => 'application/json',
'X-Auth-Email' => $this->conf(self::O_CDN_CLOUDFLARE_EMAIL),
'X-Auth-Key' => $this->conf(self::O_CDN_CLOUDFLARE_KEY),
);
}
$wp_args = array(
'method' => $method,
'headers' => $headers,
);
if ($data) {
if (is_array($data)) {
$data = wp_json_encode($data);
}
$wp_args['body'] = $data;
}
$resp = wp_remote_request($url, $wp_args);
if (is_wp_error($resp)) {
Debug2::debug('[Cloudflare] error in response');
if ($show_msg) {
$msg = __('Failed to communicate with Cloudflare', 'litespeed-cache');
Admin_Display::error($msg);
}
return false;
}
$result = wp_remote_retrieve_body($resp);
$json = \json_decode($result, true);
if ($json && $json['success'] && $json['result']) {
Debug2::debug('[Cloudflare] cloudflare_call called successfully');
if ($show_msg) {
$msg = __('Communicated with Cloudflare successfully.', 'litespeed-cache');
Admin_Display::success($msg);
}
return $json['result'];
}
Debug2::debug("[Cloudflare] cloudflare_call called failed: $result");
if ($show_msg) {
$msg = __('Failed to communicate with Cloudflare', 'litespeed-cache');
Admin_Display::error($msg);
}
return false;
}
/**
* Handle all request actions from main cls
*
* @since 1.7.2
* @access public
*/
public function handler() {
$type = Router::verify_type();
switch ($type) {
case self::TYPE_PURGE_ALL:
$this->purge_all_private();
break;
case self::TYPE_GET_DEVMODE:
$this->get_devmode();
break;
case self::TYPE_SET_DEVMODE_ON:
case self::TYPE_SET_DEVMODE_OFF:
$this->set_devmode($type);
break;
default:
break;
}
Admin::redirect();
}
}
/**
* QUIC.cloud API CLI for LiteSpeed integration.
*
* @package LiteSpeed\CLI
*/
namespace LiteSpeed\CLI;
defined( 'WPINC' ) || exit();
use LiteSpeed\Debug2;
use LiteSpeed\Cloud;
use WP_CLI;
/**
* QUIC.cloud API CLI
*/
class Online {
/**
* Cloud instance.
*
* @var Cloud
*/
private $cloud;
/**
* Constructor for Online CLI.
*/
public function __construct() {
Debug2::debug( 'CLI_Cloud init' );
$this->cloud = Cloud::cls();
}
/**
* Init domain on QUIC.cloud server (See https://quic.cloud/terms/)
*
* ## OPTIONS
*
* ## EXAMPLES
*
* # Activate domain on QUIC.cloud (! Require SERVER IP setting to be set first)
* $ wp litespeed-online init
*/
public function init() {
$resp = $this->cloud->init_qc_cli();
if ( ! empty( $resp['qc_activated'] ) ) {
$main_domain = ! empty( $resp['main_domain'] ) ? $resp['main_domain'] : false;
$this->cloud->update_qc_activation( $resp['qc_activated'], $main_domain );
WP_CLI::success( 'Init successfully. Activated type: ' . $resp['qc_activated'] );
} else {
WP_CLI::error( 'Init failed!' );
}
}
/**
* Init domain CDN service on QUIC.cloud server (See https://quic.cloud/terms/)
*
* ## OPTIONS
*
* [--method=]
* : The method to use (e.g., cname, ns, cfi).
*
* [--ssl-cert=]
* : Path to SSL certificate.
*
* [--ssl-key=]
* : Path to SSL key.
*
* [--cf-token=]
* : Cloudflare token for CFI method.
*
* [--format=]
* : Output format (e.g., json).
*
* ## EXAMPLES
*
* # Activate domain CDN on QUIC.cloud (support --format=json)
* $ wp litespeed-online cdn_init --method=cname|ns
* $ wp litespeed-online cdn_init --method=cname|ns --ssl-cert=xxx.pem --ssl-key=xxx
* $ wp litespeed-online cdn_init --method=cname|ns --ssl-cert=xxx.pem --ssl-key=xxx --json
* $ wp litespeed-online cdn_init --method=cfi --cf-token=xxxxxxxx
* $ wp litespeed-online cdn_init --method=cfi --cf-token=xxxxxxxx --ssl-cert=xxx.pem --ssl-key=xxx
* $ wp litespeed-online cdn_init --method=cfi --cf-token=xxxxxxxx --ssl-cert=xxx.pem --ssl-key=xxx --format=json
*
* @param array $args Positional arguments.
* @param array $assoc_args Associative arguments.
*/
public function cdn_init( $args, $assoc_args ) {
if ( empty( $assoc_args['method'] ) ) {
WP_CLI::error( 'Init CDN failed! Missing parameters `--method`.' );
return;
}
if ( ( ! empty( $assoc_args['ssl-cert'] ) && empty( $assoc_args['ssl-key'] ) ) || ( empty( $assoc_args['ssl-cert'] ) && ! empty( $assoc_args['ssl-key'] ) ) ) {
WP_CLI::error( 'Init CDN failed! SSL cert must be present together w/ SSL key.' );
return;
}
if ( 'cfi' === $assoc_args['method'] && empty( $assoc_args['cf-token'] ) ) {
WP_CLI::error( 'Init CDN failed! CFI must set `--cf-token`.' );
return;
}
$cert = ! empty( $assoc_args['ssl-cert'] ) ? $assoc_args['ssl-cert'] : '';
$key = ! empty( $assoc_args['ssl-key'] ) ? $assoc_args['ssl-key'] : '';
$cf_token = ! empty( $assoc_args['cf-token'] ) ? $assoc_args['cf-token'] : '';
$resp = $this->cloud->init_qc_cdn_cli( $assoc_args['method'], $cert, $key, $cf_token );
if ( ! empty( $resp['qc_activated'] ) ) {
$main_domain = ! empty( $resp['main_domain'] ) ? $resp['main_domain'] : false;
$this->cloud->update_qc_activation( $resp['qc_activated'], $main_domain, true );
}
if ( ! empty( $assoc_args['format'] ) && 'json' === $assoc_args['format'] ) {
WP_CLI::log( wp_json_encode( $resp ) );
return;
}
if ( ! empty( $resp['qc_activated'] ) ) {
WP_CLI::success( 'Init QC CDN successfully. Activated type: ' . $resp['qc_activated'] );
} else {
WP_CLI::error( 'Init QC CDN failed!' );
}
if ( ! empty( $resp['cname'] ) ) {
WP_CLI::success( 'cname: ' . $resp['cname'] );
}
if ( ! empty( $resp['msgs'] ) ) {
WP_CLI::success( 'msgs: ' . wp_json_encode( $resp['msgs'] ) );
}
}
/**
* Link user account by api key
*
* ## OPTIONS
*
* [--email=]
* : User email for QUIC.cloud account.
*
* [--api-key=]
* : API key for QUIC.cloud account.
*
* ## EXAMPLES
*
* # Link user account by api key
* $ wp litespeed-online link --email=xxx@example.com --api-key=xxxx
*
* @param array $args Positional arguments.
* @param array $assoc_args Associative arguments.
*/
public function link( $args, $assoc_args ) {
if ( empty( $assoc_args['email'] ) || empty( $assoc_args['api-key'] ) ) {
WP_CLI::error( 'Link to QUIC.cloud failed! Missing parameters `--email` or `--api-key`.' );
return;
}
$resp = $this->cloud->link_qc_cli( $assoc_args['email'], $assoc_args['api-key'] );
if ( ! empty( $resp['qc_activated'] ) ) {
$main_domain = ! empty( $resp['main_domain'] ) ? $resp['main_domain'] : false;
$this->cloud->update_qc_activation( $resp['qc_activated'], $main_domain, true );
WP_CLI::success( 'Link successfully!' );
WP_CLI::log( wp_json_encode( $resp ) );
} else {
WP_CLI::error( 'Link failed!' );
}
}
/**
* Sync usage data from QUIC.cloud
*
* ## OPTIONS
*
* [--format=]
* : Output format (e.g., json).
*
* ## EXAMPLES
*
* # Sync QUIC.cloud service usage info
* $ wp litespeed-online sync
* $ wp litespeed-online sync --json
* $ wp litespeed-online sync --format=json
*
* @param array $args Positional arguments.
* @param array $assoc_args Associative arguments.
*/
public function sync( $args, $assoc_args ) {
$json = $this->cloud->sync_usage();
if ( ! empty( $assoc_args['format'] ) ) {
WP_CLI::print_value( $json, $assoc_args );
return;
}
WP_CLI::success( 'Sync successfully' );
$list = array();
foreach ( Cloud::$services as $v ) {
$list[] = array(
'key' => $v,
'used' => ! empty( $json['usage.' . $v]['used'] ) ? $json['usage.' . $v]['used'] : 0,
'quota' => ! empty( $json['usage.' . $v]['quota'] ) ? $json['usage.' . $v]['quota'] : 0,
'PayAsYouGo_Used' => ! empty( $json['usage.' . $v]['pag_used'] ) ? $json['usage.' . $v]['pag_used'] : 0,
'PayAsYouGo_Balance' => ! empty( $json['usage.' . $v]['pag_bal'] ) ? $json['usage.' . $v]['pag_bal'] : 0,
);
}
WP_CLI\Utils\format_items( 'table', $list, array( 'key', 'used', 'quota', 'PayAsYouGo_Used', 'PayAsYouGo_Balance' ) );
}
/**
* Check QC account status
*
* ## OPTIONS
*
* ## EXAMPLES
*
* # Check QC account status
* $ wp litespeed-online cdn_status
*/
public function cdn_status() {
$resp = $this->cloud->cdn_status_cli();
WP_CLI::log( wp_json_encode( $resp ) );
}
/**
* List all QUIC.cloud services
*
* ## OPTIONS
*
* [--format=]
* : Output format (e.g., json).
*
* ## EXAMPLES
*
* # List all services tag
* $ wp litespeed-online services
* $ wp litespeed-online services --json
* $ wp litespeed-online services --format=json
*
* @param array $args Positional arguments.
* @param array $assoc_args Associative arguments.
*/
public function services( $args, $assoc_args ) {
if ( ! empty( $assoc_args['format'] ) ) {
WP_CLI::print_value( Cloud::$services, $assoc_args );
return;
}
$list = array();
foreach ( Cloud::$services as $v ) {
$list[] = array(
'service' => $v,
);
}
WP_CLI\Utils\format_items( 'table', $list, array( 'service' ) );
}
/**
* List all QUIC.cloud servers in use
*
* ## OPTIONS
*
* [--format=]
* : Output format (e.g., json).
*
* ## EXAMPLES
*
* # List all QUIC.cloud servers in use
* $ wp litespeed-online nodes
* $ wp litespeed-online nodes --json
* $ wp litespeed-online nodes --format=json
*
* @param array $args Positional arguments.
* @param array $assoc_args Associative arguments.
*/
public function nodes( $args, $assoc_args ) {
$json = Cloud::get_summary();
$list = array();
$json_output = array();
foreach ( Cloud::$services as $v ) {
$server = ! empty( $json['server.' . $v] ) ? $json['server.' . $v] : '';
$list[] = array(
'service' => $v,
'server' => $server,
);
$json_output[] = array( $v => $server );
}
if ( ! empty( $assoc_args['format'] ) ) {
WP_CLI::print_value( $json_output, $assoc_args );
return;
}
WP_CLI\Utils\format_items( 'table', $list, array( 'service', 'server' ) );
}
/**
* Detect closest node server for current service
*
* ## OPTIONS
*
* []
* : Service to ping (e.g., img_optm).
*
* [--force]
* : Force detection of the closest server.
*
* ## EXAMPLES
*
* # Detect closest node for one service
* $ wp litespeed-online ping img_optm
* $ wp litespeed-online ping img_optm --force
*
* @param array $param Positional arguments (service).
* @param array $assoc_args Associative arguments.
*/
public function ping( $param, $assoc_args ) {
$svc = $param[0];
$force = ! empty( $assoc_args['force'] );
$json = $this->cloud->detect_cloud( $svc, $force );
if ( $json ) {
WP_CLI::success( 'Updated closest server.' );
}
WP_CLI::log( 'svc = ' . $svc );
WP_CLI::log( 'node = ' . ( $json ? $json : '-' ) );
}
}
#wpforms-notifications{background:#ffffff 0 0 no-repeat padding-box;box-shadow:0 2px 4px 0 rgba(0,0,0,0.05);border-radius:6px;opacity:1;min-height:48px;margin:0 0 20px 0}#wpforms-notifications *{box-sizing:border-box}#wpforms-notifications .wpforms-notifications-header{display:flex;align-items:center;padding:10px 15px;border-bottom:1px solid #dcdcde}#wpforms-notifications .wpforms-notifications-header .wpforms-notifications-bell{position:relative;width:16px;height:20px;top:3px;margin-inline-end:10px}#wpforms-notifications .wpforms-notifications-header .wpforms-notifications-bell svg{fill:#a7aaad}#wpforms-notifications .wpforms-notifications-header .wpforms-notifications-circle{position:absolute;width:11px;height:11px;border-radius:50%;top:-4px;right:-1px;border:2px solid #ffffff;background-color:#d63638}#wpforms-notifications .wpforms-notifications-header .wpforms-notifications-title{font-size:14px;font-weight:600;font-style:normal;line-height:1;color:#2c3338}#wpforms-notifications .wpforms-notifications-body{position:relative}#wpforms-notifications .wpforms-notifications-messages{padding-block:15px;padding-inline:15px 100px}#wpforms-notifications .wpforms-notifications-messages .wpforms-notifications-message{display:none}#wpforms-notifications .wpforms-notifications-messages .wpforms-notifications-message.current{display:block}#wpforms-notifications .wpforms-notifications-messages .wpforms-notifications-title{color:#2c3338;font-size:17px;font-weight:600;line-height:25px;margin:0}#wpforms-notifications .wpforms-notifications-messages .wpforms-notifications-content{font-size:14px;font-weight:400;line-height:20px;margin:5px 0 15px 0;color:#50575e}#wpforms-notifications .wpforms-notifications-messages .wpforms-notifications-content p{font-size:inherit;line-height:inherit;margin:0}#wpforms-notifications .wpforms-notifications-messages .wpforms-notifications-content p+p{margin-top:10px}#wpforms-notifications .wpforms-notifications-messages .wpforms-notifications-buttons{margin-block:0;margin-inline:0 80px}#wpforms-notifications .wpforms-notifications-messages .wpforms-notifications-buttons a{margin-block:0;margin-inline:0 10px;min-height:unset}#wpforms-notifications .wpforms-notifications-messages .wpforms-notifications-buttons .button-secondary{background-color:#f6f7f7;border-color:#056aab;color:#056aab}#wpforms-notifications .wpforms-notifications-messages .wpforms-notifications-buttons .button-secondary:hover,#wpforms-notifications .wpforms-notifications-messages .wpforms-notifications-buttons .button-secondary:active,#wpforms-notifications .wpforms-notifications-messages .wpforms-notifications-buttons .button-secondary:focus{background-color:#f0f0f1;border-color:#04558a;color:#04558a}#wpforms-notifications .wpforms-notifications-messages .wpforms-notifications-buttons .button-secondary:focus{box-shadow:0 0 0 1px #04558a}#wpforms-notifications .wpforms-notifications-badge{display:inline-flex;justify-content:center;align-items:center;gap:5px;padding:6px 8px;margin-left:10px;border-radius:3px;background-color:#f6f6f6;color:#50575e;font-size:11px;font-weight:700;line-height:1;text-decoration:none;text-transform:uppercase}#wpforms-notifications .wpforms-notifications-badge svg{width:15px;height:13px}#wpforms-notifications .wpforms-notifications-badge:focus,#wpforms-notifications .wpforms-notifications-badge:hover{background-color:#f0f0f1;box-shadow:none}#wpforms-notifications .dismiss{position:absolute;top:15px;inset-inline-end:15px;width:14px;height:14px;fill:#a7aaad;cursor:pointer}#wpforms-notifications .dismiss:hover{fill:#d63638}#wpforms-notifications .navigation{position:absolute;bottom:15px;right:15px;width:64px;height:30px}#wpforms-notifications .navigation a{display:block;width:30px;height:30px;border:1px solid #7e8993;border-radius:3px;font-size:16px;line-height:1.625;text-align:center;cursor:pointer;background-color:#ffffff;color:#41454a}#wpforms-notifications .navigation a:hover{background-color:#f1f1f1}#wpforms-notifications .navigation .prev{float:left}#wpforms-notifications .navigation .next{float:right}#wpforms-notifications .navigation .disabled{border-color:#dddddd;color:#a0a5aa;cursor:default}#wpforms-notifications .navigation .disabled:hover{background-color:#ffffff}.lity-iframe .lity-content{margin:0 auto}@media screen and (max-width: 768px){#wpforms-notifications .wpforms-notifications-messages{padding-block:15px 10px;padding-inline:16px 50px}#wpforms-notifications .wpforms-notifications-messages .wpforms-notifications-message .wpforms-notifications-title{line-height:22px;margin-block:0 -2px;margin-inline:0 30px;min-height:24px}#wpforms-notifications .wpforms-notifications-messages .wpforms-notifications-message .wpforms-notifications-content{font-size:16px;line-height:22px}#wpforms-notifications .wpforms-notifications-messages .wpforms-notifications-message .wpforms-notifications-buttons{margin:0;padding-inline-end:40px}#wpforms-notifications .wpforms-notifications-messages .wpforms-notifications-message .wpforms-notifications-buttons a.button{margin-bottom:10px}#wpforms-notifications .navigation{bottom:20px;right:20px}}.rtl #wpforms-notifications .navigation .prev{float:right}.rtl #wpforms-notifications .navigation .next{float:left}
/**
* Tutor ecommerce functions
*
* @package TutorFunctions
* @author Themeum
* @link https://themeum.com
* @since 3.5.0
*/
use Tutor\Ecommerce\Cart\CartFactory;
use TutorPro\Ecommerce\GuestCheckout\GuestCheckout;
if ( ! function_exists( 'tutor_add_to_cart' ) ) {
/**
* Handle add to cart functionalities
*
* @since 3.5.0
*
* @param int $item_id Item id.
*
* @return object {success, message, data: {cart_url, items, total_count} }
*/
function tutor_add_to_cart( int $item_id ) {
$response = new stdClass();
$response->success = true;
$response->message = __( 'Course added to cart', 'tutor' );
$response->data = null;
$user_id = get_current_user_id();
$is_guest_checkout_enabled = tutor_is_guest_checkout_enabled();
if ( ! $user_id && ! $is_guest_checkout_enabled ) {
return array(
'success' => false,
'message' => __( 'Guest checkout is not enabled', 'tutor' ),
'data' => tutor_utils()->tutor_dashboard_url(),
'redirect' => true,
);
}
try {
$cart = tutor_get_cart_object();
if ( $cart->add( $item_id ) ) {
// Prepare data.
$cart_url = $cart->get_cart_url();
$items = $cart->get_cart_items();
$data = (object) array(
'cart_url' => $cart_url,
'items' => $items,
'total_count' => count( $items ),
);
$response->data = $data;
} else {
$response->success = false;
$response->message = $cart->get_error();
}
} catch ( \Throwable $th ) {
$response->success = false;
$response->message = $th->getMessage();
}
return $response;
}
}
if ( ! function_exists( 'tutor_get_cart_url' ) ) {
/**
* Get the cart page URL
*
* @since 3.5.0
*
* @return string
*/
function tutor_get_cart_url() {
try {
$cart = tutor_get_cart_object();
return $cart->get_cart_url();
} catch ( \Throwable $th ) {
return $th->getMessage();
}
}
}
if ( ! function_exists( 'tutor_get_cart_items' ) ) {
/**
* Get cart items
*
* @since 3.5.0
*
* @return array
*/
function tutor_get_cart_items() {
$items = array();
try {
$cart = tutor_get_cart_object();
$items = $cart->get_cart_items();
} catch ( \Throwable $th ) {
error_log( $th->getMessage() );
}
return $items;
}
}
if ( ! function_exists( 'tutor_is_item_in_cart' ) ) {
/**
* Get cart items
*
* @since 3.5.0
*
* @param int $item_id Item id to check.
*
* @return bool
*/
function tutor_is_item_in_cart( int $item_id ) {
try {
return tutor_get_cart_object()->is_item_exists( $item_id );
} catch ( \Throwable $th ) {
return false;
}
}
}
if ( ! function_exists( 'tutor_remove_cart_item' ) ) {
/**
* Get cart items
*
* @since 3.7.2
*
* @param int $item_id Item id to check.
*
* @return bool
*/
function tutor_remove_cart_item( int $item_id ) {
return tutor_get_cart_object()->remove( $item_id );
}
}
if ( ! function_exists( 'tutor_get_cart_object' ) ) {
/**
* Get cart items
*
* @since 3.5.0
*
* @throws \Throwable If cart object creation failed.
*
* @return object CartInterface
*/
function tutor_get_cart_object() {
$monetization = tutor_utils()->get_option( 'monetize_by' );
try {
return CartFactory::create_cart( $monetization );
} catch ( \Throwable $th ) {
throw $th;
}
}
}
if ( ! function_exists( 'tutor_is_guest_checkout_enabled' ) ) {
/**
* Get cart items
*
* @since 3.7.2
*
* @return bool
*/
function tutor_is_guest_checkout_enabled() {
$monetization = tutor_utils()->get_option( 'monetize_by' );
if ( tutor_utils()->is_monetize_by_tutor() ) {
return function_exists( 'tutor_pro' ) && GuestCheckout::is_enable();
} elseif ( 'wc' === $monetization ) {
return tutor_utils()->get_option( 'enable_guest_course_cart', false );
}
}
}