Passed
Push — master ( 596a36...36fff2 )
by Fabio
05:01
created

CultureInfo::getInstance()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 3
c 1
b 0
f 0
nc 2
nop 1
dl 0
loc 6
ccs 0
cts 4
cp 0
crap 6
rs 10

1 Method

Rating   Name   Duplication   Size   Complexity  
A CultureInfo::validCulture() 0 3 1
1
<?php
2
3
/**
4
 * CultureInfo class file.
5
 *
6
 * This program is free software; you can redistribute it and/or modify
7
 * it under the terms of the BSD License.
8
 *
9
 * Copyright(c) 2004 by Qiang Xue. All rights reserved.
10
 *
11
 * To contact the author write to {@link mailto:[email protected] Qiang Xue}
12
 * The latest version of PRADO can be obtained from:
13
 * {@link http://prado.sourceforge.net/}
14
 *
15
 * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
16
 * @author Fabio Bas <ctrlaltca[at]gmail[dot]com>
17
 * @package Prado\I18N\core
18
 */
19
20
namespace Prado\I18N\core;
21
22
use Exception;
23
24
/**
25
 * CultureInfo class.
26
 *
27
 * Represents information about a specific culture including the
28
 * names of the culture, the calendar used, as well as access to
29
 * culture-specific objects that provide methods for common operations,
30
 * such as formatting dates, numbers, and currency.
31
 *
32
 * The CultureInfo class holds culture-specific information, such as the
33
 * associated language, sublanguage, country/region, calendar, and cultural
34
 * conventions.
35
 *
36
 * The culture names follow the format "<languagecode>_<country/regioncode>",
37
 * where <languagecode> is a lowercase two-letter code derived from ISO 639
38
 * codes. You can find a full list of the ISO-639 codes at
39
 * http://www.ics.uci.edu/pub/ietf/http/related/iso639.txt
40
 *
41
 * The <country/regioncode2> is an uppercase two-letter code derived from
42
 * ISO 3166. A copy of ISO-3166 can be found at
43
 * http://www.chemie.fu-berlin.de/diverse/doc/ISO_3166.html
44
 *
45
 * For example, Australian English is "en_AU".
46
 *
47
 * @author Xiang Wei Zhuo <weizhuo[at]gmail[dot]com>
48
 * @author Fabio Bas <ctrlaltca[at]gmail[dot]com>
49
 * @package Prado\I18N\core
50
 */
