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

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