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

1,494 lines 46.8 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 // Adding cache key with queried cookies and variables to the cache file name.
405 if ('' !== $cache_key) {
406 // Add human-readable cache key to the filename
407 $filename .= str_replace('--', '-', '-'.$cache_key);
408 }
409
410 $filename = apply_filters('wpo_cache_filename', $filename);
411
412 // Trimming filename if it exceeds 240 characters due to filesystem limitations
413 if (strlen($filename) > 240) {
414 $filename = substr($filename, 0, 199) . '-' . sha1($filename);
415 }
416
417 $wpo_cache_filename_debug[] = 'Extension: ' . $ext;
418 $wpo_cache_filename_debug[] = 'Filename: ' . $filename.$ext;
419
420 $GLOBALS['wpo_cache_filename_debug'] = $wpo_cache_filename_debug;
421
422 return $filename . $ext;
423 }
424 endif;
425
426 /**
427 * Returns site url from site_url() function or if it is not available from cache configuration.
428 */
429 if (!function_exists('wpo_site_url')) :
430 function wpo_site_url() {
431 if (is_callable('site_url')) return site_url('/');
432
433 $site_url = empty($GLOBALS['wpo_cache_config']['site_url']) ? '' : $GLOBALS['wpo_cache_config']['site_url'];
434 return $site_url;
435 }
436 endif;
437
438 /**
439 * Get cookie names which impact on cache file name.
440 *
441 * @return array
442 */
443 if (!function_exists('wpo_cache_cookies')) :
444 function wpo_cache_cookies() {
445 $cookies = empty($GLOBALS['wpo_cache_config']['wpo_cache_cookies']) ? array() : $GLOBALS['wpo_cache_config']['wpo_cache_cookies'];
446 return $cookies;
447 }
448 endif;
449
450 /**
451 * Get GET variable names which impact on cache file name.
452 *
453 * @return array
454 */
455 if (!function_exists('wpo_cache_query_variables')) :
456 function wpo_cache_query_variables() {
457 if (defined('WPO_CACHE_URL_PARAMS') && WPO_CACHE_URL_PARAMS) {
458 $variables = array_keys($_GET);
459 } else {
460 $variables = empty($GLOBALS['wpo_cache_config']['wpo_cache_query_variables']) ? array() : $GLOBALS['wpo_cache_config']['wpo_cache_query_variables'];
461 }
462
463 if (!empty($variables)) {
464 sort($variables);
465 }
466
467 return wpo_cache_maybe_ignore_query_variables($variables);
468 }
469 endif;
470
471 /**
472 * Get list of all received HTTP headers.
473 *
474 * @return array
475 */
476 if (!function_exists('wpo_get_http_headers')) :
477 function wpo_get_http_headers() {
478
479 static $headers;
480
481 if (!empty($headers)) return $headers;
482
483 $headers = array();
484
485 // if is apache server then use get allheaders() function.
486 if (function_exists('getallheaders')) {
487 $headers = getallheaders();
488 } else {
489 // https://www.php.net/manual/en/function.getallheaders.php
490 foreach ($_SERVER as $key => $value) {
491
492 $key = strtolower($key);
493
494 if ('HTTP_' == substr($key, 0, 5)) {
495 $headers[str_replace(' ', '-', ucwords(str_replace('_', ' ', substr($key, 5))))] = $value;
496 } elseif ('content_type' == $key) {
497 $headers["Content-Type"] = $value;
498 } elseif ('content_length' == $key) {
499 $headers["Content-Length"] = $value;
500 }
501 }
502 }
503
504 return $headers;
505 }
506 endif;
507
508 /**
509 * Check if requested Accept-Encoding headers has gzip value.
510 *
511 * @return bool
512 */
513 if (!function_exists('wpo_cache_gzip_accepted')) :
514 function wpo_cache_gzip_accepted() {
515 $headers = wpo_get_http_headers();
516
517 if (isset($headers['Accept-Encoding']) && preg_match('/gzip/i', $headers['Accept-Encoding'])) return true;
518
519 return false;
520 }
521 endif;
522
523 /**
524 * Check if we can output gzip content in current answer, i.e. check Accept-Encoding headers has gzip value
525 * and function ob_gzhandler is available.
526 *
527 * @return bool
528 */
529 if (!function_exists('wpo_cache_can_output_gzip_content')) :
530 function wpo_cache_can_output_gzip_content() {
531 return wpo_cache_gzip_accepted() && function_exists('ob_gzhandler');
532 }
533 endif;
534
535 /**
536 * Check if header with certain name exists in already prepared headers and has value comparable with $header_value.
537 *
538 * @param string $header_name header name
539 * @param string $header_value header value as regexp.
540 *
541 * @return bool
542 */
543 if (!function_exists('wpo_cache_is_in_response_headers_list')) :
544 function wpo_cache_is_in_response_headers_list($header_name, $header_value) {
545 $headers_list = headers_list();
546
547 if (!empty($headers_list)) {
548 $header_name = strtolower($header_name);
549
550 foreach ($headers_list as $value) {
551 $value = explode(':', $value);
552
553 if (strtolower($value[0]) == $header_name) {
554 if (preg_match('/'.$header_value.'/', $value[1])) {
555 return true;
556 } else {
557 return false;
558 }
559 }
560 }
561 }
562
563 return false;
564 }
565 endif;
566
567 /**
568 * Check if mobile cache is enabled and current request is from moblile device.
569 *
570 * @return bool
571 */
572 if (!function_exists('wpo_cache_mobile_caching_enabled')) :
573 function wpo_cache_mobile_caching_enabled() {
574 if (!empty($GLOBALS['wpo_cache_config']['enable_mobile_caching'])) return true;
575 return false;
576 }
577 endif;
578
579 /**
580 * Check if webp images enabled
581 *
582 * @return bool
583 */
584 if (!function_exists('wpo_webp_images_enabled')) :
585 function wpo_webp_images_enabled() {
586 if (!empty($GLOBALS['wpo_cache_config']['use_webp_images'])) return true;
587 return false;
588 }
589 endif;
590
591 /**
592 * Check whether webp images using alter html method or not
593 *
594 * @return bool
595 */
596 if (!function_exists('wpo_is_using_alter_html')) :
597 function wpo_is_using_alter_html() {
598 return (isset($_SERVER['HTTP_ACCEPT']) && false !== strpos($_SERVER['HTTP_ACCEPT'], 'image/webp'));
599 }
600 endif;
601
602 /**
603 * Check whether webp images are served using redirect
604 *
605 * @return bool
606 */
607 if (!function_exists('wpo_is_using_webp_images_redirection')) :
608 function wpo_is_using_webp_images_redirection() {
609 if (empty($GLOBALS['wpo_cache_config']['uploads'])) return false;
610
611 $uploads_dir = $GLOBALS['wpo_cache_config']['uploads'];
612 $htaccess_file = $uploads_dir . '/.htaccess';
613 if (!file_exists($htaccess_file)) return false;
614 $htaccess_content = file_get_contents($htaccess_file);
615 $comment_sections = array('Register webp mime type', 'WP-Optimize WebP Rules');
616
617 if (function_exists('str_contains')) {
618 return str_contains($htaccess_content, $comment_sections[0]) && str_contains($htaccess_content, $comment_sections[1]);
619 } else {
620 return strpos($htaccess_content, $comment_sections[0]) && strpos($htaccess_content, $comment_sections[1]);
621 }
622 }
623 endif;
624
625 /**
626 * Verify if the current request is related to the Activity Stream
627 *
628 * @return bool
629 */
630 if (!function_exists('wpo_is_activity_stream_requested')) :
631 function wpo_is_activity_stream_requested() {
632 return (isset($_SERVER['HTTP_ACCEPT']) && false !== strpos($_SERVER['HTTP_ACCEPT'], 'application/activity+json'));
633 }
634 endif;
635
636 /**
637 * Verify if the current request is robots.txt
638 */
639 if (!function_exists('wpo_is_robots_txt_requested')) :
640 function wpo_is_robots_txt_requested() {
641 return (isset($_SERVER['REQUEST_URI']) && 'robots.txt' === basename($_SERVER['REQUEST_URI']));
642 }
643 endif;
644
645 /**
646 * Serves the cache and exits
647 */
648 if (!function_exists('wpo_serve_cache')) :
649 function wpo_serve_cache() {
650 $file_name = wpo_cache_filename();
651
652 $file_name_rss_xml = wpo_cache_filename('.rss-xml');
653 $send_as_feed = false;
654
655 $path_dir = WPO_CACHE_FILES_DIR . '/' . wpo_get_url_path() . '/';
656 $path = $path_dir . $file_name;
657
658 if (wpo_feeds_caching_enabled()) {
659 // check for .xml cache file if .html cache file doesn't exist
660 if (!file_exists($path_dir . $file_name) && file_exists($path_dir . $file_name_rss_xml)) {
661 $path = $path_dir . $file_name_rss_xml;
662 $send_as_feed = true;
663 }
664 }
665
666 $use_gzip = false;
667
668 // if we can use gzip and gzipped file exist in cache we use it.
669 // if headers already sent we don't use gzipped file content.
670 if (!headers_sent() && wpo_cache_gzip_accepted() && file_exists($path . '.gz')) {
671 $path .= '.gz';
672 $use_gzip = true;
673 }
674
675 $modified_time = file_exists($path) ? (int) filemtime($path) : time();
676
677 // Cache has expired, purge and exit.
678 if (!empty($GLOBALS['wpo_cache_config']['page_cache_length'])) {
679 if (time() > ($GLOBALS['wpo_cache_config']['page_cache_length'] + $modified_time)) {
680 wpo_delete_files($path);
681 return;
682 }
683 }
684
685 if ($use_gzip) {
686 // Disable zlib output compression to avoid double content compression
687 ini_set('zlib.output_compression', 'Off');
688 }
689
690 $gzip_header_already_sent = wpo_cache_is_in_response_headers_list('Content-Encoding', 'gzip');
691
692 header('Cache-Control: no-cache'); // Check back later
693
694 if (!empty($modified_time) && !empty($_SERVER['HTTP_IF_MODIFIED_SINCE']) && strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) === $modified_time) {
695 if ($use_gzip && !$gzip_header_already_sent) {
696 header('Content-Encoding: gzip');
697 }
698
699 if ($send_as_feed) {
700 header('Content-type: application/rss+xml');
701 }
702
703 header('WPO-Cache-Status: cached');
704 header('Last-Modified: ' . gmdate('D, d M Y H:i:s', $modified_time) . ' GMT');
705 header($_SERVER['SERVER_PROTOCOL'] . ' 304 Not Modified', true, 304);
706 exit;
707 }
708
709 if (file_exists($path) && is_readable($path)) {
710
711 if (wpo_is_canonical_redirection_needed()) return;
712
713 if ($use_gzip && !$gzip_header_already_sent) {
714 header('Content-Encoding: gzip');
715 }
716
717 // send correct headers for xml and txt files
718 $filename = basename(dirname($path));
719
720 if (preg_match('/\.xml$/i', $filename)) {
721 header('Content-type: text/xml');
722 }
723
724 if (preg_match('/\.txt$/i', $filename)) {
725 header('Content-type: text/plain');
726 }
727
728 if ($send_as_feed) {
729 header('Content-type: application/rss+xml');
730 }
731
732 header('WPO-Cache-Status: cached');
733 if (!empty($modified_time)) {
734 header('Last-Modified: ' . gmdate('D, d M Y H:i:s', $modified_time) . ' GMT');
735 }
736
737 readfile($path);
738
739 exit;
740 }
741 }
742 endif;
743
744 /**
745 * Checks and does redirection, if needed
746 *
747 * @return bool
748 */
749 if (!function_exists('wpo_is_canonical_redirection_needed')) :
750 function wpo_is_canonical_redirection_needed() {
751 $permalink_structure = isset($GLOBALS['wpo_cache_config']['permalink_structure']) ? $GLOBALS['wpo_cache_config']['permalink_structure'] : '';
752 $site_url = wpo_site_url();
753
754 // Exit if server variables are not available.
755 if (!isset($_SERVER['HTTP_HOST'])) return false;
756
757 $schema = isset($_SERVER['HTTPS']) && 'on' === $_SERVER['HTTPS'] ? "https" : "http";
758 $url_part = "://" . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
759 $requested_url = $schema . $url_part;
760 $url_parts = parse_url($requested_url);
761 $extension = pathinfo($url_parts['path'], PATHINFO_EXTENSION);
762
763 if (!empty($permalink_structure) && $requested_url != $site_url && ((isset($url_parts['path']) && '/' !== $url_parts['path']) || isset($url_parts['query']))) {
764 $request_uri = rtrim($_SERVER['REQUEST_URI'], '?');
765 if ('/' == substr($permalink_structure, -1) && empty($extension) && empty($url_parts['query']) && empty($url_parts['fragment'])) {
766 $url = preg_replace('/(.+?)([\/]*)(\[\?\#][^\/]+|$)/', '$1/$3', $request_uri);
767 if (0 !== strcmp($request_uri, $url)) return true;
768 } else {
769 $url = rtrim($request_uri, '/');
770 if (0 !== strcmp($request_uri, $url)) return true;
771 }
772 }
773 return false;
774 }
775 endif;
776
777 /**
778 * Clears the cache
779 */
780 if (!function_exists('wpo_cache_flush')) :
781 function wpo_cache_flush() {
782
783 if (defined('WPO_CACHE_FILES_DIR') && '' != WPO_CACHE_FILES_DIR) wpo_delete_files(WPO_CACHE_FILES_DIR);
784
785 if (function_exists('wp_cache_flush')) {
786 wp_cache_flush();
787 }
788
789 do_action('wpo_cache_flush');
790 }
791 endif;
792
793 /**
794 * Get URL path for caching
795 *
796 * @since 1.0
797 * @return string
798 */
799 if (!function_exists('wpo_get_url_path')) :
800 function wpo_get_url_path($url = '') {
801 $url = '' == $url ? wpo_current_url() : $url;
802 $url_parts = parse_url($url);
803
804 if (isset($url_parts['path']) && false !== stripos($url_parts['path'], '/index.php')) {
805 $url_parts['path'] = preg_replace('/(.*?)index\.php(\/.+)/i', '$1index-php$2', $url_parts['path']);
806 }
807
808 /*
809 * Convert the hexadecimal digits within the percent-encoded triplet to uppercase, to ensure that the path remains
810 * consistent. For instance, "example.com/%e0%a6" will be converted to "example.com/%E0%A6".
811 */
812 if (isset($url_parts['path'])) {
813 $url_parts['path'] = preg_replace_callback('/%[0-9A-F]{2}/i', function($matches) {
814 return strtoupper($matches[0]);
815 }, $url_parts['path']);
816 }
817
818 if (!isset($url_parts['host'])) $url_parts['host'] = '';
819 if (!isset($url_parts['path'])) $url_parts['path'] = '';
820
821 return $url_parts['host'].$url_parts['path'];
822 }
823 endif;
824
825 /**
826 * Get requested url.
827 *
828 * @return string
829 */
830 if (!function_exists('wpo_current_url')) :
831 function wpo_current_url() {
832 // 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).
833 // Otherwise this function would return a different URL at the beginning and end of the cache process.
834 static $url = '';
835 if ('' != $url) return $url;
836 $http_host = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : '';
837 $url = rtrim('http' . ((isset($_SERVER['HTTPS']) && ('on' == $_SERVER['HTTPS'] || 1 == $_SERVER['HTTPS']) ||
838 isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && 'https' == $_SERVER['HTTP_X_FORWARDED_PROTO']) ? 's' : '' )
839 . '://' . $http_host.$_SERVER['REQUEST_URI'], '/');
840 return $url;
841 }
842 endif;
843
844 /**
845 * Return list of conditional tag exceptions.
846 *
847 * @return array
848 */
849 if (!function_exists('wpo_get_conditional_tags_exceptions')) :
850 function wpo_get_conditional_tags_exceptions() {
851 static $exceptions = null;
852
853 if (null !== $exceptions) return $exceptions;
854
855 if (!empty($GLOBALS['wpo_cache_config'])) {
856 if (empty($GLOBALS['wpo_cache_config']['cache_exception_conditional_tags'])) {
857 $exceptions = array();
858
859 } else {
860
861 $exceptions = $GLOBALS['wpo_cache_config']['cache_exception_conditional_tags'];
862
863 }
864
865 } elseif (class_exists('WPO_Page_Cache')) {
866
867 $config = WPO_Page_Cache::instance()->config->get();
868
869 if (is_array($config) && array_key_exists('cache_exception_conditional_tags', $config)) {
870 $exceptions = $config['cache_exception_conditional_tags'];
871 } else {
872 $exceptions = array();
873 }
874
875 $exceptions = is_array($exceptions) ? $exceptions : preg_split('#(\n|\r|\r\n)#', $exceptions);
876 $exceptions = array_filter($exceptions, 'trim');
877
878 } else {
879 $exceptions = array();
880 }
881
882 return $exceptions;
883 }
884 endif;
885
886 /**
887 * Return list of url exceptions.
888 *
889 * @return array
890 */
891 if (!function_exists('wpo_get_url_exceptions')) :
892 function wpo_get_url_exceptions() {
893 static $exceptions = null;
894
895 if (null !== $exceptions) return $exceptions;
896
897 // if called from file-based-page-cache.php when WP loading
898 // and cache settings exists then use it otherwise get settings from database.
899 if (!empty($GLOBALS['wpo_cache_config'])) {
900 if (empty($GLOBALS['wpo_cache_config']['cache_exception_urls'])) {
901 $exceptions = array();
902 } else {
903 $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']);
904 }
905 } elseif (class_exists('WPO_Page_Cache')) {
906 $config = WPO_Page_Cache::instance()->config->get();
907
908 if (is_array($config) && array_key_exists('cache_exception_urls', $config)) {
909 $exceptions = $config['cache_exception_urls'];
910 } else {
911 $exceptions = array();
912 }
913
914 $exceptions = is_array($exceptions) ? $exceptions : preg_split('#(\n|\r)#', $exceptions);
915 $exceptions = array_filter($exceptions, 'trim');
916 } else {
917 $exceptions = array();
918 }
919
920 return apply_filters('wpo_get_url_exceptions', $exceptions);
921 }
922 endif;
923
924 /**
925 * Return true of exception url matches current url
926 *
927 * @param string $exception Exceptions to check URL against.
928 * @param bool $regex Whether to check with regex or not.
929 * @return bool true if matched, false otherwise
930 */
931 if (!function_exists('wpo_current_url_exception_match')) :
932 function wpo_current_url_exception_match($exception) {
933
934 return wpo_url_exception_match(wpo_current_url(), $exception);
935 }
936 endif;
937
938 /**
939 * Check if url in conditional tags exceptions list.
940 *
941 * @return string
942 */
943 if (!function_exists('wpo_url_in_conditional_tags_exceptions')) :
944 function wpo_url_in_conditional_tags_exceptions() {
945
946 $exceptions = wpo_get_conditional_tags_exceptions();
947 $restricted = '';
948 $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');
949 //Filter for add more conditional tags to whitelist in the exceptions list.
950 $allowed_functions = apply_filters('wpo_allowed_conditional_tags_exceptions', $allowed_functions);
951 if (!empty($exceptions)) {
952 foreach ($exceptions as $exception) {
953 if (false !== strpos($exception, 'is_')) {
954 $exception_function = $exception;
955 if ('()' == substr($exception, -2)) {
956 $exception_function = substr($exception, 0, -2);
957 }
958
959 if (in_array($exception_function, $allowed_functions) && function_exists($exception_function) && call_user_func($exception_function)) {
960 $restricted = sprintf(__('In the settings, caching is disabled for %s', 'wp-optimize'), $exception_function);
961 }
962 }
963 }
964 }
965 return $restricted;
966 }
967 endif;
968
969
970 /**
971 * Check if url in exceptions list.
972 *
973 * @param string $url
974 *
975 * @return bool
976 */
977 if (!function_exists('wpo_url_in_exceptions')) :
978 function wpo_url_in_exceptions($url) {
979 $exceptions = wpo_get_url_exceptions();
980
981 if (!empty($exceptions)) {
982 foreach ($exceptions as $exception) {
983
984 // don't check / - front page using regexp, we handle it in wpo_restricted_cache_page_type()
985 if ('/' == $exception) continue;
986
987 if (wpo_url_exception_match($url, $exception)) {
988 // Exception match.
989 return true;
990 }
991 }
992 }
993
994 return false;
995 }
996 endif;
997
998 /**
999 * Checks if an URL matches against listed exceptions.
1000 *
1001 * @param string $url - complete url string i.e. http(s)://domain/path
1002 * @param string $exception - complete url or absolute path, can contain (.*) wildcards; Sometimes can be urlencoded
1003 *
1004 * @return bool
1005 */
1006 if (!function_exists('wpo_url_exception_match')) :
1007 function wpo_url_exception_match($url, $exception) {
1008 if (preg_match('#^[\s]*$#', $exception)) {
1009 return false;
1010 }
1011
1012 $exception = str_replace('*', '.*', $exception);
1013
1014 $exception = trim($exception);
1015
1016 // Used to test websites placed in subdirectories.
1017 $sub_dir = '';
1018
1019 // If exception defined from root i.e. /page1 then remove domain part in url.
1020 if (preg_match('/^\//', $exception)) {
1021 // get site sub directory.
1022 $sub_dir = preg_replace('#^(http|https):\/\/.*\/#Ui', '', wpo_site_url());
1023 // add prefix slash and remove slash.
1024 $sub_dir = ('' == $sub_dir) ? '' : '/' . rtrim($sub_dir, '/');
1025 // get relative path
1026 $url = preg_replace('#^(http|https):\/\/.*\/#Ui', '/', $url);
1027 }
1028
1029 $url = urldecode(rtrim($url, '/')) . '/';
1030 $exception = rtrim($exception, '/');
1031
1032 // if we have no wildcard in the end of exception then add slash.
1033 if (!preg_match('#\(\.\*\)$#', $exception)) $exception .= '/';
1034
1035 $exception = preg_quote($exception);
1036
1037 // fix - unescape some possibly escaped mask characters
1038 $search = array(
1039 '\\.\\*',
1040 '\\-',
1041 );
1042 $replace = array(
1043 '.*',
1044 '-',
1045 );
1046 $exception = urldecode(str_replace($search, $replace, $exception));
1047
1048 return (preg_match('#^'.$exception.'$#i', $url) || preg_match('#^'.$sub_dir.$exception.'$#i', $url));
1049 }
1050 endif;
1051
1052 /**
1053 * Checks if its a mobile device
1054 *
1055 * @see https://developer.wordpress.org/reference/functions/wp_is_mobile/
1056 */
1057 if (!function_exists('wpo_is_mobile')) :
1058 function wpo_is_mobile() {
1059 if (empty($_SERVER['HTTP_USER_AGENT'])) {
1060 $is_mobile = false;
1061 // many mobile devices (all iPhone, iPad, etc.)
1062 } elseif (strpos($_SERVER['HTTP_USER_AGENT'], 'Mobile') !== false
1063 || strpos($_SERVER['HTTP_USER_AGENT'], 'Android') !== false
1064 || strpos($_SERVER['HTTP_USER_AGENT'], 'Silk/') !== false
1065 || strpos($_SERVER['HTTP_USER_AGENT'], 'Kindle') !== false
1066 || strpos($_SERVER['HTTP_USER_AGENT'], 'BlackBerry') !== false
1067 || strpos($_SERVER['HTTP_USER_AGENT'], 'Opera Mini') !== false
1068 || strpos($_SERVER['HTTP_USER_AGENT'], 'Opera Mobi') !== false
1069 ) {
1070 $is_mobile = true;
1071 } else {
1072 $is_mobile = false;
1073 }
1074
1075 return $is_mobile;
1076 }
1077 endif;
1078
1079 /**
1080 * Check if current browser agent is not disabled in options.
1081 *
1082 * @return bool
1083 */
1084 if (!function_exists('wpo_is_accepted_user_agent')) :
1085 function wpo_is_accepted_user_agent($user_agent) {
1086
1087 $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']);
1088
1089 if (!empty($exceptions)) {
1090 foreach ($exceptions as $exception) {
1091 if ('' == trim($exception)) continue;
1092
1093 if (preg_match('#'.$exception.'#i', $user_agent)) return false;
1094 }
1095 }
1096
1097 return true;
1098 }
1099 endif;
1100
1101 if (!function_exists('wpo_delete_files')) :
1102 /**
1103 * Deletes a specified source file or directory.
1104 *
1105 * If $src is a file, only that file will be deleted. If $src is a directory, the behavior depends on the
1106 * $recursive parameter. When $recursive is true, the directory and its contents (including files and subdirectories)
1107 * will be deleted. When $recursive is false, only the files in the top-level directory(eg. $src directory) will be deleted,
1108 * while the $src directory itself and its subdirectories will remain untouched.
1109 *
1110 * @param string $src The path to the source file or directory to delete.
1111 * @param bool $recursive (Optional) When set to true, the directory and its contents (including files and subdirectories)
1112 * will be deleted. If false, only the files in the top-level directory will be deleted while
1113 * its subdirectories will be preserved. Defaults to true.
1114 *
1115 * @return bool Returns true if the specified file or all files within the specified directory (and its subdirectories,
1116 * when $recursive is true) are successfully deleted. Returns false if any file(s) could not be deleted
1117 * due to file permissions or other reasons.
1118 */
1119 function wpo_delete_files($src, $recursive = true) {
1120 // If the source doesn't exist, consider it deleted and return true
1121 if (!file_exists($src)) {
1122 return true;
1123 }
1124
1125 /*
1126 * If the source is a file, delete it and return the result.
1127 * If `unlink()` fails, we also verify if the file still exists before returning the result, as another
1128 * PHP process may have already deleted the file between the execution of `is_file()` and `unlink()` operations.
1129 */
1130 if (is_file($src)) {
1131 if (!@unlink($src) && file_exists($src)) { // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- suppress PHP warning in case of failure
1132 return false;
1133 }
1134
1135 return true;
1136 }
1137
1138 $success = true;
1139
1140 // If recursive is false, delete only the top-level files and return the result
1141 if (!$recursive) {
1142 $dir_handle = @opendir($src); // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- suppress PHP warning in case of failure
1143
1144 /*
1145 * If opendir() is successful, process directory contents. If not, check if the directory exists.
1146 * If it exists, return false (failure). Otherwise, assume it's already deleted and return true (success).
1147 */
1148 if (false !== $dir_handle) {
1149
1150 while (false !== ($file = readdir($dir_handle))) {
1151 if ('.' == $file || '..' == $file) {
1152 continue;
1153 }
1154
1155 $full_path = $src . '/' . $file;
1156
1157 // If it's a file, delete it
1158 if (is_file($full_path)) {
1159 if (!wpo_delete_files($full_path)) {
1160 $success = false;
1161 }
1162 }
1163 }
1164
1165 closedir($dir_handle);
1166 } else {
1167 if (file_exists($src)) {
1168 $success = false;
1169 }
1170 }
1171
1172 return $success;
1173 }
1174
1175 // If recursive is true, delete all files and directories recursively
1176 $dir_handle = @opendir($src); // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- suppress PHP warning in case of failure
1177
1178 /*
1179 * If opendir() is successful, process directory contents. If not, check if the directory exists.
1180 * If it exists, return false (failure). Otherwise, assume it's already deleted and return true (success).
1181 */
1182 if (false !== $dir_handle) {
1183
1184 while (false !== ($file = readdir($dir_handle))) {
1185 if ('.' == $file || '..' == $file) {
1186 continue;
1187 }
1188
1189 $full_path = $src . '/' . $file;
1190
1191 if (!wpo_delete_files($full_path)) {
1192 $success = false;
1193 }
1194 }
1195
1196 closedir($dir_handle);
1197 } else {
1198 if (file_exists($src)) {
1199 $success = false;
1200 }
1201 }
1202
1203 /*
1204 * Delete the source directory itself.
1205 * Success of `rmdir` operation is not recorded; we only ultimately care about emptying, not removing
1206 * entirely (empty folders in our context are harmless)
1207 */
1208 if ($success) {
1209 @rmdir($src); // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- suppress PHP warning in case of failure
1210 }
1211
1212 // Delete cached information about cache size
1213 WP_Optimize()->get_page_cache()->delete_cache_size_information();
1214
1215 return $success;
1216 }
1217 endif;
1218
1219 if (!function_exists('wpo_is_empty_dir')) :
1220 /**
1221 * Check if selected directory is empty or has only index.php which we added for security reasons.
1222 *
1223 * @param string $dir
1224 *
1225 * @return bool
1226 */
1227 function wpo_is_empty_dir($dir) {
1228 if (!file_exists($dir) || !is_dir($dir)) return false;
1229
1230 $handle = opendir($dir);
1231
1232 if (false === $handle) return false;
1233
1234 $is_empty = true;
1235 $file = readdir($handle);
1236
1237 while (false !== $file) {
1238
1239 if ('.' != $file && '..' != $file && 'index.php' != $file) {
1240 $is_empty = false;
1241 break;
1242 }
1243
1244 $file = readdir($handle);
1245 }
1246
1247 closedir($handle);
1248 return $is_empty;
1249 }
1250 endif;
1251
1252 /**
1253 * Either store for later output, or output now. Only the most-recent call will be effective.
1254 *
1255 * @param String|Null $output - if not null, then the string to use when called by the shutdown action.
1256 */
1257 if (!function_exists('wpo_cache_add_footer_output')) :
1258 function wpo_cache_add_footer_output($output = null) {
1259
1260 static $buffered = null;
1261
1262 if (function_exists('current_filter') && 'shutdown' == current_filter()) {
1263 // Only add the line if it was a page, not something else (e.g. REST response)
1264 if (function_exists('did_action') && did_action('wp_footer')) {
1265 echo "\n<!-- WP Optimize page cache - https://getwpo.com - ".$buffered." -->\n";
1266 } elseif (defined('WPO_CACHE_DEBUG') && WPO_CACHE_DEBUG && (!defined('REST_REQUEST') || !REST_REQUEST)) {
1267 error_log('[CACHE DEBUG] '.wpo_current_url() . ' - ' . $buffered);
1268 }
1269 } else {
1270 if (null == $buffered && function_exists('add_action')) add_action('shutdown', 'wpo_cache_add_footer_output', 11);
1271 $buffered = $output;
1272 }
1273
1274 }
1275 endif;
1276
1277 /**
1278 * Remove variable names that shouldn't influence cache.
1279 *
1280 * @param array $variables List of variable names.
1281 *
1282 * @return array
1283 */
1284 if (!function_exists('wpo_cache_maybe_ignore_query_variables')) :
1285 function wpo_cache_maybe_ignore_query_variables($variables) {
1286
1287 /**
1288 * Filters the current $_GET variables that will be used when caching or excluding from cache.
1289 * Currently:
1290 * - 'wpo_cache_debug' (Shows the reason for not being cached even when WP_DEBUG isn't set)
1291 * - 'doing_wp_cron' (alternative cron)
1292 * - 'aiosp_sitemap_path', 'aiosp_sitemap_page' (All in one SEO sitemap)
1293 * - 'xml_sitemap', 'seopress_sitemap', 'seopress_news', 'seopress_video', 'seopress_cpt', 'seopress_paged' (SEOPress sitemap)
1294 * - 'sitemap', 'sitemap_n' (YOAST SEO sitemap)
1295 */
1296 $exclude_variables = array(
1297 'wpo_cache_debug', // Shows the reason for not being cached even when WP_DEBUG isn't set
1298 'doing_wp_cron', // alternative cron
1299 'aiosp_sitemap_path', // All in one SEO sitemap
1300 'aiosp_sitemap_page',
1301 'xml_sitemap', // SEOPress sitemap
1302 'seopress_sitemap',
1303 'seopress_news',
1304 'seopress_video',
1305 'seopress_cpt',
1306 'seopress_paged',
1307 'sitemap', // YOAST SEO sitemap
1308 'sitemap_n',
1309 );
1310 $exclude_variables = function_exists('apply_filters') ? apply_filters('wpo_cache_ignore_query_variables', $exclude_variables) : $exclude_variables;
1311
1312 if (empty($exclude_variables)) return $variables;
1313
1314 foreach ($exclude_variables as $variable) {
1315 $exclude = array_search($variable, $variables);
1316 if (false !== $exclude) {
1317 array_splice($variables, $exclude, 1);
1318 }
1319 }
1320
1321 return $variables;
1322 }
1323 endif;
1324
1325 /**
1326 * Get cache config
1327 *
1328 * @param string $key - The config item
1329 * @param mixed $default - The default value
1330 *
1331 * @return mixed
1332 */
1333 if (!function_exists('wpo_cache_config_get')) :
1334 function wpo_cache_config_get($key, $default = false) {
1335 $config = $GLOBALS['wpo_cache_config'];
1336
1337 if (!$config) return false;
1338
1339 if (isset($config[$key])) {
1340 return $config[$key];
1341 } else {
1342 return $default;
1343 }
1344 }
1345 endif;
1346
1347 if (!function_exists('wpo_disable_cache_directories_viewing')) :
1348 function wpo_disable_cache_directories_viewing() {
1349 global $is_apache, $is_IIS, $is_iis7;
1350
1351 if (!is_dir(WPO_CACHE_FILES_DIR)) return;
1352
1353 // Create .htaccess file for apache server.
1354 if ($is_apache) {
1355 $htaccess_filename = WPO_CACHE_FILES_DIR . '/.htaccess';
1356
1357 // CS does not like heredoc
1358 // phpcs:disable
1359 $htaccess_content = <<<EOF
1360 # Disable directory browsing
1361 Options -Indexes
1362
1363 # Disable access to any files
1364 <FilesMatch ".*">
1365 Order allow,deny
1366 Deny from all
1367 </FilesMatch>
1368 EOF;
1369 // phpcs:enable
1370
1371 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
1372 }
1373
1374 // Create web.config file for IIS servers.
1375 if ($is_IIS || $is_iis7) {
1376 $webconfig_filename = WPO_CACHE_FILES_DIR . '/web.config';
1377 $webconfig_content = "<configuration>\n<system.webServer>\n<authorization>\n<deny users=\"*\" />\n</authorization>\n</system.webServer>\n</configuration>\n";
1378
1379 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
1380 }
1381
1382 // Create empty index.php file for all servers.
1383 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
1384 }
1385 endif;
1386
1387 /**
1388 * Add the headers indicating why the page is not cached or served from cache
1389 *
1390 * @param string $message - The headers
1391 *
1392 * @return void
1393 */
1394 if (!function_exists('wpo_cache_add_nocache_http_header')) :
1395 function wpo_cache_add_nocache_http_header($message = '') {
1396 if (!headers_sent()) {
1397 header('WPO-Cache-Status: not cached');
1398 header('WPO-Cache-Message: '. trim(str_replace(array("\r", "\n", ':'), ' ', strip_tags($message))));
1399 }
1400 }
1401 endif;
1402
1403 /**
1404 * Add the headers indicating why the page is not cached or served from cache by integrating with the send_headers filter
1405 *
1406 * @param string $message - The headers
1407 *
1408 * @return void
1409 */
1410 if (!function_exists('wpo_cache_add_nocache_http_header_with_send_headers_action')) :
1411 function wpo_cache_add_nocache_http_header_with_send_headers_action($message = '') {
1412 static $buffered_message = null;
1413
1414 if (function_exists('current_filter') && 'send_headers' === current_filter() && $buffered_message && !headers_sent()) {
1415 wpo_cache_add_nocache_http_header($buffered_message);
1416 } else {
1417 if (!$buffered_message && function_exists('add_action')) add_action('send_headers', 'wpo_cache_add_nocache_http_header_with_send_headers_action', 11);
1418 $buffered_message = $message;
1419 }
1420 }
1421 endif;
1422
1423 /**
1424 * Check if feeds caching enabled
1425 *
1426 * @return bool
1427 */
1428 if (!function_exists('wpo_feeds_caching_enabled')) :
1429 function wpo_feeds_caching_enabled() {
1430 return apply_filters('wpo_feeds_caching_enabled', true);
1431 }
1432 endif;
1433
1434 if (!function_exists('wpo_debug_backtrace_summary')) {
1435 function wpo_debug_backtrace_summary($ignore_class = null, $skip_frames = 0, $pretty = true) {
1436 static $truncate_paths;
1437
1438 $trace = debug_backtrace(false);
1439 $caller = array();
1440 $check_class = !is_null($ignore_class);
1441 $skip_frames++; // Skip this function.
1442
1443 if (!isset($truncate_paths)) {
1444 $truncate_paths = array(
1445 wpo_normalize_path(WP_CONTENT_DIR),
1446 wpo_normalize_path(ABSPATH),
1447 );
1448 }
1449
1450 foreach ($trace as $call) {
1451 if ($skip_frames > 0) {
1452 $skip_frames--;
1453 } elseif (isset($call['class'])) {
1454 if ($check_class && $ignore_class == $call['class']) {
1455 continue; // Filter out calls.
1456 }
1457
1458 $caller[] = "{$call['class']}{$call['type']}{$call['function']}";
1459 } else {
1460 if (in_array($call['function'], array('do_action', 'apply_filters', 'do_action_ref_array', 'apply_filters_ref_array'), true)) {
1461 $caller[] = "{$call['function']}('{$call['args'][0]}')";
1462 } elseif (in_array($call['function'], array('include', 'include_once', 'require', 'require_once'), true)) {
1463 $filename = isset($call['args'][0]) ? $call['args'][0] : '';
1464 $caller[] = $call['function'] . "('" . str_replace($truncate_paths, '', wpo_normalize_path($filename)) . "')";
1465 } else {
1466 $caller[] = $call['function'];
1467 }
1468 }
1469 }
1470 if ($pretty) {
1471 return implode(', ', array_reverse($caller));
1472 } else {
1473 return $caller;
1474 }
1475 }
1476 }
1477
1478 if (!function_exists('wpo_normalize_path')) {
1479 function wpo_normalize_path($path) {
1480 // Standardise all paths to use '/'.
1481 $path = str_replace('\\', '/', $path);
1482
1483 // Replace multiple slashes down to a singular, allowing for network shares having two slashes.
1484 $path = preg_replace('|(?<=.)/+|', '/', $path);
1485
1486 // Windows paths should uppercase the drive letter.
1487 if (':' === substr($path, 1, 1)) {
1488 $path = ucfirst($path);
1489 }
1490
1491 return $path;
1492 }
1493 }
1494