51
class CultureInfo
52
{
53
	/**
54
	 * The ICU data array, shared by all instances of this class.
55
	 * @var array
56
	 */
57
	protected static $data = [];
58
59
	/**
60
	 * The current culture.
61
	 * @var string
62
	 */
63
	protected $culture;
64
65
	/**
66
	 * A list of resource bundles keys
67
	 * @var array
68
	 */
69
	protected static $bundleNames = [
70
		'Core' => null,
71
		'Currencies' => 'ICUDATA-curr',
72
		'Languages' => 'ICUDATA-lang',
73
		'Countries' => 'ICUDATA-region',
74
		'zoneStrings' => 'ICUDATA-zone',
75
	];
76
77
	/**
78
	 * A list of properties that are accessable/writable.
79
	 * @var array
80
	 */
81
	protected $properties = [];
82
83
	/**
84
	 * Culture type, all.
85
	 * @see getCultures()
86
	 * @var int
87
	 */
88
	const ALL = 0;
89
90
	/**
91
	 * Culture type, neutral.
92
	 * @see getCultures()
93
	 * @var int
94
	 */
95
	const NEUTRAL = 1;
96
97
	/**
98
	 * Culture type, specific.
99
	 * @see getCultures()
100
	 * @var int
101
	 */
102
	const SPECIFIC = 2;
103
104
	/**
105
	 * Display the culture name.
106
	 * @return string the culture name.
107
	 * @see getName()
108
	 */
109
	public function __toString()
110
	{
111
		return $this->getName();
112
	}
113
114
	/**
115
	 * Allow functions that begins with 'set' to be called directly
116
	 * as an attribute/property to retrieve the value.
117
	 * @param mixed $name
118
	 * @return mixed
119
	 */
120 6
	public function __get($name)
121
	{
122 6
		$getProperty = 'get' . $name;
123 6
		if (in_array($getProperty, $this->properties)) {
124 6
			return $this->$getProperty();
125
		} else {
126
			throw new Exception('Property ' . $name . ' does not exists.');
127
		}
128
	}
129
130
	/**
131
	 * Allow functions that begins with 'set' to be called directly
132
	 * as an attribute/property to set the value.
133
	 * @param mixed $name
134
	 * @param mixed $value
135
	 */
136
	public function __set($name, $value)
137
	{
138
		$setProperty = 'set' . $name;
139
		if (in_array($setProperty, $this->properties)) {
140
			$this->$setProperty($value);
141
		} else {
142
			throw new Exception('Property ' . $name . ' can not be set.');
143
		}
144
	}
145
146
147
	/**
148
	 * Initializes a new instance of the CultureInfo class based on the
149
	 * culture specified by name. E.g. <code>new CultureInfo('en_AU');</cdoe>
150
	 * The culture indentifier must be of the form
151
	 * "language_(country/region/variant)".
152
	 * @param string $culture a culture name, e.g. "en_AU".
153
	 * @return return new CultureInfo.
0 ignored issues
show
Bug introduced by
The type Prado\I18N\core\return was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
154
	 */
155 7
	public function __construct($culture = 'en')
156
	{
157 7
		$this->properties = get_class_methods($this);
158
159 7
		if (empty($culture)) {
160
			$culture = 'en';
161
		}
162
163 7
		$this->setCulture($culture);
164 7
	}
165
166
	/**
167
	 * Determine if a given culture is valid. Simply checks that the
168
	 * culture data exists.
169
	 * @param string $culture a culture
170
	 * @return bool true if valid, false otherwise.
171
	 */
172
	public static function validCulture($culture)
173
	{
174
		return in_array($culture, self::getCultures());
175
	}
176
177
	/**
178
	 * Set the culture for the current instance. The culture indentifier
179
	 * must be of the form "<language>_(country/region)".
180
	 * @param string $culture culture identifier, e.g. "fr_FR_EURO".
181
	 */
182
	protected function setCulture($culture)
183
	{
184
		if (!empty($culture)) {
185
			if (!preg_match('/^[_\\w]+$/', $culture)) {
186
				throw new Exception('Invalid culture supplied: ' . $culture);
187
			}
188
		}
189
190
		$this->culture = $culture;
191
	}
192
193
	/**
194
	 * Load the ICU culture data for the specific culture identifier.
195 7
	 * @param string $culture the culture identifier.
196
	 * @param mixed $key
197 7
	 */
198 7
	protected function loadCultureData($key)
199
	{
200
		foreach (self::$bundleNames as $bundleKey => $bundleName) {
201
			if ($key == $bundleKey) {
202
				if (!array_key_exists($this->culture, self::$data)) {
203 7
					self::$data[$this->culture] = [];
204 7
				}
205
206
				self::$data[$this->culture][$bundleKey] = \ResourceBundle::create($this->culture, $bundleName, true);
207
				break;
208
			}
209
		}
210
	}
211 5
212
	/**
213 5
	 * Find the specific ICU data information from the data.
214 5
	 * The path to the specific ICU data is separated with a slash "/".
215 5
	 * E.g. To find the default calendar used by the culture, the path
216 5
	 * "calendar/default" will return the corresponding default calendar.
217
	 * @param string $path the data you want to find.
218
	 * @param string $key bundle name.
219 5
	 * @return mixed the specific ICU data.
220 5
	 */
221
	public function findInfo($path = '/', $key = null)
222
	{
223 5
		if ($key === null) {
224
			// try to guess the bundle from the path. Always defaults to "Core".
225
			$key = 'Core';
226
			foreach (self::$bundleNames as $bundleName => $icuBundleName) {
227
				if (strpos($path, $bundleName) === 0) {
228
					$key = $bundleName;
229
					break;
230
				}
231
			}
232
		}
233
234 6
		if (!array_key_exists($this->culture, self::$data)) {
235
			$this->loadCultureData($key);
236 6
		}
237
		if (!array_key_exists($this->culture, self::$data) || !array_key_exists($key, self::$data[$this->culture])) {
238 1
			return [];
239 1
		}
240 1
241 1
		return $this->searchResources(self::$data[$this->culture][$key], $path);
242 1
	}
243
244
	/**
245
	 * Search the array for a specific value using a path separated using
246
	 * slash "/" separated path. e.g to find $info['hello']['world'],
247 6
	 * the path "hello/world" will return the corresponding value.
248 5
	 * @param array $info the array for search
249
	 * @param string $path slash "/" separated array path.
250 6
	 * @param mixed $resource
251
	 * @return mixed the value array using the path
252
	 */
253
	private function searchResources($resource, $path = '/')
254 6
	{
255
		$index = explode('/', $path);
256
		for ($i = 0, $k = count($index); $i < $k; ++$i) {
257
			if (is_object($resource)) {
258
				$resource = $resource->get($index[$i]);
259
			}
260
		}
261
262
		return $this->simplify($resource);
263
	}
264
265
	/**
266 6
	 * Gets the culture name in the format
267
	 * "<languagecode2>_(country/regioncode2)".
268 6
	 * @return string culture name.
269 6
	 */
270 6
	public function getName()
271 6
	{
272
		return $this->culture;
273
	}
274
275 6
	/**
276
	 * Gets the default calendar used by the culture, e.g. "gregorian".
277
	 * @return string the default calendar.
278
	 */
279
	public function getCalendar()
280
	{
281
		return $this->findInfo('calendar/default');
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->findInfo('calendar/default') returns the type array which is incompatible with the documented return type string.
Loading history...
282
	}
283 1
284
	/**
285 1
	 * Gets the culture name in the language that the culture is set
286
	 * to display. Returns <code>array('Language','Country');</code>
287
	 * 'Country' is omitted if the culture is neutral.
288
	 * @return array array with language and country as elements, localized.
289
	 */
290
	public function getNativeName()
291
	{
292
		$lang = substr($this->culture, 0, 2);
293
		$reg = substr($this->culture, 3, 2);
294
		$language = $this->findInfo("Languages/{$lang}");
295
		$region = $this->findInfo("Countries/{$reg}");
296
		if ($region) {
297
			return $language . ' (' . $region . ')';
0 ignored issues
show
Bug introduced by
Are you sure $region of type array can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

297
			return $language . ' (' . /** @scrutinizer ignore-type */ $region . ')';
Loading history...
Bug Best Practice introduced by
The expression return $language . ' (' . $region . ')' returns the type string which is incompatible with the documented return type array.
Loading history...
Bug introduced by
Are you sure $language of type array can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

297
			return /** @scrutinizer ignore-type */ $language . ' (' . $region . ')';
Loading history...
298
		} else {
299
			return $language;
300
		}
301
	}
302
303
	/**
304
	 * Gets the culture name in English.
305
	 * Returns <code>array('Language','Country');</code>
306
	 * 'Country' is omitted if the culture is neutral.
307
	 * @return string language (country), it may locale code string if english name does not exist.
308
	 */
309
	public function getEnglishName()
310
	{
311
		$lang = substr($this->culture, 0, 2);
312
		$reg = substr($this->culture, 3, 2);
313
		$culture = $this->getInvariantCulture();
314
315
		$language = $culture->findInfo("Languages/{$lang}");
316
		if ($language === null) {
0 ignored issues
show
introduced by
The condition $language === null is always false.
Loading history...
317
			return $this->culture;
318
		}
319
320
		$region = $culture->findInfo("Countries/{$reg}");
321
		if ($region) {
322 1
			return $language . ' (' . $region . ')';
0 ignored issues
show
Bug introduced by
Are you sure $region of type array can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

322
			return $language . ' (' . /** @scrutinizer ignore-type */ $region . ')';
Loading history...
Bug introduced by
Are you sure $language of type array can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

322
			return /** @scrutinizer ignore-type */ $language . ' (' . $region . ')';
Loading history...
323
		} else {
324 1
			return $language;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $language returns the type array which is incompatible with the documented return type string.
Loading history...
325 1
		}
326 1
	}
327
328 1
	/**
329 1
	 * Gets the CultureInfo that is culture-independent (invariant).
330 1
	 * Any changes to the invariant culture affects all other
331
	 * instances of the invariant culture.
332
	 * The invariant culture is assumed to be "en";
333
	 * @return CultureInfo invariant culture info is "en".
334
	 */
335
	public static function getInvariantCulture()
336
	{
337
		static $invariant;
338
		if ($invariant === null) {
339
			$invariant = new CultureInfo();
340
		}
341
		return $invariant;
342
	}
343
344
	/**
345
	 * Gets a value indicating whether the current CultureInfo
346
	 * represents a neutral culture. Returns true if the culture
347
	 * only contains two characters.
348 8
	 * @return bool true if culture is neutral, false otherwise.
349
	 */
350 8
	public function getIsNeutralCulture()
351 8
	{
352 1
		return strlen($this->culture) == 2;
353
	}
354 8
355
	/**
356
	 * Gets the list of supported cultures filtered by the specified
357
	 * culture type. This is an EXPENSIVE function, it needs to traverse
358
	 * a list of ICU files in the data directory.
359
	 * This function can be called statically.
360
	 * @param int $type culture type, CultureInfo::ALL, CultureInfo::NEUTRAL
361
	 * or CultureInfo::SPECIFIC.
362
	 * @return array list of culture information available.
363 1
	 */
364
	public static function getCultures($type = CultureInfo::ALL)
365 1
	{
366
		$all = \ResourceBundle::getLocales('');
367
368
		switch ($type) {
369
			case CultureInfo::ALL:
370
				return $all;
371
			case CultureInfo::NEUTRAL:
372
				foreach ($all as $key => $culture) {
373
					if (strlen($culture) != 2) {
374
						unset($all[$key]);
375
					}
376
				}
377 1
				return $all;
378
			case CultureInfo::SPECIFIC:
379 1
				foreach ($all as $key => $culture) {
380
					if (strlen($culture) == 2) {
381
						unset($all[$key]);
382 1
					}
383 1
				}
384 1
				return $all;
385 1
		}
386 1
	}
387 1
388
	/**
389
	 * Simplify a single element array into its own value.
390 1
	 * E.g. <code>array(0 => array('hello'), 1 => 'world');</code>
391 1
	 * becomes <code>array(0 => 'hello', 1 => 'world');</code>
392 1
	 * @param array $array with single elements arrays
393 1
	 * @param mixed $obj
394 1
	 * @return array simplified array.
395
	 */
396
	protected function simplify($obj)
397 1
	{
398
		if (is_scalar($obj)) {
399
			return $obj;
400
		} elseif ($obj instanceof \ResourceBundle) {
401
			$array = [];
402
			foreach ($obj as $k => $v) {
403
				$array[$k] = $v;
404
			}
405
		} else {
406
			$array = $obj;
407
		}
408
409 6
		if (is_array($array)) {
410
			for ($i = 0, $k = count($array); $i < $k; ++$i) {
411 6
				$key = key($array);
412
				if ($key !== null
413 6
					&& is_array($array[$key])
414 5
					&& count($array[$key]) == 1) {
415 5
					$array[$key] = $array[$key][0];
416 5
				}
417
				next($array);
418
			}
419 4
		}
420
		return $array;
421
	}
422 6
423 5
	/**
424 5
	 * Get a list of countries in the language of the localized version.
425 5
	 * @return array a list of localized country names.
426 5
	 */
427 5
	public function getCountries()
428
	{
429
		return $this->simplify($this->findInfo('Countries', 'Countries'));
430 5
	}
431
432
	/**
433 6
	 * Get a list of currencies in the language of the localized version.
434
	 * @return array a list of localized currencies.
435
	 */
436
	public function getCurrencies()
437
	{
438
		static $arr;
439
		if ($arr === null) {
440 1
			$arr = $this->findInfo('Currencies', 'Currencies');
441
			foreach ($arr as $k => $v) {
442 1
				$arr[$k] = $this->simplify($v);
443
			}
444
		}
445
		return $arr;
446
	}
447
448
	/**
449 1
	 * Get a list of languages in the language of the localized version.
450
	 * @return array list of localized language names.
451 1
	 */
452 1
	public function getLanguages()
453 1
	{
454 1
		return $this->simplify($this->findInfo('Languages', 'Languages'));
455 1
	}
456
457
	/**
458 1
	 * Get a list of scripts in the language of the localized version.
459
	 * @return array list of localized script names.
460
	 */
461
	public function getScripts()
462
	{
463
		return $this->simplify($this->findInfo('Scripts', 'Languages'));
464
	}
465 1
466
	/**
467 1
	 * Get a list of timezones in the language of the localized version.
468
	 * @return array list of localized timezones.
469
	 */
470
	public function getTimeZones()
471
	{
472
		static $arr;
473
		if ($arr === null) {
474 1
			$validPrefixes = ['Africa', 'America', 'Antarctica', 'Arctic', 'Asia', 'Atlantic', 'Australia', 'Etc', 'Europe', 'Indian', 'Pacific'];
475
			$tmp = $this->findInfo('zoneStrings', 'zoneStrings');
476 1
			foreach ($tmp as $k => $v) {
477
				foreach ($validPrefixes as $prefix) {
478
					if (strpos($k, $prefix) === 0) {
479
						$arr[] = str_replace(':', '/', $k);
480
						break;
481
					}
482
				}
483 1
			}
484
		}
485 1
		return $arr;
486 1
	}
487
}
488