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

1,493 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 return true;
1135 }
1136
1137 $success = true;
1138
1139 // If recursive is false, delete only the top-level files and return the result
1140 if (!$recursive) {
1141 $dir_handle = @opendir($src); // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- suppress PHP warning in case of failure
1142
1143 /*
1144 * If opendir() is successful, process directory contents. If not, check if the directory exists.
1145 * If it exists, return false (failure). Otherwise, assume it's already deleted and return true (success).
1146 */
1147 if (false !== $dir_handle) {
1148
1149 while (false !== ($file = readdir($dir_handle))) {
1150 if ('.' == $file || '..' == $file) {
1151 continue;
1152 }
1153
1154 $full_path = $src . '/' . $file;
1155
1156 // If it's a file, delete it
1157 if (is_file($full_path)) {
1158 if (!wpo_delete_files($full_path)) {
1159 $success = false;
1160 }
1161 }
1162 }
1163
1164 closedir($dir_handle);
1165 } else {
1166 if (file_exists($src)) {
1167 $success = false;
1168 }
1169 }
1170
1171 return $success;
1172 }
1173
1174 // If recursive is true, delete all files and directories recursively
1175 $dir_handle = @opendir($src); // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- suppress PHP warning in case of failure
1176
1177 /*
1178 * If opendir() is successful, process directory contents. If not, check if the directory exists.
1179 * If it exists, return false (failure). Otherwise, assume it's already deleted and return true (success).
1180 */
1181 if (false !== $dir_handle) {
1182
1183 while (false !== ($file = readdir($dir_handle))) {
1184 if ('.' == $file || '..' == $file) {
1185 continue;
1186 }
1187
1188 $full_path = $src . '/' . $file;
1189
1190 if (!wpo_delete_files($full_path)) {
1191 $success = false;
1192 }
1193 }
1194
1195 closedir($dir_handle);
1196 } else {
1197 if (file_exists($src)) {
1198 $success = false;
1199 }
1200 }
1201
1202 /*
1203 * Delete the source directory itself.
1204 * Success of `rmdir` operation is not recorded; we only ultimately care about emptying, not removing
1205 * entirely (empty folders in our context are harmless)
1206 */
1207 if ($success) {
1208 @rmdir($src); // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- suppress PHP warning in case of failure
1209 }
1210
1211 // Delete cached information about cache size
1212 WP_Optimize()->get_page_cache()->delete_cache_size_information();
1213
1214 return $success;
1215 }
1216 endif;
1217
1218 if (!function_exists('wpo_is_empty_dir')) :
1219 /**
1220 * Check if selected directory is empty or has only index.php which we added for security reasons.
1221 *
1222 * @param string $dir
1223 *
1224 * @return bool
1225 */
1226 function wpo_is_empty_dir($dir) {
1227 if (!file_exists($dir) || !is_dir($dir)) return false;
1228
1229 $handle = opendir($dir);
1230
1231 if (false === $handle) return false;
1232
1233 $is_empty = true;
1234 $file = readdir($handle);
1235
1236 while (false !== $file) {
1237
1238 if ('.' != $file && '..' != $file && 'index.php' != $file) {
1239 $is_empty = false;
1240 break;
1241 }
1242
1243 $file = readdir($handle);
1244 }
1245
1246 closedir($handle);
1247 return $is_empty;
1248 }
1249 endif;
1250
1251 /**
1252 * Either store for later output, or output now. Only the most-recent call will be effective.
1253 *
1254 * @param String|Null $output - if not null, then the string to use when called by the shutdown action.
1255 */
1256 if (!function_exists('wpo_cache_add_footer_output')) :
1257 function wpo_cache_add_footer_output($output = null) {
1258
1259 static $buffered = null;
1260
1261 if (function_exists('current_filter') && 'shutdown' == current_filter()) {
1262 // Only add the line if it was a page, not something else (e.g. REST response)
1263 if (function_exists('did_action') && did_action('wp_footer')) {
1264 echo "\n<!-- WP Optimize page cache - https://getwpo.com - ".$buffered." -->\n";
1265 } elseif (defined('WPO_CACHE_DEBUG') && WPO_CACHE_DEBUG && (!defined('REST_REQUEST') || !REST_REQUEST)) {
1266 error_log('[CACHE DEBUG] '.wpo_current_url() . ' - ' . $buffered);
1267 }
1268 } else {
1269 if (null == $buffered && function_exists('add_action')) add_action('shutdown', 'wpo_cache_add_footer_output', 11);
1270 $buffered = $output;
1271 }
1272
1273 }
1274 endif;
1275
1276 /**
1277 * Remove variable names that shouldn't influence cache.
1278 *
1279 * @param array $variables List of variable names.
1280 *
1281 * @return array
1282 */
1283 if (!function_exists('wpo_cache_maybe_ignore_query_variables')) :
1284 function wpo_cache_maybe_ignore_query_variables($variables) {
1285
1286 /**
1287 * Filters the current $_GET variables that will be used when caching or excluding from cache.
1288 * Currently:
1289 * - 'wpo_cache_debug' (Shows the reason for not being cached even when WP_DEBUG isn't set)
1290 * - 'doing_wp_cron' (alternative cron)
1291 * - 'aiosp_sitemap_path', 'aiosp_sitemap_page' (All in one SEO sitemap)
1292 * - 'xml_sitemap', 'seopress_sitemap', 'seopress_news', 'seopress_video', 'seopress_cpt', 'seopress_paged' (SEOPress sitemap)
1293 * - 'sitemap', 'sitemap_n' (YOAST SEO sitemap)
1294 */
1295 $exclude_variables = array(
1296 'wpo_cache_debug', // Shows the reason for not being cached even when WP_DEBUG isn't set
1297 'doing_wp_cron', // alternative cron
1298 'aiosp_sitemap_path', // All in one SEO sitemap
1299 'aiosp_sitemap_page',
1300 'xml_sitemap', // SEOPress sitemap
1301 'seopress_sitemap',
1302 'seopress_news',
1303 'seopress_video',
1304 'seopress_cpt',
1305 'seopress_paged',
1306 'sitemap', // YOAST SEO sitemap
1307 'sitemap_n',
1308 );
1309 $exclude_variables = function_exists('apply_filters') ? apply_filters('wpo_cache_ignore_query_variables', $exclude_variables) : $exclude_variables;
1310
1311 if (empty($exclude_variables)) return $variables;
1312
1313 foreach ($exclude_variables as $variable) {
1314 $exclude = array_search($variable, $variables);
1315 if (false !== $exclude) {
1316 array_splice($variables, $exclude, 1);
1317 }
1318 }
1319
1320 return $variables;
1321 }
1322 endif;
1323
1324 /**
1325 * Get cache config
1326 *
1327 * @param string $key - The config item
1328 * @param mixed $default - The default value
1329 *
1330 * @return mixed
1331 */
1332 if (!function_exists('wpo_cache_config_get')) :
1333 function wpo_cache_config_get($key, $default = false) {
1334 $config = $GLOBALS['wpo_cache_config'];
1335
1336 if (!$config) return false;
1337
1338 if (isset($config[$key])) {
1339 return $config[$key];
1340 } else {
1341 return $default;
1342 }
1343 }
1344 endif;
1345
1346 if (!function_exists('wpo_disable_cache_directories_viewing')) :
1347 function wpo_disable_cache_directories_viewing() {
1348 global $is_apache, $is_IIS, $is_iis7;
1349
1350 if (!is_dir(WPO_CACHE_FILES_DIR)) return;
1351
1352 // Create .htaccess file for apache server.
1353 if ($is_apache) {
1354 $htaccess_filename = WPO_CACHE_FILES_DIR . '/.htaccess';
1355
1356 // CS does not like heredoc
1357 // phpcs:disable
1358 $htaccess_content = <<<EOF
1359 # Disable directory browsing
1360 Options -Indexes
1361
1362 # Disable access to any files
1363 <FilesMatch ".*">
1364 Order allow,deny
1365 Deny from all
1366 </FilesMatch>
1367 EOF;
1368 // phpcs:enable
1369
1370 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
1371 }
1372
1373 // Create web.config file for IIS servers.
1374 if ($is_IIS || $is_iis7) {
1375 $webconfig_filename = WPO_CACHE_FILES_DIR . '/web.config';
1376 $webconfig_content = "<configuration>\n<system.webServer>\n<authorization>\n<deny users=\"*\" />\n</authorization>\n</system.webServer>\n</configuration>\n";
1377
1378 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
1379 }
1380
1381 // Create empty index.php file for all servers.
1382 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
1383 }
1384 endif;
1385
1386 /**
1387 * Add the headers indicating why the page is not cached or served from cache
1388 *
1389 * @param string $message - The headers
1390 *
1391 * @return void
1392 */
1393 if (!function_exists('wpo_cache_add_nocache_http_header')) :
1394 function wpo_cache_add_nocache_http_header($message = '') {
1395 if (!headers_sent()) {
1396 header('WPO-Cache-Status: not cached');
1397 header('WPO-Cache-Message: '. trim(str_replace(array("\r", "\n", ':'), ' ', strip_tags($message))));
1398 }
1399 }
1400 endif;
1401
1402 /**
1403 * Add the headers indicating why the page is not cached or served from cache by integrating with the send_headers filter
1404 *
1405 * @param string $message - The headers
1406 *
1407 * @return void
1408 */
1409 if (!function_exists('wpo_cache_add_nocache_http_header_with_send_headers_action')) :
1410 function wpo_cache_add_nocache_http_header_with_send_headers_action($message = '') {
1411 static $buffered_message = null;
1412
1413 if (function_exists('current_filter') && 'send_headers' === current_filter() && $buffered_message && !headers_sent()) {
1414 wpo_cache_add_nocache_http_header($buffered_message);
1415 } else {
1416 if (!$buffered_message && function_exists('add_action')) add_action('send_headers', 'wpo_cache_add_nocache_http_header_with_send_headers_action', 11);
1417 $buffered_message = $message;
1418 }
1419 }
1420 endif;
1421
1422 /**
1423 * Check if feeds caching enabled
1424 *
1425 * @return bool
1426 */
1427 if (!function_exists('wpo_feeds_caching_enabled')) :
1428 function wpo_feeds_caching_enabled() {
1429 return apply_filters('wpo_feeds_caching_enabled', true);
1430 }
1431 endif;
1432
1433 if (!function_exists('wpo_debug_backtrace_summary')) {
1434 function wpo_debug_backtrace_summary($ignore_class = null, $skip_frames = 0, $pretty = true) {
1435 static $truncate_paths;
1436
1437 $trace = debug_backtrace(false);
1438 $caller = array();
1439 $check_class = !is_null($ignore_class);
1440 $skip_frames++; // Skip this function.
1441
1442 if (!isset($truncate_paths)) {
1443 $truncate_paths = array(
1444 wpo_normalize_path(WP_CONTENT_DIR),
1445 wpo_normalize_path(ABSPATH),
1446 );
1447 }
1448
1449 foreach ($trace as $call) {
1450 if ($skip_frames > 0) {
1451 $skip_frames--;
1452 } elseif (isset($call['class'])) {
1453 if ($check_class && $ignore_class == $call['class']) {
1454 continue; // Filter out calls.
1455 }
1456
1457 $caller[] = "{$call['class']}{$call['type']}{$call['function']}";
1458 } else {
1459 if (in_array($call['function'], array('do_action', 'apply_filters', 'do_action_ref_array', 'apply_filters_ref_array'), true)) {
1460 $caller[] = "{$call['function']}('{$call['args'][0]}')";
1461 } elseif (in_array($call['function'], array('include', 'include_once', 'require', 'require_once'), true)) {
1462 $filename = isset($call['args'][0]) ? $call['args'][0] : '';
1463 $caller[] = $call['function'] . "('" . str_replace($truncate_paths, '', wpo_normalize_path($filename)) . "')";
1464 } else {
1465 $caller[] = $call['function'];
1466 }
1467 }
1468 }
1469 if ($pretty) {
1470 return implode(', ', array_reverse($caller));
1471 } else {
1472 return $caller;
1473 }
1474 }
1475 }
1476
1477 if (!function_exists('wpo_normalize_path')) {
1478 function wpo_normalize_path($path) {
1479 // Standardise all paths to use '/'.
1480 $path = str_replace('\\', '/', $path);
1481
1482 // Replace multiple slashes down to a singular, allowing for network shares having two slashes.
1483 $path = preg_replace('|(?<=.)/+|', '/', $path);
1484
1485 // Windows paths should uppercase the drive letter.
1486 if (':' === substr($path, 1, 1)) {
1487 $path = ucfirst($path);
1488 }
1489
1490 return $path;
1491 }
1492 }
1493