PluginProbe
Autoptimize / 3.1.10
Autoptimize v3.1.10
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 3.1.10, at classes/autoptimizeCache.php

856 lines 29.7 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 $_min_ext = '';
44 if ( apply_filters( 'autoptimize_filter_cache_url_add_min_ext', false ) ) {
45 $_min_ext = '.min';
46 }
47
48 $this->cachedir = AUTOPTIMIZE_CACHE_DIR;
49 $this->nogzip = AUTOPTIMIZE_CACHE_NOGZIP;
50 if ( ! $this->nogzip ) {
51 $this->filename = AUTOPTIMIZE_CACHEFILE_PREFIX . $md5 . $_min_ext . '.php';
52 } else {
53 if ( in_array( $ext, array( 'js', 'css' ) ) ) {
54 $this->filename = $ext . '/' . AUTOPTIMIZE_CACHEFILE_PREFIX . $md5 . $_min_ext . '.' . $ext;
55 } else {
56 $this->filename = AUTOPTIMIZE_CACHEFILE_PREFIX . $md5 . $_min_ext . '.' . $ext;
57 }
58 }
59 }
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 );
69 }
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' );
81 } else {
82 return file_get_contents( $this->cachedir . $this->filename );
83 }
84 }
85 return false;
86 }
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 }
130 }
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 }
139 }
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 );
145 }
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
161 return $this->filename;
162 }
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() ) {
320 return false;
321 }
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 }
339 }
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 futtta@gmail.com to discuss.', 'Autoptimize 2.9.6' );
392 }
393 return false;
394 }
395 }
396
397 if ( ! self::cacheavail() || true === apply_filters( 'autoptimize_filter_cache_clearall_disabled', false ) ) {
398 return false;
399 }
400
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 }
423 }
424 add_action( 'shutdown', 'autoptimize_do_cachepurged_action', 11 );
425 add_action( 'autoptimize_action_cachepurged', array( 'autoptimizeCache', 'flushPageCache' ), 10, 0 );
426 }
427
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 );
435 }
436
437 return true;
438 }
439
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 }
451
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() ) {
480 return 0;
481 }
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 );
491 }
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++;
529 }
530 $size += filesize( $dir . $file );
531 }
532 }
533 }
534
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() ) {
555 return false;
556 }
557
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
563 * whatever htaccess rules you might need
564 * if you want to override default AO htaccess
565 */
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>
571 ExpiresActive On
572 ExpiresByType text/css A30672000
573 ExpiresByType text/javascript A30672000
574 ExpiresByType application/javascript A30672000
575 </IfModule>
576 <IfModule mod_headers.c>
577 Header append Cache-Control "public, immutable"
578 </IfModule>
579 <IfModule mod_deflate.c>
580 <FilesMatch "\.(js|css)$">
581 SetOutputFilter DEFLATE
582 </FilesMatch>
583 </IfModule>
584 <IfModule mod_authz_core.c>
585 <Files *.php>
586 Require all granted
587 </Files>
588 </IfModule>
589 <IfModule !mod_authz_core.c>
590 <Files *.php>
591 Order allow,deny
592 Allow from all
593 </Files>
594 </IfModule>';
595 } else {
596 $content = '<IfModule mod_expires.c>
597 ExpiresActive On
598 ExpiresByType text/css A30672000
599 ExpiresByType text/javascript A30672000
600 ExpiresByType application/javascript A30672000
601 </IfModule>
602 <IfModule mod_headers.c>
603 Header append Cache-Control "public, immutable"
604 </IfModule>
605 <IfModule mod_deflate.c>
606 <FilesMatch "\.(js|css)$">
607 SetOutputFilter DEFLATE
608 </FilesMatch>
609 </IfModule>
610 <IfModule mod_authz_core.c>
611 <Files *.php>
612 Require all denied
613 </Files>
614 </IfModule>
615 <IfModule !mod_authz_core.c>
616 <Files *.php>
617 Order deny,allow
618 Deny from all
619 </Files>
620 </IfModule>';
621 }
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
627 }
628
629 if ( self::do_fallback() ) {
630 self::check_fallback_php();
631 }
632
633 // All OK!
634 return true;
635 }
636
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 ) ) {
731 return false;
732 }
733 }
734 return true;
735 }
736
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 ) ) {
757 return false;
758 }
759
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
764 }
765
766 return true;
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
855 }
856