Commit ec0da754685 for woocommerce

commit ec0da7546857e1a822ae1f9340d87b3799e4b5c1
Author: Luigi Teschio <gigitux@gmail.com>
Date:   Fri Nov 6 12:18:25 2026 +0100

    Fix variation attributes with non-Latin slugs (#70581)

    * Fix variation attributes with non-Latin slugs

    * Add changelog entry for non-Latin variation attributes

    * Update plugins/woocommerce/includes/wc-template-functions.php

    Co-authored-by: Albert Juhé Lluveras <contact@albertjuhe.com>

    ---------

    Co-authored-by: Albert Juhé Lluveras <contact@albertjuhe.com>

diff --git a/plugins/woocommerce/changelog/fix-59199-non-latin-variation-attributes b/plugins/woocommerce/changelog/fix-59199-non-latin-variation-attributes
new file mode 100644
index 00000000000..1df4c7c4e3b
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-59199-non-latin-variation-attributes
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Fix variation selection from cart links and REST API attribute handling for non-Latin slugs.
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 111bbe1a9d3..c7fb0151dc3 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
@@ -739,7 +739,7 @@ class WC_REST_Products_V2_Controller extends WC_REST_CRUD_Controller {

 				// Taxonomy-based attributes are prefixed with `pa_`, otherwise simply `attribute_`.
 				if ( 0 === strpos( $attribute_name, 'attribute_pa_' ) ) {
-					$option_term  = get_term_by( 'slug', $attribute, $name );
+					$option_term  = get_term_by( 'slug', $attribute, rawurldecode( $name ) );
 					$attributes[] = array(
 						'id'     => wc_attribute_taxonomy_id_by_name( $name ),
 						'name'   => $this->get_attribute_taxonomy_name( $name, $_product ),
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 5d7e9bfcd4f..dc91783f4f2 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
@@ -425,7 +425,7 @@ class WC_REST_Product_Variations_Controller extends WC_REST_Product_Variations_V

 				if ( $parent_attributes[ $attribute_name ]->is_taxonomy() ) {
 					// If dealing with a taxonomy, we need to get the slug from the name posted to the API.
-					$term = get_term_by( 'name', $attribute_value, $attribute_name );
+					$term = get_term_by( 'name', $attribute_value, $parent_attributes[ $attribute_name ]->get_name() );

 					if ( $term && ! is_wp_error( $term ) ) {
 						$attribute_value = $term->slug;
diff --git a/plugins/woocommerce/includes/wc-template-functions.php b/plugins/woocommerce/includes/wc-template-functions.php
index d8c7f934003..9a380a4ea8a 100644
--- a/plugins/woocommerce/includes/wc-template-functions.php
+++ b/plugins/woocommerce/includes/wc-template-functions.php
@@ -3774,7 +3774,12 @@ if ( ! function_exists( 'wc_dropdown_variation_attribute_options' ) ) {
 		if ( false === $args['selected'] && $args['attribute'] && $args['product'] instanceof WC_Product ) {
 			$selected_key = 'attribute_' . sanitize_title( $args['attribute'] );
 			// phpcs:disable WordPress.Security.NonceVerification.Recommended
-			$args['selected'] = isset( $_REQUEST[ $selected_key ] ) ? wc_clean( wp_unslash( $_REQUEST[ $selected_key ] ) ) : $args['product']->get_variation_default_attribute( $args['attribute'] );
+			if ( isset( $_REQUEST[ $selected_key ] ) && is_string( $_REQUEST[ $selected_key ] ) ) {
+				// Taxonomy term slugs may contain percent-encoded characters, which wc_clean() removes.
+				$args['selected'] = taxonomy_exists( $args['attribute'] ) ? sanitize_title( wp_unslash( $_REQUEST[ $selected_key ] ) ) : wc_clean( wp_unslash( $_REQUEST[ $selected_key ] ) );
+			} else {
+				$args['selected'] = $args['product']->get_variation_default_attribute( $args['attribute'] );
+			}
 			// phpcs:enable WordPress.Security.NonceVerification.Recommended
 		}

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 af197e24db5..879e45ce306 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
@@ -39,6 +39,54 @@ class WC_REST_Product_Variations_Controller_Tests extends WC_Unit_Test_Case {
 	 */
 	private $variation_orderby_clauses = array();

+	/**
+	 * @testdox The variation API resolves term names using the registered taxonomy for non-Latin attributes.
+	 * @testWith ["v3", "χρώμα", "κόκκινο"]
+	 *           ["v3", "χρώμα", "ερυθρό"]
+	 *           ["v3", "color", "red"]
+	 *           ["v2", "χρώμα", "κόκκινο"]
+	 *           ["v2", "χρώμα", "ερυθρό"]
+	 *           ["v2", "color", "red"]
+	 * @param string $version REST API version.
+	 * @param string $attribute_name Attribute name.
+	 * @param string $term_slug Term slug, which may differ from the term name.
+	 */
+	public function test_create_variation_with_non_latin_attribute( string $version, string $attribute_name, string $term_slug ): void {
+		if ( 'v2' === $version ) {
+			$controller = new WC_REST_Product_Variations_V2_Controller();
+			$controller->register_routes();
+		}
+
+		$attribute = WC_Helper_Product::create_product_attribute_object( $attribute_name, array( 'Κόκκινο' ) );
+		$term_id   = $attribute->get_options()[0];
+		wp_update_term( $term_id, $attribute->get_name(), array( 'slug' => $term_slug ) );
+		$term = get_term( $term_id, $attribute->get_name() );
+
+		$product = new WC_Product_Variable();
+		$product->set_attributes( array( $attribute ) );
+		$product->save();
+
+		$request = new WP_REST_Request( 'POST', "/wc/{$version}/products/{$product->get_id()}/variations" );
+		$request->set_body_params(
+			array(
+				'regular_price' => '10',
+				'attributes'    => array(
+					array(
+						'id'     => $attribute->get_id(),
+						'option' => 'Κόκκινο',
+					),
+				),
+			)
+		);
+		$response = $this->server->dispatch( $request );
+		$this->assertSame( 201, $response->get_status() );
+		$data      = $response->get_data();
+		$variation = wc_get_product( $data['id'] );
+
+		$this->assertSame( array( sanitize_title( $attribute->get_name() ) => $term->slug ), $variation->get_attributes(), 'The API must save the actual term slug, including custom slugs.' );
+		$this->assertSame( 'Κόκκινο', $data['attributes'][0]['option'], 'The API must return the term name rather than its slug.' );
+	}
+
 	/**
 	 * Runs before each test.
 	 */
diff --git a/plugins/woocommerce/tests/php/includes/wc-template-functions-test.php b/plugins/woocommerce/tests/php/includes/wc-template-functions-test.php
index 788f2e0f0de..bf17e936906 100644
--- a/plugins/woocommerce/tests/php/includes/wc-template-functions-test.php
+++ b/plugins/woocommerce/tests/php/includes/wc-template-functions-test.php
@@ -7,6 +7,47 @@ declare( strict_types = 1 );
  * @package WooCommerce\Tests\Includes
  */
 class WC_Template_Functions_Tests extends \WC_Unit_Test_Case {
+	/**
+	 * @testdox Cart links preselect taxonomy variations with non-Latin term slugs.
+	 * @testWith ["color", "Κόκκινο"]
+	 *           ["χρώμα", "Κόκκινο"]
+	 *           ["color", "Red"]
+	 * @param string $attribute_name Attribute name.
+	 * @param string $term_name Term name.
+	 */
+	public function test_variation_dropdown_preselects_attribute_from_permalink( string $attribute_name, string $term_name ): void {
+		$attribute = WC_Helper_Product::create_product_attribute_object( $attribute_name, array( $term_name ) );
+		$product   = new WC_Product_Variable();
+		$product->set_attributes( array( $attribute ) );
+		$product->save();
+		$term = $attribute->get_terms()[0];
+
+		$variation = new WC_Product_Variation();
+		$variation->set_parent_id( $product->get_id() );
+		$variation->set_attributes( array( sanitize_title( $attribute->get_name() ) => $term->slug ) );
+		$variation->save();
+
+		$previous_request = $_REQUEST; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Preserve request state for the test.
+		parse_str( wp_parse_url( $variation->get_permalink(), PHP_URL_QUERY ), $query );
+		$_REQUEST = wp_slash( $query ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Simulate following the variation permalink.
+		ob_start();
+		try {
+			wc_dropdown_variation_attribute_options(
+				array(
+					'product'   => $product,
+					'attribute' => $attribute->get_name(),
+					'options'   => array( $term->slug ),
+				)
+			);
+			$html = (string) ob_get_contents();
+		} finally {
+			ob_end_clean();
+			$_REQUEST = $previous_request;
+		}
+
+		$this->assertMatchesRegularExpression( '/value="' . preg_quote( $term->slug, '/' ) . '"\s+selected=/', $html, 'The linked variation must be selected in the dropdown.' );
+	}
+
 	/**
 	 * Render the loop add-to-cart template for a product.
 	 *