delete_advanced_cache_clear_artifacts()   B
last analyzed

Complexity

Conditions 7
Paths 3

Size

Total Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
nc 3
nop 0
dl 0
loc 27
rs 8.5546
c 0
b 0
f 0
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 ) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
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
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
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
199
                }
200
            }
201
        }
202
203
        @unlink( AUTOPTIMIZE_CACHE_DIR . '/.htaccess' ); // @codingStandardsIgnoreLine
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
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 View Code Duplication
        if ( is_multisite() && apply_filters( 'autoptimize_separate_blog_caches', true ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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 ( ! self::cacheavail() ) {
386
            return false;
387
        }
388
389
        // TODO/FIXME: If cache is big, switch to advanced/new cache clearing automatically?
390
        if ( self::advanced_cache_clear_enabled() ) {
391
            self::clear_cache_via_rename();
392
        } else {
393
            self::clear_cache_classic();
394
        }
395
396
        // Remove 404 handler if required.
397
        if ( self::do_fallback() ) {
398
            $_fallback_php = trailingslashit( WP_CONTENT_DIR ) . 'autoptimize_404_handler.php';
399
            @unlink( $_fallback_php ); // @codingStandardsIgnoreLine
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
400
        }
401
402
        // Remove the transient so it gets regenerated...
403
        delete_transient( 'autoptimize_stats' );
404
405
        // Cache was just purged, clear page cache and allow others to hook into our purging...
406
        if ( true === $propagate ) {
407
            if ( ! function_exists( 'autoptimize_do_cachepurged_action' ) ) {
408
                function autoptimize_do_cachepurged_action() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
409
                    do_action( 'autoptimize_action_cachepurged' );
410
                }
411
            }
412
            add_action( 'shutdown', 'autoptimize_do_cachepurged_action', 11 );
413
            add_action( 'autoptimize_action_cachepurged', array( 'autoptimizeCache', 'flushPageCache' ), 10, 0 );
414
        }
415
416
        // Warm cache (part of speedupper)!
417
        if ( apply_filters( 'autoptimize_filter_speedupper', true ) && false == get_transient( 'autoptimize_cache_warmer_protector' ) ) {
418
            set_transient( 'autoptimize_cache_warmer_protector', 'I shall not warm cache for another 10 minutes.', 60 * 10 );
419
            $url   = site_url() . '/?ao_speedup_cachebuster=' . rand( 1, 100000 );
420
            $url   = apply_filters( 'autoptimize_filter_cache_warmer_url', $url );
421
            $cache = @wp_remote_get( $url ); // @codingStandardsIgnoreLine
422
            unset( $cache );
423
        }
424
425
        return true;
426
    }
427
428
    /**
429
     * Wrapper for clearall but with false param
430
     * to ensure the event is not propagated to others
431
     * through our own hooks (to avoid infinite loops).
432
     *
433
     * @return bool
434
     */
435
    public static function clearall_actionless()
436
    {
437
        return self::clearall( false );
438
    }
439
440
    /**
441
     * Returns the contents of our cache dirs.
442
     *
443
     * @return array
444
     */
445
    protected static function get_cache_contents()
446
    {
447
        $contents = array();
448
449
        foreach ( array( '', 'js', 'css' ) as $dir ) {
450
            $contents[ $dir ] = scandir( AUTOPTIMIZE_CACHE_DIR . $dir );
451
        }
452
453
        return $contents;
454
    }
455
456
    /**
457
     * Returns stats about cached contents.
458
     *
459
     * @return array
460
     */
461
    public static function stats()
462
    {
463
        $stats = get_transient( 'autoptimize_stats' );
464
465
        // If no transient, do the actual scan!
466
        if ( ! is_array( $stats ) ) {
467
            if ( ! self::cacheavail() ) {
468
                return 0;
469
            }
470
            $stats = self::stats_scan();
471
            $count = $stats[0];
472
            if ( $count > 100 ) {
473
                // Store results in transient.
474
                set_transient(
475
                    'autoptimize_stats',
476
                    $stats,
477
                    apply_filters( 'autoptimize_filter_cache_statsexpiry', HOUR_IN_SECONDS )
478
                );
479
            }
480
        }
481
482
        return $stats;
483
    }
