| 1 | <?php |
||||||
| 2 | |||||||
| 3 | namespace libphonenumber; |
||||||
| 4 | |||||||
| 5 | use libphonenumber\Leniency\AbstractLeniency; |
||||||
| 6 | |||||||
| 7 | /** |
||||||
| 8 | * A class that finds and extracts telephone numbers from $text. |
||||||
| 9 | * Instances can be created using PhoneNumberUtil::findNumbers() |
||||||
| 10 | * |
||||||
| 11 | * Vanity numbers (phone numbers using alphabetic digits such as '1-800-SIX-FLAGS' are |
||||||
| 12 | * not found. |
||||||
| 13 | * |
||||||
| 14 | * @package libphonenumber |
||||||
| 15 | */ |
||||||
| 16 | class PhoneNumberMatcher implements \Iterator |
||||||
| 17 | { |
||||||
| 18 | protected static $initialized = false; |
||||||
| 19 | |||||||
| 20 | /** |
||||||
| 21 | * The phone number pattern used by $this->find(), similar to |
||||||
| 22 | * PhoneNumberUtil::VALID_PHONE_NUMBER, but with the following differences: |
||||||
| 23 | * <ul> |
||||||
| 24 | * <li>All captures are limited in order to place an upper bound to the text matched by the |
||||||
| 25 | * pattern. |
||||||
| 26 | * <ul> |
||||||
| 27 | * <li>Leading punctuation / plus signs are limited. |
||||||
| 28 | * <li>Consecutive occurrences of punctuation are limited. |
||||||
| 29 | * <li>Number of digits is limited. |
||||||
| 30 | * </ul> |
||||||
| 31 | * <li>No whitespace is allowed at the start or end. |
||||||
| 32 | * <li>No alpha digits (vanity numbers such as 1-800-SIX-FLAGS) are currently supported. |
||||||
| 33 | * </ul> |
||||||
| 34 | * |
||||||
| 35 | * @var string |
||||||
| 36 | */ |
||||||
| 37 | protected static $pattern; |
||||||
| 38 | |||||||
| 39 | /** |
||||||
| 40 | * Matches strings that look like publication pages. Example: |
||||||
| 41 | * <pre>Computing Complete Answers to Queries in the Presence of Limited Access Patterns. |
||||||
| 42 | * Chen Li. VLDB J. 12(3): 211-227 (2003).</pre> |
||||||
| 43 | * |
||||||
| 44 | * The string "211-227 (2003)" is not a telephone number. |
||||||
| 45 | * |
||||||
| 46 | * @var string |
||||||
| 47 | */ |
||||||
| 48 | protected static $pubPages = "\\d{1,5}-+\\d{1,5}\\s{0,4}\\(\\d{1,4}"; |
||||||
| 49 | |||||||
| 50 | /** |
||||||
| 51 | * Matches strings that look like dates using "/" as a separator. Examples 3/10/2011, 31/10/2011 or |
||||||
| 52 | * 08/31/95. |
||||||
| 53 | * |
||||||
| 54 | * @var string |
||||||
| 55 | */ |
||||||
| 56 | protected static $slashSeparatedDates = "(?:(?:[0-3]?\\d/[01]?\\d)|(?:[01]?\\d/[0-3]?\\d))/(?:[12]\\d)?\\d{2}"; |
||||||
| 57 | |||||||
| 58 | /** |
||||||
| 59 | * Matches timestamps. Examples: "2012-01-02 08:00". Note that the reg-ex does not include the |
||||||
| 60 | * trailing ":\d\d" -- that is covered by timeStampsSuffix. |
||||||
| 61 | * |
||||||
| 62 | * @var string |
||||||
| 63 | */ |
||||||
| 64 | protected static $timeStamps = "[12]\\d{3}[-/]?[01]\\d[-/]?[0-3]\\d +[0-2]\\d$"; |
||||||
| 65 | protected static $timeStampsSuffix = ":[0-5]\\d"; |
||||||
| 66 | |||||||
| 67 | /** |
||||||
| 68 | * Pattern to check that brackets match. Opening brackets should be closed within a phone number. |
||||||
| 69 | * This also checks that there is something inside the brackets. Having no brackets at all is also |
||||||
| 70 | * fine. |
||||||
| 71 | * |
||||||
| 72 | * @var string |
||||||
| 73 | */ |
||||||
| 74 | protected static $matchingBrackets; |
||||||
| 75 | |||||||
| 76 | /** |
||||||
| 77 | * Patterns used to extract phone numbers from a larger phone-number-like pattern. These are |
||||||
| 78 | * ordered according to specificity. For example, white-space is last since that is frequently |
||||||
| 79 | * used in numbers, not just to separate two numbers. We have separate patterns since we don't |
||||||
| 80 | * want to break up the phone-number-like text on more than one different kind of symbol at one |
||||||
| 81 | * time, although symbols of the same type (e.g. space) can be safely grouped together. |
||||||
| 82 | * |
||||||
| 83 | * Note that if there is a match, we will always check any text found up to the first match as |
||||||
| 84 | * well. |
||||||
| 85 | * |
||||||
| 86 | * @var string[] |
||||||
| 87 | */ |
||||||
| 88 | protected static $innerMatches = array(); |
||||||
| 89 | |||||||
| 90 | /** |
||||||
| 91 | * Punctuation that may be at the start of a phone number - brackets and plus signs. |
||||||
| 92 | * |
||||||
| 93 | * @var string |
||||||
| 94 | */ |
||||||
| 95 | protected static $leadClass; |
||||||
| 96 | |||||||
| 97 | /** |
||||||
| 98 | * Prefix of the files |
||||||
| 99 | * @var string |
||||||
| 100 | */ |
||||||
| 101 | protected static $alternateFormatsFilePrefix; |
||||||
| 102 | const META_DATA_FILE_PREFIX = 'PhoneNumberAlternateFormats'; |
||||||
| 103 | |||||||
| 104 | 1 | protected static function init() |
|||||
| 105 | { |
||||||
| 106 | 1 | static::$alternateFormatsFilePrefix = \dirname(__FILE__) . '/data/' . static::META_DATA_FILE_PREFIX; |
|||||
| 107 | |||||||
| 108 | 1 | static::$innerMatches = array( |
|||||
| 109 | // Breaks on the slash - e.g. "651-234-2345/332-445-1234" |
||||||
| 110 | '/+(.*)', |
||||||
| 111 | // Note that the bracket here is inside the capturing group, since we consider it part of the |
||||||
| 112 | // phone number. Will match a pattern like "(650) 223 3345 (754) 223 3321". |
||||||
| 113 | "(\\([^(]*)", |
||||||
| 114 | // Breaks on a hyphen - e.g. "12345 - 332-445-1234 is my number." |
||||||
| 115 | // We require a space on either side of the hyphen for it to be considered a separator. |
||||||
| 116 | "(?:\\p{Z}-|-\\p{Z})\\p{Z}*(.+)", |
||||||
| 117 | // Various types of wide hyphens. Note we have decided not to enforce a space here, since it's |
||||||
| 118 | // possible that it's supposed to be used to break two numbers without spaces, and we haven't |
||||||
| 119 | // seen many instances of it used within a number. |
||||||
| 120 | "[‒-―-]\\p{Z}*(.+)", |
||||||
| 121 | // Breaks on a full stop - e.g. "12345. 332-445-1234 is my number." |
||||||
| 122 | "\\.+\\p{Z}*([^.]+)", |
||||||
| 123 | // Breaks on space - e.g. "3324451234 8002341234" |
||||||
| 124 | "\\p{Z}+(\\P{Z}+)" |
||||||
| 125 | ); |
||||||
| 126 | |||||||
| 127 | /* |
||||||
| 128 | * Builds the matchingBrackets and pattern regular expressions. The building blocks exist |
||||||
| 129 | * to make the pattern more easily understood. |
||||||
| 130 | */ |
||||||
| 131 | |||||||
| 132 | 1 | $openingParens = "(\\[\xEF\xBC\x88\xEF\xBC\xBB"; |
|||||
| 133 | 1 | $closingParens = ")\\]\xEF\xBC\x89\xEF\xBC\xBD"; |
|||||
| 134 | 1 | $nonParens = '[^' . $openingParens . $closingParens . ']'; |
|||||
| 135 | |||||||
| 136 | // Limit on the number of pairs of brackets in a phone number. |
||||||
| 137 | 1 | $bracketPairLimit = static::limit(0, 3); |
|||||
| 138 | |||||||
| 139 | /* |
||||||
| 140 | * An opening bracket at the beginning may not be closed, but subsequent ones should be. It's |
||||||
| 141 | * also possible that the leading bracket was dropped, so we shouldn't be surprised if we see a |
||||||
| 142 | * closing bracket first. We limit the sets of brackets in a phone number to four. |
||||||
| 143 | */ |
||||||
| 144 | 1 | static::$matchingBrackets = |
|||||
| 145 | 1 | '(?:[' . $openingParens . '])?' . '(?:' . $nonParens . '+' . '[' . $closingParens . '])?' |
|||||
| 146 | 1 | . $nonParens . '+' |
|||||
| 147 | 1 | . '(?:[' . $openingParens . ']' . $nonParens . '+[' . $closingParens . '])' . $bracketPairLimit |
|||||
| 148 | 1 | . $nonParens . '*'; |
|||||
| 149 | |||||||
| 150 | // Limit on the number of leading (plus) characters. |
||||||
| 151 | 1 | $leadLimit = static::limit(0, 2); |
|||||
| 152 | |||||||
| 153 | // Limit on the number of consecutive punctuation characters. |
||||||
| 154 | 1 | $punctuationLimit = static::limit(0, 4); |
|||||
| 155 | |||||||
| 156 | /* |
||||||
| 157 | * The maximum number of digits allowed in a digit-separated block. As we allow all digits in a |
||||||
| 158 | * single block, set high enough to accommodate the entire national number and the international |
||||||
| 159 | * country code |
||||||
| 160 | */ |
||||||
| 161 | 1 | $digitBlockLimit = PhoneNumberUtil::MAX_LENGTH_FOR_NSN + PhoneNumberUtil::MAX_LENGTH_COUNTRY_CODE; |
|||||
| 162 | |||||||
| 163 | /* |
||||||
| 164 | * Limit on the number of blocks separated by the punctuation. Uses digitBlockLimit since some |
||||||
| 165 | * formats use spaces to separate each digit |
||||||
| 166 | */ |
||||||
| 167 | 1 | $blockLimit = static::limit(0, $digitBlockLimit); |
|||||
| 168 | |||||||
| 169 | // A punctuation sequence allowing white space |
||||||
| 170 | 1 | $punctuation = '[' . PhoneNumberUtil::VALID_PUNCTUATION . ']' . $punctuationLimit; |
|||||
| 171 | |||||||
| 172 | // A digits block without punctuation. |
||||||
| 173 | 1 | $digitSequence = "\\p{Nd}" . static::limit(1, $digitBlockLimit); |
|||||
| 174 | |||||||
| 175 | |||||||
| 176 | 1 | $leadClassChars = $openingParens . PhoneNumberUtil::PLUS_CHARS; |
|||||
| 177 | 1 | $leadClass = '[' . $leadClassChars . ']'; |
|||||
| 178 | 1 | static::$leadClass = $leadClass; |
|||||
| 179 | |||||||
| 180 | // Init extension patterns from PhoneNumberUtil |
||||||
| 181 | 1 | PhoneNumberUtil::initExtnPatterns(); |
|||||
| 182 | |||||||
| 183 | // Phone number pattern allowing optional punctuation. |
||||||
| 184 | 1 | static::$pattern = '(?:' . $leadClass . $punctuation . ')' . $leadLimit |
|||||
| 185 | 1 | . $digitSequence . '(?:' . $punctuation . $digitSequence . ')' . $blockLimit |
|||||
| 186 | 1 | . '(?:' . PhoneNumberUtil::$EXTN_PATTERNS_FOR_MATCHING . ')?'; |
|||||
| 187 | |||||||
| 188 | 1 | static::$initialized = true; |
|||||
| 189 | 1 | } |
|||||
| 190 | |||||||
| 191 | /** |
||||||
| 192 | * Helper function to generate regular expression with an upper and lower limit. |
||||||
| 193 | * |
||||||
| 194 | * @param int $lower |
||||||
| 195 | * @param int $upper |
||||||
| 196 | * @return string |
||||||
| 197 | */ |
||||||
| 198 | 1 | protected static function limit($lower, $upper) |
|||||
| 199 | { |
||||||
| 200 | 1 | if (($lower < 0) || ($upper <= 0) || ($upper < $lower)) { |
|||||
| 201 | throw new \InvalidArgumentException(); |
||||||
| 202 | } |
||||||
| 203 | |||||||
| 204 | 1 | return '{' . $lower . ',' . $upper . '}'; |
|||||
| 205 | } |
||||||
| 206 | |||||||
| 207 | /** |
||||||
| 208 | * The phone number utility. |
||||||
| 209 | * @var PhoneNumberUtil |
||||||
| 210 | */ |
||||||
| 211 | protected $phoneUtil; |
||||||
| 212 | |||||||
| 213 | /** |
||||||
| 214 | * The text searched for phone numbers. |
||||||
| 215 | * @var string |
||||||
| 216 | */ |
||||||
| 217 | protected $text; |
||||||
| 218 | |||||||
| 219 | /** |
||||||
| 220 | * The region (country) to assume for phone numbers without an international prefix, possibly |
||||||
| 221 | * null. |
||||||
| 222 | * @var string |
||||||
| 223 | */ |
||||||
| 224 | protected $preferredRegion; |
||||||
| 225 | |||||||
| 226 | /** |
||||||
| 227 | * The degrees of validation requested. |
||||||
| 228 | * @var AbstractLeniency |
||||||
| 229 | */ |
||||||
| 230 | protected $leniency; |
||||||
| 231 | |||||||
| 232 | /** |
||||||
| 233 | * The maximum number of retires after matching an invalid number. |
||||||
| 234 | * @var int |
||||||
| 235 | */ |
||||||
| 236 | protected $maxTries; |
||||||
| 237 | |||||||
| 238 | /** |
||||||
| 239 | * One of: |
||||||
| 240 | * - NOT_READY |
||||||
| 241 | * - READY |
||||||
| 242 | * - DONE |
||||||
| 243 | * @var string |
||||||
| 244 | */ |
||||||
| 245 | protected $state = 'NOT_READY'; |
||||||
| 246 | |||||||
| 247 | /** |
||||||
| 248 | * The last successful match, null unless $this->state = READY |
||||||
| 249 | * @var PhoneNumberMatch |
||||||
| 250 | */ |
||||||
| 251 | protected $lastMatch; |
||||||
| 252 | |||||||
| 253 | /** |
||||||
| 254 | * The next index to start searching at. Undefined when $this->state = DONE |
||||||
| 255 | * @var int |
||||||
| 256 | */ |
||||||
| 257 | protected $searchIndex = 0; |
||||||
| 258 | |||||||
| 259 | /** |
||||||
| 260 | * Creates a new instance. See the factory methods in PhoneNumberUtil on how to obtain a new instance. |
||||||
| 261 | * |
||||||
| 262 | * |
||||||
| 263 | * @param PhoneNumberUtil $util The Phone Number Util to use |
||||||
| 264 | * @param string|null $text The text that we will search, null for no text |
||||||
| 265 | * @param string|null $country The country to assume for phone numbers not written in international format. |
||||||
| 266 | * (with a leading plus, or with the international dialling prefix of the specified region). |
||||||
| 267 | * May be null, or "ZZ" if only numbers with a leading plus should be considered. |
||||||
| 268 | * @param AbstractLeniency $leniency The leniency to use when evaluating candidate phone numbers |
||||||
| 269 | * @param int $maxTries The maximum number of invalid numbers to try before giving up on the text. |
||||||
| 270 | * This is to cover degenerate cases where the text has a lot of false positives in it. Must be >= 0 |
||||||
| 271 | * @throws \InvalidArgumentException |
||||||
| 272 | */ |
||||||
| 273 | 207 | public function __construct(PhoneNumberUtil $util, $text, $country, AbstractLeniency $leniency, $maxTries) |
|||||
| 274 | { |
||||||
| 275 | 207 | if ($maxTries < 0) { |
|||||
| 276 | throw new \InvalidArgumentException(); |
||||||
| 277 | } |
||||||
| 278 | |||||||
| 279 | 207 | $this->phoneUtil = $util; |
|||||
| 280 | 207 | $this->text = ($text !== null) ? $text : ''; |
|||||
| 281 | 207 | $this->preferredRegion = $country; |
|||||
| 282 | 207 | $this->leniency = $leniency; |
|||||
| 283 | 207 | $this->maxTries = $maxTries; |
|||||
| 284 | |||||||
| 285 | 207 | if (static::$initialized === false) { |
|||||
| 286 | 1 | static::init(); |
|||||
| 287 | } |
||||||
| 288 | 207 | } |
|||||
| 289 | |||||||
| 290 | /** |
||||||
| 291 | * Attempts to find the next subsequence in the searched sequence on or after {@code searchIndex} |
||||||
| 292 | * that represents a phone number. Returns the next match, null if none was found. |
||||||
| 293 | * |
||||||
| 294 | * @param int $index The search index to start searching at |
||||||
| 295 | * @return PhoneNumberMatch|null The Phone Number Match found, null if none can be found |
||||||
| 296 | */ |
||||||
| 297 | 201 | protected function find($index) |
|||||
| 298 | { |
||||||
| 299 | 201 | $matcher = new Matcher(static::$pattern, $this->text); |
|||||
| 300 | 201 | while (($this->maxTries > 0) && $matcher->find($index)) { |
|||||
| 301 | 200 | $start = $matcher->start(); |
|||||
| 302 | 200 | $cutLength = $matcher->end() - $start; |
|||||
| 303 | 200 | $candidate = \mb_substr($this->text, $start, $cutLength); |
|||||
|
0 ignored issues
–
show
Bug
introduced
by
Loading history...
|
|||||||
| 304 | |||||||
| 305 | // Check for extra numbers at the end. |
||||||
| 306 | // TODO: This is the place to start when trying to support extraction of multiple phone number |
||||||
| 307 | // from split notations (+41 49 123 45 67 / 68). |
||||||
| 308 | 200 | $candidate = static::trimAfterFirstMatch(PhoneNumberUtil::$SECOND_NUMBER_START_PATTERN, $candidate); |
|||||
| 309 | |||||||
| 310 | 200 | $match = $this->extractMatch($candidate, $start); |
|||||
|
0 ignored issues
–
show
Are you sure the assignment to
$match is correct as $this->extractMatch($candidate, $start) targeting libphonenumber\PhoneNumberMatcher::extractMatch() seems to always return null.
This check looks for function or method calls that always return null and whose return value is assigned to a variable. class A
{
function getObject()
{
return null;
}
}
$a = new A();
$object = $a->getObject();
The method The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes. Loading history...
|
|||||||
| 311 | 200 | if ($match !== null) { |
|||||
| 312 | 126 | return $match; |
|||||
| 313 | } |
||||||
| 314 | |||||||
| 315 | 92 | $index = $start + \mb_strlen($candidate); |
|||||
| 316 | 92 | $this->maxTries--; |
|||||
| 317 | } |
||||||
| 318 | |||||||
| 319 | 95 | return null; |
|||||
| 320 | } |
||||||
| 321 | |||||||
| 322 | /** |
||||||
| 323 | * Trims away any characters after the first match of $pattern in $candidate, |
||||||
| 324 | * returning the trimmed version. |
||||||
| 325 | * |
||||||
| 326 | * @param string $pattern |
||||||
| 327 | * @param string $candidate |
||||||
| 328 | * @return string |
||||||
| 329 | */ |
||||||
| 330 | 200 | protected static function trimAfterFirstMatch($pattern, $candidate) |
|||||
| 331 | { |
||||||
| 332 | 200 | $trailingCharsMatcher = new Matcher($pattern, $candidate); |
|||||
| 333 | 200 | if ($trailingCharsMatcher->find()) { |
|||||
| 334 | 10 | $startChar = $trailingCharsMatcher->start(); |
|||||
| 335 | 10 | $candidate = \mb_substr($candidate, 0, $startChar); |
|||||
| 336 | } |
||||||
| 337 | 200 | return $candidate; |
|||||
| 338 | } |
||||||
| 339 | |||||||
| 340 | /** |
||||||
| 341 | * Helper method to determine if a character is a Latin-script letter or not. For our purposes, |
||||||
| 342 | * combining marks should also return true since we assume they have been added to a preceding |
||||||
| 343 | * Latin character. |
||||||
| 344 | * |
||||||
| 345 | * @param string $letter |
||||||
| 346 | * @return bool |
||||||
| 347 | * @internal |
||||||
| 348 | */ |
||||||
| 349 | 60 | public static function isLatinLetter($letter) |
|||||
| 350 | { |
||||||
| 351 | // Combining marks are a subset of non-spacing-mark. |
||||||
| 352 | 60 | if (\preg_match('/\p{L}/u', $letter) !== 1 && \preg_match('/\p{Mn}/u', $letter) !== 1) { |
|||||
| 353 | 54 | return false; |
|||||
| 354 | } |
||||||
| 355 | |||||||
| 356 | 9 | return (\preg_match('/\p{Latin}/u', $letter) === 1) |
|||||
| 357 | 9 | || (\preg_match('/\pM+/u', $letter) === 1); |
|||||
| 358 | } |
||||||
| 359 | |||||||
| 360 | /** |
||||||
| 361 | * @param string $character |
||||||
| 362 | * @return bool |
||||||
| 363 | */ |
||||||
| 364 | 49 | protected static function isInvalidPunctuationSymbol($character) |
|||||
| 365 | { |
||||||
| 366 | 49 | return $character == '%' || \preg_match('/\p{Sc}/u', $character); |
|||||
| 367 | } |
||||||
| 368 | |||||||
| 369 | /** |
||||||
| 370 | * Attempts to extract a match from a $candidate. |
||||||
| 371 | * |
||||||
| 372 | * @param string $candidate The candidate text that might contain a phone number |
||||||
| 373 | * @param int $offset The offset of $candidate within $this->text |
||||||
| 374 | * @return PhoneNumberMatch|null The match found, null if none can be found |
||||||
| 375 | */ |
||||||
| 376 | 200 | protected function extractMatch($candidate, $offset) |
|||||
| 377 | { |
||||||
| 378 | // Skip a match that is more likely to be a date. |
||||||
| 379 | 200 | $dateMatcher = new Matcher(static::$slashSeparatedDates, $candidate); |
|||||
| 380 | 200 | if ($dateMatcher->find()) { |
|||||
| 381 | 33 | return null; |
|||||
| 382 | } |
||||||
| 383 | |||||||
| 384 | // Skip potential time-stamps. |
||||||
| 385 | 180 | $timeStampMatcher = new Matcher(static::$timeStamps, $candidate); |
|||||
| 386 | 180 | if ($timeStampMatcher->find()) { |
|||||
| 387 | 20 | $followingText = \mb_substr($this->text, $offset + \mb_strlen($candidate)); |
|||||
| 388 | 20 | $timeStampSuffixMatcher = new Matcher(static::$timeStampsSuffix, $followingText); |
|||||
| 389 | 20 | if ($timeStampSuffixMatcher->lookingAt()) { |
|||||
| 390 | 16 | return null; |
|||||
| 391 | } |
||||||
| 392 | } |
||||||
| 393 | |||||||
| 394 | // Try to come up with a valid match given the entire candidate. |
||||||
| 395 | 180 | $match = $this->parseAndVerify($candidate, $offset); |
|||||
|
0 ignored issues
–
show
Are you sure the assignment to
$match is correct as $this->parseAndVerify($candidate, $offset) targeting libphonenumber\PhoneNumb...tcher::parseAndVerify() seems to always return null.
This check looks for function or method calls that always return null and whose return value is assigned to a variable. class A
{
function getObject()
{
return null;
}
}
$a = new A();
$object = $a->getObject();
The method The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes. Loading history...
|
|||||||
| 396 | 180 | if ($match !== null) { |
|||||
|
0 ignored issues
–
show
|
|||||||
| 397 | 124 | return $match; |
|||||
| 398 | } |
||||||
| 399 | |||||||
| 400 | // If that failed, try to find an "inner match" - there might be a phone number within this |
||||||
| 401 | // candidate. |
||||||
| 402 | 76 | return $this->extractInnerMatch($candidate, $offset); |
|||||
|
0 ignored issues
–
show
Are you sure the usage of
$this->extractInnerMatch($candidate, $offset) targeting libphonenumber\PhoneNumb...er::extractInnerMatch() seems to always return null.
This check looks for function or method calls that always return null and whose return value is used. class A
{
function getObject()
{
return null;
}
}
$a = new A();
if ($a->getObject()) {
The method The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes. Loading history...
|
|||||||
| 403 | } |
||||||
| 404 | |||||||
| 405 | /** |
||||||
| 406 | * Attempts to extract a match from $candidate if the whole candidate does not qualify as a |
||||||
| 407 | * match. |
||||||
| 408 | * |
||||||
| 409 | * @param string $candidate The candidate text that might contact a phone number |
||||||
| 410 | * @param int $offset The current offset of $candidate within $this->text |
||||||
| 411 | * @return PhoneNumberMatch|null The match found, null if none can be found |
||||||
| 412 | */ |
||||||
| 413 | 76 | protected function extractInnerMatch($candidate, $offset) |
|||||
| 414 | { |
||||||
| 415 | 76 | foreach (static::$innerMatches as $possibleInnerMatch) { |
|||||
| 416 | 76 | $groupMatcher = new Matcher($possibleInnerMatch, $candidate); |
|||||
| 417 | 76 | $isFirstMatch = true; |
|||||
| 418 | |||||||
| 419 | 76 | while ($groupMatcher->find() && $this->maxTries > 0) { |
|||||
| 420 | 20 | if ($isFirstMatch) { |
|||||
| 421 | // We should handle any group before this one too. |
||||||
| 422 | 20 | $group = static::trimAfterFirstMatch( |
|||||
| 423 | 20 | PhoneNumberUtil::$UNWANTED_END_CHAR_PATTERN, |
|||||
| 424 | 20 | \mb_substr($candidate, 0, $groupMatcher->start()) |
|||||
| 425 | ); |
||||||
| 426 | |||||||
| 427 | 20 | $match = $this->parseAndVerify($group, $offset); |
|||||
|
0 ignored issues
–
show
Are you sure the assignment to
$match is correct as $this->parseAndVerify($group, $offset) targeting libphonenumber\PhoneNumb...tcher::parseAndVerify() seems to always return null.
This check looks for function or method calls that always return null and whose return value is assigned to a variable. class A
{
function getObject()
{
return null;
}
}
$a = new A();
$object = $a->getObject();
The method The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes. Loading history...
|
|||||||
| 428 | 20 | if ($match !== null) { |
|||||
| 429 | 6 | return $match; |
|||||
| 430 | } |
||||||
| 431 | 17 | $this->maxTries--; |
|||||
| 432 | 17 | $isFirstMatch = false; |
|||||
| 433 | } |
||||||
| 434 | 17 | $group = static::trimAfterFirstMatch( |
|||||
| 435 | 17 | PhoneNumberUtil::$UNWANTED_END_CHAR_PATTERN, |
|||||
| 436 | 17 | $groupMatcher->group(1) |
|||||
| 437 | ); |
||||||
| 438 | 17 | $match = $this->parseAndVerify($group, $offset + $groupMatcher->start(1)); |
|||||
|
0 ignored issues
–
show
Are you sure the assignment to
$match is correct as $this->parseAndVerify($g...groupMatcher->start(1)) targeting libphonenumber\PhoneNumb...tcher::parseAndVerify() seems to always return null.
This check looks for function or method calls that always return null and whose return value is assigned to a variable. class A
{
function getObject()
{
return null;
}
}
$a = new A();
$object = $a->getObject();
The method The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes. Loading history...
|
|||||||
| 439 | 17 | if ($match !== null) { |
|||||
| 440 | 7 | return $match; |
|||||
| 441 | } |
||||||
| 442 | 16 | $this->maxTries--; |
|||||
| 443 | } |
||||||
| 444 | } |
||||||
| 445 | 72 | return null; |
|||||
| 446 | } |
||||||
| 447 | |||||||
| 448 | /** |
||||||
| 449 | * Parses a phone number from the $candidate} using PhoneNumberUtil::parse() and |
||||||
| 450 | * verifies it matches the requested leniency. If parsing and verification succeed, a |
||||||
| 451 | * corresponding PhoneNumberMatch is returned, otherwise this method returns null. |
||||||
| 452 | * |
||||||
| 453 | * @param string $candidate The candidate match |
||||||
| 454 | * @param int $offset The offset of $candidate within $this->text |
||||||
| 455 | * @return PhoneNumberMatch|null The parsed and validated phone number match, or null |
||||||
| 456 | */ |
||||||
| 457 | 180 | protected function parseAndVerify($candidate, $offset) |
|||||
| 458 | { |
||||||
| 459 | try { |
||||||
| 460 | // Check the candidate doesn't contain any formatting which would indicate that it really |
||||||
| 461 | // isn't a phone number |
||||||
| 462 | 180 | $matchingBracketsMatcher = new Matcher(static::$matchingBrackets, $candidate); |
|||||
| 463 | 180 | $pubPagesMatcher = new Matcher(static::$pubPages, $candidate); |
|||||
| 464 | 180 | if (!$matchingBracketsMatcher->matches() || $pubPagesMatcher->find()) { |
|||||
| 465 | 11 | return null; |
|||||
| 466 | } |
||||||
| 467 | |||||||
| 468 | // If leniency is set to VALID or stricter, we also want to skip numbers that are surrounded |
||||||
| 469 | // by Latin alphabetic characters, to skip cases like abc8005001234 or 8005001234def. |
||||||
| 470 | 180 | if ($this->leniency->compareTo(Leniency::VALID()) >= 0) { |
|||||
| 471 | // If the candidate is not at the start of the text, and does not start with phone-number |
||||||
| 472 | // punctuation, check the previous character. |
||||||
| 473 | 137 | $leadClassMatcher = new Matcher(static::$leadClass, $candidate); |
|||||
| 474 | 137 | if ($offset > 0 && !$leadClassMatcher->lookingAt()) { |
|||||
| 475 | 44 | $previousChar = \mb_substr($this->text, $offset - 1, 1); |
|||||
| 476 | // We return null if it is a latin letter or an invalid punctuation symbol. |
||||||
| 477 | 44 | if (static::isInvalidPunctuationSymbol($previousChar) || static::isLatinLetter($previousChar)) { |
|||||
| 478 | 2 | return null; |
|||||
| 479 | } |
||||||
| 480 | } |
||||||
| 481 | 137 | $lastCharIndex = $offset + \mb_strlen($candidate); |
|||||
| 482 | 137 | if ($lastCharIndex < \mb_strlen($this->text)) { |
|||||
| 483 | 40 | $nextChar = \mb_substr($this->text, $lastCharIndex, 1); |
|||||
| 484 | 40 | if (static::isInvalidPunctuationSymbol($nextChar) || static::isLatinLetter($nextChar)) { |
|||||
| 485 | 2 | return null; |
|||||
| 486 | } |
||||||
| 487 | } |
||||||
| 488 | } |
||||||
| 489 | |||||||
| 490 | 179 | $number = $this->phoneUtil->parseAndKeepRawInput($candidate, $this->preferredRegion); |
|||||
| 491 | |||||||
| 492 | 178 | if ($this->leniency->verify($number, $candidate, $this->phoneUtil)) { |
|||||
| 493 | // We used parseAndKeepRawInput to create this number, but for now we don't return the extra |
||||||
| 494 | // values parsed. TODO: stop clearing all values here and switch all users over |
||||||
| 495 | // to using rawInput() rather than the rawString() of PhoneNumberMatch |
||||||
| 496 | 126 | $number->clearCountryCodeSource(); |
|||||
| 497 | 126 | $number->clearRawInput(); |
|||||
| 498 | 126 | $number->clearPreferredDomesticCarrierCode(); |
|||||
| 499 | 178 | return new PhoneNumberMatch($offset, $candidate, $number); |
|||||
| 500 | } |
||||||
| 501 | 28 | } catch (NumberParseException $e) { |
|||||
| 502 | // ignore and continue |
||||||
| 503 | } |
||||||
| 504 | 74 | return null; |
|||||
| 505 | } |
||||||
| 506 | |||||||
| 507 | /** |
||||||
| 508 | * @param PhoneNumberUtil $util |
||||||
| 509 | * @param PhoneNumber $number |
||||||
| 510 | * @param string $normalizedCandidate |
||||||
| 511 | * @param string[] $formattedNumberGroups |
||||||
| 512 | * @return bool |
||||||
| 513 | */ |
||||||
| 514 | 27 | public static function allNumberGroupsRemainGrouped( |
|||||
| 515 | PhoneNumberUtil $util, |
||||||
| 516 | PhoneNumber $number, |
||||||
| 517 | $normalizedCandidate, |
||||||
| 518 | $formattedNumberGroups |
||||||
| 519 | ) { |
||||||
| 520 | 27 | $fromIndex = 0; |
|||||
| 521 | 27 | if ($number->getCountryCodeSource() !== CountryCodeSource::FROM_DEFAULT_COUNTRY) { |
|||||
| 522 | // First skip the country code if the normalized candidate contained it. |
||||||
| 523 | 11 | $countryCode = $number->getCountryCode(); |
|||||
| 524 | 11 | $fromIndex = \mb_strpos($normalizedCandidate, $countryCode) + \mb_strlen($countryCode); |
|||||
|
0 ignored issues
–
show
It seems like
$countryCode can also be of type null; however, parameter $needle of mb_strpos() does only seem to accept string, maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
It seems like
$countryCode can also be of type null; however, parameter $string of mb_strlen() does only seem to accept string, maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||||
| 525 | } |
||||||
| 526 | |||||||
| 527 | // Check each group of consecutive digits are not broken into separate groupings in the |
||||||
| 528 | // $normalizedCandidate string. |
||||||
| 529 | 27 | $formattedNumberGroupsLength = \count($formattedNumberGroups); |
|||||
| 530 | 27 | for ($i = 0; $i < $formattedNumberGroupsLength; $i++) { |
|||||
| 531 | // Fails if the substring of $normalizedCandidate starting from $fromIndex |
||||||
| 532 | // doesn't contain the consecutive digits in $formattedNumberGroups[$i]. |
||||||
| 533 | 27 | $fromIndex = \mb_strpos($normalizedCandidate, $formattedNumberGroups[$i], $fromIndex); |
|||||
| 534 | 27 | if ($fromIndex === false) { |
|||||
| 535 | 9 | return false; |
|||||
| 536 | } |
||||||
| 537 | |||||||
| 538 | // Moves $fromIndex forward. |
||||||
| 539 | 26 | $fromIndex += \mb_strlen($formattedNumberGroups[$i]); |
|||||
| 540 | 26 | if ($i === 0 && $fromIndex < \mb_strlen($normalizedCandidate)) { |
|||||
| 541 | // We are at the position right after the NDC. We get the region used for formatting |
||||||
| 542 | // information based on the country code in the phone number, rather than the number itself, |
||||||
| 543 | // as we do not need to distinguish between different countries with the same country |
||||||
| 544 | // calling code and this is faster. |
||||||
| 545 | 26 | $region = $util->getRegionCodeForCountryCode($number->getCountryCode()); |
|||||
| 546 | |||||||
| 547 | 26 | if ($util->getNddPrefixForRegion($region, true) !== null |
|||||
| 548 | 26 | && \is_int(\mb_substr($normalizedCandidate, $fromIndex, 1)) |
|||||
| 549 | ) { |
||||||
| 550 | // This means there is no formatting symbol after the NDC. In this case, we only |
||||||
| 551 | // accept the number if there is no formatting symbol at all in the number, except |
||||||
| 552 | // for extensions. This is only important for countries with national prefixes. |
||||||
| 553 | $nationalSignificantNumber = $util->getNationalSignificantNumber($number); |
||||||
| 554 | return \mb_substr( |
||||||
| 555 | \mb_substr($normalizedCandidate, $fromIndex - \mb_strlen($formattedNumberGroups[$i])), |
||||||
| 556 | \mb_strlen($nationalSignificantNumber) |
||||||
| 557 | ) === $nationalSignificantNumber; |
||||||
| 558 | } |
||||||
| 559 | } |
||||||
| 560 | } |
||||||
| 561 | // The check here makes sure that we haven't mistakenly already used the extension to |
||||||
| 562 | // match the last group of the subscriber number. Note the extension cannot have |
||||||
| 563 | // formatting in-between digits |
||||||
| 564 | |||||||
| 565 | 25 | if ($number->hasExtension()) { |
|||||
| 566 | 4 | return \mb_strpos(\mb_substr($normalizedCandidate, $fromIndex), $number->getExtension()) !== false; |
|||||
| 567 | } |
||||||
| 568 | |||||||
| 569 | 21 | return true; |
|||||
| 570 | } |
||||||
| 571 | |||||||
| 572 | /** |
||||||
| 573 | * @param PhoneNumberUtil $util |
||||||
| 574 | * @param PhoneNumber $number |
||||||
| 575 | * @param string $normalizedCandidate |
||||||
| 576 | * @param string[] $formattedNumberGroups |
||||||
| 577 | * @return bool |
||||||
| 578 | */ |
||||||
| 579 | 27 | public static function allNumberGroupsAreExactlyPresent( |
|||||
| 580 | PhoneNumberUtil $util, |
||||||
| 581 | PhoneNumber $number, |
||||||
| 582 | $normalizedCandidate, |
||||||
| 583 | $formattedNumberGroups |
||||||
| 584 | ) { |
||||||
| 585 | 27 | $candidateGroups = \preg_split(PhoneNumberUtil::NON_DIGITS_PATTERN, $normalizedCandidate); |
|||||
| 586 | |||||||
| 587 | // Set this to the last group, skipping it if the number has an extension. |
||||||
| 588 | 27 | $candidateNumberGroupIndex = $number->hasExtension() ? \count($candidateGroups) - 2 : \count($candidateGroups) - 1; |
|||||
| 589 | |||||||
| 590 | // First we check if the national significant number is formatted as a block. |
||||||
| 591 | // We use contains and not equals, since the national significant number may be present with |
||||||
| 592 | // a prefix such as a national number prefix, or the country code itself. |
||||||
| 593 | 27 | if (\count($candidateGroups) == 1 |
|||||
| 594 | 24 | || \mb_strpos( |
|||||
| 595 | 24 | $candidateGroups[$candidateNumberGroupIndex], |
|||||
| 596 | 24 | $util->getNationalSignificantNumber($number) |
|||||
| 597 | 27 | ) !== false |
|||||
| 598 | ) { |
||||||
| 599 | 8 | return true; |
|||||
| 600 | } |
||||||
| 601 | |||||||
| 602 | // Starting from the end, go through in reverse, excluding the first group, and check the |
||||||
| 603 | // candidate and number groups are the same. |
||||||
| 604 | 19 | for ($formattedNumberGroupIndex = (\count($formattedNumberGroups) - 1); |
|||||
| 605 | 19 | $formattedNumberGroupIndex > 0 && $candidateNumberGroupIndex >= 0; |
|||||
| 606 | $formattedNumberGroupIndex--, $candidateNumberGroupIndex--) { |
||||||
| 607 | 19 | if ($candidateGroups[$candidateNumberGroupIndex] != $formattedNumberGroups[$formattedNumberGroupIndex]) { |
|||||
| 608 | 6 | return false; |
|||||
| 609 | } |
||||||
| 610 | } |
||||||
| 611 | |||||||
| 612 | // Now check the first group. There may be a national prefix at the start, so we only check |
||||||
| 613 | // that the candidate group ends with the formatted number group. |
||||||
| 614 | 18 | return ($candidateNumberGroupIndex >= 0 |
|||||
| 615 | 18 | && \mb_substr( |
|||||
| 616 | 18 | $candidateGroups[$candidateNumberGroupIndex], |
|||||
| 617 | 18 | -\mb_strlen($formattedNumberGroups[0]) |
|||||
| 618 | 18 | ) == $formattedNumberGroups[0]); |
|||||
| 619 | } |
||||||
| 620 | |||||||
| 621 | /** |
||||||
| 622 | * Helper method to get the national-number part of a number, formatted without any national |
||||||
| 623 | * prefix, and return it as a set of digit blocks that would be formatted together. |
||||||
| 624 | * |
||||||
| 625 | * @param PhoneNumberUtil $util |
||||||
| 626 | * @param PhoneNumber $number |
||||||
| 627 | * @param NumberFormat $formattingPattern |
||||||
| 628 | * @return string[] |
||||||
| 629 | */ |
||||||
| 630 | 54 | protected static function getNationalNumberGroups( |
|||||
| 631 | PhoneNumberUtil $util, |
||||||
| 632 | PhoneNumber $number, |
||||||
| 633 | NumberFormat $formattingPattern = null |
||||||
| 634 | ) { |
||||||
| 635 | 54 | if ($formattingPattern === null) { |
|||||
| 636 | // This will be in the format +CC-DG;ext=EXT where DG represents groups of digits. |
||||||
| 637 | 54 | $rfc3966Format = $util->format($number, PhoneNumberFormat::RFC3966); |
|||||
| 638 | // We remove the extension part from the formatted string before splitting it into different |
||||||
| 639 | // groups. |
||||||
| 640 | 54 | $endIndex = \mb_strpos($rfc3966Format, ';'); |
|||||
| 641 | 54 | if ($endIndex === false) { |
|||||
| 642 | 44 | $endIndex = \mb_strlen($rfc3966Format); |
|||||
| 643 | } |
||||||
| 644 | |||||||
| 645 | // The country-code will have a '-' following it. |
||||||
| 646 | 54 | $startIndex = \mb_strpos($rfc3966Format, '-') + 1; |
|||||
| 647 | 54 | return \explode('-', \mb_substr($rfc3966Format, $startIndex, $endIndex - $startIndex)); |
|||||
| 648 | } |
||||||
| 649 | |||||||
| 650 | // If a format is provided, we format the NSN only, and split that according to the separator. |
||||||
| 651 | 15 | $nationalSignificantNumber = $util->getNationalSignificantNumber($number); |
|||||
| 652 | 15 | return \explode('-', $util->formatNsnUsingPattern( |
|||||
| 653 | 15 | $nationalSignificantNumber, |
|||||
| 654 | $formattingPattern, |
||||||
| 655 | 15 | PhoneNumberFormat::RFC3966 |
|||||
| 656 | )); |
||||||
| 657 | } |
||||||
| 658 | |||||||
| 659 | /** |
||||||
| 660 | * @param PhoneNumber $number |
||||||
| 661 | * @param string $candidate |
||||||
| 662 | * @param PhoneNumberUtil $util |
||||||
| 663 | * @param \Closure $checker |
||||||
| 664 | * @return bool |
||||||
| 665 | */ |
||||||
| 666 | 54 | public static function checkNumberGroupingIsValid( |
|||||
| 667 | PhoneNumber $number, |
||||||
| 668 | $candidate, |
||||||
| 669 | PhoneNumberUtil $util, |
||||||
| 670 | \Closure $checker |
||||||
| 671 | ) { |
||||||
| 672 | 54 | $normalizedCandidate = PhoneNumberUtil::normalizeDigits($candidate, true /* keep non-digits */); |
|||||
| 673 | 54 | $formattedNumberGroups = static::getNationalNumberGroups($util, $number); |
|||||
| 674 | 54 | if ($checker($util, $number, $normalizedCandidate, $formattedNumberGroups)) { |
|||||
| 675 | 39 | return true; |
|||||
| 676 | } |
||||||
| 677 | |||||||
| 678 | // If this didn't pass, see if there are any alternative formats that match, and try them instead. |
||||||
| 679 | 15 | $alternateFormats = static::getAlternateFormatsForCountry($number->getCountryCode()); |
|||||
| 680 | |||||||
| 681 | 15 | $nationalSignificantNumber = $util->getNationalSignificantNumber($number); |
|||||
| 682 | 15 | if ($alternateFormats !== null) { |
|||||
| 683 | 15 | foreach ($alternateFormats->numberFormats() as $alternateFormat) { |
|||||
| 684 | 15 | if ($alternateFormat->leadingDigitsPatternSize() > 0) { |
|||||
| 685 | // There is only one leading digits pattern for alternate formats. |
||||||
| 686 | 13 | $pattern = $alternateFormat->getLeadingDigitsPattern(0); |
|||||
| 687 | |||||||
| 688 | 13 | $nationalSignificantNumberMatcher = new Matcher($pattern, $nationalSignificantNumber); |
|||||
| 689 | 13 | if (!$nationalSignificantNumberMatcher->lookingAt()) { |
|||||
| 690 | // Leading digits don't match; try another one. |
||||||
| 691 | 13 | continue; |
|||||
| 692 | } |
||||||
| 693 | } |
||||||
| 694 | |||||||
| 695 | 15 | $formattedNumberGroups = static::getNationalNumberGroups($util, $number, $alternateFormat); |
|||||
| 696 | 15 | if ($checker($util, $number, $normalizedCandidate, $formattedNumberGroups)) { |
|||||
| 697 | 11 | return true; |
|||||
| 698 | } |
||||||
| 699 | } |
||||||
| 700 | } |
||||||
| 701 | 4 | return false; |
|||||
| 702 | } |
||||||
| 703 | |||||||
| 704 | /** |
||||||
| 705 | * @param PhoneNumber $number |
||||||
| 706 | * @param string $candidate |
||||||
| 707 | * @return bool |
||||||
| 708 | */ |
||||||
| 709 | 55 | public static function containsMoreThanOneSlashInNationalNumber(PhoneNumber $number, $candidate) |
|||||
| 710 | { |
||||||
| 711 | 55 | $firstSlashInBodyIndex = \mb_strpos($candidate, '/'); |
|||||
| 712 | 55 | if ($firstSlashInBodyIndex === false) { |
|||||
| 713 | // No slashes, this is okay |
||||||
| 714 | 53 | return false; |
|||||
| 715 | } |
||||||
| 716 | |||||||
| 717 | // Now look for a second one. |
||||||
| 718 | 2 | $secondSlashInBodyIndex = \mb_strpos($candidate, '/', $firstSlashInBodyIndex + 1); |
|||||
| 719 | 2 | if ($secondSlashInBodyIndex === false) { |
|||||
| 720 | // Only one slash, this is okay |
||||||
| 721 | 1 | return false; |
|||||
| 722 | } |
||||||
| 723 | |||||||
| 724 | // If the first slash is after the country calling code, this is permitted |
||||||
| 725 | 1 | $candidateHasCountryCode = ($number->getCountryCodeSource() === CountryCodeSource::FROM_NUMBER_WITH_PLUS_SIGN |
|||||
| 726 | 1 | || $number->getCountryCodeSource() === CountryCodeSource::FROM_NUMBER_WITHOUT_PLUS_SIGN); |
|||||
| 727 | |||||||
| 728 | 1 | if ($candidateHasCountryCode |
|||||
| 729 | 1 | && PhoneNumberUtil::normalizeDigitsOnly( |
|||||
| 730 | 1 | \mb_substr($candidate, 0, $firstSlashInBodyIndex) |
|||||
| 731 | 1 | ) == $number->getCountryCode() |
|||||
| 732 | ) { |
||||||
| 733 | // Any more slashes and this is illegal |
||||||
| 734 | 1 | return (\mb_strpos(\mb_substr($candidate, $secondSlashInBodyIndex + 1), '/') !== false); |
|||||
| 735 | } |
||||||
| 736 | |||||||
| 737 | 1 | return true; |
|||||
| 738 | } |
||||||
| 739 | |||||||
| 740 | /** |
||||||
| 741 | * @param PhoneNumber $number |
||||||
| 742 | * @param string $candidate |
||||||
| 743 | * @param PhoneNumberUtil $util |
||||||
| 744 | * @return bool |
||||||
| 745 | */ |
||||||
| 746 | 99 | public static function containsOnlyValidXChars(PhoneNumber $number, $candidate, PhoneNumberUtil $util) |
|||||
| 747 | { |
||||||
| 748 | // The characters 'x' and 'X' can be (1) a carrier code, in which case they always precede the |
||||||
| 749 | // national significant number or (2) an extension sign, in which case they always precede the |
||||||
| 750 | // extension number. We assume a carrier code is more than 1 digit, so the first case has to |
||||||
| 751 | // have more than 1 consecutive 'x' or 'X', whereas the second case can only have exactly 1 'x' |
||||||
| 752 | // or 'X'. We ignore the character if it appears as the last character of the string. |
||||||
| 753 | 99 | $candidateLength = \mb_strlen($candidate); |
|||||
| 754 | |||||||
| 755 | 99 | for ($index = 0; $index < $candidateLength - 1; $index++) { |
|||||
| 756 | 99 | $charAtIndex = \mb_substr($candidate, $index, 1); |
|||||
| 757 | 99 | if ($charAtIndex == 'x' || $charAtIndex == 'X') { |
|||||
| 758 | 15 | $charAtNextIndex = \mb_substr($candidate, $index + 1, 1); |
|||||
| 759 | 15 | if ($charAtNextIndex == 'x' || $charAtNextIndex == 'X') { |
|||||
| 760 | // This is the carrier code case, in which the 'X's always precede the national |
||||||
| 761 | // significant number. |
||||||
| 762 | $index++; |
||||||
| 763 | |||||||
| 764 | if ($util->isNumberMatch($number, \mb_substr($candidate, $index)) != MatchType::NSN_MATCH) { |
||||||
| 765 | return false; |
||||||
| 766 | } |
||||||
| 767 | 15 | } elseif (!PhoneNumberUtil::normalizeDigitsOnly(\mb_substr( |
|||||
| 768 | 15 | $candidate, |
|||||
| 769 | 15 | $index |
|||||
| 770 | 15 | )) == $number->getExtension() |
|||||
| 771 | ) { |
||||||
| 772 | // This is the extension sign case, in which the 'x' or 'X' should always precede the |
||||||
| 773 | // extension number |
||||||
| 774 | return false; |
||||||
| 775 | } |
||||||
| 776 | } |
||||||
| 777 | } |
||||||
| 778 | 99 | return true; |
|||||
| 779 | } |
||||||
| 780 | |||||||
| 781 | /** |
||||||
| 782 | * @param PhoneNumber $number |
||||||
| 783 | * @param PhoneNumberUtil $util |
||||||
| 784 | * @return bool |
||||||
| 785 | */ |
||||||
| 786 | 99 | public static function isNationalPrefixPresentIfRequired(PhoneNumber $number, PhoneNumberUtil $util) |
|||||
| 787 | { |
||||||
| 788 | // First, check how we deduced the country code. If it was written in international format, then |
||||||
| 789 | // the national prefix is not required. |
||||||
| 790 | 99 | if ($number->getCountryCodeSource() !== CountryCodeSource::FROM_DEFAULT_COUNTRY) { |
|||||
| 791 | 41 | return true; |
|||||
| 792 | } |
||||||
| 793 | |||||||
| 794 | 65 | $phoneNumberRegion = $util->getRegionCodeForCountryCode($number->getCountryCode()); |
|||||
| 795 | 65 | $metadata = $util->getMetadataForRegion($phoneNumberRegion); |
|||||
| 796 | 65 | if ($metadata === null) { |
|||||
| 797 | return true; |
||||||
| 798 | } |
||||||
| 799 | |||||||
| 800 | // Check if a national prefix should be present when formatting this number. |
||||||
| 801 | 65 | $nationalNumber = $util->getNationalSignificantNumber($number); |
|||||
| 802 | 65 | $formatRule = $util->chooseFormattingPatternForNumber($metadata->numberFormats(), $nationalNumber); |
|||||
| 803 | // To do this, we check that a national prefix formatting rule was present and that it wasn't |
||||||
| 804 | // just the first-group symbol ($1) with punctuation. |
||||||
| 805 | 65 | if (($formatRule !== null) && $formatRule->getNationalPrefixFormattingRule() !== '') { |
|||||
| 806 | 44 | if ($formatRule->getNationalPrefixOptionalWhenFormatting()) { |
|||||
| 807 | // The national-prefix is optional in these cases, so we don't need to check if it was |
||||||
| 808 | // present. |
||||||
| 809 | 7 | return true; |
|||||
| 810 | } |
||||||
| 811 | |||||||
| 812 | 37 | if (PhoneNumberUtil::formattingRuleHasFirstGroupOnly($formatRule->getNationalPrefixFormattingRule())) { |
|||||
| 813 | // National Prefix not needed for this number. |
||||||
| 814 | 3 | return true; |
|||||
| 815 | } |
||||||
| 816 | |||||||
| 817 | // Normalize the remainder. |
||||||
| 818 | 34 | $rawInputCopy = PhoneNumberUtil::normalizeDigitsOnly($number->getRawInput()); |
|||||
| 819 | 34 | $rawInput = $rawInputCopy; |
|||||
| 820 | // Check if we found a national prefix and/or carrier code at the start of the raw input, and |
||||||
| 821 | // return the result. |
||||||
| 822 | 34 | $carrierCode = null; |
|||||
| 823 | 34 | return $util->maybeStripNationalPrefixAndCarrierCode($rawInput, $metadata, $carrierCode); |
|||||
| 824 | } |
||||||
| 825 | 25 | return true; |
|||||
| 826 | } |
||||||
| 827 | |||||||
| 828 | |||||||
| 829 | /** |
||||||
| 830 | * Storage for Alternate Formats |
||||||
| 831 | * @var PhoneMetadata[] |
||||||
| 832 | */ |
||||||
| 833 | protected static $callingCodeToAlternateFormatsMap = array(); |
||||||
| 834 | |||||||
| 835 | /** |
||||||
| 836 | * @param $countryCallingCode |
||||||
| 837 | * @return PhoneMetadata|null |
||||||
| 838 | */ |
||||||
| 839 | 15 | protected static function getAlternateFormatsForCountry($countryCallingCode) |
|||||
| 840 | { |
||||||
| 841 | 15 | $countryCodeSet = AlternateFormatsCountryCodeSet::$alternateFormatsCountryCodeSet; |
|||||
| 842 | |||||||
| 843 | 15 | if (!\in_array($countryCallingCode, $countryCodeSet)) { |
|||||
| 844 | return null; |
||||||
| 845 | } |
||||||
| 846 | |||||||
| 847 | 15 | if (!isset(static::$callingCodeToAlternateFormatsMap[$countryCallingCode])) { |
|||||
| 848 | 3 | static::loadAlternateFormatsMetadataFromFile($countryCallingCode); |
|||||
| 849 | } |
||||||
| 850 | |||||||
| 851 | 15 | return static::$callingCodeToAlternateFormatsMap[$countryCallingCode]; |
|||||
| 852 | } |
||||||
| 853 | |||||||
| 854 | /** |
||||||
| 855 | * @param string $countryCallingCode |
||||||
| 856 | * @throws \Exception |
||||||
| 857 | */ |
||||||
| 858 | 3 | protected static function loadAlternateFormatsMetadataFromFile($countryCallingCode) |
|||||
| 859 | { |
||||||
| 860 | 3 | $fileName = static::$alternateFormatsFilePrefix . '_' . $countryCallingCode . '.php'; |
|||||
| 861 | |||||||
| 862 | 3 | if (!\is_readable($fileName)) { |
|||||
| 863 | throw new \Exception('missing metadata: ' . $fileName); |
||||||
| 864 | } |
||||||
| 865 | |||||||
| 866 | 3 | $metadataLoader = new DefaultMetadataLoader(); |
|||||
| 867 | 3 | $data = $metadataLoader->loadMetadata($fileName); |
|||||
| 868 | 3 | $metadata = new PhoneMetadata(); |
|||||
| 869 | 3 | $metadata->fromArray($data); |
|||||
| 870 | 3 | static::$callingCodeToAlternateFormatsMap[$countryCallingCode] = $metadata; |
|||||
| 871 | 3 | } |
|||||
| 872 | |||||||
| 873 | |||||||
| 874 | /** |
||||||
| 875 | * Return the current element |
||||||
| 876 | * @link http://php.net/manual/en/iterator.current.php |
||||||
| 877 | * @return PhoneNumberMatch|null |
||||||
| 878 | */ |
||||||
| 879 | #[\ReturnTypeWillChange] |
||||||
| 880 | 199 | public function current() |
|||||
| 881 | { |
||||||
| 882 | 199 | return $this->lastMatch; |
|||||
| 883 | } |
||||||
| 884 | |||||||
| 885 | /** |
||||||
| 886 | * Move forward to next element |
||||||
| 887 | * @link http://php.net/manual/en/iterator.next.php |
||||||
| 888 | * @return void Any returned value is ignored. |
||||||
| 889 | */ |
||||||
| 890 | #[\ReturnTypeWillChange] |
||||||
| 891 | 201 | public function next() |
|||||
| 892 | { |
||||||
| 893 | 201 | $this->lastMatch = $this->find($this->searchIndex); |
|||||
|
0 ignored issues
–
show
Are you sure the assignment to
$this->lastMatch is correct as $this->find($this->searchIndex) targeting libphonenumber\PhoneNumberMatcher::find() seems to always return null.
This check looks for function or method calls that always return null and whose return value is assigned to a variable. class A
{
function getObject()
{
return null;
}
}
$a = new A();
$object = $a->getObject();
The method The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes. Loading history...
|
|||||||
| 894 | |||||||
| 895 | 201 | if ($this->lastMatch === null) { |
|||||
| 896 | 95 | $this->state = 'DONE'; |
|||||
| 897 | } else { |
||||||
| 898 | 126 | $this->searchIndex = $this->lastMatch->end(); |
|||||
|
0 ignored issues
–
show
The method
end() does not exist on null.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces. This is most likely a typographical error or the method has been renamed. Loading history...
|
|||||||
| 899 | 126 | $this->state = 'READY'; |
|||||
| 900 | } |
||||||
| 901 | |||||||
| 902 | 201 | $this->searchIndex++; |
|||||
| 903 | 201 | } |
|||||
| 904 | |||||||
| 905 | /** |
||||||
| 906 | * Return the key of the current element |
||||||
| 907 | * @link http://php.net/manual/en/iterator.key.php |
||||||
| 908 | * @return mixed scalar on success, or null on failure. |
||||||
| 909 | * @since 5.0.0 |
||||||
| 910 | */ |
||||||
| 911 | #[\ReturnTypeWillChange] |
||||||
| 912 | public function key() |
||||||
| 913 | { |
||||||
| 914 | return $this->searchIndex; |
||||||
| 915 | } |
||||||
| 916 | |||||||
| 917 | /** |
||||||
| 918 | * Checks if current position is valid |
||||||
| 919 | * @link http://php.net/manual/en/iterator.valid.php |
||||||
| 920 | * @return boolean The return value will be casted to boolean and then evaluated. |
||||||
| 921 | * Returns true on success or false on failure. |
||||||
| 922 | * @since 5.0.0 |
||||||
| 923 | */ |
||||||
| 924 | #[\ReturnTypeWillChange] |
||||||
| 925 | 29 | public function valid() |
|||||
| 926 | { |
||||||
| 927 | 29 | return $this->state === 'READY'; |
|||||
| 928 | } |
||||||
| 929 | |||||||
| 930 | /** |
||||||
| 931 | * Rewind the Iterator to the first element |
||||||
| 932 | * @link http://php.net/manual/en/iterator.rewind.php |
||||||
| 933 | * @return void Any returned value is ignored. |
||||||
| 934 | * @since 5.0.0 |
||||||
| 935 | */ |
||||||
| 936 | #[\ReturnTypeWillChange] |
||||||
| 937 | 18 | public function rewind() |
|||||
| 938 | { |
||||||
| 939 | 18 | $this->searchIndex = 0; |
|||||
| 940 | 18 | $this->next(); |
|||||
| 941 | 18 | } |
|||||
| 942 | } |
||||||
| 943 |