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.
*