From 1ffcecd6f370e4c2753679ba8f8d69a2be94f7d7 Mon Sep 17 00:00:00 2001 From: Piyush <296399266+skikken@users.noreply.github.com> Date: Mon, 12 Oct 2026 11:00:00 +0530 Subject: [PATCH 1/2] Multisite: Clear a deleted site's option caches. wp_uninitialize_site() drops a site's tables but left that site's option caches populated. alloptions, notoptions and is_blog_installed therefore kept answering for a site that no longer had any tables, so a deleted site still behaved as installed and switch_to_blog() gave no indication that anything was wrong. Clear those three keys once the site's tables have been dropped, while the function is still switched into the deleted site's context. The whole options group is deliberately not flushed, as on multisite it holds every site's prefixed keys. clean_blog_cache() is left untouched. Per the ticket discussion it is a network level cache cleaner, and site level clearing belongs to the deletion routine. Props rmccue, johnjamesjacoby, jeremyfelt. Fixes #29247. --- src/wp-includes/ms-site.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/wp-includes/ms-site.php b/src/wp-includes/ms-site.php index 342058156be01..4a35f82089e72 100644 --- a/src/wp-includes/ms-site.php +++ b/src/wp-includes/ms-site.php @@ -841,6 +841,17 @@ function wp_uninitialize_site( $site_id ) { $wpdb->query( "DROP TABLE IF EXISTS `$table`" ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared } + /* + * The site's options tables have just been dropped, so clear the caches that would + * otherwise keep serving the deleted site's data. `clean_blog_cache()` is deliberately + * network level only, and `switch_to_blog()` depends on these caches being cleared to + * surface the failure instead of silently continuing. + * + * @since 7.2.0 + */ + wp_cache_delete_multiple( array( 'alloptions', 'notoptions' ), 'options' ); + wp_cache_delete( 'is_blog_installed' ); + /** * Filters the upload base directory to delete when the site is deleted. * From 54f21e6c1160a9f4510108e7b80979282d06ee0e Mon Sep 17 00:00:00 2001 From: Piyush <296399266+skikken@users.noreply.github.com> Date: Mon, 12 Oct 2026 11:30:00 +0530 Subject: [PATCH 2/2] Multisite: Test the option caches after site deletion. Cover that wp_delete_site() clears alloptions, notoptions and is_blog_installed for the deleted site, and pin that clean_blog_cache() intentionally leaves the site's option caches alone. Props rmccue, johnjamesjacoby, jeremyfelt. Fixes #29247. --- tests/phpunit/tests/multisite/site.php | 54 ++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/tests/phpunit/tests/multisite/site.php b/tests/phpunit/tests/multisite/site.php index 0fa6e4581669a..1a537e7cc6f2d 100644 --- a/tests/phpunit/tests/multisite/site.php +++ b/tests/phpunit/tests/multisite/site.php @@ -1619,6 +1619,60 @@ public function test_wp_delete_site_cleans_cache() { $this->assertNull( get_site( $site_id ) ); } + /** + * @ticket 29247 + */ + public function test_wp_delete_site_clears_site_option_caches() { + $site_id = self::factory()->blog->create(); + + switch_to_blog( $site_id ); + update_option( 'test_option', 'value' ); + wp_load_alloptions( true ); + is_blog_installed(); + + // The site's caches are now populated. + $this->assertIsArray( wp_cache_get( 'alloptions', 'options' ) ); + $this->assertNotEmpty( wp_cache_get( 'is_blog_installed' ) ); + restore_current_blog(); + + wp_delete_site( $site_id ); + + /* + * The site has no tables anymore, so suppress the roles and user switch the same way + * `wp_is_site_initialized()` does. + */ + remove_action( 'switch_blog', 'wp_switch_roles_and_user', 1 ); + switch_to_blog( $site_id ); + + $this->assertFalse( wp_cache_get( 'alloptions', 'options' ) ); + $this->assertFalse( wp_cache_get( 'notoptions', 'options' ) ); + $this->assertFalse( wp_cache_get( 'is_blog_installed' ) ); + + restore_current_blog(); + add_action( 'switch_blog', 'wp_switch_roles_and_user', 1, 2 ); + } + + /** + * @ticket 29247 + */ + public function test_clean_blog_cache_does_not_clear_option_caches() { + $site_id = self::factory()->blog->create(); + + switch_to_blog( $site_id ); + update_option( 'test_option', 'value' ); + wp_load_alloptions( true ); + + $this->assertIsArray( wp_cache_get( 'alloptions', 'options' ) ); + restore_current_blog(); + + clean_blog_cache( $site_id ); + + // `clean_blog_cache()` is network level only, per the discussion on the ticket. + switch_to_blog( $site_id ); + $this->assertIsArray( wp_cache_get( 'alloptions', 'options' ) ); + restore_current_blog(); + } + /** * @ticket 40364 */