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/dropins/page-cache.php +192 -36 2.0.3trunk View file →
@@ -8,16 +8,24 @@
8 8 $powered_cache_start_time = microtime( true );
9 9
10 10 // Don't cache robots.txt or htacesss
11 11 if ( strpos( $_SERVER['REQUEST_URI'], 'robots.txt' ) !== false || strpos( $_SERVER['REQUEST_URI'], '.htaccess' ) !== false ) {
12 + powered_cache_add_cache_miss_header( "Uncacheable file" );
13 +
12 14 return;
13 15 }
14 16
15 17 // Don't cache non-GET requests
16 18 if ( ! isset( $_SERVER['REQUEST_METHOD'] ) || 'GET' !== $_SERVER['REQUEST_METHOD'] ) {
19 + powered_cache_add_cache_miss_header( "Invalid request method" );
20 +
17 21 return;
18 22 }
19 23
24 +if ( ! isset( $_SERVER['HTTP_USER_AGENT'] ) ) {
25 + $_SERVER['HTTP_USER_AGENT'] = '';
26 +}
27 +
20 28 // Don't cache wp-admin
21 29 if ( is_admin() ) {
22 30 return;
23 31 }
@@ -27,19 +35,37 @@
27 35 $file_extension = trim( preg_replace( '#^.*\.(.*)$#', '$1', $file_extension ) );
28 36
29 37 // Don't cache disallowed extensions. Prevents wp-cron.php, xmlrpc.php, etc.
30 38 if ( ! preg_match( '#index\.php$#i', $_SERVER['REQUEST_URI'] ) && in_array( $file_extension, array( 'php', 'xml', 'xsl' ), true ) ) {
39 + powered_cache_add_cache_miss_header( "Disallowed file extension" );
40 +
31 41 return;
32 42 }
33 43
34 44 if ( ! $GLOBALS['powered_cache_options']['enable_page_cache'] ) {
45 + powered_cache_add_cache_miss_header( "Page Caching is not enabled" );
46 +
35 47 return;
36 48 }
37 49
50 +if ( ! empty( $GLOBALS['powered_cache_options']['dev_mode'] ) ) {
51 + powered_cache_add_cache_miss_header( "Dev mode is enabled" );
52 +
53 + return;
54 +}
55 +
56 +if ( isset( $_GET['nopoweredcache'] ) && $_GET['nopoweredcache'] ) {
57 + powered_cache_add_cache_miss_header( "Passing nopoweredcache with the query" );
58 +
59 + return;
60 +}
61 +
38 62 // Don't cache page with these user agents
39 63 if ( isset( $powered_cache_rejected_user_agents ) && ! empty( $powered_cache_rejected_user_agents ) ) {
40 64 $rejected_user_agents = implode( '|', $powered_cache_rejected_user_agents );
41 - if ( ! empty( $rejected_user_agents ) && isset( $_SERVER['HTTP_USER_AGENT'] ) && preg_match( '#(' . $rejected_user_agents . ')#', $_SERVER['HTTP_USER_AGENT'] ) ) {
65 + if ( ! empty( $rejected_user_agents ) && isset( $_SERVER['HTTP_USER_AGENT'] ) && @preg_match( '#(' . $rejected_user_agents . ')#', $_SERVER['HTTP_USER_AGENT'] ) ) {
66 + powered_cache_add_cache_miss_header( "Rejected user agent" );
67 +
42 68 return;
43 69 }
44 70 }
45 71
@@ -50,44 +76,83 @@
50 76 $mobile_browsers = addcslashes( implode( '|', preg_split( '/[\s*,\s*]*,+[\s*,\s*]*/', $powered_cache_mobile_browsers ) ), ' ' );
51 77 $mobile_prefixes = addcslashes( implode( '|', preg_split( '/[\s*,\s*]*,+[\s*,\s*]*/', $powered_cache_mobile_prefixes ) ), ' ' );
52 78 // Don't cache if mobile detection is activated
53 79 if ( ( preg_match( '#^.*(' . $mobile_browsers . ').*#i', $_SERVER['HTTP_USER_AGENT'] ) || preg_match( '#^(' . $mobile_prefixes . ').*#i', substr( $_SERVER['HTTP_USER_AGENT'], 0, 4 ) ) ) ) {
80 + powered_cache_add_cache_miss_header( "Mobile request" );
81 +
54 82 return;
55 83 }
56 84 }
57 85
86 +// Exclude caching based on HTTP_REFERER
87 +if ( ! empty( $powered_cache_rejected_referrers ) && isset( $_SERVER['HTTP_REFERER'] ) && $_SERVER['HTTP_REFERER'] ) {
88 + foreach ( $powered_cache_rejected_referrers as $referrer_rule ) {
89 + $referrer_rule = trim( $referrer_rule );
90 + if ( $referrer_rule === '' ) {
91 + continue;
92 + }
93 + // If rule starts with ^ or contains regex special chars, treat as regex
94 + if ( preg_match( '/[\\^$.|?*+()\[\]]/', $referrer_rule ) ) {
95 + if ( @preg_match( '#' . $referrer_rule . '#i', $_SERVER['HTTP_REFERER'] ) ) {
96 + if ( preg_match( '#' . $referrer_rule . '#i', $_SERVER['HTTP_REFERER'] ) ) {
97 + powered_cache_add_cache_miss_header( "Rejected referer: $referrer_rule" );
58 98
99 + return;
100 + }
101 + }
102 + } else {
103 + // Plain string match
104 + if ( strpos( $_SERVER['HTTP_REFERER'], $referrer_rule ) !== false ) {
105 + powered_cache_add_cache_miss_header( "Rejected referer: $referrer_rule" );
106 +
107 + return;
108 + }
109 + }
110 + }
111 +}
112 +
59 113 if ( ! empty( $_COOKIE ) ) {
60 - $wp_cookies = array( 'wordpressuser_', 'wordpresspass_', 'wordpress_sec_', 'wordpress_logged_in_' );
114 + $wp_cookies = [ 'wordpressuser_', 'wordpresspass_', 'wordpress_sec_', 'wordpress_logged_in_' ];
115 + $comment_cookies = [ 'comment_author_', 'comment_author_email_', 'comment_author_url_' ];
61 116
62 - //Don't cache if logged in
63 - if ( ! isset( $GLOBALS['powered_cache_options']['loggedin_user_cache'] ) || false === $GLOBALS['powered_cache_options']['loggedin_user_cache'] ) {
64 - // check logged-in cookie
117 + // Check if logged-in caching is disabled or the user is not logged in
118 + if ( empty( $GLOBALS['powered_cache_options']['loggedin_user_cache'] ) || false === powered_cache_get_user_cookie() ) {
119 + // Standard check for logged-in status
65 120 foreach ( $_COOKIE as $key => $value ) {
66 121 foreach ( $wp_cookies as $cookie ) {
67 122 if ( strpos( $key, $cookie ) !== false ) {
68 - // Logged in!
123 + powered_cache_add_cache_miss_header( "User logged-in" );
69 124 return;
70 125 }
71 126 }
127 +
128 + // Check if the user has commented on the site
129 + foreach ( $comment_cookies as $cookie ) {
130 + if ( strpos( $key, $cookie ) !== false ) {
131 + powered_cache_add_cache_miss_header( "User left a comment" );
132 + return;
133 + }
134 + }
72 135 }
73 - }
74 136
75 -
76 - if ( ! empty( $_COOKIE['powered_cache_commented_posts'] ) ) {
77 - foreach ( $_COOKIE['powered_cache_commented_posts'] as $path ) {
78 - if ( rtrim( $path, '/' ) === rtrim( $_SERVER['REQUEST_URI'], '/' ) ) {
79 - // User commented on this post
80 - return;
137 + // Avoid caching pages with specific post comments from users
138 + // it's a bit different from $comment_cookies since we are checking the post path
139 + if ( ! empty( $_COOKIE['powered_cache_commented_posts'] ) ) {
140 + foreach ( $_COOKIE['powered_cache_commented_posts'] as $path ) {
141 + if ( rtrim( $path, '/' ) === rtrim( $_SERVER['REQUEST_URI'], '/' ) ) {
142 + powered_cache_add_cache_miss_header( "User commented" );
143 + return;
144 + }
81 145 }
82 146 }
83 147 }
84 148
85 - // don't cache specific cookie
86 - if ( isset( $powered_cache_rejected_cookies ) && ! empty( $powered_cache_rejected_cookies ) ) {
87 - $rejected_cookies = array_diff( $powered_cache_rejected_cookies, $wp_cookies ); // use diff in case caching for logged-in user
149 + // Skip caching if there are specific rejected cookies, regardless of logged-in status
150 + if ( ! empty( $powered_cache_rejected_cookies ) ) {
151 + $rejected_cookies = array_diff( $powered_cache_rejected_cookies, $wp_cookies, $comment_cookies, ['powered_cache_commented_posts'] );
88 152 $rejected_cookies = implode( '|', $rejected_cookies );
89 - if ( preg_match( '#(' . $rejected_cookies . ')#', var_export( $_COOKIE, true ) ) ) {
153 + if ( @preg_match( '#(' . $rejected_cookies . ')#', var_export( $_COOKIE, true ) ) ) {
154 + powered_cache_add_cache_miss_header( "Rejected cookie" );
90 155 return;
91 156 }
92 157 }
93 158 }
@@ -107,9 +172,11 @@
107 172 if ( empty( $exception ) ) {
108 173 continue;
109 174 }
110 175
111 - if ( preg_match( '#^(' . $exception . ')$#', $_SERVER['REQUEST_URI'] ) ) {
176 + if ( @preg_match( '#^(' . $exception . ')$#', $_SERVER['REQUEST_URI'] ) ) {
177 + powered_cache_add_cache_miss_header( "Rejected page" );
178 +
112 179 return;
113 180 }
114 181 }
115 182 }
@@ -115,16 +182,22 @@
115 182 }
116 183
117 184
118 185 if ( ! empty( $_GET ) ) {
119 - if ( ! isset( $powered_cache_accepted_query_strings ) ) {
120 - $powered_cache_accepted_query_strings = [];
186 + if ( ! isset( $powered_cache_ignored_query_strings ) ) {
187 + $powered_cache_ignored_query_strings = [];
121 188 }
122 189
123 - $query_params = array_diff_key( $_GET, array_flip( $powered_cache_accepted_query_strings ) );
190 + $query_params = array_diff_key( $_GET, array_flip( $powered_cache_ignored_query_strings ) );
124 191
192 + if ( ! isset( $powered_cache_cache_query_strings ) ) {
193 + $powered_cache_cache_query_strings = [];
194 + }
195 +
125 196 // don't cache when there is not allowed query parameter exists
126 - if ( ! empty( $query_params ) ) {
197 + if ( ! empty( $query_params ) && ! array_intersect_key( $_GET, array_flip( $powered_cache_cache_query_strings ) ) ) {
198 + powered_cache_add_cache_miss_header( "Disallowed query parameter exists" );
199 +
127 200 return;
128 201 }
129 202 }
130 203
@@ -147,21 +220,43 @@
147 220 if ( strlen( $buffer ) < 255 ) {
148 221 return $buffer;
149 222 }
150 223
151 - // Don't cache search, 404, or password protected
152 - if ( is_404() || is_search() || ! empty( $post->post_password ) ) {
224 + // maybe we shouldn't cache template file has this constant
225 + // dont check DONOTCACHEPAGE strictly some plugins define string instead bool flag
226 + if ( defined( 'DONOTCACHEPAGE' ) && DONOTCACHEPAGE ) {
227 + powered_cache_add_cache_miss_header( "DONOTCACHEPAGE defined" );
228 +
153 229 return $buffer;
154 230 }
155 231
156 - // maybe we shouldn't cache template file has this constant
157 - // dont check DONOTCACHEPAGE strictly some plugins define string instead bool flag
158 - if ( defined( 'DONOTCACHEPAGE' ) && DONOTCACHEPAGE ) {
159 - \PoweredCache\Utils\log( sprintf( 'DONOTCACHEPAGE DEFINED on %s', $_SERVER['REQUEST_URI'] ) );
232 + if ( ! empty( $GLOBALS['powered_cache_options']['dev_mode'] ) ) {
233 + powered_cache_add_cache_miss_header( "Dev mode is enabled" );
160 234
161 235 return $buffer;
162 236 }
163 237
238 + // Don't cache password protected posts
239 + if ( ! empty( $post->post_password ) ) {
240 + powered_cache_add_cache_miss_header( "Password protected posts are not cached" );
241 +
242 + return $buffer;
243 + }
244 +
245 + // Don't cache 404 results
246 + if ( function_exists( 'is_404' ) && is_404() ) {
247 + powered_cache_add_cache_miss_header( "404 pages are not cached" );
248 +
249 + return $buffer;
250 + }
251 +
252 + // Don't cache search results
253 + if ( function_exists( 'is_search' ) && is_search() ) {
254 + powered_cache_add_cache_miss_header( "Search result page is not cached" );
255 +
256 + return $buffer;
257 + }
258 +
164 259 /**
165 260 * Filter whether to enable page cache for this request
166 261 *
167 262 * @hook powered_cache_page_cache_enable
@@ -170,13 +265,17 @@
170 265 *
171 266 * @since 1.0
172 267 */
173 268 if ( true !== apply_filters( 'powered_cache_page_cache_enable', true ) ) {
269 + powered_cache_add_cache_miss_header( "Page Cache not enabled for this post" );
270 +
174 271 return $buffer;
175 272 }
176 273
177 274 // only cache when http ok
178 275 if ( 200 !== http_response_code() ) {
276 + powered_cache_add_cache_miss_header( "Response code is not 200" );
277 +
179 278 return $buffer;
180 279 }
181 280
182 281 if ( ! function_exists( '\PoweredCache\Utils\get_cache_dir' ) ) {
@@ -186,8 +285,10 @@
186 285 // Make sure we can read/write files and that proper folders exist
187 286 if ( ! file_exists( untrailingslashit( \PoweredCache\Utils\get_cache_dir() ) ) ) {
188 287 if ( ! @mkdir( untrailingslashit( \PoweredCache\Utils\get_cache_dir() ) ) ) {
189 288 // Can not cache!
289 + powered_cache_add_cache_miss_header( "The cache directory does not exist for storing cached output" );
290 +
190 291 return $buffer;
191 292 }
192 293 }
193 294
@@ -193,8 +294,10 @@
193 294
194 295 if ( ! file_exists( \PoweredCache\Utils\get_page_cache_dir() ) ) {
195 296 if ( ! @mkdir( \PoweredCache\Utils\get_page_cache_dir() ) ) {
196 297 // Can not cache!
298 + powered_cache_add_cache_miss_header( "The page cache directory does not exist for storing cached output" );
299 +
197 300 return $buffer;
198 301 }
199 302 }
200 303
@@ -242,9 +345,9 @@
242 345
243 346 if ( array_key_exists( 'cache_footprint', $GLOBALS['powered_cache_options'] ) && true === $GLOBALS['powered_cache_options']['cache_footprint'] ) {
244 347 if ( preg_match( '#</html>#i', $buffer ) ) {
245 348 $buffer .= PHP_EOL;
246 - $buffer .= "<!-- Cache served by PoweredCache -->";
349 + $buffer .= "<!-- Cache served by Powered Cache -->";
247 350 $buffer .= PHP_EOL;
248 351 $buffer .= "<!-- If you like fast websites like this, visit: https://poweredcache.com -->";
249 352 $buffer .= PHP_EOL;
250 353 $buffer .= "<!-- Last modified: " . gmdate( 'D, d M Y H:i:s', $modified_time ) . " GMT -->";
@@ -250,8 +353,12 @@
250 353 $buffer .= "<!-- Last modified: " . gmdate( 'D, d M Y H:i:s', $modified_time ) . " GMT -->";
251 354 $buffer .= PHP_EOL;
252 355 $buffer .= "<!-- Dynamic page generated in $generation_time -->";
253 356 $buffer .= PHP_EOL;
357 + if ( false !== stripos( $_SERVER['HTTP_USER_AGENT'], 'Powered Cache Preloader' ) ) {
358 + $buffer .= "<!-- This page is preloaded by Powered Cache Preloader -->";
359 + $buffer .= PHP_EOL;
360 + }
254 361 }
255 362 }
256 363
257 364
@@ -378,15 +485,21 @@
378 485 exit;
379 486 }
380 487
381 488 // trailingslash check
382 - if ( isset( $powered_cache_slash_check ) && $powered_cache_slash_check ) {
489 + if ( isset( $powered_cache_slash_check ) ) {
383 490 $current_path = parse_url( $_SERVER['REQUEST_URI'], PHP_URL_PATH );
384 - if ( ! empty( $current_path ) && '/' !== substr( $current_path, - 1 ) ) {
491 + if ( $powered_cache_slash_check && ! empty( $current_path ) && '/' !== substr( $current_path, - 1 ) ) {
385 492 header( 'X-Powered-Cache: Passing to WordPress' );
386 493
387 494 return;
388 495 }
496 +
497 + if ( ! $powered_cache_slash_check && ! empty( $current_path ) && '/' === substr( $current_path, - 1 ) ) {
498 + header( 'X-Powered-Cache: Passing to WordPress' );
499 +
500 + return;
501 + }
389 502 }
390 503
391 504 if ( @file_exists( $file_path ) && @is_readable( $file_path ) ) {
392 505
@@ -396,8 +509,10 @@
396 509 }
397 510 }
398 511
399 512 header( 'X-Powered-Cache: PHP' );
513 + header( 'X-Cache-Enabled: true' );
514 + header( sprintf( "age: %d", time() - filemtime( $file_path ) ) );
400 515
401 516 if ( function_exists( 'gzencode' ) && $GLOBALS['powered_cache_options']['gzip_compression'] ) {
402 517 header( 'Content-Encoding: gzip' );
403 518 }
@@ -449,9 +564,9 @@
449 564 * @since 1.2 `$content_type`
450 565 * @since 1.0
451 566 */
452 567 function powered_cache_index_file( $content_type = 'text/html' ) {
453 - global $powered_cache_mobile_browsers, $powered_cache_mobile_prefixes, $powered_cache_vary_cookies;
568 + global $powered_cache_mobile_browsers, $powered_cache_mobile_prefixes, $powered_cache_vary_cookies, $powered_cache_cache_query_strings;
454 569
455 570 $file_name = 'index';
456 571
457 572 if ( is_ssl() ) {
@@ -473,10 +588,10 @@
473 588 $usr_cookie = powered_cache_get_user_cookie();
474 589 if ( false !== $usr_cookie ) {
475 590 $cookie_info = explode( '|', $usr_cookie );
476 591
477 - // user specific cache dir
478 - $file_name .= '_' . $cookie_info[0] . '-' . $cookie_info[1];
592 + // user specific cache index
593 + $file_name .= '-user_' . $cookie_info[0] . '-' . substr( sha1( $cookie_info[0] ), 0, 6 );
479 594 }
480 595 }
481 596
482 597 // change filename based on vary cookies
@@ -486,9 +601,10 @@
486 601 if ( is_array( $vary_cookie ) ) {
487 602 if ( ! empty( $_COOKIE[ $key ] ) ) {
488 603 foreach ( $vary_cookie as $vary_sub_cookie ) {
489 604 if ( isset( $_COOKIE[ $key ][ $vary_sub_cookie ] ) ) {
490 - $cookie_file_name .= strtolower( $key . $vary_sub_cookie . $_COOKIE[ $key ][ $vary_sub_cookie ] );
605 + $cookie_value = preg_replace( '/[^A-Za-z0-9. -]/', '', $_COOKIE[ $key ][ $vary_sub_cookie ] );
606 + $cookie_file_name .= strtolower( $key . $vary_sub_cookie . $cookie_value );
491 607 }
492 608 }
493 609 }
494 610
@@ -495,9 +611,10 @@
495 611 continue;
496 612 }
497 613
498 614 if ( isset( $_COOKIE[ $vary_cookie ] ) && ! empty( $_COOKIE[ $vary_cookie ] ) ) {
499 - $cookie_file_name .= strtolower( $vary_cookie . $_COOKIE[ $vary_cookie ] );
615 + $cookie_value = preg_replace( '/[^A-Za-z0-9. -]/', '', $_COOKIE[ $vary_cookie ] );
616 + $cookie_file_name .= strtolower( $vary_cookie . $cookie_value );
500 617 }
501 618 }
502 619
503 620 if ( ! empty( $cookie_file_name ) ) {
@@ -510,9 +627,27 @@
510 627 }
511 628
512 629 }
513 630
631 + // change filename for provided cache query string
632 + if ( ! empty( $_SERVER['QUERY_STRING'] ) ) {
633 + parse_str( $_SERVER['QUERY_STRING'], $query_string );
634 + $qs_variable = '';
635 + sort( $powered_cache_cache_query_strings );
636 + foreach ( $powered_cache_cache_query_strings as $query_parameter ) {
637 + if ( isset( $query_string[ $query_parameter ] ) ) {
638 + $qs_variable .= '_' . $query_parameter;
639 + $qs_variable .= is_array( $query_string[ $query_parameter ] ) ? implode( '|', $query_string[ $query_parameter ] ) : $query_string[ $query_parameter ];
640 + }
641 + }
514 642
643 + if ( ! empty( $qs_variable ) ) {
644 + $qs_variable = 'query_' . $qs_variable;
645 + $file_name .= '_' . sha1( $qs_variable );
646 + }
647 + }
648 +
649 +
515 650 /**
516 651 * Content-Type is not always text/html (like feed, wp-json etc..)
517 652 * Adding hash by simply escaping from rewrite matches in htaccess or nginx
518 653 * Different types of content should be serve via PHP, in order to restore header info
@@ -520,8 +655,9 @@
520 655 if ( false === strpos( $content_type, 'text/html' ) ) {
521 656 $file_name .= '-' . substr( sha1( $content_type ), 0, 6 );
522 657 }
523 658
659 +
524 660 $file_name .= '.html';
525 661
526 662 if ( function_exists( 'gzencode' ) && $GLOBALS['powered_cache_options']['gzip_compression'] ) {
527 663 $file_name .= '.gz';
@@ -529,4 +665,24 @@
529 665
530 666 return $file_name;
531 667 }
532 668
669 +/**
670 + * Add cache miss header and reason
671 + *
672 + * @param string $reason Cache Miss info
673 + *
674 + * @since 2.2
675 + */
676 +function powered_cache_add_cache_miss_header( $reason ) {
677 + if ( headers_sent() ) {
678 + return;
679 + }
680 +
681 + header( 'X-Powered-Cache: MISS' );
682 +
683 + if ( ( defined( 'POWERED_CACHE_ENABLE_LOG' ) && POWERED_CACHE_ENABLE_LOG )
684 + || ( defined( 'POWERED_CACHE_MISS_REASON' ) && POWERED_CACHE_MISS_REASON )
685 + ) {
686 + header( "X-Powered-Cache-Miss-Reason: $reason" );
687 + }
688 +}