1 /*
2 +----------------------------------------------------------------------+
3 | PHP Version 7 |
4 +----------------------------------------------------------------------+
5 | This source file is subject to version 3.01 of the PHP license, |
6 | that is bundled with this package in the file LICENSE, and is |
7 | available through the world-wide-web at the following url: |
8 | http://www.php.net/license/3_01.txt |
9 | If you did not receive a copy of the PHP license and are unable to |
10 | obtain it through the world-wide-web, please send a note to |
11 | license@php.net so we can mail you a copy immediately. |
12 +----------------------------------------------------------------------+
13 | Authors: Kirti Velankar <kirtig@yahoo-inc.com> |
14 +----------------------------------------------------------------------+
15 */
16
17 /* $Id$ */
18
19 #ifdef HAVE_CONFIG_H
20 #include "config.h"
21 #endif
22
23 #include <unicode/ustring.h>
24 #include <unicode/udata.h>
25 #include <unicode/putil.h>
26 #include <unicode/ures.h>
27
28 #include "php_intl.h"
29 #include "locale.h"
30 #include "locale_class.h"
31 #include "locale_methods.h"
32 #include "intl_convert.h"
33 #include "intl_data.h"
34
35 #include <zend_API.h>
36 #include <zend.h>
37 #include <php.h>
38 #include "main/php_ini.h"
39 #include "zend_smart_str.h"
40
41 ZEND_EXTERN_MODULE_GLOBALS( intl )
42
43 /* Sizes required for the strings "variant15" , "extlang11", "private12" etc. */
44 #define SEPARATOR "_"
45 #define SEPARATOR1 "-"
46 #define DELIMITER "-_"
47 #define EXTLANG_PREFIX "a"
48 #define PRIVATE_PREFIX "x"
49 #define DISP_NAME "name"
50
51 #define MAX_NO_VARIANT 15
52 #define MAX_NO_EXTLANG 3
53 #define MAX_NO_PRIVATE 15
54 #define MAX_NO_LOOKUP_LANG_TAG 100
55
56 #define LOC_NOT_FOUND 1
57
58 /* Sizes required for the strings "variant15" , "extlang3", "private12" etc. */
59 #define VARIANT_KEYNAME_LEN 11
60 #define EXTLANG_KEYNAME_LEN 10
61 #define PRIVATE_KEYNAME_LEN 11
62
63 /* Based on IANA registry at the time of writing this code
64 *
65 */
66 static const char * const LOC_GRANDFATHERED[] = {
67 "art-lojban", "i-klingon", "i-lux", "i-navajo", "no-bok", "no-nyn",
68 "cel-gaulish", "en-GB-oed", "i-ami",
69 "i-bnn", "i-default", "i-enochian",
70 "i-mingo", "i-pwn", "i-tao",
71 "i-tay", "i-tsu", "sgn-BE-fr",
72 "sgn-BE-nl", "sgn-CH-de", "zh-cmn",
73 "zh-cmn-Hans", "zh-cmn-Hant", "zh-gan" ,
74 "zh-guoyu", "zh-hakka", "zh-min",
75 "zh-min-nan", "zh-wuu", "zh-xiang",
76 "zh-yue", NULL
77 };
78
79 /* Based on IANA registry at the time of writing this code
80 * This array lists the preferred values for the grandfathered tags if applicable
81 * This is in sync with the array LOC_GRANDFATHERED
82 * e.g. the offsets of the grandfathered tags match the offset of the preferred value
83 */
84 static const int LOC_PREFERRED_GRANDFATHERED_LEN = 6;
85 static const char * const LOC_PREFERRED_GRANDFATHERED[] = {
86 "jbo", "tlh", "lb",
87 "nv", "nb", "nn",
88 NULL
89 };
90
91 /*returns TRUE if a is an ID separator FALSE otherwise*/
92 #define isIDSeparator(a) (a == '_' || a == '-')
93 #define isKeywordSeparator(a) (a == '@' )
94 #define isEndOfTag(a) (a == '\0' )
95
96 #define isPrefixLetter(a) ((a=='x')||(a=='X')||(a=='i')||(a=='I'))
97
98 /*returns TRUE if one of the special prefixes is here (s=string)
99 'x-' or 'i-' */
100 #define isIDPrefix(s) (isPrefixLetter(s[0])&&isIDSeparator(s[1]))
101 #define isKeywordPrefix(s) ( isKeywordSeparator(s[0]) )
102
103 /* Dot terminates it because of POSIX form where dot precedes the codepage
104 * except for variant */
105 #define isTerminator(a) ((a==0)||(a=='.')||(a=='@'))
106
107 /* {{{ return the offset of 'key' in the array 'list'.
108 * returns -1 if not present */
findOffset(const char * const * list,const char * key)109 static int16_t findOffset(const char* const* list, const char* key)
110 {
111 const char* const* anchor = list;
112 while (*list != NULL) {
113 if (strcmp(key, *list) == 0) {
114 return (int16_t)(list - anchor);
115 }
116 list++;
117 }
118
119 return -1;
120
121 }
122 /*}}}*/
123
getPreferredTag(const char * gf_tag)124 static char* getPreferredTag(const char* gf_tag)
125 {
126 char* result = NULL;
127 int grOffset = 0;
128
129 grOffset = findOffset( LOC_GRANDFATHERED ,gf_tag);
130 if(grOffset < 0) {
131 return NULL;
132 }
133 if( grOffset < LOC_PREFERRED_GRANDFATHERED_LEN ){
134 /* return preferred tag */
135 result = estrdup( LOC_PREFERRED_GRANDFATHERED[grOffset] );
136 } else {
137 /* Return correct grandfathered language tag */
138 result = estrdup( LOC_GRANDFATHERED[grOffset] );
139 }
140 return result;
141 }
142
143 /* {{{
144 * returns the position of next token for lookup
145 * or -1 if no token
146 * strtokr equivalent search for token in reverse direction
147 */
getStrrtokenPos(char * str,int savedPos)148 static int getStrrtokenPos(char* str, int savedPos)
149 {
150 int result =-1;
151 int i;
152
153 for(i=savedPos-1; i>=0; i--) {
154 if(isIDSeparator(*(str+i)) ){
155 /* delimiter found; check for singleton */
156 if(i>=2 && isIDSeparator(*(str+i-2)) ){
157 /* a singleton; so send the position of token before the singleton */
158 result = i-2;
159 } else {
160 result = i;
161 }
162 break;
163 }
164 }
165 if(result < 1){
166 /* Just in case inavlid locale e.g. '-x-xyz' or '-sl_Latn' */
167 result =-1;
168 }
169 return result;
170 }
171 /* }}} */
172
173 /* {{{
174 * returns the position of a singleton if present
175 * returns -1 if no singleton
176 * strtok equivalent search for singleton
177 */
getSingletonPos(const char * str)178 static int getSingletonPos(const char* str)
179 {
180 int result =-1;
181 int i=0;
182 int len = 0;
183
184 if( str && ((len=strlen(str))>0) ){
185 for( i=0; i<len ; i++){
186 if( isIDSeparator(*(str+i)) ){
187 if( i==1){
188 /* string is of the form x-avy or a-prv1 */
189 result =0;
190 break;
191 } else {
192 /* delimiter found; check for singleton */
193 if( isIDSeparator(*(str+i+2)) ){
194 /* a singleton; so send the position of separator before singleton */
195 result = i+1;
196 break;
197 }
198 }
199 }
200 }/* end of for */
201
202 }
203 return result;
204 }
205 /* }}} */
206
207 /* {{{ proto static string Locale::getDefault( )
208 Get default locale */
209 /* }}} */
210 /* {{{ proto static string locale_get_default( )
211 Get default locale */
PHP_NAMED_FUNCTION(zif_locale_get_default)212 PHP_NAMED_FUNCTION(zif_locale_get_default)
213 {
214 RETURN_STRING( intl_locale_get_default( ) );
215 }
216
217 /* }}} */
218
219 /* {{{ proto static string Locale::setDefault( string $locale )
220 Set default locale */
221 /* }}} */
222 /* {{{ proto static string locale_set_default( string $locale )
223 Set default locale */
PHP_NAMED_FUNCTION(zif_locale_set_default)224 PHP_NAMED_FUNCTION(zif_locale_set_default)
225 {
226 zend_string* locale_name;
227 zend_string *ini_name;
228 char *default_locale = NULL;
229
230 if(zend_parse_parameters( ZEND_NUM_ARGS(), "S", &locale_name) == FAILURE)
231 {
232 intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR,
233 "locale_set_default: unable to parse input params", 0 );
234
235 RETURN_FALSE;
236 }
237
238 if (ZSTR_LEN(locale_name) == 0) {
239 default_locale = (char *)uloc_getDefault();
240 locale_name = zend_string_init(default_locale, strlen(default_locale), 0);
241 }
242
243 ini_name = zend_string_init(LOCALE_INI_NAME, sizeof(LOCALE_INI_NAME) - 1, 0);
244 zend_alter_ini_entry(ini_name, locale_name, PHP_INI_USER, PHP_INI_STAGE_RUNTIME);
245 zend_string_release(ini_name);
246 if (default_locale != NULL) {
247 zend_string_release(locale_name);
248 }
249
250 RETURN_TRUE;
251 }
252 /* }}} */
253
254 /* {{{
255 * Gets the value from ICU
256 * common code shared by get_primary_language,get_script or get_region or get_variant
257 * result = 0 if error, 1 if successful , -1 if no value
258 */
get_icu_value_internal(const char * loc_name,char * tag_name,int * result,int fromParseLocale)259 static zend_string* get_icu_value_internal( const char* loc_name , char* tag_name, int* result , int fromParseLocale)
260 {
261 zend_string* tag_value = NULL;
262 int32_t tag_value_len = 512;
263
264 int singletonPos = 0;
265 char* mod_loc_name = NULL;
266 int grOffset = 0;
267
268 int32_t buflen = 512;
269 UErrorCode status = U_ZERO_ERROR;
270
271 if (strlen(loc_name) > INTL_MAX_LOCALE_LEN) {
272 return NULL;
273 }
274
275 if( strcmp(tag_name, LOC_CANONICALIZE_TAG) != 0 ){
276 /* Handle grandfathered languages */
277 grOffset = findOffset( LOC_GRANDFATHERED , loc_name );
278 if( grOffset >= 0 ){
279 if( strcmp(tag_name , LOC_LANG_TAG)==0 ){
280 return zend_string_init(loc_name, strlen(loc_name), 0);
281 } else {
282 /* Since Grandfathered , no value , do nothing , retutn NULL */
283 return NULL;
284 }
285 }
286
287 if( fromParseLocale==1 ){
288 /* Handle singletons */
289 if( strcmp(tag_name , LOC_LANG_TAG)==0 ){
290 if( strlen(loc_name)>1 && (isIDPrefix(loc_name) == 1) ){
291 return zend_string_init(loc_name, strlen(loc_name), 0);
292 }
293 }
294
295 singletonPos = getSingletonPos( loc_name );
296 if( singletonPos == 0){
297 /* singleton at start of script, region , variant etc.
298 * or invalid singleton at start of language */
299 return NULL;
300 } else if( singletonPos > 0 ){
301 /* singleton at some position except at start
302 * strip off the singleton and rest of the loc_name */
303 mod_loc_name = estrndup ( loc_name , singletonPos-1);
304 }
305 } /* end of if fromParse */
306
307 } /* end of if != LOC_CANONICAL_TAG */
308
309 if( mod_loc_name == NULL){
310 mod_loc_name = estrdup(loc_name );
311 }
312
313 /* Proceed to ICU */
314 do{
315 if (tag_value) {
316 tag_value = zend_string_realloc( tag_value , buflen, 0);
317 } else {
318 tag_value = zend_string_alloc( buflen, 0);
319 }
320 tag_value_len = buflen;
321
322 if( strcmp(tag_name , LOC_SCRIPT_TAG)==0 ){
323 buflen = uloc_getScript ( mod_loc_name , tag_value->val , tag_value_len , &status);
324 }
325 if( strcmp(tag_name , LOC_LANG_TAG )==0 ){
326 buflen = uloc_getLanguage ( mod_loc_name , tag_value->val , tag_value_len , &status);
327 }
328 if( strcmp(tag_name , LOC_REGION_TAG)==0 ){
329 buflen = uloc_getCountry ( mod_loc_name , tag_value->val , tag_value_len , &status);
330 }
331 if( strcmp(tag_name , LOC_VARIANT_TAG)==0 ){
332 buflen = uloc_getVariant ( mod_loc_name , tag_value->val , tag_value_len , &status);
333 }
334 if( strcmp(tag_name , LOC_CANONICALIZE_TAG)==0 ){
335 buflen = uloc_canonicalize ( mod_loc_name , tag_value->val , tag_value_len , &status);
336 }
337
338 if( U_FAILURE( status ) ) {
339 if( status == U_BUFFER_OVERFLOW_ERROR ) {
340 status = U_ZERO_ERROR;
341 buflen++; /* add space for \0 */
342 continue;
343 }
344
345 /* Error in retriving data */
346 *result = 0;
347 if( tag_value ){
348 zend_string_release( tag_value );
349 }
350 if( mod_loc_name ){
351 efree( mod_loc_name);
352 }
353 return NULL;
354 }
355 } while( buflen > tag_value_len );
356
357 if( buflen ==0 ){
358 /* No value found */
359 *result = -1;
360 if( tag_value ){
361 zend_string_release( tag_value );
362 }
363 if( mod_loc_name ){
364 efree( mod_loc_name);
365 }
366 return NULL;
367 } else {
368 *result = 1;
369 }
370
371 if( mod_loc_name ){
372 efree( mod_loc_name);
373 }
374
375 tag_value->len = strlen(tag_value->val);
376 return tag_value;
377 }
378 /* }}} */
379
380 /* {{{
381 * Gets the value from ICU , called when PHP userspace function is called
382 * common code shared by get_primary_language,get_script or get_region or get_variant
383 */
get_icu_value_src_php(char * tag_name,INTERNAL_FUNCTION_PARAMETERS)384 static void get_icu_value_src_php( char* tag_name, INTERNAL_FUNCTION_PARAMETERS)
385 {
386
387 const char* loc_name = NULL;
388 size_t loc_name_len = 0;
389
390 zend_string* tag_value = NULL;
391 char* empty_result = "";
392
393 int result = 0;
394 char* msg = NULL;
395
396 UErrorCode status = U_ZERO_ERROR;
397
398 intl_error_reset( NULL );
399
400 if(zend_parse_parameters( ZEND_NUM_ARGS(), "s",
401 &loc_name ,&loc_name_len ) == FAILURE) {
402 spprintf(&msg , 0, "locale_get_%s : unable to parse input params", tag_name );
403 intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR, msg , 1 );
404 efree(msg);
405
406 RETURN_FALSE;
407 }
408
409 if(loc_name_len == 0) {
410 loc_name = intl_locale_get_default();
411 loc_name_len = strlen(loc_name);
412 }
413
414 INTL_CHECK_LOCALE_LEN(loc_name_len);
415
416 /* Call ICU get */
417 tag_value = get_icu_value_internal( loc_name , tag_name , &result ,0);
418
419 /* No value found */
420 if( result == -1 ) {
421 if( tag_value){
422 zend_string_release( tag_value);
423 }
424 RETURN_STRING( empty_result);
425 }
426
427 /* value found */
428 if( tag_value){
429 RETVAL_STR( tag_value );
430 return;
431 }
432
433 /* Error encountered while fetching the value */
434 if( result ==0) {
435 spprintf(&msg , 0, "locale_get_%s : unable to get locale %s", tag_name , tag_name );
436 intl_error_set( NULL, status, msg , 1 );
437 efree(msg);
438 RETURN_NULL();
439 }
440
441 }
442 /* }}} */
443
444 /* {{{ proto static string Locale::getScript($locale)
445 * gets the script for the $locale
446 }}} */
447 /* {{{ proto static string locale_get_script($locale)
448 * gets the script for the $locale
449 */
PHP_FUNCTION(locale_get_script)450 PHP_FUNCTION( locale_get_script )
451 {
452 get_icu_value_src_php( LOC_SCRIPT_TAG , INTERNAL_FUNCTION_PARAM_PASSTHRU );
453 }
454 /* }}} */
455
456 /* {{{ proto static string Locale::getRegion($locale)
457 * gets the region for the $locale
458 }}} */
459 /* {{{ proto static string locale_get_region($locale)
460 * gets the region for the $locale
461 */
PHP_FUNCTION(locale_get_region)462 PHP_FUNCTION( locale_get_region )
463 {
464 get_icu_value_src_php( LOC_REGION_TAG , INTERNAL_FUNCTION_PARAM_PASSTHRU );
465 }
466 /* }}} */
467
468 /* {{{ proto static string Locale::getPrimaryLanguage($locale)
469 * gets the primary language for the $locale
470 }}} */
471 /* {{{ proto static string locale_get_primary_language($locale)
472 * gets the primary language for the $locale
473 */
PHP_FUNCTION(locale_get_primary_language)474 PHP_FUNCTION(locale_get_primary_language )
475 {
476 get_icu_value_src_php( LOC_LANG_TAG , INTERNAL_FUNCTION_PARAM_PASSTHRU );
477 }
478 /* }}} */
479
480
481 /* {{{
482 * common code shared by display_xyz functions to get the value from ICU
483 }}} */
get_icu_disp_value_src_php(char * tag_name,INTERNAL_FUNCTION_PARAMETERS)484 static void get_icu_disp_value_src_php( char* tag_name, INTERNAL_FUNCTION_PARAMETERS)
485 {
486 const char* loc_name = NULL;
487 size_t loc_name_len = 0;
488
489 const char* disp_loc_name = NULL;
490 size_t disp_loc_name_len = 0;
491 int free_loc_name = 0;
492
493 UChar* disp_name = NULL;
494 int32_t disp_name_len = 0;
495
496 char* mod_loc_name = NULL;
497
498 int32_t buflen = 512;
499 UErrorCode status = U_ZERO_ERROR;
500
501 zend_string* u8str;
502
503 char* msg = NULL;
504 int grOffset = 0;
505
506 intl_error_reset( NULL );
507
508 if(zend_parse_parameters( ZEND_NUM_ARGS(), "s|s",
509 &loc_name, &loc_name_len ,
510 &disp_loc_name ,&disp_loc_name_len ) == FAILURE)
511 {
512 spprintf(&msg , 0, "locale_get_display_%s : unable to parse input params", tag_name );
513 intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR, msg , 1 );
514 efree(msg);
515 RETURN_FALSE;
516 }
517
518 if(loc_name_len > ULOC_FULLNAME_CAPACITY) {
519 /* See bug 67397: overlong locale names cause trouble in uloc_getDisplayName */
520 spprintf(&msg , 0, "locale_get_display_%s : name too long", tag_name );
521 intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR, msg , 1 );
522 efree(msg);
523 RETURN_FALSE;
524 }
525
526 if(loc_name_len == 0) {
527 loc_name = intl_locale_get_default();
528 }
529
530 if( strcmp(tag_name, DISP_NAME) != 0 ){
531 /* Handle grandfathered languages */
532 grOffset = findOffset( LOC_GRANDFATHERED , loc_name );
533 if( grOffset >= 0 ){
534 if( strcmp(tag_name , LOC_LANG_TAG)==0 ){
535 mod_loc_name = getPreferredTag( loc_name );
536 } else {
537 /* Since Grandfathered, no value, do nothing, retutn NULL */
538 RETURN_FALSE;
539 }
540 }
541 } /* end of if != LOC_CANONICAL_TAG */
542
543 if( mod_loc_name==NULL ){
544 mod_loc_name = estrdup( loc_name );
545 }
546
547 /* Check if disp_loc_name passed , if not use default locale */
548 if( !disp_loc_name){
549 disp_loc_name = estrdup(intl_locale_get_default());
550 free_loc_name = 1;
551 }
552
553 /* Get the disp_value for the given locale */
554 do{
555 disp_name = erealloc( disp_name , buflen * sizeof(UChar) );
556 disp_name_len = buflen;
557
558 if( strcmp(tag_name , LOC_LANG_TAG)==0 ){
559 buflen = uloc_getDisplayLanguage ( mod_loc_name , disp_loc_name , disp_name , disp_name_len , &status);
560 } else if( strcmp(tag_name , LOC_SCRIPT_TAG)==0 ){
561 buflen = uloc_getDisplayScript ( mod_loc_name , disp_loc_name , disp_name , disp_name_len , &status);
562 } else if( strcmp(tag_name , LOC_REGION_TAG)==0 ){
563 buflen = uloc_getDisplayCountry ( mod_loc_name , disp_loc_name , disp_name , disp_name_len , &status);
564 } else if( strcmp(tag_name , LOC_VARIANT_TAG)==0 ){
565 buflen = uloc_getDisplayVariant ( mod_loc_name , disp_loc_name , disp_name , disp_name_len , &status);
566 } else if( strcmp(tag_name , DISP_NAME)==0 ){
567 buflen = uloc_getDisplayName ( mod_loc_name , disp_loc_name , disp_name , disp_name_len , &status);
568 }
569
570 /* U_STRING_NOT_TERMINATED_WARNING is admissible here; don't look for it */
571 if( U_FAILURE( status ) )
572 {
573 if( status == U_BUFFER_OVERFLOW_ERROR )
574 {
575 status = U_ZERO_ERROR;
576 continue;
577 }
578
579 spprintf(&msg, 0, "locale_get_display_%s : unable to get locale %s", tag_name , tag_name );
580 intl_error_set( NULL, status, msg , 1 );
581 efree(msg);
582 if( disp_name){
583 efree( disp_name );
584 }
585 if( mod_loc_name){
586 efree( mod_loc_name );
587 }
588 if (free_loc_name) {
589 efree((void *)disp_loc_name);
590 disp_loc_name = NULL;
591 }
592 RETURN_FALSE;
593 }
594 } while( buflen > disp_name_len );
595
596 if( mod_loc_name){
597 efree( mod_loc_name );
598 }
599 if (free_loc_name) {
600 efree((void *)disp_loc_name);
601 disp_loc_name = NULL;
602 }
603 /* Convert display locale name from UTF-16 to UTF-8. */
604 u8str = intl_convert_utf16_to_utf8(disp_name, buflen, &status );
605 efree( disp_name );
606 if( !u8str )
607 {
608 spprintf(&msg, 0, "locale_get_display_%s :error converting display name for %s to UTF-8", tag_name , tag_name );
609 intl_error_set( NULL, status, msg , 1 );
610 efree(msg);
611 RETURN_FALSE;
612 }
613
614 RETVAL_NEW_STR( u8str );
615 }
616 /* }}} */
617
618 /* {{{ proto static string Locale::getDisplayName($locale[, $in_locale = null])
619 * gets the name for the $locale in $in_locale or default_locale
620 }}} */
621 /* {{{ proto static string get_display_name($locale[, $in_locale = null])
622 * gets the name for the $locale in $in_locale or default_locale
623 */
PHP_FUNCTION(locale_get_display_name)624 PHP_FUNCTION(locale_get_display_name)
625 {
626 get_icu_disp_value_src_php( DISP_NAME , INTERNAL_FUNCTION_PARAM_PASSTHRU );
627 }
628 /* }}} */
629
630 /* {{{ proto static string Locale::getDisplayLanguage($locale[, $in_locale = null])
631 * gets the language for the $locale in $in_locale or default_locale
632 }}} */
633 /* {{{ proto static string get_display_language($locale[, $in_locale = null])
634 * gets the language for the $locale in $in_locale or default_locale
635 */
PHP_FUNCTION(locale_get_display_language)636 PHP_FUNCTION(locale_get_display_language)
637 {
638 get_icu_disp_value_src_php( LOC_LANG_TAG , INTERNAL_FUNCTION_PARAM_PASSTHRU );
639 }
640 /* }}} */
641
642 /* {{{ proto static string Locale::getDisplayScript($locale, $in_locale = null)
643 * gets the script for the $locale in $in_locale or default_locale
644 }}} */
645 /* {{{ proto static string get_display_script($locale, $in_locale = null)
646 * gets the script for the $locale in $in_locale or default_locale
647 */
PHP_FUNCTION(locale_get_display_script)648 PHP_FUNCTION(locale_get_display_script)
649 {
650 get_icu_disp_value_src_php( LOC_SCRIPT_TAG , INTERNAL_FUNCTION_PARAM_PASSTHRU );
651 }
652 /* }}} */
653
654 /* {{{ proto static string Locale::getDisplayRegion($locale, $in_locale = null)
655 * gets the region for the $locale in $in_locale or default_locale
656 }}} */
657 /* {{{ proto static string get_display_region($locale, $in_locale = null)
658 * gets the region for the $locale in $in_locale or default_locale
659 */
PHP_FUNCTION(locale_get_display_region)660 PHP_FUNCTION(locale_get_display_region)
661 {
662 get_icu_disp_value_src_php( LOC_REGION_TAG , INTERNAL_FUNCTION_PARAM_PASSTHRU );
663 }
664 /* }}} */
665
666 /* {{{
667 * proto static string Locale::getDisplayVariant($locale, $in_locale = null)
668 * gets the variant for the $locale in $in_locale or default_locale
669 }}} */
670 /* {{{
671 * proto static string get_display_variant($locale, $in_locale = null)
672 * gets the variant for the $locale in $in_locale or default_locale
673 */
PHP_FUNCTION(locale_get_display_variant)674 PHP_FUNCTION(locale_get_display_variant)
675 {
676 get_icu_disp_value_src_php( LOC_VARIANT_TAG , INTERNAL_FUNCTION_PARAM_PASSTHRU );
677 }
678 /* }}} */
679
680 /* {{{ proto static array getKeywords(string $locale) {
681 * return an associative array containing keyword-value
682 * pairs for this locale. The keys are keys to the array (doh!)
683 * }}}*/
684 /* {{{ proto static array locale_get_keywords(string $locale) {
685 * return an associative array containing keyword-value
686 * pairs for this locale. The keys are keys to the array (doh!)
687 */
PHP_FUNCTION(locale_get_keywords)688 PHP_FUNCTION( locale_get_keywords )
689 {
690 UEnumeration* e = NULL;
691 UErrorCode status = U_ZERO_ERROR;
692
693 const char* kw_key = NULL;
694 int32_t kw_key_len = 0;
695
696 const char* loc_name = NULL;
697 size_t loc_name_len = 0;
698
699 /*
700 ICU expects the buffer to be allocated before calling the function
701 and so the buffer size has been explicitly specified
702 ICU uloc.h #define ULOC_KEYWORD_AND_VALUES_CAPACITY 100
703 hence the kw_value buffer size is 100
704 */
705 zend_string *kw_value_str;
706 int32_t kw_value_len = 100;
707
708 intl_error_reset( NULL );
709
710 if(zend_parse_parameters( ZEND_NUM_ARGS(), "s",
711 &loc_name, &loc_name_len ) == FAILURE)
712 {
713 intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR,
714 "locale_get_keywords: unable to parse input params", 0 );
715
716 RETURN_FALSE;
717 }
718
719 INTL_CHECK_LOCALE_LEN(strlen(loc_name));
720
721 if(loc_name_len == 0) {
722 loc_name = intl_locale_get_default();
723 }
724
725 /* Get the keywords */
726 e = uloc_openKeywords( loc_name, &status );
727 if( e != NULL )
728 {
729 /* Traverse it, filling the return array. */
730 array_init( return_value );
731
732 while( ( kw_key = uenum_next( e, &kw_key_len, &status ) ) != NULL ){
733 kw_value_len = 100;
734 kw_value_str = zend_string_alloc(kw_value_len, 0);
735
736 /* Get the keyword value for each keyword */
737 kw_value_len=uloc_getKeywordValue( loc_name, kw_key, ZSTR_VAL(kw_value_str), kw_value_len, &status );
738 if (status == U_BUFFER_OVERFLOW_ERROR) {
739 status = U_ZERO_ERROR;
740 kw_value_str = zend_string_extend(kw_value_str, kw_value_len, 0);
741 kw_value_len=uloc_getKeywordValue( loc_name,kw_key, ZSTR_VAL(kw_value_str), kw_value_len+1, &status );
742 } else if(!U_FAILURE(status)) {
743 kw_value_str = zend_string_truncate(kw_value_str, kw_value_len, 0);
744 }
745 if (U_FAILURE(status)) {
746 intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR, "locale_get_keywords: Error encountered while getting the keyword value for the keyword", 0 );
747 if( kw_value_str){
748 zend_string_free( kw_value_str );
749 }
750 zval_dtor(return_value);
751 RETURN_FALSE;
752 }
753
754 add_assoc_str( return_value, (char *)kw_key, kw_value_str);
755 } /* end of while */
756
757 } /* end of if e!=NULL */
758
759 uenum_close( e );
760 }
761 /* }}} */
762
763 /* {{{ proto static string Locale::canonicalize($locale)
764 * @return string the canonicalized locale
765 * }}} */
766 /* {{{ proto static string locale_canonicalize(Locale $loc, string $locale)
767 * @param string $locale The locale string to canonicalize
768 */
PHP_FUNCTION(locale_canonicalize)769 PHP_FUNCTION(locale_canonicalize)
770 {
771 get_icu_value_src_php( LOC_CANONICALIZE_TAG , INTERNAL_FUNCTION_PARAM_PASSTHRU );
772 }
773 /* }}} */
774
775 /* {{{ append_key_value
776 * Internal function which is called from locale_compose
777 * gets the value for the key_name and appends to the loc_name
778 * returns 1 if successful , -1 if not found ,
779 * 0 if array element is not a string , -2 if buffer-overflow
780 */
append_key_value(smart_str * loc_name,HashTable * hash_arr,char * key_name)781 static int append_key_value(smart_str* loc_name, HashTable* hash_arr, char* key_name)
782 {
783 zval *ele_value;
784
785 if ((ele_value = zend_hash_str_find(hash_arr , key_name, strlen(key_name))) != NULL ) {
786 if(Z_TYPE_P(ele_value)!= IS_STRING ){
787 /* element value is not a string */
788 return FAILURE;
789 }
790 if(strcmp(key_name, LOC_LANG_TAG) != 0 &&
791 strcmp(key_name, LOC_GRANDFATHERED_LANG_TAG)!=0 ) {
792 /* not lang or grandfathered tag */
793 smart_str_appendl(loc_name, SEPARATOR , sizeof(SEPARATOR)-1);
794 }
795 smart_str_appendl(loc_name, Z_STRVAL_P(ele_value) , Z_STRLEN_P(ele_value));
796 return SUCCESS;
797 }
798
799 return LOC_NOT_FOUND;
800 }
801 /* }}} */
802
803 /* {{{ append_prefix , appends the prefix needed
804 * e.g. private adds 'x'
805 */
add_prefix(smart_str * loc_name,char * key_name)806 static void add_prefix(smart_str* loc_name, char* key_name)
807 {
808 if( strncmp(key_name , LOC_PRIVATE_TAG , 7) == 0 ){
809 smart_str_appendl(loc_name, SEPARATOR , sizeof(SEPARATOR)-1);
810 smart_str_appendl(loc_name, PRIVATE_PREFIX , sizeof(PRIVATE_PREFIX)-1);
811 }
812 }
813 /* }}} */
814
815 /* {{{ append_multiple_key_values
816 * Internal function which is called from locale_compose
817 * gets the multiple values for the key_name and appends to the loc_name
818 * used for 'variant','extlang','private'
819 * returns 1 if successful , -1 if not found ,
820 * 0 if array element is not a string , -2 if buffer-overflow
821 */
append_multiple_key_values(smart_str * loc_name,HashTable * hash_arr,char * key_name)822 static int append_multiple_key_values(smart_str* loc_name, HashTable* hash_arr, char* key_name)
823 {
824 zval *ele_value;
825 int i = 0;
826 int isFirstSubtag = 0;
827 int max_value = 0;
828
829 /* Variant/ Extlang/Private etc. */
830 if ((ele_value = zend_hash_str_find( hash_arr , key_name , strlen(key_name))) != NULL) {
831 if( Z_TYPE_P(ele_value) == IS_STRING ){
832 add_prefix( loc_name , key_name);
833
834 smart_str_appendl(loc_name, SEPARATOR , sizeof(SEPARATOR)-1);
835 smart_str_appendl(loc_name, Z_STRVAL_P(ele_value) , Z_STRLEN_P(ele_value));
836 return SUCCESS;
837 } else if(Z_TYPE_P(ele_value) == IS_ARRAY ) {
838 HashTable *arr = Z_ARRVAL_P(ele_value);
839 zval *data;
840
841 ZEND_HASH_FOREACH_VAL(arr, data) {
842 if(Z_TYPE_P(data) != IS_STRING) {
843 return FAILURE;
844 }
845 if (isFirstSubtag++ == 0){
846 add_prefix(loc_name , key_name);
847 }
848 smart_str_appendl(loc_name, SEPARATOR , sizeof(SEPARATOR)-1);
849 smart_str_appendl(loc_name, Z_STRVAL_P(data) , Z_STRLEN_P(data));
850 } ZEND_HASH_FOREACH_END();
851 return SUCCESS;
852 } else {
853 return FAILURE;
854 }
855 } else {
856 char cur_key_name[31];
857 /* Decide the max_value: the max. no. of elements allowed */
858 if( strcmp(key_name , LOC_VARIANT_TAG) ==0 ){
859 max_value = MAX_NO_VARIANT;
860 }
861 if( strcmp(key_name , LOC_EXTLANG_TAG) ==0 ){
862 max_value = MAX_NO_EXTLANG;
863 }
864 if( strcmp(key_name , LOC_PRIVATE_TAG) ==0 ){
865 max_value = MAX_NO_PRIVATE;
866 }
867
868 /* Multiple variant values as variant0, variant1 ,variant2 */
869 isFirstSubtag = 0;
870 for( i=0 ; i< max_value; i++ ){
871 snprintf( cur_key_name , 30, "%s%d", key_name , i);
872 if ((ele_value = zend_hash_str_find( hash_arr , cur_key_name , strlen(cur_key_name))) != NULL) {
873 if( Z_TYPE_P(ele_value)!= IS_STRING ){
874 /* variant is not a string */
875 return FAILURE;
876 }
877 /* Add the contents */
878 if (isFirstSubtag++ == 0){
879 add_prefix(loc_name , cur_key_name);
880 }
881 smart_str_appendl(loc_name, SEPARATOR , sizeof(SEPARATOR)-1);
882 smart_str_appendl(loc_name, Z_STRVAL_P(ele_value) , Z_STRLEN_P(ele_value));
883 }
884 } /* end of for */
885 } /* end of else */
886
887 return SUCCESS;
888 }
889 /* }}} */
890
891 /*{{{
892 * If applicable sets error message and aborts locale_compose gracefully
893 * returns 0 if locale_compose needs to be aborted
894 * otherwise returns 1
895 */
handleAppendResult(int result,smart_str * loc_name)896 static int handleAppendResult( int result, smart_str* loc_name)
897 {
898 intl_error_reset( NULL );
899 if( result == FAILURE) {
900 intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR,
901 "locale_compose: parameter array element is not a string", 0 );
902 smart_str_free(loc_name);
903 return 0;
904 }
905 return 1;
906 }
907 /* }}} */
908
909 #define RETURN_SMART_STR(str) smart_str_0((str)); RETURN_NEW_STR((str)->s)
910 /* {{{ proto static string Locale::composeLocale($array)
911 * Creates a locale by combining the parts of locale-ID passed
912 * }}} */
913 /* {{{ proto static string compose_locale($array)
914 * Creates a locale by combining the parts of locale-ID passed
915 * }}} */
PHP_FUNCTION(locale_compose)916 PHP_FUNCTION(locale_compose)
917 {
918 smart_str loc_name_s = {0};
919 smart_str *loc_name = &loc_name_s;
920 zval* arr = NULL;
921 HashTable* hash_arr = NULL;
922 int result = 0;
923
924 intl_error_reset( NULL );
925
926 if(zend_parse_parameters( ZEND_NUM_ARGS(), "a",
927 &arr) == FAILURE)
928 {
929 intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR,
930 "locale_compose: unable to parse input params", 0 );
931 RETURN_FALSE;
932 }
933
934 hash_arr = Z_ARRVAL_P( arr );
935
936 if( !hash_arr || zend_hash_num_elements( hash_arr ) == 0 )
937 RETURN_FALSE;
938
939 /* Check for grandfathered first */
940 result = append_key_value(loc_name, hash_arr, LOC_GRANDFATHERED_LANG_TAG);
941 if( result == SUCCESS){
942 RETURN_SMART_STR(loc_name);
943 }
944 if( !handleAppendResult( result, loc_name)){
945 RETURN_FALSE;
946 }
947
948 /* Not grandfathered */
949 result = append_key_value(loc_name, hash_arr , LOC_LANG_TAG);
950 if( result == LOC_NOT_FOUND ){
951 intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR,
952 "locale_compose: parameter array does not contain 'language' tag.", 0 );
953 smart_str_free(loc_name);
954 RETURN_FALSE;
955 }
956 if( !handleAppendResult( result, loc_name)){
957 RETURN_FALSE;
958 }
959
960 /* Extlang */
961 result = append_multiple_key_values(loc_name, hash_arr , LOC_EXTLANG_TAG);
962 if( !handleAppendResult( result, loc_name)){
963 RETURN_FALSE;
964 }
965
966 /* Script */
967 result = append_key_value(loc_name, hash_arr , LOC_SCRIPT_TAG);
968 if( !handleAppendResult( result, loc_name)){
969 RETURN_FALSE;
970 }
971
972 /* Region */
973 result = append_key_value( loc_name, hash_arr , LOC_REGION_TAG);
974 if( !handleAppendResult( result, loc_name)){
975 RETURN_FALSE;
976 }
977
978 /* Variant */
979 result = append_multiple_key_values( loc_name, hash_arr , LOC_VARIANT_TAG);
980 if( !handleAppendResult( result, loc_name)){
981 RETURN_FALSE;
982 }
983
984 /* Private */
985 result = append_multiple_key_values( loc_name, hash_arr , LOC_PRIVATE_TAG);
986 if( !handleAppendResult( result, loc_name)){
987 RETURN_FALSE;
988 }
989
990 RETURN_SMART_STR(loc_name);
991 }
992 /* }}} */
993
994
995 /*{{{
996 * Parses the locale and returns private subtags if existing
997 * else returns NULL
998 * e.g. for locale='en_US-x-prv1-prv2-prv3'
999 * returns a pointer to the string 'prv1-prv2-prv3'
1000 */
get_private_subtags(const char * loc_name)1001 static zend_string* get_private_subtags(const char* loc_name)
1002 {
1003 zend_string* result =NULL;
1004 int singletonPos = 0;
1005 int len =0;
1006 const char* mod_loc_name =NULL;
1007
1008 if( loc_name && (len = strlen(loc_name)>0 ) ){
1009 mod_loc_name = loc_name ;
1010 len = strlen(mod_loc_name);
1011 while( (singletonPos = getSingletonPos(mod_loc_name))!= -1){
1012
1013 if( singletonPos!=-1){
1014 if( (*(mod_loc_name+singletonPos)=='x') || (*(mod_loc_name+singletonPos)=='X') ){
1015 /* private subtag start found */
1016 if( singletonPos + 2 == len){
1017 /* loc_name ends with '-x-' ; return NULL */
1018 }
1019 else{
1020 /* result = mod_loc_name + singletonPos +2; */
1021 result = zend_string_init(mod_loc_name + singletonPos+2 , (len -( singletonPos +2) ), 0);
1022 }
1023 break;
1024 }
1025 else{
1026 if( singletonPos + 1 >= len){
1027 /* String end */
1028 break;
1029 } else {
1030 /* singleton found but not a private subtag , hence check further in the string for the private subtag */
1031 mod_loc_name = mod_loc_name + singletonPos +1;
1032 len = strlen(mod_loc_name);
1033 }
1034 }
1035 }
1036
1037 } /* end of while */
1038 }
1039
1040 return result;
1041 }
1042 /* }}} */
1043
1044 /* {{{ code used by locale_parse
1045 */
add_array_entry(const char * loc_name,zval * hash_arr,char * key_name)1046 static int add_array_entry(const char* loc_name, zval* hash_arr, char* key_name)
1047 {
1048 zend_string* key_value = NULL;
1049 char* cur_key_name = NULL;
1050 char* token = NULL;
1051 char* last_ptr = NULL;
1052
1053 int result = 0;
1054 int cur_result = 0;
1055 int cnt = 0;
1056
1057
1058 if( strcmp(key_name , LOC_PRIVATE_TAG)==0 ){
1059 key_value = get_private_subtags( loc_name );
1060 result = 1;
1061 } else {
1062 key_value = get_icu_value_internal( loc_name , key_name , &result,1 );
1063 }
1064 if( (strcmp(key_name , LOC_PRIVATE_TAG)==0) ||
1065 ( strcmp(key_name , LOC_VARIANT_TAG)==0) ){
1066 if( result > 0 && key_value){
1067 /* Tokenize on the "_" or "-" */
1068 token = php_strtok_r( key_value->val , DELIMITER ,&last_ptr);
1069 if( cur_key_name ){
1070 efree( cur_key_name);
1071 }
1072 cur_key_name = (char*)ecalloc( 25, 25);
1073 sprintf( cur_key_name , "%s%d", key_name , cnt++);
1074 add_assoc_string( hash_arr, cur_key_name , token);
1075 /* tokenize on the "_" or "-" and stop at singleton if any */
1076 while( (token = php_strtok_r(NULL , DELIMITER , &last_ptr)) && (strlen(token)>1) ){
1077 sprintf( cur_key_name , "%s%d", key_name , cnt++);
1078 add_assoc_string( hash_arr, cur_key_name , token);
1079 }
1080 /*
1081 if( strcmp(key_name, LOC_PRIVATE_TAG) == 0 ){
1082 }
1083 */
1084 }
1085 if (key_value) {
1086 zend_string_release(key_value);
1087 }
1088 } else {
1089 if( result == 1 ){
1090 add_assoc_str( hash_arr, key_name , key_value);
1091 cur_result = 1;
1092 } else if (key_value) {
1093 zend_string_release(key_value);
1094 }
1095 }
1096
1097 if( cur_key_name ){
1098 efree( cur_key_name);
1099 }
1100 /*if( key_name != LOC_PRIVATE_TAG && key_value){*/
1101 return cur_result;
1102 }
1103 /* }}} */
1104
1105 /* {{{ proto static array Locale::parseLocale($locale)
1106 * parses a locale-id into an array the different parts of it
1107 }}} */
1108 /* {{{ proto static array parse_locale($locale)
1109 * parses a locale-id into an array the different parts of it
1110 */
PHP_FUNCTION(locale_parse)1111 PHP_FUNCTION(locale_parse)
1112 {
1113 const char* loc_name = NULL;
1114 size_t loc_name_len = 0;
1115 int grOffset = 0;
1116
1117 intl_error_reset( NULL );
1118
1119 if(zend_parse_parameters( ZEND_NUM_ARGS(), "s",
1120 &loc_name, &loc_name_len ) == FAILURE)
1121 {
1122 intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR,
1123 "locale_parse: unable to parse input params", 0 );
1124
1125 RETURN_FALSE;
1126 }
1127
1128 INTL_CHECK_LOCALE_LEN(strlen(loc_name));
1129
1130 if(loc_name_len == 0) {
1131 loc_name = intl_locale_get_default();
1132 }
1133
1134 array_init( return_value );
1135
1136 grOffset = findOffset( LOC_GRANDFATHERED , loc_name );
1137 if( grOffset >= 0 ){
1138 add_assoc_string( return_value , LOC_GRANDFATHERED_LANG_TAG, (char *)loc_name);
1139 }
1140 else{
1141 /* Not grandfathered */
1142 add_array_entry( loc_name , return_value , LOC_LANG_TAG);
1143 add_array_entry( loc_name , return_value , LOC_SCRIPT_TAG);
1144 add_array_entry( loc_name , return_value , LOC_REGION_TAG);
1145 add_array_entry( loc_name , return_value , LOC_VARIANT_TAG);
1146 add_array_entry( loc_name , return_value , LOC_PRIVATE_TAG);
1147 }
1148 }
1149 /* }}} */
1150
1151 /* {{{ proto static array Locale::getAllVariants($locale)
1152 * gets an array containing the list of variants, or null
1153 }}} */
1154 /* {{{ proto static array locale_get_all_variants($locale)
1155 * gets an array containing the list of variants, or null
1156 */
PHP_FUNCTION(locale_get_all_variants)1157 PHP_FUNCTION(locale_get_all_variants)
1158 {
1159 const char* loc_name = NULL;
1160 size_t loc_name_len = 0;
1161
1162 int result = 0;
1163 char* token = NULL;
1164 zend_string* variant = NULL;
1165 char* saved_ptr = NULL;
1166
1167 intl_error_reset( NULL );
1168
1169 if(zend_parse_parameters( ZEND_NUM_ARGS(), "s",
1170 &loc_name, &loc_name_len ) == FAILURE)
1171 {
1172 intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR,
1173 "locale_parse: unable to parse input params", 0 );
1174
1175 RETURN_FALSE;
1176 }
1177
1178 if(loc_name_len == 0) {
1179 loc_name = intl_locale_get_default();
1180 loc_name_len = strlen(loc_name);
1181 }
1182
1183 INTL_CHECK_LOCALE_LEN(loc_name_len);
1184
1185 array_init( return_value );
1186
1187 /* If the locale is grandfathered, stop, no variants */
1188 if( findOffset( LOC_GRANDFATHERED , loc_name ) >= 0 ){
1189 /* ("Grandfathered Tag. No variants."); */
1190 }
1191 else {
1192 /* Call ICU variant */
1193 variant = get_icu_value_internal( loc_name , LOC_VARIANT_TAG , &result ,0);
1194 if( result > 0 && variant){
1195 /* Tokenize on the "_" or "-" */
1196 token = php_strtok_r( variant->val , DELIMITER , &saved_ptr);
1197 add_next_index_stringl( return_value, token , strlen(token));
1198 /* tokenize on the "_" or "-" and stop at singleton if any */
1199 while( (token = php_strtok_r(NULL , DELIMITER, &saved_ptr)) && (strlen(token)>1) ){
1200 add_next_index_stringl( return_value, token , strlen(token));
1201 }
1202 }
1203 if( variant ){
1204 zend_string_release( variant );
1205 }
1206 }
1207
1208
1209 }
1210 /* }}} */
1211
1212 /*{{{
1213 * Converts to lower case and also replaces all hyphens with the underscore
1214 */
strToMatch(const char * str,char * retstr)1215 static int strToMatch(const char* str ,char *retstr)
1216 {
1217 char* anchor = NULL;
1218 const char* anchor1 = NULL;
1219 int result = 0;
1220
1221 if( (!str) || str[0] == '\0'){
1222 return result;
1223 } else {
1224 anchor = retstr;
1225 anchor1 = str;
1226 while( (*str)!='\0' ){
1227 if( *str == '-' ){
1228 *retstr = '_';
1229 } else {
1230 *retstr = tolower(*str);
1231 }
1232 str++;
1233 retstr++;
1234 }
1235 *retstr = '\0';
1236 retstr= anchor;
1237 str= anchor1;
1238 result = 1;
1239 }
1240
1241 return(result);
1242 }
1243 /* }}} */
1244
1245 /* {{{ proto static boolean Locale::filterMatches(string $langtag, string $locale[, bool $canonicalize])
1246 * Checks if a $langtag filter matches with $locale according to RFC 4647's basic filtering algorithm
1247 */
1248 /* }}} */
1249 /* {{{ proto boolean locale_filter_matches(string $langtag, string $locale[, bool $canonicalize])
1250 * Checks if a $langtag filter matches with $locale according to RFC 4647's basic filtering algorithm
1251 */
PHP_FUNCTION(locale_filter_matches)1252 PHP_FUNCTION(locale_filter_matches)
1253 {
1254 char* lang_tag = NULL;
1255 size_t lang_tag_len = 0;
1256 const char* loc_range = NULL;
1257 size_t loc_range_len = 0;
1258
1259 int result = 0;
1260 char* token = 0;
1261 char* chrcheck = NULL;
1262
1263 zend_string* can_lang_tag = NULL;
1264 zend_string* can_loc_range = NULL;
1265
1266 char* cur_lang_tag = NULL;
1267 char* cur_loc_range = NULL;
1268
1269 zend_bool boolCanonical = 0;
1270 UErrorCode status = U_ZERO_ERROR;
1271
1272 intl_error_reset( NULL );
1273
1274 if(zend_parse_parameters( ZEND_NUM_ARGS(), "ss|b",
1275 &lang_tag, &lang_tag_len , &loc_range , &loc_range_len ,
1276 &boolCanonical) == FAILURE)
1277 {
1278 intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR,
1279 "locale_filter_matches: unable to parse input params", 0 );
1280
1281 RETURN_FALSE;
1282 }
1283
1284 if(loc_range_len == 0) {
1285 loc_range = intl_locale_get_default();
1286 loc_range_len = strlen(loc_range);
1287 }
1288
1289 if( strcmp(loc_range,"*")==0){
1290 RETURN_TRUE;
1291 }
1292
1293 INTL_CHECK_LOCALE_LEN(loc_range_len);
1294 INTL_CHECK_LOCALE_LEN(lang_tag_len);
1295
1296 if( boolCanonical ){
1297 /* canonicalize loc_range */
1298 can_loc_range=get_icu_value_internal( loc_range , LOC_CANONICALIZE_TAG , &result , 0);
1299 if( result ==0) {
1300 intl_error_set( NULL, status,
1301 "locale_filter_matches : unable to canonicalize loc_range" , 0 );
1302 RETURN_FALSE;
1303 }
1304
1305 /* canonicalize lang_tag */
1306 can_lang_tag = get_icu_value_internal( lang_tag , LOC_CANONICALIZE_TAG , &result , 0);
1307 if( result ==0) {
1308 intl_error_set( NULL, status,
1309 "locale_filter_matches : unable to canonicalize lang_tag" , 0 );
1310 RETURN_FALSE;
1311 }
1312
1313 /* Convert to lower case for case-insensitive comparison */
1314 cur_lang_tag = ecalloc( 1, can_lang_tag->len + 1);
1315
1316 /* Convert to lower case for case-insensitive comparison */
1317 result = strToMatch( can_lang_tag->val , cur_lang_tag);
1318 if( result == 0) {
1319 efree( cur_lang_tag );
1320 zend_string_release( can_lang_tag );
1321 RETURN_FALSE;
1322 }
1323
1324 cur_loc_range = ecalloc( 1, can_loc_range->len + 1);
1325 result = strToMatch( can_loc_range->val , cur_loc_range );
1326 if( result == 0) {
1327 efree( cur_lang_tag );
1328 zend_string_release( can_lang_tag );
1329 efree( cur_loc_range );
1330 zend_string_release( can_loc_range );
1331 RETURN_FALSE;
1332 }
1333
1334 /* check if prefix */
1335 token = strstr( cur_lang_tag , cur_loc_range );
1336
1337 if( token && (token==cur_lang_tag) ){
1338 /* check if the char. after match is SEPARATOR */
1339 chrcheck = token + (strlen(cur_loc_range));
1340 if( isIDSeparator(*chrcheck) || isEndOfTag(*chrcheck) ){
1341 if( cur_lang_tag){
1342 efree( cur_lang_tag );
1343 }
1344 if( cur_loc_range){
1345 efree( cur_loc_range );
1346 }
1347 if( can_lang_tag){
1348 zend_string_release( can_lang_tag );
1349 }
1350 if( can_loc_range){
1351 zend_string_release( can_loc_range );
1352 }
1353 RETURN_TRUE;
1354 }
1355 }
1356
1357 /* No prefix as loc_range */
1358 if( cur_lang_tag){
1359 efree( cur_lang_tag );
1360 }
1361 if( cur_loc_range){
1362 efree( cur_loc_range );
1363 }
1364 if( can_lang_tag){
1365 zend_string_release( can_lang_tag );
1366 }
1367 if( can_loc_range){
1368 zend_string_release( can_loc_range );
1369 }
1370 RETURN_FALSE;
1371
1372 } /* end of if isCanonical */
1373 else{
1374 /* Convert to lower case for case-insensitive comparison */
1375 cur_lang_tag = ecalloc( 1, strlen(lang_tag ) + 1);
1376
1377 result = strToMatch( lang_tag , cur_lang_tag);
1378 if( result == 0) {
1379 efree( cur_lang_tag );
1380 RETURN_FALSE;
1381 }
1382 cur_loc_range = ecalloc( 1, strlen(loc_range ) + 1);
1383 result = strToMatch( loc_range , cur_loc_range );
1384 if( result == 0) {
1385 efree( cur_lang_tag );
1386 efree( cur_loc_range );
1387 RETURN_FALSE;
1388 }
1389
1390 /* check if prefix */
1391 token = strstr( cur_lang_tag , cur_loc_range );
1392
1393 if( token && (token==cur_lang_tag) ){
1394 /* check if the char. after match is SEPARATOR */
1395 chrcheck = token + (strlen(cur_loc_range));
1396 if( isIDSeparator(*chrcheck) || isEndOfTag(*chrcheck) ){
1397 if( cur_lang_tag){
1398 efree( cur_lang_tag );
1399 }
1400 if( cur_loc_range){
1401 efree( cur_loc_range );
1402 }
1403 RETURN_TRUE;
1404 }
1405 }
1406
1407 /* No prefix as loc_range */
1408 if( cur_lang_tag){
1409 efree( cur_lang_tag );
1410 }
1411 if( cur_loc_range){
1412 efree( cur_loc_range );
1413 }
1414 RETURN_FALSE;
1415
1416 }
1417 }
1418 /* }}} */
1419
array_cleanup(char * arr[],int arr_size)1420 static void array_cleanup( char* arr[] , int arr_size)
1421 {
1422 int i=0;
1423 for( i=0; i< arr_size; i++ ){
1424 if( arr[i*2] ){
1425 efree( arr[i*2]);
1426 }
1427 }
1428 efree(arr);
1429 }
1430
1431 #define LOOKUP_CLEAN_RETURN(value) array_cleanup(cur_arr, cur_arr_len); return (value)
1432 /* {{{
1433 * returns the lookup result to lookup_loc_range_src_php
1434 * internal function
1435 */
lookup_loc_range(const char * loc_range,HashTable * hash_arr,int canonicalize)1436 static zend_string* lookup_loc_range(const char* loc_range, HashTable* hash_arr, int canonicalize )
1437 {
1438 int i = 0;
1439 int cur_arr_len = 0;
1440 int result = 0;
1441
1442 zend_string* lang_tag = NULL;
1443 zval* ele_value = NULL;
1444 char** cur_arr = NULL;
1445
1446 char* cur_loc_range = NULL;
1447 zend_string* can_loc_range = NULL;
1448 int saved_pos = 0;
1449
1450 zend_string* return_value = NULL;
1451
1452 cur_arr = ecalloc(zend_hash_num_elements(hash_arr)*2, sizeof(char *));
1453 ZEND_HASH_FOREACH_VAL(hash_arr, ele_value) {
1454 /* convert the array to lowercase , also replace hyphens with the underscore and store it in cur_arr */
1455 if(Z_TYPE_P(ele_value)!= IS_STRING) {
1456 /* element value is not a string */
1457 intl_error_set(NULL, U_ILLEGAL_ARGUMENT_ERROR, "lookup_loc_range: locale array element is not a string", 0);
1458 LOOKUP_CLEAN_RETURN(NULL);
1459 }
1460 cur_arr[cur_arr_len*2] = estrndup(Z_STRVAL_P(ele_value), Z_STRLEN_P(ele_value));
1461 result = strToMatch(Z_STRVAL_P(ele_value), cur_arr[cur_arr_len*2]);
1462 if(result == 0) {
1463 intl_error_set(NULL, U_ILLEGAL_ARGUMENT_ERROR, "lookup_loc_range: unable to canonicalize lang_tag", 0);
1464 LOOKUP_CLEAN_RETURN(NULL);
1465 }
1466 cur_arr[cur_arr_len*2+1] = Z_STRVAL_P(ele_value);
1467 cur_arr_len++ ;
1468 } ZEND_HASH_FOREACH_END(); /* end of for */
1469
1470 /* Canonicalize array elements */
1471 if(canonicalize) {
1472 for(i=0; i<cur_arr_len; i++) {
1473 lang_tag = get_icu_value_internal(cur_arr[i*2], LOC_CANONICALIZE_TAG, &result, 0);
1474 if(result != 1 || lang_tag == NULL || !lang_tag->val[0]) {
1475 if(lang_tag) {
1476 zend_string_release(lang_tag);
1477 }
1478 intl_error_set(NULL, U_ILLEGAL_ARGUMENT_ERROR, "lookup_loc_range: unable to canonicalize lang_tag" , 0);
1479 LOOKUP_CLEAN_RETURN(NULL);
1480 }
1481 cur_arr[i*2] = erealloc(cur_arr[i*2], lang_tag->len+1);
1482 result = strToMatch(lang_tag->val, cur_arr[i*2]);
1483 zend_string_release(lang_tag);
1484 if(result == 0) {
1485 intl_error_set(NULL, U_ILLEGAL_ARGUMENT_ERROR, "lookup_loc_range: unable to canonicalize lang_tag" , 0);
1486 LOOKUP_CLEAN_RETURN(NULL);
1487 }
1488 }
1489
1490 }
1491
1492 if(canonicalize) {
1493 /* Canonicalize the loc_range */
1494 can_loc_range = get_icu_value_internal(loc_range, LOC_CANONICALIZE_TAG, &result , 0);
1495 if( result != 1 || can_loc_range == NULL || !can_loc_range->val[0]) {
1496 /* Error */
1497 intl_error_set(NULL, U_ILLEGAL_ARGUMENT_ERROR, "lookup_loc_range: unable to canonicalize loc_range" , 0 );
1498 if(can_loc_range) {
1499 zend_string_release(can_loc_range);
1500 }
1501 LOOKUP_CLEAN_RETURN(NULL);
1502 } else {
1503 loc_range = can_loc_range->val;
1504 }
1505 }
1506
1507 cur_loc_range = ecalloc(1, strlen(loc_range)+1);
1508 /* convert to lower and replace hyphens */
1509 result = strToMatch(loc_range, cur_loc_range);
1510 if(can_loc_range) {
1511 zend_string_release(can_loc_range);
1512 }
1513 if(result == 0) {
1514 intl_error_set(NULL, U_ILLEGAL_ARGUMENT_ERROR, "lookup_loc_range: unable to canonicalize lang_tag" , 0);
1515 LOOKUP_CLEAN_RETURN(NULL);
1516 }
1517
1518 /* Lookup for the lang_tag match */
1519 saved_pos = strlen(cur_loc_range);
1520 while(saved_pos > 0) {
1521 for(i=0; i< cur_arr_len; i++){
1522 if(cur_arr[i*2] != NULL && strlen(cur_arr[i*2]) == saved_pos && strncmp(cur_loc_range, cur_arr[i*2], saved_pos) == 0) {
1523 /* Match found */
1524 char *str = canonicalize ? cur_arr[i*2] : cur_arr[i*2+1];
1525 return_value = zend_string_init(str, strlen(str), 0);
1526 efree(cur_loc_range);
1527 LOOKUP_CLEAN_RETURN(return_value);
1528 }
1529 }
1530 saved_pos = getStrrtokenPos(cur_loc_range, saved_pos);
1531 }
1532
1533 /* Match not found */
1534 efree(cur_loc_range);
1535 LOOKUP_CLEAN_RETURN(NULL);
1536 }
1537 /* }}} */
1538
1539 /* {{{ proto string Locale::lookup(array $langtag, string $locale[, bool $canonicalize[, string $default = null]])
1540 * Searchs the items in $langtag for the best match to the language
1541 * range
1542 */
1543 /* }}} */
1544 /* {{{ proto string locale_lookup(array $langtag, string $locale[, bool $canonicalize[, string $default = null]])
1545 * Searchs the items in $langtag for the best match to the language
1546 * range
1547 */
PHP_FUNCTION(locale_lookup)1548 PHP_FUNCTION(locale_lookup)
1549 {
1550 zend_string* fallback_loc_str = NULL;
1551 const char* loc_range = NULL;
1552 size_t loc_range_len = 0;
1553
1554 zval* arr = NULL;
1555 HashTable* hash_arr = NULL;
1556 zend_bool boolCanonical = 0;
1557 zend_string* result_str = NULL;
1558
1559 intl_error_reset( NULL );
1560
1561 if(zend_parse_parameters( ZEND_NUM_ARGS(), "as|bS", &arr, &loc_range, &loc_range_len,
1562 &boolCanonical, &fallback_loc_str) == FAILURE) {
1563 intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR, "locale_lookup: unable to parse input params", 0 );
1564 RETURN_FALSE;
1565 }
1566
1567 if(loc_range_len == 0) {
1568 if(fallback_loc_str) {
1569 loc_range = ZSTR_VAL(fallback_loc_str);
1570 loc_range_len = ZSTR_LEN(fallback_loc_str);
1571 } else {
1572 loc_range = intl_locale_get_default();
1573 loc_range_len = strlen(loc_range);
1574 }
1575 }
1576
1577 hash_arr = Z_ARRVAL_P(arr);
1578
1579 INTL_CHECK_LOCALE_LEN(loc_range_len);
1580
1581 if( !hash_arr || zend_hash_num_elements( hash_arr ) == 0 ) {
1582 RETURN_EMPTY_STRING();
1583 }
1584
1585 result_str = lookup_loc_range(loc_range, hash_arr, boolCanonical);
1586 if(result_str == NULL || ZSTR_VAL(result_str)[0] == '\0') {
1587 if( fallback_loc_str ) {
1588 result_str = zend_string_copy(fallback_loc_str);
1589 } else {
1590 RETURN_EMPTY_STRING();
1591 }
1592 }
1593
1594 RETURN_STR(result_str);
1595 }
1596 /* }}} */
1597
1598 /* {{{ proto string Locale::acceptFromHttp(string $http_accept)
1599 * Tries to find out best available locale based on HTTP �Accept-Language� header
1600 */
1601 /* }}} */
1602 /* {{{ proto string locale_accept_from_http(string $http_accept)
1603 * Tries to find out best available locale based on HTTP �Accept-Language� header
1604 */
PHP_FUNCTION(locale_accept_from_http)1605 PHP_FUNCTION(locale_accept_from_http)
1606 {
1607 UEnumeration *available;
1608 char *http_accept = NULL;
1609 size_t http_accept_len;
1610 UErrorCode status = 0;
1611 int len;
1612 char resultLocale[INTL_MAX_LOCALE_LEN+1];
1613 UAcceptResult outResult;
1614
1615 if(zend_parse_parameters( ZEND_NUM_ARGS(), "s", &http_accept, &http_accept_len) == FAILURE)
1616 {
1617 intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR,
1618 "locale_accept_from_http: unable to parse input parameters", 0 );
1619 RETURN_FALSE;
1620 }
1621 if(http_accept_len > ULOC_FULLNAME_CAPACITY) {
1622 /* check each fragment, if any bigger than capacity, can't do it due to bug #72533 */
1623 char *start = http_accept;
1624 char *end;
1625 size_t len;
1626 do {
1627 end = strchr(start, ',');
1628 len = end ? end-start : http_accept_len-(start-http_accept);
1629 if(len > ULOC_FULLNAME_CAPACITY) {
1630 intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR,
1631 "locale_accept_from_http: locale string too long", 0 );
1632 RETURN_FALSE;
1633 }
1634 if(end) {
1635 start = end+1;
1636 }
1637 } while(end != NULL);
1638 }
1639
1640 available = ures_openAvailableLocales(NULL, &status);
1641 INTL_CHECK_STATUS(status, "locale_accept_from_http: failed to retrieve locale list");
1642 len = uloc_acceptLanguageFromHTTP(resultLocale, INTL_MAX_LOCALE_LEN,
1643 &outResult, http_accept, available, &status);
1644 uenum_close(available);
1645 INTL_CHECK_STATUS(status, "locale_accept_from_http: failed to find acceptable locale");
1646 if (len < 0 || outResult == ULOC_ACCEPT_FAILED) {
1647 RETURN_FALSE;
1648 }
1649 RETURN_STRINGL(resultLocale, len);
1650 }
1651 /* }}} */
1652
1653 /*
1654 * Local variables:
1655 * tab-width: 4
1656 * c-basic-offset: 4
1657 * End:
1658 * vim600: noet sw=4 ts=4 fdm=marker
1659 * vim<600: noet sw=4 ts=4
1660 *can_loc_len
1661 */
1662