defined('WPINC') || exit();
class Optimize extends Base {
const LIB_FILE_CSS_ASYNC = 'assets/js/css_async.min.js';
const LIB_FILE_WEBFONTLOADER = 'assets/js/webfontloader.min.js';
const LIB_FILE_JS_DELAY = 'assets/js/js_delay.min.js';
const ITEM_TIMESTAMP_PURGE_CSS = 'timestamp_purge_css';
const DUMMY_CSS_REGEX = "#<link [ \w='\"/]*id=['\"]litespeed-cache-dummy-css['\"] href=['\"].+assets/css/litespeed-dummy\.css[?\w.=-]*['\"][ \w='\"/]*>#isU";
private $cfg_js_delay_inc = array();
private $cfg_js_defer_exc = false;
private $cfg_ggfonts_async;
private $_conf_css_font_display;
private $_ggfonts_urls = array();
private $html_foot = ''; // The html info append to <body>
private $html_head = ''; // The html info append to <head>
private $html_head_early = ''; // The html info prepend to top of head
private static $_var_i = 0;
private $_var_preserve_js = array();
public function __construct() {
$this->__optimizer = $this->cls('Optimizer');
$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')) {
if ($this->cfg_js_defer == 2) {
function ( $con, $file_type ) {
if ($file_type == 'js') {
$con = str_replace('DOMContentLoaded', 'DOMContentLiteSpeedLoaded', $con);
// $con = str_replace( 'addEventListener("load"', 'addEventListener("litespeedLoad"', $con );
// To remove emoji from WP
if ($this->conf(self::O_OPTM_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));
* Exclude js from deferred setting
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
add_filter('litespeed_buffer_finalize', array( $this, 'finalize' ), 20);
// Inject a dummy CSS file to control final optimized data location in <head>
wp_enqueue_style(Core::PLUGIN_NAME . '-dummy', LSWCP_PLUGIN_URL . 'assets/css/litespeed-dummy.css');
* Exclude role from optimization filter
public function vary_add_role_exclude( $vary ) {
if ($this->cls('Conf')->in_optm_exc_roles()) {
$vary['role_exclude_optm'] = 1;
* @since 2.9.8 Changed to 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
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
public function rm_cache_folder( $subsite_id = false ) {
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);
file_exists(LITESPEED_STATIC_DIR . '/css') && File::rrmdir(LITESPEED_STATIC_DIR . '/css');
file_exists(LITESPEED_STATIC_DIR . '/js') && File::rrmdir(LITESPEED_STATIC_DIR . '/js');
public function remove_query_strings( $src ) {
if (strpos($src, '_litespeed_rm_qs=0') || strpos($src, '/recaptcha')) {
if (!Utility::is_internal_file($src)) {
if (strpos($src, '.js?') !== false || strpos($src, '.css?') !== false) {
$src = preg_replace('/\?.*/', '', $src);
* NOTE: As this is after cache finalized, can NOT set any cache control anymore
* @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 );
private function _finalize( $content ) {
if (defined('LITESPEED_NO_PAGEOPTM')) {
self::debug2('bypass: NO_PAGEOPTM const');
if (!defined('LITESPEED_IS_HTML')) {
self::debug('bypass: Not frontend HTML type');
if (!defined('LITESPEED_GUEST_OPTM')) {
if (!Control::is_cacheable()) {
self::debug('bypass: Not cacheable');
// 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);
self::debug('bypass: hit URI Excludes setting: ' . $result);
$this->content_ori = $this->content = $content;
private function _optimize() {
// get current request url
$permalink_structure = get_option( 'permalink_structure' );
if ( ! empty( $permalink_structure ) ) {
$this->_request_url = trailingslashit( home_url( $wp->request ) );
$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');
if ($this->cfg_css_async) {
$this->_ccss = $this->cls('CSS')->prepare_ccss();
self::debug('❌ CCSS set to OFF due to CCSS not generated yet');
$this->cfg_css_async = false;
} elseif (strpos($this->_ccss, '<style id="litespeed-ccss" data-error') === 0) {
self::debug('❌ CCSS set to OFF due to CCSS failed to generate');
$this->cfg_css_async = false;
do_action('litespeed_optm');
// Parse css from content
if ($this->cfg_css_min || $this->cfg_css_comb || $this->cfg_ggfonts_rm || $this->cfg_css_async || $this->cfg_ggfonts_async || $this->_conf_css_font_display) {
add_filter('litespeed_optimize_css_excludes', array( $this->cls('Data'), 'load_css_exc' ));
list($src_list, $html_list) = $this->_parse_css();
if ($this->cfg_css_min || $this->cfg_css_comb) {
if ($this->cfg_css_comb) {
// Check if has inline UCSS enabled or not
if ((defined('LITESPEED_GUEST_OPTM') || $this->conf(self::O_OPTM_UCSS)) && $this->conf(self::O_OPTM_UCSS_INLINE)) {
$filename = $this->cls('UCSS')->load($this->_request_url, true);
$filepath_prefix = $this->_build_filepath_prefix('ucss');
$this->_ucss = File::read(LITESPEED_STATIC_DIR . $filepath_prefix . $filename);
$this->content = str_replace($html_list, '', $this->content);
$url = $this->_build_hash_url($src_list);
if ($this->cfg_css_async) {
'<link rel="preload" data-asynced="1" data-optimized="2" as="style" onload="this.onload=null;this.rel=\'stylesheet\'" href="' .
'" />'; // todo: How to use " in attr wrapper "
$this->html_head .= '<link data-optimized="2" rel="stylesheet" href="' . Str::trim_quotes($url) . '" />'; // use 2 as combined
$this->content = str_replace($html_list, '', $this->content);
elseif ($this->cfg_css_min) {
// will handle async css load inside
$this->_src_queue_handler($src_list, $html_list);
foreach ($src_list as $src_info) {
if (!empty($src_info['inl'])) {
// Handle css lazy load if not handled async loaded yet
if ($this->cfg_css_async && !$this->cfg_css_min && !$this->cfg_css_comb) {
$html_list_async = $this->_async_css_list($html_list, $src_list);
$this->content = str_replace($html_list, $html_list_async, $this->content);
// Parse js from buffer as needed
if ($this->cfg_js_min || $this->cfg_js_comb || $this->cfg_js_defer || $this->cfg_js_delay_inc) {
add_filter('litespeed_optimize_js_excludes', array( $this->cls('Data'), 'load_js_exc' ));
list($src_list, $html_list) = $this->_parse_js();
if ($this->cfg_js_comb) {
$url = $this->_build_hash_url($src_list, 'js');
$this->html_foot .= $this->_build_js_tag($url);
// Will move all JS to bottom combined one
$this->content = str_replace($html_list, '', $this->content);
elseif ($this->cfg_js_min) {
// Will handle js defer inside
$this->_src_queue_handler($src_list, $html_list, 'js');
// Only HTTP2 push and Defer
foreach ($src_list as $k => $src_info) {
if (!empty($src_info['inl'])) {
if ($this->cfg_js_defer) {
$attrs = !empty($src_info['attrs']) ? $src_info['attrs'] : '';
$deferred = $this->_js_inline_defer($src_info['src'], $attrs);
$this->content = str_replace($html_list[$k], $deferred, $this->content);
elseif ($this->cfg_js_defer) {
$deferred = $this->_js_defer($html_list[$k], $src_info['src']);
$this->content = str_replace($html_list[$k], $deferred, $this->content);
} elseif ($this->cfg_js_delay_inc) {
$deferred = $this->_js_delay($html_list[$k], $src_info['src']);
$this->content = str_replace($html_list[$k], $deferred, $this->content);
// Append JS inline var for preserved ESI
// Shouldn't give any optm (defer/delay) @since 4.4
if ($this->_var_preserve_js) {
$this->html_head .= '<script>var ' . implode(',', $this->_var_preserve_js) . ';</script>';
self::debug2('Inline JS defer vars', $this->_var_preserve_js);
// Append async compatibility lib to head
if ($this->cfg_css_async) {
if ($this->conf(self::O_OPTM_CSS_ASYNC_INLINE)) {
$this->html_head .= $this->_build_js_inline(File::read(LSCWP_DIR . self::LIB_FILE_CSS_ASYNC), true);
$css_async_lib_url = LSWCP_PLUGIN_URL . self::LIB_FILE_CSS_ASYNC;
$this->html_head .= $this->_build_js_tag($css_async_lib_url); // Don't exclude it from defer for now
* Handle google fonts async
* This will result in a JS snippet in head, so need to put it in the end to avoid being replaced by JS parser
$this->_maybe_js_delay();
if ($this->conf(self::O_OPTM_HTML_LAZY)) {
$this->html_head = $this->cls('CSS')->prepare_html_lazy() . $this->html_head;
// Maybe prepend inline UCSS
$this->html_head = '<style id="litespeed-ucss">' . $this->_ucss . '</style>' . $this->html_head;
// Check if there is any critical css rules setting
if ($this->cfg_css_async && $this->_ccss) {
$this->html_head = $this->_ccss . $this->html_head;
// Replace html head part
$this->html_head_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, '<meta charset')) {
self::debug('Put early optm data to be after <meta charset>');
$this->content = preg_replace('#<meta charset([^>]*)>#isU', '<meta charset$1>' . $this->html_head_early, $this->content, 1);
self::debug('Put early optm data to be right after <head>');
$this->content = preg_replace('#<head([^>]*)>#isU', '<head$1>' . $this->html_head_early, $this->content, 1);
$this->html_head = apply_filters('litespeed_optm_html_head', $this->html_head);
if (apply_filters('litespeed_optm_html_after_head', false)) {
$this->content = str_replace('</head>', $this->html_head . '</head>', $this->content);