Commit 73fee4f9705 for woocommerce
commit 73fee4f9705bd1d1109bae56abfbc043c5a2a276
Author: Jacob Max Jensen <jmj@webko.dk>
Date: Fri Mar 20 06:20:18 2026 +0100
Fix: Attribute terms REST API writes menu_order to wrong meta key (#63390)
* Fix attribute terms REST API using wrong meta key for menu_order
* Add unit test for attribute terms menu_order meta key
* Fix tests, add isset guard, add changelog for menu_order meta key fix
- Remove garbled test file (space character filename) and add tests to
the existing V1 controller test class at the correct path.
- Add isset($request['menu_order']) guard to prevent overwriting order
meta when updating unrelated term fields (matches categories controller).
- Add tests for create, update, read, old-key regression, and the
isset guard preserving existing order.
- Add missing changelog entry.
* Fix PHPCS lint issues in attribute terms controller tests
- Move file docblock before declare(strict_types) per WooCommerce standards
- Add short descriptions to all test method docblocks
- Fix equals sign alignment
---------
Co-authored-by: Brandon Kraft <public@brandonkraft.com>
diff --git a/plugins/woocommerce/changelog/fix-attribute-terms-menu-order-meta-key b/plugins/woocommerce/changelog/fix-attribute-terms-menu-order-meta-key
new file mode 100644
index 00000000000..6f4732bcbb2
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-attribute-terms-menu-order-meta-key
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Fix attribute terms REST API writing menu_order to wrong meta key.
diff --git a/plugins/woocommerce/includes/rest-api/Controllers/Version1/class-wc-rest-product-attribute-terms-v1-controller.php b/plugins/woocommerce/includes/rest-api/Controllers/Version1/class-wc-rest-product-attribute-terms-v1-controller.php
index 564ba016667..5fdb3f09148 100644
--- a/plugins/woocommerce/includes/rest-api/Controllers/Version1/class-wc-rest-product-attribute-terms-v1-controller.php
+++ b/plugins/woocommerce/includes/rest-api/Controllers/Version1/class-wc-rest-product-attribute-terms-v1-controller.php
@@ -135,7 +135,7 @@ class WC_REST_Product_Attribute_Terms_V1_Controller extends WC_REST_Terms_Contro
*/
public function prepare_item_for_response( $item, $request ) {
// Get term order.
- $menu_order = get_term_meta( $item->term_id, 'order_' . $this->taxonomy, true );
+ $menu_order = get_term_meta( $item->term_id, 'order', true );
$data = array(
'id' => (int) $item->term_id,
@@ -176,7 +176,9 @@ class WC_REST_Product_Attribute_Terms_V1_Controller extends WC_REST_Terms_Contro
protected function update_term_meta_fields( $term, $request ) {
$id = (int) $term->term_id;
- update_term_meta( $id, 'order_' . $this->taxonomy, $request['menu_order'] );
+ if ( isset( $request['menu_order'] ) ) {
+ update_term_meta( $id, 'order', $request['menu_order'] );
+ }
return true;
}
diff --git a/plugins/woocommerce/tests/php/includes/rest-api/Controllers/Version1/class-wc-rest-product-attribute-terms-v1-controller-tests.php b/plugins/woocommerce/tests/php/includes/rest-api/Controllers/Version1/class-wc-rest-product-attribute-terms-v1-controller-tests.php
index 8dddd3f8008..ee5f26def72 100644
--- a/plugins/woocommerce/tests/php/includes/rest-api/Controllers/Version1/class-wc-rest-product-attribute-terms-v1-controller-tests.php
+++ b/plugins/woocommerce/tests/php/includes/rest-api/Controllers/Version1/class-wc-rest-product-attribute-terms-v1-controller-tests.php
@@ -1,4 +1,10 @@
<?php
+/**
+ * Product attribute terms controller tests for V1 REST API.
+ *
+ * @package WooCommerce\Tests\RestApi
+ */
+
declare( strict_types = 1 );
use Automattic\WooCommerce\Tests\Blocks\Helpers\FixtureData;
@@ -8,12 +14,14 @@ use Automattic\WooCommerce\Tests\Blocks\Helpers\FixtureData;
*/
class WC_REST_Product_Attribute_Terms_V1_Controller_Tests extends WC_REST_Unit_Test_Case {
/**
- * @var int Admin user id.
+ * Admin user ID.
+ *
+ * @var int
*/
private $admin_id;
/**
- * Test setup.
+ * Set up test fixtures.
*/
public function setUp(): void {
parent::setUp();
@@ -21,6 +29,8 @@ class WC_REST_Product_Attribute_Terms_V1_Controller_Tests extends WC_REST_Unit_T
}
/**
+ * Test that the item schema contains expected properties.
+ *
* @testdox Product attribute terms item schema contains expected properties.
*/
public function test_get_item_schema() {
@@ -42,6 +52,8 @@ class WC_REST_Product_Attribute_Terms_V1_Controller_Tests extends WC_REST_Unit_T
}
/**
+ * Test that creating a term with an empty slug succeeds.
+ *
* @testdox Creating a product attribute term with an empty slug succeeds.
*/
public function test_create_with_empty_slug() {
@@ -61,4 +73,136 @@ class WC_REST_Product_Attribute_Terms_V1_Controller_Tests extends WC_REST_Unit_T
$this->assertEquals( 201, $response->get_status() );
}
+
+ /**
+ * Test that creating a term stores menu_order under the correct meta key.
+ *
+ * @testdox Creating a term via REST API stores menu_order under the 'order' meta key.
+ */
+ public function test_menu_order_writes_to_correct_meta_key() {
+ wp_set_current_user( $this->admin_id );
+
+ $attribute_id = wc_create_attribute(
+ array(
+ 'name' => 'Test Size',
+ 'slug' => 'test-size',
+ 'order_by' => 'menu_order',
+ )
+ );
+ $taxonomy = wc_attribute_taxonomy_name( 'test-size' );
+ register_taxonomy( $taxonomy, array( 'product' ) );
+
+ $request = new WP_REST_Request( 'POST', '/wc/v1/products/attributes/' . $attribute_id . '/terms' );
+ $request->set_body_params(
+ array(
+ 'name' => 'Large',
+ 'slug' => 'large',
+ 'menu_order' => 5,
+ )
+ );
+ $response = $this->server->dispatch( $request );
+ $data = $response->get_data();
+
+ $this->assertEquals( 201, $response->get_status() );
+ $this->assertEquals( 5, $data['menu_order'] );
+ $this->assertEquals( 5, (int) get_term_meta( $data['id'], 'order', true ) );
+ $this->assertEmpty( get_term_meta( $data['id'], 'order_' . $taxonomy, true ), 'Old meta key should not be written' );
+
+ wc_delete_attribute( $attribute_id );
+ }
+
+ /**
+ * Test that updating menu_order updates the correct meta key.
+ *
+ * @testdox Updating menu_order via REST API updates the 'order' meta key.
+ */
+ public function test_menu_order_update_writes_to_correct_meta_key() {
+ wp_set_current_user( $this->admin_id );
+
+ $attribute_id = wc_create_attribute(
+ array(
+ 'name' => 'Test Weight',
+ 'slug' => 'test-weight',
+ 'order_by' => 'menu_order',
+ )
+ );
+ $taxonomy = wc_attribute_taxonomy_name( 'test-weight' );
+ register_taxonomy( $taxonomy, array( 'product' ) );
+
+ $term = wp_insert_term( 'Medium', $taxonomy, array( 'slug' => 'medium' ) );
+ update_term_meta( $term['term_id'], 'order', 0 );
+
+ $request = new WP_REST_Request( 'PUT', '/wc/v1/products/attributes/' . $attribute_id . '/terms/' . $term['term_id'] );
+ $request->set_body_params( array( 'menu_order' => 3 ) );
+ $response = $this->server->dispatch( $request );
+ $data = $response->get_data();
+
+ $this->assertEquals( 200, $response->get_status() );
+ $this->assertEquals( 3, $data['menu_order'] );
+ $this->assertEquals( 3, (int) get_term_meta( $term['term_id'], 'order', true ) );
+
+ wc_delete_attribute( $attribute_id );
+ }
+
+ /**
+ * Test that reading menu_order returns the value from the correct meta key.
+ *
+ * @testdox Reading menu_order via GET returns value from the 'order' meta key.
+ */
+ public function test_menu_order_read_uses_correct_meta_key() {
+ wp_set_current_user( $this->admin_id );
+
+ $attribute_id = wc_create_attribute(
+ array(
+ 'name' => 'Test Material',
+ 'slug' => 'test-material',
+ 'order_by' => 'menu_order',
+ )
+ );
+ $taxonomy = wc_attribute_taxonomy_name( 'test-material' );
+ register_taxonomy( $taxonomy, array( 'product' ) );
+
+ $term = wp_insert_term( 'Cotton', $taxonomy, array( 'slug' => 'cotton' ) );
+ update_term_meta( $term['term_id'], 'order', 7 );
+
+ $request = new WP_REST_Request( 'GET', '/wc/v1/products/attributes/' . $attribute_id . '/terms/' . $term['term_id'] );
+ $response = $this->server->dispatch( $request );
+ $data = $response->get_data();
+
+ $this->assertEquals( 200, $response->get_status() );
+ $this->assertEquals( 7, $data['menu_order'] );
+
+ wc_delete_attribute( $attribute_id );
+ }
+
+ /**
+ * Test that updating a term without menu_order preserves existing order.
+ *
+ * @testdox Updating a term without menu_order does not overwrite existing order.
+ */
+ public function test_update_without_menu_order_preserves_existing_order() {
+ wp_set_current_user( $this->admin_id );
+
+ $attribute_id = wc_create_attribute(
+ array(
+ 'name' => 'Test Style',
+ 'slug' => 'test-style',
+ 'order_by' => 'menu_order',
+ )
+ );
+ $taxonomy = wc_attribute_taxonomy_name( 'test-style' );
+ register_taxonomy( $taxonomy, array( 'product' ) );
+
+ $term = wp_insert_term( 'Casual', $taxonomy, array( 'slug' => 'casual' ) );
+ update_term_meta( $term['term_id'], 'order', 5 );
+
+ $request = new WP_REST_Request( 'PUT', '/wc/v1/products/attributes/' . $attribute_id . '/terms/' . $term['term_id'] );
+ $request->set_body_params( array( 'description' => 'Updated description' ) );
+ $response = $this->server->dispatch( $request );
+
+ $this->assertEquals( 200, $response->get_status() );
+ $this->assertEquals( 5, (int) get_term_meta( $term['term_id'], 'order', true ), 'Order should be preserved when menu_order is not in the request' );
+
+ wc_delete_attribute( $attribute_id );
+ }
}