PluginProbe
Powered Cache – Caching and Optimization for WordPress – Easily Improve PageSpeed & Web Vitals Score / trunk
Powered Cache – Caching and Optimization for WordPress – Easily Improve PageSpeed & Web Vitals Score vtrunk
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
← All changes | includes/utils.php +473 -147 2.1.2trunk View file →
@@ -6,8 +6,9 @@
6 6 */
7 7
8 8 namespace PoweredCache\Utils;
9 9
10 +use PoweredCache\Encryption;
10 11 use const PoweredCache\Constants\SETTING_OPTION;
11 12 use RecursiveDirectoryIterator;
12 13 use RecursiveIteratorIterator;
13 14
@@ -42,97 +43,126 @@
42 43 * @return array
43 44 * @since 2.0
44 45 */
45 46 function get_settings( $force_network_wide = false ) {
47 + global $is_apache;
48 +
46 49 $settings = [
47 50 // 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,
51 + 'enable_page_cache' => true,
52 + 'object_cache' => 'off',
53 + 'cache_mobile' => true,
54 + 'cache_mobile_separate_file' => false,
55 + 'loggedin_user_cache' => false,
56 + 'ssl_cache' => true, // deprecated
57 + 'gzip_compression' => false,
58 + 'cache_timeout' => 1440,
56 59 // 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' => '',
60 + 'auto_configure_htaccess' => $is_apache,
61 + 'rejected_user_agents' => '',
62 + 'rejected_cookies' => '',
63 + 'rejected_referrers' => '',
64 + 'vary_cookies' => '',
65 + 'rejected_uri' => '',
66 + 'ignored_query_strings' => '',
67 + 'cache_query_strings' => '',
68 + 'purge_additional_pages' => '',
64 69 // 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,
70 + 'minify_html' => false,
71 + 'minify_html_dom_optimization' => false,
72 + 'combine_google_fonts' => false,
73 + 'swap_google_fonts_display' => true,
74 + 'use_bunny_fonts' => false,
75 + 'minify_css' => false,
76 + 'combine_css' => false,
77 + 'critical_css' => false,
78 + 'critical_css_additional_files' => '',
79 + 'critical_css_excluded_files' => '',
80 + 'critical_css_appended_content' => '',
81 + 'critical_css_fallback' => '',
82 + 'excluded_css_files' => '',
83 + 'remove_unused_css' => false,
84 + 'ucss_safelist' => '',
85 + 'ucss_excluded_files' => '',
86 + 'minify_js' => false,
87 + 'combine_js' => false,
88 + 'excluded_js_files' => '',
89 + 'js_execution_method' => 'blocking', // deprecated @since 3.2
90 + 'js_defer' => false,
91 + 'js_defer_exclusions' => '',
92 + 'js_delay' => false,
93 + 'js_delay_exclusions' => '',
94 + 'js_delay_timeout' => 0,
95 + 'js_execution_optimized_only' => true, // deprecated @since 3.2
96 + 'rewrite_file_optimizer' => $is_apache,
80 97 // media optimization
98 + 'enable_image_optimization' => false,
99 + 'image_optimizer_preferred_format' => '',
100 + 'add_missing_image_dimensions' => false,
81 101 // 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,
102 + 'enable_lazy_load' => false,
103 + 'lazy_load_post_content' => true,
104 + 'lazy_load_images' => true,
105 + 'lazy_load_iframes' => true,
106 + 'lazy_load_widgets' => true,
107 + 'lazy_load_post_thumbnail' => true,
108 + 'lazy_load_avatars' => true,
109 + 'lazy_load_youtube' => false,
110 + 'lazy_load_skip_first_nth_img' => 3,
111 + 'lazy_load_exclusions' => '',
112 + 'disable_wp_lazy_load' => false,
113 + 'disable_wp_embeds' => false,
114 + 'disable_emoji_scripts' => false,
92 115 // cdn
93 - 'enable_cdn' => false,
94 - 'cdn_hostname' => array( '' ),
95 - 'cdn_zone' => array( '' ),
96 - 'cdn_rejected_files' => '',
116 + 'enable_cdn' => false,
117 + 'cdn_hostname' => array( '' ),
118 + 'cdn_zone' => array( '' ),
119 + 'cdn_rejected_files' => '',
97 120 // 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' => '',
121 + 'enable_cache_preload' => false,
122 + 'preload_homepage' => true,
123 + 'preload_public_posts' => true,
124 + 'preload_public_tax' => true,
125 + 'enable_sitemap_preload' => false,
126 + 'preload_request_interval' => 2, // in seconds
127 + 'preload_sitemap' => '',
128 + 'prefetch_dns' => '',
129 + 'preconnect_resource' => '',
130 + 'prefetch_links' => true,
131 + 'enable_lcp_optimization' => false,
105 132 // 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',
133 + 'db_cleanup_post_revisions' => false,
134 + 'db_cleanup_auto_drafts' => false,
135 + 'db_cleanup_trashed_posts' => false,
136 + 'db_cleanup_spam_comments' => false,
137 + 'db_cleanup_trashed_comments' => false,
138 + 'db_cleanup_expired_transients' => false,
139 + 'db_cleanup_all_transients' => false,
140 + 'db_cleanup_optimize_tables' => false,
141 + 'enable_scheduled_db_cleanup' => false,
142 + 'scheduled_db_cleanup_frequency' => 'daily',
116 143 // 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' => '',
144 + 'enable_cloudflare' => false,
145 + 'cloudflare_api_token' => '',
146 + 'cloudflare_email' => '',
147 + 'cloudflare_api_key' => '',
148 + 'cloudflare_zone' => '',
149 + 'enable_heartbeat' => false, // extension status
150 + 'heartbeat_dashboard_status' => 'enable', // enable,disable,modify
151 + 'heartbeat_dashboard_interval' => 60, // default interval in seconds
152 + 'heartbeat_editor_status' => 'enable', // enable,disable,modify
153 + 'heartbeat_editor_interval' => 15, // default interval in seconds
154 + 'heartbeat_frontend_status' => 'enable', // enable,disable,modify
155 + 'heartbeat_frontend_interval' => 60, // default interval in seconds
156 + 'enable_varnish' => false,
157 + 'varnish_ip' => '',
130 158 // misc
131 - 'cache_footprint' => true,
159 + 'cache_footprint' => true,
160 + 'async_cache_cleaning' => false,
161 + 'dev_mode' => false,
132 162 // new options needs to migrate from extensions
133 - 'enable_google_tracking' => false,
134 - 'enable_fb_tracking' => false,
163 + 'enable_google_tracking' => false,
164 + 'enable_fb_tracking' => false,
135 165 ];
136 166
137 167 /**
138 168 * Filter default settings.
@@ -178,9 +208,8 @@
178 208 * Object cache methods keys will use as option
179 209 *
180 210 * @return array $object_caches
181 211 * @since 1.2 apcu added
182 - *
183 212 * @since 1.0
184 213 */
185 214 function get_object_cache_dropins() {
186 215
@@ -340,8 +369,10 @@
340 369
341 370 /**
342 371 * Supported js execution methods
343 372 *
373 + * @depreacated since 3.2
374 + *
344 375 * @return mixed|void
345 376 */
346 377 function js_execution_methods() {
347 378 $methods = [
@@ -347,8 +378,9 @@
347 378 $methods = [
348 379 'blocking' => esc_html__( 'Blocking – (default)', 'powered-cache' ),
349 380 'async' => esc_html__( 'Non-blocking using async', 'powered-cache' ),
350 381 'defer' => esc_html__( 'Non-blocking using defer', 'powered-cache' ),
382 + 'delayed' => esc_html__( 'Delayed for user interaction', 'powered-cache' ),
351 383 ];
352 384
353 385 /**
354 386 * Filter supported JS execution methods.
@@ -482,9 +514,8 @@
482 514 }
483 515
484 516 /**
485 517 * remove directories recursively
486 - *
487 518 * Adopted from W3TC Utility
488 519 *
489 520 * @param string $path The target path
490 521 * @param array $exclude list of the files that will excluded
@@ -688,80 +719,172 @@
688 719 * @since 1.0
689 720 * @since 1.1 powered_cache_post_related_urls filter added
690 721 */
691 722 function get_post_related_urls( $post_id ) {
723 + // Valid post statuses that require cache purging.
724 + $valid_post_statuses = [ 'publish', 'private', 'trash', 'pending', 'draft' ];
725 + $post_status = get_post_status( $post_id );
726 + $post = get_post( $post_id );
692 727
693 - $current_post_status = get_post_status( $post_id );
728 + // Post types that should not have their cache purged.
729 + $excluded_post_types = [ 'nav_menu_item', 'revision' ];
730 + $post_type = get_post_type( $post_id );
731 + $rest_api_route = 'wp/v2';
694 732
695 - // array to collect all our URLs
696 - $related_urls = array();
733 + $related_urls = [];
697 734
698 - if ( get_permalink( $post_id ) ) {
699 - // we're going to add a ton of things to flush.
735 + if ( false !== get_permalink( $post_id ) && in_array( $post_status, $valid_post_statuses, true ) && ! in_array( $post_type, $excluded_post_types, true ) ) {
736 + // Add the post URL.
737 + $related_urls[] = get_permalink( $post_id );
700 738
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 ) );
739 + // Add REST API URL if applicable.
740 + if ( $rest_api_route ) {
741 + $post_type_object = get_post_type_object( $post_type );
742 + if ( ! empty( $post_type_object->show_in_rest ) ) {
743 + $post_type_base = $post_type_object->rest_base ? $post_type_object->rest_base : $post_type_object->name;
744 + $related_urls[] = get_rest_url() . $rest_api_route . '/' . $post_type_base . '/' . $post_id . '/';
745 + } elseif ( in_array( $post_type, [ 'post', 'page' ], true ) ) {
746 + $related_urls[] = get_rest_url() . $rest_api_route . '/' . $post_type . 's/' . $post_id . '/';
706 747 }
707 748 }
708 749
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 ) );
750 + // Add AMP URL if AMP plugin is active.
751 + if ( function_exists( 'amp_get_permalink' ) ) {
752 + $related_urls[] = amp_get_permalink( $post_id );
753 + }
754 +
755 + // Regular AMP url for posts if ant of the following are active:
756 + // https://wordpress.org/plugins/accelerated-mobile-pages/
757 + if ( defined( 'AMPFORWP_AMP_QUERY_VAR' ) ) {
758 + $related_urls[] = get_permalink( $post_id ) . 'amp/';
759 + }
760 +
761 + // Handle trashed post URLs.
762 + if ( 'trash' === $post_status ) {
763 + $trash_permalink = str_replace( '__trashed', '', get_permalink( $post_id ) );
764 + $related_urls[] = $trash_permalink;
765 + $related_urls[] = $trash_permalink . 'feed/';
766 + }
767 +
768 + $taxonomies = get_object_taxonomies( get_post_type( $post_id ), 'objects' );
769 +
770 + // Purge terms associated with the post.
771 + foreach ( $taxonomies as $taxonomy ) {
772 + // Skip non-public taxonomies.
773 + if ( ! $taxonomy->public ) {
774 + continue;
714 775 }
715 - }
716 776
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 ) ) );
777 + $terms = get_the_terms( $post_id, $taxonomy->name );
719 778
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 ) ) );
779 + if ( empty( $terms ) || is_wp_error( $terms ) ) {
780 + continue;
781 + }
782 +
783 + foreach ( $terms as $term ) {
784 + $term_url = get_term_link( $term->slug, $taxonomy->name );
785 + if ( ! is_wp_error( $term_url ) ) {
786 + $related_urls[] = $term_url;
787 + if ( $taxonomy->show_in_rest ) {
788 + $taxonomy_base = $taxonomy->rest_base ? $taxonomy->rest_base : $taxonomy->name;
789 + $related_urls[] = rest_url( "{$taxonomy->rest_namespace}/{$taxonomy_base}/{$term->term_id}/" ); // REST API URL for the term
790 + }
791 + }
792 +
793 + if ( ! is_taxonomy_hierarchical( $taxonomy->name ) ) {
794 + continue;
795 + }
796 +
797 + $ancestors = (array) get_ancestors( $term->term_id, $taxonomy->name );
798 + foreach ( $ancestors as $ancestor ) {
799 + $ancestor_object = get_term( $ancestor, $taxonomy->name );
800 + if ( ! is_a( $ancestor, '\WP_Term' ) ) {
801 + continue;
802 + }
803 +
804 + $ancestor_term_url = get_term_link( $ancestor_object->slug, $taxonomy->name );
805 + if ( ! is_wp_error( $ancestor_term_url ) ) {
806 + $related_urls[] = $ancestor_term_url;
807 + if ( $taxonomy->show_in_rest ) {
808 + $taxonomy_base = $taxonomy->rest_base ? $taxonomy->rest_base : $taxonomy->name;
809 + $related_urls[] = rest_url( "{$taxonomy->rest_namespace}/{$taxonomy_base}/{$ancestor_object->term_id}/" ); // REST API URL for the ancestor term
810 + }
811 + }
812 + }
813 + }
723 814 }
724 815
725 - // Post URL
726 - array_push( $related_urls, get_permalink( $post_id ) );
816 + // Purge author and feed URLs for posts.
817 + if ( 'post' === $post_type ) {
818 + $author_id = get_post_field( 'post_author', $post_id );
819 + $related_urls[] = get_author_posts_url( $author_id );
820 + $related_urls[] = get_author_feed_link( $author_id );
821 + if ( $rest_api_route ) {
822 + $related_urls[] = get_rest_url() . $rest_api_route . '/users/' . $author_id . '/';
823 + }
727 824
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/' );
825 + // Include various feed URLs.
826 + $feed_urls = [
827 + get_bloginfo_rss( 'rdf_url' ),
828 + get_bloginfo_rss( 'rss_url' ),
829 + get_bloginfo_rss( 'rss2_url' ),
830 + get_bloginfo_rss( 'atom_url' ),
831 + get_bloginfo_rss( 'comments_rss2_url' ),
832 + get_post_comments_feed_link( $post_id ),
833 + ];
834 + $related_urls = array_merge( $related_urls, $feed_urls );
733 835 }
734 836
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 ) );
837 + // Purge archive pages if not excluded.
838 + if ( ! in_array( $post_type, [ 'post', 'page' ], true ) ) {
839 + $related_urls[] = get_post_type_archive_link( $post_type );
840 + $related_urls[] = get_post_type_archive_feed_link( $post_type );
738 841 }
739 842
740 - // Regular AMP url for posts
741 - array_push( $related_urls, get_permalink( $post_id ) . 'amp/' );
843 + $post_date = strtotime( $post->post_date );
742 844
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 ) );
845 + if ( $post_date ) {
846 + // Generate the date archive URLs
847 + $year = gmdate( 'Y', $post_date );
848 + $month = gmdate( 'm', $post_date );
849 + $day = gmdate( 'd', $post_date );
745 850
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' ) ) );
851 + $related_urls[] = get_year_link( $year );
852 + $related_urls[] = get_month_link( $year, $month );
853 + $related_urls[] = get_day_link( $year, $month, $day );
854 + }
855 +
856 + // Always purge the home page.
857 + $related_urls[] = home_url( '/' );
858 +
859 + // Purge the posts page if it's set to a static page.
860 + if ( 'page' === get_option( 'show_on_front' ) ) {
861 + $posts_page_id = get_option( 'page_for_posts' );
862 + if ( $posts_page_id ) {
863 + $related_urls[] = get_permalink( $posts_page_id );
752 864 }
753 865 }
754 866 }
755 867
868 + // Remove query strings and ensure unique URLs before purging.
869 + $related_urls = array_unique(
870 + array_map(
871 + function ( $url ) {
872 + return strtok( $url, '?' );
873 + },
874 + $related_urls
875 + )
876 + );
877 +
756 878 /**
757 879 * Filters post related urls.
758 880 *
759 - * @hook powered_cache_post_related_urls
881 + * @hook powered_cache_post_related_urls
760 882 *
761 - * @param {array} $related_urls The list of the URLs that related with the post.
883 + * @param {array} $related_urls The list of the URLs that related with the post.
762 884 *
763 - * @since 1.0
885 + * @return {array} New value.
886 + * @since 1.0
764 887 */
765 888 $related_urls = apply_filters( 'powered_cache_post_related_urls', $related_urls );
766 889
767 890 return $related_urls;
@@ -770,17 +893,17 @@
770 893
771 894 /**
772 895 * Delete cache file
773 896 *
774 - * @param string $url Target URL
897 + * @param string $url Target URL
898 + * @param bool $delete_subdirectories Whether delete subdirectories or not
775 899 *
776 900 * @return bool true when found cache dir, otherwise false
777 901 * @since 1.0
778 902 */
779 -function delete_page_cache( $url ) {
903 +function delete_page_cache( $url, $delete_subdirectories = false ) {
904 + $dir = get_url_dir( trim( $url ) );
780 905
781 - $dir = trailingslashit( get_url_dir( trim( $url ) ) );
782 -
783 906 if ( is_dir( $dir ) ) {
784 907 $files = scandir( $dir );
785 908 foreach ( $files as $file ) {
786 909 /**
@@ -786,15 +909,15 @@
786 909 /**
787 910 * Don't need to lookup for index-https, index-https-mobile etc..
788 911 * Just clean that directory's files only.
789 912 */
790 - if ( ! is_dir( $dir . $file ) && ! in_array( $file, array( '.', '..' ), true ) ) {
913 + if ( ! is_dir( $dir . $file ) && file_exists( $dir . $file ) && ! in_array( $file, array( '.', '..' ), true ) ) {
791 914 unlink( $dir . $file );
792 915 }
916 + }
793 917
794 - if ( file_exists( $dir ) && is_dir_empty( $dir ) ) {
795 - remove_dir( $dir );
796 - }
918 + if ( file_exists( $dir ) && ( $delete_subdirectories || is_dir_empty( $dir ) ) ) {
919 + remove_dir( $dir );
797 920 }
798 921
799 922 return true;
800 923 }
@@ -812,9 +935,9 @@
812 935 * @since 1.1
813 936 */
814 937 function get_url_dir( $url ) {
815 938 $url_info = wp_parse_url( $url );
816 - $sub_dir = $url_info['host'];
939 + $sub_dir = isset( $url_info['host'] ) ? $url_info['host'] : '';
817 940
818 941 if ( ! empty( $url_info['path'] ) ) {
819 942 $sub_dir .= $url_info['path'];
820 943 }
@@ -819,8 +942,9 @@
819 942 $sub_dir .= $url_info['path'];
820 943 }
821 944
822 945 $path = trailingslashit( get_page_cache_dir() ) . ltrim( $sub_dir, '/' );
946 + $path = trailingslashit( $path );
823 947
824 948 /**
825 949 * Filters the path of the given url in the cache directory.
826 950 *
@@ -846,8 +970,12 @@
846 970 $zones = $settings['cdn_zone'];
847 971
848 972 $cdn_addresses = array();
849 973 foreach ( $hostnames as $host_key => $host ) {
974 + if ( filter_var( $host, FILTER_VALIDATE_URL ) ) {
975 + $host = wp_parse_url( $host, PHP_URL_HOST );
976 + }
977 +
850 978 if ( ! empty( $host ) ) {
851 979 $cdn_addresses[ $zones[ $host_key ] ][] = $host;
852 980 }
853 981 }
@@ -856,12 +984,11 @@
856 984 * Filters CDN Addresses.
857 985 *
858 986 * @hook powered_cache_cdn_addresses
859 987 *
860 - * @param {array} $cdn_addresses CDN Adresses.
988 + * @param {array} $cdn_addresses CDN Addresses.
861 989 *
862 990 * @return {array} New value.
863 - *
864 991 * @since 1.0
865 992 */
866 993 return apply_filters( 'powered_cache_cdn_addresses', $cdn_addresses );
867 994 }
@@ -921,9 +1048,8 @@
921 1048 /**
922 1049 * Fires after cache flush.
923 1050 *
924 1051 * @hook powered_cache_flushed
925 - *
926 1052 * @since 1.0
927 1053 */
928 1054 do_action( 'powered_cache_flushed' );
929 1055 }
@@ -953,9 +1079,8 @@
953 1079 *
954 1080 * @param {string} $log_message The log message.
955 1081 *
956 1082 * @return {string} New value.
957 - *
958 1083 * @since 2.0
959 1084 */
960 1085 $log_message = apply_filters( 'powered_cache_log_message', $log_message );
961 1086
@@ -963,15 +1088,14 @@
963 1088 * Filters log message type.
964 1089 *
965 1090 * @hook powered_cache_log_message_type
966 1091 *
967 - * @param {null|int} null default message type
1092 + * @param {int} 0 default message type since 3.6
968 1093 *
969 - * @return {null|int} New value.
970 - *
1094 + * @return {int} New value.
971 1095 * @since 2.0
972 1096 */
973 - $message_type = apply_filters( 'powered_cache_log_message_type', null );
1097 + $message_type = apply_filters( 'powered_cache_log_message_type', 0 );
974 1098 $destination = null;
975 1099
976 1100 if ( defined( 'POWERED_CACHE_LOG_FILE' ) ) {
977 1101 $destination = POWERED_CACHE_LOG_FILE;
@@ -985,9 +1109,8 @@
985 1109 *
986 1110 * @param {null|string} $destination The destination of the log.
987 1111 *
988 1112 * @return {null|string} New value.
989 - *
990 1113 * @since 2.0
991 1114 */
992 1115 $log_destination = apply_filters( 'powered_cache_log_destination', $destination );
993 1116
@@ -995,8 +1118,10 @@
995 1118 if ( defined( 'POWERED_CACHE_LOG_IP' ) && POWERED_CACHE_LOG_IP !== get_client_ip() ) {
996 1119 return false;
997 1120 }
998 1121
1122 + $message_type = absint( $message_type );
1123 +
999 1124 return error_log( $log_message, $message_type, $log_destination ); // phpcs:ignore
1000 1125 }
1001 1126
1002 1127 /**
@@ -1005,12 +1130,14 @@
1005 1130 * @return mixed
1006 1131 */
1007 1132 function get_client_ip() {
1008 1133 if ( ! empty( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) {
1009 - return $_SERVER['HTTP_X_FORWARDED_FOR'];
1134 + return wp_unslash( $_SERVER['HTTP_X_FORWARDED_FOR'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
1010 1135 }
1011 1136
1012 - return $_SERVER['REMOTE_ADDR'];
1137 + if ( isset( $_SERVER['REMOTE_ADDR'] ) ) {
1138 + return wp_unslash( $_SERVER['REMOTE_ADDR'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
1139 + }
1013 1140 }
1014 1141
1015 1142 /**
1016 1143 * Fragment caching
@@ -1102,9 +1229,8 @@
1102 1229 *
1103 1230 * @param {array} $known_headers The list of known HTTP headers.
1104 1231 *
1105 1232 * @return {array} New value.
1106 - *
1107 1233 * @since 1.2
1108 1234 */
1109 1235 $known_headers = apply_filters( 'powered_cache_known_headers', $known_headers );
1110 1236
@@ -1258,13 +1384,213 @@
1258 1384 function powered_cache_is_mobile() {
1259 1385
1260 1386 global $powered_cache_mobile_browsers, $powered_cache_mobile_prefixes;
1261 1387
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 ) ), ' ' );
1388 + $mobile_browsers = addcslashes( implode( '|', preg_split( '/[\s*,\s*]*,+[\s*,\s*]*/', (string) $powered_cache_mobile_browsers ) ), ' ' );
1389 + $mobile_prefixes = addcslashes( implode( '|', preg_split( '/[\s*,\s*]*,+[\s*,\s*]*/', (string) $powered_cache_mobile_prefixes ) ), ' ' );
1264 1390
1265 - if ( ( preg_match( '#^.*(' . $mobile_browsers . ').*#i', $_SERVER['HTTP_USER_AGENT'] ) || preg_match( '#^(' . $mobile_prefixes . ').*#i', substr( $_SERVER['HTTP_USER_AGENT'], 0, 4 ) ) ) ) {
1391 + if ( ! isset( $_SERVER['HTTP_USER_AGENT'] ) ) {
1392 + return false;
1393 + }
1394 +
1395 + $user_agent = wp_unslash( $_SERVER['HTTP_USER_AGENT'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
1396 +
1397 + if ( ( preg_match( '#^.*(' . $mobile_browsers . ').*#i', $user_agent ) || preg_match( '#^(' . $mobile_prefixes . ').*#i', substr( $user_agent, 0, 4 ) ) ) ) {
1266 1398 return true;
1267 1399 }
1268 1400
1269 1401 return false;
1402 +}
1403 +
1404 +/**
1405 + * If the site is a local site.
1406 + *
1407 + * @return bool
1408 + * @since 2.2
1409 + */
1410 +function is_local_site() {
1411 + $site_url = site_url();
1412 + if ( ! is_string( $site_url ) ) {
1413 + $site_url = '';
1414 + }
1415 +
1416 + // Check for localhost and sites using an IP only first.
1417 + $is_local = $site_url && false === strpos( $site_url, '.' );
1418 +
1419 + // Use Core's environment check, if available. Added in 5.5.0 / 5.5.1 (for `local` return value).
1420 + if ( function_exists( 'wp_get_environment_type' ) && 'local' === wp_get_environment_type() ) {
1421 + $is_local = true;
1422 + }
1423 +
1424 + // Then check for usual usual domains used by local dev tools.
1425 + $known_local = array(
1426 + '#\.local$#i',
1427 + '#\.localhost$#i',
1428 + '#\.test$#i',
1429 + '#\.docksal$#i', // Docksal.
1430 + '#\.docksal\.site$#i', // Docksal.
1431 + '#\.dev\.cc$#i', // ServerPress.
1432 + '#\.lndo\.site$#i', // Lando.
1433 + );
1434 +
1435 + if ( ! $is_local ) {
1436 + foreach ( $known_local as $url ) {
1437 + if ( preg_match( $url, $site_url ) ) {
1438 + $is_local = true;
1439 + break;
1440 + }
1441 + }
1442 + }
1443 +
1444 + /**
1445 + * Filters is_local_site check.
1446 + *
1447 + * @param bool $is_local If the current site is a local site.
1448 + *
1449 + * @since 2.2
1450 + */
1451 + $is_local = apply_filters( 'powered_cache_is_local_site', $is_local );
1452 +
1453 + return $is_local;
1454 +}
1455 +
1456 +/**
1457 + * Check whether request for bypass or process normally
1458 + *
1459 + * @return bool
1460 + * @since 3.0
1461 + */
1462 +function bypass_request() {
1463 + if ( isset( $_GET['nopoweredcache'] ) && $_GET['nopoweredcache'] ) { // phpcs:ignore
1464 + return true;
1465 + }
1466 +
1467 + return false;
1468 +}
1469 +
1470 +
1471 +/**
1472 + * Calculate total amount of autoloaded data.
1473 + *
1474 + * @return int autoloaded data in bytes.
1475 + * @global wpdb $wpdb WordPress database abstraction object.
1476 + * @since 3.4
1477 + */
1478 +function autoloaded_options_size() {
1479 + global $wpdb;
1480 +
1481 + return (int) $wpdb->get_var( 'SELECT SUM(LENGTH(option_value)) FROM ' . $wpdb->prefix . 'options WHERE autoload = \'yes\'' ); // phpcs:ignore
1482 +}
1483 +
1484 +/**
1485 + * Mask the string with asterisk
1486 + *
1487 + * @param string $input_string String
1488 + * @param int $unmask_length The length of the string that will not be masked
1489 + *
1490 + * @return string
1491 + * @since 3.4
1492 + */
1493 +function mask_string( $input_string, $unmask_length ) {
1494 + $output_string = substr( $input_string, 0, $unmask_length );
1495 +
1496 + if ( strlen( $input_string ) > $unmask_length ) {
1497 + $output_string .= str_repeat( '*', strlen( $input_string ) - $unmask_length );
1498 + }
1499 +
1500 + return $output_string;
1501 +}
1502 +
1503 +/**
1504 + * Get sensitive data in decrypted form
1505 + *
1506 + * @param string $field field name
1507 + *
1508 + * @return bool|mixed|string
1509 + */
1510 +function get_decrypted_setting( $field ) {
1511 + $settings = \PoweredCache\Utils\get_settings();
1512 + $value = isset( $settings[ $field ] ) ? $settings[ $field ] : '';
1513 +
1514 + // decrypt the value
1515 + $encryption = new Encryption();
1516 + $decrypted_value = $encryption->decrypt( $value );
1517 + if ( false !== $decrypted_value ) {
1518 + return $decrypted_value;
1519 + }
1520 +
1521 + return $value;
1522 +}
1523 +
1524 +
1525 +/**
1526 + * Check if a given IP is within a specific range.
1527 + * Supports both IPv4 and IPv6 addresses.
1528 + *
1529 + * @param string $ip The IP address to check.
1530 + * @param string $range The IP range in CIDR notation.
1531 + *
1532 + * @return bool True if the IP is in the range, false otherwise.
1533 + */
1534 +function is_ip_in_range( $ip, $range ) {
1535 + if ( false !== strpos( $range, '/' ) ) {
1536 + list( $subnet, $bits ) = explode( '/', $range, 2 );
1537 + } else {
1538 + $subnet = $range;
1539 + $bits = ( false === filter_var( $subnet, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6 ) ) ? 32 : 128;
1540 + }
1541 +
1542 + $bits = intval( $bits );
1543 +
1544 + if ( false !== filter_var( $subnet, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6 ) ) {
1545 + $subnet_bin = inet_pton( $subnet );
1546 + $ip_bin = inet_pton( $ip );
1547 +
1548 + if ( false === $subnet_bin || false === $ip_bin ) {
1549 + return false;
1550 + }
1551 +
1552 + $subnet_bin = str_pad( $subnet_bin, 16, "\0" );
1553 + $ip_bin = str_pad( $ip_bin, 16, "\0" );
1554 +
1555 + for ( $i = 0; $i * 8 < $bits; $i ++ ) {
1556 + if ( $bits >= ( $i + 1 ) * 8 && $subnet_bin[ $i ] !== $ip_bin[ $i ] ) {
1557 + return false;
1558 + } elseif ( $bits > $i * 8 ) {
1559 + $bitmask = 0xff00 >> ( $bits % 8 );
1560 + if ( ( ord( $subnet_bin[ $i ] ) & $bitmask ) !== ( ord( $ip_bin[ $i ] ) & $bitmask ) ) {
1561 + return false;
1562 + }
1563 + }
1564 + }
1565 +
1566 + return true;
1567 + }
1568 +
1569 + if ( false === filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 ) ) {
1570 + return false;
1571 + }
1572 +
1573 + $subnet_decimal = ip2long( $subnet );
1574 + $ip_decimal = ip2long( $ip );
1575 + $mask_decimal = - 1 << ( 32 - $bits );
1576 +
1577 + return ( $subnet_decimal & $mask_decimal ) === ( $ip_decimal & $mask_decimal );
1578 +}
1579 +
1580 +/**
1581 + * Check if the dev mode is active
1582 + *
1583 + * @return bool
1584 + * @since 3.6
1585 + */
1586 +function is_dev_mode_active() {
1587 + // Check global config first (fast)
1588 + if ( ! empty( $GLOBALS['powered_cache_options']['dev_mode'] ) ) {
1589 + return true;
1590 + }
1591 +
1592 + // Fallback to database option
1593 + $settings = \PoweredCache\Utils\get_settings();
1594 +
1595 + return ! empty( $settings['dev_mode'] );
1270 1596 }