PluginProbe
Autoptimize / 2.4.4
Autoptimize v2.4.4
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.4, at classes/autoptimizeCache.php

664 lines 20.5 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 if ( is_array( $files ) && ! empty( $files ) ) {
286 foreach ( $files as $file ) {
287 $path = $parent . '/' . $file;
288 $prefixed = ( false !== strpos( $path, $prefix ) );
289 // Removing only our own (prefixed) directories...
290 if ( is_dir( $path ) && $prefixed ) {
291 $ok = self::rmdir( $path );
292 }
293 }
294 }
295
296 return $ok;
297 }
298
299 /**
300 * Returns the cache directory pathname used.
301 * Done as a function so we canSlightly different
302 * if multisite is used and `autoptimize_separate_blog_caches` filter
303 * is used.
304 *
305 * @return string
306 */
307 public static function get_pathname()
308 {
309 $pathname = self::get_pathname_base();
310
311 if ( is_multisite() && apply_filters( 'autoptimize_separate_blog_caches', true ) ) {
312 $blog_id = get_current_blog_id();
313 $pathname .= $blog_id . '/';
314 }
315
316 return $pathname;
317 }
318
319 /**
320 * Returns the base path of our cache directory.
321 *
322 * @return string
323 */
324 protected static function get_pathname_base()
325 {
326 $pathname = WP_CONTENT_DIR . AUTOPTIMIZE_CACHE_CHILD_DIR;
327
328 return $pathname;
329 }
330
331 /**
332 * Deletes everything from the cache directories.
333 *
334 * @param bool $propagate Whether to trigger additional actions when cache is purged.
335 *
336 * @return bool
337 */
338 public static function clearall( $propagate = true )
339 {
340 if ( ! self::cacheavail() ) {
341 return false;
342 }
343
344 // TODO/FIXME: If cache is big, switch to advanced/new cache clearing automatically?
345 if ( self::advanced_cache_clear_enabled() ) {
346 self::clear_cache_via_rename();
347 } else {
348 self::clear_cache_classic();
349 }
350
351 // Remove the transient so it gets regenerated...
352 delete_transient( 'autoptimize_stats' );
353
354 // Cache was just purged, clear page cache and allow others to hook into our purging...
355 if ( true === $propagate ) {
356 if ( ! function_exists( 'autoptimize_do_cachepurged_action' ) ) {
357 function autoptimize_do_cachepurged_action() {
358 do_action( 'autoptimize_action_cachepurged' );
359 }
360 }
361 add_action( 'shutdown', 'autoptimize_do_cachepurged_action', 11 );
362 add_action( 'autoptimize_action_cachepurged', array( 'autoptimizeCache', 'flushPageCache' ), 10, 0 );
363 }
364
365 // Warm cache (part of speedupper)!
366 if ( apply_filters( 'autoptimize_filter_speedupper', true ) ) {
367 $url = site_url() . '/?ao_speedup_cachebuster=' . rand( 1, 100000 );
368 $cache = @wp_remote_get( $url ); // @codingStandardsIgnoreLine
369 unset( $cache );
370 }
371
372 return true;
373 }
374
375 /**
376 * Wrapper for clearall but with false param
377 * to ensure the event is not propagated to others
378 * through our own hooks (to avoid infinite loops).
379 *
380 * @return bool
381 */
382 public static function clearall_actionless()
383 {
384 return self::clearall( false );
385 }
386
387 /**
388 * Returns the contents of our cache dirs.
389 *
390 * @return array
391 */
392 protected static function get_cache_contents()
393 {
394 $contents = array();
395
396 foreach ( array( '', 'js', 'css' ) as $dir ) {
397 $contents[ $dir ] = scandir( AUTOPTIMIZE_CACHE_DIR . $dir );
398 }
399
400 return $contents;
401 }
402
403 /**
404 * Returns stats about cached contents.
405 *
406 * @return array
407 */
408 public static function stats()
409 {
410 $stats = get_transient( 'autoptimize_stats' );
411
412 // If no transient, do the actual scan!
413 if ( ! is_array( $stats ) ) {
414 if ( ! self::cacheavail() ) {
415 return 0;
416 }
417 $stats = self::stats_scan();
418 $count = $stats[0];
419 if ( $count > 100 ) {
420 // Store results in transient.
421 set_transient(
422 'autoptimize_stats',
423 $stats,
424 apply_filters( 'autoptimize_filter_cache_statsexpiry', HOUR_IN_SECONDS )
425 );
426 }
427 }
428
429 return $stats;
430 }
431
432 /**
433 * Performs a scan of cache directory contents and returns an array
434 * with 3 values: count, size, timestamp.
435 * count = total number of found files
436 * size = total filesize (in bytes) of found files
437 * timestamp = unix timestamp when the scan was last performed/finished.
438 *
439 * @return array
440 */
441 protected static function stats_scan()
442 {
443 $count = 0;
444 $size = 0;
445
446 // Scan everything in our cache directories.
447 foreach ( self::get_cache_contents() as $name => $files ) {
448 $dir = rtrim( AUTOPTIMIZE_CACHE_DIR . $name, '/' ) . '/';
449 foreach ( $files as $file ) {
450 if ( self::is_valid_cache_file( $dir, $file ) ) {
451 if ( AUTOPTIMIZE_CACHE_NOGZIP &&
452 (
453 false !== strpos( $file, '.js' ) ||
454 false !== strpos( $file, '.css' ) ||
455 false !== strpos( $file, '.img' ) ||
456 false !== strpos( $file, '.txt' )
457 )
458 ) {
459 // Web server is gzipping, we count .js|.css|.img|.txt files.
460 $count++;
461 } elseif ( ! AUTOPTIMIZE_CACHE_NOGZIP && false !== strpos( $file, '.none' ) ) {
462 // We are gzipping ourselves via php, counting only .none files.
463 $count++;
464 }
465 $size += filesize( $dir . $file );
466 }
467 }
468 }
469
470 $stats = array( $count, $size, time() );
471
472 return $stats;
473 }
474
475 /**
476 * Ensures the cache directory exists, is writeable and contains the
477 * required .htaccess files.
478 * Returns false in case it fails to ensure any of those things.
479 *
480 * @return bool
481 */
482 public static function cacheavail()
483 {
484 if ( ! defined( 'AUTOPTIMIZE_CACHE_DIR' ) ) {
485 // We didn't set a cache.
486 return false;
487 }
488
489 foreach ( array( '', 'js', 'css' ) as $dir ) {
490 if ( ! self::check_cache_dir( AUTOPTIMIZE_CACHE_DIR . $dir ) ) {
491 return false;
492 }
493 }
494
495 // Using .htaccess inside our cache folder to overrule wp-super-cache.
496 $htaccess = AUTOPTIMIZE_CACHE_DIR . '/.htaccess';
497 if ( ! is_file( $htaccess ) ) {
498 /**
499 * Create `wp-content/AO_htaccess_tmpl` file with
500 * whatever htaccess rules you might need
501 * if you want to override default AO htaccess
502 */
503 $htaccess_tmpl = WP_CONTENT_DIR . '/AO_htaccess_tmpl';
504 if ( is_file( $htaccess_tmpl ) ) {
505 $content = file_get_contents( $htaccess_tmpl );
506 } elseif ( is_multisite() || ! AUTOPTIMIZE_CACHE_NOGZIP ) {
507 $content = '<IfModule mod_expires.c>
508 ExpiresActive On
509 ExpiresByType text/css A30672000
510 ExpiresByType text/javascript A30672000
511 ExpiresByType application/javascript A30672000
512 </IfModule>
513 <IfModule mod_headers.c>
514 Header append Cache-Control "public, immutable"
515 </IfModule>
516 <IfModule mod_deflate.c>
517 <FilesMatch "\.(js|css)$">
518 SetOutputFilter DEFLATE
519 </FilesMatch>
520 </IfModule>
521 <IfModule mod_authz_core.c>
522 <Files *.php>
523 Require all granted
524 </Files>
525 </IfModule>
526 <IfModule !mod_authz_core.c>
527 <Files *.php>
528 Order allow,deny
529 Allow from all
530 </Files>
531 </IfModule>';
532 } else {
533 $content = '<IfModule mod_expires.c>
534 ExpiresActive On
535 ExpiresByType text/css A30672000
536 ExpiresByType text/javascript A30672000
537 ExpiresByType application/javascript A30672000
538 </IfModule>
539 <IfModule mod_headers.c>
540 Header append Cache-Control "public, immutable"
541 </IfModule>
542 <IfModule mod_deflate.c>
543 <FilesMatch "\.(js|css)$">
544 SetOutputFilter DEFLATE
545 </FilesMatch>
546 </IfModule>
547 <IfModule mod_authz_core.c>
548 <Files *.php>
549 Require all denied
550 </Files>
551 </IfModule>
552 <IfModule !mod_authz_core.c>
553 <Files *.php>
554 Order deny,allow
555 Deny from all
556 </Files>
557 </IfModule>';
558 }
559 @file_put_contents( $htaccess, $content ); // @codingStandardsIgnoreLine
560 }
561
562 // All OK!
563 return true;
564 }
565
566 /**
567 * Ensures the specified `$dir` exists and is writeable.
568 * Returns false if that's not the case.
569 *
570 * @param string $dir Directory to check/create.
571 *
572 * @return bool
573 */
574 protected static function check_cache_dir( $dir )
575 {
576 // Try creating the dir if it doesn't exist.
577 if ( ! file_exists( $dir ) ) {
578 @mkdir( $dir, 0775, true ); // @codingStandardsIgnoreLine
579 if ( ! file_exists( $dir ) ) {
580 return false;
581 }
582 }
583
584 // If we still cannot write, bail.
585 if ( ! is_writable( $dir ) ) {
586 return false;
587 }
588
589 // Create an index.html in there to avoid prying eyes!
590 $idx_file = rtrim( $dir, '/\\' ) . '/index.html';
591 if ( ! is_file( $idx_file ) ) {
592 @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
593 }
594
595 return true;
596 }
597
598 /**
599 * Flushes as many page cache plugin's caches as possible.
600 *
601 * @return void
602 */
603 // @codingStandardsIgnoreStart
604 public static function flushPageCache()
605 {
606 if ( function_exists( 'wp_cache_clear_cache' ) ) {
607 if ( is_multisite() ) {
608 $blog_id = get_current_blog_id();
609 wp_cache_clear_cache( $blog_id );
610 } else {
611 wp_cache_clear_cache();
612 }
613 } elseif ( has_action( 'cachify_flush_cache' ) ) {
614 do_action( 'cachify_flush_cache' );
615 } elseif ( function_exists( 'w3tc_pgcache_flush' ) ) {
616 w3tc_pgcache_flush();
617 } elseif ( function_exists( 'wp_fast_cache_bulk_delete_all' ) ) {
618 wp_fast_cache_bulk_delete_all();
619 } elseif ( class_exists( 'WpFastestCache' ) ) {
620 $wpfc = new WpFastestCache();
621 $wpfc->deleteCache();
622 } elseif ( class_exists( 'c_ws_plugin__qcache_purging_routines' ) ) {
623 c_ws_plugin__qcache_purging_routines::purge_cache_dir(); // quick cache
624 } elseif ( class_exists( 'zencache' ) ) {
625 zencache::clear();
626 } elseif ( class_exists( 'comet_cache' ) ) {
627 comet_cache::clear();
628 } elseif ( class_exists( 'WpeCommon' ) ) {
629 // WPEngine cache purge/flush methods to call by default
630 $wpe_methods = array(
631 'purge_varnish_cache',
632 );
633
634 // More agressive clear/flush/purge behind a filter
635 if ( apply_filters( 'autoptimize_flush_wpengine_aggressive', false ) ) {
636 $wpe_methods = array_merge( $wpe_methods, array( 'purge_memcached', 'clear_maxcdn_cache' ) );
637 }
638
639 // Filtering the entire list of WpeCommon methods to be called (for advanced usage + easier testing)
640 $wpe_methods = apply_filters( 'autoptimize_flush_wpengine_methods', $wpe_methods );
641
642 foreach ( $wpe_methods as $wpe_method ) {
643 if ( method_exists( 'WpeCommon', $wpe_method ) ) {
644 WpeCommon::$wpe_method();
645 }
646 }
647 } elseif ( function_exists( 'sg_cachepress_purge_cache' ) ) {
648 sg_cachepress_purge_cache();
649 } elseif ( file_exists( WP_CONTENT_DIR . '/wp-cache-config.php' ) && function_exists( 'prune_super_cache' ) ) {
650 // fallback for WP-Super-Cache
651 global $cache_path;
652 if ( is_multisite() ) {
653 $blog_id = get_current_blog_id();
654 prune_super_cache( get_supercache_dir( $blog_id ), true );
655 prune_super_cache( $cache_path . 'blogs/', true );
656 } else {
657 prune_super_cache( $cache_path . 'supercache/', true );
658 prune_super_cache( $cache_path, true );
659 }
660 }
661 }
662 // @codingStandardsIgnoreEnd
663 }
664