PluginProbe
Powered Cache – Caching and Optimization for WordPress – Easily Improve PageSpeed & Web Vitals Score / 2.2.1
Powered Cache – Caching and Optimization for WordPress – Easily Improve PageSpeed & Web Vitals Score v2.2.1
trunk 1.0 1.0.1 1.1 1.1.1 1.1.2 1.2 1.2.1 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 2.0 2.0.1 2.0.2 2.0.3 2.0.4 2.1 2.1.1 2.1.2 2.2 2.2.1 All 69 releases
powered-cache / includes / dropins / page-cache.php

page-cache.php in Powered Cache – Caching and Optimization for WordPress – Easily Improve PageSpeed & Web Vitals Score 2.2.1, at includes/dropins/page-cache.php

592 lines 17.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Forked from https://github.com/tlovett1/simple-cache/
4 */
5
6 defined( 'ABSPATH' ) || exit;
7
8 $powered_cache_start_time = microtime( true );
9
10 // Don't cache robots.txt or htacesss
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
14 return;
15 }
16
17 // Don't cache non-GET requests
18 if ( ! isset( $_SERVER['REQUEST_METHOD'] ) || 'GET' !== $_SERVER['REQUEST_METHOD'] ) {
19 powered_cache_add_cache_miss_header( "Invalid request method" );
20
21 return;
22 }
23
24 if ( ! isset( $_SERVER['HTTP_USER_AGENT'] ) ) {
25 $_SERVER['HTTP_USER_AGENT'] = '';
26 }
27
28 // Don't cache wp-admin
29 if ( is_admin() ) {
30 return;
31 }
32
33 $file_extension = $_SERVER['REQUEST_URI'];
34 $file_extension = preg_replace( '#^(.*?)\?.*$#', '$1', $file_extension );
35 $file_extension = trim( preg_replace( '#^.*\.(.*)$#', '$1', $file_extension ) );
36
37 // Don't cache disallowed extensions. Prevents wp-cron.php, xmlrpc.php, etc.
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
41 return;
42 }
43
44 if ( ! $GLOBALS['powered_cache_options']['enable_page_cache'] ) {
45 powered_cache_add_cache_miss_header( "Page Caching is not enabled" );
46
47 return;
48 }
49
50 // Don't cache page with these user agents
51 if ( isset( $powered_cache_rejected_user_agents ) && ! empty( $powered_cache_rejected_user_agents ) ) {
52 $rejected_user_agents = implode( '|', $powered_cache_rejected_user_agents );
53 if ( ! empty( $rejected_user_agents ) && isset( $_SERVER['HTTP_USER_AGENT'] ) && preg_match( '#(' . $rejected_user_agents . ')#', $_SERVER['HTTP_USER_AGENT'] ) ) {
54 powered_cache_add_cache_miss_header( "Rejected user agent" );
55
56 return;
57 }
58 }
59
60 // dont cache mobile
61 if ( empty( $GLOBALS['powered_cache_options']['cache_mobile'] ) ) {
62 global $powered_cache_mobile_browsers, $powered_cache_mobile_prefixes;
63
64 $mobile_browsers = addcslashes( implode( '|', preg_split( '/[\s*,\s*]*,+[\s*,\s*]*/', $powered_cache_mobile_browsers ) ), ' ' );
65 $mobile_prefixes = addcslashes( implode( '|', preg_split( '/[\s*,\s*]*,+[\s*,\s*]*/', $powered_cache_mobile_prefixes ) ), ' ' );
66 // Don't cache if mobile detection is activated
67 if ( ( preg_match( '#^.*(' . $mobile_browsers . ').*#i', $_SERVER['HTTP_USER_AGENT'] ) || preg_match( '#^(' . $mobile_prefixes . ').*#i', substr( $_SERVER['HTTP_USER_AGENT'], 0, 4 ) ) ) ) {
68 powered_cache_add_cache_miss_header( "Mobile request" );
69
70 return;
71 }
72 }
73
74
75 if ( ! empty( $_COOKIE ) ) {
76 $wp_cookies = array( 'wordpressuser_', 'wordpresspass_', 'wordpress_sec_', 'wordpress_logged_in_' );
77
78 //Don't cache if logged in
79 if ( ! isset( $GLOBALS['powered_cache_options']['loggedin_user_cache'] ) || false === $GLOBALS['powered_cache_options']['loggedin_user_cache'] ) {
80 // check logged-in cookie
81 foreach ( $_COOKIE as $key => $value ) {
82 foreach ( $wp_cookies as $cookie ) {
83 if ( strpos( $key, $cookie ) !== false ) {
84 // Logged in!
85 powered_cache_add_cache_miss_header( "User logged-in" );
86
87 return;
88 }
89 }
90 }
91 }
92
93
94 if ( ! empty( $_COOKIE['powered_cache_commented_posts'] ) ) {
95 foreach ( $_COOKIE['powered_cache_commented_posts'] as $path ) {
96 if ( rtrim( $path, '/' ) === rtrim( $_SERVER['REQUEST_URI'], '/' ) ) {
97 // User commented on this post
98 powered_cache_add_cache_miss_header( "User commented" );
99
100 return;
101 }
102 }
103 }
104
105 // don't cache specific cookie
106 if ( isset( $powered_cache_rejected_cookies ) && ! empty( $powered_cache_rejected_cookies ) ) {
107 $rejected_cookies = array_diff( $powered_cache_rejected_cookies, $wp_cookies ); // use diff in case caching for logged-in user
108 $rejected_cookies = implode( '|', $rejected_cookies );
109 if ( preg_match( '#(' . $rejected_cookies . ')#', var_export( $_COOKIE, true ) ) ) {
110 powered_cache_add_cache_miss_header( "Rejected cookie" );
111
112 return;
113 }
114 }
115 }
116
117 // Don't cache rejected pages
118 if ( ! empty( $powered_cache_rejected_uri ) ) {
119 foreach ( (array) $powered_cache_rejected_uri as $exception ) {
120 if ( preg_match( '#^[\s]*$#', $exception ) ) {
121 continue;
122 }
123
124 // full url exception
125 if ( preg_match( '#^https?://#', $exception ) ) {
126 $exception = parse_url( $exception, PHP_URL_PATH );
127 }
128
129 if ( empty( $exception ) ) {
130 continue;
131 }
132
133 if ( preg_match( '#^(' . $exception . ')$#', $_SERVER['REQUEST_URI'] ) ) {
134 powered_cache_add_cache_miss_header( "Rejected page" );
135
136 return;
137 }
138 }
139 }
140
141
142 if ( ! empty( $_GET ) ) {
143 if ( ! isset( $powered_cache_accepted_query_strings ) ) {
144 $powered_cache_accepted_query_strings = [];
145 }
146
147 $query_params = array_diff_key( $_GET, array_flip( $powered_cache_accepted_query_strings ) );
148
149 // don't cache when there is not allowed query parameter exists
150 if ( ! empty( $query_params ) ) {
151 powered_cache_add_cache_miss_header( "Disallowed query parameter exists" );
152
153 return;
154 }
155 }
156
157 powered_cache_serve_cache();
158
159 ob_start( 'powered_cache_page_buffer' );
160
161 /**
162 * Cache output before it goes to the browser
163 *
164 * @param string $buffer
165 * @param int $flags
166 *
167 * @return string
168 * @since 1.0
169 */
170 function powered_cache_page_buffer( $buffer, $flags ) {
171 global $powered_cache_start_time, $post;
172
173 if ( strlen( $buffer ) < 255 ) {
174 return $buffer;
175 }
176
177 // Don't cache search, 404, or password protected
178 if ( is_404() || is_search() || ! empty( $post->post_password ) ) {
179 powered_cache_add_cache_miss_header( "404, search results or password protected posts are not cached" );
180
181 return $buffer;
182 }
183
184 // maybe we shouldn't cache template file has this constant
185 // dont check DONOTCACHEPAGE strictly some plugins define string instead bool flag
186 if ( defined( 'DONOTCACHEPAGE' ) && DONOTCACHEPAGE ) {
187 \PoweredCache\Utils\log( sprintf( 'DONOTCACHEPAGE DEFINED on %s', $_SERVER['REQUEST_URI'] ) );
188 powered_cache_add_cache_miss_header( "DONOTCACHEPAGE defined" );
189
190 return $buffer;
191 }
192
193 /**
194 * Filter whether to enable page cache for this request
195 *
196 * @hook powered_cache_page_cache_enable
197 *
198 * @param {boolean} $enable true for caching
199 *
200 * @since 1.0
201 */
202 if ( true !== apply_filters( 'powered_cache_page_cache_enable', true ) ) {
203 powered_cache_add_cache_miss_header( "Page Cache not enabled for this post" );
204
205 return $buffer;
206 }
207
208 // only cache when http ok
209 if ( 200 !== http_response_code() ) {
210 powered_cache_add_cache_miss_header( "Response code is not 200" );
211
212 return $buffer;
213 }
214
215 if ( ! function_exists( '\PoweredCache\Utils\get_cache_dir' ) ) {
216 return $buffer;
217 }
218
219 // Make sure we can read/write files and that proper folders exist
220 if ( ! file_exists( untrailingslashit( \PoweredCache\Utils\get_cache_dir() ) ) ) {
221 if ( ! @mkdir( untrailingslashit( \PoweredCache\Utils\get_cache_dir() ) ) ) {
222 // Can not cache!
223 powered_cache_add_cache_miss_header( "The cache directory does not exist for storing cached output" );
224
225 return $buffer;
226 }
227 }
228
229 if ( ! file_exists( \PoweredCache\Utils\get_page_cache_dir() ) ) {
230 if ( ! @mkdir( \PoweredCache\Utils\get_page_cache_dir() ) ) {
231 // Can not cache!
232 powered_cache_add_cache_miss_header( "The page cache directory does not exist for storing cached output" );
233
234 return $buffer;
235 }
236 }
237
238 /**
239 * Filters HTML buffer
240 *
241 * @hook powered_cache_page_caching_buffer
242 *
243 * @param {string} $buffer Output buffer.
244 *
245 * @return {string} New value.
246 * @since 1.0
247 */
248 $buffer = apply_filters( 'powered_cache_page_caching_buffer', $buffer );
249
250 $url_path = powered_cache_get_url_path();
251
252 $dirs = explode( '/', $url_path );
253
254 $path = untrailingslashit( \PoweredCache\Utils\get_page_cache_dir() );
255
256 foreach ( $dirs as $dir ) {
257 if ( ! empty( $dir ) ) {
258 $path .= '/' . $dir;
259
260 if ( ! file_exists( $path ) ) {
261 if ( ! @mkdir( $path ) ) {
262 // Can not cache!
263 return $buffer;
264 }
265 }
266 }
267 }
268
269 $modified_time = time(); // Make sure modified time is consistent
270 $generation_time = number_format( microtime( true ) - $powered_cache_start_time, 3 );
271
272 $home_url = get_home_url();
273 // prevent mixed content
274 if ( ! is_ssl() && 'https' === strtolower( parse_url( $home_url, PHP_URL_SCHEME ) ) ) {
275 $https_home_url = $home_url;
276 $http_home_url = str_replace( 'https://', 'http://', $https_home_url );
277 $buffer = str_replace( esc_url( $http_home_url ), esc_url( $https_home_url ), $buffer );
278 }
279
280 if ( array_key_exists( 'cache_footprint', $GLOBALS['powered_cache_options'] ) && true === $GLOBALS['powered_cache_options']['cache_footprint'] ) {
281 if ( preg_match( '#</html>#i', $buffer ) ) {
282 $buffer .= PHP_EOL;
283 $buffer .= "<!-- Cache served by PoweredCache -->";
284 $buffer .= PHP_EOL;
285 $buffer .= "<!-- If you like fast websites like this, visit: https://poweredcache.com -->";
286 $buffer .= PHP_EOL;
287 $buffer .= "<!-- Last modified: " . gmdate( 'D, d M Y H:i:s', $modified_time ) . " GMT -->";
288 $buffer .= PHP_EOL;
289 $buffer .= "<!-- Dynamic page generated in $generation_time -->";
290 $buffer .= PHP_EOL;
291 }
292 }
293
294
295 $meta_file_name = 'meta.php';
296 $meta_file = '<?php exit; ?>' . PHP_EOL;
297
298 $meta_params = array(); // holds to metadata for cached file
299
300 $response_headers = \PoweredCache\Utils\get_response_headers();
301
302 foreach ( (array) $response_headers as $key => $value ) {
303 $meta_params['headers'][ $key ] = "$key: $value";
304 }
305
306 /**
307 * Filters meta parameters.
308 *
309 * @hook powered_cache_page_cache_meta_params
310 *
311 * @param {array} $meta_params Meta parameters.
312 * @param {array} $response_headers Supported response header list.
313 *
314 * @return {array} New value.
315 * @since 1.2
316 */
317 $meta_params = apply_filters( 'powered_cache_page_cache_meta_params', $meta_params, $response_headers );
318 $meta_file_contents = $meta_file . json_encode( $meta_params );
319
320 /**
321 * Filters meta file contents.
322 *
323 * @hook powered_cache_page_cache_meta_info
324 *
325 * @param {string} $meta_file_contents The content of the meta file.*
326 *
327 * @return {array} New value.
328 * @since 1.2
329 */
330 $meta_file_contents = apply_filters( 'powered_cache_page_cache_meta_info', $meta_file_contents );
331
332 file_put_contents( $path . '/' . $meta_file_name, $meta_file_contents );
333 touch( $path . '/' . $meta_file_name, $modified_time );
334
335 if ( ! empty( $meta_params['headers']['Content-Type'] ) ) {
336 $index_name = powered_cache_index_file( $meta_params['headers']['Content-Type'] );
337 } else {
338 $index_name = powered_cache_index_file();
339 }
340
341 if ( $GLOBALS['powered_cache_options']['gzip_compression'] && function_exists( 'gzencode' ) ) {
342 file_put_contents( $path . '/' . $index_name, gzencode( $buffer, 3 ) );
343 touch( $path . '/' . $index_name, $modified_time );
344 } else {
345 file_put_contents( $path . '/' . $index_name, $buffer );
346 touch( $path . '/' . $index_name, $modified_time );
347 }
348
349 /**
350 * Fires after caching a page.
351 *
352 * @hook powered_cache_page_cached
353 *
354 * @param {string} $buffer HTML Output.
355 *
356 * @since 1.0
357 */
358 do_action( 'powered_cache_page_cached', $buffer );
359
360 header( 'Cache-Control: no-cache' ); // Check back every time to see if re-download is necessary
361
362 header( 'X-Powered-Cache: MISS' );
363
364 header( 'Last-Modified: ' . gmdate( 'D, d M Y H:i:s', $modified_time ) . ' GMT' );
365
366 if ( function_exists( 'ob_gzhandler' ) && $GLOBALS['powered_cache_options']['gzip_compression'] ) {
367 return ob_gzhandler( $buffer, $flags );
368 } else {
369 return $buffer;
370 }
371 }
372
373
374 /**
375 * Optionally serve cache and exit
376 *
377 * @since 1.0
378 */
379 function powered_cache_serve_cache() {
380 global $powered_cache_slash_check;
381
382 $path = rtrim( $GLOBALS['powered_cache_options']['cache_location'], '/' ) . '/powered-cache/' . rtrim( powered_cache_get_url_path(), '/' ) . '/';
383
384 $meta_file = $path . '/meta.php';
385
386 $header_params = [];
387 $content_type = 'text/html';
388
389 if ( @file_exists( $meta_file ) ) {
390 $meta_contents = trim( file_get_contents( $meta_file ) );
391 $meta_contents = str_replace( '<?php exit; ?>', '', $meta_contents );
392 $meta_params = json_decode( trim( $meta_contents ), true );
393 $header_params = $meta_params['headers'];
394 }
395
396 if ( ! empty( $header_params['Content-Type'] ) ) {
397 $content_type = $header_params['Content-Type'];
398 }
399
400
401 $file_name = powered_cache_index_file( $content_type );
402 $file_path = $path . $file_name;
403
404 // check file exists?
405 if ( ! file_exists( $file_path ) ) {
406 return;
407 }
408
409 $modified_time = (int) @filemtime( $file_path );
410
411 header( 'Cache-Control: no-cache' ); // Check back in an hour
412
413 if ( ! empty( $modified_time ) && ! empty( $_SERVER['HTTP_IF_MODIFIED_SINCE'] ) && strtotime( $_SERVER['HTTP_IF_MODIFIED_SINCE'] ) === $modified_time ) {
414 header( $_SERVER['SERVER_PROTOCOL'] . ' 304 Not Modified', true, 304 );
415 exit;
416 }
417
418 // trailingslash check
419 if ( isset( $powered_cache_slash_check ) && $powered_cache_slash_check ) {
420 $current_path = parse_url( $_SERVER['REQUEST_URI'], PHP_URL_PATH );
421 if ( ! empty( $current_path ) && '/' !== substr( $current_path, - 1 ) ) {
422 header( 'X-Powered-Cache: Passing to WordPress' );
423
424 return;
425 }
426 }
427
428 if ( @file_exists( $file_path ) && @is_readable( $file_path ) ) {
429
430 if ( ! empty( $header_params ) ) {
431 foreach ( $header_params as $key => $response_header ) {
432 header( $response_header );
433 }
434 }
435
436 header( 'X-Powered-Cache: PHP' );
437
438 if ( function_exists( 'gzencode' ) && $GLOBALS['powered_cache_options']['gzip_compression'] ) {
439 header( 'Content-Encoding: gzip' );
440 }
441
442 @readfile( $file_path );
443
444 exit;
445 }
446 }
447
448 /**
449 * Get URL path for caching
450 *
451 * @return string
452 * @since 1.0
453 * @since 2.0 $request_uri without query string
454 */
455 function powered_cache_get_url_path() {
456 $host = ( isset( $_SERVER['HTTP_HOST'] ) ? $_SERVER['HTTP_HOST'] : '' );
457 $request_uri = explode( '?', $_SERVER['REQUEST_URI'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
458 $request_uri = reset( $request_uri );
459 $request_uri = preg_replace( '/(\/+)/', '/', $request_uri );
460 $request_uri = str_replace( '..', '', preg_replace( '/[ <>\'\"\r\n\t\(\)]/', '', $request_uri ) );
461
462 return rtrim( $host, '/' ) . $request_uri;
463 }
464
465 function powered_cache_get_user_cookie() {
466 if ( empty( $_COOKIE ) ) {
467 return false;
468 }
469
470 foreach ( $_COOKIE as $c_key => $val ) {
471 if ( false !== strpos( $c_key, 'wordpress_logged_in_' ) ) {
472 return $val;
473 }
474 }
475
476 return false;
477 }
478
479
480 /**
481 * Determines cache file names
482 *
483 * @param string $content_type
484 *
485 * @return string
486 * @since 1.2 `$content_type`
487 * @since 1.0
488 */
489 function powered_cache_index_file( $content_type = 'text/html' ) {
490 global $powered_cache_mobile_browsers, $powered_cache_mobile_prefixes, $powered_cache_vary_cookies;
491
492 $file_name = 'index';
493
494 if ( is_ssl() ) {
495 $file_name .= '-https';
496 }
497
498 // separate file for mobile cache
499 if ( ! empty( $GLOBALS['powered_cache_options']['cache_mobile'] ) && ! empty( $GLOBALS['powered_cache_options']['cache_mobile_separate_file'] ) ) {
500 $mobile_browsers = addcslashes( implode( '|', preg_split( '/[\s*,\s*]*,+[\s*,\s*]*/', $powered_cache_mobile_browsers ) ), ' ' );
501 $mobile_prefixes = addcslashes( implode( '|', preg_split( '/[\s*,\s*]*,+[\s*,\s*]*/', $powered_cache_mobile_prefixes ) ), ' ' );
502
503 // Don't cache if mobile detection is activated
504 if ( ( preg_match( '#^.*(' . $mobile_browsers . ').*#i', $_SERVER['HTTP_USER_AGENT'] ) || preg_match( '#^(' . $mobile_prefixes . ').*#i', substr( $_SERVER['HTTP_USER_AGENT'], 0, 4 ) ) ) ) {
505 $file_name .= '-mobile';
506 }
507 }
508
509 if ( ! empty( $GLOBALS['powered_cache_options']['loggedin_user_cache'] ) ) {
510 $usr_cookie = powered_cache_get_user_cookie();
511 if ( false !== $usr_cookie ) {
512 $cookie_info = explode( '|', $usr_cookie );
513
514 // user specific cache dir
515 $file_name .= '_' . $cookie_info[0] . '-' . $cookie_info[1];
516 }
517 }
518
519 // change filename based on vary cookies
520 if ( ! empty( $powered_cache_vary_cookies ) ) {
521 $cookie_file_name = '';
522 foreach ( $powered_cache_vary_cookies as $key => $vary_cookie ) {
523 if ( is_array( $vary_cookie ) ) {
524 if ( ! empty( $_COOKIE[ $key ] ) ) {
525 foreach ( $vary_cookie as $vary_sub_cookie ) {
526 if ( isset( $_COOKIE[ $key ][ $vary_sub_cookie ] ) ) {
527 $cookie_value = preg_replace( '/[^A-Za-z0-9. -]/', '', $_COOKIE[ $key ][ $vary_sub_cookie ] );
528 $cookie_file_name .= strtolower( $key . $vary_sub_cookie . $cookie_value );
529 }
530 }
531 }
532
533 continue;
534 }
535
536 if ( isset( $_COOKIE[ $vary_cookie ] ) && ! empty( $_COOKIE[ $vary_cookie ] ) ) {
537 $cookie_value = preg_replace( '/[^A-Za-z0-9. -]/', '', $_COOKIE[ $vary_cookie ] );
538 $cookie_file_name .= strtolower( $vary_cookie . $cookie_value );
539 }
540 }
541
542 if ( ! empty( $cookie_file_name ) ) {
543 /**
544 * Hashing for no particular reason
545 * preg_replace( '/[^a-z0-9_\-]/', '', strtolower( $cookie_file_name ) )
546 * can create a messy filename
547 */
548 $file_name .= '-' . sha1( $cookie_file_name );
549 }
550
551 }
552
553
554 /**
555 * Content-Type is not always text/html (like feed, wp-json etc..)
556 * Adding hash by simply escaping from rewrite matches in htaccess or nginx
557 * Different types of content should be serve via PHP, in order to restore header info
558 */
559 if ( false === strpos( $content_type, 'text/html' ) ) {
560 $file_name .= '-' . substr( sha1( $content_type ), 0, 6 );
561 }
562
563 $file_name .= '.html';
564
565 if ( function_exists( 'gzencode' ) && $GLOBALS['powered_cache_options']['gzip_compression'] ) {
566 $file_name .= '.gz';
567 }
568
569 return $file_name;
570 }
571
572 /**
573 * Add cache miss header and reason
574 *
575 * @param string $reason Cache Miss info
576 *
577 * @since 2.2
578 */
579 function powered_cache_add_cache_miss_header( $reason ) {
580 if ( headers_sent() ) {
581 return;
582 }
583
584 header( 'X-Powered-Cache: MISS' );
585
586 if ( ( defined( 'POWERED_CACHE_ENABLE_LOG' ) && POWERED_CACHE_ENABLE_LOG )
587 || ( defined( 'POWERED_CACHE_MISS_REASON' ) && POWERED_CACHE_MISS_REASON )
588 ) {
589 header( "X-Powered-Cache-Miss-Reason: $reason" );
590 }
591 }
592