PluginProbe
PayPlug for WooCommerce (Official) / 1.2.2
PayPlug for WooCommerce (Official) v1.2.2
3.0.0 2.18.0 1.0.17 1.0.18 1.0.19 1.0.20 1.0.21 1.0.22 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.0.8 1.1.0 1.10.0 1.10.1 1.2.1 1.2.10 1.2.11 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 All 101 releases
payplug / lib / libphonenumber / PhoneNumberUtil.php

PhoneNumberUtil.php in PayPlug for WooCommerce (Official) 1.2.2, at lib/libphonenumber/PhoneNumberUtil.php

3,601 lines 174.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace libphonenumber;
4
5 use libphonenumber\Leniency\AbstractLeniency;
6
7 /**
8 * Utility for international phone numbers. Functionality includes formatting, parsing and
9 * validation.
10 *
11 * <p>If you use this library, and want to be notified about important changes, please sign up to
12 * our <a href="http://groups.google.com/group/libphonenumber-discuss/about">mailing list</a>.
13 *
14 * NOTE: A lot of methods in this class require Region Code strings. These must be provided using
15 * CLDR two-letter region-code format. These should be in upper-case. The list of the codes
16 * can be found here:
17 * http://www.unicode.org/cldr/charts/30/supplemental/territory_information.html
18 *
19 * @author Shaopeng Jia
20 * @see https://github.com/googlei18n/libphonenumber
21 */
22 class PhoneNumberUtil
23 {
24 /** Flags to use when compiling regular expressions for phone numbers */
25 const REGEX_FLAGS = 'ui'; //Unicode and case insensitive
26 // The minimum and maximum length of the national significant number.
27 const MIN_LENGTH_FOR_NSN = 2;
28 // The ITU says the maximum length should be 15, but we have found longer numbers in Germany.
29 const MAX_LENGTH_FOR_NSN = 17;
30
31 // We don't allow input strings for parsing to be longer than 250 chars. This prevents malicious
32 // input from overflowing the regular-expression engine.
33 const MAX_INPUT_STRING_LENGTH = 250;
34
35 // The maximum length of the country calling code.
36 const MAX_LENGTH_COUNTRY_CODE = 3;
37
38 const REGION_CODE_FOR_NON_GEO_ENTITY = '001';
39 const META_DATA_FILE_PREFIX = 'PhoneNumberMetadata';
40 const TEST_META_DATA_FILE_PREFIX = 'PhoneNumberMetadataForTesting';
41
42 // Region-code for the unknown region.
43 const UNKNOWN_REGION = 'ZZ';
44
45 const NANPA_COUNTRY_CODE = 1;
46 /*
47 * The prefix that needs to be inserted in front of a Colombian landline number when dialed from
48 * a mobile number in Colombia.
49 */
50 const COLOMBIA_MOBILE_TO_FIXED_LINE_PREFIX = '3';
51 // The PLUS_SIGN signifies the international prefix.
52 const PLUS_SIGN = '+';
53 const PLUS_CHARS = '++';
54 const STAR_SIGN = '*';
55
56 const RFC3966_EXTN_PREFIX = ';ext=';
57 const RFC3966_PREFIX = 'tel:';
58 const RFC3966_PHONE_CONTEXT = ';phone-context=';
59 const RFC3966_ISDN_SUBADDRESS = ';isub=';
60
61 // We use this pattern to check if the phone number has at least three letters in it - if so, then
62 // we treat it as a number where some phone-number digits are represented by letters.
63 const VALID_ALPHA_PHONE_PATTERN = '(?:.*?[A-Za-z]){3}.*';
64 // We accept alpha characters in phone numbers, ASCII only, upper and lower case.
65 const VALID_ALPHA = 'A-Za-z';
66
67
68 // Default extension prefix to use when formatting. This will be put in front of any extension
69 // component of the number, after the main national number is formatted. For example, if you wish
70 // the default extension formatting to be " extn: 3456", then you should specify " extn: " here
71 // as the default extension prefix. This can be overridden by region-specific preferences.
72 const DEFAULT_EXTN_PREFIX = ' ext. ';
73
74 // Regular expression of acceptable punctuation found in phone numbers, used to find numbers in
75 // text and to decide what is a viable phone number. This excludes diallable characters.
76 // This consists of dash characters, white space characters, full stops, slashes,
77 // square brackets, parentheses and tildes. It also includes the letter 'x' as that is found as a
78 // placeholder for carrier information in some phone numbers. Full-width variants are also
79 // present.
80 const VALID_PUNCTUATION = "-x\xE2\x80\x90-\xE2\x80\x95\xE2\x88\x92\xE3\x83\xBC\xEF\xBC\x8D-\xEF\xBC\x8F \xC2\xA0\xC2\xAD\xE2\x80\x8B\xE2\x81\xA0\xE3\x80\x80()\xEF\xBC\x88\xEF\xBC\x89\xEF\xBC\xBB\xEF\xBC\xBD.\\[\\]/~\xE2\x81\x93\xE2\x88\xBC";
81 const DIGITS = "\\p{Nd}";
82
83 // Pattern that makes it easy to distinguish whether a region has a single international dialing
84 // prefix or not. If a region has a single international prefix (e.g. 011 in USA), it will be
85 // represented as a string that contains a sequence of ASCII digits, and possible a tilde, which
86 // signals waiting for the tone. If there are multiple available international prefixes in a
87 // region, they will be represented as a regex string that always contains one or more characters
88 // that are not ASCII digits or a tilde.
89 const SINGLE_INTERNATIONAL_PREFIX = "[\\d]+(?:[~\xE2\x81\x93\xE2\x88\xBC\xEF\xBD\x9E][\\d]+)?";
90 const NON_DIGITS_PATTERN = "(\\D+)";
91
92 // The FIRST_GROUP_PATTERN was originally set to $1 but there are some countries for which the
93 // first group is not used in the national pattern (e.g. Argentina) so the $1 group does not match
94 // correctly. Therefore, we use \d, so that the first group actually used in the pattern will be
95 // matched.
96 const FIRST_GROUP_PATTERN = "(\\$\\d)";
97 // Constants used in the formatting rules to represent the national prefix, first group and
98 // carrier code respectively.
99 const NP_STRING = '$NP';
100 const FG_STRING = '$FG';
101 const CC_STRING = '$CC';
102
103 // A pattern that is used to determine if the national prefix formatting rule has the first group
104 // only, i.e., does not start with the national prefix. Note that the pattern explicitly allows
105 // for unbalanced parentheses.
106 const FIRST_GROUP_ONLY_PREFIX_PATTERN = '\\(?\\$1\\)?';
107 public static $PLUS_CHARS_PATTERN;
108 protected static $SEPARATOR_PATTERN;
109 protected static $CAPTURING_DIGIT_PATTERN;
110 protected static $VALID_START_CHAR_PATTERN;
111 public static $SECOND_NUMBER_START_PATTERN = '[\\\\/] *x';
112 public static $UNWANTED_END_CHAR_PATTERN = "[[\\P{N}&&\\P{L}]&&[^#]]+$";
113 protected static $DIALLABLE_CHAR_MAPPINGS = array();
114 protected static $CAPTURING_EXTN_DIGITS;
115
116 /**
117 * @var PhoneNumberUtil
118 */
119 protected static $instance;
120
121 /**
122 * Only upper-case variants of alpha characters are stored.
123 * @var array
124 */
125 protected static $ALPHA_MAPPINGS = array(
126 'A' => '2',
127 'B' => '2',
128 'C' => '2',
129 'D' => '3',
130 'E' => '3',
131 'F' => '3',
132 'G' => '4',
133 'H' => '4',
134 'I' => '4',
135 'J' => '5',
136 'K' => '5',
137 'L' => '5',
138 'M' => '6',
139 'N' => '6',
140 'O' => '6',
141 'P' => '7',
142 'Q' => '7',
143 'R' => '7',
144 'S' => '7',
145 'T' => '8',
146 'U' => '8',
147 'V' => '8',
148 'W' => '9',
149 'X' => '9',
150 'Y' => '9',
151 'Z' => '9',
152 );
153
154 /**
155 * Map of country calling codes that use a mobile token before the area code. One example of when
156 * this is relevant is when determining the length of the national destination code, which should
157 * be the length of the area code plus the length of the mobile token.
158 * @var array
159 */
160 protected static $MOBILE_TOKEN_MAPPINGS = array();
161
162 /**
163 * Set of country codes that have geographically assigned mobile numbers (see GEO_MOBILE_COUNTRIES
164 * below) which are not based on *area codes*. For example, in China mobile numbers start with a
165 * carrier indicator, and beyond that are geographically assigned: this carrier indicator is not
166 * considered to be an area code.
167 *
168 * @var array
169 */
170 protected static $GEO_MOBILE_COUNTRIES_WITHOUT_MOBILE_AREA_CODES;
171
172 /**
173 * Set of country calling codes that have geographically assigned mobile numbers. This may not be
174 * complete; we add calling codes case by case, as we find geographical mobile numbers or hear
175 * from user reports. Note that countries like the US, where we can't distinguish between
176 * fixed-line or mobile numbers, are not listed here, since we consider FIXED_LINE_OR_MOBILE to be
177 * a possibly geographically-related type anyway (like FIXED_LINE).
178 *
179 * @var array
180 */
181 protected static $GEO_MOBILE_COUNTRIES;
182
183 /**
184 * For performance reasons, amalgamate both into one map.
185 * @var array
186 */
187 protected static $ALPHA_PHONE_MAPPINGS;
188
189 /**
190 * Separate map of all symbols that we wish to retain when formatting alpha numbers. This
191 * includes digits, ASCII letters and number grouping symbols such as "-" and " ".
192 * @var array
193 */
194 protected static $ALL_PLUS_NUMBER_GROUPING_SYMBOLS;
195
196 /**
197 * Simple ASCII digits map used to populate ALPHA_PHONE_MAPPINGS and
198 * ALL_PLUS_NUMBER_GROUPING_SYMBOLS.
199 * @var array
200 */
201 protected static $asciiDigitMappings = array(
202 '0' => '0',
203 '1' => '1',
204 '2' => '2',
205 '3' => '3',
206 '4' => '4',
207 '5' => '5',
208 '6' => '6',
209 '7' => '7',
210 '8' => '8',
211 '9' => '9',
212 );
213
214 /**
215 * Regexp of all possible ways to write extensions, for use when parsing. This will be run as a
216 * case-insensitive regexp match. Wide character versions are also provided after each ASCII
217 * version.
218 * @var String
219 */
220 protected static $EXTN_PATTERNS_FOR_PARSING;
221 /**
222 * @var string
223 * @internal
224 */
225 public static $EXTN_PATTERNS_FOR_MATCHING;
226 protected static $EXTN_PATTERN;
227 protected static $VALID_PHONE_NUMBER_PATTERN;
228 protected static $MIN_LENGTH_PHONE_NUMBER_PATTERN;
229 /**
230 * Regular expression of viable phone numbers. This is location independent. Checks we have at
231 * least three leading digits, and only valid punctuation, alpha characters and
232 * digits in the phone number. Does not include extension data.
233 * The symbol 'x' is allowed here as valid punctuation since it is often used as a placeholder for
234 * carrier codes, for example in Brazilian phone numbers. We also allow multiple "+" characters at
235 * the start.
236 * Corresponds to the following:
237 * [digits]{minLengthNsn}|
238 * plus_sign*(([punctuation]|[star])*[digits]){3,}([punctuation]|[star]|[digits]|[alpha])*
239 *
240 * The first reg-ex is to allow short numbers (two digits long) to be parsed if they are entered
241 * as "15" etc, but only if there is no punctuation in them. The second expression restricts the
242 * number of digits to three or more, but then allows them to be in international form, and to
243 * have alpha-characters and punctuation.
244 *
245 * Note VALID_PUNCTUATION starts with a -, so must be the first in the range.
246 * @var string
247 */
248 protected static $VALID_PHONE_NUMBER;
249 protected static $numericCharacters = array(
250 "\xef\xbc\x90" => 0,
251 "\xef\xbc\x91" => 1,
252 "\xef\xbc\x92" => 2,
253 "\xef\xbc\x93" => 3,
254 "\xef\xbc\x94" => 4,
255 "\xef\xbc\x95" => 5,
256 "\xef\xbc\x96" => 6,
257 "\xef\xbc\x97" => 7,
258 "\xef\xbc\x98" => 8,
259 "\xef\xbc\x99" => 9,
260
261 "\xd9\xa0" => 0,
262 "\xd9\xa1" => 1,
263 "\xd9\xa2" => 2,
264 "\xd9\xa3" => 3,
265 "\xd9\xa4" => 4,
266 "\xd9\xa5" => 5,
267 "\xd9\xa6" => 6,
268 "\xd9\xa7" => 7,
269 "\xd9\xa8" => 8,
270 "\xd9\xa9" => 9,
271
272 "\xdb\xb0" => 0,
273 "\xdb\xb1" => 1,
274 "\xdb\xb2" => 2,
275 "\xdb\xb3" => 3,
276 "\xdb\xb4" => 4,
277 "\xdb\xb5" => 5,
278 "\xdb\xb6" => 6,
279 "\xdb\xb7" => 7,
280 "\xdb\xb8" => 8,
281 "\xdb\xb9" => 9,
282
283 "\xe1\xa0\x90" => 0,
284 "\xe1\xa0\x91" => 1,
285 "\xe1\xa0\x92" => 2,
286 "\xe1\xa0\x93" => 3,
287 "\xe1\xa0\x94" => 4,
288 "\xe1\xa0\x95" => 5,
289 "\xe1\xa0\x96" => 6,
290 "\xe1\xa0\x97" => 7,
291 "\xe1\xa0\x98" => 8,
292 "\xe1\xa0\x99" => 9,
293 );
294
295 /**
296 * The set of county calling codes that map to the non-geo entity region ("001").
297 * @var array
298 */
299 protected $countryCodesForNonGeographicalRegion = array();
300 /**
301 * The set of regions the library supports.
302 * @var array
303 */
304 protected $supportedRegions = array();
305
306 /**
307 * A mapping from a country calling code to the region codes which denote the region represented
308 * by that country calling code. In the case of multiple regions sharing a calling code, such as
309 * the NANPA regions, the one indicated with "isMainCountryForCode" in the metadata should be
310 * first.
311 * @var array
312 */
313 protected $countryCallingCodeToRegionCodeMap = array();
314 /**
315 * The set of regions that share country calling code 1.
316 * @var array
317 */
318 protected $nanpaRegions = array();
319
320 /**
321 * @var MetadataSourceInterface
322 */
323 protected $metadataSource;
324
325 /**
326 * @var MatcherAPIInterface
327 */
328 protected $matcherAPI;
329
330 /**
331 * This class implements a singleton, so the only constructor is protected.
332 * @param MetadataSourceInterface $metadataSource
333 * @param $countryCallingCodeToRegionCodeMap
334 */
335 protected function __construct(MetadataSourceInterface $metadataSource, $countryCallingCodeToRegionCodeMap)
336 {
337 $this->metadataSource = $metadataSource;
338 $this->countryCallingCodeToRegionCodeMap = $countryCallingCodeToRegionCodeMap;
339 $this->init();
340 $this->matcherAPI = RegexBasedMatcher::create();
341 static::initCapturingExtnDigits();
342 static::initExtnPatterns();
343 static::initExtnPattern();
344 static::$PLUS_CHARS_PATTERN = '[' . static::PLUS_CHARS . ']+';
345 static::$SEPARATOR_PATTERN = '[' . static::VALID_PUNCTUATION . ']+';
346 static::$CAPTURING_DIGIT_PATTERN = '(' . static::DIGITS . ')';
347 static::initValidStartCharPattern();
348 static::initAlphaPhoneMappings();
349 static::initDiallableCharMappings();
350
351 static::$ALL_PLUS_NUMBER_GROUPING_SYMBOLS = array();
352 // Put (lower letter -> upper letter) and (upper letter -> upper letter) mappings.
353 foreach (static::$ALPHA_MAPPINGS as $c => $value) {
354 static::$ALL_PLUS_NUMBER_GROUPING_SYMBOLS[strtolower($c)] = $c;
355 static::$ALL_PLUS_NUMBER_GROUPING_SYMBOLS[$c] = $c;
356 }
357 static::$ALL_PLUS_NUMBER_GROUPING_SYMBOLS += static::$asciiDigitMappings;
358 static::$ALL_PLUS_NUMBER_GROUPING_SYMBOLS['-'] = '-';
359 static::$ALL_PLUS_NUMBER_GROUPING_SYMBOLS["\xEF\xBC\x8D"] = '-';
360 static::$ALL_PLUS_NUMBER_GROUPING_SYMBOLS["\xE2\x80\x90"] = '-';
361 static::$ALL_PLUS_NUMBER_GROUPING_SYMBOLS["\xE2\x80\x91"] = '-';
362 static::$ALL_PLUS_NUMBER_GROUPING_SYMBOLS["\xE2\x80\x92"] = '-';
363 static::$ALL_PLUS_NUMBER_GROUPING_SYMBOLS["\xE2\x80\x93"] = '-';
364 static::$ALL_PLUS_NUMBER_GROUPING_SYMBOLS["\xE2\x80\x94"] = '-';
365 static::$ALL_PLUS_NUMBER_GROUPING_SYMBOLS["\xE2\x80\x95"] = '-';
366 static::$ALL_PLUS_NUMBER_GROUPING_SYMBOLS["\xE2\x88\x92"] = '-';
367 static::$ALL_PLUS_NUMBER_GROUPING_SYMBOLS['/'] = '/';
368 static::$ALL_PLUS_NUMBER_GROUPING_SYMBOLS["\xEF\xBC\x8F"] = '/';
369 static::$ALL_PLUS_NUMBER_GROUPING_SYMBOLS[' '] = ' ';
370 static::$ALL_PLUS_NUMBER_GROUPING_SYMBOLS["\xE3\x80\x80"] = ' ';
371 static::$ALL_PLUS_NUMBER_GROUPING_SYMBOLS["\xE2\x81\xA0"] = ' ';
372 static::$ALL_PLUS_NUMBER_GROUPING_SYMBOLS['.'] = '.';
373 static::$ALL_PLUS_NUMBER_GROUPING_SYMBOLS["\xEF\xBC\x8E"] = '.';
374
375
376 static::initValidPhoneNumberPatterns();
377
378 static::$UNWANTED_END_CHAR_PATTERN = '[^' . static::DIGITS . static::VALID_ALPHA . '#]+$';
379
380 static::initMobileTokenMappings();
381
382 static::$GEO_MOBILE_COUNTRIES_WITHOUT_MOBILE_AREA_CODES = array();
383 static::$GEO_MOBILE_COUNTRIES_WITHOUT_MOBILE_AREA_CODES[] = 86; // China
384
385 static::$GEO_MOBILE_COUNTRIES = array();
386 static::$GEO_MOBILE_COUNTRIES[] = 52; // Mexico
387 static::$GEO_MOBILE_COUNTRIES[] = 54; // Argentina
388 static::$GEO_MOBILE_COUNTRIES[] = 55; // Brazil
389 static::$GEO_MOBILE_COUNTRIES[] = 62; // Indonesia: some prefixes only (fixed CMDA wireless)
390
391 static::$GEO_MOBILE_COUNTRIES = array_merge(static::$GEO_MOBILE_COUNTRIES, static::$GEO_MOBILE_COUNTRIES_WITHOUT_MOBILE_AREA_CODES);
392 }
393
394 /**
395 * Gets a {@link PhoneNumberUtil} instance to carry out international phone number formatting,
396 * parsing, or validation. The instance is loaded with phone number metadata for a number of most
397 * commonly used regions.
398 *
399 * <p>The {@link PhoneNumberUtil} is implemented as a singleton. Therefore, calling getInstance
400 * multiple times will only result in one instance being created.
401 *
402 * @param string $baseFileLocation
403 * @param array|null $countryCallingCodeToRegionCodeMap
404 * @param MetadataLoaderInterface|null $metadataLoader
405 * @param MetadataSourceInterface|null $metadataSource
406 * @return PhoneNumberUtil instance
407 */
408 public static function getInstance($baseFileLocation = self::META_DATA_FILE_PREFIX, array $countryCallingCodeToRegionCodeMap = null, MetadataLoaderInterface $metadataLoader = null, MetadataSourceInterface $metadataSource = null)
409 {
410 if (static::$instance === null) {
411 if ($countryCallingCodeToRegionCodeMap === null) {
412 $countryCallingCodeToRegionCodeMap = CountryCodeToRegionCodeMap::$countryCodeToRegionCodeMap;
413 }
414
415 if ($metadataLoader === null) {
416 $metadataLoader = new DefaultMetadataLoader();
417 }
418
419 if ($metadataSource === null) {
420 $metadataSource = new MultiFileMetadataSourceImpl($metadataLoader, __DIR__ . '/data/' . $baseFileLocation);
421 }
422
423 static::$instance = new static($metadataSource, $countryCallingCodeToRegionCodeMap);
424 }
425 return static::$instance;
426 }
427
428 protected function init()
429 {
430 $supportedRegions = array(array());
431
432 foreach ($this->countryCallingCodeToRegionCodeMap as $countryCode => $regionCodes) {
433 // We can assume that if the country calling code maps to the non-geo entity region code then
434 // that's the only region code it maps to.
435 if (count($regionCodes) === 1 && static::REGION_CODE_FOR_NON_GEO_ENTITY === $regionCodes[0]) {
436 // This is the subset of all country codes that map to the non-geo entity region code.
437 $this->countryCodesForNonGeographicalRegion[] = $countryCode;
438 } else {
439 // The supported regions set does not include the "001" non-geo entity region code.
440 $supportedRegions[] = $regionCodes;
441 }
442 }
443
444 $this->supportedRegions = call_user_func_array('array_merge', $supportedRegions);
445
446
447 // If the non-geo entity still got added to the set of supported regions it must be because
448 // there are entries that list the non-geo entity alongside normal regions (which is wrong).
449 // If we discover this, remove the non-geo entity from the set of supported regions and log.
450 $idx_region_code_non_geo_entity = array_search(static::REGION_CODE_FOR_NON_GEO_ENTITY, $this->supportedRegions);
451 if ($idx_region_code_non_geo_entity !== false) {
452 unset($this->supportedRegions[$idx_region_code_non_geo_entity]);
453 }
454 $this->nanpaRegions = $this->countryCallingCodeToRegionCodeMap[static::NANPA_COUNTRY_CODE];
455 }
456
457 /**
458 * @internal
459 */
460 public static function initCapturingExtnDigits()
461 {
462 static::$CAPTURING_EXTN_DIGITS = '(' . static::DIGITS . '{1,7})';
463 }
464
465 /**
466 * @internal
467 */
468 public static function initExtnPatterns()
469 {
470 // One-character symbols that can be used to indicate an extension.
471 $singleExtnSymbolsForMatching = "x\xEF\xBD\x98#\xEF\xBC\x83~\xEF\xBD\x9E";
472 // For parsing, we are slightly more lenient in our interpretation than for matching. Here we
473 // allow "comma" and "semicolon" as possible extension indicators. When matching, these are
474 // hardly ever used to indicate this.
475 $singleExtnSymbolsForParsing = ',;' . $singleExtnSymbolsForMatching;
476
477 static::$EXTN_PATTERNS_FOR_PARSING = static::createExtnPattern($singleExtnSymbolsForParsing);
478 static::$EXTN_PATTERNS_FOR_MATCHING = static::createExtnPattern($singleExtnSymbolsForMatching);
479 }
480
481 /**
482 * Helper initialiser method to create the regular-expression pattern to match extensions,
483 * allowing the one-char extension symbols provided by {@code singleExtnSymbols}.
484 * @param string $singleExtnSymbols
485 * @return string
486 */
487 protected static function createExtnPattern($singleExtnSymbols)
488 {
489 // There are three regular expressions here. The first covers RFC 3966 format, where the
490 // extension is added using ";ext=". The second more generic one starts with optional white
491 // space and ends with an optional full stop (.), followed by zero or more spaces/tabs/commas
492 // and then the numbers themselves. The other one covers the special case of American numbers
493 // where the extension is written with a hash at the end, such as "- 503#"
494 // Note that the only capturing groups should be around the digits that you want to capture as
495 // part of the extension, or else parsing will fail!
496 // Canonical-equivalence doesn't seem to be an option with Android java, so we allow two options
497 // for representing the accented o - the character itself, and one in the unicode decomposed
498 // form with the combining acute accent.
499 return (static::RFC3966_EXTN_PREFIX . static::$CAPTURING_EXTN_DIGITS . '|' . "[ \xC2\xA0\\t,]*" .
500 "(?:e?xt(?:ensi(?:o\xCC\x81?|\xC3\xB3))?n?|(?:\xEF\xBD\x85)?\xEF\xBD\x98\xEF\xBD\x94(?:\xEF\xBD\x8E)?|" .
501 'доб|' . '[' . $singleExtnSymbols . "]|int|\xEF\xBD\x89\xEF\xBD\x8E\xEF\xBD\x94|anexo)" .
502 "[:\\.\xEF\xBC\x8E]?[ \xC2\xA0\\t,-]*" . static::$CAPTURING_EXTN_DIGITS . "\\#?|" .
503 '[- ]+(' . static::DIGITS . "{1,5})\\#");
504 }
505
506 protected static function initExtnPattern()
507 {
508 static::$EXTN_PATTERN = '/(?:' . static::$EXTN_PATTERNS_FOR_PARSING . ')$/' . static::REGEX_FLAGS;
509 }
510
511 protected static function initValidPhoneNumberPatterns()
512 {
513 static::initCapturingExtnDigits();
514 static::initExtnPatterns();
515 static::$MIN_LENGTH_PHONE_NUMBER_PATTERN = '[' . static::DIGITS . ']{' . static::MIN_LENGTH_FOR_NSN . '}';
516 static::$VALID_PHONE_NUMBER = '[' . static::PLUS_CHARS . ']*(?:[' . static::VALID_PUNCTUATION . static::STAR_SIGN . ']*[' . static::DIGITS . ']){3,}[' . static::VALID_PUNCTUATION . static::STAR_SIGN . static::VALID_ALPHA . static::DIGITS . ']*';
517 static::$VALID_PHONE_NUMBER_PATTERN = '%^' . static::$MIN_LENGTH_PHONE_NUMBER_PATTERN . '$|^' . static::$VALID_PHONE_NUMBER . '(?:' . static::$EXTN_PATTERNS_FOR_PARSING . ')?$%' . static::REGEX_FLAGS;
518 }
519
520 protected static function initAlphaPhoneMappings()
521 {
522 static::$ALPHA_PHONE_MAPPINGS = static::$ALPHA_MAPPINGS + static::$asciiDigitMappings;
523 }
524
525 protected static function initValidStartCharPattern()
526 {
527 static::$VALID_START_CHAR_PATTERN = '[' . static::PLUS_CHARS . static::DIGITS . ']';
528 }
529
530 protected static function initMobileTokenMappings()
531 {
532 static::$MOBILE_TOKEN_MAPPINGS = array();
533 static::$MOBILE_TOKEN_MAPPINGS['52'] = '1';
534 static::$MOBILE_TOKEN_MAPPINGS['54'] = '9';
535 }
536
537 protected static function initDiallableCharMappings()
538 {
539 static::$DIALLABLE_CHAR_MAPPINGS = static::$asciiDigitMappings;
540 static::$DIALLABLE_CHAR_MAPPINGS[static::PLUS_SIGN] = static::PLUS_SIGN;
541 static::$DIALLABLE_CHAR_MAPPINGS['*'] = '*';
542 static::$DIALLABLE_CHAR_MAPPINGS['#'] = '#';
543 }
544
545 /**
546 * Used for testing purposes only to reset the PhoneNumberUtil singleton to null.
547 */
548 public static function resetInstance()
549 {
550 static::$instance = null;
551 }
552
553 /**
554 * Converts all alpha characters in a number to their respective digits on a keypad, but retains
555 * existing formatting.
556 * @param string $number
557 * @return string
558 */
559 public static function convertAlphaCharactersInNumber($number)
560 {
561 if (static::$ALPHA_PHONE_MAPPINGS === null) {
562 static::initAlphaPhoneMappings();
563 }
564
565 return static::normalizeHelper($number, static::$ALPHA_PHONE_MAPPINGS, false);
566 }
567
568 /**
569 * Normalizes a string of characters representing a phone number by replacing all characters found
570 * in the accompanying map with the values therein, and stripping all other characters if
571 * removeNonMatches is true.
572 *
573 * @param string $number a string of characters representing a phone number
574 * @param array $normalizationReplacements a mapping of characters to what they should be replaced by in
575 * the normalized version of the phone number
576 * @param bool $removeNonMatches indicates whether characters that are not able to be replaced
577 * should be stripped from the number. If this is false, they will be left unchanged in the number.
578 * @return string the normalized string version of the phone number
579 */
580 protected static function normalizeHelper($number, array $normalizationReplacements, $removeNonMatches)
581 {
582 $normalizedNumber = '';
583 $strLength = mb_strlen($number, 'UTF-8');
584 for ($i = 0; $i < $strLength; $i++) {
585 $character = mb_substr($number, $i, 1, 'UTF-8');
586 if (isset($normalizationReplacements[mb_strtoupper($character, 'UTF-8')])) {
587 $normalizedNumber .= $normalizationReplacements[mb_strtoupper($character, 'UTF-8')];
588 } elseif (!$removeNonMatches) {
589 $normalizedNumber .= $character;
590 }
591 // If neither of the above are true, we remove this character.
592 }
593 return $normalizedNumber;
594 }
595
596 /**
597 * Helper function to check if the national prefix formatting rule has the first group only, i.e.,
598 * does not start with the national prefix.
599 * @param string $nationalPrefixFormattingRule
600 * @return bool
601 */
602 public static function formattingRuleHasFirstGroupOnly($nationalPrefixFormattingRule)
603 {
604 $firstGroupOnlyPrefixPatternMatcher = new Matcher(static::FIRST_GROUP_ONLY_PREFIX_PATTERN,
605 $nationalPrefixFormattingRule);
606
607 return mb_strlen($nationalPrefixFormattingRule) === 0
608 || $firstGroupOnlyPrefixPatternMatcher->matches();
609 }
610
611 /**
612 * Returns all regions the library has metadata for.
613 *
614 * @return array An unordered array of the two-letter region codes for every geographical region the
615 * library supports
616 */
617 public function getSupportedRegions()
618 {
619 return $this->supportedRegions;
620 }
621
622 /**
623 * Returns all global network calling codes the library has metadata for.
624 *
625 * @return array An unordered array of the country calling codes for every non-geographical entity
626 * the library supports
627 */
628 public function getSupportedGlobalNetworkCallingCodes()
629 {
630 return $this->countryCodesForNonGeographicalRegion;
631 }
632
633 /**
634 * Returns all country calling codes the library has metadata for, covering both non-geographical
635 * entities (global network calling codes) and those used for geographical entities. The could be
636 * used to populate a drop-down box of country calling codes for a phone-number widget, for
637 * instance.
638 *
639 * @return array An unordered array of the country calling codes for every geographical and
640 * non-geographical entity the library supports
641 */
642 public function getSupportedCallingCodes()
643 {
644 return array_keys($this->countryCallingCodeToRegionCodeMap);
645 }
646
647 /**
648 * Returns true if there is any possible number data set for a particular PhoneNumberDesc.
649 *
650 * @param PhoneNumberDesc $desc
651 * @return bool
652 */
653 protected static function descHasPossibleNumberData(PhoneNumberDesc $desc)
654 {
655 // If this is empty, it means numbers of this type inherit from the "general desc" -> the value
656 // '-1' means that no numbers exist for this type.
657 $possibleLength = $desc->getPossibleLength();
658 return count($possibleLength) != 1 || $possibleLength[0] != -1;
659 }
660
661 /**
662 * Returns true if there is any data set for a particular PhoneNumberDesc.
663 *
664 * @param PhoneNumberDesc $desc
665 * @return bool
666 */
667 protected static function descHasData(PhoneNumberDesc $desc)
668 {
669 // Checking most properties since we don't know what's present, since a custom build may have
670 // stripped just one of them (e.g. liteBuild strips exampleNumber). We don't bother checking the
671 // possibleLengthsLocalOnly, since if this is the only thing that's present we don't really
672 // support the type at all: no type-specific methods will work with only this data.
673 return $desc->hasExampleNumber()
674 || static::descHasPossibleNumberData($desc)
675 || $desc->hasNationalNumberPattern();
676 }
677
678 /**
679 * Returns the types we have metadata for based on the PhoneMetadata object passed in
680 *
681 * @param PhoneMetadata $metadata
682 * @return array
683 */
684 private function getSupportedTypesForMetadata(PhoneMetadata $metadata)
685 {
686 $types = array();
687 foreach (array_keys(PhoneNumberType::values()) as $type) {
688 if ($type === PhoneNumberType::FIXED_LINE_OR_MOBILE || $type === PhoneNumberType::UNKNOWN) {
689 // Never return FIXED_LINE_OR_MOBILE (it is a convenience type, and represents that a
690 // particular number type can't be determined) or UNKNOWN (the non-type).
691 continue;
692 }
693
694 if (self::descHasData($this->getNumberDescByType($metadata, $type))) {
695 $types[] = $type;
696 }
697 }
698
699 return $types;
700 }
701
702 /**
703 * Returns the types for a given region which the library has metadata for. Will not include
704 * FIXED_LINE_OR_MOBILE (if the numbers in this region could be classified as FIXED_LINE_OR_MOBILE,
705 * both FIXED_LINE and MOBILE would be present) and UNKNOWN.
706 *
707 * No types will be returned for invalid or unknown region codes.
708 *
709 * @param string $regionCode
710 * @return array
711 */
712 public function getSupportedTypesForRegion($regionCode)
713 {
714 if (!$this->isValidRegionCode($regionCode)) {
715 return array();
716 }
717 $metadata = $this->getMetadataForRegion($regionCode);
718 return $this->getSupportedTypesForMetadata($metadata);
719 }
720
721 /**
722 * Returns the types for a country-code belonging to a non-geographical entity which the library
723 * has metadata for. Will not include FIXED_LINE_OR_MOBILE (if numbers for this non-geographical
724 * entity could be classified as FIXED_LINE_OR_MOBILE, both FIXED_LINE and MOBILE would be
725 * present) and UNKNOWN.
726 *
727 * @param int $countryCallingCode
728 * @return array
729 */
730 public function getSupportedTypesForNonGeoEntity($countryCallingCode)
731 {
732 $metadata = $this->getMetadataForNonGeographicalRegion($countryCallingCode);
733 if ($metadata === null) {
734 return array();
735 }
736
737 return $this->getSupportedTypesForMetadata($metadata);
738 }
739
740 /**
741 * Gets the length of the geographical area code from the {@code nationalNumber} field of the
742 * PhoneNumber object passed in, so that clients could use it to split a national significant
743 * number into geographical area code and subscriber number. It works in such a way that the
744 * resultant subscriber number should be diallable, at least on some devices. An example of how
745 * this could be used:
746 *
747 * <code>
748 * $phoneUtil = PhoneNumberUtil::getInstance();
749 * $number = $phoneUtil->parse("16502530000", "US");
750 * $nationalSignificantNumber = $phoneUtil->getNationalSignificantNumber($number);
751 *
752 * $areaCodeLength = $phoneUtil->getLengthOfGeographicalAreaCode($number);
753 * if ($areaCodeLength > 0)
754 * {
755 * $areaCode = substr($nationalSignificantNumber, 0,$areaCodeLength);
756 * $subscriberNumber = substr($nationalSignificantNumber, $areaCodeLength);
757 * } else {
758 * $areaCode = "";
759 * $subscriberNumber = $nationalSignificantNumber;
760 * }
761 * </code>
762 *
763 * N.B.: area code is a very ambiguous concept, so the I18N team generally recommends against
764 * using it for most purposes, but recommends using the more general {@code nationalNumber}
765 * instead. Read the following carefully before deciding to use this method:
766 * <ul>
767 * <li> geographical area codes change over time, and this method honors those changes;
768 * therefore, it doesn't guarantee the stability of the result it produces.
769 * <li> subscriber numbers may not be diallable from all devices (notably mobile devices, which
770 * typically requires the full national_number to be dialled in most regions).
771 * <li> most non-geographical numbers have no area codes, including numbers from non-geographical
772 * entities
773 * <li> some geographical numbers have no area codes.
774 * </ul>
775 * @param PhoneNumber $number PhoneNumber object for which clients want to know the length of the area code.
776 * @return int the length of area code of the PhoneNumber object passed in.
777 */
778 public function getLengthOfGeographicalAreaCode(PhoneNumber $number)
779 {
780 $metadata = $this->getMetadataForRegion($this->getRegionCodeForNumber($number));
781 if ($metadata === null) {
782 return 0;
783 }
784 // If a country doesn't use a national prefix, and this number doesn't have an Italian leading
785 // zero, we assume it is a closed dialling plan with no area codes.
786 if (!$metadata->hasNationalPrefix() && !$number->isItalianLeadingZero()) {
787 return 0;
788 }
789
790 $type = $this->getNumberType($number);
791 $countryCallingCode = $number->getCountryCode();
792
793 if ($type === PhoneNumberType::MOBILE
794 // Note this is a rough heuristic; it doesn't cover Indonesia well, for example, where area
795 // codes are present for some mobile phones but not for others. We have no better way of
796 // representing this in the metadata at this point.
797 && in_array($countryCallingCode, self::$GEO_MOBILE_COUNTRIES_WITHOUT_MOBILE_AREA_CODES)
798 ) {
799 return 0;
800 }
801
802 if (!$this->isNumberGeographical($type, $countryCallingCode)) {
803 return 0;
804 }
805
806 return $this->getLengthOfNationalDestinationCode($number);
807 }
808
809 /**
810 * Returns the metadata for the given region code or {@code null} if the region code is invalid
811 * or unknown.
812 * @param string $regionCode
813 * @return PhoneMetadata
814 */
815 public function getMetadataForRegion($regionCode)
816 {
817 if (!$this->isValidRegionCode($regionCode)) {
818 return null;
819 }
820
821 return $this->metadataSource->getMetadataForRegion($regionCode);
822 }
823
824 /**
825 * Helper function to check region code is not unknown or null.
826 * @param string $regionCode
827 * @return bool
828 */
829 protected function isValidRegionCode($regionCode)
830 {
831 return $regionCode !== null && in_array($regionCode, $this->supportedRegions);
832 }
833
834 /**
835 * Returns the region where a phone number is from. This could be used for geocoding at the region
836 * level. Only guarantees correct results for valid, full numbers (not short-codes, or invalid
837 * numbers).
838 *
839 * @param PhoneNumber $number the phone number whose origin we want to know
840 * @return null|string the region where the phone number is from, or null if no region matches this calling
841 * code
842 */
843 public function getRegionCodeForNumber(PhoneNumber $number)
844 {
845 $countryCode = $number->getCountryCode();
846 if (!isset($this->countryCallingCodeToRegionCodeMap[$countryCode])) {
847 return null;
848 }
849 $regions = $this->countryCallingCodeToRegionCodeMap[$countryCode];
850 if (count($regions) == 1) {
851 return $regions[0];
852 }
853
854 return $this->getRegionCodeForNumberFromRegionList($number, $regions);
855 }
856
857 /**
858 * @param PhoneNumber $number
859 * @param array $regionCodes
860 * @return null|string
861 */
862 protected function getRegionCodeForNumberFromRegionList(PhoneNumber $number, array $regionCodes)
863 {
864 $nationalNumber = $this->getNationalSignificantNumber($number);
865 foreach ($regionCodes as $regionCode) {
866 // If leadingDigits is present, use this. Otherwise, do full validation.
867 // Metadata cannot be null because the region codes come from the country calling code map.
868 $metadata = $this->getMetadataForRegion($regionCode);
869 if ($metadata->hasLeadingDigits()) {
870 $nbMatches = preg_match(
871 '/' . $metadata->getLeadingDigits() . '/',
872 $nationalNumber,
873 $matches,
874 PREG_OFFSET_CAPTURE
875 );
876 if ($nbMatches > 0 && $matches[0][1] === 0) {
877 return $regionCode;
878 }
879 } elseif ($this->getNumberTypeHelper($nationalNumber, $metadata) != PhoneNumberType::UNKNOWN) {
880 return $regionCode;
881 }
882 }
883 return null;
884 }
885
886 /**
887 * Gets the national significant number of the a phone number. Note a national significant number
888 * doesn't contain a national prefix or any formatting.
889 *
890 * @param PhoneNumber $number the phone number for which the national significant number is needed
891 * @return string the national significant number of the PhoneNumber object passed in
892 */
893 public function getNationalSignificantNumber(PhoneNumber $number)
894 {
895 // If leading zero(s) have been set, we prefix this now. Note this is not a national prefix.
896 $nationalNumber = '';
897 if ($number->isItalianLeadingZero() && $number->getNumberOfLeadingZeros() > 0) {
898 $zeros = str_repeat('0', $number->getNumberOfLeadingZeros());
899 $nationalNumber .= $zeros;
900 }
901 $nationalNumber .= $number->getNationalNumber();
902 return $nationalNumber;
903 }
904
905 /**
906 * @param string $nationalNumber
907 * @param PhoneMetadata $metadata
908 * @return int PhoneNumberType constant
909 */
910 protected function getNumberTypeHelper($nationalNumber, PhoneMetadata $metadata)
911 {
912 if (!$this->isNumberMatchingDesc($nationalNumber, $metadata->getGeneralDesc())) {
913 return PhoneNumberType::UNKNOWN;
914 }
915 if ($this->isNumberMatchingDesc($nationalNumber, $metadata->getPremiumRate())) {
916 return PhoneNumberType::PREMIUM_RATE;
917 }
918 if ($this->isNumberMatchingDesc($nationalNumber, $metadata->getTollFree())) {
919 return PhoneNumberType::TOLL_FREE;
920 }
921
922
923 if ($this->isNumberMatchingDesc($nationalNumber, $metadata->getSharedCost())) {
924 return PhoneNumberType::SHARED_COST;
925 }
926 if ($this->isNumberMatchingDesc($nationalNumber, $metadata->getVoip())) {
927 return PhoneNumberType::VOIP;
928 }
929 if ($this->isNumberMatchingDesc($nationalNumber, $metadata->getPersonalNumber())) {
930 return PhoneNumberType::PERSONAL_NUMBER;
931 }
932 if ($this->isNumberMatchingDesc($nationalNumber, $metadata->getPager())) {
933 return PhoneNumberType::PAGER;
934 }
935 if ($this->isNumberMatchingDesc($nationalNumber, $metadata->getUan())) {
936 return PhoneNumberType::UAN;
937 }
938 if ($this->isNumberMatchingDesc($nationalNumber, $metadata->getVoicemail())) {
939 return PhoneNumberType::VOICEMAIL;
940 }
941 $isFixedLine = $this->isNumberMatchingDesc($nationalNumber, $metadata->getFixedLine());
942 if ($isFixedLine) {
943 if ($metadata->getSameMobileAndFixedLinePattern()) {
944 return PhoneNumberType::FIXED_LINE_OR_MOBILE;
945 }
946
947 if ($this->isNumberMatchingDesc($nationalNumber, $metadata->getMobile())) {
948 return PhoneNumberType::FIXED_LINE_OR_MOBILE;
949 }
950 return PhoneNumberType::FIXED_LINE;
951 }
952 // Otherwise, test to see if the number is mobile. Only do this if certain that the patterns for
953 // mobile and fixed line aren't the same.
954 if (!$metadata->getSameMobileAndFixedLinePattern() &&
955 $this->isNumberMatchingDesc($nationalNumber, $metadata->getMobile())
956 ) {
957 return PhoneNumberType::MOBILE;
958 }
959 return PhoneNumberType::UNKNOWN;
960 }
961
962 /**
963 * @param string $nationalNumber
964 * @param PhoneNumberDesc $numberDesc
965 * @return bool
966 */
967 public function isNumberMatchingDesc($nationalNumber, PhoneNumberDesc $numberDesc)
968 {
969 // Check if any possible number lengths are present; if so, we use them to avoid checking the
970 // validation pattern if they don't match. If they are absent, this means they match the general
971 // description, which we have already checked before checking a specific number type.
972 $actualLength = mb_strlen($nationalNumber);
973 $possibleLengths = $numberDesc->getPossibleLength();
974 if (count($possibleLengths) > 0 && !in_array($actualLength, $possibleLengths)) {
975 return false;
976 }
977
978 return $this->matcherAPI->matchNationalNumber($nationalNumber, $numberDesc, false);
979 }
980
981 /**
982 * isNumberGeographical(PhoneNumber)
983 *
984 * Tests whether a phone number has a geographical association. It checks if the number is
985 * associated with a certain region in the country to which it belongs. Note that this doesn't
986 * verify if the number is actually in use.
987 *
988 * isNumberGeographical(PhoneNumberType, $countryCallingCode)
989 *
990 * Tests whether a phone number has a geographical association, as represented by its type and the
991 * country it belongs to.
992 *
993 * This version exists since calculating the phone number type is expensive; if we have already
994 * done this, we don't want to do it again.
995 *
996 * @param PhoneNumber|int $phoneNumberObjOrType A PhoneNumber object, or a PhoneNumberType integer
997 * @param int|null $countryCallingCode Used when passing a PhoneNumberType
998 * @return bool
999 */
1000 public function isNumberGeographical($phoneNumberObjOrType, $countryCallingCode = null)
1001 {
1002 if ($phoneNumberObjOrType instanceof PhoneNumber) {
1003 return $this->isNumberGeographical($this->getNumberType($phoneNumberObjOrType), $phoneNumberObjOrType->getCountryCode());
1004 }
1005
1006 return $phoneNumberObjOrType == PhoneNumberType::FIXED_LINE
1007 || $phoneNumberObjOrType == PhoneNumberType::FIXED_LINE_OR_MOBILE
1008 || (in_array($countryCallingCode, static::$GEO_MOBILE_COUNTRIES)
1009 && $phoneNumberObjOrType == PhoneNumberType::MOBILE);
1010 }
1011
1012 /**
1013 * Gets the type of a valid phone number.
1014 * @param PhoneNumber $number the number the phone number that we want to know the type
1015 * @return int PhoneNumberType the type of the phone number, or UNKNOWN if it is invalid
1016 */
1017 public function getNumberType(PhoneNumber $number)
1018 {
1019 $regionCode = $this->getRegionCodeForNumber($number);
1020 $metadata = $this->getMetadataForRegionOrCallingCode($number->getCountryCode(), $regionCode);
1021 if ($metadata === null) {
1022 return PhoneNumberType::UNKNOWN;
1023 }
1024 $nationalSignificantNumber = $this->getNationalSignificantNumber($number);
1025 return $this->getNumberTypeHelper($nationalSignificantNumber, $metadata);
1026 }
1027
1028 /**
1029 * @param int $countryCallingCode
1030 * @param string $regionCode
1031 * @return PhoneMetadata
1032 */
1033 protected function getMetadataForRegionOrCallingCode($countryCallingCode, $regionCode)
1034 {
1035 return static::REGION_CODE_FOR_NON_GEO_ENTITY === $regionCode ?
1036 $this->getMetadataForNonGeographicalRegion($countryCallingCode) : $this->getMetadataForRegion($regionCode);
1037 }
1038
1039 /**
1040 * @param int $countryCallingCode
1041 * @return PhoneMetadata
1042 */
1043 public function getMetadataForNonGeographicalRegion($countryCallingCode)
1044 {
1045 if (!isset($this->countryCallingCodeToRegionCodeMap[$countryCallingCode])) {
1046 return null;
1047 }
1048 return $this->metadataSource->getMetadataForNonGeographicalRegion($countryCallingCode);
1049 }
1050
1051 /**
1052 * Gets the length of the national destination code (NDC) from the PhoneNumber object passed in,
1053 * so that clients could use it to split a national significant number into NDC and subscriber
1054 * number. The NDC of a phone number is normally the first group of digit(s) right after the
1055 * country calling code when the number is formatted in the international format, if there is a
1056 * subscriber number part that follows.
1057 *
1058 * follows.
1059 *
1060 * N.B.: similar to an area code, not all numbers have an NDC!
1061 *
1062 * An example of how this could be used:
1063 *
1064 * <code>
1065 * $phoneUtil = PhoneNumberUtil::getInstance();
1066 * $number = $phoneUtil->parse("18002530000", "US");
1067 * $nationalSignificantNumber = $phoneUtil->getNationalSignificantNumber($number);
1068 *
1069 * $nationalDestinationCodeLength = $phoneUtil->getLengthOfNationalDestinationCode($number);
1070 * if ($nationalDestinationCodeLength > 0) {
1071 * $nationalDestinationCode = substr($nationalSignificantNumber, 0, $nationalDestinationCodeLength);
1072 * $subscriberNumber = substr($nationalSignificantNumber, $nationalDestinationCodeLength);
1073 * } else {
1074 * $nationalDestinationCode = "";
1075 * $subscriberNumber = $nationalSignificantNumber;
1076 * }
1077 * </code>
1078 *
1079 * Refer to the unit tests to see the difference between this function and
1080 * {@link #getLengthOfGeographicalAreaCode}.
1081 *
1082 * @param PhoneNumber $number the PhoneNumber object for which clients want to know the length of the NDC.
1083 * @return int the length of NDC of the PhoneNumber object passed in, which could be zero
1084 */
1085 public function getLengthOfNationalDestinationCode(PhoneNumber $number)
1086 {
1087 if ($number->hasExtension()) {
1088 // We don't want to alter the proto given to us, but we don't want to include the extension
1089 // when we format it, so we copy it and clear the extension here.
1090 $copiedProto = new PhoneNumber();
1091 $copiedProto->mergeFrom($number);
1092 $copiedProto->clearExtension();
1093 } else {
1094 $copiedProto = clone $number;
1095 }
1096
1097 $nationalSignificantNumber = $this->format($copiedProto, PhoneNumberFormat::INTERNATIONAL);
1098
1099 $numberGroups = preg_split('/' . static::NON_DIGITS_PATTERN . '/', $nationalSignificantNumber);
1100
1101 // The pattern will start with "+COUNTRY_CODE " so the first group will always be the empty
1102 // string (before the + symbol) and the second group will be the country calling code. The third
1103 // group will be area code if it is not the last group.
1104 if (count($numberGroups) <= 3) {
1105 return 0;
1106 }
1107
1108 if ($this->getNumberType($number) == PhoneNumberType::MOBILE) {
1109 // For example Argentinian mobile numbers, when formatted in the international format, are in
1110 // the form of +54 9 NDC XXXX.... As a result, we take the length of the third group (NDC) and
1111 // add the length of the second group (which is the mobile token), which also forms part of
1112 // the national significant number. This assumes that the mobile token is always formatted
1113 // separately from the rest of the phone number.
1114
1115 $mobileToken = static::getCountryMobileToken($number->getCountryCode());
1116 if ($mobileToken !== '') {
1117 return mb_strlen($numberGroups[2]) + mb_strlen($numberGroups[3]);
1118 }
1119 }
1120 return mb_strlen($numberGroups[2]);
1121 }
1122
1123 /**
1124 * Formats a phone number in the specified format using default rules. Note that this does not
1125 * promise to produce a phone number that the user can dial from where they are - although we do
1126 * format in either 'national' or 'international' format depending on what the client asks for, we
1127 * do not currently support a more abbreviated format, such as for users in the same "area" who
1128 * could potentially dial the number without area code. Note that if the phone number has a
1129 * country calling code of 0 or an otherwise invalid country calling code, we cannot work out
1130 * which formatting rules to apply so we return the national significant number with no formatting
1131 * applied.
1132 *
1133 * @param PhoneNumber $number the phone number to be formatted
1134 * @param int $numberFormat the PhoneNumberFormat the phone number should be formatted into
1135 * @return string the formatted phone number
1136 */
1137 public function format(PhoneNumber $number, $numberFormat)
1138 {
1139 if ($number->getNationalNumber() == 0 && $number->hasRawInput()) {
1140 // Unparseable numbers that kept their raw input just use that.
1141 // This is the only case where a number can be formatted as E164 without a
1142 // leading '+' symbol (but the original number wasn't parseable anyway).
1143 // TODO: Consider removing the 'if' above so that unparseable
1144 // strings without raw input format to the empty string instead of "+00"
1145 $rawInput = $number->getRawInput();
1146 if (mb_strlen($rawInput) > 0) {
1147 return $rawInput;
1148 }
1149 }
1150
1151 $formattedNumber = '';
1152 $countryCallingCode = $number->getCountryCode();
1153 $nationalSignificantNumber = $this->getNationalSignificantNumber($number);
1154
1155 if ($numberFormat == PhoneNumberFormat::E164) {
1156 // Early exit for E164 case (even if the country calling code is invalid) since no formatting
1157 // of the national number needs to be applied. Extensions are not formatted.
1158 $formattedNumber .= $nationalSignificantNumber;
1159 $this->prefixNumberWithCountryCallingCode($countryCallingCode, PhoneNumberFormat::E164, $formattedNumber);
1160 return $formattedNumber;
1161 }
1162
1163 if (!$this->hasValidCountryCallingCode($countryCallingCode)) {
1164 $formattedNumber .= $nationalSignificantNumber;
1165 return $formattedNumber;
1166 }
1167
1168 // Note getRegionCodeForCountryCode() is used because formatting information for regions which
1169 // share a country calling code is contained by only one region for performance reasons. For
1170 // example, for NANPA regions it will be contained in the metadata for US.
1171 $regionCode = $this->getRegionCodeForCountryCode($countryCallingCode);
1172 // Metadata cannot be null because the country calling code is valid (which means that the
1173 // region code cannot be ZZ and must be one of our supported region codes).
1174 $metadata = $this->getMetadataForRegionOrCallingCode($countryCallingCode, $regionCode);
1175 $formattedNumber .= $this->formatNsn($nationalSignificantNumber, $metadata, $numberFormat);
1176 $this->maybeAppendFormattedExtension($number, $metadata, $numberFormat, $formattedNumber);
1177 $this->prefixNumberWithCountryCallingCode($countryCallingCode, $numberFormat, $formattedNumber);
1178 return $formattedNumber;
1179 }
1180
1181 /**
1182 * A helper function that is used by format and formatByPattern.
1183 * @param int $countryCallingCode
1184 * @param int $numberFormat PhoneNumberFormat
1185 * @param string $formattedNumber
1186 */
1187 protected function prefixNumberWithCountryCallingCode($countryCallingCode, $numberFormat, &$formattedNumber)
1188 {
1189 switch ($numberFormat) {
1190 case PhoneNumberFormat::E164:
1191 $formattedNumber = static::PLUS_SIGN . $countryCallingCode . $formattedNumber;
1192 return;
1193 case PhoneNumberFormat::INTERNATIONAL:
1194 $formattedNumber = static::PLUS_SIGN . $countryCallingCode . ' ' . $formattedNumber;
1195 return;
1196 case PhoneNumberFormat::RFC3966:
1197 $formattedNumber = static::RFC3966_PREFIX . static::PLUS_SIGN . $countryCallingCode . '-' . $formattedNumber;
1198 return;
1199 case PhoneNumberFormat::NATIONAL:
1200 default:
1201 return;
1202 }
1203 }
1204
1205 /**
1206 * Helper function to check the country calling code is valid.
1207 * @param int $countryCallingCode
1208 * @return bool
1209 */
1210 protected function hasValidCountryCallingCode($countryCallingCode)
1211 {
1212 return isset($this->countryCallingCodeToRegionCodeMap[$countryCallingCode]);
1213 }
1214
1215 /**
1216 * Returns the region code that matches the specific country calling code. In the case of no
1217 * region code being found, ZZ will be returned. In the case of multiple regions, the one
1218 * designated in the metadata as the "main" region for this calling code will be returned. If the
1219 * countryCallingCode entered is valid but doesn't match a specific region (such as in the case of
1220 * non-geographical calling codes like 800) the value "001" will be returned (corresponding to
1221 * the value for World in the UN M.49 schema).
1222 *
1223 * @param int $countryCallingCode
1224 * @return string
1225 */
1226 public function getRegionCodeForCountryCode($countryCallingCode)
1227 {
1228 $regionCodes = isset($this->countryCallingCodeToRegionCodeMap[$countryCallingCode]) ? $this->countryCallingCodeToRegionCodeMap[$countryCallingCode] : null;
1229 return $regionCodes === null ? static::UNKNOWN_REGION : $regionCodes[0];
1230 }
1231
1232 /**
1233 * Note in some regions, the national number can be written in two completely different ways
1234 * depending on whether it forms part of the NATIONAL format or INTERNATIONAL format. The
1235 * numberFormat parameter here is used to specify which format to use for those cases. If a
1236 * carrierCode is specified, this will be inserted into the formatted string to replace $CC.
1237 * @param string $number
1238 * @param PhoneMetadata $metadata
1239 * @param int $numberFormat PhoneNumberFormat
1240 * @param null|string $carrierCode
1241 * @return string
1242 */
1243 protected function formatNsn($number, PhoneMetadata $metadata, $numberFormat, $carrierCode = null)
1244 {
1245 $intlNumberFormats = $metadata->intlNumberFormats();
1246 // When the intlNumberFormats exists, we use that to format national number for the
1247 // INTERNATIONAL format instead of using the numberDesc.numberFormats.
1248 $availableFormats = (count($intlNumberFormats) == 0 || $numberFormat == PhoneNumberFormat::NATIONAL)
1249 ? $metadata->numberFormats()
1250 : $metadata->intlNumberFormats();
1251 $formattingPattern = $this->chooseFormattingPatternForNumber($availableFormats, $number);
1252 return ($formattingPattern === null)
1253 ? $number
1254 : $this->formatNsnUsingPattern($number, $formattingPattern, $numberFormat, $carrierCode);
1255 }
1256
1257 /**
1258 * @param NumberFormat[] $availableFormats
1259 * @param string $nationalNumber
1260 * @return NumberFormat|null
1261 */
1262 public function chooseFormattingPatternForNumber(array $availableFormats, $nationalNumber)
1263 {
1264 foreach ($availableFormats as $numFormat) {
1265 $leadingDigitsPatternMatcher = null;
1266 $size = $numFormat->leadingDigitsPatternSize();
1267 // We always use the last leading_digits_pattern, as it is the most detailed.
1268 if ($size > 0) {
1269 $leadingDigitsPatternMatcher = new Matcher(
1270 $numFormat->getLeadingDigitsPattern($size - 1),
1271 $nationalNumber
1272 );
1273 }
1274 if ($size == 0 || $leadingDigitsPatternMatcher->lookingAt()) {
1275 $m = new Matcher($numFormat->getPattern(), $nationalNumber);
1276 if ($m->matches() > 0) {
1277 return $numFormat;
1278 }
1279 }
1280 }
1281 return null;
1282 }
1283
1284 /**
1285 * Note that carrierCode is optional - if null or an empty string, no carrier code replacement
1286 * will take place.
1287 * @param string $nationalNumber
1288 * @param NumberFormat $formattingPattern
1289 * @param int $numberFormat PhoneNumberFormat
1290 * @param null|string $carrierCode
1291 * @return string
1292 */
1293 public function formatNsnUsingPattern(
1294 $nationalNumber,
1295 NumberFormat $formattingPattern,
1296 $numberFormat,
1297 $carrierCode = null
1298 ) {
1299 $numberFormatRule = $formattingPattern->getFormat();
1300 $m = new Matcher($formattingPattern->getPattern(), $nationalNumber);
1301 if ($numberFormat === PhoneNumberFormat::NATIONAL &&
1302 $carrierCode !== null && mb_strlen($carrierCode) > 0 &&
1303 mb_strlen($formattingPattern->getDomesticCarrierCodeFormattingRule()) > 0
1304 ) {
1305 // Replace the $CC in the formatting rule with the desired carrier code.
1306 $carrierCodeFormattingRule = $formattingPattern->getDomesticCarrierCodeFormattingRule();
1307 $carrierCodeFormattingRule = str_replace(static::CC_STRING, $carrierCode, $carrierCodeFormattingRule);
1308 // Now replace the $FG in the formatting rule with the first group and the carrier code
1309 // combined in the appropriate way.
1310 $firstGroupMatcher = new Matcher(static::FIRST_GROUP_PATTERN, $numberFormatRule);
1311 $numberFormatRule = $firstGroupMatcher->replaceFirst($carrierCodeFormattingRule);
1312 $formattedNationalNumber = $m->replaceAll($numberFormatRule);
1313 } else {
1314 // Use the national prefix formatting rule instead.
1315 $nationalPrefixFormattingRule = $formattingPattern->getNationalPrefixFormattingRule();
1316 if ($numberFormat == PhoneNumberFormat::NATIONAL &&
1317 $nationalPrefixFormattingRule !== null &&
1318 mb_strlen($nationalPrefixFormattingRule) > 0
1319 ) {
1320 $firstGroupMatcher = new Matcher(static::FIRST_GROUP_PATTERN, $numberFormatRule);
1321 $formattedNationalNumber = $m->replaceAll(
1322 $firstGroupMatcher->replaceFirst($nationalPrefixFormattingRule)
1323 );
1324 } else {
1325 $formattedNationalNumber = $m->replaceAll($numberFormatRule);
1326 }
1327 }
1328 if ($numberFormat == PhoneNumberFormat::RFC3966) {
1329 // Strip any leading punctuation.
1330 $matcher = new Matcher(static::$SEPARATOR_PATTERN, $formattedNationalNumber);
1331 if ($matcher->lookingAt()) {
1332 $formattedNationalNumber = $matcher->replaceFirst('');
1333 }
1334 // Replace the rest with a dash between each number group.
1335 $formattedNationalNumber = $matcher->reset($formattedNationalNumber)->replaceAll('-');
1336 }
1337 return $formattedNationalNumber;
1338 }
1339
1340 /**
1341 * Appends the formatted extension of a phone number to formattedNumber, if the phone number had
1342 * an extension specified.
1343 *
1344 * @param PhoneNumber $number
1345 * @param PhoneMetadata|null $metadata
1346 * @param int $numberFormat PhoneNumberFormat
1347 * @param string $formattedNumber
1348 */
1349 protected function maybeAppendFormattedExtension(PhoneNumber $number, $metadata, $numberFormat, &$formattedNumber)
1350 {
1351 if ($number->hasExtension() && mb_strlen($number->getExtension()) > 0) {
1352 if ($numberFormat === PhoneNumberFormat::RFC3966) {
1353 $formattedNumber .= static::RFC3966_EXTN_PREFIX . $number->getExtension();
1354 } elseif (!empty($metadata) && $metadata->hasPreferredExtnPrefix()) {
1355 $formattedNumber .= $metadata->getPreferredExtnPrefix() . $number->getExtension();
1356 } else {
1357 $formattedNumber .= static::DEFAULT_EXTN_PREFIX . $number->getExtension();
1358 }
1359 }
1360 }
1361
1362 /**
1363 * Returns the mobile token for the provided country calling code if it has one, otherwise
1364 * returns an empty string. A mobile token is a number inserted before the area code when dialing
1365 * a mobile number from that country from abroad.
1366 *
1367 * @param int $countryCallingCode the country calling code for which we want the mobile token
1368 * @return string the mobile token, as a string, for the given country calling code
1369 */
1370 public static function getCountryMobileToken($countryCallingCode)
1371 {
1372 if (count(static::$MOBILE_TOKEN_MAPPINGS) === 0) {
1373 static::initMobileTokenMappings();
1374 }
1375
1376 if (array_key_exists($countryCallingCode, static::$MOBILE_TOKEN_MAPPINGS)) {
1377 return static::$MOBILE_TOKEN_MAPPINGS[$countryCallingCode];
1378 }
1379 return '';
1380 }
1381
1382 /**
1383 * Checks if the number is a valid vanity (alpha) number such as 800 MICROSOFT. A valid vanity
1384 * number will start with at least 3 digits and will have three or more alpha characters. This
1385 * does not do region-specific checks - to work out if this number is actually valid for a region,
1386 * it should be parsed and methods such as {@link #isPossibleNumberWithReason} and
1387 * {@link #isValidNumber} should be used.
1388 *
1389 * @param string $number the number that needs to be checked
1390 * @return bool true if the number is a valid vanity number
1391 */
1392 public function isAlphaNumber($number)
1393 {
1394 if (!static::isViablePhoneNumber($number)) {
1395 // Number is too short, or doesn't match the basic phone number pattern.
1396 return false;
1397 }
1398 $this->maybeStripExtension($number);
1399 return (bool)preg_match('/' . static::VALID_ALPHA_PHONE_PATTERN . '/' . static::REGEX_FLAGS, $number);
1400 }
1401
1402 /**
1403 * Checks to see if the string of characters could possibly be a phone number at all. At the
1404 * moment, checks to see that the string begins with at least 2 digits, ignoring any punctuation
1405 * commonly found in phone numbers.
1406 * This method does not require the number to be normalized in advance - but does assume that
1407 * leading non-number symbols have been removed, such as by the method extractPossibleNumber.
1408 *
1409 * @param string $number to be checked for viability as a phone number
1410 * @return boolean true if the number could be a phone number of some sort, otherwise false
1411 */
1412 public static function isViablePhoneNumber($number)
1413 {
1414 if (static::$VALID_PHONE_NUMBER_PATTERN === null) {
1415 static::initValidPhoneNumberPatterns();
1416 }
1417
1418 if (mb_strlen($number) < static::MIN_LENGTH_FOR_NSN) {
1419 return false;
1420 }
1421
1422 $validPhoneNumberPattern = static::getValidPhoneNumberPattern();
1423
1424 $m = preg_match($validPhoneNumberPattern, $number);
1425 return $m > 0;
1426 }
1427
1428 /**
1429 * We append optionally the extension pattern to the end here, as a valid phone number may
1430 * have an extension prefix appended, followed by 1 or more digits.
1431 * @return string
1432 */
1433 protected static function getValidPhoneNumberPattern()
1434 {
1435 return static::$VALID_PHONE_NUMBER_PATTERN;
1436 }
1437
1438 /**
1439 * Strips any extension (as in, the part of the number dialled after the call is connected,
1440 * usually indicated with extn, ext, x or similar) from the end of the number, and returns it.
1441 *
1442 * @param string $number the non-normalized telephone number that we wish to strip the extension from
1443 * @return string the phone extension
1444 */
1445 protected function maybeStripExtension(&$number)
1446 {
1447 $matches = array();
1448 $find = preg_match(static::$EXTN_PATTERN, $number, $matches, PREG_OFFSET_CAPTURE);
1449 // If we find a potential extension, and the number preceding this is a viable number, we assume
1450 // it is an extension.
1451 if ($find > 0 && static::isViablePhoneNumber(substr($number, 0, $matches[0][1]))) {
1452 // The numbers are captured into groups in the regular expression.
1453
1454 for ($i = 1, $length = count($matches); $i <= $length; $i++) {
1455 if ($matches[$i][0] != '') {
1456 // We go through the capturing groups until we find one that captured some digits. If none
1457 // did, then we will return the empty string.
1458 $extension = $matches[$i][0];
1459 $number = substr($number, 0, $matches[0][1]);
1460 return $extension;
1461 }
1462 }
1463 }
1464 return '';
1465 }
1466
1467 /**
1468 * Parses a string and returns it in proto buffer format. This method differs from {@link #parse}
1469 * in that it always populates the raw_input field of the protocol buffer with numberToParse as
1470 * well as the country_code_source field.
1471 *
1472 * @param string $numberToParse number that we are attempting to parse. This can contain formatting
1473 * such as +, ( and -, as well as a phone number extension. It can also
1474 * be provided in RFC3966 format.
1475 * @param string $defaultRegion region that we are expecting the number to be from. This is only used
1476 * if the number being parsed is not written in international format.
1477 * The country calling code for the number in this case would be stored
1478 * as that of the default region supplied.
1479 * @param PhoneNumber $phoneNumber
1480 * @return PhoneNumber a phone number proto buffer filled with the parsed number
1481 */
1482 public function parseAndKeepRawInput($numberToParse, $defaultRegion, PhoneNumber $phoneNumber = null)
1483 {
1484 if ($phoneNumber === null) {
1485 $phoneNumber = new PhoneNumber();
1486 }
1487 $this->parseHelper($numberToParse, $defaultRegion, true, true, $phoneNumber);
1488 return $phoneNumber;
1489 }
1490
1491 /**
1492 * Returns an iterable over all PhoneNumberMatches in $text
1493 *
1494 * @param string $text
1495 * @param string $defaultRegion
1496 * @param AbstractLeniency $leniency Defaults to Leniency::VALID()
1497 * @param int $maxTries Defaults to PHP_INT_MAX
1498 * @return PhoneNumberMatcher
1499 */
1500 public function findNumbers($text, $defaultRegion, AbstractLeniency $leniency = null, $maxTries = PHP_INT_MAX)
1501 {
1502 if ($leniency === null) {
1503 $leniency = Leniency::VALID();
1504 }
1505
1506 return new PhoneNumberMatcher($this, $text, $defaultRegion, $leniency, $maxTries);
1507 }
1508
1509 /**
1510 * Gets an AsYouTypeFormatter for the specific region.
1511 *
1512 * @param string $regionCode The region where the phone number is being entered.
1513 * @return AsYouTypeFormatter
1514 */
1515 public function getAsYouTypeFormatter($regionCode)
1516 {
1517 return new AsYouTypeFormatter($regionCode);
1518 }
1519
1520 /**
1521 * A helper function to set the values related to leading zeros in a PhoneNumber.
1522 * @param string $nationalNumber
1523 * @param PhoneNumber $phoneNumber
1524 */
1525 public static function setItalianLeadingZerosForPhoneNumber($nationalNumber, PhoneNumber $phoneNumber)
1526 {
1527 if (strlen($nationalNumber) > 1 && substr($nationalNumber, 0, 1) == '0') {
1528 $phoneNumber->setItalianLeadingZero(true);
1529 $numberOfLeadingZeros = 1;
1530 // Note that if the national number is all "0"s, the last "0" is not counted as a leading
1531 // zero.
1532 while ($numberOfLeadingZeros < (strlen($nationalNumber) - 1) &&
1533 substr($nationalNumber, $numberOfLeadingZeros, 1) == '0') {
1534 $numberOfLeadingZeros++;
1535 }
1536
1537 if ($numberOfLeadingZeros != 1) {
1538 $phoneNumber->setNumberOfLeadingZeros($numberOfLeadingZeros);
1539 }
1540 }
1541 }
1542
1543 /**
1544 * Parses a string and fills up the phoneNumber. This method is the same as the public
1545 * parse() method, with the exception that it allows the default region to be null, for use by
1546 * isNumberMatch(). checkRegion should be set to false if it is permitted for the default region
1547 * to be null or unknown ("ZZ").
1548 * @param string $numberToParse
1549 * @param string $defaultRegion
1550 * @param bool $keepRawInput
1551 * @param bool $checkRegion
1552 * @param PhoneNumber $phoneNumber
1553 * @throws NumberParseException
1554 */
1555 protected function parseHelper($numberToParse, $defaultRegion, $keepRawInput, $checkRegion, PhoneNumber $phoneNumber)
1556 {
1557 if ($numberToParse === null) {
1558 throw new NumberParseException(NumberParseException::NOT_A_NUMBER, 'The phone number supplied was null.');
1559 }
1560
1561 $numberToParse = trim($numberToParse);
1562
1563 if (mb_strlen($numberToParse) > static::MAX_INPUT_STRING_LENGTH) {
1564 throw new NumberParseException(
1565 NumberParseException::TOO_LONG,
1566 'The string supplied was too long to parse.'
1567 );
1568 }
1569
1570 $nationalNumber = '';
1571 $this->buildNationalNumberForParsing($numberToParse, $nationalNumber);
1572
1573 if (!static::isViablePhoneNumber($nationalNumber)) {
1574 throw new NumberParseException(
1575 NumberParseException::NOT_A_NUMBER,
1576 'The string supplied did not seem to be a phone number.'
1577 );
1578 }
1579
1580 // Check the region supplied is valid, or that the extracted number starts with some sort of +
1581 // sign so the number's region can be determined.
1582 if ($checkRegion && !$this->checkRegionForParsing($nationalNumber, $defaultRegion)) {
1583 throw new NumberParseException(
1584 NumberParseException::INVALID_COUNTRY_CODE,
1585 'Missing or invalid default region.'
1586 );
1587 }
1588
1589 if ($keepRawInput) {
1590 $phoneNumber->setRawInput($numberToParse);
1591 }
1592 // Attempt to parse extension first, since it doesn't require region-specific data and we want
1593 // to have the non-normalised number here.
1594 $extension = $this->maybeStripExtension($nationalNumber);
1595 if (mb_strlen($extension) > 0) {
1596 $phoneNumber->setExtension($extension);
1597 }
1598
1599 $regionMetadata = $this->getMetadataForRegion($defaultRegion);
1600 // Check to see if the number is given in international format so we know whether this number is
1601 // from the default region or not.
1602 $normalizedNationalNumber = '';
1603 try {
1604 // TODO: This method should really just take in the string buffer that has already
1605 // been created, and just remove the prefix, rather than taking in a string and then
1606 // outputting a string buffer.
1607 $countryCode = $this->maybeExtractCountryCode(
1608 $nationalNumber,
1609 $regionMetadata,
1610 $normalizedNationalNumber,
1611 $keepRawInput,
1612 $phoneNumber
1613 );
1614 } catch (NumberParseException $e) {
1615 $matcher = new Matcher(static::$PLUS_CHARS_PATTERN, $nationalNumber);
1616 if ($e->getErrorType() == NumberParseException::INVALID_COUNTRY_CODE && $matcher->lookingAt()) {
1617 // Strip the plus-char, and try again.
1618 $countryCode = $this->maybeExtractCountryCode(
1619 substr($nationalNumber, $matcher->end()),
1620 $regionMetadata,
1621 $normalizedNationalNumber,
1622 $keepRawInput,
1623 $phoneNumber
1624 );
1625 if ($countryCode == 0) {
1626 throw new NumberParseException(
1627 NumberParseException::INVALID_COUNTRY_CODE,
1628 'Could not interpret numbers after plus-sign.'
1629 );
1630 }
1631 } else {
1632 throw new NumberParseException($e->getErrorType(), $e->getMessage(), $e);
1633 }
1634 }
1635 if ($countryCode !== 0) {
1636 $phoneNumberRegion = $this->getRegionCodeForCountryCode($countryCode);
1637 if ($phoneNumberRegion != $defaultRegion) {
1638 // Metadata cannot be null because the country calling code is valid.
1639 $regionMetadata = $this->getMetadataForRegionOrCallingCode($countryCode, $phoneNumberRegion);
1640 }
1641 } else {
1642 // If no extracted country calling code, use the region supplied instead. The national number
1643 // is just the normalized version of the number we were given to parse.
1644
1645 $normalizedNationalNumber .= static::normalize($nationalNumber);
1646 if ($defaultRegion !== null) {
1647 $countryCode = $regionMetadata->getCountryCode();
1648 $phoneNumber->setCountryCode($countryCode);
1649 } elseif ($keepRawInput) {
1650 $phoneNumber->clearCountryCodeSource();
1651 }
1652 }
1653 if (mb_strlen($normalizedNationalNumber) < static::MIN_LENGTH_FOR_NSN) {
1654 throw new NumberParseException(
1655 NumberParseException::TOO_SHORT_NSN,
1656 'The string supplied is too short to be a phone number.'
1657 );
1658 }
1659 if ($regionMetadata !== null) {
1660 $carrierCode = '';
1661 $potentialNationalNumber = $normalizedNationalNumber;
1662 $this->maybeStripNationalPrefixAndCarrierCode($potentialNationalNumber, $regionMetadata, $carrierCode);
1663 // We require that the NSN remaining after stripping the national prefix and carrier code be
1664 // long enough to be a possible length for the region. Otherwise, we don't do the stripping,
1665 // since the original number could be a valid short number.
1666 $validationResult = $this->testNumberLength($potentialNationalNumber, $regionMetadata);
1667 if ($validationResult !== ValidationResult::TOO_SHORT
1668 && $validationResult !== ValidationResult::IS_POSSIBLE_LOCAL_ONLY
1669 && $validationResult !== ValidationResult::INVALID_LENGTH) {
1670 $normalizedNationalNumber = $potentialNationalNumber;
1671 if ($keepRawInput && mb_strlen($carrierCode) > 0) {
1672 $phoneNumber->setPreferredDomesticCarrierCode($carrierCode);
1673 }
1674 }
1675 }
1676 $lengthOfNationalNumber = mb_strlen($normalizedNationalNumber);
1677 if ($lengthOfNationalNumber < static::MIN_LENGTH_FOR_NSN) {
1678 throw new NumberParseException(
1679 NumberParseException::TOO_SHORT_NSN,
1680 'The string supplied is too short to be a phone number.'
1681 );
1682 }
1683 if ($lengthOfNationalNumber > static::MAX_LENGTH_FOR_NSN) {
1684 throw new NumberParseException(
1685 NumberParseException::TOO_LONG,
1686 'The string supplied is too long to be a phone number.'
1687 );
1688 }
1689 static::setItalianLeadingZerosForPhoneNumber($normalizedNationalNumber, $phoneNumber);
1690
1691 /*
1692 * We have to store the National Number as a string instead of a "long" as Google do
1693 *
1694 * Since PHP doesn't always support 64 bit INTs, this was a float, but that had issues
1695 * with long numbers.
1696 *
1697 * We have to remove the leading zeroes ourself though
1698 */
1699 if ((int)$normalizedNationalNumber == 0) {
1700 $normalizedNationalNumber = '0';
1701 } else {
1702 $normalizedNationalNumber = ltrim($normalizedNationalNumber, '0');
1703 }
1704
1705 $phoneNumber->setNationalNumber($normalizedNationalNumber);
1706 }
1707
1708 /**
1709 * Returns a new phone number containing only the fields needed to uniquely identify a phone
1710 * number, rather than any fields that capture the context in which the phone number was created.
1711 * These fields correspond to those set in parse() rather than parseAndKeepRawInput()
1712 *
1713 * @param PhoneNumber $phoneNumberIn
1714 * @return PhoneNumber
1715 */
1716 protected static function copyCoreFieldsOnly(PhoneNumber $phoneNumberIn)
1717 {
1718 $phoneNumber = new PhoneNumber();
1719 $phoneNumber->setCountryCode($phoneNumberIn->getCountryCode());
1720 $phoneNumber->setNationalNumber($phoneNumberIn->getNationalNumber());
1721 if (mb_strlen($phoneNumberIn->getExtension()) > 0) {
1722 $phoneNumber->setExtension($phoneNumberIn->getExtension());
1723 }
1724 if ($phoneNumberIn->isItalianLeadingZero()) {
1725 $phoneNumber->setItalianLeadingZero(true);
1726 // This field is only relevant if there are leading zeros at all.
1727 $phoneNumber->setNumberOfLeadingZeros($phoneNumberIn->getNumberOfLeadingZeros());
1728 }
1729 return $phoneNumber;
1730 }
1731
1732 /**
1733 * Converts numberToParse to a form that we can parse and write it to nationalNumber if it is
1734 * written in RFC3966; otherwise extract a possible number out of it and write to nationalNumber.
1735 * @param string $numberToParse
1736 * @param string $nationalNumber
1737 */
1738 protected function buildNationalNumberForParsing($numberToParse, &$nationalNumber)
1739 {
1740 $indexOfPhoneContext = strpos($numberToParse, static::RFC3966_PHONE_CONTEXT);
1741 if ($indexOfPhoneContext !== false) {
1742 $phoneContextStart = $indexOfPhoneContext + mb_strlen(static::RFC3966_PHONE_CONTEXT);
1743 // If the phone context contains a phone number prefix, we need to capture it, whereas domains
1744 // will be ignored.
1745 if ($phoneContextStart < (strlen($numberToParse) - 1)
1746 && substr($numberToParse, $phoneContextStart, 1) == static::PLUS_SIGN) {
1747 // Additional parameters might follow the phone context. If so, we will remove them here
1748 // because the parameters after phone context are not important for parsing the
1749 // phone number.
1750 $phoneContextEnd = strpos($numberToParse, ';', $phoneContextStart);
1751 if ($phoneContextEnd > 0) {
1752 $nationalNumber .= substr($numberToParse, $phoneContextStart, $phoneContextEnd - $phoneContextStart);
1753 } else {
1754 $nationalNumber .= substr($numberToParse, $phoneContextStart);
1755 }
1756 }
1757
1758 // Now append everything between the "tel:" prefix and the phone-context. This should include
1759 // the national number, an optional extension or isdn-subaddress component. Note we also
1760 // handle the case when "tel:" is missing, as we have seen in some of the phone number inputs.
1761 // In that case, we append everything from the beginning.
1762
1763 $indexOfRfc3966Prefix = strpos($numberToParse, static::RFC3966_PREFIX);
1764 $indexOfNationalNumber = ($indexOfRfc3966Prefix !== false) ? $indexOfRfc3966Prefix + strlen(static::RFC3966_PREFIX) : 0;
1765 $nationalNumber .= substr($numberToParse, $indexOfNationalNumber,
1766 $indexOfPhoneContext - $indexOfNationalNumber);
1767 } else {
1768 // Extract a possible number from the string passed in (this strips leading characters that
1769 // could not be the start of a phone number.)
1770 $nationalNumber .= static::extractPossibleNumber($numberToParse);
1771 }
1772
1773 // Delete the isdn-subaddress and everything after it if it is present. Note extension won't
1774 // appear at the same time with isdn-subaddress according to paragraph 5.3 of the RFC3966 spec,
1775 $indexOfIsdn = strpos($nationalNumber, static::RFC3966_ISDN_SUBADDRESS);
1776 if ($indexOfIsdn > 0) {
1777 $nationalNumber = substr($nationalNumber, 0, $indexOfIsdn);
1778 }
1779 // If both phone context and isdn-subaddress are absent but other parameters are present, the
1780 // parameters are left in nationalNumber. This is because we are concerned about deleting
1781 // content from a potential number string when there is no strong evidence that the number is
1782 // actually written in RFC3966.
1783 }
1784
1785 /**
1786 * Attempts to extract a possible number from the string passed in. This currently strips all
1787 * leading characters that cannot be used to start a phone number. Characters that can be used to
1788 * start a phone number are defined in the VALID_START_CHAR_PATTERN. If none of these characters
1789 * are found in the number passed in, an empty string is returned. This function also attempts to
1790 * strip off any alternative extensions or endings if two or more are present, such as in the case
1791 * of: (530) 583-6985 x302/x2303. The second extension here makes this actually two phone numbers,
1792 * (530) 583-6985 x302 and (530) 583-6985 x2303. We remove the second extension so that the first
1793 * number is parsed correctly.
1794 *
1795 * @param int $number the string that might contain a phone number
1796 * @return string the number, stripped of any non-phone-number prefix (such as "Tel:") or an empty
1797 * string if no character used to start phone numbers (such as + or any digit) is
1798 * found in the number
1799 */
1800 public static function extractPossibleNumber($number)
1801 {
1802 if (static::$VALID_START_CHAR_PATTERN === null) {
1803 static::initValidStartCharPattern();
1804 }
1805
1806 $matches = array();
1807 $match = preg_match('/' . static::$VALID_START_CHAR_PATTERN . '/ui', $number, $matches, PREG_OFFSET_CAPTURE);
1808 if ($match > 0) {
1809 $number = substr($number, $matches[0][1]);
1810 // Remove trailing non-alpha non-numerical characters.
1811 $trailingCharsMatcher = new Matcher(static::$UNWANTED_END_CHAR_PATTERN, $number);
1812 if ($trailingCharsMatcher->find() && $trailingCharsMatcher->start() > 0) {
1813 $number = substr($number, 0, $trailingCharsMatcher->start());
1814 }
1815
1816 // Check for extra numbers at the end.
1817 $match = preg_match('%' . static::$SECOND_NUMBER_START_PATTERN . '%', $number, $matches, PREG_OFFSET_CAPTURE);
1818 if ($match > 0) {
1819 $number = substr($number, 0, $matches[0][1]);
1820 }
1821
1822 return $number;
1823 }
1824
1825 return '';
1826 }
1827
1828 /**
1829 * Checks to see that the region code used is valid, or if it is not valid, that the number to
1830 * parse starts with a + symbol so that we can attempt to infer the region from the number.
1831 * Returns false if it cannot use the region provided and the region cannot be inferred.
1832 * @param string $numberToParse
1833 * @param string $defaultRegion
1834 * @return bool
1835 */
1836 protected function checkRegionForParsing($numberToParse, $defaultRegion)
1837 {
1838 if (!$this->isValidRegionCode($defaultRegion)) {
1839 // If the number is null or empty, we can't infer the region.
1840 $plusCharsPatternMatcher = new Matcher(static::$PLUS_CHARS_PATTERN, $numberToParse);
1841 if ($numberToParse === null || mb_strlen($numberToParse) == 0 || !$plusCharsPatternMatcher->lookingAt()) {
1842 return false;
1843 }
1844 }
1845 return true;
1846 }
1847
1848 /**
1849 * Tries to extract a country calling code from a number. This method will return zero if no
1850 * country calling code is considered to be present. Country calling codes are extracted in the
1851 * following ways:
1852 * <ul>
1853 * <li> by stripping the international dialing prefix of the region the person is dialing from,
1854 * if this is present in the number, and looking at the next digits
1855 * <li> by stripping the '+' sign if present and then looking at the next digits
1856 * <li> by comparing the start of the number and the country calling code of the default region.
1857 * If the number is not considered possible for the numbering plan of the default region
1858 * initially, but starts with the country calling code of this region, validation will be
1859 * reattempted after stripping this country calling code. If this number is considered a
1860 * possible number, then the first digits will be considered the country calling code and
1861 * removed as such.
1862 * </ul>
1863 * It will throw a NumberParseException if the number starts with a '+' but the country calling
1864 * code supplied after this does not match that of any known region.
1865 *
1866 * @param string $number non-normalized telephone number that we wish to extract a country calling
1867 * code from - may begin with '+'
1868 * @param PhoneMetadata $defaultRegionMetadata metadata about the region this number may be from
1869 * @param string $nationalNumber a string buffer to store the national significant number in, in the case
1870 * that a country calling code was extracted. The number is appended to any existing contents.
1871 * If no country calling code was extracted, this will be left unchanged.
1872 * @param bool $keepRawInput true if the country_code_source and preferred_carrier_code fields of
1873 * phoneNumber should be populated.
1874 * @param PhoneNumber $phoneNumber the PhoneNumber object where the country_code and country_code_source need
1875 * to be populated. Note the country_code is always populated, whereas country_code_source is
1876 * only populated when keepCountryCodeSource is true.
1877 * @return int the country calling code extracted or 0 if none could be extracted
1878 * @throws NumberParseException
1879 */
1880 public function maybeExtractCountryCode(
1881 $number,
1882 PhoneMetadata $defaultRegionMetadata = null,
1883 &$nationalNumber,
1884 $keepRawInput,
1885 PhoneNumber $phoneNumber
1886 ) {
1887 if (mb_strlen($number) == 0) {
1888 return 0;
1889 }
1890 $fullNumber = $number;
1891 // Set the default prefix to be something that will never match.
1892 $possibleCountryIddPrefix = 'NonMatch';
1893 if ($defaultRegionMetadata !== null) {
1894 $possibleCountryIddPrefix = $defaultRegionMetadata->getInternationalPrefix();
1895 }
1896 $countryCodeSource = $this->maybeStripInternationalPrefixAndNormalize($fullNumber, $possibleCountryIddPrefix);
1897
1898 if ($keepRawInput) {
1899 $phoneNumber->setCountryCodeSource($countryCodeSource);
1900 }
1901 if ($countryCodeSource != CountryCodeSource::FROM_DEFAULT_COUNTRY) {
1902 if (mb_strlen($fullNumber) <= static::MIN_LENGTH_FOR_NSN) {
1903 throw new NumberParseException(
1904 NumberParseException::TOO_SHORT_AFTER_IDD,
1905 'Phone number had an IDD, but after this was not long enough to be a viable phone number.'
1906 );
1907 }
1908 $potentialCountryCode = $this->extractCountryCode($fullNumber, $nationalNumber);
1909
1910 if ($potentialCountryCode != 0) {
1911 $phoneNumber->setCountryCode($potentialCountryCode);
1912 return $potentialCountryCode;
1913 }
1914
1915 // If this fails, they must be using a strange country calling code that we don't recognize,
1916 // or that doesn't exist.
1917 throw new NumberParseException(
1918 NumberParseException::INVALID_COUNTRY_CODE,
1919 'Country calling code supplied was not recognised.'
1920 );
1921 }
1922
1923 if ($defaultRegionMetadata !== null) {
1924 // Check to see if the number starts with the country calling code for the default region. If
1925 // so, we remove the country calling code, and do some checks on the validity of the number
1926 // before and after.
1927 $defaultCountryCode = $defaultRegionMetadata->getCountryCode();
1928 $defaultCountryCodeString = (string)$defaultCountryCode;
1929 $normalizedNumber = $fullNumber;
1930 if (strpos($normalizedNumber, $defaultCountryCodeString) === 0) {
1931 $potentialNationalNumber = substr($normalizedNumber, mb_strlen($defaultCountryCodeString));
1932 $generalDesc = $defaultRegionMetadata->getGeneralDesc();
1933 // Don't need the carrier code.
1934 $carriercode = null;
1935 $this->maybeStripNationalPrefixAndCarrierCode(
1936 $potentialNationalNumber,
1937 $defaultRegionMetadata,
1938 $carriercode
1939 );
1940 // If the number was not valid before but is valid now, or if it was too long before, we
1941 // consider the number with the country calling code stripped to be a better result and
1942 // keep that instead.
1943 if ((!$this->matcherAPI->matchNationalNumber($fullNumber, $generalDesc, false)
1944 && $this->matcherAPI->matchNationalNumber($potentialNationalNumber, $generalDesc, false))
1945 || $this->testNumberLength($fullNumber, $defaultRegionMetadata) === ValidationResult::TOO_LONG
1946 ) {
1947 $nationalNumber .= $potentialNationalNumber;
1948 if ($keepRawInput) {
1949 $phoneNumber->setCountryCodeSource(CountryCodeSource::FROM_NUMBER_WITHOUT_PLUS_SIGN);
1950 }
1951 $phoneNumber->setCountryCode($defaultCountryCode);
1952 return $defaultCountryCode;
1953 }
1954 }
1955 }
1956 // No country calling code present.
1957 $phoneNumber->setCountryCode(0);
1958 return 0;
1959 }
1960
1961 /**
1962 * Strips any international prefix (such as +, 00, 011) present in the number provided, normalizes
1963 * the resulting number, and indicates if an international prefix was present.
1964 *
1965 * @param string $number the non-normalized telephone number that we wish to strip any international
1966 * dialing prefix from.
1967 * @param string $possibleIddPrefix string the international direct dialing prefix from the region we
1968 * think this number may be dialed in
1969 * @return int the corresponding CountryCodeSource if an international dialing prefix could be
1970 * removed from the number, otherwise CountryCodeSource.FROM_DEFAULT_COUNTRY if the number did
1971 * not seem to be in international format.
1972 */
1973 public function maybeStripInternationalPrefixAndNormalize(&$number, $possibleIddPrefix)
1974 {
1975 if (mb_strlen($number) == 0) {
1976 return CountryCodeSource::FROM_DEFAULT_COUNTRY;
1977 }
1978 $matches = array();
1979 // Check to see if the number begins with one or more plus signs.
1980 $match = preg_match('/^' . static::$PLUS_CHARS_PATTERN . '/' . static::REGEX_FLAGS, $number, $matches, PREG_OFFSET_CAPTURE);
1981 if ($match > 0) {
1982 $number = mb_substr($number, $matches[0][1] + mb_strlen($matches[0][0]));
1983 // Can now normalize the rest of the number since we've consumed the "+" sign at the start.
1984 $number = static::normalize($number);
1985 return CountryCodeSource::FROM_NUMBER_WITH_PLUS_SIGN;
1986 }
1987 // Attempt to parse the first digits as an international prefix.
1988 $iddPattern = $possibleIddPrefix;
1989 $number = static::normalize($number);
1990 return $this->parsePrefixAsIdd($iddPattern, $number)
1991 ? CountryCodeSource::FROM_NUMBER_WITH_IDD
1992 : CountryCodeSource::FROM_DEFAULT_COUNTRY;
1993 }
1994
1995 /**
1996 * Normalizes a string of characters representing a phone number. This performs
1997 * the following conversions:
1998 * Punctuation is stripped.
1999 * For ALPHA/VANITY numbers:
2000 * Letters are converted to their numeric representation on a telephone
2001 * keypad. The keypad used here is the one defined in ITU Recommendation
2002 * E.161. This is only done if there are 3 or more letters in the number,
2003 * to lessen the risk that such letters are typos.
2004 * For other numbers:
2005 * - Wide-ascii digits are converted to normal ASCII (European) digits.
2006 * - Arabic-Indic numerals are converted to European numerals.
2007 * - Spurious alpha characters are stripped.
2008 *
2009 * @param string $number a string of characters representing a phone number.
2010 * @return string the normalized string version of the phone number.
2011 */
2012 public static function normalize(&$number)
2013 {
2014 if (static::$ALPHA_PHONE_MAPPINGS === null) {
2015 static::initAlphaPhoneMappings();
2016 }
2017
2018 $m = new Matcher(static::VALID_ALPHA_PHONE_PATTERN, $number);
2019 if ($m->matches()) {
2020 return static::normalizeHelper($number, static::$ALPHA_PHONE_MAPPINGS, true);
2021 }
2022
2023 return static::normalizeDigitsOnly($number);
2024 }
2025
2026 /**
2027 * Normalizes a string of characters representing a phone number. This converts wide-ascii and
2028 * arabic-indic numerals to European numerals, and strips punctuation and alpha characters.
2029 *
2030 * @param $number string a string of characters representing a phone number
2031 * @return string the normalized string version of the phone number
2032 */
2033 public static function normalizeDigitsOnly($number)
2034 {
2035 return static::normalizeDigits($number, false /* strip non-digits */);
2036 }
2037
2038 /**
2039 * @param string $number
2040 * @param bool $keepNonDigits
2041 * @return string
2042 */
2043 public static function normalizeDigits($number, $keepNonDigits)
2044 {
2045 $normalizedDigits = '';
2046 $numberAsArray = preg_split('/(?<!^)(?!$)/u', $number);
2047 foreach ($numberAsArray as $character) {
2048 // Check if we are in the unicode number range
2049 if (array_key_exists($character, static::$numericCharacters)) {
2050 $normalizedDigits .= static::$numericCharacters[$character];
2051 } elseif (is_numeric($character)) {
2052 $normalizedDigits .= $character;
2053 } elseif ($keepNonDigits) {
2054 $normalizedDigits .= $character;
2055 }
2056 }
2057 return $normalizedDigits;
2058 }
2059
2060 /**
2061 * Strips the IDD from the start of the number if present. Helper function used by
2062 * maybeStripInternationalPrefixAndNormalize.
2063 * @param string $iddPattern
2064 * @param string $number
2065 * @return bool
2066 */
2067 protected function parsePrefixAsIdd($iddPattern, &$number)
2068 {
2069 $m = new Matcher($iddPattern, $number);
2070 if ($m->lookingAt()) {
2071 $matchEnd = $m->end();
2072 // Only strip this if the first digit after the match is not a 0, since country calling codes
2073 // cannot begin with 0.
2074 $digitMatcher = new Matcher(static::$CAPTURING_DIGIT_PATTERN, substr($number, $matchEnd));
2075 if ($digitMatcher->find()) {
2076 $normalizedGroup = static::normalizeDigitsOnly($digitMatcher->group(1));
2077 if ($normalizedGroup == '0') {
2078 return false;
2079 }
2080 }
2081 $number = substr($number, $matchEnd);
2082 return true;
2083 }
2084 return false;
2085 }
2086
2087 /**
2088 * Extracts country calling code from fullNumber, returns it and places the remaining number in nationalNumber.
2089 * It assumes that the leading plus sign or IDD has already been removed.
2090 * Returns 0 if fullNumber doesn't start with a valid country calling code, and leaves nationalNumber unmodified.
2091 * @param string $fullNumber
2092 * @param string $nationalNumber
2093 * @return int
2094 * @internal
2095 */
2096 public function extractCountryCode($fullNumber, &$nationalNumber)
2097 {
2098 if ((mb_strlen($fullNumber) == 0) || ($fullNumber[0] == '0')) {
2099 // Country codes do not begin with a '0'.
2100 return 0;
2101 }
2102 $numberLength = mb_strlen($fullNumber);
2103 for ($i = 1; $i <= static::MAX_LENGTH_COUNTRY_CODE && $i <= $numberLength; $i++) {
2104 $potentialCountryCode = (int)substr($fullNumber, 0, $i);
2105 if (isset($this->countryCallingCodeToRegionCodeMap[$potentialCountryCode])) {
2106 $nationalNumber .= substr($fullNumber, $i);
2107 return $potentialCountryCode;
2108 }
2109 }
2110 return 0;
2111 }
2112
2113 /**
2114 * Strips any national prefix (such as 0, 1) present in the number provided.
2115 *
2116 * @param string $number the normalized telephone number that we wish to strip any national
2117 * dialing prefix from
2118 * @param PhoneMetadata $metadata the metadata for the region that we think this number is from
2119 * @param string $carrierCode a place to insert the carrier code if one is extracted
2120 * @return bool true if a national prefix or carrier code (or both) could be extracted.
2121 */
2122 public function maybeStripNationalPrefixAndCarrierCode(&$number, PhoneMetadata $metadata, &$carrierCode)
2123 {
2124 $numberLength = mb_strlen($number);
2125 $possibleNationalPrefix = $metadata->getNationalPrefixForParsing();
2126 if ($numberLength == 0 || $possibleNationalPrefix === null || mb_strlen($possibleNationalPrefix) == 0) {
2127 // Early return for numbers of zero length.
2128 return false;
2129 }
2130
2131 // Attempt to parse the first digits as a national prefix.
2132 $prefixMatcher = new Matcher($possibleNationalPrefix, $number);
2133 if ($prefixMatcher->lookingAt()) {
2134 $generalDesc = $metadata->getGeneralDesc();
2135 // Check if the original number is viable.
2136 $isViableOriginalNumber = $this->matcherAPI->matchNationalNumber($number, $generalDesc, false);
2137 // $prefixMatcher->group($numOfGroups) === null implies nothing was captured by the capturing
2138 // groups in $possibleNationalPrefix; therefore, no transformation is necessary, and we just
2139 // remove the national prefix
2140 $numOfGroups = $prefixMatcher->groupCount();
2141 $transformRule = $metadata->getNationalPrefixTransformRule();
2142 if ($transformRule === null
2143 || mb_strlen($transformRule) == 0
2144 || $prefixMatcher->group($numOfGroups - 1) === null
2145 ) {
2146 // If the original number was viable, and the resultant number is not, we return.
2147 if ($isViableOriginalNumber &&
2148 !$this->matcherAPI->matchNationalNumber(
2149 substr($number, $prefixMatcher->end()), $generalDesc, false)) {
2150 return false;
2151 }
2152 if ($carrierCode !== null && $numOfGroups > 0 && $prefixMatcher->group($numOfGroups) !== null) {
2153 $carrierCode .= $prefixMatcher->group(1);
2154 }
2155
2156 $number = substr($number, $prefixMatcher->end());
2157 return true;
2158 }
2159
2160 // Check that the resultant number is still viable. If not, return. Check this by copying
2161 // the string and making the transformation on the copy first.
2162 $transformedNumber = $number;
2163 $transformedNumber = substr_replace(
2164 $transformedNumber,
2165 $prefixMatcher->replaceFirst($transformRule),
2166 0,
2167 $numberLength
2168 );
2169 if ($isViableOriginalNumber
2170 && !$this->matcherAPI->matchNationalNumber($transformedNumber, $generalDesc, false)) {
2171 return false;
2172 }
2173 if ($carrierCode !== null && $numOfGroups > 1) {
2174 $carrierCode .= $prefixMatcher->group(1);
2175 }
2176 $number = substr_replace($number, $transformedNumber, 0, mb_strlen($number));
2177 return true;
2178 }
2179 return false;
2180 }
2181
2182 /**
2183 * Convenience wrapper around isPossibleNumberForTypeWithReason. Instead of returning the reason
2184 * reason for failure, this method returns true if the number is either a possible fully-qualified
2185 * number (containing the area code and country code), or if the number could be a possible local
2186 * number (with a country code, but missing an area code). Local numbers are considered possible
2187 * if they could be possibly dialled in this format: if the area code is needed for a call to
2188 * connect, the number is not considered possible without it.
2189 *
2190 * @param PhoneNumber $number The number that needs to be checked
2191 * @param int $type PhoneNumberType The type we are interested in
2192 * @return bool true if the number is possible for this particular type
2193 */
2194 public function isPossibleNumberForType(PhoneNumber $number, $type)
2195 {
2196 $result = $this->isPossibleNumberForTypeWithReason($number, $type);
2197 return $result === ValidationResult::IS_POSSIBLE
2198 || $result === ValidationResult::IS_POSSIBLE_LOCAL_ONLY;
2199 }
2200
2201 /**
2202 * Helper method to check a number against possible lengths for this number type, and determine
2203 * whether it matches, or is too short or too long.
2204 *
2205 * @param string $number
2206 * @param PhoneMetadata $metadata
2207 * @param int $type PhoneNumberType
2208 * @return int ValidationResult
2209 */
2210 protected function testNumberLength($number, PhoneMetadata $metadata, $type = PhoneNumberType::UNKNOWN)
2211 {
2212 $descForType = $this->getNumberDescByType($metadata, $type);
2213 // There should always be "possibleLengths" set for every element. This is declared in the XML
2214 // schema which is verified by PhoneNumberMetadataSchemaTest.
2215 // For size efficiency, where a sub-description (e.g. fixed-line) has the same possibleLengths
2216 // as the parent, this is missing, so we fall back to the general desc (where no numbers of the
2217 // type exist at all, there is one possible length (-1) which is guaranteed not to match the
2218 // length of any real phone number).
2219 $possibleLengths = (count($descForType->getPossibleLength()) === 0)
2220 ? $metadata->getGeneralDesc()->getPossibleLength() : $descForType->getPossibleLength();
2221
2222 $localLengths = $descForType->getPossibleLengthLocalOnly();
2223
2224 if ($type === PhoneNumberType::FIXED_LINE_OR_MOBILE) {
2225 if (!static::descHasPossibleNumberData($this->getNumberDescByType($metadata, PhoneNumberType::FIXED_LINE))) {
2226 // The rate case has been encountered where no fixedLine data is available (true for some
2227 // non-geographical entities), so we just check mobile.
2228 return $this->testNumberLength($number, $metadata, PhoneNumberType::MOBILE);
2229 }
2230
2231 $mobileDesc = $this->getNumberDescByType($metadata, PhoneNumberType::MOBILE);
2232 if (static::descHasPossibleNumberData($mobileDesc)) {
2233 // Note that when adding the possible lengths from mobile, we have to again check they
2234 // aren't empty since if they are this indicates they are the same as the general desc and
2235 // should be obtained from there.
2236 $possibleLengths = array_merge($possibleLengths,
2237 (count($mobileDesc->getPossibleLength()) === 0)
2238 ? $metadata->getGeneralDesc()->getPossibleLength() : $mobileDesc->getPossibleLength());
2239
2240 // The current list is sorted; we need to merge in the new list and re-sort (duplicates
2241 // are okay). Sorting isn't so expensive because the lists are very small.
2242 sort($possibleLengths);
2243
2244 if (count($localLengths) === 0) {
2245 $localLengths = $mobileDesc->getPossibleLengthLocalOnly();
2246 } else {
2247 $localLengths = array_merge($localLengths, $mobileDesc->getPossibleLengthLocalOnly());
2248 sort($localLengths);
2249 }
2250 }
2251 }
2252
2253
2254 // If the type is not supported at all (indicated by the possible lengths containing -1 at this
2255 // point) we return invalid length.
2256
2257 if ($possibleLengths[0] === -1) {
2258 return ValidationResult::INVALID_LENGTH;
2259 }
2260
2261 $actualLength = mb_strlen($number);
2262
2263 // This is safe because there is never an overlap between the possible lengths and the local-only
2264 // lengths; this is checked at build time.
2265
2266 if (in_array($actualLength, $localLengths)) {
2267 return ValidationResult::IS_POSSIBLE_LOCAL_ONLY;
2268 }
2269
2270 $minimumLength = reset($possibleLengths);
2271 if ($minimumLength == $actualLength) {
2272 return ValidationResult::IS_POSSIBLE;
2273 }
2274
2275 if ($minimumLength > $actualLength) {
2276 return ValidationResult::TOO_SHORT;
2277 } elseif (isset($possibleLengths[count($possibleLengths) - 1]) && $possibleLengths[count($possibleLengths) - 1] < $actualLength) {
2278 return ValidationResult::TOO_LONG;
2279 }
2280
2281 // We skip the first element; we've already checked it.
2282 array_shift($possibleLengths);
2283 return in_array($actualLength, $possibleLengths) ? ValidationResult::IS_POSSIBLE : ValidationResult::INVALID_LENGTH;
2284 }
2285
2286 /**
2287 * Returns a list with the region codes that match the specific country calling code. For
2288 * non-geographical country calling codes, the region code 001 is returned. Also, in the case
2289 * of no region code being found, an empty list is returned.
2290 * @param int $countryCallingCode
2291 * @return array
2292 */
2293 public function getRegionCodesForCountryCode($countryCallingCode)
2294 {
2295 $regionCodes = isset($this->countryCallingCodeToRegionCodeMap[$countryCallingCode]) ? $this->countryCallingCodeToRegionCodeMap[$countryCallingCode] : null;
2296 return $regionCodes === null ? array() : $regionCodes;
2297 }
2298
2299 /**
2300 * Returns the country calling code for a specific region. For example, this would be 1 for the
2301 * United States, and 64 for New Zealand. Assumes the region is already valid.
2302 *
2303 * @param string $regionCode the region that we want to get the country calling code for
2304 * @return int the country calling code for the region denoted by regionCode
2305 */
2306 public function getCountryCodeForRegion($regionCode)
2307 {
2308 if (!$this->isValidRegionCode($regionCode)) {
2309 return 0;
2310 }
2311 return $this->getCountryCodeForValidRegion($regionCode);
2312 }
2313
2314 /**
2315 * Returns the country calling code for a specific region. For example, this would be 1 for the
2316 * United States, and 64 for New Zealand. Assumes the region is already valid.
2317 *
2318 * @param string $regionCode the region that we want to get the country calling code for
2319 * @return int the country calling code for the region denoted by regionCode
2320 * @throws \InvalidArgumentException if the region is invalid
2321 */
2322 protected function getCountryCodeForValidRegion($regionCode)
2323 {
2324 $metadata = $this->getMetadataForRegion($regionCode);
2325 if ($metadata === null) {
2326 throw new \InvalidArgumentException('Invalid region code: ' . $regionCode);
2327 }
2328 return $metadata->getCountryCode();
2329 }
2330
2331 /**
2332 * Returns a number formatted in such a way that it can be dialed from a mobile phone in a
2333 * specific region. If the number cannot be reached from the region (e.g. some countries block
2334 * toll-free numbers from being called outside of the country), the method returns an empty
2335 * string.
2336 *
2337 * @param PhoneNumber $number the phone number to be formatted
2338 * @param string $regionCallingFrom the region where the call is being placed
2339 * @param boolean $withFormatting whether the number should be returned with formatting symbols, such as
2340 * spaces and dashes.
2341 * @return string the formatted phone number
2342 */
2343 public function formatNumberForMobileDialing(PhoneNumber $number, $regionCallingFrom, $withFormatting)
2344 {
2345 $countryCallingCode = $number->getCountryCode();
2346 if (!$this->hasValidCountryCallingCode($countryCallingCode)) {
2347 return $number->hasRawInput() ? $number->getRawInput() : '';
2348 }
2349
2350 $formattedNumber = '';
2351 // Clear the extension, as that part cannot normally be dialed together with the main number.
2352 $numberNoExt = new PhoneNumber();
2353 $numberNoExt->mergeFrom($number)->clearExtension();
2354 $regionCode = $this->getRegionCodeForCountryCode($countryCallingCode);
2355 $numberType = $this->getNumberType($numberNoExt);
2356 $isValidNumber = ($numberType !== PhoneNumberType::UNKNOWN);
2357 if ($regionCallingFrom == $regionCode) {
2358 $isFixedLineOrMobile = ($numberType == PhoneNumberType::FIXED_LINE) || ($numberType == PhoneNumberType::MOBILE) || ($numberType == PhoneNumberType::FIXED_LINE_OR_MOBILE);
2359 // Carrier codes may be needed in some countries. We handle this here.
2360 if ($regionCode == 'CO' && $numberType == PhoneNumberType::FIXED_LINE) {
2361 $formattedNumber = $this->formatNationalNumberWithCarrierCode(
2362 $numberNoExt,
2363 static::COLOMBIA_MOBILE_TO_FIXED_LINE_PREFIX
2364 );
2365 } elseif ($regionCode == 'BR' && $isFixedLineOrMobile) {
2366 // Historically, we set this to an empty string when parsing with raw input if none was
2367 // found in the input string. However, this doesn't result in a number we can dial. For this
2368 // reason, we treat the empty string the same as if it isn't set at all.
2369 $formattedNumber = mb_strlen($numberNoExt->getPreferredDomesticCarrierCode()) > 0
2370 ? $this->formatNationalNumberWithPreferredCarrierCode($numberNoExt, '')
2371 // Brazilian fixed line and mobile numbers need to be dialed with a carrier code when
2372 // called within Brazil. Without that, most of the carriers won't connect the call.
2373 // Because of that, we return an empty string here.
2374 : '';
2375 } elseif ($isValidNumber && $regionCode == 'HU') {
2376 // The national format for HU numbers doesn't contain the national prefix, because that is
2377 // how numbers are normally written down. However, the national prefix is obligatory when
2378 // dialing from a mobile phone, except for short numbers. As a result, we add it back here
2379 // if it is a valid regular length phone number.
2380 $formattedNumber = $this->getNddPrefixForRegion(
2381 $regionCode,
2382 true /* strip non-digits */
2383 ) . ' ' . $this->format($numberNoExt, PhoneNumberFormat::NATIONAL);
2384 } elseif ($countryCallingCode === static::NANPA_COUNTRY_CODE) {
2385 // For NANPA countries, we output international format for numbers that can be dialed
2386 // internationally, since that always works, except for numbers which might potentially be
2387 // short numbers, which are always dialled in national format.
2388 $regionMetadata = $this->getMetadataForRegion($regionCallingFrom);
2389 if ($this->canBeInternationallyDialled($numberNoExt)
2390 && $this->testNumberLength($this->getNationalSignificantNumber($numberNoExt), $regionMetadata)
2391 !== ValidationResult::TOO_SHORT
2392 ) {
2393 $formattedNumber = $this->format($numberNoExt, PhoneNumberFormat::INTERNATIONAL);
2394 } else {
2395 $formattedNumber = $this->format($numberNoExt, PhoneNumberFormat::NATIONAL);
2396 }
2397 } elseif (($regionCode == static::REGION_CODE_FOR_NON_GEO_ENTITY ||
2398 // MX fixed line and mobile numbers should always be formatted in international format,
2399 // even when dialed within MX. For national format to work, a carrier code needs to be
2400 // used, and the correct carrier code depends on if the caller and callee are from the
2401 // same local area. It is trickier to get that to work correctly than using
2402 // international format, which is tested to work fine on all carriers.
2403 // CL fixed line numbers need the national prefix when dialing in the national format,
2404 // but don't have it when used for display. The reverse is true for mobile numbers.
2405 // As a result, we output them in the international format to make it work.
2406 (
2407 ($regionCode === 'MX' || $regionCode === 'CL' || $regionCode === 'UZ')
2408 && $isFixedLineOrMobile
2409 )
2410 ) && $this->canBeInternationallyDialled($numberNoExt)
2411 ) {
2412 $formattedNumber = $this->format($numberNoExt, PhoneNumberFormat::INTERNATIONAL);
2413 } else {
2414 $formattedNumber = $this->format($numberNoExt, PhoneNumberFormat::NATIONAL);
2415 }
2416 } elseif ($isValidNumber && $this->canBeInternationallyDialled($numberNoExt)) {
2417 // We assume that short numbers are not diallable from outside their region, so if a number
2418 // is not a valid regular length phone number, we treat it as if it cannot be internationally
2419 // dialled.
2420 return $withFormatting ?
2421 $this->format($numberNoExt, PhoneNumberFormat::INTERNATIONAL) :
2422 $this->format($numberNoExt, PhoneNumberFormat::E164);
2423 }
2424 return $withFormatting ? $formattedNumber : static::normalizeDiallableCharsOnly($formattedNumber);
2425 }
2426
2427 /**
2428 * Formats a phone number in national format for dialing using the carrier as specified in the
2429 * {@code carrierCode}. The {@code carrierCode} will always be used regardless of whether the
2430 * phone number already has a preferred domestic carrier code stored. If {@code carrierCode}
2431 * contains an empty string, returns the number in national format without any carrier code.
2432 *
2433 * @param PhoneNumber $number the phone number to be formatted
2434 * @param string $carrierCode the carrier selection code to be used
2435 * @return string the formatted phone number in national format for dialing using the carrier as
2436 * specified in the {@code carrierCode}
2437 */
2438 public function formatNationalNumberWithCarrierCode(PhoneNumber $number, $carrierCode)
2439 {
2440 $countryCallingCode = $number->getCountryCode();
2441 $nationalSignificantNumber = $this->getNationalSignificantNumber($number);
2442 if (!$this->hasValidCountryCallingCode($countryCallingCode)) {
2443 return $nationalSignificantNumber;
2444 }
2445
2446 // Note getRegionCodeForCountryCode() is used because formatting information for regions which
2447 // share a country calling code is contained by only one region for performance reasons. For
2448 // example, for NANPA regions it will be contained in the metadata for US.
2449 $regionCode = $this->getRegionCodeForCountryCode($countryCallingCode);
2450 // Metadata cannot be null because the country calling code is valid.
2451 $metadata = $this->getMetadataForRegionOrCallingCode($countryCallingCode, $regionCode);
2452
2453 $formattedNumber = $this->formatNsn(
2454 $nationalSignificantNumber,
2455 $metadata,
2456 PhoneNumberFormat::NATIONAL,
2457 $carrierCode
2458 );
2459 $this->maybeAppendFormattedExtension($number, $metadata, PhoneNumberFormat::NATIONAL, $formattedNumber);
2460 $this->prefixNumberWithCountryCallingCode(
2461 $countryCallingCode,
2462 PhoneNumberFormat::NATIONAL,
2463 $formattedNumber
2464 );
2465 return $formattedNumber;
2466 }
2467
2468 /**
2469 * Formats a phone number in national format for dialing using the carrier as specified in the
2470 * preferredDomesticCarrierCode field of the PhoneNumber object passed in. If that is missing,
2471 * use the {@code fallbackCarrierCode} passed in instead. If there is no
2472 * {@code preferredDomesticCarrierCode}, and the {@code fallbackCarrierCode} contains an empty
2473 * string, return the number in national format without any carrier code.
2474 *
2475 * <p>Use {@link #formatNationalNumberWithCarrierCode} instead if the carrier code passed in
2476 * should take precedence over the number's {@code preferredDomesticCarrierCode} when formatting.
2477 *
2478 * @param PhoneNumber $number the phone number to be formatted
2479 * @param string $fallbackCarrierCode the carrier selection code to be used, if none is found in the
2480 * phone number itself
2481 * @return string the formatted phone number in national format for dialing using the number's
2482 * {@code preferredDomesticCarrierCode}, or the {@code fallbackCarrierCode} passed in if
2483 * none is found
2484 */
2485 public function formatNationalNumberWithPreferredCarrierCode(PhoneNumber $number, $fallbackCarrierCode)
2486 {
2487 return $this->formatNationalNumberWithCarrierCode(
2488 $number,
2489 // Historically, we set this to an empty string when parsing with raw input if none was
2490 // found in the input string. However, this doesn't result in a number we can dial. For this
2491 // reason, we treat the empty string the same as if it isn't set at all.
2492 mb_strlen($number->getPreferredDomesticCarrierCode()) > 0
2493 ? $number->getPreferredDomesticCarrierCode()
2494 : $fallbackCarrierCode
2495 );
2496 }
2497
2498 /**
2499 * Returns true if the number can be dialled from outside the region, or unknown. If the number
2500 * can only be dialled from within the region, returns false. Does not check the number is a valid
2501 * number. Note that, at the moment, this method does not handle short numbers (which are
2502 * currently all presumed to not be diallable from outside their country).
2503 *
2504 * @param PhoneNumber $number the phone-number for which we want to know whether it is diallable from outside the region
2505 * @return bool
2506 */
2507 public function canBeInternationallyDialled(PhoneNumber $number)
2508 {
2509 $metadata = $this->getMetadataForRegion($this->getRegionCodeForNumber($number));
2510 if ($metadata === null) {
2511 // Note numbers belonging to non-geographical entities (e.g. +800 numbers) are always
2512 // internationally diallable, and will be caught here.
2513 return true;
2514 }
2515 $nationalSignificantNumber = $this->getNationalSignificantNumber($number);
2516 return !$this->isNumberMatchingDesc($nationalSignificantNumber, $metadata->getNoInternationalDialling());
2517 }
2518
2519 /**
2520 * Normalizes a string of characters representing a phone number. This strips all characters which
2521 * are not diallable on a mobile phone keypad (including all non-ASCII digits).
2522 *
2523 * @param string $number a string of characters representing a phone number
2524 * @return string the normalized string version of the phone number
2525 */
2526 public static function normalizeDiallableCharsOnly($number)
2527 {
2528 if (count(static::$DIALLABLE_CHAR_MAPPINGS) === 0) {
2529 static::initDiallableCharMappings();
2530 }
2531
2532 return static::normalizeHelper($number, static::$DIALLABLE_CHAR_MAPPINGS, true /* remove non matches */);
2533 }
2534
2535 /**
2536 * Formats a phone number for out-of-country dialing purposes.
2537 *
2538 * Note that in this version, if the number was entered originally using alpha characters and
2539 * this version of the number is stored in raw_input, this representation of the number will be
2540 * used rather than the digit representation. Grouping information, as specified by characters
2541 * such as "-" and " ", will be retained.
2542 *
2543 * <p><b>Caveats:</b></p>
2544 * <ul>
2545 * <li> This will not produce good results if the country calling code is both present in the raw
2546 * input _and_ is the start of the national number. This is not a problem in the regions
2547 * which typically use alpha numbers.
2548 * <li> This will also not produce good results if the raw input has any grouping information
2549 * within the first three digits of the national number, and if the function needs to strip
2550 * preceding digits/words in the raw input before these digits. Normally people group the
2551 * first three digits together so this is not a huge problem - and will be fixed if it
2552 * proves to be so.
2553 * </ul>
2554 *
2555 * @param PhoneNumber $number the phone number that needs to be formatted
2556 * @param String $regionCallingFrom the region where the call is being placed
2557 * @return String the formatted phone number
2558 */
2559 public function formatOutOfCountryKeepingAlphaChars(PhoneNumber $number, $regionCallingFrom)
2560 {
2561 $rawInput = $number->getRawInput();
2562 // If there is no raw input, then we can't keep alpha characters because there aren't any.
2563 // In this case, we return formatOutOfCountryCallingNumber.
2564 if (mb_strlen($rawInput) == 0) {
2565 return $this->formatOutOfCountryCallingNumber($number, $regionCallingFrom);
2566 }
2567 $countryCode = $number->getCountryCode();
2568 if (!$this->hasValidCountryCallingCode($countryCode)) {
2569 return $rawInput;
2570 }
2571 // Strip any prefix such as country calling code, IDD, that was present. We do this by comparing
2572 // the number in raw_input with the parsed number.
2573 // To do this, first we normalize punctuation. We retain number grouping symbols such as " "
2574 // only.
2575 $rawInput = self::normalizeHelper($rawInput, static::$ALL_PLUS_NUMBER_GROUPING_SYMBOLS, true);
2576 // Now we trim everything before the first three digits in the parsed number. We choose three
2577 // because all valid alpha numbers have 3 digits at the start - if it does not, then we don't
2578 // trim anything at all. Similarly, if the national number was less than three digits, we don't
2579 // trim anything at all.
2580 $nationalNumber = $this->getNationalSignificantNumber($number);
2581 if (mb_strlen($nationalNumber) > 3) {
2582 $firstNationalNumberDigit = strpos($rawInput, substr($nationalNumber, 0, 3));
2583 if ($firstNationalNumberDigit !== false) {
2584 $rawInput = substr($rawInput, $firstNationalNumberDigit);
2585 }
2586 }
2587 $metadataForRegionCallingFrom = $this->getMetadataForRegion($regionCallingFrom);
2588 if ($countryCode == static::NANPA_COUNTRY_CODE) {
2589 if ($this->isNANPACountry($regionCallingFrom)) {
2590 return $countryCode . ' ' . $rawInput;
2591 }
2592 } elseif ($metadataForRegionCallingFrom !== null &&
2593 $countryCode == $this->getCountryCodeForValidRegion($regionCallingFrom)
2594 ) {
2595 $formattingPattern =
2596 $this->chooseFormattingPatternForNumber(
2597 $metadataForRegionCallingFrom->numberFormats(),
2598 $nationalNumber
2599 );
2600 if ($formattingPattern === null) {
2601 // If no pattern above is matched, we format the original input.
2602 return $rawInput;
2603 }
2604 $newFormat = new NumberFormat();
2605 $newFormat->mergeFrom($formattingPattern);
2606 // The first group is the first group of digits that the user wrote together.
2607 $newFormat->setPattern("(\\d+)(.*)");
2608 // Here we just concatenate them back together after the national prefix has been fixed.
2609 $newFormat->setFormat('$1$2');
2610 // Now we format using this pattern instead of the default pattern, but with the national
2611 // prefix prefixed if necessary.
2612 // This will not work in the cases where the pattern (and not the leading digits) decide
2613 // whether a national prefix needs to be used, since we have overridden the pattern to match
2614 // anything, but that is not the case in the metadata to date.
2615 return $this->formatNsnUsingPattern($rawInput, $newFormat, PhoneNumberFormat::NATIONAL);
2616 }
2617 $internationalPrefixForFormatting = '';
2618 // If an unsupported region-calling-from is entered, or a country with multiple international
2619 // prefixes, the international format of the number is returned, unless there is a preferred
2620 // international prefix.
2621 if ($metadataForRegionCallingFrom !== null) {
2622 $internationalPrefix = $metadataForRegionCallingFrom->getInternationalPrefix();
2623 $uniqueInternationalPrefixMatcher = new Matcher(static::SINGLE_INTERNATIONAL_PREFIX, $internationalPrefix);
2624 $internationalPrefixForFormatting =
2625 $uniqueInternationalPrefixMatcher->matches()
2626 ? $internationalPrefix
2627 : $metadataForRegionCallingFrom->getPreferredInternationalPrefix();
2628 }
2629 $formattedNumber = $rawInput;
2630 $regionCode = $this->getRegionCodeForCountryCode($countryCode);
2631 // Metadata cannot be null because the country calling code is valid.
2632 $metadataForRegion = $this->getMetadataForRegionOrCallingCode($countryCode, $regionCode);
2633 $this->maybeAppendFormattedExtension(
2634 $number,
2635 $metadataForRegion,
2636 PhoneNumberFormat::INTERNATIONAL,
2637 $formattedNumber
2638 );
2639 if (mb_strlen($internationalPrefixForFormatting) > 0) {
2640 $formattedNumber = $internationalPrefixForFormatting . ' ' . $countryCode . ' ' . $formattedNumber;
2641 } else {
2642 // Invalid region entered as country-calling-from (so no metadata was found for it) or the
2643 // region chosen has multiple international dialling prefixes.
2644 $this->prefixNumberWithCountryCallingCode(
2645 $countryCode,
2646 PhoneNumberFormat::INTERNATIONAL,
2647 $formattedNumber
2648 );
2649 }
2650 return $formattedNumber;
2651 }
2652
2653 /**
2654 * Formats a phone number for out-of-country dialing purposes. If no regionCallingFrom is
2655 * supplied, we format the number in its INTERNATIONAL format. If the country calling code is the
2656 * same as that of the region where the number is from, then NATIONAL formatting will be applied.
2657 *
2658 * <p>If the number itself has a country calling code of zero or an otherwise invalid country
2659 * calling code, then we return the number with no formatting applied.
2660 *
2661 * <p>Note this function takes care of the case for calling inside of NANPA and between Russia and
2662 * Kazakhstan (who share the same country calling code). In those cases, no international prefix
2663 * is used. For regions which have multiple international prefixes, the number in its
2664 * INTERNATIONAL format will be returned instead.
2665 *
2666 * @param PhoneNumber $number the phone number to be formatted
2667 * @param string $regionCallingFrom the region where the call is being placed
2668 * @return string the formatted phone number
2669 */
2670 public function formatOutOfCountryCallingNumber(PhoneNumber $number, $regionCallingFrom)
2671 {
2672 if (!$this->isValidRegionCode($regionCallingFrom)) {
2673 return $this->format($number, PhoneNumberFormat::INTERNATIONAL);
2674 }
2675 $countryCallingCode = $number->getCountryCode();
2676 $nationalSignificantNumber = $this->getNationalSignificantNumber($number);
2677 if (!$this->hasValidCountryCallingCode($countryCallingCode)) {
2678 return $nationalSignificantNumber;
2679 }
2680 if ($countryCallingCode == static::NANPA_COUNTRY_CODE) {
2681 if ($this->isNANPACountry($regionCallingFrom)) {
2682 // For NANPA regions, return the national format for these regions but prefix it with the
2683 // country calling code.
2684 return $countryCallingCode . ' ' . $this->format($number, PhoneNumberFormat::NATIONAL);
2685 }
2686 } elseif ($countryCallingCode == $this->getCountryCodeForValidRegion($regionCallingFrom)) {
2687 // If regions share a country calling code, the country calling code need not be dialled.
2688 // This also applies when dialling within a region, so this if clause covers both these cases.
2689 // Technically this is the case for dialling from La Reunion to other overseas departments of
2690 // France (French Guiana, Martinique, Guadeloupe), but not vice versa - so we don't cover this
2691 // edge case for now and for those cases return the version including country calling code.
2692 // Details here: http://www.petitfute.com/voyage/225-info-pratiques-reunion
2693 return $this->format($number, PhoneNumberFormat::NATIONAL);
2694 }
2695 // Metadata cannot be null because we checked 'isValidRegionCode()' above.
2696 $metadataForRegionCallingFrom = $this->getMetadataForRegion($regionCallingFrom);
2697
2698 $internationalPrefix = $metadataForRegionCallingFrom->getInternationalPrefix();
2699
2700 // For regions that have multiple international prefixes, the international format of the
2701 // number is returned, unless there is a preferred international prefix.
2702 $internationalPrefixForFormatting = '';
2703 $uniqueInternationalPrefixMatcher = new Matcher(static::SINGLE_INTERNATIONAL_PREFIX, $internationalPrefix);
2704
2705 if ($uniqueInternationalPrefixMatcher->matches()) {
2706 $internationalPrefixForFormatting = $internationalPrefix;
2707 } elseif ($metadataForRegionCallingFrom->hasPreferredInternationalPrefix()) {
2708 $internationalPrefixForFormatting = $metadataForRegionCallingFrom->getPreferredInternationalPrefix();
2709 }
2710
2711 $regionCode = $this->getRegionCodeForCountryCode($countryCallingCode);
2712 // Metadata cannot be null because the country calling code is valid.
2713 $metadataForRegion = $this->getMetadataForRegionOrCallingCode($countryCallingCode, $regionCode);
2714 $formattedNationalNumber = $this->formatNsn(
2715 $nationalSignificantNumber,
2716 $metadataForRegion,
2717 PhoneNumberFormat::INTERNATIONAL
2718 );
2719 $formattedNumber = $formattedNationalNumber;
2720 $this->maybeAppendFormattedExtension(
2721 $number,
2722 $metadataForRegion,
2723 PhoneNumberFormat::INTERNATIONAL,
2724 $formattedNumber
2725 );
2726 if (mb_strlen($internationalPrefixForFormatting) > 0) {
2727 $formattedNumber = $internationalPrefixForFormatting . ' ' . $countryCallingCode . ' ' . $formattedNumber;
2728 } else {
2729 $this->prefixNumberWithCountryCallingCode(
2730 $countryCallingCode,
2731 PhoneNumberFormat::INTERNATIONAL,
2732 $formattedNumber
2733 );
2734 }
2735 return $formattedNumber;
2736 }
2737
2738 /**
2739 * Checks if this is a region under the North American Numbering Plan Administration (NANPA).
2740 * @param string $regionCode
2741 * @return boolean true if regionCode is one of the regions under NANPA
2742 */
2743 public function isNANPACountry($regionCode)
2744 {
2745 return in_array($regionCode, $this->nanpaRegions);
2746 }
2747
2748 /**
2749 * Formats a phone number using the original phone number format that the number is parsed from.
2750 * The original format is embedded in the country_code_source field of the PhoneNumber object
2751 * passed in. If such information is missing, the number will be formatted into the NATIONAL
2752 * format by default. When we don't have a formatting pattern for the number, the method returns
2753 * the raw inptu when it is available.
2754 *
2755 * Note this method guarantees no digit will be inserted, removed or modified as a result of
2756 * formatting.
2757 *
2758 * @param PhoneNumber $number the phone number that needs to be formatted in its original number format
2759 * @param string $regionCallingFrom the region whose IDD needs to be prefixed if the original number
2760 * has one
2761 * @return string the formatted phone number in its original number format
2762 */
2763 public function formatInOriginalFormat(PhoneNumber $number, $regionCallingFrom)
2764 {
2765 if ($number->hasRawInput() && !$this->hasFormattingPatternForNumber($number)) {
2766 // We check if we have the formatting pattern because without that, we might format the number
2767 // as a group without national prefix.
2768 return $number->getRawInput();
2769 }
2770 if (!$number->hasCountryCodeSource()) {
2771 return $this->format($number, PhoneNumberFormat::NATIONAL);
2772 }
2773 switch ($number->getCountryCodeSource()) {
2774 case CountryCodeSource::FROM_NUMBER_WITH_PLUS_SIGN:
2775 $formattedNumber = $this->format($number, PhoneNumberFormat::INTERNATIONAL);
2776 break;
2777 case CountryCodeSource::FROM_NUMBER_WITH_IDD:
2778 $formattedNumber = $this->formatOutOfCountryCallingNumber($number, $regionCallingFrom);
2779 break;
2780 case CountryCodeSource::FROM_NUMBER_WITHOUT_PLUS_SIGN:
2781 $formattedNumber = substr($this->format($number, PhoneNumberFormat::INTERNATIONAL), 1);
2782 break;
2783 case CountryCodeSource::FROM_DEFAULT_COUNTRY:
2784 // Fall-through to default case.
2785 default:
2786
2787 $regionCode = $this->getRegionCodeForCountryCode($number->getCountryCode());
2788 // We strip non-digits from the NDD here, and from the raw input later, so that we can
2789 // compare them easily.
2790 $nationalPrefix = $this->getNddPrefixForRegion($regionCode, true /* strip non-digits */);
2791 $nationalFormat = $this->format($number, PhoneNumberFormat::NATIONAL);
2792 if ($nationalPrefix === null || mb_strlen($nationalPrefix) == 0) {
2793 // If the region doesn't have a national prefix at all, we can safely return the national
2794 // format without worrying about a national prefix being added.
2795 $formattedNumber = $nationalFormat;
2796 break;
2797 }
2798 // Otherwise, we check if the original number was entered with a national prefix.
2799 if ($this->rawInputContainsNationalPrefix(
2800 $number->getRawInput(),
2801 $nationalPrefix,
2802 $regionCode
2803 )
2804 ) {
2805 // If so, we can safely return the national format.
2806 $formattedNumber = $nationalFormat;
2807 break;
2808 }
2809 // Metadata cannot be null here because getNddPrefixForRegion() (above) returns null if
2810 // there is no metadata for the region.
2811 $metadata = $this->getMetadataForRegion($regionCode);
2812 $nationalNumber = $this->getNationalSignificantNumber($number);
2813 $formatRule = $this->chooseFormattingPatternForNumber($metadata->numberFormats(), $nationalNumber);
2814 // The format rule could still be null here if the national number was 0 and there was no
2815 // raw input (this should not be possible for numbers generated by the phonenumber library
2816 // as they would also not have a country calling code and we would have exited earlier).
2817 if ($formatRule === null) {
2818 $formattedNumber = $nationalFormat;
2819 break;
2820 }
2821 // When the format we apply to this number doesn't contain national prefix, we can just
2822 // return the national format.
2823 // TODO: Refactor the code below with the code in isNationalPrefixPresentIfRequired.
2824 $candidateNationalPrefixRule = $formatRule->getNationalPrefixFormattingRule();
2825 // We assume that the first-group symbol will never be _before_ the national prefix.
2826 $indexOfFirstGroup = strpos($candidateNationalPrefixRule, '$1');
2827 if ($indexOfFirstGroup <= 0) {
2828 $formattedNumber = $nationalFormat;
2829 break;
2830 }
2831 $candidateNationalPrefixRule = substr($candidateNationalPrefixRule, 0, $indexOfFirstGroup);
2832 $candidateNationalPrefixRule = static::normalizeDigitsOnly($candidateNationalPrefixRule);
2833 if (mb_strlen($candidateNationalPrefixRule) == 0) {
2834 // National prefix not used when formatting this number.
2835 $formattedNumber = $nationalFormat;
2836 break;
2837 }
2838 // Otherwise, we need to remove the national prefix from our output.
2839 $numFormatCopy = new NumberFormat();
2840 $numFormatCopy->mergeFrom($formatRule);
2841 $numFormatCopy->clearNationalPrefixFormattingRule();
2842 $numberFormats = array();
2843 $numberFormats[] = $numFormatCopy;
2844 $formattedNumber = $this->formatByPattern($number, PhoneNumberFormat::NATIONAL, $numberFormats);
2845 break;
2846 }
2847 $rawInput = $number->getRawInput();
2848 // If no digit is inserted/removed/modified as a result of our formatting, we return the
2849 // formatted phone number; otherwise we return the raw input the user entered.
2850 if ($formattedNumber !== null && mb_strlen($rawInput) > 0) {
2851 $normalizedFormattedNumber = static::normalizeDiallableCharsOnly($formattedNumber);
2852 $normalizedRawInput = static::normalizeDiallableCharsOnly($rawInput);
2853 if ($normalizedFormattedNumber != $normalizedRawInput) {
2854 $formattedNumber = $rawInput;
2855 }
2856 }
2857 return $formattedNumber;
2858 }
2859
2860 /**
2861 * @param PhoneNumber $number
2862 * @return bool
2863 */
2864 protected function hasFormattingPatternForNumber(PhoneNumber $number)
2865 {
2866 $countryCallingCode = $number->getCountryCode();
2867 $phoneNumberRegion = $this->getRegionCodeForCountryCode($countryCallingCode);
2868 $metadata = $this->getMetadataForRegionOrCallingCode($countryCallingCode, $phoneNumberRegion);
2869 if ($metadata === null) {
2870 return false;
2871 }
2872 $nationalNumber = $this->getNationalSignificantNumber($number);
2873 $formatRule = $this->chooseFormattingPatternForNumber($metadata->numberFormats(), $nationalNumber);
2874 return $formatRule !== null;
2875 }
2876
2877 /**
2878 * Returns the national dialling prefix for a specific region. For example, this would be 1 for
2879 * the United States, and 0 for New Zealand. Set stripNonDigits to true to strip symbols like "~"
2880 * (which indicates a wait for a dialling tone) from the prefix returned. If no national prefix is
2881 * present, we return null.
2882 *
2883 * <p>Warning: Do not use this method for do-your-own formatting - for some regions, the
2884 * national dialling prefix is used only for certain types of numbers. Use the library's
2885 * formatting functions to prefix the national prefix when required.
2886 *
2887 * @param string $regionCode the region that we want to get the dialling prefix for
2888 * @param boolean $stripNonDigits true to strip non-digits from the national dialling prefix
2889 * @return string the dialling prefix for the region denoted by regionCode
2890 */
2891 public function getNddPrefixForRegion($regionCode, $stripNonDigits)
2892 {
2893 $metadata = $this->getMetadataForRegion($regionCode);
2894 if ($metadata === null) {
2895 return null;
2896 }
2897 $nationalPrefix = $metadata->getNationalPrefix();
2898 // If no national prefix was found, we return null.
2899 if (mb_strlen($nationalPrefix) == 0) {
2900 return null;
2901 }
2902 if ($stripNonDigits) {
2903 // Note: if any other non-numeric symbols are ever used in national prefixes, these would have
2904 // to be removed here as well.
2905 $nationalPrefix = str_replace('~', '', $nationalPrefix);
2906 }
2907 return $nationalPrefix;
2908 }
2909
2910 /**
2911 * Check if rawInput, which is assumed to be in the national format, has a national prefix. The
2912 * national prefix is assumed to be in digits-only form.
2913 * @param string $rawInput
2914 * @param string $nationalPrefix
2915 * @param string $regionCode
2916 * @return bool
2917 */
2918 protected function rawInputContainsNationalPrefix($rawInput, $nationalPrefix, $regionCode)
2919 {
2920 $normalizedNationalNumber = static::normalizeDigitsOnly($rawInput);
2921 if (strpos($normalizedNationalNumber, $nationalPrefix) === 0) {
2922 try {
2923 // Some Japanese numbers (e.g. 00777123) might be mistaken to contain the national prefix
2924 // when written without it (e.g. 0777123) if we just do prefix matching. To tackle that, we
2925 // check the validity of the number if the assumed national prefix is removed (777123 won't
2926 // be valid in Japan).
2927 return $this->isValidNumber(
2928 $this->parse(substr($normalizedNationalNumber, mb_strlen($nationalPrefix)), $regionCode)
2929 );
2930 } catch (NumberParseException $e) {
2931 return false;
2932 }
2933 }
2934 return false;
2935 }
2936
2937 /**
2938 * Tests whether a phone number matches a valid pattern. Note this doesn't verify the number
2939 * is actually in use, which is impossible to tell by just looking at a number itself. It only
2940 * verifies whether the parsed, canonicalised number is valid: not whether a particular series of
2941 * digits entered by the user is diallable from the region provided when parsing. For example, the
2942 * number +41 (0) 78 927 2696 can be parsed into a number with country code "41" and national
2943 * significant number "789272696". This is valid, while the original string is not diallable.
2944 *
2945 * @param PhoneNumber $number the phone number that we want to validate
2946 * @return boolean that indicates whether the number is of a valid pattern
2947 */
2948 public function isValidNumber(PhoneNumber $number)
2949 {
2950 $regionCode = $this->getRegionCodeForNumber($number);
2951 return $this->isValidNumberForRegion($number, $regionCode);
2952 }
2953
2954 /**
2955 * Tests whether a phone number is valid for a certain region. Note this doesn't verify the number
2956 * is actually in use, which is impossible to tell by just looking at a number itself. If the
2957 * country calling code is not the same as the country calling code for the region, this
2958 * immediately exits with false. After this, the specific number pattern rules for the region are
2959 * examined. This is useful for determining for example whether a particular number is valid for
2960 * Canada, rather than just a valid NANPA number.
2961 * Warning: In most cases, you want to use {@link #isValidNumber} instead. For example, this
2962 * method will mark numbers from British Crown dependencies such as the Isle of Man as invalid for
2963 * the region "GB" (United Kingdom), since it has its own region code, "IM", which may be
2964 * undesirable.
2965 *
2966 * @param PhoneNumber $number the phone number that we want to validate
2967 * @param string $regionCode the region that we want to validate the phone number for
2968 * @return boolean that indicates whether the number is of a valid pattern
2969 */
2970 public function isValidNumberForRegion(PhoneNumber $number, $regionCode)
2971 {
2972 $countryCode = $number->getCountryCode();
2973 $metadata = $this->getMetadataForRegionOrCallingCode($countryCode, $regionCode);
2974 if (($metadata === null) ||
2975 (static::REGION_CODE_FOR_NON_GEO_ENTITY !== $regionCode &&
2976 $countryCode !== $this->getCountryCodeForValidRegion($regionCode))
2977 ) {
2978 // Either the region code was invalid, or the country calling code for this number does not
2979 // match that of the region code.
2980 return false;
2981 }
2982 $nationalSignificantNumber = $this->getNationalSignificantNumber($number);
2983
2984 return $this->getNumberTypeHelper($nationalSignificantNumber, $metadata) != PhoneNumberType::UNKNOWN;
2985 }
2986
2987 /**
2988 * Parses a string and returns it as a phone number in proto buffer format. The method is quite
2989 * lenient and looks for a number in the input text (raw input) and does not check whether the
2990 * string is definitely only a phone number. To do this, it ignores punctuation and white-space,
2991 * as well as any text before the number (e.g. a leading “Tel: ”) and trims the non-number bits.
2992 * It will accept a number in any format (E164, national, international etc), assuming it can
2993 * interpreted with the defaultRegion supplied. It also attempts to convert any alpha characters
2994 * into digits if it thinks this is a vanity number of the type "1800 MICROSOFT".
2995 *
2996 * <p> This method will throw a {@link NumberParseException} if the number is not considered to
2997 * be a possible number. Note that validation of whether the number is actually a valid number
2998 * for a particular region is not performed. This can be done separately with {@link #isValidNumber}.
2999 *
3000 * <p> Note this method canonicalizes the phone number such that different representations can be
3001 * easily compared, no matter what form it was originally entered in (e.g. national,
3002 * international). If you want to record context about the number being parsed, such as the raw
3003 * input that was entered, how the country code was derived etc. then call {@link
3004 * #parseAndKeepRawInput} instead.
3005 *
3006 * @param string $numberToParse number that we are attempting to parse. This can contain formatting
3007 * such as +, ( and -, as well as a phone number extension.
3008 * @param string|null $defaultRegion region that we are expecting the number to be from. This is only used
3009 * if the number being parsed is not written in international format.
3010 * The country_code for the number in this case would be stored as that
3011 * of the default region supplied. If the number is guaranteed to
3012 * start with a '+' followed by the country calling code, then
3013 * "ZZ" or null can be supplied.
3014 * @param PhoneNumber|null $phoneNumber
3015 * @param bool $keepRawInput
3016 * @return PhoneNumber a phone number proto buffer filled with the parsed number
3017 * @throws NumberParseException if the string is not considered to be a viable phone number (e.g.
3018 * too few or too many digits) or if no default region was supplied
3019 * and the number is not in international format (does not start
3020 * with +)
3021 */
3022 public function parse($numberToParse, $defaultRegion = null, PhoneNumber $phoneNumber = null, $keepRawInput = false)
3023 {
3024 if ($phoneNumber === null) {
3025 $phoneNumber = new PhoneNumber();
3026 }
3027 $this->parseHelper($numberToParse, $defaultRegion, $keepRawInput, true, $phoneNumber);
3028 return $phoneNumber;
3029 }
3030
3031 /**
3032 * Formats a phone number in the specified format using client-defined formatting rules. Note that
3033 * if the phone number has a country calling code of zero or an otherwise invalid country calling
3034 * code, we cannot work out things like whether there should be a national prefix applied, or how
3035 * to format extensions, so we return the national significant number with no formatting applied.
3036 *
3037 * @param PhoneNumber $number the phone number to be formatted
3038 * @param int $numberFormat the format the phone number should be formatted into
3039 * @param array $userDefinedFormats formatting rules specified by clients
3040 * @return String the formatted phone number
3041 */
3042 public function formatByPattern(PhoneNumber $number, $numberFormat, array $userDefinedFormats)
3043 {
3044 $countryCallingCode = $number->getCountryCode();
3045 $nationalSignificantNumber = $this->getNationalSignificantNumber($number);
3046 if (!$this->hasValidCountryCallingCode($countryCallingCode)) {
3047 return $nationalSignificantNumber;
3048 }
3049 // Note getRegionCodeForCountryCode() is used because formatting information for regions which
3050 // share a country calling code is contained by only one region for performance reasons. For
3051 // example, for NANPA regions it will be contained in the metadata for US.
3052 $regionCode = $this->getRegionCodeForCountryCode($countryCallingCode);
3053 // Metadata cannot be null because the country calling code is valid
3054 $metadata = $this->getMetadataForRegionOrCallingCode($countryCallingCode, $regionCode);
3055
3056 $formattedNumber = '';
3057
3058 $formattingPattern = $this->chooseFormattingPatternForNumber($userDefinedFormats, $nationalSignificantNumber);
3059 if ($formattingPattern === null) {
3060 // If no pattern above is matched, we format the number as a whole.
3061 $formattedNumber .= $nationalSignificantNumber;
3062 } else {
3063 $numFormatCopy = new NumberFormat();
3064 // Before we do a replacement of the national prefix pattern $NP with the national prefix, we
3065 // need to copy the rule so that subsequent replacements for different numbers have the
3066 // appropriate national prefix.
3067 $numFormatCopy->mergeFrom($formattingPattern);
3068 $nationalPrefixFormattingRule = $formattingPattern->getNationalPrefixFormattingRule();
3069 if (mb_strlen($nationalPrefixFormattingRule) > 0) {
3070 $nationalPrefix = $metadata->getNationalPrefix();
3071 if (mb_strlen($nationalPrefix) > 0) {
3072 // Replace $NP with national prefix and $FG with the first group ($1).
3073 $nationalPrefixFormattingRule = str_replace(array(static::NP_STRING, static::FG_STRING),
3074 array($nationalPrefix, '$1'), $nationalPrefixFormattingRule);
3075 $numFormatCopy->setNationalPrefixFormattingRule($nationalPrefixFormattingRule);
3076 } else {
3077 // We don't want to have a rule for how to format the national prefix if there isn't one.
3078 $numFormatCopy->clearNationalPrefixFormattingRule();
3079 }
3080 }
3081 $formattedNumber .= $this->formatNsnUsingPattern($nationalSignificantNumber, $numFormatCopy, $numberFormat);
3082 }
3083 $this->maybeAppendFormattedExtension($number, $metadata, $numberFormat, $formattedNumber);
3084 $this->prefixNumberWithCountryCallingCode($countryCallingCode, $numberFormat, $formattedNumber);
3085 return $formattedNumber;
3086 }
3087
3088 /**
3089 * Gets a valid number for the specified region.
3090 *
3091 * @param string regionCode the region for which an example number is needed
3092 * @return PhoneNumber a valid fixed-line number for the specified region. Returns null when the metadata
3093 * does not contain such information, or the region 001 is passed in. For 001 (representing
3094 * non-geographical numbers), call {@link #getExampleNumberForNonGeoEntity} instead.
3095 */
3096 public function getExampleNumber($regionCode)
3097 {
3098 return $this->getExampleNumberForType($regionCode, PhoneNumberType::FIXED_LINE);
3099 }
3100
3101 /**
3102 * Gets an invalid number for the specified region. This is useful for unit-testing purposes,
3103 * where you want to test what will happen with an invalid number. Note that the number that is
3104 * returned will always be able to be parsed and will have the correct country code. It may also
3105 * be a valid *short* number/code for this region. Validity checking such numbers is handled with
3106 * {@link ShortNumberInfo}.
3107 *
3108 * @param string $regionCode The region for which an example number is needed
3109 * @return PhoneNumber|null An invalid number for the specified region. Returns null when an unsupported region
3110 * or the region 001 (Earth) is passed in.
3111 */
3112 public function getInvalidExampleNumber($regionCode)
3113 {
3114 if (!$this->isValidRegionCode($regionCode)) {
3115 return null;
3116 }
3117
3118 // We start off with a valid fixed-line number since every country supports this. Alternatively
3119 // we could start with a different number type, since fixed-line numbers typically have a wide
3120 // breadth of valid number lengths and we may have to make it very short before we get an
3121 // invalid number.
3122
3123 $desc = $this->getNumberDescByType($this->getMetadataForRegion($regionCode), PhoneNumberType::FIXED_LINE);
3124
3125 if ($desc->getExampleNumber() == '') {
3126 // This shouldn't happen; we have a test for this.
3127 return null;
3128 }
3129
3130 $exampleNumber = $desc->getExampleNumber();
3131
3132 // Try and make the number invalid. We do this by changing the length. We try reducing the
3133 // length of the number, since currently no region has a number that is the same length as
3134 // MIN_LENGTH_FOR_NSN. This is probably quicker than making the number longer, which is another
3135 // alternative. We could also use the possible number pattern to extract the possible lengths of
3136 // the number to make this faster, but this method is only for unit-testing so simplicity is
3137 // preferred to performance. We don't want to return a number that can't be parsed, so we check
3138 // the number is long enough. We try all possible lengths because phone number plans often have
3139 // overlapping prefixes so the number 123456 might be valid as a fixed-line number, and 12345 as
3140 // a mobile number. It would be faster to loop in a different order, but we prefer numbers that
3141 // look closer to real numbers (and it gives us a variety of different lengths for the resulting
3142 // phone numbers - otherwise they would all be MIN_LENGTH_FOR_NSN digits long.)
3143 for ($phoneNumberLength = mb_strlen($exampleNumber) - 1; $phoneNumberLength >= static::MIN_LENGTH_FOR_NSN; $phoneNumberLength--) {
3144 $numberToTry = mb_substr($exampleNumber, 0, $phoneNumberLength);
3145 try {
3146 $possiblyValidNumber = $this->parse($numberToTry, $regionCode);
3147 if (!$this->isValidNumber($possiblyValidNumber)) {
3148 return $possiblyValidNumber;
3149 }
3150 } catch (NumberParseException $e) {
3151 // Shouldn't happen: we have already checked the length, we know example numbers have
3152 // only valid digits, and we know the region code is fine.
3153 }
3154 }
3155 // We have a test to check that this doesn't happen for any of our supported regions.
3156 return null;
3157 }
3158
3159 /**
3160 * Gets a valid number for the specified region and number type.
3161 *
3162 * @param string|int $regionCodeOrType the region for which an example number is needed
3163 * @param int $type the PhoneNumberType of number that is needed
3164 * @return PhoneNumber a valid number for the specified region and type. Returns null when the metadata
3165 * does not contain such information or if an invalid region or region 001 was entered.
3166 * For 001 (representing non-geographical numbers), call
3167 * {@link #getExampleNumberForNonGeoEntity} instead.
3168 *
3169 * If $regionCodeOrType is the only parameter supplied, then a valid number for the specified number type
3170 * will be returned that may belong to any country.
3171 */
3172 public function getExampleNumberForType($regionCodeOrType, $type = null)
3173 {
3174 if ($regionCodeOrType !== null && $type === null) {
3175 /*
3176 * Gets a valid number for the specified number type (it may belong to any country).
3177 */
3178 foreach ($this->getSupportedRegions() as $regionCode) {
3179 $exampleNumber = $this->getExampleNumberForType($regionCode, $regionCodeOrType);
3180 if ($exampleNumber !== null) {
3181 return $exampleNumber;
3182 }
3183 }
3184
3185 // If there wasn't an example number for a region, try the non-geographical entities
3186 foreach ($this->getSupportedGlobalNetworkCallingCodes() as $countryCallingCode) {
3187 $desc = $this->getNumberDescByType($this->getMetadataForNonGeographicalRegion($countryCallingCode), $regionCodeOrType);
3188 try {
3189 if ($desc->getExampleNumber() != '') {
3190 return $this->parse('+' . $countryCallingCode . $desc->getExampleNumber(), static::UNKNOWN_REGION);
3191 }
3192 } catch (NumberParseException $e) {
3193 // noop
3194 }
3195 }
3196 // There are no example numbers of this type for any country in the library.
3197 return null;
3198 }
3199
3200 // Check the region code is valid.
3201 if (!$this->isValidRegionCode($regionCodeOrType)) {
3202 return null;
3203 }
3204 $desc = $this->getNumberDescByType($this->getMetadataForRegion($regionCodeOrType), $type);
3205 try {
3206 if ($desc->hasExampleNumber()) {
3207 return $this->parse($desc->getExampleNumber(), $regionCodeOrType);
3208 }
3209 } catch (NumberParseException $e) {
3210 // noop
3211 }
3212 return null;
3213 }
3214
3215 /**
3216 * @param PhoneMetadata $metadata
3217 * @param int $type PhoneNumberType
3218 * @return PhoneNumberDesc
3219 */
3220 protected function getNumberDescByType(PhoneMetadata $metadata, $type)
3221 {
3222 switch ($type) {
3223 case PhoneNumberType::PREMIUM_RATE:
3224 return $metadata->getPremiumRate();
3225 case PhoneNumberType::TOLL_FREE:
3226 return $metadata->getTollFree();
3227 case PhoneNumberType::MOBILE:
3228 return $metadata->getMobile();
3229 case PhoneNumberType::FIXED_LINE:
3230 case PhoneNumberType::FIXED_LINE_OR_MOBILE:
3231 return $metadata->getFixedLine();
3232 case PhoneNumberType::SHARED_COST:
3233 return $metadata->getSharedCost();
3234 case PhoneNumberType::VOIP:
3235 return $metadata->getVoip();
3236 case PhoneNumberType::PERSONAL_NUMBER:
3237 return $metadata->getPersonalNumber();
3238 case PhoneNumberType::PAGER:
3239 return $metadata->getPager();
3240 case PhoneNumberType::UAN:
3241 return $metadata->getUan();
3242 case PhoneNumberType::VOICEMAIL:
3243 return $metadata->getVoicemail();
3244 default:
3245 return $metadata->getGeneralDesc();
3246 }
3247 }
3248
3249 /**
3250 * Gets a valid number for the specified country calling code for a non-geographical entity.
3251 *
3252 * @param int $countryCallingCode the country calling code for a non-geographical entity
3253 * @return PhoneNumber a valid number for the non-geographical entity. Returns null when the metadata
3254 * does not contain such information, or the country calling code passed in does not belong
3255 * to a non-geographical entity.
3256 */
3257 public function getExampleNumberForNonGeoEntity($countryCallingCode)
3258 {
3259 $metadata = $this->getMetadataForNonGeographicalRegion($countryCallingCode);
3260 if ($metadata !== null) {
3261 // For geographical entities, fixed-line data is always present. However, for non-geographical
3262 // entities, this is not the case, so we have to go through different types to find the
3263 // example number. We don't check fixed-line or personal number since they aren't used by
3264 // non-geographical entities (if this changes, a unit-test will catch this.)
3265 /** @var PhoneNumberDesc[] $list */
3266 $list = array(
3267 $metadata->getMobile(),
3268 $metadata->getTollFree(),
3269 $metadata->getSharedCost(),
3270 $metadata->getVoip(),
3271 $metadata->getVoicemail(),
3272 $metadata->getUan(),
3273 $metadata->getPremiumRate(),
3274 );
3275 foreach ($list as $desc) {
3276 try {
3277 if ($desc !== null && $desc->hasExampleNumber()) {
3278 return $this->parse('+' . $countryCallingCode . $desc->getExampleNumber(), self::UNKNOWN_REGION);
3279 }
3280 } catch (NumberParseException $e) {
3281 // noop
3282 }
3283 }
3284 }
3285 return null;
3286 }
3287
3288
3289 /**
3290 * Takes two phone numbers and compares them for equality.
3291 *
3292 * <p>Returns EXACT_MATCH if the country_code, NSN, presence of a leading zero
3293 * for Italian numbers and any extension present are the same. Returns NSN_MATCH
3294 * if either or both has no region specified, and the NSNs and extensions are
3295 * the same. Returns SHORT_NSN_MATCH if either or both has no region specified,
3296 * or the region specified is the same, and one NSN could be a shorter version
3297 * of the other number. This includes the case where one has an extension
3298 * specified, and the other does not. Returns NO_MATCH otherwise. For example,
3299 * the numbers +1 345 657 1234 and 657 1234 are a SHORT_NSN_MATCH. The numbers
3300 * +1 345 657 1234 and 345 657 are a NO_MATCH.
3301 *
3302 * @param $firstNumberIn PhoneNumber|string First number to compare. If it is a
3303 * string it can contain formatting, and can have country calling code specified
3304 * with + at the start.
3305 * @param $secondNumberIn PhoneNumber|string Second number to compare. If it is a
3306 * string it can contain formatting, and can have country calling code specified
3307 * with + at the start.
3308 * @throws \InvalidArgumentException
3309 * @return int {MatchType} NOT_A_NUMBER, NO_MATCH,
3310 */
3311 public function isNumberMatch($firstNumberIn, $secondNumberIn)
3312 {
3313 if (is_string($firstNumberIn) && is_string($secondNumberIn)) {
3314 try {
3315 $firstNumberAsProto = $this->parse($firstNumberIn, static::UNKNOWN_REGION);
3316 return $this->isNumberMatch($firstNumberAsProto, $secondNumberIn);
3317 } catch (NumberParseException $e) {
3318 if ($e->getErrorType() === NumberParseException::INVALID_COUNTRY_CODE) {
3319 try {
3320 $secondNumberAsProto = $this->parse($secondNumberIn, static::UNKNOWN_REGION);
3321 return $this->isNumberMatch($secondNumberAsProto, $firstNumberIn);
3322 } catch (NumberParseException $e2) {
3323 if ($e2->getErrorType() === NumberParseException::INVALID_COUNTRY_CODE) {
3324 try {
3325 $firstNumberProto = new PhoneNumber();
3326 $secondNumberProto = new PhoneNumber();
3327 $this->parseHelper($firstNumberIn, null, false, false, $firstNumberProto);
3328 $this->parseHelper($secondNumberIn, null, false, false, $secondNumberProto);
3329 return $this->isNumberMatch($firstNumberProto, $secondNumberProto);
3330 } catch (NumberParseException $e3) {
3331 // Fall through and return MatchType::NOT_A_NUMBER
3332 }
3333 }
3334 }
3335 }
3336 }
3337 return MatchType::NOT_A_NUMBER;
3338 }
3339 if ($firstNumberIn instanceof PhoneNumber && is_string($secondNumberIn)) {
3340 // First see if the second number has an implicit country calling code, by attempting to parse
3341 // it.
3342 try {
3343 $secondNumberAsProto = $this->parse($secondNumberIn, static::UNKNOWN_REGION);
3344 return $this->isNumberMatch($firstNumberIn, $secondNumberAsProto);
3345 } catch (NumberParseException $e) {
3346 if ($e->getErrorType() === NumberParseException::INVALID_COUNTRY_CODE) {
3347 // The second number has no country calling code. EXACT_MATCH is no longer possible.
3348 // We parse it as if the region was the same as that for the first number, and if
3349 // EXACT_MATCH is returned, we replace this with NSN_MATCH.
3350 $firstNumberRegion = $this->getRegionCodeForCountryCode($firstNumberIn->getCountryCode());
3351 try {
3352 if ($firstNumberRegion != static::UNKNOWN_REGION) {
3353 $secondNumberWithFirstNumberRegion = $this->parse($secondNumberIn, $firstNumberRegion);
3354 $match = $this->isNumberMatch($firstNumberIn, $secondNumberWithFirstNumberRegion);
3355 if ($match === MatchType::EXACT_MATCH) {
3356 return MatchType::NSN_MATCH;
3357 }
3358 return $match;
3359 }
3360
3361 // If the first number didn't have a valid country calling code, then we parse the
3362 // second number without one as well.
3363 $secondNumberProto = new PhoneNumber();
3364 $this->parseHelper($secondNumberIn, null, false, false, $secondNumberProto);
3365 return $this->isNumberMatch($firstNumberIn, $secondNumberProto);
3366 } catch (NumberParseException $e2) {
3367 // Fall-through to return NOT_A_NUMBER.
3368 }
3369 }
3370 }
3371 }
3372 if ($firstNumberIn instanceof PhoneNumber && $secondNumberIn instanceof PhoneNumber) {
3373 // We only care about the fields that uniquely define a number, so we copy these across
3374 // explicitly.
3375 $firstNumber = self::copyCoreFieldsOnly($firstNumberIn);
3376 $secondNumber = self::copyCoreFieldsOnly($secondNumberIn);
3377
3378 // Early exit if both had extensions and these are different.
3379 if ($firstNumber->hasExtension() && $secondNumber->hasExtension() &&
3380 $firstNumber->getExtension() != $secondNumber->getExtension()
3381 ) {
3382 return MatchType::NO_MATCH;
3383 }
3384
3385 $firstNumberCountryCode = $firstNumber->getCountryCode();
3386 $secondNumberCountryCode = $secondNumber->getCountryCode();
3387 // Both had country_code specified.
3388 if ($firstNumberCountryCode != 0 && $secondNumberCountryCode != 0) {
3389 if ($firstNumber->equals($secondNumber)) {
3390 return MatchType::EXACT_MATCH;
3391 }
3392
3393 if ($firstNumberCountryCode == $secondNumberCountryCode &&
3394 $this->isNationalNumberSuffixOfTheOther($firstNumber, $secondNumber)) {
3395 // A SHORT_NSN_MATCH occurs if there is a difference because of the presence or absence of
3396 // an 'Italian leading zero', the presence or absence of an extension, or one NSN being a
3397 // shorter variant of the other.
3398 return MatchType::SHORT_NSN_MATCH;
3399 }
3400 // This is not a match.
3401 return MatchType::NO_MATCH;
3402 }
3403 // Checks cases where one or both country_code fields were not specified. To make equality
3404 // checks easier, we first set the country_code fields to be equal.
3405 $firstNumber->setCountryCode($secondNumberCountryCode);
3406 // If all else was the same, then this is an NSN_MATCH.
3407 if ($firstNumber->equals($secondNumber)) {
3408 return MatchType::NSN_MATCH;
3409 }
3410 if ($this->isNationalNumberSuffixOfTheOther($firstNumber, $secondNumber)) {
3411 return MatchType::SHORT_NSN_MATCH;
3412 }
3413 return MatchType::NO_MATCH;
3414 }
3415 return MatchType::NOT_A_NUMBER;
3416 }
3417
3418 /**
3419 * Returns true when one national number is the suffix of the other or both are the same.
3420 * @param PhoneNumber $firstNumber
3421 * @param PhoneNumber $secondNumber
3422 * @return bool
3423 */
3424 protected function isNationalNumberSuffixOfTheOther(PhoneNumber $firstNumber, PhoneNumber $secondNumber)
3425 {
3426 $firstNumberNationalNumber = trim((string)$firstNumber->getNationalNumber());
3427 $secondNumberNationalNumber = trim((string)$secondNumber->getNationalNumber());
3428 return $this->stringEndsWithString($firstNumberNationalNumber, $secondNumberNationalNumber) ||
3429 $this->stringEndsWithString($secondNumberNationalNumber, $firstNumberNationalNumber);
3430 }
3431
3432 protected function stringEndsWithString($hayStack, $needle)
3433 {
3434 $revNeedle = strrev($needle);
3435 $revHayStack = strrev($hayStack);
3436 return strpos($revHayStack, $revNeedle) === 0;
3437 }
3438
3439 /**
3440 * Returns true if the supplied region supports mobile number portability. Returns false for
3441 * invalid, unknown or regions that don't support mobile number portability.
3442 *
3443 * @param string $regionCode the region for which we want to know whether it supports mobile number
3444 * portability or not.
3445 * @return bool
3446 */
3447 public function isMobileNumberPortableRegion($regionCode)
3448 {
3449 $metadata = $this->getMetadataForRegion($regionCode);
3450 if ($metadata === null) {
3451 return false;
3452 }
3453
3454 return $metadata->isMobileNumberPortableRegion();
3455 }
3456
3457 /**
3458 * Check whether a phone number is a possible number given a number in the form of a string, and
3459 * the region where the number could be dialed from. It provides a more lenient check than
3460 * {@link #isValidNumber}. See {@link #isPossibleNumber(PhoneNumber)} for details.
3461 *
3462 * Convenience wrapper around {@link #isPossibleNumberWithReason}. Instead of returning the reason
3463 * for failure, this method returns a boolean value.
3464 * for failure, this method returns true if the number is either a possible fully-qualified number
3465 * (containing the area code and country code), or if the number could be a possible local number
3466 * (with a country code, but missing an area code). Local numbers are considered possible if they
3467 * could be possibly dialled in this format: if the area code is needed for a call to connect, the
3468 * number is not considered possible without it.
3469 *
3470 * Note: There are two ways to call this method.
3471 *
3472 * isPossibleNumber(PhoneNumber $numberObject)
3473 * isPossibleNumber(string '+441174960126', string 'GB')
3474 *
3475 * @param PhoneNumber|string $number the number that needs to be checked, in the form of a string
3476 * @param string|null $regionDialingFrom the region that we are expecting the number to be dialed from.
3477 * Note this is different from the region where the number belongs. For example, the number
3478 * +1 650 253 0000 is a number that belongs to US. When written in this form, it can be
3479 * dialed from any region. When it is written as 00 1 650 253 0000, it can be dialed from any
3480 * region which uses an international dialling prefix of 00. When it is written as
3481 * 650 253 0000, it can only be dialed from within the US, and when written as 253 0000, it
3482 * can only be dialed from within a smaller area in the US (Mountain View, CA, to be more
3483 * specific).
3484 * @return boolean true if the number is possible
3485 */
3486 public function isPossibleNumber($number, $regionDialingFrom = null)
3487 {
3488 if ($regionDialingFrom !== null && is_string($number)) {
3489 try {
3490 return $this->isPossibleNumber($this->parse($number, $regionDialingFrom));
3491 } catch (NumberParseException $e) {
3492 return false;
3493 }
3494 } else {
3495 $result = $this->isPossibleNumberWithReason($number);
3496 return $result === ValidationResult::IS_POSSIBLE
3497 || $result === ValidationResult::IS_POSSIBLE_LOCAL_ONLY;
3498 }
3499 }
3500
3501
3502 /**
3503 * Check whether a phone number is a possible number. It provides a more lenient check than
3504 * {@link #isValidNumber} in the following sense:
3505 * <ol>
3506 * <li> It only checks the length of phone numbers. In particular, it doesn't check starting
3507 * digits of the number.
3508 * <li> It doesn't attempt to figure out the type of the number, but uses general rules which
3509 * applies to all types of phone numbers in a region. Therefore, it is much faster than
3510 * isValidNumber.
3511 * <li> For some numbers (particularly fixed-line), many regions have the concept of area code,
3512 * which together with subscriber number constitute the national significant number. It is
3513 * sometimes okay to dial only the subscriber number when dialing in the same area. This
3514 * function will return IS_POSSIBLE_LOCAL_ONLY if the subscriber-number-only version is
3515 * passed in. On the other hand, because isValidNumber validates using information on both
3516 * starting digits (for fixed line numbers, that would most likely be area codes) and
3517 * length (obviously includes the length of area codes for fixed line numbers), it will
3518 * return false for the subscriber-number-only version.
3519 * </ol>
3520 * @param PhoneNumber $number the number that needs to be checked
3521 * @return int a ValidationResult object which indicates whether the number is possible
3522 */
3523 public function isPossibleNumberWithReason(PhoneNumber $number)
3524 {
3525 return $this->isPossibleNumberForTypeWithReason($number, PhoneNumberType::UNKNOWN);
3526 }
3527
3528 /**
3529 * Check whether a phone number is a possible number of a particular type. For types that don't
3530 * exist in a particular region, this will return a result that isn't so useful; it is recommended
3531 * that you use {@link #getSupportedTypesForRegion} or {@link #getSupportedTypesForNonGeoEntity}
3532 * respectively before calling this method to determine whether you should call it for this number
3533 * at all.
3534 *
3535 * This provides a more lenient check than {@link #isValidNumber} in the following sense:
3536 *
3537 * <ol>
3538 * <li> It only checks the length of phone numbers. In particular, it doesn't check starting
3539 * digits of the number.
3540 * <li> For some numbers (particularly fixed-line), many regions have the concept of area code,
3541 * which together with subscriber number constitute the national significant number. It is
3542 * sometimes okay to dial only the subscriber number when dialing in the same area. This
3543 * function will return IS_POSSIBLE_LOCAL_ONLY if the subscriber-number-only version is
3544 * passed in. On the other hand, because isValidNumber validates using information on both
3545 * starting digits (for fixed line numbers, that would most likely be area codes) and
3546 * length (obviously includes the length of area codes for fixed line numbers), it will
3547 * return false for the subscriber-number-only version.
3548 * </ol>
3549 *
3550 * @param PhoneNumber $number the number that needs to be checked
3551 * @param int $type the PhoneNumberType we are interested in
3552 * @return int a ValidationResult object which indicates whether the number is possible
3553 */
3554 public function isPossibleNumberForTypeWithReason(PhoneNumber $number, $type)
3555 {
3556 $nationalNumber = $this->getNationalSignificantNumber($number);
3557 $countryCode = $number->getCountryCode();
3558
3559 // Note: For regions that share a country calling code, like NANPA numbers, we just use the
3560 // rules from the default region (US in this case) since the getRegionCodeForNumber will not
3561 // work if the number is possible but not valid. There is in fact one country calling code (290)
3562 // where the possible number pattern differs between various regions (Saint Helena and Tristan
3563 // da Cuñha), but this is handled by putting all possible lengths for any country with this
3564 // country calling code in the metadata for the default region in this case.
3565 if (!$this->hasValidCountryCallingCode($countryCode)) {
3566 return ValidationResult::INVALID_COUNTRY_CODE;
3567 }
3568
3569 $regionCode = $this->getRegionCodeForCountryCode($countryCode);
3570 // Metadata cannot be null because the country calling code is valid.
3571 $metadata = $this->getMetadataForRegionOrCallingCode($countryCode, $regionCode);
3572 return $this->testNumberLength($nationalNumber, $metadata, $type);
3573 }
3574
3575 /**
3576 * Attempts to extract a valid number from a phone number that is too long to be valid, and resets
3577 * the PhoneNumber object passed in to that valid version. If no valid number could be extracted,
3578 * the PhoneNumber object passed in will not be modified.
3579 * @param PhoneNumber $number a PhoneNumber object which contains a number that is too long to be valid.
3580 * @return boolean true if a valid phone number can be successfully extracted.
3581 */
3582 public function truncateTooLongNumber(PhoneNumber $number)
3583 {
3584 if ($this->isValidNumber($number)) {
3585 return true;
3586 }
3587 $numberCopy = new PhoneNumber();
3588 $numberCopy->mergeFrom($number);
3589 $nationalNumber = $number->getNationalNumber();
3590 do {
3591 $nationalNumber = floor($nationalNumber / 10);
3592 $numberCopy->setNationalNumber($nationalNumber);
3593 if ($this->isPossibleNumberWithReason($numberCopy) == ValidationResult::TOO_SHORT || $nationalNumber == 0) {
3594 return false;
3595 }
3596 } while (!$this->isValidNumber($numberCopy));
3597 $number->setNationalNumber($nationalNumber);
3598 return true;
3599 }
3600 }
3601