PluginProbe
ezCache / 1.3
ezCache v1.3
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.3, at includes/Cache.php

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