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

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