UIUX: Interpret first token as thousands separator 21.500,00 (when French, ...). (#33832)

* Qual: Add testcase for price2num to convert '21.500,00' in FR

* NEW: Accept numbers like 1.213,00 in locales with whitespace/empty thousands separator

# NEW: Accept numbers like 1.213,00 in locales with whitespace/empty thousands separator

- Graceful handling of comma and dot as decimal points and thousands separators in the `price2num` function.
- Improved the `getNonEmptyString` function to include proper indentation and spacing (automatic).

* Add testcases for price2num

* en_US tests for invalid prices, add doc for price2num regex

* Restore lost test to validate compatibility

* Update FunctionsLibTest.php

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
This commit is contained in:
MDW
2025-04-21 11:57:13 +02:00
committed by GitHub
parent e013e1c1b4
commit 69e3d4b5df
2 changed files with 71 additions and 39 deletions

View File

@@ -2595,7 +2595,7 @@ function dol_syslog($message, $level = LOG_INFO, $ident = 0, $suffixinfilename =
$data['ip'] .= (($j == 1) ? ' [via ' : ',').$remoteip; $data['ip'] .= (($j == 1) ? ' [via ' : ',').$remoteip;
} }
$data['ip'] .= (($j > 0) ? ']' : ''); $data['ip'] .= (($j > 0) ? ']' : '');
} elseif (!empty($_SERVER['HTTP_CLIENT_IP']) ) { } elseif (!empty($_SERVER['HTTP_CLIENT_IP'])) {
$tmpips = explode(',', $_SERVER['HTTP_CLIENT_IP']); $tmpips = explode(',', $_SERVER['HTTP_CLIENT_IP']);
$data['ip'] = ''; $data['ip'] = '';
$foundremoteip = 0; $foundremoteip = 0;
@@ -7258,9 +7258,30 @@ function price2num($amount, $rounding = '', $option = 0)
} }
//print "QQ".$amount."<br>\n"; //print "QQ".$amount."<br>\n";
// Now make replace (the main goal of function) // Now make replaceents (the main goal of function)
if ($thousand != ',' && $thousand != '.') { if ($thousand != ',' && $thousand != '.') {
$amount = str_replace(',', '.', $amount); // To accept 2 notations for french users // Accept the two types of decimal points french users (i.e., using ' ' for thousands)
// REGEX: Find the integral and decimal parts.
//
// We require that the decimal point only appears once in $amount.
// The regex `/^(?<int>[^,]*,|[^.]*\.)(?<dec>[^.,]*)$/u` can be broken down as follows:
// - `(?<int>[^,]*,|[^.]*\.)` is any accepted sequence up to the last potential decimal point '.' or ',' and named `int`.
// It covers two cases:
// - `[^,]*,`: Any sequence of characters that is not ',' with ',' accepted as the decimal point (from start of string because of earlier `^`);
// - `[^.]*\.`: Any sequence of characters that is not a '.' with '.' accepted as the decimal point (from start of string.
// - `(?<dec>[^.,]*)`: The sequence after the character accepted as the decimal point, not including it.
if (preg_match('/^(?<int>[^,]*,|[^.]*\.)(?<dec>[^.,]*)$/u', $amount, $matches)) {
$intPart = $matches['int'];
$decPart = $matches['dec'];
// Remove all commas and dots from intPart
$intPart = str_replace(['.', ','], '', $intPart);
// Combine intPart and decPart with a dot
$amount = $intPart . $dec . $decPart;
}
} }
$amount = str_replace(' ', '', $amount); // To avoid spaces $amount = str_replace(' ', '', $amount); // To avoid spaces
@@ -12122,16 +12143,23 @@ function dolExplodeKeepIfQuotes($input)
* @return string * @return string
*/ */
static function ($a, $b, $c) { static function ($a, $b, $c) {
if ($a !== '') return $a; if ($a !== '') {
if ($b !== '') return $b; return $a;
if ($c !== '') return $c; }
if ($b !== '') {
return $b;
}
if ($c !== '') {
return $c;
}
return ''; return '';
}, },
$matches[1], $matches[1],
$matches[2], $matches[2],
$matches[3] $matches[3]
); );
return array_values(array_filter($result, return array_values(array_filter(
$result,
/** /**
* Filter out empty strings from the result array. * Filter out empty strings from the result array.
* *
@@ -12140,7 +12168,8 @@ function dolExplodeKeepIfQuotes($input)
*/ */
static function ($val) { static function ($val) {
return $val !== ''; return $val !== '';
})); }
));
} }

