Commit 33dccc18864 for woocommerce

commit 33dccc1886452ebfb3ad1c601ba825f764e203a1
Author: Chris Lilitsas <1105590+xristos3490@users.noreply.github.com>
Date:   Thu Oct 15 19:02:40 2026 +0300

    Fix personal data erasure missing back-in-stock sign-ups made under a previous email (#69498)

    * fix: erase BIS sign-ups stored under a user's previous email

    * fix: skip BIS eraser account lookup when the user ID is not positive

    * fix: look up the BIS eraser account before normalizing the email

    wp_users.user_email keeps the case it was saved with, so on a
    case-sensitive collation the lowercased email missed the account and
    the eraser silently fell back to matching by email only. Look the user
    up with the email as entered, as NotificationCreatePage already does,
    and drop the redundant ID check since get_user_by() returns false or a
    saved user.

    * test: cover the BIS eraser dedupe with a row matching both queries

    The email-change test had no row matching both the email and the user
    ID query, so removing array_unique() still passed. Add a sign-up made
    after the email change and assert it is reported once.

diff --git a/plugins/woocommerce/changelog/fix-wooplug-7911-bis-eraser-user-id b/plugins/woocommerce/changelog/fix-wooplug-7911-bis-eraser-user-id
new file mode 100644
index 00000000000..076fabda8c5
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-wooplug-7911-bis-eraser-user-id
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Erase back-in-stock sign-ups made under a customer's previous account email when their personal data is erased.
diff --git a/plugins/woocommerce/src/Internal/StockNotifications/Privacy/PrivacyEraser.php b/plugins/woocommerce/src/Internal/StockNotifications/Privacy/PrivacyEraser.php
index f209ec1f3b4..16bb72b407d 100644
--- a/plugins/woocommerce/src/Internal/StockNotifications/Privacy/PrivacyEraser.php
+++ b/plugins/woocommerce/src/Internal/StockNotifications/Privacy/PrivacyEraser.php
@@ -40,7 +40,7 @@ class PrivacyEraser extends \WC_Abstract_Privacy {
 	}

 	/**
-	 * Erase customer stock notification data for a given email address.
+	 * Erase customer stock notification data for a given email address and the account it belongs to.
 	 *
 	 * This method anonymizes the user email and sets the status of the notifications to 'cancelled'.
 	 *
@@ -56,6 +56,9 @@ class PrivacyEraser extends \WC_Abstract_Privacy {
 			'done'           => true,
 		);

+		// Look up the account with the letter case as entered: `wp_users.user_email` is never
+		// normalized, so on a case-sensitive collation the lowercased form would miss it.
+		$user          = get_user_by( 'email', $email_address );
 		$email_address = EmailNormalizer::normalize( $email_address );

 		$notifications = NotificationQuery::get_notifications(
@@ -64,6 +67,20 @@ class PrivacyEraser extends \WC_Abstract_Privacy {
 			)
 		);

+		// Sign-ups made before an account email change keep the old email, so also match by user ID.
+		if ( $user instanceof \WP_User ) {
+			$notifications = array_unique(
+				array_merge(
+					$notifications,
+					NotificationQuery::get_notifications(
+						array(
+							'user_id' => $user->ID,
+						)
+					)
+				)
+			);
+		}
+
 		foreach ( $notifications as $notification_id ) {
 			$notification = Factory::get_notification( $notification_id );
 			if ( ! $notification instanceof Notification ) {
diff --git a/plugins/woocommerce/tests/php/src/Internal/StockNotifications/Privacy/PrivacyEraserTests.php b/plugins/woocommerce/tests/php/src/Internal/StockNotifications/Privacy/PrivacyEraserTests.php
index 2d3e49a272c..0dcb9771bd6 100644
--- a/plugins/woocommerce/tests/php/src/Internal/StockNotifications/Privacy/PrivacyEraserTests.php
+++ b/plugins/woocommerce/tests/php/src/Internal/StockNotifications/Privacy/PrivacyEraserTests.php
@@ -64,4 +64,61 @@ class PrivacyEraserTests extends \WC_Unit_Test_Case {
 		$this->assertTrue( $response['items_removed'] );
 		$this->assertEquals( NotificationStatus::CANCELLED, ( new Notification( $notification_id ) )->get_status() );
 	}
+
+	/**
+	 * @testdox Should erase a user's sign-ups under their previous and current email once each, plus guest rows for the current email only.
+	 */
+	public function test_privacy_eraser_matches_user_id_after_email_change(): void {
+		$user_id = self::factory()->user->create( array( 'user_email' => 'old@doe.com' ) );
+		$other   = self::factory()->user->create( array( 'user_email' => 'other@doe.com' ) );
+
+		$user_notification = new Notification();
+		$user_notification->set_user_id( $user_id );
+		$user_notification->set_user_email( 'old@doe.com' );
+		$user_notification->set_product_id( 1 );
+		$user_notification_id = $user_notification->save();
+
+		$guest_notification = new Notification();
+		$guest_notification->set_user_email( 'new@doe.com' );
+		$guest_notification->set_product_id( 2 );
+		$guest_notification_id = $guest_notification->save();
+
+		$other_notification = new Notification();
+		$other_notification->set_user_id( $other );
+		$other_notification->set_user_email( 'other@doe.com' );
+		$other_notification->set_product_id( 3 );
+		$other_notification_id = $other_notification->save();
+
+		wp_update_user(
+			array(
+				'ID'         => $user_id,
+				'user_email' => 'new@doe.com',
+			)
+		);
+
+		// Signed up after the email change, so it matches both the email and the user ID query.
+		$current_notification = new Notification();
+		$current_notification->set_user_id( $user_id );
+		$current_notification->set_user_email( 'new@doe.com' );
+		$current_notification->set_product_id( 4 );
+		$current_notification_id = $current_notification->save();
+
+		$response = PrivacyEraser::erase_notification_data( 'new@doe.com' );
+
+		$this->assertTrue( $response['items_removed'] );
+		$this->assertCount( 3, $response['messages'] );
+
+		$erased_user_notification = new Notification( $user_notification_id );
+		$this->assertSame( NotificationStatus::CANCELLED, $erased_user_notification->get_status() );
+		$this->assertSame( 0, $erased_user_notification->get_user_id() );
+		$this->assertSame( wp_privacy_anonymize_data( 'email', '' ), $erased_user_notification->get_user_email() );
+
+		$this->assertSame( NotificationStatus::CANCELLED, ( new Notification( $guest_notification_id ) )->get_status() );
+		$this->assertSame( NotificationStatus::CANCELLED, ( new Notification( $current_notification_id ) )->get_status() );
+
+		$untouched_notification = new Notification( $other_notification_id );
+		$this->assertNotSame( NotificationStatus::CANCELLED, $untouched_notification->get_status() );
+		$this->assertSame( $other, $untouched_notification->get_user_id() );
+		$this->assertSame( 'other@doe.com', $untouched_notification->get_user_email() );
+	}
 }