| @@ -1,5 +1,6 @@ | ||
| 1 | 1 | <?php |
| 2 | + | |
| 2 | 3 | namespace Upress\EzCache; |
| 3 | 4 | |
| 4 | 5 | use MatthiasMullie\Minify\CSS; |
| 5 | 6 | use MatthiasMullie\Minify\JS; |
| @@ -7,8 +8,9 @@ | ||
| 7 | 8 | use RecursiveIteratorIterator; |
| 8 | 9 | use RegexIterator; |
| 9 | 10 | use UnexpectedValueException; |
| 10 | 11 | use Upress\EzCache\BackgroundProcesses\ConvertWebpProcess; |
| 12 | +use Upress\EzCache\FileOptimizer\CombineGoogleFonts; | |
| 11 | 13 | use Upress\EzCache\FileOptimizer\CssMinifier; |
| 12 | 14 | use Upress\EzCache\FileOptimizer\CssCombiner; |
| 13 | 15 | use Upress\EzCache\FileOptimizer\JsMinifier; |
| 14 | 16 | use Upress\EzCache\FileOptimizer\JsCombiner; |
| @@ -13,8 +15,10 @@ | ||
| 13 | 15 | use Upress\EzCache\FileOptimizer\JsMinifier; |
| 14 | 16 | use Upress\EzCache\FileOptimizer\JsCombiner; |
| 15 | 17 | use Upress\EzCache\FileOptimizer\WebpConverter; |
| 16 | 18 | use Upress\EzCache\ThirdParty\Minify_HTML; |
| 19 | +use Upress\EzCache\Utilities\Logger; | |
| 20 | +use Upress\EzCache\PremiumFeatures; | |
| 17 | 21 | |
| 18 | 22 | class Cache { |
| 19 | 23 | protected static $instance; |
| 20 | 24 | protected $settings; |
| @@ -21,9 +25,8 @@ | ||
| 21 | 25 | protected $cache_start_time; |
| 22 | 26 | protected $webp_processor; |
| 23 | 27 | protected $root_cache_dir = WP_CONTENT_DIR . '/cache/ezcache/'; |
| 24 | 28 | |
| 25 | - | |
| 26 | 29 | public static function instance() { |
| 27 | 30 | if ( ! self::$instance ) { |
| 28 | 31 | self::$instance = new self(); |
| 29 | 32 | } |
| @@ -31,10 +34,10 @@ | ||
| 31 | 34 | return self::$instance; |
| 32 | 35 | } |
| 33 | 36 | |
| 34 | 37 | private function __construct() { |
| 35 | - $this->settings = Settings::get_settings(); | |
| 36 | - $this->webp_processor = new ConvertWebpProcess(); | |
| 38 | + $this->settings = Settings::get_settings(); | |
| 39 | + $this->webp_processor = new ConvertWebpProcess(); | |
| 37 | 40 | $this->cache_start_time = microtime( true ); |
| 38 | 41 | } |
| 39 | 42 | |
| 40 | 43 | /** |
| @@ -41,8 +44,9 @@ | ||
| 41 | 44 | * @return string |
| 42 | 45 | */ |
| 43 | 46 | public function get_default_cache_path() { |
| 44 | 47 | $hostname = preg_replace( '/:.*$/', '', $this->get_http_host() ); |
| 48 | + | |
| 45 | 49 | return $this->root_cache_dir . $hostname . '/'; |
| 46 | 50 | } |
| 47 | 51 | |
| 48 | 52 | /** |
| @@ -62,44 +66,50 @@ | ||
| 62 | 66 | return ''; |
| 63 | 67 | } |
| 64 | 68 | |
| 65 | 69 | /** |
| 66 | - * Get the logged in cookie value | |
| 67 | - * @return string | |
| 70 | + * Check if the current user has a log in cookie set (ie. the user is logged in) | |
| 71 | + * @return bool | |
| 68 | 72 | */ |
| 69 | - public function get_cookies_values() { | |
| 70 | - static $string = ''; | |
| 73 | + public function has_login_cookie() { | |
| 74 | + $cookiehash = ''; | |
| 75 | + if ( defined( 'COOKIEHASH' ) ) { | |
| 76 | + $cookiehash = preg_quote( constant( 'COOKIEHASH' ), '|' ); | |
| 77 | + } | |
| 71 | 78 | |
| 72 | - if ( $string != '' ) { | |
| 73 | - return $string; | |
| 79 | + $regex = "|^wordpress_logged_in_{$cookiehash}|"; | |
| 80 | + if ( defined( 'LOGGED_IN_COOKIE' ) ) { | |
| 81 | + $regex = "|^" . preg_quote( constant( 'LOGGED_IN_COOKIE' ), '|' ) . '|'; | |
| 74 | 82 | } |
| 75 | 83 | |
| 84 | + foreach ( $_COOKIE as $key => $value ) { | |
| 85 | + if ( preg_match( $regex, $key ) ) { | |
| 86 | + return true; | |
| 87 | + } | |
| 88 | + } | |
| 89 | + | |
| 90 | + return false; | |
| 91 | + } | |
| 92 | + | |
| 93 | + /** | |
| 94 | + * Check if a cookie is set to show that the current user has left comments and saved their data | |
| 95 | + * @return bool | |
| 96 | + */ | |
| 97 | + public function has_comment_author_cookie() { | |
| 98 | + $cookiehash = ''; | |
| 76 | 99 | if ( defined( 'COOKIEHASH' ) ) { |
| 77 | 100 | $cookiehash = preg_quote( constant( 'COOKIEHASH' ) ); |
| 78 | - } else { | |
| 79 | - $cookiehash = ''; | |
| 80 | 101 | } |
| 81 | 102 | |
| 82 | - $regex = "/^wp-postpass_{$cookiehash}|^comment_author_{$cookiehash}"; | |
| 83 | - if ( defined( 'LOGGED_IN_COOKIE' ) ) { | |
| 84 | - $regex .= "|^" . preg_quote( constant( 'LOGGED_IN_COOKIE' ) ); | |
| 85 | - } else { | |
| 86 | - $regex .= "|^wordpress_logged_in_{$cookiehash}"; | |
| 87 | - } | |
| 88 | - $regex .= "/"; | |
| 89 | - while ( $key = key( $_COOKIE ) ) { | |
| 103 | + $regex = "/^wp-postpass_{$cookiehash}|^comment_author_{$cookiehash}/"; | |
| 104 | + | |
| 105 | + foreach ( $_COOKIE as $key => $value ) { | |
| 90 | 106 | if ( preg_match( $regex, $key ) ) { |
| 91 | - $string .= $_COOKIE[ $key ] . ","; | |
| 107 | + return true; | |
| 92 | 108 | } |
| 93 | - next( $_COOKIE ); | |
| 94 | 109 | } |
| 95 | - reset( $_COOKIE ); | |
| 96 | 110 | |
| 97 | - if ( $string != '' ) { | |
| 98 | - $string = md5( $string ); | |
| 99 | - } | |
| 100 | - | |
| 101 | - return $string; | |
| 111 | + return false; | |
| 102 | 112 | } |
| 103 | 113 | |
| 104 | 114 | /** |
| 105 | 115 | * Check if the request supports gzip compression |
| @@ -106,8 +116,12 @@ | ||
| 106 | 116 | * |
| 107 | 117 | * @return bool |
| 108 | 118 | */ |
| 109 | 119 | public function gzip_accepted() { |
| 120 | + if ( defined( 'EZCACHE_DISABLE_GZIP' ) && EZCACHE_DISABLE_GZIP ) { | |
| 121 | + return false; | |
| 122 | + } | |
| 123 | + | |
| 110 | 124 | return isset( $_SERVER['HTTP_ACCEPT_ENCODING'] ) && false !== strpos( $_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip' ); |
| 111 | 125 | } |
| 112 | 126 | |
| 113 | 127 | /** |
| @@ -130,9 +144,9 @@ | ||
| 130 | 144 | } |
| 131 | 145 | |
| 132 | 146 | $script = isset( $_SERVER['PHP_SELF'] ) ? basename( $_SERVER['PHP_SELF'] ) : ''; |
| 133 | 147 | if ( $script !== 'index.php' ) { |
| 134 | - if ( in_array( $script, array( 'wp-login.php', 'xmlrpc.php', 'wp-cron.php' ) ) ) { | |
| 148 | + if ( in_array( $script, [ 'wp-login.php', 'xmlrpc.php', 'wp-cron.php' ] ) ) { | |
| 135 | 149 | return true; |
| 136 | 150 | } elseif ( defined( 'DOING_CRON' ) && DOING_CRON ) { |
| 137 | 151 | return true; |
| 138 | 152 | } elseif ( PHP_SAPI == 'cli' || ( defined( 'WP_CLI' ) && WP_CLI ) ) { |
| @@ -143,51 +157,126 @@ | ||
| 143 | 157 | return false; |
| 144 | 158 | } |
| 145 | 159 | |
| 146 | 160 | /** |
| 161 | + * Return the relative URL based on the url provided or false if the url is not on this website | |
| 162 | + * @param string $url | |
| 163 | + * | |
| 164 | + * @return string|bool | |
| 165 | + */ | |
| 166 | + public function get_relative_url( $url ) { | |
| 167 | + $site_url = site_url(); | |
| 168 | + if ( false === strpos( $url, $site_url ) ) { | |
| 169 | + if ( preg_match( '`^(https?:)?//([^/]+)(/.*)?$`i', $url, $matches ) ) { | |
| 170 | + $url = isset( $matches[3] ) ? $matches[3] : ''; | |
| 171 | + } | |
| 172 | + } else { | |
| 173 | + $url = str_replace( $site_url, '', $url ); | |
| 174 | + if ( 0 !== strpos( $url, '/' ) ) { | |
| 175 | + $url = '/' . $url; | |
| 176 | + } | |
| 177 | + } | |
| 178 | + | |
| 179 | + if ( preg_match( '/^https?:\/\//i', $url ) ) { | |
| 180 | + return false; | |
| 181 | + } | |
| 182 | + | |
| 183 | + return $url; | |
| 184 | + } | |
| 185 | + | |
| 186 | + /** | |
| 147 | 187 | * Should we serve the cached file |
| 148 | 188 | * |
| 149 | 189 | * @return bool |
| 150 | 190 | */ |
| 151 | 191 | public function should_serve_cached_data() { |
| 152 | - $settings = $this->settings; | |
| 153 | - | |
| 154 | - if ( $settings->no_cache_known_users && $this->get_cookies_values() ) { | |
| 192 | + // Dev Mode — bypass cache entirely | |
| 193 | + if ( self::is_dev_mode_active() ) { | |
| 155 | 194 | return false; |
| 156 | 195 | } |
| 196 | + if ( defined( 'WP_CLI' ) && WP_CLI ) { | |
| 197 | + return false; | |
| 198 | + } | |
| 199 | + if ( defined( 'DOING_CRON' ) && DOING_CRON ) { | |
| 200 | + return false; | |
| 201 | + } | |
| 202 | + if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) { | |
| 203 | + return false; | |
| 204 | + } | |
| 205 | + if ( defined( 'JSON_REQUEST' ) && JSON_REQUEST ) { | |
| 206 | + return false; | |
| 207 | + } | |
| 208 | + if ( defined( 'WC_API_REQUEST' ) && WC_API_REQUEST ) { | |
| 209 | + return false; | |
| 210 | + } | |
| 211 | + if ( defined( 'WP_ADMIN' ) && WP_ADMIN ) { | |
| 212 | + return false; | |
| 213 | + } | |
| 214 | + if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) { | |
| 215 | + return false; | |
| 216 | + } | |
| 217 | + if ( defined( 'WP_USE_THEMES' ) && false === WP_USE_THEMES ) { | |
| 218 | + return false; | |
| 219 | + } | |
| 157 | 220 | |
| 158 | - if ( ( isset( $_SERVER['REQUEST_METHOD'] ) && in_array( $_SERVER['REQUEST_METHOD'], [ | |
| 221 | + if ( ! isset( $_SERVER['REQUEST_METHOD'] ) || ( isset( $_SERVER['REQUEST_METHOD'] ) && in_array( $_SERVER['REQUEST_METHOD'], [ | |
| 222 | + 'HEAD', | |
| 159 | 223 | 'POST', |
| 160 | 224 | 'PUT', |
| 161 | 225 | 'PATCH', |
| 162 | - 'DELETE' | |
| 226 | + 'DELETE', | |
| 163 | 227 | ] ) ) || isset( $_GET['customize_changeset_uuid'] ) || isset( $_POST['wp_customize'] ) ) { |
| 164 | 228 | return false; |
| 165 | 229 | } |
| 166 | 230 | |
| 231 | + $settings = $this->settings; | |
| 232 | + | |
| 233 | + if ( $settings->no_cache_known_users && $this->has_login_cookie() ) { | |
| 234 | + return false; | |
| 235 | + } | |
| 236 | + | |
| 237 | + if ( $settings->no_cache_comment_authors && $this->has_comment_author_cookie() ) { | |
| 238 | + return false; | |
| 239 | + } | |
| 240 | + | |
| 167 | 241 | if ( $this->is_backend() ) { |
| 168 | 242 | return false; |
| 169 | 243 | } |
| 170 | 244 | |
| 171 | 245 | // Don't cache with variables but the cache is enabled if the visitor comes from an RSS feed, a Facebook action or Google Adsense tracking |
| 172 | - if ( $settings->no_cache_query_params && ! empty( $_GET ) | |
| 173 | - && ! isset( $_GET['utm_source'], $_GET['utm_medium'], $_GET['utm_campaign'] ) | |
| 174 | - && ! isset( $_GET['utm_expid'] ) | |
| 175 | - && ! isset( $_GET['fb_action_ids'], $_GET['fb_action_types'], $_GET['fb_source'] ) | |
| 176 | - && ! isset( $_GET['gclid'] ) | |
| 177 | - && ! isset( $_GET['permalink_name'] ) | |
| 178 | - && ! isset( $_GET['lp-variation-id'] ) | |
| 179 | - && ! isset( $_GET['lang'] ) | |
| 180 | - && ! isset( $_GET['s'] ) | |
| 181 | - && ! isset( $_GET['age-verified'] ) | |
| 182 | - && ! isset( $_GET['ao_noptimize'] ) | |
| 183 | - && ! isset( $_GET['usqp'] ) | |
| 246 | + if ( | |
| 247 | + ( | |
| 248 | + $settings->no_cache_query_params | |
| 249 | + && ! empty( $_GET ) | |
| 250 | + && ! ( | |
| 251 | + isset( $_GET['utm_source'], $_GET['utm_medium'], $_GET['utm_campaign'] ) | |
| 252 | + || isset( $_GET['utm_expid'] ) | |
| 253 | + || isset( $_GET['fb_action_ids'], $_GET['fb_action_types'], $_GET['fb_source'] ) | |
| 254 | + || isset( $_GET['gclid'] ) | |
| 255 | + ) | |
| 256 | + ) || ( | |
| 257 | + isset( $_GET['permalink_name'] ) | |
| 258 | + || isset( $_GET['lp-variation-id'] ) | |
| 259 | + || isset( $_GET['lang'] ) | |
| 260 | + || isset( $_GET['s'] ) | |
| 261 | + || isset( $_GET['age-verified'] ) | |
| 262 | + || isset( $_GET['ao_noptimize'] ) | |
| 263 | + || isset( $_GET['usqp'] ) | |
| 264 | + || isset( $_GET['woo_ajax'] ) | |
| 265 | + ) | |
| 184 | 266 | ) { |
| 267 | + | |
| 185 | 268 | return false; |
| 186 | 269 | } |
| 187 | 270 | |
| 188 | - if ( defined( 'DOING_CRON' ) && DOING_CRON ) { | |
| 189 | - return false; | |
| 271 | + // Don't cache pages where the rejected cookies are defined | |
| 272 | + if ( ! empty( $settings->rejected_cookies ) ) { | |
| 273 | + $rejected_cookies = preg_split( "/\\r\\n|\\r|\\n/u", trim( $settings->rejected_cookies ), - 1, PREG_SPLIT_NO_EMPTY ); | |
| 274 | + $rejected_cookies = array_filter( $rejected_cookies ); | |
| 275 | + | |
| 276 | + if ( preg_match( '#(' . implode( '|', $rejected_cookies ) . ')#', var_export( $_COOKIE, true ) ) ) { | |
| 277 | + return false; | |
| 278 | + } | |
| 190 | 279 | } |
| 191 | 280 | |
| 192 | 281 | return true; |
| 193 | 282 | } |
| @@ -198,8 +287,10 @@ | ||
| 198 | 287 | * |
| 199 | 288 | * @return bool |
| 200 | 289 | */ |
| 201 | 290 | public function should_save_cache() { |
| 291 | + global $wp_query; | |
| 292 | + | |
| 202 | 293 | if ( ! $this->should_serve_cached_data() ) { |
| 203 | 294 | return false; |
| 204 | 295 | } |
| 205 | 296 | |
| @@ -204,21 +295,8 @@ | ||
| 204 | 295 | } |
| 205 | 296 | |
| 206 | 297 | $settings = $this->settings; |
| 207 | 298 | |
| 208 | - if ( ( defined( 'DOING_CRON' ) && DOING_CRON ) ) { | |
| 209 | - return false; | |
| 210 | - } | |
| 211 | - if ( ( defined( 'REST_REQUEST' ) && REST_REQUEST ) ) { | |
| 212 | - return false; | |
| 213 | - } | |
| 214 | - if ( ( defined( 'JSON_REQUEST' ) && JSON_REQUEST ) ) { | |
| 215 | - return false; | |
| 216 | - } | |
| 217 | - if ( ( defined( 'WC_API_REQUEST' ) && WC_API_REQUEST ) ) { | |
| 218 | - return false; | |
| 219 | - } | |
| 220 | - | |
| 221 | 299 | // check if we have any errors or otherwise settings preventing caching |
| 222 | 300 | $error = error_get_last(); |
| 223 | 301 | if ( null !== $error && ( $error['type'] & ( E_ERROR | E_CORE_ERROR | E_PARSE | E_COMPILE_ERROR | E_USER_ERROR ) ) ) { |
| 224 | 302 | return false; |
| @@ -261,10 +339,16 @@ | ||
| 261 | 339 | } |
| 262 | 340 | if ( $settings->bypass_cache->author && is_author() ) { |
| 263 | 341 | return false; |
| 264 | 342 | } |
| 343 | + if ( function_exists( 'is_checkout' ) && is_checkout() ) { | |
| 344 | + return false; | |
| 345 | + } | |
| 346 | + if ( function_exists( 'is_cart' ) && is_cart() ) { | |
| 347 | + return false; | |
| 348 | + } | |
| 265 | 349 | |
| 266 | - if ( is_robots() || get_query_var( 'sitemap' ) || get_query_var( 'xsl' ) || get_query_var( 'xml_sitemap' ) ) { | |
| 350 | + if ( is_null( $wp_query ) || is_robots() || get_query_var( 'sitemap' ) || get_query_var( 'xsl' ) || get_query_var( 'xml_sitemap' ) ) { | |
| 267 | 351 | return false; |
| 268 | 352 | } |
| 269 | 353 | |
| 270 | 354 | if ( isset( $_GET['preview'] ) || isset( $_POST['wp_customize'] ) ) { |
| @@ -270,18 +354,35 @@ | ||
| 270 | 354 | if ( isset( $_GET['preview'] ) || isset( $_POST['wp_customize'] ) ) { |
| 271 | 355 | return false; |
| 272 | 356 | } |
| 273 | 357 | |
| 358 | + // Never cache requests carrying a nonce or an action parameter. These are | |
| 359 | + // either one-time/per-request tokens (e.g. _wpnonce) or non-idempotent | |
| 360 | + // actions (add to cart, AJAX). Caching them is both wasteful (a new cache | |
| 361 | + // variation per value) and incorrect — a cached page could serve one user's | |
| 362 | + // nonce to another. The list is filterable for site-specific additions. | |
| 363 | + $bypass_query_params = apply_filters( 'ezcache_bypass_query_params', [ | |
| 364 | + '_wpnonce', 'wc-ajax', 'add-to-cart', 'remove_item', 'removed_item', | |
| 365 | + 'action', 'doing_wp_cron', 'add_to_wishlist', | |
| 366 | + ] ); | |
| 367 | + foreach ( $bypass_query_params as $param ) { | |
| 368 | + if ( isset( $_GET[ $param ] ) ) { | |
| 369 | + return false; | |
| 370 | + } | |
| 371 | + } | |
| 372 | + | |
| 274 | 373 | if ( get_post_meta( get_the_ID(), '_ezcache_do_not_cache_post', true ) ) { |
| 275 | 374 | return false; |
| 276 | 375 | } |
| 277 | 376 | |
| 278 | 377 | // check useragent |
| 279 | - $rejected_useragents = preg_split( "/\\r\\n|\\r|\\n/u", trim( $settings->rejected_user_agent ), -1, PREG_SPLIT_NO_EMPTY ); | |
| 378 | + $rejected_useragents = preg_split( "/\\r\\n|\\r|\\n/u", trim( $settings->rejected_user_agent ), - 1, PREG_SPLIT_NO_EMPTY ); | |
| 280 | 379 | $rejected_useragents = array_filter( $rejected_useragents ); |
| 281 | 380 | if ( ! empty( $_SERVER['HTTP_USER_AGENT'] ) ) { |
| 282 | - foreach( $rejected_useragents as $ua ) { | |
| 283 | - if ( empty( $ua ) ) continue; | |
| 381 | + foreach ( $rejected_useragents as $ua ) { | |
| 382 | + if ( empty( $ua ) ) { | |
| 383 | + continue; | |
| 384 | + } | |
| 284 | 385 | |
| 285 | 386 | if ( false !== strpos( $_SERVER['HTTP_USER_AGENT'], trim( $ua ) ) ) { |
| 286 | 387 | return false; |
| 287 | 388 | } |
| @@ -288,17 +389,26 @@ | ||
| 288 | 389 | } |
| 289 | 390 | } |
| 290 | 391 | |
| 291 | 392 | // check URL |
| 292 | - $rejected_uris = preg_split( "/\\r\\n|\\r|\\n/u", trim( $settings->rejected_uri ), -1, PREG_SPLIT_NO_EMPTY ); | |
| 393 | + $rejected_uris = preg_split( "/\\r\\n|\\r|\\n/u", trim( $settings->rejected_uri ), - 1, PREG_SPLIT_NO_EMPTY ); | |
| 293 | 394 | $rejected_uris = array_filter( $rejected_uris ); |
| 294 | - $domain = untrailingslashit( home_url() ); | |
| 395 | + $domain = untrailingslashit( home_url() ); | |
| 295 | 396 | if ( ! empty( $_SERVER['REQUEST_URI'] ) ) { |
| 296 | - foreach( $rejected_uris as $url ) { | |
| 397 | + foreach ( $rejected_uris as $url ) { | |
| 297 | 398 | $url = str_replace( $domain, '', $url ); |
| 298 | 399 | $url = '/' . trim( $url, '/' ); |
| 299 | - $url = str_replace( ['\/*', '*'], ['\/?.*?', '.*?'], preg_quote( $url, '/' ) ); | |
| 300 | - if ( @preg_match( "/^{$url}\/?$/", $_SERVER['REQUEST_URI']) ) { | |
| 400 | + // Build the wildcard pattern by escaping each literal segment and | |
| 401 | + // joining the segments with `.*?`. This has to be done around | |
| 402 | + // preg_quote(), not after it: running preg_quote() first turns every | |
| 403 | + // `*` into `\*`, so a later str_replace('*', '.*?') corrupts it into | |
| 404 | + // `\.*?` (zero-or-more literal dots) and the wildcard silently never | |
| 405 | + // matches — which broke every pattern with a trailing or mid `*`. | |
| 406 | + $regex = implode( '.*?', array_map( | |
| 407 | + function ( $part ) { return preg_quote( $part, '/' ); }, | |
| 408 | + explode( '*', $url ) | |
| 409 | + ) ); | |
| 410 | + if ( @preg_match( "/^{$regex}\/?$/u", urldecode( $_SERVER['REQUEST_URI'] ) ) ) { | |
| 301 | 411 | return false; |
| 302 | 412 | } |
| 303 | 413 | } |
| 304 | 414 | } |
| @@ -367,11 +477,11 @@ | ||
| 367 | 477 | 'BlackBerry9530', |
| 368 | 478 | 'LG-TU915 Obigo', |
| 369 | 479 | 'LGE VX', |
| 370 | 480 | 'webOS', |
| 371 | - 'Nokia5800' | |
| 481 | + 'Nokia5800', | |
| 372 | 482 | ] ); |
| 373 | - $user_agent = strtolower( $_SERVER['HTTP_USER_AGENT'] ); | |
| 483 | + $user_agent = strtolower( $_SERVER['HTTP_USER_AGENT'] ); | |
| 374 | 484 | foreach ( $mobile_browsers as $browser ) { |
| 375 | 485 | if ( strstr( $user_agent, trim( strtolower( $browser ) ) ) ) { |
| 376 | 486 | return $user_agent; |
| 377 | 487 | } |
| @@ -472,9 +582,9 @@ | ||
| 472 | 582 | 'webc', |
| 473 | 583 | 'winw', |
| 474 | 584 | 'winw', |
| 475 | 585 | 'xda', |
| 476 | - 'xda-' | |
| 586 | + 'xda-', | |
| 477 | 587 | ] ); |
| 478 | 588 | foreach ( $browser_prefixes as $prefix ) { |
| 479 | 589 | if ( substr( $user_agent, 0, 4 ) == $prefix ) { |
| 480 | 590 | return $prefix; |
| @@ -516,18 +626,20 @@ | ||
| 516 | 626 | * Get the cache directory URL for the current post |
| 517 | 627 | * |
| 518 | 628 | * @param int $post_id |
| 519 | 629 | * |
| 630 | + * @param null|string $url | |
| 631 | + * | |
| 520 | 632 | * @return mixed|string |
| 521 | 633 | */ |
| 522 | - public function get_current_url_cache_dir( $post_id = 0 ) { | |
| 523 | - static $url_cache_dir = array(); | |
| 634 | + public function get_current_url_cache_dir( $post_id = 0, $url = null ) { | |
| 635 | + static $url_cache_dir = []; | |
| 524 | 636 | |
| 525 | 637 | if ( isset( $url_cache_dir[ $post_id ] ) ) { |
| 526 | 638 | return $url_cache_dir[ $post_id ]; |
| 527 | 639 | } |
| 528 | 640 | |
| 529 | - $uri = strtolower( $_SERVER['REQUEST_URI'] ); | |
| 641 | + $uri = strtolower( $url ? ( '/' . ltrim( $url, '/' ) ) : $_SERVER['REQUEST_URI'] ); | |
| 530 | 642 | |
| 531 | 643 | $DONOTREMEMBER = 0; |
| 532 | 644 | if ( 0 !== $post_id ) { |
| 533 | 645 | $site_url = site_url(); |
| @@ -552,12 +664,12 @@ | ||
| 552 | 664 | $uri = $this->deep_replace( |
| 553 | 665 | [ |
| 554 | 666 | '..', |
| 555 | 667 | '\\', |
| 556 | - 'index.php' | |
| 668 | + 'index.php', | |
| 557 | 669 | ], |
| 558 | 670 | preg_replace( |
| 559 | - '/[ <>\'\"\r\n\t\(\)]/', | |
| 671 | + '/[ <>\'\"\r\n\t()]/', | |
| 560 | 672 | '', |
| 561 | 673 | preg_replace( "/(\?.*)?(#.*)?$/", '', $uri ) |
| 562 | 674 | ) |
| 563 | 675 | ); |
| @@ -576,12 +688,14 @@ | ||
| 576 | 688 | * Get the cache directory path |
| 577 | 689 | * |
| 578 | 690 | * @param int $postid |
| 579 | 691 | * |
| 692 | + * @param null|string $url | |
| 693 | + * | |
| 580 | 694 | * @return string |
| 581 | 695 | */ |
| 582 | - public function get_real_cache_dir( $postid = 0 ) { | |
| 583 | - return $this->get_default_cache_path() . $this->get_current_url_cache_dir( $postid ); | |
| 696 | + public function get_real_cache_dir( $postid = 0, $url = null ) { | |
| 697 | + return $this->get_default_cache_path() . $this->get_current_url_cache_dir( $postid, $url ); | |
| 584 | 698 | } |
| 585 | 699 | |
| 586 | 700 | /** |
| 587 | 701 | * Get the full cache file path |
| @@ -587,12 +701,14 @@ | ||
| 587 | 701 | * Get the full cache file path |
| 588 | 702 | * |
| 589 | 703 | * @param int $postid |
| 590 | 704 | * |
| 705 | + * @param null|string $url | |
| 706 | + * | |
| 591 | 707 | * @return string |
| 592 | 708 | */ |
| 593 | - public function get_cache_file_path( $postid = 0 ) { | |
| 594 | - return $this->get_real_cache_dir( $postid ) . $this->get_cache_filename(); | |
| 709 | + public function get_cache_file_path( $postid = 0, $url = null ) { | |
| 710 | + return $this->get_real_cache_dir( $postid, $url ) . $this->get_cache_filename(); | |
| 595 | 711 | } |
| 596 | 712 | |
| 597 | 713 | /** |
| 598 | 714 | * Get the filename for the cached file |
| @@ -598,8 +714,116 @@ | ||
| 598 | 714 | * Get the filename for the cached file |
| 599 | 715 | * |
| 600 | 716 | * @return string |
| 601 | 717 | */ |
| 718 | + /** | |
| 719 | + * Lowercased list of query-string parameters to ignore when building the | |
| 720 | + * cache key. Only meaningful when the ignore_query_params setting is on. | |
| 721 | + * | |
| 722 | + * @return array | |
| 723 | + */ | |
| 724 | + private function get_ignored_query_params() { | |
| 725 | + static $cached = null; | |
| 726 | + if ( null !== $cached ) { | |
| 727 | + return $cached; | |
| 728 | + } | |
| 729 | + $raw = isset( $this->settings->ignored_query_params_list ) ? (string) $this->settings->ignored_query_params_list : ''; | |
| 730 | + $list = preg_split( '/[\s,]+/', strtolower( $raw ), -1, PREG_SPLIT_NO_EMPTY ); | |
| 731 | + | |
| 732 | + /** | |
| 733 | + * Filters the query-string parameters ignored when building the cache key. | |
| 734 | + * | |
| 735 | + * @param array $list Lowercased parameter names. | |
| 736 | + */ | |
| 737 | + $list = apply_filters( 'ezcache_ignored_query_params', $list ); | |
| 738 | + $cached = array_values( array_unique( array_map( 'strtolower', (array) $list ) ) ); | |
| 739 | + | |
| 740 | + return $cached; | |
| 741 | + } | |
| 742 | + | |
| 743 | + /** | |
| 744 | + * Normalize a raw query string for cache-key purposes. When the | |
| 745 | + * ignore_query_params feature is on, drop the ignored (tracking) parameters | |
| 746 | + * and sort the rest so different orderings and tracking values map to the | |
| 747 | + * same cache entry. Returns '' when nothing meaningful remains. | |
| 748 | + * | |
| 749 | + * @param string $query_string | |
| 750 | + * @return string | |
| 751 | + */ | |
| 752 | + private function normalize_query_string( $query_string ) { | |
| 753 | + if ( '' === (string) $query_string ) { | |
| 754 | + return ''; | |
| 755 | + } | |
| 756 | + if ( empty( $this->settings->ignore_query_params ) ) { | |
| 757 | + return $query_string; // feature off — behaviour unchanged | |
| 758 | + } | |
| 759 | + parse_str( (string) $query_string, $params ); | |
| 760 | + if ( empty( $params ) ) { | |
| 761 | + return ''; | |
| 762 | + } | |
| 763 | + $ignored = $this->get_ignored_query_params(); | |
| 764 | + foreach ( array_keys( $params ) as $key ) { | |
| 765 | + if ( $this->query_param_is_ignored( strtolower( $key ), $ignored ) ) { | |
| 766 | + unset( $params[ $key ] ); | |
| 767 | + } | |
| 768 | + } | |
| 769 | + if ( empty( $params ) ) { | |
| 770 | + return ''; | |
| 771 | + } | |
| 772 | + ksort( $params ); | |
| 773 | + | |
| 774 | + return http_build_query( $params ); | |
| 775 | + } | |
| 776 | + | |
| 777 | + /** | |
| 778 | + * Whether a (lowercased) query parameter name matches the ignore list. | |
| 779 | + * Supports exact names and trailing-"*" prefix patterns (e.g. "utm_*"). | |
| 780 | + * A bare "*" is skipped to avoid accidentally dropping every parameter. | |
| 781 | + * | |
| 782 | + * @param string $key Lowercased parameter name. | |
| 783 | + * @param array $ignored Lowercased ignore patterns. | |
| 784 | + * @return bool | |
| 785 | + */ | |
| 786 | + private function query_param_is_ignored( $key, $ignored ) { | |
| 787 | + foreach ( $ignored as $pattern ) { | |
| 788 | + if ( '' === $pattern || '*' === $pattern ) { | |
| 789 | + continue; | |
| 790 | + } | |
| 791 | + if ( '*' === substr( $pattern, -1 ) ) { | |
| 792 | + $prefix = substr( $pattern, 0, -1 ); | |
| 793 | + if ( '' !== $prefix && 0 === strpos( $key, $prefix ) ) { | |
| 794 | + return true; | |
| 795 | + } | |
| 796 | + } elseif ( $key === $pattern ) { | |
| 797 | + return true; | |
| 798 | + } | |
| 799 | + } | |
| 800 | + | |
| 801 | + return false; | |
| 802 | + } | |
| 803 | + | |
| 804 | + /** | |
| 805 | + * Build the full-page (Redis) cache URL for the current request, applying | |
| 806 | + * the same query-string normalization used for the disk cache key. | |
| 807 | + * | |
| 808 | + * @return string | |
| 809 | + */ | |
| 810 | + private function build_fullpage_url() { | |
| 811 | + $scheme = ( is_ssl() ? 'https://' : 'http://' ); | |
| 812 | + $host = $_SERVER['HTTP_HOST'] ?? ''; | |
| 813 | + $uri = $_SERVER['REQUEST_URI'] ?? '/'; | |
| 814 | + $path = $uri; | |
| 815 | + $qs = ''; | |
| 816 | + $pos = strpos( $uri, '?' ); | |
| 817 | + if ( false !== $pos ) { | |
| 818 | + $path = substr( $uri, 0, $pos ); | |
| 819 | + $qs = substr( $uri, $pos + 1 ); | |
| 820 | + } | |
| 821 | + $norm = $this->normalize_query_string( $qs ); | |
| 822 | + | |
| 823 | + return $scheme . $host . $path . ( '' !== $norm ? '?' . $norm : '' ); | |
| 824 | + } | |
| 825 | + | |
| 602 | 826 | public function get_cache_filename() { |
| 603 | 827 | $settings = $this->settings; |
| 604 | 828 | |
| 605 | 829 | // Add support for https and http caching |
| @@ -613,15 +837,20 @@ | ||
| 613 | 837 | $extra_str .= '-mobile'; |
| 614 | 838 | } |
| 615 | 839 | } |
| 616 | 840 | |
| 617 | - if ( $settings->enable_webp_support && $this->webp_accepted()) { | |
| 841 | + if ( $settings->enable_webp_support && $this->webp_accepted() ) { | |
| 618 | 842 | $extra_str .= '-webp'; |
| 619 | 843 | } |
| 620 | 844 | |
| 621 | 845 | $filename = 'index'; |
| 622 | 846 | if ( ! empty( $_SERVER['QUERY_STRING'] ) ) { |
| 623 | - $filename = md5( $_SERVER['QUERY_STRING'] ); | |
| 847 | + $normalized = $this->normalize_query_string( $_SERVER['QUERY_STRING'] ); | |
| 848 | + // When every parameter was ignored, fall back to 'index' so the | |
| 849 | + // request maps to the same cache entry as the clean URL. | |
| 850 | + if ( '' !== $normalized ) { | |
| 851 | + $filename = md5( $normalized ); | |
| 852 | + } | |
| 624 | 853 | } |
| 625 | 854 | |
| 626 | 855 | return $filename . $extra_str . '.html'; |
| 627 | 856 | } |
| @@ -633,14 +862,32 @@ | ||
| 633 | 862 | if ( ! $this->should_serve_cached_data() ) { |
| 634 | 863 | return; |
| 635 | 864 | } |
| 636 | 865 | |
| 637 | - $cache_file = $this->get_cache_file_path(); | |
| 866 | + // ── Redis Full-Page Cache fast path ──────────────────── | |
| 867 | + // When enabled, try Redis first. A hit is sub-millisecond and skips | |
| 868 | + // the disk read entirely. On miss we fall through to the disk path | |
| 869 | + // below (and the response handler in maybe_write_cache_file will | |
| 870 | + // populate Redis for next time). | |
| 871 | + if ( | |
| 872 | + ! empty( $this->settings->enable_redis_fullpage ) | |
| 873 | + && class_exists( '\\Upress\\EzCache\\RedisObjectCache' ) | |
| 874 | + ) { | |
| 875 | + $current_url = $this->build_fullpage_url(); | |
| 876 | + $cached_html = \Upress\EzCache\RedisObjectCache::get_page( $current_url ); | |
| 877 | + if ( false !== $cached_html && '' !== $cached_html ) { | |
| 878 | + header( 'X-Cached-With: ezCache (Redis)' ); | |
| 879 | + header( 'Vary: Accept-Encoding, Cookie' ); | |
| 880 | + echo $cached_html; | |
| 881 | + exit; | |
| 882 | + } | |
| 883 | + } | |
| 884 | + | |
| 885 | + $cache_file = $this->get_cache_file_path(); | |
| 638 | 886 | $gzip_accepted = $this->gzip_accepted(); |
| 639 | 887 | |
| 640 | - $serve_gzip = $gzip_accepted && file_exists( $cache_file . '.gz' ); | |
| 641 | - $cache_file = $serve_gzip ? ($cache_file . '.gz') : $cache_file; | |
| 642 | - $filesize = file_exists( $cache_file ) ? @filesize( $cache_file ) : false; | |
| 888 | + $cache_file = $cache_file . '.gz'; | |
| 889 | + $filesize = file_exists( $cache_file ) ? @filesize( $cache_file ) : false; | |
| 643 | 890 | |
| 644 | 891 | if ( ! $filesize ) { |
| 645 | 892 | // the file is empty, we have nothing to serve |
| 646 | 893 | return; |
| @@ -647,9 +894,8 @@ | ||
| 647 | 894 | } |
| 648 | 895 | |
| 649 | 896 | header( "X-Cached-With: ezCache" ); |
| 650 | 897 | header( "Vary: Accept-Encoding, Cookie" ); |
| 651 | -// header( "Content-Length: {$filesize}" ); | |
| 652 | 898 | header( 'Last-Modified: ' . gmdate( 'D, d M Y H:i:s', filemtime( $cache_file ) ) . ' GMT' ); |
| 653 | 899 | |
| 654 | 900 | // Getting If-Modified-Since headers sent by the client. |
| 655 | 901 | if ( function_exists( 'apache_request_headers' ) ) { |
| @@ -666,19 +912,56 @@ | ||
| 666 | 912 | exit; |
| 667 | 913 | } |
| 668 | 914 | |
| 669 | 915 | // Serve the cache if file isn't store in the client browser cache. |
| 670 | - if ( $serve_gzip ) { | |
| 916 | + // if the browser does not support gzip read the file and output it without gzip encoding | |
| 917 | + if ( ! $gzip_accepted ) { | |
| 671 | 918 | readgzfile( $cache_file ); |
| 672 | 919 | exit; |
| 673 | 920 | } |
| 674 | 921 | |
| 922 | + // otherwise output the gzipped file as-is | |
| 923 | + header( "Content-Length: {$filesize}" ); | |
| 924 | + header( "Content-Encoding: gzip" ); | |
| 675 | 925 | readfile( $cache_file ); |
| 676 | 926 | exit; |
| 677 | 927 | } |
| 678 | 928 | |
| 929 | + public function do_frontend_optimizations() { | |
| 930 | + if ( ! $this->should_serve_cached_data() ) { | |
| 931 | + return; | |
| 932 | + } | |
| 933 | + | |
| 934 | + $settings = $this->settings; | |
| 935 | + | |
| 936 | + if ( isset( $settings->disable_wp_emoji ) && $settings->disable_wp_emoji ) { | |
| 937 | + add_action( 'init', function () { | |
| 938 | + remove_action( 'admin_print_styles', 'print_emoji_styles' ); | |
| 939 | + remove_action( 'wp_head', 'print_emoji_detection_script', 7 ); | |
| 940 | + remove_action( 'admin_print_scripts', 'print_emoji_detection_script' ); | |
| 941 | + remove_action( 'wp_print_styles', 'print_emoji_styles' ); | |
| 942 | + remove_filter( 'wp_mail', 'wp_staticize_emoji_for_email' ); | |
| 943 | + remove_filter( 'the_content_feed', 'wp_staticize_emoji' ); | |
| 944 | + remove_filter( 'comment_text_rss', 'wp_staticize_emoji' ); | |
| 945 | + add_filter( 'emoji_svg_url', '__return_false' ); | |
| 946 | + }, 999 ); | |
| 947 | + } | |
| 948 | + | |
| 949 | + | |
| 950 | + if ( ! empty( $settings->critical_css ) ) { | |
| 951 | + add_action( 'wp_enqueue_scripts', [ $this, 'enqueue_critical_css' ], PHP_INT_MAX ); | |
| 952 | + } | |
| 953 | + } | |
| 954 | + | |
| 955 | + public function enqueue_critical_css() { | |
| 956 | + wp_register_style( 'ezcache-critical-css', false ); | |
| 957 | + wp_enqueue_style( 'ezcache-critical-css' ); | |
| 958 | + wp_add_inline_style( 'ezcache-critical-css', $this->settings->critical_css ); | |
| 959 | + } | |
| 960 | + | |
| 679 | 961 | /** |
| 680 | 962 | * Write cache file if we need to |
| 963 | + * @noinspection PhpUnused | |
| 681 | 964 | */ |
| 682 | 965 | public function maybe_write_cache_file() { |
| 683 | 966 | if ( ! $this->should_serve_cached_data() ) { |
| 684 | 967 | return; |
| @@ -701,17 +984,41 @@ | ||
| 701 | 984 | if ( ! $this->should_save_cache() ) { |
| 702 | 985 | return $buffer; |
| 703 | 986 | } |
| 704 | 987 | |
| 705 | - $real_cache_dir = $this->get_real_cache_dir(); | |
| 706 | - $cache_file = $this->get_cache_file_path(); | |
| 988 | + // Only process and cache real HTML responses. Non-HTML output — text/plain | |
| 989 | + // (IndexNow key files, llms.txt), RSS/Atom feeds, JSON, etc. — must pass | |
| 990 | + // through untouched: appending the footer comment or running the HTML | |
| 991 | + // transforms (minify, WebP, combine) on it corrupts the content, and | |
| 992 | + // IndexNow in particular requires a byte-exact body. We bail only on an | |
| 993 | + // explicit non-HTML Content-Type; a missing header is treated as HTML so | |
| 994 | + // normal page caching is never disabled. | |
| 995 | + $content_type = ''; | |
| 996 | + foreach ( headers_list() as $header ) { | |
| 997 | + if ( stripos( $header, 'content-type:' ) === 0 ) { | |
| 998 | + $content_type = strtolower( $header ); | |
| 999 | + } | |
| 1000 | + } | |
| 1001 | + if ( '' !== $content_type | |
| 1002 | + && false === stripos( $content_type, 'text/html' ) | |
| 1003 | + && false === stripos( $content_type, 'application/xhtml' ) ) { | |
| 1004 | + return $buffer; | |
| 1005 | + } | |
| 1006 | + | |
| 1007 | + $real_cache_dir = $this->get_real_cache_dir(); | |
| 1008 | + $cache_file = $this->get_cache_file_path() . '.gz'; | |
| 707 | 1009 | $asset_cache_dir = $this->get_default_cache_path() . 'min/'; |
| 708 | - $asset_cache_url = trailingslashit( trailingslashit( get_home_url() ) . trim( str_replace( dirname( WP_CONTENT_DIR ), '', $asset_cache_dir ), '/' ) ); | |
| 709 | - $settings = $this->settings; | |
| 1010 | + $asset_cache_url = trailingslashit( trailingslashit( get_site_url() ) . trim( str_replace( dirname( WP_CONTENT_DIR ), '', $asset_cache_dir ), '/' ) ); | |
| 1011 | + $settings = $this->settings; | |
| 710 | 1012 | |
| 1013 | + if ( $settings->optimize_google_fonts ) { | |
| 1014 | + $optimizer = new CombineGoogleFonts(); | |
| 1015 | + $buffer = $optimizer->optimize( $buffer ); | |
| 1016 | + } | |
| 1017 | + | |
| 711 | 1018 | if ( $settings->minify_css ) { |
| 712 | 1019 | if ( $settings->combine_css ) { |
| 713 | - $optimizer = new CssCombiner( $asset_cache_dir, $asset_cache_url ); | |
| 1020 | + $optimizer = new CssCombiner( $asset_cache_dir, $asset_cache_url, $settings->combine_css_footer ); | |
| 714 | 1021 | } else { |
| 715 | 1022 | $optimizer = new CssMinifier( $asset_cache_dir, $asset_cache_url ); |
| 716 | 1023 | } |
| 717 | 1024 | |
| @@ -719,40 +1026,48 @@ | ||
| 719 | 1026 | } |
| 720 | 1027 | |
| 721 | 1028 | if ( $settings->minify_js ) { |
| 722 | 1029 | if ( $settings->combine_head_js ) { |
| 723 | - $optimizer = new JsCombiner( $asset_cache_dir, $asset_cache_url, 'head' ); | |
| 724 | - $buffer = $optimizer->optimize( $buffer ); | |
| 1030 | + $optimizer = new JsCombiner( $asset_cache_dir, $asset_cache_url, 'head', $settings->combine_head_inline_js ); | |
| 1031 | + $buffer = $optimizer->optimize( $buffer ); | |
| 725 | 1032 | } |
| 726 | 1033 | |
| 727 | 1034 | if ( $settings->combine_body_js ) { |
| 728 | - $optimizer = new JsCombiner( $asset_cache_dir, $asset_cache_url, 'body' ); | |
| 729 | - $buffer = $optimizer->optimize( $buffer ); | |
| 1035 | + $optimizer = new JsCombiner( $asset_cache_dir, $asset_cache_url, 'body', $settings->combine_body_inline_js ); | |
| 1036 | + $buffer = $optimizer->optimize( $buffer ); | |
| 730 | 1037 | } |
| 731 | 1038 | |
| 732 | - if( ! $settings->combine_head_js && ! $settings->combine_body_js ) { | |
| 1039 | + if ( ! $settings->combine_head_js && ! $settings->combine_body_js ) { | |
| 733 | 1040 | $optimizer = new JsMinifier( $asset_cache_dir, $asset_cache_url ); |
| 734 | - $buffer = $optimizer->optimize( $buffer ); | |
| 1041 | + $buffer = $optimizer->optimize( $buffer ); | |
| 735 | 1042 | } |
| 736 | 1043 | } |
| 737 | 1044 | |
| 738 | 1045 | if ( $settings->minify_html ) { |
| 1046 | + wp_raise_memory_limit( 'image' ); | |
| 1047 | + | |
| 739 | 1048 | $buffer = Minify_HTML::minify( $buffer, [ |
| 740 | - 'cssMinifier' => function( $css ) use ($settings) { | |
| 1049 | + 'htmlCleanComments' => $settings->minify_html_comments, | |
| 1050 | + | |
| 1051 | + 'cssMinifier' => function ( $css ) use ( $settings ) { | |
| 741 | 1052 | if ( ! $settings->minify_inline_css ) { |
| 742 | 1053 | return $css; |
| 743 | 1054 | } |
| 744 | 1055 | |
| 745 | 1056 | $minifier = new CSS( $css ); |
| 1057 | + $minifier->setMaxImportSize( 0 ); | |
| 1058 | + $minifier->setImportExtensions( [] ); | |
| 1059 | + | |
| 746 | 1060 | return $minifier->minify(); |
| 747 | 1061 | }, |
| 748 | 1062 | |
| 749 | - 'jsMinifier' => function( $js ) use ($settings) { | |
| 1063 | + 'jsMinifier' => function ( $js ) use ( $settings ) { | |
| 750 | 1064 | if ( ! $settings->minify_inline_js ) { |
| 751 | 1065 | return $js; |
| 752 | 1066 | } |
| 753 | 1067 | |
| 754 | 1068 | $minifier = new JS( $js ); |
| 1069 | + | |
| 755 | 1070 | return $minifier->minify(); |
| 756 | 1071 | }, |
| 757 | 1072 | ] ); |
| 758 | 1073 | } |
| @@ -758,37 +1073,60 @@ | ||
| 758 | 1073 | } |
| 759 | 1074 | |
| 760 | 1075 | if ( $settings->enable_webp_support && $this->webp_accepted() ) { |
| 761 | 1076 | $optimizer = new WebpConverter( $real_cache_dir, $cache_file, $this->webp_processor, $wpdb ); |
| 762 | - $buffer = $optimizer->optimize( $buffer ); | |
| 1077 | + $buffer = $optimizer->optimize( $buffer ); | |
| 763 | 1078 | } |
| 764 | 1079 | |
| 1080 | + $buffer = trim( $buffer ); | |
| 1081 | + if ( empty( $buffer ) ) { | |
| 1082 | + Logger::log( 'ezCache will not save cache file for a blank page' ); | |
| 1083 | + | |
| 1084 | + return $buffer; | |
| 1085 | + } | |
| 1086 | + | |
| 765 | 1087 | if ( ! apply_filters( 'wp_bost_hide_cache_time_comment', false ) ) { |
| 766 | 1088 | $total_time = number_format( microtime( true ) - $this->cache_start_time, 2 ); |
| 767 | - $buffer .= "\n<!-- Cached by ezCache -->\n<!-- Cache created in {$total_time}s -->"; | |
| 1089 | + $cache_type = ( \Upress\EzCache\Settings::get_settings()->enable_redis_fullpage ?? false ) ? 'Redis' : 'Disk'; | |
| 1090 | + $buffer .= "\n<!-- Cached by ezCache | Full-Page Cache: {$cache_type} | Generated: " . date('Y-m-d H:i:s') . " | Time: {$total_time}s -->"; | |
| 768 | 1091 | } |
| 769 | 1092 | |
| 770 | 1093 | $buffer = apply_filters( 'ezcache_before_save_cache', $buffer ); |
| 771 | 1094 | |
| 1095 | + // ── Redis Full-Page Cache write ─────────────────────── | |
| 1096 | + // Mirror the cached HTML to Redis when the flag is on. TTL matches | |
| 1097 | + // the disk-cache lifetime so both backends expire in sync. | |
| 1098 | + if ( | |
| 1099 | + ! empty( $settings->enable_redis_fullpage ) | |
| 1100 | + && class_exists( '\\Upress\\EzCache\\RedisObjectCache' ) | |
| 1101 | + ) { | |
| 1102 | + $current_url = $this->build_fullpage_url(); | |
| 1103 | + $ttl = ! empty( $settings->cache_lifetime ) ? (int) $settings->cache_lifetime : 604800; | |
| 1104 | + \Upress\EzCache\RedisObjectCache::set_page( $current_url, $buffer, $ttl ); | |
| 1105 | + } | |
| 1106 | + | |
| 772 | 1107 | if ( ! file_exists( $real_cache_dir ) ) { |
| 773 | 1108 | if ( ! @wp_mkdir_p( $real_cache_dir ) ) { |
| 774 | - error_log( 'ezCache could not create directory ' . $real_cache_dir ); | |
| 1109 | + Logger::log( 'ezCache could not create directory ' . $real_cache_dir ); | |
| 1110 | + | |
| 775 | 1111 | return $buffer; |
| 776 | 1112 | } |
| 777 | 1113 | } |
| 778 | 1114 | |
| 779 | - // write regular file | |
| 780 | - if ( ! @file_put_contents( $cache_file, $buffer ) ) { | |
| 781 | - error_log( 'ezCache could not write to ' . str_replace( ABSPATH, '', $cache_file ) ); | |
| 1115 | + // write gzipped file | |
| 1116 | + $handle = @fopen( $cache_file, 'w' ); | |
| 1117 | + | |
| 1118 | + if ( $handle && @flock( $handle, LOCK_EX ) ) { | |
| 1119 | + fwrite( $handle, gzencode( $buffer, 6, FORCE_GZIP ) ); | |
| 1120 | + flock( $handle, LOCK_UN ); | |
| 1121 | + } else { | |
| 1122 | + Logger::log( 'ezCache could not write to ' . str_replace( ABSPATH, '', $cache_file ) ); | |
| 782 | 1123 | } |
| 783 | 1124 | |
| 784 | - // write gzipped file | |
| 785 | - if ( ! @file_put_contents( $cache_file . '.gz', gzencode( $buffer, 6, FORCE_GZIP ) ) ) { | |
| 786 | - error_log( 'ezCache could not write to ' . str_replace( ABSPATH, '', $cache_file . '.gz' ) ); | |
| 1125 | + if ( $handle ) { | |
| 1126 | + fclose( $handle ); | |
| 787 | 1127 | } |
| 788 | 1128 | |
| 789 | - $this->webp_processor->dispatch(); | |
| 790 | - | |
| 791 | 1129 | return $buffer; |
| 792 | 1130 | } |
| 793 | 1131 | |
| 794 | 1132 | /** |
| @@ -802,10 +1140,15 @@ | ||
| 802 | 1140 | } |
| 803 | 1141 | |
| 804 | 1142 | $files = glob( $path . '/*' ); |
| 805 | 1143 | foreach ( $files as $file ) { |
| 806 | - is_dir( $file ) ? $this->rmdir_recursive( $file ) : unlink( $file ); | |
| 1144 | + if ( file_exists( $file ) && is_dir( $file ) ) { | |
| 1145 | + $this->rmdir_recursive( $file ); | |
| 1146 | + } elseif ( file_exists( $file ) ) { | |
| 1147 | + unlink( $file ); | |
| 1148 | + } | |
| 807 | 1149 | } |
| 1150 | + | |
| 808 | 1151 | rmdir( $path ); |
| 809 | 1152 | } |
| 810 | 1153 | |
| 811 | 1154 | /** |
| @@ -820,36 +1163,39 @@ | ||
| 820 | 1163 | 'ezcache_mobile_useragent', |
| 821 | 1164 | 'Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) AppleWebKit/ 604.1.21 (KHTML, like Gecko) Version/ 12.0 Mobile/17A6278a Safari/602.1.26 (ezCache Preload)' |
| 822 | 1165 | ); |
| 823 | 1166 | |
| 824 | - wp_safe_remote_get( home_url(), [ | |
| 1167 | + wp_safe_remote_get( site_url(), [ | |
| 825 | 1168 | 'user-agent' => $desktop_ua, |
| 826 | - 'timeout' => 0.1, | |
| 1169 | + 'timeout' => 0.1, | |
| 827 | 1170 | ] ); |
| 828 | 1171 | |
| 829 | - wp_safe_remote_get( home_url(), [ | |
| 1172 | + wp_safe_remote_get( site_url(), [ | |
| 830 | 1173 | 'user-agent' => $mobile_ua, |
| 831 | - 'timeout' => 0.1, | |
| 1174 | + 'timeout' => 0.1, | |
| 832 | 1175 | ] ); |
| 833 | 1176 | } |
| 834 | 1177 | |
| 835 | - function delete_missing_webp_images() { | |
| 1178 | + function delete_missing_webp_images( $delete_all = false ) { | |
| 836 | 1179 | global $wpdb; |
| 837 | 1180 | |
| 838 | - $ids = [0]; | |
| 839 | - $images = $wpdb->get_results( "SELECT * FROM `{$wpdb->prefix}ezcache_webp_images` WHERE `status` = 'completed'" ); | |
| 840 | - foreach( $images as $image ) { | |
| 1181 | + // delete the actual files | |
| 1182 | + $ids = [ 0 ]; | |
| 1183 | + $where = $delete_all ? '' : "WHERE `status` = 'completed'"; | |
| 1184 | + $images = $wpdb->get_results( "SELECT * FROM `{$wpdb->prefix}ezcache_webp_images` {$where}" ); | |
| 1185 | + foreach ( $images as $image ) { | |
| 841 | 1186 | if ( ! file_exists( $image->webp_path ) ) { |
| 842 | 1187 | $ids[] = $image->id; |
| 843 | - } elseif ( ! file_exists( $image->path ) && file_exists( $image->webp_path ) ) { | |
| 1188 | + } elseif ( ( $delete_all || ! file_exists( $image->path ) ) && file_exists( $image->webp_path ) ) { | |
| 844 | 1189 | unlink( $image->webp_path ); |
| 845 | 1190 | $ids[] = $image->id; |
| 846 | 1191 | } |
| 847 | 1192 | } |
| 848 | 1193 | |
| 1194 | + // clean the database | |
| 849 | 1195 | $wpdb->query( |
| 850 | 1196 | $wpdb->prepare( |
| 851 | - "DELETE FROM `{$wpdb->prefix}ezcache_webp_images` WHERE `status` = 'failed' OR `id` IN ( " . substr( str_repeat( "%d, ", count( $ids ) ), 0, -2 ) . " )", | |
| 1197 | + "DELETE FROM `{$wpdb->prefix}ezcache_webp_images` WHERE `status` = 'failed' OR `id` IN ( " . substr( str_repeat( "%d, ", count( $ids ) ), 0, - 2 ) . " )", | |
| 852 | 1198 | $ids |
| 853 | 1199 | ) |
| 854 | 1200 | ); |
| 855 | 1201 | |
| @@ -855,20 +1201,42 @@ | ||
| 855 | 1201 | |
| 856 | 1202 | $wpdb->query( "OPTIMIZE TABLE `{$wpdb->prefix}ezcache_webp_images`" ); |
| 857 | 1203 | } |
| 858 | 1204 | |
| 1205 | + function delete_all_webp_images() { | |
| 1206 | + $this->delete_missing_webp_images( true ); | |
| 1207 | + } | |
| 1208 | + | |
| 859 | 1209 | /** |
| 860 | 1210 | * Clear all caches |
| 1211 | + * | |
| 1212 | + * @param bool $clear_webp Should deleting cache clear the WebP images | |
| 861 | 1213 | */ |
| 862 | - public function clear_cache() { | |
| 863 | - $cache_dir = $this->get_default_cache_path(); | |
| 1214 | + public function clear_cache( $clear_webp = false ) { | |
| 1215 | + $this->rmdir_recursive( $this->root_cache_dir ); | |
| 1216 | + @wp_mkdir_p( $this->root_cache_dir ); | |
| 864 | 1217 | |
| 865 | - $this->rmdir_recursive( $cache_dir ); | |
| 866 | - @wp_mkdir_p( $cache_dir ); | |
| 1218 | + if ( $clear_webp ) { | |
| 1219 | + $this->delete_all_webp_images(); | |
| 1220 | + } else { | |
| 1221 | + $this->delete_missing_webp_images(); | |
| 1222 | + } | |
| 867 | 1223 | |
| 868 | - $this->delete_missing_webp_images(); | |
| 1224 | + $this->purge_varnish_cache(); | |
| 869 | 1225 | |
| 1226 | + // Also flush Redis (both object cache and full-page keys live under ezcache:*). | |
| 1227 | + // If Redis is disabled or unavailable this is a no-op. | |
| 1228 | + if ( class_exists( '\\Upress\\EzCache\\RedisObjectCache' ) ) { | |
| 1229 | + \Upress\EzCache\RedisObjectCache::flush(); | |
| 1230 | + } | |
| 1231 | + | |
| 870 | 1232 | $this->preload_homepage(); |
| 1233 | + | |
| 1234 | + /** | |
| 1235 | + * Fires after the entire cache has been cleared. | |
| 1236 | + * Used by the Preload module to start a fresh preload run. | |
| 1237 | + */ | |
| 1238 | + do_action( 'ezcache_after_clear_cache' ); | |
| 871 | 1239 | } |
| 872 | 1240 | |
| 873 | 1241 | /** |
| 874 | 1242 | * Clear cache for a single post |
| @@ -878,10 +1246,104 @@ | ||
| 878 | 1246 | public function clear_cache_single( $post_id ) { |
| 879 | 1247 | $real_cache_dir = $this->get_real_cache_dir( $post_id ); |
| 880 | 1248 | |
| 881 | 1249 | $this->rmdir_recursive( $real_cache_dir ); |
| 1250 | + | |
| 1251 | + $this->purge_varnish_cache(); | |
| 1252 | + | |
| 1253 | + // Remove the matching Redis full-page key so the next request rebuilds. | |
| 1254 | + if ( class_exists( '\\Upress\\EzCache\\RedisObjectCache' ) ) { | |
| 1255 | + $url = get_permalink( $post_id ); | |
| 1256 | + if ( $url ) { | |
| 1257 | + \Upress\EzCache\RedisObjectCache::delete_page( $url ); | |
| 1258 | + } | |
| 1259 | + } | |
| 1260 | + | |
| 1261 | + /** | |
| 1262 | + * Fires after a single post's cache has been cleared. | |
| 1263 | + * | |
| 1264 | + * @param int $post_id | |
| 1265 | + */ | |
| 1266 | + do_action( 'ezcache_after_clear_cache_single', $post_id ); | |
| 882 | 1267 | } |
| 883 | 1268 | |
| 1269 | + public function clear_cache_url( $url ) { | |
| 1270 | + $real_cache_dir = $this->get_real_cache_dir( 0, $url ); | |
| 1271 | + | |
| 1272 | + $this->rmdir_recursive( $real_cache_dir ); | |
| 1273 | + | |
| 1274 | + $this->purge_varnish_cache(); | |
| 1275 | + | |
| 1276 | + if ( class_exists( '\\Upress\\EzCache\\RedisObjectCache' ) ) { | |
| 1277 | + \Upress\EzCache\RedisObjectCache::delete_page( $url ); | |
| 1278 | + } | |
| 1279 | + | |
| 1280 | + /** | |
| 1281 | + * Fires after a URL's cache has been cleared. | |
| 1282 | + * | |
| 1283 | + * @param string $url | |
| 1284 | + */ | |
| 1285 | + do_action( 'ezcache_after_clear_cache_url', $url ); | |
| 1286 | + } | |
| 1287 | + | |
| 1288 | + public function purge_varnish_cache() { | |
| 1289 | + // Whether Varnish PURGE is enabled (on by default). Can be turned off from | |
| 1290 | + // the settings screen on servers where Varnish is not in the request path, | |
| 1291 | + // to avoid generating needless 403 noise in the logs. | |
| 1292 | + $enabled = ! isset( $this->settings->enable_varnish_purge ) || ! empty( $this->settings->enable_varnish_purge ); | |
| 1293 | + | |
| 1294 | + /** | |
| 1295 | + * Filters whether ezCache should send a PURGE request to Varnish. | |
| 1296 | + * | |
| 1297 | + * Return false to skip the PURGE entirely. | |
| 1298 | + * | |
| 1299 | + * @param bool $enabled Whether the PURGE request should be sent. | |
| 1300 | + */ | |
| 1301 | + if ( ! apply_filters( 'ezcache_should_purge_varnish', $enabled ) ) { | |
| 1302 | + return; | |
| 1303 | + } | |
| 1304 | + | |
| 1305 | + $desktop_ua = apply_filters( | |
| 1306 | + 'ezcache_desktop_useragent', | |
| 1307 | + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36 (ezCache Preload)' | |
| 1308 | + ); | |
| 1309 | + | |
| 1310 | + $parseUrl = parse_url( home_url() ); | |
| 1311 | + | |
| 1312 | + $schema = 'http://'; | |
| 1313 | + if ( isset( $parseUrl['scheme'] ) ) { | |
| 1314 | + $schema = $parseUrl['scheme'] . '://'; | |
| 1315 | + } | |
| 1316 | + | |
| 1317 | + $host = $parseUrl['host']; | |
| 1318 | + | |
| 1319 | + // Send the PURGE to the local Varnish instance over loopback rather than to | |
| 1320 | + // the public host. The public hostname is preserved in the Host header so | |
| 1321 | + // Varnish still matches the right cache objects, while the request originates | |
| 1322 | + // from 127.0.0.1 — which is what Varnish/nginx PURGE ACLs typically allow, | |
| 1323 | + // avoiding the public 403 errors seen when the request leaves and re-enters | |
| 1324 | + // the server via its public IP. | |
| 1325 | + $purge_host = apply_filters( 'ezcache_varnish_purge_host', '127.0.0.1' ); | |
| 1326 | + | |
| 1327 | + $request_args = [ | |
| 1328 | + 'method' => 'PURGE', | |
| 1329 | + 'headers' => [ | |
| 1330 | + 'Host' => $host, | |
| 1331 | + 'User-Agent' => $desktop_ua, | |
| 1332 | + ], | |
| 1333 | + 'sslverify' => false, | |
| 1334 | + ]; | |
| 1335 | + $response = wp_remote_request( $schema . $purge_host . '/.*', $request_args ); | |
| 1336 | + if ( is_wp_error( $response ) || $response['response']['code'] != '200' ) { | |
| 1337 | + if ( $schema === 'https://' ) { | |
| 1338 | + $schema = 'http://'; | |
| 1339 | + } else { | |
| 1340 | + $schema = 'https://'; | |
| 1341 | + } | |
| 1342 | + wp_remote_request( $schema . $purge_host . '/.*', $request_args ); | |
| 1343 | + } | |
| 1344 | + } | |
| 1345 | + | |
| 884 | 1346 | /** |
| 885 | 1347 | * Delete expired cache |
| 886 | 1348 | */ |
| 887 | 1349 | public function clear_expired_cache() { |
| @@ -887,12 +1349,12 @@ | ||
| 887 | 1349 | public function clear_expired_cache() { |
| 888 | 1350 | $settings = $this->settings; |
| 889 | 1351 | |
| 890 | 1352 | try { |
| 891 | - $dir = new RecursiveDirectoryIterator( $this->root_cache_dir ); | |
| 892 | - $ite = new RecursiveIteratorIterator( $dir ); | |
| 893 | - $files = new RegexIterator( $ite, '/^.+\.(?:gz|html|js|css)$/i', RegexIterator::GET_MATCH ); | |
| 894 | - } catch( UnexpectedValueException $ex) { | |
| 1353 | + $dir = new RecursiveDirectoryIterator( $this->root_cache_dir ); | |
| 1354 | + $iterator = new RecursiveIteratorIterator( $dir ); | |
| 1355 | + $files = new RegexIterator( $iterator, '/^.+\.(?:gz|html|js|css)$/i', RegexIterator::GET_MATCH ); | |
| 1356 | + } catch ( UnexpectedValueException $ex ) { | |
| 895 | 1357 | if ( strpos( $ex->getMessage(), 'No such file or directory' ) ) { |
| 896 | 1358 | $files = []; |
| 897 | 1359 | } else { |
| 898 | 1360 | throw $ex; |
| @@ -898,9 +1360,9 @@ | ||
| 898 | 1360 | throw $ex; |
| 899 | 1361 | } |
| 900 | 1362 | } |
| 901 | 1363 | |
| 902 | - foreach($files as $file) { | |
| 1364 | + foreach ( $files as $file ) { | |
| 903 | 1365 | if ( is_array( $file ) ) { |
| 904 | 1366 | $file = array_shift( $file ); |
| 905 | 1367 | } |
| 906 | 1368 | |
| @@ -918,16 +1380,16 @@ | ||
| 918 | 1380 | * Get caching statistics and file sizes |
| 919 | 1381 | * @return array |
| 920 | 1382 | */ |
| 921 | 1383 | public function get_cache_stats() { |
| 922 | - $settings = $this->settings; | |
| 1384 | + $settings = $this->settings; | |
| 923 | 1385 | $cache_dir = $this->get_default_cache_path(); |
| 924 | 1386 | |
| 925 | 1387 | try { |
| 926 | - $dir = new RecursiveDirectoryIterator( $cache_dir ); | |
| 927 | - $ite = new RecursiveIteratorIterator( $dir ); | |
| 928 | - $files = new RegexIterator( $ite, '/^.+\.(?:gz|html|css|js)$/i', RegexIterator::GET_MATCH ); | |
| 929 | - } catch( UnexpectedValueException $ex) { | |
| 1388 | + $dir = new RecursiveDirectoryIterator( $cache_dir ); | |
| 1389 | + $iterator = new RecursiveIteratorIterator( $dir ); | |
| 1390 | + $files = new RegexIterator( $iterator, '/^.+\.(?:gz|html|css|js)$/i', RegexIterator::GET_MATCH ); | |
| 1391 | + } catch ( UnexpectedValueException $ex ) { | |
| 930 | 1392 | if ( strpos( $ex->getMessage(), 'No such file or directory' ) ) { |
| 931 | 1393 | $files = []; |
| 932 | 1394 | } else { |
| 933 | 1395 | throw $ex; |
| @@ -935,58 +1397,58 @@ | ||
| 935 | 1397 | } |
| 936 | 1398 | |
| 937 | 1399 | $raw_data = []; |
| 938 | 1400 | |
| 939 | - $mobile_count = 0; | |
| 940 | - $mobile_size = 0; | |
| 941 | - $mobile_expired_count = 0; | |
| 942 | - $mobile_expired_size = 0; | |
| 943 | - $desktop_count = 0; | |
| 944 | - $desktop_size = 0; | |
| 1401 | + $mobile_count = 0; | |
| 1402 | + $mobile_size = 0; | |
| 1403 | + $mobile_expired_count = 0; | |
| 1404 | + $mobile_expired_size = 0; | |
| 1405 | + $desktop_count = 0; | |
| 1406 | + $desktop_size = 0; | |
| 945 | 1407 | $desktop_expired_count = 0; |
| 946 | - $desktop_expired_size = 0; | |
| 947 | - $js_count = 0; | |
| 948 | - $js_size = 0; | |
| 949 | - $js_expired_count = 0; | |
| 950 | - $js_expired_size = 0; | |
| 951 | - $css_count = 0; | |
| 952 | - $css_size = 0; | |
| 953 | - $css_expired_count = 0; | |
| 954 | - $css_expired_size = 0; | |
| 1408 | + $desktop_expired_size = 0; | |
| 1409 | + $js_count = 0; | |
| 1410 | + $js_size = 0; | |
| 1411 | + $js_expired_count = 0; | |
| 1412 | + $js_expired_size = 0; | |
| 1413 | + $css_count = 0; | |
| 1414 | + $css_size = 0; | |
| 1415 | + $css_expired_count = 0; | |
| 1416 | + $css_expired_size = 0; | |
| 955 | 1417 | |
| 956 | - foreach($files as $file) { | |
| 1418 | + foreach ( $files as $file ) { | |
| 957 | 1419 | if ( is_array( $file ) ) { |
| 958 | 1420 | $file = array_shift( $file ); |
| 959 | 1421 | } |
| 960 | 1422 | |
| 961 | - $stats = stat( $file ); | |
| 962 | - $expired = $stats['mtime'] <= (time() - $settings->cache_lifetime); | |
| 1423 | + $stats = stat( $file ); | |
| 1424 | + $expired = $stats['mtime'] <= ( time() - $settings->cache_lifetime ); | |
| 963 | 1425 | |
| 964 | 1426 | $raw_data[] = [ |
| 965 | - 'path' => $file, | |
| 966 | - 'stats' => $stats, | |
| 1427 | + 'path' => $file, | |
| 1428 | + 'stats' => $stats, | |
| 967 | 1429 | 'expired' => $expired, |
| 968 | 1430 | ]; |
| 969 | 1431 | |
| 970 | 1432 | if ( preg_match( '/^.+?-mobile\.html(\.gz)?$/i', $file ) ) { |
| 971 | 1433 | if ( ! $expired ) { |
| 972 | - $mobile_count++; | |
| 1434 | + $mobile_count ++; | |
| 973 | 1435 | $mobile_size += $stats['size']; |
| 974 | 1436 | } else { |
| 975 | - $mobile_expired_count++; | |
| 1437 | + $mobile_expired_count ++; | |
| 976 | 1438 | $mobile_expired_size += $stats['size']; |
| 977 | 1439 | } |
| 978 | - } elseif( preg_match( '/\.css$/i', $file ) ) { | |
| 1440 | + } elseif ( preg_match( '/\.css$/i', $file ) ) { | |
| 979 | 1441 | if ( $expired ) { |
| 980 | - $css_expired_count++; | |
| 1442 | + $css_expired_count ++; | |
| 981 | 1443 | $css_expired_size += $stats['size']; |
| 982 | 1444 | } else { |
| 983 | 1445 | $css_count ++; |
| 984 | 1446 | $css_size += $stats['size']; |
| 985 | 1447 | } |
| 986 | - } elseif( preg_match( '/\.js$/i', $file ) ) { | |
| 1448 | + } elseif ( preg_match( '/\.js$/i', $file ) ) { | |
| 987 | 1449 | if ( $expired ) { |
| 988 | - $js_expired_count++; | |
| 1450 | + $js_expired_count ++; | |
| 989 | 1451 | $js_expired_size += $stats['size']; |
| 990 | 1452 | } else { |
| 991 | 1453 | $js_count ++; |
| 992 | 1454 | $js_size += $stats['size']; |
| @@ -992,12 +1454,12 @@ | ||
| 992 | 1454 | $js_size += $stats['size']; |
| 993 | 1455 | } |
| 994 | 1456 | } else { |
| 995 | 1457 | if ( ! $expired ) { |
| 996 | - $desktop_count++; | |
| 1458 | + $desktop_count ++; | |
| 997 | 1459 | $desktop_size += $stats['size']; |
| 998 | 1460 | } else { |
| 999 | - $desktop_expired_count++; | |
| 1461 | + $desktop_expired_count ++; | |
| 1000 | 1462 | $desktop_expired_size += $stats['size']; |
| 1001 | 1463 | } |
| 1002 | 1464 | } |
| 1003 | 1465 | } |
| @@ -1002,15 +1464,15 @@ | ||
| 1002 | 1464 | } |
| 1003 | 1465 | } |
| 1004 | 1466 | |
| 1005 | 1467 | // we want to count only the number of pages which have cache, but we have 2 files for each page |
| 1006 | - $mobile_count = $mobile_count / 2; | |
| 1468 | + $mobile_count = $mobile_count / 2; | |
| 1007 | 1469 | $desktop_count = $desktop_count / 2; |
| 1008 | 1470 | |
| 1009 | 1471 | |
| 1010 | 1472 | global $wpdb; |
| 1011 | - $webp_images = 0; | |
| 1012 | - $webp_images_size = 0; | |
| 1473 | + $webp_images = 0; | |
| 1474 | + $webp_images_size = 0; | |
| 1013 | 1475 | $webp_images_original_size = 0; |
| 1014 | 1476 | |
| 1015 | 1477 | $results = $wpdb->get_row( "SELECT COUNT(*) AS total, SUM(`original_size`) AS total_original_size, SUM(`webp_size`) AS total_webp_size FROM `{$wpdb->prefix}ezcache_webp_images` WHERE `status` = 'completed'" ); |
| 1016 | 1478 | if ( $results ) { |
| @@ -1018,9 +1480,9 @@ | ||
| 1018 | 1480 | $webp_images_size = intval( $results->total_webp_size ); |
| 1019 | 1481 | $webp_images_original_size = intval( $results->total_original_size ); |
| 1020 | 1482 | } |
| 1021 | 1483 | |
| 1022 | - return compact( 'webp_images', 'webp_images_original_size', 'webp_images_size', 'mobile_count', 'desktop_count', 'mobile_expired_count', 'desktop_expired_count', 'mobile_size', 'desktop_size', 'mobile_expired_size', 'desktop_expired_size' ,'css_size', 'css_count', 'js_count', 'js_size', 'css_expired_count', 'css_expired_size', 'js_expired_count', 'js_expired_size' ); | |
| 1484 | + return compact( 'webp_images', 'webp_images_original_size', 'webp_images_size', 'mobile_count', 'desktop_count', 'mobile_expired_count', 'desktop_expired_count', 'mobile_size', 'desktop_size', 'mobile_expired_size', 'desktop_expired_size', 'css_size', 'css_count', 'js_count', 'js_size', 'css_expired_count', 'css_expired_size', 'js_expired_count', 'js_expired_size' ); | |
| 1023 | 1485 | } |
| 1024 | 1486 | |
| 1025 | 1487 | /** |
| 1026 | 1488 | * Get the status of the cache |
| @@ -1027,16 +1489,20 @@ | ||
| 1027 | 1489 | * |
| 1028 | 1490 | * @return array |
| 1029 | 1491 | */ |
| 1030 | 1492 | public function get_status() { |
| 1031 | - $wp_cache_enabled = defined( 'WP_CACHE' ) && WP_CACHE; | |
| 1032 | - $adv_cache_exists = file_exists( WP_CONTENT_DIR . '/advanced-cache.php' ); | |
| 1493 | + global $wpdb; | |
| 1494 | + | |
| 1495 | + $wp_cache_enabled = defined( 'WP_CACHE' ) && WP_CACHE; | |
| 1496 | + $adv_cache_exists = file_exists( WP_CONTENT_DIR . '/advanced-cache.php' ); | |
| 1033 | 1497 | $correct_advanced_cache = $adv_cache_exists && strpos( file_get_contents( WP_CONTENT_DIR . '/advanced-cache.php' ), 'ezCache Advanced Cache' ) !== false; |
| 1498 | + $webp_table_exists = ! is_null( $wpdb->get_row( "SHOW TABLES LIKE '{$wpdb->prefix}ezcache_webp_images'" ) ); | |
| 1034 | 1499 | |
| 1035 | 1500 | return [ |
| 1036 | - 'cache_enabled' => $wp_cache_enabled, | |
| 1037 | - 'adv_cache_exists' => $adv_cache_exists, | |
| 1501 | + 'cache_enabled' => $wp_cache_enabled, | |
| 1502 | + 'adv_cache_exists' => $adv_cache_exists, | |
| 1038 | 1503 | 'correct_cache_exists' => $correct_advanced_cache, |
| 1504 | + 'webp_table_exists' => $webp_table_exists, | |
| 1039 | 1505 | ]; |
| 1040 | 1506 | } |
| 1041 | 1507 | |
| 1042 | 1508 | /** |
| @@ -1047,26 +1513,27 @@ | ||
| 1047 | 1513 | * @return bool|string |
| 1048 | 1514 | */ |
| 1049 | 1515 | public static function url_to_path( $url ) { |
| 1050 | 1516 | $root_dir = trailingslashit( dirname( WP_CONTENT_DIR ) ); |
| 1051 | - $root_url = str_replace( wp_basename( WP_CONTENT_DIR ), '', WP_CONTENT_URL ); | |
| 1517 | + $root_url = str_replace( wp_basename( WP_CONTENT_DIR ), '', content_url() ); | |
| 1052 | 1518 | $url_host = wp_parse_url( $url, PHP_URL_HOST ); |
| 1053 | 1519 | |
| 1520 | + // relative path. | |
| 1054 | 1521 | if ( null === $url_host ) { |
| 1055 | - $subdir_levels = substr_count( preg_replace( '/https?:\/\//','', site_url() ), '/' ); | |
| 1056 | - $url = site_url() . str_repeat( '/..', $subdir_levels ) . $url; | |
| 1522 | + $subdir_levels = substr_count( preg_replace( '/https?:\/\//', '', site_url() ), '/' ); | |
| 1523 | + $url = trailingslashit( site_url() . str_repeat( '/..', $subdir_levels ) ) . ltrim( $url, '/' ); | |
| 1057 | 1524 | } |
| 1058 | 1525 | |
| 1059 | - $root_url = preg_replace( '/^https?:/', '', $root_url ); | |
| 1060 | - $url = preg_replace( '/^https?:/', '', $url ); | |
| 1061 | - $file = str_replace( $root_url, $root_dir, $url ); | |
| 1062 | - $file = self::realpath( $file ); | |
| 1526 | + $root_url = preg_replace( '/^https?:/', '', $root_url ); | |
| 1527 | + $url_rep = preg_replace( '/^https?:/', '', $url ); | |
| 1528 | + $file = str_replace( $root_url, $root_dir, $url_rep ); | |
| 1529 | + $real_path = self::realpath( $file ); | |
| 1063 | 1530 | |
| 1064 | - if ( ! file_exists( $file ) ) { | |
| 1531 | + if ( ! file_exists( $real_path ) ) { | |
| 1065 | 1532 | return false; |
| 1066 | 1533 | } |
| 1067 | 1534 | |
| 1068 | - return $file; | |
| 1535 | + return $real_path; | |
| 1069 | 1536 | } |
| 1070 | 1537 | |
| 1071 | 1538 | /** |
| 1072 | 1539 | * Returns canonicalized absolute pathname. |
| @@ -1072,16 +1539,16 @@ | ||
| 1072 | 1539 | * Returns canonicalized absolute pathname. |
| 1073 | 1540 | * The resulting path will have no symbolic link, '/./' or '/../' components. |
| 1074 | 1541 | * Same as the defautl PHP realpath() function but works even when the files does not exist. |
| 1075 | 1542 | * |
| 1076 | - * @see \realpath() | |
| 1077 | - * | |
| 1078 | 1543 | * @param string $file The path being checked. |
| 1079 | 1544 | * |
| 1080 | 1545 | * @return string |
| 1546 | + * @see \realpath() | |
| 1547 | + * | |
| 1081 | 1548 | */ |
| 1082 | 1549 | public static function realpath( $file ) { |
| 1083 | - $path = array(); | |
| 1550 | + $path = []; | |
| 1084 | 1551 | |
| 1085 | 1552 | foreach ( explode( '/', $file ) as $part ) { |
| 1086 | 1553 | if ( '' === $part || '.' === $part ) { |
| 1087 | 1554 | continue; |
| @@ -1088,10 +1555,9 @@ | ||
| 1088 | 1555 | } |
| 1089 | 1556 | |
| 1090 | 1557 | if ( '..' !== $part ) { |
| 1091 | 1558 | array_push( $path, $part ); |
| 1092 | - } | |
| 1093 | - elseif ( count( $path ) > 0 ) { | |
| 1559 | + } elseif ( count( $path ) > 0 ) { | |
| 1094 | 1560 | array_pop( $path ); |
| 1095 | 1561 | } |
| 1096 | 1562 | } |
| 1097 | 1563 | |
| @@ -1097,6 +1563,55 @@ | ||
| 1097 | 1563 | |
| 1098 | 1564 | $prefix = 'WIN' === strtoupper( substr( PHP_OS, 0, 3 ) ) ? '' : '/'; |
| 1099 | 1565 | |
| 1100 | 1566 | return $prefix . join( '/', $path ); |
| 1567 | + } | |
| 1568 | + | |
| 1569 | + /** | |
| 1570 | + * Check if Development Mode is active (file-based, works before WP loads) | |
| 1571 | + */ | |
| 1572 | + public static function is_dev_mode_active() { | |
| 1573 | + $flag_file = (defined('WP_CONTENT_DIR') ? WP_CONTENT_DIR : dirname(__DIR__)) . '/cache/ezcache/.dev-mode'; | |
| 1574 | + if ( ! file_exists( $flag_file ) ) { | |
| 1575 | + return false; | |
| 1576 | + } | |
| 1577 | + $expires = (int) trim( @file_get_contents( $flag_file ) ); | |
| 1578 | + if ( $expires === 0 ) { | |
| 1579 | + return true; | |
| 1580 | + } | |
| 1581 | + if ( time() >= $expires ) { | |
| 1582 | + @unlink( $flag_file ); | |
| 1583 | + return false; | |
| 1584 | + } | |
| 1585 | + return true; | |
| 1586 | + } | |
| 1587 | + | |
| 1588 | + public static function enable_dev_mode( $seconds = 3600 ) { | |
| 1589 | + $dir = WP_CONTENT_DIR . '/cache/ezcache'; | |
| 1590 | + if ( ! is_dir( $dir ) ) { | |
| 1591 | + @mkdir( $dir, 0755, true ); | |
| 1592 | + } | |
| 1593 | + $expires = ( $seconds === 0 ) ? 0 : time() + $seconds; | |
| 1594 | + file_put_contents( $dir . '/.dev-mode', (string) $expires ); | |
| 1595 | + } | |
| 1596 | + | |
| 1597 | + public static function disable_dev_mode() { | |
| 1598 | + @unlink( WP_CONTENT_DIR . '/cache/ezcache/.dev-mode' ); | |
| 1599 | + } | |
| 1600 | + | |
| 1601 | + public static function get_dev_mode_status() { | |
| 1602 | + $flag = WP_CONTENT_DIR . '/cache/ezcache/.dev-mode'; | |
| 1603 | + if ( ! file_exists( $flag ) ) { | |
| 1604 | + return [ 'active' => false ]; | |
| 1605 | + } | |
| 1606 | + $expires = (int) trim( @file_get_contents( $flag ) ); | |
| 1607 | + if ( $expires > 0 && time() >= $expires ) { | |
| 1608 | + @unlink( $flag ); | |
| 1609 | + return [ 'active' => false ]; | |
| 1610 | + } | |
| 1611 | + return [ | |
| 1612 | + 'active' => true, | |
| 1613 | + 'expires' => $expires === 0 ? 'permanent' : $expires, | |
| 1614 | + 'remaining' => $expires === 0 ? null : $expires - time(), | |
| 1615 | + ]; | |
| 1101 | 1616 | } |
| 1102 | 1617 | } |