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

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