Commit 750d90b2985 for woocommerce

commit 750d90b2985cc226ae2075043189c48a9f881b91
Author: Mike Jolley <mike.jolley@me.com>
Date:   Mon Oct 26 11:05:01 2026 +0000

    Fix Store API filtering by taxonomies shared with other post types (#70124)

    * Fix Store API filtering by taxonomies shared with other post types

    ProductQuery built its _unstable_tax_* allowlist with get_taxonomies()
    filtered on object_type, which only matches taxonomies registered for
    exactly ['product']. Use get_object_taxonomies() so taxonomies also
    registered for variations or other post types can be filtered on. The
    'objects' output keeps the taxonomy-name keys the filter loop relies on.

    Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

    * Fix Store API variation filtering by taxonomies registered for variations

    ---------

    Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

diff --git a/plugins/woocommerce/changelog/44298-store-api-product-taxonomies b/plugins/woocommerce/changelog/44298-store-api-product-taxonomies
new file mode 100644
index 00000000000..7e594bf4139
--- /dev/null
+++ b/plugins/woocommerce/changelog/44298-store-api-product-taxonomies
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Fix Store API product filtering by custom taxonomies that are also registered for variations or other post types.
diff --git a/plugins/woocommerce/src/StoreApi/Utilities/ProductQuery.php b/plugins/woocommerce/src/StoreApi/Utilities/ProductQuery.php
index 8e7ba1ad272..18e49b1425e 100644
--- a/plugins/woocommerce/src/StoreApi/Utilities/ProductQuery.php
+++ b/plugins/woocommerce/src/StoreApi/Utilities/ProductQuery.php
@@ -110,11 +110,12 @@ class ProductQuery implements QueryClausesGenerator {
 		// Gets all registered product taxonomies and prefixes them with `tax_`.
 		// This is needed to avoid situations where a user registers a new product taxonomy with the same name as default field.
 		// eg an `sku` taxonomy will be mapped to `tax_sku`.
+		// The `objects` output is used because it is keyed by taxonomy name, which the loop below relies on.
 		$all_product_taxonomies = array_map(
-			function ( $value ) {
-				return '_unstable_tax_' . $value;
+			function ( $taxonomy ) {
+				return '_unstable_tax_' . $taxonomy->name;
 			},
-			get_taxonomies( array( 'object_type' => array( 'product' ) ), 'names' )
+			get_object_taxonomies( 'product', 'objects' )
 		);

 		// Map between taxonomy name and arg key.
@@ -177,8 +178,19 @@ class ProductQuery implements QueryClausesGenerator {
 		if ( ! empty( $tax_query ) && 'product_variation' !== $args['post_type'] ) {
 			$args['tax_query'] = $tax_query; // phpcs:ignore
 		} else {
-			// For product_variations we need to convert the tax_query to a meta_query.
-			$args['meta_query'] = $this->convert_tax_query_to_meta_query( $tax_query ); // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
+			// Taxonomies registered for variations are queried directly. The rest are stored on variations as attribute meta.
+			$variation_tax_query = array_filter(
+				$tax_query,
+				function ( $tax_query_item ) {
+					return isset( $tax_query_item['taxonomy'] ) && is_object_in_taxonomy( 'product_variation', $tax_query_item['taxonomy'] );
+				}
+			);
+
+			if ( ! empty( $variation_tax_query ) ) {
+				$args['tax_query'] = array_values( $variation_tax_query ); // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
+			}
+
+			$args['meta_query'] = $this->convert_tax_query_to_meta_query( array_diff_key( $tax_query, $variation_tax_query ) ); // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
 		}

 		// Filter featured.
diff --git a/plugins/woocommerce/tests/php/src/Blocks/StoreApi/Utilities/ProductQueryTest.php b/plugins/woocommerce/tests/php/src/Blocks/StoreApi/Utilities/ProductQueryTest.php
index 7d3dac1e3e8..3fa9f535d93 100644
--- a/plugins/woocommerce/tests/php/src/Blocks/StoreApi/Utilities/ProductQueryTest.php
+++ b/plugins/woocommerce/tests/php/src/Blocks/StoreApi/Utilities/ProductQueryTest.php
@@ -378,6 +378,60 @@ class ProductQueryTest extends \WC_Unit_Test_Case {
 		$this->assertSame( array( 0 ), $args['post__in'] );
 	}

+	/**
+	 * @testdox Taxonomy filters work for product taxonomies that are also registered for other post types.
+	 */
+	public function test_taxonomy_filter_supports_taxonomy_registered_for_multiple_object_types(): void {
+		$taxonomy = 'test_multi_object_tax';
+		register_taxonomy( $taxonomy, array( 'product', 'product_variation' ) );
+
+		try {
+			$fixtures      = new FixtureData();
+			$alpha_product = $fixtures->get_simple_product( array( 'name' => 'Alpha product' ) );
+			$beta_product  = $fixtures->get_simple_product( array( 'name' => 'Beta product' ) );
+			wp_set_object_terms( $alpha_product->get_id(), 'alpha', $taxonomy );
+			wp_set_object_terms( $beta_product->get_id(), 'beta', $taxonomy );
+
+			$request = new \WP_REST_Request( 'GET', '/wc/store/v1/products' );
+			$request->set_param( 'include', array( $alpha_product->get_id(), $beta_product->get_id() ) );
+			$request->set_param( '_unstable_tax_' . $taxonomy, array( 'alpha' ) );
+
+			$results = $this->product_query->get_results( $request );
+		} finally {
+			unregister_taxonomy( $taxonomy );
+		}
+
+		$this->assertSame( array( $alpha_product->get_id() ), $results['results'], 'Only the product assigned the requested term should be returned.' );
+	}
+
+	/**
+	 * @testdox Taxonomy filters on variations use terms for taxonomies that are registered for variations.
+	 */
+	public function test_variation_taxonomy_filter_supports_taxonomy_registered_for_variations(): void {
+		$taxonomy = 'test_multi_object_tax';
+		register_taxonomy( $taxonomy, array( 'product', 'product_variation' ) );
+
+		try {
+			$fixtures        = new FixtureData();
+			$parent_product  = $fixtures->get_variable_product( array() );
+			$alpha_variation = $fixtures->get_variation_product( $parent_product->get_id() );
+			$beta_variation  = $fixtures->get_variation_product( $parent_product->get_id() );
+			wp_set_object_terms( $alpha_variation->get_id(), 'alpha', $taxonomy );
+			wp_set_object_terms( $beta_variation->get_id(), 'beta', $taxonomy );
+
+			$request = new \WP_REST_Request( 'GET', '/wc/store/v1/products' );
+			$request->set_param( 'type', 'variation' );
+			$request->set_param( 'include', array( $alpha_variation->get_id(), $beta_variation->get_id() ) );
+			$request->set_param( '_unstable_tax_' . $taxonomy, array( 'alpha' ) );
+
+			$results = $this->product_query->get_results( $request );
+		} finally {
+			unregister_taxonomy( $taxonomy );
+		}
+
+		$this->assertSame( array( $alpha_variation->get_id() ), $results['results'], 'Only the variation assigned the requested term should be returned.' );
+	}
+
 	/**
 	 * Get product IDs matched by a Store API slug filter.
 	 *