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

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