| 1 | <?php |
||
| 2 | |||
| 3 | namespace libphonenumber\geocoding; |
||
| 4 | |||
| 5 | use Giggsey\Locale\Locale; |
||
| 6 | use libphonenumber\NumberParseException; |
||
| 7 | use libphonenumber\PhoneNumber; |
||
| 8 | use libphonenumber\PhoneNumberType; |
||
| 9 | use libphonenumber\PhoneNumberUtil; |
||
| 10 | use libphonenumber\prefixmapper\PrefixFileReader; |
||
| 11 | |||
| 12 | class PhoneNumberOfflineGeocoder |
||
| 13 | { |
||
| 14 | const MAPPING_DATA_DIRECTORY = '/data'; |
||
| 15 | /** |
||
| 16 | * @var PhoneNumberOfflineGeocoder |
||
| 17 | */ |
||
| 18 | protected static $instance; |
||
| 19 | /** |
||
| 20 | * @var PhoneNumberUtil |
||
| 21 | */ |
||
| 22 | protected $phoneUtil; |
||
| 23 | /** |
||
| 24 | * @var PrefixFileReader |
||
| 25 | */ |
||
| 26 | protected $prefixFileReader; |
||
| 27 | |||
| 28 | /** |
||
| 29 | * PhoneNumberOfflineGeocoder constructor. |
||
| 30 | * @param string $phonePrefixDataDirectory |
||
| 31 | */ |
||
| 32 | 246 | protected function __construct($phonePrefixDataDirectory) |
|
| 33 | { |
||
| 34 | 246 | $this->phoneUtil = PhoneNumberUtil::getInstance(); |
|
| 35 | |||
| 36 | 246 | $this->prefixFileReader = new PrefixFileReader(__DIR__ . DIRECTORY_SEPARATOR . $phonePrefixDataDirectory); |
|
| 37 | 246 | } |
|
| 38 | |||
| 39 | /** |
||
| 40 | * Gets a PhoneNumberOfflineGeocoder instance to carry out international phone number geocoding. |
||
| 41 | * |
||
| 42 | * <p>The PhoneNumberOfflineGeocoder is implemented as a singleton. Therefore, calling this method |
||
| 43 | * multiple times will only result in one instance being created. |
||
| 44 | * |
||
| 45 | * @param string $mappingDir (Optional) Mapping Data Directory |
||
| 46 | * @return PhoneNumberOfflineGeocoder |
||
| 47 | */ |
||
| 48 | 251 | public static function getInstance($mappingDir = self::MAPPING_DATA_DIRECTORY) |
|
| 49 | { |
||
| 50 | 251 | if (static::$instance === null) { |
|
| 51 | 246 | static::$instance = new static($mappingDir); |
|
| 52 | } |
||
| 53 | |||
| 54 | 251 | return static::$instance; |
|
| 55 | } |
||
| 56 | |||
| 57 | 246 | public static function resetInstance() |
|
| 58 | { |
||
| 59 | 246 | static::$instance = null; |
|
| 60 | 246 | } |
|
| 61 | |||
| 62 | /** |
||
| 63 | * As per getDescriptionForValidNumber, but explicitly checks the validity of the number |
||
| 64 | * passed in. |
||
| 65 | * |
||
| 66 | * |
||
| 67 | * @see getDescriptionForValidNumber |
||
| 68 | * @param PhoneNumber $number a valid phone number for which we want to get a text description |
||
| 69 | * @param string $locale the language code for which the description should be written |
||
| 70 | * @param string $userRegion the region code for a given user. This region will be omitted from the |
||
| 71 | * description if the phone number comes from this region. It is a two-letter uppercase CLDR region |
||
| 72 | * code. |
||
| 73 | * @return string a text description for the given language code for the given phone number, or empty |
||
| 74 | * string if the number passed in is invalid |
||
| 75 | */ |
||
| 76 | 17 | public function getDescriptionForNumber(PhoneNumber $number, $locale, $userRegion = null) |
|
| 77 | { |
||
| 78 | 17 | $numberType = $this->phoneUtil->getNumberType($number); |
|
| 79 | |||
| 80 | 17 | if ($numberType === PhoneNumberType::UNKNOWN) { |
|
| 81 | 3 | return ''; |
|
| 82 | } |
||
| 83 | |||
| 84 | 16 | if (!$this->phoneUtil->isNumberGeographical($numberType, $number->getCountryCode())) { |
|
| 85 | 5 | return $this->getCountryNameForNumber($number, $locale); |
|
| 86 | } |
||
| 87 | |||
| 88 | 12 | return $this->getDescriptionForValidNumber($number, $locale, $userRegion); |
|
| 89 | } |
||
| 90 | |||
| 91 | /** |
||
| 92 | * Returns the customary display name in the given language for the given territory the phone |
||
| 93 | * number is from. If it could be from many territories, nothing is returned. |
||
| 94 | * |
||
| 95 | * @param PhoneNumber $number |
||
| 96 | * @param string $locale |
||
| 97 | * @return string |
||
| 98 | */ |
||
| 99 | 8 | protected function getCountryNameForNumber(PhoneNumber $number, $locale) |
|
| 100 | { |
||
| 101 | 8 | $regionCodes = $this->phoneUtil->getRegionCodesForCountryCode($number->getCountryCode()); |
|
| 102 | |||
| 103 | 8 | if (\count($regionCodes) === 1) { |
|
| 104 | 4 | return $this->getRegionDisplayName($regionCodes[0], $locale); |
|
| 105 | } |
||
| 106 | |||
| 107 | 5 | $regionWhereNumberIsValid = 'ZZ'; |
|
| 108 | 5 | foreach ($regionCodes as $regionCode) { |
|
| 109 | 5 | if ($this->phoneUtil->isValidNumberForRegion($number, $regionCode)) { |
|
| 110 | // If the number has already been found valid for one region, then we don't know which |
||
| 111 | // region it belongs to so we return nothing. |
||
| 112 | 5 | if ($regionWhereNumberIsValid !== 'ZZ') { |
|
| 113 | 1 | return ''; |
|
| 114 | } |
||
| 115 | 5 | $regionWhereNumberIsValid = $regionCode; |
|
| 116 | } |
||
| 117 | } |
||
| 118 | |||
| 119 | 4 | return $this->getRegionDisplayName($regionWhereNumberIsValid, $locale); |
|
| 120 | } |
||
| 121 | |||
| 122 | /** |
||
| 123 | * Returns the customary display name in the given language for the given region. |
||
| 124 | * |
||
| 125 | * @param $regionCode |
||
| 126 | * @param $locale |
||
| 127 | * @return string |
||
| 128 | */ |
||
| 129 | 241 | protected function getRegionDisplayName($regionCode, $locale) |
|
| 130 | { |
||
| 131 | 241 | if ($regionCode === null || $regionCode == 'ZZ' || $regionCode === PhoneNumberUtil::REGION_CODE_FOR_NON_GEO_ENTITY) { |
|
| 132 | 1 | return ''; |
|
| 133 | } |
||
| 134 | |||
| 135 | 241 | return Locale::getDisplayRegion( |
|
| 136 | 241 | '-' . $regionCode, |
|
| 137 | $locale |
||
| 138 | ); |
||
| 139 | } |
||
| 140 | |||
| 141 | /** |
||
| 142 | * Returns a text description for the given phone number, in the language provided. The |
||
| 143 | * description might consist of the name of the country where the phone number is from, or the |
||
| 144 | * name of the geographical area the phone number is from if more detailed information is |
||
| 145 | * available. |
||
| 146 | * |
||
| 147 | * <p>This method assumes the validity of the number passed in has already been checked, and that |
||
| 148 | * the number is suitable for geocoding. We consider fixed-line and mobile numbers possible |
||
| 149 | * candidates for geocoding. |
||
| 150 | * |
||
| 151 | * <p>If $userRegion is set, we also consider the region of the user. If the phone number is from |
||
| 152 | * the same region as the user, only a lower-level description will be returned, if one exists. |
||
| 153 | * Otherwise, the phone number's region will be returned, with optionally some more detailed |
||
| 154 | * information. |
||
| 155 | * |
||
| 156 | * <p>For example, for a user from the region "US" (United States), we would show "Mountain View, |
||
| 157 | * CA" for a particular number, omitting the United States from the description. For a user from |
||
| 158 | * the United Kingdom (region "GB"), for the same number we may show "Mountain View, CA, United |
||
| 159 | * States" or even just "United States". |
||
| 160 | * |
||
| 161 | * @param PhoneNumber $number a valid phone number for which we want to get a text description |
||
| 162 | * @param string $locale the language code for which the description should be written |
||
| 163 | * @param string $userRegion the region code for a given user. This region will be omitted from the |
||
| 164 | * description if the phone number comes from this region. It is a two-letter upper-case CLDR |
||
| 165 | * region code. |
||
| 166 | * @return string a text description for the given language code for the given phone number, or an |
||
| 167 | * empty string if the number could come from multiple countries, or the country code is |
||
| 168 | * in fact invalid |
||
| 169 | */ |
||
| 170 | 245 | public function getDescriptionForValidNumber(PhoneNumber $number, $locale, $userRegion = null) |
|
| 171 | { |
||
| 172 | // If the user region matches the number's region, then we just show the lower-level |
||
| 173 | // description, if one exists - if no description exists, we will show the region(country) name |
||
| 174 | // for the number. |
||
| 175 | 245 | $regionCode = $this->phoneUtil->getRegionCodeForNumber($number); |
|
| 176 | 245 | if ($userRegion == null || $userRegion == $regionCode) { |
|
|
0 ignored issues
–
show
Bug
introduced
by
Loading history...
|
|||
| 177 | 12 | $languageStr = Locale::getPrimaryLanguage($locale); |
|
| 178 | 12 | $scriptStr = ''; |
|
| 179 | 12 | $regionStr = Locale::getRegion($locale); |
|
| 180 | |||
| 181 | 12 | $mobileToken = PhoneNumberUtil::getCountryMobileToken($number->getCountryCode()); |
|
| 182 | 12 | $nationalNumber = $this->phoneUtil->getNationalSignificantNumber($number); |
|
| 183 | 12 | if ($mobileToken !== '' && (!\strncmp($nationalNumber, $mobileToken, \strlen($mobileToken)))) { |
|
| 184 | // In some countries, eg. Argentina, mobile numbers have a mobile token before the national |
||
| 185 | // destination code, this should be removed before geocoding. |
||
| 186 | 1 | $nationalNumber = \substr($nationalNumber, \strlen($mobileToken)); |
|
| 187 | 1 | $region = $this->phoneUtil->getRegionCodeForCountryCode($number->getCountryCode()); |
|
| 188 | try { |
||
| 189 | 1 | $copiedNumber = $this->phoneUtil->parse($nationalNumber, $region); |
|
| 190 | } catch (NumberParseException $e) { |
||
| 191 | // If this happens, just reuse what we had. |
||
| 192 | $copiedNumber = $number; |
||
| 193 | } |
||
| 194 | 1 | $areaDescription = $this->prefixFileReader->getDescriptionForNumber($copiedNumber, $languageStr, $scriptStr, $regionStr); |
|
| 195 | } else { |
||
| 196 | 11 | $areaDescription = $this->prefixFileReader->getDescriptionForNumber($number, $languageStr, $scriptStr, $regionStr); |
|
| 197 | } |
||
| 198 | |||
| 199 | 12 | return (\strlen($areaDescription) > 0) ? $areaDescription : $this->getCountryNameForNumber($number, $locale); |
|
| 200 | } |
||
| 201 | // Otherwise, we just show the region(country) name for now. |
||
| 202 | 234 | return $this->getRegionDisplayName($regionCode, $locale); |
|
| 203 | // TODO: Concatenate the lower-level and country-name information in an appropriate |
||
| 204 | // way for each language. |
||
| 205 | } |
||
| 206 | } |
||
| 207 |