PluginProbe
Autoptimize / 3.0.3
Autoptimize v3.0.3
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.0.3, at classes/autoptimizeUtils.php

512 lines 19.2 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 * @return bool
403 */
404 public static function find_pagecache( $disregard_transient = false ) {
405 static $_found_pagecache = null;
406
407 if ( null === $_found_pagecache ) {
408 $_page_cache_constants = array( 'NgInx' => 'NGINX_HELPER_BASENAME', 'Kinsta' => 'KINSTA_CACHE_ZONE', 'Presslabs' => 'PL_INSTANCE_REF', '' => 'Pressidium', 'Cache Enabler' => 'CACHE_ENABLER_VERSION', 'Speed Booster Pack' => 'SBP_PLUGIN_NAME', 'Servebolt' => 'SERVEBOLT_PLUGIN_FILE', 'WP CloudFlare Super Page Cache' => 'SWCFPC_PLUGIN_PATH', 'Cachify' => 'CACHIFY_CACHE_DIR', 'WP Rocket' => 'WP_ROCKET_CACHE_PATH', 'WP Optimize' => 'WPO_VERSION', 'Autoptimize Pro' => 'AO_PRO_PAGECACHE_CACHE_DIR' );
409 $_page_cache_classes = array( 'Swift Performance' => 'Swift_Performance_Cache', 'WP Fastest Cache' => 'WpFastestCache', 'Quick Cache' => 'c_ws_plugin__qcache_purging_routines', 'ZenCache' => 'zencache', 'Comet Cache' => 'comet_cache', 'WP Engine' => 'WpeCommon', 'Flywheel' => 'FlywheelNginxCompat', 'Pagely' => 'PagelyCachePurge' );
410 $_page_cache_functions = array( 'WP Super Cache' => 'wp_cache_clear_cache', 'W3 Total Cache' => 'w3tc_pgcache_flush', 'WP Fast Cache' => 'wp_fast_cache_bulk_delete_all', 'Rapidcache' => 'rapidcache_clear_cache', 'Siteground' => 'sg_cachepress_purge_cache', 'WP Super Cache' => 'prune_super_cache' );
411
412 $_found_pagecache = false;
413 if ( true !== $disregard_transient ) {
414 $_ao_pagecache_transient = 'autoptimize_pagecache_check';
415 $_found_pagecache = get_transient( $_ao_pagecache_transient );
416 }
417
418 if ( current_user_can( 'manage_options' ) && false === $_found_pagecache ) {
419 // loop through known pagecache constants.
420 foreach ( $_page_cache_constants as $_name => $_constant ) {
421 if ( defined( $_constant ) ) {
422 $_found_pagecache = $_name;
423 break;
424 }
425 }
426 // and loop through known pagecache classes.
427 if ( false === $_found_pagecache ) {
428 foreach ( $_page_cache_classes as $_name => $_class ) {
429 if ( class_exists( $_class ) ) {
430 $_found_pagecache = $_name;
431 break;
432 }
433 }
434 }
435 // and loop through known pagecache functions.
436 if ( false === $_found_pagecache ) {
437 foreach ( $_page_cache_functions as $_name => $_function ) {
438 if ( function_exists( $_function ) ) {
439 $_found_pagecache = $_name;
440 break;
441 }
442 }
443 }
444
445 // store in transient for 1 week if pagecache found.
446 if ( true === $_found_pagecache && true !== $disregard_transient ) {
447 set_transient( $_ao_pagecache_transient, true, WEEK_IN_SECONDS );
448 }
449 }
450 }
451
452 return $_found_pagecache;
453 }
454
455 /**
456 * Returns true if on one of the AO settings tabs, false if not.
457 * Used to limit notifications to AO settings pages.
458 *
459 * @return bool
460 */
461 public static function is_ao_settings() {
462 $_is_ao_settings = ( str_replace( array( 'autoptimize', 'autoptimize_imgopt', 'ao_critcss', 'autoptimize_extra', 'ao_partners' ), '', $_SERVER['REQUEST_URI'] ) !== $_SERVER['REQUEST_URI'] ? true : false );
463 return $_is_ao_settings;
464 }
465
466 /**
467 * Returns false if no conflicting plugins are found, the name if the plugin if found.
468 *
469 * @return bool|string
470 */
471 public static function find_potential_conflicts() {
472 if ( defined( 'WPFC_WP_CONTENT_BASENAME' ) ) {
473 $_wpfc_options = json_decode( get_option( 'WpFastestCache' ) );
474 foreach ( array( 'wpFastestCacheMinifyCss', 'wpFastestCacheCombineCss','wpFastestCacheCombineJs' ) as $_wpfc_conflicting ) {
475 if ( isset( $_wpfc_options->$_wpfc_conflicting ) && $_wpfc_options->$_wpfc_conflicting === 'on' ) {
476 return 'WP Fastest Cache';
477 }
478 }
479 } elseif ( defined( 'W3TC_VERSION' ) ) {
480 $w3tcConfig = file_get_contents( WP_CONTENT_DIR . '/w3tc-config/master.php' );
481 $w3tc_minify_on = strpos( $w3tcConfig, '"minify.enabled": true' );
482 if ( $w3tc_minify_on ) {
483 return 'W3 Total Cache';
484 }
485 } elseif ( defined('SiteGround_Optimizer\VERSION') ) {
486 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 ) {
487 return 'Siteground Optimizer';
488 }
489 } elseif ( defined( 'WPO_VERSION' ) ) {
490 $_wpo_options = get_site_option( 'wpo_minify_config' );
491 if ( is_array( $_wpo_options ) && $_wpo_options['enabled'] == 1 && ( $_wpo_options['enable_css'] == 1 || $_wpo_options['enable_js'] == 1 ) ) {
492 return 'WP Optimize';
493 }
494 } elseif ( defined( 'WPACU_PLUGIN_VERSION' ) || defined( 'WPACU_PRO_PLUGIN_VERSION' ) ) {
495 $wpacuSettingsClass = new \WpAssetCleanUp\Settings();
496 $wpacuSettings = $wpacuSettingsClass->getAll();
497
498 if ( $wpacuSettings['minify_loaded_css'] || $wpacuSettings['minify_loaded_js'] || $wpacuSettings['combine_loaded_js'] || $wpacuSettings['combine_loaded_css'] ) {
499 return 'Asset Cleanup';
500 }
501 } elseif ( defined( 'WP_ROCKET_VERSION' ) && function_exists( 'get_rocket_option' ) ) {
502 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' ) ) {
503 return 'WP Rocket';
504 }
505 } elseif ( function_exists( 'fvm_get_settings' ) ) {
506 return 'Fast Velocity Minify';
507 }
508
509 return false;
510 }
511 }
512