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 +507 -157 2.0.1trunk 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,92 +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 - '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,
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,
75 97 // media optimization
98 + 'enable_image_optimization' => false,
99 + 'image_optimizer_preferred_format' => '',
100 + 'add_missing_image_dimensions' => false,
76 101 // 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,
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,
87 115 // cdn
88 - 'enable_cdn' => false,
89 - 'cdn_hostname' => array( '' ),
90 - 'cdn_zone' => array( '' ),
91 - 'cdn_rejected_files' => '',
116 + 'enable_cdn' => false,
117 + 'cdn_hostname' => array( '' ),
118 + 'cdn_zone' => array( '' ),
119 + 'cdn_rejected_files' => '',
92 120 // 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' => '',
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,
100 132 // 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',
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',
111 143 // 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' => '',
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' => '',
125 158 // misc
126 - 'cache_footprint' => true,
159 + 'cache_footprint' => true,
160 + 'async_cache_cleaning' => false,
161 + 'dev_mode' => false,
127 162 // new options needs to migrate from extensions
128 - 'enable_google_tracking' => false,
129 - 'enable_fb_tracking' => false,
163 + 'enable_google_tracking' => false,
164 + 'enable_fb_tracking' => false,
130 165 ];
131 166
132 167 /**
133 168 * Filter default settings.
@@ -173,9 +208,8 @@
173 208 * Object cache methods keys will use as option
174 209 *
175 210 * @return array $object_caches
176 211 * @since 1.2 apcu added
177 - *
178 212 * @since 1.0
179 213 */
180 214 function get_object_cache_dropins() {
181 215
@@ -335,8 +369,10 @@
335 369
336 370 /**
337 371 * Supported js execution methods
338 372 *
373 + * @depreacated since 3.2
374 + *
339 375 * @return mixed|void
340 376 */
341 377 function js_execution_methods() {
342 378 $methods = [
@@ -342,8 +378,9 @@
342 378 $methods = [
343 379 'blocking' => esc_html__( 'Blocking – (default)', 'powered-cache' ),
344 380 'async' => esc_html__( 'Non-blocking using async', 'powered-cache' ),
345 381 'defer' => esc_html__( 'Non-blocking using defer', 'powered-cache' ),
382 + 'delayed' => esc_html__( 'Delayed for user interaction', 'powered-cache' ),
346 383 ];
347 384
348 385 /**
349 386 * Filter supported JS execution methods.
@@ -386,26 +423,8 @@
386 423 return apply_filters( 'powered_cache_cdn_zones', $zones );
387 424 }
388 425
389 426 /**
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 427 * Which version of plugin running
409 428 *
410 429 * @return bool
411 430 */
@@ -495,9 +514,8 @@
495 514 }
496 515
497 516 /**
498 517 * remove directories recursively
499 - *
500 518 * Adopted from W3TC Utility
501 519 *
502 520 * @param string $path The target path
503 521 * @param array $exclude list of the files that will excluded
@@ -701,80 +719,172 @@
701 719 * @since 1.0
702 720 * @since 1.1 powered_cache_post_related_urls filter added
703 721 */
704 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 );
705 727
706 - $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';
707 732
708 - // array to collect all our URLs
709 - $related_urls = array();
733 + $related_urls = [];
710 734
711 - if ( get_permalink( $post_id ) ) {
712 - // 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 );
713 738
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 ) );
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 . '/';
719 747 }
720 748 }
721 749
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 ) );
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;
727 775 }
728 - }
729 776
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 ) ) );
777 + $terms = get_the_terms( $post_id, $taxonomy->name );
732 778
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 ) ) );
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 + }
736 814 }
737 815
738 - // Post URL
739 - 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 + }
740 824
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/' );
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 );
746 835 }
747 836
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 ) );
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 );
751 841 }
752 842
753 - // Regular AMP url for posts
754 - array_push( $related_urls, get_permalink( $post_id ) . 'amp/' );
843 + $post_date = strtotime( $post->post_date );
755 844
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 ) );
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 );
758 850
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' ) ) );
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 );
765 864 }
766 865 }
767 866 }
768 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 +
769 878 /**
770 879 * Filters post related urls.
771 880 *
772 - * @hook powered_cache_post_related_urls
881 + * @hook powered_cache_post_related_urls
773 882 *
774 - * @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.
775 884 *
776 - * @since 1.0
885 + * @return {array} New value.
886 + * @since 1.0
777 887 */
778 888 $related_urls = apply_filters( 'powered_cache_post_related_urls', $related_urls );
779 889
780 890 return $related_urls;
@@ -783,17 +893,17 @@
783 893
784 894 /**
785 895 * Delete cache file
786 896 *
787 - * @param string $url Target URL
897 + * @param string $url Target URL
898 + * @param bool $delete_subdirectories Whether delete subdirectories or not
788 899 *
789 900 * @return bool true when found cache dir, otherwise false
790 901 * @since 1.0
791 902 */
792 -function delete_page_cache( $url ) {
903 +function delete_page_cache( $url, $delete_subdirectories = false ) {
904 + $dir = get_url_dir( trim( $url ) );
793 905
794 - $dir = trailingslashit( get_url_dir( trim( $url ) ) );
795 -
796 906 if ( is_dir( $dir ) ) {
797 907 $files = scandir( $dir );
798 908 foreach ( $files as $file ) {
799 909 /**
@@ -799,15 +909,15 @@
799 909 /**
800 910 * Don't need to lookup for index-https, index-https-mobile etc..
801 911 * Just clean that directory's files only.
802 912 */
803 - 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 ) ) {
804 914 unlink( $dir . $file );
805 915 }
916 + }
806 917
807 - if ( file_exists( $dir ) && is_dir_empty( $dir ) ) {
808 - remove_dir( $dir );
809 - }
918 + if ( file_exists( $dir ) && ( $delete_subdirectories || is_dir_empty( $dir ) ) ) {
919 + remove_dir( $dir );
810 920 }
811 921
812 922 return true;
813 923 }
@@ -825,9 +935,9 @@
825 935 * @since 1.1
826 936 */
827 937 function get_url_dir( $url ) {
828 938 $url_info = wp_parse_url( $url );
829 - $sub_dir = $url_info['host'];
939 + $sub_dir = isset( $url_info['host'] ) ? $url_info['host'] : '';
830 940
831 941 if ( ! empty( $url_info['path'] ) ) {
832 942 $sub_dir .= $url_info['path'];
833 943 }
@@ -832,8 +942,9 @@
832 942 $sub_dir .= $url_info['path'];
833 943 }
834 944
835 945 $path = trailingslashit( get_page_cache_dir() ) . ltrim( $sub_dir, '/' );
946 + $path = trailingslashit( $path );
836 947
837 948 /**
838 949 * Filters the path of the given url in the cache directory.
839 950 *
@@ -859,8 +970,12 @@
859 970 $zones = $settings['cdn_zone'];
860 971
861 972 $cdn_addresses = array();
862 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 +
863 978 if ( ! empty( $host ) ) {
864 979 $cdn_addresses[ $zones[ $host_key ] ][] = $host;
865 980 }
866 981 }
@@ -869,12 +984,11 @@
869 984 * Filters CDN Addresses.
870 985 *
871 986 * @hook powered_cache_cdn_addresses
872 987 *
873 - * @param {array} $cdn_addresses CDN Adresses.
988 + * @param {array} $cdn_addresses CDN Addresses.
874 989 *
875 990 * @return {array} New value.
876 - *
877 991 * @since 1.0
878 992 */
879 993 return apply_filters( 'powered_cache_cdn_addresses', $cdn_addresses );
880 994 }
@@ -934,9 +1048,8 @@
934 1048 /**
935 1049 * Fires after cache flush.
936 1050 *
937 1051 * @hook powered_cache_flushed
938 - *
939 1052 * @since 1.0
940 1053 */
941 1054 do_action( 'powered_cache_flushed' );
942 1055 }
@@ -966,9 +1079,8 @@
966 1079 *
967 1080 * @param {string} $log_message The log message.
968 1081 *
969 1082 * @return {string} New value.
970 - *
971 1083 * @since 2.0
972 1084 */
973 1085 $log_message = apply_filters( 'powered_cache_log_message', $log_message );
974 1086
@@ -976,15 +1088,14 @@
976 1088 * Filters log message type.
977 1089 *
978 1090 * @hook powered_cache_log_message_type
979 1091 *
980 - * @param {null|int} null default message type
1092 + * @param {int} 0 default message type since 3.6
981 1093 *
982 - * @return {null|int} New value.
983 - *
1094 + * @return {int} New value.
984 1095 * @since 2.0
985 1096 */
986 - $message_type = apply_filters( 'powered_cache_log_message_type', null );
1097 + $message_type = apply_filters( 'powered_cache_log_message_type', 0 );
987 1098 $destination = null;
988 1099
989 1100 if ( defined( 'POWERED_CACHE_LOG_FILE' ) ) {
990 1101 $destination = POWERED_CACHE_LOG_FILE;
@@ -998,9 +1109,8 @@
998 1109 *
999 1110 * @param {null|string} $destination The destination of the log.
1000 1111 *
1001 1112 * @return {null|string} New value.
1002 - *
1003 1113 * @since 2.0
1004 1114 */
1005 1115 $log_destination = apply_filters( 'powered_cache_log_destination', $destination );
1006 1116
@@ -1008,8 +1118,10 @@
1008 1118 if ( defined( 'POWERED_CACHE_LOG_IP' ) && POWERED_CACHE_LOG_IP !== get_client_ip() ) {
1009 1119 return false;
1010 1120 }
1011 1121
1122 + $message_type = absint( $message_type );
1123 +
1012 1124 return error_log( $log_message, $message_type, $log_destination ); // phpcs:ignore
1013 1125 }
1014 1126
1015 1127 /**
@@ -1018,12 +1130,14 @@
1018 1130 * @return mixed
1019 1131 */
1020 1132 function get_client_ip() {
1021 1133 if ( ! empty( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) {
1022 - return $_SERVER['HTTP_X_FORWARDED_FOR'];
1134 + return wp_unslash( $_SERVER['HTTP_X_FORWARDED_FOR'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
1023 1135 }
1024 1136
1025 - return $_SERVER['REMOTE_ADDR'];
1137 + if ( isset( $_SERVER['REMOTE_ADDR'] ) ) {
1138 + return wp_unslash( $_SERVER['REMOTE_ADDR'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
1139 + }
1026 1140 }
1027 1141
1028 1142 /**
1029 1143 * Fragment caching
@@ -1115,9 +1229,8 @@
1115 1229 *
1116 1230 * @param {array} $known_headers The list of known HTTP headers.
1117 1231 *
1118 1232 * @return {array} New value.
1119 - *
1120 1233 * @since 1.2
1121 1234 */
1122 1235 $known_headers = apply_filters( 'powered_cache_known_headers', $known_headers );
1123 1236
@@ -1242,5 +1355,242 @@
1242 1355 $doc_url .= '#' . $fragment;
1243 1356 }
1244 1357
1245 1358 return $doc_url;
1359 +}
1360 +
1361 +/**
1362 + * Sanitize CSS
1363 + *
1364 + * @param string $css Input
1365 + *
1366 + * @return string|string[] $css
1367 + * @since 2.1
1368 + */
1369 +function sanitize_css( $css ) {
1370 + $css = wp_strip_all_tags( $css );
1371 +
1372 + if ( false !== strpos( $css, '<' ) ) {
1373 + $css = preg_replace( '#<(\/?\w+)#', '\00003C$1', $css );
1374 + }
1375 +
1376 + return $css;
1377 +}
1378 +
1379 +
1380 +/**
1381 + * Test if the current browser runs on a mobile device (smart phone, tablet, etc.)
1382 + * Sort of custom version of wp_is_mobile
1383 + */
1384 +function powered_cache_is_mobile() {
1385 +
1386 + global $powered_cache_mobile_browsers, $powered_cache_mobile_prefixes;
1387 +
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 ) ), ' ' );
1390 +
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 ) ) ) ) {
1398 + return true;
1399 + }
1400 +
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'] );
1246 1596 }