PluginProbe
WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance / 4.7.0
WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance v4.7.0
4.7.0 4.6.1 4.6.0 4.5.5 4.5.4 4.5.3 4.5.2 3.2.20 3.2.21 3.2.22 3.2.3 3.2.5 3.2.6 3.2.7 3.2.9 3.3.0 3.3.1 3.3.2 3.4.0 3.4.1 3.4.2 3.5.0 3.6.0 3.7.0 3.7.1 All 111 releases
← All changes | cache/file-based-page-cache-functions.php +99 -22 4.5.2 → 4.7.0 View file →
@@ -12,8 +12,13 @@
12 12 */
13 13 if (!defined('WPO_CACHE_FILES_DIR')) define('WPO_CACHE_FILES_DIR', untrailingslashit(WP_CONTENT_DIR).'/cache/wpo-cache');
14 14
15 15 /**
16 + * Minimum Firefox version for WebP support
17 + */
18 +if (!defined('WPO_MIN_FIREFOX_VERSION_FOR_WEBP')) define('WPO_MIN_FIREFOX_VERSION_FOR_WEBP', '65.0.0');
19 +
20 +/**
16 21 * Holds utility functions used by file based cache
17 22 */
18 23
19 24 /**
@@ -89,10 +94,14 @@
89 94 $no_cache_because[] = __('Comments are opened and the visitor saved his information.', 'wp-optimize');
90 95 }
91 96 }
92 97
98 + if (wpo_restricted_cache_page_with_cart_items()) {
99 + $no_cache_because[] = __('User has items in WooCommerce cart.', 'wp-optimize');
100 + }
101 +
93 102 $can_cache_page = true;
94 -
103 +
95 104 if (defined('DONOTCACHEPAGE') && DONOTCACHEPAGE) {
96 105 $can_cache_page = false;
97 106 }
98 107
@@ -127,12 +136,14 @@
127 136 }
128 137
129 138 // Get cache file name
130 139 $file_ext = '.html';
140 + $is_feed_cache = false;
131 141
132 142 if (wpo_feeds_caching_enabled()) {
133 143 if (is_feed()) {
134 144 $file_ext = '.rss-xml';
145 + $is_feed_cache = true;
135 146 }
136 147 }
137 148
138 149 $cache_filename = wpo_cache_filename($file_ext);
@@ -164,13 +175,13 @@
164 175 // Add http headers
165 176 wpo_cache_add_nocache_http_header($message);
166 177
167 178 if ((!defined('DOING_CRON') || !DOING_CRON) && (!defined('REST_REQUEST') || !REST_REQUEST)) {
168 - $not_cached_details = "";
179 + $not_cached_details = '';
169 180
170 181 // Output the reason only when the user has turned on debugging
171 182 if (((defined('WP_DEBUG') && WP_DEBUG) || isset($_GET['wpo_cache_debug']))) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Not using the value, only checks for existence
172 - $not_cached_details = "because: ".htmlspecialchars($message) . " ";
183 + $not_cached_details = "because: ".htmlspecialchars($message, ENT_QUOTES, 'UTF-8') . " ";
173 184 }
174 185
175 186 $buffer .= sprintf("\n<!-- WP Optimize page cache - https://teamupdraft.com/wp-optimize/ - page NOT cached %s-->\n", $not_cached_details);
176 187 }
@@ -237,9 +248,11 @@
237 248
238 249 if (defined('WPO_CACHE_FILENAME_DEBUG') && WPO_CACHE_FILENAME_DEBUG) {
239 250 $add_to_footer .= "\n<!-- WP Optimize page cache debug information -->\n";
240 251 if (!empty($GLOBALS['wpo_cache_filename_debug']) && is_array($GLOBALS['wpo_cache_filename_debug'])) {
241 - $add_to_footer .= "<!-- \n" . join("\n", array_map('htmlspecialchars', $GLOBALS['wpo_cache_filename_debug'])) . "\n -->";
252 + $add_to_footer .= "<!-- \n" . join("\n", array_map(function($s) {
253 + return htmlspecialchars((string) $s, ENT_QUOTES, 'UTF-8');
254 + }, $GLOBALS['wpo_cache_filename_debug'])) . "\n -->";
242 255 }
243 256 }
244 257
245 258 if (function_exists('gzencode') && apply_filters('wpo_allow_cache_gzip_files', true)) {
@@ -246,8 +259,23 @@
246 259 // Only replace inside the addition, not inside the main buffer (e.g. post content)
247 260 $add_to_footer = str_replace('by WP-Optimize', 'by WP-Optimize (gzip)', $add_to_footer);
248 261 }
249 262
263 + // Allow extensions to inject content before </body> in cached HTML (HTML pages only, not sitemaps or RSS feeds).
264 + // Content is inserted before the last </body> tag to produce valid HTML.
265 + // Falls back to appending after </html> if no </body> is found (e.g., partial HTML responses).
266 + if (!wpo_is_cacheable_sitemap_request() && !$is_feed_cache) {
267 + $footer_injection = apply_filters('wpo_cache_add_to_footer', '', $cache_filename);
268 + if ('' !== $footer_injection) {
269 + $body_close_pos = strripos($buffer, '</body>');
270 + if (false !== $body_close_pos) {
271 + $buffer = substr($buffer, 0, $body_close_pos) . $footer_injection . substr($buffer, $body_close_pos);
272 + } else {
273 + $buffer .= $footer_injection;
274 + }
275 + }
276 + }
277 +
250 278 // XML documents must not contain HTML comments in the footer, as this would invalidate the XML
251 279 if (wpo_is_cacheable_sitemap_request() && '' !== $add_to_footer) {
252 280 $pattern = '#</([a-zA-Z0-9:_-]+)>\s*$#';
253 281 $replacement = $add_to_footer . "\n</$1>";
@@ -424,10 +452,10 @@
424 452
425 453 if (wpo_cache_mobile_caching_enabled() && wpo_is_mobile()) {
426 454 $filename = 'mobile.' . $filename;
427 455 }
428 -
429 - if (wpo_webp_images_enabled() && !wpo_is_using_webp_images_redirection() && wpo_is_using_alter_html()) {
456 +
457 + if (wpo_webp_images_enabled() && !wpo_is_using_webp_images_redirection() && wpo_is_browser_supports_webp()) {
430 458 $filename = $filename . '.webp';
431 459 }
432 460
433 461 $cookies = wpo_cache_cookies();
@@ -528,8 +556,9 @@
528 556 $filename = 'index';
529 557 $cache_key = '';
530 558
531 559 if (!empty($params)) {
560 + ksort($params);
532 561 foreach ($params as $key => $value) {
533 562 if (is_array($value)) $value = serialize($value);
534 563 $_cache_key = $key.'_'.$value;
535 564 $_cache_key = preg_replace('/[^a-z0-9_\-]/i', '-', $_cache_key);
@@ -706,15 +735,27 @@
706 735 }
707 736 endif;
708 737
709 738 /**
710 - * Check whether webp images using alter html method or not
739 + * Check whether the requesting browser supports WebP images
711 740 *
712 741 * @return bool
713 742 */
714 -if (!function_exists('wpo_is_using_alter_html')) :
715 - function wpo_is_using_alter_html() {
716 - return (isset($_SERVER['HTTP_ACCEPT']) && false !== strpos($_SERVER['HTTP_ACCEPT'], 'image/webp')); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Executes before WP fully loads, only doing string comparison
743 +if (!function_exists('wpo_is_browser_supports_webp')) :
744 + function wpo_is_browser_supports_webp() {
745 + // Direct Accept header check (works for image sub-resource requests)
746 + if (isset($_SERVER['HTTP_ACCEPT']) && false !== strpos($_SERVER['HTTP_ACCEPT'], 'image/webp')) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Executes before WP fully loads, only doing string comparison
747 + return true;
748 + }
749 +
750 + // Fallback for older Firefox versions, which support WebP but don't send 'image/webp' in the Accept header.
751 + $user_agent = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Executes before WP fully loads, only extracting a version number via regex for a version_compare, not used for output or storage
752 +
753 + if (!empty($user_agent) && preg_match('/Firefox\/([\d\.]+[a-z\d]*)/', $user_agent, $matches)) {
754 + return version_compare(WPO_MIN_FIREFOX_VERSION_FOR_WEBP, $matches[1], '<=');
755 + }
756 +
757 + return false;
717 758 }
718 759 endif;
719 760
720 761 /**
@@ -808,9 +849,11 @@
808 849
809 850 if ($use_gzip) $path .= '.gz';
810 851
811 852 $modified_time = file_exists($path) ? (int) filemtime($path) : time();
812 -
853 +
854 + $modified_time = apply_filters('wpo_cache_modified_time', $modified_time, $path);
855 +
813 856 // Cache has expired, purge and exit.
814 857 if (!empty($GLOBALS['wpo_cache_config']['page_cache_length'])) {
815 858 if (time() > ($GLOBALS['wpo_cache_config']['page_cache_length'] + $modified_time)) {
816 859 wpo_delete_files($path);
@@ -960,9 +1003,9 @@
960 1003 return false;
961 1004 }
962 1005
963 1006 // check in not disabled current user agent
964 - $user_agent = isset($_SERVER['HTTP_USER_AGENT']) && is_string($_SERVER['HTTP_USER_AGENT']) ? htmlspecialchars(stripslashes($_SERVER['HTTP_USER_AGENT'])) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash -- false positive
1007 + $user_agent = isset($_SERVER['HTTP_USER_AGENT']) && is_string($_SERVER['HTTP_USER_AGENT']) ? stripslashes($_SERVER['HTTP_USER_AGENT']) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- value used for comparison only, not output
965 1008 if (!empty($user_agent) && false === wpo_is_accepted_user_agent($user_agent)) {
966 1009 $no_cache_because[] = "In the settings, caching is disabled for matches for this request's user agent";
967 1010 }
968 1011
@@ -1000,8 +1043,12 @@
1000 1043 }
1001 1044 }
1002 1045 }
1003 1046
1047 + if (wpo_restricted_cache_page_with_cart_items()) {
1048 + $no_cache_because[] = 'User has items in WooCommerce cart.';
1049 + }
1050 +
1004 1051 $restricted_page_type_cache = wpo_restricted_cache_page_type('');
1005 1052 if (!empty($restricted_page_type_cache)) {
1006 1053 $no_cache_because[] = $restricted_page_type_cache;
1007 1054 }
@@ -1045,8 +1092,19 @@
1045 1092 }
1046 1093 endif;
1047 1094
1048 1095 /**
1096 + * Checks if the current request has WooCommerce cart items.
1097 + *
1098 + * @return bool Returns true if the user has items in the WooCommerce cart, false otherwise.
1099 + */
1100 +if (!function_exists('wpo_restricted_cache_page_with_cart_items')) :
1101 + function wpo_restricted_cache_page_with_cart_items(): bool {
1102 + return !empty($_COOKIE['woocommerce_items_in_cart']) || !empty($_COOKIE['woocommerce_cart_hash']);
1103 + }
1104 +endif;
1105 +
1106 +/**
1049 1107 * Checks if the current request is a cacheable sitemap request
1050 1108 *
1051 1109 * @return bool
1052 1110 */
@@ -1148,23 +1206,18 @@
1148 1206 function wpo_get_url_path($url = '') {
1149 1207 $url = '' === $url ? wpo_current_url() : $url;
1150 1208 $url_parts = parse_url($url); // phpcs:ignore WordPress.WP.AlternativeFunctions.parse_url_parse_url -- wp_parse_url not available this early
1151 1209
1210 + // Normalize path to avoid issues with encoded characters, and to ensure that the path is consistent.
1211 + if (isset($url_parts['path'])) {
1212 + $url_parts['path'] = wpo_normalize_url_path($url_parts['path']);
1213 + }
1214 +
1152 1215 if (isset($url_parts['path']) && false !== stripos($url_parts['path'], '/index.')) {
1153 1216 $url_parts['path'] = preg_replace('/(.*?)index\.(php|html)(\/.+)/i', '$1index-$2$3', $url_parts['path']);
1154 1217 $url_parts['path'] = preg_replace('/index\.(php|html)/i', 'index-$1', $url_parts['path']);
1155 1218 }
1156 1219
1157 - /*
1158 - * Convert the hexadecimal digits within the percent-encoded triplet to uppercase, to ensure that the path remains
1159 - * consistent. For instance, "example.com/%e0%a6" will be converted to "example.com/%E0%A6".
1160 - */
1161 - if (isset($url_parts['path'])) {
1162 - $url_parts['path'] = preg_replace_callback('/%[0-9A-F]{2}/i', function($matches) {
1163 - return strtoupper($matches[0]);
1164 - }, $url_parts['path']);
1165 - }
1166 -
1167 1220 if (!isset($url_parts['host'])) $url_parts['host'] = '';
1168 1221 if (!isset($url_parts['path'])) $url_parts['path'] = '';
1169 1222
1170 1223 return $url_parts['host'].$url_parts['path'];
@@ -2040,8 +2093,32 @@
2040 2093
2041 2094 return $path;
2042 2095 }
2043 2096 }
2097 +
2098 +/**
2099 + * Normalize url path
2100 + *
2101 + * @param string $url_path
2102 + * @return string
2103 + */
2104 +if (!function_exists('wpo_normalize_url_path')) :
2105 + function wpo_normalize_url_path($url_path) {
2106 + $prev = null;
2107 + $iterations = 0;
2108 +
2109 + while ($url_path !== $prev && 5 > $iterations) {
2110 + $prev = $url_path;
2111 + $url_path = rawurldecode($url_path);
2112 + $iterations++;
2113 + }
2114 +
2115 + $url_path = preg_replace('/\.\.?\//', '-', $url_path); // replace './' and '../' with '-' to prevent directory traversal
2116 + $url_path = strtolower($url_path);
2117 +
2118 + return $url_path;
2119 + }
2120 +endif;
2044 2121
2045 2122 /**
2046 2123 * Get path to wp-config.php when called from WP-CLI.
2047 2124 *