Commit 580b090e365 for woocommerce

commit 580b090e36514ffc4ee91e63613e6da62b3bfdfd
Author: Puneet Dixit <puneetdixit4321@gmail.com>
Date:   Sat Jul 11 08:59:55 2026 +0530

    Fix order meta_data schema to use a standard type union instead of 'mixed' (#65513)

    Replaces the non-standard JSON Schema 'mixed' type on every order meta_data 'value' field (and line item 'display_value') with the explicit union array( 'null', 'object', 'string', 'number', 'boolean', 'integer', 'array' ), and marks the line item display fields read-only. This lets REST order updates accept round-tripped line item meta display values with complex data, and makes the published orders schema standards-compliant. Behavior-preserving: the write/validation path already normalized 'mixed' to this exact union.

    Closes #32038.

diff --git a/plugins/woocommerce/changelog/fix-32038-line-item-meta-display-values b/plugins/woocommerce/changelog/fix-32038-line-item-meta-display-values
new file mode 100644
index 00000000000..5ab9012f4f7
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-32038-line-item-meta-display-values
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Allow REST order updates to accept round-tripped line item meta display values with complex data.
diff --git a/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-orders-v2-controller.php b/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-orders-v2-controller.php
index 80385dbf660..fa8bde2a2a3 100644
--- a/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-orders-v2-controller.php
+++ b/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-orders-v2-controller.php
@@ -1480,7 +1480,7 @@ class WC_REST_Orders_V2_Controller extends WC_REST_CRUD_Controller {
 							),
 							'value' => array(
 								'description' => __( 'Meta value.', 'woocommerce' ),
-								'type'        => 'mixed',
+								'type'        => array( 'null', 'object', 'string', 'number', 'boolean', 'integer', 'array' ),
 								'context'     => array( 'view', 'edit' ),
 							),
 						),
@@ -1597,18 +1597,20 @@ class WC_REST_Orders_V2_Controller extends WC_REST_CRUD_Controller {
 										),
 										'value'         => array(
 											'description' => __( 'Meta value.', 'woocommerce' ),
-											'type'        => 'mixed',
+											'type'        => array( 'null', 'object', 'string', 'number', 'boolean', 'integer', 'array' ),
 											'context'     => array( 'view', 'edit' ),
 										),
 										'display_key'   => array(
 											'description' => __( 'Meta key for UI display.', 'woocommerce' ),
 											'type'        => 'string',
 											'context'     => array( 'view', 'edit' ),
+											'readonly'    => true,
 										),
 										'display_value' => array(
-											'description' => __( 'Meta value for UI display.', 'woocommerce' ),
-											'type'        => 'string',
+											'description' => __( 'Meta value for UI display. May be a string or, when mirroring a complex meta value, an array or object.', 'woocommerce' ),
+											'type'        => array( 'null', 'object', 'string', 'number', 'boolean', 'integer', 'array' ),
 											'context'     => array( 'view', 'edit' ),
+											'readonly'    => true,
 										),
 									),
 								),
@@ -1723,7 +1725,7 @@ class WC_REST_Orders_V2_Controller extends WC_REST_CRUD_Controller {
 										),
 										'value' => array(
 											'description' => __( 'Meta value.', 'woocommerce' ),
-											'type'        => 'mixed',
+											'type'        => array( 'null', 'object', 'string', 'number', 'boolean', 'integer', 'array' ),
 											'context'     => array( 'view', 'edit' ),
 										),
 									),
@@ -1814,7 +1816,7 @@ class WC_REST_Orders_V2_Controller extends WC_REST_CRUD_Controller {
 										),
 										'value' => array(
 											'description' => __( 'Meta value.', 'woocommerce' ),
-											'type'        => 'mixed',
+											'type'        => array( 'null', 'object', 'string', 'number', 'boolean', 'integer', 'array' ),
 											'context'     => array( 'view', 'edit' ),
 										),
 									),
@@ -1912,7 +1914,7 @@ class WC_REST_Orders_V2_Controller extends WC_REST_CRUD_Controller {
 										),
 										'value' => array(
 											'description' => __( 'Meta value.', 'woocommerce' ),
-											'type'        => 'mixed',
+											'type'        => array( 'null', 'object', 'string', 'number', 'boolean', 'integer', 'array' ),
 											'context'     => array( 'view', 'edit' ),
 										),
 									),
@@ -1988,7 +1990,7 @@ class WC_REST_Orders_V2_Controller extends WC_REST_CRUD_Controller {
 										),
 										'value' => array(
 											'description' => __( 'Meta value.', 'woocommerce' ),
-											'type'        => 'mixed',
+											'type'        => array( 'null', 'object', 'string', 'number', 'boolean', 'integer', 'array' ),
 											'context'     => array( 'view', 'edit' ),
 										),
 									),
