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

2,714 lines 94.1 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 $last_modified = WP_Optimize_Minify_Functions::get_modification_time($script['url']);
2039 $handle = $script['handle'];
2040 $version = $script['version'];
2041
2042 // Module script handles use slashes and @ symbols,
2043 // replace them with '-' and empty string, break if handle is still ''
2044 if (!empty($handle)) {
2045 $handle_no_slash = str_replace(array('/', '@'), array('-', ''), $handle);
2046 } else {
2047 continue;
2048 }
2049
2050 // code and log initialization
2051 $log_header = "PROCESSED on ".date('r')." from ".home_url(add_query_arg(null, null));
2052 $log = array(
2053 'header' => $log_header,
2054 'files' => array()
2055 );
2056 $meta_log = $this->meta_log_initialization($log_header);
2057 $code = '';
2058
2059 // create cache files and urls
2060 $file_name = 'wpo-minify-'.$handle_no_slash.($minify_js ? '.min' : '');
2061 $file = $cache_dir.'/'.$file_name.'.js';
2062 $meta_json_file = $cache_base_dir.'/'.'meta.json';
2063 $file_url = WP_Optimize_Minify_Functions::get_protocol($cache_dir_url.'/'.$file_name.'.js');
2064
2065 // download, minify, cache
2066 $tkey = 'js-'.hash('adler32', $handle_no_slash . $href).'.js';
2067 $json = WP_Optimize_Minify_Cache_Functions::get_transient($tkey);
2068 if (false === $json) {
2069 $enable_minification = $minify_js && !WP_Optimize_Minify_Functions::is_minified_css_js_filename($href);
2070 $json = WP_Optimize_Minify_Functions::download_and_minify($href, null, $enable_minification, 'js', $handle_no_slash, $version);
2071 if ($this->options['debug']) {
2072 echo "<!-- wpo_min DEBUG: Uncached file processing now for $handle / $href / $version -->\n";
2073 }
2074 WP_Optimize_Minify_Cache_Functions::set_transient($tkey, $json);
2075 }
2076
2077 // decode
2078 $res = json_decode($json, true);
2079
2080 if (null === $res) continue;
2081
2082 // Reset minified script module
2083 if ($this->should_reset_minify_assets($res, $href, 'js', $handle, $version)) {
2084 WP_Optimize_Minify_Cache_Functions::reset();
2085 $this->minify_cache_incremented = true;
2086 $meta_log['invalidation_reason'] = 'Checksum Mismatch and version changed from ' . $res['request']['version'] . ' to ' . $version;
2087 $meta_log['files_that_changed'][$handle] = $res['log'];
2088 }
2089
2090 if (!$res['status']) continue;
2091
2092 $code .= isset($res['code']) ? $res['code'] : '';
2093 $code = WP_Optimize_Minify_Functions::prepare_merged_js($code, $href);
2094 $log['files'][$handle] = $res['log'];
2095 $log['files'][$handle]['version'] = $version;
2096 $log['files'][$handle]['last_modified'] = $last_modified;
2097
2098 // If code is empty (nothing to minify) break loop
2099 if (empty($code)) continue;
2100
2101 // Only generate meta.json file if cache is reset
2102 if (!empty($meta_log['invalidation_reason'])) {
2103 file_put_contents($meta_json_file, json_encode($meta_log));
2104 }
2105 WP_Optimize_Minify_Print::write_combined_asset($file, $code, $log);
2106
2107 // Extract importmap script tag
2108 $json_map = $this->get_script_module_importmap($buffer);
2109 $json_map_update = $json_map;
2110
2111 if (!empty($json_map) && isset($json_map['imports'])) {
2112 $json_map_update['imports'][$handle] = $file_url;
2113 } else {
2114 continue;
2115 }
2116
2117 // Update module source file and importmap with new file url
2118 $buffer = str_replace($script['url'], $file_url, $buffer);
2119 $buffer = str_replace(json_encode($json_map), json_encode($json_map_update), $buffer);
2120 }
2121
2122 return $buffer;
2123 }
2124
2125 /**
2126 * Orders the CSS per media type
2127 *
2128 * @param array $list - The list of assets
2129 * @return array
2130 */
2131 public function order_stylesheets_per_media_type($list) {
2132 // get unique mediatypes
2133 $allmedia = array();
2134 foreach ($list as $array) {
2135 if (isset($array['media'])) {
2136 $allmedia[$array['media']] = '';
2137 }
2138 }
2139
2140 // extract handles by mediatype
2141 $grouphandles = array();
2142 foreach ($allmedia as $md => $var) {
2143 foreach ($list as $array) {
2144 if (isset($array['media']) && $array['media'] === $md) {
2145 foreach ($array['handles'] as $h) {
2146 $grouphandles[$md][] = $h;
2147 }
2148 }
2149 }
2150 }
2151
2152 // reset and reorder list by mediatypes
2153 $newlist = array();
2154 foreach ($allmedia as $md => $var) {
2155 $newlist[] = array('handles' => $grouphandles[$md], 'media' => $md);
2156 }
2157 if (count($newlist) > 0) {
2158 $list = $newlist;
2159 }
2160
2161 return $list;
2162 }
2163
2164 /**
2165 * Merged google font
2166 *
2167 * @return void;
2168 */
2169 public function add_google_fonts_merged() {
2170 // must have something to do
2171 if (count($this->collect_google_fonts) == 0) return;
2172
2173 // merge google fonts
2174 $href = WP_Optimize_Minify_Fonts::concatenate_google_fonts($this->collect_google_fonts);
2175 if (empty($href)) {
2176 return;
2177 }
2178
2179 // hide google fonts from PageSpeedIndex
2180 if ('exclude' === $this->options['gfonts_method']) {
2181 // make a stylesheet, hide from PageSpeedIndex
2182 WP_Optimize_Minify_Print::exclude_style($href);
2183 } elseif ('async' === $this->options['gfonts_method']) {
2184 // load google fonts async
2185 WP_Optimize_Minify_Print::async_style($href);
2186 } else {
2187 // fallback to normal inline
2188 WP_Optimize_Minify_Print::style($href);
2189 }
2190
2191 // unset per hook
2192 foreach ($this->collect_google_fonts as $k => $v) {
2193 unset($this->collect_google_fonts[$k]);
2194 }
2195 }
2196
2197 /**
2198 * Check if the string given contains 'wpo-minify'
2199 *
2200 * @param string $var
2201 *
2202 * @return boolean
2203 */
2204 public function check_wpo($var) {
2205 return (false === strpos($var, 'assets/wpo-minify'));
2206 }
2207
2208 /**
2209 * Generate preload headers file
2210 *
2211 * @return boolean
2212 */
2213 public function generate_preload_headers() {
2214
2215 // always false for admin pages
2216 if (is_admin()) return false;
2217
2218 // get host with multisite support and query strings
2219 $host = htmlentities($_SERVER['SERVER_NAME']);
2220 if (empty($host)) {
2221 $host = isset($_SERVER['HTTP_HOST']) ? htmlentities($_SERVER['HTTP_HOST']) : '';
2222 }
2223 $request_query = parse_url($_SERVER['REQUEST_URI'], PHP_URL_QUERY);
2224 $request_uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
2225
2226 // initialize headers
2227 $headers = array();
2228
2229 // css headers
2230 if ($this->options['enabled_css_preload']
2231 && count($this->collect_preload_css) > 0
2232 ) {
2233 foreach ($this->collect_preload_css as $u) {
2234 // filter out footer footer files, because they are not in the critical path
2235 if (strpos($u, '/assets/wpo-minify-footer-') !== false) {
2236 continue;
2237 }
2238
2239 // add headers
2240 $headers[] = "Link: <$u>; rel=preload; as=style";
2241 }
2242 }
2243
2244 // js headers
2245 if ($this->options['enabled_js_preload']
2246 && count($this->collect_preload_js) > 0
2247 ) {
2248 foreach ($this->collect_preload_js as $u) {
2249
2250 // filter out footer footer files, because they are not in the critical path
2251 if (false !== strpos($u, '/assets/wpo-minify-footer-')) {
2252 continue;
2253 }
2254
2255 // add headers
2256 $headers[] = "Link: <$u>; rel=preload; as=script";
2257 }
2258 }
2259
2260 // must have something
2261 if (count($headers) == 0) {
2262 return false;
2263 } else {
2264 $headers = implode("\n", $headers);
2265 }
2266
2267 // get cache path
2268 $cache_path = WP_Optimize_Minify_Cache_Functions::cache_path();
2269 $header_dir = $cache_path['headerdir'];
2270
2271 // possible cache file locations
2272 $b = $header_dir . '/' . md5($host.'-'.$request_uri) . '.header';
2273 $a = $header_dir . '/' . md5($host.'-'.$request_uri . $request_query).'.header';
2274
2275 // reset file cache
2276 clearstatcache();
2277
2278 // if there are no query strings
2279 if ($b == $a) {
2280 if (!file_exists($a)) {
2281 WP_Optimize_Minify_Print::write_header($a, $headers);
2282 }
2283 return false;
2284 } elseif (!file_exists($b)) {
2285 WP_Optimize_Minify_Print::write_header($b, $headers);
2286 }
2287
2288 return false;
2289 }
2290
2291 /**
2292 * Collect all wpo_min JS files and save them to a headers file
2293 *
2294 * @param String $html
2295 * @param String $handle
2296 * @param String $src
2297 *
2298 * @return String
2299 */
2300 public function collect_js_preload_headers($html, $handle, $src) {
2301 if (false !== strpos($src, 'assets/wpo-minify')) {
2302 if (!in_array($src, $this->collect_preload_js)) {
2303 $this->collect_preload_js[] = $src;
2304 }
2305 }
2306 return $html . "\n";
2307 }
2308
2309 /**
2310 * Collect all wpo_min CSS files and save them to a headers file
2311 * this function intercepts the styles that are enqueued
2312 * take note that if the css is loaded ASYNC, it's not enqueued
2313 *
2314 * @param string $html - Html string
2315 * @param string $handle - Handle
2316 * @param string $src - The CSS file path
2317 *
2318 * @return string
2319 */
2320 public function collect_css_preload_headers($html, $handle, $src) {
2321 if (false !== strpos($src, 'assets/wpo-minify')) {
2322 if (!in_array($src, $this->collect_preload_css)) {
2323 $this->collect_preload_css[] = $src;
2324 }
2325 }
2326 return $html . "\n";
2327 }
2328
2329 /**
2330 * Get current headers file for the url
2331 *
2332 * @return bool|string
2333 */
2334 private function get_preload_headers() {
2335
2336 // always false for admin pages
2337 if (is_admin()) return false;
2338
2339 $enabled_css_preload = $this->options['enabled_css_preload'];
2340 $enabled_js_preload = $this->options['enabled_js_preload'];
2341
2342 if (!$enabled_css_preload && !$enabled_js_preload) {
2343 return false;
2344 }
2345
2346 // get host with multisite support and query strings
2347 $host = htmlentities($_SERVER['SERVER_NAME']);
2348 if (empty($host)) {
2349 $host = isset($_SERVER['HTTP_HOST']) ? htmlentities($_SERVER['HTTP_HOST']) : '';
2350 }
2351 $request_query = parse_url($_SERVER['REQUEST_URI'], PHP_URL_QUERY);
2352 $request_uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
2353
2354 // get cache path
2355 $cache_path = WP_Optimize_Minify_Cache_Functions::cache_path();
2356 $header_dir = $cache_path['headerdir'];
2357 $cache_file_base = $header_dir . '/';
2358
2359 // possible cache file locations
2360 $b = $cache_file_base . md5("$host-$request_uri").'.header';
2361 $a = $cache_file_base . md5("$host-$request_uri$request_query").'.header';
2362
2363 // reset file cache
2364 clearstatcache();
2365
2366 // return header files or fallback
2367 if ($b == $a && file_exists($a)) {
2368 return file_get_contents($a);
2369 }
2370 if ($b != $a && file_exists($b)) {
2371 return file_get_contents($b);
2372 }
2373
2374 return false;
2375 }
2376
2377 /**
2378 * Add pre-connect and pre-load headers
2379 *
2380 * @return void
2381 */
2382 public function extra_preload_headers() {
2383 if (!$this->run_on_page('extra_preload_headers')) return;
2384
2385 // fetch headers
2386 $pre_connect = $this->get_pre_connect_list();
2387
2388 // preload
2389 if (is_array($pre_connect) && count($pre_connect) > 0) {
2390 foreach ($pre_connect as $url) {
2391 if (!empty($url) && filter_var($url, FILTER_VALIDATE_URL)) {
2392 header("Link: <$url>; rel=preconnect", false);
2393 }
2394 }
2395 }
2396
2397 $headers = $this->get_preload_headers();
2398 if (false != $headers) {
2399 $nh = array_map('trim', explode("\n", $headers));
2400 foreach ($nh as $h) {
2401 if (!empty($h)) {
2402 header($h, false);
2403 }
2404 }
2405 }
2406 }
2407
2408 /**
2409 * Add caching headers
2410 *
2411 * @param array $headers An array of headers
2412 *
2413 * @return array An array of modified headers
2414 */
2415 public function add_caching_headers($headers) {
2416 $cache = WP_Optimize()->get_page_cache();
2417 if ($cache->is_enabled()) return $headers;
2418
2419 if (!isset($headers['Cache-Control'])) {
2420 $headers['Cache-Control'] = 'must-revalidate';
2421 }
2422
2423 if (!isset($headers['Last-Modified'])) {
2424 $headers['Last-Modified'] = gmdate('D, d M Y H:i:s', time()) . ' GMT';
2425 }
2426
2427 return $headers;
2428 }
2429
2430 /**
2431 * Includes dependency files
2432 */
2433 private function include_dependencies() {
2434 if (!class_exists('WP_Optimize_Minify_Functions')) {
2435 include WP_OPTIMIZE_MINIFY_DIR.'/class-wp-optimize-minify-functions.php';
2436 }
2437
2438 if (!class_exists('WP_Optimize_Minify_Print')) {
2439 include WP_OPTIMIZE_MINIFY_DIR.'/class-wp-optimize-minify-print.php';
2440 }
2441
2442 if (!class_exists('WP_Optimize_Minify_Fonts')) {
2443 include WP_OPTIMIZE_MINIFY_DIR.'/class-wp-optimize-minify-fonts.php';
2444 }
2445 }
2446
2447 /**
2448 * Handles emoji removal
2449 */
2450 private function disable_emojis() {
2451 WP_Optimize_Minify_Functions::disable_wp_emojicons();
2452 add_filter('tiny_mce_plugins', array('WP_Optimize_Minify_Functions', 'disable_emojis_tinymce' ));
2453 }
2454
2455 /**
2456 * Handles header meta information removal
2457 */
2458 private function remove_header_meta_info() {
2459 // no resource hints, generator tag, shortlinks, manifest link, etc
2460 remove_action('wp_head', 'wp_resource_hints', 2);
2461 remove_action('wp_head', 'wp_generator');
2462 remove_action('template_redirect', 'wp_shortlink_header', 11);
2463 remove_action('wp_head', 'wlwmanifest_link');
2464 remove_action('wp_head', 'rsd_link');
2465 remove_action('wp_head', 'adjacent_posts_rel_link_wp_head', 10, 0);
2466 remove_action('wp_head', 'feed_links', 2);
2467 remove_action('wp_head', 'feed_links_extra', 3);
2468 WP_Optimize_Minify_Functions::remove_redundant_shortlink();
2469 }
2470
2471 /**
2472 * Handles javascript processing
2473 */
2474 private function process_js() {
2475 add_action('wp_print_scripts', array($this, 'process_header_scripts'), PHP_INT_MAX);
2476 add_action('wp_print_footer_scripts', array($this, 'process_footer_scripts'), 9);
2477
2478 // Defer JS
2479 add_filter('script_loader_tag', array($this, 'defer_js'), 10, 3);
2480
2481 // Preloading
2482 if ($this->options['enabled_js_preload']) {
2483 add_filter('script_loader_tag', array($this, 'collect_js_preload_headers'), PHP_INT_MAX, 3);
2484 }
2485
2486 // add the LoadAsync JavaScript function
2487 $async_js = $this->get_async_js_list();
2488 if (count($async_js) > 0
2489 || ( 'all' === $this->options['enable_defer_js'] && 'async_using_js' === $this->options['defer_js_type'] )
2490 ) {
2491 add_action('wp_head', array('WP_Optimize_Minify_Print', 'add_load_async'), 0);
2492 }
2493
2494 // Process script module from generated html
2495 ob_start(array($this, "process_script_modules"));
2496 }
2497
2498 /**
2499 * Extract scripts tags from html buffer using regex
2500 *
2501 * @param string $html - HTML document as string
2502 * @return array
2503 */
2504 private function get_scripts($html) {
2505
2506 preg_match_all('/<script([^>]*)>/Umi', $html, $script_matches, PREG_SET_ORDER);
2507
2508 $modified_script_matches = array();
2509 foreach ($script_matches as $script) {
2510 $script['url'] = '';
2511 $script['handle'] = '';
2512 $script['version'] = '';
2513 $script['type'] = '';
2514 $attributes = WP_Optimize_Utils::parse_attributes($script[1]);
2515
2516 if (isset($attributes['src'])) {
2517 $script['url'] = $attributes['src'];
2518 $url_query = wp_parse_url($script['url'], PHP_URL_QUERY);
2519 $output = array();
2520 wp_parse_str($url_query, $output);
2521 if (isset($output['ver'])) $script['version'] = $output['ver'];
2522 }
2523
2524 // Extract the id/handle from attribute's array if present
2525 if (isset($attributes['id'])) $script['handle'] = $attributes['id'];
2526
2527 // Extract the type from attribute's array if present
2528 if (isset($attributes['type'])) $script['type'] = $attributes['type'];
2529
2530 $modified_script_matches[] = $script;
2531 }
2532
2533 return $modified_script_matches;
2534 }
2535
2536 /**
2537 * Extract `importmap` json object from html buffer using regex
2538 *
2539 * @param string $html - HTML document as string
2540 * @return array
2541 */
2542 private function get_script_module_importmap($html) {
2543 preg_match('/<script\s+type\s*=\s*[\'"]importmap.*>(?<importmap>.*)<\/script>/Umsi', $html, $match);
2544 if (!empty($match) && isset($match['importmap'])) return json_decode($match['importmap'], true);
2545
2546 return array();
2547 }
2548
2549 /**
2550 * Handles css processing
2551 */
2552 private function process_css() {
2553 add_action('wp_head', array($this, 'add_critical_path'), 2);
2554
2555 // merge, if inline is selected but prevent optimization for these locations
2556 if ($this->options['inline_css']) {
2557 // this prints the styles (not fonts) and checks the 'colllect google fonts'
2558 add_filter('style_loader_tag', array($this, 'inline_css'), PHP_INT_MAX, 4);
2559 // this prints the google fonts
2560 add_action('wp_print_styles', array($this, 'add_google_fonts_merged'), PHP_INT_MAX);
2561 add_action('wp_print_footer_scripts', array($this, 'add_google_fonts_merged'), PHP_INT_MAX);
2562 }
2563 // Preloading
2564 if ($this->options['enabled_css_preload']) {
2565 add_filter('style_loader_tag', array($this, 'collect_css_preload_headers'), PHP_INT_MAX, 3);
2566 }
2567 // Optimize the css and collect the google fonts for merging
2568 add_action('wp_print_styles', array($this, 'process_header_css'), PHP_INT_MAX);
2569 add_action('wp_print_footer_scripts', array($this, 'process_footer_css'), 9);
2570
2571 /**
2572 * Filters whether or not to ignore the order of the CSS files, and group them by media type
2573 *
2574 * @param boolean $maintain_css_order
2575 * @return boolean
2576 * @default true
2577 */
2578 if (!apply_filters('wpo_minify_maintain_css_order', true)) {
2579 // Reorder stylesheets
2580 add_filter('wpo_minify_stylesheets', array($this, 'order_stylesheets_per_media_type'), 10);
2581 }
2582 }
2583
2584 /**
2585 * Determines whether we should minify html or not
2586 */
2587 private function should_process_html() {
2588 return $this->options['html_minification'] && !is_admin() && $this->is_cache_preload();
2589 }
2590
2591 /**
2592 * Determines whether to use loadCSS polyfill or not
2593 */
2594 private function should_use_loadCSS() {
2595 return $this->options['loadcss']
2596 || 'async' === $this->options['fawesome_method']
2597 || 'async' === $this->options['gfonts_method'];
2598 }
2599
2600 /**
2601 * Removes query string from static assets
2602 */
2603 private function remove_query_string_from_static_assets() {
2604 add_filter('style_loader_src', array('WP_Optimize_Minify_Functions', 'remove_cssjs_ver'), 10, 2);
2605 add_filter('script_loader_src', array('WP_Optimize_Minify_Functions', 'remove_cssjs_ver'), 10, 2);
2606 }
2607
2608 /**
2609 * Converts the lines in a textarea option value into an array
2610 *
2611 * @param string $option
2612 * @return array
2613 */
2614 private function convert_option_to_array($option) {
2615 return trim($option) ? array_map('trim', explode("\n", trim($option))) : array();
2616 }
2617
2618 /**
2619 * Retrieves excluded css URLs as an array
2620 *
2621 * @return array
2622 */
2623 private function get_excluded_css_list() {
2624 $excluded_css = $this->options['exclude_css'];
2625 return $this->convert_option_to_array($excluded_css);
2626 }
2627
2628 /**
2629 * Retrieves async css URLs as an array
2630 *
2631 * @return array
2632 */
2633 private function get_async_css_list() {
2634 $async_css = $this->options['async_css'];
2635 return $this->convert_option_to_array($async_css);
2636 }
2637
2638 /**
2639 * Retrieves excluded javascript URLs as an array
2640 *
2641 * @return array
2642 */
2643 private function get_excluded_js_list() {
2644 $excluded_js = $this->options['exclude_js'];
2645 return $this->convert_option_to_array($excluded_js);
2646 }
2647
2648 /**
2649 * Retrieves async javascript URLs as an array
2650 *
2651 * @return array
2652 */
2653 private function get_async_js_list() {
2654 $async_js = $this->options['async_js'];
2655 return $this->convert_option_to_array($async_js);
2656 }
2657
2658 /**
2659 * Retrieves pre connect headers as an array
2660 *
2661 * @return array
2662 */
2663 private function get_pre_connect_list() {
2664 $pre_connect = $this->options['hpreconnect'];
2665 return $this->convert_option_to_array($pre_connect);
2666 }
2667
2668 /**
2669 * Array to string conversion
2670 *
2671 * @param array $arr
2672 * @return string
2673 */
2674 private function array_to_string_conversion($arr) {
2675 $str = '';
2676 // implode won't work, because element can be an array
2677 foreach ($arr as $value) {
2678 $str .= is_array($value) ? '' : $value;
2679 }
2680 return $str;
2681 }
2682
2683 /**
2684 * Decides whether minify cache should be incremented or not
2685 *
2686 * @param string $res
2687 * @param string $href
2688 * @param string $type
2689 * @param string $handle
2690 * @param string $version
2691 *
2692 * @return bool true when cache is invalid, false otherwise
2693 */
2694 private function should_reset_minify_assets($res, $href, $type, $handle, $version) {
2695 if (isset($res['request']['version']) && $res['request']['version'] != $version && !$this->minify_cache_incremented) {
2696 $enable_minification = $this->options['enable_'.$type.'_minification'] && !WP_Optimize_Minify_Functions::is_minified_css_js_filename($href);
2697 $new_json = WP_Optimize_Minify_Functions::download_and_minify($href, null, $enable_minification, $type, $handle, $version);
2698 $new_res = json_decode($new_json, true);
2699
2700 /**
2701 * In case `download_and_minify` fails because of
2702 * Network issues, Syntax errors in updated code,
2703 * Wrong use of `wpo_minify_get_js` and `wpo_minify_get_css` filters
2704 * We don't want to invalidate current assets, so bail out
2705 */
2706 if (null === $new_res) return false;
2707
2708 $hash = hash('sha256', $res['code']);
2709 $new_hash = hash('sha256', $new_res['code']);
2710 return strcmp($hash, $new_hash);
2711 }
2712 }
2713 }
2714