PluginProbe
WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance / 4.7.0
WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance v4.7.0
4.7.0 4.6.1 4.6.0 4.5.5 4.5.4 4.5.3 4.5.2 3.2.20 3.2.21 3.2.22 3.2.3 3.2.5 3.2.6 3.2.7 3.2.9 3.3.0 3.3.1 3.3.2 3.4.0 3.4.1 3.4.2 3.5.0 3.6.0 3.7.0 3.7.1 All 111 releases
← All changes | minify/class-wp-optimize-minify-front-end.php +797 -224 3.2.3 → 4.7.0 View file →
@@ -30,8 +30,12 @@
30 30 add_action('fl_builder_after_save_layout', array('WP_Optimize_Minify_Cache_Functions', 'reset'));
31 31
32 32 // extra_preload_headers is currently not available to users
33 33 // add_action('send_headers', array($this, 'extra_preload_headers'));
34 +
35 + if (!is_admin()) {
36 + add_action('wp_headers', array($this, 'add_caching_headers'));
37 + }
34 38 }
35 39
36 40 /**
37 41 * Fired on wp init
@@ -60,8 +64,14 @@
60 64 if ($this->options['enable_js']) {
61 65 $this->process_js();
62 66 }
63 67
68 + if ($this->options['host_local_google_fonts']) {
69 + // Add filter to replace Goole fonts urls to local if needed
70 + add_filter('wpo_minify_css_string', array($this, 'replace_gfont_urls_with_local'));
71 + add_filter('wpo_minify_get_css', array($this, 'replace_gfont_urls_with_local'));
72 + }
73 +
64 74 if ($this->options['enable_css']) {
65 75 $this->process_css();
66 76 }
67 77
@@ -70,9 +80,9 @@
70 80 add_action('wp_head', array($this, 'add_assets_preload'), 2);
71 81 }
72 82
73 83 if ($this->should_process_html()) {
74 - add_action('template_redirect', array('WP_Optimize_Minify_Functions', 'html_compression_start'), PHP_INT_MAX);
84 + add_action('template_redirect', array('WP_Optimize_Minify_Functions', 'html_compression_start'));
75 85 }
76 86
77 87 if ($this->should_use_loadCSS()) {
78 88 add_action('wp_footer', array('WP_Optimize_Minify_Print', 'add_load_css'), PHP_INT_MAX);
@@ -81,30 +91,54 @@
81 91 $this->remove_query_string_from_static_assets();
82 92 }
83 93
84 94 /**
95 + * Replace Google fonts urls to local versions
96 + *
97 + * @param string $content
98 + * @return string
99 + */
100 + public function replace_gfont_urls_with_local($content): string {
101 + return WP_Optimize_Host_Google_Fonts::instance()->replace_gfont_urls_with_local($content);
102 + }
103 +
104 + /**
85 105 * Detects whether cache preloading is running or not
86 106 *
87 107 * @return bool
88 108 */
89 109 private function is_cache_preload() {
90 - return isset($_SERVER['HTTP_X_WP_OPTIMIZE_CACHE_PRELOAD']) && 0 === strcmp($_SERVER['HTTP_X_WP_OPTIMIZE_CACHE_PRELOAD'], 'Yes');
110 + return isset($_SERVER['HTTP_X_WP_OPTIMIZE_CACHE_PRELOAD']) && 0 === strcmp(sanitize_text_field(wp_unslash($_SERVER['HTTP_X_WP_OPTIMIZE_CACHE_PRELOAD'])), 'Yes');
91 111 }
92 112
93 113 /**
94 - * Wether to run the feature on a page or not
114 + * Set initial values for the meta log object
95 115 *
116 + * @param string $header - Info on time the log was processed
117 + * @return array
118 + */
119 + private function meta_log_initialization($header) {
120 + return array(
121 + 'header' => $header,
122 + 'invalidation_reason' => "",
123 + 'files_that_changed' => array()
124 + );
125 + }
126 +
127 + /**
128 + * Whether to run the feature on a page or not
129 + *
96 130 * @param string $context - Optional, The context where the check is done
97 131 * @return boolean
98 132 */
99 133 public function run_on_page($context = 'default') {
100 134 /**
101 - * Filters wether the functionality is ran on the current page.
135 + * Filters whether the functionality is run on the current page.
102 136 *
103 137 * @param boolean $run_on_page
104 138 * @param string $context - Optional, The feature where the check is done
105 139 */
106 - return apply_filters(
140 + $filtered_value = apply_filters(
107 141 'wpo_minify_run_on_page',
108 142 !is_admin()
109 143 && (!defined('SCRIPT_DEBUG') || !SCRIPT_DEBUG)
110 144 && !is_preview()
@@ -113,25 +147,26 @@
113 147 && !(function_exists('is_amp_endpoint') && is_amp_endpoint())
114 148 && !WP_Optimize_Minify_Functions::exclude_contents(),
115 149 $context
116 150 );
151 + return is_bool($filtered_value) ? $filtered_value : false;
117 152 }
118 153
119 154 /**
120 155 * Inline css in place, instead of inlining the large file
121 156 *
122 - * @param String $html
123 - * @param String $handle
124 - * @param String $href
125 - * @param String $media
157 + * @param string $html
158 + * @param string $handle
159 + * @param string $href
160 + * @param mixed $media
126 161 *
127 - * @return String
162 + * @return string|boolean
128 163 */
129 164 public function inline_css($html, $handle, $href, $media) {
130 - $exclude_css = array_map('trim', explode("\n", trim($this->options['exclude_css'])));
165 + $exclude_css = $this->get_excluded_css_list();
131 166 $ignore_list = WP_Optimize_Minify_Functions::compile_ignore_list($exclude_css);
132 167 $blacklist = WP_Optimize_Minify_Functions::get_ie_blacklist();
133 - $async_css = array_map('trim', explode("\n", trim($this->options['async_css'])));
168 + $async_css = $this->get_async_css_list();
134 169 $master_ignore = array_merge($ignore_list, $blacklist);
135 170
136 171 // make sure href is complete
137 172 $href = WP_Optimize_Minify_Functions::get_hurl($href);
@@ -136,9 +171,9 @@
136 171 // make sure href is complete
137 172 $href = WP_Optimize_Minify_Functions::get_hurl($href);
138 173
139 174 if ($this->options['debug']) {
140 - echo "<!-- wpo_min DEBUG: Inline CSS processing start $handle / $href -->\n";
175 + echo "<!-- wpo_min DEBUG: Inline CSS processing start ". esc_html($handle) . " / " . esc_html($href) . " -->\n";
141 176 }
142 177
143 178
144 179 // skip all this, if the async css option is enabled
@@ -148,17 +183,16 @@
148 183 // leave conditionals alone
149 184 if (wp_styles()->get_data($handle, 'conditional')) return $html;
150 185
151 186 // mediatype fix for some plugins + remove print mediatypes
152 - if ('screen' == $media
153 - || 'screen, print' == $media
154 - || empty($media)
155 - || is_null($media)
156 - || false == $media
187 + if (!is_string($media)
188 + || '' === $media
189 + || 'screen' === $media
190 + || 'screen, print' === $media
157 191 ) {
158 192 $media = 'all';
159 193 }
160 - if (!empty($this->options['remove_print_mediatypes']) && 'print' == $media) {
194 + if (!empty($this->options['remove_print_mediatypes']) && 'print' === $media) {
161 195 return false;
162 196 }
163 197
164 198 // Exclude specific CSS files from PageSpeedInsights?
@@ -174,12 +208,17 @@
174 208 if (count($master_ignore) > 0 && WP_Optimize_Minify_Functions::in_arrayi($href, $master_ignore)) {
175 209 return $html;
176 210 }
177 211
178 - // check if working with a font awesom link
212 + // return if already minified
213 + if (WP_Optimize_Minify_Functions::is_minified_css_js_filename($href)) {
214 + return $html;
215 + }
216 +
217 + // check if working with a font awesome link
179 218 if (WP_Optimize_Minify_Functions::is_font_awesome($href)) {
180 219 // font awesome processing, async css
181 - if ('async' == $this->options['fawesome_method']) {
220 + if ('async' === $this->options['fawesome_method']) {
182 221 WP_Optimize_Minify_Print::async_style($href, $media);
183 222 return false;
184 223 } elseif ('exclude' === $this->options['fawesome_method']) {
185 224 // font awesome processing, async and exclude from PageSpeedIndex
@@ -184,17 +223,19 @@
184 223 } elseif ('exclude' === $this->options['fawesome_method']) {
185 224 // font awesome processing, async and exclude from PageSpeedIndex
186 225 WP_Optimize_Minify_Print::exclude_style($href);
187 226 return false;
188 - } elseif ('inline' == $this->options['fawesome_method']) {
227 + } elseif ('inline' === $this->options['fawesome_method']) {
189 228 WP_Optimize_Minify_Print::inline_style($handle, $href);
190 229 return false;
191 230 }
192 231 }
193 232
194 - // Check if working with google font url
233 + // Check if working with Google font url
195 234
196 - if ('fonts.googleapis.com' == parse_url($href, PHP_URL_HOST)) {
235 + if ('fonts.googleapis.com' === wp_parse_url($href, PHP_URL_HOST)) {
236 + // if option disabled google fonts processing
237 + if ($this->options['disable_google_fonts_processing']) return $html;
197 238 // check if google fonts should be removed
198 239 if ($this->options['remove_googlefonts']) return false;
199 240 // check if google fonts should be merged
200 241 if ($this->options['merge_google_fonts']) {
@@ -216,9 +257,9 @@
216 257 $json = WP_Optimize_Minify_Cache_Functions::get_transient($tkey);
217 258 if (false === $json) {
218 259 $json = WP_Optimize_Minify_Functions::download_and_minify($href, null, $this->options['enable_css_minification'], 'css', $handle);
219 260 if ($this->options['debug']) {
220 - echo "<!-- wpo_min DEBUG: Uncached file processing now for $handle / $href -->\n";
261 + echo "<!-- wpo_min DEBUG: Uncached file processing now for " . esc_html($handle) . " / " . esc_html($href) . " -->\n";
221 262 }
222 263 WP_Optimize_Minify_Cache_Functions::set_transient($tkey, $json);
223 264 }
224 265
@@ -223,20 +264,22 @@
223 264 }
224 265
225 266 // decode
226 267 $res = json_decode($json, true);
227 -
228 - // add font-display
229 - // https://developers.google.com/web/updates/2016/02/font-display
230 - $res['code'] = str_ireplace('font-style:normal;', 'font-display:block;font-style:normal;', $res['code']);
231 -
268 +
232 269 // inline css or fail
233 - if (false != $res['status']) {
234 - echo '<style class="optimize_css_1" type="text/css" media="all">'.$res['code'].'</style>' . "\n";
270 + if (null !== $res && false !== $res['status']) {
271 + // add font-display
272 + // https://developers.google.com/web/updates/2016/02/font-display
273 + $res['code'] = str_ireplace('font-style:normal;', 'font-display:block;font-style:normal;', $res['code']);
274 +
275 + echo '<style class="optimize_css_1" type="text/css" media="all">';
276 + echo $res['code']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- This is already escaped
277 + echo '</style>' . "\n";
235 278 return false;
236 279 } else {
237 280 if ($this->options['debug']) {
238 - echo "<!-- wpo_min DEBUG: Google fonts request failed for $href -->\n";
281 + echo "<!-- wpo_min DEBUG: Google fonts request failed for " . esc_html($href) . " -->\n";
239 282 }
240 283 return $html;
241 284 }
242 285 } elseif ('async' === $this->options['gfonts_method']) {
@@ -268,9 +311,9 @@
268 311 $json = WP_Optimize_Minify_Cache_Functions::get_transient($tkey);
269 312 if (false === $json) {
270 313 $json = WP_Optimize_Minify_Functions::download_and_minify($href, null, $this->options['enable_css_minification'], 'css', $handle);
271 314 if ($this->options['debug']) {
272 - echo "<!-- wpo_min DEBUG: Uncached file processing now for $handle / $href -->" . "\n";
315 + echo "<!-- wpo_min DEBUG: Uncached file processing now for " . esc_html($handle) . " / " . esc_html($href) . " -->" . "\n";
273 316 }
274 317 WP_Optimize_Minify_Cache_Functions::set_transient($tkey, $json);
275 318 }
276 319
@@ -277,21 +320,25 @@
277 320 // decode
278 321 $res = json_decode($json, true);
279 322
280 323 // inline it + other inlined children styles
281 - if (false != $res['status']) {
282 - echo '<style class="optimize_css_2" type="text/css" media="'.$media.'">'.$res['code'].'</style>' . "\n";
324 + if (null !== $res && $res['status']) {
325 + echo '<style class="optimize_css_2" type="text/css" media="'.esc_attr($media).'">';
326 + echo $res['code']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- This is already escaped
327 + echo '</style>' . "\n";
283 328
284 329 // get inline_styles for this handle, minify and print
285 330 $inline_styles = array();
286 331 $inline_styles = wp_styles()->get_data($handle, 'after');
287 - if (false != $inline_styles) {
332 + if ($inline_styles) {
288 333
289 334 // string type
290 335 if (is_string($inline_styles)) {
291 336 $code = WP_Optimize_Minify_Functions::get_css($href, $inline_styles, $this->options['enable_css_minification']);
292 - if (!empty($code) && false != $code) {
293 - echo '<style class="optimize_css_3" type="text/css" media="'.$media.'">'.$code.'</style>' . "\n";
337 + if (!empty($code)) {
338 + echo '<style class="optimize_css_3" type="text/css" media="'.esc_attr($media).'">';
339 + echo $code; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- This is already escaped
340 + echo '</style>' . "\n";
294 341 }
295 342 }
296 343
297 344 // array type
@@ -297,10 +344,12 @@
297 344 // array type
298 345 if (is_array($inline_styles)) {
299 346 foreach ($inline_styles as $st) {
300 347 $code = WP_Optimize_Minify_Functions::get_css($href, $st, $this->options['enable_css_minification']);
301 - if (!empty($code) && false != $code) {
302 - echo '<style class="optimize_css_4" type="text/css" media="'.$media.'">'.$code.'</style>' . "\n";
348 + if (!empty($code)) {
349 + echo '<style class="optimize_css_4" type="text/css" media="'.esc_attr($media).'">';
350 + echo $code; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- This is already escaped
351 + echo '</style>' . "\n";
303 352 }
304 353 }
305 354 }
306 355 }
@@ -308,9 +357,9 @@
308 357 // prevent default
309 358 return false;
310 359 } else {
311 360 if ($this->options['debug']) {
312 - echo "<!-- wpo_min DEBUG: $handle / $href returned an empty from minification -->" . "\n";
361 + echo "<!-- wpo_min DEBUG: " . esc_html($handle) . " / " . esc_html($href) . " returned an empty from minification -->" . "\n";
313 362 }
314 363 return $html;
315 364 }
316 365 echo "<!-- ERROR: WPO-Minify couldn't catch the CSS file below. Please report this on https://wordpress.org/support/plugin/wp-optimize/ -->\n";
@@ -317,25 +366,26 @@
317 366 return $html;
318 367 }
319 368
320 369 /**
321 - * Enable defer for JavaScript (WP 4.1 and above) and remove query strings for ignored files
370 + * Enable defer for JavaScript and remove query strings for ignored and all non-minified files
322 371 *
323 - * @param String $tag
324 - * @param String $handle
325 - * @param String $src
372 + * @param string $tag
373 + * @param string $handle
374 + * @param string $src
326 375 *
327 - * @return String
376 + * @return string
328 377 */
329 378 public function defer_js($tag, $handle, $src) {
330 379 $wp_domain = trim(str_ireplace(array('http://', 'https://'), '', trim(site_url(), '/')));
331 - $exclude_js = array_map('trim', explode("\n", trim($this->options['exclude_js'])));
380 + $exclude_js = $this->get_excluded_js_list();
332 381 $ignore_list = WP_Optimize_Minify_Functions::compile_ignore_list($exclude_js);
333 382 // Should this defer the Poly fills for IE?
334 383 $blacklist = WP_Optimize_Minify_Functions::get_ie_blacklist();
384 +
335 385 // no query strings
336 - if (false !== stripos($src, '?ver')) {
337 - $srcf = stristr($src, '?ver', true); // phpcs:ignore PHPCompatibility.FunctionUse.NewFunctionParameters.stristr_before_needleFound
386 + if (false !== stripos($src, '?ver') && WP_Optimize_Minify_Functions::is_already_minified($src)) {
387 + $srcf = stristr($src, '?ver', true);
338 388 $tag = str_ireplace($src, $srcf, $tag);
339 389 $src = $srcf;
340 390 }
341 391
@@ -369,9 +419,9 @@
369 419 }
370 420
371 421 // should we exclude defer on the login page?
372 422 if ($this->options['exclude_defer_login']
373 - && false !== stripos($_SERVER["SCRIPT_NAME"], strrchr(wp_login_url(), '/'))
423 + && isset($_SERVER['SCRIPT_NAME']) && false !== stripos(sanitize_text_field(wp_unslash($_SERVER["SCRIPT_NAME"])), strrchr(wp_login_url(), '/'))
374 424 ) {
375 425 return $tag;
376 426 }
377 427
@@ -421,11 +471,15 @@
421 471 * @return void
422 472 */
423 473 public function add_critical_path() {
424 474 if (is_front_page() && !empty($this->options['critical_path_css_is_front_page'])) {
425 - 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";
475 + echo '<style id="critical-path-is-front-page" type="text/css" media="all">' . "\n";
476 + echo $this->options['critical_path_css_is_front_page'] . "\n"; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- This is already escaped
477 + echo '</style>' . "\n";
426 478 } elseif (!empty($this->options['critical_path_css'])) {
427 - echo '<style id="critical-path-global" type="text/css" media="all">' . "\n" . $this->options['critical_path_css'] . "\n" . '</style>' . "\n";
479 + echo '<style id="critical-path-global" type="text/css" media="all">' . "\n";
480 + echo $this->options['critical_path_css'] . "\n"; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- This is already escaped
481 + echo '</style>' . "\n";
428 482 }
429 483 }
430 484
431 485 /**
@@ -453,18 +507,20 @@
453 507 global $wp_styles;
454 508 if (!is_object($wp_styles)) return false;
455 509
456 510 $cache_path = WP_Optimize_Minify_Cache_Functions::cache_path();
511 + $cache_base_dir = $cache_path['cachebasedir'];
457 512 $cache_dir = $cache_path['cachedir'];
458 513 $cache_dir_url = $cache_path['cachedirurl'];
459 - $exclude_css = array_map('trim', explode("\n", trim($this->options['exclude_css'])));
514 + $exclude_css = $this->get_excluded_css_list();
460 515 $ignore_list = WP_Optimize_Minify_Functions::compile_ignore_list($exclude_css);
461 - $async_css = array_map('trim', explode("\n", trim($this->options['async_css'])));
516 + $async_css = $this->get_async_css_list();
462 517
463 518 $minify_css = $this->options['enable_css_minification'];
464 519 $merge_css = $this->options['enable_merging_of_css'];
465 520 $merge_inline_extra_css_js = $this->options['merge_inline_extra_css_js'];
466 521 $process_css = $minify_css || $merge_css;
522 + $disable_google_fonts_processing = $this->options['disable_google_fonts_processing'];
467 523 $styles = clone $wp_styles;
468 524 $styles->all_deps($styles->queue);
469 525 $done = $styles->done;
470 526 $header = array();
@@ -493,9 +549,9 @@
493 549 }
494 550
495 551 // mediatype
496 552 $mt = isset($wp_styles->registered[$handle]->args) ? $wp_styles->registered[$handle]->args : 'all';
497 - if ('screen' == $mt || 'screen, print' == $mt || empty($mt) || is_null($mt) || false == $mt) {
553 + if ('screen' === $mt || 'screen, print' === $mt || empty($mt) || is_null($mt) || false === $mt) {
498 554 $mt = 'all';
499 555 }
500 556 $mediatype = $mt;
501 557
@@ -508,8 +564,9 @@
508 564
509 565
510 566 // mark duplicates as done and remove from the queue
511 567 if (!empty($href)) {
568 + $version = is_array($version) ? '' : $version;
512 569 $key = hash('adler32', $href . $version);
513 570 if (isset($uniq[$key])) {
514 571 $done = array_merge($done, array($handle));
515 572 continue;
@@ -516,11 +573,15 @@
516 573 } else {
517 574 $uniq[$key] = $handle;
518 575 }
519 576 }
577 +
578 + $version = '?ver=' . (!empty($version) ? $version : $wp_styles->default_version);
579 + $href_versioned = $href . $version;
580 +
520 581 // Exclude specific CSS files from PageSpeedIndex?
521 582 if (WP_Optimize_Minify_Functions::in_arrayi($href, $async_css)) {
522 - WP_Optimize_Minify_Print::exclude_style($href);
583 + WP_Optimize_Minify_Print::exclude_style($href_versioned);
523 584 $done = array_merge($done, array($handle));
524 585 continue;
525 586 }
526 587 // Fonts Awesome Processing
@@ -525,17 +586,17 @@
525 586 }
526 587 // Fonts Awesome Processing
527 588 if (WP_Optimize_Minify_Functions::is_font_awesome($href)) {
528 589 if ('inline' === $this->options['fawesome_method']) {
529 - WP_Optimize_Minify_Print::inline_style($handle, $href);
590 + WP_Optimize_Minify_Print::inline_style($handle, $href_versioned);
530 591 $done = array_merge($done, array($handle));
531 592 continue;
532 593 } elseif ('async' === $this->options['fawesome_method']) {
533 - WP_Optimize_Minify_Print::async_style($href, $mediatype);
594 + WP_Optimize_Minify_Print::async_style($href_versioned, $mediatype);
534 595 $done = array_merge($done, array($handle));
535 596 continue;
536 597 } elseif ('exclude' === $this->options['fawesome_method']) {
537 - WP_Optimize_Minify_Print::exclude_style($href);
598 + WP_Optimize_Minify_Print::exclude_style($href_versioned);
538 599 $done = array_merge($done, array($handle));
539 600 continue;
540 601 }
541 602 }
@@ -554,9 +615,9 @@
554 615 'mediatype' => $mediatype
555 616 );
556 617
557 618 // google fonts to the top (collect and skip process array)
558 - if (WP_Optimize_Minify_Functions::is_google_font($href)) {
619 + if (!$disable_google_fonts_processing && WP_Optimize_Minify_Functions::is_google_font($href)) {
559 620 if ($this->options['remove_googlefonts']) {
560 621 $done = array_merge($done, array($handle));
561 622 continue;
562 623 }
@@ -575,8 +636,12 @@
575 636 // merge google fonts if force inlining is enabled?
576 637 $nfonts = array();
577 638 if ($this->options['merge_google_fonts']) {
578 639 $nfonts[] = WP_Optimize_Minify_Fonts::concatenate_google_fonts($google_fonts);
640 + // mark the Google fonts as done so they don't get processed
641 + if ('inherit' !== $this->options['gfonts_method']) {
642 + $done = array_merge($done, array_keys($google_fonts));
643 + }
579 644 } else {
580 645 foreach ($google_fonts as $h => $a) {
581 646 if (!empty($a)) {
582 647 $nfonts[$h] = $a;
@@ -591,13 +656,13 @@
591 656 $tkey = 'css-'.hash('adler32', $href).'.css';
592 657 // this returns false if the cache is empty! but it doesn't check for failed
593 658 $json = WP_Optimize_Minify_Cache_Functions::get_transient($tkey);
594 659 $json = json_decode($json, true);
595 - // check if the cache is empty or if the cache has code
596 - if (false === $json || empty($json['code'])) {
660 + // check if the cache is empty or has no code
661 + if (null === $json || false === $json || empty($json['code'])) {
597 662 $res = WP_Optimize_Minify_Functions::download_and_minify($href, null, $minify_css, 'css', null);
598 663 if ($this->options['debug']) {
599 - echo "<!-- wpo_min DEBUG: Uncached file processing now for $href -->\n";
664 + echo "<!-- wpo_min DEBUG: Uncached file processing now for " . esc_html($href) . " -->\n";
600 665 }
601 666 WP_Optimize_Minify_Cache_Functions::set_transient($tkey, $res);
602 667 // decode
603 668 $json = json_decode($res, true);
@@ -603,18 +668,20 @@
603 668 $json = json_decode($res, true);
604 669 }
605 670
606 671 // inline css or fail
607 - if (!empty($json['code'])) {
672 + if (null !== $json && !empty($json['code'])) {
608 673 // add font-display
609 674 // https://developers.google.com/web/updates/2016/02/font-display
610 675 $json['code'] = str_ireplace('font-style:normal;', 'font-display:block;font-style:normal;', $json['code']);
611 - echo '<style type="text/css" media="all">'.$json['code'].'</style>' . "\n";
676 + echo '<style type="text/css" media="all">';
677 + echo $json['code']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- This is already escaped
678 + echo '</style>' . "\n";
612 679 $done = array_merge($done, array($handle));
613 680 } else {
614 - echo "<!-- GOOGLE FONTS REQUEST FAILED for $href -->" . "\n";
681 + echo "<!-- GOOGLE FONTS REQUEST FAILED for " . esc_html($href) . " -->" . "\n";
615 682 // inlining failed, so enqueue again
616 - wp_enqueue_style($handle, $href, array(), null);
683 + wp_enqueue_style($handle, $href, array(), null); // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion -- Not needed as this is a google font and from cache
617 684 }
618 685 } elseif ('async' === $this->options['gfonts_method']) {
619 686 WP_Optimize_Minify_Print::async_style($href);
620 687 $done = array_merge($done, array($handle));
@@ -656,16 +723,19 @@
656 723 $file_size = WP_Optimize_Minify_Functions::get_file_size($href);
657 724
658 725 // If we can't determine file size, then we still need to proceed with normal minify process
659 726 if (!apply_filters('wp_optimize_skip_inlining', false === $file_size || $file_size > 20480 || !$this->options['inline_css'], $file_size, $href)) continue;
660 -
661 - // skip ignore list, conditional css, external css, font-awesome merge
662 - if (($process_css && !WP_Optimize_Minify_Functions::in_arrayi($href, $ignore_list) && !isset($conditional) && WP_Optimize_Minify_Functions::internal_url($href, site_url()))
727 +
728 + // check if the current URL points to a minified file.
729 + $exclude_minified_css = WP_Optimize_Minify_Functions::is_minified_css_js_filename($href);
730 + $normal_processing = $exclude_minified_css && !$merge_css;
731 + // skip ignore list, conditional css, external css, font-awesome merge and already minified js
732 + 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()))
663 733 || empty($href)
664 - || ($process_css && 'inherit' == $this->options['fawesome_method'] && WP_Optimize_Minify_Functions::is_font_awesome($href))
665 - || ($process_css && 'inherit' == $this->options['gfonts_method'] && WP_Optimize_Minify_Functions::is_google_font($href))
734 + || ($process_css && 'inherit' === $this->options['fawesome_method'] && WP_Optimize_Minify_Functions::is_font_awesome($href))
735 + || ($process_css && !$disable_google_fonts_processing && 'inherit' === $this->options['gfonts_method'] && WP_Optimize_Minify_Functions::is_google_font($href))
666 736 ) {
667 - // colect inline css for this handle
737 + // collect inline css for this handle
668 738 if (isset($wp_styles->registered[$handle]->extra['after']) && is_array($wp_styles->registered[$handle]->extra['after'])) {
669 739 $inline_css[$handle] = WP_Optimize_Minify_Functions::minify_css_string(implode('', $wp_styles->registered[$handle]->extra['after'])); // save
670 740 $wp_styles->registered[$handle]->extra['after'] = null; // dequeue
671 741 }
@@ -670,19 +740,24 @@
670 740 $wp_styles->registered[$handle]->extra['after'] = null; // dequeue
671 741 }
672 742
673 743 // process
674 - if (isset($header[count($header)-1]['handle']) || count($header) == 0 || $header[count($header)-1]['media'] != $mediatype || !$merge_css) {
744 + if (isset($header[count($header)-1]['handle']) || count($header) === 0 || $header[count($header)-1]['media'] !== $mediatype || !$merge_css) {
675 745 array_push($header, array('handles' => array(), 'media' => $mediatype, 'versions' => array()));
676 746 }
677 747
678 748 // push it to the array
679 749 array_push($header[count($header)-1]['handles'], $handle);
680 - array_push($header[count($header)-1]['handles'], $wp_styles->registered[$handle]->ver);
681 750
751 + // We need this because some plugins/theme use empty array as version
752 + // https://plugins.trac.wordpress.org/browser/mailin/tags/3.1.54/sendinblue.php#L456
753 + $version_str = is_array($wp_styles->registered[$handle]->ver) ? $this->array_to_string_conversion($wp_styles->registered[$handle]->ver) : $wp_styles->registered[$handle]->ver;
754 + array_push($header[count($header)-1]['versions'], $version_str);
755 +
756 + $header[count($header) - 1]['last_modified'][] = WP_Optimize_Minify_Functions::get_modification_time($wp_styles->registered[$handle]->src);
757 +
682 758 // external and ignored css
683 759 } else {
684 -
685 760 // normal enqueuing
686 761 array_push($header, array('handle' => $handle));
687 762 }
688 763 }
@@ -697,21 +772,29 @@
697 772 $header = apply_filters('wpo_minify_stylesheets', $header, 'header');
698 773
699 774 // loop through header css and merge
700 775 for ($i=0,$l=count($header); $i<$l; $i++) {
776 +
701 777 if (!isset($header[$i]['handle'])) {
778 +
779 + $handle_str = is_array($header[$i]['handles']) ? $this->array_to_string_conversion($header[$i]['handles']) : strval($header[$i]['handles']);
780 + $last_modified_str = empty($header[$i]['last_modified']) ? '' : (is_array($header[$i]['last_modified']) ? $this->array_to_string_conversion($header[$i]['last_modified']) : strval($header[$i]['last_modified']));
781 +
702 782 if ($merge_css) {
703 783 // get hash for the inline css in this group
704 784 $inline_css_group = array();
705 - foreach ($header[$i]['handles'] as $h) {
706 - if (isset($inline_css[$h]) && !empty($inline_css[$h])) {
707 - $inline_css_group[] = $inline_css[$h];
785 +
786 + if (is_array($header[$i]['handles'])) {
787 + foreach ($header[$i]['handles'] as $h) {
788 + if (!empty($inline_css) && isset($inline_css[$h]) && !empty($inline_css[$h])) {
789 + $inline_css_group[] = $inline_css[$h];
790 + }
708 791 }
709 792 }
710 793 $inline_css_hash = md5(implode('', $inline_css_group));
711 - $hash = hash('adler32', implode('', $header[$i]['handles']).$inline_css_hash . implode('', $header[$i]['versions']));
794 + $hash = hash('adler32', $handle_str . $inline_css_hash . $last_modified_str);
712 795 } else {
713 - $hash = implode('', $header[$i]['handles']) . implode('', $header[$i]['versions']);
796 + $hash = sanitize_file_name($handle_str . $last_modified_str);
714 797 }
715 798
716 799 // static cache file info
717 800 $file_name = 'wpo-minify-header-'.$hash.($minify_css ? '.min' : '');
@@ -717,9 +800,9 @@
717 800 $file_name = 'wpo-minify-header-'.$hash.($minify_css ? '.min' : '');
718 801
719 802 // create cache files and urls
720 803 $file = $cache_dir.'/'.$file_name.'.css';
721 -
804 + $meta_json_file = $cache_base_dir.'/'.'meta.json';
722 805 $file_url = WP_Optimize_Minify_Functions::get_protocol("$cache_dir_url/$file_name.css");
723 806
724 807 // generate a new cache file
725 808 clearstatcache();
@@ -725,12 +808,14 @@
725 808 clearstatcache();
726 809 if (!file_exists($file)) {
727 810
728 811 // code and log initialization
812 + $log_header = $this->get_log_header();
729 813 $log = array(
730 - 'header' => "PROCESSED on ".date('r')." from ".home_url(add_query_arg(null, null)),
814 + 'header' => $log_header,
731 815 'files' => array()
732 816 );
817 + $meta_log = $this->meta_log_initialization($log_header);
733 818 $code = '';
734 819
735 820 // minify and write to file
736 821 foreach ($header[$i]['handles'] as $handle) {
@@ -736,10 +821,12 @@
736 821 foreach ($header[$i]['handles'] as $handle) {
737 822 if (!empty($wp_styles->registered[$handle]->src)) {
738 823
739 824 // get href per handle
740 - $href = WP_Optimize_Minify_Functions::get_hurl($wp_styles->registered[$handle]->src);
825 + $handle_src = $wp_styles->registered[$handle]->src;
826 + $href = WP_Optimize_Minify_Functions::get_hurl($handle_src);
741 827 $version = $wp_styles->registered[$handle]->ver;
828 + $last_modified = WP_Optimize_Minify_Functions::get_modification_time($handle_src);
742 829
743 830 // inlined scripts without file
744 831 if (empty($href)) continue;
745 832
@@ -747,11 +834,12 @@
747 834 $tkey = 'css-'.hash('adler32', $handle . $href).'.css';
748 835 $json = false;
749 836 $json = WP_Optimize_Minify_Cache_Functions::get_transient($tkey);
750 837 if (false === $json) {
751 - $json = WP_Optimize_Minify_Functions::download_and_minify($href, null, $this->options['enable_css_minification'], 'css', $handle, $version);
838 + $enable_minification = $this->options['enable_css_minification'] && !WP_Optimize_Minify_Functions::is_minified_css_js_filename($href);
839 + $json = WP_Optimize_Minify_Functions::download_and_minify($href, null, $enable_minification, 'css', $handle, $version);
752 840 if ($this->options['debug']) {
753 - echo "<!-- wpo_min DEBUG: Uncached file processing now for $handle / $href / $version -->" . "\n";
841 + echo "<!-- wpo_min DEBUG: Uncached file processing now for " . esc_html($handle) . " / " . esc_html($href) . " / " . esc_html($version) . " -->" . "\n";
754 842 }
755 843 WP_Optimize_Minify_Cache_Functions::set_transient($tkey, $json);
756 844 }
757 845
@@ -757,16 +845,22 @@
757 845
758 846 // decode
759 847 $res = json_decode($json, true);
760 848
761 - if (isset($res['request']['version']) && $res['request']['version'] != $version && !$this->minify_cache_incremented) {
849 + if (null === $res) continue;
850 +
851 + if ($this->should_reset_minify_assets($res, $href, 'css', $handle, $version)) {
762 852 WP_Optimize_Minify_Cache_Functions::reset();
763 853 $this->minify_cache_incremented = true;
854 + $meta_log['invalidation_reason'] = 'Checksum Mismatch and version changed from ' . $res['request']['version'] . ' to ' . $version;
855 + $meta_log['files_that_changed'][$handle] = $res['log'];
764 856 }
765 857
766 858 // response has failed
767 - if (true != $res['status']) {
859 + if (true !== $res['status']) {
768 860 $log['files'][$handle] = $res['log'];
861 + $log['files'][$handle]['version'] = $version;
862 + $log['files'][$handle]['last_modified'] = $last_modified;
769 863 continue;
770 864 }
771 865
772 866 // Only add the $handle to $done if it was successfully downloaded
@@ -771,10 +865,12 @@
771 865
772 866 // Only add the $handle to $done if it was successfully downloaded
773 867 $done[] = $handle;
774 868 // append code to merged file
775 - $code .= isset($res['code']) ? $res['code'] : '';
869 + $code .= $res['code'] ?? '';
776 870 $log['files'][$handle] = $res['log'];
871 + $log['files'][$handle]['version'] = $version;
872 + $log['files'][$handle]['last_modified'] = $last_modified;
777 873
778 874 // append inlined styles
779 875 if ($merge_inline_extra_css_js && isset($inline_css[$handle]) && !empty($inline_css[$handle])) {
780 876 $code.= $inline_css[$handle];
@@ -788,8 +884,12 @@
788 884 };
789 885
790 886 // generate cache, write log
791 887 if (!empty($code)) {
888 + // Only generate meta.json file if cache is reset
889 + if (!empty($meta_log['invalidation_reason'])) {
890 + file_put_contents($meta_json_file, wp_json_encode($meta_log));
891 + }
792 892 WP_Optimize_Minify_Print::write_combined_asset($file, $code, $log);
793 893 }
794 894 } else {
795 895 $log_file = $file.'.json';
@@ -797,8 +897,9 @@
797 897 $saved_log = json_decode(file_get_contents($log_file));
798 898 if (is_object($saved_log) && property_exists($saved_log, 'files')) {
799 899 $files = (array) $saved_log->files;
800 900 foreach ($header[$i]['handles'] as $handle) {
901 + $handle = is_array($handle) ? '' : $handle;
801 902 if (isset($files[$handle]) && $files[$handle]->success) {
802 903 $done[] = $handle;
803 904 }
804 905 }
@@ -819,24 +920,18 @@
819 920 WP_Optimize_Minify_Print::async_style($file_url, $mt);
820 921 // enqueue file, if not empty
821 922 } else {
822 923 if (file_exists($file) && filesize($file) > 0) {
823 -
824 - // inline CSS if mediatype is not of type "all" (such as mobile only), if the file is smaller than 20KB
825 - if (filesize($file) < 20000 && 'all' != $header[$i]['media']) {
826 - echo '<style id="wpo-min-header-'.$i.'" media="'.$header[$i]['media'].'">'.file_get_contents($file).'</style>' . "\n";
827 - } else {
828 - // enqueue it
829 - wp_enqueue_style("wpo_min-header-$i", $file_url, array(), 'mycoolversion', $header[$i]['media']);
830 - foreach ($header[$i]['handles'] as $h) {
831 - if (!$merge_inline_extra_css_js && isset($inline_css[$h]) && !empty($inline_css[$h])) {
832 - wp_add_inline_style("wpo_min-header-$i", $inline_css[$h]);
833 - }
924 + // enqueue it
925 + wp_enqueue_style("wpo_min-header-$i", $file_url, array(), 'mycoolversion', $header[$i]['media']);
926 + foreach ($header[$i]['handles'] as $h) {
927 + if (!$merge_inline_extra_css_js && isset($inline_css[$h]) && !empty($inline_css[$h])) {
928 + wp_add_inline_style("wpo_min-header-$i", $inline_css[$h]);
834 929 }
835 930 }
836 931 } else {
837 932 // file could not be generated, output something meaningful
838 - echo "<!-- ERROR: WP-Optimize Minify was not allowed to save its cache on - ".str_replace(ABSPATH, '', $file)." -->";
933 + echo "<!-- ERROR: WP-Optimize Minify was not allowed to save its cache on - ".esc_html(str_replace(ABSPATH, '', $file))." -->";
839 934 echo "<!-- Please check if the path above is correct and ensure your server has write permission there! -->";
840 935 }
841 936 }
842 937 // other css need to be requeued for the order of files to be kept
@@ -861,15 +956,16 @@
861 956 if (!is_object($wp_scripts)) {
862 957 return;
863 958 }
864 959 $cache_path = WP_Optimize_Minify_Cache_Functions::cache_path();
960 + $cache_base_dir = $cache_path['cachebasedir'];
865 961 $cache_dir = $cache_path['cachedir'];
866 962 $cache_dir_url = $cache_path['cachedirurl'];
867 963
868 964
869 - $exclude_js = array_map('trim', explode("\n", trim($this->options['exclude_js'])));
965 + $exclude_js = $this->get_excluded_js_list();
870 966 $ignore_list = WP_Optimize_Minify_Functions::compile_ignore_list($exclude_js);
871 - $async_js = trim($this->options['async_js']) ? array_map('trim', explode("\n", trim($this->options['async_js']))) : array();
967 + $async_js = $this->get_async_js_list();
872 968 $scripts = clone $wp_scripts;
873 969 $scripts->all_deps($scripts->queue);
874 970 $footer = array();
875 971 $minify_js = $this->options['enable_js_minification'];
@@ -879,8 +975,10 @@
879 975
880 976 // mark as done (as we go)
881 977 $done = $scripts->done;
882 978
979 + $previous_load_strategy = null;
980 +
883 981 // get groups of handles
884 982 foreach ($scripts->to_do as $handle) :
885 983
886 984 // get full url
@@ -891,9 +989,9 @@
891 989 continue;
892 990 }
893 991
894 992 // Exclude JS files from PageSpeedIndex (Async) takes priority over the ignore list
895 - if (false != $async_js || is_array($async_js)) {
993 + if (!empty($async_js)) {
896 994
897 995 // check for string match
898 996 $skipjs = false;
899 997 foreach ($async_js as $l) {
@@ -898,15 +996,16 @@
898 996 $skipjs = false;
899 997 foreach ($async_js as $l) {
900 998 if (stripos($href, $l) !== false) {
901 999 // print code if there are no linebreaks, or return
902 - WP_Optimize_Minify_Print::async_script($href);
1000 + $href_versioned = $href . (!empty($wp_scripts->registered[$handle]->ver) ? '?ver=' . $wp_scripts->registered[$handle]->ver : '');
1001 + WP_Optimize_Minify_Print::async_script($href_versioned);
903 1002 $skipjs = true;
904 1003 $done = array_merge($done, array($handle));
905 1004 break;
906 1005 }
907 1006 }
908 - if (false != $skipjs) {
1007 + if (false !== $skipjs) {
909 1008 continue;
910 1009 }
911 1010 }
912 1011
@@ -915,15 +1014,24 @@
915 1014 if ($ieonly) {
916 1015 continue;
917 1016 }
918 1017
1018 + // check if the current URL points to a minified file.
1019 + $exclude_minified_js = WP_Optimize_Minify_Functions::is_minified_css_js_filename($href);
1020 + $normal_processing = $exclude_minified_js && !$merge_js;
1021 +
919 1022 // skip ignore list, scripts with conditionals, external scripts
920 - if (($process_js && !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()))
1023 + 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()))
921 1024 || empty($href)
922 1025 ) {
923 1026
924 1027 // process
925 - if (isset($footer[count($footer)-1]['handle']) || !count($footer) || !$merge_js) {
1028 + if (isset($footer[count($footer)-1]['handle'])
1029 + || !count($footer)
1030 + || !$merge_js
1031 + || (isset($scripts->registered[$handle]->extra['strategy']) && $previous_load_strategy !== $scripts->registered[$handle]->extra['strategy'])
1032 + || (!isset($scripts->registered[$handle]->extra['strategy']) && null !== $previous_load_strategy)
1033 + ) {
926 1034 array_push($footer, array('handles' => array(), 'versions' => array()));
927 1035 }
928 1036
929 1037 if (isset($wp_scripts->registered[$handle]->extra['before'])) {
@@ -934,9 +1042,17 @@
934 1042
935 1043 // push it to the array
936 1044 array_push($footer[count($footer)-1]['handles'], $handle);
937 1045 array_push($footer[count($footer)-1]['versions'], $wp_scripts->registered[$handle]->ver);
1046 +
1047 + // Register strategy if it is used
1048 + if (isset($scripts->registered[$handle]->extra['strategy'])) $footer[count($footer)-1]['strategy'] = $scripts->registered[$handle]->extra['strategy'];
1049 +
1050 + $footer[count($footer) - 1]['last_modified'][] = WP_Optimize_Minify_Functions::get_modification_time($wp_scripts->registered[$handle]->src);
938 1051 // external and ignored scripts
1052 +
1053 + $previous_load_strategy = $scripts->registered[$handle]->extra['strategy'] ?? null;
1054 +
939 1055 } else {
940 1056 array_push($footer, array('handle' => $handle));
941 1057 }
942 1058 endforeach;
@@ -942,15 +1058,19 @@
942 1058 endforeach;
943 1059
944 1060 // loop through footer scripts and merge
945 1061 for ($i=0,$l=count($footer); $i<$l; $i++) {
1062 +
946 1063 if (!isset($footer[$i]['handle'])) {
947 1064
1065 + $handles_str = is_array($footer[$i]['handles']) ? $this->array_to_string_conversion($footer[$i]['handles']) : strval($footer[$i]['handles']);
1066 + $last_modified_str = empty($footer[$i]['last_modified']) ? '' : (is_array($footer[$i]['last_modified']) ? $this->array_to_string_conversion($footer[$i]['last_modified']) : strval($footer[$i]['last_modified']));
1067 +
948 1068 if ($merge_js) {
949 - // Change the hash based on version numbers
950 - $hash = hash('adler32', implode('', $footer[$i]['handles']) . implode('', $footer[$i]['versions']));
1069 + // Change the hash based on last modified timestamp
1070 + $hash = hash('adler32', $handles_str . $last_modified_str);
951 1071 } else {
952 - $hash = implode('', $footer[$i]['handles']) . implode('', $footer[$i]['versions']);
1072 + $hash = sanitize_file_name($handles_str . $last_modified_str);
953 1073 }
954 1074
955 1075 // static cache file info
956 1076 $file_name = 'wpo-minify-footer-'.$hash.($minify_js ? '.min' : '');
@@ -956,8 +1076,9 @@
956 1076 $file_name = 'wpo-minify-footer-'.$hash.($minify_js ? '.min' : '');
957 1077
958 1078 // create cache files and urls
959 1079 $file = $cache_dir.'/'.$file_name.'.js';
1080 + $meta_json_file = $cache_base_dir.'/'.'meta.json';
960 1081 $file_url = WP_Optimize_Minify_Functions::get_protocol($cache_dir_url.'/'.$file_name.'.js');
961 1082
962 1083 // generate a new cache file
963 1084 clearstatcache();
@@ -963,20 +1084,29 @@
963 1084 clearstatcache();
964 1085 if (!file_exists($file)) {
965 1086
966 1087 // code and log initialization
1088 + $log_header = $this->get_log_header();
967 1089 $log = array(
968 - 'header' => "PROCESSED on ".date('r')." from ".home_url(add_query_arg(null, null)),
1090 + 'header' => $log_header,
969 1091 'files' => array()
970 1092 );
1093 + $meta_log = $this->meta_log_initialization($log_header);
1094 + $before_code = '';
971 1095 $code = '';
1096 + $after_code = '';
972 1097
973 1098 // minify and write to file
974 1099 foreach ($footer[$i]['handles'] as $handle) :
975 1100 if (!empty($wp_scripts->registered[$handle]->src)) {
1101 + // only add minified scripts once to the bundle
1102 + if (in_array($handle, $done)) continue;
1103 +
976 1104 // get href per handle
977 - $href = WP_Optimize_Minify_Functions::get_hurl($wp_scripts->registered[$handle]->src);
1105 + $handle_src = $wp_scripts->registered[$handle]->src;
1106 + $href = WP_Optimize_Minify_Functions::get_hurl($handle_src);
978 1107 $version = $wp_scripts->registered[$handle]->ver;
1108 + $last_modified = WP_Optimize_Minify_Functions::get_modification_time($handle_src);
979 1109 // inlined scripts without file
980 1110 if (empty($href)) {
981 1111 continue;
982 1112 }
@@ -984,11 +1114,12 @@
984 1114 $tkey = 'js-'.hash('adler32', $handle . $href).'.js';
985 1115 $json = false;
986 1116 $json = WP_Optimize_Minify_Cache_Functions::get_transient($tkey);
987 1117 if (false === $json) {
988 - $json = WP_Optimize_Minify_Functions::download_and_minify($href, null, $minify_js, 'js', $handle, $version);
1118 + $enable_minification = $minify_js && !WP_Optimize_Minify_Functions::is_minified_css_js_filename($href);
1119 + $json = WP_Optimize_Minify_Functions::download_and_minify($href, null, $enable_minification, 'js', $handle, $version);
989 1120 if ($this->options['debug']) {
990 - echo "<!-- wpo_min DEBUG: Uncached file processing now for $handle / $href / $version -->\n";
1121 + echo "<!-- wpo_min DEBUG: Uncached file processing now for " . esc_html($handle) . " / " . esc_html($href) . " / " . esc_html($version) . " -->\n";
991 1122 }
992 1123 WP_Optimize_Minify_Cache_Functions::set_transient($tkey, $json);
993 1124 }
994 1125
@@ -993,17 +1124,24 @@
993 1124 }
994 1125
995 1126 // decode
996 1127 $res = json_decode($json, true);
997 -
998 - if (isset($res['request']['version']) && $res['request']['version'] != $version && !$this->minify_cache_incremented) {
1128 +
1129 + if (null === $res) continue;
1130 +
1131 + if ($this->should_reset_minify_assets($res, $href, 'js', $handle, $version)) {
999 1132 WP_Optimize_Minify_Cache_Functions::reset();
1000 1133 $this->minify_cache_incremented = true;
1134 + $meta_log['invalidation_reason'] = 'Checksum Mismatch and version changed from ' . $res['request']['version'] . ' to ' . $version;
1135 + $meta_log['files_that_changed'][$handle] = $res['log'];
1001 1136 }
1002 1137
1003 1138 // response has failed
1004 - if (true != $res['status']) {
1139 + if (true !== $res['status']) {
1005 1140 $log['files'][$handle] = $res['log'];
1141 + $log['files'][$handle]['uses_loading_strategy'] = isset($footer[$i]['strategy']);
1142 + $log['files'][$handle]['version'] = $version;
1143 + $log['files'][$handle]['last_modified'] = $last_modified;
1006 1144 continue;
1007 1145 }
1008 1146
1009 1147 $done[] = $handle;
@@ -1010,9 +1148,9 @@
1010 1148 // Add extra data from wp_add_inline_script before
1011 1149 if (!empty($wp_scripts->registered[$handle]->extra)) {
1012 1150 if (!empty($wp_scripts->registered[$handle]->extra['before']) && is_array($wp_scripts->registered[$handle]->extra['before'])) {
1013 1151 if ($merge_inline_extra_css_js) {
1014 - $code.= "\n" . WP_Optimize_Minify_Functions::prepare_merged_js(implode("\n", array_filter($wp_scripts->registered[$handle]->extra['before'])), $href . ' - BEFORE');
1152 + $before_code .= "\n" . implode("\n", array_filter($wp_scripts->registered[$handle]->extra['before']));
1015 1153 }
1016 1154 }
1017 1155 }
1018 1156
@@ -1019,22 +1157,28 @@
1019 1157 // Add translation
1020 1158 if (!empty($wp_scripts->registered[$handle]->textdomain)) {
1021 1159 $code .= "\n" . $wp_scripts->print_translations($handle, false);
1022 1160 }
1023 -
1024 - // append code to merged file
1025 - $code .= isset($res['code']) ? WP_Optimize_Minify_Functions::prepare_merged_js($res['code'], $href) : '';
1026 - $log['files'][$handle] = $res['log'];
1027 1161
1028 1162 // Add extra data from wp_add_inline_script after
1029 1163 if (!empty($wp_scripts->registered[$handle]->extra)) {
1030 1164 if (!empty($wp_scripts->registered[$handle]->extra['after']) && is_array($wp_scripts->registered[$handle]->extra['after'])) {
1031 1165 if ($merge_inline_extra_css_js) {
1032 - $code.= "\n" . WP_Optimize_Minify_Functions::prepare_merged_js(implode("\n", array_filter($wp_scripts->registered[$handle]->extra['after'])), $href. ' - AFTER');
1166 + $after_code .= "\n" . implode("\n", array_filter($wp_scripts->registered[$handle]->extra['after']));
1033 1167 }
1034 1168 }
1035 1169 }
1036 -
1170 +
1171 + // append code to merged file
1172 + $code .= $before_code . "\n";
1173 + $code .= $res['code'] ?? '';
1174 + $code .= "\n" . $after_code . "\n";
1175 + $code = WP_Optimize_Minify_Functions::prepare_merged_js($code, $href);
1176 + $log['files'][$handle] = $res['log'];
1177 + $log['files'][$handle]['uses_loading_strategy'] = isset($footer[$i]['strategy']);
1178 + $log['files'][$handle]['version'] = $version;
1179 + $log['files'][$handle]['last_modified'] = $last_modified;
1180 +
1037 1181 // consider dependencies on handles with an empty src
1038 1182 } else {
1039 1183 wp_dequeue_script($handle);
1040 1184 wp_enqueue_script($handle);
@@ -1042,8 +1186,12 @@
1042 1186 endforeach;
1043 1187
1044 1188 // generate cache, write log
1045 1189 if (!empty($code)) {
1190 + // Only generate meta.json file if cache is reset
1191 + if (!empty($meta_log['invalidation_reason'])) {
1192 + file_put_contents($meta_json_file, wp_json_encode($meta_log));
1193 + }
1046 1194 WP_Optimize_Minify_Print::write_combined_asset($file, $code, $log);
1047 1195 }
1048 1196 } else {
1049 1197 $log_file = $file.'.json';
@@ -1066,9 +1214,9 @@
1066 1214 }
1067 1215 }
1068 1216
1069 1217 // register minified file
1070 - wp_register_script("wpo_min-footer-$i", $file_url, array(), null, false);
1218 + wp_register_script("wpo_min-footer-$i", $file_url, array(), null, false); // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion -- Not needed as this is a timestamp based cache
1071 1219
1072 1220 // add all extra data from wp_localize_script
1073 1221 $before_code = '';
1074 1222 $data = array();
@@ -1081,14 +1229,14 @@
1081 1229 // Add extra data from wp_add_inline_script before
1082 1230 if (!empty($wp_scripts->registered[$handle]->extra)) {
1083 1231 if (!empty($wp_scripts->registered[$handle]->extra['before']) && is_array($wp_scripts->registered[$handle]->extra['before'])) {
1084 1232 if (!$merge_inline_extra_css_js) {
1085 - $before_code.= "\n" . WP_Optimize_Minify_Functions::prepare_merged_js(implode("\n", array_filter($wp_scripts->registered[$handle]->extra['before'])), $href.' - BEFORE');
1233 + $before_code.= "\n" . implode("\n", array_filter($wp_scripts->registered[$handle]->extra['before'])) . "\n";
1086 1234 }
1087 1235 }
1088 1236 if (!empty($wp_scripts->registered[$handle]->extra['after']) && is_array($wp_scripts->registered[$handle]->extra['after'])) {
1089 1237 if (!$merge_inline_extra_css_js) {
1090 - $after_code.= "\n" . WP_Optimize_Minify_Functions::prepare_merged_js(implode("\n", array_filter($wp_scripts->registered[$handle]->extra['after'])), $href.' - AFTER');
1238 + $after_code.= "\n" . implode("\n", array_filter($wp_scripts->registered[$handle]->extra['after'])) . "\n";
1091 1239 }
1092 1240 }
1093 1241 }
1094 1242 }
@@ -1104,11 +1252,16 @@
1104 1252 wp_enqueue_script("wpo_min-footer-$i");
1105 1253 if (!empty($after_code)) {
1106 1254 wp_add_inline_script("wpo_min-footer-$i", $after_code, 'after');
1107 1255 }
1256 +
1257 + if (isset($footer[$i]['strategy'])) {
1258 + // Backward compatible way of doing this
1259 + wp_script_add_data( "wpo_min-footer-$i", 'strategy', $footer[$i]['strategy']);
1260 + }
1108 1261 } else {
1109 1262 // file could not be generated, output something meaningful
1110 - echo "<!-- ERROR: WP-Optimize Minify was not allowed to save its cache on - ".str_replace(ABSPATH, '', $file)." -->";
1263 + echo "<!-- ERROR: WP-Optimize Minify was not allowed to save its cache on - ".esc_html(str_replace(ABSPATH, '', $file))." -->";
1111 1264 echo "<!-- Please check if the path above is correct and ensure your server has write permission there! -->";
1112 1265 }
1113 1266
1114 1267 // other scripts need to be requeued for the order of files to be kept
@@ -1123,9 +1276,9 @@
1123 1276 }
1124 1277
1125 1278 /**
1126 1279 * Process header JavaScript
1127 - * Dependant on 'enable_js' option
1280 + * Dependent on 'enable_js' option
1128 1281 *
1129 1282 * @return void
1130 1283 */
1131 1284 public function process_header_scripts() {
@@ -1131,13 +1284,14 @@
1131 1284 public function process_header_scripts() {
1132 1285 global $wp_scripts;
1133 1286 if (!is_object($wp_scripts)) return;
1134 1287 $cache_path = WP_Optimize_Minify_Cache_Functions::cache_path();
1288 + $cache_base_dir = $cache_path['cachebasedir'];
1135 1289 $cache_dir = $cache_path['cachedir'];
1136 1290 $cache_dir_url = $cache_path['cachedirurl'];
1137 - $exclude_js = array_map('trim', explode("\n", trim($this->options['exclude_js'])));
1291 + $exclude_js = $this->get_excluded_js_list();
1138 1292 $ignore_list = WP_Optimize_Minify_Functions::compile_ignore_list($exclude_js);
1139 - $async_js = trim($this->options['async_js']) ? array_map('trim', explode("\n", trim($this->options['async_js']))) : array();
1293 + $async_js = $this->get_async_js_list();
1140 1294 $scripts = clone $wp_scripts;
1141 1295 $scripts->all_deps($scripts->queue);
1142 1296 $minify_js = $this->options['enable_js_minification'];
1143 1297 $merge_js = $this->options['enable_merging_of_js'];
@@ -1147,8 +1301,10 @@
1147 1301 // mark as done (as we go)
1148 1302 $done = $scripts->done;
1149 1303 $excluded_dependencies = array();
1150 1304
1305 + $previous_load_strategy = null;
1306 +
1151 1307 // Prepare and separate assets (get groups of handles)
1152 1308 foreach ($scripts->to_do as $handle) {
1153 1309 // get full url
1154 1310 $href = WP_Optimize_Minify_Functions::get_hurl($wp_scripts->registered[$handle]->src);
@@ -1153,9 +1309,9 @@
1153 1309 // get full url
1154 1310 $href = WP_Optimize_Minify_Functions::get_hurl($wp_scripts->registered[$handle]->src);
1155 1311 // inlined scripts without file
1156 1312 if (empty($href)) {
1157 - wp_enqueue_script($handle, false);
1313 + wp_enqueue_script($handle, false); // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion -- Not needed
1158 1314 continue;
1159 1315 }
1160 1316
1161 1317 // Only go through items without a group
@@ -1164,9 +1320,10 @@
1164 1320 // check for string match
1165 1321 $skipjs = false;
1166 1322 foreach ($async_js as $l) {
1167 1323 if (stripos($href, $l) !== false) {
1168 - WP_Optimize_Minify_Print::async_script($href);
1324 + $href_versioned = $href . (!empty($wp_scripts->registered[$handle]->ver) ? '?ver=' . $wp_scripts->registered[$handle]->ver : '');
1325 + WP_Optimize_Minify_Print::async_script($href_versioned);
1169 1326 $skipjs = true;
1170 1327 $done = array_merge($done, array($handle));
1171 1328 break;
1172 1329 }
@@ -1177,9 +1334,9 @@
1177 1334 ) {
1178 1335 continue;
1179 1336 }
1180 1337
1181 - // Skip jQuery from the merged files if defering is ENABLED and defer_jquery is DISABLED
1338 + // Skip jQuery from the merged files if deferring is ENABLED and defer_jquery is DISABLED
1182 1339 if ('all' === $this->options['enable_defer_js']
1183 1340 && !$this->options['defer_jquery']
1184 1341 && (false !== stripos($href, '/jquery.js')
1185 1342 || false !== stripos($href, '/jquery.min.js')
@@ -1187,14 +1344,23 @@
1187 1344 ) {
1188 1345 continue;
1189 1346 }
1190 1347
1348 + // check if the current URL points to a minified file.
1349 + $exclude_minified_js = WP_Optimize_Minify_Functions::is_minified_css_js_filename($href);
1350 + $normal_processing = $exclude_minified_js && !$merge_js;
1351 +
1191 1352 // Group handles - skip ignore list, scripts with conditionals, external scripts
1192 - if (($process_js && !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()))
1353 + 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()))
1193 1354 || empty($href)
1194 1355 ) {
1195 1356 // process
1196 - if (isset($header[count($header)-1]['handle']) || !count($header) || !$merge_js) {
1357 + if (isset($header[count($header)-1]['handle'])
1358 + || !count($header)
1359 + || !$merge_js
1360 + || (isset($scripts->registered[$handle]->extra['strategy']) && $previous_load_strategy !== $scripts->registered[$handle]->extra['strategy'])
1361 + || (!isset($scripts->registered[$handle]->extra['strategy']) && null !== $previous_load_strategy)
1362 + ) {
1197 1363 array_push($header, array('handles' => array(), 'versions' => array()));
1198 1364 }
1199 1365
1200 1366 // Force loading of dependencies
@@ -1227,9 +1393,16 @@
1227 1393
1228 1394 // push it to the array
1229 1395 array_push($header[count($header)-1]['handles'], $handle);
1230 1396 array_push($header[count($header)-1]['versions'], $scripts->registered[$handle]->ver);
1397 + $header[count($header) - 1]['last_modified'][] = WP_Optimize_Minify_Functions::get_modification_time($wp_scripts->registered[$handle]->src);
1231 1398
1399 + // Register strategy if it is used
1400 + if (isset($scripts->registered[$handle]->extra['strategy'])) $header[count($header)-1]['strategy'] = $scripts->registered[$handle]->extra['strategy'];
1401 +
1402 + $previous_load_strategy = $scripts->registered[$handle]->extra['strategy'] ?? null;
1403 +
1404 +
1232 1405 // external and ignored scripts
1233 1406 } else {
1234 1407 // add the ignored assets
1235 1408 array_push($header, array('handle' => $handle));
@@ -1236,20 +1409,24 @@
1236 1409 }
1237 1410
1238 1411 // make sure that the scripts skipped here, show up in the footer
1239 1412 } else {
1240 - wp_enqueue_script($handle, $href, array(), null, true);
1413 + wp_enqueue_script($handle, $href, array(), null, true); // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion -- Not needed as this is a timestamp based cache
1241 1414 }
1242 1415 }
1243 1416
1244 1417 // loop through header scripts and merge
1245 1418 for ($i=0,$l=count($header); $i < $l; $i++) {
1419 +
1246 1420 if (!isset($header[$i]['handle'])) {
1247 -
1421 +
1422 + $handles_str = is_array($header[$i]['handles']) ? $this->array_to_string_conversion($header[$i]['handles']) : strval($header[$i]['handles']);
1423 + $last_modified_str = empty($header[$i]['last_modified']) ? '' : (is_array($header[$i]['last_modified']) ? $this->array_to_string_conversion($header[$i]['last_modified']) : strval($header[$i]['last_modified']));
1424 +
1248 1425 if ($merge_js) {
1249 - $hash = hash('adler32', implode('', $header[$i]['handles']) . implode('', $header[$i]['versions']));
1426 + $hash = hash('adler32', $handles_str . $last_modified_str);
1250 1427 } else {
1251 - $hash = implode('', $header[$i]['handles']) . implode('', $header[$i]['versions']);
1428 + $hash = sanitize_file_name($handles_str . $last_modified_str);
1252 1429 }
1253 1430
1254 1431 // static cache file info
1255 1432 $file_name = 'wpo-minify-header-'.$hash.($minify_js ? '.min' : '');
@@ -1254,8 +1431,9 @@
1254 1431 // static cache file info
1255 1432 $file_name = 'wpo-minify-header-'.$hash.($minify_js ? '.min' : '');
1256 1433 // create cache files and urls
1257 1434 $file = $cache_dir.'/'.$file_name.'.js';
1435 + $meta_json_file = $cache_base_dir.'/'.'meta.json';
1258 1436 $file_url = WP_Optimize_Minify_Functions::get_protocol($cache_dir_url.'/'.$file_name.'.js');
1259 1437
1260 1438 // generate a new cache file
1261 1439 clearstatcache();
@@ -1261,21 +1439,29 @@
1261 1439 clearstatcache();
1262 1440 if (!file_exists($file)) {
1263 1441
1264 1442 // code and log initialization
1443 + $log_header = $this->get_log_header();
1265 1444 $log = array(
1266 - 'header' => "PROCESSED on ".date('r')." from ".home_url(add_query_arg(null, null)),
1445 + 'header' => $log_header,
1267 1446 'files' => array()
1268 1447 );
1448 + $meta_log = $this->meta_log_initialization($log_header);
1449 + $before_code = '';
1269 1450 $code = '';
1451 + $after_code = '';
1270 1452
1271 1453 // minify and write to file
1272 1454 foreach ($header[$i]['handles'] as $handle) {
1273 1455 if (!empty($wp_scripts->registered[$handle]->src)) {
1456 + // only add minified scripts once to the bundle
1457 + if (in_array($handle, $done)) continue;
1274 1458
1275 1459 // get href per handle
1276 - $href = WP_Optimize_Minify_Functions::get_hurl($wp_scripts->registered[$handle]->src);
1460 + $handle_src = $wp_scripts->registered[$handle]->src;
1461 + $href = WP_Optimize_Minify_Functions::get_hurl($handle_src);
1277 1462 $version = $wp_scripts->registered[$handle]->ver;
1463 + $last_modified = WP_Optimize_Minify_Functions::get_modification_time($handle_src);
1278 1464 if (empty($href)) continue;
1279 1465 // download, minify, cache
1280 1466 $tkey = 'js-'.hash('adler32', $handle . $href).'.js';
1281 1467 $json = false;
@@ -1280,11 +1466,12 @@
1280 1466 $tkey = 'js-'.hash('adler32', $handle . $href).'.js';
1281 1467 $json = false;
1282 1468 $json = WP_Optimize_Minify_Cache_Functions::get_transient($tkey);
1283 1469 if (false === $json) {
1284 - $json = WP_Optimize_Minify_Functions::download_and_minify($href, null, $minify_js, 'js', $handle, $version);
1470 + $enable_minification = $minify_js && !WP_Optimize_Minify_Functions::is_minified_css_js_filename($href);
1471 + $json = WP_Optimize_Minify_Functions::download_and_minify($href, null, $enable_minification, 'js', $handle, $version);
1285 1472 if ($this->options['debug']) {
1286 - echo "<!-- wpo_min DEBUG: Uncached file processing now for $handle / $href / $version -->" . "\n";
1473 + echo "<!-- wpo_min DEBUG: Uncached file processing now for " . esc_html($handle) . " / " . esc_html($href) . " / " . esc_html($version) . " -->" . "\n";
1287 1474 }
1288 1475 WP_Optimize_Minify_Cache_Functions::set_transient($tkey, $json);
1289 1476 }
1290 1477
@@ -1289,17 +1476,24 @@
1289 1476 }
1290 1477
1291 1478 // decode
1292 1479 $res = json_decode($json, true);
1480 +
1481 + if (null === $res) continue;
1293 1482
1294 - if (isset($res['request']['version']) && $res['request']['version'] != $version && !$this->minify_cache_incremented) {
1483 + if ($this->should_reset_minify_assets($res, $href, 'js', $handle, $version)) {
1295 1484 WP_Optimize_Minify_Cache_Functions::reset();
1296 1485 $this->minify_cache_incremented = true;
1486 + $meta_log['invalidation_reason'] = 'Checksum Mismatch and version changed from ' . $res['request']['version'] . ' to ' . $version;
1487 + $meta_log['files_that_changed'][$handle] = $res['log'];
1297 1488 }
1298 1489
1299 1490 // response has failed
1300 - if (true != $res['status']) {
1491 + if (true !== $res['status']) {
1301 1492 $log['files'][$handle] = $res['log'];
1493 + $log['files'][$handle]['uses_loading_strategy'] = isset($header[$i]['strategy']);
1494 + $log['files'][$handle]['version'] = $version;
1495 + $log['files'][$handle]['last_modified'] = $last_modified;
1302 1496 continue;
1303 1497 }
1304 1498
1305 1499 // Add extra data from wp_add_inline_script before
@@ -1305,9 +1499,9 @@
1305 1499 // Add extra data from wp_add_inline_script before
1306 1500 if (!empty($wp_scripts->registered[$handle]->extra)) {
1307 1501 if (!empty($wp_scripts->registered[$handle]->extra['before']) && is_array($wp_scripts->registered[$handle]->extra['before'])) {
1308 1502 if ($merge_inline_extra_css_js) {
1309 - $code .= "\n" . WP_Optimize_Minify_Functions::prepare_merged_js(implode("\n", array_filter($wp_scripts->registered[$handle]->extra['before'])), $href.' - BEFORE');
1503 + $before_code .= "\n" . implode("\n", array_filter($wp_scripts->registered[$handle]->extra['before']));
1310 1504 }
1311 1505 }
1312 1506 }
1313 1507
@@ -1318,21 +1512,27 @@
1318 1512 if (!empty($wp_scripts->registered[$handle]->textdomain)) {
1319 1513 $code .= "\n" . $wp_scripts->print_translations($handle, false);
1320 1514 }
1321 1515
1322 - // append code to merged file
1323 - $code .= isset($res['code']) ? WP_Optimize_Minify_Functions::prepare_merged_js($res['code'], $href) : '';
1324 - $log['files'][$handle] = $res['log'];
1325 -
1326 1516 // Add extra data from wp_add_inline_script after
1327 1517 if (!empty($wp_scripts->registered[$handle]->extra)) {
1328 1518 if (!empty($wp_scripts->registered[$handle]->extra['after']) && is_array($wp_scripts->registered[$handle]->extra['after'])) {
1329 1519 if ($merge_inline_extra_css_js) {
1330 - $code.= "\n" . WP_Optimize_Minify_Functions::prepare_merged_js(implode("\n", array_filter($wp_scripts->registered[$handle]->extra['after'])), $href.' - AFTER');
1520 + $after_code.= "\n" . implode("\n", array_filter($wp_scripts->registered[$handle]->extra['after']));
1331 1521 }
1332 1522 }
1333 1523 }
1334 -
1524 +
1525 + // append code to merged file
1526 + $code .= $before_code . "\n";
1527 + $code .= isset($res['code']) ? $res['code'] : '';
1528 + $code .= "\n" . $after_code . "\n";
1529 + $code = WP_Optimize_Minify_Functions::prepare_merged_js($code, $href);
1530 + $log['files'][$handle] = $res['log'];
1531 + $log['files'][$handle]['uses_loading_strategy'] = isset($header[$i]['strategy']);
1532 + $log['files'][$handle]['version'] = $version;
1533 + $log['files'][$handle]['last_modified'] = $last_modified;
1534 +
1335 1535 // consider dependencies on handles with an empty src
1336 1536 } else {
1337 1537 wp_dequeue_script($handle);
1338 1538 wp_enqueue_script($handle);
@@ -1340,8 +1540,12 @@
1340 1540 }
1341 1541
1342 1542 // generate cache, write log
1343 1543 if (!empty($code)) {
1544 + // Only generate meta.json file if cache is reset
1545 + if (!empty($meta_log['invalidation_reason'])) {
1546 + file_put_contents($meta_json_file, wp_json_encode($meta_log));
1547 + }
1344 1548 WP_Optimize_Minify_Print::write_combined_asset($file, $code, $log);
1345 1549 }
1346 1550 } else {
1347 1551 $log_file = $file.'.json';
@@ -1349,8 +1553,9 @@
1349 1553 $saved_log = json_decode(file_get_contents($log_file));
1350 1554 if (is_object($saved_log) && property_exists($saved_log, 'files')) {
1351 1555 $files = (array) $saved_log->files;
1352 1556 foreach ($header[$i]['handles'] as $handle) {
1557 + $handle = is_array($handle) ? '' : $handle;
1353 1558 if (isset($files[$handle]) && $files[$handle]->success) {
1354 1559 $done[] = $handle;
1355 1560 }
1356 1561 }
@@ -1365,9 +1570,9 @@
1365 1570 }
1366 1571
1367 1572 // register minified file
1368 1573 $dependencies = isset($excluded_dependencies[$i]) ? $excluded_dependencies[$i] : array();
1369 - wp_register_script("wpo_min-header-$i", $file_url, $dependencies, null, false);
1574 + wp_register_script("wpo_min-header-$i", $file_url, $dependencies, null, false); // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion -- Not needed as this is a timestamp based cache
1370 1575
1371 1576 // add all extra data from wp_localize_script
1372 1577 $before_code = '';
1373 1578 $data = array();
@@ -1381,14 +1586,14 @@
1381 1586 // Add extra data from wp_add_inline_script before
1382 1587 if (!empty($wp_scripts->registered[$handle]->extra)) {
1383 1588 if (!empty($wp_scripts->registered[$handle]->extra['before']) && is_array($wp_scripts->registered[$handle]->extra['before'])) {
1384 1589 if (!$merge_inline_extra_css_js) {
1385 - $before_code.= "\n" . WP_Optimize_Minify_Functions::prepare_merged_js(implode("\n", array_filter($wp_scripts->registered[$handle]->extra['before'])), $href.' - BEFORE');
1590 + $before_code.= "\n" . implode("\n", array_filter($wp_scripts->registered[$handle]->extra['before'])) . "\n";
1386 1591 }
1387 1592 }
1388 1593 if (!empty($wp_scripts->registered[$handle]->extra['after']) && is_array($wp_scripts->registered[$handle]->extra['after'])) {
1389 1594 if (!$merge_inline_extra_css_js) {
1390 - $after_code.= "\n" . WP_Optimize_Minify_Functions::prepare_merged_js(implode("\n", array_filter($wp_scripts->registered[$handle]->extra['after'])), $href.' - AFTER');
1595 + $after_code.= "\n" . implode("\n", array_filter($wp_scripts->registered[$handle]->extra['after'])) . "\n";
1391 1596 }
1392 1597 }
1393 1598 }
1394 1599 }
@@ -1404,11 +1609,16 @@
1404 1609 wp_enqueue_script("wpo_min-header-$i");
1405 1610 if (!empty($after_code)) {
1406 1611 wp_add_inline_script("wpo_min-header-$i", $after_code, 'after');
1407 1612 }
1613 +
1614 + if (isset($header[$i]['strategy'])) {
1615 + // Backward compatible way of doing this
1616 + wp_script_add_data( "wpo_min-header-$i", 'strategy', $header[$i]['strategy']);
1617 + }
1408 1618 } else {
1409 1619 // file could not be generated, output something meaningful
1410 - echo "<!-- ERROR: WP-Optimize minify was not allowed to save its cache on - ".str_replace(ABSPATH, '', $file)." -->";
1620 + echo "<!-- ERROR: WP-Optimize minify was not allowed to save its cache on - ".esc_html(str_replace(ABSPATH, '', $file))." -->";
1411 1621 echo "<!-- Please check if the path above is correct and ensure your server has write permission there! -->";
1412 1622 echo "<!-- If you found a bug, please report this on https://wordpress.org/support/plugin/wp-optimize/ -->";
1413 1623 }
1414 1624
@@ -1431,16 +1641,18 @@
1431 1641 public function process_footer_css() {
1432 1642 global $wp_styles;
1433 1643 if (!is_object($wp_styles)) return;
1434 1644 $cache_path = WP_Optimize_Minify_Cache_Functions::cache_path();
1645 + $cache_base_dir = $cache_path['cachebasedir'];
1435 1646 $cache_dir = $cache_path['cachedir'];
1436 1647 $cache_dir_url = $cache_path['cachedirurl'];
1437 - $exclude_css = array_map('trim', explode("\n", trim($this->options['exclude_css'])));
1648 + $exclude_css = $this->get_excluded_css_list();
1438 1649 $ignore_list = WP_Optimize_Minify_Functions::compile_ignore_list($exclude_css);
1439 - $async_css = array_map('trim', explode("\n", trim($this->options['async_css'])));
1650 + $async_css = $this->get_async_css_list();
1440 1651 $minify_css = $this->options['enable_css_minification'];
1441 1652 $merge_css = $this->options['enable_merging_of_css'];
1442 1653 $process_css = $minify_css || $merge_css;
1654 + $disable_google_fonts_processing = $this->options['disable_google_fonts_processing'];
1443 1655 $log = "";
1444 1656 $code = "";
1445 1657 $styles = clone $wp_styles;
1446 1658 $styles->all_deps($styles->queue);
@@ -1459,15 +1671,15 @@
1459 1671 $wp_styles->done = $done;
1460 1672 return;
1461 1673 }
1462 1674
1463 - // dequeue and get a list of google fonts, or requeue external
1675 + // dequeue and get a list of Google fonts, or requeue external
1464 1676 foreach ($styles->to_do as $handle) {
1465 1677 $href = WP_Optimize_Minify_Functions::get_hurl($wp_styles->registered[$handle]->src);
1466 1678 // inlined scripts without file
1467 1679 if (empty($href)) continue;
1468 1680
1469 - if (WP_Optimize_Minify_Functions::is_google_font($href)) {
1681 + if (!$disable_google_fonts_processing && WP_Optimize_Minify_Functions::is_google_font($href)) {
1470 1682 wp_dequeue_style($handle);
1471 1683 if ($this->options['remove_googlefonts']) {
1472 1684 $done = array_merge($done, array($handle));
1473 1685 continue;
@@ -1528,15 +1740,14 @@
1528 1740 WP_Optimize_Minify_Print::inline_style($handle, $href);
1529 1741 $done = array_merge($done, array($handle));
1530 1742 } else {
1531 1743 // fallback, enqueue google fonts
1532 - wp_enqueue_style($handle, $href, array(), null, 'all');
1744 + wp_enqueue_style($handle, $href, array(), null, 'all'); // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion -- Not needed as this google font and from cache
1533 1745 }
1534 1746 }
1535 1747 }
1536 1748 }
1537 1749
1538 -
1539 1750 // get groups of handles
1540 1751 $uniq = array();
1541 1752 foreach ($styles->to_do as $handle) {
1542 1753
@@ -1549,11 +1760,11 @@
1549 1760 $conditional = null; if (isset($wp_styles->registered[$handle]->extra["conditional"])) {
1550 1761 $conditional = $wp_styles->registered[$handle]->extra["conditional"]; // such as ie7, ie8, ie9, etc
1551 1762 }
1552 1763
1553 - // mediatype
1764 + // media type
1554 1765 $mt = isset($wp_styles->registered[$handle]->args) ? $wp_styles->registered[$handle]->args : 'all';
1555 - if ('screen' == $mt || 'screen, print' == $mt || empty($mt) || is_null($mt) || false == $mt) {
1766 + if ('screen' === $mt || 'screen, print' === $mt || empty($mt)) {
1556 1767 $mt = 'all';
1557 1768 }
1558 1769 $mediatype = $mt;
1559 1770
@@ -1567,8 +1778,9 @@
1567 1778 }
1568 1779
1569 1780 // mark duplicates as done and remove from the queue
1570 1781 if (!empty($href)) {
1782 + $version = is_array($version) ? '' : $version;
1571 1783 $key = hash('adler32', $href . $version);
1572 1784 if (isset($uniq[$key])) {
1573 1785 $done = array_merge($done, array($handle));
1574 1786 continue;
@@ -1582,11 +1794,14 @@
1582 1794 if ($ieonly) {
1583 1795 continue;
1584 1796 }
1585 1797
1798 + $version = '?ver=' . (!empty($version) ? $version : $wp_styles->default_version);
1799 + $href_versioned = $href . $version;
1800 +
1586 1801 // Exclude specific CSS files from PageSpeedIndex?
1587 - if (false != $async_css && is_array($async_css) && WP_Optimize_Minify_Functions::in_arrayi($href, $async_css)) {
1588 - WP_Optimize_Minify_Print::exclude_style($href);
1802 + if (is_array($async_css) && WP_Optimize_Minify_Functions::in_arrayi($href, $async_css)) {
1803 + WP_Optimize_Minify_Print::exclude_style($href_versioned);
1589 1804 $done = array_merge($done, array($handle));
1590 1805 continue;
1591 1806 }
1592 1807
@@ -1591,17 +1806,17 @@
1591 1806 }
1592 1807
1593 1808 if (WP_Optimize_Minify_Functions::is_font_awesome($href)) {
1594 1809 if ('inline' === $this->options['fawesome_method']) {
1595 - WP_Optimize_Minify_Print::inline_style($handle, $href);
1810 + WP_Optimize_Minify_Print::inline_style($handle, $href_versioned);
1596 1811 $done = array_merge($done, array($handle));
1597 1812 continue;
1598 1813 } elseif ('async' === $this->options['fawesome_method']) {
1599 - WP_Optimize_Minify_Print::async_style($href, $mediatype);
1814 + WP_Optimize_Minify_Print::async_style($href_versioned, $mediatype);
1600 1815 $done = array_merge($done, array($handle));
1601 1816 continue;
1602 1817 } elseif ('exclude' === $this->options['fawesome_method']) {
1603 - WP_Optimize_Minify_Print::exclude_style($href);
1818 + WP_Optimize_Minify_Print::exclude_style($href_versioned);
1604 1819 $done = array_merge($done, array($handle));
1605 1820 continue;
1606 1821 }
1607 1822 }
@@ -1611,14 +1826,17 @@
1611 1826 $done = array_merge($done, array($handle));
1612 1827 continue;
1613 1828 }
1614 1829
1615 - // skip ignore list, conditional css, external css, font-awesome merge
1616 - if (($process_css && !WP_Optimize_Minify_Functions::in_arrayi($href, $ignore_list) && !isset($conditional) && WP_Optimize_Minify_Functions::internal_url($href, site_url()))
1830 + // check if the current URL points to a minified file.
1831 + $exclude_minified_css = WP_Optimize_Minify_Functions::is_minified_css_js_filename($href);
1832 + $normal_processing = $exclude_minified_css && !$merge_css;
1833 + // skip ignore list, conditional css, external css, font-awesome merge and already minified css
1834 + 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()))
1617 1835 || empty($href)
1618 1836 ) {
1619 1837
1620 - // colect inline css for this handle
1838 + // collect inline css for this handle
1621 1839 if (isset($wp_styles->registered[$handle]->extra['after']) && is_array($wp_styles->registered[$handle]->extra['after'])) {
1622 1840 $inline_css[$handle] = WP_Optimize_Minify_Functions::minify_css_string(implode('', $wp_styles->registered[$handle]->extra['after'])); // save
1623 1841 $wp_styles->registered[$handle]->extra['after'] = null; // dequeue
1624 1842 }
@@ -1623,15 +1841,16 @@
1623 1841 $wp_styles->registered[$handle]->extra['after'] = null; // dequeue
1624 1842 }
1625 1843
1626 1844 // process
1627 - if (isset($footer[count($footer)-1]['handle']) || !count($footer) || $footer[count($footer)-1]['media'] != $wp_styles->registered[$handle]->args || !$merge_css) {
1845 + if (isset($footer[count($footer)-1]['handle']) || !count($footer) || $footer[count($footer)-1]['media'] !== $wp_styles->registered[$handle]->args || !$merge_css) {
1628 1846 array_push($footer, array('handles' => array(), 'media' => $mediatype, 'versions' => array()));
1629 1847 }
1630 1848
1631 1849 // push it to the array get latest modified time
1632 1850 array_push($footer[count($footer)-1]['handles'], $handle);
1633 - array_push($footer[count($footer)-1]['handles'], $version);
1851 + array_push($footer[count($footer)-1]['versions'], $version);
1852 + $footer[count($footer) - 1]['last_modified'][] = WP_Optimize_Minify_Functions::get_modification_time($wp_styles->registered[$handle]->src);
1634 1853
1635 1854 // external and ignored css
1636 1855 } else {
1637 1856
@@ -1650,21 +1869,28 @@
1650 1869 $footer = apply_filters('wpo_minify_stylesheets', $footer, 'footer');
1651 1870
1652 1871 // loop through footer css and merge
1653 1872 for ($i=0,$l=count($footer); $i<$l; $i++) {
1873 +
1654 1874 if (!isset($footer[$i]['handle'])) {
1875 +
1876 + $handles_str = is_array($footer[$i]['handles']) ? $this->array_to_string_conversion($footer[$i]['handles']) : strval($footer[$i]['handles']);
1877 + $last_modified_str = empty($footer[$i]['last_modified']) ? '' : (is_array($footer[$i]['last_modified']) ? $this->array_to_string_conversion($footer[$i]['last_modified']) : strval($footer[$i]['last_modified']));
1878 +
1655 1879 if ($merge_css) {
1656 1880 // get hash for the inline css in this group
1657 1881 $inline_css_group = array();
1658 - foreach ($footer[$i]['handles'] as $h) {
1659 - if (isset($inline_css[$h]) && !empty($inline_css[$h])) {
1660 - $inline_css_group[] = $inline_css[$h];
1882 + if (is_array($footer[$i]['handles'])) {
1883 + foreach ($footer[$i]['handles'] as $h) {
1884 + if (isset($inline_css[$h]) && !empty($inline_css[$h])) {
1885 + $inline_css_group[] = $inline_css[$h];
1886 + }
1661 1887 }
1662 1888 }
1663 1889 $inline_css_hash = md5(implode('', $inline_css_group));
1664 - $hash = hash('adler32', implode('', $footer[$i]['handles']) . $inline_css_hash . implode('', $footer[$i]['versions']));
1890 + $hash = hash('adler32', $handles_str . $inline_css_hash . $last_modified_str);
1665 1891 } else {
1666 - $hash = implode('', $footer[$i]['handles']) . implode('', $footer[$i]['versions']);
1892 + $hash = sanitize_file_name($handles_str . $last_modified_str);
1667 1893 }
1668 1894
1669 1895 // static cache file info
1670 1896 $file_name = 'wpo-minify-footer-'.$hash.($minify_css ? '.min' : '');
@@ -1670,8 +1896,9 @@
1670 1896 $file_name = 'wpo-minify-footer-'.$hash.($minify_css ? '.min' : '');
1671 1897
1672 1898 // create cache files and urls
1673 1899 $file = $cache_dir.'/'.$file_name.'.css';
1900 + $meta_json_file = $cache_base_dir.'/'.'meta.json';
1674 1901 $file_url = WP_Optimize_Minify_Functions::get_protocol($cache_dir_url.'/'.$file_name.'.css');
1675 1902
1676 1903 // generate a new cache file
1677 1904 clearstatcache();
@@ -1677,12 +1904,14 @@
1677 1904 clearstatcache();
1678 1905 if (!file_exists($file)) {
1679 1906
1680 1907 // code and log initialization
1908 + $log_header = $this->get_log_header();
1681 1909 $log = array(
1682 - 'header' => "PROCESSED on ".date('r')." from ".home_url(add_query_arg(null, null)),
1910 + 'header' => $log_header,
1683 1911 'files' => array()
1684 1912 );
1913 + $meta_log = $this->meta_log_initialization($log_header);
1685 1914 $code = '';
1686 1915
1687 1916 // minify and write to file
1688 1917 foreach ($footer[$i]['handles'] as $handle) {
@@ -1688,11 +1917,13 @@
1688 1917 foreach ($footer[$i]['handles'] as $handle) {
1689 1918 if (!empty($wp_styles->registered[$handle]->src)) {
1690 1919
1691 1920 // get href per handle
1692 - $href = WP_Optimize_Minify_Functions::get_hurl($wp_styles->registered[$handle]->src);
1921 + $handle_src = $wp_styles->registered[$handle]->src;
1922 + $href = WP_Optimize_Minify_Functions::get_hurl($handle_src);
1693 1923 $version = $wp_styles->registered[$handle]->ver;
1694 - // inlined scripts without hreffile
1924 + $last_modified = WP_Optimize_Minify_Functions::get_modification_time($handle_src);
1925 + // inlined scripts without href file
1695 1926 if (empty($href)) continue;
1696 1927
1697 1928 // download, minify, cache
1698 1929 $tkey = 'css-'.hash('adler32', $handle . $href).'.css';
@@ -1698,11 +1929,12 @@
1698 1929 $tkey = 'css-'.hash('adler32', $handle . $href).'.css';
1699 1930 $json = false;
1700 1931 $json = WP_Optimize_Minify_Cache_Functions::get_transient($tkey);
1701 1932 if (false === $json) {
1702 - $json = WP_Optimize_Minify_Functions::download_and_minify($href, null, $minify_css, 'css', $handle, $version);
1933 + $enable_minification = $minify_css && !WP_Optimize_Minify_Functions::is_minified_css_js_filename($href);
1934 + $json = WP_Optimize_Minify_Functions::download_and_minify($href, null, $enable_minification, 'css', $handle, $version);
1703 1935 if ($this->options['debug']) {
1704 - echo "<!-- wpo_min DEBUG: Uncached file processing now for $handle / $href / $version -->" . "\n";
1936 + echo "<!-- wpo_min DEBUG: Uncached file processing now for " . esc_html($handle) . " / " . esc_html($href) . " / " . esc_html($version) . " -->" . "\n";
1705 1937 }
1706 1938 WP_Optimize_Minify_Cache_Functions::set_transient($tkey, $json);
1707 1939 }
1708 1940
@@ -1708,16 +1940,22 @@
1708 1940
1709 1941 // decode
1710 1942 $res = json_decode($json, true);
1711 1943
1712 - if (isset($res['request']['version']) && $res['request']['version'] != $version && !$this->minify_cache_incremented) {
1944 + if (null === $res) continue;
1945 +
1946 + if ($this->should_reset_minify_assets($res, $href, 'css', $handle, $version)) {
1713 1947 WP_Optimize_Minify_Cache_Functions::reset();
1714 1948 $this->minify_cache_incremented = true;
1949 + $meta_log['invalidation_reason'] = 'Checksum Mismatch and version changed from ' . $res['request']['version'] . ' to ' . $version;
1950 + $meta_log['files_that_changed'][$handle] = $res['log'];
1715 1951 }
1716 1952
1717 1953 // response has failed
1718 - if (true != $res['status']) {
1954 + if (true !== $res['status']) {
1719 1955 $log['files'][$handle] = $res['log'];
1956 + $log['files'][$handle]['version'] = $version;
1957 + $log['files'][$handle]['last_modified'] = $last_modified;
1720 1958 continue;
1721 1959 }
1722 1960
1723 1961 $done[] = $handle;
@@ -1722,10 +1960,12 @@
1722 1960
1723 1961 $done[] = $handle;
1724 1962
1725 1963 // append code to merged file
1726 - $code .= isset($res['code']) ? $res['code'] : '';
1964 + $code .= $res['code'] ?? '';
1727 1965 $log['files'][$handle] = $res['log'];
1966 + $log['files'][$handle]['version'] = $version;
1967 + $log['files'][$handle]['last_modified'] = $last_modified;
1728 1968
1729 1969 // append inlined styles
1730 1970 if (isset($inline_css[$handle]) && !empty($inline_css[$handle])) {
1731 1971 $code.= $inline_css[$handle];
@@ -1735,12 +1975,16 @@
1735 1975 } else {
1736 1976 wp_dequeue_script($handle);
1737 1977 wp_enqueue_script($handle);
1738 1978 }
1739 - };
1979 + }
1740 1980
1741 1981 // generate cache, add inline css, write log
1742 1982 if (!empty($code)) {
1983 + // Only generate meta.json file if cache is reset
1984 + if (!empty($meta_log['invalidation_reason'])) {
1985 + file_put_contents($meta_json_file, wp_json_encode($meta_log));
1986 + }
1743 1987 WP_Optimize_Minify_Print::write_combined_asset($file, $code, $log);
1744 1988 }
1745 1989 } else {
1746 1990 $log_file = $file.'.json';
@@ -1775,13 +2019,13 @@
1775 2019 if (filesize($file) < 20000 && $this->options['inline_css']) {
1776 2020 $this->inline_css(file_get_contents($file), $handle, $file_url, $footer[$i]['media']);
1777 2021 } else {
1778 2022 // enqueue it
1779 - wp_enqueue_style("wpo_min-footer-$i", $file_url, array(), null, $footer[$i]['media']);
2023 + wp_enqueue_style("wpo_min-footer-$i", $file_url, array(), null, $footer[$i]['media']); // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion -- Not needed as this is a timestamp based cache
1780 2024 }
1781 2025 } else {
1782 2026 // file could not be generated, output something meaningful
1783 - echo "<!-- ERROR: WP-Optimize Minify was not allowed to save its cache on - ".str_replace(ABSPATH, '', $file)." -->";
2027 + echo "<!-- ERROR: WP-Optimize Minify was not allowed to save its cache on - ".esc_html(str_replace(ABSPATH, '', $file))." -->";
1784 2028 echo "<!-- Please check if the path above is correct and ensure your server has write permission there! -->";
1785 2029 }
1786 2030 }
1787 2031
@@ -1796,8 +2040,129 @@
1796 2040 $wp_styles->done = $done;
1797 2041 }
1798 2042
1799 2043 /**
2044 + * Process JS Scripts of type module
2045 + *
2046 + * @param string $buffer Page HTML.
2047 + *
2048 + * @return string
2049 + */
2050 + public function process_script_modules($buffer) {
2051 +
2052 + if ('' === $buffer) return '';
2053 +
2054 + if (!WP_Optimize_Utils::is_valid_html($buffer)) return $buffer;
2055 +
2056 + $minify_js = $this->options['enable_js_minification'];
2057 + $cache_path = WP_Optimize_Minify_Cache_Functions::cache_path();
2058 + $cache_dir = $cache_path['cachedir'];
2059 + $cache_dir_url = $cache_path['cachedirurl'];
2060 + $cache_base_dir = $cache_path['cachebasedir'];
2061 +
2062 + $scripts = $this->get_scripts($buffer);
2063 + if (empty($scripts)) return $buffer;
2064 +
2065 + foreach ($scripts as $script) {
2066 + if ("module" !== $script['type']) continue;
2067 +
2068 + $href = WP_Optimize_Minify_Functions::get_hurl($script['url']);
2069 + if (WP_Optimize_Minify_Functions::is_minified_css_js_filename($href)) continue;
2070 +
2071 + $asset_content = WP_Optimize_Minify_Functions::get_asset_content($href);
2072 + $code = $asset_content['content'];
2073 +
2074 +
2075 + if (WP_Optimize_Minify_Functions::has_js_import_statements($code)) {
2076 + // If the module script contains import statements, we cannot safely minify or cache it, so skip it.
2077 + continue;
2078 + }
2079 +
2080 + $last_modified = WP_Optimize_Minify_Functions::get_modification_time($script['url']);
2081 + $handle = $script['handle'];
2082 + $version = $script['version'];
2083 +
2084 + // Module script handles use slashes and @ symbols,
2085 + // replace them with '-' and empty string, break if handle is still ''
2086 + if (!empty($handle)) {
2087 + $handle_no_slash = str_replace(array('/', '@'), array('-', ''), $handle);
2088 + } else {
2089 + continue;
2090 + }
2091 +
2092 + // code and log initialization
2093 + $log_header = $this->get_log_header();
2094 + $log = array(
2095 + 'header' => $log_header,
2096 + 'files' => array()
2097 + );
2098 + $meta_log = $this->meta_log_initialization($log_header);
2099 + $code = '';
2100 +
2101 + // create cache files and urls
2102 + $file_name = 'wpo-minify-'.$handle_no_slash.($minify_js ? '.min' : '');
2103 + $file = $cache_dir.'/'.$file_name.'.js';
2104 + $meta_json_file = $cache_base_dir.'/'.'meta.json';
2105 + $file_url = WP_Optimize_Minify_Functions::get_protocol($cache_dir_url.'/'.$file_name.'.js');
2106 +
2107 + // download, minify, cache
2108 + $tkey = 'js-'.hash('adler32', $handle_no_slash . $href).'.js';
2109 + $json = WP_Optimize_Minify_Cache_Functions::get_transient($tkey);
2110 + if (false === $json) {
2111 + $enable_minification = $minify_js && !WP_Optimize_Minify_Functions::is_minified_css_js_filename($href);
2112 + $json = WP_Optimize_Minify_Functions::download_and_minify($href, null, $enable_minification, 'js', $handle_no_slash, $version);
2113 + if ($this->options['debug']) {
2114 + $buffer .= "<!-- wpo_min DEBUG: Uncached file processing now for " . esc_html($handle) . " / " . esc_html($href) . " / " . esc_html($version) . " -->\n";
2115 + }
2116 + WP_Optimize_Minify_Cache_Functions::set_transient($tkey, $json);
2117 + }
2118 +
2119 + // decode
2120 + $res = json_decode($json, true);
2121 +
2122 + if (null === $res) continue;
2123 +
2124 + // Reset minified script module
2125 + if ($this->should_reset_minify_assets($res, $href, 'js', $handle, $version)) {
2126 + WP_Optimize_Minify_Cache_Functions::reset();
2127 + $this->minify_cache_incremented = true;
2128 + $meta_log['invalidation_reason'] = 'Checksum Mismatch and version changed from ' . $res['request']['version'] . ' to ' . $version;
2129 + $meta_log['files_that_changed'][$handle] = $res['log'];
2130 + }
2131 +
2132 + if (!$res['status']) continue;
2133 +
2134 + $code .= $res['code'] ?? '';
2135 + $code = WP_Optimize_Minify_Functions::prepare_merged_js($code, $href);
2136 + $log['files'][$handle] = $res['log'];
2137 + $log['files'][$handle]['version'] = $version;
2138 + $log['files'][$handle]['last_modified'] = $last_modified;
2139 +
2140 + // If code is empty (nothing to minify) break loop
2141 + if (empty($code)) continue;
2142 +
2143 + // Only generate meta.json file if cache is reset
2144 + if (!empty($meta_log['invalidation_reason'])) {
2145 + file_put_contents($meta_json_file, wp_json_encode($meta_log));
2146 + }
2147 + WP_Optimize_Minify_Print::write_combined_asset($file, $code, $log);
2148 +
2149 + // Extract importmap script tag
2150 + $json_map = $this->get_script_module_importmap($buffer);
2151 + $json_map_update = $json_map;
2152 +
2153 + // Update module source file and importmap with new file url (if necessary)
2154 + $buffer = str_replace($script['url'], $file_url, $buffer);
2155 + if (!empty($json_map) && isset($json_map['imports'][$handle])) {
2156 + $json_map_update['imports'][$handle] = $file_url;
2157 + $buffer = str_replace(wp_json_encode($json_map), wp_json_encode($json_map_update), $buffer);
2158 + }
2159 + }
2160 +
2161 + return $buffer;
2162 + }
2163 +
2164 + /**
1800 2165 * Orders the CSS per media type
1801 2166 *
1802 2167 * @param array $list - The list of assets
1803 2168 * @return array
@@ -1841,9 +2206,9 @@
1841 2206 * @return void;
1842 2207 */
1843 2208 public function add_google_fonts_merged() {
1844 2209 // must have something to do
1845 - if (count($this->collect_google_fonts) == 0) return;
2210 + if (count($this->collect_google_fonts) === 0) return;
1846 2211
1847 2212 // merge google fonts
1848 2213 $href = WP_Optimize_Minify_Fonts::concatenate_google_fonts($this->collect_google_fonts);
1849 2214 if (empty($href)) {
@@ -1889,14 +2254,19 @@
1889 2254 // always false for admin pages
1890 2255 if (is_admin()) return false;
1891 2256
1892 2257 // get host with multisite support and query strings
1893 - $host = htmlentities($_SERVER['SERVER_NAME']);
2258 + $host = isset($_SERVER['SERVER_NAME']) ? sanitize_text_field(wp_unslash($_SERVER['SERVER_NAME'])) : '';
1894 2259 if (empty($host)) {
1895 - $host = htmlentities($_SERVER['HTTP_HOST']);
2260 + $host = isset($_SERVER['HTTP_HOST']) ? sanitize_text_field(wp_unslash($_SERVER['HTTP_HOST'])) : '';
1896 2261 }
1897 - $request_query = parse_url($_SERVER['REQUEST_URI'], PHP_URL_QUERY);
1898 - $request_uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
2262 +
2263 + $request_query = '';
2264 + $request_uri = isset($_SERVER['REQUEST_URI']) ? esc_url_raw(wp_unslash($_SERVER['REQUEST_URI'])) : '';
2265 + if (!empty($request_uri)) {
2266 + $request_query = wp_parse_url($request_uri, PHP_URL_QUERY);
2267 + $request_uri = wp_parse_url($request_uri, PHP_URL_PATH);
2268 + }
1899 2269
1900 2270 // initialize headers
1901 2271 $headers = array();
1902 2272
@@ -1904,9 +2274,9 @@
1904 2274 if ($this->options['enabled_css_preload']
1905 2275 && count($this->collect_preload_css) > 0
1906 2276 ) {
1907 2277 foreach ($this->collect_preload_css as $u) {
1908 - // filter out footer footer files, because they are not in the critical path
2278 + // filter out footer files, because they are not in the critical path
1909 2279 if (strpos($u, '/assets/wpo-minify-footer-') !== false) {
1910 2280 continue;
1911 2281 }
1912 2282
@@ -1920,9 +2290,9 @@
1920 2290 && count($this->collect_preload_js) > 0
1921 2291 ) {
1922 2292 foreach ($this->collect_preload_js as $u) {
1923 2293
1924 - // filter out footer footer files, because they are not in the critical path
2294 + // filter out footer files, because they are not in the critical path
1925 2295 if (false !== strpos($u, '/assets/wpo-minify-footer-')) {
1926 2296 continue;
1927 2297 }
1928 2298
@@ -1931,9 +2301,9 @@
1931 2301 }
1932 2302 }
1933 2303
1934 2304 // must have something
1935 - if (count($headers) == 0) {
2305 + if (count($headers) === 0) {
1936 2306 return false;
1937 2307 } else {
1938 2308 $headers = implode("\n", $headers);
1939 2309 }
@@ -1949,9 +2319,9 @@
1949 2319 // reset file cache
1950 2320 clearstatcache();
1951 2321
1952 2322 // if there are no query strings
1953 - if ($b == $a) {
2323 + if ($b === $a) {
1954 2324 if (!file_exists($a)) {
1955 2325 WP_Optimize_Minify_Print::write_header($a, $headers);
1956 2326 }
1957 2327 return false;
@@ -1964,13 +2334,13 @@
1964 2334
1965 2335 /**
1966 2336 * Collect all wpo_min JS files and save them to a headers file
1967 2337 *
1968 - * @param String $html
1969 - * @param String $handle
1970 - * @param String $src
2338 + * @param string $html
2339 + * @param string $handle
2340 + * @param string $src
1971 2341 *
1972 - * @return String
2342 + * @return string
1973 2343 */
1974 2344 public function collect_js_preload_headers($html, $handle, $src) {
1975 2345 if (false !== strpos($src, 'assets/wpo-minify')) {
1976 2346 if (!in_array($src, $this->collect_preload_js)) {
@@ -2017,14 +2387,18 @@
2017 2387 return false;
2018 2388 }
2019 2389
2020 2390 // get host with multisite support and query strings
2021 - $host = htmlentities($_SERVER['SERVER_NAME']);
2391 + $host = isset($_SERVER['SERVER_NAME']) ? sanitize_text_field(wp_unslash($_SERVER['SERVER_NAME'])) : '';
2022 2392 if (empty($host)) {
2023 - $host = htmlentities($_SERVER['HTTP_HOST']);
2393 + $host = isset($_SERVER['HTTP_HOST']) ? sanitize_text_field(wp_unslash($_SERVER['HTTP_HOST'])) : '';
2024 2394 }
2025 - $request_query = parse_url($_SERVER['REQUEST_URI'], PHP_URL_QUERY);
2026 - $request_uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
2395 + $request_query = '';
2396 + $request_uri = isset($_SERVER['REQUEST_URI']) ? esc_url_raw(wp_unslash($_SERVER['REQUEST_URI'])) : '';
2397 + if (!empty($request_uri)) {
2398 + $request_query = wp_parse_url($request_uri, PHP_URL_QUERY);
2399 + $request_uri = wp_parse_url($request_uri, PHP_URL_PATH);
2400 + }
2027 2401
2028 2402 // get cache path
2029 2403 $cache_path = WP_Optimize_Minify_Cache_Functions::cache_path();
2030 2404 $header_dir = $cache_path['headerdir'];
@@ -2037,12 +2411,12 @@
2037 2411 // reset file cache
2038 2412 clearstatcache();
2039 2413
2040 2414 // return header files or fallback
2041 - if ($b == $a && file_exists($a)) {
2415 + if ($b === $a && file_exists($a)) {
2042 2416 return file_get_contents($a);
2043 2417 }
2044 - if ($b != $a && file_exists($b)) {
2418 + if ($b !== $a && file_exists($b)) {
2045 2419 return file_get_contents($b);
2046 2420 }
2047 2421
2048 2422 return false;
@@ -2048,9 +2422,9 @@
2048 2422 return false;
2049 2423 }
2050 2424
2051 2425 /**
2052 - * Add pre-connect and pre-load headers
2426 + * Add pre-connect and preload headers
2053 2427 *
2054 2428 * @return void
2055 2429 */
2056 2430 public function extra_preload_headers() {
@@ -2056,12 +2430,12 @@
2056 2430 public function extra_preload_headers() {
2057 2431 if (!$this->run_on_page('extra_preload_headers')) return;
2058 2432
2059 2433 // fetch headers
2060 - $pre_connect = array_map('trim', explode("\n", trim($this->options['hpreconnect'])));
2434 + $pre_connect = $this->get_pre_connect_list();
2061 2435
2062 2436 // preload
2063 - if (is_array($pre_connect) && count($pre_connect) > 0) {
2437 + if (count($pre_connect) > 0) {
2064 2438 foreach ($pre_connect as $url) {
2065 2439 if (!empty($url) && filter_var($url, FILTER_VALIDATE_URL)) {
2066 2440 header("Link: <$url>; rel=preconnect", false);
2067 2441 }
@@ -2068,9 +2442,9 @@
2068 2442 }
2069 2443 }
2070 2444
2071 2445 $headers = $this->get_preload_headers();
2072 - if (false != $headers) {
2446 + if ($headers) {
2073 2447 $nh = array_map('trim', explode("\n", $headers));
2074 2448 foreach ($nh as $h) {
2075 2449 if (!empty($h)) {
2076 2450 header($h, false);
@@ -2079,8 +2453,30 @@
2079 2453 }
2080 2454 }
2081 2455
2082 2456 /**
2457 + * Add caching headers
2458 + *
2459 + * @param array $headers An array of headers
2460 + *
2461 + * @return array An array of modified headers
2462 + */
2463 + public function add_caching_headers($headers) {
2464 + $cache = WP_Optimize()->get_page_cache();
2465 + if ($cache->is_enabled()) return $headers;
2466 +
2467 + if (!isset($headers['Cache-Control'])) {
2468 + $headers['Cache-Control'] = 'must-revalidate';
2469 + }
2470 +
2471 + if (!isset($headers['Last-Modified'])) {
2472 + $headers['Last-Modified'] = gmdate('D, d M Y H:i:s', time()) . ' GMT';
2473 + }
2474 +
2475 + return $headers;
2476 + }
2477 +
2478 + /**
2083 2479 * Includes dependency files
2084 2480 */
2085 2481 private function include_dependencies() {
2086 2482 if (!class_exists('WP_Optimize_Minify_Functions')) {
@@ -2107,15 +2503,15 @@
2107 2503 /**
2108 2504 * Handles header meta information removal
2109 2505 */
2110 2506 private function remove_header_meta_info() {
2111 - // no resource hints, generator tag, shortlinks, manifest link, etc
2507 + // no resource hints, generator tag, shortlink, manifest link, etc
2112 2508 remove_action('wp_head', 'wp_resource_hints', 2);
2113 2509 remove_action('wp_head', 'wp_generator');
2114 2510 remove_action('template_redirect', 'wp_shortlink_header', 11);
2115 2511 remove_action('wp_head', 'wlwmanifest_link');
2116 2512 remove_action('wp_head', 'rsd_link');
2117 - remove_action('wp_head', 'adjacent_posts_rel_link_wp_head', 10, 0);
2513 + remove_action('wp_head', 'adjacent_posts_rel_link_wp_head');
2118 2514 remove_action('wp_head', 'feed_links', 2);
2119 2515 remove_action('wp_head', 'feed_links_extra', 3);
2120 2516 WP_Optimize_Minify_Functions::remove_redundant_shortlink();
2121 2517 }
@@ -2135,17 +2531,71 @@
2135 2531 add_filter('script_loader_tag', array($this, 'collect_js_preload_headers'), PHP_INT_MAX, 3);
2136 2532 }
2137 2533
2138 2534 // add the LoadAsync JavaScript function
2139 - $async_js = trim($this->options['async_js']) ? array_map('trim', explode("\n", trim($this->options['async_js']))) : array();
2535 + $async_js = $this->get_async_js_list();
2140 2536 if (count($async_js) > 0
2141 2537 || ( 'all' === $this->options['enable_defer_js'] && 'async_using_js' === $this->options['defer_js_type'] )
2142 2538 ) {
2143 2539 add_action('wp_head', array('WP_Optimize_Minify_Print', 'add_load_async'), 0);
2144 2540 }
2541 +
2542 + // Process script module from generated html
2543 + ob_start(array($this, "process_script_modules"));
2145 2544 }
2146 2545
2147 2546 /**
2547 + * Extract scripts tags from html buffer using regex
2548 + *
2549 + * @param string $html - HTML document as string
2550 + * @return array
2551 + */
2552 + private function get_scripts($html) {
2553 +
2554 + preg_match_all('/<script([^>]*)>/Umi', $html, $script_matches, PREG_SET_ORDER);
2555 +
2556 + $modified_script_matches = array();
2557 + foreach ($script_matches as $script) {
2558 + $script['url'] = '';
2559 + $script['handle'] = '';
2560 + $script['version'] = '';
2561 + $script['type'] = '';
2562 + $attributes = WP_Optimize_Utils::parse_attributes($script[1]);
2563 +
2564 + if (isset($attributes['src'])) {
2565 + $script['url'] = $attributes['src'];
2566 + $url_query = wp_parse_url($script['url'], PHP_URL_QUERY);
2567 + $output = array();
2568 + wp_parse_str($url_query, $output);
2569 + if (isset($output['ver'])) $script['version'] = $output['ver'];
2570 + }
2571 +
2572 + // Extract the id/handle from attribute's array if present
2573 + if (isset($attributes['id'])) $script['handle'] = $attributes['id'];
2574 +
2575 + // Extract the type from attribute's array if present
2576 + if (isset($attributes['type'])) $script['type'] = $attributes['type'];
2577 +
2578 + $modified_script_matches[] = $script;
2579 + }
2580 +
2581 + return $modified_script_matches;
2582 + }
2583 +
2584 + /**
2585 + * Extract `importmap` json object from html buffer using regex
2586 + *
2587 + * @param string $html - HTML document as string
2588 + * @return array
2589 + */
2590 + private function get_script_module_importmap($html) {
2591 + preg_match('/<script\s+type\s*=\s*[\'"]importmap.*>(?<importmap>.*)<\/script>/Umsi', $html, $match);
2592 + if (!empty($match) && isset($match['importmap'])) return json_decode($match['importmap'], true);
2593 +
2594 + return array();
2595 + }
2596 +
2597 + /**
2148 2598 * Handles css processing
2149 2599 */
2150 2600 private function process_css() {
2151 2601 add_action('wp_head', array($this, 'add_critical_path'), 2);
@@ -2151,11 +2601,11 @@
2151 2601 add_action('wp_head', array($this, 'add_critical_path'), 2);
2152 2602
2153 2603 // merge, if inline is selected but prevent optimization for these locations
2154 2604 if ($this->options['inline_css']) {
2155 - // this prints the styles (not fonts) and checks the 'colllect google fonts'
2605 + // this prints the styles (not fonts) and checks the 'collect google fonts'
2156 2606 add_filter('style_loader_tag', array($this, 'inline_css'), PHP_INT_MAX, 4);
2157 - // this prints the google fonts
2607 + // this prints the Google fonts
2158 2608 add_action('wp_print_styles', array($this, 'add_google_fonts_merged'), PHP_INT_MAX);
2159 2609 add_action('wp_print_footer_scripts', array($this, 'add_google_fonts_merged'), PHP_INT_MAX);
2160 2610 }
2161 2611 // Preloading
@@ -2161,14 +2611,14 @@
2161 2611 // Preloading
2162 2612 if ($this->options['enabled_css_preload']) {
2163 2613 add_filter('style_loader_tag', array($this, 'collect_css_preload_headers'), PHP_INT_MAX, 3);
2164 2614 }
2165 - // Optimize the css and collect the google fonts for merging
2615 + // Optimize the css and collect the Google fonts for merging
2166 2616 add_action('wp_print_styles', array($this, 'process_header_css'), PHP_INT_MAX);
2167 2617 add_action('wp_print_footer_scripts', array($this, 'process_footer_css'), 9);
2168 2618
2169 2619 /**
2170 - * Filters whether or not to ignore the order of the CSS files, and group them by media type
2620 + * Filters whether to ignore the order of the CSS files, and group them by media type
2171 2621 *
2172 2622 * @param boolean $maintain_css_order
2173 2623 * @return boolean
2174 2624 * @default true
@@ -2174,14 +2624,16 @@
2174 2624 * @default true
2175 2625 */
2176 2626 if (!apply_filters('wpo_minify_maintain_css_order', true)) {
2177 2627 // Reorder stylesheets
2178 - add_filter('wpo_minify_stylesheets', array($this, 'order_stylesheets_per_media_type'), 10);
2628 + add_filter('wpo_minify_stylesheets', array($this, 'order_stylesheets_per_media_type'));
2179 2629 }
2180 2630 }
2181 2631
2182 2632 /**
2183 2633 * Determines whether we should minify html or not
2634 + *
2635 + * @return bool
2184 2636 */
2185 2637 private function should_process_html() {
2186 2638 return $this->options['html_minification'] && !is_admin() && $this->is_cache_preload();
2187 2639 }
@@ -2187,8 +2639,10 @@
2187 2639 }
2188 2640
2189 2641 /**
2190 2642 * Determines whether to use loadCSS polyfill or not
2643 + *
2644 + * @return bool
2191 2645 */
2192 2646 private function should_use_loadCSS() {
2193 2647 return $this->options['loadcss']
2194 2648 || 'async' === $this->options['fawesome_method']
@@ -2199,6 +2653,125 @@
2199 2653 * Removes query string from static assets
2200 2654 */
2201 2655 private function remove_query_string_from_static_assets() {
2202 2656 add_filter('style_loader_src', array('WP_Optimize_Minify_Functions', 'remove_cssjs_ver'), 10, 2);
2657 + add_filter('script_loader_src', array('WP_Optimize_Minify_Functions', 'remove_cssjs_ver'), 10, 2);
2658 + }
2659 +
2660 + /**
2661 + * Converts the lines in a textarea option value into an array
2662 + *
2663 + * @param string $option
2664 + * @return array
2665 + */
2666 + private function convert_option_to_array($option) {
2667 + return trim($option) ? array_map('trim', explode("\n", trim($option))) : array();
2668 + }
2669 +
2670 + /**
2671 + * Retrieves excluded css URLs as an array
2672 + *
2673 + * @return array
2674 + */
2675 + private function get_excluded_css_list() {
2676 + $excluded_css = $this->options['exclude_css'];
2677 + return $this->convert_option_to_array($excluded_css);
2678 + }
2679 +
2680 + /**
2681 + * Retrieves async css URLs as an array
2682 + *
2683 + * @return array
2684 + */
2685 + private function get_async_css_list() {
2686 + $async_css = $this->options['async_css'];
2687 + return $this->convert_option_to_array($async_css);
2688 + }
2689 +
2690 + /**
2691 + * Retrieves excluded javascript URLs as an array
2692 + *
2693 + * @return array
2694 + */
2695 + private function get_excluded_js_list() {
2696 + $excluded_js = $this->options['exclude_js'];
2697 + return $this->convert_option_to_array($excluded_js);
2698 + }
2699 +
2700 + /**
2701 + * Retrieves async javascript URLs as an array
2702 + *
2703 + * @return array
2704 + */
2705 + private function get_async_js_list() {
2706 + $async_js = $this->options['async_js'];
2707 + return $this->convert_option_to_array($async_js);
2708 + }
2709 +
2710 + /**
2711 + * Retrieves pre connect headers as an array
2712 + *
2713 + * @return array
2714 + */
2715 + private function get_pre_connect_list() {
2716 + $pre_connect = $this->options['hpreconnect'];
2717 + return $this->convert_option_to_array($pre_connect);
2718 + }
2719 +
2720 + /**
2721 + * Array to string conversion
2722 + *
2723 + * @param array $arr
2724 + * @return string
2725 + */
2726 + private function array_to_string_conversion($arr) {
2727 + $str = '';
2728 + // implode won't work, because element can be an array
2729 + foreach ($arr as $value) {
2730 + $str .= is_array($value) ? '' : $value;
2731 + }
2732 + return $str;
2733 + }
2734 +
2735 + /**
2736 + * Decides whether minify cache should be incremented or not
2737 + *
2738 + * @param array $res
2739 + * @param string $href
2740 + * @param string $type
2741 + * @param string $handle
2742 + * @param mixed $version
2743 + *
2744 + * @return bool true when cache is invalid, false otherwise
2745 + */
2746 + private function should_reset_minify_assets($res, $href, $type, $handle, $version) {
2747 + if (isset($res['request']['version']) && $res['request']['version'] != $version && !$this->minify_cache_incremented) {
2748 + $enable_minification = $this->options['enable_'.$type.'_minification'] && !WP_Optimize_Minify_Functions::is_minified_css_js_filename($href);
2749 + $new_json = WP_Optimize_Minify_Functions::download_and_minify($href, null, $enable_minification, $type, $handle, $version);
2750 + $new_res = json_decode($new_json, true);
2751 +
2752 + /**
2753 + * In case `download_and_minify` fails because of
2754 + * Network issues, Syntax errors in updated code,
2755 + * Wrong use of `wpo_minify_get_js` and `wpo_minify_get_css` filters
2756 + * We don't want to invalidate current assets, so bail out
2757 + */
2758 + if (null === $new_res) return false;
2759 +
2760 + $hash = hash('sha256', $res['code']);
2761 + $new_hash = hash('sha256', $new_res['code']);
2762 + return strcmp($hash, $new_hash);
2763 + }
2764 + return false;
2765 + }
2766 +
2767 + /**
2768 + * Returns the log header with the current date and url
2769 + *
2770 + * @return string
2771 + */
2772 + private function get_log_header() {
2773 + $request_uri = isset($_SERVER['REQUEST_URI']) ? esc_url_raw(wp_unslash($_SERVER['REQUEST_URI'])) : '';
2774 + $log_header = "PROCESSED on ".gmdate('r')." from ".home_url($request_uri);
2775 + return $log_header;
2203 2776 }
2204 2777 }