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

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