484
485
    /**
486
     * Performs a scan of cache directory contents and returns an array
487
     * with 3 values: count, size, timestamp.
488
     * count = total number of found files
489
     * size = total filesize (in bytes) of found files
490
     * timestamp = unix timestamp when the scan was last performed/finished.
491
     *
492
     * @return array
493
     */
494
    protected static function stats_scan()
495
    {
496
        $count = 0;
497
        $size  = 0;
498
499
        // Scan everything in our cache directories.
500
        foreach ( self::get_cache_contents() as $name => $files ) {
501
            $dir = rtrim( AUTOPTIMIZE_CACHE_DIR . $name, '/' ) . '/';
502
            foreach ( $files as $file ) {
503
                if ( self::is_valid_cache_file( $dir, $file ) ) {
504
                    if ( AUTOPTIMIZE_CACHE_NOGZIP &&
505
                        (
506
                            false !== strpos( $file, '.js' ) ||
507
                            false !== strpos( $file, '.css' ) ||
508
                            false !== strpos( $file, '.img' ) ||
509
                            false !== strpos( $file, '.txt' )
510
                        )
511
                    ) {
512
                        // Web server is gzipping, we count .js|.css|.img|.txt files.
513
                        $count++;
514
                    } elseif ( ! AUTOPTIMIZE_CACHE_NOGZIP && false !== strpos( $file, '.none' ) ) {
515
                        // We are gzipping ourselves via php, counting only .none files.
516
                        $count++;
517
                    }
518
                    $size += filesize( $dir . $file );
519
                }
520
            }
521
        }
522
523
        $stats = array( $count, $size, time() );
524
525
        return $stats;
526
    }
527
528
    /**
529
     * Ensures the cache directory exists, is writeable and contains the
530
     * required .htaccess files.
531
     * Returns false in case it fails to ensure any of those things.
532
     *
533
     * @return bool
534
     */
535
    public static function cacheavail()
