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