PluginProbe
Powered Cache – Caching and Optimization for WordPress – Easily Improve PageSpeed & Web Vitals Score / 2.1
Powered Cache – Caching and Optimization for WordPress – Easily Improve PageSpeed & Web Vitals Score v2.1
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.1, at includes/utils.php

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