Fix shipping cost formatting to respect shipping formula (#42916)

This commit is contained in:
Ilyas Foo 2023-12-21 05:25:43 +08:00 committed by GitHub
parent 4d7bf10c96
commit 4a5373409b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 134 additions and 7 deletions

View File

@ -3,7 +3,44 @@
*/
import { useContext, useEffect } from '@wordpress/element';
import { CurrencyContext } from '@woocommerce/currency';
import { numberFormat } from '@woocommerce/number';
import { numberFormat, parseNumber } from '@woocommerce/number';
/**
* Escape special characters for user input in regex.
*
* @param {string} string
* @return {string} string
*/
const escapeRegExp = ( string ) => {
return string.replace( /[.*+?^${}()|[\]\\]/g, '\\$&' );
};
/**
* Format number when it's exclusively a number or a string of numbers, otherwise return the input.
*/
export const safeNumberFormat = ( config, number ) => {
if ( typeof number === 'number' ) {
return numberFormat( config, number );
}
if ( typeof number === 'string' ) {
const dot = escapeRegExp( config.decimalSeparator );
const comma = escapeRegExp( config.thousandSeparator );
// Regex to match strictly numbers with arbitrary thousands and decimal separators.
// Example: /^\s*(\d+|\d{1,3}(?:,\d{3})*)(?:\.\d+)?\s*$/ for default config.
const regex = new RegExp(
`^\\s*(\\d+|\\d{1,3}(?:${ comma }\\d{3})*)(?:${ dot }\\d+)?\\s*$`
);
return number.replace( regex, ( n ) => {
const parsed = parseNumber( config, n );
return numberFormat( config, parsed );
} );
}
return number;
};
export const ShippingCurrencyContext = () => {
const context = useContext( CurrencyContext );
@ -12,7 +49,7 @@ export const ShippingCurrencyContext = () => {
window.wc.ShippingCurrencyContext =
window.wc.ShippingCurrencyContext || context;
window.wc.ShippingCurrencyNumberFormat =
window.wc.ShippingCurrencyNumberFormat || numberFormat;
window.wc.ShippingCurrencyNumberFormat || safeNumberFormat;
}, [ context ] );
return null;

View File

@ -0,0 +1,80 @@
/**
* External dependencies
*/
import { numberFormat } from '@woocommerce/number';
/**
* Internal dependencies
*/
import { safeNumberFormat } from '../currency-context';
const config = {
code: 'USD',
symbol: '$',
symbolPosition: 'left',
decimalSeparator: '.',
priceFormat: '%1$s%2$s',
thousandSeparator: ',',
precision: 2,
};
describe( 'CurrencyContext', () => {
it( 'should format a number input correctly', () => {
expect( safeNumberFormat( config, 1234 ) ).toBe(
numberFormat( config, 1234 )
);
} );
it( 'should format string numbers correctly', () => {
expect( safeNumberFormat( config, '123456789' ) ).toBe(
'123,456,789.00'
);
} );
it( 'should format with swapped decimal and thousand separator', () => {
const customConfig = {
...config,
decimalSeparator: ',',
thousandSeparator: '.',
};
expect( safeNumberFormat( customConfig, '123.456.789' ) ).toBe(
'123.456.789,00'
);
} );
it( 'should not format incorrectly formatted numbers according to current config', () => {
const customConfig = {
...config,
decimalSeparator: ',',
thousandSeparator: '.',
};
expect( safeNumberFormat( customConfig, '7.5' ) ).toBe( '7.5' );
} );
it( 'should format number according to precision', () => {
const customConfig = {
...config,
precision: 5,
};
expect( safeNumberFormat( customConfig, '123.4' ) ).toBe( '123.40000' );
} );
it( 'should format number and trim leading and trailing spaces', () => {
expect( safeNumberFormat( config, ' 1234 ' ) ).toBe( '1,234.00' );
} );
it( 'should not format numbers when text is included', () => {
expect( safeNumberFormat( config, 'Value 1234' ) ).toBe( 'Value 1234' );
} );
it( 'should not format when formula is included', () => {
expect(
safeNumberFormat( config, '50 + (([qty]*2+1)(5*10))(1)' )
).toBe( '50 + (([qty]*2+1)(5*10))(1)' );
} );
it( 'should return the original input for non-string, non-number inputs', () => {
const input = { some: 'object' };
expect( safeNumberFormat( config, input ) ).toBe( input );
} );
} );

View File

@ -0,0 +1,4 @@
Significance: patch
Type: fix
Fix shipping cost formatting to respect shipping formula

View File

@ -281,7 +281,7 @@ class WC_Shipping_Flat_Rate extends WC_Shipping_Method {
public function sanitize_cost( $value ) {
$value = is_null( $value ) ? '' : $value;
$value = wp_kses_post( trim( wp_unslash( $value ) ) );
$value = str_replace( array( get_woocommerce_currency_symbol(), html_entity_decode( get_woocommerce_currency_symbol() ), wc_get_price_thousand_separator() ), '', $value );
$value = str_replace( array( get_woocommerce_currency_symbol(), html_entity_decode( get_woocommerce_currency_symbol() ) ), '', $value );
// Thrown an error on the front end if the evaluate_cost will fail.
$dummy_cost = $this->evaluate_cost(
$value,

View File

@ -94,9 +94,12 @@ class WC_Shipping_Free_Shipping extends WC_Shipping_Method {
public function sanitize_cost( $value ) {
$value = is_null( $value ) ? '' : $value;
$value = wp_kses_post( trim( wp_unslash( $value ) ) );
$value = str_replace( array( get_woocommerce_currency_symbol(), html_entity_decode( get_woocommerce_currency_symbol() ), wc_get_price_thousand_separator() ), '', $value );
$value = str_replace( array( get_woocommerce_currency_symbol(), html_entity_decode( get_woocommerce_currency_symbol() ) ), '', $value );
if ( ! is_numeric( $value ) ) {
$test_value = str_replace( wc_get_price_decimal_separator(), '.', $value );
$test_value = str_replace( array( get_woocommerce_currency_symbol(), html_entity_decode( get_woocommerce_currency_symbol() ), wc_get_price_thousand_separator() ), '', $test_value );
if ( $test_value && ! is_numeric( $test_value ) ) {
throw new Exception( __( 'Please enter a valid number', 'woocommerce' ) );
}

View File

@ -90,9 +90,12 @@ class WC_Shipping_Local_Pickup extends WC_Shipping_Method {
public function sanitize_cost( $value ) {
$value = is_null( $value ) ? '' : $value;
$value = wp_kses_post( trim( wp_unslash( $value ) ) );
$value = str_replace( array( get_woocommerce_currency_symbol(), html_entity_decode( get_woocommerce_currency_symbol() ), wc_get_price_thousand_separator() ), '', $value );
$value = str_replace( array( get_woocommerce_currency_symbol(), html_entity_decode( get_woocommerce_currency_symbol() ) ), '', $value );
if ( $value && ! is_numeric( $value ) ) {
$test_value = str_replace( wc_get_price_decimal_separator(), '.', $value );
$test_value = str_replace( array( get_woocommerce_currency_symbol(), html_entity_decode( get_woocommerce_currency_symbol() ), wc_get_price_thousand_separator() ), '', $test_value );
if ( $test_value && ! is_numeric( $test_value ) ) {
throw new Exception( __( 'Please enter a valid number', 'woocommerce' ) );
}