*/ namespace LiteSpeed; defined( 'WPINC' ) || exit; class Optimizer extends Root { private $_conf_css_font_display; /** * Init optimizer * * @since 1.9 */ public function __construct() { $this->_conf_css_font_display = $this->conf( Base::O_OPTM_CSS_FONT_DISPLAY ); } /** * Run HTML minify process and return final content * * @since 1.9 * @access public */ public function html_min( $content, $force_inline_minify = false ) { $options = array(); if ( $force_inline_minify ) { $options[ 'jsMinifier' ] = __CLASS__ . '::minify_js'; } /** * Added exception capture when minify * @since 2.2.3 */ try { $obj = new Lib\HTML_MIN( $content, $options ); $content_final = $obj->process(); if ( ! defined( 'LSCACHE_ESI_SILENCE' ) ) { $content_final .= "\n" . ''; } return $content_final; } catch ( \Exception $e ) { Debug2::debug( '******[Optmer] html_min failed: ' . $e->getMessage() ); error_log( '****** LiteSpeed Optimizer html_min failed: ' . $e->getMessage() ); return $content; } } /** * Run minify process and save content * * @since 1.9 * @access public */ public function serve( $request_url, $file_type, $minify, $src_list ) { // Try Unique CSS if ( $file_type == 'css' ) { $content = false; if ( defined( 'LITESPEED_GUEST_OPTM' ) || $this->conf( Base::O_OPTM_UCSS ) ) { $filename = $this->cls( 'CSS' )->load_ucss( $request_url ); if ( $filename ) { return array( $filename, 'ucss' ); } } } // Before generated, don't know the contented hash filename yet, so used url hash as tmp filename $file_path_prefix = $this->_build_filepath_prefix( $file_type ); $static_file = LITESPEED_STATIC_DIR . $file_path_prefix . ( is_404() ? '404' : md5( $request_url ) ) . '.' . $file_type; // Create tmp file to avoid conflict $tmp_static_file = $static_file . '.tmp'; if ( file_exists( $tmp_static_file ) && time() - filemtime( $tmp_static_file ) <= 600 ) { // some other request is generating return false; } // File::save( $tmp_static_file, '/* ' . ( is_404() ? '404' : $request_url ) . ' */', true ); // Can't use this bcos this will get filecon md5 changed File::save( $tmp_static_file, '', true ); // Load content $real_files = array(); foreach ( $src_list as $src_info ) { $is_min = false; if ( ! empty( $src_info[ 'inl' ] ) ) { // Load inline $content = $src_info[ 'src' ]; } else { // Load file $content = $this->load_file( $src_info[ 'src' ], $file_type ); if ( ! $content ) { continue; } $is_min = $this->is_min( $src_info[ 'src' ] ); } $content = $this->optm_snippet( $content, $file_type, $minify && ! $is_min, $src_info[ 'src' ], ! empty( $src_info[ 'media' ] ) ? $src_info[ 'media' ] : false ); // Write to file File::save( $tmp_static_file, $content, true, true ); } // validate md5 $filecon_md5 = md5_file( $tmp_static_file ); $final_file_path = $file_path_prefix . $filecon_md5 . '.' . $file_type; $realfile = LITESPEED_STATIC_DIR . $final_file_path; if ( ! file_exists( $realfile ) ) { rename( $tmp_static_file, $realfile ); Debug2::debug2( '[Optmer] Saved static file [path] ' . $realfile ); } else { unlink( $tmp_static_file ); } $vary = $this->cls( 'Vary' )->finalize_full_varies(); Debug2::debug2( "[Optmer] Save URL to file for [file_type] $file_type [file] $filecon_md5 [vary] $vary " ); $this->cls( 'Data' )->save_url( is_404() ? '404' : $request_url, $vary, $file_type, $filecon_md5, dirname( $realfile ) ); return array( $filecon_md5 . '.' . $file_type, $file_type ); } /** * Load a single file * @since 4.0 */ public function optm_snippet( $content, $file_type, $minify, $src, $media = false ) { // CSS related features if ( $file_type == 'css' ) { // Font optimize if ( $this->_conf_css_font_display ) { $content = preg_replace( '#(@font\-face\s*\{)#isU', '${1}font-display:swap;', $content ); } $content = preg_replace( '/@charset[^;]+;\\s*/', '', $content ); if ( $media ) { $content = '@media ' . $media . '{' . $content . "\n}"; } if ( $minify ) { $content = self::minify_css( $content ); } $content = $this->cls( 'CDN' )->finalize( $content ); if ( defined( 'LITESPEED_GUEST_OPTM' ) || $this->conf( Base::O_IMG_OPTM_WEBP_REPLACE ) ) { $content = $this->cls( 'Media' )->replace_background_webp( $content ); } } else { if ( $minify ) { $content = self::minify_js( $content ); } else { $content = $this->_null_minifier( $content ); } $content .= "\n;"; } // Add filter $content = apply_filters( 'litespeed_optm_cssjs', $content, $file_type, $src ); return $content; } /** * Load remote/local resource * * @since 3.5 */ public function load_file( $src, $file_type = 'css' ) { $real_file = Utility::is_internal_file( $src ); $postfix = pathinfo( parse_url( $src, PHP_URL_PATH ), PATHINFO_EXTENSION ); if ( ! $real_file || $postfix != $file_type ) { Debug2::debug2( '[CSS] Load Remote [' . $file_type . '] ' . $src ); $this_url = substr( $src, 0, 2 ) == '//' ? set_url_scheme( $src ) : $src; $res = wp_remote_get( $this_url ); $res_code = wp_remote_retrieve_response_code( $res ); if ( is_wp_error( $res ) || $res_code == 404 ) { Debug2::debug2( '[CSS] ❌ Load Remote error [code] ' . $res_code ); return false; } $con = wp_remote_retrieve_body( $res ); if ( ! $con ) { return false; } if ( $file_type == 'css' ) { $dirname = dirname( $this_url ) . '/'; $con = Lib\CSS_MIN\UriRewriter::prepend( $con, $dirname ); } } else { Debug2::debug2( '[CSS] Load local [' . $file_type . '] ' . $real_file[ 0 ] ); $con = File::read( $real_file[ 0 ] ); if ( $file_type == 'css' ) { $dirname = dirname( $real_file[ 0 ] ); $con = Lib\CSS_MIN\UriRewriter::rewrite( $con, $dirname ); } } return $con; } /** * Minify CSS * * @since 2.2.3 * @access private */ public static function minify_css( $data ) { try { $obj = new Lib\CSS_MIN\Minifier(); return $obj->run( $data ); } catch ( \Exception $e ) { Debug2::debug( '******[Optmer] minify_css failed: ' . $e->getMessage() ); error_log( '****** LiteSpeed Optimizer minify_css failed: ' . $e->getMessage() ); return $data; } } /** * Minify JS * * Added exception capture when minify * * @since 2.2.3 * @access private */ public static function minify_js( $data, $js_type = '' ) { // For inline JS optimize, need to check if it's js type if ( $js_type ) { preg_match( '#type=([\'"])(.+)\g{1}#isU', $js_type, $matches ); if ( $matches && $matches[ 2 ] != 'text/javascript' ) { Debug2::debug( '******[Optmer] minify_js bypass due to type: ' . $matches[ 2 ] ); return $data; } } try { $data = Lib\JSMin::minify( $data ); return $data; } catch ( \Exception $e ) { Debug2::debug( '******[Optmer] minify_js failed: ' . $e->getMessage() ); // error_log( '****** LiteSpeed Optimizer minify_js failed: ' . $e->getMessage() ); return $data; } } /** * Basic minifier * * @access private */ private function _null_minifier( $content ) { $content = str_replace( "\r\n", "\n", $content ); return trim( $content ); } /** * Check if the file is already min file * * @since 1.9 */ public function is_min( $filename ) { $basename = basename( $filename ); if ( preg_match( '/[-\.]min\.(?:[a-zA-Z]+)$/i', $basename ) ) { return true; } return false; } }