Commit 86f11e2027 for woocommerce
commit 86f11e202717b852a027d5201c283471b42e7528
Author: Neil Carlo Sucuangco <necafasu@gmail.com>
Date: Tue Jan 20 21:07:40 2026 +0800
Fix: Order endpoint returns empty values for 'Any' variation attributes (#62476)
* Fix: Order endpoint returns empty values for 'Any' variation attributes
* Add changefile(s) from automation for the following project(s): woocommerce
* Update OrderItemSchema.php
* Update OrderItemSchema.php
* yoda conditional to satisfy lint
---------
Co-authored-by: github-actions <github-actions@github.com>
Co-authored-by: Mike Jolley <mike.jolley@me.com>
diff --git a/plugins/woocommerce/changelog/62476-fix-order-variation-attributes-any b/plugins/woocommerce/changelog/62476-fix-order-variation-attributes-any
new file mode 100644
index 0000000000..7b2e5de5b5
--- /dev/null
+++ b/plugins/woocommerce/changelog/62476-fix-order-variation-attributes-any
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Fix Store API order endpoint returning empty values for "Any" variation attributes. Retrieves customer selections from order metadata to match cart endpoint behavior.
\ No newline at end of file
diff --git a/plugins/woocommerce/src/StoreApi/Schemas/V1/OrderItemSchema.php b/plugins/woocommerce/src/StoreApi/Schemas/V1/OrderItemSchema.php
index 03cef4899b..9941817d05 100644
--- a/plugins/woocommerce/src/StoreApi/Schemas/V1/OrderItemSchema.php
+++ b/plugins/woocommerce/src/StoreApi/Schemas/V1/OrderItemSchema.php
@@ -16,6 +16,13 @@ class OrderItemSchema extends ItemSchema {
*/
protected $title = 'order_item';
+ /**
+ * Cache for parent product attributes.
+ *
+ * @var array|null
+ */
+ private $cached_parent_attributes = null;
+
/**
* The schema item identifier.
*
@@ -30,6 +37,8 @@ class OrderItemSchema extends ItemSchema {
* @return array
*/
public function get_item_response( $order_item ) {
+ $this->cached_parent_attributes = null;
+
$order = $order_item->get_order();
$product = $order_item->get_product();
@@ -72,10 +81,11 @@ class OrderItemSchema extends ItemSchema {
$product_properties['prices'] = $this->prepare_product_price_response( $product, get_option( 'woocommerce_tax_display_cart' ) );
$product_properties['sold_individually'] = $product->is_sold_individually();
$product_properties['images'] = $this->get_images( $product );
+
// Only include variation data for product variations, not simple products.
// This is consistent with the cart endpoint behavior.
if ( $product instanceof \WC_Product_Variation ) {
- $product_properties['variation'] = $this->format_variation_data( $product->get_attributes(), $product );
+ $product_properties['variation'] = $this->get_variation_data_from_order_item( $order_item, $product );
}
}
@@ -121,4 +131,85 @@ class OrderItemSchema extends ItemSchema {
'line_total_tax' => $this->prepare_money_response( $order_item->get_total_tax(), wc_get_price_decimals() ),
];
}
+
+ /**
+ * Get variation data from order item metadata.
+ *
+ * Gets the customer's actual attribute choices from the order metadata.
+ * This fixes variations set to "Any" returning empty values.
+ *
+ * @param \WC_Order_Item_Product $order_item Order item instance.
+ * @param \WC_Product $product Product instance.
+ * @return array Formatted variation data.
+ */
+ protected function get_variation_data_from_order_item( $order_item, $product ) {
+ $variation_data = array();
+ $meta_data = $order_item->get_meta_data();
+
+ $parent_attributes = $this->get_parent_product_attributes( $product );
+
+ foreach ( $meta_data as $meta ) {
+ $meta_key = $meta->key;
+ $meta_value = $meta->value;
+
+ if ( empty( $meta_key ) || ! is_scalar( $meta_value ) || '' === $meta_value || strpos( $meta_key, '_' ) === 0 ) {
+ continue;
+ }
+
+ $is_variation_attribute = false;
+ $normalized_key = $meta_key;
+
+ if ( strpos( $meta_key, 'attribute_' ) === 0 ) {
+ $normalized_key = substr( $meta_key, strlen( 'attribute_' ) );
+ }
+
+ if ( strpos( $normalized_key, 'pa_' ) === 0 ) {
+ $is_variation_attribute = true;
+ } else {
+ foreach ( $parent_attributes as $attribute ) {
+ if ( strtolower( $attribute->get_name() ) === strtolower( $normalized_key ) ) {
+ $is_variation_attribute = true;
+ break;
+ }
+ }
+ }
+
+ if ( $is_variation_attribute ) {
+ $variation_data[ wc_variation_attribute_name( $normalized_key ) ] = $meta_value;
+ }
+ }
+
+ return $this->format_variation_data( $variation_data, $product );
+ }
+
+ /**
+ * Get parent product attributes.
+ *
+ * Cached to avoid multiple DB lookups when processing multiple meta items.
+ *
+ * @param \WC_Product $product Product instance.
+ * @return array Array of WC_Product_Attribute objects.
+ *
+ * @since 10.5.0
+ */
+ protected function get_parent_product_attributes( $product ) {
+ if ( null !== $this->cached_parent_attributes ) {
+ return $this->cached_parent_attributes;
+ }
+
+ $this->cached_parent_attributes = array();
+
+ if ( ! $product->get_parent_id() ) {
+ return $this->cached_parent_attributes;
+ }
+
+ $parent_product = wc_get_product( $product->get_parent_id() );
+ if ( ! $parent_product instanceof \WC_Product ) {
+ return $this->cached_parent_attributes;
+ }
+
+ $this->cached_parent_attributes = $parent_product->get_attributes();
+
+ return $this->cached_parent_attributes;
+ }
}
diff --git a/plugins/woocommerce/tests/php/src/Blocks/StoreApi/Schemas/OrderItemSchemaTest.php b/plugins/woocommerce/tests/php/src/Blocks/StoreApi/Schemas/OrderItemSchemaTest.php
index 21bd272146..0d88bad611 100644
--- a/plugins/woocommerce/tests/php/src/Blocks/StoreApi/Schemas/OrderItemSchemaTest.php
+++ b/plugins/woocommerce/tests/php/src/Blocks/StoreApi/Schemas/OrderItemSchemaTest.php
@@ -240,4 +240,181 @@ class OrderItemSchemaTest extends TestCase {
$order->delete( true );
$variable_product->delete( true );
}
+
+ /**
+ * Test that variations with "Any" attributes return user-selected values.
+ */
+ public function test_get_item_response_any_variation_returns_user_selected_values(): void {
+ $variable_product = new \WC_Product_Variable();
+ $variable_product->set_name( 'Test Variable Product' );
+
+ $attribute = new \WC_Product_Attribute();
+ $attribute->set_id( 0 );
+ $attribute->set_name( 'pa_size' );
+ $attribute->set_options( array( 'small', 'medium', 'large' ) );
+ $attribute->set_visible( true );
+ $attribute->set_variation( true );
+
+ $variable_product->set_attributes( array( $attribute ) );
+ $variable_product->save();
+
+ $variation = new \WC_Product_Variation();
+ $variation->set_parent_id( $variable_product->get_id() );
+ $variation->set_attributes( array( 'pa_size' => '' ) );
+ $variation->set_regular_price( '10.00' );
+ $variation->save();
+
+ $order = new \WC_Order();
+ $item = new \WC_Order_Item_Product();
+ $item->set_props(
+ array(
+ 'product_id' => $variable_product->get_id(),
+ 'variation_id' => $variation->get_id(),
+ 'quantity' => 1,
+ 'subtotal' => '10.00',
+ 'total' => '10.00',
+ 'name' => $variation->get_name(),
+ )
+ );
+
+ $item->add_meta_data( 'pa_size', 'large', true );
+ $order->add_item( $item );
+ $order->save();
+
+ $result = $this->sut->get_item_response( $item );
+
+ $this->assertArrayHasKey( 'variation', $result );
+ $this->assertIsArray( $result['variation'] );
+ $this->assertNotEmpty( $result['variation'] );
+ $this->assertCount( 1, $result['variation'] );
+
+ $variation_data = $result['variation'][0];
+ $this->assertArrayHasKey( 'raw_attribute', $variation_data );
+ $this->assertArrayHasKey( 'attribute', $variation_data );
+ $this->assertArrayHasKey( 'value', $variation_data );
+ $this->assertEquals( 'attribute_pa_size', $variation_data['raw_attribute'] );
+ $this->assertEquals( 'size', $variation_data['attribute'] );
+ $this->assertEquals( 'large', $variation_data['value'] );
+
+ $order->delete( true );
+ $variation->delete( true );
+ $variable_product->delete( true );
+ }
+
+ /**
+ * Test variations with mixed global and custom "Any" attributes.
+ */
+ public function test_get_item_response_any_variation_with_mixed_attributes(): void {
+ $variable_product = new \WC_Product_Variable();
+ $variable_product->set_name( 'Test Variable Product with Mixed Attributes' );
+
+ $global_attribute = new \WC_Product_Attribute();
+ $global_attribute->set_id( 0 );
+ $global_attribute->set_name( 'pa_size' );
+ $global_attribute->set_options( array( 'small', 'medium', 'large' ) );
+ $global_attribute->set_visible( true );
+ $global_attribute->set_variation( true );
+
+ $custom_attribute = new \WC_Product_Attribute();
+ $custom_attribute->set_id( 0 );
+ $custom_attribute->set_name( 'color' );
+ $custom_attribute->set_options( array( 'red', 'blue', 'green' ) );
+ $custom_attribute->set_visible( true );
+ $custom_attribute->set_variation( true );
+
+ $variable_product->set_attributes( array( $global_attribute, $custom_attribute ) );
+ $variable_product->save();
+
+ $variation = new \WC_Product_Variation();
+ $variation->set_parent_id( $variable_product->get_id() );
+ $variation->set_attributes(
+ array(
+ 'pa_size' => '',
+ 'color' => '',
+ )
+ );
+ $variation->set_regular_price( '15.00' );
+ $variation->save();
+
+ $order = new \WC_Order();
+ $item = new \WC_Order_Item_Product();
+ $item->set_props(
+ array(
+ 'product_id' => $variable_product->get_id(),
+ 'variation_id' => $variation->get_id(),
+ 'quantity' => 1,
+ 'subtotal' => '15.00',
+ 'total' => '15.00',
+ 'name' => $variation->get_name(),
+ )
+ );
+
+ $item->add_meta_data( 'pa_size', 'large', true );
+ $item->add_meta_data( 'color', 'blue', true );
+ $order->add_item( $item );
+ $order->save();
+
+ $result = $this->sut->get_item_response( $item );
+
+ $this->assertArrayHasKey( 'variation', $result );
+ $this->assertIsArray( $result['variation'] );
+ $this->assertCount( 2, $result['variation'] );
+
+ $global_attr = null;
+ $custom_attr = null;
+ foreach ( $result['variation'] as $attr ) {
+ if ( 'attribute_pa_size' === $attr['raw_attribute'] ) {
+ $global_attr = $attr;
+ } elseif ( 'attribute_color' === $attr['raw_attribute'] ) {
+ $custom_attr = $attr;
+ }
+ }
+
+ $this->assertNotNull( $global_attr );
+ $this->assertEquals( 'attribute_pa_size', $global_attr['raw_attribute'] );
+ $this->assertEquals( 'size', $global_attr['attribute'] );
+ $this->assertEquals( 'large', $global_attr['value'] );
+
+ $this->assertNotNull( $custom_attr );
+ $this->assertEquals( 'attribute_color', $custom_attr['raw_attribute'] );
+ $this->assertEquals( 'color', $custom_attr['attribute'] );
+ $this->assertEquals( 'blue', $custom_attr['value'] );
+
+ $order->delete( true );
+ $variation->delete( true );
+ $variable_product->delete( true );
+ }
+
+ /**
+ * Test that specific (non-Any) variations still work correctly.
+ */
+ public function test_get_item_response_specific_variation_returns_correct_values(): void {
+ $variable_product = \WC_Helper_Product::create_variation_product();
+ $variations = $variable_product->get_children();
+ $variation = wc_get_product( $variations[0] );
+
+ $order = new \WC_Order();
+ $order->add_product( $variation, 1 );
+ $order->save();
+
+ $items = $order->get_items();
+ $item = reset( $items );
+
+ $result = $this->sut->get_item_response( $item );
+
+ $this->assertArrayHasKey( 'variation', $result );
+ $this->assertIsArray( $result['variation'] );
+ $this->assertNotEmpty( $result['variation'] );
+
+ foreach ( $result['variation'] as $attr ) {
+ $this->assertArrayHasKey( 'raw_attribute', $attr );
+ $this->assertArrayHasKey( 'attribute', $attr );
+ $this->assertArrayHasKey( 'value', $attr );
+ $this->assertStringStartsWith( 'attribute_', $attr['raw_attribute'] );
+ $this->assertNotEmpty( $attr['value'] );
+ }
+
+ $order->delete( true );
+ $variable_product->delete( true );
+ }
}