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

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