536
    {
537
        // readonly FS explicitly OK'ed by dev, let's assume the cache dirs are there!
538
        if ( defined( 'AUTOPTIMIZE_CACHE_READONLY' ) ) {
539
            return true;
540
        }
541
542
        if ( false === autoptimizeCache::check_and_create_dirs() ) {
543
            return false;
544
        }
545
546
        // Using .htaccess inside our cache folder to overrule wp-super-cache.
547
        $htaccess = AUTOPTIMIZE_CACHE_DIR . '/.htaccess';
548
        if ( ! is_file( $htaccess ) ) {
549
            /**
550
             * Create `wp-content/AO_htaccess_tmpl` file with
551
             * whatever htaccess rules you might need
552
             * if you want to override default AO htaccess
553
             */
554
            $htaccess_tmpl = WP_CONTENT_DIR . '/AO_htaccess_tmpl';
555
            if ( is_file( $htaccess_tmpl ) ) {
556
                $content = file_get_contents( $htaccess_tmpl );
557
            } elseif ( is_multisite() || ! AUTOPTIMIZE_CACHE_NOGZIP ) {
558
                $content = '<IfModule mod_expires.c>
559
        ExpiresActive On
560
        ExpiresByType text/css A30672000
561
        ExpiresByType text/javascript A30672000
562
        ExpiresByType application/javascript A30672000
563
</IfModule>
564
<IfModule mod_headers.c>
565
    Header append Cache-Control "public, immutable"
566
</IfModule>
567
<IfModule mod_deflate.c>
568
        <FilesMatch "\.(js|css)$">
569
        SetOutputFilter DEFLATE
570
    </FilesMatch>
571
</IfModule>
572
<IfModule mod_authz_core.c>
573
    <Files *.php>
574
        Require all granted
575
    </Files>
576
</IfModule>
577
<IfModule !mod_authz_core.c>
578
    <Files *.php>
579
        Order allow,deny
580
        Allow from all
581
    </Files>
582
</IfModule>';
583
            } else {
584
                $content = '<IfModule mod_expires.c>
585
        ExpiresActive On
586
        ExpiresByType text/css A30672000
587
        ExpiresByType text/javascript A30672000
588
        ExpiresByType application/javascript A30672000
589
</IfModule>
590
<IfModule mod_headers.c>
591
    Header append Cache-Control "public, immutable"
592
</IfModule>
593
<IfModule mod_deflate.c>
594
    <FilesMatch "\.(js|css)$">
595
        SetOutputFilter DEFLATE
596
    </FilesMatch>
597
</IfModule>
598
<IfModule mod_authz_core.c>
599
    <Files *.php>
600
        Require all denied
601
    </Files>
602
</IfModule>
603
<IfModule !mod_authz_core.c>
604
    <Files *.php>
605
        Order deny,allow
606
        Deny from all
607
    </Files>
608
</IfModule>';
609
            }
610
611
            if ( self::do_fallback() === true ) {
612
                $content .= "\nErrorDocument 404 " . trailingslashit( parse_url( content_url(), PHP_URL_PATH ) ) . 'autoptimize_404_handler.php';
613
            }
614
            @file_put_contents( $htaccess, $content ); // @codingStandardsIgnoreLine
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
615
        }
616
617
        if ( self::do_fallback() ) {
618
            self::check_fallback_php();
619
        }
620
621
        // All OK!
622
        return true;
623
    }
624
625
    /**
626
     * Checks if fallback-php file exists and create it if not.
627
     *
628
     * Return bool
629
     */
630
    public static function check_fallback_php() {
631
        $_fallback_filename = 'autoptimize_404_handler.php';
632
        $_fallback_php      = trailingslashit( WP_CONTENT_DIR ) . $_fallback_filename;
633
        $_fallback_status   = true;
634
635
        if ( ! file_exists( $_fallback_php ) && is_writable( WP_CONTENT_DIR ) ) {
636
            $_fallback_php_contents = file_get_contents( AUTOPTIMIZE_PLUGIN_DIR . 'config/' . $_fallback_filename );
637
            $_fallback_php_contents = str_replace( '<?php exit;', '<?php', $_fallback_php_contents );
638
            $_fallback_php_contents = str_replace( '<!--ao-cache-dir-->', AUTOPTIMIZE_CACHE_DIR, $_fallback_php_contents );
639
            if ( apply_filters( 'autoptimize_filter_cache_fallback_log_errors', false ) ) {
640
                $_fallback_php_contents = str_replace( '// error_log', 'error_log', $_fallback_php_contents );
641
            }
642
            $_fallback_status = file_put_contents( $_fallback_php, $_fallback_php_contents );
643
        }
644
645
        return $_fallback_status;
646
    }
647
648
    /**
649
     * Tells if AO should try to avoid 404's by creating fallback filesize
650
     * and create a php 404 handler and tell .htaccess to redirect to said handler
651
     * and hook into WordPress to redirect 404 to said handler as well. NGINX users
652
     * are smart enough to get this working, no? ;-)
653
     *
654
     * Return bool
655
     */
656
    public static function do_fallback() {
657
        static $_do_fallback = null;
658
659
        if ( null === $_do_fallback ) {
660
            $_do_fallback = (bool) apply_filters( 'autoptimize_filter_cache_do_fallback', autoptimizeOptionWrapper::get_option( 'autoptimize_cache_fallback', '1' ) );
661
        }
662
663
        return $_do_fallback;
664
    }
