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

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