diff --git a/plugins/woocommerce/tests/php/includes/rest-api/Controllers/Version3/class-wc-rest-orders-controller-tests.php b/plugins/woocommerce/tests/php/includes/rest-api/Controllers/Version3/class-wc-rest-orders-controller-tests.php
index a0958cc8f95..ccb31880ea0 100644
--- a/plugins/woocommerce/tests/php/includes/rest-api/Controllers/Version3/class-wc-rest-orders-controller-tests.php
+++ b/plugins/woocommerce/tests/php/includes/rest-api/Controllers/Version3/class-wc-rest-orders-controller-tests.php
@@ -1023,4 +1023,98 @@ class WC_REST_Orders_Controller_Tests extends WC_REST_Unit_Test_Case {

 		$this->assert_incomplete_meta_data_handled_correctly( wc_get_order( $order->get_id() ) );
 	}
+
+	/**
+	 * @testdox Updating an order accepts round-tripped line item meta_data display values with complex values.
+	 */
+	public function test_update_line_item_meta_data_ignores_display_values_with_complex_values(): void {
+		$product            = WC_Helper_Product::create_simple_product();
+		$complex_meta_value = array(
+			array(
+				'guid'      => 'https://example.com/wp-content/uploads/2022/03/upload.jpg',
+				'file_type' => 'image/jpeg',
+				'file_name' => 'upload.jpg',
+				'title'     => 'upload',
+				'key'       => 'file-key',
+			),
+		);
+
+		$request = new WP_REST_Request( 'POST', '/wc/v3/orders' );
+		$request->set_body_params(
+			array(
+				'line_items' => array(
+					array(
+						'product_id' => $product->get_id(),
+						'quantity'   => 1,
+						'meta_data'  => array(
+							array(
+								'key'   => '_file_upload_data',
+								'value' => $complex_meta_value,
+							),
+						),
+					),
+				),
+			)
+		);
+
+		$response = $this->server->dispatch( $request );
+		$this->assertEquals( 201, $response->get_status() );
+
+		$response_data  = $response->get_data();
+		$line_item_data = $response_data['line_items'][0];
+		$meta_data      = $line_item_data['meta_data'][0];
+		$this->assertIsArray( $meta_data['display_value'] );
+
+		$request = new WP_REST_Request( 'PUT', '/wc/v3/orders/' . $response_data['id'] );
+		$request->set_header( 'content-type', 'application/json' );
+		$request->set_body(
+			wp_json_encode(
+				array(
+					'status'     => OrderStatus::PROCESSING,
+					'line_items' => array(
+						array(
+							'id'        => $line_item_data['id'],
+							'meta_data' => array( $meta_data ),
+						),
+					),
+				)
+			)
+		);
+
+		$response = $this->server->dispatch( $request );
+		$this->assertEquals( 200, $response->get_status() );
+
+		$order      = wc_get_order( $response_data['id'] );
+		$line_items = $order->get_items( 'line_item' );
+		$line_item  = $line_items[ $line_item_data['id'] ];
+		$this->assertEquals( $complex_meta_value, $line_item->get_meta( '_file_upload_data' ) );
+	}
+
+	/**
+	 * @testdox Published order schema advertises a standard type union for meta_data value/display_value, not the non-standard 'mixed'.
+	 */
+	public function test_meta_data_schema_advertises_type_union(): void {
+		$expected_union = array( 'null', 'object', 'string', 'number', 'boolean', 'integer', 'array' );
+
+		$schema     = $this->endpoint->get_item_schema();
+		$properties = $schema['properties'];
+
+		// Every order meta_data value field, at every level, should advertise the union instead of 'mixed'.
+		$meta_containers = array(
+			$properties['meta_data']['items']['properties'],
+			$properties['line_items']['items']['properties']['meta_data']['items']['properties'],
+			$properties['shipping_lines']['items']['properties']['meta_data']['items']['properties'],
+			$properties['fee_lines']['items']['properties']['meta_data']['items']['properties'],
+			$properties['coupon_lines']['items']['properties']['meta_data']['items']['properties'],
+		);
+		foreach ( $meta_containers as $meta_properties ) {
+			$this->assertEquals( $expected_union, $meta_properties['value']['type'] );
+		}
+
+		// The line item block also exposes read-only display fields with the same union type.
+		$line_item_meta = $properties['line_items']['items']['properties']['meta_data']['items']['properties'];
+		$this->assertEquals( $expected_union, $line_item_meta['display_value']['type'] );
+		$this->assertTrue( $line_item_meta['display_value']['readonly'] );
+		$this->assertTrue( $line_item_meta['display_key']['readonly'] );
+	}
 }