Commit 75f677505b8 for woocommerce

commit 75f677505b8a6579aa7646047eea8cb155b7c2c1
Author: Vincent Borgers <88669109+VincentBorgers@users.noreply.github.com>
Date:   Mon Oct 12 12:58:39 2026 +0200

    Show no products in the On Sale collection when nothing is on sale (#69695)

    * Show no products in the On Sale collection when nothing is on sale

    When no products were on sale, the on-sale query passed an empty post__in.
    merge_post__in() drops empty arrays, so the filter was ignored and the
    collection showed every product. Use array( -1 ), which the Product
    Collection queries already use for no results.

    * Use 0 instead of -1 when no products are on sale

    WP_Query passes post__in through absint(), so -1 becomes 1 and a product
    with ID 1 was returned. 0 matches no product, as in the products REST
    endpoints and the sale_products shortcode.

    * Return 0 from merge_post__in() when the filters do not overlap

    The empty intersection returned array( -1 ), which WP_Query turns into 1
    with absint(). Combining the on sale filter with hand-picked products that
    include product 1 then returned that product.

diff --git a/plugins/woocommerce/changelog/fix-product-collection-on-sale-empty b/plugins/woocommerce/changelog/fix-product-collection-on-sale-empty
new file mode 100644
index 00000000000..91243349ed4
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-product-collection-on-sale-empty
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Product Collection: show no products in the On Sale collection when no products are on sale, instead of every product.
diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/ProductCollection/QueryBuilder.php b/plugins/woocommerce/src/Blocks/BlockTypes/ProductCollection/QueryBuilder.php
index f5cd17a0ec8..a0b45460736 100644
--- a/plugins/woocommerce/src/Blocks/BlockTypes/ProductCollection/QueryBuilder.php
+++ b/plugins/woocommerce/src/Blocks/BlockTypes/ProductCollection/QueryBuilder.php
@@ -939,8 +939,11 @@ class QueryBuilder {
 			return array();
 		}

+		$on_sale_ids = wc_get_product_ids_on_sale();
+
+		// An empty post__in is ignored when queries are merged, so use 0 to match no product.
 		return array(
-			'post__in' => wc_get_product_ids_on_sale(),
+			'post__in' => empty( $on_sale_ids ) ? array( 0 ) : $on_sale_ids,
 		);
 	}

@@ -1155,9 +1158,9 @@ class QueryBuilder {
 		if ( count( $post__in ) > 1 ) {
 			$post__in = array_intersect( ...$post__in );
 			// An empty array means that there was no overlap between the filters and so
-			// the query should return no results.
+			// the query should return no results. WP_Query applies absint(), so use 0.
 			if ( empty( $post__in ) ) {
-				return array( -1 );
+				return array( 0 );
 			}
 		} else {
 			$post__in = reset( $post__in );
diff --git a/plugins/woocommerce/tests/php/src/Blocks/BlockTypes/ProductCollection/QueryBuilder.php b/plugins/woocommerce/tests/php/src/Blocks/BlockTypes/ProductCollection/QueryBuilder.php
index 642e635be18..51461d96aab 100644
--- a/plugins/woocommerce/tests/php/src/Blocks/BlockTypes/ProductCollection/QueryBuilder.php
+++ b/plugins/woocommerce/tests/php/src/Blocks/BlockTypes/ProductCollection/QueryBuilder.php
@@ -76,6 +76,35 @@ class QueryBuilder extends \WP_UnitTestCase {
 		delete_transient( 'wc_products_onsale' );
 	}

+	/**
+	 * @testdox Should return no products when no products are on sale.
+	 */
+	public function test_merging_on_sale_queries_without_products_on_sale(): void {
+		set_transient( 'wc_products_onsale', array(), DAY_IN_SECONDS * 30 );
+
+		$parsed_block                                        = Utils::get_base_parsed_block();
+		$parsed_block['attrs']['query']['woocommerceOnSale'] = true;
+
+		$merged_query = Utils::initialize_merged_query( $this->block_instance, $parsed_block );
+
+		$this->assertSame( array( 0 ), $merged_query['post__in'], 'The on sale filter should match no products when none are on sale.' );
+	}
+
+	/**
+	 * @testdox Should return no products when no products are on sale and hand-picked products are set.
+	 */
+	public function test_merging_on_sale_queries_without_products_on_sale_and_handpicked_products(): void {
+		set_transient( 'wc_products_onsale', array(), DAY_IN_SECONDS * 30 );
+
+		$parsed_block                                        = Utils::get_base_parsed_block();
+		$parsed_block['attrs']['query']['woocommerceOnSale'] = true;
+		$parsed_block['attrs']['query']['woocommerceHandPickedProducts'] = array( 1, 2 );
+
+		$merged_query = Utils::initialize_merged_query( $this->block_instance, $parsed_block );
+
+		$this->assertSame( array( 0 ), $merged_query['post__in'], 'Hand-picked products should not be returned when none are on sale.' );
+	}
+
 	/**
 	 * Test merging stock status queries.
 	 */
@@ -739,7 +768,7 @@ class QueryBuilder extends \WP_UnitTestCase {

 		$merged_query = Utils::initialize_merged_query( $this->block_instance, $parsed_block );

-		$this->assertEquals( array( -1 ), $merged_query['post__in'] );
+		$this->assertEquals( array( 0 ), $merged_query['post__in'] );
 	}

 	/**