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 +98 -22 4.5.4 → 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();
@@ -707,15 +735,27 @@
707 735 }
708 736 endif;
709 737
710 738 /**
711 - * Check whether webp images using alter html method or not
739 + * Check whether the requesting browser supports WebP images
712 740 *
713 741 * @return bool
714 742 */
715 -if (!function_exists('wpo_is_using_alter_html')) :
716 - function wpo_is_using_alter_html() {
717 - 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;
718 758 }
719 759 endif;
720 760
721 761 /**
@@ -809,9 +849,11 @@
809 849
810 850 if ($use_gzip) $path .= '.gz';
811 851
812 852 $modified_time = file_exists($path) ? (int) filemtime($path) : time();
813 -
853 +
854 + $modified_time = apply_filters('wpo_cache_modified_time', $modified_time, $path);
855 +
814 856 // Cache has expired, purge and exit.
815 857 if (!empty($GLOBALS['wpo_cache_config']['page_cache_length'])) {
816 858 if (time() > ($GLOBALS['wpo_cache_config']['page_cache_length'] + $modified_time)) {
817 859 wpo_delete_files($path);
@@ -961,9 +1003,9 @@
961 1003 return false;
962 1004 }
963 1005
964 1006 // check in not disabled current user agent
965 - $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
966 1008 if (!empty($user_agent) && false === wpo_is_accepted_user_agent($user_agent)) {
967 1009 $no_cache_because[] = "In the settings, caching is disabled for matches for this request's user agent";
968 1010 }
969 1011
@@ -1001,8 +1043,12 @@
1001 1043 }
1002 1044 }
1003 1045 }
1004 1046
1047 + if (wpo_restricted_cache_page_with_cart_items()) {
1048 + $no_cache_because[] = 'User has items in WooCommerce cart.';
1049 + }
1050 +
1005 1051 $restricted_page_type_cache = wpo_restricted_cache_page_type('');
1006 1052 if (!empty($restricted_page_type_cache)) {
1007 1053 $no_cache_because[] = $restricted_page_type_cache;
1008 1054 }
@@ -1046,8 +1092,19 @@
1046 1092 }
1047 1093 endif;
1048 1094
1049 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 +/**
1050 1107 * Checks if the current request is a cacheable sitemap request
1051 1108 *
1052 1109 * @return bool
1053 1110 */
@@ -1149,23 +1206,18 @@
1149 1206 function wpo_get_url_path($url = '') {
1150 1207 $url = '' === $url ? wpo_current_url() : $url;
1151 1208 $url_parts = parse_url($url); // phpcs:ignore WordPress.WP.AlternativeFunctions.parse_url_parse_url -- wp_parse_url not available this early
1152 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 +
1153 1215 if (isset($url_parts['path']) && false !== stripos($url_parts['path'], '/index.')) {
1154 1216 $url_parts['path'] = preg_replace('/(.*?)index\.(php|html)(\/.+)/i', '$1index-$2$3', $url_parts['path']);
1155 1217 $url_parts['path'] = preg_replace('/index\.(php|html)/i', 'index-$1', $url_parts['path']);
1156 1218 }
1157 1219
1158 - /*
1159 - * Convert the hexadecimal digits within the percent-encoded triplet to uppercase, to ensure that the path remains
1160 - * consistent. For instance, "example.com/%e0%a6" will be converted to "example.com/%E0%A6".
1161 - */
1162 - if (isset($url_parts['path'])) {
1163 - $url_parts['path'] = preg_replace_callback('/%[0-9A-F]{2}/i', function($matches) {
1164 - return strtoupper($matches[0]);
1165 - }, $url_parts['path']);
1166 - }
1167 -
1168 1220 if (!isset($url_parts['host'])) $url_parts['host'] = '';
1169 1221 if (!isset($url_parts['path'])) $url_parts['path'] = '';
1170 1222
1171 1223 return $url_parts['host'].$url_parts['path'];
@@ -2041,8 +2093,32 @@
2041 2093
2042 2094 return $path;
2043 2095 }
2044 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;
2045 2121
2046 2122 /**
2047 2123 * Get path to wp-config.php when called from WP-CLI.
2048 2124 *