Commit bd747fe53fd for woocommerce
commit bd747fe53fdd29277d763e1baff78b913e15fd63
Author: Raluca Stan <ralucastn@gmail.com>
Date: Tue Aug 25 10:54:14 2026 +0200
Fix RTL currency symbols not respecting the currency position setting in PHP (#67686)
* Fix RTL currency symbols not respecting the currency position setting
* Add changelog entry for the RTL currency symbol fix
* Clarify why the currency symbol uses dir=auto rather than dir=ltr
* Cover all four configured currency positions in the wc_price bidi test
diff --git a/plugins/woocommerce/changelog/fix-31637-rtl-currency-symbol-bidi b/plugins/woocommerce/changelog/fix-31637-rtl-currency-symbol-bidi
new file mode 100644
index 00000000000..d5512f6f23a
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-31637-rtl-currency-symbol-bidi
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Direction-isolate the currency symbol in wc_price() so RTL-script symbols (e.g. Lebanese Pound) respect the configured currency position and no longer bleed into surrounding text.
diff --git a/plugins/woocommerce/includes/wc-formatting-functions.php b/plugins/woocommerce/includes/wc-formatting-functions.php
index 6e511236f33..cc24a326aea 100644
--- a/plugins/woocommerce/includes/wc-formatting-functions.php
+++ b/plugins/woocommerce/includes/wc-formatting-functions.php
@@ -644,7 +644,8 @@ function wc_price( $price, $args = array() ) {
}
if ( $args['in_span'] ) {
- $formatted_price = ( $negative ? '-' : '' ) . sprintf( $args['price_format'], '<span class="woocommerce-Price-currencySymbol" translate="no">' . get_woocommerce_currency_symbol( $args['currency'] ) . '</span>', $price );
+ // dir="auto" isolates the symbol so RTL-script symbols (e.g. Lebanese Pound) can't flip the visual order of the price or bleed into surrounding text; it must stay "auto" (not "ltr") so those symbols' glyphs keep rendering in their native right-to-left order.
+ $formatted_price = ( $negative ? '-' : '' ) . sprintf( $args['price_format'], '<span class="woocommerce-Price-currencySymbol" translate="no" dir="auto">' . get_woocommerce_currency_symbol( $args['currency'] ) . '</span>', $price );
$aria_hidden = $args['aria-hidden'] ? ' aria-hidden="true"' : '';
$return = '<span class="woocommerce-Price-amount amount"' . $aria_hidden . '><bdi>' . $formatted_price . '</bdi></span>';
} else {
diff --git a/plugins/woocommerce/tests/legacy/unit-tests/cart/cart.php b/plugins/woocommerce/tests/legacy/unit-tests/cart/cart.php
index 1a396934edb..a05ae660a43 100644
--- a/plugins/woocommerce/tests/legacy/unit-tests/cart/cart.php
+++ b/plugins/woocommerce/tests/legacy/unit-tests/cart/cart.php
@@ -1091,7 +1091,7 @@ class WC_Tests_Cart extends WC_Unit_Test_Case {
WC()->cart->add_to_cart( $product2->get_id(), 1 );
WC()->cart->calculate_totals();
- $expected_price = '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol" translate="no">€</span>68,50</bdi></span>';
+ $expected_price = '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol" translate="no" dir="auto">€</span>68,50</bdi></span>';
$this->assertEquals( $expected_price, WC()->cart->get_total() );
$this->assertEquals( '12.36', wc_round_tax_total( WC()->cart->get_total_tax( 'edit' ) ) );
@@ -1100,7 +1100,7 @@ class WC_Tests_Cart extends WC_Unit_Test_Case {
WC()->cart->add_to_cart( $product4->get_id(), 1 );
WC()->cart->calculate_totals();
- $expected_price = '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol" translate="no">€</span>112,00</bdi></span>';
+ $expected_price = '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol" translate="no" dir="auto">€</span>112,00</bdi></span>';
$this->assertEquals( $expected_price, WC()->cart->get_total() );
$this->assertEquals( '20.19', wc_round_tax_total( WC()->cart->get_total_tax( 'edit' ) ) );
@@ -1111,7 +1111,7 @@ class WC_Tests_Cart extends WC_Unit_Test_Case {
WC()->cart->add_to_cart( $product6->get_id(), 1 );
WC()->cart->calculate_totals();
- $expected_price = '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol" translate="no">€</span>239,00</bdi></span>';
+ $expected_price = '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol" translate="no" dir="auto">€</span>239,00</bdi></span>';
$this->assertEquals( $expected_price, WC()->cart->get_total() );
$this->assertEquals( '43.09', wc_round_tax_total( WC()->cart->get_total_tax( 'edit' ) ) );
}
diff --git a/plugins/woocommerce/tests/legacy/unit-tests/formatting/functions.php b/plugins/woocommerce/tests/legacy/unit-tests/formatting/functions.php
index bd46c90cdf9..ab7fe28a322 100644
--- a/plugins/woocommerce/tests/legacy/unit-tests/formatting/functions.php
+++ b/plugins/woocommerce/tests/legacy/unit-tests/formatting/functions.php
@@ -611,38 +611,69 @@ class WC_Tests_Formatting_Functions extends WC_Unit_Test_Case {
*/
public function test_wc_price() {
// Common prices.
- $this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol" translate="no">$</span>1.00</bdi></span>', wc_price( 1 ) );
- $this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol" translate="no">$</span>1.10</bdi></span>', wc_price( 1.1 ) );
- $this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol" translate="no">$</span>1.17</bdi></span>', wc_price( 1.17 ) );
- $this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol" translate="no">$</span>1,111.17</bdi></span>', wc_price( 1111.17 ) );
- $this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol" translate="no">$</span>0.00</bdi></span>', wc_price( 0 ) );
+ $this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol" translate="no" dir="auto">$</span>1.00</bdi></span>', wc_price( 1 ) );
+ $this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol" translate="no" dir="auto">$</span>1.10</bdi></span>', wc_price( 1.1 ) );
+ $this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol" translate="no" dir="auto">$</span>1.17</bdi></span>', wc_price( 1.17 ) );
+ $this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol" translate="no" dir="auto">$</span>1,111.17</bdi></span>', wc_price( 1111.17 ) );
+ $this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol" translate="no" dir="auto">$</span>0.00</bdi></span>', wc_price( 0 ) );
// Different currency.
- $this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol" translate="no">£</span>1,111.17</bdi></span>', wc_price( 1111.17, array( 'currency' => 'GBP' ) ) );
+ $this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol" translate="no" dir="auto">£</span>1,111.17</bdi></span>', wc_price( 1111.17, array( 'currency' => 'GBP' ) ) );
// Negative price.
- $this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi>-<span class="woocommerce-Price-currencySymbol" translate="no">$</span>1.17</bdi></span>', wc_price( -1.17 ) );
+ $this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi>-<span class="woocommerce-Price-currencySymbol" translate="no" dir="auto">$</span>1.17</bdi></span>', wc_price( -1.17 ) );
// Aria hidden option.
- $this->assertEquals( '<span class="woocommerce-Price-amount amount" aria-hidden="true"><bdi>-<span class="woocommerce-Price-currencySymbol" translate="no">$</span>1.17</bdi></span>', wc_price( -1.17, array( 'aria-hidden' => true ) ) );
+ $this->assertEquals( '<span class="woocommerce-Price-amount amount" aria-hidden="true"><bdi>-<span class="woocommerce-Price-currencySymbol" translate="no" dir="auto">$</span>1.17</bdi></span>', wc_price( -1.17, array( 'aria-hidden' => true ) ) );
// Bogus prices.
- $this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol" translate="no">$</span>0.00</bdi></span>', wc_price( null ) );
- $this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol" translate="no">$</span>0.00</bdi></span>', wc_price( 'Q' ) );
- $this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol" translate="no">$</span>0.00</bdi></span>', wc_price( 'ಠ_ಠ' ) );
+ $this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol" translate="no" dir="auto">$</span>0.00</bdi></span>', wc_price( null ) );
+ $this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol" translate="no" dir="auto">$</span>0.00</bdi></span>', wc_price( 'Q' ) );
+ $this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol" translate="no" dir="auto">$</span>0.00</bdi></span>', wc_price( 'ಠ_ಠ' ) );
// Trim zeros.
add_filter( 'woocommerce_price_trim_zeros', '__return_true' );
- $this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol" translate="no">$</span>1</bdi></span>', wc_price( 1.00 ) );
+ $this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol" translate="no" dir="auto">$</span>1</bdi></span>', wc_price( 1.00 ) );
remove_filter( 'woocommerce_price_trim_zeros', '__return_true' );
// Ex tax label.
$calc_taxes = get_option( 'woocommerce_calc_taxes' );
update_option( 'woocommerce_calc_taxes', 'yes' );
- $this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol" translate="no">$</span>1,111.17</bdi></span> <small class="woocommerce-Price-taxLabel tax_label">(ex. tax)</small>', wc_price( '1111.17', array( 'ex_tax_label' => true ) ) );
+ $this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol" translate="no" dir="auto">$</span>1,111.17</bdi></span> <small class="woocommerce-Price-taxLabel tax_label">(ex. tax)</small>', wc_price( '1111.17', array( 'ex_tax_label' => true ) ) );
update_option( 'woocommerce_calc_taxes', $calc_taxes );
}
+ /**
+ * Test that wc_price() direction-isolates the currency symbol, so RTL-script
+ * symbols (e.g. the Lebanese Pound) cannot visually flip the configured
+ * currency position or bleed into surrounding text.
+ *
+ * @see https://github.com/woocommerce/woocommerce/issues/31637
+ * @since 11.1.0
+ */
+ public function test_wc_price_currency_symbol_is_direction_isolated() {
+ $original_position = get_option( 'woocommerce_currency_pos' );
+ $symbol = '<span class="woocommerce-Price-currencySymbol" translate="no" dir="auto">ل.ل</span>';
+
+ $expected_by_position = array(
+ 'left' => $symbol . '15.00',
+ 'right' => '15.00' . $symbol,
+ 'left_space' => $symbol . ' 15.00',
+ 'right_space' => '15.00 ' . $symbol,
+ );
+
+ foreach ( $expected_by_position as $position => $expected_price ) {
+ update_option( 'woocommerce_currency_pos', $position );
+ $this->assertEquals(
+ '<span class="woocommerce-Price-amount amount"><bdi>' . $expected_price . '</bdi></span>',
+ wc_price( 15, array( 'currency' => 'LBP' ) ),
+ "Currency position '{$position}' should render the direction-isolated symbol on the configured side"
+ );
+ }
+
+ update_option( 'woocommerce_currency_pos', $original_position );
+ }
+
/**
* Test wc_let_to_num().
*
@@ -996,7 +1027,7 @@ class WC_Tests_Formatting_Functions extends WC_Unit_Test_Case {
* @since 3.3.0
*/
public function test_wc_format_sale_price() {
- $this->assertEquals( '<del aria-hidden="true"><span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol" translate="no">$</span>10.00</bdi></span></del> <span class="screen-reader-text">Original price was: $10.00.</span><ins aria-hidden="true"><span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol" translate="no">$</span>5.00</bdi></span></ins><span class="screen-reader-text">Current price is: $5.00.</span>', wc_format_sale_price( '10', '5' ) );
+ $this->assertEquals( '<del aria-hidden="true"><span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol" translate="no" dir="auto">$</span>10.00</bdi></span></del> <span class="screen-reader-text">Original price was: $10.00.</span><ins aria-hidden="true"><span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol" translate="no" dir="auto">$</span>5.00</bdi></span></ins><span class="screen-reader-text">Current price is: $5.00.</span>', wc_format_sale_price( '10', '5' ) );
}
/**
@@ -1005,7 +1036,7 @@ class WC_Tests_Formatting_Functions extends WC_Unit_Test_Case {
* @since 3.3.0
*/
public function test_wc_format_price_range() {
- $this->assertEquals( '<span class="woocommerce-Price-amount amount" aria-hidden="true"><bdi><span class="woocommerce-Price-currencySymbol" translate="no">$</span>10.00</bdi></span> <span aria-hidden="true">–</span> <span class="woocommerce-Price-amount amount" aria-hidden="true"><bdi><span class="woocommerce-Price-currencySymbol" translate="no">$</span>5.00</bdi></span><span class="screen-reader-text">Price range: $10.00 through $5.00</span>', wc_format_price_range( '10', '5' ) );
+ $this->assertEquals( '<span class="woocommerce-Price-amount amount" aria-hidden="true"><bdi><span class="woocommerce-Price-currencySymbol" translate="no" dir="auto">$</span>10.00</bdi></span> <span aria-hidden="true">–</span> <span class="woocommerce-Price-amount amount" aria-hidden="true"><bdi><span class="woocommerce-Price-currencySymbol" translate="no" dir="auto">$</span>5.00</bdi></span><span class="screen-reader-text">Price range: $10.00 through $5.00</span>', wc_format_price_range( '10', '5' ) );
}
/**
diff --git a/plugins/woocommerce/tests/legacy/unit-tests/order/class-wc-tests-crud-orders.php b/plugins/woocommerce/tests/legacy/unit-tests/order/class-wc-tests-crud-orders.php
index dbd21a31a3d..13d62b3d464 100644
--- a/plugins/woocommerce/tests/legacy/unit-tests/order/class-wc-tests-crud-orders.php
+++ b/plugins/woocommerce/tests/legacy/unit-tests/order/class-wc-tests-crud-orders.php
@@ -1037,7 +1037,7 @@ class WC_Tests_CRUD_Orders extends WC_Unit_Test_Case {
$object = new WC_Order();
$object->set_total( 100 );
$object->set_currency( 'USD' );
- $this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol" translate="no">$</span>100.00</bdi></span>', $object->get_formatted_order_total() );
+ $this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol" translate="no" dir="auto">$</span>100.00</bdi></span>', $object->get_formatted_order_total() );
}
/**
diff --git a/plugins/woocommerce/tests/legacy/unit-tests/product/data.php b/plugins/woocommerce/tests/legacy/unit-tests/product/data.php
index a07f48972fd..6af7a0e4e28 100644
--- a/plugins/woocommerce/tests/legacy/unit-tests/product/data.php
+++ b/plugins/woocommerce/tests/legacy/unit-tests/product/data.php
@@ -576,15 +576,15 @@ class WC_Tests_Product_Data extends WC_Unit_Test_Case {
$product = wc_get_product( $product1_id );
$this->assertEquals( $product1_id, $product->get_id() );
- $this->assertEquals( '<del aria-hidden="true"><span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol" translate="no">$</span>10.00</bdi></span></del> <span class="screen-reader-text">Original price was: $10.00.</span><ins aria-hidden="true"><span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol" translate="no">$</span>7.00</bdi></span></ins><span class="screen-reader-text">Current price is: $7.00.</span>', $product->get_price_html() );
+ $this->assertEquals( '<del aria-hidden="true"><span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol" translate="no" dir="auto">$</span>10.00</bdi></span></del> <span class="screen-reader-text">Original price was: $10.00.</span><ins aria-hidden="true"><span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol" translate="no" dir="auto">$</span>7.00</bdi></span></ins><span class="screen-reader-text">Current price is: $7.00.</span>', $product->get_price_html() );
$product = wc_get_product( $product2_id );
$this->assertEquals( $product2_id, $product->get_id() );
- $this->assertEquals( '<del aria-hidden="true"><span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol" translate="no">$</span>20.00</bdi></span></del> <span class="screen-reader-text">Original price was: $20.00.</span><ins aria-hidden="true"><span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol" translate="no">$</span>16.00</bdi></span></ins><span class="screen-reader-text">Current price is: $16.00.</span>', $product->get_price_html() );
+ $this->assertEquals( '<del aria-hidden="true"><span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol" translate="no" dir="auto">$</span>20.00</bdi></span></del> <span class="screen-reader-text">Original price was: $20.00.</span><ins aria-hidden="true"><span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol" translate="no" dir="auto">$</span>16.00</bdi></span></ins><span class="screen-reader-text">Current price is: $16.00.</span>', $product->get_price_html() );
$product = wc_get_product( $product3_id );
$this->assertEquals( $product3_id, $product->get_id() );
- $this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol" translate="no">$</span>50.00</bdi></span>', $product->get_price_html() );
+ $this->assertEquals( '<span class="woocommerce-Price-amount amount"><bdi><span class="woocommerce-Price-currencySymbol" translate="no" dir="auto">$</span>50.00</bdi></span>', $product->get_price_html() );
}
/**