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';
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 prepend to <body>
private static $_var_i = 0;
private $_var_preserve_js = array();
public function __construct()
Debug2::debug('[Optm] init');
$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->conf(self::O_API_KEY)) {
Debug2::debug('[Optm] ❌ CCSS set to OFF due to missing domain key');
$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)) {
Debug2::debug('[Optm] ❌ 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
add_filter('litespeed_vary', array($this, 'vary_add_role_exclude'));
$this->_dns_prefetch_init();
$this->_dns_preconnect_init();
add_filter('litespeed_buffer_finalize', array($this, 'finalize'), 20);
* 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)
if (defined('LITESPEED_NO_PAGEOPTM')) {
Debug2::debug2('[Optm] bypass: NO_PAGEOPTM const');
if (!defined('LITESPEED_IS_HTML')) {
Debug2::debug('[Optm] bypass: Not frontend HTML type');
if (!defined('LITESPEED_GUEST_OPTM')) {
if (!Control::is_cacheable()) {
Debug2::debug('[Optm] 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);
Debug2::debug('[Optm] bypass: hit URI Excludes setting: ' . $result);
Debug2::debug('[Optm] start');
$this->content_ori = $this->content = $content;
private function _optimize()
$this->_request_url = home_url($wp->request);
$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()) {
Debug2::debug('[Optm] bypass: admin/feed/preview');
if ($this->cfg_css_async) {
$this->_ccss = $this->cls('CSS')->prepare_ccss();
Debug2::debug('[Optm] ❌ 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) {
Debug2::debug('[Optm] ❌ 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="' . $url . '" />'; // todo: How to use " in attr wrapper "
$this->html_head .= '<link data-optimized="2" rel="stylesheet" href="' . $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);
if ($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>';
Debug2::debug2('[Optm] 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, 'litespeed-css-async-lib'); // 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 = 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);
// Put header content to be after charset
if (strpos($this->content, '<meta charset') !== false) {
$this->content = preg_replace('#<meta charset([^>]*)>#isU', '<meta charset$1>' . $this->html_head, $this->content, 1);
$this->content = preg_replace('#<head([^>]*)>#isU', '<head$1>' . $this->html_head, $this->content, 1);
// Replace html foot part
$this->html_foot = apply_filters('litespeed_optm_html_foot', $this->html_foot);
$this->content = str_replace('</body>', $this->html_foot . '</body>', $this->content);
// Drop noscript if enabled
if ($this->conf(self::O_OPTM_NOSCRIPT_RM)) {
// $this->content = preg_replace( '#<noscript>.*</noscript>#isU', '', $this->content );