Commit ac7eeb4d39b for woocommerce
commit ac7eeb4d39b071cc5c43968d094fc1dd585b661e
Author: Jorge A. Torres <jorge.torres@automattic.com>
Date: Mon Jun 22 13:12:14 2026 +0100
Check product status when saving items to shopper lists (#65889)
diff --git a/plugins/woocommerce/changelog/fix-shopper-lists-non-published-products b/plugins/woocommerce/changelog/fix-shopper-lists-non-published-products
new file mode 100644
index 00000000000..18735a4b1f0
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-shopper-lists-non-published-products
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Only save published products to shopper lists.
diff --git a/plugins/woocommerce/src/Internal/ShopperLists/ShopperListItem.php b/plugins/woocommerce/src/Internal/ShopperLists/ShopperListItem.php
index 139a05d8d62..6fe986b5280 100644
--- a/plugins/woocommerce/src/Internal/ShopperLists/ShopperListItem.php
+++ b/plugins/woocommerce/src/Internal/ShopperLists/ShopperListItem.php
@@ -130,11 +130,11 @@ class ShopperListItem {
* @param int $product_or_variation_id Product or variation ID.
* @param array $variation Variation attributes keyed by attribute name.
* @param int $quantity Saved quantity. Coerced to a minimum of 1.
- * @return self|null Null if the underlying product can't be resolved.
+ * @return self|null Null if the underlying product can't be resolved or isn't published.
*/
public static function from_product( int $product_or_variation_id, array $variation = array(), int $quantity = 1 ): ?self {
$product = wc_get_product( absint( $product_or_variation_id ) );
- if ( ! $product ) {
+ if ( ! $product || ! self::product_is_live( $product ) ) {
return null;
}
@@ -313,7 +313,16 @@ class ShopperListItem {
*/
public function is_live(): bool {
$product = $this->get_product();
- if ( ! $product instanceof \WC_Product || ProductStatus::PUBLISH !== $product->get_status() ) {
+ return $product instanceof \WC_Product && self::product_is_live( $product );
+ }
+
+ /**
+ * Whether a resolved product (and its parent, for variations) is `publish`.
+ *
+ * @param \WC_Product $product Resolved product or variation.
+ */
+ private static function product_is_live( \WC_Product $product ): bool {
+ if ( ProductStatus::PUBLISH !== $product->get_status() ) {
return false;
}
diff --git a/plugins/woocommerce/tests/php/src/Internal/ShopperLists/ShopperListItemTests.php b/plugins/woocommerce/tests/php/src/Internal/ShopperLists/ShopperListItemTests.php
index 4f1479dc6e9..54b3da1b4e6 100644
--- a/plugins/woocommerce/tests/php/src/Internal/ShopperLists/ShopperListItemTests.php
+++ b/plugins/woocommerce/tests/php/src/Internal/ShopperLists/ShopperListItemTests.php
@@ -85,6 +85,29 @@ class ShopperListItemTests extends WC_Unit_Test_Case {
$this->assertSame( $original->to_array(), $rebuilt->to_array() );
}
+ /**
+ * @testdox from_product should return null for a product that isn't publicly live.
+ */
+ public function test_from_product_returns_null_for_non_published_product(): void {
+ $this->product->set_status( 'private' );
+ $this->product->save();
+
+ $this->assertNull( ShopperListItem::from_product( $this->product->get_id() ) );
+ }
+
+ /**
+ * @testdox from_product should return null for a non-published variable product, not a variation error.
+ */
+ public function test_from_product_returns_null_for_non_published_variable_product(): void {
+ $variable = \WC_Helper_Product::create_variation_product();
+ $variable->set_status( 'draft' );
+ $variable->save();
+
+ $this->assertNull( ShopperListItem::from_product( $variable->get_id() ) );
+
+ $variable->delete( true );
+ }
+
/**
* @testdox from_product validates the variation array against the variation product, like cart does.
*/