PluginProbe
ezCache / 1.2.1
ezCache v1.2.1
2.6.5 2.6.4 2.6.2 2.6.3 2.6.1 2.6.0 2.5.6 2.5.5 2.5.4 2.5.3 2.5.2 2.5.1 2.5 2.2.1 2.2.2 trunk 1.2 1.2.1 1.2.2 1.2.3 1.2.4 1.3 1.3.1 1.3.10 1.3.11 All 49 releases
ezcache / includes / Cache.php

Cache.php in ezCache 1.2.1, at includes/Cache.php

1,103 lines 27.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace Upress\EzCache;
3
4 use MatthiasMullie\Minify\CSS;
5 use MatthiasMullie\Minify\JS;
6 use RecursiveDirectoryIterator;
7 use RecursiveIteratorIterator;
8 use RegexIterator;
9 use UnexpectedValueException;
10 use Upress\EzCache\BackgroundProcesses\ConvertWebpProcess;
11 use Upress\EzCache\FileOptimizer\CssMinifier;
12 use Upress\EzCache\FileOptimizer\CssCombiner;
13 use Upress\EzCache\FileOptimizer\JsMinifier;
14 use Upress\EzCache\FileOptimizer\JsCombiner;
15 use Upress\EzCache\FileOptimizer\WebpConverter;
16 use Upress\EzCache\ThirdParty\Minify_HTML;
17
18 class Cache {
19 protected static $instance;
20 protected $settings;
21 protected $cache_start_time;
22 protected $webp_processor;
23 protected $root_cache_dir = WP_CONTENT_DIR . '/cache/ezcache/';
24
25
26 public static function instance() {
27 if ( ! self::$instance ) {
28 self::$instance = new self();
29 }
30
31 return self::$instance;
32 }
33
34 private function __construct() {
35 $this->settings = Settings::get_settings();
36 $this->webp_processor = new ConvertWebpProcess();
37 $this->cache_start_time = microtime( true );
38 }
39
40 /**
41 * @return string
42 */
43 public function get_default_cache_path() {
44 $hostname = preg_replace( '/:.*$/', '', $this->get_http_host() );
45 return $this->root_cache_dir . $hostname . '/';
46 }
47
48 /**
49 * Get the HTTP host
50 *
51 * @return string
52 */
53 public function get_http_host() {
54 if ( ! empty( $_SERVER['HTTP_HOST'] ) ) {
55 $host = function_exists( 'mb_strtolower' ) ? mb_strtolower( $_SERVER['HTTP_HOST'] ) : strtolower( $_SERVER['HTTP_HOST'] );
56
57 return htmlentities( $host );
58 } elseif ( function_exists( 'get_option' ) ) {
59 return (string) parse_url( get_option( 'home' ), PHP_URL_HOST );
60 }
61
62 return '';
63 }
64
65 /**
66 * Get the logged in cookie value
67 * @return string
68 */
69 public function get_cookies_values() {
70 static $string = '';
71
72 if ( $string != '' ) {
73 return $string;
74 }
75
76 if ( defined( 'COOKIEHASH' ) ) {
77 $cookiehash = preg_quote( constant( 'COOKIEHASH' ) );
78 } else {
79 $cookiehash = '';
80 }
81
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 ) ) {
90 if ( preg_match( $regex, $key ) ) {
91 $string .= $_COOKIE[ $key ] . ",";
92 }
93 next( $_COOKIE );
94 }
95 reset( $_COOKIE );
96
97 if ( $string != '' ) {
98 $string = md5( $string );
99 }
100
101 return $string;
102 }
103
104 /**
105 * Check if the request supports gzip compression
106 *
107 * @return bool
108 */
109 public function gzip_accepted() {
110 return isset( $_SERVER['HTTP_ACCEPT_ENCODING'] ) && false !== strpos( $_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip' );
111 }
112
113 /**
114 * Check if the request supports webp images
115 *
116 * @return bool
117 */
118 public function webp_accepted() {
119 return isset( $_SERVER['HTTP_ACCEPT'] ) && false !== strpos( $_SERVER['HTTP_ACCEPT'], 'image/webp' );
120 }
121
122 /**
123 * Check if the request comes from the backend
124 *
125 * @return bool
126 */
127 public function is_backend() {
128 if ( is_admin() ) {
129 return true;
130 }
131
132 $script = isset( $_SERVER['PHP_SELF'] ) ? basename( $_SERVER['PHP_SELF'] ) : '';
133 if ( $script !== 'index.php' ) {
134 if ( in_array( $script, array( 'wp-login.php', 'xmlrpc.php', 'wp-cron.php' ) ) ) {
135 return true;
136 } elseif ( defined( 'DOING_CRON' ) && DOING_CRON ) {
137 return true;
138 } elseif ( PHP_SAPI == 'cli' || ( defined( 'WP_CLI' ) && WP_CLI ) ) {
139 return true;
140 }
141 }
142
143 return false;
144 }
145
146 /**
147 * Should we serve the cached file
148 *
149 * @return bool
150 */
151 public function should_serve_cached_data() {
152 $settings = $this->settings;
153
154 if ( $settings->no_cache_known_users && $this->get_cookies_values() ) {
155 return false;
156 }
157
158 if ( ( isset( $_SERVER['REQUEST_METHOD'] ) && in_array( $_SERVER['REQUEST_METHOD'], [
159 'POST',
160 'PUT',
161 'PATCH',
162 'DELETE'
163 ] ) ) || isset( $_GET['customize_changeset_uuid'] ) || isset( $_POST['wp_customize'] ) ) {
164 return false;
165 }
166
167 if ( $this->is_backend() ) {
168 return false;
169 }
170
171 // 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'] )
184 ) {
185 return false;
186 }
187
188 if ( defined( 'DOING_CRON' ) && DOING_CRON ) {
189 return false;
190 }
191
192 return true;
193 }
194
195 /**
196 * Should we save the cache files.
197 * Most of the checks here have to be run after the page was rendered as they require WordPress.
198 *
199 * @return bool
200 */
201 public function should_save_cache() {
202 if ( ! $this->should_serve_cached_data() ) {
203 return false;
204 }
205
206 $settings = $this->settings;
207
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 // check if we have any errors or otherwise settings preventing caching
222 $error = error_get_last();
223 if ( null !== $error && ( $error['type'] & ( E_ERROR | E_CORE_ERROR | E_PARSE | E_COMPILE_ERROR | E_USER_ERROR ) ) ) {
224 return false;
225 }
226
227 if ( function_exists( 'http_response_code' ) && http_response_code() > 300 ) {
228 return false;
229 }
230
231 if ( is_404() ) {
232 return false;
233 }
234
235 if ( $settings->bypass_cache->single && is_single() ) {
236 return false;
237 }
238 if ( $settings->bypass_cache->pages && is_page() ) {
239 return false;
240 }
241 if ( $settings->bypass_cache->frontpage && is_front_page() ) {
242 return false;
243 }
244 if ( $settings->bypass_cache->home && is_home() ) {
245 return false;
246 }
247 if ( $settings->bypass_cache->archives && is_archive() ) {
248 return false;
249 }
250 if ( $settings->bypass_cache->tag && is_tag() ) {
251 return false;
252 }
253 if ( $settings->bypass_cache->category && is_category() ) {
254 return false;
255 }
256 if ( $settings->bypass_cache->feed && is_feed() ) {
257 return false;
258 }
259 if ( $settings->bypass_cache->search && is_search() ) {
260 return false;
261 }
262 if ( $settings->bypass_cache->author && is_author() ) {
263 return false;
264 }
265
266 if ( is_robots() || get_query_var( 'sitemap' ) || get_query_var( 'xsl' ) || get_query_var( 'xml_sitemap' ) ) {
267 return false;
268 }
269
270 if ( isset( $_GET['preview'] ) || isset( $_POST['wp_customize'] ) ) {
271 return false;
272 }
273
274 if ( get_post_meta( get_the_ID(), '_ezcache_do_not_cache_post', true ) ) {
275 return false;
276 }
277
278 // check useragent
279 $rejected_useragents = preg_split( "/\\r\\n|\\r|\\n/u", trim( $settings->rejected_user_agent ), -1, PREG_SPLIT_NO_EMPTY );
280 $rejected_useragents = array_filter( $rejected_useragents );
281 if ( ! empty( $_SERVER['HTTP_USER_AGENT'] ) ) {
282 foreach( $rejected_useragents as $ua ) {
283 if ( empty( $ua ) ) continue;
284
285 if ( false !== strpos( $_SERVER['HTTP_USER_AGENT'], trim( $ua ) ) ) {
286 return false;
287 }
288 }
289 }
290
291 // check URL
292 $rejected_uris = preg_split( "/\\r\\n|\\r|\\n/u", trim( $settings->rejected_uri ), -1, PREG_SPLIT_NO_EMPTY );
293 $rejected_uris = array_filter( $rejected_uris );
294 $domain = untrailingslashit( home_url() );
295 if ( ! empty( $_SERVER['REQUEST_URI'] ) ) {
296 foreach( $rejected_uris as $url ) {
297 $url = str_replace( $domain, '', $url );
298 $url = '/' . trim( $url, '/' );
299 $url = str_replace( ['\/*', '*'], ['\/?.*?', '.*?'], preg_quote( $url, '/' ) );
300 if ( @preg_match( "/^{$url}\/?$/", $_SERVER['REQUEST_URI']) ) {
301 return false;
302 }
303 }
304 }
305
306 return true;
307 }
308
309 /**
310 * Get the mobile browser name
311 *
312 * @return string
313 */
314 public function detect_mobile() {
315 if ( empty( $_SERVER['HTTP_USER_AGENT'] ) ) {
316 return '';
317 }
318
319 $mobile_browsers = apply_filters( 'ezcache_mobile_browsers', [
320 '2.0 MMP',
321 '240x320',
322 '400X240',
323 'AvantGo',
324 'BlackBerry',
325 'Blazer',
326 'Cellphone',
327 'Danger',
328 'DoCoMo',
329 'Elaine/3.0',
330 'EudoraWeb',
331 'Googlebot-Mobile',
332 'hiptop',
333 'IEMobile',
334 'KYOCERA/WX310K',
335 'LG/U990',
336 'MIDP-2.',
337 'MMEF20',
338 'MOT-V',
339 'NetFront',
340 'Newt',
341 'Nintendo Wii',
342 'Nitro',
343 'Nokia',
344 'Opera Mini',
345 'Palm',
346 'PlayStation Portable',
347 'portalmmm',
348 'Proxinet',
349 'ProxiNet',
350 'SHARP-TQ-GX10',
351 'SHG-i900',
352 'Small',
353 'SonyEricsson',
354 'Symbian OS',
355 'SymbianOS',
356 'TS21i-10',
357 'UP.Browser',
358 'UP.Link',
359 'webOS',
360 'Windows CE',
361 'WinWAP',
362 'YahooSeeker/M1A1-R2D2',
363 'iPhone',
364 'iPod',
365 'iPad',
366 'Android',
367 'BlackBerry9530',
368 'LG-TU915 Obigo',
369 'LGE VX',
370 'webOS',
371 'Nokia5800'
372 ] );
373 $user_agent = strtolower( $_SERVER['HTTP_USER_AGENT'] );
374 foreach ( $mobile_browsers as $browser ) {
375 if ( strstr( $user_agent, trim( strtolower( $browser ) ) ) ) {
376 return $user_agent;
377 }
378 }
379
380 if ( isset( $_SERVER['HTTP_X_WAP_PROFILE'] ) ) {
381 return $_SERVER['HTTP_X_WAP_PROFILE'];
382 }
383
384 if ( isset( $_SERVER['HTTP_PROFILE'] ) ) {
385 return $_SERVER['HTTP_PROFILE'];
386 }
387
388 $browser_prefixes = apply_filters( 'ezcache_mobile_browser_prefixes', [
389 'w3c',
390 'w3c-',
391 'acs-',
392 'alav',
393 'alca',
394 'amoi',
395 'audi',
396 'avan',
397 'benq',
398 'bird',
399 'blac',
400 'blaz',
401 'brew',
402 'cell',
403 'cldc',
404 'cmd-',
405 'dang',
406 'doco',
407 'eric',
408 'hipt',
409 'htc_',
410 'inno',
411 'ipaq',
412 'ipod',
413 'jigs',
414 'kddi',
415 'keji',
416 'leno',
417 'lg-c',
418 'lg-d',
419 'lg-g',
420 'lge-',
421 'lg/u',
422 'maui',
423 'maxo',
424 'midp',
425 'mits',
426 'mmef',
427 'mobi',
428 'mot-',
429 'moto',
430 'mwbp',
431 'nec-',
432 'newt',
433 'noki',
434 'palm',
435 'pana',
436 'pant',
437 'phil',
438 'play',
439 'port',
440 'prox',
441 'qwap',
442 'sage',
443 'sams',
444 'sany',
445 'sch-',
446 'sec-',
447 'send',
448 'seri',
449 'sgh-',
450 'shar',
451 'sie-',
452 'siem',
453 'smal',
454 'smar',
455 'sony',
456 'sph-',
457 'symb',
458 't-mo',
459 'teli',
460 'tim-',
461 'tosh',
462 'tsm-',
463 'upg1',
464 'upsi',
465 'vk-v',
466 'voda',
467 'wap-',
468 'wapa',
469 'wapi',
470 'wapp',
471 'wapr',
472 'webc',
473 'winw',
474 'winw',
475 'xda',
476 'xda-'
477 ] );
478 foreach ( $browser_prefixes as $prefix ) {
479 if ( substr( $user_agent, 0, 4 ) == $prefix ) {
480 return $prefix;
481 }
482 }
483
484 $accept = isset( $_SERVER['HTTP_ACCEPT'] ) ? strtolower( $_SERVER['HTTP_ACCEPT'] ) : '';
485 if ( strpos( $accept, 'wap' ) !== false ) {
486 return 'wap';
487 }
488
489 if ( isset( $_SERVER['ALL_HTTP'] ) && false !== strpos( strtolower( $_SERVER['ALL_HTTP'] ), 'operamini' ) ) {
490 return 'operamini';
491 }
492
493 return '';
494 }
495
496 /**
497 * Search & replace in a string
498 *
499 * @param string|string[] $search
500 * @param string $subject
501 *
502 * @return string
503 */
504 public function deep_replace( $search, $subject ) {
505 $subject = (string) $subject;
506
507 $count = 1;
508 while ( $count ) {
509 $subject = str_replace( $search, '', $subject, $count );
510 }
511
512 return $subject;
513 }
514
515 /**
516 * Get the cache directory URL for the current post
517 *
518 * @param int $post_id
519 *
520 * @return mixed|string
521 */
522 public function get_current_url_cache_dir( $post_id = 0 ) {
523 static $url_cache_dir = array();
524
525 if ( isset( $url_cache_dir[ $post_id ] ) ) {
526 return $url_cache_dir[ $post_id ];
527 }
528
529 $uri = strtolower( $_SERVER['REQUEST_URI'] );
530
531 $DONOTREMEMBER = 0;
532 if ( 0 !== $post_id ) {
533 $site_url = site_url();
534 $permalink = get_permalink( $post_id );
535 if ( false === strpos( $permalink, $site_url ) ) {
536 $DONOTREMEMBER = 1;
537 if ( preg_match( '`^(https?:)?//([^/]+)(/.*)?$`i', $permalink, $matches ) ) {
538 $uri = isset( $matches[3] ) ? $matches[3] : '';
539 } elseif ( preg_match( '`^/([^/]+)(/.*)?$`i', $permalink, $matches ) ) {
540 $uri = $permalink;
541 } else {
542 $uri = '';
543 }
544 } else {
545 $uri = str_replace( $site_url, '', $permalink );
546 if ( 0 !== strpos( $uri, '/' ) ) {
547 $uri = '/' . $uri;
548 }
549 }
550 }
551
552 $uri = $this->deep_replace(
553 [
554 '..',
555 '\\',
556 'index.php'
557 ],
558 preg_replace(
559 '/[ <>\'\"\r\n\t\(\)]/',
560 '',
561 preg_replace( "/(\?.*)?(#.*)?$/", '', $uri )
562 )
563 );
564
565 $uri = md5( $uri );
566 $dir = str_replace( '..', '', str_replace( '//', '/', $uri . '/' ) );
567
568 if ( $DONOTREMEMBER == 0 ) {
569 $url_cache_dir[ $post_id ] = $dir;
570 }
571
572 return $dir;
573 }
574
575 /**
576 * Get the cache directory path
577 *
578 * @param int $postid
579 *
580 * @return string
581 */
582 public function get_real_cache_dir( $postid = 0 ) {
583 return $this->get_default_cache_path() . $this->get_current_url_cache_dir( $postid );
584 }
585
586 /**
587 * Get the full cache file path
588 *
589 * @param int $postid
590 *
591 * @return string
592 */
593 public function get_cache_file_path( $postid = 0 ) {
594 return $this->get_real_cache_dir( $postid ) . $this->get_cache_filename();
595 }
596
597 /**
598 * Get the filename for the cached file
599 *
600 * @return string
601 */
602 public function get_cache_filename() {
603 $settings = $this->settings;
604
605 // Add support for https and http caching
606 // also supports https requests coming from an nginx reverse proxy
607 $is_https = ( ( isset( $_SERVER['HTTPS'] ) && 'on' == strtolower( $_SERVER['HTTPS'] ) ) || ( isset( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) && 'https' == strtolower( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) ) );
608 $extra_str = $is_https ? '-https' : '';
609
610 if ( $settings->separate_mobile_cache ) {
611 $mobile_ua = $this->detect_mobile();
612 if ( ! empty( $mobile_ua ) ) {
613 $extra_str .= '-mobile';
614 }
615 }
616
617 if ( $settings->enable_webp_support && $this->webp_accepted()) {
618 $extra_str .= '-webp';
619 }
620
621 $filename = 'index';
622 if ( ! empty( $_SERVER['QUERY_STRING'] ) ) {
623 $filename = md5( $_SERVER['QUERY_STRING'] );
624 }
625
626 return $filename . $extra_str . '.html';
627 }
628
629 /**
630 * Check if we have a cached file and serve it
631 */
632 public function maybe_serve_cached_data() {
633 if ( ! $this->should_serve_cached_data() ) {
634 return;
635 }
636
637 $cache_file = $this->get_cache_file_path();
638 $gzip_accepted = $this->gzip_accepted();
639
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;
643
644 if ( ! $filesize ) {
645 // the file is empty, we have nothing to serve
646 return;
647 }
648
649 header( "X-Cached-With: ezCache" );
650 header( "Vary: Accept-Encoding, Cookie" );
651 // header( "Content-Length: {$filesize}" );
652 header( 'Last-Modified: ' . gmdate( 'D, d M Y H:i:s', filemtime( $cache_file ) ) . ' GMT' );
653
654 // Getting If-Modified-Since headers sent by the client.
655 if ( function_exists( 'apache_request_headers' ) ) {
656 $headers = apache_request_headers();
657 $http_if_modified_since = ( isset( $headers['If-Modified-Since'] ) ) ? $headers['If-Modified-Since'] : '';
658 } else {
659 $http_if_modified_since = ( isset( $_SERVER['HTTP_IF_MODIFIED_SINCE'] ) ) ? $_SERVER['HTTP_IF_MODIFIED_SINCE'] : '';
660 }
661
662 // Checking if the client is validating his cache and if it is current.
663 if ( $http_if_modified_since && ( strtotime( $http_if_modified_since ) === @filemtime( $cache_file ) ) ) {
664 // Client's cache is current, so we just respond '304 Not Modified'.
665 header( $_SERVER['SERVER_PROTOCOL'] . ' 304 Not Modified', true, 304 );
666 exit;
667 }
668
669 // Serve the cache if file isn't store in the client browser cache.
670 if ( $serve_gzip ) {
671 readgzfile( $cache_file );
672 exit;
673 }
674
675 readfile( $cache_file );
676 exit;
677 }
678
679 /**
680 * Write cache file if we need to
681 */
682 public function maybe_write_cache_file() {
683 if ( ! $this->should_serve_cached_data() ) {
684 return;
685 }
686
687 ob_start( [ $this, 'optimize_and_write_cache_file' ] );
688 }
689
690 /**
691 * Optimize output and write the buffer to the cache file
692 *
693 * @param string $buffer
694 *
695 * @return string
696 */
697 public function optimize_and_write_cache_file( $buffer ) {
698 global $wpdb;
699
700 // we need these check to run after WordPress is finished preparing the page
701 if ( ! $this->should_save_cache() ) {
702 return $buffer;
703 }
704
705 $real_cache_dir = $this->get_real_cache_dir();
706 $cache_file = $this->get_cache_file_path();
707 $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;
710
711 if ( $settings->minify_css ) {
712 if ( $settings->combine_css ) {
713 $optimizer = new CssCombiner( $asset_cache_dir, $asset_cache_url );
714 } else {
715 $optimizer = new CssMinifier( $asset_cache_dir, $asset_cache_url );
716 }
717
718 $buffer = $optimizer->optimize( $buffer );
719 }
720
721 if ( $settings->minify_js ) {
722 if ( $settings->combine_head_js ) {
723 $optimizer = new JsCombiner( $asset_cache_dir, $asset_cache_url, 'head' );
724 $buffer = $optimizer->optimize( $buffer );
725 }
726
727 if ( $settings->combine_body_js ) {
728 $optimizer = new JsCombiner( $asset_cache_dir, $asset_cache_url, 'body' );
729 $buffer = $optimizer->optimize( $buffer );
730 }
731
732 if( ! $settings->combine_head_js && ! $settings->combine_body_js ) {
733 $optimizer = new JsMinifier( $asset_cache_dir, $asset_cache_url );
734 $buffer = $optimizer->optimize( $buffer );
735 }
736 }
737
738 if ( $settings->minify_html ) {
739 $buffer = Minify_HTML::minify( $buffer, [
740 'cssMinifier' => function( $css ) use ($settings) {
741 if ( ! $settings->minify_inline_css ) {
742 return $css;
743 }
744
745 $minifier = new CSS( $css );
746 return $minifier->minify();
747 },
748
749 'jsMinifier' => function( $js ) use ($settings) {
750 if ( ! $settings->minify_inline_js ) {
751 return $js;
752 }
753
754 $minifier = new JS( $js );
755 return $minifier->minify();
756 },
757 ] );
758 }
759
760 if ( $settings->enable_webp_support && $this->webp_accepted() ) {
761 $optimizer = new WebpConverter( $real_cache_dir, $cache_file, $this->webp_processor, $wpdb );
762 $buffer = $optimizer->optimize( $buffer );
763 }
764
765 if ( ! apply_filters( 'wp_bost_hide_cache_time_comment', false ) ) {
766 $total_time = number_format( microtime( true ) - $this->cache_start_time, 2 );
767 $buffer .= "\n<!-- Cached by ezCache -->\n<!-- Cache created in {$total_time}s -->";
768 }
769
770 $buffer = apply_filters( 'ezcache_before_save_cache', $buffer );
771
772 if ( ! file_exists( $real_cache_dir ) ) {
773 if ( ! @wp_mkdir_p( $real_cache_dir ) ) {
774 error_log( 'ezCache could not create directory ' . $real_cache_dir );
775 return $buffer;
776 }
777 }
778
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 ) );
782 }
783
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' ) );
787 }
788
789 $this->webp_processor->dispatch();
790
791 return $buffer;
792 }
793
794 /**
795 * Delete a path recursively
796 *
797 * @param string $path
798 */
799 public function rmdir_recursive( $path ) {
800 if ( ! file_exists( $path ) ) {
801 return;
802 }
803
804 $files = glob( $path . '/*' );
805 foreach ( $files as $file ) {
806 is_dir( $file ) ? $this->rmdir_recursive( $file ) : unlink( $file );
807 }
808 rmdir( $path );
809 }
810
811 /**
812 * Preload the homepage and immediately create cache for it
813 */
814 public function preload_homepage() {
815 $desktop_ua = apply_filters(
816 'ezcache_desktop_useragent',
817 '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)'
818 );
819 $mobile_ua = apply_filters(
820 'ezcache_mobile_useragent',
821 '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 );
823
824 wp_safe_remote_get( home_url(), [
825 'user-agent' => $desktop_ua,
826 'timeout' => 0.1,
827 ] );
828
829 wp_safe_remote_get( home_url(), [
830 'user-agent' => $mobile_ua,
831 'timeout' => 0.1,
832 ] );
833 }
834
835 function delete_missing_webp_images() {
836 global $wpdb;
837
838 $ids = [0];
839 $images = $wpdb->get_results( "SELECT * FROM `{$wpdb->prefix}ezcache_webp_images` WHERE `status` = 'completed'" );
840 foreach( $images as $image ) {
841 if ( ! file_exists( $image->webp_path ) ) {
842 $ids[] = $image->id;
843 } elseif ( ! file_exists( $image->path ) && file_exists( $image->webp_path ) ) {
844 unlink( $image->webp_path );
845 $ids[] = $image->id;
846 }
847 }
848
849 $wpdb->query(
850 $wpdb->prepare(
851 "DELETE FROM `{$wpdb->prefix}ezcache_webp_images` WHERE `status` = 'failed' OR `id` IN ( " . substr( str_repeat( "%d, ", count( $ids ) ), 0, -2 ) . " )",
852 $ids
853 )
854 );
855
856 $wpdb->query( "OPTIMIZE TABLE `{$wpdb->prefix}ezcache_webp_images`" );
857 }
858
859 /**
860 * Clear all caches
861 */
862 public function clear_cache() {
863 $cache_dir = $this->get_default_cache_path();
864
865 $this->rmdir_recursive( $cache_dir );
866 @wp_mkdir_p( $cache_dir );
867
868 $this->delete_missing_webp_images();
869
870 $this->preload_homepage();
871 }
872
873 /**
874 * Clear cache for a single post
875 *
876 * @param int $post_id
877 */
878 public function clear_cache_single( $post_id ) {
879 $real_cache_dir = $this->get_real_cache_dir( $post_id );
880
881 $this->rmdir_recursive( $real_cache_dir );
882 }
883
884 /**
885 * Delete expired cache
886 */
887 public function clear_expired_cache() {
888 $settings = $this->settings;
889
890 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) {
895 if ( strpos( $ex->getMessage(), 'No such file or directory' ) ) {
896 $files = [];
897 } else {
898 throw $ex;
899 }
900 }
901
902 foreach($files as $file) {
903 if ( is_array( $file ) ) {
904 $file = array_shift( $file );
905 }
906
907 $stats = stat( $file );
908 if ( $stats['mtime'] > ( time() - $settings->cache_lifetime ) ) {
909 // skip not expired files
910 continue;
911 }
912
913 @unlink( $file );
914 }
915 }
916
917 /**
918 * Get caching statistics and file sizes
919 * @return array
920 */
921 public function get_cache_stats() {
922 $settings = $this->settings;
923 $cache_dir = $this->get_default_cache_path();
924
925 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) {
930 if ( strpos( $ex->getMessage(), 'No such file or directory' ) ) {
931 $files = [];
932 } else {
933 throw $ex;
934 }
935 }
936
937 $raw_data = [];
938
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;
945 $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;
955
956 foreach($files as $file) {
957 if ( is_array( $file ) ) {
958 $file = array_shift( $file );
959 }
960
961 $stats = stat( $file );
962 $expired = $stats['mtime'] <= (time() - $settings->cache_lifetime);
963
964 $raw_data[] = [
965 'path' => $file,
966 'stats' => $stats,
967 'expired' => $expired,
968 ];
969
970 if ( preg_match( '/^.+?-mobile\.html(\.gz)?$/i', $file ) ) {
971 if ( ! $expired ) {
972 $mobile_count++;
973 $mobile_size += $stats['size'];
974 } else {
975 $mobile_expired_count++;
976 $mobile_expired_size += $stats['size'];
977 }
978 } elseif( preg_match( '/\.css$/i', $file ) ) {
979 if ( $expired ) {
980 $css_expired_count++;
981 $css_expired_size += $stats['size'];
982 } else {
983 $css_count ++;
984 $css_size += $stats['size'];
985 }
986 } elseif( preg_match( '/\.js$/i', $file ) ) {
987 if ( $expired ) {
988 $js_expired_count++;
989 $js_expired_size += $stats['size'];
990 } else {
991 $js_count ++;
992 $js_size += $stats['size'];
993 }
994 } else {
995 if ( ! $expired ) {
996 $desktop_count++;
997 $desktop_size += $stats['size'];
998 } else {
999 $desktop_expired_count++;
1000 $desktop_expired_size += $stats['size'];
1001 }
1002 }
1003 }
1004
1005 // 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;
1007 $desktop_count = $desktop_count / 2;
1008
1009
1010 global $wpdb;
1011 $webp_images = 0;
1012 $webp_images_size = 0;
1013 $webp_images_original_size = 0;
1014
1015 $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 if ( $results ) {
1017 $webp_images = intval( $results->total );
1018 $webp_images_size = intval( $results->total_webp_size );
1019 $webp_images_original_size = intval( $results->total_original_size );
1020 }
1021
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' );
1023 }
1024
1025 /**
1026 * Get the status of the cache
1027 *
1028 * @return array
1029 */
1030 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' );
1033 $correct_advanced_cache = $adv_cache_exists && strpos( file_get_contents( WP_CONTENT_DIR . '/advanced-cache.php' ), 'ezCache Advanced Cache' ) !== false;
1034
1035 return [
1036 'cache_enabled' => $wp_cache_enabled,
1037 'adv_cache_exists' => $adv_cache_exists,
1038 'correct_cache_exists' => $correct_advanced_cache,
1039 ];
1040 }
1041
1042 /**
1043 * Get a path by the URL
1044 *
1045 * @param string $url
1046 *
1047 * @return bool|string
1048 */
1049 public static function url_to_path( $url ) {
1050 $root_dir = trailingslashit( dirname( WP_CONTENT_DIR ) );
1051 $root_url = str_replace( wp_basename( WP_CONTENT_DIR ), '', WP_CONTENT_URL );
1052 $url_host = wp_parse_url( $url, PHP_URL_HOST );
1053
1054 if ( null === $url_host ) {
1055 $subdir_levels = substr_count( preg_replace( '/https?:\/\//','', site_url() ), '/' );
1056 $url = site_url() . str_repeat( '/..', $subdir_levels ) . $url;
1057 }
1058
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 );
1063
1064 if ( ! file_exists( $file ) ) {
1065 return false;
1066 }
1067
1068 return $file;
1069 }
1070
1071 /**
1072 * Returns canonicalized absolute pathname.
1073 * The resulting path will have no symbolic link, '/./' or '/../' components.
1074 * Same as the defautl PHP realpath() function but works even when the files does not exist.
1075 *
1076 * @see \realpath()
1077 *
1078 * @param string $file The path being checked.
1079 *
1080 * @return string
1081 */
1082 public static function realpath( $file ) {
1083 $path = array();
1084
1085 foreach ( explode( '/', $file ) as $part ) {
1086 if ( '' === $part || '.' === $part ) {
1087 continue;
1088 }
1089
1090 if ( '..' !== $part ) {
1091 array_push( $path, $part );
1092 }
1093 elseif ( count( $path ) > 0 ) {
1094 array_pop( $path );
1095 }
1096 }
1097
1098 $prefix = 'WIN' === strtoupper( substr( PHP_OS, 0, 3 ) ) ? '' : '/';
1099
1100 return $prefix . join( '/', $path );
1101 }
1102 }
1103