PluginProbe
WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance / 3.2.3
WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance v3.2.3
4.7.0 4.6.1 4.6.0 4.5.5 4.5.4 4.5.3 4.5.2 3.2.20 3.2.21 3.2.22 3.2.3 3.2.5 3.2.6 3.2.7 3.2.9 3.3.0 3.3.1 3.3.2 3.4.0 3.4.1 3.4.2 3.5.0 3.6.0 3.7.0 3.7.1 All 111 releases
wp-optimize / cache / file-based-page-cache-functions.php

file-based-page-cache-functions.php in WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance 3.2.3, at cache/file-based-page-cache-functions.php

1,210 lines 36.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 if (!defined('ABSPATH')) die('No direct access allowed');
4
5 /**
6 * Extensions directory.
7 */
8 if (!defined('WPO_CACHE_EXT_DIR')) define('WPO_CACHE_EXT_DIR', dirname(__FILE__).'/extensions');
9
10 /**
11 * Holds utility functions used by file based cache
12 */
13
14 /**
15 * Cache output before it goes to the browser. If moving/renaming this function, then also change the check above.
16 *
17 * @param String $buffer Page HTML.
18 * @param Int $flags OB flags to be passed through.
19 *
20 * @return String
21 */
22 if (!function_exists('wpo_cache')) :
23 function wpo_cache($buffer, $flags) {
24
25 // This case appears to happen for unclear reasons without WP being fully loaded, e.g. https://wordpress.org/support/topic/fatal-error-since-wp-5-8-update/ . It is simplest just to short-circuit it.
26 if ('' === $buffer) return '';
27
28 // This array records reasons why no cacheing took place. Be careful not to allow actions to proceed that should not - i.e. take note of its state appropriately.
29 $no_cache_because = array();
30
31 if (strlen($buffer) < 255) {
32 $no_cache_because[] = sprintf(__('Output is too small (less than %d bytes) to be worth caching', 'wp-optimize'), 255);
33 }
34
35 // Don't cache pages for logged in users.
36 if (empty($GLOBALS['wpo_cache_config']['enable_user_specific_cache']) && (!function_exists('wpo_we_cache_per_role') || !wpo_we_cache_per_role()) && (!function_exists('is_user_logged_in') || (function_exists('wp_get_current_user') && is_user_logged_in()))) {
37 $no_cache_because[] = __('User is logged in', 'wp-optimize');
38 }
39
40 $restricted_page_type_cache = apply_filters('wpo_restricted_cache_page_type', false);
41 if ($restricted_page_type_cache) {
42 $no_cache_because[] = $restricted_page_type_cache;
43 }
44
45 // No root cache folder, so short-circuit here
46 if (!file_exists(WPO_CACHE_DIR)) {
47 $no_cache_because[] = __('WP-O cache parent directory was not found', 'wp-optimize').' ('.WPO_CACHE_DIR.')';
48 } elseif (!file_exists(WPO_CACHE_FILES_DIR)) {
49 // Try creating a folder for cached files, if it was flushed recently
50 if (!mkdir(WPO_CACHE_FILES_DIR)) {
51 $no_cache_because[] = __('WP-O cache directory was not found', 'wp-optimize').' ('.WPO_CACHE_FILES_DIR.')';
52 } else {
53 wpo_disable_cache_directories_viewing();
54 }
55 }
56
57 // If comments are opened and the user has saved his information.
58 if (function_exists('comments_open') && function_exists('get_post') && get_post() && comments_open()) {
59 $commenter = wp_get_current_commenter();
60 // if any of the fields contain something, do not save to cache
61 if ('' != $commenter['comment_author'] || '' != $commenter['comment_author_email'] || '' != $commenter['comment_author_url']) {
62 $no_cache_because[] = __('Comments are opened and the visitor saved his information.', 'wp-optimize');
63 }
64 }
65
66 $can_cache_page = true;
67
68 if (defined('DONOTCACHEPAGE') && DONOTCACHEPAGE) {
69 $can_cache_page = false;
70 }
71
72 /**
73 * Defines if the page can be cached or not
74 *
75 * @param boolean $can_cache_page
76 */
77 $can_cache_page_filter = apply_filters('wpo_can_cache_page', $can_cache_page);
78
79 if (!$can_cache_page_filter) {
80 if ($can_cache_page) {
81 $can_cache_page = false;
82 $no_cache_because[] = __('wpo_can_cache_page filter forbade it', 'wp-optimize');
83 } else {
84 $no_cache_because[] = __('DONOTCACHEPAGE constant forbade it and wpo_can_cache_page filter did not over-ride it', 'wp-optimize');
85 }
86 }
87
88 if (defined('REST_REQUEST') && REST_REQUEST) {
89 $no_cache_because[] = __('This is a REST API request (identified by REST_REQUEST constant)', 'wp-optimize');
90 }
91
92 // Don't cache with fatal error pages.
93 $last_error = error_get_last();
94 if (is_array($last_error) && E_ERROR == $last_error['type']) {
95 $no_cache_because[] = __('This page has a fatal error', 'wp-optimize');
96 }
97
98 if (http_response_code() >= 500) {
99 $no_cache_because[] = sprintf(__('This page has a critical error (HTTP code %s)', 'wp-optimize'), http_response_code());
100 } elseif (http_response_code() >= 400) {
101 $no_cache_because[] = sprintf(__('This page returned an HTTP unauthorised response code (%s)', 'wp-optimize'), http_response_code());
102 }
103
104 if (empty($no_cache_because)) {
105
106 $buffer = apply_filters('wpo_pre_cache_buffer', $buffer, $flags);
107
108 $url_path = wpo_get_url_path();
109
110 $dirs = explode('/', $url_path);
111
112 $path = WPO_CACHE_FILES_DIR;
113
114 foreach ($dirs as $dir) {
115 if (!empty($dir)) {
116 $path .= '/' . $dir;
117
118 if (!file_exists($path)) {
119 if (!mkdir($path)) {
120 $no_cache_because[] = __('Attempt to create subfolder within cache directory failed', 'wp-optimize')." ($path)";
121 break;
122 }
123 }
124 }
125 }
126 }
127
128 if (!empty($no_cache_because)) {
129
130 $message = implode(', ', $no_cache_because);
131
132 // Add http headers
133 wpo_cache_add_nocache_http_header($message);
134
135 // Only output if the user has turned on debugging output
136 if (((defined('WP_DEBUG') && WP_DEBUG) || isset($_GET['wpo_cache_debug'])) && (!defined('DOING_CRON') || !DOING_CRON) && (!defined('REST_REQUEST') || !REST_REQUEST)) {
137 $buffer .= "\n<!-- WP Optimize page cache - https://getwpo.com - page NOT cached because: ".htmlspecialchars($message)." -->\n";
138 }
139
140 return $buffer;
141
142 } else {
143
144 // Prevent mixed content when there's an http request but the site URL uses https.
145 $home_url = get_home_url();
146
147 if (!is_ssl() && 'https' === strtolower(parse_url($home_url, PHP_URL_SCHEME))) {
148 $https_home_url = $home_url;
149 $http_home_url = str_ireplace('https://', 'http://', $https_home_url);
150 $buffer = str_replace(esc_url($http_home_url), esc_url($https_home_url), $buffer);
151 }
152
153 $modified_time = time(); // Take this as soon before writing as possible
154
155 $add_to_footer = '';
156
157 /**
158 * Filter wether to display the html comment <!-- Cached by WP-Optimize ... -->
159 *
160 * @param boolean $show - Wether to display the html comment
161 * @return boolean
162 */
163 if (preg_match('#</html>#i', $buffer) && (apply_filters('wpo_cache_show_cached_by_comment', true) || (defined('WP_DEBUG') && WP_DEBUG))) {
164 if (!empty($GLOBALS['wpo_cache_config']['enable_mobile_caching']) && wpo_is_mobile()) {
165 $add_to_footer .= "\n<!-- Cached by WP-Optimize - for mobile devices - https://getwpo.com - Last modified: " . gmdate('D, d M Y H:i:s', $modified_time) . " GMT -->\n";
166 } else {
167 $add_to_footer .= "\n<!-- Cached by WP-Optimize - https://getwpo.com - Last modified: " . gmdate('D, d M Y H:i:s', $modified_time) . " GMT -->\n";
168 }
169 }
170
171 // Create an empty index.php file in the cache directory for disable directory viewing.
172 if (!is_file($path . '/index.php')) file_put_contents($path . '/index.php', '');
173
174 /**
175 * Save $buffer into cache file.
176 */
177 $file_ext = '.html';
178
179 if (wpo_feeds_caching_enabled()) {
180 if (is_feed()) {
181 $file_ext = '.rss-xml';
182 }
183 }
184
185 $cache_filename = wpo_cache_filename($file_ext);
186 $cache_file = $path . '/' .$cache_filename;
187
188 if (defined('WPO_CACHE_FILENAME_DEBUG') && WPO_CACHE_FILENAME_DEBUG) {
189 $add_to_footer .= "\n<!-- WP Optimize page cache debug information -->\n";
190 if (!empty($GLOBALS['wpo_cache_filename_debug']) && is_array($GLOBALS['wpo_cache_filename_debug'])) {
191 $add_to_footer .= "<!-- \n" . join("\n", array_map('htmlspecialchars', $GLOBALS['wpo_cache_filename_debug'])) . "\n --->";
192 }
193 }
194
195 // if we can then cache gzipped content in .gz file.
196 if (function_exists('gzencode')) {
197 // Only replace inside the addition, not inside the main buffer (e.g. post content)
198 file_put_contents($cache_file . '.gz', gzencode($buffer.str_replace('by WP-Optimize', 'by WP-Optimize (gzip)', $add_to_footer), apply_filters('wpo_cache_gzip_level', 6)));
199 }
200
201 file_put_contents($cache_file, $buffer.$add_to_footer);
202
203 if (is_callable('WP_Optimize')) {
204 // delete cached information about cache size.
205 WP_Optimize()->get_page_cache()->delete_cache_size_information();
206 } else {
207 error_log('[WPO_CACHE] WP_Optimize() is not callable.');
208 $message = 'Please report this to WP-O support: ';
209 if (function_exists('wp_debug_backtrace_summary')) {
210 $message .= wp_debug_backtrace_summary();
211 } else {
212 $message .= wpo_debug_backtrace_summary();
213 }
214 error_log($message);
215 }
216
217 header('Cache-Control: no-cache'); // Check back every time to see if re-download is necessary.
218 header('Last-Modified: ' . gmdate('D, d M Y H:i:s', $modified_time) . ' GMT');
219 header('WPO-Cache-Status: saving to cache');
220
221 if (wpo_cache_can_output_gzip_content()) {
222
223 if (!wpo_cache_is_in_response_headers_list('Content-Encoding', 'gzip')) {
224 header('Content-Encoding: gzip');
225 }
226
227 // disable php gzip to avoid double compression.
228 ini_set('zlib.output_compression', 'Off'); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.runtime_configuration_ini_set
229
230 return ob_gzhandler($buffer, $flags);
231 } else {
232 return $buffer;
233 }
234 }
235 }
236 endif;
237
238 /**
239 * Load files for support plugins.
240 */
241 if (!function_exists('wpo_cache_load_extensions')) :
242 function wpo_cache_load_extensions() {
243 $extensions = glob(WPO_CACHE_EXT_DIR . '/*.php');
244
245 // Add external extensions
246 if (defined('WPO_CACHE_CUSTOM_EXT_DIR') && is_dir(WPO_CACHE_CUSTOM_EXT_DIR)) {
247 $extensions = array_merge($extensions, glob(WPO_CACHE_CUSTOM_EXT_DIR . '/*.php'));
248 }
249
250 if (empty($extensions)) return;
251
252 foreach ($extensions as $extension) {
253 if (is_file($extension)) require_once $extension;
254 }
255 }
256 endif;
257
258 if (!function_exists('wpo_restricted_cache_page_type')) {
259 function wpo_restricted_cache_page_type($restricted) {
260 global $post;
261
262 // Don't cache search or password protected.
263 if ((function_exists('is_search') && is_search()) || (function_exists('is_404') && is_404()) || !empty($post->post_password)) {
264 $restricted = __('Page type is not cacheable (search, 404 or password-protected)', 'wp-optimize');
265 }
266
267 // Don't cache the front page if option is set.
268 if (in_array('/', wpo_get_url_exceptions()) && function_exists('is_front_page') && is_front_page()) {
269
270 $restricted = __('In the settings, caching is disabled for the front page', 'wp-optimize');
271 }
272
273 // Don't cache htacesss. Remember to properly escape any output to prevent injection.
274 if (strpos($_SERVER['REQUEST_URI'], '.htaccess') !== false) {
275 $restricted = 'The file path is unsuitable for caching ('.$_SERVER['REQUEST_URI'].')';
276 }
277
278 // Don't cache feeds.
279 if (function_exists('is_feed') && is_feed() && !wpo_feeds_caching_enabled()) {
280 $restricted = __('We don\'t cache RSS feeds', 'wp-optimize');
281 }
282
283 return $restricted;
284 }
285 }
286
287 /**
288 * Returns true if we need cache content for loggedin users.
289 *
290 * @return bool
291 */
292 if (!function_exists('wpo_cache_loggedin_users')) :
293 function wpo_cache_loggedin_users() {
294 return !empty($GLOBALS['wpo_cache_config']['enable_user_caching']) || !empty($GLOBALS['wpo_cache_config']['enable_user_specific_cache']) || (function_exists('wpo_we_cache_per_role') && wpo_we_cache_per_role());
295 }
296 endif;
297
298 /**
299 * Returns true if we need to cache content for loggedin users.
300 *
301 * @return bool
302 */
303 if (!function_exists('wpo_user_specific_cache_enabled')) :
304 function wpo_user_specific_cache_enabled() {
305 return !empty($GLOBALS['wpo_cache_config']['enable_user_specific_cache']) && !empty($GLOBALS['wpo_cache_config']['wp_salt_auth']) && !empty($GLOBALS['wpo_cache_config']['wp_salt_logged_in']);
306 }
307 endif;
308
309 /**
310 * Get filename for store cache, depending on gzip, mobile and cookie settings.
311 *
312 * @param string $ext
313 * @return string
314 */
315 if (!function_exists('wpo_cache_filename')) :
316 function wpo_cache_filename($ext = '.html') {
317
318 $wpo_cache_filename_debug = array();
319
320 $filename = 'index';
321
322 if (wpo_cache_mobile_caching_enabled() && wpo_is_mobile()) {
323 $filename = 'mobile.' . $filename;
324 }
325
326 $cookies = wpo_cache_cookies();
327
328 $cache_key = '';
329
330 /**
331 * Add cookie values to filename if need.
332 * This section was inspired by things learned from WP-Rocket.
333 */
334 if (!empty($cookies)) {
335 foreach ($cookies as $key => $cookie_name) {
336 if (is_array($cookie_name) && isset($_COOKIE[$key])) {
337 foreach ($cookie_name as $cookie_key) {
338 if (isset($_COOKIE[$key][$cookie_key]) && '' !== $_COOKIE[$key][$cookie_key]) {
339 $_cache_key = $cookie_key.'='.$_COOKIE[$key][$cookie_key];
340 $_cache_key = preg_replace('/[^a-z0-9_\-\=]/i', '-', $_cache_key);
341 $cache_key .= '-' . $_cache_key;
342 $wpo_cache_filename_debug[] = 'Cookie: name: ' . $key . '[' . $cookie_key . '], value: *** , cache_key:' . $_cache_key;
343 }
344 }
345 continue;
346 }
347
348 if (isset($_COOKIE[$cookie_name]) && '' !== $_COOKIE[$cookie_name]) {
349 $_cache_key = $cookie_name.'='.$_COOKIE[$cookie_name];
350 $_cache_key = preg_replace('/[^a-z0-9_\-\=]/i', '-', $_cache_key);
351 $cache_key .= '-' . $_cache_key;
352 $wpo_cache_filename_debug[] = 'Cookie: name: ' . $cookie_name . ', value: *** , cache_key:' . $_cache_key;
353 }
354 }
355 }
356
357 $query_variables = wpo_cache_query_variables();
358
359 /**
360 * Add GET variables to cache file name if need.
361 */
362 if (!empty($query_variables)) {
363 foreach ($query_variables as $variable) {
364 if (isset($_GET[$variable]) && !empty($_GET[$variable])) {
365 $_cache_key = $variable.'='.$_GET[$variable];
366 $_cache_key = preg_replace('/[^a-z0-9_\-\=]/i', '-', $_cache_key);
367 $cache_key .= '-' . $_cache_key;
368 $wpo_cache_filename_debug[] = 'GET parameter: name: ' . $variable . ', value:' . htmlentities($_GET[$variable]) . ', cache_key:' . $_cache_key;
369 }
370 }
371 }
372
373 // add hash of queried cookies and variables to cache file name.
374 if ('' !== $cache_key) {
375 $hash = md5($cache_key);
376 $filename .= '-'.$hash;
377 $wpo_cache_filename_debug[] = 'Hash: ' . $hash;
378 }
379
380 $filename = apply_filters('wpo_cache_filename', $filename);
381
382 $wpo_cache_filename_debug[] = 'Extension: ' . $ext;
383 $wpo_cache_filename_debug[] = 'Filename: ' . $filename.$ext;
384
385 $GLOBALS['wpo_cache_filename_debug'] = $wpo_cache_filename_debug;
386
387 return $filename . $ext;
388 }
389 endif;
390
391 /**
392 * Returns site url from site_url() function or if it is not available from cache configuration.
393 */
394 if (!function_exists('wpo_site_url')) :
395 function wpo_site_url() {
396 if (is_callable('site_url')) return site_url('/');
397
398 $site_url = empty($GLOBALS['wpo_cache_config']['site_url']) ? '' : $GLOBALS['wpo_cache_config']['site_url'];
399 return $site_url;
400 }
401 endif;
402
403 /**
404 * Get cookie names which impact on cache file name.
405 *
406 * @return array
407 */
408 if (!function_exists('wpo_cache_cookies')) :
409 function wpo_cache_cookies() {
410 $cookies = empty($GLOBALS['wpo_cache_config']['wpo_cache_cookies']) ? array() : $GLOBALS['wpo_cache_config']['wpo_cache_cookies'];
411 return $cookies;
412 }
413 endif;
414
415 /**
416 * Get GET variable names which impact on cache file name.
417 *
418 * @return array
419 */
420 if (!function_exists('wpo_cache_query_variables')) :
421 function wpo_cache_query_variables() {
422 if (defined('WPO_CACHE_URL_PARAMS') && WPO_CACHE_URL_PARAMS) {
423 $variables = array_keys($_GET);
424 } else {
425 $variables = empty($GLOBALS['wpo_cache_config']['wpo_cache_query_variables']) ? array() : $GLOBALS['wpo_cache_config']['wpo_cache_query_variables'];
426 }
427
428 if (!empty($variables)) {
429 sort($variables);
430 }
431
432 return wpo_cache_maybe_ignore_query_variables($variables);
433 }
434 endif;
435
436 /**
437 * Get list of all received HTTP headers.
438 *
439 * @return array
440 */
441 if (!function_exists('wpo_get_http_headers')) :
442 function wpo_get_http_headers() {
443
444 static $headers;
445
446 if (!empty($headers)) return $headers;
447
448 $headers = array();
449
450 // if is apache server then use get allheaders() function.
451 if (function_exists('getallheaders')) {
452 $headers = getallheaders();
453 } else {
454 // https://www.php.net/manual/en/function.getallheaders.php
455 foreach ($_SERVER as $key => $value) {
456
457 $key = strtolower($key);
458
459 if ('HTTP_' == substr($key, 0, 5)) {
460 $headers[str_replace(' ', '-', ucwords(str_replace('_', ' ', substr($key, 5))))] = $value;
461 } elseif ('content_type' == $key) {
462 $headers["Content-Type"] = $value;
463 } elseif ('content_length' == $key) {
464 $headers["Content-Length"] = $value;
465 }
466 }
467 }
468
469 return $headers;
470 }
471 endif;
472
473 /**
474 * Check if requested Accept-Encoding headers has gzip value.
475 *
476 * @return bool
477 */
478 if (!function_exists('wpo_cache_gzip_accepted')) :
479 function wpo_cache_gzip_accepted() {
480 $headers = wpo_get_http_headers();
481
482 if (isset($headers['Accept-Encoding']) && preg_match('/gzip/i', $headers['Accept-Encoding'])) return true;
483
484 return false;
485 }
486 endif;
487
488 /**
489 * Check if we can output gzip content in current answer, i.e. check Accept-Encoding headers has gzip value
490 * and function ob_gzhandler is available.
491 *
492 * @return bool
493 */
494 if (!function_exists('wpo_cache_can_output_gzip_content')) :
495 function wpo_cache_can_output_gzip_content() {
496 return wpo_cache_gzip_accepted() && function_exists('ob_gzhandler');
497 }
498 endif;
499
500 /**
501 * Check if header with certain name exists in already prepared headers and has value comparable with $header_value.
502 *
503 * @param string $header_name header name
504 * @param string $header_value header value as regexp.
505 *
506 * @return bool
507 */
508 if (!function_exists('wpo_cache_is_in_response_headers_list')) :
509 function wpo_cache_is_in_response_headers_list($header_name, $header_value) {
510 $headers_list = headers_list();
511
512 if (!empty($headers_list)) {
513 $header_name = strtolower($header_name);
514
515 foreach ($headers_list as $value) {
516 $value = explode(':', $value);
517
518 if (strtolower($value[0]) == $header_name) {
519 if (preg_match('/'.$header_value.'/', $value[1])) {
520 return true;
521 } else {
522 return false;
523 }
524 }
525 }
526 }
527
528 return false;
529 }
530 endif;
531
532 /**
533 * Check if mobile cache is enabled and current request is from moblile device.
534 *
535 * @return bool
536 */
537 if (!function_exists('wpo_cache_mobile_caching_enabled')) :
538 function wpo_cache_mobile_caching_enabled() {
539 if (!empty($GLOBALS['wpo_cache_config']['enable_mobile_caching'])) return true;
540 return false;
541 }
542 endif;
543
544 /**
545 * Serves the cache and exits
546 */
547 if (!function_exists('wpo_serve_cache')) :
548 function wpo_serve_cache() {
549 $file_name = wpo_cache_filename();
550
551 $file_name_rss_xml = wpo_cache_filename('.rss-xml');
552 $send_as_feed = false;
553
554 $path_dir = WPO_CACHE_FILES_DIR . '/' . wpo_get_url_path() . '/';
555 $path = $path_dir . $file_name;
556
557 if (wpo_feeds_caching_enabled()) {
558 // check for .xml cache file if .html cache file doesn't exist
559 if (!file_exists($path_dir . $file_name) && file_exists($path_dir . $file_name_rss_xml)) {
560 $path = $path_dir . $file_name_rss_xml;
561 $send_as_feed = true;
562 }
563 }
564
565 $use_gzip = false;
566
567 // if we can use gzip and gzipped file exist in cache we use it.
568 // if headers already sent we don't use gzipped file content.
569 if (!headers_sent() && wpo_cache_gzip_accepted() && file_exists($path . '.gz')) {
570 $path .= '.gz';
571 $use_gzip = true;
572 }
573
574 $modified_time = file_exists($path) ? (int) filemtime($path) : time();
575
576 // Cache has expired, purge and exit.
577 if (!empty($GLOBALS['wpo_cache_config']['page_cache_length'])) {
578 if (time() > ($GLOBALS['wpo_cache_config']['page_cache_length'] + $modified_time)) {
579 wpo_delete_files($path);
580 return;
581 }
582 }
583
584 // disable zlib output compression to avoid double content compression.
585 if ($use_gzip) {
586 ini_set('zlib.output_compression', 'Off'); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.runtime_configuration_ini_set
587 }
588
589 $gzip_header_already_sent = wpo_cache_is_in_response_headers_list('Content-Encoding', 'gzip');
590
591 header('Cache-Control: no-cache'); // Check back later
592
593 if (!empty($modified_time) && !empty($_SERVER['HTTP_IF_MODIFIED_SINCE']) && strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) === $modified_time) {
594 if ($use_gzip && !$gzip_header_already_sent) {
595 header('Content-Encoding: gzip');
596 }
597
598 if ($send_as_feed) {
599 header('Content-type: application/rss+xml');
600 }
601
602 header('WPO-Cache-Status: cached');
603 header('Last-Modified: ' . gmdate('D, d M Y H:i:s', $modified_time) . ' GMT');
604 header($_SERVER['SERVER_PROTOCOL'] . ' 304 Not Modified', true, 304);
605 exit;
606 }
607
608 if (file_exists($path) && is_readable($path)) {
609 if ($use_gzip && !$gzip_header_already_sent) {
610 header('Content-Encoding: gzip');
611 }
612
613 // send correct headers for xml and txt files
614 $filename = basename(dirname($path));
615
616 if (preg_match('/\.xml$/i', $filename)) {
617 header('Content-type: text/xml');
618 }
619
620 if (preg_match('/\.txt$/i', $filename)) {
621 header('Content-type: text/plain');
622 }
623
624 if ($send_as_feed) {
625 header('Content-type: application/rss+xml');
626 }
627
628 header('WPO-Cache-Status: cached');
629 if (!empty($modified_time)) {
630 header('Last-Modified: ' . gmdate('D, d M Y H:i:s', $modified_time) . ' GMT');
631 }
632
633 readfile($path);
634
635 exit;
636 }
637 }
638 endif;
639
640 /**
641 * Clears the cache
642 */
643 if (!function_exists('wpo_cache_flush')) :
644 function wpo_cache_flush() {
645
646 if (defined('WPO_CACHE_FILES_DIR') && '' != WPO_CACHE_FILES_DIR) wpo_delete_files(WPO_CACHE_FILES_DIR);
647
648 if (function_exists('wp_cache_flush')) {
649 wp_cache_flush();
650 }
651
652 do_action('wpo_cache_flush');
653 }
654 endif;
655
656 /**
657 * Get URL path for caching
658 *
659 * @since 1.0
660 * @return string
661 */
662 if (!function_exists('wpo_get_url_path')) :
663 function wpo_get_url_path($url = '') {
664 $url = '' == $url ? wpo_current_url() : $url;
665 $url_parts = parse_url($url);
666
667 if (isset($url_parts['path']) && false !== stripos($url_parts['path'], '/index.php')) {
668 $url_parts['path'] = preg_replace('/(.*?)index\.php(\/.+)/i', '$1index-php$2', $url_parts['path']);
669 }
670
671 if (!isset($url_parts['host'])) $url_parts['host'] = '';
672 if (!isset($url_parts['path'])) $url_parts['path'] = '';
673
674 return $url_parts['host'].$url_parts['path'];
675 }
676 endif;
677
678 /**
679 * Get requested url.
680 *
681 * @return string
682 */
683 if (!function_exists('wpo_current_url')) :
684 function wpo_current_url() {
685 // Note: We use `static $url` to save the first value we retrieve, as some plugins change $_SERVER later on in the process (e.g. Weglot).
686 // Otherwise this function would return a different URL at the begining and end of the cache process.
687 static $url = '';
688 if ('' != $url) return $url;
689 $http_host = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : '';
690 $url = rtrim('http' . ((isset($_SERVER['HTTPS']) && ('on' == $_SERVER['HTTPS'] || 1 == $_SERVER['HTTPS']) ||
691 isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && 'https' == $_SERVER['HTTP_X_FORWARDED_PROTO']) ? 's' : '' )
692 . '://' . $http_host.$_SERVER['REQUEST_URI'], '/');
693 return $url;
694 }
695 endif;
696
697 /**
698 * Return list of url exceptions.
699 *
700 * @return array
701 */
702 if (!function_exists('wpo_get_url_exceptions')) :
703 function wpo_get_url_exceptions() {
704 static $exceptions = null;
705
706 if (null !== $exceptions) return $exceptions;
707
708 // if called from file-based-page-cache.php when WP loading
709 // and cache settings exists then use it otherwise get settings from database.
710 if (isset($GLOBALS['wpo_cache_config']['cache_exception_urls'])) {
711 if (empty($GLOBALS['wpo_cache_config']['cache_exception_urls'])) {
712 $exceptions = array();
713 } else {
714 $exceptions = is_array($GLOBALS['wpo_cache_config']['cache_exception_urls']) ? $GLOBALS['wpo_cache_config']['cache_exception_urls'] : preg_split('#(\n|\r)#', $GLOBALS['wpo_cache_config']['cache_exception_urls']);
715 }
716 } else {
717 $config = WPO_Page_Cache::instance()->config->get();
718
719 if (is_array($config) && array_key_exists('cache_exception_urls', $config)) {
720 $exceptions = $config['cache_exception_urls'];
721 } else {
722 $exceptions = array();
723 }
724
725 $exceptions = is_array($exceptions) ? $exceptions : preg_split('#(\n|\r)#', $exceptions);
726 $exceptions = array_filter($exceptions, 'trim');
727 }
728
729 return apply_filters('wpo_get_url_exceptions', $exceptions);
730 }
731 endif;
732
733 /**
734 * Return true of exception url matches current url
735 *
736 * @param string $exception Exceptions to check URL against.
737 * @param bool $regex Whether to check with regex or not.
738 * @return bool true if matched, false otherwise
739 */
740 if (!function_exists('wpo_current_url_exception_match')) :
741 function wpo_current_url_exception_match($exception) {
742
743 return wpo_url_exception_match(wpo_current_url(), $exception);
744 }
745 endif;
746
747 /**
748 * Check if url in exceptions list.
749 *
750 * @param string $url
751 *
752 * @return bool
753 */
754 if (!function_exists('wpo_url_in_exceptions')) :
755 function wpo_url_in_exceptions($url) {
756 $exceptions = wpo_get_url_exceptions();
757
758 if (!empty($exceptions)) {
759 foreach ($exceptions as $exception) {
760
761 // don't check / - front page using regexp, we handle it in wpo_restricted_cache_page_type()
762 if ('/' == $exception) continue;
763
764 if (wpo_url_exception_match($url, $exception)) {
765 // Exception match.
766 return true;
767 }
768 }
769 }
770
771 return false;
772 }
773 endif;
774
775 /**
776 * Check if url string match with exception.
777 *
778 * @param string $url - complete url string i.e. http(s):://domain/path
779 * @param string $exception - complete url or absolute path, can consist (.*) wildcards
780 *
781 * @return bool
782 */
783 if (!function_exists('wpo_url_exception_match')) :
784 function wpo_url_exception_match($url, $exception) {
785 if (preg_match('#^[\s]*$#', $exception)) {
786 return false;
787 }
788
789 $exception = str_replace('*', '.*', $exception);
790
791 $exception = trim($exception);
792
793 // used to test websites placed in subdirectories.
794 $sub_dir = '';
795
796 // if exception defined from root i.e. /page1 then remove domain part in url.
797 if (preg_match('/^\//', $exception)) {
798 // get site sub directory.
799 $sub_dir = preg_replace('#^(http|https):\/\/.*\/#Ui', '', wpo_site_url());
800 // add prefix slash and remove slash.
801 $sub_dir = ('' == $sub_dir) ? '' : '/' . rtrim($sub_dir, '/');
802 // get relative path
803 $url = preg_replace('#^(http|https):\/\/.*\/#Ui', '/', $url);
804 }
805
806 $url = rtrim($url, '/') . '/';
807 $exception = rtrim($exception, '/');
808
809 // if we have no wildcat in the end of exception then add slash.
810 if (!preg_match('#\(\.\*\)$#', $exception)) $exception .= '/';
811
812 $exception = preg_quote($exception);
813
814 // fix - unescape possible escaped mask .*
815 $exception = str_replace('\\.\\*', '.*', $exception);
816
817 return preg_match('#^'.$exception.'$#i', $url) || preg_match('#^'.$sub_dir.$exception.'$#i', $url);
818 }
819 endif;
820
821 /**
822 * Checks if its a mobile device
823 *
824 * @see https://developer.wordpress.org/reference/functions/wp_is_mobile/
825 */
826 if (!function_exists('wpo_is_mobile')) :
827 function wpo_is_mobile() {
828 if (empty($_SERVER['HTTP_USER_AGENT'])) {
829 $is_mobile = false;
830 // many mobile devices (all iPhone, iPad, etc.)
831 } elseif (strpos($_SERVER['HTTP_USER_AGENT'], 'Mobile') !== false
832 || strpos($_SERVER['HTTP_USER_AGENT'], 'Android') !== false
833 || strpos($_SERVER['HTTP_USER_AGENT'], 'Silk/') !== false
834 || strpos($_SERVER['HTTP_USER_AGENT'], 'Kindle') !== false
835 || strpos($_SERVER['HTTP_USER_AGENT'], 'BlackBerry') !== false
836 || strpos($_SERVER['HTTP_USER_AGENT'], 'Opera Mini') !== false
837 || strpos($_SERVER['HTTP_USER_AGENT'], 'Opera Mobi') !== false
838 ) {
839 $is_mobile = true;
840 } else {
841 $is_mobile = false;
842 }
843
844 return $is_mobile;
845 }
846 endif;
847
848 /**
849 * Check if current browser agent is not disabled in options.
850 *
851 * @return bool
852 */
853 if (!function_exists('wpo_is_accepted_user_agent')) :
854 function wpo_is_accepted_user_agent($user_agent) {
855
856 $exceptions = is_array($GLOBALS['wpo_cache_config']['cache_exception_browser_agents']) ? $GLOBALS['wpo_cache_config']['cache_exception_browser_agents'] : preg_split('#(\n|\r)#', $GLOBALS['wpo_cache_config']['cache_exception_browser_agents']);
857
858 if (!empty($exceptions)) {
859 foreach ($exceptions as $exception) {
860 if ('' == trim($exception)) continue;
861
862 if (preg_match('#'.$exception.'#i', $user_agent)) return false;
863 }
864 }
865
866 return true;
867 }
868 endif;
869
870 /**
871 * Delete function that deals with directories recursively
872 *
873 * @param string $src Path of the folder
874 * @param boolean $recursive If $src is a folder, recursively delete the inner folders. If set to false, only the files will be deleted.
875 *
876 * @return bool
877 */
878 if (!function_exists('wpo_delete_files')) :
879 function wpo_delete_files($src, $recursive = true) {
880 if (!file_exists($src) || '' == $src || '/' == $src) {
881 return true;
882 }
883
884 if (is_file($src)) {
885 return unlink($src);
886 }
887
888 $success = true;
889 $has_dir = false;
890
891 if ($recursive) {
892 // N.B. If opendir() fails, then a false positive (i.e. true) will be returned
893 if (false !== ($dir = opendir($src))) {
894 $file = readdir($dir);
895 while (false !== $file) {
896 if ('.' == $file || '..' == $file) {
897 $file = readdir($dir);
898 continue;
899 }
900 if (is_dir($src . '/' . $file)) {
901 if (!wpo_delete_files($src . '/' . $file)) {
902 $success = false;
903 }
904 } else {
905 if (!unlink($src . '/' . $file)) {
906 $success = false;
907 }
908 }
909
910 $file = readdir($dir);
911 }
912 closedir($dir);
913 }
914 } else {
915 // Not recursive, so we only delete the files
916 // scan directories recursively.
917 $handle = opendir($src);
918
919 if (false === $handle) return false;
920
921 $file = readdir($handle);
922
923 while (false !== $file) {
924
925 if ('.' != $file && '..' != $file) {
926 if (is_dir($src . '/' . $file)) {
927 $has_dir = true;
928 } elseif (!unlink($src . '/' . $file)) {
929 $success = false;
930 }
931 }
932
933 $file = readdir($handle);
934
935 }
936 }
937
938 if ($success && !$has_dir) {
939 // Success of this operation is not recorded; we only ultimately care about emptying, not removing entirely (empty folders in our context are harmless)
940 rmdir($src);
941 }
942
943 // delete cached information about cache size.
944 WP_Optimize()->get_page_cache()->delete_cache_size_information();
945
946 return $success;
947 }
948 endif;
949
950 if (!function_exists('wpo_is_empty_dir')) :
951 /**
952 * Check if selected directory is empty or has only index.php which we added for security reasons.
953 *
954 * @param string $dir
955 *
956 * @return bool
957 */
958 function wpo_is_empty_dir($dir) {
959 if (!file_exists($dir) || !is_dir($dir)) return false;
960
961 $handle = opendir($dir);
962
963 if (false === $handle) return false;
964
965 $is_empty = true;
966 $file = readdir($handle);
967
968 while (false !== $file) {
969
970 if ('.' != $file && '..' != $file && 'index.php' != $file) {
971 $is_empty = false;
972 break;
973 }
974
975 $file = readdir($handle);
976 }
977
978 closedir($handle);
979 return $is_empty;
980 }
981 endif;
982
983 /**
984 * Either store for later output, or output now. Only the most-recent call will be effective.
985 *
986 * @param String|Null $output - if not null, then the string to use when called by the shutdown action.
987 */
988 if (!function_exists('wpo_cache_add_footer_output')) :
989 function wpo_cache_add_footer_output($output = null) {
990
991 static $buffered = null;
992
993 if (function_exists('current_filter') && 'shutdown' == current_filter()) {
994 // Only add the line if it was a page, not something else (e.g. REST response)
995 if (function_exists('did_action') && did_action('wp_footer')) {
996 echo "\n<!-- WP Optimize page cache - https://getwpo.com - ".$buffered." -->\n";
997 } elseif (defined('WPO_CACHE_DEBUG') && WPO_CACHE_DEBUG) {
998 error_log('[CACHE DEBUG] '.wpo_current_url() . ' - ' . $buffered);
999 }
1000 } else {
1001 if (null == $buffered && function_exists('add_action')) add_action('shutdown', 'wpo_cache_add_footer_output', 11);
1002 $buffered = $output;
1003 }
1004
1005 }
1006 endif;
1007
1008 /**
1009 * Remove variable names that shouldn't influence cache.
1010 *
1011 * @param array $variables List of variable names.
1012 *
1013 * @return array
1014 */
1015 if (!function_exists('wpo_cache_maybe_ignore_query_variables')) :
1016 function wpo_cache_maybe_ignore_query_variables($variables) {
1017
1018 /**
1019 * Filters the current $_GET variables that will be used when caching or excluding from cache.
1020 * Currently:
1021 * - 'wpo_cache_debug' (Shows the reason for not being cached even when WP_DEBUG isn't set)
1022 * - 'doing_wp_cron' (alternative cron)
1023 * - 'aiosp_sitemap_path', 'aiosp_sitemap_page' (All in one SEO sitemap)
1024 * - 'xml_sitemap', 'seopress_sitemap', 'seopress_news', 'seopress_video', 'seopress_cpt', 'seopress_paged' (SEOPress sitemap)
1025 * - 'sitemap', 'sitemap_n' (YOAST SEO sitemap)
1026 */
1027 $exclude_variables = array(
1028 'wpo_cache_debug', // Shows the reason for not being cached even when WP_DEBUG isn't set
1029 'doing_wp_cron', // alternative cron
1030 'aiosp_sitemap_path', // All in one SEO sitemap
1031 'aiosp_sitemap_page',
1032 'xml_sitemap', // SEOPress sitemap
1033 'seopress_sitemap',
1034 'seopress_news',
1035 'seopress_video',
1036 'seopress_cpt',
1037 'seopress_paged',
1038 'sitemap', // YOAST SEO sitemap
1039 'sitemap_n',
1040 );
1041 $exclude_variables = function_exists('apply_filters') ? apply_filters('wpo_cache_ignore_query_variables', $exclude_variables) : $exclude_variables;
1042
1043 if (empty($exclude_variables)) return $variables;
1044
1045 foreach ($exclude_variables as $variable) {
1046 $exclude = array_search($variable, $variables);
1047 if (false !== $exclude) {
1048 array_splice($variables, $exclude, 1);
1049 }
1050 }
1051
1052 return $variables;
1053 }
1054 endif;
1055
1056 /**
1057 * Get cache config
1058 *
1059 * @param string $key - The config item
1060 * @param mixed $default - The default value
1061 *
1062 * @return mixed
1063 */
1064 if (!function_exists('wpo_cache_config_get')) :
1065 function wpo_cache_config_get($key, $default = false) {
1066 $config = $GLOBALS['wpo_cache_config'];
1067
1068 if (!$config) return false;
1069
1070 if (isset($config[$key])) {
1071 return $config[$key];
1072 } else {
1073 return $default;
1074 }
1075 }
1076 endif;
1077
1078 if (!function_exists('wpo_disable_cache_directories_viewing')) :
1079 function wpo_disable_cache_directories_viewing() {
1080 global $is_apache, $is_IIS, $is_iis7;
1081
1082 if (!is_dir(WPO_CACHE_FILES_DIR)) return;
1083
1084 // Create .htaccess file for apache server.
1085 if ($is_apache) {
1086 $htaccess_filename = WPO_CACHE_FILES_DIR . '/.htaccess';
1087
1088 // CS does not like heredoc
1089 // phpcs:disable
1090 $htaccess_content = <<<EOF
1091 # Disable directory browsing
1092 Options -Indexes
1093
1094 # Disable access to any files
1095 <FilesMatch ".*">
1096 Order allow,deny
1097 Deny from all
1098 </FilesMatch>
1099 EOF;
1100 // phpcs:enable
1101
1102 if (!is_file($htaccess_filename)) @file_put_contents($htaccess_filename, $htaccess_content); // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
1103 }
1104
1105 // Create web.config file for IIS servers.
1106 if ($is_IIS || $is_iis7) {
1107 $webconfig_filename = WPO_CACHE_FILES_DIR . '/web.config';
1108 $webconfig_content = "<configuration>\n<system.webServer>\n<authorization>\n<deny users=\"*\" />\n</authorization>\n</system.webServer>\n</configuration>\n";
1109
1110 if (!is_file($webconfig_filename)) @file_put_contents($webconfig_filename, $webconfig_content); // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
1111 }
1112
1113 // Create empty index.php file for all servers.
1114 if (!is_file(WPO_CACHE_FILES_DIR . '/index.php')) @file_put_contents(WPO_CACHE_FILES_DIR . '/index.php', '');// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
1115 }
1116 endif;
1117
1118 /**
1119 * Add the headers indicating why the page is not cached or served from cache
1120 *
1121 * @param string $message - The headers
1122 *
1123 * @return void
1124 */
1125 if (!function_exists('wpo_cache_add_nocache_http_header')) :
1126 function wpo_cache_add_nocache_http_header($message = '') {
1127 static $buffered_message = null;
1128
1129 if (function_exists('current_filter') && 'send_headers' === current_filter() && $buffered_message && !headers_sent()) {
1130 header('WPO-Cache-Status: not cached');
1131 header('WPO-Cache-Message: '. trim(str_replace(array("\r", "\n", ':'), ' ', strip_tags($buffered_message))));
1132 } else {
1133 if (!$buffered_message && function_exists('add_action')) add_action('send_headers', 'wpo_cache_add_nocache_http_header', 11);
1134 $buffered_message = $message;
1135 }
1136 }
1137 endif;
1138
1139 /**
1140 * Check if feeds caching enabled
1141 *
1142 * @return bool
1143 */
1144 if (!function_exists('wpo_feeds_caching_enabled')) :
1145 function wpo_feeds_caching_enabled() {
1146 return apply_filters('wpo_feeds_caching_enabled', true);
1147 }
1148 endif;
1149
1150 if (!function_exists('wpo_debug_backtrace_summary')) {
1151 function wpo_debug_backtrace_summary($ignore_class = null, $skip_frames = 0, $pretty = true) {
1152 static $truncate_paths;
1153
1154 $trace = debug_backtrace(false);
1155 $caller = array();
1156 $check_class = !is_null($ignore_class);
1157 $skip_frames++; // Skip this function.
1158
1159 if (!isset($truncate_paths)) {
1160 $truncate_paths = array(
1161 wpo_normalize_path(WP_CONTENT_DIR),
1162 wpo_normalize_path(ABSPATH),
1163 );
1164 }
1165
1166 foreach ($trace as $call) {
1167 if ($skip_frames > 0) {
1168 $skip_frames--;
1169 } elseif (isset($call['class'])) {
1170 if ($check_class && $ignore_class == $call['class']) {
1171 continue; // Filter out calls.
1172 }
1173
1174 $caller[] = "{$call['class']}{$call['type']}{$call['function']}";
1175 } else {
1176 if (in_array($call['function'], array('do_action', 'apply_filters', 'do_action_ref_array', 'apply_filters_ref_array'), true)) {
1177 $caller[] = "{$call['function']}('{$call['args'][0]}')";
1178 } elseif (in_array($call['function'], array('include', 'include_once', 'require', 'require_once'), true)) {
1179 $filename = isset($call['args'][0]) ? $call['args'][0] : '';
1180 $caller[] = $call['function'] . "('" . str_replace($truncate_paths, '', wpo_normalize_path($filename)) . "')";
1181 } else {
1182 $caller[] = $call['function'];
1183 }
1184 }
1185 }
1186 if ($pretty) {
1187 return implode(', ', array_reverse($caller));
1188 } else {
1189 return $caller;
1190 }
1191 }
1192 }
1193
1194 if (!function_exists('wpo_normalize_path')) {
1195 function wpo_normalize_path($path) {
1196 // Standardise all paths to use '/'.
1197 $path = str_replace('\\', '/', $path);
1198
1199 // Replace multiple slashes down to a singular, allowing for network shares having two slashes.
1200 $path = preg_replace('|(?<=.)/+|', '/', $path);
1201
1202 // Windows paths should uppercase the drive letter.
1203 if (':' === substr($path, 1, 1)) {
1204 $path = ucfirst($path);
1205 }
1206
1207 return $path;
1208 }
1209 }
1210