PluginProbe
WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance / 3.5.0
WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance v3.5.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
wp-optimize / minify / class-wp-optimize-minify-front-end.php

class-wp-optimize-minify-front-end.php in WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance 3.5.0, at minify/class-wp-optimize-minify-front-end.php

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