Commit a11eadff11a for woocommerce

commit a11eadff11a9fabf5efe621756057031a6d2abc9
Author: Mike Jolley <mike.jolley@me.com>
Date:   Mon Oct 26 15:32:41 2026 +0000

    Fix Store API attribute filters for variations (#70232)

    * Keep Store API variation attribute filters on attribute meta

    Variations store attribute selections as attribute_* meta, never as
    terms. If an extension registers a pa_* taxonomy for product_variation,
    the routing added in #70124 sent the filter to a term query, so "in"
    filters matched nothing and "not_in" filters matched everything.

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

    * Add changelog entry for variation attribute filter routing fix

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

    ---------

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

diff --git a/plugins/woocommerce/changelog/fix-variation-attribute-filter-routing b/plugins/woocommerce/changelog/fix-variation-attribute-filter-routing
new file mode 100644
index 00000000000..00a55d5125b
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-variation-attribute-filter-routing
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+Comment: Keep variation attribute filters on attribute meta; follow-up to an unreleased Store API fix.
+
diff --git a/plugins/woocommerce/src/StoreApi/Utilities/ProductQuery.php b/plugins/woocommerce/src/StoreApi/Utilities/ProductQuery.php
index 18e49b1425e..96e446df11d 100644
--- a/plugins/woocommerce/src/StoreApi/Utilities/ProductQuery.php
+++ b/plugins/woocommerce/src/StoreApi/Utilities/ProductQuery.php
@@ -178,11 +178,13 @@ class ProductQuery implements QueryClausesGenerator {
 		if ( ! empty( $tax_query ) && 'product_variation' !== $args['post_type'] ) {
 			$args['tax_query'] = $tax_query; // phpcs:ignore
 		} else {
-			// Taxonomies registered for variations are queried directly. The rest are stored on variations as attribute meta.
+			// Taxonomies registered for variations are queried directly. Attributes are always stored on variations as meta, even when registered for them.
 			$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'] );
+					return isset( $tax_query_item['taxonomy'] )
+						&& ! taxonomy_is_product_attribute( $tax_query_item['taxonomy'] )
+						&& is_object_in_taxonomy( 'product_variation', $tax_query_item['taxonomy'] );
 				}
 			);

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 3fa9f535d93..c80cf0a8058 100644
--- a/plugins/woocommerce/tests/php/src/Blocks/StoreApi/Utilities/ProductQueryTest.php
+++ b/plugins/woocommerce/tests/php/src/Blocks/StoreApi/Utilities/ProductQueryTest.php
@@ -432,6 +432,37 @@ class ProductQueryTest extends \WC_Unit_Test_Case {
 		$this->assertSame( array( $alpha_variation->get_id() ), $results['results'], 'Only the variation assigned the requested term should be returned.' );
 	}

+	/**
+	 * @testdox Attribute filters on variations use attribute meta even when the attribute taxonomy is registered for variations.
+	 */
+	public function test_variation_attribute_filter_uses_meta_when_attribute_is_registered_for_variations(): void {
+		$fixtures  = new FixtureData();
+		$attribute = $fixtures->get_product_attribute( 'variation-registered', array( 'red', 'blue' ) );
+		$taxonomy  = $attribute['attribute_taxonomy'];
+		register_taxonomy_for_object_type( $taxonomy, 'product_variation' );
+
+		$parent_product = $fixtures->get_variable_product( array(), array( $attribute ) );
+		$red_variation  = $fixtures->get_variation_product( $parent_product->get_id(), array( $taxonomy => 'red' ) );
+		$blue_variation = $fixtures->get_variation_product( $parent_product->get_id(), array( $taxonomy => 'blue' ) );
+
+		$request = new \WP_REST_Request( 'GET', '/wc/store/v1/products' );
+		$request->set_param( 'type', 'variation' );
+		$request->set_param( 'include', array( $red_variation->get_id(), $blue_variation->get_id() ) );
+		$request->set_param(
+			'attributes',
+			array(
+				array(
+					'attribute' => $taxonomy,
+					'slug'      => array( 'red' ),
+				),
+			)
+		);
+
+		$results = $this->product_query->get_results( $request );
+
+		$this->assertSame( array( $red_variation->get_id() ), $results['results'], 'Only the variation with the requested attribute value should be returned.' );
+	}
+
 	/**
 	 * Get product IDs matched by a Store API slug filter.
 	 *