PluginProbe
WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance / 3.7.0
WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance v3.7.0
4.7.0 4.6.1 4.6.0 4.5.5 4.5.4 4.5.3 4.5.2 3.2.20 3.2.21 3.2.22 3.2.3 3.2.5 3.2.6 3.2.7 3.2.9 3.3.0 3.3.1 3.3.2 3.4.0 3.4.1 3.4.2 3.5.0 3.6.0 3.7.0 3.7.1 All 111 releases
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.7.0, at minify/class-wp-optimize-minify-front-end.php

2,713 lines 94.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
742 if (!isset($header[$i]['handle'])) {
743
744 $handle_str = is_array($header[$i]['handles']) ? $this->array_to_string_conversion($header[$i]['handles']) : strval($header[$i]['handles']);
745 $last_modified_str = is_array($header[$i]['last_modified']) ? $this->array_to_string_conversion($header[$i]['last_modified']) : strval($header[$i]['last_modified']);
746
747 if ($merge_css) {
748 // get hash for the inline css in this group
749 $inline_css_group = array();
750
751 if (is_array($header[$i]['handles'])) {
752 foreach ($header[$i]['handles'] as $h) {
753 if (!empty($inline_css) && isset($inline_css[$h]) && !empty($inline_css[$h])) {
754 $inline_css_group[] = $inline_css[$h];
755 }
756 }
757 }
758 $inline_css_hash = md5(implode('', $inline_css_group));
759 $hash = hash('adler32', $handle_str . $inline_css_hash . $last_modified_str);
760 } else {
761 $hash = $handle_str . $last_modified_str;
762 }
763
764 // static cache file info
765 $file_name = 'wpo-minify-header-'.$hash.($minify_css ? '.min' : '');
766
767 // create cache files and urls
768 $file = $cache_dir.'/'.$file_name.'.css';
769 $meta_json_file = $cache_base_dir.'/'.'meta.json';
770 $file_url = WP_Optimize_Minify_Functions::get_protocol("$cache_dir_url/$file_name.css");
771
772 // generate a new cache file
773 clearstatcache();
774 if (!file_exists($file)) {
775
776 // code and log initialization
777 $log_header = "PROCESSED on ".date('r')." from ".home_url(add_query_arg(null, null));
778 $log = array(
779 'header' => $log_header,
780 'files' => array()
781 );
782 $meta_log = $this->meta_log_initialization($log_header);
783 $code = '';
784
785 // minify and write to file
786 foreach ($header[$i]['handles'] as $handle) {
787 if (!empty($wp_styles->registered[$handle]->src)) {
788
789 // get href per handle
790 $handle_src = $wp_styles->registered[$handle]->src;
791 $href = WP_Optimize_Minify_Functions::get_hurl($handle_src);
792 $version = $wp_styles->registered[$handle]->ver;
793 $last_modified = WP_Optimize_Minify_Functions::get_modification_time($handle_src);
794
795 // inlined scripts without file
796 if (empty($href)) continue;
797
798 // download, minify, cache
799 $tkey = 'css-'.hash('adler32', $handle . $href).'.css';
800 $json = false;
801 $json = WP_Optimize_Minify_Cache_Functions::get_transient($tkey);
802 if (false === $json) {
803 $enable_minification = $this->options['enable_css_minification'] && !WP_Optimize_Minify_Functions::is_minified_css_js_filename($href);
804 $json = WP_Optimize_Minify_Functions::download_and_minify($href, null, $enable_minification, 'css', $handle, $version);
805 if ($this->options['debug']) {
806 echo "<!-- wpo_min DEBUG: Uncached file processing now for $handle / $href / $version -->" . "\n";
807 }
808 WP_Optimize_Minify_Cache_Functions::set_transient($tkey, $json);
809 }
810
811 // decode
812 $res = json_decode($json, true);
813
814 if (null === $res) continue;
815
816 if ($this->should_reset_minify_assets($res, $href, 'css', $handle, $version)) {
817 WP_Optimize_Minify_Cache_Functions::reset();
818 $this->minify_cache_incremented = true;
819 $meta_log['invalidation_reason'] = 'Checksum Mismatch and version changed from ' . $res['request']['version'] . ' to ' . $version;
820 $meta_log['files_that_changed'][$handle] = $res['log'];
821 }
822
823 // response has failed
824 if (true != $res['status']) {
825 $log['files'][$handle] = $res['log'];
826 $log['files'][$handle]['version'] = $version;
827 $log['files'][$handle]['last_modified'] = $last_modified;
828 continue;
829 }
830
831 // Only add the $handle to $done if it was successfully downloaded
832 $done[] = $handle;
833 // append code to merged file
834 $code .= isset($res['code']) ? $res['code'] : '';
835 $log['files'][$handle] = $res['log'];
836 $log['files'][$handle]['version'] = $version;
837 $log['files'][$handle]['last_modified'] = $last_modified;
838
839 // append inlined styles
840 if ($merge_inline_extra_css_js && isset($inline_css[$handle]) && !empty($inline_css[$handle])) {
841 $code.= $inline_css[$handle];
842 }
843
844 // consider dependencies on handles with an empty src
845 } else {
846 wp_dequeue_script($handle);
847 wp_enqueue_script($handle);
848 }
849 };
850
851 // generate cache, write log
852 if (!empty($code)) {
853 // Only generate meta.json file if cache is reset
854 if (!empty($meta_log['invalidation_reason'])) {
855 file_put_contents($meta_json_file, json_encode($meta_log));
856 }
857 WP_Optimize_Minify_Print::write_combined_asset($file, $code, $log);
858 }
859 } else {
860 $log_file = $file.'.json';
861 if (file_exists($log_file)) {
862 $saved_log = json_decode(file_get_contents($log_file));
863 if (is_object($saved_log) && property_exists($saved_log, 'files')) {
864 $files = (array) $saved_log->files;
865 foreach ($header[$i]['handles'] as $handle) {
866 $handle = is_array($handle) ? '' : $handle;
867 if (isset($files[$handle]) && $files[$handle]->success) {
868 $done[] = $handle;
869 }
870 }
871 } else {
872 // The merged file already exists, so add all files to $done.
873 $done = array_merge($done, $header[$i]['handles']);
874 }
875 } else {
876 // The merged file already exists, so add all files to $done.
877 $done = array_merge($done, $header[$i]['handles']);
878 }
879 }
880
881 // the developers tab, takes precedence
882 // Async CSS with loadCSS ?
883 if ($this->options['loadcss'] && empty($this->options['remove_css'])) {
884 $mt = $header[$i]['media'];
885 WP_Optimize_Minify_Print::async_style($file_url, $mt);
886 // enqueue file, if not empty
887 } else {
888 if (file_exists($file) && filesize($file) > 0) {
889 // enqueue it
890 wp_enqueue_style("wpo_min-header-$i", $file_url, array(), 'mycoolversion', $header[$i]['media']);
891 foreach ($header[$i]['handles'] as $h) {
892 if (!$merge_inline_extra_css_js && isset($inline_css[$h]) && !empty($inline_css[$h])) {
893 wp_add_inline_style("wpo_min-header-$i", $inline_css[$h]);
894 }
895 }
896 } else {
897 // file could not be generated, output something meaningful
898 echo "<!-- ERROR: WP-Optimize Minify was not allowed to save its cache on - ".str_replace(ABSPATH, '', $file)." -->";
899 echo "<!-- Please check if the path above is correct and ensure your server has write permission there! -->";
900 }
901 }
902 // other css need to be requeued for the order of files to be kept
903 } else {
904 wp_dequeue_style($header[$i]['handle']);
905 wp_enqueue_style($header[$i]['handle']);
906 }
907 }
908
909 // remove from queue
910 $wp_styles->done = $done;
911 return true;
912 }
913
914 /**
915 * Process JS in the footer
916 *
917 * @return void
918 */
919 public function process_footer_scripts() {
920 global $wp_scripts;
921 if (!is_object($wp_scripts)) {
922 return;
923 }
924 $cache_path = WP_Optimize_Minify_Cache_Functions::cache_path();
925 $cache_base_dir = $cache_path['cachebasedir'];
926 $cache_dir = $cache_path['cachedir'];
927 $cache_dir_url = $cache_path['cachedirurl'];
928
929
930 $exclude_js = $this->get_excluded_js_list();
931 $ignore_list = WP_Optimize_Minify_Functions::compile_ignore_list($exclude_js);
932 $async_js = $this->get_async_js_list();
933 $scripts = clone $wp_scripts;
934 $scripts->all_deps($scripts->queue);
935 $footer = array();
936 $minify_js = $this->options['enable_js_minification'];
937 $merge_js = $this->options['enable_merging_of_js'];
938 $process_js = $minify_js || $merge_js;
939 $merge_inline_extra_css_js = $this->options['merge_inline_extra_css_js'];
940
941 // mark as done (as we go)
942 $done = $scripts->done;
943
944 $previous_load_strategy = null;
945
946 // get groups of handles
947 foreach ($scripts->to_do as $handle) :
948
949 // get full url
950 $href = WP_Optimize_Minify_Functions::get_hurl($wp_scripts->registered[$handle]->src);
951
952 // inlined scripts without file
953 if (empty($href)) {
954 continue;
955 }
956
957 // Exclude JS files from PageSpeedIndex (Async) takes priority over the ignore list
958 if (false != $async_js || is_array($async_js)) {
959
960 // check for string match
961 $skipjs = false;
962 foreach ($async_js as $l) {
963 if (stripos($href, $l) !== false) {
964 // print code if there are no linebreaks, or return
965 WP_Optimize_Minify_Print::async_script($href);
966 $skipjs = true;
967 $done = array_merge($done, array($handle));
968 break;
969 }
970 }
971 if (false != $skipjs) {
972 continue;
973 }
974 }
975
976 // IE only files don't increment things
977 $ieonly = WP_Optimize_Minify_Functions::is_url_in_ie_blacklist($href);
978 if ($ieonly) {
979 continue;
980 }
981
982 // check if the current URL points to a minified file.
983 $exclude_minified_js = WP_Optimize_Minify_Functions::is_minified_css_js_filename($href);
984 $normal_processing = $exclude_minified_js && !$merge_js;
985
986 // skip ignore list, scripts with conditionals, external scripts
987 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()))
988 || empty($href)
989 ) {
990
991 // process
992 if (isset($footer[count($footer)-1]['handle'])
993 || !count($footer)
994 || !$merge_js
995 || (isset($scripts->registered[$handle]->extra['strategy']) && $previous_load_strategy != $scripts->registered[$handle]->extra['strategy'])
996 || (!isset($scripts->registered[$handle]->extra['strategy']) && null != $previous_load_strategy)
997 ) {
998 array_push($footer, array('handles' => array(), 'versions' => array()));
999 }
1000
1001 if (isset($wp_scripts->registered[$handle]->extra['before'])) {
1002 if (!empty($footer[count($footer)-1]['handles'])) {
1003 array_push($footer, array('handles' => array(), 'versions' => array()));
1004 }
1005 }
1006
1007 // push it to the array
1008 array_push($footer[count($footer)-1]['handles'], $handle);
1009 array_push($footer[count($footer)-1]['versions'], $wp_scripts->registered[$handle]->ver);
1010
1011 // Register strategy if it is used
1012 if (isset($scripts->registered[$handle]->extra['strategy'])) $footer[count($footer)-1]['strategy'] = $scripts->registered[$handle]->extra['strategy'];
1013
1014 $footer[count($footer) - 1]['last_modified'][] = WP_Optimize_Minify_Functions::get_modification_time($wp_scripts->registered[$handle]->src);
1015 // external and ignored scripts
1016
1017 if (isset($scripts->registered[$handle]->extra['strategy'])) {
1018 $previous_load_strategy = $scripts->registered[$handle]->extra['strategy'];
1019 } else {
1020 $previous_load_strategy = null;
1021 }
1022
1023 } else {
1024 array_push($footer, array('handle' => $handle));
1025 }
1026 endforeach;
1027
1028 // loop through footer scripts and merge
1029 for ($i=0,$l=count($footer); $i<$l; $i++) {
1030
1031 if (!isset($footer[$i]['handle'])) {
1032
1033 $handles_str = is_array($footer[$i]['handles']) ? $this->array_to_string_conversion($footer[$i]['handles']) : strval($footer[$i]['handles']);
1034 $last_modified_str = is_array($footer[$i]['last_modified']) ? $this->array_to_string_conversion($footer[$i]['last_modified']) : strval($footer[$i]['last_modified']);
1035
1036 if ($merge_js) {
1037 // Change the hash based on last modified timestamp
1038 $hash = hash('adler32', $handles_str . $last_modified_str);
1039 } else {
1040 $hash = $handles_str . $last_modified_str;
1041 }
1042
1043 // static cache file info
1044 $file_name = 'wpo-minify-footer-'.$hash.($minify_js ? '.min' : '');
1045
1046 // create cache files and urls
1047 $file = $cache_dir.'/'.$file_name.'.js';
1048 $meta_json_file = $cache_base_dir.'/'.'meta.json';
1049 $file_url = WP_Optimize_Minify_Functions::get_protocol($cache_dir_url.'/'.$file_name.'.js');
1050
1051 // generate a new cache file
1052 clearstatcache();
1053 if (!file_exists($file)) {
1054
1055 // code and log initialization
1056 $log_header = "PROCESSED on ".date('r')." from ".home_url(add_query_arg(null, null));
1057 $log = array(
1058 'header' => $log_header,
1059 'files' => array()
1060 );
1061 $meta_log = $this->meta_log_initialization($log_header);
1062 $before_code = '';
1063 $code = '';
1064 $after_code = '';
1065
1066 // minify and write to file
1067 foreach ($footer[$i]['handles'] as $handle) :
1068 if (!empty($wp_scripts->registered[$handle]->src)) {
1069 // only add minified scripts once to the bundle
1070 if (in_array($handle, $done)) continue;
1071
1072 // get href per handle
1073 $handle_src = $wp_scripts->registered[$handle]->src;
1074 $href = WP_Optimize_Minify_Functions::get_hurl($handle_src);
1075 $version = $wp_scripts->registered[$handle]->ver;
1076 $last_modified = WP_Optimize_Minify_Functions::get_modification_time($handle_src);
1077 // inlined scripts without file
1078 if (empty($href)) {
1079 continue;
1080 }
1081 // download, minify, cache
1082 $tkey = 'js-'.hash('adler32', $handle . $href).'.js';
1083 $json = false;
1084 $json = WP_Optimize_Minify_Cache_Functions::get_transient($tkey);
1085 if (false === $json) {
1086 $enable_minification = $minify_js && !WP_Optimize_Minify_Functions::is_minified_css_js_filename($href);
1087 $json = WP_Optimize_Minify_Functions::download_and_minify($href, null, $enable_minification, 'js', $handle, $version);
1088 if ($this->options['debug']) {
1089 echo "<!-- wpo_min DEBUG: Uncached file processing now for $handle / $href / $version -->\n";
1090 }
1091 WP_Optimize_Minify_Cache_Functions::set_transient($tkey, $json);
1092 }
1093
1094 // decode
1095 $res = json_decode($json, true);
1096
1097 if (null === $res) continue;
1098
1099 if ($this->should_reset_minify_assets($res, $href, 'js', $handle, $version)) {
1100 WP_Optimize_Minify_Cache_Functions::reset();
1101 $this->minify_cache_incremented = true;
1102 $meta_log['invalidation_reason'] = 'Checksum Mismatch and version changed from ' . $res['request']['version'] . ' to ' . $version;
1103 $meta_log['files_that_changed'][$handle] = $res['log'];
1104 }
1105
1106 // response has failed
1107 if (true != $res['status']) {
1108 $log['files'][$handle] = $res['log'];
1109 $log['files'][$handle]['uses_loading_strategy'] = isset($footer[$i]['strategy']);
1110 $log['files'][$handle]['version'] = $version;
1111 $log['files'][$handle]['last_modified'] = $last_modified;
1112 continue;
1113 }
1114
1115 $done[] = $handle;
1116 // Add extra data from wp_add_inline_script before
1117 if (!empty($wp_scripts->registered[$handle]->extra)) {
1118 if (!empty($wp_scripts->registered[$handle]->extra['before']) && is_array($wp_scripts->registered[$handle]->extra['before'])) {
1119 if ($merge_inline_extra_css_js) {
1120 $before_code .= "\n" . implode("\n", array_filter($wp_scripts->registered[$handle]->extra['before']));
1121 }
1122 }
1123 }
1124
1125 // Add translation
1126 if (!empty($wp_scripts->registered[$handle]->textdomain)) {
1127 $code .= "\n" . $wp_scripts->print_translations($handle, false);
1128 }
1129
1130 // Add extra data from wp_add_inline_script after
1131 if (!empty($wp_scripts->registered[$handle]->extra)) {
1132 if (!empty($wp_scripts->registered[$handle]->extra['after']) && is_array($wp_scripts->registered[$handle]->extra['after'])) {
1133 if ($merge_inline_extra_css_js) {
1134 $after_code .= "\n" . implode("\n", array_filter($wp_scripts->registered[$handle]->extra['after']));
1135 }
1136 }
1137 }
1138
1139 // append code to merged file
1140 $code .= $before_code . "\n";
1141 $code .= isset($res['code']) ? $res['code'] : '';
1142 $code .= "\n" . $after_code . "\n";
1143 $code = WP_Optimize_Minify_Functions::prepare_merged_js($code, $href);
1144 $log['files'][$handle] = $res['log'];
1145 $log['files'][$handle]['uses_loading_strategy'] = isset($footer[$i]['strategy']);
1146 $log['files'][$handle]['version'] = $version;
1147 $log['files'][$handle]['last_modified'] = $last_modified;
1148
1149 // consider dependencies on handles with an empty src
1150 } else {
1151 wp_dequeue_script($handle);
1152 wp_enqueue_script($handle);
1153 }
1154 endforeach;
1155
1156 // generate cache, write log
1157 if (!empty($code)) {
1158 // Only generate meta.json file if cache is reset
1159 if (!empty($meta_log['invalidation_reason'])) {
1160 file_put_contents($meta_json_file, json_encode($meta_log));
1161 }
1162 WP_Optimize_Minify_Print::write_combined_asset($file, $code, $log);
1163 }
1164 } else {
1165 $log_file = $file.'.json';
1166 if (file_exists($log_file)) {
1167 $saved_log = json_decode(file_get_contents($log_file));
1168 if (is_object($saved_log) && property_exists($saved_log, 'files')) {
1169 $files = (array) $saved_log->files;
1170 foreach ($footer[$i]['handles'] as $handle) {
1171 if (isset($files[$handle]) && $files[$handle]->success) {
1172 $done[] = $handle;
1173 }
1174 }
1175 } else {
1176 // The merged file already exists, so add all files to $done.
1177 $done = array_merge($done, $footer[$i]['handles']);
1178 }
1179 } else {
1180 // The merged file already exists, so add all files to $done.
1181 $done = array_merge($done, $footer[$i]['handles']);
1182 }
1183 }
1184
1185 // register minified file
1186 wp_register_script("wpo_min-footer-$i", $file_url, array(), null, false);
1187
1188 // add all extra data from wp_localize_script
1189 $before_code = '';
1190 $data = array();
1191 $after_code = '';
1192
1193 foreach ($footer[$i]['handles'] as $handle) {
1194 if (isset($wp_scripts->registered[$handle]->extra['data'])) {
1195 $data[] = $wp_scripts->registered[$handle]->extra['data'];
1196 }
1197 // Add extra data from wp_add_inline_script before
1198 if (!empty($wp_scripts->registered[$handle]->extra)) {
1199 if (!empty($wp_scripts->registered[$handle]->extra['before']) && is_array($wp_scripts->registered[$handle]->extra['before'])) {
1200 if (!$merge_inline_extra_css_js) {
1201 $before_code.= "\n" . implode("\n", array_filter($wp_scripts->registered[$handle]->extra['before'])) . "\n";
1202 }
1203 }
1204 if (!empty($wp_scripts->registered[$handle]->extra['after']) && is_array($wp_scripts->registered[$handle]->extra['after'])) {
1205 if (!$merge_inline_extra_css_js) {
1206 $after_code.= "\n" . implode("\n", array_filter($wp_scripts->registered[$handle]->extra['after'])) . "\n";
1207 }
1208 }
1209 }
1210 }
1211 if (count($data) > 0) {
1212 $wp_scripts->registered["wpo_min-footer-$i"]->extra['data'] = implode("\n", $data);
1213 }
1214
1215 // enqueue file, if not empty
1216 if (file_exists($file) && (filesize($file) > 0 || count($data) > 0)) {
1217 if (!empty($before_code)) {
1218 wp_add_inline_script("wpo_min-footer-$i", $before_code, 'before');
1219 }
1220 wp_enqueue_script("wpo_min-footer-$i");
1221 if (!empty($after_code)) {
1222 wp_add_inline_script("wpo_min-footer-$i", $after_code, 'after');
1223 }
1224
1225 if (isset($footer[$i]['strategy'])) {
1226 // Backward compatible way of doing this
1227 wp_script_add_data( "wpo_min-footer-$i", 'strategy', $footer[$i]['strategy']);
1228 }
1229 } else {
1230 // file could not be generated, output something meaningful
1231 echo "<!-- ERROR: WP-Optimize Minify was not allowed to save its cache on - ".str_replace(ABSPATH, '', $file)." -->";
1232 echo "<!-- Please check if the path above is correct and ensure your server has write permission there! -->";
1233 }
1234
1235 // other scripts need to be requeued for the order of files to be kept
1236 } else {
1237 wp_dequeue_script($footer[$i]['handle']);
1238 wp_enqueue_script($footer[$i]['handle']);
1239 }
1240 }
1241
1242 // remove from queue
1243 $wp_scripts->done = $done;
1244 }
1245
1246 /**
1247 * Process header JavaScript
1248 * Dependant on 'enable_js' option
1249 *
1250 * @return void
1251 */
1252 public function process_header_scripts() {
1253 global $wp_scripts;
1254 if (!is_object($wp_scripts)) return;
1255 $cache_path = WP_Optimize_Minify_Cache_Functions::cache_path();
1256 $cache_base_dir = $cache_path['cachebasedir'];
1257 $cache_dir = $cache_path['cachedir'];
1258 $cache_dir_url = $cache_path['cachedirurl'];
1259 $exclude_js = $this->get_excluded_js_list();
1260 $ignore_list = WP_Optimize_Minify_Functions::compile_ignore_list($exclude_js);
1261 $async_js = $this->get_async_js_list();
1262 $scripts = clone $wp_scripts;
1263 $scripts->all_deps($scripts->queue);
1264 $minify_js = $this->options['enable_js_minification'];
1265 $merge_js = $this->options['enable_merging_of_js'];
1266 $process_js = $minify_js || $merge_js;
1267 $merge_inline_extra_css_js = $this->options['merge_inline_extra_css_js'];
1268 $header = array();
1269 // mark as done (as we go)
1270 $done = $scripts->done;
1271 $excluded_dependencies = array();
1272
1273 $previous_load_strategy = null;
1274
1275 // Prepare and separate assets (get groups of handles)
1276 foreach ($scripts->to_do as $handle) {
1277 // get full url
1278 $href = WP_Optimize_Minify_Functions::get_hurl($wp_scripts->registered[$handle]->src);
1279 // inlined scripts without file
1280 if (empty($href)) {
1281 wp_enqueue_script($handle, false);
1282 continue;
1283 }
1284
1285 // Only go through items without a group
1286 if (!$scripts->groups[$handle]) {
1287 // Exclude JS files from PageSpeedIndex (Async) takes priority over the ignore list
1288 // check for string match
1289 $skipjs = false;
1290 foreach ($async_js as $l) {
1291 if (stripos($href, $l) !== false) {
1292 WP_Optimize_Minify_Print::async_script($href);
1293 $skipjs = true;
1294 $done = array_merge($done, array($handle));
1295 break;
1296 }
1297 }
1298 // IE only files don't increment things
1299 if ($skipjs
1300 || WP_Optimize_Minify_Functions::is_url_in_ie_blacklist($href)
1301 ) {
1302 continue;
1303 }
1304
1305 // Skip jQuery from the merged files if deferring is ENABLED and defer_jquery is DISABLED
1306 if ('all' === $this->options['enable_defer_js']
1307 && !$this->options['defer_jquery']
1308 && (false !== stripos($href, '/jquery.js')
1309 || false !== stripos($href, '/jquery.min.js')
1310 || (false !== stripos($href, '/jquery-') && false !== stripos($href, '.js')))
1311 ) {
1312 continue;
1313 }
1314
1315 // check if the current URL points to a minified file.
1316 $exclude_minified_js = WP_Optimize_Minify_Functions::is_minified_css_js_filename($href);
1317 $normal_processing = $exclude_minified_js && !$merge_js;
1318
1319 // Group handles - skip ignore list, scripts with conditionals, external scripts
1320 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()))
1321 || empty($href)
1322 ) {
1323 // process
1324 if (isset($header[count($header)-1]['handle'])
1325 || !count($header)
1326 || !$merge_js
1327 || (isset($scripts->registered[$handle]->extra['strategy']) && $previous_load_strategy != $scripts->registered[$handle]->extra['strategy'])
1328 || (!isset($scripts->registered[$handle]->extra['strategy']) && null != $previous_load_strategy)
1329 ) {
1330 array_push($header, array('handles' => array(), 'versions' => array()));
1331 }
1332
1333 // Force loading of dependencies
1334 foreach ($wp_scripts->registered[$handle]->deps as $dep) {
1335 // If the handle is not present in $done yet, or excluded, enqueue it.
1336 $dep_href = WP_Optimize_Minify_Functions::get_hurl($wp_scripts->registered[$dep]->src);
1337 if (!in_array($dep, $done) && !WP_Optimize_Minify_Functions::in_arrayi($dep_href, $ignore_list)) {
1338 // Include any dependency
1339 array_push($header[count($header)-1]['handles'], $dep);
1340 array_push($header[count($header)-1]['versions'], $wp_scripts->registered[$dep]->ver);
1341 } elseif (!in_array($dep, $done) && WP_Optimize_Minify_Functions::in_arrayi($dep_href, $ignore_list)) {
1342 // The dependency is in the exclude list
1343 array_push($header, array('handle' => $dep));
1344 // Record dependency to be added to the minified script as dependency array
1345 if (isset($excluded_dependencies[count($header)])) {
1346 $excluded_dependencies[count($header)][] = $dep;
1347 } else {
1348 $excluded_dependencies[count($header)] = array($dep);
1349 }
1350 // Adds the 'handles' record for the main script
1351 array_push($header, array('handles' => array(), 'versions' => array()));
1352 }
1353 }
1354
1355 if (isset($wp_scripts->registered[$handle]->extra['before'])) {
1356 if (!empty($header[count($header)-1]['handles'])) {
1357 array_push($header, array('handles' => array(), 'versions' => array()));
1358 }
1359 }
1360
1361 // push it to the array
1362 array_push($header[count($header)-1]['handles'], $handle);
1363 array_push($header[count($header)-1]['versions'], $scripts->registered[$handle]->ver);
1364 $header[count($header) - 1]['last_modified'][] = WP_Optimize_Minify_Functions::get_modification_time($wp_scripts->registered[$handle]->src);
1365
1366 // Register strategy if it is used
1367 if (isset($scripts->registered[$handle]->extra['strategy'])) $header[count($header)-1]['strategy'] = $scripts->registered[$handle]->extra['strategy'];
1368
1369 if (isset($scripts->registered[$handle]->extra['strategy'])) {
1370 $previous_load_strategy = $scripts->registered[$handle]->extra['strategy'];
1371 } else {
1372 $previous_load_strategy = null;
1373 }
1374
1375
1376 // external and ignored scripts
1377 } else {
1378 // add the ignored assets
1379 array_push($header, array('handle' => $handle));
1380 }
1381
1382 // make sure that the scripts skipped here, show up in the footer
1383 } else {
1384 wp_enqueue_script($handle, $href, array(), null, true);
1385 }
1386 }
1387
1388 // loop through header scripts and merge
1389 for ($i=0,$l=count($header); $i < $l; $i++) {
1390
1391 if (!isset($header[$i]['handle'])) {
1392
1393 $handles_str = is_array($header[$i]['handles']) ? $this->array_to_string_conversion($header[$i]['handles']) : strval($header[$i]['handles']);
1394 $last_modified_str = is_array($header[$i]['last_modified']) ? $this->array_to_string_conversion($header[$i]['last_modified']) : strval($header[$i]['last_modified']);
1395
1396 if ($merge_js) {
1397 $hash = hash('adler32', $handles_str . $last_modified_str);
1398 } else {
1399 $hash = $handles_str . $last_modified_str;
1400 }
1401
1402 // static cache file info
1403 $file_name = 'wpo-minify-header-'.$hash.($minify_js ? '.min' : '');
1404 // create cache files and urls
1405 $file = $cache_dir.'/'.$file_name.'.js';
1406 $meta_json_file = $cache_base_dir.'/'.'meta.json';
1407 $file_url = WP_Optimize_Minify_Functions::get_protocol($cache_dir_url.'/'.$file_name.'.js');
1408
1409 // generate a new cache file
1410 clearstatcache();
1411 if (!file_exists($file)) {
1412
1413 // code and log initialization
1414 $log_header = "PROCESSED on ".date('r')." from ".home_url(add_query_arg(null, null));
1415 $log = array(
1416 'header' => $log_header,
1417 'files' => array()
1418 );
1419 $meta_log = $this->meta_log_initialization($log_header);
1420 $before_code = '';
1421 $code = '';
1422 $after_code = '';
1423
1424 // minify and write to file
1425 foreach ($header[$i]['handles'] as $handle) {
1426 if (!empty($wp_scripts->registered[$handle]->src)) {
1427 // only add minified scripts once to the bundle
1428 if (in_array($handle, $done)) continue;
1429
1430 // get href per handle
1431 $handle_src = $wp_scripts->registered[$handle]->src;
1432 $href = WP_Optimize_Minify_Functions::get_hurl($handle_src);
1433 $version = $wp_scripts->registered[$handle]->ver;
1434 $last_modified = WP_Optimize_Minify_Functions::get_modification_time($handle_src);
1435 if (empty($href)) continue;
1436 // download, minify, cache
1437 $tkey = 'js-'.hash('adler32', $handle . $href).'.js';
1438 $json = false;
1439 $json = WP_Optimize_Minify_Cache_Functions::get_transient($tkey);
1440 if (false === $json) {
1441 $enable_minification = $minify_js && !WP_Optimize_Minify_Functions::is_minified_css_js_filename($href);
1442 $json = WP_Optimize_Minify_Functions::download_and_minify($href, null, $enable_minification, 'js', $handle, $version);
1443 if ($this->options['debug']) {
1444 echo "<!-- wpo_min DEBUG: Uncached file processing now for $handle / $href / $version -->" . "\n";
1445 }
1446 WP_Optimize_Minify_Cache_Functions::set_transient($tkey, $json);
1447 }
1448
1449 // decode
1450 $res = json_decode($json, true);
1451
1452 if (null === $res) continue;
1453
1454 if ($this->should_reset_minify_assets($res, $href, 'js', $handle, $version)) {
1455 WP_Optimize_Minify_Cache_Functions::reset();
1456 $this->minify_cache_incremented = true;
1457 $meta_log['invalidation_reason'] = 'Checksum Mismatch and version changed from ' . $res['request']['version'] . ' to ' . $version;
1458 $meta_log['files_that_changed'][$handle] = $res['log'];
1459 }
1460
1461 // response has failed
1462 if (true != $res['status']) {
1463 $log['files'][$handle] = $res['log'];
1464 $log['files'][$handle]['uses_loading_strategy'] = isset($header[$i]['strategy']);
1465 $log['files'][$handle]['version'] = $version;
1466 $log['files'][$handle]['last_modified'] = $last_modified;
1467 continue;
1468 }
1469
1470 // Add extra data from wp_add_inline_script before
1471 if (!empty($wp_scripts->registered[$handle]->extra)) {
1472 if (!empty($wp_scripts->registered[$handle]->extra['before']) && is_array($wp_scripts->registered[$handle]->extra['before'])) {
1473 if ($merge_inline_extra_css_js) {
1474 $before_code .= "\n" . implode("\n", array_filter($wp_scripts->registered[$handle]->extra['before']));
1475 }
1476 }
1477 }
1478
1479 // Only add the $handle to $done if it was successfully downloaded
1480 $done[] = $handle;
1481
1482 // Add translations
1483 if (!empty($wp_scripts->registered[$handle]->textdomain)) {
1484 $code .= "\n" . $wp_scripts->print_translations($handle, false);
1485 }
1486
1487 // Add extra data from wp_add_inline_script after
1488 if (!empty($wp_scripts->registered[$handle]->extra)) {
1489 if (!empty($wp_scripts->registered[$handle]->extra['after']) && is_array($wp_scripts->registered[$handle]->extra['after'])) {
1490 if ($merge_inline_extra_css_js) {
1491 $after_code.= "\n" . implode("\n", array_filter($wp_scripts->registered[$handle]->extra['after']));
1492 }
1493 }
1494 }
1495
1496 // append code to merged file
1497 $code .= $before_code . "\n";
1498 $code .= isset($res['code']) ? $res['code'] : '';
1499 $code .= "\n" . $after_code . "\n";
1500 $code = WP_Optimize_Minify_Functions::prepare_merged_js($code, $href);
1501 $log['files'][$handle] = $res['log'];
1502 $log['files'][$handle]['uses_loading_strategy'] = isset($header[$i]['strategy']);
1503 $log['files'][$handle]['version'] = $version;
1504 $log['files'][$handle]['last_modified'] = $last_modified;
1505
1506 // consider dependencies on handles with an empty src
1507 } else {
1508 wp_dequeue_script($handle);
1509 wp_enqueue_script($handle);
1510 }
1511 }
1512
1513 // generate cache, write log
1514 if (!empty($code)) {
1515 // Only generate meta.json file if cache is reset
1516 if (!empty($meta_log['invalidation_reason'])) {
1517 file_put_contents($meta_json_file, json_encode($meta_log));
1518 }
1519 WP_Optimize_Minify_Print::write_combined_asset($file, $code, $log);
1520 }
1521 } else {
1522 $log_file = $file.'.json';
1523 if (file_exists($log_file)) {
1524 $saved_log = json_decode(file_get_contents($log_file));
1525 if (is_object($saved_log) && property_exists($saved_log, 'files')) {
1526 $files = (array) $saved_log->files;
1527 foreach ($header[$i]['handles'] as $handle) {
1528 $handle = is_array($handle) ? '' : $handle;
1529 if (isset($files[$handle]) && $files[$handle]->success) {
1530 $done[] = $handle;
1531 }
1532 }
1533 } else {
1534 // The merged file already exists, so add all files to $done.
1535 $done = array_merge($done, $header[$i]['handles']);
1536 }
1537 } else {
1538 // The merged file already exists, so add all files to $done.
1539 $done = array_merge($done, $header[$i]['handles']);
1540 }
1541 }
1542
1543 // register minified file
1544 $dependencies = isset($excluded_dependencies[$i]) ? $excluded_dependencies[$i] : array();
1545 wp_register_script("wpo_min-header-$i", $file_url, $dependencies, null, false);
1546
1547 // add all extra data from wp_localize_script
1548 $before_code = '';
1549 $data = array();
1550 $after_code = '';
1551
1552 foreach ($header[$i]['handles'] as $handle) {
1553 if (isset($wp_scripts->registered[$handle]->extra['data'])) {
1554 $data[] = $wp_scripts->registered[$handle]->extra['data'];
1555 }
1556
1557 // Add extra data from wp_add_inline_script before
1558 if (!empty($wp_scripts->registered[$handle]->extra)) {
1559 if (!empty($wp_scripts->registered[$handle]->extra['before']) && is_array($wp_scripts->registered[$handle]->extra['before'])) {
1560 if (!$merge_inline_extra_css_js) {
1561 $before_code.= "\n" . implode("\n", array_filter($wp_scripts->registered[$handle]->extra['before'])) . "\n";
1562 }
1563 }
1564 if (!empty($wp_scripts->registered[$handle]->extra['after']) && is_array($wp_scripts->registered[$handle]->extra['after'])) {
1565 if (!$merge_inline_extra_css_js) {
1566 $after_code.= "\n" . implode("\n", array_filter($wp_scripts->registered[$handle]->extra['after'])) . "\n";
1567 }
1568 }
1569 }
1570 }
1571 if (count($data) > 0) {
1572 $wp_scripts->registered["wpo_min-header-$i"]->extra['data'] = implode("\n", $data);
1573 }
1574
1575 // enqueue file, if not empty
1576 if (file_exists($file) && (filesize($file) > 0 || count($data) > 0)) {
1577 if (!empty($before_code)) {
1578 wp_add_inline_script("wpo_min-header-$i", $before_code, 'before');
1579 }
1580 wp_enqueue_script("wpo_min-header-$i");
1581 if (!empty($after_code)) {
1582 wp_add_inline_script("wpo_min-header-$i", $after_code, 'after');
1583 }
1584
1585 if (isset($header[$i]['strategy'])) {
1586 // Backward compatible way of doing this
1587 wp_script_add_data( "wpo_min-header-$i", 'strategy', $header[$i]['strategy']);
1588 }
1589 } else {
1590 // file could not be generated, output something meaningful
1591 echo "<!-- ERROR: WP-Optimize minify was not allowed to save its cache on - ".str_replace(ABSPATH, '', $file)." -->";
1592 echo "<!-- Please check if the path above is correct and ensure your server has write permission there! -->";
1593 echo "<!-- If you found a bug, please report this on https://wordpress.org/support/plugin/wp-optimize/ -->";
1594 }
1595
1596 // other scripts need to be requeued for the order of files to be kept
1597 } else {
1598 wp_dequeue_script($header[$i]['handle']);
1599 wp_enqueue_script($header[$i]['handle']);
1600 }
1601 }
1602
1603 // remove from queue
1604 $wp_scripts->done = $done;
1605 }
1606
1607 /**
1608 * Process CSS in the footer
1609 *
1610 * @return void
1611 */
1612 public function process_footer_css() {
1613 global $wp_styles;
1614 if (!is_object($wp_styles)) return;
1615 $cache_path = WP_Optimize_Minify_Cache_Functions::cache_path();
1616 $cache_base_dir = $cache_path['cachebasedir'];
1617 $cache_dir = $cache_path['cachedir'];
1618 $cache_dir_url = $cache_path['cachedirurl'];
1619 $exclude_css = $this->get_excluded_css_list();
1620 $ignore_list = WP_Optimize_Minify_Functions::compile_ignore_list($exclude_css);
1621 $async_css = $this->get_async_css_list();
1622 $minify_css = $this->options['enable_css_minification'];
1623 $merge_css = $this->options['enable_merging_of_css'];
1624 $process_css = $minify_css || $merge_css;
1625 $disable_google_fonts_processing = $this->options['disable_google_fonts_processing'];
1626 $log = "";
1627 $code = "";
1628 $styles = clone $wp_styles;
1629 $styles->all_deps($styles->queue);
1630 $done = $styles->done;
1631 $footer = array();
1632 $google_fonts = array();
1633 $inline_css = array();
1634
1635 // dequeue all styles
1636 if (isset($this->options['remove_css']) && $this->options['remove_css']) {
1637 foreach ($styles->to_do as $handle) :
1638 $done = array_merge($done, array($handle));
1639 endforeach;
1640
1641 // remove from queue
1642 $wp_styles->done = $done;
1643 return;
1644 }
1645
1646 // dequeue and get a list of google fonts, or requeue external
1647 foreach ($styles->to_do as $handle) {
1648 $href = WP_Optimize_Minify_Functions::get_hurl($wp_styles->registered[$handle]->src);
1649 // inlined scripts without file
1650 if (empty($href)) continue;
1651
1652 if (!$disable_google_fonts_processing && WP_Optimize_Minify_Functions::is_google_font($href)) {
1653 wp_dequeue_style($handle);
1654 if ($this->options['remove_googlefonts']) {
1655 $done = array_merge($done, array($handle));
1656 continue;
1657 }
1658 // mark as done if to be removed
1659 if ($this->options['merge_google_fonts']
1660 || 'inline' === $this->options['gfonts_method']
1661 ) {
1662 if (WP_Optimize_Minify_Functions::is_flatsome_handle($handle)) {
1663 $href = WP_Optimize_Minify_Functions::fix_flatsome_google_fonts_url($href);
1664 $google_fonts[$handle] = $href;
1665 } else {
1666 $google_fonts[$handle] = $href;
1667 }
1668 } else {
1669 // skip google fonts optimization?
1670 wp_enqueue_style($handle);
1671 }
1672 } else {
1673 // failsafe
1674 wp_dequeue_style($handle);
1675 wp_enqueue_style($handle);
1676 }
1677
1678 }
1679
1680 // concat google fonts, if enabled
1681 if ($this->options['merge_google_fonts']
1682 && count($google_fonts) > 0
1683 || ('inline' === $this->options['gfonts_method'] && count($google_fonts) > 0)
1684 ) {
1685
1686 // merge google fonts if force inlining is enabled?
1687 $nfonts = array();
1688 if ($this->options['merge_google_fonts']) {
1689 $nfonts[] = WP_Optimize_Minify_Fonts::concatenate_google_fonts($google_fonts);
1690 } else {
1691 foreach ($google_fonts as $h => $a) {
1692 if (!empty($a)) {
1693 $nfonts[$h] = $a;
1694 }
1695 }
1696 }
1697
1698 // foreach google font (will be one if merged is not disabled)
1699 if (count($nfonts) > 0) {
1700 foreach ($nfonts as $handle => $href) {
1701 // hide from PageSpeedIndex, async, inline, or default
1702 if ('exclude' === $this->options['gfonts_method']) {
1703 WP_Optimize_Minify_Print::exclude_style($href);
1704 $done = array_merge($done, array($handle));
1705 } elseif ('async' === $this->options['gfonts_method']) {
1706 // async CSS
1707 WP_Optimize_Minify_Print::async_style($href);
1708 $done = array_merge($done, array($handle));
1709 } elseif ('inline' === $this->options['gfonts_method']) {
1710 // inline css
1711 WP_Optimize_Minify_Print::inline_style($handle, $href);
1712 $done = array_merge($done, array($handle));
1713 } else {
1714 // fallback, enqueue google fonts
1715 wp_enqueue_style($handle, $href, array(), null, 'all');
1716 }
1717 }
1718 }
1719 }
1720
1721
1722 // get groups of handles
1723 $uniq = array();
1724 foreach ($styles->to_do as $handle) {
1725
1726 // skip already processed google fonts
1727 if (isset($google_fonts[$handle])) {
1728 continue;
1729 }
1730
1731 // conditionals
1732 $conditional = null; if (isset($wp_styles->registered[$handle]->extra["conditional"])) {
1733 $conditional = $wp_styles->registered[$handle]->extra["conditional"]; // such as ie7, ie8, ie9, etc
1734 }
1735
1736 // mediatype
1737 $mt = isset($wp_styles->registered[$handle]->args) ? $wp_styles->registered[$handle]->args : 'all';
1738 if ('screen' == $mt || 'screen, print' == $mt || empty($mt) || is_null($mt) || false == $mt) {
1739 $mt = 'all';
1740 }
1741 $mediatype = $mt;
1742
1743 // get full url
1744 $href = WP_Optimize_Minify_Functions::get_hurl($wp_styles->registered[$handle]->src);
1745 $version = $wp_styles->registered[$handle]->ver;
1746
1747 // inlined scripts without file
1748 if (empty($href)) {
1749 continue;
1750 }
1751
1752 // mark duplicates as done and remove from the queue
1753 if (!empty($href)) {
1754 $version = is_array($version) ? '' : $version;
1755 $key = hash('adler32', $href . $version);
1756 if (isset($uniq[$key])) {
1757 $done = array_merge($done, array($handle));
1758 continue;
1759 } else {
1760 $uniq[$key] = $handle;
1761 }
1762 }
1763
1764 // IE only files don't increment things
1765 $ieonly = WP_Optimize_Minify_Functions::is_url_in_ie_blacklist($href);
1766 if ($ieonly) {
1767 continue;
1768 }
1769
1770 // Exclude specific CSS files from PageSpeedIndex?
1771 if (false != $async_css && is_array($async_css) && WP_Optimize_Minify_Functions::in_arrayi($href, $async_css)) {
1772 WP_Optimize_Minify_Print::exclude_style($href);
1773 $done = array_merge($done, array($handle));
1774 continue;
1775 }
1776
1777 if (WP_Optimize_Minify_Functions::is_font_awesome($href)) {
1778 if ('inline' === $this->options['fawesome_method']) {
1779 WP_Optimize_Minify_Print::inline_style($handle, $href);
1780 $done = array_merge($done, array($handle));
1781 continue;
1782 } elseif ('async' === $this->options['fawesome_method']) {
1783 WP_Optimize_Minify_Print::async_style($href, $mediatype);
1784 $done = array_merge($done, array($handle));
1785 continue;
1786 } elseif ('exclude' === $this->options['fawesome_method']) {
1787 WP_Optimize_Minify_Print::exclude_style($href);
1788 $done = array_merge($done, array($handle));
1789 continue;
1790 }
1791 }
1792
1793 // Exclude Print mediatype
1794 if ($this->options['remove_print_mediatypes'] && 'print' === $mediatype) {
1795 $done = array_merge($done, array($handle));
1796 continue;
1797 }
1798
1799 // check if the current URL points to a minified file.
1800 $exclude_minified_css = WP_Optimize_Minify_Functions::is_minified_css_js_filename($href);
1801 $normal_processing = $exclude_minified_css && !$merge_css;
1802 // skip ignore list, conditional css, external css, font-awesome merge and already minified css
1803 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()))
1804 || empty($href)
1805 ) {
1806
1807 // colect inline css for this handle
1808 if (isset($wp_styles->registered[$handle]->extra['after']) && is_array($wp_styles->registered[$handle]->extra['after'])) {
1809 $inline_css[$handle] = WP_Optimize_Minify_Functions::minify_css_string(implode('', $wp_styles->registered[$handle]->extra['after'])); // save
1810 $wp_styles->registered[$handle]->extra['after'] = null; // dequeue
1811 }
1812
1813 // process
1814 if (isset($footer[count($footer)-1]['handle']) || !count($footer) || $footer[count($footer)-1]['media'] != $wp_styles->registered[$handle]->args || !$merge_css) {
1815 array_push($footer, array('handles' => array(), 'media' => $mediatype, 'versions' => array()));
1816 }
1817
1818 // push it to the array get latest modified time
1819 array_push($footer[count($footer)-1]['handles'], $handle);
1820 array_push($footer[count($footer)-1]['versions'], $version);
1821 $footer[count($footer) - 1]['last_modified'][] = WP_Optimize_Minify_Functions::get_modification_time($wp_styles->registered[$handle]->src);
1822
1823 // external and ignored css
1824 } else {
1825
1826 // normal enqueueing
1827 array_push($footer, array('handle' => $handle));
1828 }
1829 }
1830
1831 /**
1832 * Filters the array of stylesheets before processing them
1833 *
1834 * @param array $list - The list of items filtered
1835 * @param string $location - The location of the list (footer or header)
1836 * @return array
1837 */
1838 $footer = apply_filters('wpo_minify_stylesheets', $footer, 'footer');
1839
1840 // loop through footer css and merge
1841 for ($i=0,$l=count($footer); $i<$l; $i++) {
1842
1843 if (!isset($footer[$i]['handle'])) {
1844
1845 $handles_str = is_array($footer[$i]['handles']) ? $this->array_to_string_conversion($footer[$i]['handles']) : strval($footer[$i]['handles']);
1846 $last_modified_str = is_array($footer[$i]['last_modified']) ? $this->array_to_string_conversion($footer[$i]['last_modified']) : strval($footer[$i]['last_modified']);
1847
1848 if ($merge_css) {
1849 // get hash for the inline css in this group
1850 $inline_css_group = array();
1851 if (is_array($footer[$i]['handles'])) {
1852 foreach ($footer[$i]['handles'] as $h) {
1853 if (isset($inline_css[$h]) && !empty($inline_css[$h])) {
1854 $inline_css_group[] = $inline_css[$h];
1855 }
1856 }
1857 }
1858 $inline_css_hash = md5(implode('', $inline_css_group));
1859 $hash = hash('adler32', $handles_str . $inline_css_hash . $last_modified_str);
1860 } else {
1861 $hash = $handles_str . $last_modified_str;
1862 }
1863
1864 // static cache file info
1865 $file_name = 'wpo-minify-footer-'.$hash.($minify_css ? '.min' : '');
1866
1867 // create cache files and urls
1868 $file = $cache_dir.'/'.$file_name.'.css';
1869 $meta_json_file = $cache_base_dir.'/'.'meta.json';
1870 $file_url = WP_Optimize_Minify_Functions::get_protocol($cache_dir_url.'/'.$file_name.'.css');
1871
1872 // generate a new cache file
1873 clearstatcache();
1874 if (!file_exists($file)) {
1875
1876 // code and log initialization
1877 $log_header = "PROCESSED on ".date('r')." from ".home_url(add_query_arg(null, null));
1878 $log = array(
1879 'header' => $log_header,
1880 'files' => array()
1881 );
1882 $meta_log = $this->meta_log_initialization($log_header);
1883 $code = '';
1884
1885 // minify and write to file
1886 foreach ($footer[$i]['handles'] as $handle) {
1887 if (!empty($wp_styles->registered[$handle]->src)) {
1888
1889 // get href per handle
1890 $handle_src = $wp_styles->registered[$handle]->src;
1891 $href = WP_Optimize_Minify_Functions::get_hurl($handle_src);
1892 $version = $wp_styles->registered[$handle]->ver;
1893 $last_modified = WP_Optimize_Minify_Functions::get_modification_time($handle_src);
1894 // inlined scripts without hreffile
1895 if (empty($href)) continue;
1896
1897 // download, minify, cache
1898 $tkey = 'css-'.hash('adler32', $handle . $href).'.css';
1899 $json = false;
1900 $json = WP_Optimize_Minify_Cache_Functions::get_transient($tkey);
1901 if (false === $json) {
1902 $enable_minification = $minify_css && !WP_Optimize_Minify_Functions::is_minified_css_js_filename($href);
1903 $json = WP_Optimize_Minify_Functions::download_and_minify($href, null, $enable_minification, 'css', $handle, $version);
1904 if ($this->options['debug']) {
1905 echo "<!-- wpo_min DEBUG: Uncached file processing now for $handle / $href / $version -->" . "\n";
1906 }
1907 WP_Optimize_Minify_Cache_Functions::set_transient($tkey, $json);
1908 }
1909
1910 // decode
1911 $res = json_decode($json, true);
1912
1913 if (null === $res) continue;
1914
1915 if ($this->should_reset_minify_assets($res, $href, 'css', $handle, $version)) {
1916 WP_Optimize_Minify_Cache_Functions::reset();
1917 $this->minify_cache_incremented = true;
1918 $meta_log['invalidation_reason'] = 'Checksum Mismatch and version changed from ' . $res['request']['version'] . ' to ' . $version;
1919 $meta_log['files_that_changed'][$handle] = $res['log'];
1920 }
1921
1922 // response has failed
1923 if (true != $res['status']) {
1924 $log['files'][$handle] = $res['log'];
1925 $log['files'][$handle]['version'] = $version;
1926 $log['files'][$handle]['last_modified'] = $last_modified;
1927 continue;
1928 }
1929
1930 $done[] = $handle;
1931
1932 // append code to merged file
1933 $code .= isset($res['code']) ? $res['code'] : '';
1934 $log['files'][$handle] = $res['log'];
1935 $log['files'][$handle]['version'] = $version;
1936 $log['files'][$handle]['last_modified'] = $last_modified;
1937
1938 // append inlined styles
1939 if (isset($inline_css[$handle]) && !empty($inline_css[$handle])) {
1940 $code.= $inline_css[$handle];
1941 }
1942
1943 // consider dependencies on handles with an empty src
1944 } else {
1945 wp_dequeue_script($handle);
1946 wp_enqueue_script($handle);
1947 }
1948 };
1949
1950 // generate cache, add inline css, write log
1951 if (!empty($code)) {
1952 // Only generate meta.json file if cache is reset
1953 if (!empty($meta_log['invalidation_reason'])) {
1954 file_put_contents($meta_json_file, json_encode($meta_log));
1955 }
1956 WP_Optimize_Minify_Print::write_combined_asset($file, $code, $log);
1957 }
1958 } else {
1959 $log_file = $file.'.json';
1960 if (file_exists($log_file)) {
1961 $saved_log = json_decode(file_get_contents($log_file));
1962 if (is_object($saved_log) && property_exists($saved_log, 'files')) {
1963 $files = (array) $saved_log->files;
1964 foreach ($footer[$i]['handles'] as $handle) {
1965 if (isset($files[$handle]) && $files[$handle]->success) {
1966 $done[] = $handle;
1967 }
1968 }
1969 } else {
1970 // The merged file already exists, so add all files to $done.
1971 $done = array_merge($done, $footer[$i]['handles']);
1972 }
1973 } else {
1974 // The merged file already exists, so add all files to $done.
1975 $done = array_merge($done, $footer[$i]['handles']);
1976 }
1977 }
1978
1979 // Async CSS with loadCSS ?
1980 if ($this->options['loadcss'] && !$this->options['remove_css']) {
1981 $mt = $footer[$i]['media'];
1982 WP_Optimize_Minify_Print::async_style($file_url, $mt);
1983 // enqueue file, if not empty
1984 } else {
1985 if (file_exists($file) && filesize($file) > 0) {
1986
1987 // inline if the file is smaller than 20KB or option has been enabled
1988 if (filesize($file) < 20000 && $this->options['inline_css']) {
1989 $this->inline_css(file_get_contents($file), $handle, $file_url, $footer[$i]['media']);
1990 } else {
1991 // enqueue it
1992 wp_enqueue_style("wpo_min-footer-$i", $file_url, array(), null, $footer[$i]['media']);
1993 }
1994 } else {
1995 // file could not be generated, output something meaningful
1996 echo "<!-- ERROR: WP-Optimize Minify was not allowed to save its cache on - ".str_replace(ABSPATH, '', $file)." -->";
1997 echo "<!-- Please check if the path above is correct and ensure your server has write permission there! -->";
1998 }
1999 }
2000
2001 // other css need to be requeued for the order of files to be kept
2002 } else {
2003 wp_dequeue_style($footer[$i]['handle']);
2004 wp_enqueue_style($footer[$i]['handle']);
2005 }
2006 }
2007
2008 // remove from queue
2009 $wp_styles->done = $done;
2010 }
2011
2012 /**
2013 * Process JS Scripts of type module
2014 *
2015 * @param String $buffer Page HTML.
2016 *
2017 * @return String
2018 */
2019 public function process_script_modules($buffer) {
2020
2021 if ('' === $buffer) return '';
2022
2023 if (!WP_Optimize_Utils::is_valid_html($buffer)) return $buffer;
2024
2025 $minify_js = $this->options['enable_js_minification'];
2026 $cache_path = WP_Optimize_Minify_Cache_Functions::cache_path();
2027 $cache_dir = $cache_path['cachedir'];
2028 $cache_dir_url = $cache_path['cachedirurl'];
2029 $cache_base_dir = $cache_path['cachebasedir'];
2030
2031 $scripts = $this->get_scripts($buffer);
2032 if (empty($scripts)) return $buffer;
2033
2034 foreach ($scripts as $script) {
2035 if ("module" !== $script['type']) continue;
2036
2037 $href = WP_Optimize_Minify_Functions::get_hurl($script['url']);
2038 if (WP_Optimize_Minify_Functions::is_minified_css_js_filename($href)) continue;
2039
2040 $last_modified = WP_Optimize_Minify_Functions::get_modification_time($script['url']);
2041 $handle = $script['handle'];
2042 $version = $script['version'];
2043
2044 // Module script handles use slashes and @ symbols,
2045 // replace them with '-' and empty string, break if handle is still ''
2046 if (!empty($handle)) {
2047 $handle_no_slash = str_replace(array('/', '@'), array('-', ''), $handle);
2048 } else {
2049 continue;
2050 }
2051
2052 // code and log initialization
2053 $log_header = "PROCESSED on ".date('r')." from ".home_url(add_query_arg(null, null));
2054 $log = array(
2055 'header' => $log_header,
2056 'files' => array()
2057 );
2058 $meta_log = $this->meta_log_initialization($log_header);
2059 $code = '';
2060
2061 // create cache files and urls
2062 $file_name = 'wpo-minify-'.$handle_no_slash.($minify_js ? '.min' : '');
2063 $file = $cache_dir.'/'.$file_name.'.js';
2064 $meta_json_file = $cache_base_dir.'/'.'meta.json';
2065 $file_url = WP_Optimize_Minify_Functions::get_protocol($cache_dir_url.'/'.$file_name.'.js');
2066
2067 // download, minify, cache
2068 $tkey = 'js-'.hash('adler32', $handle_no_slash . $href).'.js';
2069 $json = WP_Optimize_Minify_Cache_Functions::get_transient($tkey);
2070 if (false === $json) {
2071 $enable_minification = $minify_js && !WP_Optimize_Minify_Functions::is_minified_css_js_filename($href);
2072 $json = WP_Optimize_Minify_Functions::download_and_minify($href, null, $enable_minification, 'js', $handle_no_slash, $version);
2073 if ($this->options['debug']) {
2074 echo "<!-- wpo_min DEBUG: Uncached file processing now for $handle / $href / $version -->\n";
2075 }
2076 WP_Optimize_Minify_Cache_Functions::set_transient($tkey, $json);
2077 }
2078
2079 // decode
2080 $res = json_decode($json, true);
2081
2082 if (null === $res) continue;
2083
2084 // Reset minified script module
2085 if ($this->should_reset_minify_assets($res, $href, 'js', $handle, $version)) {
2086 WP_Optimize_Minify_Cache_Functions::reset();
2087 $this->minify_cache_incremented = true;
2088 $meta_log['invalidation_reason'] = 'Checksum Mismatch and version changed from ' . $res['request']['version'] . ' to ' . $version;
2089 $meta_log['files_that_changed'][$handle] = $res['log'];
2090 }
2091
2092 if (!$res['status']) continue;
2093
2094 $code .= isset($res['code']) ? $res['code'] : '';
2095 $code = WP_Optimize_Minify_Functions::prepare_merged_js($code, $href);
2096 $log['files'][$handle] = $res['log'];
2097 $log['files'][$handle]['version'] = $version;
2098 $log['files'][$handle]['last_modified'] = $last_modified;
2099
2100 // If code is empty (nothing to minify) break loop
2101 if (empty($code)) continue;
2102
2103 // Only generate meta.json file if cache is reset
2104 if (!empty($meta_log['invalidation_reason'])) {
2105 file_put_contents($meta_json_file, json_encode($meta_log));
2106 }
2107 WP_Optimize_Minify_Print::write_combined_asset($file, $code, $log);
2108
2109 // Extract importmap script tag
2110 $json_map = $this->get_script_module_importmap($buffer);
2111 $json_map_update = $json_map;
2112
2113 // Update module source file and importmap with new file url (if necessary)
2114 $buffer = str_replace($script['url'], $file_url, $buffer);
2115 if (!empty($json_map) && isset($json_map['imports'][$handle])) {
2116 $json_map_update['imports'][$handle] = $file_url;
2117 $buffer = str_replace(json_encode($json_map), json_encode($json_map_update), $buffer);
2118 }
2119 }
2120
2121 return $buffer;
2122 }
2123
2124 /**
2125 * Orders the CSS per media type
2126 *
2127 * @param array $list - The list of assets
2128 * @return array
2129 */
2130 public function order_stylesheets_per_media_type($list) {
2131 // get unique mediatypes
2132 $allmedia = array();
2133 foreach ($list as $array) {
2134 if (isset($array['media'])) {
2135 $allmedia[$array['media']] = '';
2136 }
2137 }
2138
2139 // extract handles by mediatype
2140 $grouphandles = array();
2141 foreach ($allmedia as $md => $var) {
2142 foreach ($list as $array) {
2143 if (isset($array['media']) && $array['media'] === $md) {
2144 foreach ($array['handles'] as $h) {
2145 $grouphandles[$md][] = $h;
2146 }
2147 }
2148 }
2149 }
2150
2151 // reset and reorder list by mediatypes
2152 $newlist = array();
2153 foreach ($allmedia as $md => $var) {
2154 $newlist[] = array('handles' => $grouphandles[$md], 'media' => $md);
2155 }
2156 if (count($newlist) > 0) {
2157 $list = $newlist;
2158 }
2159
2160 return $list;
2161 }
2162
2163 /**
2164 * Merged google font
2165 *
2166 * @return void;
2167 */
2168 public function add_google_fonts_merged() {
2169 // must have something to do
2170 if (count($this->collect_google_fonts) == 0) return;
2171
2172 // merge google fonts
2173 $href = WP_Optimize_Minify_Fonts::concatenate_google_fonts($this->collect_google_fonts);
2174 if (empty($href)) {
2175 return;
2176 }
2177
2178 // hide google fonts from PageSpeedIndex
2179 if ('exclude' === $this->options['gfonts_method']) {
2180 // make a stylesheet, hide from PageSpeedIndex
2181 WP_Optimize_Minify_Print::exclude_style($href);
2182 } elseif ('async' === $this->options['gfonts_method']) {
2183 // load google fonts async
2184 WP_Optimize_Minify_Print::async_style($href);
2185 } else {
2186 // fallback to normal inline
2187 WP_Optimize_Minify_Print::style($href);
2188 }
2189
2190 // unset per hook
2191 foreach ($this->collect_google_fonts as $k => $v) {
2192 unset($this->collect_google_fonts[$k]);
2193 }
2194 }
2195
2196 /**
2197 * Check if the string given contains 'wpo-minify'
2198 *
2199 * @param string $var
2200 *
2201 * @return boolean
2202 */
2203 public function check_wpo($var) {
2204 return (false === strpos($var, 'assets/wpo-minify'));
2205 }
2206
2207 /**
2208 * Generate preload headers file
2209 *
2210 * @return boolean
2211 */
2212 public function generate_preload_headers() {
2213
2214 // always false for admin pages
2215 if (is_admin()) return false;
2216
2217 // get host with multisite support and query strings
2218 $host = htmlentities($_SERVER['SERVER_NAME']);
2219 if (empty($host)) {
2220 $host = isset($_SERVER['HTTP_HOST']) ? htmlentities($_SERVER['HTTP_HOST']) : '';
2221 }
2222 $request_query = parse_url($_SERVER['REQUEST_URI'], PHP_URL_QUERY);
2223 $request_uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
2224
2225 // initialize headers
2226 $headers = array();
2227
2228 // css headers
2229 if ($this->options['enabled_css_preload']
2230 && count($this->collect_preload_css) > 0
2231 ) {
2232 foreach ($this->collect_preload_css as $u) {
2233 // filter out footer footer files, because they are not in the critical path
2234 if (strpos($u, '/assets/wpo-minify-footer-') !== false) {
2235 continue;
2236 }
2237
2238 // add headers
2239 $headers[] = "Link: <$u>; rel=preload; as=style";
2240 }
2241 }
2242
2243 // js headers
2244 if ($this->options['enabled_js_preload']
2245 && count($this->collect_preload_js) > 0
2246 ) {
2247 foreach ($this->collect_preload_js as $u) {
2248
2249 // filter out footer footer files, because they are not in the critical path
2250 if (false !== strpos($u, '/assets/wpo-minify-footer-')) {
2251 continue;
2252 }
2253
2254 // add headers
2255 $headers[] = "Link: <$u>; rel=preload; as=script";
2256 }
2257 }
2258
2259 // must have something
2260 if (count($headers) == 0) {
2261 return false;
2262 } else {
2263 $headers = implode("\n", $headers);
2264 }
2265
2266 // get cache path
2267 $cache_path = WP_Optimize_Minify_Cache_Functions::cache_path();
2268 $header_dir = $cache_path['headerdir'];
2269
2270 // possible cache file locations
2271 $b = $header_dir . '/' . md5($host.'-'.$request_uri) . '.header';
2272 $a = $header_dir . '/' . md5($host.'-'.$request_uri . $request_query).'.header';
2273
2274 // reset file cache
2275 clearstatcache();
2276
2277 // if there are no query strings
2278 if ($b == $a) {
2279 if (!file_exists($a)) {
2280 WP_Optimize_Minify_Print::write_header($a, $headers);
2281 }
2282 return false;
2283 } elseif (!file_exists($b)) {
2284 WP_Optimize_Minify_Print::write_header($b, $headers);
2285 }
2286
2287 return false;
2288 }
2289
2290 /**
2291 * Collect all wpo_min JS files and save them to a headers file
2292 *
2293 * @param String $html
2294 * @param String $handle
2295 * @param String $src
2296 *
2297 * @return String
2298 */
2299 public function collect_js_preload_headers($html, $handle, $src) {
2300 if (false !== strpos($src, 'assets/wpo-minify')) {
2301 if (!in_array($src, $this->collect_preload_js)) {
2302 $this->collect_preload_js[] = $src;
2303 }
2304 }
2305 return $html . "\n";
2306 }
2307
2308 /**
2309 * Collect all wpo_min CSS files and save them to a headers file
2310 * this function intercepts the styles that are enqueued
2311 * take note that if the css is loaded ASYNC, it's not enqueued
2312 *
2313 * @param string $html - Html string
2314 * @param string $handle - Handle
2315 * @param string $src - The CSS file path
2316 *
2317 * @return string
2318 */
2319 public function collect_css_preload_headers($html, $handle, $src) {
2320 if (false !== strpos($src, 'assets/wpo-minify')) {
2321 if (!in_array($src, $this->collect_preload_css)) {
2322 $this->collect_preload_css[] = $src;
2323 }
2324 }
2325 return $html . "\n";
2326 }
2327
2328 /**
2329 * Get current headers file for the url
2330 *
2331 * @return bool|string
2332 */
2333 private function get_preload_headers() {
2334
2335 // always false for admin pages
2336 if (is_admin()) return false;
2337
2338 $enabled_css_preload = $this->options['enabled_css_preload'];
2339 $enabled_js_preload = $this->options['enabled_js_preload'];
2340
2341 if (!$enabled_css_preload && !$enabled_js_preload) {
2342 return false;
2343 }
2344
2345 // get host with multisite support and query strings
2346 $host = htmlentities($_SERVER['SERVER_NAME']);
2347 if (empty($host)) {
2348 $host = isset($_SERVER['HTTP_HOST']) ? htmlentities($_SERVER['HTTP_HOST']) : '';
2349 }
2350 $request_query = parse_url($_SERVER['REQUEST_URI'], PHP_URL_QUERY);
2351 $request_uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
2352
2353 // get cache path
2354 $cache_path = WP_Optimize_Minify_Cache_Functions::cache_path();
2355 $header_dir = $cache_path['headerdir'];
2356 $cache_file_base = $header_dir . '/';
2357
2358 // possible cache file locations
2359 $b = $cache_file_base . md5("$host-$request_uri").'.header';
2360 $a = $cache_file_base . md5("$host-$request_uri$request_query").'.header';
2361
2362 // reset file cache
2363 clearstatcache();
2364
2365 // return header files or fallback
2366 if ($b == $a && file_exists($a)) {
2367 return file_get_contents($a);
2368 }
2369 if ($b != $a && file_exists($b)) {
2370 return file_get_contents($b);
2371 }
2372
2373 return false;
2374 }
2375
2376 /**
2377 * Add pre-connect and pre-load headers
2378 *
2379 * @return void
2380 */
2381 public function extra_preload_headers() {
2382 if (!$this->run_on_page('extra_preload_headers')) return;
2383
2384 // fetch headers
2385 $pre_connect = $this->get_pre_connect_list();
2386
2387 // preload
2388 if (is_array($pre_connect) && count($pre_connect) > 0) {
2389 foreach ($pre_connect as $url) {
2390 if (!empty($url) && filter_var($url, FILTER_VALIDATE_URL)) {
2391 header("Link: <$url>; rel=preconnect", false);
2392 }
2393 }
2394 }
2395
2396 $headers = $this->get_preload_headers();
2397 if (false != $headers) {
2398 $nh = array_map('trim', explode("\n", $headers));
2399 foreach ($nh as $h) {
2400 if (!empty($h)) {
2401 header($h, false);
2402 }
2403 }
2404 }
2405 }
2406
2407 /**
2408 * Add caching headers
2409 *
2410 * @param array $headers An array of headers
2411 *
2412 * @return array An array of modified headers
2413 */
2414 public function add_caching_headers($headers) {
2415 $cache = WP_Optimize()->get_page_cache();
2416 if ($cache->is_enabled()) return $headers;
2417
2418 if (!isset($headers['Cache-Control'])) {
2419 $headers['Cache-Control'] = 'must-revalidate';
2420 }
2421
2422 if (!isset($headers['Last-Modified'])) {
2423 $headers['Last-Modified'] = gmdate('D, d M Y H:i:s', time()) . ' GMT';
2424 }
2425
2426 return $headers;
2427 }
2428
2429 /**
2430 * Includes dependency files
2431 */
2432 private function include_dependencies() {
2433 if (!class_exists('WP_Optimize_Minify_Functions')) {
2434 include WP_OPTIMIZE_MINIFY_DIR.'/class-wp-optimize-minify-functions.php';
2435 }
2436
2437 if (!class_exists('WP_Optimize_Minify_Print')) {
2438 include WP_OPTIMIZE_MINIFY_DIR.'/class-wp-optimize-minify-print.php';
2439 }
2440
2441 if (!class_exists('WP_Optimize_Minify_Fonts')) {
2442 include WP_OPTIMIZE_MINIFY_DIR.'/class-wp-optimize-minify-fonts.php';
2443 }
2444 }
2445
2446 /**
2447 * Handles emoji removal
2448 */
2449 private function disable_emojis() {
2450 WP_Optimize_Minify_Functions::disable_wp_emojicons();
2451 add_filter('tiny_mce_plugins', array('WP_Optimize_Minify_Functions', 'disable_emojis_tinymce' ));
2452 }
2453
2454 /**
2455 * Handles header meta information removal
2456 */
2457 private function remove_header_meta_info() {
2458 // no resource hints, generator tag, shortlinks, manifest link, etc
2459 remove_action('wp_head', 'wp_resource_hints', 2);
2460 remove_action('wp_head', 'wp_generator');
2461 remove_action('template_redirect', 'wp_shortlink_header', 11);
2462 remove_action('wp_head', 'wlwmanifest_link');
2463 remove_action('wp_head', 'rsd_link');
2464 remove_action('wp_head', 'adjacent_posts_rel_link_wp_head', 10, 0);
2465 remove_action('wp_head', 'feed_links', 2);
2466 remove_action('wp_head', 'feed_links_extra', 3);
2467 WP_Optimize_Minify_Functions::remove_redundant_shortlink();
2468 }
2469
2470 /**
2471 * Handles javascript processing
2472 */
2473 private function process_js() {
2474 add_action('wp_print_scripts', array($this, 'process_header_scripts'), PHP_INT_MAX);
2475 add_action('wp_print_footer_scripts', array($this, 'process_footer_scripts'), 9);
2476
2477 // Defer JS
2478 add_filter('script_loader_tag', array($this, 'defer_js'), 10, 3);
2479
2480 // Preloading
2481 if ($this->options['enabled_js_preload']) {
2482 add_filter('script_loader_tag', array($this, 'collect_js_preload_headers'), PHP_INT_MAX, 3);
2483 }
2484
2485 // add the LoadAsync JavaScript function
2486 $async_js = $this->get_async_js_list();
2487 if (count($async_js) > 0
2488 || ( 'all' === $this->options['enable_defer_js'] && 'async_using_js' === $this->options['defer_js_type'] )
2489 ) {
2490 add_action('wp_head', array('WP_Optimize_Minify_Print', 'add_load_async'), 0);
2491 }
2492
2493 // Process script module from generated html
2494 ob_start(array($this, "process_script_modules"));
2495 }
2496
2497 /**
2498 * Extract scripts tags from html buffer using regex
2499 *
2500 * @param string $html - HTML document as string
2501 * @return array
2502 */
2503 private function get_scripts($html) {
2504
2505 preg_match_all('/<script([^>]*)>/Umi', $html, $script_matches, PREG_SET_ORDER);
2506
2507 $modified_script_matches = array();
2508 foreach ($script_matches as $script) {
2509 $script['url'] = '';
2510 $script['handle'] = '';
2511 $script['version'] = '';
2512 $script['type'] = '';
2513 $attributes = WP_Optimize_Utils::parse_attributes($script[1]);
2514
2515 if (isset($attributes['src'])) {
2516 $script['url'] = $attributes['src'];
2517 $url_query = wp_parse_url($script['url'], PHP_URL_QUERY);
2518 $output = array();
2519 wp_parse_str($url_query, $output);
2520 if (isset($output['ver'])) $script['version'] = $output['ver'];
2521 }
2522
2523 // Extract the id/handle from attribute's array if present
2524 if (isset($attributes['id'])) $script['handle'] = $attributes['id'];
2525
2526 // Extract the type from attribute's array if present
2527 if (isset($attributes['type'])) $script['type'] = $attributes['type'];
2528
2529 $modified_script_matches[] = $script;
2530 }
2531
2532 return $modified_script_matches;
2533 }
2534
2535 /**
2536 * Extract `importmap` json object from html buffer using regex
2537 *
2538 * @param string $html - HTML document as string
2539 * @return array
2540 */
2541 private function get_script_module_importmap($html) {
2542 preg_match('/<script\s+type\s*=\s*[\'"]importmap.*>(?<importmap>.*)<\/script>/Umsi', $html, $match);
2543 if (!empty($match) && isset($match['importmap'])) return json_decode($match['importmap'], true);
2544
2545 return array();
2546 }
2547
2548 /**
2549 * Handles css processing
2550 */
2551 private function process_css() {
2552 add_action('wp_head', array($this, 'add_critical_path'), 2);
2553
2554 // merge, if inline is selected but prevent optimization for these locations
2555 if ($this->options['inline_css']) {
2556 // this prints the styles (not fonts) and checks the 'colllect google fonts'
2557 add_filter('style_loader_tag', array($this, 'inline_css'), PHP_INT_MAX, 4);
2558 // this prints the google fonts
2559 add_action('wp_print_styles', array($this, 'add_google_fonts_merged'), PHP_INT_MAX);
2560 add_action('wp_print_footer_scripts', array($this, 'add_google_fonts_merged'), PHP_INT_MAX);
2561 }
2562 // Preloading
2563 if ($this->options['enabled_css_preload']) {
2564 add_filter('style_loader_tag', array($this, 'collect_css_preload_headers'), PHP_INT_MAX, 3);
2565 }
2566 // Optimize the css and collect the google fonts for merging
2567 add_action('wp_print_styles', array($this, 'process_header_css'), PHP_INT_MAX);
2568 add_action('wp_print_footer_scripts', array($this, 'process_footer_css'), 9);
2569
2570 /**
2571 * Filters whether or not to ignore the order of the CSS files, and group them by media type
2572 *
2573 * @param boolean $maintain_css_order
2574 * @return boolean
2575 * @default true
2576 */
2577 if (!apply_filters('wpo_minify_maintain_css_order', true)) {
2578 // Reorder stylesheets
2579 add_filter('wpo_minify_stylesheets', array($this, 'order_stylesheets_per_media_type'), 10);
2580 }
2581 }
2582
2583 /**
2584 * Determines whether we should minify html or not
2585 */
2586 private function should_process_html() {
2587 return $this->options['html_minification'] && !is_admin() && $this->is_cache_preload();
2588 }
2589
2590 /**
2591 * Determines whether to use loadCSS polyfill or not
2592 */
2593 private function should_use_loadCSS() {
2594 return $this->options['loadcss']
2595 || 'async' === $this->options['fawesome_method']
2596 || 'async' === $this->options['gfonts_method'];
2597 }
2598
2599 /**
2600 * Removes query string from static assets
2601 */
2602 private function remove_query_string_from_static_assets() {
2603 add_filter('style_loader_src', array('WP_Optimize_Minify_Functions', 'remove_cssjs_ver'), 10, 2);
2604 add_filter('script_loader_src', array('WP_Optimize_Minify_Functions', 'remove_cssjs_ver'), 10, 2);
2605 }
2606
2607 /**
2608 * Converts the lines in a textarea option value into an array
2609 *
2610 * @param string $option
2611 * @return array
2612 */
2613 private function convert_option_to_array($option) {
2614 return trim($option) ? array_map('trim', explode("\n", trim($option))) : array();
2615 }
2616
2617 /**
2618 * Retrieves excluded css URLs as an array
2619 *
2620 * @return array
2621 */
2622 private function get_excluded_css_list() {
2623 $excluded_css = $this->options['exclude_css'];
2624 return $this->convert_option_to_array($excluded_css);
2625 }
2626
2627 /**
2628 * Retrieves async css URLs as an array
2629 *
2630 * @return array
2631 */
2632 private function get_async_css_list() {
2633 $async_css = $this->options['async_css'];
2634 return $this->convert_option_to_array($async_css);
2635 }
2636
2637 /**
2638 * Retrieves excluded javascript URLs as an array
2639 *
2640 * @return array
2641 */
2642 private function get_excluded_js_list() {
2643 $excluded_js = $this->options['exclude_js'];
2644 return $this->convert_option_to_array($excluded_js);
2645 }
2646
2647 /**
2648 * Retrieves async javascript URLs as an array
2649 *
2650 * @return array
2651 */
2652 private function get_async_js_list() {
2653 $async_js = $this->options['async_js'];
2654 return $this->convert_option_to_array($async_js);
2655 }
2656
2657 /**
2658 * Retrieves pre connect headers as an array
2659 *
2660 * @return array
2661 */
2662 private function get_pre_connect_list() {
2663 $pre_connect = $this->options['hpreconnect'];
2664 return $this->convert_option_to_array($pre_connect);
2665 }
2666
2667 /**
2668 * Array to string conversion
2669 *
2670 * @param array $arr
2671 * @return string
2672 */
2673 private function array_to_string_conversion($arr) {
2674 $str = '';
2675 // implode won't work, because element can be an array
2676 foreach ($arr as $value) {
2677 $str .= is_array($value) ? '' : $value;
2678 }
2679 return $str;
2680 }
2681
2682 /**
2683 * Decides whether minify cache should be incremented or not
2684 *
2685 * @param string $res
2686 * @param string $href
2687 * @param string $type
2688 * @param string $handle
2689 * @param string $version
2690 *
2691 * @return bool true when cache is invalid, false otherwise
2692 */
2693 private function should_reset_minify_assets($res, $href, $type, $handle, $version) {
2694 if (isset($res['request']['version']) && $res['request']['version'] != $version && !$this->minify_cache_incremented) {
2695 $enable_minification = $this->options['enable_'.$type.'_minification'] && !WP_Optimize_Minify_Functions::is_minified_css_js_filename($href);
2696 $new_json = WP_Optimize_Minify_Functions::download_and_minify($href, null, $enable_minification, $type, $handle, $version);
2697 $new_res = json_decode($new_json, true);
2698
2699 /**
2700 * In case `download_and_minify` fails because of
2701 * Network issues, Syntax errors in updated code,
2702 * Wrong use of `wpo_minify_get_js` and `wpo_minify_get_css` filters
2703 * We don't want to invalidate current assets, so bail out
2704 */
2705 if (null === $new_res) return false;
2706
2707 $hash = hash('sha256', $res['code']);
2708 $new_hash = hash('sha256', $new_res['code']);
2709 return strcmp($hash, $new_hash);
2710 }
2711 }
2712 }
2713