Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
11.03% |
30 / 272 |
|
12.50% |
4 / 32 |
CRAP | |
0.00% |
0 / 1 |
| Boost_Cache | |
5.49% |
14 / 255 |
|
12.50% |
4 / 32 |
8712.39 | |
0.00% |
0 / 1 |
| __construct | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
| init_actions | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
2 | |||
| load_extra | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
6 | |||
| serve | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
20 | |||
| is_loaded | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| get_storage | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| serve_cached | |
0.00% |
0 / 17 |
|
0.00% |
0 / 1 |
56 | |||
| send_header | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
6 | |||
| ob_start | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
| ob_callback | |
0.00% |
0 / 14 |
|
0.00% |
0 / 1 |
72 | |||
| rebuild_front_page | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
12 | |||
| invalidate_on_comment_transition | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
20 | |||
| rebuild_on_comment_edit | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
| rebuild_on_comment_post | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
20 | |||
| is_published | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
6 | |||
| invalidate_on_post_transition | |
0.00% |
0 / 22 |
|
0.00% |
0 / 1 |
72 | |||
| delete_on_post_trash | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
12 | |||
| get_post_path_for_invalidation | |
0.00% |
0 / 15 |
|
0.00% |
0 / 1 |
56 | |||
| rebuild_post_terms_cache | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
30 | |||
| rebuild_author_page | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
6 | |||
| rebuild_all | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| delete_post_cache | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
12 | |||
| rebuild_post_cache | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
12 | |||
| rebuild_page | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
1 | |||
| delete_page | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
2 | |||
| rebuild_recursive | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
2 | |||
| delete_recursive | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
2 | |||
| invalidate_cache_success | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| ignore_get_parameters | |
0.00% |
0 / 18 |
|
0.00% |
0 / 1 |
42 | |||
| ignore_cookies | |
0.00% |
0 / 38 |
|
0.00% |
0 / 1 |
110 | |||
| disable_caching_on_error | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
6 | |||
| init_do_cache | |
100.00% |
1 / 1 |
|
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 | |
| 6 | namespace Automattic\Jetpack_Boost\Modules\Optimizations\Page_Cache\Pre_WordPress; |
| 7 | |
| 8 | use WP_Comment; |
| 9 | use 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 | */ |
| 15 | require_once __DIR__ . '/path-actions/interface-path-action.php'; |
| 16 | require_once __DIR__ . '/path-actions/class-filter-older.php'; |
| 17 | require_once __DIR__ . '/path-actions/class-rebuild-file.php'; |
| 18 | require_once __DIR__ . '/path-actions/class-simple-delete.php'; |
| 19 | require_once __DIR__ . '/boost-cache-actions.php'; |
| 20 | require_once __DIR__ . '/class-boost-cache-error.php'; |
| 21 | require_once __DIR__ . '/class-boost-cache-settings.php'; |
| 22 | require_once __DIR__ . '/class-boost-cache-utils.php'; |
| 23 | require_once __DIR__ . '/class-filesystem-utils.php'; |
| 24 | require_once __DIR__ . '/class-logger.php'; |
| 25 | require_once __DIR__ . '/class-request.php'; |
| 26 | require_once __DIR__ . '/storage/interface-storage.php'; |
| 27 | require_once __DIR__ . '/storage/class-file-storage.php'; |
| 28 | |
| 29 | // Define how many seconds the cache should last for each cached page. |
| 30 | if ( ! 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. |
| 35 | if ( ! defined( 'JETPACK_BOOST_CACHE_REBUILD_DURATION' ) ) { |
| 36 | define( 'JETPACK_BOOST_CACHE_REBUILD_DURATION', 10 ); |
| 37 | } |
| 38 | |
| 39 | class 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 | } |