diff --git a/ext/intl/locale/locale.stub.php b/ext/intl/locale/locale.stub.php index 03813ecaf194..80a9c5d8d16b 100644 --- a/ext/intl/locale/locale.stub.php +++ b/ext/intl/locale/locale.stub.php @@ -92,6 +92,18 @@ public static function getDisplayLanguage(string $locale, ?string $displayLocale */ public static function getDisplayVariant(string $locale, ?string $displayLocale = null): string|false {} + /** + * @tentative-return-type + * @alias locale_get_display_keyword + */ + public static function getDisplayKeyword(string $keyword, ?string $displayLocale = null): string|false {} + + /** + * @tentative-return-type + * @alias locale_get_display_keyword_value + */ + public static function getDisplayKeywordValue(string $locale, string $keyword, ?string $displayLocale = null): string|false {} + /** * @tentative-return-type * @alias locale_compose diff --git a/ext/intl/locale/locale_arginfo.h b/ext/intl/locale/locale_arginfo.h index 1d6e6683e21a..e897089dc066 100644 --- a/ext/intl/locale/locale_arginfo.h +++ b/ext/intl/locale/locale_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit locale.stub.php instead. - * Stub hash: ff1f75bd34a52f57210734e2f5e29efb87566137 */ + * Stub hash: 0e2cadbe162396249f8e0c90d8e899c7cf6b394e */ ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_Locale_getDefault, 0, 0, IS_STRING, 0) ZEND_END_ARG_INFO() @@ -33,6 +33,17 @@ ZEND_END_ARG_INFO() #define arginfo_class_Locale_getDisplayVariant arginfo_class_Locale_getDisplayScript +ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_MASK_EX(arginfo_class_Locale_getDisplayKeyword, 0, 1, MAY_BE_STRING|MAY_BE_FALSE) + ZEND_ARG_TYPE_INFO(0, keyword, IS_STRING, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, displayLocale, IS_STRING, 1, "null") +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_MASK_EX(arginfo_class_Locale_getDisplayKeywordValue, 0, 2, MAY_BE_STRING|MAY_BE_FALSE) + ZEND_ARG_TYPE_INFO(0, locale, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, keyword, IS_STRING, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, displayLocale, IS_STRING, 1, "null") +ZEND_END_ARG_INFO() + ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_MASK_EX(arginfo_class_Locale_composeLocale, 0, 1, MAY_BE_STRING|MAY_BE_FALSE) ZEND_ARG_TYPE_INFO(0, subtags, IS_ARRAY, 0) ZEND_END_ARG_INFO() @@ -83,6 +94,8 @@ ZEND_FUNCTION(locale_get_display_region); ZEND_FUNCTION(locale_get_display_name); ZEND_FUNCTION(locale_get_display_language); ZEND_FUNCTION(locale_get_display_variant); +ZEND_FUNCTION(locale_get_display_keyword); +ZEND_FUNCTION(locale_get_display_keyword_value); ZEND_FUNCTION(locale_compose); ZEND_FUNCTION(locale_parse); ZEND_FUNCTION(locale_get_all_variants); @@ -106,6 +119,8 @@ static const zend_function_entry class_Locale_methods[] = { ZEND_RAW_FENTRY("getDisplayName", zif_locale_get_display_name, arginfo_class_Locale_getDisplayName, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC, NULL, NULL) ZEND_RAW_FENTRY("getDisplayLanguage", zif_locale_get_display_language, arginfo_class_Locale_getDisplayLanguage, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC, NULL, NULL) ZEND_RAW_FENTRY("getDisplayVariant", zif_locale_get_display_variant, arginfo_class_Locale_getDisplayVariant, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC, NULL, NULL) + ZEND_RAW_FENTRY("getDisplayKeyword", zif_locale_get_display_keyword, arginfo_class_Locale_getDisplayKeyword, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC, NULL, NULL) + ZEND_RAW_FENTRY("getDisplayKeywordValue", zif_locale_get_display_keyword_value, arginfo_class_Locale_getDisplayKeywordValue, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC, NULL, NULL) ZEND_RAW_FENTRY("composeLocale", zif_locale_compose, arginfo_class_Locale_composeLocale, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC, NULL, NULL) ZEND_RAW_FENTRY("parseLocale", zif_locale_parse, arginfo_class_Locale_parseLocale, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC, NULL, NULL) ZEND_RAW_FENTRY("getAllVariants", zif_locale_get_all_variants, arginfo_class_Locale_getAllVariants, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC, NULL, NULL) diff --git a/ext/intl/locale/locale_methods.cpp b/ext/intl/locale/locale_methods.cpp index 1ee32a2f094e..7d82548d1c50 100644 --- a/ext/intl/locale/locale_methods.cpp +++ b/ext/intl/locale/locale_methods.cpp @@ -44,6 +44,8 @@ ZEND_EXTERN_MODULE_GLOBALS( intl ) #define EXTLANG_PREFIX "a" #define PRIVATE_PREFIX "x" #define DISP_NAME "name" +#define DISP_KEYWORD "keyword" +#define DISP_KEYWORD_VALUE "keyword_value" #define MAX_NO_VARIANT 15 #define MAX_NO_EXTLANG 3 @@ -671,6 +673,107 @@ static void get_icu_disp_value_src_php( const char* tag_name, INTERNAL_FUNCTION_ } /* }}} */ +/* {{{ + * common code shared by display keyword functions to get the value from ICU + }}} */ +static void get_icu_disp_keyword_value_src_php(const char* tag_name, INTERNAL_FUNCTION_PARAMETERS) +{ + char* loc_name = NULL; + size_t loc_name_len = 0; + char* keyword_name = NULL; + size_t keyword_name_len = 0; + char* disp_loc_name = NULL; + size_t disp_loc_name_len = 0; + int free_loc_name = 0; + + UChar* disp_name = NULL; + int32_t disp_name_len = 0; + int32_t buflen = 512; + UErrorCode status = U_ZERO_ERROR; + + zend_string* u8str; + char* msg = NULL; + + intl_error_reset( NULL ); + + if (strcmp(tag_name, DISP_KEYWORD) == 0) { + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_PATH(keyword_name, keyword_name_len) + Z_PARAM_OPTIONAL + Z_PARAM_PATH_OR_NULL(disp_loc_name, disp_loc_name_len) + ZEND_PARSE_PARAMETERS_END(); + } else { + ZEND_PARSE_PARAMETERS_START(2, 3) + Z_PARAM_PATH(loc_name, loc_name_len) + Z_PARAM_PATH(keyword_name, keyword_name_len) + Z_PARAM_OPTIONAL + Z_PARAM_PATH_OR_NULL(disp_loc_name, disp_loc_name_len) + ZEND_PARSE_PARAMETERS_END(); + + if (loc_name_len > ULOC_FULLNAME_CAPACITY) { + intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR, "name too long"); + RETURN_FALSE; + } + + if (loc_name_len == 0) { + loc_name = (char *)intl_locale_get_default(); + } + } + + if (!disp_loc_name) { + disp_loc_name = estrdup(intl_locale_get_default()); + free_loc_name = 1; + } + + do { + disp_name = reinterpret_cast(erealloc(disp_name, buflen * sizeof(UChar))); + disp_name_len = buflen; + + if (strcmp(tag_name, DISP_KEYWORD) == 0) { + buflen = uloc_getDisplayKeyword(keyword_name, disp_loc_name, disp_name, disp_name_len, &status); + } else { + buflen = uloc_getDisplayKeywordValue(loc_name, keyword_name, disp_loc_name, disp_name, disp_name_len, &status); + } + + /* U_STRING_NOT_TERMINATED_WARNING is admissible here; don't look for it */ + if (U_FAILURE(status)) { + if (status == U_BUFFER_OVERFLOW_ERROR) { + status = U_ZERO_ERROR; + continue; + } + + spprintf(&msg, 0, "unable to get locale %s", tag_name); + intl_error_set( NULL, status, msg); + efree(msg); + if (disp_name) { + efree(disp_name); + } + if (free_loc_name) { + efree((void *)disp_loc_name); + disp_loc_name = NULL; + } + RETURN_FALSE; + } + } while (buflen > disp_name_len); + + if (free_loc_name) { + efree((void *)disp_loc_name); + disp_loc_name = NULL; + } + + u8str = intl_convert_utf16_to_utf8(disp_name, buflen, &status); + efree(disp_name); + if (!u8str) { + spprintf(&msg, 0, "error converting display name for %s to UTF-8", tag_name); + intl_error_set( NULL, status, msg); + efree(msg); + RETURN_FALSE; + } + + RETVAL_NEW_STR(u8str); +} +/* }}} */ + /* {{{ gets the name for the $locale in $in_locale or default_locale */ U_CFUNC PHP_FUNCTION(locale_get_display_name) { @@ -711,6 +814,20 @@ U_CFUNC PHP_FUNCTION(locale_get_display_variant) { get_icu_disp_value_src_php( LOC_VARIANT_TAG , INTERNAL_FUNCTION_PARAM_PASSTHRU ); } +/* }}} */ + +/* {{{ gets the keyword display label in $in_locale or default_locale */ +U_CFUNC PHP_FUNCTION(locale_get_display_keyword) +{ + get_icu_disp_keyword_value_src_php(DISP_KEYWORD, INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + +/* {{{ gets the keyword value display label in $in_locale or default_locale */ +U_CFUNC PHP_FUNCTION(locale_get_display_keyword_value) +{ + get_icu_disp_keyword_value_src_php(DISP_KEYWORD_VALUE, INTERNAL_FUNCTION_PARAM_PASSTHRU); +} /* }}} */ /* {{{ return an associative array containing keyword-value diff --git a/ext/intl/php_intl.stub.php b/ext/intl/php_intl.stub.php index 4bcb8587f786..1e6c5cacf203 100644 --- a/ext/intl/php_intl.stub.php +++ b/ext/intl/php_intl.stub.php @@ -487,6 +487,10 @@ function locale_get_display_language(string $locale, ?string $displayLocale = nu function locale_get_display_variant(string $locale, ?string $displayLocale = null): string|false {} +function locale_get_display_keyword(string $keyword, ?string $displayLocale = null): string|false {} + +function locale_get_display_keyword_value(string $locale, string $keyword, ?string $displayLocale = null): string|false {} + function locale_compose(array $subtags): string|false {} function locale_parse(string $locale): ?array {} diff --git a/ext/intl/php_intl_arginfo.h b/ext/intl/php_intl_arginfo.h index 81160349980c..00de5986f1ef 100644 --- a/ext/intl/php_intl_arginfo.h +++ b/ext/intl/php_intl_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit php_intl.stub.php instead. - * Stub hash: c52fd0def2530be628beedbbcdcfecdcb07449a8 */ + * Stub hash: f94e7c9cc372878f1f8bd0e948092ea72076e687 */ ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_intlcal_create_instance, 0, 0, IntlCalendar, 1) ZEND_ARG_OBJ_TYPE_MASK(0, timezone, IntlTimeZone|DateTimeZone, MAY_BE_STRING|MAY_BE_NULL, "null") @@ -553,6 +553,17 @@ ZEND_END_ARG_INFO() #define arginfo_locale_get_display_variant arginfo_locale_get_display_script +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_locale_get_display_keyword, 0, 1, MAY_BE_STRING|MAY_BE_FALSE) + ZEND_ARG_TYPE_INFO(0, keyword, IS_STRING, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, displayLocale, IS_STRING, 1, "null") +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_locale_get_display_keyword_value, 0, 2, MAY_BE_STRING|MAY_BE_FALSE) + ZEND_ARG_TYPE_INFO(0, locale, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, keyword, IS_STRING, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, displayLocale, IS_STRING, 1, "null") +ZEND_END_ARG_INFO() + ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_locale_compose, 0, 1, MAY_BE_STRING|MAY_BE_FALSE) ZEND_ARG_TYPE_INFO(0, subtags, IS_ARRAY, 0) ZEND_END_ARG_INFO() @@ -941,6 +952,8 @@ ZEND_FUNCTION(locale_get_display_region); ZEND_FUNCTION(locale_get_display_name); ZEND_FUNCTION(locale_get_display_language); ZEND_FUNCTION(locale_get_display_variant); +ZEND_FUNCTION(locale_get_display_keyword); +ZEND_FUNCTION(locale_get_display_keyword_value); ZEND_FUNCTION(locale_compose); ZEND_FUNCTION(locale_parse); ZEND_FUNCTION(locale_get_all_variants); @@ -1133,6 +1146,8 @@ static const zend_function_entry ext_functions[] = { ZEND_FE(locale_get_display_name, arginfo_locale_get_display_name) ZEND_FE(locale_get_display_language, arginfo_locale_get_display_language) ZEND_FE(locale_get_display_variant, arginfo_locale_get_display_variant) + ZEND_FE(locale_get_display_keyword, arginfo_locale_get_display_keyword) + ZEND_FE(locale_get_display_keyword_value, arginfo_locale_get_display_keyword_value) ZEND_FE(locale_compose, arginfo_locale_compose) ZEND_FE(locale_parse, arginfo_locale_parse) ZEND_FE(locale_get_all_variants, arginfo_locale_get_all_variants) diff --git a/ext/intl/tests/locale/bug74993.phpt b/ext/intl/tests/locale/bug74993.phpt index 1d0a1a97b75c..7bcf931c4e2b 100644 --- a/ext/intl/tests/locale/bug74993.phpt +++ b/ext/intl/tests/locale/bug74993.phpt @@ -5,6 +5,8 @@ intl --FILE-- --EXPECT-- +Function [ function locale_get_display_keyword ] { + + - Parameters [2] { + Parameter #0 [ string $keyword ] + Parameter #1 [ ?string $displayLocale = null ] + } + - Return [ string|false ] +} +Function [ function locale_get_display_keyword_value ] { + + - Parameters [3] { + Parameter #0 [ string $locale ] + Parameter #1 [ string $keyword ] + Parameter #2 [ ?string $displayLocale = null ] + } + - Return [ string|false ] +} Function [ function locale_get_display_language ] { - Parameters [2] { diff --git a/ext/intl/tests/locale_get_display_keyword.phpt b/ext/intl/tests/locale_get_display_keyword.phpt new file mode 100644 index 000000000000..b8f7f99b762b --- /dev/null +++ b/ext/intl/tests/locale_get_display_keyword.phpt @@ -0,0 +1,35 @@ +--TEST-- +locale_get_display_keyword() basic +--EXTENSIONS-- +intl +--FILE-- + +--EXPECT-- +string(8) "Calendar" +string(8) "Calendar" +string(18) "Gregorian Calendar" +string(18) "Gregorian Calendar" +string(20) "Phonebook Sort Order" +string(8) "Calendar" +string(8) "Calendar" +string(18) "Gregorian Calendar" +string(18) "Gregorian Calendar" +string(20) "Phonebook Sort Order" diff --git a/ext/intl/tests/locale_get_display_keyword_null_bytes.phpt b/ext/intl/tests/locale_get_display_keyword_null_bytes.phpt new file mode 100644 index 000000000000..fe83ac1b731f --- /dev/null +++ b/ext/intl/tests/locale_get_display_keyword_null_bytes.phpt @@ -0,0 +1,40 @@ +--TEST-- +locale_get_display_keyword() throwing null bytes exceptions. +--EXTENSIONS-- +intl +--FILE-- + ut_loc_get_display_keyword("cur\0rency", "fr"), + fn() => ut_loc_get_display_keyword("currency", "f\0r"), + fn() => ut_loc_get_display_keyword_value("de_DE@calendar=gregorian\0", "calendar", "en"), + fn() => ut_loc_get_display_keyword_value("de_DE@calendar=gregorian", "cal\0endar", "en"), + fn() => ut_loc_get_display_keyword_value("de_DE@calendar=gregorian", "calendar", "e\0n"), + ]; + + foreach ($calls as $call) { + try { + $call(); + } catch (\ValueError $e) { + echo $e->getMessage(), PHP_EOL; + } + } +} + +include_once 'ut_common.inc'; +ut_run(); +?> +--EXPECT-- +Locale::getDisplayKeyword(): Argument #1 ($keyword) must not contain any null bytes +Locale::getDisplayKeyword(): Argument #2 ($displayLocale) must not contain any null bytes +Locale::getDisplayKeywordValue(): Argument #1 ($locale) must not contain any null bytes +Locale::getDisplayKeywordValue(): Argument #2 ($keyword) must not contain any null bytes +Locale::getDisplayKeywordValue(): Argument #3 ($displayLocale) must not contain any null bytes +locale_get_display_keyword(): Argument #1 ($keyword) must not contain any null bytes +locale_get_display_keyword(): Argument #2 ($displayLocale) must not contain any null bytes +locale_get_display_keyword_value(): Argument #1 ($locale) must not contain any null bytes +locale_get_display_keyword_value(): Argument #2 ($keyword) must not contain any null bytes +locale_get_display_keyword_value(): Argument #3 ($displayLocale) must not contain any null bytes diff --git a/ext/intl/tests/ut_common.inc b/ext/intl/tests/ut_common.inc index fdc013dea41c..26f33f75f7fe 100644 --- a/ext/intl/tests/ut_common.inc +++ b/ext/intl/tests/ut_common.inc @@ -272,6 +272,14 @@ function ut_loc_get_display_variant( $locale , $dispLocale ) { return $GLOBALS['oo-mode'] ? Locale::getDisplayVariant( $locale , $dispLocale ) : locale_get_display_variant( $locale, $dispLocale ); } +function ut_loc_get_display_keyword( $keyword , $dispLocale ) +{ + return $GLOBALS['oo-mode'] ? Locale::getDisplayKeyword( $keyword , $dispLocale ) : locale_get_display_keyword( $keyword, $dispLocale ); +} +function ut_loc_get_display_keyword_value( $locale , $keyword , $dispLocale ) +{ + return $GLOBALS['oo-mode'] ? Locale::getDisplayKeywordValue( $locale , $keyword , $dispLocale ) : locale_get_display_keyword_value( $locale, $keyword, $dispLocale ); +} function ut_loc_locale_compose( $loc_parts_arr ) { return $GLOBALS['oo-mode'] ? Locale::composeLocale( $loc_parts_arr ) : locale_compose( $loc_parts_arr );