PluginProbe
Autoptimize / trunk
Autoptimize vtrunk
3.1.16 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 All 108 releases
← All changes | classes/autoptimizeCache.php +736 -151 2.3.3 → trunk View file →
@@ -1,193 +1,574 @@
1 1 <?php
2 -if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2 +/**
3 + * Handles disk-cache-related operations.
4 + */
3 5
4 -class autoptimizeCache {
6 +if ( ! defined( 'ABSPATH' ) ) {
7 + exit;
8 +}
9 +
10 +class autoptimizeCache
11 +{
12 + /**
13 + * Cache filename.
14 + *
15 + * @var string
16 + */
5 17 private $filename;
6 - private $mime;
18 +
19 + /**
20 + * Cache directory path (with a trailing slash).
21 + *
22 + * @var string
23 + */
7 24 private $cachedir;
8 - private $delayed;
9 -
10 - public function __construct($md5,$ext='php') {
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 + $_min_ext = '';
44 + if ( apply_filters( 'autoptimize_filter_cache_url_add_min_ext', false ) ) {
45 + $_min_ext = '.min';
46 + }
47 +
11 48 $this->cachedir = AUTOPTIMIZE_CACHE_DIR;
12 - $this->delayed = AUTOPTIMIZE_CACHE_DELAY;
13 - $this->nogzip = AUTOPTIMIZE_CACHE_NOGZIP;
14 - if($this->nogzip == false) {
15 - $this->filename = AUTOPTIMIZE_CACHEFILE_PREFIX.$md5.'.php';
49 + $this->nogzip = AUTOPTIMIZE_CACHE_NOGZIP;
50 + if ( ! $this->nogzip ) {
51 + $this->filename = AUTOPTIMIZE_CACHEFILE_PREFIX . $md5 . $_min_ext . '.php';
16 52 } else {
17 - if (in_array($ext, array("js","css"))) {
18 - $this->filename = $ext.'/'.AUTOPTIMIZE_CACHEFILE_PREFIX.$md5.'.'.$ext;
53 + if ( in_array( $ext, array( 'js', 'css' ) ) ) {
54 + $this->filename = $ext . '/' . AUTOPTIMIZE_CACHEFILE_PREFIX . $md5 . $_min_ext . '.' . $ext;
19 55 } else {
20 - $this->filename = AUTOPTIMIZE_CACHEFILE_PREFIX.$md5.'.'.$ext;
56 + $this->filename = AUTOPTIMIZE_CACHEFILE_PREFIX . $md5 . $_min_ext . '.' . $ext;
21 57 }
22 58 }
23 59 }
24 -
25 - public function check() {
26 - if(!file_exists($this->cachedir.$this->filename)) {
27 - // No cached file, sorry
28 - return false;
29 - }
30 - // Cache exists!
31 - return true;
60 +
61 + /**
62 + * Returns true if the cached file exists on disk.
63 + *
64 + * @return bool
65 + */
66 + public function check()
67 + {
68 + return file_exists( $this->cachedir . $this->filename );
32 69 }
33 -
34 - public function retrieve() {
35 - if($this->check()) {
36 - if($this->nogzip == false) {
37 - return file_get_contents($this->cachedir.$this->filename.'.none');
70 +
71 + /**
72 + * Returns cache contents if they exist, false otherwise.
73 + *
74 + * @return string|false
75 + */
76 + public function retrieve()
77 + {
78 + if ( $this->check() ) {
79 + if ( false == $this->nogzip ) {
80 + return file_get_contents( $this->cachedir . $this->filename . '.none' );
38 81 } else {
39 - return file_get_contents($this->cachedir.$this->filename);
82 + return file_get_contents( $this->cachedir . $this->filename );
40 83 }
41 84 }
42 85 return false;
43 86 }
44 -
45 - public function cache($code,$mime) {
46 - if($this->nogzip == false) {
47 - $file = ($this->delayed ? 'delayed.php' : 'default.php');
48 - $phpcode = file_get_contents(AUTOPTIMIZE_PLUGIN_DIR.'/config/'.$file);
49 - $phpcode = str_replace(array('%%CONTENT%%','exit;'),array($mime,''),$phpcode);
50 - file_put_contents($this->cachedir.$this->filename,$phpcode, LOCK_EX);
51 - file_put_contents($this->cachedir.$this->filename.'.none',$code, LOCK_EX);
52 - if(!$this->delayed) {
53 - // Compress now!
54 - file_put_contents($this->cachedir.$this->filename.'.deflate',gzencode($code,9,FORCE_DEFLATE), LOCK_EX);
55 - file_put_contents($this->cachedir.$this->filename.'.gzip',gzencode($code,9,FORCE_GZIP), LOCK_EX);
87 +
88 + /**
89 + * Stores given $data in cache.
90 + *
91 + * @param string $data Data to cache.
92 + * @param string $mime Mimetype.
93 + *
94 + * @return void|bool
95 + */
96 + public function cache( $data, $mime )
97 + {
98 + // readonly FS explicitly OK'ed by developer, so just pretend all is OK.
99 + if ( defined( 'AUTOPTIMIZE_CACHE_READONLY' ) ) {
100 + return true;
101 + }
102 +
103 + // off by default; check if cachedirs exist every time before caching
104 + //
105 + // to be activated for users that experience these ugly errors;
106 + // PHP Warning: file_put_contents failed to open stream: No such file or directory.
107 + if ( apply_filters( 'autoptimize_filter_cache_checkdirs_on_write', false ) ) {
108 + $this->check_and_create_dirs();
109 + }
110 +
111 + if ( false === $this->nogzip ) {
112 + // We handle gzipping ourselves.
113 + $file = 'default.php';
114 + $phpcode = file_get_contents( AUTOPTIMIZE_PLUGIN_DIR . 'config/' . $file );
115 + $phpcode = str_replace( array( '%%CONTENT%%', 'exit;' ), array( $mime, '' ), $phpcode );
116 +
117 + file_put_contents( $this->cachedir . $this->filename, $phpcode );
118 + file_put_contents( $this->cachedir . $this->filename . '.none', $data );
119 + } else {
120 + // Write code to cache without doing anything else.
121 + file_put_contents( $this->cachedir . $this->filename, $data );
122 +
123 + // save fallback .js or .css file if filter true (to be false by default) but not if snippet or single.
124 + if ( self::do_fallback() && strpos( $this->filename, '_snippet_' ) === false && strpos( $this->filename, '_single_' ) === false ) {
125 + $_extension = pathinfo( $this->filename, PATHINFO_EXTENSION );
126 + $_fallback_file = AUTOPTIMIZE_CACHEFILE_PREFIX . 'fallback.' . $_extension;
127 + if ( ( 'css' === $_extension || 'js' === $_extension ) && ! file_exists( $this->cachedir . $_extension . '/' . $_fallback_file ) ) {
128 + file_put_contents( $this->cachedir . $_extension . '/' . $_fallback_file, $data );
129 + }
56 130 }
57 - } else {
58 - // Write code to cache without doing anything else
59 - file_put_contents($this->cachedir.$this->filename,$code, LOCK_EX);
60 - if (apply_filters('autoptimize_filter_cache_create_static_gzip', false)) {
61 - // Create an additional cached gzip file
62 - file_put_contents($this->cachedir.$this->filename.'.gz', gzencode($code,9,FORCE_GZIP), LOCK_EX);
131 +
132 + if ( apply_filters( 'autoptimize_filter_cache_create_static_gzip', false ) ) {
133 + // Create an additional cached gzip file.
134 + file_put_contents( $this->cachedir . $this->filename . '.gz', gzencode( $data, 9, FORCE_GZIP ) );
135 + // If PHP Brotli extension is installed, create an additional cached Brotli file.
136 + if ( function_exists( 'brotli_compress' ) ) {
137 + file_put_contents( $this->cachedir . $this->filename . '.br', brotli_compress( $data, 11, BROTLI_GENERIC ) );
138 + }
63 139 }
64 140 }
141 +
142 + // Provide 3rd party action hook for every cache file that is created.
143 + // This hook can for example be used to inject a copy of the created cache file to a other domain.
144 + do_action( 'autoptimize_action_cache_file_created', $this->cachedir . $this->filename );
65 145 }
66 -
67 - public function getname() {
68 - apply_filters('autoptimize_filter_cache_getname',AUTOPTIMIZE_CACHE_URL.$this->filename);
146 +
147 + /**
148 + * Get cache filename.
149 + *
150 + * @return string
151 + */
152 + public function getname()
153 + {
154 + // NOTE: This could've maybe been a do_action() instead, however,
155 + // that ship has sailed.
156 + // The original idea here was to provide 3rd party code a hook so that
157 + // it can "listen" to all the complete autoptimized-urls that the page
158 + // will emit... Or something to that effect I think?
159 + apply_filters( 'autoptimize_filter_cache_getname', AUTOPTIMIZE_CACHE_URL . $this->filename );
160 +
69 161 return $this->filename;
70 162 }
71 -
72 - static function clearall() {
73 - if(!autoptimizeCache::cacheavail()) {
163 +
164 + /**
165 + * Returns true if given `$file` is considered a valid Autoptimize cache file,
166 + * false otherwise.
167 + *
168 + * @param string $dir Directory name (with a trailing slash).
169 + * @param string $file Filename.
170 + * @return bool
171 + */
172 + protected static function is_valid_cache_file( $dir, $file )
173 + {
174 + if ( '.' !== $file && '..' !== $file &&
175 + false !== strpos( $file, AUTOPTIMIZE_CACHEFILE_PREFIX ) &&
176 + is_file( $dir . $file ) ) {
177 +
178 + // It's a valid file!
179 + return true;
180 + }
181 +
182 + // Everything else is considered invalid!
183 + return false;
184 + }
185 +
186 + /**
187 + * Clears contents of AUTOPTIMIZE_CACHE_DIR.
188 + *
189 + * @return void
190 + */
191 + protected static function clear_cache_classic()
192 + {
193 + $contents = self::get_cache_contents();
194 + foreach ( $contents as $name => $files ) {
195 + $dir = rtrim( AUTOPTIMIZE_CACHE_DIR . $name, '/' ) . '/';
196 + foreach ( $files as $file ) {
197 + if ( self::is_valid_cache_file( $dir, $file ) ) {
198 + @unlink( $dir . $file ); // @codingStandardsIgnoreLine
199 + }
200 + }
201 + }
202 +
203 + @unlink( AUTOPTIMIZE_CACHE_DIR . '/.htaccess' ); // @codingStandardsIgnoreLine
204 + }
205 +
206 + /**
207 + * Recursively deletes the specified pathname (file/directory) if possible.
208 + * Returns true on success, false otherwise.
209 + *
210 + * @param string $pathname Pathname to remove.
211 + *
212 + * @return bool
213 + */
214 + protected static function rmdir( $pathname )
215 + {
216 + $files = self::get_dir_contents( $pathname );
217 + foreach ( $files as $file ) {
218 + $path = $pathname . '/' . $file;
219 + if ( is_dir( $path ) ) {
220 + self::rmdir( $path );
221 + } else {
222 + unlink( $path );
223 + }
224 + }
225 +
226 + return rmdir( $pathname );
227 + }
228 +
229 + /**
230 + * Clears contents of AUTOPTIMIZE_CACHE_DIR by renaming the current
231 + * cache directory into a new one with a unique name and then
232 + * re-creating the default (empty) cache directory.
233 + *
234 + * Important/ Fixme: this does not take multisite into account, so
235 + * if advanced_cache_clear_enabled is true (it is not by default)
236 + * then the content for all subsites is zapped!
237 + *
238 + * @return bool Returns true when everything is done successfully, false otherwise.
239 + */
240 + protected static function clear_cache_via_rename()
241 + {
242 + $ok = false;
243 + $dir = self::get_pathname_base();
244 + $new_name = self::get_unique_name();
245 +
246 + // Makes sure the new pathname is on the same level...
247 + $new_pathname = dirname( $dir ) . '/' . $new_name;
248 + $renamed = @rename( $dir, $new_pathname ); // @codingStandardsIgnoreLine
249 +
250 + // When renamed, re-create the default cache directory back so it's
251 + // available again...
252 + if ( $renamed ) {
253 + $ok = self::cacheavail();
254 + }
255 +
256 + return $ok;
257 + }
258 +
259 + /**
260 + * Returns true when advanced cache clearing is enabled.
261 + *
262 + * @return bool
263 + */
264 + public static function advanced_cache_clear_enabled()
265 + {
266 + return apply_filters( 'autoptimize_filter_cache_clear_advanced', false );
267 + }
268 +
269 + /**
270 + * Returns a (hopefully) unique new cache folder name for renaming purposes.
271 + *
272 + * @return string
273 + */
274 + protected static function get_unique_name()
275 + {
276 + $prefix = self::get_advanced_cache_clear_prefix();
277 + $new_name = uniqid( $prefix, true );
278 +
279 + return $new_name;
280 + }
281 +
282 + /**
283 + * Get cache prefix name used in advanced cache clearing mode.
284 + *
285 + * @return string
286 + */
287 + protected static function get_advanced_cache_clear_prefix()
288 + {
289 + $pathname = self::get_pathname_base();
290 + $basename = basename( $pathname );
291 + $prefix = $basename . '-artifact-';
292 +
293 + return $prefix;
294 + }
295 +
296 + /**
297 + * Returns an array of file and directory names found within
298 + * the given $pathname without '.' and '..' elements.
299 + *
300 + * @param string $pathname Pathname.
301 + *
302 + * @return array
303 + */
304 + protected static function get_dir_contents( $pathname )
305 + {
306 + return array_slice( scandir( $pathname ), 2 );
307 + }
308 +
309 + /**
310 + * Wipes directories which were created as part of the fast cache clearing
311 + * routine (which renames the current cache directory into a new one with
312 + * a custom-prefixed unique name).
313 + *
314 + * @return bool
315 + */
316 + public static function delete_advanced_cache_clear_artifacts()
317 + {
318 + // Don't go through these motions (called from the cachechecker) if advanced cache clear isn't even active.
319 + if ( ! self::advanced_cache_clear_enabled() ) {
74 320 return false;
75 321 }
76 -
77 - // scan the cachedirs
78 - foreach (array("","js","css") as $scandirName) {
79 - $scan[$scandirName] = scandir(AUTOPTIMIZE_CACHE_DIR.$scandirName);
322 +
323 + $dir = self::get_pathname_base();
324 + $prefix = self::get_advanced_cache_clear_prefix();
325 + $parent = dirname( $dir );
326 + $ok = false;
327 +
328 + // Returns the list of files without '.' and '..' elements.
329 + $files = self::get_dir_contents( $parent );
330 + if ( is_array( $files ) && ! empty( $files ) ) {
331 + foreach ( $files as $file ) {
332 + $path = $parent . '/' . $file;
333 + $prefixed = ( false !== strpos( $path, $prefix ) );
334 + // Removing only our own (prefixed) directories...
335 + if ( is_dir( $path ) && $prefixed ) {
336 + $ok = self::rmdir( $path );
337 + }
338 + }
80 339 }
81 -
82 - // clear the cachedirs
83 - foreach ($scan as $scandirName=>$scanneddir) {
84 - $thisAoCacheDir=rtrim(AUTOPTIMIZE_CACHE_DIR.$scandirName,"/")."/";
85 - foreach($scanneddir as $file) {
86 - if(!in_array($file,array('.','..')) && strpos($file,AUTOPTIMIZE_CACHEFILE_PREFIX) !== false && is_file($thisAoCacheDir.$file)) {
87 - @unlink($thisAoCacheDir.$file);
340 +
341 + return $ok;
342 + }
343 +
344 + /**
345 + * Returns the cache directory pathname used.
346 + * Done as a function so we canSlightly different
347 + * if multisite is used and `autoptimize_separate_blog_caches` filter
348 + * is used.
349 + *
350 + * @return string
351 + */
352 + public static function get_pathname()
353 + {
354 + $pathname = self::get_pathname_base();
355 +
356 + if ( is_multisite() && apply_filters( 'autoptimize_separate_blog_caches', true ) ) {
357 + $blog_id = get_current_blog_id();
358 + $pathname .= $blog_id . '/';
359 + }
360 +
361 + return $pathname;
362 + }
363 +
364 + /**
365 + * Returns the base path of our cache directory.
366 + *
367 + * @return string
368 + */
369 + protected static function get_pathname_base()
370 + {
371 + $pathname = WP_CONTENT_DIR . AUTOPTIMIZE_CACHE_CHILD_DIR;
372 +
373 + return $pathname;
374 + }
375 +
376 + /**
377 + * Deletes everything from the cache directories.
378 + *
379 + * @param bool $propagate Whether to trigger additional actions when cache is purged.
380 + *
381 + * @return bool
382 + */
383 + public static function clearall( $propagate = true )
384 + {
385 + if ( defined( 'ET_CORE_VERSION' ) && 'Divi' === get_template() ) {
386 + // see https://blog.futtta.be/2018/11/17/warning-divi-purging-autoptimizes-cache/ .
387 + $dbt = debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS, 2 );
388 + $caller = isset( $dbt[1]['function'] ) ? $dbt[1]['function'] : null;
389 + if ( 'et_core_clear_wp_cache' === $caller ) {
390 + if ( apply_filters( 'autoptimize_filter_cache_divi_wrong_complain', true ) ) {
391 + _doing_it_wrong( 'autoptimizeCache::clearall', 'Divi devs: please don\'t clear Autoptimize\'s cache, it is unneeded and can break sites. You can contact me at [email protected] to discuss.', 'Autoptimize 2.9.6' );
88 392 }
393 + return false;
89 394 }
90 395 }
91 396
92 - @unlink(AUTOPTIMIZE_CACHE_DIR."/.htaccess");
93 - delete_transient("autoptimize_stats");
397 + if ( ! self::cacheavail() || true === apply_filters( 'autoptimize_filter_cache_clearall_disabled', false ) ) {
398 + return false;
399 + }
94 400
95 - // add cachepurged action
96 - if (!function_exists('autoptimize_do_cachepurged_action')) {
97 - function autoptimize_do_cachepurged_action() {
98 - do_action("autoptimize_action_cachepurged");
401 + // TODO/FIXME: If cache is big, switch to advanced/new cache clearing automatically?
402 + if ( self::advanced_cache_clear_enabled() ) {
403 + self::clear_cache_via_rename();
404 + } else {
405 + self::clear_cache_classic();
406 + }
407 +
408 + // Remove 404 handler if required.
409 + if ( self::do_fallback() ) {
410 + $_fallback_php = trailingslashit( WP_CONTENT_DIR ) . 'autoptimize_404_handler.php';
411 + @unlink( $_fallback_php ); // @codingStandardsIgnoreLine
412 + }
413 +
414 + // Remove the transient so it gets regenerated...
415 + delete_transient( 'autoptimize_stats' );
416 +
417 + // Cache was just purged, clear page cache and allow others to hook into our purging...
418 + if ( true === $propagate ) {
419 + if ( ! function_exists( 'autoptimize_do_cachepurged_action' ) ) {
420 + function autoptimize_do_cachepurged_action() {
421 + do_action( 'autoptimize_action_cachepurged' );
422 + }
99 423 }
424 + add_action( 'shutdown', 'autoptimize_do_cachepurged_action', 11 );
425 + add_action( 'autoptimize_action_cachepurged', array( 'autoptimizeCache', 'flushPageCache' ), 10, 0 );
100 426 }
101 - add_action("shutdown","autoptimize_do_cachepurged_action",11);
102 -
103 - // try to purge caching plugins cache-files?
104 - include_once(AUTOPTIMIZE_PLUGIN_DIR.'classlesses/autoptimizePageCacheFlush.php');
105 - add_action("autoptimize_action_cachepurged","autoptimize_flush_pagecache",10,0);
106 427
107 - // warm cache (part of speedupper)?
108 - if ( apply_filters('autoptimize_filter_speedupper', true) ) {
109 - $warmCacheUrl = site_url()."/?ao_speedup_cachebuster=".rand(1,100000);
110 - $warmCache = @wp_remote_get($warmCacheUrl);
111 - unset($warmCache);
428 + // Warm cache (part of speedupper)!
429 + if ( apply_filters( 'autoptimize_filter_speedupper', true ) && false == get_transient( 'autoptimize_cache_warmer_protector' ) ) {
430 + set_transient( 'autoptimize_cache_warmer_protector', 'I shall not warm cache for another 10 minutes.', 60 * 10 );
431 + $url = site_url() . '/?ao_speedup_cachebuster=' . rand( 1, 100000 );
432 + $url = apply_filters( 'autoptimize_filter_cache_warmer_url', $url );
433 + $cache = @wp_remote_get( $url ); // @codingStandardsIgnoreLine
434 + unset( $cache );
112 435 }
113 436
114 437 return true;
115 438 }
116 439
117 - static function stats() {
118 - $AOstats=get_transient("autoptimize_stats");
440 + /**
441 + * Wrapper for clearall but with false param
442 + * to ensure the event is not propagated to others
443 + * through our own hooks (to avoid infinite loops).
444 + *
445 + * @return bool
446 + */
447 + public static function clearall_actionless()
448 + {
449 + return self::clearall( false );
450 + }
119 451
120 - if (empty($AOstats)) {
121 - // Cache not available :(
122 - if(!autoptimizeCache::cacheavail()) {
452 + /**
453 + * Returns the contents of our cache dirs.
454 + *
455 + * @return array
456 + */
457 + protected static function get_cache_contents()
458 + {
459 + $contents = array();
460 +
461 + foreach ( array( '', 'js', 'css' ) as $dir ) {
462 + $contents[ $dir ] = scandir( AUTOPTIMIZE_CACHE_DIR . $dir );
463 + }
464 +
465 + return $contents;
466 + }
467 +
468 + /**
469 + * Returns stats about cached contents.
470 + *
471 + * @return array
472 + */
473 + public static function stats()
474 + {
475 + $stats = get_transient( 'autoptimize_stats' );
476 +
477 + // If no transient, do the actual scan!
478 + if ( ! is_array( $stats ) ) {
479 + if ( ! self::cacheavail() ) {
123 480 return 0;
124 481 }
125 -
126 - // Count cached info
127 - $count = 0;
128 - $size = 0;
129 -
130 - // scan the cachedirs
131 - foreach (array("","js","css") as $scandirName) {
132 - $scan[$scandirName] = scandir(AUTOPTIMIZE_CACHE_DIR.$scandirName);
482 + $stats = self::stats_scan();
483 + $count = $stats[0];
484 + if ( $count > 100 ) {
485 + // Store results in transient.
486 + set_transient(
487 + 'autoptimize_stats',
488 + $stats,
489 + apply_filters( 'autoptimize_filter_cache_statsexpiry', HOUR_IN_SECONDS )
490 + );
133 491 }
134 -
135 - foreach ($scan as $scandirName=>$scanneddir) {
136 - $thisAoCacheDir=rtrim(AUTOPTIMIZE_CACHE_DIR.$scandirName,"/")."/";
137 - foreach($scanneddir as $file) {
138 - if(!in_array($file,array('.','..')) && strpos($file,AUTOPTIMIZE_CACHEFILE_PREFIX) !== false) {
139 - if(is_file($thisAoCacheDir.$file)) {
140 - if(AUTOPTIMIZE_CACHE_NOGZIP && (strpos($file,'.js') !== false || strpos($file,'.css') !== false || strpos($file,'.img') !== false || strpos($file,'.txt') !== false )) {
141 - $count++;
142 - } elseif(!AUTOPTIMIZE_CACHE_NOGZIP && strpos($file,'.none') !== false) {
143 - $count++;
144 - }
145 - $size+=filesize($thisAoCacheDir.$file);
146 - }
492 + }
493 +
494 + return $stats;
495 + }
496 +
497 + /**
498 + * Performs a scan of cache directory contents and returns an array
499 + * with 3 values: count, size, timestamp.
500 + * count = total number of found files
501 + * size = total filesize (in bytes) of found files
502 + * timestamp = unix timestamp when the scan was last performed/finished.
503 + *
504 + * @return array
505 + */
506 + protected static function stats_scan()
507 + {
508 + $count = 0;
509 + $size = 0;
510 +
511 + // Scan everything in our cache directories.
512 + foreach ( self::get_cache_contents() as $name => $files ) {
513 + $dir = rtrim( AUTOPTIMIZE_CACHE_DIR . $name, '/' ) . '/';
514 + foreach ( $files as $file ) {
515 + if ( self::is_valid_cache_file( $dir, $file ) ) {
516 + if ( AUTOPTIMIZE_CACHE_NOGZIP &&
517 + (
518 + false !== strpos( $file, '.js' ) ||
519 + false !== strpos( $file, '.css' ) ||
520 + false !== strpos( $file, '.img' ) ||
521 + false !== strpos( $file, '.txt' )
522 + )
523 + ) {
524 + // Web server is gzipping, we count .js|.css|.img|.txt files.
525 + $count++;
526 + } elseif ( ! AUTOPTIMIZE_CACHE_NOGZIP && false !== strpos( $file, '.none' ) ) {
527 + // We are gzipping ourselves via php, counting only .none files.
528 + $count++;
147 529 }
530 + $size += filesize( $dir . $file );
148 531 }
149 532 }
150 - $AOstats=array($count,$size,time());
151 - if ($count>100) {
152 - set_transient( "autoptimize_stats", $AOstats, apply_filters( 'autoptimize_filter_cache_statsexpiry', HOUR_IN_SECONDS ) );
153 - }
154 533 }
155 - // print the number of instances
156 - return $AOstats;
157 - }
158 534
159 - static function cacheavail() {
160 - if(!defined('AUTOPTIMIZE_CACHE_DIR')) {
161 - // We didn't set a cache
535 + $stats = array( $count, $size, time() );
536 +
537 + return $stats;
538 + }
539 +
540 + /**
541 + * Ensures the cache directory exists, is writeable and contains the
542 + * required .htaccess files.
543 + * Returns false in case it fails to ensure any of those things.
544 + *
545 + * @return bool
546 + */
547 + public static function cacheavail()
548 + {
549 + // readonly FS explicitly OK'ed by dev, let's assume the cache dirs are there!
550 + if ( defined( 'AUTOPTIMIZE_CACHE_READONLY' ) ) {
551 + return true;
552 + }
553 +
554 + if ( false === autoptimizeCache::check_and_create_dirs() ) {
162 555 return false;
163 556 }
164 -
165 - foreach (array("","js","css") as $checkDir) {
166 - if(!autoptimizeCache::checkCacheDir(AUTOPTIMIZE_CACHE_DIR.$checkDir)) {
167 - return false;
168 - }
169 - }
170 -
171 - /** write index.html here to avoid prying eyes */
172 - $indexFile=AUTOPTIMIZE_CACHE_DIR.'/index.html';
173 - if(!is_file($indexFile)) {
174 - @file_put_contents($indexFile,'<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>');
175 - }
176 557
177 - /** write .htaccess here to overrule wp_super_cache */
178 - $htAccess=AUTOPTIMIZE_CACHE_DIR.'/.htaccess';
179 - if(!is_file($htAccess)) {
180 - /**
181 - * create wp-content/AO_htaccess_tmpl with
558 + // Using .htaccess inside our cache folder to overrule wp-super-cache.
559 + $htaccess = AUTOPTIMIZE_CACHE_DIR . '/.htaccess';
560 + if ( ! is_file( $htaccess ) ) {
561 + /**
562 + * Create `wp-content/AO_htaccess_tmpl` file with
182 563 * whatever htaccess rules you might need
183 564 * if you want to override default AO htaccess
184 565 */
185 - $htaccess_tmpl=WP_CONTENT_DIR."/AO_htaccess_tmpl";
186 - if (is_file($htaccess_tmpl)) {
187 - $htAccessContent=file_get_contents($htaccess_tmpl);
188 - } else if (is_multisite() || AUTOPTIMIZE_CACHE_NOGZIP == false) {
189 - $htAccessContent='<IfModule mod_expires.c>
566 + $htaccess_tmpl = WP_CONTENT_DIR . '/AO_htaccess_tmpl';
567 + if ( is_file( $htaccess_tmpl ) ) {
568 + $content = file_get_contents( $htaccess_tmpl );
569 + } elseif ( is_multisite() || ! AUTOPTIMIZE_CACHE_NOGZIP ) {
570 + $content = '<IfModule mod_expires.c>
190 571 ExpiresActive On
191 572 ExpiresByType text/css A30672000
192 573 ExpiresByType text/javascript A30672000
193 574 ExpiresByType application/javascript A30672000
@@ -211,9 +592,9 @@
211 592 Allow from all
212 593 </Files>
213 594 </IfModule>';
214 595 } else {
215 - $htAccessContent='<IfModule mod_expires.c>
596 + $content = '<IfModule mod_expires.c>
216 597 ExpiresActive On
217 598 ExpiresByType text/css A30672000
218 599 ExpiresByType text/javascript A30672000
219 600 ExpiresByType application/javascript A30672000
@@ -237,34 +618,238 @@
237 618 Deny from all
238 619 </Files>
239 620 </IfModule>';
240 621 }
241 - @file_put_contents($htAccess,$htAccessContent);
622 +
623 + if ( self::do_fallback() === true ) {
624 + $content .= "\nErrorDocument 404 " . trailingslashit( parse_url( content_url(), PHP_URL_PATH ) ) . 'autoptimize_404_handler.php';
625 + }
626 + @file_put_contents( $htaccess, $content ); // @codingStandardsIgnoreLine
242 627 }
243 628
244 - // All OK
629 + if ( self::do_fallback() ) {
630 + self::check_fallback_php();
631 + }
632 +
633 + // All OK!
245 634 return true;
246 635 }
247 636
248 - static function checkCacheDir($dir) {
249 - // Check and create if not exists
250 - if(!file_exists($dir)) {
251 - @mkdir($dir,0775,true);
252 - if(!file_exists($dir)) {
637 + /**
638 + * Checks if fallback-php file exists and create it if not.
639 + *
640 + * Return bool
641 + */
642 + public static function check_fallback_php() {
643 + $_fallback_filename = 'autoptimize_404_handler.php';
644 + $_fallback_php = trailingslashit( WP_CONTENT_DIR ) . $_fallback_filename;
645 + $_fallback_status = true;
646 +
647 + if ( ! file_exists( $_fallback_php ) && is_writable( WP_CONTENT_DIR ) ) {
648 + $_fallback_php_contents = file_get_contents( AUTOPTIMIZE_PLUGIN_DIR . 'config/' . $_fallback_filename );
649 + $_fallback_php_contents = str_replace( '<?php exit;', '<?php', $_fallback_php_contents );
650 + $_fallback_php_contents = str_replace( '<!--ao-cache-dir-->', AUTOPTIMIZE_CACHE_DIR, $_fallback_php_contents );
651 + $_fallback_php_contents = str_replace( '<!--ao-cachefile-prefix-->', AUTOPTIMIZE_CACHEFILE_PREFIX, $_fallback_php_contents );
652 + if ( is_multisite() ) {
653 + $_fallback_php_contents = str_replace( '$multisite = false;', '$multisite = true;', $_fallback_php_contents );
654 + }
655 + if ( apply_filters( 'autoptimize_filter_cache_fallback_log_errors', false ) ) {
656 + $_fallback_php_contents = str_replace( '// error_log', 'error_log', $_fallback_php_contents );
657 + }
658 + $_fallback_status = file_put_contents( $_fallback_php, $_fallback_php_contents );
659 + }
660 +
661 + return $_fallback_status;
662 + }
663 +
664 + /**
665 + * Tells if AO should try to avoid 404's by creating fallback filesize
666 + * and create a php 404 handler and tell .htaccess to redirect to said handler
667 + * and hook into WordPress to redirect 404 to said handler as well. NGINX users
668 + * are smart enough to get this working, no? ;-)
669 + *
670 + * Return bool
671 + */
672 + public static function do_fallback() {
673 + static $_do_fallback = null;
674 +
675 + if ( null === $_do_fallback ) {
676 + $_do_fallback = (bool) apply_filters( 'autoptimize_filter_cache_do_fallback', autoptimizeOptionWrapper::get_option( 'autoptimize_cache_fallback', '1' ) );
677 + }
678 +
679 + return $_do_fallback;
680 + }
681 +
682 + /**
683 + * Hooks into template_redirect, will act on 404-ing requests for
684 + * Autoptimized files and redirects to the fallback CSS/ JS if available
685 + * and 410'ing ("Gone") if fallback not available.
686 + */
687 + public static function wordpress_notfound_fallback() {
688 + $original_request = strtok( $_SERVER['REQUEST_URI'], '?' );
689 + if ( strpos( $original_request, wp_basename( WP_CONTENT_DIR ) . AUTOPTIMIZE_CACHE_CHILD_DIR ) !== false && is_404() ) {
690 + // make sure this is not considered a 404.
691 + global $wp_query;
692 + $wp_query->is_404 = false;
693 +
694 + // set fallback path.
695 + $js_or_css = pathinfo( $original_request, PATHINFO_EXTENSION );
696 + $fallback_path = AUTOPTIMIZE_CACHE_DIR . $js_or_css . '/autoptimize_fallback.' . $js_or_css;
697 +
698 + // prepare for Shakeeb's Unused CSS files to be 404-handled as well.
699 + if ( strpos( $original_request, 'uucss/uucss-' ) !== false ) {
700 + $original_request = preg_replace( '/uucss\/uucss-[a-z0-9]{32}-/', 'css/', $original_request );
701 + }
702 +
703 + // set fallback URL.
704 + $fallback_target = preg_replace( '/(.*)_(?:[a-z0-9]{32})\.(js|css)$/', '${1}_fallback.${2}', $original_request );
705 +
706 + // redirect to fallback if possible.
707 + if ( $original_request !== $fallback_target && file_exists( $fallback_path ) ) {
708 + // redirect to fallback.
709 + wp_redirect( $fallback_target, 302 );
710 + } else {
711 + // return HTTP 410 (gone) reponse.
712 + status_header( 410 );
713 + }
714 + }
715 + }
716 +
717 + /**
718 + * Checks if cache dirs exist and create if not.
719 + * Returns false if not succesful.
720 + *
721 + * @return bool
722 + */
723 + public static function check_and_create_dirs() {
724 + if ( ! defined( 'AUTOPTIMIZE_CACHE_DIR' ) ) {
725 + // We didn't set a cache.
726 + return false;
727 + }
728 +
729 + foreach ( array( '', 'js', 'css' ) as $dir ) {
730 + if ( ! self::check_cache_dir( AUTOPTIMIZE_CACHE_DIR . $dir ) ) {
253 731 return false;
254 732 }
255 733 }
734 + return true;
735 + }
256 736
257 - // check if we can now write
258 - if(!is_writable($dir)) {
737 + /**
738 + * Ensures the specified `$dir` exists and is writeable.
739 + * Returns false if that's not the case.
740 + *
741 + * @param string $dir Directory to check/create.
742 + *
743 + * @return bool
744 + */
745 + protected static function check_cache_dir( $dir )
746 + {
747 + // Try creating the dir if it doesn't exist.
748 + if ( ! file_exists( $dir ) ) {
749 + @mkdir( $dir, 0775, true ); // @codingStandardsIgnoreLine
750 + if ( ! file_exists( $dir ) ) {
751 + return false;
752 + }
753 + }
754 +
755 + // If we still cannot write, bail.
756 + if ( ! is_writable( $dir ) ) {
259 757 return false;
260 758 }
261 759
262 - // and write index.html here to avoid prying eyes
263 - $indexFile=$dir.'/index.html';
264 - if(!is_file($indexFile)) {
265 - @file_put_contents($indexFile,'<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>');
760 + // Create an index.html in there to avoid prying eyes!
761 + $idx_file = rtrim( $dir, '/\\' ) . '/index.html';
762 + if ( ! is_file( $idx_file ) ) {
763 + @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
266 764 }
267 -
765 +
268 766 return true;
269 767 }
768 +
769 + /**
770 + * Flushes as many page cache plugin's caches as possible.
771 + *
772 + * @return void
773 + */
774 + // @codingStandardsIgnoreStart
775 + public static function flushPageCache()
776 + {
777 + if ( function_exists( 'wp_cache_clear_cache' ) ) {
778 + if ( is_multisite() ) {
779 + $blog_id = get_current_blog_id();
780 + wp_cache_clear_cache( $blog_id );
781 + } else {
782 + wp_cache_clear_cache();
783 + }
784 + } elseif ( has_action( 'cachify_flush_cache' ) ) {
785 + do_action( 'cachify_flush_cache' );
786 + } elseif ( function_exists( 'w3tc_pgcache_flush' ) ) {
787 + w3tc_pgcache_flush();
788 + } elseif ( function_exists( 'wp_fast_cache_bulk_delete_all' ) ) {
789 + wp_fast_cache_bulk_delete_all();
790 + } elseif ( function_exists( 'rapidcache_clear_cache' ) ) {
791 + rapidcache_clear_cache();
792 + } elseif ( class_exists( 'Swift_Performance_Cache' ) ) {
793 + Swift_Performance_Cache::clear_all_cache();
794 + } elseif ( class_exists( 'WpFastestCache' ) ) {
795 + $wpfc = new WpFastestCache();
796 + $wpfc->deleteCache();
797 + } elseif ( class_exists( 'c_ws_plugin__qcache_purging_routines' ) ) {
798 + c_ws_plugin__qcache_purging_routines::purge_cache_dir(); // quick cache
799 + } elseif ( class_exists( 'zencache' ) ) {
800 + zencache::clear();
801 + } elseif ( class_exists( 'comet_cache' ) ) {
802 + comet_cache::clear();
803 + } elseif ( class_exists( 'WpeCommon' ) ) {
804 + // WPEngine cache purge/flush methods to call by default
805 + $wpe_methods = array(
806 + 'purge_varnish_cache',
807 + );
808 +
809 + // More agressive clear/flush/purge behind a filter
810 + if ( apply_filters( 'autoptimize_flush_wpengine_aggressive', false ) ) {
811 + $wpe_methods = array_merge( $wpe_methods, array( 'purge_memcached', 'clear_maxcdn_cache' ) );
812 + }
813 +
814 + // Filtering the entire list of WpeCommon methods to be called (for advanced usage + easier testing)
815 + $wpe_methods = apply_filters( 'autoptimize_flush_wpengine_methods', $wpe_methods );
816 +
817 + foreach ( $wpe_methods as $wpe_method ) {
818 + if ( method_exists( 'WpeCommon', $wpe_method ) ) {
819 + WpeCommon::$wpe_method();
820 + }
821 + }
822 + } elseif ( function_exists( 'sg_cachepress_purge_cache' ) ) {
823 + sg_cachepress_purge_cache();
824 + } elseif ( array_key_exists( 'KINSTA_CACHE_ZONE', $_SERVER ) ) {
825 + $_kinsta_clear_cache_url = 'https://localhost/kinsta-clear-cache-all';
826 + $_kinsta_response = wp_remote_get(
827 + $_kinsta_clear_cache_url,
828 + array(
829 + 'sslverify' => false,
830 + 'timeout' => 5,
831 + )
832 + );
833 + } elseif ( class_exists( 'RaidboxesNginxCacheFunctions' ) ) {
834 + $rb_cache_helper = new RaidboxesNginxCacheFunctions();
835 + $rb_cache_helper->purge_cache();
836 + } elseif ( defined('NGINX_HELPER_BASENAME') ) {
837 + do_action( 'rt_nginx_helper_purge_all' );
838 + } elseif ( file_exists( WP_CONTENT_DIR . '/wp-cache-config.php' ) && function_exists( 'prune_super_cache' ) ) {
839 + // fallback for WP-Super-Cache
840 + global $cache_path;
841 + if ( is_multisite() ) {
842 + $blog_id = get_current_blog_id();
843 + prune_super_cache( get_supercache_dir( $blog_id ), true );
844 + prune_super_cache( $cache_path . 'blogs/', true );
845 + } else {
846 + prune_super_cache( $cache_path . 'supercache/', true );
847 + prune_super_cache( $cache_path, true );
848 + }
849 + } elseif ( class_exists( 'NginxCache' ) ) {
850 + $nginx_cache = new NginxCache();
851 + $nginx_cache->purge_zone_once();
852 + }
853 + }
854 + // @codingStandardsIgnoreEnd
270 855 }