PluginProbe
Powered Cache – Caching and Optimization for WordPress – Easily Improve PageSpeed & Web Vitals Score / 3.6.3
Powered Cache – Caching and Optimization for WordPress – Easily Improve PageSpeed & Web Vitals Score v3.6.3
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 +334 -132 1.2.6 → 3.6.3 View file →
@@ -1,9 +1,9 @@
1 1 <?php
2 2 /**
3 - * File based caching drop-in based on Taylor Lovett's Simple Cache
4 - * @link https://github.com/tlovett1/simple-cache/blob/master/inc/dropins/file-based-page-cache.php
3 + * Forked from https://github.com/tlovett1/simple-cache/
5 4 */
5 +
6 6 defined( 'ABSPATH' ) || exit;
7 7
8 8 $powered_cache_start_time = microtime( true );
9 9
@@ -8,106 +8,159 @@
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 }
24 32
25 -
26 -
27 33 $file_extension = $_SERVER['REQUEST_URI'];
28 34 $file_extension = preg_replace( '#^(.*?)\?.*$#', '$1', $file_extension );
29 35 $file_extension = trim( preg_replace( '#^.*\.(.*)$#', '$1', $file_extension ) );
30 36
31 37 // Don't cache disallowed extensions. Prevents wp-cron.php, xmlrpc.php, etc.
32 -if ( ! preg_match( '#index\.php$#i', $_SERVER['REQUEST_URI'] ) && in_array( $file_extension, array( 'php', 'xml', 'xsl' ) ) ) {
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 +
33 41 return;
34 42 }
35 43
36 -if ( ! $GLOBALS['powered_cache_options']['enable_page_caching'] ) {
44 +if ( ! $GLOBALS['powered_cache_options']['enable_page_cache'] ) {
45 + powered_cache_add_cache_miss_header( "Page Caching is not enabled" );
46 +
37 47 return;
38 48 }
39 49
40 -// Don't cache page with these user agents
41 -if ( ! empty( $GLOBALS['powered_cache_options']['rejected_user_agents'] ) ) {
42 - $rejected_user_agents = preg_split( '#(\r\n|\r|\n)#', $GLOBALS['powered_cache_options']['rejected_user_agents'] );
43 - $rejected_user_agents = implode( '|', $rejected_user_agents );
44 - if ( ! empty( $rejected_user_agents ) && isset( $_SERVER['HTTP_USER_AGENT'] ) && preg_match( '#(' . $rejected_user_agents . ')#', $_SERVER['HTTP_USER_AGENT'] ) ) {
45 - return;
46 - }
50 +if ( ! empty( $GLOBALS['powered_cache_options']['dev_mode'] ) ) {
51 + powered_cache_add_cache_miss_header( "Dev mode is enabled" );
52 +
53 + return;
47 54 }
48 55
56 +if ( isset( $_GET['nopoweredcache'] ) && $_GET['nopoweredcache'] ) {
57 + powered_cache_add_cache_miss_header( "Passing nopoweredcache with the query" );
49 58
50 -// Don't cache SSL
51 -if ( powered_cache_is_ssl() && ( ! isset( $GLOBALS['powered_cache_options']['ssl_cache'] ) || false === $GLOBALS['powered_cache_options']['ssl_cache'] ) ) {
52 59 return;
53 60 }
54 61
62 +// Don't cache page with these user agents
63 +if ( isset( $powered_cache_rejected_user_agents ) && ! empty( $powered_cache_rejected_user_agents ) ) {
64 + $rejected_user_agents = implode( '|', $powered_cache_rejected_user_agents );
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" );
55 67
68 + return;
69 + }
70 +}
71 +
56 72 // dont cache mobile
57 -if ( ! isset( $GLOBALS['powered_cache_options']['cache_mobile'] ) || true !== $GLOBALS['powered_cache_options']['cache_mobile'] ) {
73 +if ( empty( $GLOBALS['powered_cache_options']['cache_mobile'] ) ) {
58 74 global $powered_cache_mobile_browsers, $powered_cache_mobile_prefixes;
59 75
60 76 $mobile_browsers = addcslashes( implode( '|', preg_split( '/[\s*,\s*]*,+[\s*,\s*]*/', $powered_cache_mobile_browsers ) ), ' ' );
61 77 $mobile_prefixes = addcslashes( implode( '|', preg_split( '/[\s*,\s*]*,+[\s*,\s*]*/', $powered_cache_mobile_prefixes ) ), ' ' );
62 78 // Don't cache if mobile detection is activated
63 - if ( (preg_match( '#^.*(' . $mobile_browsers . ').*#i', $_SERVER['HTTP_USER_AGENT'] ) || preg_match( '#^(' . $mobile_prefixes . ').*#i', substr( $_SERVER['HTTP_USER_AGENT'], 0, 4 ) ) ) ) {
64 - return '';
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 +
82 + return;
65 83 }
66 84 }
67 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" );
68 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 +
69 113 if ( ! empty( $_COOKIE ) ) {
70 - //Don't cache if logged in
71 - if ( ! isset( $GLOBALS['powered_cache_options']['loggedin_user_cache'] ) || false === $GLOBALS['powered_cache_options']['loggedin_user_cache'] ) {
72 - $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_' ];
73 116
74 - // 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
75 120 foreach ( $_COOKIE as $key => $value ) {
76 121 foreach ( $wp_cookies as $cookie ) {
77 122 if ( strpos( $key, $cookie ) !== false ) {
78 - // Logged in!
123 + powered_cache_add_cache_miss_header( "User logged-in" );
79 124 return;
80 125 }
81 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 + }
82 135 }
83 - }
84 136
85 -
86 - if ( ! empty( $_COOKIE['powered_cache_commented_posts'] ) ) {
87 - foreach ( $_COOKIE['powered_cache_commented_posts'] as $path ) {
88 - if ( rtrim( $path, '/' ) === rtrim( $_SERVER['REQUEST_URI'], '/' ) ) {
89 - // User commented on this post
90 - 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 + }
91 145 }
92 146 }
93 147 }
94 148
95 - // don't cache specific cookie
96 - if ( ! empty( $GLOBALS['powered_cache_options']['rejected_cookies'] ) ) {
97 - $rejected_cookies = preg_split( '#(\r\n|\r|\n)#', $GLOBALS['powered_cache_options']['rejected_cookies'] );
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'] );
98 152 $rejected_cookies = implode( '|', $rejected_cookies );
99 153 if ( preg_match( '#(' . $rejected_cookies . ')#', var_export( $_COOKIE, true ) ) ) {
154 + powered_cache_add_cache_miss_header( "Rejected cookie" );
100 155 return;
101 156 }
102 157 }
103 -} // End if().
158 +}
104 159
105 -// Deal with optional cache exceptions
106 -if ( ! empty( $GLOBALS['powered_cache_options']['rejected_uri'] ) ) {
107 - $exceptions = preg_split( '#(\r\n|\r|\n)#', $GLOBALS['powered_cache_options']['rejected_uri'] );
108 -
109 - foreach ( $exceptions as $exception ) {
160 +// Don't cache rejected pages
161 +if ( ! empty( $powered_cache_rejected_uri ) ) {
162 + foreach ( (array) $powered_cache_rejected_uri as $exception ) {
110 163 if ( preg_match( '#^[\s]*$#', $exception ) ) {
111 164 continue;
112 165 }
113 166
@@ -115,9 +168,15 @@
115 168 if ( preg_match( '#^https?://#', $exception ) ) {
116 169 $exception = parse_url( $exception, PHP_URL_PATH );
117 170 }
118 171
172 + if ( empty( $exception ) ) {
173 + continue;
174 + }
175 +
119 176 if ( preg_match( '#^(' . $exception . ')$#', $_SERVER['REQUEST_URI'] ) ) {
177 + powered_cache_add_cache_miss_header( "Rejected page" );
178 +
120 179 return;
121 180 }
122 181 }
123 182 }
@@ -122,16 +181,25 @@
122 181 }
123 182 }
124 183
125 184
126 -$accepted_query_strings = array();
127 -// cache url with allowed query string
128 -if ( ! empty( $GLOBALS['powered_cache_options']['accepted_query_strings'] ) ) {
129 - $accepted_query_strings = preg_split( '#(\r\n|\r|\n)#', $GLOBALS['powered_cache_options']['accepted_query_strings'] );
130 -}
185 +if ( ! empty( $_GET ) ) {
186 + if ( ! isset( $powered_cache_ignored_query_strings ) ) {
187 + $powered_cache_ignored_query_strings = [];
188 + }
131 189
132 -if ( ! empty( $_GET ) && isset( $accepted_query_strings ) && is_array( $accepted_query_strings ) && ! array_intersect( array_keys( $_GET ), $accepted_query_strings ) ) {
133 - return;
190 + $query_params = array_diff_key( $_GET, array_flip( $powered_cache_ignored_query_strings ) );
191 +
192 + if ( ! isset( $powered_cache_cache_query_strings ) ) {
193 + $powered_cache_cache_query_strings = [];
194 + }
195 +
196 + // don't cache when there is not allowed query parameter exists
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 +
200 + return;
201 + }
134 202 }
135 203
136 204 powered_cache_serve_cache();
137 205
@@ -139,12 +207,13 @@
139 207
140 208 /**
141 209 * Cache output before it goes to the browser
142 210 *
143 - * @param string $buffer
144 - * @param int $flags
211 + * @param string $buffer
212 + * @param int $flags
213 + *
214 + * @return string
145 215 * @since 1.0
146 - * @return string
147 216 */
148 217 function powered_cache_page_buffer( $buffer, $flags ) {
149 218 global $powered_cache_start_time, $post;
150 219
@@ -151,55 +220,98 @@
151 220 if ( strlen( $buffer ) < 255 ) {
152 221 return $buffer;
153 222 }
154 223
155 - // Don't cache search, 404, or password protected
156 - 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 +
157 229 return $buffer;
158 230 }
159 231
160 - // maybe we shouldn't cache template file has this constant
161 - if ( defined( 'DONOTCACHEPAGE' ) && true === DONOTCACHEPAGE ) {
232 + if ( ! empty( $GLOBALS['powered_cache_options']['dev_mode'] ) ) {
233 + powered_cache_add_cache_miss_header( "Dev mode is enabled" );
234 +
162 235 return $buffer;
163 236 }
164 237
165 - // plugins might want to use filter
166 - if ( true !== apply_filters( 'powered_cache_page_cache_enable', true ) ) {
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 +
167 242 return $buffer;
168 243 }
169 244
170 - if ( ! defined( 'FS_CHMOD_DIR' ) ) {
171 - define( 'FS_CHMOD_DIR', ( fileperms( ABSPATH ) & 0777 | 0755 ) );
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;
172 250 }
173 251
174 - if ( ! defined( 'FS_CHMOD_FILE' ) ) {
175 - define( 'FS_CHMOD_FILE', ( fileperms( ABSPATH . 'index.php' ) & 0777 | 0644 ) );
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;
176 257 }
177 258
178 - include_once ABSPATH . 'wp-admin/includes/class-wp-filesystem-base.php';
179 - include_once ABSPATH . 'wp-admin/includes/class-wp-filesystem-direct.php';
259 + /**
260 + * Filter whether to enable page cache for this request
261 + *
262 + * @hook powered_cache_page_cache_enable
263 + *
264 + * @param {boolean} $enable true for caching
265 + *
266 + * @since 1.0
267 + */
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" );
180 270
181 - $filesystem = new WP_Filesystem_Direct( new StdClass() );
271 + return $buffer;
272 + }
182 273
183 - if ( ! function_exists( 'powered_cache_get_cache_dir' ) ) {
274 + // only cache when http ok
275 + if ( 200 !== http_response_code() ) {
276 + powered_cache_add_cache_miss_header( "Response code is not 200" );
277 +
184 278 return $buffer;
185 279 }
186 280
281 + if ( ! function_exists( '\PoweredCache\Utils\get_cache_dir' ) ) {
282 + return $buffer;
283 + }
284 +
187 285 // Make sure we can read/write files and that proper folders exist
188 - if ( ! $filesystem->exists( untrailingslashit( powered_cache_get_cache_dir() ) ) ) {
189 - if ( ! $filesystem->mkdir( untrailingslashit( powered_cache_get_cache_dir() ) ) ) {
286 + if ( ! file_exists( untrailingslashit( \PoweredCache\Utils\get_cache_dir() ) ) ) {
287 + if ( ! @mkdir( untrailingslashit( \PoweredCache\Utils\get_cache_dir() ) ) ) {
190 288 // Can not cache!
289 + powered_cache_add_cache_miss_header( "The cache directory does not exist for storing cached output" );
290 +
191 291 return $buffer;
192 292 }
193 293 }
194 294
195 - if ( ! $filesystem->exists( powered_cache_get_page_cache_dir() ) ) {
196 - if ( ! $filesystem->mkdir( powered_cache_get_page_cache_dir() ) ) {
295 + if ( ! file_exists( \PoweredCache\Utils\get_page_cache_dir() ) ) {
296 + if ( ! @mkdir( \PoweredCache\Utils\get_page_cache_dir() ) ) {
197 297 // Can not cache!
298 + powered_cache_add_cache_miss_header( "The page cache directory does not exist for storing cached output" );
299 +
198 300 return $buffer;
199 301 }
200 302 }
201 303
304 + /**
305 + * Filters HTML buffer
306 + *
307 + * @hook powered_cache_page_caching_buffer
308 + *
309 + * @param {string} $buffer Output buffer.
310 + *
311 + * @return {string} New value.
312 + * @since 1.0
313 + */
202 314 $buffer = apply_filters( 'powered_cache_page_caching_buffer', $buffer );
203 315
204 316 $url_path = powered_cache_get_url_path();
205 317
@@ -204,16 +316,16 @@
204 316 $url_path = powered_cache_get_url_path();
205 317
206 318 $dirs = explode( '/', $url_path );
207 319
208 - $path = untrailingslashit( powered_cache_get_page_cache_dir() );
320 + $path = untrailingslashit( \PoweredCache\Utils\get_page_cache_dir() );
209 321
210 322 foreach ( $dirs as $dir ) {
211 323 if ( ! empty( $dir ) ) {
212 324 $path .= '/' . $dir;
213 325
214 - if ( ! $filesystem->exists( $path ) ) {
215 - if ( ! $filesystem->mkdir( $path ) ) {
326 + if ( ! file_exists( $path ) ) {
327 + if ( ! @mkdir( $path ) ) {
216 328 // Can not cache!
217 329 return $buffer;
218 330 }
219 331 }
@@ -219,15 +331,13 @@
219 331 }
220 332 }
221 333 }
222 334
223 -
224 335 $modified_time = time(); // Make sure modified time is consistent
225 336 $generation_time = number_format( microtime( true ) - $powered_cache_start_time, 3 );
226 337
227 - // Prevent mixed content when there's an http request but the site URL uses https
228 - // @see https://github.com/tlovett1/simple-cache/issues/67
229 338 $home_url = get_home_url();
339 + // prevent mixed content
230 340 if ( ! is_ssl() && 'https' === strtolower( parse_url( $home_url, PHP_URL_SCHEME ) ) ) {
231 341 $https_home_url = $home_url;
232 342 $http_home_url = str_replace( 'https://', 'http://', $https_home_url );
233 343 $buffer = str_replace( esc_url( $http_home_url ), esc_url( $https_home_url ), $buffer );
@@ -232,12 +342,12 @@
232 342 $http_home_url = str_replace( 'https://', 'http://', $https_home_url );
233 343 $buffer = str_replace( esc_url( $http_home_url ), esc_url( $https_home_url ), $buffer );
234 344 }
235 345
236 - if ( array_key_exists( 'show_cache_message', $GLOBALS['powered_cache_options'] ) && true === $GLOBALS['powered_cache_options']['show_cache_message'] ) {
346 + if ( array_key_exists( 'cache_footprint', $GLOBALS['powered_cache_options'] ) && true === $GLOBALS['powered_cache_options']['cache_footprint'] ) {
237 347 if ( preg_match( '#</html>#i', $buffer ) ) {
238 348 $buffer .= PHP_EOL;
239 - $buffer .= "<!-- Cache served by PoweredCache -->";
349 + $buffer .= "<!-- Cache served by Powered Cache -->";
240 350 $buffer .= PHP_EOL;
241 351 $buffer .= "<!-- If you like fast websites like this, visit: https://poweredcache.com -->";
242 352 $buffer .= PHP_EOL;
243 353 $buffer .= "<!-- Last modified: " . gmdate( 'D, d M Y H:i:s', $modified_time ) . " GMT -->";
@@ -243,32 +353,57 @@
243 353 $buffer .= "<!-- Last modified: " . gmdate( 'D, d M Y H:i:s', $modified_time ) . " GMT -->";
244 354 $buffer .= PHP_EOL;
245 355 $buffer .= "<!-- Dynamic page generated in $generation_time -->";
246 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 + }
247 361 }
248 362 }
249 363
250 364
251 365 $meta_file_name = 'meta.php';
252 - $meta_file = '<?php exit; ?>' . PHP_EOL;
366 + $meta_file = '<?php exit; ?>' . PHP_EOL;
253 367
254 368 $meta_params = array(); // holds to metadata for cached file
255 369
256 - $response_headers = powered_cache_get_response_headers();
370 + $response_headers = \PoweredCache\Utils\get_response_headers();
257 371
258 372 foreach ( (array) $response_headers as $key => $value ) {
259 373 $meta_params['headers'][ $key ] = "$key: $value";
260 374 }
261 375
262 - $meta_params = apply_filters( 'powered_cache_page_cache_meta_params', $meta_params, $response_headers );
263 - $meta_file_contents = $meta_file . serialize( $meta_params );
376 + /**
377 + * Filters meta parameters.
378 + *
379 + * @hook powered_cache_page_cache_meta_params
380 + *
381 + * @param {array} $meta_params Meta parameters.
382 + * @param {array} $response_headers Supported response header list.
383 + *
384 + * @return {array} New value.
385 + * @since 1.2
386 + */
387 + $meta_params = apply_filters( 'powered_cache_page_cache_meta_params', $meta_params, $response_headers );
388 + $meta_file_contents = $meta_file . json_encode( $meta_params );
264 389
390 + /**
391 + * Filters meta file contents.
392 + *
393 + * @hook powered_cache_page_cache_meta_info
394 + *
395 + * @param {string} $meta_file_contents The content of the meta file.*
396 + *
397 + * @return {array} New value.
398 + * @since 1.2
399 + */
265 400 $meta_file_contents = apply_filters( 'powered_cache_page_cache_meta_info', $meta_file_contents );
266 401
267 - $filesystem->put_contents( $path . '/' . $meta_file_name, $meta_file_contents, FS_CHMOD_FILE );
268 - $filesystem->touch( $path . '/' . $meta_file_name, $modified_time );
402 + file_put_contents( $path . '/' . $meta_file_name, $meta_file_contents );
403 + touch( $path . '/' . $meta_file_name, $modified_time );
269 404
270 - if ( isset( $meta_params['headers']['Content-Type'] ) ) {
405 + if ( ! empty( $meta_params['headers']['Content-Type'] ) ) {
271 406 $index_name = powered_cache_index_file( $meta_params['headers']['Content-Type'] );
272 407 } else {
273 408 $index_name = powered_cache_index_file();
274 409 }
@@ -273,19 +408,30 @@
273 408 $index_name = powered_cache_index_file();
274 409 }
275 410
276 411 if ( $GLOBALS['powered_cache_options']['gzip_compression'] && function_exists( 'gzencode' ) ) {
277 - $filesystem->put_contents( $path . '/' . $index_name, gzencode( $buffer, 3 ), FS_CHMOD_FILE );
278 - $filesystem->touch( $path . '/' . $index_name, $modified_time );
412 + file_put_contents( $path . '/' . $index_name, gzencode( $buffer, 3 ) );
413 + touch( $path . '/' . $index_name, $modified_time );
279 414 } else {
280 - $filesystem->put_contents( $path . '/' . $index_name, $buffer, FS_CHMOD_FILE );
281 - $filesystem->touch( $path . '/' . $index_name, $modified_time );
415 + file_put_contents( $path . '/' . $index_name, $buffer );
416 + touch( $path . '/' . $index_name, $modified_time );
282 417 }
283 418
419 + /**
420 + * Fires after caching a page.
421 + *
422 + * @hook powered_cache_page_cached
423 + *
424 + * @param {string} $buffer HTML Output.
425 + *
426 + * @since 1.0
427 + */
284 428 do_action( 'powered_cache_page_cached', $buffer );
285 429
286 430 header( 'Cache-Control: no-cache' ); // Check back every time to see if re-download is necessary
287 431
432 + header( 'X-Powered-Cache: MISS' );
433 +
288 434 header( 'Last-Modified: ' . gmdate( 'D, d M Y H:i:s', $modified_time ) . ' GMT' );
289 435
290 436 if ( function_exists( 'ob_gzhandler' ) && $GLOBALS['powered_cache_options']['gzip_compression'] ) {
291 437 return ob_gzhandler( $buffer, $flags );
@@ -304,30 +450,40 @@
304 450 global $powered_cache_slash_check;
305 451
306 452 $path = rtrim( $GLOBALS['powered_cache_options']['cache_location'], '/' ) . '/powered-cache/' . rtrim( powered_cache_get_url_path(), '/' ) . '/';
307 453
308 - $meta_file = $path.'/meta.php';
454 + $meta_file = $path . '/meta.php';
309 455
456 + $header_params = [];
457 + $content_type = 'text/html';
310 458
311 459 if ( @file_exists( $meta_file ) ) {
312 460 $meta_contents = trim( file_get_contents( $meta_file ) );
313 461 $meta_contents = str_replace( '<?php exit; ?>', '', $meta_contents );
314 - $meta_params = unserialize( trim( $meta_contents ) );
462 + $meta_params = json_decode( trim( $meta_contents ), true );
315 463 $header_params = $meta_params['headers'];
316 464 }
317 465
318 - $content_type = @$header_params['Content-Type'];
466 + if ( ! empty( $header_params['Content-Type'] ) ) {
467 + $content_type = $header_params['Content-Type'];
468 + }
319 469
470 +
320 471 $file_name = powered_cache_index_file( $content_type );
321 - $file_path = $path.$file_name;
472 + $file_path = $path . $file_name;
322 473
474 + // check file exists?
475 + if ( ! file_exists( $file_path ) ) {
476 + return;
477 + }
478 +
323 479 $modified_time = (int) @filemtime( $file_path );
324 480
325 481 header( 'Cache-Control: no-cache' ); // Check back in an hour
326 482
327 483 if ( ! empty( $modified_time ) && ! empty( $_SERVER['HTTP_IF_MODIFIED_SINCE'] ) && strtotime( $_SERVER['HTTP_IF_MODIFIED_SINCE'] ) === $modified_time ) {
328 - header( $_SERVER['SERVER_PROTOCOL'] . ' 304 Not Modified', true, 304 );
329 - exit;
484 + header( $_SERVER['SERVER_PROTOCOL'] . ' 304 Not Modified', true, 304 );
485 + exit;
330 486 }
331 487
332 488 // trailingslash check
333 489 if ( isset( $powered_cache_slash_check ) && $powered_cache_slash_check ) {
@@ -347,8 +503,10 @@
347 503 }
348 504 }
349 505
350 506 header( 'X-Powered-Cache: PHP' );
507 + header( 'X-Cache-Enabled: true' );
508 + header( sprintf( "age: %d", time() - filemtime( $file_path ) ) );
351 509
352 510 if ( function_exists( 'gzencode' ) && $GLOBALS['powered_cache_options']['gzip_compression'] ) {
353 511 header( 'Content-Encoding: gzip' );
354 512 }
@@ -361,15 +519,17 @@
361 519
362 520 /**
363 521 * Get URL path for caching
364 522 *
523 + * @return string
365 524 * @since 1.0
366 - * @return string
525 + * @since 2.0 $request_uri without query string
367 526 */
368 527 function powered_cache_get_url_path() {
369 -
370 - $host = ( isset( $_SERVER['HTTP_HOST'] ) ) ? $_SERVER['HTTP_HOST'] : '';
371 - $request_uri = preg_replace( '/(\/+)/', '/', $_SERVER['REQUEST_URI'] );
528 + $host = ( isset( $_SERVER['HTTP_HOST'] ) ? $_SERVER['HTTP_HOST'] : '' );
529 + $request_uri = explode( '?', $_SERVER['REQUEST_URI'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
530 + $request_uri = reset( $request_uri );
531 + $request_uri = preg_replace( '/(\/+)/', '/', $request_uri );
372 532 $request_uri = str_replace( '..', '', preg_replace( '/[ <>\'\"\r\n\t\(\)]/', '', $request_uri ) );
373 533
374 534 return rtrim( $host, '/' ) . $request_uri;
375 535 }
@@ -393,88 +553,130 @@
393 553 * Determines cache file names
394 554 *
395 555 * @param string $content_type
396 556 *
557 + * @return string
558 + * @since 1.2 `$content_type`
397 559 * @since 1.0
398 - * @since 1.2 `$content_type`
399 - * @return string
400 560 */
401 561 function powered_cache_index_file( $content_type = 'text/html' ) {
562 + global $powered_cache_mobile_browsers, $powered_cache_mobile_prefixes, $powered_cache_vary_cookies, $powered_cache_cache_query_strings;
563 +
402 564 $file_name = 'index';
403 565
404 - if ( powered_cache_is_ssl() ) {
566 + if ( is_ssl() ) {
405 567 $file_name .= '-https';
406 568 }
407 569
408 570 // separate file for mobile cache
409 - if ( isset( $GLOBALS['powered_cache_options']['cache_mobile'], $GLOBALS['powered_cache_options']['cache_mobile_separate_file'] )
410 - && true === $GLOBALS['powered_cache_options']['cache_mobile']
411 - && true === $GLOBALS['powered_cache_options']['cache_mobile_separate_file']
412 - ) {
413 -
414 - global $powered_cache_mobile_browsers, $powered_cache_mobile_prefixes;
415 -
571 + if ( ! empty( $GLOBALS['powered_cache_options']['cache_mobile'] ) && ! empty( $GLOBALS['powered_cache_options']['cache_mobile_separate_file'] ) ) {
416 572 $mobile_browsers = addcslashes( implode( '|', preg_split( '/[\s*,\s*]*,+[\s*,\s*]*/', $powered_cache_mobile_browsers ) ), ' ' );
417 573 $mobile_prefixes = addcslashes( implode( '|', preg_split( '/[\s*,\s*]*,+[\s*,\s*]*/', $powered_cache_mobile_prefixes ) ), ' ' );
418 574
419 575 // Don't cache if mobile detection is activated
420 - if ( (preg_match( '#^.*(' . $mobile_browsers . ').*#i', $_SERVER['HTTP_USER_AGENT'] ) || preg_match( '#^(' . $mobile_prefixes . ').*#i', substr( $_SERVER['HTTP_USER_AGENT'], 0, 4 ) ) ) ) {
576 + if ( ( preg_match( '#^.*(' . $mobile_browsers . ').*#i', $_SERVER['HTTP_USER_AGENT'] ) || preg_match( '#^(' . $mobile_prefixes . ').*#i', substr( $_SERVER['HTTP_USER_AGENT'], 0, 4 ) ) ) ) {
421 577 $file_name .= '-mobile';
422 578 }
423 579 }
424 580
425 - if ( isset( $GLOBALS['powered_cache_options']['loggedin_user_cache'] )
426 - && true === $GLOBALS['powered_cache_options']['loggedin_user_cache']
427 - ) {
581 + if ( ! empty( $GLOBALS['powered_cache_options']['loggedin_user_cache'] ) ) {
428 582 $usr_cookie = powered_cache_get_user_cookie();
429 583 if ( false !== $usr_cookie ) {
430 584 $cookie_info = explode( '|', $usr_cookie );
431 585
432 - // user specific cache dir
433 - $file_name .= '_' . $cookie_info[0] . '-' . $cookie_info[1];
586 + // user specific cache index
587 + $file_name .= '-user_' . $cookie_info[0] . '-' . substr( sha1( $cookie_info[0] ), 0, 6 );
434 588 }
435 589 }
436 590
591 + // change filename based on vary cookies
592 + if ( ! empty( $powered_cache_vary_cookies ) ) {
593 + $cookie_file_name = '';
594 + foreach ( $powered_cache_vary_cookies as $key => $vary_cookie ) {
595 + if ( is_array( $vary_cookie ) ) {
596 + if ( ! empty( $_COOKIE[ $key ] ) ) {
597 + foreach ( $vary_cookie as $vary_sub_cookie ) {
598 + if ( isset( $_COOKIE[ $key ][ $vary_sub_cookie ] ) ) {
599 + $cookie_value = preg_replace( '/[^A-Za-z0-9. -]/', '', $_COOKIE[ $key ][ $vary_sub_cookie ] );
600 + $cookie_file_name .= strtolower( $key . $vary_sub_cookie . $cookie_value );
601 + }
602 + }
603 + }
604 +
605 + continue;
606 + }
607 +
608 + if ( isset( $_COOKIE[ $vary_cookie ] ) && ! empty( $_COOKIE[ $vary_cookie ] ) ) {
609 + $cookie_value = preg_replace( '/[^A-Za-z0-9. -]/', '', $_COOKIE[ $vary_cookie ] );
610 + $cookie_file_name .= strtolower( $vary_cookie . $cookie_value );
611 + }
612 + }
613 +
614 + if ( ! empty( $cookie_file_name ) ) {
615 + /**
616 + * Hashing for no particular reason
617 + * preg_replace( '/[^a-z0-9_\-]/', '', strtolower( $cookie_file_name ) )
618 + * can create a messy filename
619 + */
620 + $file_name .= '-' . sha1( $cookie_file_name );
621 + }
622 +
623 + }
624 +
625 + // change filename for provided cache query string
437 626 if ( ! empty( $_SERVER['QUERY_STRING'] ) ) {
438 - $file_name .= '_' . sha1( $_SERVER['QUERY_STRING'] );
627 + parse_str( $_SERVER['QUERY_STRING'], $query_string );
628 + $qs_variable = '';
629 + sort( $powered_cache_cache_query_strings );
630 + foreach ( $powered_cache_cache_query_strings as $query_parameter ) {
631 + if ( isset( $query_string[ $query_parameter ] ) ) {
632 + $qs_variable .= '_' . $query_parameter;
633 + $qs_variable .= is_array( $query_string[ $query_parameter ] ) ? implode( '|', $query_string[ $query_parameter ] ) : $query_string[ $query_parameter ];
634 + }
635 + }
636 +
637 + if ( ! empty( $qs_variable ) ) {
638 + $qs_variable = 'query_' . $qs_variable;
639 + $file_name .= '_' . sha1( $qs_variable );
640 + }
439 641 }
440 642
643 +
441 644 /**
442 645 * Content-Type is not always text/html (like feed, wp-json etc..)
443 - * We should respect proper format!
444 - * Alternatively, we can add multiple level lookup for apache/nginx config
445 - * but that makes things much more complicated.
646 + * Adding hash by simply escaping from rewrite matches in htaccess or nginx
647 + * Different types of content should be serve via PHP, in order to restore header info
446 648 */
447 649 if ( false === strpos( $content_type, 'text/html' ) ) {
448 650 $file_name .= '-' . substr( sha1( $content_type ), 0, 6 );
449 651 }
450 652
653 +
451 654 $file_name .= '.html';
452 655
453 656 if ( function_exists( 'gzencode' ) && $GLOBALS['powered_cache_options']['gzip_compression'] ) {
454 - $file_name .= '_gzip';
657 + $file_name .= '.gz';
455 658 }
456 659
457 660 return $file_name;
458 661 }
459 662
460 -
461 663 /**
462 - * is_ssl moved load.php in WP 4.6 but we support WP 4.1+
664 + * Add cache miss header and reason
463 665 *
464 - * @return bool
666 + * @param string $reason Cache Miss info
667 + *
668 + * @since 2.2
465 669 */
466 -function powered_cache_is_ssl() {
467 - if ( isset( $_SERVER['HTTPS'] ) ) {
468 - if ( 'on' == strtolower( $_SERVER['HTTPS'] ) ) {
469 - return true;
470 - }
670 +function powered_cache_add_cache_miss_header( $reason ) {
671 + if ( headers_sent() ) {
672 + return;
673 + }
471 674
472 - if ( '1' == $_SERVER['HTTPS'] ) {
473 - return true;
474 - }
475 - } elseif ( isset( $_SERVER['SERVER_PORT'] ) && ( '443' == $_SERVER['SERVER_PORT'] ) ) {
476 - return true;
675 + header( 'X-Powered-Cache: MISS' );
676 +
677 + if ( ( defined( 'POWERED_CACHE_ENABLE_LOG' ) && POWERED_CACHE_ENABLE_LOG )
678 + || ( defined( 'POWERED_CACHE_MISS_REASON' ) && POWERED_CACHE_MISS_REASON )
679 + ) {
680 + header( "X-Powered-Cache-Miss-Reason: $reason" );
477 681 }
478 -
479 - return false;
480 682 }