Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Boost: add cache rebuild functionality #37151

Merged
merged 33 commits into from
May 9, 2024
Merged
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
1910174
Rebuild system
donnchawp Apr 30, 2024
39730b0
changelog
donnchawp Apr 30, 2024
a45e676
Rename delete_expired_files to gc_expired_files
donnchawp May 1, 2024
dec83b8
Rename delete_cache_for_author to invalidate_cache_for_author
donnchawp May 1, 2024
0956b4a
Rename functions and make rebuild the default action
donnchawp May 1, 2024
d097c4e
Call walk_directory as the name changed
donnchawp May 1, 2024
0a3a525
Fix up function name and comments.
donnchawp May 1, 2024
af91b1a
Fix clearing cache from UI
donnchawp May 1, 2024
3eaf086
Merge branch 'trunk' into add/boost/cache-rebuild
donnchawp May 1, 2024
8b3f2f8
Fix more comments
donnchawp May 1, 2024
facd375
Delete the cache if a public post was made private
donnchawp May 1, 2024
28bb772
If a comment is deleted, then delete the cache file
donnchawp May 1, 2024
8c5c043
Fix the name of this function to avoid fatal errors
donnchawp May 1, 2024
a170733
Updating versions
donnchawp May 1, 2024
bc42352
Fix for linting error
donnchawp May 1, 2024
536e487
Merge remote-tracking branch 'origin/trunk' into add/boost/cache-rebuild
donnchawp May 1, 2024
edce4be
Update phan files
donnchawp May 1, 2024
3cc11de
changelog
donnchawp May 1, 2024
4976384
Rebuild posts with deleted comments, no need to delete.
donnchawp May 2, 2024
d4d299b
Rename cache deletion functions in Boost_Cache_Actions.php
donnchawp May 2, 2024
41fc391
Add cache stale duration constant in Boost_Cache.php and File_Storage…
donnchawp May 2, 2024
8ffde87
Fix typo in cache deletion condition in Boost_Cache.php
donnchawp May 2, 2024
50796f5
Use stale cache TTL for rebuild files and delete expired ones in garb…
donnchawp May 2, 2024
84f5dbe
Delete the cache file if a user comments, clicks remember me as the p…
donnchawp May 2, 2024
f3f8436
When deleting a post, delete the post cache, rebuild the archive caches.
donnchawp May 2, 2024
051cd32
Merge branch 'trunk' into add/boost/cache-rebuild
donnchawp May 5, 2024
ff0f486
Rename STALE -> REBUILD.
donnchawp May 7, 2024
6ef2a83
Update projects/plugins/boost/app/modules/optimizations/page-cache/pr…
donnchawp May 7, 2024
d89ffbe
Merge branch 'add/boost/cache-rebuild' of github.com:Automattic/jetpa…
donnchawp May 7, 2024
2dda6fb
Check if file is a rebuild one before making it one.
donnchawp May 7, 2024
fd41407
Update the file modification time to the current time.
donnchawp May 7, 2024
707a225
Merge branch 'trunk' into add/boost/cache-rebuild
donnchawp May 8, 2024
a0bfdf8
Update versions for changelogger
donnchawp May 8, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion projects/plugins/boost/.phan/baseline.php
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,6 @@
'app/modules/optimizations/page-cache/Page_Cache_Setup.php' => ['PhanTypeMismatchArgument', 'PhanTypeMismatchReturnProbablyReal', 'PhanTypeMissingReturn'],
'app/modules/optimizations/page-cache/data-sync/Page_Cache_Entry.php' => ['PhanTypeMismatchReturnProbablyReal'],
'app/modules/optimizations/page-cache/pre-wordpress/Boost_Cache.php' => ['PhanTypeMismatchReturnProbablyReal'],
'app/modules/optimizations/page-cache/pre-wordpress/Filesystem_Utils.php' => ['PhanTypeSuspiciousStringExpression'],
'app/modules/optimizations/page-cache/pre-wordpress/Logger.php' => ['PhanCoalescingNeverNull', 'PhanPluginDuplicateConditionalNullCoalescing'],
'app/modules/optimizations/page-cache/pre-wordpress/Request.php' => ['PhanPluginDuplicateConditionalNullCoalescing', 'PhanTypeMismatchPropertyDefault'],
'app/modules/optimizations/page-cache/pre-wordpress/storage/File_Storage.php' => ['PhanTypeMismatchArgument'],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ public static function deactivate() {
public static function uninstall() {
self::deactivate();

$result = Filesystem_Utils::delete_directory( WP_CONTENT_DIR . '/boost-cache', Filesystem_Utils::DELETE_ALL );
$result = Filesystem_Utils::walk_directory( WP_CONTENT_DIR . '/boost-cache', Filesystem_Utils::DELETE_ALL );
if ( $result instanceof Boost_Cache_Error ) {
return $result->to_wp_error();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Automattic\Jetpack\WP_JS_Data_Sync\Contracts\Data_Sync_Action;
use Automattic\Jetpack_Boost\Modules\Optimizations\Page_Cache\Pre_WordPress\Boost_Cache;
use Automattic\Jetpack_Boost\Modules\Optimizations\Page_Cache\Pre_WordPress\Filesystem_Utils;

/**
* Page Cache: Clear page cache
Expand All @@ -18,7 +19,7 @@ class Clear_Page_Cache implements Data_Sync_Action {
*/
public function handle( $_data, $_request ) {
$cache = new Boost_Cache();
$delete = $cache->delete_cache();
$delete = $cache->invalidate_cache( Filesystem_Utils::DELETE_ALL );

if ( $delete === null || is_wp_error( $delete ) ) {
return array(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@
define( 'JETPACK_BOOST_CACHE_DURATION', HOUR_IN_SECONDS );
}

// Define how many seconds the rebuild cache should be considered stale, but usable, for each cached page.
if ( ! defined( 'JETPACK_BOOST_CACHE_REBUILD_DURATION' ) ) {
define( 'JETPACK_BOOST_CACHE_REBUILD_DURATION', 10 );
}

class Boost_Cache {
/**
* @var Boost_Cache_Settings - The settings for the page cache.
Expand Down Expand Up @@ -63,11 +68,11 @@ public function __construct( $storage = null ) {
* Initialize the actions for the cache.
*/
public function init_actions() {
add_action( 'transition_post_status', array( $this, 'delete_on_post_transition' ), 10, 3 );
add_action( 'transition_comment_status', array( $this, 'delete_on_comment_transition' ), 10, 3 );
add_action( 'comment_post', array( $this, 'delete_on_comment_post' ), 10, 3 );
add_action( 'edit_comment', array( $this, 'delete_on_comment_edit' ), 10, 2 );
add_action( 'switch_theme', array( $this, 'delete_cache' ) );
add_action( 'transition_post_status', array( $this, 'invalidate_on_post_transition' ), 10, 3 );
add_action( 'transition_comment_status', array( $this, 'invalidate_on_comment_transition' ), 10, 3 );
add_action( 'comment_post', array( $this, 'rebuild_on_comment_post' ), 10, 3 );
add_action( 'edit_comment', array( $this, 'rebuild_on_comment_edit' ), 10, 2 );
add_action( 'switch_theme', array( $this, 'invalidate_cache' ) );
add_action( 'wp_trash_post', array( $this, 'delete_on_post_trash' ), 10, 2 );
add_filter( 'wp_php_error_message', array( $this, 'disable_caching_on_error' ) );
}
Expand Down Expand Up @@ -119,15 +124,24 @@ public function serve_cached() {
return false;
}

$cached = $this->storage->read( $this->request->get_uri(), $this->request->get_parameters() );
// check if rebuild file exists and rename it to the correct file
$rebuild_found = $this->storage->reset_rebuild_file( $this->request->get_uri(), $this->request->get_parameters() );
if ( $rebuild_found ) {
Logger::debug( 'Rebuild file found. Will be used for cache until new file created.' );
$cached = false;
} else {
$cached = $this->storage->read( $this->request->get_uri(), $this->request->get_parameters() );
}

if ( is_string( $cached ) ) {
$this->send_header( 'X-Jetpack-Boost-Cache: hit' );
Logger::debug( 'Serving cached page' );
echo $cached; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
die();
}

$this->send_header( 'X-Jetpack-Boost-Cache: miss' );
$cache_status = $rebuild_found ? 'rebuild' : 'miss';
$this->send_header( 'X-Jetpack-Boost-Cache: ' . $cache_status );

return false;
}
Expand Down Expand Up @@ -180,87 +194,91 @@ public function ob_callback( $buffer ) {
}

/**
* Delete the cache for the front page and paged archives.
* Delete/rebuild the cache for the front page and paged archives.
* This is called when a post is edited, deleted, or published.
*
* @param string $action - The action to take when deleting the cache.
*/
public function delete_cache_for_front_page() {
public function invalidate_cache_for_front_page( $action = Filesystem_Utils::REBUILD_FILES ) {
if ( get_option( 'show_on_front' ) === 'page' ) {
$front_page_id = get_option( 'page_on_front' ); // static page
if ( $front_page_id ) {
Logger::debug( 'delete_cache_for_front_page: deleting front page cache' );
$this->delete_cache_for_post( get_post( $front_page_id ) );
Logger::debug( 'invalidate_cache_for_front_page: deleting front page cache' );
$this->invalidate_cache_for_post( get_post( $front_page_id ), $action );
}
$posts_page_id = get_option( 'page_for_posts' ); // posts page
if ( $posts_page_id ) {
Logger::debug( 'delete_cache_for_front_page: deleting posts page cache' );
$this->delete_cache_for_post( get_post( $posts_page_id ) );
Logger::debug( 'invalidate_cache_for_front_page: deleting posts page cache' );
$this->invalidate_cache_for_post( get_post( $posts_page_id ), $action );
}
} else {
$this->storage->invalidate( home_url(), Filesystem_Utils::DELETE_FILES );
$this->storage->invalidate( home_url(), $action );
Logger::debug( 'delete front page cache ' . Boost_Cache_Utils::normalize_request_uri( home_url() ) );
}
}

/**
* Delete the cache for the given post.
* Delete/rebuild the cache for the given post.
*
* @param int $post_id - The ID of the post to delete the cache for.
* @param int $post_id - The ID of the post to delete the cache for.
* @param string $action - The action to take when deleting the cache.
*/
public function delete_cache_by_post_id( $post_id ) {
public function invalidate_cache_by_post_id( $post_id, $action = Filesystem_Utils::REBUILD_ALL ) {
$post = get_post( (int) $post_id );
if ( $post ) {
$this->delete_cache_for_post( $post );
$this->invalidate_cache_for_post( $post, $action );
}
}

/**
* Delete the cache for the post if the comment transitioned from one state to another.
* Rebuild the cache for the post if the comment transitioned from one state to another.
*
* @param string $new_status - The new status of the comment.
* @param string $old_status - The old status of the comment.
* @param WP_Comment $comment - The comment that transitioned.
*/
public function delete_on_comment_transition( $new_status, $old_status, $comment ) {
public function invalidate_on_comment_transition( $new_status, $old_status, $comment ) {
if ( $new_status === $old_status ) {
return;
}
Logger::debug( "delete_on_comment_transition: $new_status, $old_status" );
Logger::debug( "invalidate_on_comment_transition: $new_status, $old_status" );

if ( $new_status !== 'approved' && $old_status !== 'approved' ) {
Logger::debug( 'delete_on_comment_transition: comment not approved' );
Logger::debug( 'invalidate_on_comment_transition: comment not approved' );
return;
}

$post = get_post( $comment->comment_post_ID );
$this->delete_cache_for_post( $post );

$this->invalidate_cache_for_post( $post );
}

/**
* After editing a comment, delete the cache for the post if the comment is approved.
* If changing state and editing, both actions will be called, but the cache will only be deleted once.
* After editing a comment, rebuild the cache for the post if the comment is approved.
* If changing state and editing, both actions will be called, but the cache will only be rebuilt once.
*
* @param int $comment_id - The id of the comment.
* @param array $commentdata - The comment data.
*/
public function delete_on_comment_edit( $comment_id, $commentdata ) {
public function rebuild_on_comment_edit( $comment_id, $commentdata ) {
$post = get_post( $commentdata['comment_post_ID'] );

if ( (int) $commentdata['comment_approved'] === 1 ) {
$this->delete_cache_for_post( $post );
$this->invalidate_cache_for_post( $post );
}
}

/**
* After a comment is posted, delete the cache for the post if the comment is approved.
* If the comment is not approved, only delete the cache for this post for this visitor.
* After a comment is posted, rebuild the cache for the post if the comment is approved.
* If the comment is not approved, only rebuild the cache for this post for this visitor.
*
* @param int $comment_id - The id of the comment.
* @param int $comment_approved - The approval status of the comment.
* @param array $commentdata - The comment data.
*/
public function delete_on_comment_post( $comment_id, $comment_approved, $commentdata ) {
public function rebuild_on_comment_post( $comment_id, $comment_approved, $commentdata ) {
$post = get_post( $commentdata['comment_post_ID'] );
Logger::debug( "delete_on_comment_post: $comment_id, $comment_approved, {$post->ID}" );
Logger::debug( "rebuild_on_comment_post: $comment_id, $comment_approved, {$post->ID}" );
/**
* If a comment is not approved, we only need to delete the cache for
* this post for this visitor so the unmoderated comment is shown to them.
Expand All @@ -281,7 +299,7 @@ public function delete_on_comment_post( $comment_id, $comment_approved, $comment
return;
}

$this->delete_cache_for_post( $post );
$this->invalidate_cache_for_post( $post );
}

/**
Expand All @@ -301,12 +319,12 @@ private function is_published( $status ) {
* @param string $old_status - The old status of the post.
* @param WP_Post $post - The post that transitioned.
*/
public function delete_on_post_transition( $new_status, $old_status, $post ) {
public function invalidate_on_post_transition( $new_status, $old_status, $post ) {
// Special case: Delete cache if the post type can effect the whole site.
$special_post_types = array( 'wp_template', 'wp_template_part', 'wp_global_styles' );
if ( in_array( $post->post_type, $special_post_types, true ) ) {
Logger::debug( 'delete_on_post_transition: special post type ' . $post->post_type );
$this->delete_cache();
Logger::debug( 'invalidate_on_post_transition: special post type ' . $post->post_type );
$this->invalidate_cache();
return;
}

Expand All @@ -318,20 +336,26 @@ public function delete_on_post_transition( $new_status, $old_status, $post ) {
return;
}

Logger::debug( "delete_on_post_transition: $new_status, $old_status, {$post->ID}" );
Logger::debug( "invalidate_on_post_transition: $new_status, $old_status, {$post->ID}" );

// Don't delete the cache for posts that weren't published and aren't published now
if ( ! $this->is_published( $new_status ) && ! $this->is_published( $old_status ) ) {
Logger::debug( 'delete_on_post_transition: not published' );
Logger::debug( 'invalidate_on_post_transition: not published' );
return;
}

Logger::debug( "delete_on_post_transition: deleting post {$post->ID}" );
// delete the cache files entirely if the post was unpublished
if ( 'publish' === $old_status && 'publish' !== $new_status ) {
Logger::debug( 'invalidate_on_post_transition: delete cache on new private page' );
$this->delete_on_post_trash( $post->ID, $old_status );
return;
}
Logger::debug( "invalidate_on_post_transition: rebuilding post {$post->ID}" );

$this->delete_cache_for_post( $post );
$this->delete_cache_for_post_terms( $post );
$this->delete_cache_for_front_page();
$this->delete_cache_for_author( $post->post_author );
$this->invalidate_cache_for_post( $post );
$this->invalidate_cache_for_post_terms( $post );
$this->invalidate_cache_for_front_page();
$this->invalidate_cache_for_author( $post->post_author );
}

/**
Expand All @@ -343,26 +367,26 @@ public function delete_on_post_transition( $new_status, $old_status, $post ) {
public function delete_on_post_trash( $post_id, $old_status ) {
if ( $this->is_published( $old_status ) ) {
$post = get_post( $post_id );
$this->delete_cache_for_post( $post );
$this->delete_cache_for_post_terms( $post );
$this->delete_cache_for_front_page();
$this->delete_cache_for_author( $post->post_author );
$this->invalidate_cache_for_post( $post, Filesystem_Utils::DELETE_ALL );
$this->invalidate_cache_for_post_terms( $post );
$this->invalidate_cache_for_front_page();
$this->invalidate_cache_for_author( $post->post_author );
}
}

/**
* Deletes cache files for the given post.
* Deletes/rebuilds cache files for the given post.
*
* @param WP_Post $post - The post to delete the cache file for.
*/
public function delete_cache_for_post( $post ) {
public function invalidate_cache_for_post( $post, $action = Filesystem_Utils::REBUILD_ALL ) {
static $already_deleted = -1;
if ( $already_deleted === $post->ID ) {
return;
}

/**
* Don't delete the cache for post types that are not public.
* Don't invalidate the cache for post types that are not public.
*/
if ( ! Boost_Cache_Utils::is_visible_post_type( $post ) ) {
return;
Expand All @@ -376,29 +400,29 @@ public function delete_cache_for_post( $post ) {
* the post name. We need to get the post name from the post object.
*/
$permalink = get_permalink( $post->ID );
Logger::debug( "delete_cache_for_post: $permalink" );
$this->delete_cache_for_url( $permalink );
Logger::debug( "invalidate_cache_for_post: $permalink" );
$this->invalidate_cache_for_url( $permalink, $action );
}

/**
* Delete the cache for terms associated with this post.
*
* @param WP_Post $post - The post to delete the cache for.
*/
public function delete_cache_for_post_terms( $post ) {
public function invalidate_cache_for_post_terms( $post, $action = Filesystem_Utils::REBUILD_ALL ) {
$categories = get_the_category( $post->ID );
if ( is_array( $categories ) ) {
foreach ( $categories as $category ) {
$link = trailingslashit( get_category_link( $category->term_id ) );
$this->delete_cache_for_url( $link );
$this->invalidate_cache_for_url( $link, $action );
}
}

$tags = get_the_tags( $post->ID );
if ( is_array( $tags ) ) {
foreach ( $tags as $tag ) {
$link = trailingslashit( get_tag_link( $tag->term_id ) );
$this->delete_cache_for_url( $link );
$this->invalidate_cache_for_url( $link, $action );
}
}
}
Expand All @@ -409,32 +433,32 @@ public function delete_cache_for_post_terms( $post ) {
* @param int $author_id - The id of the author.
* @return bool|WP_Error - True if the cache was deleted, WP_Error otherwise.
*/
public function delete_cache_for_author( $author_id ) {
public function invalidate_cache_for_author( $author_id, $action = Filesystem_Utils::REBUILD_ALL ) {
$author = get_userdata( $author_id );
if ( ! $author ) {
return;
}

$author_link = get_author_posts_url( $author_id, $author->user_nicename );
return $this->delete_cache_for_url( $author_link );
return $this->invalidate_cache_for_url( $author_link, $action );
}

/**
* Delete the cache for the given url.
*
* @param string $url - The url to delete the cache for.
*/
public function delete_cache_for_url( $url ) {
Logger::debug( 'delete_cache_for_url: ' . $url );
public function invalidate_cache_for_url( $url, $action = Filesystem_Utils::REBUILD_ALL ) {
Logger::debug( 'invalidate_cache_for_url: ' . $url );

return $this->storage->invalidate( $url, Filesystem_Utils::DELETE_ALL );
return $this->storage->invalidate( $url, $action );
}

/**
* Delete the entire cache.
* Invalidate the entire cache.
*/
public function delete_cache() {
return $this->delete_cache_for_url( home_url() );
public function invalidate_cache( $action = Filesystem_Utils::REBUILD_ALL ) {
return $this->invalidate_cache_for_url( home_url(), $action );
}

public function disable_caching_on_error( $message ) {
Expand Down
Loading
Loading