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

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