Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
11.03% covered (danger)
11.03%
30 / 272
12.50% covered (danger)
12.50%
4 / 32
CRAP
0.00% covered (danger)
0.00%
0 / 1
Boost_Cache
5.49% covered (danger)
5.49%
14 / 255
12.50% covered (danger)
12.50%
4 / 32
8712.39
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 init_actions
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
2
 load_extra
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 serve
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
20
 is_loaded
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 get_storage
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 serve_cached
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
56
 send_header
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 ob_start
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 ob_callback
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
72
 rebuild_front_page
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
12
 invalidate_on_comment_transition
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
20
 rebuild_on_comment_edit
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 rebuild_on_comment_post
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
20
 is_published
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
6
 invalidate_on_post_transition
0.00% covered (danger)
0.00%
0 / 22
0.00% covered (danger)
0.00%
0 / 1
72
 delete_on_post_trash
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
12
 get_post_path_for_invalidation
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
56
 rebuild_post_terms_cache
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
30
 rebuild_author_page
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 rebuild_all
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 delete_post_cache
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 rebuild_post_cache
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 rebuild_page
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
1
 delete_page
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 rebuild_recursive
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 delete_recursive
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 invalidate_cache_success
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 ignore_get_parameters
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
42
 ignore_cookies
0.00% covered (danger)
0.00%
0 / 38
0.00% covered (danger)
0.00%
0 / 1
110
 disable_caching_on_error
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
6
 init_do_cache
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2/*
3 * This file is loaded by advanced-cache.php, and so cannot rely on autoloading.
4 */
5
6namespace Automattic\Jetpack_Boost\Modules\Optimizations\Page_Cache\Pre_WordPress;
7
8use WP_Comment;
9use WP_Post;
10
11/*
12 * Require all pre-wordpress files here. These files aren't autoloaded as they are loaded before WordPress is fully initialized.
13 * pre-wordpress files assume all other pre-wordpress files are loaded here.
14 */
15require_once __DIR__ . '/path-actions/interface-path-action.php';
16require_once __DIR__ . '/path-actions/class-filter-older.php';
17require_once __DIR__ . '/path-actions/class-rebuild-file.php';
18require_once __DIR__ . '/path-actions/class-simple-delete.php';
19require_once __DIR__ . '/boost-cache-actions.php';
20require_once __DIR__ . '/class-boost-cache-error.php';
21require_once __DIR__ . '/class-boost-cache-settings.php';
22require_once __DIR__ . '/class-boost-cache-utils.php';
23require_once __DIR__ . '/class-filesystem-utils.php';
24require_once __DIR__ . '/class-logger.php';
25require_once __DIR__ . '/class-request.php';
26require_once __DIR__ . '/storage/interface-storage.php';
27require_once __DIR__ . '/storage/class-file-storage.php';
28
29// Define how many seconds the cache should last for each cached page.
30if ( ! defined( 'JETPACK_BOOST_CACHE_DURATION' ) ) {
31    define( 'JETPACK_BOOST_CACHE_DURATION', HOUR_IN_SECONDS );
32}
33
34// Define how many seconds the rebuild cache should be considered stale, but usable, for each cached page.
35if ( ! defined( 'JETPACK_BOOST_CACHE_REBUILD_DURATION' ) ) {
36    define( 'JETPACK_BOOST_CACHE_REBUILD_DURATION', 10 );
37}
38
39class Boost_Cache {
40    /**
41     * @var Boost_Cache_Settings - The settings for the page cache.
42     */
43    private $settings;
44
45    /**
46     * @var Storage\Storage - The storage system used by Boost Cache.
47     */
48    private $storage;
49
50    /**
51     * @var Request - The request object that provides utility for the current request.
52     */
53    private $request = null;
54
55    /**
56     * @var bool - Indicates whether the cache engine has been loaded.
57     */
58    private static $cache_engine_loaded = false;
59
60    /**
61     * @var bool - Indicates whether WordPress initialized correctly and we can cache the page.
62     */
63    private $do_cache = false;
64
65    /**
66     * @var string - The ignored cookies that were removed from the cache parameters.
67     */
68    private $ignored_cookies = '';
69
70    /**
71     * @var string - The ignored GET parameters that were removed from the cache parameters.
72     */
73    private $ignored_get_parameters = '';
74
75    /**
76     * @param ?Storage\Storage $storage - Optionally provide a Storage subclass to handle actually storing and retrieving cached content. Defaults to a new instance of File_Storage.
77     */
78    public function __construct( $storage = null ) {
79        $this->settings = Boost_Cache_Settings::get_instance();
80        $home           = isset( $_SERVER['HTTP_HOST'] ) ? strtolower( $_SERVER['HTTP_HOST'] ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash
81        $this->storage  = $storage ?? new Storage\File_Storage( $home );
82        $this->request  = Request::current();
83    }
84
85    /**
86     * Initialize the actions for the cache.
87     */
88    public function init_actions() {
89        add_action( 'transition_post_status', array( $this, 'invalidate_on_post_transition' ), 10, 3 );
90        add_action( 'transition_comment_status', array( $this, 'invalidate_on_comment_transition' ), 10, 3 );
91        add_action( 'comment_post', array( $this, 'rebuild_on_comment_post' ), 10, 3 );
92        add_action( 'edit_comment', array( $this, 'rebuild_on_comment_edit' ), 10, 2 );
93        add_action( 'switch_theme', array( $this, 'rebuild_all' ) );
94        add_action( 'wp_trash_post', array( $this, 'delete_on_post_trash' ), 10, 2 );
95        add_filter( 'wp_php_error_message', array( $this, 'disable_caching_on_error' ) );
96        add_filter( 'init', array( $this, 'init_do_cache' ) );
97        add_filter( 'jetpack_boost_cache_parameters', array( $this, 'ignore_cookies' ) );
98        add_filter( 'jetpack_boost_cache_parameters', array( $this, 'ignore_get_parameters' ) );
99        $this->load_extra();
100    }
101
102    private function load_extra() {
103        if ( file_exists( WP_CONTENT_DIR . '/boost-cache-extra.php' ) ) {
104            include_once WP_CONTENT_DIR . '/boost-cache-extra.php';
105        }
106    }
107
108    /**
109     * Serve the cached page if it exists, otherwise start output buffering.
110     */
111    public function serve() {
112        if ( ! $this->settings->get_enabled() ) {
113            return;
114        }
115
116        // Indicate that the cache engine has been loaded.
117        self::$cache_engine_loaded = true;
118
119        if ( ! $this->request->is_cacheable() ) {
120            return;
121        }
122
123        if ( ! $this->serve_cached() ) {
124            $this->ob_start();
125        }
126    }
127
128    /**
129     * Check if the cache engine has been loaded.
130     *
131     * @return bool - True if the cache engine has been loaded, false otherwise.
132     */
133    public static function is_loaded() {
134        return self::$cache_engine_loaded;
135    }
136
137    /**
138     * Get the storage instance used by Boost Cache.
139     *
140     * @return Storage\Storage
141     */
142    public function get_storage() {
143        return $this->storage;
144    }
145
146    /**
147     * Serve cached content, if any is available for the current request. Will terminate if it does so.
148     * Otherwise, returns false.
149     */
150    public function serve_cached() {
151        if ( ! $this->request->is_cacheable() ) {
152            return false;
153        }
154
155        // check if rebuild file exists and rename it to the correct file
156        $rebuild_found = $this->storage->reset_rebuild_file( $this->request->get_uri(), $this->request->get_parameters() );
157        if ( $rebuild_found ) {
158            Logger::debug( 'Rebuild file found. Will be used for cache until new file created.' );
159            $cached = false;
160        } else {
161            $cached = $this->storage->read( $this->request->get_uri(), $this->request->get_parameters() );
162        }
163
164        if ( is_string( $cached ) ) {
165            $this->send_header( 'X-Jetpack-Boost-Cache: hit' );
166            $ignored_cookies_message = $this->ignored_cookies === '' ? '' : " and ignored cookies: {$this->ignored_cookies}";
167            $ignored_get_message     = $this->ignored_get_parameters === '' ? '' : " and ignored GET parameters: {$this->ignored_get_parameters}";
168            Logger::debug( 'Serving cached page' . $ignored_cookies_message . $ignored_get_message );
169            echo $cached; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
170            die( 0 );
171        }
172
173        $cache_status = $rebuild_found ? 'rebuild' : 'miss';
174        $this->send_header( 'X-Jetpack-Boost-Cache: ' . $cache_status );
175
176        return false;
177    }
178
179    private function send_header( $header ) {
180        if ( ! headers_sent() ) {
181            header( $header );
182        }
183    }
184
185    /**
186     * Starts output buffering and sets the callback to save the cache file.
187     *
188     * @return bool - false if page is not cacheable.
189     */
190    public function ob_start() {
191        if ( ! $this->request->is_cacheable() ) {
192            return false;
193        }
194
195        ob_start( array( $this, 'ob_callback' ) );
196        return true;
197    }
198
199    /**
200     * Callback function from output buffer. This function saves the output
201     * buffer to a cache file and then returns the buffer so PHP will send it
202     * to the browser.
203     *
204     * @param string $buffer - The output buffer to save to the cache file.
205     * @return string - The output buffer.
206     */
207    public function ob_callback( $buffer ) {
208        if ( strlen( $buffer ) > 0 && $this->request->is_cacheable() ) {
209
210            // Do not cache the page as WordPress did not initialize correctly.
211            if ( ! $this->do_cache ) {
212                Logger::debug( 'Page exited early. Do not cache.' );
213                return $buffer;
214            }
215
216            if ( false === stripos( $buffer, '</html>' ) ) {
217                Logger::debug( 'Closing HTML tag not found, not caching' );
218                return $buffer;
219            }
220
221            $result = $this->storage->write( $this->request->get_uri(), $this->request->get_parameters(), $buffer );
222
223            if ( $result instanceof Boost_Cache_Error ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedIf
224                Logger::debug( 'Error writing cache file: ' . $result->get_error_message() );
225            } else {
226                $ignored_cookies_message = $this->ignored_cookies === '' ? '' : " and ignored cookies: {$this->ignored_cookies}";
227                $ignored_get_message     = $this->ignored_get_parameters === '' ? '' : " and ignored GET parameters: {$this->ignored_get_parameters}";
228                Logger::debug( 'Cache file created' . $ignored_cookies_message . $ignored_get_message );
229            }
230        }
231
232        return $buffer;
233    }
234
235    /**
236     * Delete/rebuild the cache for the front page and paged archives.
237     * This is called when a post is edited, deleted, or published.
238     */
239    public function rebuild_front_page() {
240        if ( get_option( 'show_on_front' ) === 'page' ) {
241            $this->rebuild_page( home_url() );
242            $posts_page_id = get_option( 'page_for_posts' ); // posts page
243            if ( $posts_page_id ) {
244                Logger::debug( 'rebuild_front_page: deleting posts page cache' );
245                $this->rebuild_post_cache( get_post( $posts_page_id ) );
246            }
247        } else {
248            $this->rebuild_page( home_url() );
249            Logger::debug( 'delete front page cache ' . Boost_Cache_Utils::normalize_request_uri( home_url() ) );
250        }
251    }
252
253    /**
254     * Rebuild the cache for the post if the comment transitioned from one state to another.
255     *
256     * @param string     $new_status - The new status of the comment.
257     * @param string     $old_status - The old status of the comment.
258     * @param WP_Comment $comment - The comment that transitioned.
259     */
260    public function invalidate_on_comment_transition( $new_status, $old_status, $comment ) {
261        if ( $new_status === $old_status ) {
262            return;
263        }
264        Logger::debug( "invalidate_on_comment_transition: $new_status$old_status" );
265
266        if ( $new_status !== 'approved' && $old_status !== 'approved' ) {
267            Logger::debug( 'invalidate_on_comment_transition: comment not approved' );
268            return;
269        }
270
271        $post = get_post( (int) $comment->comment_post_ID );
272        $this->rebuild_post_cache( $post );
273    }
274
275    /**
276     * After editing a comment, rebuild the cache for the post if the comment is approved.
277     * If changing state and editing, both actions will be called, but the cache will only be rebuilt once.
278     *
279     * @param int   $comment_id - The id of the comment.
280     * @param array $commentdata - The comment data.
281     */
282    public function rebuild_on_comment_edit( $comment_id, $commentdata ) {
283        $post = get_post( $commentdata['comment_post_ID'] );
284
285        if ( (int) $commentdata['comment_approved'] === 1 ) {
286            $this->rebuild_post_cache( $post );
287        }
288    }
289
290    /**
291     * After a comment is posted, rebuild the cache for the post if the comment is approved.
292     * If the comment is not approved, only rebuild the cache for this post for this visitor.
293     *
294     * @param int   $comment_id - The id of the comment.
295     * @param int   $comment_approved - The approval status of the comment.
296     * @param array $commentdata - The comment data.
297     */
298    public function rebuild_on_comment_post( $comment_id, $comment_approved, $commentdata ) {
299        $post = get_post( $commentdata['comment_post_ID'] );
300        Logger::debug( "rebuild_on_comment_post: $comment_id$comment_approved{$post->ID}" );
301        /**
302         * If a comment is not approved, we only need to delete the cache for
303         * this post for this visitor so the unmoderated comment is shown to them.
304         */
305        if ( $comment_approved !== 1 ) {
306            $parameters = $this->request->get_parameters();
307
308            /*
309             * If there are no cookies, then visitor did not click "remember me".
310             * They'll be redirected to a page with a hash in the URL for the
311             * moderation message.
312             * Only delete the cache for visitors who clicked "remember me".
313             */
314            if ( isset( $parameters['cookies'] ) && ! empty( $parameters['cookies'] ) ) {
315                $this->delete_page( get_permalink( $post->ID ), $parameters );
316            }
317            return;
318        }
319
320        $this->rebuild_post_cache( $post );
321    }
322
323    /**
324     * Returns true if the post is published or private.
325     *
326     * @param string $status - The status of the post.
327     * @return bool
328     */
329    private function is_published( $status ) {
330        return $status === 'publish' || $status === 'private';
331    }
332
333    /**
334     * Delete the cached post if it transitioned from one state to another.
335     *
336     * @param string  $new_status - The new status of the post.
337     * @param string  $old_status - The old status of the post.
338     * @param WP_Post $post - The post that transitioned.
339     */
340    public function invalidate_on_post_transition( $new_status, $old_status, $post ) {
341        // Special case: Delete cache if the post type can effect the whole site.
342        $special_post_types = array( 'wp_template', 'wp_template_part', 'wp_global_styles' );
343        if ( in_array( $post->post_type, $special_post_types, true ) ) {
344            Logger::debug( 'invalidate_on_post_transition: special post type ' . $post->post_type );
345            $this->rebuild_all();
346            return;
347        }
348
349        if ( ! Boost_Cache_Utils::is_visible_post_type( $post ) ) {
350            return;
351        }
352
353        if ( $new_status === 'trash' ) {
354            return;
355        }
356
357        Logger::debug( "invalidate_on_post_transition: $new_status$old_status{$post->ID}" );
358
359        // Don't delete the cache for posts that weren't published and aren't published now
360        if ( ! $this->is_published( $new_status ) && ! $this->is_published( $old_status ) ) {
361            Logger::debug( 'invalidate_on_post_transition: not published' );
362            return;
363        }
364
365        // delete the cache files entirely if the post was unpublished
366        if ( 'publish' === $old_status && 'publish' !== $new_status ) {
367            Logger::debug( 'invalidate_on_post_transition: delete cache on new private page' );
368            $this->delete_on_post_trash( $post->ID, $old_status );
369            return;
370        }
371        Logger::debug( "invalidate_on_post_transition: rebuilding post {$post->ID}" );
372
373        $this->rebuild_post_cache( $post );
374        $this->rebuild_post_terms_cache( $post );
375        $this->rebuild_front_page();
376        $this->rebuild_author_page( (int) $post->post_author );
377    }
378
379    /**
380     * Delete the cache for the post if it was trashed.
381     *
382     * @param int    $post_id - The id of the post.
383     * @param string $old_status - The old status of the post.
384     */
385    public function delete_on_post_trash( $post_id, $old_status ) {
386        if ( $this->is_published( $old_status ) ) {
387            $post      = get_post( $post_id );
388            $post_path = $this->get_post_path_for_invalidation( $post );
389            if ( $post_path ) {
390                $this->delete_recursive( $post_path );
391            }
392            $this->rebuild_post_terms_cache( $post );
393            $this->rebuild_front_page();
394            $this->rebuild_author_page( (int) $post->post_author );
395        }
396    }
397
398    private function get_post_path_for_invalidation( $post ) {
399        static $already_deleted = -1;
400        if ( $already_deleted === $post->ID ) {
401            return null;
402        }
403
404        /**
405         * Don't invalidate the cache for post types that are not public.
406         */
407        if ( ! Boost_Cache_Utils::is_visible_post_type( $post ) ) {
408            return null;
409        }
410
411        $already_deleted = $post->ID;
412
413        /**
414         * If a post is unpublished, the permalink will be deleted. In that case,
415         * get_sample_permalink() will return a permalink with ?p=123 instead of
416         * the post name. We need to get the post name from the post object.
417         */
418        $permalink = get_permalink( $post->ID );
419        if ( strpos( $permalink, '?p=' ) !== false || strpos( $permalink, '?page_id=' ) !== false ) {
420            if ( $post->post_type === 'page' ) {
421                $permalink = get_page_link( $post->ID, false, true );
422            } else {
423                if ( ! function_exists( 'get_sample_permalink' ) ) {
424                    require_once ABSPATH . 'wp-admin/includes/post.php';
425                }
426                list( $permalink, $post_name ) = get_sample_permalink( $post->ID );
427                $permalink                     = str_replace( '%postname%', $post_name, $permalink );
428            }
429        }
430        return $permalink;
431    }
432
433    /**
434     * Delete the cache for terms associated with this post.
435     *
436     * @param WP_Post $post - The post to delete the cache for.
437     */
438    public function rebuild_post_terms_cache( $post ) {
439        $categories = get_the_category( $post->ID );
440        if ( is_array( $categories ) ) {
441            foreach ( $categories as $category ) {
442                $link = trailingslashit( get_category_link( $category->term_id ) );
443                $this->rebuild_recursive( $link );
444            }
445        }
446
447        $tags = get_the_tags( $post->ID );
448        if ( is_array( $tags ) ) {
449            foreach ( $tags as $tag ) {
450                $link = trailingslashit( get_tag_link( $tag->term_id ) );
451                $this->rebuild_recursive( $link );
452            }
453        }
454    }
455
456    /**
457     * Delete the entire cache for the author's archive page.
458     *
459     * @param int $author_id - The id of the author.
460     */
461    public function rebuild_author_page( $author_id ) {
462        $author = get_userdata( $author_id );
463        if ( ! $author ) {
464            return;
465        }
466
467        $author_link = get_author_posts_url( $author_id, $author->user_nicename );
468        $this->rebuild_recursive( $author_link );
469    }
470
471    /**
472     * Rebuild the entire cache.
473     */
474    public function rebuild_all() {
475        $this->rebuild_recursive( home_url() );
476    }
477
478    public function delete_post_cache( $post ) {
479        $post_path = $this->get_post_path_for_invalidation( $post );
480        if ( null === $post_path ) {
481            return;
482        }
483
484        if ( Boost_Cache_Utils::trailingslashit( $post_path ) !== Boost_Cache_Utils::trailingslashit( home_url() ) ) {
485            $this->delete_recursive( $post_path );
486        } else {
487            $this->delete_page( $post_path );
488        }
489    }
490
491    public function rebuild_post_cache( $post ) {
492        $post_path = $this->get_post_path_for_invalidation( $post );
493        if ( null === $post_path ) {
494            return;
495        }
496
497        if ( Boost_Cache_Utils::trailingslashit( $post_path ) !== Boost_Cache_Utils::trailingslashit( home_url() ) ) {
498            $this->rebuild_recursive( $post_path );
499        } else {
500            $this->rebuild_page( $post_path );
501        }
502    }
503
504    public function rebuild_page( $path, $parameters = false ) {
505        $this->storage->clear(
506            $path,
507            array(
508                'rebuild'    => true,
509                'parameters' => $parameters,
510            )
511        );
512        $this->invalidate_cache_success( $path, 'rebuild', 'page' );
513    }
514
515    public function delete_page( $path, $parameters = false ) {
516        $this->storage->clear(
517            $path,
518            array(
519                'rebuild'    => false,
520                'parameters' => $parameters,
521            )
522        );
523        $this->invalidate_cache_success( $path, 'delete', 'page' );
524    }
525
526    public function rebuild_recursive( $path ) {
527        $this->storage->clear(
528            $path,
529            array(
530                'rebuild'   => true,
531                'recursive' => true,
532            )
533        );
534        $this->invalidate_cache_success( $path, 'rebuild', 'recursive' );
535    }
536
537    public function delete_recursive( $path ) {
538        $this->storage->clear(
539            $path,
540            array(
541                'rebuild'   => false,
542                'recursive' => true,
543            )
544        );
545        $this->invalidate_cache_success( $path, 'delete', 'recursive' );
546    }
547
548    private function invalidate_cache_success( $path, $type, $scope ) {
549        do_action( 'jetpack_boost_invalidate_cache_success', $path, $type, $scope );
550    }
551
552    /**
553     * Ignore certain GET parameters in the cache parameters so cached pages can be served to these visitors.
554     *
555     * @param array $parameters - The parameters with the GET array to filter.
556     * @return array - The parameters with GET parameters removed.
557     */
558    public function ignore_get_parameters( $parameters ) {
559        static $params = false;
560
561        // Only run this once as it may be called multiple times on uncached pages.
562        if ( $params ) {
563            return $params;
564        }
565
566        /**
567         * Filters the GET parameters so cached pages can be served to these visitors.
568         * The list is an array of regex patterns. The default list contains the
569         * most common GET parameters used by analytics services.
570         *
571         * @since 3.8.0
572         *
573         * @param array $get_parameters An array of regexes to remove items from the GET parameter list.
574         */
575        $get_parameters = apply_filters(
576            'jetpack_boost_ignore_get_parameters',
577            array( 'utm_source', 'utm_medium', 'utm_campaign', 'utm_content', 'utm_term', 'ysclid', 'srsltid', 'yclid' )
578        );
579
580        $get_parameters = array_unique(
581            array_map(
582                'trim',
583                $get_parameters
584            )
585        );
586
587        foreach ( $get_parameters as $get_parameter ) {
588            foreach ( array_keys( $parameters['get'] ) as $get_parameter_name ) {
589                if ( preg_match( '/^' . $get_parameter . '$/', $get_parameter_name ) ) {
590                    unset( $parameters['get'][ $get_parameter_name ] );
591                    $this->ignored_get_parameters .= $get_parameter_name . ',';
592                }
593            }
594        }
595        if ( $this->ignored_get_parameters !== '' ) {
596            $this->ignored_get_parameters = rtrim( $this->ignored_get_parameters, ',' );
597        }
598
599        $params = $parameters;
600
601        return $parameters;
602    }
603
604    /**
605     * Ignore certain cookies in the cache parameters so cached pages can be served to these visitors.
606     *
607     * @param array $parameters - The parameters with the cookies array to filter.
608     * @return array - The parameters with cookies removed.
609     */
610    public function ignore_cookies( $parameters ) {
611        static $params = false;
612
613        // Only run this once as it may be called multiple times on uncached pages.
614        if ( $params ) {
615            return $params;
616        }
617
618        $default_cookies = array(
619            'cf_clearance',
620            'cf_chl_rc_i',
621            'cf_chl_rc_ni',
622            'cf_chl_rc_m',
623            '_cfuvid',
624            '__cfruid',
625            '__cfwaitingroom',
626            'cf_ob_info',
627            'cf_use_ob',
628            '__cfseq',
629            '__cf_bm',
630            '__cflb',
631
632            // Sourcebuster
633            'sbjs_(.*)',
634
635            // Google Analytics
636            '_ga(?:_[A-Z0-9]*)?',
637
638            // AWS Load Balancer
639            'AWSELB',
640            'AWSELBCORS',
641            'AWSALB',
642            'AWSALBCORS',
643        );
644        $jetpack_cookies = array( 'tk_ai', 'tk_qs' );
645        $cookies         = array_merge( $default_cookies, $jetpack_cookies );
646
647        /**
648         * Filters the browser cookies so cached pages can be served to these visitors.
649         * The list is an array of regex patterns. The default list contains the
650         * cookies used by Cloudflare, and the regex pattern for the sbjs_ cookies
651         * used by sourcebuster.js
652         *
653         * @since 3.8.0
654         *
655         * @param array $cookies An array of regexes to remove items from the cookie list.
656         */
657        $cookies = apply_filters(
658            'jetpack_boost_ignore_cookies',
659            $cookies
660        );
661
662        $cookies = array_unique(
663            array_map(
664                'trim',
665                $cookies
666            )
667        );
668
669        /**
670         * The Jetpack Cookie Banner plugin sets a cookie to indicate that the
671         * user has accepted the cookie policy.
672         * The value of the cookie is the expiry date of the cookie, which means
673         * that everyone who has accepted the cookie policy will use a different
674         * cache file.
675         * Set it to 1 here so those visitors will use the same cache file.
676         */
677        if ( isset( $parameters['cookies']['eucookielaw'] ) ) {
678            $parameters['cookies']['eucookielaw'] = 1;
679        }
680
681        /**
682         * This is for the personalized ads consent cookie.
683         */
684        if ( isset( $parameters['cookies']['personalized-ads-consent'] ) ) {
685            $parameters['cookies']['personalized-ads-consent'] = 1;
686        }
687
688        $cookie_keys = array();
689        if ( isset( $parameters['cookies'] ) && is_array( $parameters['cookies'] ) ) {
690            $cookie_keys = array_keys( $parameters['cookies'] );
691        } else {
692            return $parameters;
693        }
694
695        foreach ( $cookies as $cookie ) {
696            foreach ( $cookie_keys as $cookie_name ) {
697                if ( preg_match( '/^' . $cookie . '$/', $cookie_name ) ) {
698                    unset( $parameters['cookies'][ $cookie_name ] );
699                    $this->ignored_cookies .= $cookie_name . ',';
700                }
701            }
702        }
703        if ( $this->ignored_cookies !== '' ) {
704            $this->ignored_cookies = rtrim( $this->ignored_cookies, ',' );
705        }
706
707        $params = $parameters;
708
709        return $parameters;
710    }
711
712    public function disable_caching_on_error( $message ) {
713        if ( ! defined( 'DONOTCACHEPAGE' ) ) {
714            define( 'DONOTCACHEPAGE', true );
715        }
716        Logger::debug( 'Fatal error detected, caching disabled' );
717        return $message;
718    }
719
720    /**
721     * This function is called after WordPress is loaded, on "init".
722     * It is used to indicate that it is safe to cache and that no
723     * fatal errors occurred.
724     */
725    public function init_do_cache() {
726        $this->do_cache = true;
727    }
728}