PluginProbe
WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance / 3.4.1
WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance v3.4.1
4.7.0 4.6.1 4.6.0 4.5.5 4.5.4 4.5.3 4.5.2 3.2.20 3.2.21 3.2.22 3.2.3 3.2.5 3.2.6 3.2.7 3.2.9 3.3.0 3.3.1 3.3.2 3.4.0 3.4.1 3.4.2 3.5.0 3.6.0 3.7.0 3.7.1 All 111 releases
wp-optimize / minify / class-wp-optimize-minify-functions.php

class-wp-optimize-minify-functions.php in WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance 3.4.1, at minify/class-wp-optimize-minify-functions.php

1,230 lines 36.9 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 // handle better utf-8 and unicode encoding
6 if (function_exists('mb_internal_encoding')) {
7 mb_internal_encoding('UTF-8');
8 }
9
10 // must have
11 ini_set('pcre.backtrack_limit', 5000000);
12 ini_set('pcre.recursion_limit', 5000000);
13
14 use MatthiasMullie\Minify; // phpcs:ignore PHPCompatibility.Keywords.NewKeywords.t_useFound, PHPCompatibility.LanguageConstructs.NewLanguageConstructs.t_ns_separatorFound
15
16 class WP_Optimize_Minify_Functions {
17
18 /**
19 * Applies `strip_tags` function for given array of messages
20 *
21 * @param array $messages Array of messages
22 * @param string $allowed_tags Tags to retain in message (optional)
23 *
24 * @return array
25 */
26 public static function apply_strip_tags_for_messages_array($messages, $allowed_tags = '<strong>') {
27 return array_map(function($message) use ($allowed_tags) {
28 return strip_tags($message, $allowed_tags);
29 }, $messages);
30 }
31
32 /**
33 * Detect external or internal scripts
34 *
35 * @param string $src
36 * @return boolean
37 */
38 public static function is_local_domain($src) {
39 $wpo_minify_options = wp_optimize_minify_config()->get();
40
41 $locations = array(home_url(), site_url(), network_home_url(), network_site_url());
42 $async_using_js = 'all' === $wpo_minify_options['enable_defer_js'] && 'async_using_js' === $wpo_minify_options['defer_js_type'];
43
44 // excluded from cdn because of https://www.chromestatus.com/feature/5718547946799104 (we use document.write to preserve render blocking)
45 if (!empty($wpo_minify_options['cdn_url'])
46 && (!$async_using_js || $wpo_minify_options['cdn_force'])
47 ) {
48 array_push($locations, $wpo_minify_options['cdn_url']);
49 }
50
51 // cleanup locations
52 $locations = array_filter(array_unique($locations));
53
54 // external or not?
55 $ret = false;
56 foreach ($locations as $l) {
57 $l = preg_replace('/^https?:\/\//i', '', trim($l));
58 $l = trim(trim(preg_replace('/^www./', '', $l), '/'));
59 if (stripos($src, $l) !== false && false === $ret) {
60 $ret = true;
61 }
62 }
63
64 // response
65 return $ret;
66 }
67
68 /**
69 * Functions, get hurl info
70 *
71 * @param mixed $src
72 *
73 * @return string
74 */
75 public static function get_hurl($src) {
76 $wp_home = site_url();
77 $wp_domain = trim(str_ireplace(array('http://', 'https://'), '', trim($wp_home, '/')));
78 // preserve empty source handles
79 $hurl = null === $src ? '' : trim($src);
80 if (empty($hurl)) {
81 return $hurl;
82 }
83
84 // some fixes
85 $hurl = str_ireplace(array('&#038;', '&amp;'), '&', $hurl);
86
87 if (is_ssl()) {
88 $protocol = 'https://';
89 } else {
90 $protocol = 'http://';
91 }
92
93 // make sure wp_home doesn't have a forward slash
94 $wp_home = rtrim($wp_home, '/');
95
96 // apply some filters
97 if (substr($hurl, 0, 2) === "//") {
98 $hurl = $protocol.ltrim($hurl, "/");
99 }//end if
100 if (substr($hurl, 0, 4) === "http" && stripos($hurl, $wp_domain) === false) {
101 return $hurl;
102 }//end if
103 if (substr($hurl, 0, 4) !== "http" && stripos($hurl, $wp_domain) !== false) {
104 $hurl = $wp_home.'/'.ltrim($hurl, "/");
105 }//end if
106
107 // prevent double forward slashes in the middle
108 $hurl = str_ireplace('###', '://', str_ireplace('//', '/', str_ireplace('://', '###', $hurl)));
109
110 // consider different wp-content directory
111 $proceed = 0;
112 if (!empty($wp_home)) {
113 $alt_wp_content = basename($wp_home);
114 if (substr($hurl, 0, strlen($alt_wp_content)) === $alt_wp_content) {
115 $proceed = 1;
116 }
117 }
118
119 // Get the name of the WP-CONTENT folder. Default is wp-content, but can be changed by the user.
120 $wp_content_folder = str_replace(ABSPATH, '', WP_CONTENT_DIR);
121
122 // protocol + home for relative paths
123 if ("/".WPINC === substr($hurl, 0, 12)
124 || "/wp-admin" === substr($hurl, 0, 9)
125 || "/$wp_content_folder" === substr($hurl, 0, 11)
126 || 1 == $proceed
127 ) {
128 $hurl = $wp_home.'/'.ltrim($hurl, "/");
129 }
130
131 // make sure there is a protocol prefix as required
132 $hurl = $protocol.preg_replace('/^https?:\/\//i', '', $hurl); // enforce protocol
133
134 // no query strings
135 if (stripos($hurl, '.js?v') !== false) {
136 $hurl = stristr($hurl, '.js?v', true).'.js';
137 }//end if
138 if (stripos($hurl, '.css?v') !== false) {
139 $hurl = stristr($hurl, '.css?v', true).'.css';
140 }//end if
141
142 return $hurl;
143 }
144
145 /**
146 * Check if it's an internal url or not
147 *
148 * @param string $hurl
149 * @param string $wp_home
150 * @param mixed $noxtra
151 * @return boolean
152 */
153 public static function internal_url($hurl, $wp_home, $noxtra = null) {
154 $wpo_minify_options = wp_optimize_minify_config()->get();
155
156 if (substr($hurl, 0, strlen($wp_home)) === $wp_home) {
157 return true;
158 }
159 if (stripos($hurl, $wp_home) !== false) {
160 return true;
161 }
162 if (isset($_SERVER['HTTP_HOST']) && stripos($hurl, preg_replace('/:\d+$/', '', $_SERVER['HTTP_HOST'])) !== false) {
163 return true;
164 }
165 if (isset($_SERVER['SERVER_NAME']) && stripos($hurl, preg_replace('/:\d+$/', '', $_SERVER['SERVER_NAME'])) !== false) {
166 return true;
167 }
168 if (isset($_SERVER['SERVER_ADDR']) && '::1' != $_SERVER['SERVER_ADDR'] && stripos($hurl, preg_replace('/:\d+$/', '', $_SERVER['SERVER_ADDR'])) !== false) {
169 return true;
170 }
171
172 // allow specific external urls to be merged
173 if (null === $noxtra) {
174 $merge_allowed_urls = array_map('trim', explode("\n", $wpo_minify_options['merge_allowed_urls']));
175 if (is_array($merge_allowed_urls) && strlen(implode($merge_allowed_urls)) > 0) {
176 foreach ($merge_allowed_urls as $e) {
177 if (stripos($hurl, $e) !== false && !empty($e)) {
178 return true;
179 }
180 }
181 }
182 }
183
184 return false;
185 }
186
187 /**
188 * Case-insensitive in_array() wrapper
189 *
190 * @param string $hurl
191 * @param array $ignore
192 * @return boolean
193 */
194 public static function in_arrayi($hurl, $ignore) {
195 $hurl = preg_replace('/^https?:\/\//i', '//', $hurl); // better compatibility
196 $hurl = strtok(urldecode(rawurldecode($hurl)), '?'); // no query string, decode entities
197
198 if (!empty($hurl) && is_array($ignore)) {
199 foreach ($ignore as $i) {
200 $i = preg_replace('/^https?:\/\//i', '//', $i); // better compatibility
201 $i = strtok(urldecode(rawurldecode($i)), '?'); // no query string, decode entities
202 $i = trim(trim(trim(rtrim($i, '/')), '*')); // wildcard char removal
203 if (!empty($i) && false !== stripos($hurl, $i)) {
204 return true;
205 }
206 }
207 }
208 return false;
209 }
210
211 /**
212 * Check if selected url is point to already minifiled css/js file
213 *
214 * @param string $url
215 * @return bool
216 */
217 public static function is_minified_css_js_filename($url) {
218 $parts = wp_parse_url($url);
219 if (empty($parts['path']) || !is_string(basename($parts['path']))) return false;
220 return 1 === preg_match('/\.min\.(js|css)$/i', basename($parts['path']));
221 }
222
223 /**
224 * Better compatibility urls + fix w3.org NamespaceAndDTDIdentifiers
225 *
226 * @param string $code
227 * @return string
228 * */
229 private static function compat_urls($code) {
230 $wpo_minify_options = wp_optimize_minify_config()->get();
231 $default_protocol = $wpo_minify_options['default_protocol'];
232 if ('dynamic' == $default_protocol) {
233 if ((isset($_SERVER['HTTPS']) && ('on' == $_SERVER['HTTPS'] || 1 == $_SERVER['HTTPS']))
234 || (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && 'https' == $_SERVER['HTTP_X_FORWARDED_PROTO'])
235 ) {
236 $default_protocol = 'https://';
237 } else {
238 $default_protocol = 'http://';
239 }
240 } else {
241 $default_protocol = $default_protocol.'://';
242 }
243 $code = preg_replace('/^https?:\/\//i', $default_protocol, $code);
244 $code = str_ireplace($default_protocol.'www.w3.org', 'http://www.w3.org', $code);
245 return $code;
246 }
247
248 /**
249 * Minify css string with PHP Minify
250 *
251 * @param string $css
252 * @return string
253 */
254 public static function minify_css_string($css) {
255 $css = apply_filters('wpo_minify_css_string', $css);
256 $minifier = new Minify\CSS($css);
257 $minifier->setMaxImportSize(15); // [css only] embed assets up to 15 Kb (default 5Kb) - processes gif, png, jpg, jpeg, svg & woff
258 $min = $minifier->minify();
259 if (false !== $min) {
260 return self::compat_urls($min);
261 }
262 return self::compat_urls($css);
263 }
264
265 /**
266 * Minify js on demand (one file at one time, for compatibility)
267 *
268 * @param string $url
269 * @param string $js
270 * @param boolean $enable_js_minification
271 * @return string
272 */
273 public static function get_js($url, $js, $enable_js_minification) {
274 $wpo_minify_options = wp_optimize_minify_config()->get();
275
276 // exclude minification on already minified files + jquery (because minification might break those)
277 $excl = array('jquery.js', '.min.js', '-min.js', '/uploads/fusion-scripts/', '/min/', '.packed.js', '/includes/builder/scripts/');
278 foreach ($excl as $e) {
279 if (stripos(basename($url), $e) !== false) {
280 $enable_js_minification = false;
281 break;
282 }
283 }
284
285 $encoding = mb_detect_encoding($js);
286
287 // remove BOM
288 $js = self::remove_utf8_bom($js);
289
290 self::maybe_log_error_message($url, $encoding, $js);
291
292 // minify JS
293 if ($enable_js_minification) {
294 $js = self::minify_js_string($js);
295 } else {
296 $js = self::compat_urls($js);
297 }
298
299 // Remove source mapping files
300 $js = preg_replace('/(\/\/\s*[#]\s*sourceMappingURL\s*[=]\s*)(.+)\s*/ui', '', $js);
301
302 // needed when merging js files
303 $js = trim($js);
304 if (substr($js, -1) != ';') {
305 $js = $js.';';
306 }
307 if ($wpo_minify_options['debug']) {
308 $js = '/* info: ' . $url . ' */' . "\n" . $js;
309 }
310
311 /**
312 * Filters the imported JavaScript
313 *
314 * @param string $js - The imported JS
315 * @param string $url - The imported url
316 * @param boolean $enable_js_minification - Whether to minify or not
317 */
318 return apply_filters('wpo_minify_get_js', $js . "\n", $url, $enable_js_minification);
319 }
320
321 /**
322 * Minify JS string with PHP Minify or YUI Compressors
323 *
324 * @param string $js
325 * @return string
326 */
327 public static function minify_js_string($js) {
328 $js = apply_filters('wpo_minify_js_string', $js);
329 // PHP Minify from https://github.com/matthiasmullie/minify
330 $minifier = new Minify\JS($js);
331 $min = $minifier->minify();
332 if (false !== $min && (strlen(trim($js)) == strlen(trim($min)) || strlen(trim($min)) > 0)) {
333 return self::compat_urls($min);
334 }
335
336 // if we are here, something went wrong and minification didn't work
337 $js = "\n/*! wpo_min: Minification of the following section failed, so it has been merged instead. */\n".$js;
338 return self::compat_urls($js);
339 }
340
341 /**
342 * Wrapper to minify the inlined JS string - Adds an extra check for JSON
343 *
344 * @param string $js
345 * @return string
346 */
347 public static function minify_inline_js($js) {
348 $js = apply_filters('wpo_minify_inline_js_string', $js);
349 // Do not minify JSON (JS minification will minify `true` to `!0`, which will break the JSON)
350 if (json_decode($js)) return $js;
351 return self::minify_js_string($js, true);
352 }
353
354 /**
355 * Functions, minify html
356 *
357 * @param string $html
358 * @return string
359 */
360 public static function minify_html($html) {
361 $minify_css = wp_optimize_minify_config()->get('enable_css_minification');
362 $minify_js = wp_optimize_minify_config()->get('enable_js_minification');
363 $options = array();
364 if ($minify_css && apply_filters('wpo_minify_inline_css', true)) {
365 $options['cssMinifier'] = array('WP_Optimize_Minify_Functions', 'minify_css_string');
366 }
367 if ($minify_js && apply_filters('wpo_minify_inline_js', true)) {
368 $options['jsMinifier'] = array('WP_Optimize_Minify_Functions', 'minify_inline_js');
369 }
370 return Minify_HTML::minify($html, $options);
371 }
372
373 /**
374 * Functions to minify HTML
375 *
376 * @param string $html
377 * @return string
378 */
379 public static function html_compression_finish($html) {
380 return self::minify_html($html);
381 }
382
383 /**
384 * Start the compression
385 *
386 * @return void
387 */
388 public static function html_compression_start() {
389 if (self::exclude_contents() == true) {
390 return;
391 }
392 ob_start(array(__CLASS__, 'html_compression_finish'));
393 }
394
395 /**
396 * Remove default HTTP headers
397 *
398 * @return void
399 */
400 public static function remove_redundant_shortlink() {
401 remove_action('wp_head', 'wp_shortlink_wp_head', 10);
402 remove_action('template_redirect', 'wp_shortlink_header', 11);
403 }
404
405 /**
406 * Minify css on demand (one file at one time, for compatibility)
407 *
408 * @param string $url
409 * @param string $css
410 * @param boolean $enable_css_minification
411 * @return string
412 */
413 public static function get_css($url, $css, $enable_css_minification) {
414 $wpo_minify_options = wp_optimize_minify_config()->get();
415
416 $encoding = mb_detect_encoding($css);
417
418 // remove BOM
419 $css = self::remove_utf8_bom($css);
420
421 self::maybe_log_error_message($url, $encoding, $css);
422
423 // fix url paths
424 if (!empty($url)) {
425 $css = self::make_css_urls_absolute($css, $url);
426 }
427
428 $css = str_ireplace('@charset "UTF-8";', '', $css);
429
430 // remove query strings from fonts (for better seo, but add a small cache buster based on most recent updates)
431 // last update or zero
432 $cache_time = $wpo_minify_options['last-cache-update'];
433 // fonts cache buster
434 $css = preg_replace('/(.eot|.woff2|.woff|.ttf)+[?+](.+?)(\)|\'|\")/ui', "$1"."#".$cache_time."$3", $css);
435 // Remove Sourcemappingurls
436 $css = preg_replace('/(\/\*\s*[#]\s*sourceMappingURL\s*[=]\s*)(.[^*]+)\s*\*\//ui', '', $css);
437 // If @import is found, process it/them
438 if (false !== strpos($css, '@import')) $css = self::replace_css_import($css, $url);
439
440 // minify CSS
441 if ($enable_css_minification) {
442 $css = self::minify_css_string($css);
443 } else {
444 $css = self::compat_urls($css);
445 }
446
447 // cdn urls
448 $cdn_url = $wpo_minify_options['cdn_url'];
449 if (!empty($cdn_url)) {
450 $wp_domain = trim(preg_replace('/^https?:\/\//i', '', trim(site_url(), '/')));
451 $cdn_url = trim(trim(preg_replace('/^https?:\/\//i', '', trim($cdn_url, '/'))), '/');
452 $css = str_ireplace($wp_domain, $cdn_url, $css);
453 }
454
455 // add css comment
456 $css = trim($css);
457 if ($wpo_minify_options['debug']) {
458 $css = '/* info: ' . $url . ' */' . "\n" . trim($css);
459 }
460
461 /**
462 * Filters the imported CSS
463 *
464 * @param string $css - The imported CSS
465 * @param string $url - The imported url
466 * @param boolean $enable_css_minification - Whether to minify or not
467 */
468 return apply_filters('wpo_minify_get_css', $css, $url, $enable_css_minification);
469 }
470
471 /**
472 * Adds full path to relative url() rules
473 *
474 * @param string $css - The CSS to process
475 * @param string $url - The URL or the CSS being processed
476 * @return string
477 */
478 public static function make_css_urls_absolute($css, $url) {
479 $matches = array();
480 preg_match_all("/url\(\s*['\"]?(?!data:)(?!http)(?![\/'\"])(.+?)['\"]?\s*\)/ui", $css, $matches);
481 foreach ($matches[1] as $a) {
482 $b = trim($a);
483 if ($b != $a) {
484 $css = str_replace($a, $b, $css);
485 }
486 }
487 return preg_replace("/url\(\s*['\"]?(?!data:)(?!http)(?![\/'\"])(.+?)['\"]?\s*\)/ui", "url(".dirname($url)."/$1)", $css);
488 }
489
490 /**
491 * Include @import[ed] files - The @import statement can only be used at the top of a file, which breaks when merging everything.
492 *
493 * @param string $css - The original CSS containing the @import statement
494 * @param string $file_url - The original CSS' URL
495 * @return string
496 */
497 public static function replace_css_import($css, $file_url) {
498 $remove_print_mediatypes = wp_optimize_minify_config()->get('remove_print_mediatypes');
499 $debug = wp_optimize_minify_config()->get('debug');
500 return preg_replace_callback('/(?:@import)\s(?:url\()?\s?["\'](.*?)["\']\s?\)?(?:[^;]*);?/im', function($matches) use ($file_url, $remove_print_mediatypes, $debug) {
501 // @import contains url()
502 if (preg_match('/url\s*\((.[^\)]*)[\)*?](.*);/', $matches[0], $url_matches)) {
503 $url = trim(str_replace(array('"', "'"), '', $url_matches[1]));
504 $media_query = trim($url_matches[2]);
505 // @import uses quotes only
506 } elseif (preg_match('/["\'](.*)["\'](.*);?/', $matches[0], $no_url_matches)) {
507 $url = trim($no_url_matches[1]);
508 $media_query = trim($no_url_matches[2], ") ;");
509 }
510
511 // If $media_query contains print, and $remove_print_mediatypes is true, return empty string
512 if ($remove_print_mediatypes && false !== strpos($media_query, 'print') && apply_filters('wpo_minfy_remove_print_mediatypes_import', true, $url, $media_query, $matches[0], $file_url)) return ($debug ? '/*! Info: the import of "'.$url.'" was removed because the setting remove_print_mediatypes is enabled. */' : '');
513
514 $purl = parse_url($url);
515 // If there's no host, the url is relative to $file_url, so prepend with the base url.
516 if (!isset($purl['host'])) {
517 $url = dirname($file_url).'/'.$url;
518 }
519
520 // Download @import
521 $asset_content = WP_Optimize_Minify_Functions::get_asset_content($url);
522 $content = $asset_content['content'];
523
524 if (!$content) return '';
525
526 // Fix the URLs
527 $content = WP_Optimize_Minify_Functions::make_css_urls_absolute($content, $url);
528
529 if ($media_query) {
530 // Wrap the code with the media query
531 $content = "@media $media_query {\n$content\n}";
532 }
533
534 if ($debug) {
535 $content = "/*! CSS import Information: code imported from $url */\n$content\n/*! END CSS import Information */";
536 }
537
538 // If the code contains its own @import, recursively include it.
539 if (false !== strpos($content, '@import')) {
540 return WP_Optimize_Minify_Functions::replace_css_import($content, $url);
541 }
542
543 return $content;
544 }, $css);
545 }
546
547 /**
548 * Download and cache css and js files
549 *
550 * @param string $hurl
551 * @param string $inline
552 * @param boolean $enable_minification
553 * @param string $type
554 * @param string $handle
555 * @return boolean|string
556 */
557 public static function download_and_minify($hurl, $inline, $enable_minification, $type, $handle, $version = '') {
558 // must have
559 if (is_null($hurl) || empty($hurl)) {
560 return false;
561 }
562 if (!in_array($type, array('js', 'css'))) {
563 return false;
564 }
565
566 $wpo_minify_options = wp_optimize_minify_config()->get();
567
568 // filters and defaults
569 $print_url = str_ireplace(array(site_url(), home_url(), 'http:', 'https:'), '', $hurl);
570
571 $log = array(
572 'url' => $print_url,
573 );
574
575 // defaults
576 if (false != $enable_minification) {
577 $enable_minification = true;
578 }
579 if (is_null($inline) || empty($inline)) {
580 $inline = '';
581 }
582 $print_handle = '';
583 if (is_null($handle) || empty($handle)) {
584 $handle = '';
585 } else {
586 $print_handle = "[$handle]";
587 }
588
589 // debug request
590 $dreq = array(
591 'hurl' => $hurl,
592 'inline' => $inline,
593 'enable_minification' => $enable_minification,
594 'type' => $type,
595 'handle' => $handle,
596 'version' => $version
597 );
598
599 $asset_content = self::get_asset_content($hurl);
600 $code = $asset_content['content'];
601
602 // If $code is empty:
603 if (!$code) {
604 $log['success'] = false;
605 if ($wpo_minify_options['debug']) {
606 $log['debug'] = "$print_handle failed. Tried wp_remote_get and local file_get_contents.";
607 }
608 $return = array('request' => $dreq, 'log' => $log, 'code' => '', 'status' => false);
609 return json_encode($return);
610 }
611
612 if ('js' == $type) {
613 $code = self::get_js($hurl, $code, $enable_minification);
614 } else {
615 $code = self::get_css($hurl, $code.$inline, $enable_minification);
616 }
617
618 // log, save and return
619 if ($wpo_minify_options['debug']) {
620 $version_msg = ('' != $version) ? "[Version: $version]" : "";
621 $log['debug'] = $print_handle . $version_msg . ' was '.('local' === $asset_content['method'] ? 'opened' : 'fetched').' from '.$hurl;
622 }
623 $log['success'] = true;
624 $return = array('request' => $dreq, 'log' => $log, 'code' => $code, 'status' => true);
625 return json_encode($return);
626 }
627
628 /**
629 * Get the content of an asset, wether local or remote
630 *
631 * @param string $url
632 * @return array
633 */
634 public static function get_asset_content($url) {
635
636 $wp_home = site_url();
637 $wp_domain = parse_url($wp_home, PHP_URL_HOST);
638 // If the file is local.
639 if (false !== stripos($url, $wp_domain)) {
640 // default
641 $f = str_ireplace(rtrim($wp_home, '/'), rtrim(ABSPATH, '/'), $url);
642 // failover when home_url != site_url
643 if (!file_exists($f)) {
644 $nhurl = str_ireplace(site_url(), home_url(), $url);
645 $f = str_ireplace(rtrim($wp_home, '/'), rtrim(ABSPATH, '/'), $nhurl);
646 }
647 clearstatcache();
648 if (file_exists($f)) {
649 $content = file_get_contents($f);
650 // check for php code, skip if found
651 if ("<?php" != strtolower(substr($content, 0, 5)) && false === stripos($content, "<?php")) {
652 return array('content' => $content, 'method' => 'local');
653 }
654 }
655 }
656
657
658 // else, fallback to remote urls (or windows)
659 $content = self::download_remote($url);
660 if (false !== $content
661 && !empty($content)
662 && strtolower(substr($content, 0, 9)) != "<!doctype"
663 ) {
664 // check if we got HTML instead of js or css code
665 return array('content' => $content, 'method' => 'remote');
666 }
667
668
669 // fallback when home_url != site_url
670 if (stripos($url, $wp_domain) !== false && home_url() != site_url()) {
671 $nhurl = str_ireplace(site_url(), home_url(), $url);
672 $content = self::download_remote($nhurl);
673 if (false !== $content && !empty($content) && '<!doctype' != strtolower(substr($content, 0, 9))) {
674 return array('content' => $content, 'method' => 'remote');
675 }
676 }
677
678 return array('content' => '', 'method' => 'none');
679 }
680
681 /**
682 * Remove emoji support
683 *
684 * @return void
685 */
686 public static function disable_wp_emojicons() {
687 remove_action('wp_head', 'print_emoji_detection_script', 7);
688 remove_action('admin_print_scripts', 'print_emoji_detection_script');
689 remove_action('wp_print_styles', 'print_emoji_styles');
690 remove_action('wp_enqueue_scripts', 'wp_enqueue_emoji_styles');
691 remove_action('admin_print_styles', 'print_emoji_styles');
692 remove_action('admin_enqueue_scripts', 'wp_enqueue_emoji_styles');
693 remove_filter('the_content_feed', 'wp_staticize_emoji');
694 remove_filter('comment_text_rss', 'wp_staticize_emoji');
695 remove_filter('wp_mail', 'wp_staticize_emoji_for_email');
696 }
697
698 /**
699 * Remove from tinymce
700 *
701 * @return array
702 */
703 public static function disable_emojis_tinymce($plugins) {
704 if (is_array($plugins)) {
705 return array_diff($plugins, array('wpemoji'));
706 } else {
707 return array();
708 }
709 }
710
711 /**
712 * Remove UTF8 BOM.
713 * Returns BOM removed string or null when `$string` does not have a recognised encoding
714 *
715 * @param string $string
716 * @return string|null
717 */
718 public static function remove_utf8_bom($string) {
719 $bom = pack('H*', 'EFBBBF');
720 return preg_replace("/^$bom/ui", '', $string);
721 }
722
723 /**
724 * Remove query string from static css files
725 *
726 * @param string $src
727 * @return string
728 */
729 public static function remove_cssjs_ver($src) {
730 if (stripos($src, '?ver=')) {
731 $src = remove_query_arg('ver', $src);
732 }
733 return $src;
734 }
735
736 /**
737 * Rewrite cache files to http, https or dynamic
738 *
739 * @param string $url
740 * @return string
741 */
742 public static function get_protocol($url) {
743 $wp_domain = trim(preg_replace('/^https?:\/\//i', '', trim(site_url(), '/')));
744 $wpo_minify_options = wp_optimize_minify_config()->get();
745 $default_protocol = $wpo_minify_options['default_protocol'];
746
747 $url = ltrim(preg_replace('/^https?:\/\//i', '', $url), '/'); // better compatibility
748
749 // cdn support
750 $cdn_url = $wpo_minify_options['cdn_url'];
751 $cdn_url = trim(trim(preg_replace('/^https?:\/\//i', '', trim($cdn_url, '/'))), '/');
752
753 // process cdn rewrite
754 if (!empty($cdn_url) && self::is_local_domain($url) !== false) {
755
756 // for js files, we need to consider thew defer for insights option
757 if (substr($url, -3) == '.js') {
758 $async_using_js = 'all' === $wpo_minify_options['enable_defer_js'] && 'async_using_js' === $wpo_minify_options['defer_js_type'];
759 if (!$async_using_js
760 || $wpo_minify_options['cdn_force']
761 ) {
762 $url = str_ireplace($wp_domain, $cdn_url, $url);
763 }
764 } else {
765 $url = str_ireplace($wp_domain, $cdn_url, $url);
766 }
767 }
768
769 // enforce protocol if needed
770 if ('dynamic' == $default_protocol) {
771 if ((isset($_SERVER['HTTPS']) && ('on' == $_SERVER['HTTPS'] || 1 == $_SERVER['HTTPS']))
772 || (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && 'https' == $_SERVER['HTTP_X_FORWARDED_PROTO'])
773 ) {
774 $default_protocol = 'https://';
775 } else {
776 $default_protocol = 'http://';
777 }
778 } else {
779 $default_protocol = $default_protocol.'://';
780 }
781
782 // return
783 return $default_protocol . $url;
784 }
785
786 /**
787 * Exclude processing from some pages / posts / contents
788 *
789 * @return boolean
790 */
791 public static function exclude_contents() {
792 // prevent execution for specific urls
793 if (isset($_SERVER['REQUEST_URI']) && !empty($_SERVER['REQUEST_URI'])) {
794 $disable_on_url = array_filter(array_map('trim', explode("\n", get_option('wpo_min_disable_on_url', ''))));
795 foreach ($disable_on_url as $url) {
796 if (parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) == $url) {
797 return true;
798 }
799 }
800 }
801
802 // for compatibility, let's always skip the checkout page
803 if (function_exists('is_checkout') && is_checkout() === true) {
804 return true;
805 }
806
807 // exclude processing here
808 if (is_feed()
809 || is_admin()
810 || is_preview()
811 || (function_exists('is_customize_preview') && is_customize_preview())
812 || (defined('DOING_AJAX') && DOING_AJAX)
813 || (function_exists('wp_doing_ajax') && wp_doing_ajax())
814 || (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE)
815 || (defined('WP_BLOG_ADMIN') && WP_BLOG_ADMIN)
816 || (defined('WP_NETWORK_ADMIN') && WP_NETWORK_ADMIN)
817 || (defined('WP_INSTALLING') && WP_INSTALLING)
818 || (defined('WP_IMPORTING') && WP_IMPORTING)
819 || (defined('WP_REPAIRING') && WP_REPAIRING)
820 || (defined('XMLRPC_REQUEST') && XMLRPC_REQUEST)
821 || (defined('SHORTINIT') && SHORTINIT)
822 || (defined('REST_REQUEST') && REST_REQUEST)
823 || (isset($_SERVER['REQUEST_METHOD']) && 'POST' === $_SERVER['REQUEST_METHOD'])
824 || (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest')
825 || (isset($_SERVER['REQUEST_URI']) && (strtolower(substr($_SERVER['REQUEST_URI'], -4)) == '.txt' || strtolower(substr($_SERVER['REQUEST_URI'], -4)) == '.xml'))
826 ) {
827 return true;
828 }
829
830 // Thrive plugins and other post_types
831 $arr = array('tve_form_type', 'tve_lead_shortcode', 'tqb_splash');
832 foreach ($arr as $a) {
833 if (isset($_GET['post_type']) && $a === $_GET['post_type']) {
834 return true;
835 }
836 }
837
838 // Thrive architect
839 if (isset($_GET['tve']) && 'true' === $_GET['tve']) return true;
840
841
842 if (is_array($_GET)) {
843 foreach ($_GET as $k => $v) {
844 if (is_string($v) && is_string($k)) {
845 if (stripos($k, 'elementor') !== false || stripos($v, 'elementor') !== false) {
846 return true;
847 }
848 }
849 }
850 }
851
852 // Other _GET parameters
853 if (is_array($_GET)) {
854 $get_params = array_keys($_GET);
855 $excluded_params = array(
856 // customizer preview, visual composer
857 'customize_theme',
858 'preview_id',
859 'preview',
860 // Elementor
861 'elementor-preview',
862 // Divi builder
863 'et_fb',
864 'PageSpeed',
865 // Brizy Editor
866 'brizy-edit',
867 'brizy-edit-iframe',
868 // Beaver builder
869 'fl_builder',
870 'trp-edit-translation',
871 );
872 return (bool) count(array_intersect($excluded_params, $get_params));
873 }
874
875 /**
876 * Wether to exclude the content or not from the minifying process.
877 */
878 return apply_filters('wpo_minify_exclude_contents', false);
879 }
880
881 /**
882 * Get the default files which are ignored / excluded from processing
883 *
884 * @return array
885 */
886 public static function get_default_ignore() {
887 /**
888 * Filters the default exclusions
889 *
890 * @param array The exclusions
891 * @return array
892 */
893 return apply_filters('wp-optimize-minify-default-exclusions', array(
894 '/genericons.css',
895 '/Avada/assets/js/main.min.js',
896 '/woocommerce-product-search/js/product-search.js',
897 '/includes/builder/scripts/frontend-builder-scripts.js',
898 '/assets/js/jquery.themepunch.tools.min.js',
899 '/js/TweenMax.min.js',
900 '/jupiter/assets/js/min/full-scripts',
901 '/wp-content/themes/Divi/core/admin/js/react-dom.production.min.js',
902 '/LayerSlider/static/layerslider/js/greensock.js',
903 '/themes/kalium/assets/js/main.min.js',
904 '/wp-includes/js/mediaelement/wp-mediaelement.min.js',
905 'elementor-admin-bar',
906 'pdfjs-dist',
907 'wordpress-popular-posts',
908 'uploads/bb-plugin/cache', // Beaver builder page specific pages
909 ));
910 }
911
912 /**
913 * Know files that should always be ignored
914 *
915 * @param array $ignore
916 * @return array
917 */
918 public static function compile_ignore_list($ignore) {
919 $ignore_list = self::get_default_ignore();
920 $user_excluded_ignore_items = wp_optimize_minify_config()->get('ignore_list');
921 if (is_array($user_excluded_ignore_items)) $ignore_list = array_diff($ignore_list, $user_excluded_ignore_items);
922 if (is_array($ignore)) {
923 $master_ignore = array_merge(array_map('strtolower', $ignore), array_map('strtolower', $ignore_list));
924 return array_unique($master_ignore);
925 } else {
926 return $ignore_list;
927 }
928 }
929
930 /**
931 * Get the files excluded for IE compatibility
932 *
933 * @return array
934 */
935 public static function get_default_ie_blacklist() {
936 /**
937 * Filters the default IE specific / blacklisted items
938 *
939 * @param array The blacklist
940 * @return array
941 */
942 return apply_filters('wp-optimize-minify-blacklist', array(
943 '/html5shiv.js',
944 '/html5shiv-printshiv.min.js',
945 '/excanvas.js',
946 '/avada-ie9.js',
947 '/respond.js',
948 '/respond.min.js',
949 '/selectivizr.js',
950 '/Avada/assets/css/ie.css',
951 '/html5.js',
952 '/IE9.js',
953 '/fusion-ie9.js',
954 '/vc_lte_ie9.min.css',
955 '/old-ie.css',
956 '/ie.css',
957 '/vc-ie8.min.css',
958 '/mailchimp-for-wp/assets/js/third-party/placeholders.min.js',
959 '/assets/js/plugins/wp-enqueue/min/webfontloader.js',
960 '/a.optnmstr.com/app/js/api.min.js',
961 '/pixelyoursite/js/public.js',
962 '/assets/js/wcdrip-drip.js',
963 '/instantpage.js',
964 ));
965 }
966
967 /**
968 * Get the files excluded for IE compatibility
969 *
970 * @return array
971 */
972 public static function get_ie_blacklist() {
973 $user_excluded_blacklist_items = wp_optimize_minify_config()->get('blacklist');
974 $default_blacklist = self::get_default_ie_blacklist();
975 if (!is_array($user_excluded_blacklist_items)) return $default_blacklist;
976 return array_diff($default_blacklist, $user_excluded_blacklist_items);
977 }
978
979 /**
980 * IE only files that should always be ignored, without incrementing our groups
981 *
982 * @param string $url
983 * @return boolean
984 */
985 public static function is_url_in_ie_blacklist($url) {
986 // from the database
987 $blacklist = self::get_ie_blacklist();
988 // must have
989 $blacklist[] = '/wpo_min/cache/';
990
991 // is the url on our list and return
992 $res = self::in_arrayi($url, $blacklist);
993 if (true == $res) {
994 return true;
995 } else {
996 return false;
997 }
998 }
999
1000 /**
1001 * Download function with fallback
1002 *
1003 * @param string $url
1004 * @return boolean
1005 */
1006 public static function download_remote($url) {
1007
1008 $args = array(
1009 // info (needed for google fonts woff files + hinted fonts) as well as to bypass some security filters
1010 'user-agent' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.81 Safari/537.36',
1011 'timeout' => 7
1012 );
1013
1014 // fetch via wordpress functions
1015 $response = wp_remote_get(
1016 $url,
1017 /**
1018 * Filters the arguments passed to wp_remote_get when downloading the scripts.
1019 *
1020 * @param array $args - The arguments filtered
1021 * @param string $url - The URL of the downloaded asset
1022 * @return array
1023 */
1024 apply_filters('wpo_minify_download_request_args', $args, $url)
1025 );
1026
1027 $res_code = wp_remote_retrieve_response_code($response);
1028 if (200 == $res_code) {
1029 $data = wp_remote_retrieve_body($response);
1030 if (strlen($data) > 1) {
1031 return $data;
1032 }
1033 }
1034
1035 return false;
1036 }
1037
1038 /**
1039 * Prepares the merged JavaScript
1040 *
1041 * @param string $script
1042 * @param string $merged_url
1043 * @return string
1044 */
1045 public static function prepare_merged_js($script, $merged_url) {
1046 $enable_js_trycatch = wp_optimize_minify_config()->get('enable_js_trycatch');
1047 if ($enable_js_trycatch) {
1048 return 'try{'."\n".$script."\n".'}'."\n".'catch(e){console.error("WP-Optimize Minify: An error has occurred in the minified code. \n\n- Original script: '.esc_attr($merged_url).'\n- Error message: "+ e.message);}'."\n";
1049 }
1050 return $script;
1051 }
1052
1053 /**
1054 * Checks if an URL is a font-awesome resource (checks if it contains font-awesome or fontawesome)
1055 *
1056 * @param string $href
1057 * @return boolean
1058 */
1059 public static function is_font_awesome($href) {
1060 return (boolean) preg_match('/font[-_]?awesome/i', $href);
1061 }
1062
1063 /**
1064 * Checks if an URL is a google font resource
1065 *
1066 * @param string $href
1067 * @return boolean
1068 */
1069 public static function is_google_font($href) {
1070 return 'fonts.googleapis.com' === strtolower(parse_url($href, PHP_URL_HOST));
1071 }
1072
1073 /**
1074 * Get the content of an asset, whether local or remote
1075 *
1076 * @param string $url
1077 * @return int|false
1078 */
1079 public static function get_file_size($url) {
1080 $original_url = $url;
1081 if (is_multisite()) {
1082 if (function_exists('get_main_site_id')) {
1083 $site_id = get_main_site_id();
1084 } else {
1085 $network = get_network();
1086 $site_id = $network->site_id;
1087 }
1088 switch_to_blog($site_id);
1089 }
1090 $upload_dir = wp_upload_dir();
1091 $uploads_url = trailingslashit($upload_dir['baseurl']);
1092 $uploads_dir = trailingslashit($upload_dir['basedir']);
1093 if (is_multisite()) {
1094 restore_current_blog();
1095 }
1096 $possible_urls = array(
1097 WP_CONTENT_URL => WP_CONTENT_DIR,
1098 WP_PLUGIN_URL => WP_PLUGIN_DIR,
1099 $uploads_url => $uploads_dir,
1100 get_template_directory_uri() => get_template_directory(),
1101 untrailingslashit(includes_url()) => ABSPATH . WPINC,
1102 );
1103
1104 $file = false;
1105 foreach ($possible_urls as $possible_url => $path) {
1106 $pos = strpos($url, $possible_url);
1107 if (false !== $pos) {
1108 $file = substr_replace($url, $path, $pos, strlen($possible_url));
1109 break;
1110 }
1111 }
1112 if (is_string($file) && file_exists($file)) {
1113 return filesize($file);
1114 }
1115
1116 return self::get_remote_file_size($original_url);
1117 }
1118
1119 /**
1120 * Get the file size of a file hosting in other servers
1121 *
1122 * @param string $url
1123 * @return int|false
1124 */
1125 public static function get_remote_file_size($url) {
1126 $args = array(
1127 // info (needed for google fonts woff files + hinted fonts) as well as to bypass some security filters
1128 'user-agent' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.81 Safari/537.36',
1129 'timeout' => 7
1130 );
1131
1132 // fetch via wordpress functions
1133 $response = wp_remote_get($url, $args);
1134
1135 if (is_wp_error($response)) return false;
1136
1137 if (!empty($response) && is_array($response) && isset($response['headers']['Content-Length'])) {
1138 return $response['headers']['Content-Length'];
1139 }
1140
1141 return false;
1142 }
1143
1144 /**
1145 * Check if it is 'flatsome' handle
1146 *
1147 * @param string $handle Handle of enqueued css file
1148 *
1149 * @return bool
1150 */
1151 public static function is_flatsome_handle($handle) {
1152 return 0 === strcmp('flatsome-googlefonts', $handle);
1153 }
1154
1155 /**
1156 * Fix google fonts url for 'flatsome' theme
1157 *
1158 * @param string $href Enqueued google fonts url
1159 *
1160 * @return string Fixed google fonts url
1161 */
1162 public static function fix_flatsome_google_fonts_url($href) {
1163 // Get query from $href
1164 $query = parse_url($href, PHP_URL_QUERY);
1165 $query_arr = explode('&', $query);
1166
1167 // Separate 'family and display' arguments in query
1168 $family = str_replace('family=', '', $query_arr[0]);
1169 $display_type = $query_arr[1];
1170
1171 // Remove empty fonts
1172 $font_families = explode('|', $family);
1173 $font_families = array_diff($font_families, array(':regular', 'initial', 'inherit', ''));
1174
1175 $system_fonts = array('+apple+system', '-apple-system', 'BlinkMacSystemFont', 'Segoe+UI', 'Helvetica+Neue', 'sans+serif', 'sans-serif', 'Georgia', 'Times', 'Times+New+Roman', 'serif');
1176 $google_fonts = array();
1177
1178 // URL may look like following, fix it
1179 // https://fonts.googleapis.com/css?family=-apple-system,+BlinkMacSystemFont,+%22Segoe+UI%22,+Roboto,+Oxygen-Sans,+Ubuntu,+Cantarell,+%22Helvetica+Neue%22,+sans-serif:regular,700,regular,700|Bebas+Neue:regular,regular&display=block
1180 // http://fonts.googleapis.com/css?family=Bellota:regular|:regular|Lato:regular|Creepster:regular&display=block
1181 // http://fonts.googleapis.com/css?family=Lora:regular,regular|initial|Lato:regular,regular|&display=swap
1182
1183 foreach ($font_families as $font) {
1184 $font_variant = explode(':', $font);
1185
1186 // Remove beginning '+'
1187 $font = str_replace(',+', ',', $font_variant[0]);
1188
1189 // Replace '-' with '+
1190 $font = str_replace(array('-', ' '), '+', $font);
1191
1192 // Remove '"' or '%22'
1193 $font = str_replace(array('%22', '"'), '', $font);
1194 $font_arr = explode(',', $font);
1195 $font_arr = array_diff($font_arr, $system_fonts);
1196 $variant = '';
1197 if (!empty($font_variant[1])) {
1198 $variant = implode(',', array_unique(explode(',', $font_variant[1])));
1199 }
1200 if (empty($variant)) {
1201 $variant = 'regular';
1202 }
1203 foreach ($font_arr as $font) {
1204 $google_fonts[] = $font . ':' . $variant;
1205 }
1206 }
1207 $protocol = is_ssl() ? 'https:' : 'http:';
1208 return $protocol . '//fonts.googleapis.com/css?family=' . implode('|', $google_fonts) . '&' . $display_type;
1209 }
1210
1211 /**
1212 * When BOM removed code is null (due to unrecognised character encoding), logs error message
1213 *
1214 * @param string $url URL of the script/stylesheet
1215 * @param string|false $encoding Character encoding
1216 * @param string|null $code Script/Stylesheet code
1217 *
1218 * @return void
1219 */
1220 private static function maybe_log_error_message($url, $encoding, $code) {
1221 if (null === $code) {
1222 $message = "Minify: Could not process {$url}, it contains invalid characters. ";
1223 if (false === $encoding) {
1224 $message .= "Could not determine its character encoding.";
1225 }
1226 error_log($message);
1227 }
1228 }
1229 }
1230