665
666
    /**
667
     * Hooks into template_redirect, will act on 404-ing requests for
668
     * Autoptimized files and redirects to the fallback CSS/ JS if available
669
     * and 410'ing ("Gone") if fallback not available.
670
     */
671
    public static function wordpress_notfound_fallback() {
672
        $original_request = strtok( $_SERVER['REQUEST_URI'], '?' );
673
        if ( strpos( $original_request, wp_basename( WP_CONTENT_DIR ) . AUTOPTIMIZE_CACHE_CHILD_DIR ) !== false && is_404() ) {
674
            // make sure this is not considered a 404.
675
            global $wp_query;
676
            $wp_query->is_404 = false;
677
678
            // set fallback path.
679
            $js_or_css     = pathinfo( $original_request, PATHINFO_EXTENSION );
680
            $fallback_path = AUTOPTIMIZE_CACHE_DIR . $js_or_css . '/autoptimize_fallback.' . $js_or_css;
681
682
            // prepare for Shakeeb's Unused CSS files to be 404-handled as well.
683
            if ( strpos( $original_request, 'uucss/uucss-' ) !== false ) {
684
                $original_request = preg_replace( '/uucss\/uucss-[a-z0-9]{32}-/', 'css/', $original_request  );
685
            }
686
687
            // set fallback URL.
688
            $fallback_target = preg_replace( '/(.*)_(?:[a-z0-9]{32})\.(js|css)$/', '${1}_fallback.${2}', $original_request );
689
690
            // redirect to fallback if possible.
691
            if ( $original_request !== $fallback_target && file_exists( $fallback_path ) ) {
692
                // redirect to fallback.
693
                wp_redirect( $fallback_target, 302 );
694
            } else {
695
                // return HTTP 410 (gone) reponse.
696
                status_header( 410 );
697
            }
698
        }
699
    }
700
701
    /**
702
     * Checks if cache dirs exist and create if not.
703
     * Returns false if not succesful.
704
     *
705
     * @return bool
706
     */
707
    public static function check_and_create_dirs() {
708
        if ( ! defined( 'AUTOPTIMIZE_CACHE_DIR' ) ) {
709
            // We didn't set a cache.
710
            return false;
711
        }
712
713
        foreach ( array( '', 'js', 'css' ) as $dir ) {
714
            if ( ! self::check_cache_dir( AUTOPTIMIZE_CACHE_DIR . $dir ) ) {
715
                return false;
716
            }
717
        }
718
        return true;
719
    }
720
721
    /**
722
     * Ensures the specified `$dir` exists and is writeable.
723
     * Returns false if that's not the case.
724
     *
725
     * @param string $dir Directory to check/create.
726
     *
727
     * @return bool
728
     */
729
    protected static function check_cache_dir( $dir )
730
    {
731
        // Try creating the dir if it doesn't exist.
732
        if ( ! file_exists( $dir ) ) {
733
            @mkdir( $dir, 0775, true ); // @codingStandardsIgnoreLine
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
734
            if ( ! file_exists( $dir ) ) {
735
                return false;
736
            }
737
        }
738
739
        // If we still cannot write, bail.
740
        if ( ! is_writable( $dir ) ) {
741
            return false;
742
        }
743
744
        // Create an index.html in there to avoid prying eyes!
745
        $idx_file = rtrim( $dir, '/\\' ) . '/index.html';
746
        if ( ! is_file( $idx_file ) ) {
747
            @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
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
748
        }
749
750
        return true;
751
    }
752
753
    /**
754
     * Flushes as many page cache plugin's caches as possible.
755
     *
756
     * @return void
757
     */
758
    // @codingStandardsIgnoreStart
759
    public static function flushPageCache()
