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-functions.php

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

1,240 lines 37.7 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('wp_enqueue_scripts', 'wp_enqueue_emoji_styles');
701 remove_action('admin_print_styles', 'print_emoji_styles');
702 remove_action('admin_enqueue_scripts', 'wp_enqueue_emoji_styles');
703 remove_filter('the_content_feed', 'wp_staticize_emoji');
704 remove_filter('comment_text_rss', 'wp_staticize_emoji');
705 remove_filter('wp_mail', 'wp_staticize_emoji_for_email');
706 }
707
708 /**
709 * Remove from tinymce
710 *
711 * @return array
712 */
713 public static function disable_emojis_tinymce($plugins) {
714 if (is_array($plugins)) {
715 return array_diff($plugins, array('wpemoji'));
716 } else {
717 return array();
718 }
719 }
720
721 /**
722 * Remove UTF8 BOM.
723 * Returns BOM removed string or null when `$string` does not have a recognised encoding
724 *
725 * @param string $string
726 * @return string|null
727 */
728 public static function remove_utf8_bom($string) {
729 $bom = pack('H*', 'EFBBBF');
730 return preg_replace("/^$bom/ui", '', $string);
731 }
732
733 /**
734 * Remove query string from static css files
735 *
736 * @param string $src
737 * @return string
738 */
739 public static function remove_cssjs_ver($src) {
740 if (stripos($src, '?ver=')) {
741 $src = remove_query_arg('ver', $src);
742 }
743 return $src;
744 }
745
746 /**
747 * Rewrite cache files to http, https or dynamic
748 *
749 * @param string $url
750 * @return string
751 */
752 public static function get_protocol($url) {
753 $wp_domain = trim(preg_replace('/^https?:\/\//i', '', trim(site_url(), '/')));
754 $wpo_minify_options = wp_optimize_minify_config()->get();
755 $default_protocol = $wpo_minify_options['default_protocol'];
756
757 $url = ltrim(preg_replace('/^https?:\/\//i', '', $url), '/'); // better compatibility
758
759 // cdn support
760 $cdn_url = $wpo_minify_options['cdn_url'];
761 $cdn_url = trim(trim(preg_replace('/^https?:\/\//i', '', trim($cdn_url, '/'))), '/');
762
763 // process cdn rewrite
764 if (!empty($cdn_url) && self::is_local_domain($url) !== false) {
765
766 // for js files, we need to consider thew defer for insights option
767 if (substr($url, -3) == '.js') {
768 $async_using_js = 'all' === $wpo_minify_options['enable_defer_js'] && 'async_using_js' === $wpo_minify_options['defer_js_type'];
769 if (!$async_using_js
770 || $wpo_minify_options['cdn_force']
771 ) {
772 $url = str_ireplace($wp_domain, $cdn_url, $url);
773 }
774 } else {
775 $url = str_ireplace($wp_domain, $cdn_url, $url);
776 }
777 }
778
779 // enforce protocol if needed
780 if ('dynamic' == $default_protocol) {
781 if ((isset($_SERVER['HTTPS']) && ('on' == $_SERVER['HTTPS'] || 1 == $_SERVER['HTTPS']))
782 || (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && 'https' == $_SERVER['HTTP_X_FORWARDED_PROTO'])
783 ) {
784 $default_protocol = 'https://';
785 } else {
786 $default_protocol = 'http://';
787 }
788 } else {
789 $default_protocol = $default_protocol.'://';
790 }
791
792 // return
793 return $default_protocol . $url;
794 }
795
796 /**
797 * Exclude processing from some pages / posts / contents
798 *
799 * @return boolean
800 */
801 public static function exclude_contents() {
802 // prevent execution for specific urls
803 if (isset($_SERVER['REQUEST_URI']) && !empty($_SERVER['REQUEST_URI'])) {
804 $disable_on_url = array_filter(array_map('trim', explode("\n", get_option('wpo_min_disable_on_url', ''))));
805 foreach ($disable_on_url as $url) {
806 if (parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) == $url) {
807 return true;
808 }
809 }
810 }
811
812 // for compatibility, let's always skip the checkout page
813 if (function_exists('is_checkout') && is_checkout() === true) {
814 return true;
815 }
816
817 // exclude processing here
818 if (is_feed()
819 || is_admin()
820 || is_preview()
821 || (function_exists('is_customize_preview') && is_customize_preview())
822 || (defined('DOING_AJAX') && DOING_AJAX)
823 || (function_exists('wp_doing_ajax') && wp_doing_ajax())
824 || (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE)
825 || (defined('WP_BLOG_ADMIN') && WP_BLOG_ADMIN)
826 || (defined('WP_NETWORK_ADMIN') && WP_NETWORK_ADMIN)
827 || (defined('WP_INSTALLING') && WP_INSTALLING)
828 || (defined('WP_IMPORTING') && WP_IMPORTING)
829 || (defined('WP_REPAIRING') && WP_REPAIRING)
830 || (defined('XMLRPC_REQUEST') && XMLRPC_REQUEST)
831 || (defined('SHORTINIT') && SHORTINIT)
832 || (defined('REST_REQUEST') && REST_REQUEST)
833 || (isset($_SERVER['REQUEST_METHOD']) && 'POST' === $_SERVER['REQUEST_METHOD'])
834 || (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest')
835 || (isset($_SERVER['REQUEST_URI']) && (strtolower(substr($_SERVER['REQUEST_URI'], -4)) == '.txt' || strtolower(substr($_SERVER['REQUEST_URI'], -4)) == '.xml'))
836 ) {
837 return true;
838 }
839
840 // Thrive plugins and other post_types
841 $arr = array('tve_form_type', 'tve_lead_shortcode', 'tqb_splash');
842 foreach ($arr as $a) {
843 if (isset($_GET['post_type']) && $a === $_GET['post_type']) {
844 return true;
845 }
846 }
847
848 // Thrive architect
849 if (isset($_GET['tve']) && 'true' === $_GET['tve']) return true;
850
851
852 if (is_array($_GET)) {
853 foreach ($_GET as $k => $v) {
854 if (is_string($v) && is_string($k)) {
855 if (stripos($k, 'elementor') !== false || stripos($v, 'elementor') !== false) {
856 return true;
857 }
858 }
859 }
860 }
861
862 // Other _GET parameters
863 if (is_array($_GET)) {
864 $get_params = array_keys($_GET);
865 $excluded_params = array(
866 // customizer preview, visual composer
867 'customize_theme',
868 'preview_id',
869 'preview',
870 // Elementor
871 'elementor-preview',
872 // Divi builder
873 'et_fb',
874 'PageSpeed',
875 // Brizy Editor
876 'brizy-edit',
877 'brizy-edit-iframe',
878 // Beaver builder
879 'fl_builder',
880 'trp-edit-translation',
881 );
882 return (bool) count(array_intersect($excluded_params, $get_params));
883 }
884
885 /**
886 * Wether to exclude the content or not from the minifying process.
887 */
888 return apply_filters('wpo_minify_exclude_contents', false);
889 }
890
891 /**
892 * Get the default files which are ignored / excluded from processing
893 *
894 * @return array
895 */
896 public static function get_default_ignore() {
897 /**
898 * Filters the default exclusions
899 *
900 * @param array The exclusions
901 * @return array
902 */
903 return apply_filters('wp-optimize-minify-default-exclusions', array(
904 '/genericons.css',
905 '/Avada/assets/js/main.min.js',
906 '/woocommerce-product-search/js/product-search.js',
907 '/includes/builder/scripts/frontend-builder-scripts.js',
908 '/assets/js/jquery.themepunch.tools.min.js',
909 '/js/TweenMax.min.js',
910 '/jupiter/assets/js/min/full-scripts',
911 '/wp-content/themes/Divi/core/admin/js/react-dom.production.min.js',
912 '/LayerSlider/static/layerslider/js/greensock.js',
913 '/themes/kalium/assets/js/main.min.js',
914 '/wp-includes/js/mediaelement/wp-mediaelement.min.js',
915 'elementor-admin-bar',
916 'pdfjs-dist',
917 'wordpress-popular-posts',
918 'uploads/bb-plugin/cache', // Beaver builder page specific pages
919 ));
920 }
921
922 /**
923 * Know files that should always be ignored
924 *
925 * @param array $ignore
926 * @return array
927 */
928 public static function compile_ignore_list($ignore) {
929 $ignore_list = self::get_default_ignore();
930 $user_excluded_ignore_items = wp_optimize_minify_config()->get('ignore_list');
931 if (is_array($user_excluded_ignore_items)) $ignore_list = array_diff($ignore_list, $user_excluded_ignore_items);
932 if (is_array($ignore)) {
933 $master_ignore = array_merge(array_map('strtolower', $ignore), array_map('strtolower', $ignore_list));
934 return array_unique($master_ignore);
935 } else {
936 return $ignore_list;
937 }
938 }
939
940 /**
941 * Get the files excluded for IE compatibility
942 *
943 * @return array
944 */
945 public static function get_default_ie_blacklist() {
946 /**
947 * Filters the default IE specific / blacklisted items
948 *
949 * @param array The blacklist
950 * @return array
951 */
952 return apply_filters('wp-optimize-minify-blacklist', array(
953 '/html5shiv.js',
954 '/html5shiv-printshiv.min.js',
955 '/excanvas.js',
956 '/avada-ie9.js',
957 '/respond.js',
958 '/respond.min.js',
959 '/selectivizr.js',
960 '/Avada/assets/css/ie.css',
961 '/html5.js',
962 '/IE9.js',
963 '/fusion-ie9.js',
964 '/vc_lte_ie9.min.css',
965 '/old-ie.css',
966 '/ie.css',
967 '/vc-ie8.min.css',
968 '/mailchimp-for-wp/assets/js/third-party/placeholders.min.js',
969 '/assets/js/plugins/wp-enqueue/min/webfontloader.js',
970 '/a.optnmstr.com/app/js/api.min.js',
971 '/pixelyoursite/js/public.js',
972 '/assets/js/wcdrip-drip.js',
973 '/instantpage.js',
974 ));
975 }
976
977 /**
978 * Get the files excluded for IE compatibility
979 *
980 * @return array
981 */
982 public static function get_ie_blacklist() {
983 $user_excluded_blacklist_items = wp_optimize_minify_config()->get('blacklist');
984 $default_blacklist = self::get_default_ie_blacklist();
985 if (!is_array($user_excluded_blacklist_items)) return $default_blacklist;
986 return array_diff($default_blacklist, $user_excluded_blacklist_items);
987 }
988
989 /**
990 * IE only files that should always be ignored, without incrementing our groups
991 *
992 * @param string $url
993 * @return boolean
994 */
995 public static function is_url_in_ie_blacklist($url) {
996 // from the database
997 $blacklist = self::get_ie_blacklist();
998 // must have
999 $blacklist[] = '/wpo_min/cache/';
1000
1001 // is the url on our list and return
1002 $res = self::in_arrayi($url, $blacklist);
1003 if (true == $res) {
1004 return true;
1005 } else {
1006 return false;
1007 }
1008 }
1009
1010 /**
1011 * Download function with fallback
1012 *
1013 * @param string $url
1014 * @return boolean
1015 */
1016 public static function download_remote($url) {
1017
1018 $args = array(
1019 // info (needed for google fonts woff files + hinted fonts) as well as to bypass some security filters
1020 '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',
1021 'timeout' => 7
1022 );
1023
1024 // fetch via wordpress functions
1025 $response = wp_remote_get(
1026 $url,
1027 /**
1028 * Filters the arguments passed to wp_remote_get when downloading the scripts.
1029 *
1030 * @param array $args - The arguments filtered
1031 * @param string $url - The URL of the downloaded asset
1032 * @return array
1033 */
1034 apply_filters('wpo_minify_download_request_args', $args, $url)
1035 );
1036
1037 $res_code = wp_remote_retrieve_response_code($response);
1038 if (200 == $res_code) {
1039 $data = wp_remote_retrieve_body($response);
1040 if (strlen($data) > 1) {
1041 return $data;
1042 }
1043 }
1044
1045 return false;
1046 }
1047
1048 /**
1049 * Prepares the merged JavaScript
1050 *
1051 * @param string $script
1052 * @param string $merged_url
1053 * @return string
1054 */
1055 public static function prepare_merged_js($script, $merged_url) {
1056 $enable_js_trycatch = wp_optimize_minify_config()->get('enable_js_trycatch');
1057 if ($enable_js_trycatch) {
1058 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";
1059 }
1060 return $script;
1061 }
1062
1063 /**
1064 * Checks if an URL is a font-awesome resource (checks if it contains font-awesome or fontawesome)
1065 *
1066 * @param string $href
1067 * @return boolean
1068 */
1069 public static function is_font_awesome($href) {
1070 return (boolean) preg_match('/font[-_]?awesome/i', $href);
1071 }
1072
1073 /**
1074 * Checks if an URL is a google font resource
1075 *
1076 * @param string $href
1077 * @return boolean
1078 */
1079 public static function is_google_font($href) {
1080 return 'fonts.googleapis.com' === strtolower(parse_url($href, PHP_URL_HOST));
1081 }
1082
1083 /**
1084 * Get the content of an asset, whether local or remote
1085 *
1086 * @param string $url
1087 * @return int|false
1088 */
1089 public static function get_file_size($url) {
1090 $original_url = $url;
1091 if (is_multisite()) {
1092 if (function_exists('get_main_site_id')) {
1093 $site_id = get_main_site_id();
1094 } else {
1095 $network = get_network();
1096 $site_id = $network->site_id;
1097 }
1098 switch_to_blog($site_id);
1099 }
1100 $upload_dir = wp_upload_dir();
1101 $uploads_url = trailingslashit($upload_dir['baseurl']);
1102 $uploads_dir = trailingslashit($upload_dir['basedir']);
1103 if (is_multisite()) {
1104 restore_current_blog();
1105 }
1106 $possible_urls = array(
1107 WP_CONTENT_URL => WP_CONTENT_DIR,
1108 WP_PLUGIN_URL => WP_PLUGIN_DIR,
1109 $uploads_url => $uploads_dir,
1110 get_template_directory_uri() => get_template_directory(),
1111 untrailingslashit(includes_url()) => ABSPATH . WPINC,
1112 );
1113
1114 $file = false;
1115 foreach ($possible_urls as $possible_url => $path) {
1116 $pos = strpos($url, $possible_url);
1117 if (false !== $pos) {
1118 $file = substr_replace($url, $path, $pos, strlen($possible_url));
1119 break;
1120 }
1121 }
1122 if (is_string($file) && file_exists($file)) {
1123 return filesize($file);
1124 }
1125
1126 return self::get_remote_file_size($original_url);
1127 }
1128
1129 /**
1130 * Get the file size of a file hosting in other servers
1131 *
1132 * @param string $url
1133 * @return int|false
1134 */
1135 public static function get_remote_file_size($url) {
1136 $args = array(
1137 // info (needed for google fonts woff files + hinted fonts) as well as to bypass some security filters
1138 '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',
1139 'timeout' => 7
1140 );
1141
1142 // fetch via wordpress functions
1143 $response = wp_remote_get($url, $args);
1144
1145 if (is_wp_error($response)) return false;
1146
1147 if (!empty($response) && is_array($response) && isset($response['headers']['Content-Length'])) {
1148 return $response['headers']['Content-Length'];
1149 }
1150
1151 return false;
1152 }
1153
1154 /**
1155 * Check if it is 'flatsome' handle
1156 *
1157 * @param string $handle Handle of enqueued css file
1158 *
1159 * @return bool
1160 */
1161 public static function is_flatsome_handle($handle) {
1162 return 0 === strcmp('flatsome-googlefonts', $handle);
1163 }
1164
1165 /**
1166 * Fix google fonts url for 'flatsome' theme
1167 *
1168 * @param string $href Enqueued google fonts url
1169 *
1170 * @return string Fixed google fonts url
1171 */
1172 public static function fix_flatsome_google_fonts_url($href) {
1173 // Get query from $href
1174 $query = parse_url($href, PHP_URL_QUERY);
1175 $query_arr = explode('&', $query);
1176
1177 // Separate 'family and display' arguments in query
1178 $family = str_replace('family=', '', $query_arr[0]);
1179 $display_type = $query_arr[1];
1180
1181 // Remove empty fonts
1182 $font_families = explode('|', $family);
1183 $font_families = array_diff($font_families, array(':regular', 'initial', 'inherit', ''));
1184
1185 $system_fonts = array('+apple+system', '-apple-system', 'BlinkMacSystemFont', 'Segoe+UI', 'Helvetica+Neue', 'sans+serif', 'sans-serif', 'Georgia', 'Times', 'Times+New+Roman', 'serif');
1186 $google_fonts = array();
1187
1188 // URL may look like following, fix it
1189 // 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
1190 // http://fonts.googleapis.com/css?family=Bellota:regular|:regular|Lato:regular|Creepster:regular&display=block
1191 // http://fonts.googleapis.com/css?family=Lora:regular,regular|initial|Lato:regular,regular|&display=swap
1192
1193 foreach ($font_families as $font) {
1194 $font_variant = explode(':', $font);
1195
1196 // Remove beginning '+'
1197 $font = str_replace(',+', ',', $font_variant[0]);
1198
1199 // Replace '-' with '+
1200 $font = str_replace(array('-', ' '), '+', $font);
1201
1202 // Remove '"' or '%22'
1203 $font = str_replace(array('%22', '"'), '', $font);
1204 $font_arr = explode(',', $font);
1205 $font_arr = array_diff($font_arr, $system_fonts);
1206 $variant = '';
1207 if (!empty($font_variant[1])) {
1208 $variant = implode(',', array_unique(explode(',', $font_variant[1])));
1209 }
1210 if (empty($variant)) {
1211 $variant = 'regular';
1212 }
1213 foreach ($font_arr as $font) {
1214 $google_fonts[] = $font . ':' . $variant;
1215 }
1216 }
1217 $protocol = is_ssl() ? 'https:' : 'http:';
1218 return $protocol . '//fonts.googleapis.com/css?family=' . implode('|', $google_fonts) . '&' . $display_type;
1219 }
1220
1221 /**
1222 * When BOM removed code is null (due to unrecognised character encoding), logs error message
1223 *
1224 * @param string $url URL of the script/stylesheet
1225 * @param string|false $encoding Character encoding
1226 * @param string|null $code Script/Stylesheet code
1227 *
1228 * @return void
1229 */
1230 private static function maybe_log_error_message($url, $encoding, $code) {
1231 if (null === $code) {
1232 $message = "Minify: Could not process {$url}, it contains invalid characters. ";
1233 if (false === $encoding) {
1234 $message .= "Could not determine its character encoding.";
1235 }
1236 error_log($message);
1237 }
1238 }
1239 }
1240