Edit file File name : class-wp-optimize-minify-front-end.php Content :<?php if (!defined('ABSPATH')) die('No direct access allowed'); class WP_Optimize_Minify_Front_End { private $collect_preload_js = array(); private $collect_preload_css = array(); private $collect_google_fonts = array(); private $minify_cache_incremented = false; private $options = array(); /** * Initialize actions and filters * * @return void */ public function __construct() { $this->include_dependencies(); $this->options = wp_optimize_minify_config()->get(); // Main process add_action('wp', array($this, 'init')); // Beaver builder add_action('fl_builder_after_save_layout', array('WP_Optimize_Minify_Cache_Functions', 'reset')); // extra_preload_headers is currently not available to users // add_action('send_headers', array($this, 'extra_preload_headers')); if (!is_admin()) { add_action('wp_headers', array($this, 'add_caching_headers')); } } /** * Fired on wp init * * @return void */ public function init() { /** * Check whether Minify is run on the current page */ if (!$this->run_on_page()) return; if ($this->options['emoji_removal']) { $this->disable_emojis(); } if ($this->options['clean_header_one']) { $this->remove_header_meta_info(); } // Headers & Preload JS/CSS/Extra if ($this->options['enabled_css_preload'] || $this->options['enabled_js_preload']) { add_action('wp_footer', array($this, 'generate_preload_headers'), PHP_INT_MAX); } if ($this->options['enable_js']) { $this->process_js(); } if ($this->options['enable_css']) { $this->process_css(); } // Preload tags if (trim($this->options['hpreload'])) { add_action('wp_head', array($this, 'add_assets_preload'), 2); } if ($this->should_process_html()) { add_action('template_redirect', array('WP_Optimize_Minify_Functions', 'html_compression_start'), 9); } if ($this->should_use_loadCSS()) { add_action('wp_footer', array('WP_Optimize_Minify_Print', 'add_load_css'), PHP_INT_MAX); } $this->remove_query_string_from_static_assets(); } /** * Detects whether cache preloading is running or not * * @return bool */ private function is_cache_preload() { return isset($_SERVER['HTTP_X_WP_OPTIMIZE_CACHE_PRELOAD']) && 0 === strcmp($_SERVER['HTTP_X_WP_OPTIMIZE_CACHE_PRELOAD'], 'Yes'); } /** * Set initial values for the meta log object * * @param string $header - Info on time the log was processed * @return array */ private function meta_log_initialization($header) { return array( 'header' => $header, 'invalidation_reason' => "", 'files_that_changed' => array() ); } /** * Wether to run the feature on a page or not * * @param string $context - Optional, The context where the check is done * @return boolean */ public function run_on_page($context = 'default') { /** * Filters wether the functionality is ran on the current page. * * @param boolean $run_on_page * @param string $context - Optional, The feature where the check is done */ return apply_filters( 'wpo_minify_run_on_page', !is_admin() && (!defined('SCRIPT_DEBUG') || !SCRIPT_DEBUG) && !is_preview() && (!function_exists('is_customize_preview') || !is_customize_preview()) && !($this->options['disable_when_logged_in'] && is_user_logged_in()) && !(function_exists('is_amp_endpoint') && is_amp_endpoint()) && !WP_Optimize_Minify_Functions::exclude_contents(), $context ); } /** * Inline css in place, instead of inlining the large file * * @param String $html * @param String $handle * @param String $href * @param String $media * * @return String */ public function inline_css($html, $handle, $href, $media) { $exclude_css = $this->get_excluded_css_list(); $ignore_list = WP_Optimize_Minify_Functions::compile_ignore_list($exclude_css); $blacklist = WP_Optimize_Minify_Functions::get_ie_blacklist(); $async_css = $this->get_async_css_list(); $master_ignore = array_merge($ignore_list, $blacklist); // make sure href is complete $href = WP_Optimize_Minify_Functions::get_hurl($href); if ($this->options['debug']) { echo "<!-- wpo_min DEBUG: Inline CSS processing start $handle / $href -->\n"; } // skip all this, if the async css option is enabled if ($this->options['loadcss']) return $html; // remove all css? if ($this->options['remove_css']) return false; // leave conditionals alone if (wp_styles()->get_data($handle, 'conditional')) return $html; // mediatype fix for some plugins + remove print mediatypes if ('screen' == $media || 'screen, print' == $media || empty($media) || is_null($media) || false == $media ) { $media = 'all'; } if (!empty($this->options['remove_print_mediatypes']) && 'print' == $media) { return false; } // Exclude specific CSS files from PageSpeedInsights? if (WP_Optimize_Minify_Functions::in_arrayi($href, $async_css)) { WP_Optimize_Minify_Print::exclude_style($href); return false; } // remove wpo_min from the ignore list $ignore_list = array_filter($ignore_list, array($this, 'check_wpo')); // return if in any ignore or black list if (count($master_ignore) > 0 && WP_Optimize_Minify_Functions::in_arrayi($href, $master_ignore)) { return $html; } // return if already minified if (WP_Optimize_Minify_Functions::is_minified_css_js_filename($href)) { return $html; } // check if working with a font awesom link if (WP_Optimize_Minify_Functions::is_font_awesome($href)) { // font awesome processing, async css if ('async' == $this->options['fawesome_method']) { WP_Optimize_Minify_Print::async_style($href, $media); return false; } elseif ('exclude' === $this->options['fawesome_method']) { // font awesome processing, async and exclude from PageSpeedIndex WP_Optimize_Minify_Print::exclude_style($href); return false; } elseif ('inline' == $this->options['fawesome_method']) { WP_Optimize_Minify_Print::inline_style($handle, $href); return false; } } // Check if working with google font url if ('fonts.googleapis.com' == parse_url($href, PHP_URL_HOST)) { // if option disabled google fonts processing if ($this->options['disable_google_fonts_processing']) return $html; // check if google fonts should be removed if ($this->options['remove_googlefonts']) return false; // check if google fonts should be merged if ($this->options['merge_google_fonts']) { if (WP_Optimize_Minify_Functions::is_flatsome_handle($handle)) { $href = WP_Optimize_Minify_Functions::fix_flatsome_google_fonts_url($href); $this->collect_google_fonts[$handle] = $href; } else { $this->collect_google_fonts[$handle] = $href; } return false; } else { if ('inline' === $this->options['gfonts_method']) { if (WP_Optimize_Minify_Functions::is_flatsome_handle($handle)) { $href = WP_Optimize_Minify_Functions::fix_flatsome_google_fonts_url($href); } // download, minify, cache $tkey = 'css-'.hash('adler32', $handle.$href).'.css'; $json = false; $json = WP_Optimize_Minify_Cache_Functions::get_transient($tkey); if (false === $json) { $json = WP_Optimize_Minify_Functions::download_and_minify($href, null, $this->options['enable_css_minification'], 'css', $handle); if ($this->options['debug']) { echo "<!-- wpo_min DEBUG: Uncached file processing now for $handle / $href -->\n"; } WP_Optimize_Minify_Cache_Functions::set_transient($tkey, $json); } // decode $res = json_decode($json, true); // inline css or fail if (null !== $res && false != $res['status']) { // add font-display // https://developers.google.com/web/updates/2016/02/font-display $res['code'] = str_ireplace('font-style:normal;', 'font-display:block;font-style:normal;', $res['code']); echo '<style class="optimize_css_1" type="text/css" media="all">'.$res['code'].'</style>' . "\n"; return false; } else { if ($this->options['debug']) { echo "<!-- wpo_min DEBUG: Google fonts request failed for $href -->\n"; } return $html; } } elseif ('async' === $this->options['gfonts_method']) { WP_Optimize_Minify_Print::async_style($href); return false; } elseif ('exclude' === $this->options['gfonts_method']) { WP_Optimize_Minify_Print::exclude_style($href); return false; } } } // skip external scripts that are not specifically allowed if (false === WP_Optimize_Minify_Functions::internal_url($href, site_url()) || empty($href)) { if ($this->options['debug']) { echo "<!-- wpo_min DEBUG: Skipped the next external enqueued CSS -->\n"; } return $html; } $file_size = WP_Optimize_Minify_Functions::get_file_size($href); // If we can't determine file size, then we still need to proceed with normal minify process if (apply_filters('wp_optimize_skip_inlining', false === $file_size || $file_size > 20480, $file_size, $href)) return $html; // download, minify, cache $tkey = 'css-'.hash('adler32', $handle.$href).'.css'; $json = false; $json = WP_Optimize_Minify_Cache_Functions::get_transient($tkey); if (false === $json) { $json = WP_Optimize_Minify_Functions::download_and_minify($href, null, $this->options['enable_css_minification'], 'css', $handle); if ($this->options['debug']) { echo "<!-- wpo_min DEBUG: Uncached file processing now for $handle / $href -->" . "\n"; } WP_Optimize_Minify_Cache_Functions::set_transient($tkey, $json); } // decode $res = json_decode($json, true); // inline it + other inlined children styles if (null !== $res && false != $res['status']) { echo '<style class="optimize_css_2" type="text/css" media="'.$media.'">'.$res['code'].'</style>' . "\n"; // get inline_styles for this handle, minify and print $inline_styles = array(); $inline_styles = wp_styles()->get_data($handle, 'after'); if (false != $inline_styles) { // string type if (is_string($inline_styles)) { $code = WP_Optimize_Minify_Functions::get_css($href, $inline_styles, $this->options['enable_css_minification']); if (!empty($code) && false != $code) { echo '<style class="optimize_css_3" type="text/css" media="'.$media.'">'.$code.'</style>' . "\n"; } } // array type if (is_array($inline_styles)) { foreach ($inline_styles as $st) { $code = WP_Optimize_Minify_Functions::get_css($href, $st, $this->options['enable_css_minification']); if (!empty($code) && false != $code) { echo '<style class="optimize_css_4" type="text/css" media="'.$media.'">'.$code.'</style>' . "\n"; } } } } // prevent default return false; } else { if ($this->options['debug']) { echo "<!-- wpo_min DEBUG: $handle / $href returned an empty from minification -->" . "\n"; } return $html; } echo "<!-- ERROR: WPO-Minify couldn't catch the CSS file below. Please report this on https://wordpress.org/support/plugin/wp-optimize/ -->\n"; return $html; } /** * Enable defer for JavaScript (WP 4.1 and above) and remove query strings for ignored files * * @param String $tag * @param String $handle * @param String $src * * @return String */ public function defer_js($tag, $handle, $src) { $wp_domain = trim(str_ireplace(array('http://', 'https://'), '', trim(site_url(), '/'))); $exclude_js = $this->get_excluded_js_list(); $ignore_list = WP_Optimize_Minify_Functions::compile_ignore_list($exclude_js); // Should this defer the Poly fills for IE? $blacklist = WP_Optimize_Minify_Functions::get_ie_blacklist(); // no query strings if (false !== stripos($src, '?ver')) { $srcf = stristr($src, '?ver', true); $tag = str_ireplace($src, $srcf, $tag); $src = $srcf; } // return if defer option is set to individual if ('individual' === $this->options['enable_defer_js']) { return $tag; } // return the tag if it's a polyfill if (count($blacklist) > 0 && WP_Optimize_Minify_Functions::in_arrayi($src, $blacklist)) { return $tag; } // Skip deferring the jQuery library option, if the defer jQuery option is disabled if (!$this->options['defer_jquery'] && (false !== stripos($tag, '/jquery.js') || false !== stripos($tag, '/jquery.min.js') || (false !== stripos($tag, '/jquery-') && false !== stripos($tag, '.js'))) ) { return $tag; } // return if external script url https://www.chromestatus.com/feature/5718547946799104 if (WP_Optimize_Minify_Functions::is_local_domain($src) !== true) { return $tag; } // bypass if already optimized if (false !== stripos($tag, 'navigator.userAgent.match')) { return $tag; } // should we exclude defer on the login page? if ($this->options['exclude_defer_login'] && false !== stripos($_SERVER["SCRIPT_NAME"], strrchr(wp_login_url(), '/')) ) { return $tag; } // add defer attribute, but only if not having async or defer already if (stripos($tag, 'defer') === false && stripos($tag, 'async') === false) { // add cdn for PageSpeedIndex if (!empty($this->options['cdn_url'])) { $cdn_url = trim(trim(str_ireplace(array('http://', 'https://'), '', trim($this->options['cdn_url'], '/'))), '/'); $src = str_ireplace($wp_domain, $cdn_url, $src); } if ('async_using_js' === $this->options['defer_js_type']) { return WP_Optimize_Minify_Print::async_script($src, false); } // remove wpo_min from the ignore list $ignore_list = array_filter($ignore_list, array($this, 'check_wpo')); if (count($ignore_list) > 0 && WP_Optimize_Minify_Functions::in_arrayi($src, $ignore_list)) { return $tag; } else { /** * Filters whether to use the defer attribute or async. * * @default string 'defer' (any other value will be set to async) * @param string $value - 'defer' or 'async' * @param string $tag - The <script> tag * @param string $handle - The WP handle for the script * @param string $src - The SRC for the script * @return string - 'defer' or 'async' */ $defer_or_async = apply_filters('wpo_minify_defer_or_async', 'defer', $tag, $handle, $src); if ('defer' === $defer_or_async) { return str_ireplace('<script ', '<script defer ', $tag); } else { return str_ireplace('<script ', '<script async ', $tag); } } } // fallback return $tag; } /** * Add inline CSS code / Critical Path * * @return void */ public function add_critical_path() { if (is_front_page() && !empty($this->options['critical_path_css_is_front_page'])) { echo '<style id="critical-path-is-front-page" type="text/css" media="all">' . "\n" . $this->options['critical_path_css_is_front_page'] . "\n" . '</style>' . "\n"; } elseif (!empty($this->options['critical_path_css'])) { echo '<style id="critical-path-global" type="text/css" media="all">' . "\n" . $this->options['critical_path_css'] . "\n" . '</style>' . "\n"; } } /** * Add critical assets preload * * @return void */ public function add_assets_preload() { $preload = json_decode($this->options['hpreload']); if (is_array($preload)) { foreach ($preload as $asset) { if (!empty($asset)) { echo '<link rel="preload" href="'.esc_url($asset->href).'" as="'.esc_attr($asset->type).'"'.($asset->crossorigin ? ' crossorigin' : '').'>'; } } } } /** * Process header CSS * * @return boolean */ public function process_header_css() { global $wp_styles; if (!is_object($wp_styles)) return false; $cache_path = WP_Optimize_Minify_Cache_Functions::cache_path(); $cache_base_dir = $cache_path['cachebasedir']; $cache_dir = $cache_path['cachedir']; $cache_dir_url = $cache_path['cachedirurl']; $exclude_css = $this->get_excluded_css_list(); $ignore_list = WP_Optimize_Minify_Functions::compile_ignore_list($exclude_css); $async_css = $this->get_async_css_list(); $minify_css = $this->options['enable_css_minification']; $merge_css = $this->options['enable_merging_of_css']; $merge_inline_extra_css_js = $this->options['merge_inline_extra_css_js']; $process_css = $minify_css || $merge_css; $disable_google_fonts_processing = $this->options['disable_google_fonts_processing']; $styles = clone $wp_styles; $styles->all_deps($styles->queue); $done = $styles->done; $header = array(); $google_fonts = array(); $process = array(); $inline_css = array(); $log = ''; // dequeue all styles if (isset($this->options['remove_css']) && $this->options['remove_css']) { foreach ($styles->to_do as $handle) { $done = array_merge($done, array($handle)); } // remove from queue $wp_styles->done = $done; return false; } // get list of handles to process, dequeue duplicate css urls and keep empty source handles (for dependencies) $uniq = array(); foreach ($styles->to_do as $handle) { // conditionals $conditional = null; if (isset($wp_styles->registered[$handle]->extra["conditional"])) { $conditional = $wp_styles->registered[$handle]->extra["conditional"]; // such as ie7, ie8, ie9, etc } // mediatype $mt = isset($wp_styles->registered[$handle]->args) ? $wp_styles->registered[$handle]->args : 'all'; if ('screen' == $mt || 'screen, print' == $mt || empty($mt) || is_null($mt) || false == $mt) { $mt = 'all'; } $mediatype = $mt; // full url or empty $href = WP_Optimize_Minify_Functions::get_hurl($wp_styles->registered[$handle]->src); $version = $wp_styles->registered[$handle]->ver; // inlined scripts without file if (empty($href)) continue; // mark duplicates as done and remove from the queue if (!empty($href)) { $version = is_array($version) ? '' : $version; $key = hash('adler32', $href . $version); if (isset($uniq[$key])) { $done = array_merge($done, array($handle)); continue; } else { $uniq[$key] = $handle; } } // Exclude specific CSS files from PageSpeedIndex? if (WP_Optimize_Minify_Functions::in_arrayi($href, $async_css)) { WP_Optimize_Minify_Print::exclude_style($href); $done = array_merge($done, array($handle)); continue; } // Fonts Awesome Processing if (WP_Optimize_Minify_Functions::is_font_awesome($href)) { if ('inline' === $this->options['fawesome_method']) { WP_Optimize_Minify_Print::inline_style($handle, $href); $done = array_merge($done, array($handle)); continue; } elseif ('async' === $this->options['fawesome_method']) { WP_Optimize_Minify_Print::async_style($href, $mediatype); $done = array_merge($done, array($handle)); continue; } elseif ('exclude' === $this->options['fawesome_method']) { WP_Optimize_Minify_Print::exclude_style($href); $done = array_merge($done, array($handle)); continue; } } // Exclude Print mediatype if (!empty($this->options['remove_print_mediatypes']) && 'print' === $mediatype) { $done = array_merge($done, array($handle)); continue; } // array of info to save $arr = array( 'handle' => $handle, 'url' => $href, 'conditional' => $conditional, 'mediatype' => $mediatype ); // google fonts to the top (collect and skip process array) if (WP_Optimize_Minify_Functions::is_google_font($href)) { if ($this->options['remove_googlefonts']) { $done = array_merge($done, array($handle)); continue; } if (WP_Optimize_Minify_Functions::is_flatsome_handle($handle)) { $href = WP_Optimize_Minify_Functions::fix_flatsome_google_fonts_url($href); $google_fonts[$handle] = $href; } else { $google_fonts[$handle] = $href; } } $process[$handle] = $arr; } // Process Google fonts if (count($google_fonts) > 0) { // merge google fonts if force inlining is enabled? $nfonts = array(); if ($this->options['merge_google_fonts']) { $nfonts[] = WP_Optimize_Minify_Fonts::concatenate_google_fonts($google_fonts); // mark the google fonts as done so they don't get processed if ('inherit' !== $this->options['gfonts_method']) { $done = array_merge($done, array_keys($google_fonts)); } } else { foreach ($google_fonts as $h => $a) { if (!empty($a)) { $nfonts[$h] = $a; } } } // foreach google font (will be one if merged is not disabled) foreach ($nfonts as $handle => $href) { if ('inline' === $this->options['gfonts_method']) { // download, minify, cache $tkey = 'css-'.hash('adler32', $href).'.css'; // this returns false if the cache is empty! but it doesn't check for failed $json = WP_Optimize_Minify_Cache_Functions::get_transient($tkey); $json = json_decode($json, true); // check if the cache is empty or has no code if (null === $json || false === $json || empty($json['code'])) { $res = WP_Optimize_Minify_Functions::download_and_minify($href, null, $minify_css, 'css', null); if ($this->options['debug']) { echo "<!-- wpo_min DEBUG: Uncached file processing now for $href -->\n"; } WP_Optimize_Minify_Cache_Functions::set_transient($tkey, $res); // decode $json = json_decode($res, true); } // inline css or fail if (null !== $json && !empty($json['code'])) { // add font-display // https://developers.google.com/web/updates/2016/02/font-display $json['code'] = str_ireplace('font-style:normal;', 'font-display:block;font-style:normal;', $json['code']); echo '<style type="text/css" media="all">'.$json['code'].'</style>' . "\n"; $done = array_merge($done, array($handle)); } else { echo "<!-- GOOGLE FONTS REQUEST FAILED for $href -->" . "\n"; // inlining failed, so enqueue again wp_enqueue_style($handle, $href, array(), null); } } elseif ('async' === $this->options['gfonts_method']) { WP_Optimize_Minify_Print::async_style($href); $done = array_merge($done, array($handle)); } elseif ('exclude' === $this->options['gfonts_method']) { // make a stylesheet, hide from PageSpeedIndex WP_Optimize_Minify_Print::exclude_style($href); $done = array_merge($done, array($handle)); } } } // get groups of handles foreach ($styles->to_do as $handle) { // skip already processed google fonts and empty dependencies if (isset($google_fonts[$handle]) && 'inherit' !== $this->options['gfonts_method']) { continue; } if (empty($wp_styles->registered[$handle]->src)) { continue; } if (WP_Optimize_Minify_Functions::in_arrayi($handle, $done)) { continue; } if (!isset($process[$handle])) { continue; } // get full url $href = $process[$handle]['url']; $conditional = $process[$handle]['conditional']; $mediatype = $process[$handle]['mediatype']; // IE only files don't increment things $ieonly = WP_Optimize_Minify_Functions::is_url_in_ie_blacklist($href); if ($ieonly) { continue; } $file_size = WP_Optimize_Minify_Functions::get_file_size($href); // If we can't determine file size, then we still need to proceed with normal minify process if (!apply_filters('wp_optimize_skip_inlining', false === $file_size || $file_size > 20480 || !$this->options['inline_css'], $file_size, $href)) continue; // check if the current URL points to a minified file. $exclude_minified_css = WP_Optimize_Minify_Functions::is_minified_css_js_filename($href); $normal_processing = $exclude_minified_css && !$merge_css; // skip ignore list, conditional css, external css, font-awesome merge and already minified js if (($process_css && !$normal_processing && !WP_Optimize_Minify_Functions::in_arrayi($href, $ignore_list) && !isset($conditional) && WP_Optimize_Minify_Functions::internal_url($href, site_url())) || empty($href) || ($process_css && 'inherit' == $this->options['fawesome_method'] && WP_Optimize_Minify_Functions::is_font_awesome($href)) || ($process_css && !$disable_google_fonts_processing && 'inherit' == $this->options['gfonts_method'] && WP_Optimize_Minify_Functions::is_google_font($href)) ) { // colect inline css for this handle if (isset($wp_styles->registered[$handle]->extra['after']) && is_array($wp_styles->registered[$handle]->extra['after'])) { $inline_css[$handle] = WP_Optimize_Minify_Functions::minify_css_string(implode('', $wp_styles->registered[$handle]->extra['after'])); // save $wp_styles->registered[$handle]->extra['after'] = null; // dequeue } // process if (isset($header[count($header)-1]['handle']) || count($header) == 0 || $header[count($header)-1]['media'] != $mediatype || !$merge_css) { array_push($header, array('handles' => array(), 'media' => $mediatype, 'versions' => array())); } // push it to the array array_push($header[count($header)-1]['handles'], $handle); // We need this because some plugins/theme use empty array as version // https://plugins.trac.wordpress.org/browser/mailin/tags/3.1.54/sendinblue.php#L456 $version_str = is_array($wp_styles->registered[$handle]->ver) ? $this->array_to_string_conversion($wp_styles->registered[$handle]->ver) : $wp_styles->registered[$handle]->ver; array_push($header[count($header)-1]['versions'], $version_str); $header[count($header) - 1]['last_modified'][] = WP_Optimize_Minify_Functions::get_modification_time($wp_styles->registered[$handle]->src); // external and ignored css } else { // normal enqueuing array_push($header, array('handle' => $handle)); } } /** * Filters the array of stylesheets before processing them * * @param array $list - The list of items filtered * @param string $location - The location of the list (footer or header) * @return array */ $header = apply_filters('wpo_minify_stylesheets', $header, 'header'); // loop through header css and merge for ($i=0,$l=count($header); $i<$l; $i++) { if (!isset($header[$i]['handle'])) { if ($merge_css) { // get hash for the inline css in this group $inline_css_group = array(); foreach ($header[$i]['handles'] as $h) { if (!empty($inline_css) && isset($inline_css[$h]) && !empty($inline_css[$h])) { $inline_css_group[] = $inline_css[$h]; } } $inline_css_hash = md5(implode('', $inline_css_group)); $handle_str = $this->array_to_string_conversion($header[$i]['handles']); $last_modified_str = $this->array_to_string_conversion($header[$i]['last_modified']); $hash = hash('adler32', $handle_str . $inline_css_hash . $last_modified_str); } else { $hash = implode('', $header[$i]['handles']) . implode('', $header[$i]['last_modified']); } // static cache file info $file_name = 'wpo-minify-header-'.$hash.($minify_css ? '.min' : ''); // create cache files and urls $file = $cache_dir.'/'.$file_name.'.css'; $meta_json_file = $cache_base_dir.'/'.'meta.json'; $file_url = WP_Optimize_Minify_Functions::get_protocol("$cache_dir_url/$file_name.css"); // generate a new cache file clearstatcache(); if (!file_exists($file)) { // code and log initialization $log_header = "PROCESSED on ".date('r')." from ".home_url(add_query_arg(null, null)); $log = array( 'header' => $log_header, 'files' => array() ); $meta_log = $this->meta_log_initialization($log_header); $code = ''; // minify and write to file foreach ($header[$i]['handles'] as $handle) { if (!empty($wp_styles->registered[$handle]->src)) { // get href per handle $handle_src = $wp_styles->registered[$handle]->src; $href = WP_Optimize_Minify_Functions::get_hurl($handle_src); $version = $wp_styles->registered[$handle]->ver; $last_modified = WP_Optimize_Minify_Functions::get_modification_time($handle_src); // inlined scripts without file if (empty($href)) continue; // download, minify, cache $tkey = 'css-'.hash('adler32', $handle . $href).'.css'; $json = false; $json = WP_Optimize_Minify_Cache_Functions::get_transient($tkey); if (false === $json) { $enable_minification = $this->options['enable_css_minification'] && !WP_Optimize_Minify_Functions::is_minified_css_js_filename($href); $json = WP_Optimize_Minify_Functions::download_and_minify($href, null, $enable_minification, 'css', $handle, $version); if ($this->options['debug']) { echo "<!-- wpo_min DEBUG: Uncached file processing now for $handle / $href / $version -->" . "\n"; } WP_Optimize_Minify_Cache_Functions::set_transient($tkey, $json); } // decode $res = json_decode($json, true); if (null === $res) continue; if ($this->should_reset_minify_assets($res, $href, 'css', $handle, $version)) { WP_Optimize_Minify_Cache_Functions::reset(); $this->minify_cache_incremented = true; $meta_log['invalidation_reason'] = 'Checksum Mismatch and version changed from ' . $res['request']['version'] . ' to ' . $version; $meta_log['files_that_changed'][$handle] = $res['log']; } // response has failed if (true != $res['status']) { $log['files'][$handle] = $res['log']; $log['files'][$handle]['version'] = $version; $log['files'][$handle]['last_modified'] = $last_modified; continue; } // Only add the $handle to $done if it was successfully downloaded $done[] = $handle; // append code to merged file $code .= isset($res['code']) ? $res['code'] : ''; $log['files'][$handle] = $res['log']; $log['files'][$handle]['version'] = $version; $log['files'][$handle]['last_modified'] = $last_modified; // append inlined styles if ($merge_inline_extra_css_js && isset($inline_css[$handle]) && !empty($inline_css[$handle])) { $code.= $inline_css[$handle]; } // consider dependencies on handles with an empty src } else { wp_dequeue_script($handle); wp_enqueue_script($handle); } }; // generate cache, write log if (!empty($code)) { // Only generate meta.json file if cache is reset if (!empty($meta_log['invalidation_reason'])) { file_put_contents($meta_json_file, json_encode($meta_log)); } WP_Optimize_Minify_Print::write_combined_asset($file, $code, $log); } } else { $log_file = $file.'.json'; if (file_exists($log_file)) { $saved_log = json_decode(file_get_contents($log_file)); if (is_object($saved_log) && property_exists($saved_log, 'files')) { $files = (array) $saved_log->files; foreach ($header[$i]['handles'] as $handle) { $handle = is_array($handle) ? '' : $handle; if (isset($files[$handle]) && $files[$handle]->success) { $done[] = $handle; } } } else { // The merged file already exists, so add all files to $done. $done = array_merge($done, $header[$i]['handles']); } } else { // The merged file already exists, so add all files to $done. $done = array_merge($done, $header[$i]['handles']); } } // the developers tab, takes precedence // Async CSS with loadCSS ? if ($this->options['loadcss'] && empty($this->options['remove_css'])) { $mt = $header[$i]['media']; WP_Optimize_Minify_Print::async_style($file_url, $mt); // enqueue file, if not empty } else { if (file_exists($file) && filesize($file) > 0) { // enqueue it wp_enqueue_style("wpo_min-header-$i", $file_url, array(), 'mycoolversion', $header[$i]['media']); foreach ($header[$i]['handles'] as $h) { if (!$merge_inline_extra_css_js && isset($inline_css[$h]) && !empty($inline_css[$h])) { wp_add_inline_style("wpo_min-header-$i", $inline_css[$h]); } } } else { // file could not be generated, output something meaningful echo "<!-- ERROR: WP-Optimize Minify was not allowed to save its cache on - ".str_replace(ABSPATH, '', $file)." -->"; echo "<!-- Please check if the path above is correct and ensure your server has write permission there! -->"; } } // other css need to be requeued for the order of files to be kept } else { wp_dequeue_style($header[$i]['handle']); wp_enqueue_style($header[$i]['handle']); } } // remove from queue $wp_styles->done = $done; return true; } /** * Process JS in the footer * * @return void */ public function process_footer_scripts() { global $wp_scripts; if (!is_object($wp_scripts)) { return; } $cache_path = WP_Optimize_Minify_Cache_Functions::cache_path(); $cache_base_dir = $cache_path['cachebasedir']; $cache_dir = $cache_path['cachedir']; $cache_dir_url = $cache_path['cachedirurl']; $exclude_js = $this->get_excluded_js_list(); $ignore_list = WP_Optimize_Minify_Functions::compile_ignore_list($exclude_js); $async_js = $this->get_async_js_list(); $scripts = clone $wp_scripts; $scripts->all_deps($scripts->queue); $footer = array(); $minify_js = $this->options['enable_js_minification']; $merge_js = $this->options['enable_merging_of_js']; $process_js = $minify_js || $merge_js; $merge_inline_extra_css_js = $this->options['merge_inline_extra_css_js']; // mark as done (as we go) $done = $scripts->done; $previous_load_strategy = null; // get groups of handles foreach ($scripts->to_do as $handle) : // get full url $href = WP_Optimize_Minify_Functions::get_hurl($wp_scripts->registered[$handle]->src); // inlined scripts without file if (empty($href)) { continue; } // Exclude JS files from PageSpeedIndex (Async) takes priority over the ignore list if (false != $async_js || is_array($async_js)) { // check for string match $skipjs = false; foreach ($async_js as $l) { if (stripos($href, $l) !== false) { // print code if there are no linebreaks, or return WP_Optimize_Minify_Print::async_script($href); $skipjs = true; $done = array_merge($done, array($handle)); break; } } if (false != $skipjs) { continue; } } // IE only files don't increment things $ieonly = WP_Optimize_Minify_Functions::is_url_in_ie_blacklist($href); if ($ieonly) { continue; } // check if the current URL points to a minified file. $exclude_minified_js = WP_Optimize_Minify_Functions::is_minified_css_js_filename($href); $normal_processing = $exclude_minified_js && !$merge_js; // skip ignore list, scripts with conditionals, external scripts if (($process_js && !$normal_processing && !WP_Optimize_Minify_Functions::in_arrayi($href, $ignore_list) && !isset($wp_scripts->registered[$handle]->extra["conditional"]) && WP_Optimize_Minify_Functions::internal_url($href, site_url())) || empty($href) ) { // process if (isset($footer[count($footer)-1]['handle']) || !count($footer) || !$merge_js || (isset($scripts->registered[$handle]->extra['strategy']) && $previous_load_strategy != $scripts->registered[$handle]->extra['strategy']) || (!isset($scripts->registered[$handle]->extra['strategy']) && null != $previous_load_strategy) ) { array_push($footer, array('handles' => array(), 'versions' => array())); } if (isset($wp_scripts->registered[$handle]->extra['before'])) { if (!empty($footer[count($footer)-1]['handles'])) { array_push($footer, array('handles' => array(), 'versions' => array())); } } // push it to the array array_push($footer[count($footer)-1]['handles'], $handle); array_push($footer[count($footer)-1]['versions'], $wp_scripts->registered[$handle]->ver); // Register strategy if it is used if (isset($scripts->registered[$handle]->extra['strategy'])) $footer[count($footer)-1]['strategy'] = $scripts->registered[$handle]->extra['strategy']; $footer[count($footer) - 1]['last_modified'][] = WP_Optimize_Minify_Functions::get_modification_time($wp_scripts->registered[$handle]->src); // external and ignored scripts if (isset($scripts->registered[$handle]->extra['strategy'])) { $previous_load_strategy = $scripts->registered[$handle]->extra['strategy']; } else { $previous_load_strategy = null; } } else { array_push($footer, array('handle' => $handle)); } endforeach; // loop through footer scripts and merge for ($i=0,$l=count($footer); $i<$l; $i++) { if (!isset($footer[$i]['handle'])) { if ($merge_js) { // Change the hash based on last modified timestamp $hash = hash('adler32', implode('', $footer[$i]['handles']) . implode('', $footer[$i]['last_modified'])); } else { $hash = implode('', $footer[$i]['handles']) . implode('', $footer[$i]['last_modified']); } // static cache file info $file_name = 'wpo-minify-footer-'.$hash.($minify_js ? '.min' : ''); // create cache files and urls $file = $cache_dir.'/'.$file_name.'.js'; $meta_json_file = $cache_base_dir.'/'.'meta.json'; $file_url = WP_Optimize_Minify_Functions::get_protocol($cache_dir_url.'/'.$file_name.'.js'); // generate a new cache file clearstatcache(); if (!file_exists($file)) { // code and log initialization $log_header = "PROCESSED on ".date('r')." from ".home_url(add_query_arg(null, null)); $log = array( 'header' => $log_header, 'files' => array() ); $meta_log = $this->meta_log_initialization($log_header); $before_code = ''; $code = ''; $after_code = ''; // minify and write to file foreach ($footer[$i]['handles'] as $handle) : if (!empty($wp_scripts->registered[$handle]->src)) { // only add minified scripts once to the bundle if (in_array($handle, $done)) continue; // get href per handle $handle_src = $wp_scripts->registered[$handle]->src; $href = WP_Optimize_Minify_Functions::get_hurl($handle_src); $version = $wp_scripts->registered[$handle]->ver; $last_modified = WP_Optimize_Minify_Functions::get_modification_time($handle_src); // inlined scripts without file if (empty($href)) { continue; } // download, minify, cache $tkey = 'js-'.hash('adler32', $handle . $href).'.js'; $json = false; $json = WP_Optimize_Minify_Cache_Functions::get_transient($tkey); if (false === $json) { $enable_minification = $minify_js && !WP_Optimize_Minify_Functions::is_minified_css_js_filename($href); $json = WP_Optimize_Minify_Functions::download_and_minify($href, null, $enable_minification, 'js', $handle, $version); if ($this->options['debug']) { echo "<!-- wpo_min DEBUG: Uncached file processing now for $handle / $href / $version -->\n"; } WP_Optimize_Minify_Cache_Functions::set_transient($tkey, $json); } // decode $res = json_decode($json, true); if (null === $res) continue; if ($this->should_reset_minify_assets($res, $href, 'js', $handle, $version)) { WP_Optimize_Minify_Cache_Functions::reset(); $this->minify_cache_incremented = true; $meta_log['invalidation_reason'] = 'Checksum Mismatch and version changed from ' . $res['request']['version'] . ' to ' . $version; $meta_log['files_that_changed'][$handle] = $res['log']; } // response has failed if (true != $res['status']) { $log['files'][$handle] = $res['log']; $log['files'][$handle]['uses_loading_strategy'] = isset($footer[$i]['strategy']); $log['files'][$handle]['version'] = $version; $log['files'][$handle]['last_modified'] = $last_modified; continue; } $done[] = $handle; // Add extra data from wp_add_inline_script before if (!empty($wp_scripts->registered[$handle]->extra)) { if (!empty($wp_scripts->registered[$handle]->extra['before']) && is_array($wp_scripts->registered[$handle]->extra['before'])) { if ($merge_inline_extra_css_js) { $before_code .= "\n" . implode("\n", array_filter($wp_scripts->registered[$handle]->extra['before'])); } } } // Add translation if (!empty($wp_scripts->registered[$handle]->textdomain)) { $code .= "\n" . $wp_scripts->print_translations($handle, false); } // Add extra data from wp_add_inline_script after if (!empty($wp_scripts->registered[$handle]->extra)) { if (!empty($wp_scripts->registered[$handle]->extra['after']) && is_array($wp_scripts->registered[$handle]->extra['after'])) { if ($merge_inline_extra_css_js) { $after_code .= "\n" . implode("\n", array_filter($wp_scripts->registered[$handle]->extra['after'])); } } } // append code to merged file $code .= $before_code . "\n"; $code .= isset($res['code']) ? $res['code'] : ''; $code .= "\n" . $after_code . "\n"; $code = WP_Optimize_Minify_Functions::prepare_merged_js($code, $href); $log['files'][$handle] = $res['log']; $log['files'][$handle]['uses_loading_strategy'] = isset($footer[$i]['strategy']); $log['files'][$handle]['version'] = $version; $log['files'][$handle]['last_modified'] = $last_modified; // consider dependencies on handles with an empty src } else { wp_dequeue_script($handle); wp_enqueue_script($handle); } endforeach; // generate cache, write log if (!empty($code)) { // Only generate meta.json file if cache is reset if (!empty($meta_log['invalidation_reason'])) { file_put_contents($meta_json_file, json_encode($meta_log)); } WP_Optimize_Minify_Print::write_combined_asset($file, $code, $log); } } else { $log_file = $file.'.json'; if (file_exists($log_file)) { $saved_log = json_decode(file_get_contents($log_file)); if (is_object($saved_log) && property_exists($saved_log, 'files')) { $files = (array) $saved_log->files; foreach ($footer[$i]['handles'] as $handle) { if (isset($files[$handle]) && $files[$handle]->success) { $done[] = $handle; } } } else { // The merged file already exists, so add all files to $done. $done = array_merge($done, $footer[$i]['handles']); } } else { // The merged file already exists, so add all files to $done. $done = array_merge($done, $footer[$i]['handles']); } } // register minified file wp_register_script("wpo_min-footer-$i", $file_url, array(), null, false); // add all extra data from wp_localize_script $before_code = ''; $data = array(); $after_code = ''; foreach ($footer[$i]['handles'] as $handle) { if (isset($wp_scripts->registered[$handle]->extra['data'])) { $data[] = $wp_scripts->registered[$handle]->extra['data']; } // Add extra data from wp_add_inline_script before if (!empty($wp_scripts->registered[$handle]->extra)) { if (!empty($wp_scripts->registered[$handle]->extra['before']) && is_array($wp_scripts->registered[$handle]->extra['before'])) { if (!$merge_inline_extra_css_js) { $before_code.= "\n" . implode("\n", array_filter($wp_scripts->registered[$handle]->extra['before'])) . "\n"; } } if (!empty($wp_scripts->registered[$handle]->extra['after']) && is_array($wp_scripts->registered[$handle]->extra['after'])) { if (!$merge_inline_extra_css_js) { $after_code.= "\n" . implode("\n", array_filter($wp_scripts->registered[$handle]->extra['after'])) . "\n"; } } } } if (count($data) > 0) { $wp_scripts->registered["wpo_min-footer-$i"]->extra['data'] = implode("\n", $data); } // enqueue file, if not empty if (file_exists($file) && (filesize($file) > 0 || count($data) > 0)) { if (!empty($before_code)) { wp_add_inline_script("wpo_min-footer-$i", $before_code, 'before'); } wp_enqueue_script("wpo_min-footer-$i"); if (!empty($after_code)) { wp_add_inline_script("wpo_min-footer-$i", $after_code, 'after'); } if (isset($footer[$i]['strategy'])) { // Backward compatible way of doing this wp_script_add_data( "wpo_min-footer-$i", 'strategy', $footer[$i]['strategy']); } } else { // file could not be generated, output something meaningful echo "<!-- ERROR: WP-Optimize Minify was not allowed to save its cache on - ".str_replace(ABSPATH, '', $file)." -->"; echo "<!-- Please check if the path above is correct and ensure your server has write permission there! -->"; } // other scripts need to be requeued for the order of files to be kept } else { wp_dequeue_script($footer[$i]['handle']); wp_enqueue_script($footer[$i]['handle']); } } // remove from queue $wp_scripts->done = $done; } /** * Process header JavaScript * Dependant on 'enable_js' option * * @return void */ public function process_header_scripts() { global $wp_scripts; if (!is_object($wp_scripts)) return; $cache_path = WP_Optimize_Minify_Cache_Functions::cache_path(); $cache_base_dir = $cache_path['cachebasedir']; $cache_dir = $cache_path['cachedir']; $cache_dir_url = $cache_path['cachedirurl']; $exclude_js = $this->get_excluded_js_list(); $ignore_list = WP_Optimize_Minify_Functions::compile_ignore_list($exclude_js); $async_js = $this->get_async_js_list(); $scripts = clone $wp_scripts; $scripts->all_deps($scripts->queue); $minify_js = $this->options['enable_js_minification']; $merge_js = $this->options['enable_merging_of_js']; $process_js = $minify_js || $merge_js; $merge_inline_extra_css_js = $this->options['merge_inline_extra_css_js']; $header = array(); // mark as done (as we go) $done = $scripts->done; $excluded_dependencies = array(); $previous_load_strategy = null; // Prepare and separate assets (get groups of handles) foreach ($scripts->to_do as $handle) { // get full url $href = WP_Optimize_Minify_Functions::get_hurl($wp_scripts->registered[$handle]->src); // inlined scripts without file if (empty($href)) { wp_enqueue_script($handle, false); continue; } // Only go through items without a group if (!$scripts->groups[$handle]) { // Exclude JS files from PageSpeedIndex (Async) takes priority over the ignore list // check for string match $skipjs = false; foreach ($async_js as $l) { if (stripos($href, $l) !== false) { WP_Optimize_Minify_Print::async_script($href); $skipjs = true; $done = array_merge($done, array($handle)); break; } } // IE only files don't increment things if ($skipjs || WP_Optimize_Minify_Functions::is_url_in_ie_blacklist($href) ) { continue; } // Skip jQuery from the merged files if deferring is ENABLED and defer_jquery is DISABLED if ('all' === $this->options['enable_defer_js'] && !$this->options['defer_jquery'] && (false !== stripos($href, '/jquery.js') || false !== stripos($href, '/jquery.min.js') || (false !== stripos($href, '/jquery-') && false !== stripos($href, '.js'))) ) { continue; } // check if the current URL points to a minified file. $exclude_minified_js = WP_Optimize_Minify_Functions::is_minified_css_js_filename($href); $normal_processing = $exclude_minified_js && !$merge_js; // Group handles - skip ignore list, scripts with conditionals, external scripts if (($process_js && !$normal_processing && !WP_Optimize_Minify_Functions::in_arrayi($href, $ignore_list) && !isset($wp_scripts->registered[$handle]->extra["conditional"]) && WP_Optimize_Minify_Functions::internal_url($href, site_url())) || empty($href) ) { // process if (isset($header[count($header)-1]['handle']) || !count($header) || !$merge_js || (isset($scripts->registered[$handle]->extra['strategy']) && $previous_load_strategy != $scripts->registered[$handle]->extra['strategy']) || (!isset($scripts->registered[$handle]->extra['strategy']) && null != $previous_load_strategy) ) { array_push($header, array('handles' => array(), 'versions' => array())); } // Force loading of dependencies foreach ($wp_scripts->registered[$handle]->deps as $dep) { // If the handle is not present in $done yet, or excluded, enqueue it. $dep_href = WP_Optimize_Minify_Functions::get_hurl($wp_scripts->registered[$dep]->src); if (!in_array($dep, $done) && !WP_Optimize_Minify_Functions::in_arrayi($dep_href, $ignore_list)) { // Include any dependency array_push($header[count($header)-1]['handles'], $dep); array_push($header[count($header)-1]['versions'], $wp_scripts->registered[$dep]->ver); } elseif (!in_array($dep, $done) && WP_Optimize_Minify_Functions::in_arrayi($dep_href, $ignore_list)) { // The dependency is in the exclude list array_push($header, array('handle' => $dep)); // Record dependency to be added to the minified script as dependency array if (isset($excluded_dependencies[count($header)])) { $excluded_dependencies[count($header)][] = $dep; } else { $excluded_dependencies[count($header)] = array($dep); } // Adds the 'handles' record for the main script array_push($header, array('handles' => array(), 'versions' => array())); } } if (isset($wp_scripts->registered[$handle]->extra['before'])) { if (!empty($header[count($header)-1]['handles'])) { array_push($header, array('handles' => array(), 'versions' => array())); } } // push it to the array array_push($header[count($header)-1]['handles'], $handle); array_push($header[count($header)-1]['versions'], $scripts->registered[$handle]->ver); $header[count($header) - 1]['last_modified'][] = WP_Optimize_Minify_Functions::get_modification_time($wp_scripts->registered[$handle]->src); // Register strategy if it is used if (isset($scripts->registered[$handle]->extra['strategy'])) $header[count($header)-1]['strategy'] = $scripts->registered[$handle]->extra['strategy']; if (isset($scripts->registered[$handle]->extra['strategy'])) { $previous_load_strategy = $scripts->registered[$handle]->extra['strategy']; } else { $previous_load_strategy = null; } // external and ignored scripts } else { // add the ignored assets array_push($header, array('handle' => $handle)); } // make sure that the scripts skipped here, show up in the footer } else { wp_enqueue_script($handle, $href, array(), null, true); } } // loop through header scripts and merge for ($i=0,$l=count($header); $i < $l; $i++) { if (!isset($header[$i]['handle'])) { if ($merge_js) { $hash = hash('adler32', implode('', $header[$i]['handles']) . implode('', $header[$i]['last_modified'])); } else { $hash = implode('', $header[$i]['handles']) . implode('', $header[$i]['last_modified']); } // static cache file info $file_name = 'wpo-minify-header-'.$hash.($minify_js ? '.min' : ''); // create cache files and urls $file = $cache_dir.'/'.$file_name.'.js'; $meta_json_file = $cache_base_dir.'/'.'meta.json'; $file_url = WP_Optimize_Minify_Functions::get_protocol($cache_dir_url.'/'.$file_name.'.js'); // generate a new cache file clearstatcache(); if (!file_exists($file)) { // code and log initialization $log_header = "PROCESSED on ".date('r')." from ".home_url(add_query_arg(null, null)); $log = array( 'header' => $log_header, 'files' => array() ); $meta_log = $this->meta_log_initialization($log_header); $before_code = ''; $code = ''; $after_code = ''; // minify and write to file foreach ($header[$i]['handles'] as $handle) { if (!empty($wp_scripts->registered[$handle]->src)) { // only add minified scripts once to the bundle if (in_array($handle, $done)) continue; // get href per handle $handle_src = $wp_scripts->registered[$handle]->src; $href = WP_Optimize_Minify_Functions::get_hurl($handle_src); $version = $wp_scripts->registered[$handle]->ver; $last_modified = WP_Optimize_Minify_Functions::get_modification_time($handle_src); if (empty($href)) continue; // download, minify, cache $tkey = 'js-'.hash('adler32', $handle . $href).'.js'; $json = false; $json = WP_Optimize_Minify_Cache_Functions::get_transient($tkey); if (false === $json) { $enable_minification = $minify_js && !WP_Optimize_Minify_Functions::is_minified_css_js_filename($href); $json = WP_Optimize_Minify_Functions::download_and_minify($href, null, $enable_minification, 'js', $handle, $version); if ($this->options['debug']) { echo "<!-- wpo_min DEBUG: Uncached file processing now for $handle / $href / $version -->" . "\n"; } WP_Optimize_Minify_Cache_Functions::set_transient($tkey, $json); } // decode $res = json_decode($json, true); if (null === $res) continue; if ($this->should_reset_minify_assets($res, $href, 'js', $handle, $version)) { WP_Optimize_Minify_Cache_Functions::reset(); $this->minify_cache_incremented = true; $meta_log['invalidation_reason'] = 'Checksum Mismatch and version changed from ' . $res['request']['version'] . ' to ' . $version; $meta_log['files_that_changed'][$handle] = $res['log']; } // response has failed if (true != $res['status']) { $log['files'][$handle] = $res['log']; $log['files'][$handle]['uses_loading_strategy'] = isset($header[$i]['strategy']); $log['files'][$handle]['version'] = $version; $log['files'][$handle]['last_modified'] = $last_modified; continue; } // Add extra data from wp_add_inline_script before if (!empty($wp_scripts->registered[$handle]->extra)) { if (!empty($wp_scripts->registered[$handle]->extra['before']) && is_array($wp_scripts->registered[$handle]->extra['before'])) { if ($merge_inline_extra_css_js) { $before_code .= "\n" . implode("\n", array_filter($wp_scripts->registered[$handle]->extra['before'])); } } } // Only add the $handle to $done if it was successfully downloaded $done[] = $handle; // Add translations if (!empty($wp_scripts->registered[$handle]->textdomain)) { $code .= "\n" . $wp_scripts->print_translations($handle, false); } // Add extra data from wp_add_inline_script after if (!empty($wp_scripts->registered[$handle]->extra)) { if (!empty($wp_scripts->registered[$handle]->extra['after']) && is_array($wp_scripts->registered[$handle]->extra['after'])) { if ($merge_inline_extra_css_js) { $after_code.= "\n" . implode("\n", array_filter($wp_scripts->registered[$handle]->extra['after'])); } } } // append code to merged file $code .= $before_code . "\n"; $code .= isset($res['code']) ? $res['code'] : ''; $code .= "\n" . $after_code . "\n"; $code = WP_Optimize_Minify_Functions::prepare_merged_js($code, $href); $log['files'][$handle] = $res['log']; $log['files'][$handle]['uses_loading_strategy'] = isset($header[$i]['strategy']); $log['files'][$handle]['version'] = $version; $log['files'][$handle]['last_modified'] = $last_modified; // consider dependencies on handles with an empty src } else { wp_dequeue_script($handle); wp_enqueue_script($handle); } } // generate cache, write log if (!empty($code)) { // Only generate meta.json file if cache is reset if (!empty($meta_log['invalidation_reason'])) { file_put_contents($meta_json_file, json_encode($meta_log)); } WP_Optimize_Minify_Print::write_combined_asset($file, $code, $log); } } else { $log_file = $file.'.json'; if (file_exists($log_file)) { $saved_log = json_decode(file_get_contents($log_file)); if (is_object($saved_log) && property_exists($saved_log, 'files')) { $files = (array) $saved_log->files; foreach ($header[$i]['handles'] as $handle) { $handle = is_array($handle) ? '' : $handle; if (isset($files[$handle]) && $files[$handle]->success) { $done[] = $handle; } } } else { // The merged file already exists, so add all files to $done. $done = array_merge($done, $header[$i]['handles']); } } else { // The merged file already exists, so add all files to $done. $done = array_merge($done, $header[$i]['handles']); } } // register minified file $dependencies = isset($excluded_dependencies[$i]) ? $excluded_dependencies[$i] : array(); wp_register_script("wpo_min-header-$i", $file_url, $dependencies, null, false); // add all extra data from wp_localize_script $before_code = ''; $data = array(); $after_code = ''; foreach ($header[$i]['handles'] as $handle) { if (isset($wp_scripts->registered[$handle]->extra['data'])) { $data[] = $wp_scripts->registered[$handle]->extra['data']; } // Add extra data from wp_add_inline_script before if (!empty($wp_scripts->registered[$handle]->extra)) { if (!empty($wp_scripts->registered[$handle]->extra['before']) && is_array($wp_scripts->registered[$handle]->extra['before'])) { if (!$merge_inline_extra_css_js) { $before_code.= "\n" . implode("\n", array_filter($wp_scripts->registered[$handle]->extra['before'])) . "\n"; } } if (!empty($wp_scripts->registered[$handle]->extra['after']) && is_array($wp_scripts->registered[$handle]->extra['after'])) { if (!$merge_inline_extra_css_js) { $after_code.= "\n" . implode("\n", array_filter($wp_scripts->registered[$handle]->extra['after'])) . "\n"; } } } } if (count($data) > 0) { $wp_scripts->registered["wpo_min-header-$i"]->extra['data'] = implode("\n", $data); } // enqueue file, if not empty if (file_exists($file) && (filesize($file) > 0 || count($data) > 0)) { if (!empty($before_code)) { wp_add_inline_script("wpo_min-header-$i", $before_code, 'before'); } wp_enqueue_script("wpo_min-header-$i"); if (!empty($after_code)) { wp_add_inline_script("wpo_min-header-$i", $after_code, 'after'); } if (isset($header[$i]['strategy'])) { // Backward compatible way of doing this wp_script_add_data( "wpo_min-header-$i", 'strategy', $header[$i]['strategy']); } } else { // file could not be generated, output something meaningful echo "<!-- ERROR: WP-Optimize minify was not allowed to save its cache on - ".str_replace(ABSPATH, '', $file)." -->"; echo "<!-- Please check if the path above is correct and ensure your server has write permission there! -->"; echo "<!-- If you found a bug, please report this on https://wordpress.org/support/plugin/wp-optimize/ -->"; } // other scripts need to be requeued for the order of files to be kept } else { wp_dequeue_script($header[$i]['handle']); wp_enqueue_script($header[$i]['handle']); } } // remove from queue $wp_scripts->done = $done; } /** * Process CSS in the footer * * @return void */ public function process_footer_css() { global $wp_styles; if (!is_object($wp_styles)) return; $cache_path = WP_Optimize_Minify_Cache_Functions::cache_path(); $cache_base_dir = $cache_path['cachebasedir']; $cache_dir = $cache_path['cachedir']; $cache_dir_url = $cache_path['cachedirurl']; $exclude_css = $this->get_excluded_css_list(); $ignore_list = WP_Optimize_Minify_Functions::compile_ignore_list($exclude_css); $async_css = $this->get_async_css_list(); $minify_css = $this->options['enable_css_minification']; $merge_css = $this->options['enable_merging_of_css']; $process_css = $minify_css || $merge_css; $disable_google_fonts_processing = $this->options['disable_google_fonts_processing']; $log = ""; $code = ""; $styles = clone $wp_styles; $styles->all_deps($styles->queue); $done = $styles->done; $footer = array(); $google_fonts = array(); $inline_css = array(); // dequeue all styles if (isset($this->options['remove_css']) && $this->options['remove_css']) { foreach ($styles->to_do as $handle) : $done = array_merge($done, array($handle)); endforeach; // remove from queue $wp_styles->done = $done; return; } // dequeue and get a list of google fonts, or requeue external foreach ($styles->to_do as $handle) { $href = WP_Optimize_Minify_Functions::get_hurl($wp_styles->registered[$handle]->src); // inlined scripts without file if (empty($href)) continue; if (!$disable_google_fonts_processing && WP_Optimize_Minify_Functions::is_google_font($href)) { wp_dequeue_style($handle); if ($this->options['remove_googlefonts']) { $done = array_merge($done, array($handle)); continue; } // mark as done if to be removed if ($this->options['merge_google_fonts'] || 'inline' === $this->options['gfonts_method'] ) { if (WP_Optimize_Minify_Functions::is_flatsome_handle($handle)) { $href = WP_Optimize_Minify_Functions::fix_flatsome_google_fonts_url($href); $google_fonts[$handle] = $href; } else { $google_fonts[$handle] = $href; } } else { // skip google fonts optimization? wp_enqueue_style($handle); } } else { // failsafe wp_dequeue_style($handle); wp_enqueue_style($handle); } } // concat google fonts, if enabled if ($this->options['merge_google_fonts'] && count($google_fonts) > 0 || ('inline' === $this->options['gfonts_method'] && count($google_fonts) > 0) ) { // merge google fonts if force inlining is enabled? $nfonts = array(); if ($this->options['merge_google_fonts']) { $nfonts[] = WP_Optimize_Minify_Fonts::concatenate_google_fonts($google_fonts); } else { foreach ($google_fonts as $h => $a) { if (!empty($a)) { $nfonts[$h] = $a; } } } // foreach google font (will be one if merged is not disabled) if (count($nfonts) > 0) { foreach ($nfonts as $handle => $href) { // hide from PageSpeedIndex, async, inline, or default if ('exclude' === $this->options['gfonts_method']) { WP_Optimize_Minify_Print::exclude_style($href); $done = array_merge($done, array($handle)); } elseif ('async' === $this->options['gfonts_method']) { // async CSS WP_Optimize_Minify_Print::async_style($href); $done = array_merge($done, array($handle)); } elseif ('inline' === $this->options['gfonts_method']) { // inline css WP_Optimize_Minify_Print::inline_style($handle, $href); $done = array_merge($done, array($handle)); } else { // fallback, enqueue google fonts wp_enqueue_style($handle, $href, array(), null, 'all'); } } } } // get groups of handles $uniq = array(); foreach ($styles->to_do as $handle) { // skip already processed google fonts if (isset($google_fonts[$handle])) { continue; } // conditionals $conditional = null; if (isset($wp_styles->registered[$handle]->extra["conditional"])) { $conditional = $wp_styles->registered[$handle]->extra["conditional"]; // such as ie7, ie8, ie9, etc } // mediatype $mt = isset($wp_styles->registered[$handle]->args) ? $wp_styles->registered[$handle]->args : 'all'; if ('screen' == $mt || 'screen, print' == $mt || empty($mt) || is_null($mt) || false == $mt) { $mt = 'all'; } $mediatype = $mt; // get full url $href = WP_Optimize_Minify_Functions::get_hurl($wp_styles->registered[$handle]->src); $version = $wp_styles->registered[$handle]->ver; // inlined scripts without file if (empty($href)) { continue; } // mark duplicates as done and remove from the queue if (!empty($href)) { $version = is_array($version) ? '' : $version; $key = hash('adler32', $href . $version); if (isset($uniq[$key])) { $done = array_merge($done, array($handle)); continue; } else { $uniq[$key] = $handle; } } // IE only files don't increment things $ieonly = WP_Optimize_Minify_Functions::is_url_in_ie_blacklist($href); if ($ieonly) { continue; } // Exclude specific CSS files from PageSpeedIndex? if (false != $async_css && is_array($async_css) && WP_Optimize_Minify_Functions::in_arrayi($href, $async_css)) { WP_Optimize_Minify_Print::exclude_style($href); $done = array_merge($done, array($handle)); continue; } if (WP_Optimize_Minify_Functions::is_font_awesome($href)) { if ('inline' === $this->options['fawesome_method']) { WP_Optimize_Minify_Print::inline_style($handle, $href); $done = array_merge($done, array($handle)); continue; } elseif ('async' === $this->options['fawesome_method']) { WP_Optimize_Minify_Print::async_style($href, $mediatype); $done = array_merge($done, array($handle)); continue; } elseif ('exclude' === $this->options['fawesome_method']) { WP_Optimize_Minify_Print::exclude_style($href); $done = array_merge($done, array($handle)); continue; } } // Exclude Print mediatype if ($this->options['remove_print_mediatypes'] && 'print' === $mediatype) { $done = array_merge($done, array($handle)); continue; } // check if the current URL points to a minified file. $exclude_minified_css = WP_Optimize_Minify_Functions::is_minified_css_js_filename($href); $normal_processing = $exclude_minified_css && !$merge_css; // skip ignore list, conditional css, external css, font-awesome merge and already minified css if (($process_css && !$normal_processing && !WP_Optimize_Minify_Functions::in_arrayi($href, $ignore_list) && !isset($conditional) && WP_Optimize_Minify_Functions::internal_url($href, site_url())) || empty($href) ) { // colect inline css for this handle if (isset($wp_styles->registered[$handle]->extra['after']) && is_array($wp_styles->registered[$handle]->extra['after'])) { $inline_css[$handle] = WP_Optimize_Minify_Functions::minify_css_string(implode('', $wp_styles->registered[$handle]->extra['after'])); // save $wp_styles->registered[$handle]->extra['after'] = null; // dequeue } // process if (isset($footer[count($footer)-1]['handle']) || !count($footer) || $footer[count($footer)-1]['media'] != $wp_styles->registered[$handle]->args || !$merge_css) { array_push($footer, array('handles' => array(), 'media' => $mediatype, 'versions' => array())); } // push it to the array get latest modified time array_push($footer[count($footer)-1]['handles'], $handle); array_push($footer[count($footer)-1]['versions'], $version); $footer[count($footer) - 1]['last_modified'][] = WP_Optimize_Minify_Functions::get_modification_time($wp_styles->registered[$handle]->src); // external and ignored css } else { // normal enqueueing array_push($footer, array('handle' => $handle)); } } /** * Filters the array of stylesheets before processing them * * @param array $list - The list of items filtered * @param string $location - The location of the list (footer or header) * @return array */ $footer = apply_filters('wpo_minify_stylesheets', $footer, 'footer'); // loop through footer css and merge for ($i=0,$l=count($footer); $i<$l; $i++) { if (!isset($footer[$i]['handle'])) { if ($merge_css) { // get hash for the inline css in this group $inline_css_group = array(); foreach ($footer[$i]['handles'] as $h) { if (isset($inline_css[$h]) && !empty($inline_css[$h])) { $inline_css_group[] = $inline_css[$h]; } } $inline_css_hash = md5(implode('', $inline_css_group)); $hash = hash('adler32', implode('', $footer[$i]['handles']) . $inline_css_hash . implode('', $footer[$i]['last_modified'])); } else { $hash = implode('', $footer[$i]['handles']) . implode('', $footer[$i]['last_modified']); } // static cache file info $file_name = 'wpo-minify-footer-'.$hash.($minify_css ? '.min' : ''); // create cache files and urls $file = $cache_dir.'/'.$file_name.'.css'; $meta_json_file = $cache_base_dir.'/'.'meta.json'; $file_url = WP_Optimize_Minify_Functions::get_protocol($cache_dir_url.'/'.$file_name.'.css'); // generate a new cache file clearstatcache(); if (!file_exists($file)) { // code and log initialization $log_header = "PROCESSED on ".date('r')." from ".home_url(add_query_arg(null, null)); $log = array( 'header' => $log_header, 'files' => array() ); $meta_log = $this->meta_log_initialization($log_header); $code = ''; // minify and write to file foreach ($footer[$i]['handles'] as $handle) { if (!empty($wp_styles->registered[$handle]->src)) { // get href per handle $handle_src = $wp_styles->registered[$handle]->src; $href = WP_Optimize_Minify_Functions::get_hurl($handle_src); $version = $wp_styles->registered[$handle]->ver; $last_modified = WP_Optimize_Minify_Functions::get_modification_time($handle_src); // inlined scripts without hreffile if (empty($href)) continue; // download, minify, cache $tkey = 'css-'.hash('adler32', $handle . $href).'.css'; $json = false; $json = WP_Optimize_Minify_Cache_Functions::get_transient($tkey); if (false === $json) { $enable_minification = $minify_css && !WP_Optimize_Minify_Functions::is_minified_css_js_filename($href); $json = WP_Optimize_Minify_Functions::download_and_minify($href, null, $enable_minification, 'css', $handle, $version); if ($this->options['debug']) { echo "<!-- wpo_min DEBUG: Uncached file processing now for $handle / $href / $version -->" . "\n"; } WP_Optimize_Minify_Cache_Functions::set_transient($tkey, $json); } // decode $res = json_decode($json, true); if (null === $res) continue; if ($this->should_reset_minify_assets($res, $href, 'css', $handle, $version)) { WP_Optimize_Minify_Cache_Functions::reset(); $this->minify_cache_incremented = true; $meta_log['invalidation_reason'] = 'Checksum Mismatch and version changed from ' . $res['request']['version'] . ' to ' . $version; $meta_log['files_that_changed'][$handle] = $res['log']; } // response has failed if (true != $res['status']) { $log['files'][$handle] = $res['log']; $log['files'][$handle]['version'] = $version; $log['files'][$handle]['last_modified'] = $last_modified; continue; } $done[] = $handle; // append code to merged file $code .= isset($res['code']) ? $res['code'] : ''; $log['files'][$handle] = $res['log']; $log['files'][$handle]['version'] = $version; $log['files'][$handle]['last_modified'] = $last_modified; // append inlined styles if (isset($inline_css[$handle]) && !empty($inline_css[$handle])) { $code.= $inline_css[$handle]; } // consider dependencies on handles with an empty src } else { wp_dequeue_script($handle); wp_enqueue_script($handle); } }; // generate cache, add inline css, write log if (!empty($code)) { // Only generate meta.json file if cache is reset if (!empty($meta_log['invalidation_reason'])) { file_put_contents($meta_json_file, json_encode($meta_log)); } WP_Optimize_Minify_Print::write_combined_asset($file, $code, $log); } } else { $log_file = $file.'.json'; if (file_exists($log_file)) { $saved_log = json_decode(file_get_contents($log_file)); if (is_object($saved_log) && property_exists($saved_log, 'files')) { $files = (array) $saved_log->files; foreach ($footer[$i]['handles'] as $handle) { if (isset($files[$handle]) && $files[$handle]->success) { $done[] = $handle; } } } else { // The merged file already exists, so add all files to $done. $done = array_merge($done, $footer[$i]['handles']); } } else { // The merged file already exists, so add all files to $done. $done = array_merge($done, $footer[$i]['handles']); } } // Async CSS with loadCSS ? if ($this->options['loadcss'] && !$this->options['remove_css']) { $mt = $footer[$i]['media']; WP_Optimize_Minify_Print::async_style($file_url, $mt); // enqueue file, if not empty } else { if (file_exists($file) && filesize($file) > 0) { // inline if the file is smaller than 20KB or option has been enabled if (filesize($file) < 20000 && $this->options['inline_css']) { $this->inline_css(file_get_contents($file), $handle, $file_url, $footer[$i]['media']); } else { // enqueue it wp_enqueue_style("wpo_min-footer-$i", $file_url, array(), null, $footer[$i]['media']); } } else { // file could not be generated, output something meaningful echo "<!-- ERROR: WP-Optimize Minify was not allowed to save its cache on - ".str_replace(ABSPATH, '', $file)." -->"; echo "<!-- Please check if the path above is correct and ensure your server has write permission there! -->"; } } // other css need to be requeued for the order of files to be kept } else { wp_dequeue_style($footer[$i]['handle']); wp_enqueue_style($footer[$i]['handle']); } } // remove from queue $wp_styles->done = $done; } /** * Process JS Scripts of type module * * @param String $buffer Page HTML. * * @return String */ public function process_script_modules($buffer) { if ('' === $buffer) return ''; if (!WP_Optimize_Utils::is_valid_html($buffer)) return $buffer; $minify_js = $this->options['enable_js_minification']; $cache_path = WP_Optimize_Minify_Cache_Functions::cache_path(); $cache_dir = $cache_path['cachedir']; $cache_dir_url = $cache_path['cachedirurl']; $cache_base_dir = $cache_path['cachebasedir']; $scripts = $this->get_scripts($buffer); if (empty($scripts)) return $buffer; foreach ($scripts as $script) { if ("module" !== $script['type']) continue; $href = WP_Optimize_Minify_Functions::get_hurl($script['url']); $last_modified = WP_Optimize_Minify_Functions::get_modification_time($script['url']); $handle = $script['handle']; $version = $script['version']; // Module script handles use slashes and @ symbols, // replace them with '-' and empty string, break if handle is still '' if (!empty($handle)) { $handle_no_slash = str_replace(array('/', '@'), array('-', ''), $handle); } else { continue; } // code and log initialization $log_header = "PROCESSED on ".date('r')." from ".home_url(add_query_arg(null, null)); $log = array( 'header' => $log_header, 'files' => array() ); $meta_log = $this->meta_log_initialization($log_header); $code = ''; // create cache files and urls $file_name = 'wpo-minify-'.$handle_no_slash.($minify_js ? '.min' : ''); $file = $cache_dir.'/'.$file_name.'.js'; $meta_json_file = $cache_base_dir.'/'.'meta.json'; $file_url = WP_Optimize_Minify_Functions::get_protocol($cache_dir_url.'/'.$file_name.'.js'); // download, minify, cache $tkey = 'js-'.hash('adler32', $handle_no_slash . $href).'.js'; $json = WP_Optimize_Minify_Cache_Functions::get_transient($tkey); if (false === $json) { $enable_minification = $minify_js && !WP_Optimize_Minify_Functions::is_minified_css_js_filename($href); $json = WP_Optimize_Minify_Functions::download_and_minify($href, null, $enable_minification, 'js', $handle_no_slash, $version); if ($this->options['debug']) { echo "<!-- wpo_min DEBUG: Uncached file processing now for $handle / $href / $version -->\n"; } WP_Optimize_Minify_Cache_Functions::set_transient($tkey, $json); } // decode $res = json_decode($json, true); if (null === $res) continue; // Reset minified script module if ($this->should_reset_minify_assets($res, $href, 'js', $handle, $version)) { WP_Optimize_Minify_Cache_Functions::reset(); $this->minify_cache_incremented = true; $meta_log['invalidation_reason'] = 'Checksum Mismatch and version changed from ' . $res['request']['version'] . ' to ' . $version; $meta_log['files_that_changed'][$handle] = $res['log']; } if (!$res['status']) continue; $code .= isset($res['code']) ? $res['code'] : ''; $code = WP_Optimize_Minify_Functions::prepare_merged_js($code, $href); $log['files'][$handle] = $res['log']; $log['files'][$handle]['version'] = $version; $log['files'][$handle]['last_modified'] = $last_modified; // If code is empty (nothing to minify) break loop if (empty($code)) continue; // Only generate meta.json file if cache is reset if (!empty($meta_log['invalidation_reason'])) { file_put_contents($meta_json_file, json_encode($meta_log)); } WP_Optimize_Minify_Print::write_combined_asset($file, $code, $log); // Extract importmap script tag $json_map = $this->get_script_module_importmap($buffer); $json_map_update = $json_map; if (!empty($json_map) && isset($json_map['imports'])) { $json_map_update['imports'][$handle] = $file_url; } else { continue; } // Update module source file and importmap with new file url $buffer = str_replace($script['url'], $file_url, $buffer); $buffer = str_replace(json_encode($json_map), json_encode($json_map_update), $buffer); } return $buffer; } /** * Orders the CSS per media type * * @param array $list - The list of assets * @return array */ public function order_stylesheets_per_media_type($list) { // get unique mediatypes $allmedia = array(); foreach ($list as $array) { if (isset($array['media'])) { $allmedia[$array['media']] = ''; } } // extract handles by mediatype $grouphandles = array(); foreach ($allmedia as $md => $var) { foreach ($list as $array) { if (isset($array['media']) && $array['media'] === $md) { foreach ($array['handles'] as $h) { $grouphandles[$md][] = $h; } } } } // reset and reorder list by mediatypes $newlist = array(); foreach ($allmedia as $md => $var) { $newlist[] = array('handles' => $grouphandles[$md], 'media' => $md); } if (count($newlist) > 0) { $list = $newlist; } return $list; } /** * Merged google font * * @return void; */ public function add_google_fonts_merged() { // must have something to do if (count($this->collect_google_fonts) == 0) return; // merge google fonts $href = WP_Optimize_Minify_Fonts::concatenate_google_fonts($this->collect_google_fonts); if (empty($href)) { return; } // hide google fonts from PageSpeedIndex if ('exclude' === $this->options['gfonts_method']) { // make a stylesheet, hide from PageSpeedIndex WP_Optimize_Minify_Print::exclude_style($href); } elseif ('async' === $this->options['gfonts_method']) { // load google fonts async WP_Optimize_Minify_Print::async_style($href); } else { // fallback to normal inline WP_Optimize_Minify_Print::style($href); } // unset per hook foreach ($this->collect_google_fonts as $k => $v) { unset($this->collect_google_fonts[$k]); } } /** * Check if the string given contains 'wpo-minify' * * @param string $var * * @return boolean */ public function check_wpo($var) { return (false === strpos($var, 'assets/wpo-minify')); } /** * Generate preload headers file * * @return boolean */ public function generate_preload_headers() { // always false for admin pages if (is_admin()) return false; // get host with multisite support and query strings $host = htmlentities($_SERVER['SERVER_NAME']); if (empty($host)) { $host = isset($_SERVER['HTTP_HOST']) ? htmlentities($_SERVER['HTTP_HOST']) : ''; } $request_query = parse_url($_SERVER['REQUEST_URI'], PHP_URL_QUERY); $request_uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH); // initialize headers $headers = array(); // css headers if ($this->options['enabled_css_preload'] && count($this->collect_preload_css) > 0 ) { foreach ($this->collect_preload_css as $u) { // filter out footer footer files, because they are not in the critical path if (strpos($u, '/assets/wpo-minify-footer-') !== false) { continue; } // add headers $headers[] = "Link: <$u>; rel=preload; as=style"; } } // js headers if ($this->options['enabled_js_preload'] && count($this->collect_preload_js) > 0 ) { foreach ($this->collect_preload_js as $u) { // filter out footer footer files, because they are not in the critical path if (false !== strpos($u, '/assets/wpo-minify-footer-')) { continue; } // add headers $headers[] = "Link: <$u>; rel=preload; as=script"; } } // must have something if (count($headers) == 0) { return false; } else { $headers = implode("\n", $headers); } // get cache path $cache_path = WP_Optimize_Minify_Cache_Functions::cache_path(); $header_dir = $cache_path['headerdir']; // possible cache file locations $b = $header_dir . '/' . md5($host.'-'.$request_uri) . '.header'; $a = $header_dir . '/' . md5($host.'-'.$request_uri . $request_query).'.header'; // reset file cache clearstatcache(); // if there are no query strings if ($b == $a) { if (!file_exists($a)) { WP_Optimize_Minify_Print::write_header($a, $headers); } return false; } elseif (!file_exists($b)) { WP_Optimize_Minify_Print::write_header($b, $headers); } return false; } /** * Collect all wpo_min JS files and save them to a headers file * * @param String $html * @param String $handle * @param String $src * * @return String */ public function collect_js_preload_headers($html, $handle, $src) { if (false !== strpos($src, 'assets/wpo-minify')) { if (!in_array($src, $this->collect_preload_js)) { $this->collect_preload_js[] = $src; } } return $html . "\n"; } /** * Collect all wpo_min CSS files and save them to a headers file * this function intercepts the styles that are enqueued * take note that if the css is loaded ASYNC, it's not enqueued * * @param string $html - Html string * @param string $handle - Handle * @param string $src - The CSS file path * * @return string */ public function collect_css_preload_headers($html, $handle, $src) { if (false !== strpos($src, 'assets/wpo-minify')) { if (!in_array($src, $this->collect_preload_css)) { $this->collect_preload_css[] = $src; } } return $html . "\n"; } /** * Get current headers file for the url * * @return bool|string */ private function get_preload_headers() { // always false for admin pages if (is_admin()) return false; $enabled_css_preload = $this->options['enabled_css_preload']; $enabled_js_preload = $this->options['enabled_js_preload']; if (!$enabled_css_preload && !$enabled_js_preload) { return false; } // get host with multisite support and query strings $host = htmlentities($_SERVER['SERVER_NAME']); if (empty($host)) { $host = isset($_SERVER['HTTP_HOST']) ? htmlentities($_SERVER['HTTP_HOST']) : ''; } $request_query = parse_url($_SERVER['REQUEST_URI'], PHP_URL_QUERY); $request_uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH); // get cache path $cache_path = WP_Optimize_Minify_Cache_Functions::cache_path(); $header_dir = $cache_path['headerdir']; $cache_file_base = $header_dir . '/'; // possible cache file locations $b = $cache_file_base . md5("$host-$request_uri").'.header'; $a = $cache_file_base . md5("$host-$request_uri$request_query").'.header'; // reset file cache clearstatcache(); // return header files or fallback if ($b == $a && file_exists($a)) { return file_get_contents($a); } if ($b != $a && file_exists($b)) { return file_get_contents($b); } return false; } /** * Add pre-connect and pre-load headers * * @return void */ public function extra_preload_headers() { if (!$this->run_on_page('extra_preload_headers')) return; // fetch headers $pre_connect = $this->get_pre_connect_list(); // preload if (is_array($pre_connect) && count($pre_connect) > 0) { foreach ($pre_connect as $url) { if (!empty($url) && filter_var($url, FILTER_VALIDATE_URL)) { header("Link: <$url>; rel=preconnect", false); } } } $headers = $this->get_preload_headers(); if (false != $headers) { $nh = array_map('trim', explode("\n", $headers)); foreach ($nh as $h) { if (!empty($h)) { header($h, false); } } } } /** * Add caching headers * * @param array $headers An array of headers * * @return array An array of modified headers */ public function add_caching_headers($headers) { $cache = WP_Optimize()->get_page_cache(); if ($cache->is_enabled()) return $headers; if (!isset($headers['Cache-Control'])) { $headers['Cache-Control'] = 'must-revalidate'; } if (!isset($headers['Last-Modified'])) { $headers['Last-Modified'] = gmdate('D, d M Y H:i:s', time()) . ' GMT'; } return $headers; } /** * Includes dependency files */ private function include_dependencies() { if (!class_exists('WP_Optimize_Minify_Functions')) { include WP_OPTIMIZE_MINIFY_DIR.'/class-wp-optimize-minify-functions.php'; } if (!class_exists('WP_Optimize_Minify_Print')) { include WP_OPTIMIZE_MINIFY_DIR.'/class-wp-optimize-minify-print.php'; } if (!class_exists('WP_Optimize_Minify_Fonts')) { include WP_OPTIMIZE_MINIFY_DIR.'/class-wp-optimize-minify-fonts.php'; } } /** * Handles emoji removal */ private function disable_emojis() { WP_Optimize_Minify_Functions::disable_wp_emojicons(); add_filter('tiny_mce_plugins', array('WP_Optimize_Minify_Functions', 'disable_emojis_tinymce' )); } /** * Handles header meta information removal */ private function remove_header_meta_info() { // no resource hints, generator tag, shortlinks, manifest link, etc remove_action('wp_head', 'wp_resource_hints', 2); remove_action('wp_head', 'wp_generator'); remove_action('template_redirect', 'wp_shortlink_header', 11); remove_action('wp_head', 'wlwmanifest_link'); remove_action('wp_head', 'rsd_link'); remove_action('wp_head', 'adjacent_posts_rel_link_wp_head', 10, 0); remove_action('wp_head', 'feed_links', 2); remove_action('wp_head', 'feed_links_extra', 3); WP_Optimize_Minify_Functions::remove_redundant_shortlink(); } /** * Handles javascript processing */ private function process_js() { add_action('wp_print_scripts', array($this, 'process_header_scripts'), PHP_INT_MAX); add_action('wp_print_footer_scripts', array($this, 'process_footer_scripts'), 9); // Defer JS add_filter('script_loader_tag', array($this, 'defer_js'), 10, 3); // Preloading if ($this->options['enabled_js_preload']) { add_filter('script_loader_tag', array($this, 'collect_js_preload_headers'), PHP_INT_MAX, 3); } // add the LoadAsync JavaScript function $async_js = $this->get_async_js_list(); if (count($async_js) > 0 || ( 'all' === $this->options['enable_defer_js'] && 'async_using_js' === $this->options['defer_js_type'] ) ) { add_action('wp_head', array('WP_Optimize_Minify_Print', 'add_load_async'), 0); } // Process script module from generated html ob_start(array($this, "process_script_modules")); } /** * Extract scripts tags from html buffer using regex * * @param string $html - HTML document as string * @return array */ private function get_scripts($html) { preg_match_all('/<script([^>]*)>/Umi', $html, $script_matches, PREG_SET_ORDER); $modified_script_matches = array(); foreach ($script_matches as $script) { $script['url'] = ''; $script['handle'] = ''; $script['version'] = ''; $script['type'] = ''; $attributes = WP_Optimize_Utils::parse_attributes($script[1]); if (isset($attributes['src'])) { $script['url'] = $attributes['src']; $url_query = wp_parse_url($script['url'], PHP_URL_QUERY); $output = array(); wp_parse_str($url_query, $output); if (isset($output['ver'])) $script['version'] = $output['ver']; } // Extract the id/handle from attribute's array if present if (isset($attributes['id'])) $script['handle'] = $attributes['id']; // Extract the type from attribute's array if present if (isset($attributes['type'])) $script['type'] = $attributes['type']; $modified_script_matches[] = $script; } return $modified_script_matches; } /** * Extract `importmap` json object from html buffer using regex * * @param string $html - HTML document as string * @return array */ private function get_script_module_importmap($html) { preg_match('/<script\s+type\s*=\s*[\'"]importmap.*>(?<importmap>.*)<\/script>/Umsi', $html, $match); if (!empty($match) && isset($match['importmap'])) return json_decode($match['importmap'], true); return array(); } /** * Handles css processing */ private function process_css() { add_action('wp_head', array($this, 'add_critical_path'), 2); // merge, if inline is selected but prevent optimization for these locations if ($this->options['inline_css']) { // this prints the styles (not fonts) and checks the 'colllect google fonts' add_filter('style_loader_tag', array($this, 'inline_css'), PHP_INT_MAX, 4); // this prints the google fonts add_action('wp_print_styles', array($this, 'add_google_fonts_merged'), PHP_INT_MAX); add_action('wp_print_footer_scripts', array($this, 'add_google_fonts_merged'), PHP_INT_MAX); } // Preloading if ($this->options['enabled_css_preload']) { add_filter('style_loader_tag', array($this, 'collect_css_preload_headers'), PHP_INT_MAX, 3); } // Optimize the css and collect the google fonts for merging add_action('wp_print_styles', array($this, 'process_header_css'), PHP_INT_MAX); add_action('wp_print_footer_scripts', array($this, 'process_footer_css'), 9); /** * Filters whether or not to ignore the order of the CSS files, and group them by media type * * @param boolean $maintain_css_order * @return boolean * @default true */ if (!apply_filters('wpo_minify_maintain_css_order', true)) { // Reorder stylesheets add_filter('wpo_minify_stylesheets', array($this, 'order_stylesheets_per_media_type'), 10); } } /** * Determines whether we should minify html or not */ private function should_process_html() { return $this->options['html_minification'] && !is_admin() && $this->is_cache_preload(); } /** * Determines whether to use loadCSS polyfill or not */ private function should_use_loadCSS() { return $this->options['loadcss'] || 'async' === $this->options['fawesome_method'] || 'async' === $this->options['gfonts_method']; } /** * Removes query string from static assets */ private function remove_query_string_from_static_assets() { add_filter('style_loader_src', array('WP_Optimize_Minify_Functions', 'remove_cssjs_ver'), 10, 2); add_filter('script_loader_src', array('WP_Optimize_Minify_Functions', 'remove_cssjs_ver'), 10, 2); } /** * Converts the lines in a textarea option value into an array * * @param string $option * @return array */ private function convert_option_to_array($option) { return trim($option) ? array_map('trim', explode("\n", trim($option))) : array(); } /** * Retrieves excluded css URLs as an array * * @return array */ private function get_excluded_css_list() { $excluded_css = $this->options['exclude_css']; return $this->convert_option_to_array($excluded_css); } /** * Retrieves async css URLs as an array * * @return array */ private function get_async_css_list() { $async_css = $this->options['async_css']; return $this->convert_option_to_array($async_css); } /** * Retrieves excluded javascript URLs as an array * * @return array */ private function get_excluded_js_list() { $excluded_js = $this->options['exclude_js']; return $this->convert_option_to_array($excluded_js); } /** * Retrieves async javascript URLs as an array * * @return array */ private function get_async_js_list() { $async_js = $this->options['async_js']; return $this->convert_option_to_array($async_js); } /** * Retrieves pre connect headers as an array * * @return array */ private function get_pre_connect_list() { $pre_connect = $this->options['hpreconnect']; return $this->convert_option_to_array($pre_connect); } /** * Array to string conversion * * @param array $arr * @return string */ private function array_to_string_conversion($arr) { $str = ''; // implode won't work, because element can be an array foreach ($arr as $value) { $str .= is_array($value) ? '' : $value; } return $str; } /** * Decides whether minify cache should be incremented or not * * @param string $res * @param string $href * @param string $type * @param string $handle * @param string $version * * @return bool true when cache is invalid, false otherwise */ private function should_reset_minify_assets($res, $href, $type, $handle, $version) { if (isset($res['request']['version']) && $res['request']['version'] != $version && !$this->minify_cache_incremented) { $enable_minification = $this->options['enable_'.$type.'_minification'] && !WP_Optimize_Minify_Functions::is_minified_css_js_filename($href); $new_json = WP_Optimize_Minify_Functions::download_and_minify($href, null, $enable_minification, $type, $handle, $version); $new_res = json_decode($new_json, true); /** * In case `download_and_minify` fails because of * Network issues, Syntax errors in updated code, * Wrong use of `wpo_minify_get_js` and `wpo_minify_get_css` filters * We don't want to invalidate current assets, so bail out */ if (null === $new_res) return false; $hash = hash('sha256', $res['code']); $new_hash = hash('sha256', $new_res['code']); return strcmp($hash, $new_hash); } } } Save