PluginProbe
Powered Cache – Caching and Optimization for WordPress – Easily Improve PageSpeed & Web Vitals Score / 2.0.4
Powered Cache – Caching and Optimization for WordPress – Easily Improve PageSpeed & Web Vitals Score v2.0.4
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.0.4, at includes/dropins/page-cache.php

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