PluginProbe
WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance / 3.3.2
WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance v3.3.2
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.3.2, at cache/file-based-page-cache-functions.php

1,489 lines 46.6 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 caching 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 (!function_exists('is_user_logged_in') || (function_exists('wp_get_current_user') && is_user_logged_in())) {
37 if (!wpo_cache_loggedin_users()) {
38 $no_cache_because[] = __('User is logged in', 'wp-optimize');
39 } elseif (!empty($GLOBALS['wpo_cache_config']['enable_user_caching'])) {
40 // Will only run when "Serve cached pages to logged in users" is checked
41 $no_cache_because[] = __('User is logged in, this works only when the cache is preloaded', 'wp-optimize');
42 }
43 }
44
45 $restricted_page_type_cache = apply_filters('wpo_restricted_cache_page_type', false);
46 if ($restricted_page_type_cache) {
47 $no_cache_because[] = $restricted_page_type_cache;
48 }
49
50 $conditional_tag_exceptions = apply_filters('wpo_url_in_conditional_tags_exceptions', false);
51 if ($conditional_tag_exceptions) {
52 $no_cache_because[] = $conditional_tag_exceptions;
53 }
54
55 // No root cache folder, so short-circuit here
56 if (!file_exists(WPO_CACHE_DIR)) {
57 $no_cache_because[] = __('WP-O cache parent directory was not found', 'wp-optimize').' ('.WPO_CACHE_DIR.')';
58 } elseif (!file_exists(WPO_CACHE_FILES_DIR)) {
59 // Try creating a folder for cached files, if it was flushed recently
60 if (!mkdir(WPO_CACHE_FILES_DIR)) {
61 $no_cache_because[] = __('WP-O cache directory was not found', 'wp-optimize').' ('.WPO_CACHE_FILES_DIR.')';
62 } else {
63 wpo_disable_cache_directories_viewing();
64 }
65 }
66
67 // If comments are opened and the user has saved his information.
68 if (function_exists('comments_open') && function_exists('get_post') && get_post() && comments_open()) {
69 $commenter = wp_get_current_commenter();
70 // if any of the fields contain something, do not save to cache
71 if ('' != $commenter['comment_author'] || '' != $commenter['comment_author_email'] || '' != $commenter['comment_author_url']) {
72 $no_cache_because[] = __('Comments are opened and the visitor saved his information.', 'wp-optimize');
73 }
74 }
75
76 $can_cache_page = true;
77
78 if (defined('DONOTCACHEPAGE') && DONOTCACHEPAGE) {
79 $can_cache_page = false;
80 }
81
82 /**
83 * Defines if the page can be cached or not
84 *
85 * @param boolean $can_cache_page
86 */
87 $can_cache_page_filter = apply_filters('wpo_can_cache_page', $can_cache_page);
88
89 if (!$can_cache_page_filter) {
90 if ($can_cache_page) {
91 $can_cache_page = false;
92 $no_cache_because[] = __('wpo_can_cache_page filter forbade it', 'wp-optimize');
93 } else {
94 $no_cache_because[] = __('DONOTCACHEPAGE constant forbade it and wpo_can_cache_page filter did not over-ride it', 'wp-optimize');
95 }
96 }
97
98 if (defined('REST_REQUEST') && REST_REQUEST) {
99 $no_cache_because[] = __('This is a REST API request (identified by REST_REQUEST constant)', 'wp-optimize');
100 }
101
102 // Don't cache with fatal error pages.
103 $last_error = error_get_last();
104 if (is_array($last_error) && E_ERROR == $last_error['type']) {
105 $no_cache_because[] = __('This page has a fatal error', 'wp-optimize');
106 }
107
108 if (http_response_code() >= 500) {
109 $no_cache_because[] = sprintf(__('This page has a critical error (HTTP code %s)', 'wp-optimize'), http_response_code());
110 } elseif (http_response_code() >= 400) {
111 $no_cache_because[] = sprintf(__('This page returned an HTTP unauthorised response code (%s)', 'wp-optimize'), http_response_code());
112 }
113
114 if (empty($no_cache_because)) {
115
116 $buffer = apply_filters('wpo_pre_cache_buffer', $buffer, $flags);
117
118 $url_path = wpo_get_url_path();
119
120 $dirs = explode('/', $url_path);
121
122 $path = WPO_CACHE_FILES_DIR;
123
124 foreach ($dirs as $dir) {
125 if (!empty($dir)) {
126 $path .= '/' . $dir;
127
128 if (!file_exists($path)) {
129 if (!mkdir($path)) {
130 $no_cache_because[] = __('Attempt to create subfolder within cache directory failed', 'wp-optimize')." ($path)";
131 break;
132 }
133 }
134 }
135 }
136 }
137
138 if (!empty($no_cache_because)) {
139
140 $message = implode(', ', $no_cache_because);
141
142 // Add http headers
143 wpo_cache_add_nocache_http_header($message);
144
145 if ((!defined('DOING_CRON') || !DOING_CRON) && (!defined('REST_REQUEST') || !REST_REQUEST)) {
146 $not_cached_details = "";
147
148 // Output the reason only when the user has turned on debugging
149 if (((defined('WP_DEBUG') && WP_DEBUG) || isset($_GET['wpo_cache_debug']))) {
150 $not_cached_details = "because: ".htmlspecialchars($message) . " ";
151 }
152
153 $buffer .= sprintf("\n<!-- WP Optimize page cache - https://getwpo.com - page NOT cached %s-->\n", $not_cached_details);
154 }
155
156 return $buffer;
157
158 } else {
159
160 // Prevent mixed content when there's an http request but the site URL uses https.
161 $home_url = get_home_url();
162
163 if (!is_ssl() && 'https' === strtolower(parse_url($home_url, PHP_URL_SCHEME))) {
164 $https_home_url = $home_url;
165 $http_home_url = str_ireplace('https://', 'http://', $https_home_url);
166 $buffer = str_replace(esc_url($http_home_url), esc_url($https_home_url), $buffer);
167 }
168
169 $modified_time = time(); // Take this as soon before writing as possible
170 $timezone_string = '';
171 $utc = isset($GLOBALS['wpo_cache_config']['gmt_offset']) ? (float) $GLOBALS['wpo_cache_config']['gmt_offset'] : 0;
172 $modified_time += $utc * 3600;
173
174 if (!empty($GLOBALS['wpo_cache_config']['timezone_string'])) {
175 $timezone_string = 'UTC' !== $GLOBALS['wpo_cache_config']['timezone_string'] ? $GLOBALS['wpo_cache_config']['timezone_string'] : '';
176 }
177
178 if (!empty($timezone_string)) {
179 $timezone_postfix = "(".$timezone_string." UTC:". $utc .")";
180 } else {
181 $timezone_postfix = "(UTC:" . $utc . ")";
182 }
183
184 $add_to_footer = '';
185
186 /**
187 * Filter whether to display the html comment <!-- Cached by WP-Optimize ... -->
188 *
189 * @param boolean $show - Whether to display the html comment
190 * @return boolean
191 */
192 if (preg_match('#</html>#i', $buffer) && (apply_filters('wpo_cache_show_cached_by_comment', true) || (defined('WP_DEBUG') && WP_DEBUG))) {
193 $date_time_format = 'F j, Y g:i a';
194 if (!empty($GLOBALS['wpo_cache_config']['date_format']) && !empty($GLOBALS['wpo_cache_config']['time_format'])) {
195 $date_time_format = $GLOBALS['wpo_cache_config']['date_format'] . ' ' . $GLOBALS['wpo_cache_config']['time_format'];
196 }
197
198 if (!empty($GLOBALS['wpo_cache_config']['enable_mobile_caching']) && wpo_is_mobile()) {
199 $add_to_footer .= "\n<!-- Cached by WP-Optimize - for mobile devices - https://getwpo.com - Last modified: " . gmdate($date_time_format, $modified_time) . " " . $timezone_postfix . " -->\n";
200 } else {
201 $add_to_footer .= "\n<!-- Cached by WP-Optimize - https://getwpo.com - Last modified: " . gmdate($date_time_format, $modified_time) . " " . $timezone_postfix . " -->\n";
202 }
203 }
204
205 // Create an empty index.php file in the cache directory for disable directory viewing.
206 if (!is_file($path . '/index.php')) file_put_contents($path . '/index.php', '');
207
208 /**
209 * Save $buffer into cache file.
210 */
211 $file_ext = '.html';
212
213 if (wpo_feeds_caching_enabled()) {
214 if (is_feed()) {
215 $file_ext = '.rss-xml';
216 }
217 }
218
219 $cache_filename = wpo_cache_filename($file_ext);
220 $cache_file = $path . '/' .$cache_filename;
221
222 if (defined('WPO_CACHE_FILENAME_DEBUG') && WPO_CACHE_FILENAME_DEBUG) {
223 $add_to_footer .= "\n<!-- WP Optimize page cache debug information -->\n";
224 if (!empty($GLOBALS['wpo_cache_filename_debug']) && is_array($GLOBALS['wpo_cache_filename_debug'])) {
225 $add_to_footer .= "<!-- \n" . join("\n", array_map('htmlspecialchars', $GLOBALS['wpo_cache_filename_debug'])) . "\n --->";
226 }
227 }
228
229 // if we can then cache gzipped content in .gz file.
230 if (function_exists('gzencode') && apply_filters('wpo_allow_cache_gzip_files', true)) {
231 // Only replace inside the addition, not inside the main buffer (e.g. post content)
232 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)));
233 }
234
235 file_put_contents($cache_file, $buffer.$add_to_footer);
236
237 if (is_callable('WP_Optimize')) {
238 // delete cached information about cache size.
239 WP_Optimize()->get_page_cache()->delete_cache_size_information();
240 } else {
241 // If the shutdown occurs before plugins are loaded,
242 // then this will trigger a fatal error, so, we check first
243 if (!doing_action('shutdown')) {
244 error_log('[WPO_CACHE] WP_Optimize() is not callable.');
245 $message = 'Please report this to WP-O support: ';
246 if (function_exists('wp_debug_backtrace_summary')) {
247 $message .= wp_debug_backtrace_summary();
248 } else {
249 $message .= wpo_debug_backtrace_summary();
250 }
251 error_log($message);
252 }
253 }
254
255 header('Cache-Control: no-cache'); // Check back every time to see if re-download is necessary.
256 header('Last-Modified: ' . gmdate('D, d M Y H:i:s', $modified_time) . ' GMT');
257 header('WPO-Cache-Status: saving to cache');
258
259 if (wpo_cache_can_output_gzip_content()) {
260
261 if (!wpo_cache_is_in_response_headers_list('Content-Encoding', 'gzip')) {
262 header('Content-Encoding: gzip');
263 }
264
265 // Disable php gzip to avoid double compression.
266 ini_set('zlib.output_compression', 'Off');
267
268 return ob_gzhandler($buffer, $flags);
269 } else {
270 return $buffer;
271 }
272 }
273 }
274 endif;
275
276 /**
277 * Load files for support plugins.
278 */
279 if (!function_exists('wpo_cache_load_extensions')) :
280 function wpo_cache_load_extensions() {
281 $extensions = glob(WPO_CACHE_EXT_DIR . '/*.php');
282
283 // Add external extensions
284 if (defined('WPO_CACHE_CUSTOM_EXT_DIR') && is_dir(WPO_CACHE_CUSTOM_EXT_DIR)) {
285 $extensions = array_merge($extensions, glob(WPO_CACHE_CUSTOM_EXT_DIR . '/*.php'));
286 }
287
288 if (empty($extensions)) return;
289
290 foreach ($extensions as $extension) {
291 if (is_file($extension)) require_once $extension;
292 }
293 }
294 endif;
295
296 if (!function_exists('wpo_restricted_cache_page_type')) {
297 function wpo_restricted_cache_page_type($restricted) {
298 global $post;
299
300 // Don't cache search or password protected.
301 if ((function_exists('is_search') && is_search()) || (function_exists('is_404') && is_404()) || !empty($post->post_password)) {
302 $restricted = __('Page type is not cacheable (search, 404 or password-protected)', 'wp-optimize');
303 }
304
305 // Don't cache the front page if option is set.
306 if (in_array('/', wpo_get_url_exceptions()) && function_exists('is_front_page') && is_front_page()) {
307
308 $restricted = __('In the settings, caching is disabled for the front page', 'wp-optimize');
309 }
310
311 // Don't cache htacesss. Remember to properly escape any output to prevent injection.
312 if (strpos($_SERVER['REQUEST_URI'], '.htaccess') !== false) {
313 $restricted = 'The file path is unsuitable for caching ('.$_SERVER['REQUEST_URI'].')';
314 }
315
316 // Don't cache feeds.
317 if (function_exists('is_feed') && is_feed() && !wpo_feeds_caching_enabled()) {
318 $restricted = __('We don\'t cache RSS feeds', 'wp-optimize');
319 }
320
321 return $restricted;
322 }
323 }
324
325 /**
326 * Returns true if we need cache content for loggedin users.
327 *
328 * @return bool
329 */
330 if (!function_exists('wpo_cache_loggedin_users')) :
331 function wpo_cache_loggedin_users() {
332 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());
333 }
334 endif;
335
336 /**
337 * Get filename for store cache, depending on gzip, mobile and cookie settings.
338 *
339 * @param string $ext
340 * @return string
341 */
342 if (!function_exists('wpo_cache_filename')) :
343 function wpo_cache_filename($ext = '.html') {
344
345 $wpo_cache_filename_debug = array();
346
347 $filename = 'index';
348
349 if (wpo_cache_mobile_caching_enabled() && wpo_is_mobile()) {
350 $filename = 'mobile.' . $filename;
351 }
352
353 if (wpo_webp_images_enabled() && !wpo_is_using_webp_images_redirection() && wpo_is_using_alter_html()) {
354 $filename = $filename . '.webp';
355 }
356
357 $cookies = wpo_cache_cookies();
358
359 $cache_key = '';
360
361 /**
362 * Add cookie values to filename if need.
363 * This section was inspired by things learned from WP-Rocket.
364 */
365 if (!empty($cookies)) {
366 foreach ($cookies as $key => $cookie_name) {
367 if (is_array($cookie_name) && isset($_COOKIE[$key])) {
368 foreach ($cookie_name as $cookie_key) {
369 if (isset($_COOKIE[$key][$cookie_key]) && '' !== $_COOKIE[$key][$cookie_key]) {
370 $_cache_key = $cookie_key.'='.$_COOKIE[$key][$cookie_key];
371 $_cache_key = preg_replace('/[^a-z0-9_\-\=]/i', '-', $_cache_key);
372 $cache_key .= '-' . $_cache_key;
373 $wpo_cache_filename_debug[] = 'Cookie: name: ' . $key . '[' . $cookie_key . '], value: *** , cache_key:' . $_cache_key;
374 }
375 }
376 continue;
377 }
378
379 if (isset($_COOKIE[$cookie_name]) && '' !== $_COOKIE[$cookie_name]) {
380 $_cache_key = $cookie_name.'='.$_COOKIE[$cookie_name];
381 $_cache_key = preg_replace('/[^a-z0-9_\-\=]/i', '-', $_cache_key);
382 $cache_key .= '-' . $_cache_key;
383 $wpo_cache_filename_debug[] = 'Cookie: name: ' . $cookie_name . ', value: *** , cache_key:' . $_cache_key;
384 }
385 }
386 }
387
388 $query_variables = wpo_cache_query_variables();
389
390 /**
391 * Add GET variables to cache file name if need.
392 */
393 if (!empty($query_variables)) {
394 foreach ($query_variables as $variable) {
395 if (isset($_GET[$variable]) && !empty($_GET[$variable])) {
396 $_cache_key = $variable.'='.$_GET[$variable];
397 $_cache_key = preg_replace('/[^a-z0-9_\-\=]/i', '-', $_cache_key);
398 $cache_key .= '-' . $_cache_key;
399 $wpo_cache_filename_debug[] = 'GET parameter: name: ' . $variable . ', value:' . htmlentities($_GET[$variable]) . ', cache_key:' . $_cache_key;
400 }
401 }
402 }
403
404 // add hash of queried cookies and variables to cache file name.
405 if ('' !== $cache_key) {
406 $hash = md5($cache_key);
407 $filename .= '-'.$hash;
408 $wpo_cache_filename_debug[] = 'Hash: ' . $hash;
409 }
410
411 $filename = apply_filters('wpo_cache_filename', $filename);
412
413 $wpo_cache_filename_debug[] = 'Extension: ' . $ext;
414 $wpo_cache_filename_debug[] = 'Filename: ' . $filename.$ext;
415
416 $GLOBALS['wpo_cache_filename_debug'] = $wpo_cache_filename_debug;
417
418 return $filename . $ext;
419 }
420 endif;
421
422 /**
423 * Returns site url from site_url() function or if it is not available from cache configuration.
424 */
425 if (!function_exists('wpo_site_url')) :
426 function wpo_site_url() {
427 if (is_callable('site_url')) return site_url('/');
428
429 $site_url = empty($GLOBALS['wpo_cache_config']['site_url']) ? '' : $GLOBALS['wpo_cache_config']['site_url'];
430 return $site_url;
431 }
432 endif;
433
434 /**
435 * Get cookie names which impact on cache file name.
436 *
437 * @return array
438 */
439 if (!function_exists('wpo_cache_cookies')) :
440 function wpo_cache_cookies() {
441 $cookies = empty($GLOBALS['wpo_cache_config']['wpo_cache_cookies']) ? array() : $GLOBALS['wpo_cache_config']['wpo_cache_cookies'];
442 return $cookies;
443 }
444 endif;
445
446 /**
447 * Get GET variable names which impact on cache file name.
448 *
449 * @return array
450 */
451 if (!function_exists('wpo_cache_query_variables')) :
452 function wpo_cache_query_variables() {
453 if (defined('WPO_CACHE_URL_PARAMS') && WPO_CACHE_URL_PARAMS) {
454 $variables = array_keys($_GET);
455 } else {
456 $variables = empty($GLOBALS['wpo_cache_config']['wpo_cache_query_variables']) ? array() : $GLOBALS['wpo_cache_config']['wpo_cache_query_variables'];
457 }
458
459 if (!empty($variables)) {
460 sort($variables);
461 }
462
463 return wpo_cache_maybe_ignore_query_variables($variables);
464 }
465 endif;
466
467 /**
468 * Get list of all received HTTP headers.
469 *
470 * @return array
471 */
472 if (!function_exists('wpo_get_http_headers')) :
473 function wpo_get_http_headers() {
474
475 static $headers;
476
477 if (!empty($headers)) return $headers;
478
479 $headers = array();
480
481 // if is apache server then use get allheaders() function.
482 if (function_exists('getallheaders')) {
483 $headers = getallheaders();
484 } else {
485 // https://www.php.net/manual/en/function.getallheaders.php
486 foreach ($_SERVER as $key => $value) {
487
488 $key = strtolower($key);
489
490 if ('HTTP_' == substr($key, 0, 5)) {
491 $headers[str_replace(' ', '-', ucwords(str_replace('_', ' ', substr($key, 5))))] = $value;
492 } elseif ('content_type' == $key) {
493 $headers["Content-Type"] = $value;
494 } elseif ('content_length' == $key) {
495 $headers["Content-Length"] = $value;
496 }
497 }
498 }
499
500 return $headers;
501 }
502 endif;
503
504 /**
505 * Check if requested Accept-Encoding headers has gzip value.
506 *
507 * @return bool
508 */
509 if (!function_exists('wpo_cache_gzip_accepted')) :
510 function wpo_cache_gzip_accepted() {
511 $headers = wpo_get_http_headers();
512
513 if (isset($headers['Accept-Encoding']) && preg_match('/gzip/i', $headers['Accept-Encoding'])) return true;
514
515 return false;
516 }
517 endif;
518
519 /**
520 * Check if we can output gzip content in current answer, i.e. check Accept-Encoding headers has gzip value
521 * and function ob_gzhandler is available.
522 *
523 * @return bool
524 */
525 if (!function_exists('wpo_cache_can_output_gzip_content')) :
526 function wpo_cache_can_output_gzip_content() {
527 return wpo_cache_gzip_accepted() && function_exists('ob_gzhandler');
528 }
529 endif;
530
531 /**
532 * Check if header with certain name exists in already prepared headers and has value comparable with $header_value.
533 *
534 * @param string $header_name header name
535 * @param string $header_value header value as regexp.
536 *
537 * @return bool
538 */
539 if (!function_exists('wpo_cache_is_in_response_headers_list')) :
540 function wpo_cache_is_in_response_headers_list($header_name, $header_value) {
541 $headers_list = headers_list();
542
543 if (!empty($headers_list)) {
544 $header_name = strtolower($header_name);
545
546 foreach ($headers_list as $value) {
547 $value = explode(':', $value);
548
549 if (strtolower($value[0]) == $header_name) {
550 if (preg_match('/'.$header_value.'/', $value[1])) {
551 return true;
552 } else {
553 return false;
554 }
555 }
556 }
557 }
558
559 return false;
560 }
561 endif;
562
563 /**
564 * Check if mobile cache is enabled and current request is from moblile device.
565 *
566 * @return bool
567 */
568 if (!function_exists('wpo_cache_mobile_caching_enabled')) :
569 function wpo_cache_mobile_caching_enabled() {
570 if (!empty($GLOBALS['wpo_cache_config']['enable_mobile_caching'])) return true;
571 return false;
572 }
573 endif;
574
575 /**
576 * Check if webp images enabled
577 *
578 * @return bool
579 */
580 if (!function_exists('wpo_webp_images_enabled')) :
581 function wpo_webp_images_enabled() {
582 if (!empty($GLOBALS['wpo_cache_config']['use_webp_images'])) return true;
583 return false;
584 }
585 endif;
586
587 /**
588 * Check whether webp images using alter html method or not
589 *
590 * @return bool
591 */
592 if (!function_exists('wpo_is_using_alter_html')) :
593 function wpo_is_using_alter_html() {
594 return (isset($_SERVER['HTTP_ACCEPT']) && false !== strpos($_SERVER['HTTP_ACCEPT'], 'image/webp'));
595 }
596 endif;
597
598 /**
599 * Check whether webp images are served using redirect
600 *
601 * @return bool
602 */
603 if (!function_exists('wpo_is_using_webp_images_redirection')) :
604 function wpo_is_using_webp_images_redirection() {
605 if (empty($GLOBALS['wpo_cache_config']['uploads'])) return false;
606
607 $uploads_dir = $GLOBALS['wpo_cache_config']['uploads'];
608 $htaccess_file = $uploads_dir . '/.htaccess';
609 if (!file_exists($htaccess_file)) return false;
610 $htaccess_content = file_get_contents($htaccess_file);
611 $comment_sections = array('Register webp mime type', 'WP-Optimize WebP Rules');
612
613 if (function_exists('str_contains')) {
614 return str_contains($htaccess_content, $comment_sections[0]) && str_contains($htaccess_content, $comment_sections[1]);
615 } else {
616 return strpos($htaccess_content, $comment_sections[0]) && strpos($htaccess_content, $comment_sections[1]);
617 }
618 }
619 endif;
620
621 /**
622 * Verify if the current request is related to the Activity Stream
623 *
624 * @return bool
625 */
626 if (!function_exists('wpo_is_activity_stream_requested')) :
627 function wpo_is_activity_stream_requested() {
628 return (isset($_SERVER['HTTP_ACCEPT']) && false !== strpos($_SERVER['HTTP_ACCEPT'], 'application/activity+json'));
629 }
630 endif;
631
632 /**
633 * Verify if the current request is robots.txt
634 */
635 if (!function_exists('wpo_is_robots_txt_requested')) :
636 function wpo_is_robots_txt_requested() {
637 return (isset($_SERVER['REQUEST_URI']) && 'robots.txt' === basename($_SERVER['REQUEST_URI']));
638 }
639 endif;
640
641 /**
642 * Serves the cache and exits
643 */
644 if (!function_exists('wpo_serve_cache')) :
645 function wpo_serve_cache() {
646 $file_name = wpo_cache_filename();
647
648 $file_name_rss_xml = wpo_cache_filename('.rss-xml');
649 $send_as_feed = false;
650
651 $path_dir = WPO_CACHE_FILES_DIR . '/' . wpo_get_url_path() . '/';
652 $path = $path_dir . $file_name;
653
654 if (wpo_feeds_caching_enabled()) {
655 // check for .xml cache file if .html cache file doesn't exist
656 if (!file_exists($path_dir . $file_name) && file_exists($path_dir . $file_name_rss_xml)) {
657 $path = $path_dir . $file_name_rss_xml;
658 $send_as_feed = true;
659 }
660 }
661
662 $use_gzip = false;
663
664 // if we can use gzip and gzipped file exist in cache we use it.
665 // if headers already sent we don't use gzipped file content.
666 if (!headers_sent() && wpo_cache_gzip_accepted() && file_exists($path . '.gz')) {
667 $path .= '.gz';
668 $use_gzip = true;
669 }
670
671 $modified_time = file_exists($path) ? (int) filemtime($path) : time();
672
673 // Cache has expired, purge and exit.
674 if (!empty($GLOBALS['wpo_cache_config']['page_cache_length'])) {
675 if (time() > ($GLOBALS['wpo_cache_config']['page_cache_length'] + $modified_time)) {
676 wpo_delete_files($path);
677 return;
678 }
679 }
680
681 if ($use_gzip) {
682 // Disable zlib output compression to avoid double content compression
683 ini_set('zlib.output_compression', 'Off');
684 }
685
686 $gzip_header_already_sent = wpo_cache_is_in_response_headers_list('Content-Encoding', 'gzip');
687
688 header('Cache-Control: no-cache'); // Check back later
689
690 if (!empty($modified_time) && !empty($_SERVER['HTTP_IF_MODIFIED_SINCE']) && strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) === $modified_time) {
691 if ($use_gzip && !$gzip_header_already_sent) {
692 header('Content-Encoding: gzip');
693 }
694
695 if ($send_as_feed) {
696 header('Content-type: application/rss+xml');
697 }
698
699 header('WPO-Cache-Status: cached');
700 header('Last-Modified: ' . gmdate('D, d M Y H:i:s', $modified_time) . ' GMT');
701 header($_SERVER['SERVER_PROTOCOL'] . ' 304 Not Modified', true, 304);
702 exit;
703 }
704
705 if (file_exists($path) && is_readable($path)) {
706
707 if (wpo_is_canonical_redirection_needed()) return;
708
709 if ($use_gzip && !$gzip_header_already_sent) {
710 header('Content-Encoding: gzip');
711 }
712
713 // send correct headers for xml and txt files
714 $filename = basename(dirname($path));
715
716 if (preg_match('/\.xml$/i', $filename)) {
717 header('Content-type: text/xml');
718 }
719
720 if (preg_match('/\.txt$/i', $filename)) {
721 header('Content-type: text/plain');
722 }
723
724 if ($send_as_feed) {
725 header('Content-type: application/rss+xml');
726 }
727
728 header('WPO-Cache-Status: cached');
729 if (!empty($modified_time)) {
730 header('Last-Modified: ' . gmdate('D, d M Y H:i:s', $modified_time) . ' GMT');
731 }
732
733 readfile($path);
734
735 exit;
736 }
737 }
738 endif;
739
740 /**
741 * Checks and does redirection, if needed
742 *
743 * @return bool
744 */
745 if (!function_exists('wpo_is_canonical_redirection_needed')) :
746 function wpo_is_canonical_redirection_needed() {
747 $permalink_structure = isset($GLOBALS['wpo_cache_config']['permalink_structure']) ? $GLOBALS['wpo_cache_config']['permalink_structure'] : '';
748 $site_url = wpo_site_url();
749
750 // Exit if server variables are not available.
751 if (!isset($_SERVER['HTTP_HOST'])) return false;
752
753 $schema = isset($_SERVER['HTTPS']) && 'on' === $_SERVER['HTTPS'] ? "https" : "http";
754 $url_part = "://" . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
755 $requested_url = $schema . $url_part;
756 $url_parts = parse_url($requested_url);
757 $extension = pathinfo($url_parts['path'], PATHINFO_EXTENSION);
758
759 if (!empty($permalink_structure) && $requested_url != $site_url && ((isset($url_parts['path']) && '/' !== $url_parts['path']) || isset($url_parts['query']))) {
760 $request_uri = rtrim($_SERVER['REQUEST_URI'], '?');
761 if ('/' == substr($permalink_structure, -1) && empty($extension) && empty($url_parts['query']) && empty($url_parts['fragment'])) {
762 $url = preg_replace('/(.+?)([\/]*)(\[\?\#][^\/]+|$)/', '$1/$3', $request_uri);
763 if (0 !== strcmp($request_uri, $url)) return true;
764 } else {
765 $url = rtrim($request_uri, '/');
766 if (0 !== strcmp($request_uri, $url)) return true;
767 }
768 }
769 return false;
770 }
771 endif;
772
773 /**
774 * Clears the cache
775 */
776 if (!function_exists('wpo_cache_flush')) :
777 function wpo_cache_flush() {
778
779 if (defined('WPO_CACHE_FILES_DIR') && '' != WPO_CACHE_FILES_DIR) wpo_delete_files(WPO_CACHE_FILES_DIR);
780
781 if (function_exists('wp_cache_flush')) {
782 wp_cache_flush();
783 }
784
785 do_action('wpo_cache_flush');
786 }
787 endif;
788
789 /**
790 * Get URL path for caching
791 *
792 * @since 1.0
793 * @return string
794 */
795 if (!function_exists('wpo_get_url_path')) :
796 function wpo_get_url_path($url = '') {
797 $url = '' == $url ? wpo_current_url() : $url;
798 $url_parts = parse_url($url);
799
800 if (isset($url_parts['path']) && false !== stripos($url_parts['path'], '/index.php')) {
801 $url_parts['path'] = preg_replace('/(.*?)index\.php(\/.+)/i', '$1index-php$2', $url_parts['path']);
802 }
803
804 /*
805 * Convert the hexadecimal digits within the percent-encoded triplet to uppercase, to ensure that the path remains
806 * consistent. For instance, "example.com/%e0%a6" will be converted to "example.com/%E0%A6".
807 */
808 if (isset($url_parts['path'])) {
809 $url_parts['path'] = preg_replace_callback('/%[0-9A-F]{2}/i', function($matches) {
810 return strtoupper($matches[0]);
811 }, $url_parts['path']);
812 }
813
814 if (!isset($url_parts['host'])) $url_parts['host'] = '';
815 if (!isset($url_parts['path'])) $url_parts['path'] = '';
816
817 return $url_parts['host'].$url_parts['path'];
818 }
819 endif;
820
821 /**
822 * Get requested url.
823 *
824 * @return string
825 */
826 if (!function_exists('wpo_current_url')) :
827 function wpo_current_url() {
828 // 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).
829 // Otherwise this function would return a different URL at the beginning and end of the cache process.
830 static $url = '';
831 if ('' != $url) return $url;
832 $http_host = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : '';
833 $url = rtrim('http' . ((isset($_SERVER['HTTPS']) && ('on' == $_SERVER['HTTPS'] || 1 == $_SERVER['HTTPS']) ||
834 isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && 'https' == $_SERVER['HTTP_X_FORWARDED_PROTO']) ? 's' : '' )
835 . '://' . $http_host.$_SERVER['REQUEST_URI'], '/');
836 return $url;
837 }
838 endif;
839
840 /**
841 * Return list of conditional tag exceptions.
842 *
843 * @return array
844 */
845 if (!function_exists('wpo_get_conditional_tags_exceptions')) :
846 function wpo_get_conditional_tags_exceptions() {
847 static $exceptions = null;
848
849 if (null !== $exceptions) return $exceptions;
850
851 if (!empty($GLOBALS['wpo_cache_config'])) {
852 if (empty($GLOBALS['wpo_cache_config']['cache_exception_conditional_tags'])) {
853 $exceptions = array();
854
855 } else {
856
857 $exceptions = $GLOBALS['wpo_cache_config']['cache_exception_conditional_tags'];
858
859 }
860
861 } elseif (class_exists('WPO_Page_Cache')) {
862
863 $config = WPO_Page_Cache::instance()->config->get();
864
865 if (is_array($config) && array_key_exists('cache_exception_conditional_tags', $config)) {
866 $exceptions = $config['cache_exception_conditional_tags'];
867 } else {
868 $exceptions = array();
869 }
870
871 $exceptions = is_array($exceptions) ? $exceptions : preg_split('#(\n|\r|\r\n)#', $exceptions);
872 $exceptions = array_filter($exceptions, 'trim');
873
874 } else {
875 $exceptions = array();
876 }
877
878 return $exceptions;
879 }
880 endif;
881
882 /**
883 * Return list of url exceptions.
884 *
885 * @return array
886 */
887 if (!function_exists('wpo_get_url_exceptions')) :
888 function wpo_get_url_exceptions() {
889 static $exceptions = null;
890
891 if (null !== $exceptions) return $exceptions;
892
893 // if called from file-based-page-cache.php when WP loading
894 // and cache settings exists then use it otherwise get settings from database.
895 if (!empty($GLOBALS['wpo_cache_config'])) {
896 if (empty($GLOBALS['wpo_cache_config']['cache_exception_urls'])) {
897 $exceptions = array();
898 } else {
899 $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']);
900 }
901 } elseif (class_exists('WPO_Page_Cache')) {
902 $config = WPO_Page_Cache::instance()->config->get();
903
904 if (is_array($config) && array_key_exists('cache_exception_urls', $config)) {
905 $exceptions = $config['cache_exception_urls'];
906 } else {
907 $exceptions = array();
908 }
909
910 $exceptions = is_array($exceptions) ? $exceptions : preg_split('#(\n|\r)#', $exceptions);
911 $exceptions = array_filter($exceptions, 'trim');
912 } else {
913 $exceptions = array();
914 }
915
916 return apply_filters('wpo_get_url_exceptions', $exceptions);
917 }
918 endif;
919
920 /**
921 * Return true of exception url matches current url
922 *
923 * @param string $exception Exceptions to check URL against.
924 * @param bool $regex Whether to check with regex or not.
925 * @return bool true if matched, false otherwise
926 */
927 if (!function_exists('wpo_current_url_exception_match')) :
928 function wpo_current_url_exception_match($exception) {
929
930 return wpo_url_exception_match(wpo_current_url(), $exception);
931 }
932 endif;
933
934 /**
935 * Check if url in conditional tags exceptions list.
936 *
937 * @return string
938 */
939 if (!function_exists('wpo_url_in_conditional_tags_exceptions')) :
940 function wpo_url_in_conditional_tags_exceptions() {
941
942 $exceptions = wpo_get_conditional_tags_exceptions();
943 $restricted = '';
944 $allowed_functions = array('is_single', 'is_page', 'is_front_page', 'is_home', 'is_archive', 'is_tag', 'is_category', 'is_feed', 'is_search', 'is_author', 'is_woocommerce', 'is_shop', 'is_product', 'is_account_page', 'is_product_category', 'is_product_tag', 'is_wc_endpoint_url', 'is_bbpress', 'bbp_is_forum_archive', 'bbp_is_topic_archive', 'bbp_is_topic_tag', 'bbp_is_single_forum', 'bbp_is_single_topic', 'bbp_is_single_view', 'bbp_is_single_user', 'bbp_is_user_home', 'bbp_is_search');
945 //Filter for add more conditional tags to whitelist in the exceptions list.
946 $allowed_functions = apply_filters('wpo_allowed_conditional_tags_exceptions', $allowed_functions);
947 if (!empty($exceptions)) {
948 foreach ($exceptions as $exception) {
949 if (false !== strpos($exception, 'is_')) {
950 $exception_function = $exception;
951 if ('()' == substr($exception, -2)) {
952 $exception_function = substr($exception, 0, -2);
953 }
954
955 if (in_array($exception_function, $allowed_functions) && function_exists($exception_function) && call_user_func($exception_function)) {
956 $restricted = sprintf(__('In the settings, caching is disabled for %s', 'wp-optimize'), $exception_function);
957 }
958 }
959 }
960 }
961 return $restricted;
962 }
963 endif;
964
965
966 /**
967 * Check if url in exceptions list.
968 *
969 * @param string $url
970 *
971 * @return bool
972 */
973 if (!function_exists('wpo_url_in_exceptions')) :
974 function wpo_url_in_exceptions($url) {
975 $exceptions = wpo_get_url_exceptions();
976
977 if (!empty($exceptions)) {
978 foreach ($exceptions as $exception) {
979
980 // don't check / - front page using regexp, we handle it in wpo_restricted_cache_page_type()
981 if ('/' == $exception) continue;
982
983 if (wpo_url_exception_match($url, $exception)) {
984 // Exception match.
985 return true;
986 }
987 }
988 }
989
990 return false;
991 }
992 endif;
993
994 /**
995 * Checks if an URL matches against listed exceptions.
996 *
997 * @param string $url - complete url string i.e. http(s)://domain/path
998 * @param string $exception - complete url or absolute path, can contain (.*) wildcards; Sometimes can be urlencoded
999 *
1000 * @return bool
1001 */
1002 if (!function_exists('wpo_url_exception_match')) :
1003 function wpo_url_exception_match($url, $exception) {
1004 if (preg_match('#^[\s]*$#', $exception)) {
1005 return false;
1006 }
1007
1008 $exception = str_replace('*', '.*', $exception);
1009
1010 $exception = trim($exception);
1011
1012 // Used to test websites placed in subdirectories.
1013 $sub_dir = '';
1014
1015 // If exception defined from root i.e. /page1 then remove domain part in url.
1016 if (preg_match('/^\//', $exception)) {
1017 // get site sub directory.
1018 $sub_dir = preg_replace('#^(http|https):\/\/.*\/#Ui', '', wpo_site_url());
1019 // add prefix slash and remove slash.
1020 $sub_dir = ('' == $sub_dir) ? '' : '/' . rtrim($sub_dir, '/');
1021 // get relative path
1022 $url = preg_replace('#^(http|https):\/\/.*\/#Ui', '/', $url);
1023 }
1024
1025 $url = urldecode(rtrim($url, '/')) . '/';
1026 $exception = rtrim($exception, '/');
1027
1028 // if we have no wildcard in the end of exception then add slash.
1029 if (!preg_match('#\(\.\*\)$#', $exception)) $exception .= '/';
1030
1031 $exception = preg_quote($exception);
1032
1033 // fix - unescape some possibly escaped mask characters
1034 $search = array(
1035 '\\.\\*',
1036 '\\-',
1037 );
1038 $replace = array(
1039 '.*',
1040 '-',
1041 );
1042 $exception = urldecode(str_replace($search, $replace, $exception));
1043
1044 return (preg_match('#^'.$exception.'$#i', $url) || preg_match('#^'.$sub_dir.$exception.'$#i', $url));
1045 }
1046 endif;
1047
1048 /**
1049 * Checks if its a mobile device
1050 *
1051 * @see https://developer.wordpress.org/reference/functions/wp_is_mobile/
1052 */
1053 if (!function_exists('wpo_is_mobile')) :
1054 function wpo_is_mobile() {
1055 if (empty($_SERVER['HTTP_USER_AGENT'])) {
1056 $is_mobile = false;
1057 // many mobile devices (all iPhone, iPad, etc.)
1058 } elseif (strpos($_SERVER['HTTP_USER_AGENT'], 'Mobile') !== false
1059 || strpos($_SERVER['HTTP_USER_AGENT'], 'Android') !== false
1060 || strpos($_SERVER['HTTP_USER_AGENT'], 'Silk/') !== false
1061 || strpos($_SERVER['HTTP_USER_AGENT'], 'Kindle') !== false
1062 || strpos($_SERVER['HTTP_USER_AGENT'], 'BlackBerry') !== false
1063 || strpos($_SERVER['HTTP_USER_AGENT'], 'Opera Mini') !== false
1064 || strpos($_SERVER['HTTP_USER_AGENT'], 'Opera Mobi') !== false
1065 ) {
1066 $is_mobile = true;
1067 } else {
1068 $is_mobile = false;
1069 }
1070
1071 return $is_mobile;
1072 }
1073 endif;
1074
1075 /**
1076 * Check if current browser agent is not disabled in options.
1077 *
1078 * @return bool
1079 */
1080 if (!function_exists('wpo_is_accepted_user_agent')) :
1081 function wpo_is_accepted_user_agent($user_agent) {
1082
1083 $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']);
1084
1085 if (!empty($exceptions)) {
1086 foreach ($exceptions as $exception) {
1087 if ('' == trim($exception)) continue;
1088
1089 if (preg_match('#'.$exception.'#i', $user_agent)) return false;
1090 }
1091 }
1092
1093 return true;
1094 }
1095 endif;
1096
1097 if (!function_exists('wpo_delete_files')) :
1098 /**
1099 * Deletes a specified source file or directory.
1100 *
1101 * If $src is a file, only that file will be deleted. If $src is a directory, the behavior depends on the
1102 * $recursive parameter. When $recursive is true, the directory and its contents (including files and subdirectories)
1103 * will be deleted. When $recursive is false, only the files in the top-level directory(eg. $src directory) will be deleted,
1104 * while the $src directory itself and its subdirectories will remain untouched.
1105 *
1106 * @param string $src The path to the source file or directory to delete.
1107 * @param bool $recursive (Optional) When set to true, the directory and its contents (including files and subdirectories)
1108 * will be deleted. If false, only the files in the top-level directory will be deleted while
1109 * its subdirectories will be preserved. Defaults to true.
1110 *
1111 * @return bool Returns true if the specified file or all files within the specified directory (and its subdirectories,
1112 * when $recursive is true) are successfully deleted. Returns false if any file(s) could not be deleted
1113 * due to file permissions or other reasons.
1114 */
1115 function wpo_delete_files($src, $recursive = true) {
1116 // If the source doesn't exist, consider it deleted and return true
1117 if (!file_exists($src)) {
1118 return true;
1119 }
1120
1121 /*
1122 * If the source is a file, delete it and return the result.
1123 * If `unlink()` fails, we also verify if the file still exists before returning the result, as another
1124 * PHP process may have already deleted the file between the execution of `is_file()` and `unlink()` operations.
1125 */
1126 if (is_file($src)) {
1127 if (!@unlink($src) && file_exists($src)) { // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- suppress PHP warning in case of failure
1128 return false;
1129 }
1130 return true;
1131 }
1132
1133 $success = true;
1134
1135 // If recursive is false, delete only the top-level files and return the result
1136 if (!$recursive) {
1137 $dir_handle = @opendir($src); // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- suppress PHP warning in case of failure
1138
1139 /*
1140 * If opendir() is successful, process directory contents. If not, check if the directory exists.
1141 * If it exists, return false (failure). Otherwise, assume it's already deleted and return true (success).
1142 */
1143 if (false !== $dir_handle) {
1144
1145 while (false !== ($file = readdir($dir_handle))) {
1146 if ('.' == $file || '..' == $file) {
1147 continue;
1148 }
1149
1150 $full_path = $src . '/' . $file;
1151
1152 // If it's a file, delete it
1153 if (is_file($full_path)) {
1154 if (!wpo_delete_files($full_path)) {
1155 $success = false;
1156 }
1157 }
1158 }
1159
1160 closedir($dir_handle);
1161 } else {
1162 if (file_exists($src)) {
1163 $success = false;
1164 }
1165 }
1166
1167 return $success;
1168 }
1169
1170 // If recursive is true, delete all files and directories recursively
1171 $dir_handle = @opendir($src); // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- suppress PHP warning in case of failure
1172
1173 /*
1174 * If opendir() is successful, process directory contents. If not, check if the directory exists.
1175 * If it exists, return false (failure). Otherwise, assume it's already deleted and return true (success).
1176 */
1177 if (false !== $dir_handle) {
1178
1179 while (false !== ($file = readdir($dir_handle))) {
1180 if ('.' == $file || '..' == $file) {
1181 continue;
1182 }
1183
1184 $full_path = $src . '/' . $file;
1185
1186 if (!wpo_delete_files($full_path)) {
1187 $success = false;
1188 }
1189 }
1190
1191 closedir($dir_handle);
1192 } else {
1193 if (file_exists($src)) {
1194 $success = false;
1195 }
1196 }
1197
1198 /*
1199 * Delete the source directory itself.
1200 * Success of `rmdir` operation is not recorded; we only ultimately care about emptying, not removing
1201 * entirely (empty folders in our context are harmless)
1202 */
1203 if ($success) {
1204 @rmdir($src); // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- suppress PHP warning in case of failure
1205 }
1206
1207 // Delete cached information about cache size
1208 WP_Optimize()->get_page_cache()->delete_cache_size_information();
1209
1210 return $success;
1211 }
1212 endif;
1213
1214 if (!function_exists('wpo_is_empty_dir')) :
1215 /**
1216 * Check if selected directory is empty or has only index.php which we added for security reasons.
1217 *
1218 * @param string $dir
1219 *
1220 * @return bool
1221 */
1222 function wpo_is_empty_dir($dir) {
1223 if (!file_exists($dir) || !is_dir($dir)) return false;
1224
1225 $handle = opendir($dir);
1226
1227 if (false === $handle) return false;
1228
1229 $is_empty = true;
1230 $file = readdir($handle);
1231
1232 while (false !== $file) {
1233
1234 if ('.' != $file && '..' != $file && 'index.php' != $file) {
1235 $is_empty = false;
1236 break;
1237 }
1238
1239 $file = readdir($handle);
1240 }
1241
1242 closedir($handle);
1243 return $is_empty;
1244 }
1245 endif;
1246
1247 /**
1248 * Either store for later output, or output now. Only the most-recent call will be effective.
1249 *
1250 * @param String|Null $output - if not null, then the string to use when called by the shutdown action.
1251 */
1252 if (!function_exists('wpo_cache_add_footer_output')) :
1253 function wpo_cache_add_footer_output($output = null) {
1254
1255 static $buffered = null;
1256
1257 if (function_exists('current_filter') && 'shutdown' == current_filter()) {
1258 // Only add the line if it was a page, not something else (e.g. REST response)
1259 if (function_exists('did_action') && did_action('wp_footer')) {
1260 echo "\n<!-- WP Optimize page cache - https://getwpo.com - ".$buffered." -->\n";
1261 } elseif (defined('WPO_CACHE_DEBUG') && WPO_CACHE_DEBUG && (!defined('REST_REQUEST') || !REST_REQUEST)) {
1262 error_log('[CACHE DEBUG] '.wpo_current_url() . ' - ' . $buffered);
1263 }
1264 } else {
1265 if (null == $buffered && function_exists('add_action')) add_action('shutdown', 'wpo_cache_add_footer_output', 11);
1266 $buffered = $output;
1267 }
1268
1269 }
1270 endif;
1271
1272 /**
1273 * Remove variable names that shouldn't influence cache.
1274 *
1275 * @param array $variables List of variable names.
1276 *
1277 * @return array
1278 */
1279 if (!function_exists('wpo_cache_maybe_ignore_query_variables')) :
1280 function wpo_cache_maybe_ignore_query_variables($variables) {
1281
1282 /**
1283 * Filters the current $_GET variables that will be used when caching or excluding from cache.
1284 * Currently:
1285 * - 'wpo_cache_debug' (Shows the reason for not being cached even when WP_DEBUG isn't set)
1286 * - 'doing_wp_cron' (alternative cron)
1287 * - 'aiosp_sitemap_path', 'aiosp_sitemap_page' (All in one SEO sitemap)
1288 * - 'xml_sitemap', 'seopress_sitemap', 'seopress_news', 'seopress_video', 'seopress_cpt', 'seopress_paged' (SEOPress sitemap)
1289 * - 'sitemap', 'sitemap_n' (YOAST SEO sitemap)
1290 */
1291 $exclude_variables = array(
1292 'wpo_cache_debug', // Shows the reason for not being cached even when WP_DEBUG isn't set
1293 'doing_wp_cron', // alternative cron
1294 'aiosp_sitemap_path', // All in one SEO sitemap
1295 'aiosp_sitemap_page',
1296 'xml_sitemap', // SEOPress sitemap
1297 'seopress_sitemap',
1298 'seopress_news',
1299 'seopress_video',
1300 'seopress_cpt',
1301 'seopress_paged',
1302 'sitemap', // YOAST SEO sitemap
1303 'sitemap_n',
1304 );
1305 $exclude_variables = function_exists('apply_filters') ? apply_filters('wpo_cache_ignore_query_variables', $exclude_variables) : $exclude_variables;
1306
1307 if (empty($exclude_variables)) return $variables;
1308
1309 foreach ($exclude_variables as $variable) {
1310 $exclude = array_search($variable, $variables);
1311 if (false !== $exclude) {
1312 array_splice($variables, $exclude, 1);
1313 }
1314 }
1315
1316 return $variables;
1317 }
1318 endif;
1319
1320 /**
1321 * Get cache config
1322 *
1323 * @param string $key - The config item
1324 * @param mixed $default - The default value
1325 *
1326 * @return mixed
1327 */
1328 if (!function_exists('wpo_cache_config_get')) :
1329 function wpo_cache_config_get($key, $default = false) {
1330 $config = $GLOBALS['wpo_cache_config'];
1331
1332 if (!$config) return false;
1333
1334 if (isset($config[$key])) {
1335 return $config[$key];
1336 } else {
1337 return $default;
1338 }
1339 }
1340 endif;
1341
1342 if (!function_exists('wpo_disable_cache_directories_viewing')) :
1343 function wpo_disable_cache_directories_viewing() {
1344 global $is_apache, $is_IIS, $is_iis7;
1345
1346 if (!is_dir(WPO_CACHE_FILES_DIR)) return;
1347
1348 // Create .htaccess file for apache server.
1349 if ($is_apache) {
1350 $htaccess_filename = WPO_CACHE_FILES_DIR . '/.htaccess';
1351
1352 // CS does not like heredoc
1353 // phpcs:disable
1354 $htaccess_content = <<<EOF
1355 # Disable directory browsing
1356 Options -Indexes
1357
1358 # Disable access to any files
1359 <FilesMatch ".*">
1360 Order allow,deny
1361 Deny from all
1362 </FilesMatch>
1363 EOF;
1364 // phpcs:enable
1365
1366 if (!is_file($htaccess_filename)) @file_put_contents($htaccess_filename, $htaccess_content); // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- suppress the error when there is file permission issues
1367 }
1368
1369 // Create web.config file for IIS servers.
1370 if ($is_IIS || $is_iis7) {
1371 $webconfig_filename = WPO_CACHE_FILES_DIR . '/web.config';
1372 $webconfig_content = "<configuration>\n<system.webServer>\n<authorization>\n<deny users=\"*\" />\n</authorization>\n</system.webServer>\n</configuration>\n";
1373
1374 if (!is_file($webconfig_filename)) @file_put_contents($webconfig_filename, $webconfig_content); // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- suppress the error when there is file permission issues
1375 }
1376
1377 // Create empty index.php file for all servers.
1378 if (!is_file(WPO_CACHE_FILES_DIR . '/index.php')) @file_put_contents(WPO_CACHE_FILES_DIR . '/index.php', '');// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- suppress the error when there is file permission issues
1379 }
1380 endif;
1381
1382 /**
1383 * Add the headers indicating why the page is not cached or served from cache
1384 *
1385 * @param string $message - The headers
1386 *
1387 * @return void
1388 */
1389 if (!function_exists('wpo_cache_add_nocache_http_header')) :
1390 function wpo_cache_add_nocache_http_header($message = '') {
1391 if (!headers_sent()) {
1392 header('WPO-Cache-Status: not cached');
1393 header('WPO-Cache-Message: '. trim(str_replace(array("\r", "\n", ':'), ' ', strip_tags($message))));
1394 }
1395 }
1396 endif;
1397
1398 /**
1399 * Add the headers indicating why the page is not cached or served from cache by integrating with the send_headers filter
1400 *
1401 * @param string $message - The headers
1402 *
1403 * @return void
1404 */
1405 if (!function_exists('wpo_cache_add_nocache_http_header_with_send_headers_action')) :
1406 function wpo_cache_add_nocache_http_header_with_send_headers_action($message = '') {
1407 static $buffered_message = null;
1408
1409 if (function_exists('current_filter') && 'send_headers' === current_filter() && $buffered_message && !headers_sent()) {
1410 wpo_cache_add_nocache_http_header($buffered_message);
1411 } else {
1412 if (!$buffered_message && function_exists('add_action')) add_action('send_headers', 'wpo_cache_add_nocache_http_header_with_send_headers_action', 11);
1413 $buffered_message = $message;
1414 }
1415 }
1416 endif;
1417
1418 /**
1419 * Check if feeds caching enabled
1420 *
1421 * @return bool
1422 */
1423 if (!function_exists('wpo_feeds_caching_enabled')) :
1424 function wpo_feeds_caching_enabled() {
1425 return apply_filters('wpo_feeds_caching_enabled', true);
1426 }
1427 endif;
1428
1429 if (!function_exists('wpo_debug_backtrace_summary')) {
1430 function wpo_debug_backtrace_summary($ignore_class = null, $skip_frames = 0, $pretty = true) {
1431 static $truncate_paths;
1432
1433 $trace = debug_backtrace(false);
1434 $caller = array();
1435 $check_class = !is_null($ignore_class);
1436 $skip_frames++; // Skip this function.
1437
1438 if (!isset($truncate_paths)) {
1439 $truncate_paths = array(
1440 wpo_normalize_path(WP_CONTENT_DIR),
1441 wpo_normalize_path(ABSPATH),
1442 );
1443 }
1444
1445 foreach ($trace as $call) {
1446 if ($skip_frames > 0) {
1447 $skip_frames--;
1448 } elseif (isset($call['class'])) {
1449 if ($check_class && $ignore_class == $call['class']) {
1450 continue; // Filter out calls.
1451 }
1452
1453 $caller[] = "{$call['class']}{$call['type']}{$call['function']}";
1454 } else {
1455 if (in_array($call['function'], array('do_action', 'apply_filters', 'do_action_ref_array', 'apply_filters_ref_array'), true)) {
1456 $caller[] = "{$call['function']}('{$call['args'][0]}')";
1457 } elseif (in_array($call['function'], array('include', 'include_once', 'require', 'require_once'), true)) {
1458 $filename = isset($call['args'][0]) ? $call['args'][0] : '';
1459 $caller[] = $call['function'] . "('" . str_replace($truncate_paths, '', wpo_normalize_path($filename)) . "')";
1460 } else {
1461 $caller[] = $call['function'];
1462 }
1463 }
1464 }
1465 if ($pretty) {
1466 return implode(', ', array_reverse($caller));
1467 } else {
1468 return $caller;
1469 }
1470 }
1471 }
1472
1473 if (!function_exists('wpo_normalize_path')) {
1474 function wpo_normalize_path($path) {
1475 // Standardise all paths to use '/'.
1476 $path = str_replace('\\', '/', $path);
1477
1478 // Replace multiple slashes down to a singular, allowing for network shares having two slashes.
1479 $path = preg_replace('|(?<=.)/+|', '/', $path);
1480
1481 // Windows paths should uppercase the drive letter.
1482 if (':' === substr($path, 1, 1)) {
1483 $path = ucfirst($path);
1484 }
1485
1486 return $path;
1487 }
1488 }
1489