PluginProbe
Autoptimize / 3.1.16
Autoptimize v3.1.16
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.0 → 3.1.16 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 }