Commit bdc2e592c69 for woocommerce

commit bdc2e592c69756879bbf71474d59d15bd159c954
Author: Chris Lilitsas <1105590+xristos3490@users.noreply.github.com>
Date:   Wed Oct 14 15:28:13 2026 +0300

    Keep unverified stock notification sign-ups with a recent verification resend out of the daily cleanup (#69506)

    * fix: keep BIS sign-ups with a recent verification resend during cleanup

    * fix: record admin verification resends so cleanup keeps those sign-ups

    * fix: record admin verification resend before sending the email

    Match the frontend resend path: persist the resend timestamp and save
    before dispatching the email, so retention cleanup can't delete the
    row mid-send and the later save can't hit a deleted row. Also make the
    edit page test fail if the handler does not redirect.

diff --git a/plugins/woocommerce/changelog/fix-wooplug-7927-bis-cleanup-after-resend b/plugins/woocommerce/changelog/fix-wooplug-7927-bis-cleanup-after-resend
new file mode 100644
index 00000000000..3f8665ffbf0
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-wooplug-7927-bis-cleanup-after-resend
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Keep unverified back-in-stock sign-ups from being cleaned up right after the verification email is resent.
diff --git a/plugins/woocommerce/src/Internal/StockNotifications/Admin/NotificationEditPage.php b/plugins/woocommerce/src/Internal/StockNotifications/Admin/NotificationEditPage.php
index 1de1fbb14b1..68d6958f6f1 100644
--- a/plugins/woocommerce/src/Internal/StockNotifications/Admin/NotificationEditPage.php
+++ b/plugins/woocommerce/src/Internal/StockNotifications/Admin/NotificationEditPage.php
@@ -11,6 +11,7 @@ use Automattic\WooCommerce\Internal\StockNotifications\Factory;
 use Automattic\WooCommerce\Internal\StockNotifications\Emails\EmailManager;
 use Automattic\WooCommerce\Internal\StockNotifications\Admin\ListTable;
 use Automattic\WooCommerce\Internal\StockNotifications\Enums\NotificationCancellationSource;
+use Automattic\WooCommerce\Internal\StockNotifications\Frontend\NotificationManagementService;

 /**
  * Notification create page for Customer Stock Notifications.
@@ -128,6 +129,9 @@ class NotificationEditPage {
 					break;
 				}

+				// Record the resend before dispatching the email so retention cleanup can't delete the row mid-send.
+				$notification->update_meta_data( NotificationManagementService::LAST_VERIFY_EMAIL_SENT_META, (string) time() );
+				$notification->save();
 				$this->email_manager->send_verify_email( $notification );
 				// translators: %s user email.
 				$notice_message = sprintf( __( 'Verification email sent to "%s".', 'woocommerce' ), $notification->get_user_email() );
diff --git a/plugins/woocommerce/src/Internal/StockNotifications/DataRetentionController.php b/plugins/woocommerce/src/Internal/StockNotifications/DataRetentionController.php
index b395557de2b..8348f2ec82b 100644
--- a/plugins/woocommerce/src/Internal/StockNotifications/DataRetentionController.php
+++ b/plugins/woocommerce/src/Internal/StockNotifications/DataRetentionController.php
@@ -5,6 +5,7 @@ declare( strict_types = 1 );
 namespace Automattic\WooCommerce\Internal\StockNotifications;

 use Automattic\WooCommerce\Internal\StockNotifications\Enums\NotificationStatus;
+use Automattic\WooCommerce\Internal\StockNotifications\Frontend\NotificationManagementService;

 /**
  * Controller for managing data retention of customer stock notifications.
@@ -64,7 +65,7 @@ class DataRetentionController {
 	/**
 	 * Deletes overdue notifications based on the configured time threshold.
 	 * It retrieves notifications that are pending and past the threshold,
-	 * then deletes them.
+	 * then deletes them, except those whose verification email was resent within the threshold.
 	 *
 	 * @return void
 	 */
@@ -85,7 +86,13 @@ class DataRetentionController {

 		foreach ( $overdue_notifications as $notification_id ) {
 			$notification = Factory::get_notification( $notification_id );
-			if ( $notification instanceof Notification ) {
+			if ( ! $notification instanceof Notification ) {
+				continue;
+			}
+
+			// Keep sign-ups whose verification email was resent within the retention window.
+			$last_verify_email_sent_at = (int) $notification->get_meta( NotificationManagementService::LAST_VERIFY_EMAIL_SENT_META );
+			if ( $last_verify_email_sent_at <= $overdue_threshold ) {
 				$notification->delete();
 			}
 		}
diff --git a/plugins/woocommerce/tests/php/src/Internal/StockNotifications/Admin/NotificationEditPageTests.php b/plugins/woocommerce/tests/php/src/Internal/StockNotifications/Admin/NotificationEditPageTests.php
new file mode 100644
index 00000000000..47708e43120
--- /dev/null
+++ b/plugins/woocommerce/tests/php/src/Internal/StockNotifications/Admin/NotificationEditPageTests.php
@@ -0,0 +1,89 @@
+<?php
+
+declare( strict_types = 1 );
+namespace Automattic\WooCommerce\Tests\Internal\StockNotifications\Admin;
+
+use Automattic\WooCommerce\Internal\StockNotifications\Admin\NotificationEditPage;
+use Automattic\WooCommerce\Internal\StockNotifications\Admin\NotificationsPage;
+use Automattic\WooCommerce\Internal\StockNotifications\Emails\EmailManager;
+use Automattic\WooCommerce\Internal\StockNotifications\Enums\NotificationStatus;
+use Automattic\WooCommerce\Internal\StockNotifications\Frontend\NotificationManagementService;
+use Automattic\WooCommerce\Internal\StockNotifications\Notification;
+use Automattic\WooCommerce\Tests\Internal\StockNotifications\StockNotificationsFeatureTrait;
+
+/**
+ * Tests for the admin notification edit form handler.
+ */
+class NotificationEditPageTests extends \WC_Unit_Test_Case {
+
+	use StockNotificationsFeatureTrait;
+
+	/**
+	 * The System Under Test.
+	 *
+	 * @var NotificationEditPage
+	 */
+	private $sut;
+
+	/**
+	 * Set up test fixtures.
+	 */
+	public function setUp(): void {
+		parent::setUp();
+		$this->enable_stock_notifications_feature();
+		$this->sut = new NotificationEditPage();
+		$this->sut->init( $this->createMock( EmailManager::class ) );
+		add_filter( 'wp_redirect', array( $this, 'stop_redirect' ) );
+	}
+
+	/**
+	 * Tear down test fixtures.
+	 */
+	public function tearDown(): void {
+		remove_filter( 'wp_redirect', array( $this, 'stop_redirect' ) );
+		$_POST    = array();
+		$_REQUEST = array();
+		delete_option( NotificationsPage::ADMIN_NOTICE_OPTION_NAME );
+		$this->restore_stock_notifications_feature_option();
+		parent::tearDown();
+	}
+
+	/**
+	 * Throws instead of redirecting so the handler's exit is never reached.
+	 *
+	 * @throws \Exception Always.
+	 */
+	public function stop_redirect(): void {
+		throw new \Exception( 'redirect' );
+	}
+
+	/**
+	 * @testdox Sending a verification email from the edit page records the send time.
+	 */
+	public function test_send_verification_email_records_send_time(): void {
+		$notification = new Notification();
+		$notification->set_user_email( 'pending@test.com' );
+		$notification->set_product_id( 1 );
+		$notification->set_status( NotificationStatus::PENDING );
+		$notification_id = $notification->save();
+
+		$_POST = array(
+			'wc_customer_stock_notification_action'     => 'send_verification_email',
+			'customer_stock_notification_edit_security' => wp_create_nonce( 'woocommerce-customer-stock-notification-edit' ),
+		);
+
+		$_REQUEST = $_POST; // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Simulates the request the handler verifies.
+
+		$before     = time();
+		$redirected = false;
+		try {
+			$this->sut->process_edit_form( $notification );
+		} catch ( \Exception $e ) {
+			$redirected = 'redirect' === $e->getMessage();
+		}
+		$this->assertTrue( $redirected, 'Expected the handler to redirect.' );
+
+		$sent_at = (int) ( new Notification( $notification_id ) )->get_meta( NotificationManagementService::LAST_VERIFY_EMAIL_SENT_META );
+		$this->assertGreaterThanOrEqual( $before, $sent_at );
+	}
+}
diff --git a/plugins/woocommerce/tests/php/src/Internal/StockNotifications/DataRetentionControllerTests.php b/plugins/woocommerce/tests/php/src/Internal/StockNotifications/DataRetentionControllerTests.php
index 4d45602517b..48185282de8 100644
--- a/plugins/woocommerce/tests/php/src/Internal/StockNotifications/DataRetentionControllerTests.php
+++ b/plugins/woocommerce/tests/php/src/Internal/StockNotifications/DataRetentionControllerTests.php
@@ -5,6 +5,7 @@ namespace Automattic\WooCommerce\Tests\Internal\StockNotifications;

 use Automattic\WooCommerce\Internal\StockNotifications\DataRetentionController;
 use Automattic\WooCommerce\Internal\StockNotifications\Enums\NotificationStatus;
+use Automattic\WooCommerce\Internal\StockNotifications\Frontend\NotificationManagementService;
 use Automattic\WooCommerce\Internal\StockNotifications\Notification;
 use Automattic\WooCommerce\Internal\StockNotifications\NotificationQuery;

@@ -123,4 +124,39 @@ class DataRetentionControllerTests extends \WC_Unit_Test_Case {
 		$this->expectException( \Exception::class );
 		new Notification( $expired_id );
 	}
+
+	/**
+	 * @testdox Daily task keeps an overdue pending notification whose verification email was resent within the retention window.
+	 */
+	public function test_daily_task_keeps_overdue_notification_with_recent_verification_resend(): void {
+		$days_until_deletion = 5;
+		update_option(
+			'woocommerce_customer_stock_notifications_unverified_deletions_days_threshold',
+			$days_until_deletion
+		);
+		$expired_time = time() - ( ( $days_until_deletion + 1 ) * DAY_IN_SECONDS );
+
+		$notification_resent = new Notification();
+		$notification_resent->set_user_email( 'resent@test.com' );
+		$notification_resent->set_product_id( 1 );
+		$notification_resent->set_status( NotificationStatus::PENDING );
+		$notification_resent->set_date_created( gmdate( 'Y-m-d H:i:s', $expired_time ) );
+		$notification_resent->update_meta_data( NotificationManagementService::LAST_VERIFY_EMAIL_SENT_META, (string) time() );
+		$resent_id = $notification_resent->save();
+
+		$notification_stale = new Notification();
+		$notification_stale->set_user_email( 'stale@test.com' );
+		$notification_stale->set_product_id( 1 );
+		$notification_stale->set_status( NotificationStatus::PENDING );
+		$notification_stale->set_date_created( gmdate( 'Y-m-d H:i:s', $expired_time ) );
+		$notification_stale->update_meta_data( NotificationManagementService::LAST_VERIFY_EMAIL_SENT_META, (string) $expired_time );
+		$notification_stale->save();
+
+		$this->controller->do_wc_customer_stock_notifications_daily();
+
+		$notifications_after = NotificationQuery::get_notifications( array() );
+
+		$this->assertCount( 1, $notifications_after );
+		$this->assertEquals( $resent_id, (int) $notifications_after[0] );
+	}
 }