760
    {
761
        if ( function_exists( 'wp_cache_clear_cache' ) ) {
762
            if ( is_multisite() ) {
763
                $blog_id = get_current_blog_id();
764
                wp_cache_clear_cache( $blog_id );
765
            } else {
766
                wp_cache_clear_cache();
767
            }
768
        } elseif ( has_action( 'cachify_flush_cache' ) ) {
769
            do_action( 'cachify_flush_cache' );
770
        } elseif ( function_exists( 'w3tc_pgcache_flush' ) ) {
771
            w3tc_pgcache_flush();
772
        } elseif ( function_exists( 'wp_fast_cache_bulk_delete_all' ) ) {
773
            wp_fast_cache_bulk_delete_all();
774
        } elseif ( function_exists( 'rapidcache_clear_cache' ) ) {
775
            rapidcache_clear_cache();
776
        } elseif ( class_exists( 'Swift_Performance_Cache' ) ) {
777
            Swift_Performance_Cache::clear_all_cache();
778
        } elseif ( class_exists( 'WpFastestCache' ) ) {
779
            $wpfc = new WpFastestCache();
780
            $wpfc->deleteCache();
781
        } elseif ( class_exists( 'c_ws_plugin__qcache_purging_routines' ) ) {
782
            c_ws_plugin__qcache_purging_routines::purge_cache_dir(); // quick cache
783
        } elseif ( class_exists( 'zencache' ) ) {
784
            zencache::clear();
785
        } elseif ( class_exists( 'comet_cache' ) ) {
786
            comet_cache::clear();
787
        } elseif ( class_exists( 'WpeCommon' ) ) {
788
            // WPEngine cache purge/flush methods to call by default
789
            $wpe_methods = array(
790
                'purge_varnish_cache',
791
            );
792
793
            // More agressive clear/flush/purge behind a filter
794
            if ( apply_filters( 'autoptimize_flush_wpengine_aggressive', false ) ) {
795
                $wpe_methods = array_merge( $wpe_methods, array( 'purge_memcached', 'clear_maxcdn_cache' ) );
796
            }
797
798
            // Filtering the entire list of WpeCommon methods to be called (for advanced usage + easier testing)
799
            $wpe_methods = apply_filters( 'autoptimize_flush_wpengine_methods', $wpe_methods );
800
801
            foreach ( $wpe_methods as $wpe_method ) {
802
                if ( method_exists( 'WpeCommon', $wpe_method ) ) {
803
                    WpeCommon::$wpe_method();
804
                }
805
            }
806
        } elseif ( function_exists( 'sg_cachepress_purge_cache' ) ) {
807
            sg_cachepress_purge_cache();
808
        } elseif ( array_key_exists( 'KINSTA_CACHE_ZONE', $_SERVER ) ) {
809
            $_kinsta_clear_cache_url = 'https://localhost/kinsta-clear-cache-all';
810
            $_kinsta_response        = wp_remote_get(
0 ignored issues
show
Unused Code introduced by
$_kinsta_response is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
811
                $_kinsta_clear_cache_url,
812
                array( 
813
                    'sslverify' => false,
814
                    'timeout' => 5,
815
                    )
816
            );
817
        } elseif ( defined('NGINX_HELPER_BASENAME') ) {
818
            do_action( 'rt_nginx_helper_purge_all' );
819
        } elseif ( file_exists( WP_CONTENT_DIR . '/wp-cache-config.php' ) && function_exists( 'prune_super_cache' ) ) {
820
            // fallback for WP-Super-Cache
821
            global $cache_path;
822
            if ( is_multisite() ) {
823
                $blog_id = get_current_blog_id();
824
                prune_super_cache( get_supercache_dir( $blog_id ), true );
825
                prune_super_cache( $cache_path . 'blogs/', true );
826
            } else {
827
                prune_super_cache( $cache_path . 'supercache/', true );
828
                prune_super_cache( $cache_path, true );
829
            }
830
        } elseif ( class_exists( 'NginxCache' ) ) {
831
            $nginx_cache = new NginxCache();
832
            $nginx_cache->purge_zone_once();
833
        }
834
    }
835
    // @codingStandardsIgnoreEnd
836
}
837