From 1f48ff9a93bb418dd2de4379ba00a7c851803256 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 27 May 2026 09:41:06 +0000 Subject: [PATCH 1/6] Initial plan From ea96013d348d5d10ed4b4edaaa1435e87ed78e62 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 27 May 2026 09:46:15 +0000 Subject: [PATCH 2/6] Add opt-in site delete table-prefix cleanup flag Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com> --- features/site.feature | 35 +++++++++++++++++++++++++++++++++++ src/Site_Command.php | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+) diff --git a/features/site.feature b/features/site.feature index 8e626db35..bca30e964 100644 --- a/features/site.feature +++ b/features/site.feature @@ -78,6 +78,41 @@ Feature: Manage sites in a multisite installation When I try the previous command again Then the return code should be 1 + @skip-windows + Scenario: Delete a site by id and remove all prefixed tables + Given a WP multisite subdirectory install + + When I run `wp site create --slug=first --porcelain` + Then STDOUT should be a number + And save STDOUT as {SITE_ID} + And I run `wp db query "CREATE TABLE wp_{SITE_ID}_custom_data (id INTEGER PRIMARY KEY);"` + And I run `wp site delete {SITE_ID} --yes --delete-tables-with-prefix` + Then STDOUT should contain: + """ + Success: The site at ' + """ + + When I run `wp db query "CREATE TABLE wp_{SITE_ID}_custom_data (id INTEGER PRIMARY KEY);"` + Then STDOUT should be empty + And the return code should be 0 + And I run `wp db query "DROP TABLE wp_{SITE_ID}_custom_data;"` + + @skip-windows + Scenario: Deleting a site cannot combine keep-tables with delete-tables-with-prefix + Given a WP multisite subdirectory install + + When I run `wp site create --slug=first --porcelain` + Then STDOUT should be a number + And save STDOUT as {SITE_ID} + + When I try `wp site delete {SITE_ID} --yes --keep-tables --delete-tables-with-prefix` + Then STDERR should be: + """ + Error: The '--keep-tables' and '--delete-tables-with-prefix' flags cannot be used together. + """ + And STDOUT should be empty + And the return code should be 1 + @skip-windows Scenario: Filter site list Given a WP multisite install diff --git a/src/Site_Command.php b/src/Site_Command.php index 5a231d7a9..724c1e256 100644 --- a/src/Site_Command.php +++ b/src/Site_Command.php @@ -288,6 +288,9 @@ public function empty_( $args, $assoc_args ) { * [--keep-tables] * : Delete the blog from the list, but don't drop its tables. * + * [--delete-tables-with-prefix] + * : Delete all tables with the site's database table prefix after deleting the site. + * * ## EXAMPLES * * $ wp site delete 123 @@ -299,6 +302,10 @@ public function delete( $args, $assoc_args ) { WP_CLI::error( 'This is not a multisite installation.' ); } + if ( Utils\get_flag_value( $assoc_args, 'keep-tables' ) && Utils\get_flag_value( $assoc_args, 'delete-tables-with-prefix' ) ) { + WP_CLI::error( "The '--keep-tables' and '--delete-tables-with-prefix' flags cannot be used together." ); + } + if ( isset( $assoc_args['slug'] ) ) { $blog_id = get_id_from_blogname( $assoc_args['slug'] ); if ( null === $blog_id ) { @@ -329,9 +336,39 @@ public function delete( $args, $assoc_args ) { wpmu_delete_blog( (int) $blog->blog_id, ! Utils\get_flag_value( $assoc_args, 'keep-tables' ) ); + if ( Utils\get_flag_value( $assoc_args, 'delete-tables-with-prefix' ) ) { + $this->drop_tables_with_prefix( (int) $blog->blog_id ); + } + WP_CLI::success( "The site at '{$site_url}' was deleted." ); } + /** + * Drops all database tables for a site prefix. + * + * @param int $blog_id Site ID. + */ + private function drop_tables_with_prefix( $blog_id ) { + global $wpdb; + + $prefix_like = $wpdb->esc_like( $wpdb->get_blog_prefix( $blog_id ) ) . '%'; + $tables = $wpdb->get_col( $wpdb->prepare( 'SHOW TABLES LIKE %s', $prefix_like ) ); + + if ( empty( $tables ) ) { + return; + } + + $tables = array_map( + static function ( $table ) { + return '`' . str_replace( '`', '``', $table ) . '`'; + }, + $tables + ); + + // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Table identifiers are escaped and cannot be passed as placeholders. + $wpdb->query( 'DROP TABLE IF EXISTS ' . implode( ', ', $tables ) ); + } + /** * Gets details about a site in a multisite installation. * From 98ddc06c8af9f2166e42ae708ed35944904a8f47 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Wed, 27 May 2026 13:31:10 +0200 Subject: [PATCH 3/6] Update features/site.feature --- features/site.feature | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/site.feature b/features/site.feature index bca30e964..164d4eeab 100644 --- a/features/site.feature +++ b/features/site.feature @@ -87,7 +87,7 @@ Feature: Manage sites in a multisite installation And save STDOUT as {SITE_ID} And I run `wp db query "CREATE TABLE wp_{SITE_ID}_custom_data (id INTEGER PRIMARY KEY);"` And I run `wp site delete {SITE_ID} --yes --delete-tables-with-prefix` - Then STDOUT should contain: + And STDOUT should contain: """ Success: The site at ' """ From 29410a80c50b4a2b453fb0b4f10ce4b329bee468 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 27 May 2026 11:51:48 +0000 Subject: [PATCH 4/6] Fix multisite delete safety checks Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com> --- src/Site_Command.php | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/Site_Command.php b/src/Site_Command.php index 724c1e256..472000c9e 100644 --- a/src/Site_Command.php +++ b/src/Site_Command.php @@ -311,6 +311,9 @@ public function delete( $args, $assoc_args ) { if ( null === $blog_id ) { WP_CLI::error( sprintf( 'Could not find site with slug \'%s\'.', $assoc_args['slug'] ) ); } + if ( is_main_site( $blog_id ) ) { + WP_CLI::error( 'You cannot delete the root site.' ); + } $blog = get_blog_details( $blog_id ); } else { if ( empty( $args ) ) { @@ -334,7 +337,10 @@ public function delete( $args, $assoc_args ) { WP_CLI::confirm( "Are you sure you want to delete the '{$site_url}' site?", $assoc_args ); - wpmu_delete_blog( (int) $blog->blog_id, ! Utils\get_flag_value( $assoc_args, 'keep-tables' ) ); + $did_delete = wpmu_delete_blog( (int) $blog->blog_id, ! Utils\get_flag_value( $assoc_args, 'keep-tables' ) ); + if ( false === $did_delete ) { + WP_CLI::error( "The site at '{$site_url}' could not be deleted." ); + } if ( Utils\get_flag_value( $assoc_args, 'delete-tables-with-prefix' ) ) { $this->drop_tables_with_prefix( (int) $blog->blog_id ); @@ -351,7 +357,12 @@ public function delete( $args, $assoc_args ) { private function drop_tables_with_prefix( $blog_id ) { global $wpdb; - $prefix_like = $wpdb->esc_like( $wpdb->get_blog_prefix( $blog_id ) ) . '%'; + $blog_prefix = $wpdb->get_blog_prefix( $blog_id ); + if ( is_main_site( $blog_id ) || $blog_prefix === $wpdb->base_prefix ) { + WP_CLI::error( 'You cannot drop tables for the root site.' ); + } + + $prefix_like = $wpdb->esc_like( $blog_prefix ) . '%'; $tables = $wpdb->get_col( $wpdb->prepare( 'SHOW TABLES LIKE %s', $prefix_like ) ); if ( empty( $tables ) ) { From 9edf00cf04d7b96dddd28e39e0a0cf1b48b06db5 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Thu, 28 May 2026 13:15:27 +0200 Subject: [PATCH 5/6] Apply suggestion from @swissspidy --- features/site.feature | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/features/site.feature b/features/site.feature index 164d4eeab..453a358b0 100644 --- a/features/site.feature +++ b/features/site.feature @@ -85,17 +85,18 @@ Feature: Manage sites in a multisite installation When I run `wp site create --slug=first --porcelain` Then STDOUT should be a number And save STDOUT as {SITE_ID} - And I run `wp db query "CREATE TABLE wp_{SITE_ID}_custom_data (id INTEGER PRIMARY KEY);"` + And I try `wp db query "CREATE TABLE wp_{SITE_ID}_custom_data (id INTEGER PRIMARY KEY);"` And I run `wp site delete {SITE_ID} --yes --delete-tables-with-prefix` And STDOUT should contain: """ Success: The site at ' """ - When I run `wp db query "CREATE TABLE wp_{SITE_ID}_custom_data (id INTEGER PRIMARY KEY);"` + When I try `wp db query "CREATE TABLE wp_{SITE_ID}_custom_data (id INTEGER PRIMARY KEY);"` Then STDOUT should be empty And the return code should be 0 - And I run `wp db query "DROP TABLE wp_{SITE_ID}_custom_data;"` + And I try `wp db query "DROP TABLE wp_{SITE_ID}_custom_data;"` + Then the return code should be 0 @skip-windows Scenario: Deleting a site cannot combine keep-tables with delete-tables-with-prefix From 771436bccfffec518a5c6b4e788b6f1e8258b282 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Thu, 28 May 2026 13:17:35 +0200 Subject: [PATCH 6/6] Apply suggestion from @swissspidy --- features/site.feature | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/site.feature b/features/site.feature index 453a358b0..aea85e9a5 100644 --- a/features/site.feature +++ b/features/site.feature @@ -96,7 +96,7 @@ Feature: Manage sites in a multisite installation Then STDOUT should be empty And the return code should be 0 And I try `wp db query "DROP TABLE wp_{SITE_ID}_custom_data;"` - Then the return code should be 0 + And the return code should be 0 @skip-windows Scenario: Deleting a site cannot combine keep-tables with delete-tables-with-prefix