Commit 85ff2fd2394 for woocommerce
commit 85ff2fd239407f3a2433ad39aa4eec3d8a508fbb
Author: Chris Lilitsas <1105590+xristos3490@users.noreply.github.com>
Date: Tue Oct 13 17:32:20 2026 +0300
Stop back-in-stock emails for out-of-stock sibling variations when one variation is restocked (#69500)
* fix: skip BIS emails for out-of-stock variations when a sibling restocks
* fix: keep variation sign-ups in the notifications product filter
* fix: guard filtered get_children() and deprecate unused eligibility service in ListTable
diff --git a/plugins/woocommerce/changelog/fix-wooplug-7906-bis-sibling-variation-emails b/plugins/woocommerce/changelog/fix-wooplug-7906-bis-sibling-variation-emails
new file mode 100644
index 00000000000..49a16c8b714
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-wooplug-7906-bis-sibling-variation-emails
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Stop sending back-in-stock emails for out-of-stock variations when a sibling variation is restocked.
diff --git a/plugins/woocommerce/src/Internal/StockNotifications/Admin/ListTable.php b/plugins/woocommerce/src/Internal/StockNotifications/Admin/ListTable.php
index 33e3d6dd011..870fca366c7 100644
--- a/plugins/woocommerce/src/Internal/StockNotifications/Admin/ListTable.php
+++ b/plugins/woocommerce/src/Internal/StockNotifications/Admin/ListTable.php
@@ -4,6 +4,7 @@ declare( strict_types = 1 );
namespace Automattic\WooCommerce\Internal\StockNotifications\Admin;
+use Automattic\WooCommerce\Enums\ProductType;
use Automattic\WooCommerce\Internal\DataStores\StockNotifications\StockNotificationsDataStore;
use Automattic\WooCommerce\Internal\StockNotifications\Enums\NotificationStatus;
use Automattic\WooCommerce\Internal\StockNotifications\Notification;
@@ -69,7 +70,9 @@ class ListTable extends \WP_List_Table {
/**
* Eligibility service.
*
- * @var EligibilityService
+ * @deprecated 11.3.0 No longer used by this class. Will be removed in a future version.
+ *
+ * @var EligibilityService|null
*/
public $eligibility_service;
@@ -78,6 +81,8 @@ class ListTable extends \WP_List_Table {
*
* @internal
*
+ * @deprecated 11.3.0 No longer used by this class. Will be removed in a future version.
+ *
* @param EligibilityService $eligibility_service Eligibility service.
*/
final public function init( EligibilityService $eligibility_service ) {
@@ -377,8 +382,10 @@ class ListTable extends \WP_List_Table {
$filter = absint( wp_unslash( $_GET['customer_stock_notifications_product_filter'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
$product = wc_get_product( $filter );
if ( $product instanceof \WC_Product ) {
- $target_ids = $this->eligibility_service->get_target_product_ids( $product );
- $query_args['product_id'] = $target_ids;
+ $children = $product->get_children();
+ $query_args['product_id'] = $product->is_type( ProductType::VARIABLE )
+ ? array_merge( array( $product->get_id() ), is_array( $children ) ? $children : array() )
+ : array( $product->get_id() );
} else {
NotificationsPage::add_notice( __( 'Invalid product selected.', 'woocommerce' ), 'error' );
}
diff --git a/plugins/woocommerce/src/Internal/StockNotifications/Utilities/EligibilityService.php b/plugins/woocommerce/src/Internal/StockNotifications/Utilities/EligibilityService.php
index 1e77ce88e85..d97ffaa6b85 100644
--- a/plugins/woocommerce/src/Internal/StockNotifications/Utilities/EligibilityService.php
+++ b/plugins/woocommerce/src/Internal/StockNotifications/Utilities/EligibilityService.php
@@ -122,9 +122,9 @@ class EligibilityService {
/**
* Get the product IDs that need to be checked for stock notifications.
*
- * For simple products, this returns just the product ID. For variable products,
- * it returns both the parent product ID and the IDs of all variations whose stock
- * is managed by the parent product.
+ * For simple products, this returns just the product ID. For variable products that
+ * manage stock, it also returns the IDs of the variations that share the parent's stock.
+ * Variations of a parent that does not manage stock keep their own stock status.
*
* This is used in two key scenarios:
* 1. Checking if a product has any active notifications
@@ -137,7 +137,7 @@ class EligibilityService {
*/
public function get_target_product_ids( WC_Product $product ): array {
$lookup_ids = array( $product->get_id() );
- if ( $product->is_type( ProductType::VARIABLE ) ) {
+ if ( $product->is_type( ProductType::VARIABLE ) && $product->get_manage_stock() ) {
$children_ids = $this->stock_management_helper->get_managed_variations( $product );
$lookup_ids = array_merge( $lookup_ids, $children_ids );
}
diff --git a/plugins/woocommerce/tests/php/src/Internal/StockNotifications/AsyncTasks/NotificationsProcessorTests.php b/plugins/woocommerce/tests/php/src/Internal/StockNotifications/AsyncTasks/NotificationsProcessorTests.php
index d13651bf482..583aeb5fbe8 100644
--- a/plugins/woocommerce/tests/php/src/Internal/StockNotifications/AsyncTasks/NotificationsProcessorTests.php
+++ b/plugins/woocommerce/tests/php/src/Internal/StockNotifications/AsyncTasks/NotificationsProcessorTests.php
@@ -210,6 +210,38 @@ class NotificationsProcessorTests extends \WC_Unit_Test_Case {
$this->assertEquals( NotificationStatus::SENT, $notification_on_variation->get_status() );
}
+ /**
+ * Test process_batch on a parent that does not manage stock leaves out-of-stock variations alone.
+ */
+ public function test_process_batch_variable_without_parent_stock_skips_out_of_stock_siblings() {
+ $variable = WC_Helper_Product::create_variation_product();
+ $variations = array_map( 'wc_get_product', $variable->get_children() );
+ $this->assertFalse( $variable->get_manage_stock() );
+
+ $restocked = $variations[0];
+ $restocked->set_stock_status( ProductStockStatus::IN_STOCK );
+ $restocked->save();
+ $sibling = $variations[1];
+ $sibling->set_stock_status( ProductStockStatus::OUT_OF_STOCK );
+ $sibling->save();
+
+ \WC_Product_Variable::sync( $variable->get_id() );
+ $variable = wc_get_product( $variable->get_id() );
+ $this->assertEquals( ProductStockStatus::IN_STOCK, $variable->get_stock_status() );
+
+ $notification = new Notification();
+ $notification->set_product_id( $sibling->get_id() );
+ $notification->set_user_id( 1 );
+ $notification->set_status( NotificationStatus::ACTIVE );
+ $notification->save();
+
+ $this->sut->process_batch( $variable->get_id() );
+
+ $notification = new Notification( $notification->get_id() );
+ $this->assertEquals( NotificationStatus::ACTIVE, $notification->get_status() );
+ $this->assertEmpty( $notification->get_date_notified() );
+ }
+
/**
* Test process_batch method bail out when product is not in stock.
*/
diff --git a/plugins/woocommerce/tests/php/src/Internal/StockNotifications/Utilities/EligibilityServiceTests.php b/plugins/woocommerce/tests/php/src/Internal/StockNotifications/Utilities/EligibilityServiceTests.php
index 8e0434a8427..7f9b7536984 100644
--- a/plugins/woocommerce/tests/php/src/Internal/StockNotifications/Utilities/EligibilityServiceTests.php
+++ b/plugins/woocommerce/tests/php/src/Internal/StockNotifications/Utilities/EligibilityServiceTests.php
@@ -101,7 +101,13 @@ class EligibilityServiceTests extends \WC_Unit_Test_Case {
$product = WC_Helper_Product::create_simple_product();
$this->assertEquals( array( $product->get_id() ), $this->sut->get_target_product_ids( $product ) );
- $variable = WC_Helper_Product::create_variation_product();
+ $variable = WC_Helper_Product::create_variation_product();
+ $this->assertFalse( $variable->get_manage_stock() );
+ $this->assertEquals( array( $variable->get_id() ), $this->sut->get_target_product_ids( $variable ), 'Variations of a parent that does not manage stock keep their own stock status.' );
+
+ $variable->set_manage_stock( true );
+ $variable->set_stock_quantity( 10 );
+ $variable->save();
$managed_variations = array_filter(
array_map(
function ( $variation ) {
@@ -115,6 +121,7 @@ class EligibilityServiceTests extends \WC_Unit_Test_Case {
)
);
+ $this->assertNotEmpty( $managed_variations );
$this->assertEquals( array( $variable->get_id(), ...$managed_variations ), $this->sut->get_target_product_ids( $variable ) );
$variation = $variable->get_children()[0];