Commit 47ffbcf6fd0 for woocommerce

commit 47ffbcf6fd06da303907a232063a992e7c36ec26
Author: Tung Du <dinhtungdu@gmail.com>
Date:   Tue Oct 6 14:39:10 2026 +0700

    Fix product filters with unresolved taxonomy groups (#69403)

    * Fix product filters with unresolved taxonomy groups

    * Add changelog for product filter taxonomy fix

    * Remove stale product filter PHPStan baseline entry

diff --git a/plugins/woocommerce/changelog/fix-product-filter-taxonomy-groups b/plugins/woocommerce/changelog/fix-product-filter-taxonomy-groups
new file mode 100644
index 00000000000..0d06bf0355f
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-product-filter-taxonomy-groups
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Return no products when a selected category or tag filter has no valid values.
diff --git a/plugins/woocommerce/phpstan-baseline.neon b/plugins/woocommerce/phpstan-baseline.neon
index de5852b1cfc..09642122ad3 100644
--- a/plugins/woocommerce/phpstan-baseline.neon
+++ b/plugins/woocommerce/phpstan-baseline.neon
@@ -62448,12 +62448,6 @@ parameters:
 			count: 1
 			path: src/Internal/ProductFilters/QueryClauses.php

-		-
-			message: '#^Variable \$term_ids in empty\(\) always exists and is not falsy\.$#'
-			identifier: empty.variable
-			count: 1
-			path: src/Internal/ProductFilters/QueryClauses.php
-
 		-
 			message: '#^Method Automattic\\WooCommerce\\Internal\\ProductFilters\\TaxonomyHierarchyData\:\:build_term_tree\(\) has no return type specified\.$#'
 			identifier: missingType.return
diff --git a/plugins/woocommerce/src/Internal/ProductFilters/QueryClauses.php b/plugins/woocommerce/src/Internal/ProductFilters/QueryClauses.php
index b9d13a099c6..e5fc122235b 100644
--- a/plugins/woocommerce/src/Internal/ProductFilters/QueryClauses.php
+++ b/plugins/woocommerce/src/Internal/ProductFilters/QueryClauses.php
@@ -450,8 +450,11 @@ class QueryClauses implements QueryClausesGenerator, MainQueryClausesGenerator {
 			$term_ids_by_taxonomy[ $term->taxonomy ][] = $term->term_id;
 		}

-		foreach ( $term_ids_by_taxonomy as $taxonomy => $term_ids ) {
+		foreach ( array_keys( $chosen_taxonomies ) as $taxonomy ) {
+			$term_ids = $term_ids_by_taxonomy[ $taxonomy ] ?? array();
+
 			if ( empty( $term_ids ) ) {
+				$tax_queries[] = '0=1';
 				continue;
 			}

@@ -501,11 +504,7 @@ class QueryClauses implements QueryClausesGenerator, MainQueryClausesGenerator {
 			);
 		}

-		if ( ! empty( $tax_queries ) ) {
-			$args['where'] .= ' AND (' . implode( ' AND ', $tax_queries ) . ')';
-		} else {
-			$args['where'] .= ' AND 1=0';
-		}
+		$args['where'] .= ' AND (' . implode( ' AND ', $tax_queries ) . ')';

 		return $args;
 	}
diff --git a/plugins/woocommerce/tests/php/src/Internal/ProductFilters/QueryClausesTest.php b/plugins/woocommerce/tests/php/src/Internal/ProductFilters/QueryClausesTest.php
index 0256af2a4a5..d0a3893523e 100644
--- a/plugins/woocommerce/tests/php/src/Internal/ProductFilters/QueryClausesTest.php
+++ b/plugins/woocommerce/tests/php/src/Internal/ProductFilters/QueryClausesTest.php
@@ -275,6 +275,7 @@ class QueryClausesTest extends AbstractProductFiltersTest {
 	 * @testWith ["product_cat", ["cat-1"]]
 	 *           ["product_cat", ["cat-2"]]
 	 *           ["product_cat", ["cat-1", "cat-2"]]
+	 *           ["product_cat", ["cat-1", "not-exist-slug"]]
 	 *           ["product_tag", ["tag-1"]]
 	 *           ["product_tag", ["tag-2", "tag-3"]]
 	 *
@@ -320,9 +321,9 @@ class QueryClausesTest extends AbstractProductFiltersTest {
 	}

 	/**
-	 * @testdox Taxonomy filtering ignores selected taxonomies with no matching term.
+	 * @testdox Taxonomy filtering returns no products when a selected taxonomy has no matching terms.
 	 */
-	public function test_taxonomy_clauses_ignore_taxonomies_without_matching_terms(): void {
+	public function test_taxonomy_clauses_fail_closed_when_a_taxonomy_has_no_matching_terms(): void {
 		$chosen_taxonomies = array(
 			'product_cat' => array( 'cat-1' ),
 			'product_tag' => array( 'not-exist-slug' ),
@@ -335,7 +336,26 @@ class QueryClausesTest extends AbstractProductFiltersTest {
 		$received_products = $this->get_data_from_products_array( wc_get_products( array() ) );
 		remove_filter( 'posts_clauses', $filter_callback );

-		$this->assertEqualsCanonicalizing( array( 'Product 1', 'Product 4', 'Product 5', 'Product 6' ), $received_products );
+		$this->assertSame( array(), $received_products );
+	}
+
+	/**
+	 * @testdox Taxonomy filtering ignores unknown terms when that taxonomy also has a matching term.
+	 */
+	public function test_taxonomy_clauses_ignore_unknown_terms_in_a_matching_taxonomy(): void {
+		$chosen_taxonomies = array(
+			'product_cat' => array( 'cat-1' ),
+			'product_tag' => array( 'tag-1', 'not-exist-slug' ),
+		);
+		$filter_callback   = function ( $args ) use ( $chosen_taxonomies ) {
+			return $this->sut->add_taxonomy_clauses( $args, $chosen_taxonomies );
+		};
+
+		add_filter( 'posts_clauses', $filter_callback );
+		$received_products = $this->get_data_from_products_array( wc_get_products( array() ) );
+		remove_filter( 'posts_clauses', $filter_callback );
+
+		$this->assertEqualsCanonicalizing( array( 'Product 1', 'Product 4' ), $received_products );
 	}

 	/**