View File

@@ -2,6 +2,7 @@
/* Copyright (C) 2010-2014 Laurent Destailleur <eldy@users.sourceforge.net> /* Copyright (C) 2010-2014 Laurent Destailleur <eldy@users.sourceforge.net>
* Copyright (C) 2015 Juanjo Menent <jmenent@2byte.es> * Copyright (C) 2015 Juanjo Menent <jmenent@2byte.es>
* Copyright (C) 2023 Alexandre Janniaux <alexandre.janniaux@gmail.com> * Copyright (C) 2023 Alexandre Janniaux <alexandre.janniaux@gmail.com>
* Copyright (C) 2025 MDW <mdeweerd@users.noreply.github.com>
* *
* This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@@ -1618,6 +1619,9 @@ class FunctionsLibTest extends CommonClassTest
$this->assertEquals('12.4', price2num('12.4$')); $this->assertEquals('12.4', price2num('12.4$'));
$this->assertEquals('12.4', price2num('12r.4$')); $this->assertEquals('12.4', price2num('12r.4$'));
$this->assertEquals('1.023210.00', price2num('1.023,210.00'), 'Test invalid 1.023,210.00 with en_US');
$this->assertEquals('1023.21000', price2num('1,023.210,00'), 'Test invalid 1,023.210,00 with en_US');
// For spanish language SeparatorThousand=. and SeparatorDecimal=, // For spanish language SeparatorThousand=. and SeparatorDecimal=,
$newlangs2 = new Translate('', $conf); $newlangs2 = new Translate('', $conf);
$newlangs2->setDefaultLang('es_ES'); $newlangs2->setDefaultLang('es_ES');
@@ -1667,11 +1671,10 @@ class FunctionsLibTest extends CommonClassTest
$this->assertEquals(21500000, price2num('21 500 000'), 'Test 21 500 000 give 21500000 with french language'); $this->assertEquals(21500000, price2num('21 500 000'), 'Test 21 500 000 give 21500000 with french language');
$this->assertEquals(21500, price2num('21500.00'), 'Test 21500.00 give 21500 with french language'); $this->assertEquals(21500, price2num('21500.00'), 'Test 21500.00 give 21500 with french language');
$this->assertEquals(21500, price2num('21500,00'), 'Test 21500,00 give 21500 with french language'); $this->assertEquals(21500, price2num('21500,00'), 'Test 21500,00 give 21500 with french language');
/*
$this->assertEquals(21500, price2num('21.500,00'), 'Test 21.500,00 give 21500 with french language'); $this->assertEquals(21500, price2num('21.500,00'), 'Test 21.500,00 give 21500 with french language');
$this->assertEquals('1.023.210.00', price2num('1.023,210.00'), 'Test invalid 1.023,210.00 with french language'); $this->assertEquals('1.023.210.00', price2num('1.023,210.00'), 'Test invalid 1.023,210.00 with french language');
$this->assertEquals('1.023.210.00', price2num('1,023.210,00'), 'Test invalid 1,023.210,00 with french language'); $this->assertEquals('1.023.210.00', price2num('1,023.210,00'), 'Test invalid 1,023.210,00 with french language');
*/
$langs = $oldlangs; $langs = $oldlangs;