PluginProbe
Powered Cache – Caching and Optimization for WordPress – Easily Improve PageSpeed & Web Vitals Score / 2.2
Powered Cache – Caching and Optimization for WordPress – Easily Improve PageSpeed & Web Vitals Score v2.2
trunk 1.0 1.0.1 1.1 1.1.1 1.1.2 1.2 1.2.1 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 2.0 2.0.1 2.0.2 2.0.3 2.0.4 2.1 2.1.1 2.1.2 2.2 2.2.1 All 69 releases
powered-cache / includes / utils.php

utils.php in Powered Cache – Caching and Optimization for WordPress – Easily Improve PageSpeed & Web Vitals Score 2.2, at includes/utils.php

1,327 lines 32.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Utils
4 *
5 * @package PoweredCache
6 */
7
8 namespace PoweredCache\Utils;
9
10 use const PoweredCache\Constants\SETTING_OPTION;
11 use RecursiveDirectoryIterator;
12 use RecursiveIteratorIterator;
13
14 /**
15 * Is plugin activated network wide?
16 *
17 * @param string $plugin_file file path
18 *
19 * @return bool
20 * @since 2.0
21 */
22 function is_network_wide( $plugin_file ) {
23 if ( ! is_multisite() ) {
24 return false;
25 }
26
27 if ( ! function_exists( 'is_plugin_active_for_network' ) ) {
28 require_once ABSPATH . '/wp-admin/includes/plugin.php';
29 }
30
31 return is_plugin_active_for_network( plugin_basename( $plugin_file ) );
32 }
33
34 /**
35 * Get settings with defaults
36 *
37 * @param bool $force_network_wide Whether getting settings for network or not.
38 * The function respects `POWERED_CACHE_IS_NETWORK` by default.
39 * However, `POWERED_CACHE_IS_NETWORK` is not functional on
40 * (de)activation hooks.
41 *
42 * @return array
43 * @since 2.0
44 */
45 function get_settings( $force_network_wide = false ) {
46 $settings = [
47 // basic options
48 'enable_page_cache' => true,
49 'object_cache' => 'off',
50 'cache_mobile' => true,
51 'cache_mobile_separate_file' => false,
52 'loggedin_user_cache' => false,
53 'ssl_cache' => true, // deprecated
54 'gzip_compression' => false,
55 'cache_timeout' => 1440,
56 // advanced options
57 'auto_configure_htaccess' => true,
58 'rejected_user_agents' => '',
59 'rejected_cookies' => '',
60 'vary_cookies' => '',
61 'rejected_uri' => '',
62 'accepted_query_strings' => '',
63 'purge_additional_pages' => '',
64 // file optimization
65 'minify_html' => false,
66 'combine_google_fonts' => false,
67 'swap_google_fonts_display' => true,
68 'minify_css' => false,
69 'combine_css' => false,
70 'critical_css' => false,
71 'critical_css_additional_files' => '',
72 'critical_css_excluded_files' => '',
73 'critical_css_appended_content' => '',
74 'critical_css_fallback' => '',
75 'excluded_css_files' => '',
76 'minify_js' => false,
77 'combine_js' => false,
78 'excluded_js_files' => '',
79 'js_execution_method' => 'blocking',
80 'js_execution_optimized_only' => true,
81 // media optimization
82 'enable_image_optimization' => false,
83 // lazyload
84 'enable_lazy_load' => false,
85 'lazy_load_post_content' => true,
86 'lazy_load_images' => true,
87 'lazy_load_iframes' => true,
88 'lazy_load_widgets' => true,
89 'lazy_load_post_thumbnail' => true,
90 'lazy_load_avatars' => true,
91 'disable_wp_lazy_load' => false,
92 'disable_wp_embeds' => false,
93 'disable_emoji_scripts' => false,
94 // cdn
95 'enable_cdn' => false,
96 'cdn_hostname' => array( '' ),
97 'cdn_zone' => array( '' ),
98 'cdn_rejected_files' => '',
99 // preload
100 'enable_cache_preload' => false,
101 'preload_homepage' => true,
102 'preload_public_posts' => true,
103 'preload_public_tax' => true,
104 'enable_sitemap_preload' => false,
105 'preload_sitemap' => '',
106 'prefetch_dns' => '',
107 'preconnect_resource' => '',
108 // db options
109 'db_cleanup_post_revisions' => false,
110 'db_cleanup_auto_drafts' => false,
111 'db_cleanup_trashed_posts' => false,
112 'db_cleanup_spam_comments' => false,
113 'db_cleanup_trashed_comments' => false,
114 'db_cleanup_expired_transients' => false,
115 'db_cleanup_all_transients' => false,
116 'db_cleanup_optimize_tables' => false,
117 'enable_scheduled_db_cleanup' => false,
118 'scheduled_db_cleanup_frequency' => 'daily',
119 // add-ons
120 'enable_cloudflare' => false,
121 'cloudflare_email' => '',
122 'cloudflare_api_key' => '',
123 'cloudflare_zone' => '',
124 'enable_heartbeat' => false, // extention status
125 'heartbeat_dashboard_status' => 'enable', // enable,disable,modify
126 'heartbeat_dashboard_interval' => 60, // default interval in seconds
127 'heartbeat_editor_status' => 'enable', // enable,disable,modify
128 'heartbeat_editor_interval' => 15, // default interval in seconds
129 'heartbeat_frontend_status' => 'enable', // enable,disable,modify
130 'heartbeat_frontend_interval' => 60, // default interval in seconds
131 'enable_varnish' => false,
132 'varnish_ip' => '',
133 // misc
134 'cache_footprint' => true,
135 // new options needs to migrate from extensions
136 'enable_google_tracking' => false,
137 'enable_fb_tracking' => false,
138 ];
139
140 /**
141 * Filter default settings.
142 *
143 * @hook powered_cache_default_settings
144 *
145 * @param {array} $settings Default settings.
146 *
147 * @return {array} New value
148 * @since 2.0
149 */
150 $default_settings = apply_filters( 'powered_cache_default_settings', $settings );
151
152 if ( POWERED_CACHE_IS_NETWORK || $force_network_wide ) {
153 $settings = get_site_option( SETTING_OPTION, [] );
154 } else {
155 $settings = get_option( SETTING_OPTION, [] );
156 }
157
158 $settings = wp_parse_args( $settings, $default_settings );
159
160 return $settings;
161 }
162
163
164 /**
165 * return base caching dir
166 * use this function to get base caching directory instead of directly calling constant
167 *
168 * @return string path
169 * @since 1.0
170 */
171 function get_cache_dir() {
172 if ( defined( 'POWERED_CACHE_CACHE_DIR' ) ) {
173 return POWERED_CACHE_CACHE_DIR; // don't change unless have a particular reason
174 }
175
176 return WP_CONTENT_DIR . '/cache/';
177 }
178
179
180 /**
181 * Object cache methods keys will use as option
182 *
183 * @return array $object_caches
184 * @since 1.2 apcu added
185 *
186 * @since 1.0
187 */
188 function get_object_cache_dropins() {
189
190 $object_caches = array(
191 'memcache' => POWERED_CACHE_DROPIN_DIR . 'memcache-object-cache.php',
192 'memcached' => POWERED_CACHE_DROPIN_DIR . 'memcached-object-cache.php',
193 'redis' => POWERED_CACHE_DROPIN_DIR . 'redis-object-cache.php',
194 'apcu' => POWERED_CACHE_DROPIN_DIR . 'apcu-object-cache.php',
195 );
196
197 /**
198 * Filter object cache dropins.
199 *
200 * @hook powered_cache_object_cache_dropins
201 *
202 * @param {array} $object_caches The list of supported object-cache dropins.
203 *
204 * @return {array} New value
205 * @since 1.0
206 */
207 return apply_filters( 'powered_cache_object_cache_dropins', $object_caches );
208 }
209
210
211 /**
212 * Get available object cache backends
213 *
214 * @return array
215 * @since 1.2 unset apcu
216 * @since 1.0
217 */
218 function get_available_object_caches() {
219 $object_cache_methods = get_object_cache_dropins();
220
221 if ( ! class_exists( '\Memcache' ) || version_compare( PHP_VERSION, '5.6.20', '<' ) ) {
222 unset( $object_cache_methods['memcache'] );
223 }
224
225 if ( ! class_exists( '\Memcached' ) ) {
226 unset( $object_cache_methods['memcached'] );
227 }
228
229 if ( ! class_exists( '\Redis' ) ) {
230 unset( $object_cache_methods['redis'] );
231 }
232
233 if ( ! function_exists( '\apcu_add' ) ) {
234 unset( $object_cache_methods['apcu'] );
235 }
236
237 return array_keys( $object_cache_methods );
238 }
239
240
241 /**
242 * convert minutes to possible time format
243 *
244 * @param int $timeout_in_minutes TTL in minutes
245 *
246 * @return array
247 * @since 1.1
248 */
249 function get_timeout_with_interval( $timeout_in_minutes ) {
250 $cache_timeout = $timeout_in_minutes;
251 $selected_interval = 'MINUTE';
252
253 if ( $cache_timeout > 0 ) {
254 if ( 0 === (int) ( $cache_timeout % 1440 ) ) {
255 $cache_timeout = $cache_timeout / 1440;
256 $selected_interval = 'DAY';
257 } elseif ( 0 === (int) ( $cache_timeout % 60 ) ) {
258 $cache_timeout = $cache_timeout / 60;
259 $selected_interval = 'HOUR';
260 }
261 }
262
263 return array(
264 $cache_timeout,
265 $selected_interval,
266 );
267 }
268
269 /**
270 * Determine whether display or not display htaccess configuration
271 * .htaccess can affect the way of serving cached files.
272 * Therefore it's only available for network admin on multisite
273 *
274 * @return bool
275 */
276 function can_configure_htaccess() {
277 global $is_apache;
278
279 if ( ! $is_apache ) {
280 return false;
281 }
282
283 if ( POWERED_CACHE_IS_NETWORK && current_user_can( 'manage_network' ) ) {
284 return true;
285 }
286
287 if ( is_multisite() && ! POWERED_CACHE_IS_NETWORK ) {
288 return false;
289 }
290
291 if ( current_user_can( 'manage_options' ) ) {
292 return true;
293 }
294
295 return false;
296 }
297
298 /**
299 * Whether current user capable to do any configuration changes
300 *
301 * @return bool
302 */
303 function can_control_all_settings() {
304 if ( is_multisite() ) {
305 if ( current_user_can( 'manage_network' ) ) {
306 return true;
307 }
308
309 return false;
310 }
311
312 if ( current_user_can( 'manage_options' ) ) {
313 return true;
314 }
315
316 return false;
317 }
318
319
320 /**
321 * Object cache has an effect on all WP
322 * So, it should be available for the network admin on multisite
323 * regardless of network-wide or individual activated
324 *
325 * @return bool
326 */
327 function can_configure_object_cache() {
328 if ( is_multisite() ) {
329 // only allow on network-wide activation
330 if ( POWERED_CACHE_IS_NETWORK && current_user_can( 'manage_network' ) ) {
331 return true;
332 }
333
334 return false;
335 }
336
337 if ( current_user_can( 'manage_options' ) ) {
338 return true;
339 }
340
341 return false;
342 }
343
344 /**
345 * Supported js execution methods
346 *
347 * @return mixed|void
348 */
349 function js_execution_methods() {
350 $methods = [
351 'blocking' => esc_html__( 'Blocking – (default)', 'powered-cache' ),
352 'async' => esc_html__( 'Non-blocking using async', 'powered-cache' ),
353 'defer' => esc_html__( 'Non-blocking using defer', 'powered-cache' ),
354 ];
355
356 /**
357 * Filter supported JS execution methods.
358 *
359 * @hook powered_cache_js_execution_methods
360 *
361 * @param {array} $powered_cache_js_execution_methods JS execution methods.
362 *
363 * @return {array} New value
364 * @since 2.0
365 */
366 return apply_filters( 'powered_cache_js_execution_methods', $methods );
367 }
368
369
370 /**
371 * Get available zones
372 *
373 * @return mixed|void
374 * @since 1.0
375 */
376 function cdn_zones() {
377 $zones = [
378 'all' => esc_html__( 'All files', 'powered-cache' ),
379 'image' => esc_html__( 'Images', 'powered-cache' ),
380 'js' => esc_html__( 'JavaScript', 'powered-cache' ),
381 'css' => esc_html__( 'CSS', 'powered-cache' ),
382 ];
383
384 /**
385 * Filter CDN zone options.
386 *
387 * @hook powered_cache_cdn_zones
388 *
389 * @param {array} $zones CDN Zones (all,image,js,css)
390 *
391 * @return {array} New value
392 * @since 1.0
393 */
394 return apply_filters( 'powered_cache_cdn_zones', $zones );
395 }
396
397 /**
398 * Which version of plugin running
399 *
400 * @return bool
401 */
402 function is_premium() {
403 if ( defined( 'POWERED_CACHE_PREMIUM_PLUGIN_FILE' ) && POWERED_CACHE_PREMIUM_PLUGIN_FILE ) {
404 return true;
405 }
406
407 return false;
408 }
409
410 /**
411 * Scheduled cleanup options
412 *
413 * @return array
414 */
415 function scheduled_cleanup_frequency_options() {
416 $options = [
417 'daily' => esc_html__( 'Daily', 'powered-cache' ),
418 'weekly' => esc_html__( 'Weekly', 'powered-cache' ),
419 'monthly' => esc_html__( 'Monthly', 'powered-cache' ),
420 ];
421
422 /**
423 * Filter scheduled cleanup options.
424 *
425 * @hook powered_cache_scheduled_cleanup_frequency_options
426 *
427 * @param {array} $options The list of supported schedules.
428 *
429 * @return {array} New value
430 * @since 2.0
431 */
432 return apply_filters( 'powered_cache_scheduled_cleanup_frequency_options', $options );
433 }
434
435 /**
436 * ports \settings_errors for SUI
437 *
438 * @param string $setting Slug title of a specific setting
439 * @param bool $sanitize Whether to re-sanitize the setting value before returning errors
440 * @param bool $hide_on_update Whether hide or not hide on update
441 *
442 * @see settings_errors
443 */
444 function settings_errors( $setting = '', $sanitize = false, $hide_on_update = false ) {
445
446 if ( $hide_on_update && ! empty( $_GET['settings-updated'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
447 return;
448 }
449
450 $settings_errors = get_settings_errors( $setting, $sanitize );
451
452 if ( empty( $settings_errors ) ) {
453 return;
454 }
455
456 $output = '';
457
458 foreach ( $settings_errors as $key => $details ) {
459 if ( 'updated' === $details['type'] ) {
460 $details['type'] = 'sui-notice-success';
461 }
462
463 if ( in_array( $details['type'], array( 'error', 'success', 'warning', 'info' ), true ) ) {
464 $details['type'] = 'sui-notice-' . $details['type'];
465 }
466
467 $css_id = sprintf(
468 'setting-error-%s',
469 esc_attr( $details['code'] )
470 );
471
472 $css_class = sprintf(
473 'sui-notice %s settings-error is-dismissible',
474 esc_attr( $details['type'] )
475 );
476
477 $output .= "<div id='$css_id' class='$css_class'> \n";
478 $output .= "<div class='sui-notice-content'><div class='sui-notice-message'>";
479 $output .= "<span class='sui-notice-icon sui-icon-info sui-md' aria-hidden='true'></span>";
480 $output .= "<p>{$details['message']}</p></div></div>";
481 $output .= "</div> \n";
482 }
483
484 echo $output; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
485 }
486
487 /**
488 * remove directories recursively
489 *
490 * Adopted from W3TC Utility
491 *
492 * @param string $path The target path
493 * @param array $exclude list of the files that will excluded
494 *
495 * @return void
496 * @since 1.2.5
497 */
498 function remove_dir( $path, $exclude = array() ) {
499 // phpcs:disable WordPress.PHP.NoSilencedErrors.Discouraged
500 $dir = @opendir( $path );
501
502 if ( $dir ) {
503 while ( ( $entry = @readdir( $dir ) ) !== false ) { // phpcs:ignore WordPress.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition
504 if ( '.' === $entry || '..' === $entry ) {
505 continue;
506 }
507
508 foreach ( $exclude as $mask ) {
509 if ( fnmatch( $mask, basename( $entry ) ) ) {
510 continue 2;
511 }
512 }
513
514 $full_path = $path . DIRECTORY_SEPARATOR . $entry;
515
516 if ( @is_dir( $full_path ) ) {
517 remove_dir( $full_path, $exclude );
518 } else {
519 @unlink( $full_path );
520 }
521 }
522
523 @closedir( $dir );
524 @rmdir( $path );
525 }
526 // phpcs:enable WordPress.PHP.NoSilencedErrors.Discouraged
527 }
528
529 /**
530 * Get base caching directory of the site.
531 *
532 * @return mixed|void
533 * @since 1.1
534 */
535 function site_cache_dir() {
536 $base_dir = get_page_cache_dir();
537
538 // compatible with multisite
539 $site_url = get_site_url();
540
541 $site_url_parsed = wp_parse_url( $site_url );
542
543 $site_path = $site_url_parsed['host'];
544
545 if ( ! empty( $site_url_parsed['path'] ) ) {
546 $site_path .= $site_url_parsed['path'];
547 }
548
549 $site_cache_dir = trailingslashit( $base_dir . $site_path );
550
551 /**
552 * Filter get base caching directory of site
553 *
554 * @hook powered_cache_site_cache_dir
555 *
556 * @param {string} $site_cache_dir Site cache dir.
557 *
558 * @return {string} New value
559 * @since 1.1
560 */
561 return apply_filters( 'powered_cache_site_cache_dir', $site_cache_dir );
562 }
563
564 /**
565 * Page cache base directory.
566 *
567 * @return string
568 * @since 1.1 $url parameter removed
569 * @since 1.0
570 */
571 function get_page_cache_dir() {
572 $path = get_cache_dir() . 'powered-cache/';
573
574 /**
575 * Filter page cache base directory.
576 *
577 * @hook powered_cache_get_page_cache_dir
578 *
579 * @param {string} $path Page cache dir
580 *
581 * @return {string} New value
582 * @since 1.0
583 */
584 return apply_filters( 'powered_cache_get_page_cache_dir', $path );
585 }
586
587 /**
588 * Clean up cache directory
589 *
590 * @return mixed
591 * @since 1.0
592 */
593 function clean_page_cache_dir() {
594 remove_dir( get_page_cache_dir() );
595 }
596
597 /**
598 * Clean cache base for the current site
599 *
600 * @return mixed
601 * @since 1.1
602 */
603 function clean_site_cache_dir() {
604 $site_cache_dir = site_cache_dir();
605
606 /**
607 * When deleting cache for the main site on multisite subdirectory setup
608 * Don't delete other site's cache
609 */
610 if ( is_multisite() && ! is_subdomain_install() && is_main_site() ) {
611 $base_dir = get_page_cache_dir();
612 $directories = glob( $site_cache_dir . '*', GLOB_ONLYDIR );
613 $site_url = get_site_url();
614
615 $site_domain = wp_parse_url( $site_url, PHP_URL_HOST );
616 foreach ( $directories as $directory ) {
617 $dir_name = str_replace( $base_dir . $site_domain, '', $directory );
618 $site_info = get_site_by_path( $site_domain, $dir_name );
619 if ( ! $site_info || is_main_site( $site_info->blog_id ) ) {
620 remove_dir( $directory );
621 }
622 }
623 } else {
624 remove_dir( $site_cache_dir );
625 }
626
627 /**
628 * Fires after deleting site cache dir
629 *
630 * @hook powered_cache_clean_site_cache_dir
631 *
632 * @param {string} $site_cache_dir The caching directory of the current site.
633 *
634 * @since 2.0
635 */
636 do_action( 'powered_cache_clean_site_cache_dir', $site_cache_dir );
637 }
638
639
640 /**
641 * Supported mobile browsers
642 *
643 * @return mixed|void
644 * @since 1.0
645 */
646 function mobile_browsers() {
647 $mobile_browsers
648 = '2.0 MMP, 240x320, 400X240, AvantGo, BlackBerry, Blazer, Cellphone, Danger, DoCoMo, Elaine/3.0, EudoraWeb, Googlebot-Mobile, hiptop, IEMobile, KYOCERA/WX310K, LG/U990, MIDP-2., MMEF20, MOT-V, NetFront, Newt, Nintendo Wii, Nitro, Nokia, Opera Mini, Palm, PlayStation Portable, portalmmm, Proxinet, ProxiNet, SHARP-TQ-GX10, SHG-i900, Small, SonyEricsson, Symbian OS, SymbianOS, TS21i-10, UP.Browser, UP.Link, webOS, Windows CE, WinWAP, YahooSeeker/M1A1-R2D2, iPhone, iPod, Android, BlackBerry9530, LG-TU915 Obigo, LGE VX, webOS, Nokia5800';
649
650 /**
651 * Filters supported mobile browsers.
652 *
653 * @hook powered_cache_mobile_browsers
654 *
655 * @param {string} $mobile_browsers Comma separated list of the defined mobile browsers.
656 *
657 * @since 1.0
658 */
659 return apply_filters( 'powered_cache_mobile_browsers', $mobile_browsers );
660 }
661
662 /**
663 * Supported mobile prefixes
664 *
665 * @return mixed|void
666 * @since 1.0
667 */
668 function mobile_prefixes() {
669 $mobile_prefixes
670 = 'w3c , w3c-, acs-, alav, alca, amoi, audi, avan, benq, bird, blac, blaz, brew, cell, cldc, cmd-, dang, doco, eric, hipt, htc_, inno, ipaq, ipod, jigs, kddi, keji, leno, lg-c, lg-d, lg-g, lge-, lg/u, maui, maxo, midp, mits, mmef, mobi, mot-, moto, mwbp, nec-, newt, noki, palm, pana, pant, phil, play, port, prox, qwap, sage, sams, sany, sch-, sec-, send, seri, sgh-, shar, sie-, siem, smal, smar, sony, sph-, symb, t-mo, teli, tim-, tosh, tsm-, upg1, upsi, vk-v, voda, wap-, wapa, wapi, wapp, wapr, webc, winw, winw, xda , xda-';
671
672 /**
673 * Filters supported mobile prefixes.
674 *
675 * @hook powered_cache_mobile_prefixes
676 *
677 * @param {string} $mobile_prefixes Comma separated list of the defined mobile prefixes.
678 *
679 * @since 1.0
680 */
681 return apply_filters( 'powered_cache_mobile_prefixes', $mobile_prefixes );
682 }
683
684
685 /**
686 * Collect post related urls
687 *
688 * @param int $post_id Post ID
689 *
690 * @return array
691 * @since 1.0
692 * @since 1.1 powered_cache_post_related_urls filter added
693 */
694 function get_post_related_urls( $post_id ) {
695
696 $current_post_status = get_post_status( $post_id );
697
698 // array to collect all our URLs
699 $related_urls = array();
700
701 if ( get_permalink( $post_id ) ) {
702 // we're going to add a ton of things to flush.
703
704 // related category urls
705 $categories = get_the_category( $post_id );
706 if ( $categories ) {
707 foreach ( $categories as $cat ) {
708 array_push( $related_urls, get_category_link( $cat->term_id ) );
709 }
710 }
711
712 // related tags url
713 $tags = get_the_tags( $post_id );
714 if ( $tags ) {
715 foreach ( $tags as $tag ) {
716 array_push( $related_urls, get_tag_link( $tag->term_id ) );
717 }
718 }
719
720 // Author URL
721 array_push( $related_urls, get_author_posts_url( get_post_field( 'post_author', $post_id ) ), get_author_feed_link( get_post_field( 'post_author', $post_id ) ) );
722
723 // Archives and their feeds
724 if ( get_post_type_archive_link( get_post_type( $post_id ) ) === true ) {
725 array_push( $related_urls, get_post_type_archive_link( get_post_type( $post_id ) ), get_post_type_archive_feed_link( get_post_type( $post_id ) ) );
726 }
727
728 // Post URL
729 array_push( $related_urls, get_permalink( $post_id ) );
730
731 // Also clean URL for trashed post.
732 if ( 'trash' === $current_post_status ) {
733 $trashpost = get_permalink( $post_id );
734 $trashpost = str_replace( '__trashed', '', $trashpost );
735 array_push( $related_urls, $trashpost, $trashpost . 'feed/' );
736 }
737
738 // Add in AMP permalink if Automattic's AMP is installed
739 if ( function_exists( 'amp_get_permalink' ) ) {
740 array_push( $related_urls, amp_get_permalink( $post_id ) );
741 }
742
743 // Regular AMP url for posts
744 array_push( $related_urls, get_permalink( $post_id ) . 'amp/' );
745
746 // Feeds
747 array_push( $related_urls, get_bloginfo_rss( 'rdf_url' ), get_bloginfo_rss( 'rss_url' ), get_bloginfo_rss( 'rss2_url' ), get_bloginfo_rss( 'atom_url' ), get_bloginfo_rss( 'comments_rss2_url' ), get_post_comments_feed_link( $post_id ) );
748
749 // Home Page and (if used) posts page
750 array_push( $related_urls, trailingslashit( home_url() ) );
751 if ( get_option( 'show_on_front' ) === 'page' ) {
752 // Ensure we have a page_for_posts setting to avoid empty URL
753 if ( get_option( 'page_for_posts' ) ) {
754 array_push( $related_urls, get_permalink( get_option( 'page_for_posts' ) ) );
755 }
756 }
757 }
758
759 /**
760 * Filters post related urls.
761 *
762 * @hook powered_cache_post_related_urls
763 *
764 * @param {array} $related_urls The list of the URLs that related with the post.
765 *
766 * @since 1.0
767 */
768 $related_urls = apply_filters( 'powered_cache_post_related_urls', $related_urls );
769
770 return $related_urls;
771 }
772
773
774 /**
775 * Delete cache file
776 *
777 * @param string $url Target URL
778 *
779 * @return bool true when found cache dir, otherwise false
780 * @since 1.0
781 */
782 function delete_page_cache( $url ) {
783
784 $dir = trailingslashit( get_url_dir( trim( $url ) ) );
785
786 if ( is_dir( $dir ) ) {
787 $files = scandir( $dir );
788 foreach ( $files as $file ) {
789 /**
790 * Don't need to lookup for index-https, index-https-mobile etc..
791 * Just clean that directory's files only.
792 */
793 if ( ! is_dir( $dir . $file ) && ! in_array( $file, array( '.', '..' ), true ) ) {
794 unlink( $dir . $file );
795 }
796
797 if ( file_exists( $dir ) && is_dir_empty( $dir ) ) {
798 remove_dir( $dir );
799 }
800 }
801
802 return true;
803 }
804
805 return false;
806 }
807
808
809 /**
810 * Get cache location of given url
811 *
812 * @param string $url The url to retrieve path
813 *
814 * @return mixed|void
815 * @since 1.1
816 */
817 function get_url_dir( $url ) {
818 $url_info = wp_parse_url( $url );
819 $sub_dir = $url_info['host'];
820
821 if ( ! empty( $url_info['path'] ) ) {
822 $sub_dir .= $url_info['path'];
823 }
824
825 $path = trailingslashit( get_page_cache_dir() ) . ltrim( $sub_dir, '/' );
826
827 /**
828 * Filters the path of the given url in the cache directory.
829 *
830 * @hook powered_cache_get_url_dir
831 *
832 * @param {string} $path The cache directory of the given URL.
833 *
834 * @since 1.1
835 */
836 return apply_filters( 'powered_cache_get_url_dir', $path );
837 }
838
839 /**
840 * Prepare cdn addresses with hostname + zone
841 *
842 * @return mixed|void
843 * @since 1.0
844 */
845 function cdn_addresses() {
846 $settings = get_settings(); // phpcs:ignore WordPress.WP.DeprecatedFunctions.get_settingsFound
847
848 $hostnames = $settings['cdn_hostname'];
849 $zones = $settings['cdn_zone'];
850
851 $cdn_addresses = array();
852 foreach ( $hostnames as $host_key => $host ) {
853 if ( filter_var( $host, FILTER_VALIDATE_URL ) ) {
854 $host = wp_parse_url( $host, PHP_URL_HOST );
855 }
856
857 if ( ! empty( $host ) ) {
858 $cdn_addresses[ $zones[ $host_key ] ][] = $host;
859 }
860 }
861
862 /**
863 * Filters CDN Addresses.
864 *
865 * @hook powered_cache_cdn_addresses
866 *
867 * @param {array} $cdn_addresses CDN Adresses.
868 *
869 * @return {array} New value.
870 *
871 * @since 1.0
872 */
873 return apply_filters( 'powered_cache_cdn_addresses', $cdn_addresses );
874 }
875
876
877 /**
878 * Get list of expired files for given directory
879 *
880 * @param string $path directory location
881 * @param int $lifespan lifespan in seconds
882 *
883 * @return array expired file list
884 * @since 1.1
885 */
886 function get_expired_files( $path, $lifespan = 0 ) {
887
888 $current_time = time();
889
890 $expired_files = array();
891
892 // return immediately if the path is not exist!
893 if ( ! file_exists( $path ) ) {
894 return $expired_files;
895 }
896
897 $files = new RecursiveIteratorIterator( new RecursiveDirectoryIterator( $path ) );
898
899 foreach ( $files as $file ) {
900
901 if ( $file->isDir() ) {
902 continue;
903 }
904
905 $path = $file->getPathname();
906
907 if ( @filemtime( $path ) + $lifespan <= $current_time ) { // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
908 $expired_files[] = $path;
909 }
910 }
911
912 return $expired_files;
913 }
914
915
916 /**
917 * Flush object cache and clean cache directory
918 *
919 * @since 1.0
920 */
921 function powered_cache_flush() {
922 if ( function_exists( 'wp_cache_flush' ) ) {
923 wp_cache_flush();
924 }
925
926 remove_dir( get_cache_dir() );
927
928 /**
929 * Fires after cache flush.
930 *
931 * @hook powered_cache_flushed
932 *
933 * @since 1.0
934 */
935 do_action( 'powered_cache_flushed' );
936 }
937
938 /**
939 * Log to stdout or a file
940 *
941 * @param string $message Log message
942 *
943 * @return bool
944 */
945 function log( $message ) {
946 if ( ! defined( 'POWERED_CACHE_ENABLE_LOG' ) ) {
947 return false;
948 }
949
950 if ( ! POWERED_CACHE_ENABLE_LOG ) {
951 return false;
952 }
953
954 $log_message = gmdate( 'H:i:s' ) . ' ' . getmypid() . ' ' . get_client_ip() . " {$message}" . PHP_EOL;
955
956 /**
957 * Filters log message.
958 *
959 * @hook powered_cache_log_message
960 *
961 * @param {string} $log_message The log message.
962 *
963 * @return {string} New value.
964 *
965 * @since 2.0
966 */
967 $log_message = apply_filters( 'powered_cache_log_message', $log_message );
968
969 /**
970 * Filters log message type.
971 *
972 * @hook powered_cache_log_message_type
973 *
974 * @param {null|int} null default message type
975 *
976 * @return {null|int} New value.
977 *
978 * @since 2.0
979 */
980 $message_type = apply_filters( 'powered_cache_log_message_type', null );
981 $destination = null;
982
983 if ( defined( 'POWERED_CACHE_LOG_FILE' ) ) {
984 $destination = POWERED_CACHE_LOG_FILE;
985 $message_type = 3;
986 }
987
988 /**
989 * Filters destination of the log.
990 *
991 * @hook powered_cache_log_destination
992 *
993 * @param {null|string} $destination The destination of the log.
994 *
995 * @return {null|string} New value.
996 *
997 * @since 2.0
998 */
999 $log_destination = apply_filters( 'powered_cache_log_destination', $destination );
1000
1001 // don't log anything when it used for particular IP address
1002 if ( defined( 'POWERED_CACHE_LOG_IP' ) && POWERED_CACHE_LOG_IP !== get_client_ip() ) {
1003 return false;
1004 }
1005
1006 return error_log( $log_message, $message_type, $log_destination ); // phpcs:ignore
1007 }
1008
1009 /**
1010 * Get client raw ip
1011 *
1012 * @return mixed
1013 */
1014 function get_client_ip() {
1015 if ( ! empty( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) {
1016 return $_SERVER['HTTP_X_FORWARDED_FOR'];
1017 }
1018
1019 return $_SERVER['REMOTE_ADDR'];
1020 }
1021
1022 /**
1023 * Fragment caching
1024 *
1025 * @link https://gist.github.com/markjaquith/2653957
1026 * @see https://gist.github.com/westonruter/5475349
1027 *
1028 * @param string $key Fragment key
1029 * @param int $ttl Cache TTL
1030 * @param callable $function callback
1031 *
1032 * @throws \Exception Exception
1033 * @since 1.2
1034 * @since 2.0 Renamed powered_cache_fragment -> \PoweredCache\Utils\cache_fragment
1035 */
1036 function cache_fragment( $key, $ttl, $function ) {
1037 $group = 'powered-fragments';
1038 $output = wp_cache_get( $key, $group );
1039 if ( empty( $output ) ) {
1040 ob_start();
1041 call_user_func( $function );
1042 $output = ob_get_clean();
1043 wp_cache_add( $key, $output, $group, $ttl );
1044 }
1045 echo $output; // phpcs:ignore
1046 }
1047
1048 /**
1049 * Fetches known headers, ported from WP Super Cache but not using apache_response_headers
1050 *
1051 * @return array|false
1052 * @since 1.2
1053 */
1054 function get_response_headers() {
1055 static $known_headers = array(
1056 'Access-Control-Allow-Origin',
1057 'Accept-Ranges',
1058 'Age',
1059 'Allow',
1060 'Cache-Control',
1061 'Connection',
1062 'Content-Encoding',
1063 'Content-Language',
1064 'Content-Length',
1065 'Content-Location',
1066 'Content-MD5',
1067 'Content-Disposition',
1068 'Content-Range',
1069 'Content-Type',
1070 'Date',
1071 'ETag',
1072 'Expires',
1073 'Last-Modified',
1074 'Link',
1075 'Location',
1076 'P3P',
1077 'Pragma',
1078 'Proxy-Authenticate',
1079 'Referrer-Policy',
1080 'Refresh',
1081 'Retry-After',
1082 'Server',
1083 'Status',
1084 'Strict-Transport-Security',
1085 'Trailer',
1086 'Transfer-Encoding',
1087 'Upgrade',
1088 'Vary',
1089 'Via',
1090 'Warning',
1091 'WWW-Authenticate',
1092 'X-Frame-Options',
1093 'Public-Key-Pins',
1094 'X-XSS-Protection',
1095 'Content-Security-Policy',
1096 'X-Pingback',
1097 'X-Content-Security-Policy',
1098 'X-WebKit-CSP',
1099 'X-Content-Type-Options',
1100 'X-Powered-By',
1101 'X-UA-Compatible',
1102 'X-Robots-Tag',
1103 );
1104
1105 /**
1106 * Filters known headers.
1107 *
1108 * @hook powered_cache_known_headers
1109 *
1110 * @param {array} $known_headers The list of known HTTP headers.
1111 *
1112 * @return {array} New value.
1113 *
1114 * @since 1.2
1115 */
1116 $known_headers = apply_filters( 'powered_cache_known_headers', $known_headers );
1117
1118 if ( ! isset( $known_headers['age'] ) ) {
1119 $known_headers = array_map( 'strtolower', $known_headers );
1120 }
1121
1122 $headers = array();
1123
1124 if ( function_exists( 'headers_list' ) ) {
1125 $headers = array();
1126 foreach ( headers_list() as $hdr ) {
1127 $header_parts = explode( ':', $hdr, 2 );
1128 $header_name = isset( $header_parts[0] ) ? trim( $header_parts[0] ) : '';
1129 $header_value = isset( $header_parts[1] ) ? trim( $header_parts[1] ) : '';
1130
1131 $headers[ $header_name ] = $header_value;
1132 }
1133 }
1134
1135 foreach ( $headers as $key => $value ) {
1136 if ( ! in_array( strtolower( $key ), $known_headers, true ) ) {
1137 unset( $headers[ $key ] );
1138 }
1139 }
1140
1141 return $headers;
1142 }
1143
1144
1145 /**
1146 * Check if the given url exists in the cache
1147 *
1148 * @param string $url URL
1149 * @param bool $is_mobile Check mobile cache
1150 * @param bool $is_gzip check gzipped cache
1151 *
1152 * @return bool
1153 */
1154 function is_url_cached( $url, $is_mobile = false, $is_gzip = false ) {
1155 $file_name = 'index';
1156 $url_parts = wp_parse_url( $url );
1157
1158 if ( 'https' === $url_parts['scheme'] ) {
1159 $file_name .= '-https';
1160 }
1161
1162 if ( $is_mobile ) {
1163 $file_name .= '-mobile';
1164 }
1165
1166 $file_name .= '.html';
1167
1168 if ( $is_gzip ) {
1169 $file_name .= '.gz';
1170 }
1171
1172 $rel_path = $url_parts['host'];
1173 if ( ! empty( $url_parts['path'] ) ) {
1174 $rel_path .= $url_parts['path'];
1175 }
1176
1177 $path = trailingslashit( get_page_cache_dir() . $rel_path );
1178
1179 $cache_file = $path . $file_name;
1180
1181 return file_exists( $cache_file );
1182 }
1183
1184 /**
1185 * Check if the permalink structure of the site end with trailingslash
1186 *
1187 * @return bool
1188 * @since 2.0
1189 */
1190 function permalink_structure_has_trailingslash() {
1191 if ( '/' === substr( get_option( 'permalink_structure' ), - 1 ) ) {
1192 return true;
1193 }
1194
1195 return false;
1196 }
1197
1198 /**
1199 * Check if the given directory empty
1200 *
1201 * @param string $dir Path
1202 *
1203 * @return bool
1204 */
1205 function is_dir_empty( $dir ) {
1206 foreach ( new \DirectoryIterator( $dir ) as $file_info ) {
1207 if ( $file_info->isDot() ) {
1208 continue;
1209 }
1210
1211 return false;
1212 }
1213
1214 return true;
1215 }
1216
1217 /**
1218 * Get the documentation url
1219 *
1220 * @param string $path The path of documentation
1221 * @param string $fragment URL Fragment
1222 *
1223 * @return string final URL
1224 */
1225 function get_doc_url( $path = null, $fragment = '' ) {
1226 $doc_site = 'https://docs.poweredcache.com/';
1227 $utm_parameters = '?utm_source=wp_admin&utm_medium=plugin&utm_campaign=settings_page';
1228
1229 if ( ! empty( $path ) ) {
1230 $doc_site .= ltrim( $path, '/' );
1231 }
1232
1233 $doc_url = trailingslashit( $doc_site ) . $utm_parameters;
1234
1235 if ( ! empty( $fragment ) ) {
1236 $doc_url .= '#' . $fragment;
1237 }
1238
1239 return $doc_url;
1240 }
1241
1242 /**
1243 * Sanitize CSS
1244 *
1245 * @param string $css Input
1246 *
1247 * @return string|string[] $css
1248 * @since 2.1
1249 */
1250 function sanitize_css( $css ) {
1251 $css = wp_strip_all_tags( $css );
1252
1253 if ( false !== strpos( $css, '<' ) ) {
1254 $css = preg_replace( '#<(\/?\w+)#', '\00003C$1', $css );
1255 }
1256
1257 return $css;
1258 }
1259
1260
1261 /**
1262 * Test if the current browser runs on a mobile device (smart phone, tablet, etc.)
1263 * Sort of custom version of wp_is_mobile
1264 */
1265 function powered_cache_is_mobile() {
1266
1267 global $powered_cache_mobile_browsers, $powered_cache_mobile_prefixes;
1268
1269 $mobile_browsers = addcslashes( implode( '|', preg_split( '/[\s*,\s*]*,+[\s*,\s*]*/', $powered_cache_mobile_browsers ) ), ' ' );
1270 $mobile_prefixes = addcslashes( implode( '|', preg_split( '/[\s*,\s*]*,+[\s*,\s*]*/', $powered_cache_mobile_prefixes ) ), ' ' );
1271
1272 if ( ( preg_match( '#^.*(' . $mobile_browsers . ').*#i', $_SERVER['HTTP_USER_AGENT'] ) || preg_match( '#^(' . $mobile_prefixes . ').*#i', substr( $_SERVER['HTTP_USER_AGENT'], 0, 4 ) ) ) ) {
1273 return true;
1274 }
1275
1276 return false;
1277 }
1278
1279 /**
1280 * If the site is a local site.
1281 *
1282 * @return bool
1283 * @since 2.2
1284 */
1285 function is_local_site() {
1286 $site_url = site_url();
1287
1288 // Check for localhost and sites using an IP only first.
1289 $is_local = $site_url && false === strpos( $site_url, '.' );
1290
1291 // Use Core's environment check, if available. Added in 5.5.0 / 5.5.1 (for `local` return value).
1292 if ( function_exists( 'wp_get_environment_type' ) && 'local' === wp_get_environment_type() ) {
1293 $is_local = true;
1294 }
1295
1296 // Then check for usual usual domains used by local dev tools.
1297 $known_local = array(
1298 '#\.local$#i',
1299 '#\.localhost$#i',
1300 '#\.test$#i',
1301 '#\.docksal$#i', // Docksal.
1302 '#\.docksal\.site$#i', // Docksal.
1303 '#\.dev\.cc$#i', // ServerPress.
1304 '#\.lndo\.site$#i', // Lando.
1305 );
1306
1307 if ( ! $is_local ) {
1308 foreach ( $known_local as $url ) {
1309 if ( preg_match( $url, $site_url ) ) {
1310 $is_local = true;
1311 break;
1312 }
1313 }
1314 }
1315
1316 /**
1317 * Filters is_local_site check.
1318 *
1319 * @param bool $is_local If the current site is a local site.
1320 *
1321 * @since 2.2
1322 */
1323 $is_local = apply_filters( 'powered_cache_is_local_site', $is_local );
1324
1325 return $is_local;
1326 }
1327