1
0
mirror of https://github.com/php/php-src.git synced 2026-03-24 00:02:20 +01:00

Merge branch 'PHP-8.3' into PHP-8.4

* PHP-8.3:
  Fix GH-11952: better locale strings canonicalization for IntlDateFormatter and NumberFormatter (#19593)
This commit is contained in:
Alexandre Daubois
2025-09-08 16:38:31 +02:00
7 changed files with 115 additions and 6 deletions

4
NEWS
View File

@@ -32,6 +32,10 @@ PHP NEWS
- FPM:
. Fixed failed debug assertion when php_admin_value setting fails. (ilutov)
- Intl:
. Fixed bug GH-11952 (Fix locale strings canonicalization for IntlDateFormatter
and NumberFormatter). (alexandre-daubois)
- Opcache:
. Fixed bug GH-19493 (JIT variable not stored before YIELD). (Arnaud)

View File

@@ -22,6 +22,7 @@
extern "C" {
#include <unicode/ustring.h>
#include <unicode/udat.h>
#include <unicode/uloc.h>
#include "php_intl.h"
#include "dateformat_create.h"
@@ -110,7 +111,12 @@ static zend_result datefmt_ctor(INTERNAL_FUNCTION_PARAMETERS, zend_error_handlin
if (locale_len == 0) {
locale_str = (char *) intl_locale_get_default();
}
locale = Locale::createFromName(locale_str);
char* canonicalized_locale = canonicalize_locale_string(locale_str);
const char* final_locale = canonicalized_locale ? canonicalized_locale : locale_str;
const char* stored_locale = canonicalized_locale ? canonicalized_locale : locale_str;
locale = Locale::createFromName(final_locale);
/* get*Name accessors being set does not preclude being bogus */
if (locale.isBogus() || ((locale_len == 1 && locale_str[0] != 'C') || (locale_len > 1 && strlen(locale.getISO3Language()) == 0))) {
zend_argument_value_error(1, "\"%s\" is invalid", locale_str);
@@ -149,7 +155,7 @@ static zend_result datefmt_ctor(INTERNAL_FUNCTION_PARAMETERS, zend_error_handlin
}
DATE_FORMAT_OBJECT(dfo) = udat_open((UDateFormatStyle)time_type,
(UDateFormatStyle)date_type, locale_str, NULL, 0, svalue,
(UDateFormatStyle)date_type, final_locale, NULL, 0, svalue,
slength, &INTL_DATA_ERROR_CODE(dfo));
if (pattern_str && pattern_str_len > 0) {
@@ -182,9 +188,13 @@ static zend_result datefmt_ctor(INTERNAL_FUNCTION_PARAMETERS, zend_error_handlin
dfo->date_type = date_type;
dfo->time_type = time_type;
dfo->calendar = calendar_type;
dfo->requested_locale = estrdup(locale_str);
/* Store the canonicalized locale, or fallback to original if canonicalization failed */
dfo->requested_locale = estrdup(stored_locale);
error:
if (canonicalized_locale) {
efree(canonicalized_locale);
}
if (svalue) {
efree(svalue);
}

View File

@@ -69,12 +69,18 @@ static int numfmt_ctor(INTERNAL_FUNCTION_PARAMETERS, zend_error_handling *error_
return FAILURE;
}
/* Create an ICU number formatter. */
FORMATTER_OBJECT(nfo) = unum_open(style, spattern, spattern_len, locale, NULL, &INTL_DATA_ERROR_CODE(nfo));
char* canonicalized_locale = canonicalize_locale_string(locale);
const char* final_locale = canonicalized_locale ? canonicalized_locale : locale;
if(spattern) {
FORMATTER_OBJECT(nfo) = unum_open(style, spattern, spattern_len, final_locale, NULL, &INTL_DATA_ERROR_CODE(nfo));
if (spattern) {
efree(spattern);
}
if (canonicalized_locale) {
efree(canonicalized_locale);
}
INTL_CTOR_CHECK_STATUS(nfo, "numfmt_create: number formatter creation failed");
return SUCCESS;

View File

@@ -96,6 +96,20 @@ const char *intl_locale_get_default( void )
return INTL_G(default_locale);
}
char* canonicalize_locale_string(const char* locale) {
char canonicalized[ULOC_FULLNAME_CAPACITY];
UErrorCode status = U_ZERO_ERROR;
int32_t canonicalized_len;
canonicalized_len = uloc_canonicalize(locale, canonicalized, sizeof(canonicalized), &status);
if (U_FAILURE(status) || canonicalized_len <= 0) {
return NULL;
}
return estrdup(canonicalized);
}
/* {{{ INI Settings */
PHP_INI_BEGIN()
STD_PHP_INI_ENTRY(LOCALE_INI_NAME, NULL, PHP_INI_ALL, OnUpdateStringUnempty, default_locale, zend_intl_globals, intl_globals)

View File

@@ -68,6 +68,7 @@ PHP_RSHUTDOWN_FUNCTION(intl);
PHP_MINFO_FUNCTION(intl);
const char *intl_locale_get_default( void );
char *canonicalize_locale_string(const char* locale);
#define PHP_INTL_VERSION PHP_VERSION

View File

@@ -0,0 +1,37 @@
--TEST--
Fix GH-11942: IntlDateFormatter should canonicalize locale strings
--EXTENSIONS--
intl
--FILE--
<?php
$test_cases = [
['pt', 'pt'],
['pt-PT', 'pt_PT'],
['pt_PT.utf8', 'pt_PT'],
['fr_CA@euro', 'fr_CA'],
];
echo "Testing IntlDateFormatter locale canonicalization:\n";
foreach ($test_cases as $test_case) {
[$input, $expected] = $test_case;
$formatter = new IntlDateFormatter($input, IntlDateFormatter::SHORT, IntlDateFormatter::NONE, 'UTC');
$actual = $formatter->getLocale();
$status = ($actual === $expected) ? 'PASS' : 'FAIL';
echo "Input: $input -> Expected: $expected -> Actual: $actual -> $status\n";
}
$dateFormatter = new IntlDateFormatter('pt_PT.utf8', IntlDateFormatter::SHORT, IntlDateFormatter::NONE, 'UTC');
$dateResult = $dateFormatter->format(1691585260);
echo "\nDateFormatter with pt_PT.utf8: " . $dateResult . "\n";
?>
--EXPECT--
Testing IntlDateFormatter locale canonicalization:
Input: pt -> Expected: pt -> Actual: pt -> PASS
Input: pt-PT -> Expected: pt_PT -> Actual: pt_PT -> PASS
Input: pt_PT.utf8 -> Expected: pt_PT -> Actual: pt_PT -> PASS
Input: fr_CA@euro -> Expected: fr_CA -> Actual: fr_CA -> PASS
DateFormatter with pt_PT.utf8: 09/08/23

View File

@@ -0,0 +1,37 @@
--TEST--
Fix GH-11942: NumberFormatter should canonicalize locale strings
--EXTENSIONS--
intl
--FILE--
<?php
$test_cases = [
['pt', 'pt'],
['pt-PT', 'pt_PT'],
['pt_PT.utf8', 'pt_PT'],
['fr_CA@euro', 'fr_CA'],
];
echo "Testing NumberFormatter locale canonicalization:\n";
foreach ($test_cases as $test_case) {
[$input, $expected] = $test_case;
$formatter = new NumberFormatter($input, NumberFormatter::DECIMAL);
$actual = $formatter->getLocale();
$status = ($actual === $expected) ? 'PASS' : 'FAIL';
echo "Input: $input -> Expected: $expected -> Actual: $actual -> $status\n";
}
$numFormatter = new NumberFormatter('pt_PT.utf8', NumberFormatter::DECIMAL);
$numResult = $numFormatter->format(1234.56);
echo "\nNumberFormatter with pt_PT.utf8: " . $numResult . "\n";
?>
--EXPECT--
Testing NumberFormatter locale canonicalization:
Input: pt -> Expected: pt -> Actual: pt -> PASS
Input: pt-PT -> Expected: pt_PT -> Actual: pt_PT -> PASS
Input: pt_PT.utf8 -> Expected: pt_PT -> Actual: pt_PT -> PASS
Input: fr_CA@euro -> Expected: fr_CA -> Actual: fr_CA -> PASS
NumberFormatter with pt_PT.utf8: 1 234,56