/** * Extracts the pattern for international format. If there is no intlFormat, default to using the * national format. If the intlFormat is set to "NA" the intlFormat should be ignored. * * @param PhoneMetadata $metadata * @param \DOMElement $numberFormatElement * @param NumberFormat $nationalFormat * @throws \RuntimeException if multiple intlFormats have been encountered. * @return bool whether an international number format is defined. */ private static function loadInternationalFormat(PhoneMetadata $metadata, \DOMElement $numberFormatElement, NumberFormat $nationalFormat) { $intlFormat = new NumberFormat(); $intlFormatPattern = $numberFormatElement->getElementsByTagName(self::INTL_FORMAT); $hasExplicitIntlFormatDefined = false; if ($intlFormatPattern->length > 1) { $countryId = strlen($metadata->getId()) > 0 ? $metadata->getId() : $metadata->getCountryCode(); throw new \RuntimeException("Invalid number of intlFormat patterns for country: " . $countryId); } elseif ($intlFormatPattern->length == 0) { // Default to use the same as the national pattern if none is defined. $intlFormat->mergeFrom($nationalFormat); } else { $intlFormat->setPattern($numberFormatElement->getAttribute(self::PATTERN)); self::setLeadingDigitsPatterns($numberFormatElement, $intlFormat); $intlFormatPatternValue = $intlFormatPattern->item(0)->firstChild->nodeValue; if ($intlFormatPatternValue !== "NA") { $intlFormat->setFormat($intlFormatPatternValue); } $hasExplicitIntlFormatDefined = true; } if ($intlFormat->hasFormat()) { $metadata->addIntlNumberFormat($intlFormat); } return $hasExplicitIntlFormatDefined; }
/** * Tries to extract a country calling code from a number. This method will return zero if no * country calling code is considered to be present. Country calling codes are extracted in the * following ways: * <ul> * <li> by stripping the international dialing prefix of the region the person is dialing from, * if this is present in the number, and looking at the next digits * <li> by stripping the '+' sign if present and then looking at the next digits * <li> by comparing the start of the number and the country calling code of the default region. * If the number is not considered possible for the numbering plan of the default region * initially, but starts with the country calling code of this region, validation will be * reattempted after stripping this country calling code. If this number is considered a * possible number, then the first digits will be considered the country calling code and * removed as such. * </ul> * It will throw a NumberParseException if the number starts with a '+' but the country calling * code supplied after this does not match that of any known region. * * @param string $number non-normalized telephone number that we wish to extract a country calling * code from - may begin with '+' * @param PhoneMetadata $defaultRegionMetadata metadata about the region this number may be from * @param string $nationalNumber a string buffer to store the national significant number in, in the case * that a country calling code was extracted. The number is appended to any existing contents. * If no country calling code was extracted, this will be left unchanged. * @param bool $keepRawInput true if the country_code_source and preferred_carrier_code fields of * phoneNumber should be populated. * @param PhoneNumber $phoneNumber the PhoneNumber object where the country_code and country_code_source need * to be populated. Note the country_code is always populated, whereas country_code_source is * only populated when keepCountryCodeSource is true. * @return int the country calling code extracted or 0 if none could be extracted * @throws NumberParseException */ public function maybeExtractCountryCode($number, PhoneMetadata $defaultRegionMetadata = null, &$nationalNumber, $keepRawInput, PhoneNumber $phoneNumber) { if (mb_strlen($number) == 0) { return 0; } $fullNumber = $number; // Set the default prefix to be something that will never match. $possibleCountryIddPrefix = "NonMatch"; if ($defaultRegionMetadata !== null) { $possibleCountryIddPrefix = $defaultRegionMetadata->getInternationalPrefix(); } $countryCodeSource = $this->maybeStripInternationalPrefixAndNormalize($fullNumber, $possibleCountryIddPrefix); if ($keepRawInput) { $phoneNumber->setCountryCodeSource($countryCodeSource); } if ($countryCodeSource != CountryCodeSource::FROM_DEFAULT_COUNTRY) { if (mb_strlen($fullNumber) <= self::MIN_LENGTH_FOR_NSN) { throw new NumberParseException(NumberParseException::TOO_SHORT_AFTER_IDD, "Phone number had an IDD, but after this was not " . "long enough to be a viable phone number."); } $potentialCountryCode = $this->extractCountryCode($fullNumber, $nationalNumber); if ($potentialCountryCode != 0) { $phoneNumber->setCountryCode($potentialCountryCode); return $potentialCountryCode; } // If this fails, they must be using a strange country calling code that we don't recognize, // or that doesn't exist. throw new NumberParseException(NumberParseException::INVALID_COUNTRY_CODE, "Country calling code supplied was not recognised."); } else { if ($defaultRegionMetadata !== null) { // Check to see if the number starts with the country calling code for the default region. If // so, we remove the country calling code, and do some checks on the validity of the number // before and after. $defaultCountryCode = $defaultRegionMetadata->getCountryCode(); $defaultCountryCodeString = (string) $defaultCountryCode; $normalizedNumber = (string) $fullNumber; if (strpos($normalizedNumber, $defaultCountryCodeString) === 0) { $potentialNationalNumber = substr($normalizedNumber, mb_strlen($defaultCountryCodeString)); $generalDesc = $defaultRegionMetadata->getGeneralDesc(); $validNumberPattern = $generalDesc->getNationalNumberPattern(); // Don't need the carrier code. $carriercode = null; $this->maybeStripNationalPrefixAndCarrierCode($potentialNationalNumber, $defaultRegionMetadata, $carriercode); $possibleNumberPattern = $generalDesc->getPossibleNumberPattern(); // If the number was not valid before but is valid now, or if it was too long before, we // consider the number with the country calling code stripped to be a better result and // keep that instead. if (preg_match('/^(' . $validNumberPattern . ')$/x', $fullNumber) == 0 && preg_match('/^(' . $validNumberPattern . ')$/x', $potentialNationalNumber) > 0 || $this->testNumberLengthAgainstPattern($possibleNumberPattern, (string) $fullNumber) == ValidationResult::TOO_LONG) { $nationalNumber .= $potentialNationalNumber; if ($keepRawInput) { $phoneNumber->setCountryCodeSource(CountryCodeSource::FROM_NUMBER_WITHOUT_PLUS_SIGN); } $phoneNumber->setCountryCode($defaultCountryCode); return $defaultCountryCode; } } } } // No country calling code present. $phoneNumber->setCountryCode(0); return 0; }