PluginProbe
Autoptimize / 2.4.0
Autoptimize v2.4.0
2.2.2 2.3.0 2.3.1 2.3.2 2.3.3 2.3.4 2.4.0 2.4.1 2.4.2 2.4.3 2.4.4 2.5.0 2.5.1 2.6.0 2.6.1 2.6.2 2.7.0 2.7.1 2.7.2 2.7.3 2.7.4 2.7.5 2.7.6 2.7.7 2.7.8 All 107 releases
autoptimize / classes / autoptimizeCache.php

autoptimizeCache.php in Autoptimize 2.4.0, at classes/autoptimizeCache.php

662 lines 20.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Handles disk-cache-related operations.
4 */
5
6 if ( ! defined( 'ABSPATH' ) ) {
7 exit;
8 }
9
10 class autoptimizeCache
11 {
12 /**
13 * Cache filename.
14 *
15 * @var string
16 */
17 private $filename;
18
19 /**
20 * Cache directory path (with a trailing slash).
21 *
22 * @var string
23 */
24 private $cachedir;
25
26 /**
27 * Whether gzipping is done by the web server or us.
28 * True => we don't gzip, the web server does it.
29 * False => we do it ourselves.
30 *
31 * @var bool
32 */
33 private $nogzip;
34
35 /**
36 * Ctor.
37 *
38 * @param string $md5 Hash.
39 * @param string $ext Extension.
40 */
41 public function __construct( $md5, $ext = 'php' )
42 {
43 $this->cachedir = AUTOPTIMIZE_CACHE_DIR;
44 $this->nogzip = AUTOPTIMIZE_CACHE_NOGZIP;
45 if ( ! $this->nogzip ) {
46 $this->filename = AUTOPTIMIZE_CACHEFILE_PREFIX . $md5 . '.php';
47 } else {
48 if ( in_array( $ext, array( 'js', 'css' ) ) ) {
49 $this->filename = $ext . '/' . AUTOPTIMIZE_CACHEFILE_PREFIX . $md5 . '.' . $ext;
50 } else {
51 $this->filename = AUTOPTIMIZE_CACHEFILE_PREFIX . $md5 . '.' . $ext;
52 }
53 }
54 }
55
56 /**
57 * Returns true if the cached file exists on disk.
58 *
59 * @return bool
60 */
61 public function check()
62 {
63 return file_exists( $this->cachedir . $this->filename );
64 }
65
66 /**
67 * Returns cache contents if they exist, false otherwise.
68 *
69 * @return string|false
70 */
71 public function retrieve()
72 {
73 if ( $this->check() ) {
74 if ( false == $this->nogzip ) {
75 return file_get_contents( $this->cachedir . $this->filename . '.none' );
76 } else {
77 return file_get_contents( $this->cachedir . $this->filename );
78 }
79 }
80 return false;
81 }
82
83 /**
84 * Stores given $data in cache.
85 *
86 * @param string $data Data to cache.
87 * @param string $mime Mimetype.
88 *
89 * @return void
90 */
91 public function cache( $data, $mime )
92 {
93 if ( false === $this->nogzip ) {
94 // We handle gzipping ourselves.
95 $file = 'default.php';
96 $phpcode = file_get_contents( AUTOPTIMIZE_PLUGIN_DIR . 'config/' . $file );
97 $phpcode = str_replace( array( '%%CONTENT%%', 'exit;' ), array( $mime, '' ), $phpcode );
98
99 file_put_contents( $this->cachedir . $this->filename, $phpcode );
100 file_put_contents( $this->cachedir . $this->filename . '.none', $data );
101 } else {
102 // Write code to cache without doing anything else.
103 file_put_contents( $this->cachedir . $this->filename, $data );
104 if ( apply_filters( 'autoptimize_filter_cache_create_static_gzip', false ) ) {
105 // Create an additional cached gzip file.
106 file_put_contents( $this->cachedir . $this->filename . '.gz', gzencode( $data, 9, FORCE_GZIP ) );
107 }
108 }
109 }
110
111 /**
112 * Get cache filename.
113 *
114 * @return string
115 */
116 public function getname()
117 {
118 // NOTE: This could've maybe been a do_action() instead, however,
119 // that ship has sailed.
120 // The original idea here was to provide 3rd party code a hook so that
121 // it can "listen" to all the complete autoptimized-urls that the page
122 // will emit... Or something to that effect I think?
123 apply_filters( 'autoptimize_filter_cache_getname', AUTOPTIMIZE_CACHE_URL . $this->filename );
124
125 return $this->filename;
126 }
127
128 /**
129 * Returns true if given `$file` is considered a valid Autoptimize cache file,
130 * false otherwise.
131 *
132 * @param string $dir Directory name (with a trailing slash).
133 * @param string $file Filename.
134 * @return bool
135 */
136 protected static function is_valid_cache_file( $dir, $file )
137 {
138 if ( '.' !== $file && '..' !== $file &&
139 false !== strpos( $file, AUTOPTIMIZE_CACHEFILE_PREFIX ) &&
140 is_file( $dir . $file ) ) {
141
142 // It's a valid file!
143 return true;
144 }
145
146 // Everything else is considered invalid!
147 return false;
148 }
149
150 /**
151 * Clears contents of AUTOPTIMIZE_CACHE_DIR.
152 *
153 * @return void
154 */
155 protected static function clear_cache_classic()
156 {
157 $contents = self::get_cache_contents();
158 foreach ( $contents as $name => $files ) {
159 $dir = rtrim( AUTOPTIMIZE_CACHE_DIR . $name, '/' ) . '/';
160 foreach ( $files as $file ) {
161 if ( self::is_valid_cache_file( $dir, $file ) ) {
162 @unlink( $dir . $file ); // @codingStandardsIgnoreLine
163 }
164 }
165 }
166
167 @unlink( AUTOPTIMIZE_CACHE_DIR . '/.htaccess' ); // @codingStandardsIgnoreLine
168 }
169
170 /**
171 * Recursively deletes the specified pathname (file/directory) if possible.
172 * Returns true on success, false otherwise.
173 *
174 * @param string $pathname Pathname to remove.
175 *
176 * @return bool
177 */
178 protected static function rmdir( $pathname )
179 {
180 $files = self::get_dir_contents( $pathname );
181 foreach ( $files as $file ) {
182 $path = $pathname . '/' . $file;
183 if ( is_dir( $path ) ) {
184 self::rmdir( $path );
185 } else {
186 unlink( $path );
187 }
188 }
189
190 return rmdir( $pathname );
191 }
192
193 /**
194 * Clears contents of AUTOPTIMIZE_CACHE_DIR by renaming the current
195 * cache directory into a new one with a unique name and then
196 * re-creating the default (empty) cache directory.
197 *
198 * @return bool Returns true when everything is done successfully, false otherwise.
199 */
200 protected static function clear_cache_via_rename()
201 {
202 $ok = false;
203 $dir = self::get_pathname_base();
204 $new_name = self::get_unique_name();
205
206 // Makes sure the new pathname is on the same level...
207 $new_pathname = dirname( $dir ) . '/' . $new_name;
208 $renamed = @rename( $dir, $new_pathname ); // @codingStandardsIgnoreLine
209
210 // When renamed, re-create the default cache directory back so it's
211 // available again...
212 if ( $renamed ) {
213 $ok = self::cacheavail();
214 }
215
216 return $ok;
217 }
218
219 /**
220 * Returns true when advanced cache clearing is enabled.
221 *
222 * @return bool
223 */
224 public static function advanced_cache_clear_enabled()
225 {
226 return apply_filters( 'autoptimize_filter_cache_clear_advanced', false );
227 }
228
229 /**
230 * Returns a (hopefully) unique new cache folder name for renaming purposes.
231 *
232 * @return string
233 */
234 protected static function get_unique_name()
235 {
236 $prefix = self::get_advanced_cache_clear_prefix();
237 $new_name = uniqid( $prefix, true );
238
239 return $new_name;
240 }
241
242 /**
243 * Get cache prefix name used in advanced cache clearing mode.
244 *
245 * @return string
246 */
247 protected static function get_advanced_cache_clear_prefix()
248 {
249 $pathname = self::get_pathname_base();
250 $basename = basename( $pathname );
251 $prefix = $basename . '-';
252
253 return $prefix;
254 }
255
256 /**
257 * Returns an array of file and directory names found within
258 * the given $pathname without '.' and '..' elements.
259 *
260 * @param string $pathname Pathname.
261 *
262 * @return array
263 */
264 protected static function get_dir_contents( $pathname )
265 {
266 return array_slice( scandir( $pathname ), 2 );
267 }
268
269 /**
270 * Wipes directories which were created as part of the fast cache clearing
271 * routine (which renames the current cache directory into a new one with
272 * a custom-prefixed unique name).
273 *
274 * @return bool
275 */
276 public static function delete_advanced_cache_clear_artifacts()
277 {
278 $dir = self::get_pathname_base();
279 $prefix = self::get_advanced_cache_clear_prefix();
280 $parent = dirname( $dir );
281 $ok = false;
282
283 // Returns the list of files without '.' and '..' elements.
284 $files = self::get_dir_contents( $parent );
285 foreach ( $files as $file ) {
286 $path = $parent . '/' . $file;
287 $prefixed = ( false !== strpos( $path, $prefix ) );
288 // Removing only our own (prefixed) directories...
289 if ( is_dir( $path ) && $prefixed ) {
290 $ok = self::rmdir( $path );
291 }
292 }
293
294 return $ok;
295 }
296
297 /**
298 * Returns the cache directory pathname used.
299 * Done as a function so we canSlightly different
300 * if multisite is used and `autoptimize_separate_blog_caches` filter
301 * is used.
302 *
303 * @return string
304 */
305 public static function get_pathname()
306 {
307 $pathname = self::get_pathname_base();
308
309 if ( is_multisite() && apply_filters( 'autoptimize_separate_blog_caches', true ) ) {
310 $blog_id = get_current_blog_id();
311 $pathname .= $blog_id . '/';
312 }
313
314 return $pathname;
315 }
316
317 /**
318 * Returns the base path of our cache directory.
319 *
320 * @return string
321 */
322 protected static function get_pathname_base()
323 {
324 $pathname = WP_CONTENT_DIR . AUTOPTIMIZE_CACHE_CHILD_DIR;
325
326 return $pathname;
327 }
328
329 /**
330 * Deletes everything from the cache directories.
331 *
332 * @param bool $propagate Whether to trigger additional actions when cache is purged.
333 *
334 * @return bool
335 */
336 public static function clearall( $propagate = true )
337 {
338 if ( ! self::cacheavail() ) {
339 return false;
340 }
341
342 // TODO/FIXME: If cache is big, switch to advanced/new cache clearing automatically?
343 if ( self::advanced_cache_clear_enabled() ) {
344 self::clear_cache_via_rename();
345 } else {
346 self::clear_cache_classic();
347 }
348
349 // Remove the transient so it gets regenerated...
350 delete_transient( 'autoptimize_stats' );
351
352 // Cache was just purged, clear page cache and allow others to hook into our purging...
353 if ( true === $propagate ) {
354 if ( ! function_exists( 'autoptimize_do_cachepurged_action' ) ) {
355 function autoptimize_do_cachepurged_action() {
356 do_action( 'autoptimize_action_cachepurged' );
357 }
358 }
359 add_action( 'shutdown', 'autoptimize_do_cachepurged_action', 11 );
360 add_action( 'autoptimize_action_cachepurged', array( 'autoptimizeCache', 'flushPageCache' ), 10, 0 );
361 }
362
363 // Warm cache (part of speedupper)!
364 if ( apply_filters( 'autoptimize_filter_speedupper', true ) ) {
365 $url = site_url() . '/?ao_speedup_cachebuster=' . rand( 1, 100000 );
366 $cache = @wp_remote_get( $url ); // @codingStandardsIgnoreLine
367 unset( $cache );
368 }
369
370 return true;
371 }
372
373 /**
374 * Wrapper for clearall but with false param
375 * to ensure the event is not propagated to others
376 * through our own hooks (to avoid infinite loops).
377 *
378 * @return bool
379 */
380 public static function clearall_actionless()
381 {
382 return self::clearall( false );
383 }
384
385 /**
386 * Returns the contents of our cache dirs.
387 *
388 * @return array
389 */
390 protected static function get_cache_contents()
391 {
392 $contents = array();
393
394 foreach ( array( '', 'js', 'css' ) as $dir ) {
395 $contents[ $dir ] = scandir( AUTOPTIMIZE_CACHE_DIR . $dir );
396 }
397
398 return $contents;
399 }
400
401 /**
402 * Returns stats about cached contents.
403 *
404 * @return array
405 */
406 public static function stats()
407 {
408 $stats = get_transient( 'autoptimize_stats' );
409
410 // If no transient, do the actual scan!
411 if ( ! is_array( $stats ) ) {
412 if ( ! self::cacheavail() ) {
413 return 0;
414 }
415 $stats = self::stats_scan();
416 $count = $stats[0];
417 if ( $count > 100 ) {
418 // Store results in transient.
419 set_transient(
420 'autoptimize_stats',
421 $stats,
422 apply_filters( 'autoptimize_filter_cache_statsexpiry', HOUR_IN_SECONDS )
423 );
424 }
425 }
426
427 return $stats;
428 }
429
430 /**
431 * Performs a scan of cache directory contents and returns an array
432 * with 3 values: count, size, timestamp.
433 * count = total number of found files
434 * size = total filesize (in bytes) of found files
435 * timestamp = unix timestamp when the scan was last performed/finished.
436 *
437 * @return array
438 */
439 protected static function stats_scan()
440 {
441 $count = 0;
442 $size = 0;
443
444 // Scan everything in our cache directories.
445 foreach ( self::get_cache_contents() as $name => $files ) {
446 $dir = rtrim( AUTOPTIMIZE_CACHE_DIR . $name, '/' ) . '/';
447 foreach ( $files as $file ) {
448 if ( self::is_valid_cache_file( $dir, $file ) ) {
449 if ( AUTOPTIMIZE_CACHE_NOGZIP &&
450 (
451 false !== strpos( $file, '.js' ) ||
452 false !== strpos( $file, '.css' ) ||
453 false !== strpos( $file, '.img' ) ||
454 false !== strpos( $file, '.txt' )
455 )
456 ) {
457 // Web server is gzipping, we count .js|.css|.img|.txt files.
458 $count++;
459 } elseif ( ! AUTOPTIMIZE_CACHE_NOGZIP && false !== strpos( $file, '.none' ) ) {
460 // We are gzipping ourselves via php, counting only .none files.
461 $count++;
462 }
463 $size += filesize( $dir . $file );
464 }
465 }
466 }
467
468 $stats = array( $count, $size, time() );
469
470 return $stats;
471 }
472
473 /**
474 * Ensures the cache directory exists, is writeable and contains the
475 * required .htaccess files.
476 * Returns false in case it fails to ensure any of those things.
477 *
478 * @return bool
479 */
480 public static function cacheavail()
481 {
482 if ( ! defined( 'AUTOPTIMIZE_CACHE_DIR' ) ) {
483 // We didn't set a cache.
484 return false;
485 }
486
487 foreach ( array( '', 'js', 'css' ) as $dir ) {
488 if ( ! self::check_cache_dir( AUTOPTIMIZE_CACHE_DIR . $dir ) ) {
489 return false;
490 }
491 }
492
493 // Using .htaccess inside our cache folder to overrule wp-super-cache.
494 $htaccess = AUTOPTIMIZE_CACHE_DIR . '/.htaccess';
495 if ( ! is_file( $htaccess ) ) {
496 /**
497 * Create `wp-content/AO_htaccess_tmpl` file with
498 * whatever htaccess rules you might need
499 * if you want to override default AO htaccess
500 */
501 $htaccess_tmpl = WP_CONTENT_DIR . '/AO_htaccess_tmpl';
502 if ( is_file( $htaccess_tmpl ) ) {
503 $content = file_get_contents( $htaccess_tmpl );
504 } elseif ( is_multisite() || ! AUTOPTIMIZE_CACHE_NOGZIP ) {
505 $content = '<IfModule mod_expires.c>
506 ExpiresActive On
507 ExpiresByType text/css A30672000
508 ExpiresByType text/javascript A30672000
509 ExpiresByType application/javascript A30672000
510 </IfModule>
511 <IfModule mod_headers.c>
512 Header append Cache-Control "public, immutable"
513 </IfModule>
514 <IfModule mod_deflate.c>
515 <FilesMatch "\.(js|css)$">
516 SetOutputFilter DEFLATE
517 </FilesMatch>
518 </IfModule>
519 <IfModule mod_authz_core.c>
520 <Files *.php>
521 Require all granted
522 </Files>
523 </IfModule>
524 <IfModule !mod_authz_core.c>
525 <Files *.php>
526 Order allow,deny
527 Allow from all
528 </Files>
529 </IfModule>';
530 } else {
531 $content = '<IfModule mod_expires.c>
532 ExpiresActive On
533 ExpiresByType text/css A30672000
534 ExpiresByType text/javascript A30672000
535 ExpiresByType application/javascript A30672000
536 </IfModule>
537 <IfModule mod_headers.c>
538 Header append Cache-Control "public, immutable"
539 </IfModule>
540 <IfModule mod_deflate.c>
541 <FilesMatch "\.(js|css)$">
542 SetOutputFilter DEFLATE
543 </FilesMatch>
544 </IfModule>
545 <IfModule mod_authz_core.c>
546 <Files *.php>
547 Require all denied
548 </Files>
549 </IfModule>
550 <IfModule !mod_authz_core.c>
551 <Files *.php>
552 Order deny,allow
553 Deny from all
554 </Files>
555 </IfModule>';
556 }
557 @file_put_contents( $htaccess, $content ); // @codingStandardsIgnoreLine
558 }
559
560 // All OK!
561 return true;
562 }
563
564 /**
565 * Ensures the specified `$dir` exists and is writeable.
566 * Returns false if that's not the case.
567 *
568 * @param string $dir Directory to check/create.
569 *
570 * @return bool
571 */
572 protected static function check_cache_dir( $dir )
573 {
574 // Try creating the dir if it doesn't exist.
575 if ( ! file_exists( $dir ) ) {
576 @mkdir( $dir, 0775, true ); // @codingStandardsIgnoreLine
577 if ( ! file_exists( $dir ) ) {
578 return false;
579 }
580 }
581
582 // If we still cannot write, bail.
583 if ( ! is_writable( $dir ) ) {
584 return false;
585 }
586
587 // Create an index.html in there to avoid prying eyes!
588 $idx_file = rtrim( $dir, '/\\' ) . '/index.html';
589 if ( ! is_file( $idx_file ) ) {
590 @file_put_contents( $idx_file, '<html><head><meta name="robots" content="noindex, nofollow"></head><body>Generated by <a href="http://wordpress.org/extend/plugins/autoptimize/" rel="nofollow">Autoptimize</a></body></html>' ); // @codingStandardsIgnoreLine
591 }
592
593 return true;
594 }
595
596 /**
597 * Flushes as many page cache plugin's caches as possible.
598 *
599 * @return void
600 */
601 // @codingStandardsIgnoreStart
602 public static function flushPageCache()
603 {
604 if ( function_exists( 'wp_cache_clear_cache' ) ) {
605 if ( is_multisite() ) {
606 $blog_id = get_current_blog_id();
607 wp_cache_clear_cache( $blog_id );
608 } else {
609 wp_cache_clear_cache();
610 }
611 } elseif ( has_action( 'cachify_flush_cache' ) ) {
612 do_action( 'cachify_flush_cache' );
613 } elseif ( function_exists( 'w3tc_pgcache_flush' ) ) {
614 w3tc_pgcache_flush();
615 } elseif ( function_exists( 'wp_fast_cache_bulk_delete_all' ) ) {
616 wp_fast_cache_bulk_delete_all();
617 } elseif ( class_exists( 'WpFastestCache' ) ) {
618 $wpfc = new WpFastestCache();
619 $wpfc->deleteCache();
620 } elseif ( class_exists( 'c_ws_plugin__qcache_purging_routines' ) ) {
621 c_ws_plugin__qcache_purging_routines::purge_cache_dir(); // quick cache
622 } elseif ( class_exists( 'zencache' ) ) {
623 zencache::clear();
624 } elseif ( class_exists( 'comet_cache' ) ) {
625 comet_cache::clear();
626 } elseif ( class_exists( 'WpeCommon' ) ) {
627 // WPEngine cache purge/flush methods to call by default
628 $wpe_methods = array(
629 'purge_varnish_cache',
630 );
631
632 // More agressive clear/flush/purge behind a filter
633 if ( apply_filters( 'autoptimize_flush_wpengine_aggressive', false ) ) {
634 $wpe_methods = array_merge( $wpe_methods, array( 'purge_memcached', 'clear_maxcdn_cache' ) );
635 }
636
637 // Filtering the entire list of WpeCommon methods to be called (for advanced usage + easier testing)
638 $wpe_methods = apply_filters( 'autoptimize_flush_wpengine_methods', $wpe_methods );
639
640 foreach ( $wpe_methods as $wpe_method ) {
641 if ( method_exists( 'WpeCommon', $wpe_method ) ) {
642 WpeCommon::$wpe_method();
643 }
644 }
645 } elseif ( function_exists( 'sg_cachepress_purge_cache' ) ) {
646 sg_cachepress_purge_cache();
647 } elseif ( file_exists( WP_CONTENT_DIR . '/wp-cache-config.php' ) && function_exists( 'prune_super_cache' ) ) {
648 // fallback for WP-Super-Cache
649 global $cache_path;
650 if ( is_multisite() ) {
651 $blog_id = get_current_blog_id();
652 prune_super_cache( get_supercache_dir( $blog_id ), true );
653 prune_super_cache( $cache_path . 'blogs/', true );
654 } else {
655 prune_super_cache( $cache_path . 'supercache/', true );
656 prune_super_cache( $cache_path, true );
657 }
658 }
659 }
660 // @codingStandardsIgnoreEnd
661 }
662