Commit 90768f288f for woocommerce

commit 90768f288f97c903e414196f7a05128f3fbc6991
Author: Albert Juhé Lluveras <contact@albertjuhe.com>
Date:   Tue Dec 9 10:35:30 2025 +0100

    Fix REST API handling of attribute names containing special characters when creating product variations (#61939)

    * Allow creating variations with attribute names containing special characters using the Rest API

    * Add changelog file

    * Improve test comments

    * Decode attribute slug before returning it

diff --git a/plugins/woocommerce/changelog/fix-create-variation-special-chras-rest-api b/plugins/woocommerce/changelog/fix-create-variation-special-chras-rest-api
new file mode 100644
index 0000000000..4b37ee64e5
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-create-variation-special-chras-rest-api
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Fixed REST API handling of attribute names containing special characters when creating product variations
diff --git a/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-products-v2-controller.php b/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-products-v2-controller.php
index e76a89158a..669a28484f 100644
--- a/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-products-v2-controller.php
+++ b/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-products-v2-controller.php
@@ -614,7 +614,7 @@ class WC_REST_Products_V2_Controller extends WC_REST_CRUD_Controller {
 					$attributes[] = array(
 						'id'     => wc_attribute_taxonomy_id_by_name( $name ),
 						'name'   => $this->get_attribute_taxonomy_name( $name, $_product ),
-						'slug'   => $name,
+						'slug'   => rawurldecode( $name ),
 						'option' => $option_term && ! is_wp_error( $option_term ) ? $option_term->name : $attribute,
 					);
 				} else {
diff --git a/plugins/woocommerce/includes/rest-api/Controllers/Version3/class-wc-rest-product-variations-controller.php b/plugins/woocommerce/includes/rest-api/Controllers/Version3/class-wc-rest-product-variations-controller.php
index c464eeaf8f..fc3d505938 100644
--- a/plugins/woocommerce/includes/rest-api/Controllers/Version3/class-wc-rest-product-variations-controller.php
+++ b/plugins/woocommerce/includes/rest-api/Controllers/Version3/class-wc-rest-product-variations-controller.php
@@ -350,7 +350,7 @@ class WC_REST_Product_Variations_Controller extends WC_REST_Product_Variations_V
 				// Check ID for global attributes or name for product attributes.
 				if ( ! empty( $attribute['id'] ) ) {
 					$attribute_id   = absint( $attribute['id'] );
-					$attribute_name = wc_attribute_taxonomy_name_by_id( $attribute_id );
+					$attribute_name = sanitize_title( wc_attribute_taxonomy_name_by_id( $attribute_id ) );
 				} elseif ( ! empty( $attribute['name'] ) ) {
 					$attribute_name = sanitize_title( $attribute['name'] );
 				}
diff --git a/plugins/woocommerce/tests/php/includes/rest-api/Controllers/Version3/class-wc-rest-product-variations-controller-tests.php b/plugins/woocommerce/tests/php/includes/rest-api/Controllers/Version3/class-wc-rest-product-variations-controller-tests.php
index 6a954077fa..a26374466e 100644
--- a/plugins/woocommerce/tests/php/includes/rest-api/Controllers/Version3/class-wc-rest-product-variations-controller-tests.php
+++ b/plugins/woocommerce/tests/php/includes/rest-api/Controllers/Version3/class-wc-rest-product-variations-controller-tests.php
@@ -166,6 +166,66 @@ class WC_REST_Product_Variations_Controller_Tests extends WC_REST_Unit_Test_Case
 		$this->assertEquals( 200, $response->get_status() );
 	}

+	/**
+	 * Test that creating a variation with attributes containing special characters in their slug
+	 * properly saves the attributes.
+	 *
+	 * This test verifies the fix for issue #61791 where attributes with non-ASCII characters
+	 * (like Persian) were not saved when creating variations via the REST API.
+	 * @see https://github.com/woocommerce/woocommerce/issues/61791
+	 */
+	public function test_create_variation_with_persian_attribute_by_id() {
+		// Create a variable product with Persian attribute names.
+		$product         = WC_Helper_Product::create_variation_product();
+		$color_attribute = WC_Helper_Product::create_product_attribute_object( 'رنگ', array( 'blue', 'green' ) );
+		$product->set_attributes( array( $color_attribute ) );
+		$product->save();
+
+		// Create a variation via REST API.
+		$request = new WP_REST_Request( 'POST', '/wc/v3/products/' . $product->get_id() . '/variations' );
+		$request->set_body_params(
+			array(
+				'regular_price'  => '0',
+				'sale_price'     => '0',
+				'manage_stock'   => true,
+				'stock_quantity' => 0,
+				'attributes'     => array(
+					array(
+						'id'     => $color_attribute->get_id(),
+						'option' => 'green',
+					),
+				),
+			)
+		);
+
+		$response  = $this->server->dispatch( $request );
+		$variation = $response->get_data();
+
+		// Verify the variation was created successfully.
+		$this->assertEquals( 201, $response->get_status() );
+		$this->assertNotEmpty( $variation['id'] );
+		$this->assertEquals( 'variation', $variation['type'] );
+		$this->assertEquals( $product->get_id(), $variation['parent_id'] );
+
+		// Verify the attribute is properly set.
+		$this->assertNotEmpty( $variation['attributes'], 'Attributes array should not be empty' );
+		$this->assertCount( 1, $variation['attributes'], 'Variation should have 1 attribute' );
+		$this->assertEquals( 'رنگ', $variation['attributes'][0]['name'], 'Variation should contain color attribute' );
+		$this->assertEquals( 'green', $variation['attributes'][0]['option'], 'Variation should contain color option green' );
+
+		// Verify the variation can be retrieved and has the attribute data.
+		$get_request         = new WP_REST_Request( 'GET', '/wc/v3/products/' . $product->get_id() . '/variations/' . $variation['id'] );
+		$get_response        = $this->server->dispatch( $get_request );
+		$retrieved_variation = $get_response->get_data();
+
+		$this->assertEquals( 200, $get_response->get_status() );
+		$this->assertNotEmpty( $retrieved_variation['attributes'], 'Retrieved variation should have attributes' );
+		$this->assertCount( 1, $retrieved_variation['attributes'], 'Retrieved variation should have 1 attribute' );
+		$this->assertEquals( 'رنگ', $retrieved_variation['attributes'][0]['name'], 'Variation should contain color attribute' );
+		$this->assertEquals( 'pa_رنگ', $retrieved_variation['attributes'][0]['slug'], 'Variation should contain color attribute slug' );
+		$this->assertEquals( 'green', $retrieved_variation['attributes'][0]['option'], 'Variation should contain color option green' );
+	}
+
 	/**
 	 * Test that the products endpoint can filter by global_unique_id and also return matched variations.
 	 *