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

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

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