Skip to content

Commit

Permalink
Merge pull request #272 from wp-graphql/fix/260-invalidate-menus
Browse files Browse the repository at this point in the history
fix: invalidate caches for menu items
  • Loading branch information
jasonbahl authored Feb 19, 2024
2 parents 7181a68 + 426bd7a commit 10a9581
Show file tree
Hide file tree
Showing 5 changed files with 327 additions and 42 deletions.
93 changes: 82 additions & 11 deletions src/Cache/Invalidation.php
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,9 @@ public function init() {
add_action( 'deleted_term_meta', [ $this, 'on_updated_menu_meta_cb' ], 10, 4 );

// @todo: evict caches when meta on menu items are changed. This happens outside *_post_meta hooks as nav_menu_item is a "different" type of post type
add_action( 'added_term_relationship', [ $this, 'on_menu_item_added_to_menu_cb' ], 10, 3 );
add_action( 'wp_update_nav_menu_item', [ $this, 'on_menu_item_updated_cb' ], 10, 3 );
add_action( 'deleted_post', [ $this, 'on_menu_item_deleted_cb' ], 10, 2 );

add_action( 'updated_post_meta', [ $this, 'on_menu_item_change_cb' ], 10, 4 );
add_action( 'added_post_meta', [ $this, 'on_menu_item_change_cb' ], 10, 4 );
Expand Down Expand Up @@ -732,11 +735,6 @@ public function on_postmeta_change_cb( $meta_id, $post_id, $meta_key, $meta_valu
return;
}

// if the post type is not tracked, ignore it
if ( ! in_array( $post->post_type, \WPGraphQL::get_allowed_post_types(), true ) ) {
return;
}

$post_type_object = get_post_type_object( $post->post_type );

if ( ! $post_type_object instanceof \WP_Post_Type ) {
Expand Down Expand Up @@ -773,7 +771,7 @@ public function on_postmeta_change_cb( $meta_id, $post_id, $meta_key, $meta_valu
* @return bool
* @throws Exception
*/
public function is_menu_public( $menu_id ) {
public function is_menu_public( int $menu_id ): bool {
$nav_menu = get_term( $menu_id, 'nav_menu' );
if ( ! $nav_menu instanceof WP_Term ) {
return false;
Expand Down Expand Up @@ -825,8 +823,8 @@ public function on_set_nav_menu_locations_cb( $value, $old_value ) {
* @return void
* @throws Exception
*/
public function on_update_nav_menu_cb( $menu_id ) {
if ( ! $this->is_menu_public( $menu_id ) ) {
public function on_update_nav_menu_cb( int $menu_id ): void {
if ( ! $this->is_menu_public( absint( $menu_id ) ) ) {
return;
}

Expand All @@ -845,8 +843,8 @@ public function on_update_nav_menu_cb( $menu_id ) {
* @return void
* @throws Exception
*/
public function on_create_nav_menu_cb( $menu_id, array $menu_data ) {
if ( ! $this->is_menu_public( $menu_id ) ) {
public function on_create_nav_menu_cb( int $menu_id, array $menu_data ) {
if ( ! $this->is_menu_public( absint( $menu_id ) ) ) {
return;
}

Expand Down Expand Up @@ -877,7 +875,7 @@ public function on_updated_menu_meta_cb( $meta_id, $object_id, $meta_key, $meta_
}

// if the menu isn't public do nothing
if ( ! $this->is_menu_public( $term->term_id ) ) {
if ( ! $this->is_menu_public( absint( $term->term_id ) ) ) {
return;
}

Expand All @@ -888,6 +886,79 @@ public function on_updated_menu_meta_cb( $meta_id, $object_id, $meta_key, $meta_
$this->purge_nodes( 'term', $term->term_id, 'menu_meta_updated' );
}

/**
* Listen for when a term relationship has changed between nav_menu_item and nav_menu
*
* @param int $object_id The ID of the object the taxonomy is associated with
* @param int $tt_id The Term Taxonomy ID of the term
* @param string $taxonomy The name of the taxonomy the term belongs to
*
* @return void
* @throws Exception
*/
public function on_menu_item_added_to_menu_cb( int $object_id, int $tt_id, string $taxonomy ): void {

if ( 'nav_menu' !== $taxonomy ) {
return;
}

$menu_term = get_term_by( 'term_taxonomy_id', absint( $tt_id ), $taxonomy );

// if the menu isn't public do nothing
if ( ! isset( $menu_term->term_id ) || ! $this->is_menu_public( absint( $menu_term->term_id ) ) ) {
return;
}

$this->purge( 'list:menuitem', 'nav_menu_item_added' );

}

/**
* Listen for when a menu item is updated
*
* @param int $menu_id ID of the updated menu.
* @param int $menu_item_db_id ID of the updated menu item.
* @param array $args An array of arguments used to update a menu item.
*
* @return void
* @throws Exception
*/
public function on_menu_item_updated_cb( int $menu_id, int $menu_item_db_id, array $args ): void {

$menu_term = get_term_by( 'term_id', absint( $menu_id ), 'nav_menu' );

// if the menu isn't public do nothing
if ( ! isset( $menu_term->term_id ) || ! $this->is_menu_public( absint( $menu_term->term_id ) ) ) {
return;
}

$this->purge_nodes( 'post', $menu_item_db_id, 'update_menu_item' );

}

/**
* Listen for menu items being deleted and purge relevant caches
*
* @param int $post_id The ID of the post being deleted
* @param WP_Post $post The Post object that is being deleted
*
* @return void
*/
public function on_menu_item_deleted_cb( int $post_id, WP_Post $post ): void {

if ( 'nav_menu_item' !== $post->post_type ) {
return;
}

if ( 'publish' !== $post->post_status ) {
return;
}

$this->purge_nodes( 'post', $post->ID, 'nav_menu_item_deleted' );

}


/**
* Listens for changes to meta for menu items
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,17 @@ class WPGraphQLSmartCacheTestCaseWithSeedDataAndPopulatedCaches extends WPGraphQ
/**
* @var WP_Term
*/
public $menu;
public $public_menu;

/**
* @var WP_Term
*/
public $private_menu;

/**
* @var WP_Nav_Menu_Item
*/
public $private_menu_item;

/**
* @var WP_Nav_Menu_Item
Expand Down Expand Up @@ -424,41 +434,37 @@ public function _createSeedData() {
'post_author' => $this->admin->ID,
]);

$this->menu = self::factory()->term->create_and_get([
$this->public_menu = self::factory()->term->create_and_get([
'name' => 'test menu',
'taxonomy' => 'nav_menu'
]);

$this->menu_item_1 = self::factory()->post->create_and_get([
'post_type' => 'nav_menu_item',
'post_status' => 'publish'
$this->private_menu = self::factory()->term->create_and_get([
'name' => 'private menu',
'taxonomy' => 'nav_menu'
]);

$this->child_menu_item = self::factory()->post->create_and_get([
$this->menu_item_1 = self::factory()->post->create_and_get([
'post_type' => 'nav_menu_item',
'post_status' => 'publish'
]);

$this->approved_comment = self::factory()->comment->create_and_get([
'comment_approved' => 1,
'comment_post_ID' => $this->published_post->ID,
]);

$this->unapproved_comment = self::factory()->comment->create_and_get([
'comment_approved' => 0,
]);

// set the parent menu item
wp_update_nav_menu_item( $this->menu->term_id, $this->menu_item_1->ID, [
wp_update_nav_menu_item( $this->public_menu->term_id, $this->menu_item_1->ID, [
'menu-item-title' => 'Test Item',
'menu-item-object' => 'post',
'menu-item-object-id' => $this->published_post->ID,
'menu-item-status' => 'publish',
'menu-item-type' => 'post_type',
]);

$this->child_menu_item = self::factory()->post->create_and_get([
'post_type' => 'nav_menu_item',
'post_status' => 'publish'
]);

// set a child menu item
wp_update_nav_menu_item( $this->menu->term_id, $this->child_menu_item->ID, [
wp_update_nav_menu_item( $this->public_menu->term_id, $this->child_menu_item->ID, [
'menu-item-title' => 'Child Item',
'menu-item-object' => 'page',
'menu-item-object-id' => $this->published_page->ID,
Expand All @@ -467,11 +473,33 @@ public function _createSeedData() {
'menu-item-parent-id' => $this->menu_item_1->ID
]);

$this->private_menu_item = self::factory()->post->create_and_get([
'post_type' => 'nav_menu_item',
'post_status' => 'publish'
]);

wp_update_nav_menu_item( $this->private_menu->term_id, $this->private_menu_item->ID, [
'menu-item-title' => 'Private Menu Item',
'menu-item-object' => 'page',
'menu-item-object-id' => $this->published_page->ID,
'menu-item-status' => 'publish',
'menu-item-type' => 'post_type',
]);

// register a menu location
register_nav_menu( 'default-location', 'Test Menu Location' );

// add the menu to a default location
set_theme_mod( 'nav_menu_locations', [ 'default-location' => (int) $this->menu->term_id ] );
set_theme_mod( 'nav_menu_locations', [ 'default-location' => (int) $this->public_menu->term_id ] );

$this->approved_comment = self::factory()->comment->create_and_get([
'comment_approved' => 1,
'comment_post_ID' => $this->published_post->ID,
]);

$this->unapproved_comment = self::factory()->comment->create_and_get([
'comment_approved' => 0,
]);

// $this->assertInstanceOf( \WP_User::class, $this->admin );
// $this->assertInstanceOf( \WP_Post::class, $this->published_post );
Expand Down Expand Up @@ -1098,7 +1126,7 @@ public function getQueries() {
}
',
'variables' => [
'id' => (int) $this->menu->term_id
'id' => (int) $this->public_menu->term_id
]
],
'listMenu' => [
Expand All @@ -1114,6 +1142,20 @@ public function getQueries() {
}
'
],
'singlePrivateMenu' => [
'name' => 'singlePrivateMenu',
'query' => '
query GetMenu($id:ID!) {
menu(id:$id idType: DATABASE_ID) {
__typename
databaseId
}
}
',
'variables' => [
'id' => (int) $this->private_menu->term_id
]
],
'listMenuItem' => [
'name' => 'listMenuItem',
'query' => '
Expand Down Expand Up @@ -1158,6 +1200,21 @@ public function getQueries() {
'id' => $this->child_menu_item->ID
]
],
'singlePrivateMenuItem' => [
'name' => 'singlePrivateMenuItem',
'query' => '
query GetMenuItem($id:ID!) {
menuItem(id:$id idType: DATABASE_ID) {
__typename
databaseId
parentDatabaseId
}
}
',
'variables' => [
'id' => $this->private_menu_item->ID,
]
],
'singleMediaItem' => [
'name' => 'singleMediaItem',
'query' => '
Expand Down Expand Up @@ -1312,17 +1369,17 @@ public function executeAndCacheQueries() {
* @return array
*/
public function getEvictedCaches() {
$empty = [];
$evicted = [];
if ( ! empty( $this->query_results ) ) {
foreach ( $this->query_results as $name => $result ) {
$cache = $this->collection->get( $result['cacheKey'] );
if ( empty( $cache ) ) {
$empty[] = $name;
$evicted[] = $name;
}
}
}

return $empty;
return $evicted;
}

/**
Expand Down
27 changes: 19 additions & 8 deletions tests/wpunit/MenuCacheInvalidationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public function testAssignNavMenuToLocationEvictsQueriesForMenus() {
$this->assertEmpty( $this->getEvictedCaches() );

// assign the menu to a location. This should evict caches for queries for menus
set_theme_mod( 'nav_menu_locations', [ $location_name => (int) $this->menu->term_id ] );
set_theme_mod( 'nav_menu_locations', [ $location_name => (int) $this->public_menu->term_id ] );

$evicted_caches = $this->getEvictedCaches();

Expand All @@ -70,8 +70,8 @@ public function testUpdateMenuThatIsAssignedToALocationShouldEvictCaches() {

$this->assertEmpty( $this->getEvictedCaches() );

wp_update_nav_menu_object( $this->menu->term_id, [
'menu-name' => $this->menu->name,
wp_update_nav_menu_object( $this->public_menu->term_id, [
'menu-name' => $this->public_menu->name,
'description' => 'updated description...',
] );

Expand Down Expand Up @@ -122,7 +122,7 @@ public function testDeleteMenuAssignedToALocationShouldEvictCache() {

$this->assertEmpty( $this->getEvictedCaches() );

wp_delete_nav_menu( $this->menu->term_id );
wp_delete_nav_menu( $this->public_menu->term_id );

$evicted = $this->getEvictedCaches();

Expand All @@ -134,7 +134,18 @@ public function testDeleteMenuAssignedToALocationShouldEvictCache() {
'singleMenu',

// deleting a menu that was in the listMenu results should evict the query
'listMenu'
'listMenu',

// deleting a menu that had this menu item in it should purge queries
// for this menu item as it will also delete this menu item
'singleChildMenuItem',

// deleting a menu that had this menu item in it should purge queries
// for this menu item as it will also delete this menu item
'singleMenuItem',

// Deleting a public menu should invalidate a query for a list of menuItems
'listMenuItem'
], $evicted );


Expand All @@ -159,7 +170,7 @@ public function testUpdateTermMetaOnMenuAssignedToALocationEvictsCache() {
$this->assertEmpty( $this->getEvictedCaches() );

// update term meta on a public menu _should_ evict cache
update_term_meta( $this->menu->term_id, 'meta_key', uniqid( null, true ) );
update_term_meta( $this->public_menu->term_id, 'meta_key', uniqid( null, true ) );

$evicted = $this->getEvictedCaches();

Expand All @@ -179,7 +190,7 @@ public function testUpdateTermMetaOnMenuAssignedToALocationEvictsCache() {
public function testDeleteTermMetaOnMenuAssignedToALocationEvictsCache() {

// setup some term meta to start with
update_term_meta( $this->menu->term_id, 'meta_key', uniqid( null, true ) );
update_term_meta( $this->public_menu->term_id, 'meta_key', uniqid( null, true ) );

// reset caches as the update above would have evicted some
$this->_populateCaches();
Expand All @@ -188,7 +199,7 @@ public function testDeleteTermMetaOnMenuAssignedToALocationEvictsCache() {
$this->assertEmpty( $this->getEvictedCaches() );

// delete term meta on a public menu _should_ evict cache
delete_term_meta( $this->menu->term_id, 'meta_key' );
delete_term_meta( $this->public_menu->term_id, 'meta_key' );

$evicted = $this->getEvictedCaches();

Expand Down
Loading

0 comments on commit 10a9581

Please sign in to comment.