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] );
+ }
}