From eb3daf750135044cda0129ef152c663259797505 Mon Sep 17 00:00:00 2001 From: Ben Bowler Date: Wed, 19 Nov 2025 13:17:25 +0000 Subject: [PATCH 1/3] Delete Email Log posts on reset. --- includes/Core/Util/Reset.php | 50 +++++++++++ .../integration/Core/Util/ResetTest.php | 85 +++++++++++++++++++ 2 files changed, 135 insertions(+) diff --git a/includes/Core/Util/Reset.php b/includes/Core/Util/Reset.php index 1b33e99bc77..8625d3bcec7 100644 --- a/includes/Core/Util/Reset.php +++ b/includes/Core/Util/Reset.php @@ -11,6 +11,7 @@ namespace Google\Site_Kit\Core\Util; use Google\Site_Kit\Context; +use Google\Site_Kit\Core\Email_Reporting\Email_Log; use Google\Site_Kit\Core\Authentication\Authentication; use Google\Site_Kit\Core\Permissions\Permissions; use Google\Site_Kit\Core\REST_API\REST_Route; @@ -113,12 +114,14 @@ public function all() { $this->delete_user_options( 'site' ); $this->delete_post_meta( 'site' ); $this->delete_term_meta( 'site' ); + $this->delete_posts( 'site' ); if ( $this->context->is_network_mode() ) { $this->delete_options( 'network' ); $this->delete_user_options( 'network' ); $this->delete_post_meta( 'network' ); $this->delete_term_meta( 'network' ); + $this->delete_posts( 'network' ); } wp_cache_flush(); @@ -256,6 +259,53 @@ private function delete_term_meta( $scope ) { } } + /** + * Deletes all Site Kit custom post type posts. + * + * @since n.e.x.t + * + * @param string $scope Scope of the deletion ('site' or 'network'). + */ + private function delete_posts( $scope ) { + global $wpdb; + + $sites = array(); + if ( 'network' === $scope ) { + $sites = get_sites( + array( + 'fields' => 'ids', + 'number' => 9999999, + ) + ); + } elseif ( 'site' === $scope ) { + $sites[] = get_current_blog_id(); + } else { + return; + } + + foreach ( $sites as $site_id ) { + $prefix = $wpdb->get_blog_prefix( $site_id ); + $posts_table = "{$prefix}posts"; + $postmeta_table = "{$prefix}postmeta"; + + // phpcs:ignore WordPress.DB.DirectDatabaseQuery + $wpdb->query( + $wpdb->prepare( + "DELETE FROM {$postmeta_table} WHERE post_id IN ( SELECT ID FROM {$posts_table} WHERE post_type = %s )", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared + Email_Log::POST_TYPE + ) + ); + + // phpcs:ignore WordPress.DB.DirectDatabaseQuery + $wpdb->query( + $wpdb->prepare( + "DELETE FROM {$posts_table} WHERE post_type = %s", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared + Email_Log::POST_TYPE + ) + ); + } + } + /** * Gets related REST routes. * diff --git a/tests/phpunit/integration/Core/Util/ResetTest.php b/tests/phpunit/integration/Core/Util/ResetTest.php index 6acc42074f6..8a7a919f0a8 100644 --- a/tests/phpunit/integration/Core/Util/ResetTest.php +++ b/tests/phpunit/integration/Core/Util/ResetTest.php @@ -11,6 +11,7 @@ namespace Google\Site_Kit\Tests\Core\Util; use Google\Site_Kit\Context; +use Google\Site_Kit\Core\Email_Reporting\Email_Log; use Google\Site_Kit\Core\Permissions\Permissions; use Google\Site_Kit\Core\Util\Reset; use Google\Site_Kit\Tests\Exception\RedirectException; @@ -36,6 +37,13 @@ class ResetTest extends TestCase { */ protected $context_with_mutable_input; + /** + * Indicates whether the email log post type and metadata were registered. + * + * @var bool + */ + protected $email_log_registered = false; + public function set_up() { parent::set_up(); @@ -46,6 +54,15 @@ public function set_up() { $this->context_with_mutable_input = new Context( GOOGLESITEKIT_PLUGIN_MAIN_FILE, new MutableInput() ); } + public function tear_down() { + if ( $this->email_log_registered ) { + $this->unregister_email_log_dependencies(); + $this->email_log_registered = false; + } + + parent::tear_down(); + } + public function test_all() { $context = new Context( GOOGLESITEKIT_PLUGIN_MAIN_FILE ); $this->assertFalse( $context->is_network_mode(), 'Context should not be in network mode by default.' ); @@ -79,6 +96,35 @@ public function test_all() { $this->assertEquals( '', get_term_meta( $term_id, 'googlesitekit_keep', true ), 'Term meta with underscore should be deleted.' ); } + public function test_all_deletes_email_log_posts() { + $this->register_email_log_dependencies(); + + $context = new Context( GOOGLESITEKIT_PLUGIN_MAIN_FILE ); + + $first_log = $this->factory()->post->create( + array( + 'post_type' => Email_Log::POST_TYPE, + 'post_status' => Email_Log::STATUS_SCHEDULED, + ) + ); + add_post_meta( $first_log, Email_Log::META_BATCH_ID, 'batch-a' ); + + $second_log = $this->factory()->post->create( + array( + 'post_type' => Email_Log::POST_TYPE, + 'post_status' => Email_Log::STATUS_FAILED, + ) + ); + add_post_meta( $second_log, Email_Log::META_SEND_ATTEMPTS, 2 ); + + $this->run_reset( $context ); + + $this->assertNull( get_post( $first_log ), 'Email log posts should be deleted after reset.' ); + $this->assertNull( get_post( $second_log ), 'Email log posts should be deleted after reset.' ); + $this->assertFalse( metadata_exists( 'post', $first_log, Email_Log::META_BATCH_ID ), 'Email log post meta should be deleted after reset.' ); + $this->assertFalse( metadata_exists( 'post', $second_log, Email_Log::META_SEND_ATTEMPTS ), 'Email log post meta should be deleted after reset.' ); + } + /** * @group ms-required */ @@ -206,4 +252,43 @@ protected function run_reset( Context $context ) { $this->assertUserOptionsDeleted( $user_id, $is_network_mode ); $this->assertTransientsDeleted( $is_network_mode ); } + + private function register_email_log_dependencies() { + if ( $this->email_log_registered ) { + return; + } + + $email_log = new Email_Log( new Context( GOOGLESITEKIT_PLUGIN_MAIN_FILE ) ); + $register_method = new \ReflectionMethod( Email_Log::class, 'register_email_log' ); + $register_method->setAccessible( true ); + $register_method->invoke( $email_log ); + + $this->email_log_registered = true; + } + + private function unregister_email_log_dependencies() { + if ( function_exists( 'unregister_post_type' ) && post_type_exists( Email_Log::POST_TYPE ) ) { + unregister_post_type( Email_Log::POST_TYPE ); + } + + if ( function_exists( 'unregister_post_status' ) ) { + foreach ( array( Email_Log::STATUS_SENT, Email_Log::STATUS_FAILED, Email_Log::STATUS_SCHEDULED ) as $status ) { + unregister_post_status( $status ); + } + } + + if ( function_exists( 'unregister_meta_key' ) ) { + foreach ( + array( + Email_Log::META_REPORT_FREQUENCY, + Email_Log::META_BATCH_ID, + Email_Log::META_SEND_ATTEMPTS, + Email_Log::META_ERROR_DETAILS, + Email_Log::META_REPORT_REFERENCE_DATES, + ) as $meta_key + ) { + unregister_meta_key( 'post', Email_Log::POST_TYPE, $meta_key ); + } + } + } } From 2e7f76c3de82cb237b5dc3be4529a390684a58a3 Mon Sep 17 00:00:00 2001 From: Ben Bowler Date: Mon, 24 Nov 2025 10:04:48 +0000 Subject: [PATCH 2/3] Switch to using wp_delete_post + add multi site tests. --- includes/Core/Util/Reset.php | 48 +++++++------ .../integration/Core/Util/ResetTest.php | 67 ++++++++++++++++++- 2 files changed, 93 insertions(+), 22 deletions(-) diff --git a/includes/Core/Util/Reset.php b/includes/Core/Util/Reset.php index 8625d3bcec7..faad6049c43 100644 --- a/includes/Core/Util/Reset.php +++ b/includes/Core/Util/Reset.php @@ -267,8 +267,6 @@ private function delete_term_meta( $scope ) { * @param string $scope Scope of the deletion ('site' or 'network'). */ private function delete_posts( $scope ) { - global $wpdb; - $sites = array(); if ( 'network' === $scope ) { $sites = get_sites( @@ -284,25 +282,37 @@ private function delete_posts( $scope ) { } foreach ( $sites as $site_id ) { - $prefix = $wpdb->get_blog_prefix( $site_id ); - $posts_table = "{$prefix}posts"; - $postmeta_table = "{$prefix}postmeta"; + $switched = false; - // phpcs:ignore WordPress.DB.DirectDatabaseQuery - $wpdb->query( - $wpdb->prepare( - "DELETE FROM {$postmeta_table} WHERE post_id IN ( SELECT ID FROM {$posts_table} WHERE post_type = %s )", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared - Email_Log::POST_TYPE - ) - ); + if ( get_current_blog_id() !== (int) $site_id ) { + // phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.switch_to_blog_switch_to_blog + switch_to_blog( $site_id ); + $switched = true; + } - // phpcs:ignore WordPress.DB.DirectDatabaseQuery - $wpdb->query( - $wpdb->prepare( - "DELETE FROM {$posts_table} WHERE post_type = %s", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared - Email_Log::POST_TYPE - ) - ); + $posts_per_batch = 100; + do { + // phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.get_posts_get_posts + $post_ids = get_posts( + array( + 'post_type' => Email_Log::POST_TYPE, + 'post_status' => 'any', + 'fields' => 'ids', + 'posts_per_page' => $posts_per_batch, + 'no_found_rows' => true, + 'orderby' => 'ID', + 'order' => 'ASC', + ) + ); + + foreach ( $post_ids as $post_id ) { + wp_delete_post( $post_id, true ); + } + } while ( ! empty( $post_ids ) ); + + if ( $switched ) { + restore_current_blog(); + } } } diff --git a/tests/phpunit/integration/Core/Util/ResetTest.php b/tests/phpunit/integration/Core/Util/ResetTest.php index 8a7a919f0a8..c5b0c8a2762 100644 --- a/tests/phpunit/integration/Core/Util/ResetTest.php +++ b/tests/phpunit/integration/Core/Util/ResetTest.php @@ -140,6 +140,67 @@ public function test_network_mode_all() { $this->run_reset( $context ); } + /** + * @group ms-required + */ + public function test_network_mode_all_deletes_email_log_posts() { + $this->network_activate_site_kit(); + add_filter( 'googlesitekit_is_network_mode', '__return_true' ); + + $this->register_email_log_dependencies(); + + $context = new Context( GOOGLESITEKIT_PLUGIN_MAIN_FILE ); + $this->assertTrue( $context->is_network_mode(), 'Context should be in network mode when filter is enabled.' ); + + $blog_ids = array( + get_current_blog_id(), + $this->factory()->blog->create(), + ); + + $email_logs = array(); + foreach ( $blog_ids as $blog_id ) { + $switched = false; + + if ( get_current_blog_id() !== (int) $blog_id ) { + // phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.switch_to_blog_switch_to_blog + switch_to_blog( $blog_id ); + $switched = true; + } + + $email_logs[ $blog_id ] = $this->factory()->post->create( + array( + 'post_type' => Email_Log::POST_TYPE, + 'post_status' => Email_Log::STATUS_FAILED, + ) + ); + add_post_meta( $email_logs[ $blog_id ], Email_Log::META_BATCH_ID, 'network-batch' ); + + if ( $switched ) { + restore_current_blog(); + } + } + + $this->run_reset( $context ); + + foreach ( $blog_ids as $blog_id ) { + $switched = false; + + if ( get_current_blog_id() !== (int) $blog_id ) { + // phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.switch_to_blog_switch_to_blog + switch_to_blog( $blog_id ); + $switched = true; + } + + $log_id = $email_logs[ $blog_id ]; + $this->assertNull( get_post( $log_id ), 'Email log posts in each site should be deleted after reset.' ); + $this->assertFalse( metadata_exists( 'post', $log_id, Email_Log::META_BATCH_ID ), 'Email log post meta should be deleted for every site after reset.' ); + + if ( $switched ) { + restore_current_blog(); + } + } + } + public function test_handle_reset_action_with_bad_nonce() { remove_all_actions( 'admin_action_' . Reset::ACTION ); $reset = new Reset( $this->context_with_mutable_input ); @@ -268,12 +329,12 @@ private function register_email_log_dependencies() { private function unregister_email_log_dependencies() { if ( function_exists( 'unregister_post_type' ) && post_type_exists( Email_Log::POST_TYPE ) ) { - unregister_post_type( Email_Log::POST_TYPE ); + call_user_func( 'unregister_post_type', Email_Log::POST_TYPE ); } if ( function_exists( 'unregister_post_status' ) ) { foreach ( array( Email_Log::STATUS_SENT, Email_Log::STATUS_FAILED, Email_Log::STATUS_SCHEDULED ) as $status ) { - unregister_post_status( $status ); + call_user_func( 'unregister_post_status', $status ); } } @@ -287,7 +348,7 @@ private function unregister_email_log_dependencies() { Email_Log::META_REPORT_REFERENCE_DATES, ) as $meta_key ) { - unregister_meta_key( 'post', Email_Log::POST_TYPE, $meta_key ); + call_user_func( 'unregister_meta_key', 'post', Email_Log::POST_TYPE, $meta_key ); } } } From ee78b3780cb41b6b03b96a08f37efe3d03540ddc Mon Sep 17 00:00:00 2001 From: Ben Bowler Date: Mon, 24 Nov 2025 13:05:25 +0000 Subject: [PATCH 3/3] Fix delete post status in query. --- includes/Core/Util/Reset.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/includes/Core/Util/Reset.php b/includes/Core/Util/Reset.php index faad6049c43..71b76a38cae 100644 --- a/includes/Core/Util/Reset.php +++ b/includes/Core/Util/Reset.php @@ -296,7 +296,11 @@ private function delete_posts( $scope ) { $post_ids = get_posts( array( 'post_type' => Email_Log::POST_TYPE, - 'post_status' => 'any', + 'post_status' => array( + Email_Log::STATUS_SENT, + Email_Log::STATUS_FAILED, + Email_Log::STATUS_SCHEDULED, + ), 'fields' => 'ids', 'posts_per_page' => $posts_per_batch, 'no_found_rows' => true,