PluginProbe
Autoptimize / 3.1.5
Autoptimize v3.1.5
2.2.2 2.3.0 2.3.1 2.3.2 2.3.3 2.3.4 2.4.0 2.4.1 2.4.2 2.4.3 2.4.4 2.5.0 2.5.1 2.6.0 2.6.1 2.6.2 2.7.0 2.7.1 2.7.2 2.7.3 2.7.4 2.7.5 2.7.6 2.7.7 2.7.8 All 107 releases
autoptimize / classes / autoptimizeUtils.php

autoptimizeUtils.php in Autoptimize 3.1.5, at classes/autoptimizeUtils.php

575 lines 21.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * General helpers.
4 */
5
6 if ( ! defined( 'ABSPATH' ) ) {
7 exit;
8 }
9
10 class autoptimizeUtils
11 {
12 /**
13 * Returns true when mbstring is available.
14 *
15 * @param bool|null $override Allows overriding the decision.
16 *
17 * @return bool
18 */
19 public static function mbstring_available( $override = null )
20 {
21 static $available = null;
22
23 if ( null === $available ) {
24 $available = \extension_loaded( 'mbstring' );
25 }
26
27 if ( null !== $override ) {
28 $available = $override;
29 }
30
31 return $available;
32 }
33
34 /**
35 * Multibyte-capable strpos() if support is available on the server.
36 * If not, it falls back to using \strpos().
37 *
38 * @param string $haystack Haystack.
39 * @param string $needle Needle.
40 * @param int $offset Offset.
41 * @param string|null $encoding Encoding. Default null.
42 *
43 * @return int|false
44 */
45 public static function strpos( $haystack, $needle, $offset = 0, $encoding = null )
46 {
47 if ( self::mbstring_available() ) {
48 return ( null === $encoding ) ? \mb_strpos( $haystack, $needle, $offset ) : \mb_strpos( $haystack, $needle, $offset, $encoding );
49 } else {
50 return \strpos( $haystack, $needle, $offset );
51 }
52 }
53
54 /**
55 * Attempts to return the number of characters in the given $string if
56 * mbstring is available. Returns the number of bytes
57 * (instead of characters) as fallback.
58 *
59 * @param string $string String.
60 * @param string|null $encoding Encoding.
61 *
62 * @return int Number of characters or bytes in given $string
63 * (characters if/when supported, bytes otherwise).
64 */
65 public static function strlen( $string, $encoding = null )
66 {
67 if ( self::mbstring_available() ) {
68 return ( null === $encoding ) ? \mb_strlen( $string ) : \mb_strlen( $string, $encoding );
69 } else {
70 return \strlen( $string );
71 }
72 }
73
74 /**
75 * Our wrapper around implementations of \substr_replace()
76 * that attempts to not break things horribly if at all possible.
77 * Uses mbstring if available, before falling back to regular
78 * substr_replace() (which works just fine in the majority of cases).
79 *
80 * @param string $string String.
81 * @param string $replacement Replacement.
82 * @param int $start Start offset.
83 * @param int|null $length Length.
84 * @param string|null $encoding Encoding.
85 *
86 * @return string
87 */
88 public static function substr_replace( $string, $replacement, $start, $length = null, $encoding = null )
89 {
90 if ( self::mbstring_available() ) {
91 $strlen = self::strlen( $string, $encoding );
92
93 if ( $start < 0 ) {
94 if ( -$start < $strlen ) {
95 $start = $strlen + $start;
96 } else {
97 $start = 0;
98 }
99 } elseif ( $start > $strlen ) {
100 $start = $strlen;
101 }
102
103 if ( null === $length || '' === $length ) {
104 $start2 = $strlen;
105 } elseif ( $length < 0 ) {
106 $start2 = $strlen + $length;
107 if ( $start2 < $start ) {
108 $start2 = $start;
109 }
110 } else {
111 $start2 = $start + $length;
112 }
113
114 if ( null === $encoding ) {
115 $leader = $start ? \mb_substr( $string, 0, $start ) : '';
116 $trailer = ( $start2 < $strlen ) ? \mb_substr( $string, $start2, null ) : '';
117 } else {
118 $leader = $start ? \mb_substr( $string, 0, $start, $encoding ) : '';
119 $trailer = ( $start2 < $strlen ) ? \mb_substr( $string, $start2, null, $encoding ) : '';
120 }
121
122 return "{$leader}{$replacement}{$trailer}";
123 }
124
125 return ( null === $length ) ? \substr_replace( $string, $replacement, $start ) : \substr_replace( $string, $replacement, $start, $length );
126 }
127
128 /**
129 * Decides whether this is a "subdirectory site" or not.
130 *
131 * @param bool $override Allows overriding the decision when needed.
132 *
133 * @return bool
134 */
135 public static function siteurl_not_root( $override = null )
136 {
137 static $subdir = null;
138
139 if ( null === $subdir ) {
140 $parts = self::get_ao_wp_site_url_parts();
141 $subdir = ( isset( $parts['path'] ) && ( '/' !== $parts['path'] ) );
142 }
143
144 if ( null !== $override ) {
145 $subdir = $override;
146 }
147
148 return $subdir;
149 }
150
151 /**
152 * Parse AUTOPTIMIZE_WP_SITE_URL into components using \parse_url(), but do
153 * so only once per request/lifecycle.
154 *
155 * @return array
156 */
157 public static function get_ao_wp_site_url_parts()
158 {
159 static $parts = array();
160
161 if ( empty( $parts ) ) {
162 $parts = \parse_url( AUTOPTIMIZE_WP_SITE_URL );
163 }
164
165 return $parts;
166 }
167
168 /**
169 * Modify given $cdn_url to include the site path when needed.
170 *
171 * @param string $cdn_url CDN URL to tweak.
172 * @param bool $force_cache_miss Force a cache miss in order to be able
173 * to re-run the filter.
174 *
175 * @return string
176 */
177 public static function tweak_cdn_url_if_needed( $cdn_url, $force_cache_miss = false )
178 {
179 static $results = array();
180
181 if ( ! isset( $results[ $cdn_url ] ) || $force_cache_miss ) {
182
183 // In order to return unmodified input when there's no need to tweak.
184 $results[ $cdn_url ] = $cdn_url;
185
186 // Behind a default true filter for backcompat, and only for sites
187 // in a subfolder/subdirectory, but still easily turned off if
188 // not wanted/needed...
189 if ( autoptimizeUtils::siteurl_not_root() ) {
190 $check = apply_filters( 'autoptimize_filter_cdn_magic_path_check', true, $cdn_url );
191 if ( $check ) {
192 $site_url_parts = autoptimizeUtils::get_ao_wp_site_url_parts();
193 $cdn_url_parts = \parse_url( $cdn_url );
194 $schemeless = self::is_protocol_relative( $cdn_url );
195 $cdn_url_parts = self::maybe_replace_cdn_path( $site_url_parts, $cdn_url_parts );
196 if ( false !== $cdn_url_parts ) {
197 $results[ $cdn_url ] = self::assemble_parsed_url( $cdn_url_parts, $schemeless );
198 }
199 }
200 }
201 }
202
203 return $results[ $cdn_url ];
204 }
205
206 /**
207 * When siteurl contains a path other than '/' and the CDN URL does not have
208 * a path or it's path is '/', this will modify the CDN URL's path component
209 * to match that of the siteurl.
210 * This is to support "magic" CDN urls that worked that way before v2.4...
211 *
212 * @param array $site_url_parts Site URL components array.
213 * @param array $cdn_url_parts CDN URL components array.
214 *
215 * @return array|false
216 */
217 public static function maybe_replace_cdn_path( array $site_url_parts, array $cdn_url_parts )
218 {
219 if ( isset( $site_url_parts['path'] ) && '/' !== $site_url_parts['path'] ) {
220 if ( ! isset( $cdn_url_parts['path'] ) || '/' === $cdn_url_parts['path'] ) {
221 $cdn_url_parts['path'] = $site_url_parts['path'];
222 return $cdn_url_parts;
223 }
224 }
225
226 return false;
227 }
228
229 /**
230 * Given an array or components returned from \parse_url(), assembles back
231 * the complete URL.
232 * If optional
233 *
234 * @param array $parsed_url URL components array.
235 * @param bool $schemeless Whether the assembled URL should be
236 * protocol-relative (schemeless) or not.
237 *
238 * @return string
239 */
240 public static function assemble_parsed_url( array $parsed_url, $schemeless = false )
241 {
242 $scheme = isset( $parsed_url['scheme'] ) ? $parsed_url['scheme'] . '://' : '';
243 if ( $schemeless ) {
244 $scheme = '//';
245 }
246 $host = isset( $parsed_url['host'] ) ? $parsed_url['host'] : '';
247 $port = isset( $parsed_url['port'] ) ? ':' . $parsed_url['port'] : '';
248 $user = isset( $parsed_url['user'] ) ? $parsed_url['user'] : '';
249 $pass = isset( $parsed_url['pass'] ) ? ':' . $parsed_url['pass'] : '';
250 $pass = ( $user || $pass ) ? "$pass@" : '';
251 $path = isset( $parsed_url['path'] ) ? $parsed_url['path'] : '';
252 $query = isset( $parsed_url['query'] ) ? '?' . $parsed_url['query'] : '';
253 $fragment = isset( $parsed_url['fragment'] ) ? '#' . $parsed_url['fragment'] : '';
254
255 return "$scheme$user$pass$host$port$path$query$fragment";
256 }
257
258 /**
259 * Returns true if given $url is protocol-relative.
260 *
261 * @param string $url URL to check.
262 *
263 * @return bool
264 */
265 public static function is_protocol_relative( $url )
266 {
267 $result = false;
268
269 if ( ! empty( $url ) ) {
270 $result = ( 0 === strpos( $url, '//' ) );
271 }
272
273 return $result;
274 }
275
276 /**
277 * Canonicalizes the given path regardless of it existing or not.
278 *
279 * @param string $path Path to normalize.
280 *
281 * @return string
282 */
283 public static function path_canonicalize( $path )
284 {
285 $patterns = array(
286 '~/{2,}~',
287 '~/(\./)+~',
288 '~([^/\.]+/(?R)*\.{2,}/)~',
289 '~\.\./~',
290 );
291 $replacements = array(
292 '/',
293 '/',
294 '',
295 '',
296 );
297
298 return preg_replace( $patterns, $replacements, $path );
299 }
300
301 /**
302 * Checks to see if 3rd party services are available and stores result in option
303 *
304 * TODO This should be two separate methods.
305 *
306 * @param string $return_result should we return resulting service status array (default no).
307 *
308 * @return null|array Service status or null.
309 */
310 public static function check_service_availability( $return_result = false )
311 {
312 $service_availability_resp = wp_remote_get( 'https://misc.optimizingmatters.com/api/autoptimize_service_availablity.json?from=aomain&ver=' . AUTOPTIMIZE_PLUGIN_VERSION );
313 if ( ! is_wp_error( $service_availability_resp ) ) {
314 if ( '200' == wp_remote_retrieve_response_code( $service_availability_resp ) ) {
315 $availabilities = json_decode( wp_remote_retrieve_body( $service_availability_resp ), true );
316 if ( is_array( $availabilities ) ) {
317 autoptimizeOptionWrapper::update_option( 'autoptimize_service_availablity', $availabilities );
318 if ( $return_result ) {
319 return $availabilities;
320 }
321 }
322 }
323 }
324 return null;
325 }
326
327 /**
328 * Returns true if the string is a valid regex.
329 *
330 * @param string $string String, duh.
331 *
332 * @return bool
333 */
334 public static function str_is_valid_regex( $string )
335 {
336 set_error_handler( function() {}, E_WARNING );
337 $is_regex = ( false !== preg_match( $string, '' ) );
338 restore_error_handler();
339
340 return $is_regex;
341 }
342
343 /**
344 * Returns true if a certain WP plugin is active/loaded.
345 *
346 * @param string $plugin_file Main plugin file.
347 *
348 * @return bool
349 */
350 public static function is_plugin_active( $plugin_file )
351 {
352 static $ipa_exists = null;
353 if ( null === $ipa_exists ) {
354 if ( ! function_exists( '\is_plugin_active' ) ) {
355 require_once ABSPATH . 'wp-admin/includes/plugin.php';
356 }
357 $ipa_exists = function_exists( '\is_plugin_active' );
358 }
359
360 return $ipa_exists && \is_plugin_active( $plugin_file );
361 }
362
363 /**
364 * Returns a node without ID attrib for use in noscript tags
365 *
366 * @param string $node an html tag.
367 *
368 * @return string
369 */
370 public static function remove_id_from_node( $node ) {
371 if ( strpos( $node, 'id=' ) === false || apply_filters( 'autoptimize_filter_utils_keep_ids', false ) ) {
372 return $node;
373 } else {
374 return preg_replace( '#(.*) id=[\'|"].*[\'|"] (.*)#Um', '$1 $2', $node );
375 }
376 }
377
378 /**
379 * Returns true if given $str ends with given $test.
380 *
381 * @param string $str String to check.
382 * @param string $test Ending to match.
383 *
384 * @return bool
385 */
386 public static function str_ends_in( $str, $test )
387 {
388 // @codingStandardsIgnoreStart
389 // substr_compare() is bugged on 5.5.11: https://3v4l.org/qGYBH
390 // return ( 0 === substr_compare( $str, $test, -strlen( $test ) ) );
391 // @codingStandardsIgnoreEnd
392
393 $length = strlen( $test );
394
395 return ( substr( $str, -$length, $length ) === $test );
396 }
397
398 /**
399 * Returns true if a pagecache is found, false if not.
400 * Now used to show notice, might be used later on to (un)hide page caching in AO if no page cache found.
401 *
402 * @param bool $disregard_transient False by default, but can be used to ignore the transient and retest.
403 *
404 * @return bool
405 */
406 public static function find_pagecache( $disregard_transient = false ) {
407 static $_found_pagecache = null;
408
409 if ( null === $_found_pagecache ) {
410 $_page_cache_constants = array(
411 'NgInx' => 'NGINX_HELPER_BASENAME',
412 'Kinsta' => 'KINSTA_CACHE_ZONE',
413 'Presslabs' => 'PL_INSTANCE_REF',
414 'Cache Enabler' => 'CACHE_ENABLER_VERSION',
415 'Speed Booster Pack' => 'SBP_PLUGIN_NAME',
416 'Servebolt' => 'SERVEBOLT_PLUGIN_FILE',
417 'WP CloudFlare Super Page Cache' => 'SWCFPC_PLUGIN_PATH',
418 'Cachify' => 'CACHIFY_CACHE_DIR',
419 'WP Rocket' => 'WP_ROCKET_CACHE_PATH',
420 'WP Optimize' => 'WPO_VERSION',
421 'Autoptimize Pro' => 'AO_PRO_PAGECACHE_CACHE_DIR',
422 );
423 $_page_cache_classes = array(
424 'Pressidium' => 'Ninukis_Plugin',
425 'Swift Performance' => 'Swift_Performance_Cache',
426 'WP Fastest Cache' => 'WpFastestCache',
427 'Quick Cache' => 'c_ws_plugin__qcache_purging_routines',
428 'ZenCache' => 'zencache',
429 'Comet Cache' => 'comet_cache',
430 'WP Engine' => 'WpeCommon',
431 'Flywheel' => 'FlywheelNginxCompat',
432 'Pagely' => 'PagelyCachePurge',
433 );
434 $_page_cache_functions = array(
435 'WP Super Cache' => 'wp_cache_clear_cache',
436 'W3 Total Cache' => 'w3tc_pgcache_flush',
437 'WP Fast Cache' => 'wp_fast_cache_bulk_delete_all',
438 'Rapidcache' => 'rapidcache_clear_cache',
439 'Siteground' => 'sg_cachepress_purge_cache',
440 'WP Super Cache' => 'prune_super_cache',
441 );
442
443 $_found_pagecache = false;
444 if ( true !== $disregard_transient ) {
445 $_ao_pagecache_transient = 'autoptimize_pagecache_check';
446 $_found_pagecache = get_transient( $_ao_pagecache_transient );
447 }
448
449 if ( current_user_can( 'manage_options' ) && false === $_found_pagecache ) {
450 // loop through known pagecache constants.
451 foreach ( $_page_cache_constants as $_name => $_constant ) {
452 if ( defined( $_constant ) ) {
453 $_found_pagecache = $_name;
454 break;
455 }
456 }
457 // and loop through known pagecache classes.
458 if ( false === $_found_pagecache ) {
459 foreach ( $_page_cache_classes as $_name => $_class ) {
460 if ( class_exists( $_class ) ) {
461 $_found_pagecache = $_name;
462 break;
463 }
464 }
465 }
466 // and loop through known pagecache functions.
467 if ( false === $_found_pagecache ) {
468 foreach ( $_page_cache_functions as $_name => $_function ) {
469 if ( function_exists( $_function ) ) {
470 $_found_pagecache = $_name;
471 break;
472 }
473 }
474 }
475
476 // store in transient for 1 week if pagecache found.
477 if ( true === $_found_pagecache && true !== $disregard_transient ) {
478 set_transient( $_ao_pagecache_transient, true, WEEK_IN_SECONDS );
479 }
480 }
481 }
482
483 return $_found_pagecache;
484 }
485
486 /**
487 * Returns true if on one of the AO settings tabs, false if not.
488 * Used to limit notifications to AO settings pages.
489 *
490 * @return bool
491 */
492 public static function is_ao_settings() {
493 $_is_ao_settings = ( str_replace( array( 'autoptimize', 'autoptimize_imgopt', 'ao_critcss', 'autoptimize_extra', 'ao_partners', 'ao_pro_boosters' ), '', $_SERVER['REQUEST_URI'] ) !== $_SERVER['REQUEST_URI'] ? true : false );
494 return $_is_ao_settings;
495 }
496
497 /**
498 * Returns false if no conflicting plugins are found, the name if the plugin if found.
499 *
500 * @return bool|string
501 */
502 public static function find_potential_conflicts() {
503 if ( defined( 'WPFC_WP_CONTENT_BASENAME' ) ) {
504 $_wpfc_options = json_decode( get_option( 'WpFastestCache' ) );
505 foreach ( array( 'wpFastestCacheMinifyCss', 'wpFastestCacheCombineCss', 'wpFastestCacheCombineJs' ) as $_wpfc_conflicting ) {
506 if ( isset( $_wpfc_options->$_wpfc_conflicting ) && 'on' === $_wpfc_options->$_wpfc_conflicting ) {
507 return 'WP Fastest Cache';
508 }
509 }
510 } elseif ( defined( 'W3TC_VERSION' ) ) {
511 $w3tc_config = file_get_contents( WP_CONTENT_DIR . '/w3tc-config/master.php' );
512 $w3tc_minify_on = strpos( $w3tc_config, '"minify.enabled": true' );
513 if ( $w3tc_minify_on ) {
514 return 'W3 Total Cache';
515 }
516 } elseif ( defined( 'SiteGround_Optimizer\VERSION' ) ) {
517 if ( get_option( 'siteground_optimizer_optimize_css' ) == 1 || get_option( 'siteground_optimizer_optimize_javascript' ) == 1 || get_option( 'siteground_optimizer_combine_javascript' ) == 1 || get_option( 'siteground_optimizer_combine_css' ) == 1 ) {
518 return 'Siteground Optimizer';
519 }
520 } elseif ( defined( 'WPO_VERSION' ) ) {
521 $_wpo_options = get_site_option( 'wpo_minify_config' );
522 if ( is_array( $_wpo_options ) && 1 == $_wpo_options['enabled'] && ( 1 == $_wpo_options['enable_css'] || 1 == $_wpo_options['enable_js'] ) ) {
523 return 'WP Optimize';
524 }
525 } elseif ( defined( 'WPACU_PLUGIN_VERSION' ) || defined( 'WPACU_PRO_PLUGIN_VERSION' ) ) {
526 $wpacu_settings_class = new \WpAssetCleanUp\Settings();
527 $wpacu_settings = $wpacu_settings_class->getAll();
528
529 if ( $wpacu_settings['minify_loaded_css'] || $wpacu_settings['minify_loaded_js'] || $wpacu_settings['combine_loaded_js'] || $wpacu_settings['combine_loaded_css'] ) {
530 return 'Asset Cleanup';
531 }
532 } elseif ( defined( 'WP_ROCKET_VERSION' ) && function_exists( 'get_rocket_option' ) ) {
533 if ( get_rocket_option( 'minify_js' ) || get_rocket_option( 'minify_concatenate_js' ) || get_rocket_option( 'minify_css' ) || get_rocket_option( 'minify_concatenate_css' ) || get_rocket_option( 'async_css' ) ) {
534 return 'WP Rocket';
535 }
536 } elseif ( function_exists( 'fvm_get_settings' ) ) {
537 return 'Fast Velocity Minify';
538 }
539
540 return false;
541 }
542
543 /**
544 * Returns true if false if on a local dev environment, true if not.
545 * Used to disallow image opt/ critcss for local dev environments.
546 *
547 * @return bool
548 */
549 public static function is_local_server( $_domain = AUTOPTIMIZE_SITE_DOMAIN ) {
550 static $_is_local_server = null;
551
552 if ( null === $_is_local_server ) {
553 if ( false === strpos( $_domain, '.' ) && false === strpos( $_domain, ':' ) ) {
554 // no dots in domain or colon (ipv6 address), so impossible to reach, this also matches 'localhost' or any other single-word domain.
555 $_is_local_server = true;
556 } elseif ( in_array( $_domain, array( '127.0.0.1', '0000:0000:0000:0000:0000:0000:0000:0001', '0:0:0:0:0:0:0:1', '::1' ) ) ) {
557 // localhost IPv4/ IPv6.
558 $_is_local_server = true;
559 } elseif ( 0 === strpos( $_domain, '127.' ) || 0 === strpos( $_domain, '192.168.' ) || 0 === strpos( $_domain, 'fd' ) ) {
560 // private ranges so unreachable for imgopt/ CCSS.
561 $_is_local_server = true;
562 } elseif ( autoptimizeUtils::str_ends_in( $_domain, '.local') ) {
563 // matches 'whatever.local'.
564 $_is_local_server = true;
565 } else {
566 // likely OK.
567 $_is_local_server = false;
568 }
569 }
570
571 // filter to override result for testing purposes.
572 return apply_filters( 'autoptimize_filter_utils_is_local_server', $_is_local_server );
573 }
574 }
575