Completed
Push — master ( a931c2...f3802f )
by Joshua
19s queued 11s
created

maybeStripNationalPrefixAndCarrierCode()   C

Complexity

Conditions 17
Paths 8

Size

Total Lines 61
Code Lines 36

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 34
CRAP Score 17.0496

Importance

Changes 1
Bugs 1 Features 0
Metric Value
cc 17
eloc 36
c 1
b 1
f 0
nc 8
nop 3
dl 0
loc 61
ccs 34
cts 36
cp 0.9444
crap 17.0496
rs 5.2166

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace libphonenumber;
4
5
use libphonenumber\Leniency\AbstractLeniency;
6
7
/**
8
 * Utility for international phone numbers. Functionality includes formatting, parsing and
9
 * validation.
10
 *
11
 * <p>If you use this library, and want to be notified about important changes, please sign up to
12
 * our <a href="http://groups.google.com/group/libphonenumber-discuss/about">mailing list</a>.
13
 *
14
 * NOTE: A lot of methods in this class require Region Code strings. These must be provided using
15
 * CLDR two-letter region-code format. These should be in upper-case. The list of the codes
16
 * can be found here:
17
 * http://www.unicode.org/cldr/charts/30/supplemental/territory_information.html
18
 *
19
 * @author Shaopeng Jia
20
 * @see https://github.com/google/libphonenumber
21
 */
22
class PhoneNumberUtil
23
{
24
    /** Flags to use when compiling regular expressions for phone numbers */
25
    const REGEX_FLAGS = 'ui'; //Unicode and case insensitive
26
    // The minimum and maximum length of the national significant number.
27
    const MIN_LENGTH_FOR_NSN = 2;
28
    // The ITU says the maximum length should be 15, but we have found longer numbers in Germany.
29
    const MAX_LENGTH_FOR_NSN = 17;
30
31
    // We don't allow input strings for parsing to be longer than 250 chars. This prevents malicious
32
    // input from overflowing the regular-expression engine.
33
    const MAX_INPUT_STRING_LENGTH = 250;
34
35
    // The maximum length of the country calling code.
36
    const MAX_LENGTH_COUNTRY_CODE = 3;
37
38
    const REGION_CODE_FOR_NON_GEO_ENTITY = '001';
39
    const META_DATA_FILE_PREFIX = 'PhoneNumberMetadata';
40
    const TEST_META_DATA_FILE_PREFIX = 'PhoneNumberMetadataForTesting';
41
42
    // Region-code for the unknown region.
43
    const UNKNOWN_REGION = 'ZZ';
44
45
    const NANPA_COUNTRY_CODE = 1;
46
    /*
47
     * The prefix that needs to be inserted in front of a Colombian landline number when dialed from
48
     * a mobile number in Colombia.
49
     */
50
    const COLOMBIA_MOBILE_TO_FIXED_LINE_PREFIX = '3';
51
    // The PLUS_SIGN signifies the international prefix.
52
    const PLUS_SIGN = '+';
53
    const PLUS_CHARS = '++';
54
    const STAR_SIGN = '*';
55
56
    const RFC3966_EXTN_PREFIX = ';ext=';
57
    const RFC3966_PREFIX = 'tel:';
58
    const RFC3966_PHONE_CONTEXT = ';phone-context=';
59
    const RFC3966_ISDN_SUBADDRESS = ';isub=';
60
61
    // We use this pattern to check if the phone number has at least three letters in it - if so, then
62
    // we treat it as a number where some phone-number digits are represented by letters.
63
    const VALID_ALPHA_PHONE_PATTERN = '(?:.*?[A-Za-z]){3}.*';
64
    // We accept alpha characters in phone numbers, ASCII only, upper and lower case.
65
    const VALID_ALPHA = 'A-Za-z';
66
67
68
    // Default extension prefix to use when formatting. This will be put in front of any extension
69
    // component of the number, after the main national number is formatted. For example, if you wish
70
    // the default extension formatting to be " extn: 3456", then you should specify " extn: " here
71
    // as the default extension prefix. This can be overridden by region-specific preferences.
72
    const DEFAULT_EXTN_PREFIX = ' ext. ';
73
74
    // Regular expression of acceptable punctuation found in phone numbers, used to find numbers in
75
    // text and to decide what is a viable phone number. This excludes diallable characters.
76
    // This consists of dash characters, white space characters, full stops, slashes,
77
    // square brackets, parentheses and tildes. It also includes the letter 'x' as that is found as a
78
    // placeholder for carrier information in some phone numbers. Full-width variants are also
79
    // present.
80
    const VALID_PUNCTUATION = "-x\xE2\x80\x90-\xE2\x80\x95\xE2\x88\x92\xE3\x83\xBC\xEF\xBC\x8D-\xEF\xBC\x8F \xC2\xA0\xC2\xAD\xE2\x80\x8B\xE2\x81\xA0\xE3\x80\x80()\xEF\xBC\x88\xEF\xBC\x89\xEF\xBC\xBB\xEF\xBC\xBD.\\[\\]/~\xE2\x81\x93\xE2\x88\xBC";
81
    const DIGITS = "\\p{Nd}";
82
83
    // Pattern that makes it easy to distinguish whether a region has a single international dialing
84
    // prefix or not. If a region has a single international prefix (e.g. 011 in USA), it will be
85
    // represented as a string that contains a sequence of ASCII digits, and possible a tilde, which
86
    // signals waiting for the tone. If there are multiple available international prefixes in a
87
    // region, they will be represented as a regex string that always contains one or more characters
88
    // that are not ASCII digits or a tilde.
89
    const SINGLE_INTERNATIONAL_PREFIX = "[\\d]+(?:[~\xE2\x81\x93\xE2\x88\xBC\xEF\xBD\x9E][\\d]+)?";
90
    const NON_DIGITS_PATTERN = "(\\D+)";
91
92
    // The FIRST_GROUP_PATTERN was originally set to $1 but there are some countries for which the
93
    // first group is not used in the national pattern (e.g. Argentina) so the $1 group does not match
94
    // correctly. Therefore, we use \d, so that the first group actually used in the pattern will be
95
    // matched.
96
    const FIRST_GROUP_PATTERN = "(\\$\\d)";
97
    // Constants used in the formatting rules to represent the national prefix, first group and
98
    // carrier code respectively.
99
    const NP_STRING = '$NP';
100
    const FG_STRING = '$FG';
101
    const CC_STRING = '$CC';
102
103
    // A pattern that is used to determine if the national prefix formatting rule has the first group
104
    // only, i.e, does not start with the national prefix. Note that the pattern explicitly allows
105
    // for unbalanced parentheses.
106
    const FIRST_GROUP_ONLY_PREFIX_PATTERN = '\\(?\\$1\\)?';
107
    public static $PLUS_CHARS_PATTERN;
108
    protected static $SEPARATOR_PATTERN;
109
    protected static $CAPTURING_DIGIT_PATTERN;
110
    protected static $VALID_START_CHAR_PATTERN;
111
    public static $SECOND_NUMBER_START_PATTERN = '[\\\\/] *x';
112
    public static $UNWANTED_END_CHAR_PATTERN = "[[\\P{N}&&\\P{L}]&&[^#]]+$";
113
    protected static $DIALLABLE_CHAR_MAPPINGS = array();
114
    protected static $CAPTURING_EXTN_DIGITS;
115
116
    /**
117
     * @var PhoneNumberUtil
118
     */
119
    protected static $instance;
120
121
    /**
122
     * Only upper-case variants of alpha characters are stored.
123
     *
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 672
    protected function __construct(MetadataSourceInterface $metadataSource, $countryCallingCodeToRegionCodeMap)
347
    {
348 672
        $this->metadataSource = $metadataSource;
349 672
        $this->countryCallingCodeToRegionCodeMap = $countryCallingCodeToRegionCodeMap;
350 672
        $this->init();
351 672
        $this->matcherAPI = RegexBasedMatcher::create();
352 672
        static::initExtnPatterns();
353 672
        static::initExtnPattern();
354 672
        static::$PLUS_CHARS_PATTERN = '[' . static::PLUS_CHARS . ']+';
355 672
        static::$SEPARATOR_PATTERN = '[' . static::VALID_PUNCTUATION . ']+';
356 672
        static::$CAPTURING_DIGIT_PATTERN = '(' . static::DIGITS . ')';
357 672
        static::initValidStartCharPattern();
358 672
        static::initAlphaPhoneMappings();
359 672
        static::initDiallableCharMappings();
360
361 672
        static::$ALL_PLUS_NUMBER_GROUPING_SYMBOLS = array();
362
        // Put (lower letter -> upper letter) and (upper letter -> upper letter) mappings.
363 672
        foreach (static::$ALPHA_MAPPINGS as $c => $value) {
364 672
            static::$ALL_PLUS_NUMBER_GROUPING_SYMBOLS[strtolower($c)] = $c;
365 672
            static::$ALL_PLUS_NUMBER_GROUPING_SYMBOLS[$c] = $c;
366
        }
367 672
        static::$ALL_PLUS_NUMBER_GROUPING_SYMBOLS += static::$asciiDigitMappings;
368 672
        static::$ALL_PLUS_NUMBER_GROUPING_SYMBOLS['-'] = '-';
369 672
        static::$ALL_PLUS_NUMBER_GROUPING_SYMBOLS["\xEF\xBC\x8D"] = '-';
370 672
        static::$ALL_PLUS_NUMBER_GROUPING_SYMBOLS["\xE2\x80\x90"] = '-';
371 672
        static::$ALL_PLUS_NUMBER_GROUPING_SYMBOLS["\xE2\x80\x91"] = '-';
372 672
        static::$ALL_PLUS_NUMBER_GROUPING_SYMBOLS["\xE2\x80\x92"] = '-';
373 672
        static::$ALL_PLUS_NUMBER_GROUPING_SYMBOLS["\xE2\x80\x93"] = '-';
374 672
        static::$ALL_PLUS_NUMBER_GROUPING_SYMBOLS["\xE2\x80\x94"] = '-';
375 672
        static::$ALL_PLUS_NUMBER_GROUPING_SYMBOLS["\xE2\x80\x95"] = '-';
376 672
        static::$ALL_PLUS_NUMBER_GROUPING_SYMBOLS["\xE2\x88\x92"] = '-';
377 672
        static::$ALL_PLUS_NUMBER_GROUPING_SYMBOLS['/'] = '/';
378 672
        static::$ALL_PLUS_NUMBER_GROUPING_SYMBOLS["\xEF\xBC\x8F"] = '/';
379 672
        static::$ALL_PLUS_NUMBER_GROUPING_SYMBOLS[' '] = ' ';
380 672
        static::$ALL_PLUS_NUMBER_GROUPING_SYMBOLS["\xE3\x80\x80"] = ' ';
381 672
        static::$ALL_PLUS_NUMBER_GROUPING_SYMBOLS["\xE2\x81\xA0"] = ' ';
382 672
        static::$ALL_PLUS_NUMBER_GROUPING_SYMBOLS['.'] = '.';
383 672
        static::$ALL_PLUS_NUMBER_GROUPING_SYMBOLS["\xEF\xBC\x8E"] = '.';
384
385
386 672
        static::initValidPhoneNumberPatterns();
387
388 672
        static::$UNWANTED_END_CHAR_PATTERN = '[^' . static::DIGITS . static::VALID_ALPHA . '#]+$';
389
390 672
        static::initMobileTokenMappings();
391
392 672
        static::$GEO_MOBILE_COUNTRIES_WITHOUT_MOBILE_AREA_CODES = array();
393 672
        static::$GEO_MOBILE_COUNTRIES_WITHOUT_MOBILE_AREA_CODES[] = 86; // China
394
395 672
        static::$GEO_MOBILE_COUNTRIES = array();
396 672
        static::$GEO_MOBILE_COUNTRIES[] = 52; // Mexico
397 672
        static::$GEO_MOBILE_COUNTRIES[] = 54; // Argentina
398 672
        static::$GEO_MOBILE_COUNTRIES[] = 55; // Brazil
399 672
        static::$GEO_MOBILE_COUNTRIES[] = 62; // Indonesia: some prefixes only (fixed CMDA wireless)
400
401 672
        static::$GEO_MOBILE_COUNTRIES = array_merge(static::$GEO_MOBILE_COUNTRIES, static::$GEO_MOBILE_COUNTRIES_WITHOUT_MOBILE_AREA_CODES);
402 672
    }
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 6325
    public static function getInstance($baseFileLocation = self::META_DATA_FILE_PREFIX, array $countryCallingCodeToRegionCodeMap = null, MetadataLoaderInterface $metadataLoader = null, MetadataSourceInterface $metadataSource = null)
419
    {
420 6325
        if (static::$instance === null) {
421 672
            if ($countryCallingCodeToRegionCodeMap === null) {
422 270
                $countryCallingCodeToRegionCodeMap = CountryCodeToRegionCodeMap::$countryCodeToRegionCodeMap;
423
            }
424
425 672
            if ($metadataLoader === null) {
426 672
                $metadataLoader = new DefaultMetadataLoader();
427
            }
428
429 672
            if ($metadataSource === null) {
430 672
                $metadataSource = new MultiFileMetadataSourceImpl($metadataLoader, __DIR__ . '/data/' . $baseFileLocation);
431
            }
432
433 672
            static::$instance = new static($metadataSource, $countryCallingCodeToRegionCodeMap);
434
        }
435 6325
        return static::$instance;
436
    }
437
438 672
    protected function init()
439
    {
440 672
        $supportedRegions = array(array());
441
442 672
        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 672
            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 672
                $this->countryCodesForNonGeographicalRegion[] = $countryCode;
448
            } else {
449
                // The supported regions set does not include the "001" non-geo entity region code.
450 672
                $supportedRegions[] = $regionCodes;
451
            }
452
        }
453
454 672
        $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 672
        $idx_region_code_non_geo_entity = array_search(static::REGION_CODE_FOR_NON_GEO_ENTITY, $this->supportedRegions);
461 672
        if ($idx_region_code_non_geo_entity !== false) {
462
            unset($this->supportedRegions[$idx_region_code_non_geo_entity]);
463
        }
464 672
        $this->nanpaRegions = $this->countryCallingCodeToRegionCodeMap[static::NANPA_COUNTRY_CODE];
465 672
    }
466
467
    /**
468
     * @internal
469
     */
470 674
    public static function initExtnPatterns()
471
    {
472 674
        static::$EXTN_PATTERNS_FOR_PARSING = static::createExtnPattern(true);
473 674
        static::$EXTN_PATTERNS_FOR_MATCHING = static::createExtnPattern(false);
474 674
    }
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 674
    private static function extnDigits($maxLength)
483
    {
484 674
        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 674
    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 674
        $extLimitAfterExplicitLabel = 20;
502 674
        $extLimitAfterLikelyLabel = 15;
503 674
        $extLimitAfterAmbiguousChar = 9;
504 674
        $extLimitWhenNotSure = 6;
505
        
506
        
507
508 674
        $possibleSeparatorsBetweenNumberAndExtLabel = "[ \xC2\xA0\\t,]*";
509
        // Optional full stop (.) or colon, followed by zero or more spaces/tabs/commas.
510 674
        $possibleCharsAfterExtLabel = "[:\\.\xEf\xBC\x8E]?[ \xC2\xA0\\t,-]*";
511 674
        $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 674
        $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 674
        $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 674
        $ambiguousSeparator = "[- ]+";
523
524 674
        $rfcExtn = static::RFC3966_EXTN_PREFIX . static::extnDigits($extLimitAfterExplicitLabel);
525 674
        $explicitExtn = $possibleSeparatorsBetweenNumberAndExtLabel . $explicitExtLabels
526 674
            . $possibleCharsAfterExtLabel . static::extnDigits($extLimitAfterExplicitLabel)
527 674
            . $optionalExtnSuffix;
528 674
        $ambiguousExtn = $possibleSeparatorsBetweenNumberAndExtLabel . $ambiguousExtLabels
529 674
            . $possibleCharsAfterExtLabel . static::extnDigits($extLimitAfterAmbiguousChar) . $optionalExtnSuffix;
530 674
        $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 674
            $rfcExtn . "|"
542 674
            . $explicitExtn . "|"
543 674
            . $ambiguousExtn . "|"
544 674
            . $americanStyleExtnWithSuffix;
545
        // Additional pattern that is supported when parsing extensions, not when matching.
546 674
        if ($forParsing) {
547
            // This is same as possibleSeparatorsBetweenNumberAndExtLabel, but not matching comma as
548
            // extension label may have it.
549 674
            $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 674
            $autoDiallingAndExtLabelsFound = "(?:,{2}|;)";
554
555
            $autoDiallingExtn = $possibleSeparatorsNumberExtLabelNoComma
556 674
                . $autoDiallingAndExtLabelsFound . $possibleCharsAfterExtLabel
557 674
                . static::extnDigits($extLimitAfterLikelyLabel) . $optionalExtnSuffix;
558
            $onlyCommasExtn = $possibleSeparatorsNumberExtLabelNoComma
559 674
                . '(?:,)+' . $possibleCharsAfterExtLabel . static::extnDigits($extLimitAfterAmbiguousChar)
560 674
                . $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 674
            return $extensionPattern . "|"
566 674
                . $autoDiallingExtn . "|"
567 674
                . $onlyCommasExtn;
568
        }
569 674
        return $extensionPattern;
570
    }
571
572 672
    protected static function initExtnPattern()
573
    {
574 672
        static::$EXTN_PATTERN = '/(?:' . static::$EXTN_PATTERNS_FOR_PARSING . ')$/' . static::REGEX_FLAGS;
575 672
    }
576
577 674
    protected static function initValidPhoneNumberPatterns()
578
    {
579 674
        static::initExtnPatterns();
580 674
        static::$MIN_LENGTH_PHONE_NUMBER_PATTERN = '[' . static::DIGITS . ']{' . static::MIN_LENGTH_FOR_NSN . '}';
581 674
        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 674
        static::$VALID_PHONE_NUMBER_PATTERN = '%^' . static::$MIN_LENGTH_PHONE_NUMBER_PATTERN . '$|^' . static::$VALID_PHONE_NUMBER . '(?:' . static::$EXTN_PATTERNS_FOR_PARSING . ')?$%' . static::REGEX_FLAGS;
583 674
    }
584
585 674
    protected static function initAlphaPhoneMappings()
586
    {
587 674
        static::$ALPHA_PHONE_MAPPINGS = static::$ALPHA_MAPPINGS + static::$asciiDigitMappings;
588 674
    }
589
590 673
    protected static function initValidStartCharPattern()
591
    {
592 673
        static::$VALID_START_CHAR_PATTERN = '[' . static::PLUS_CHARS . static::DIGITS . ']';
593 673
    }
594
595 673
    protected static function initMobileTokenMappings()
596
    {
597 673
        static::$MOBILE_TOKEN_MAPPINGS = array();
598 673
        static::$MOBILE_TOKEN_MAPPINGS['54'] = '9';
599 673
    }
600
601 673
    protected static function initDiallableCharMappings()
602
    {
603 673
        static::$DIALLABLE_CHAR_MAPPINGS = static::$asciiDigitMappings;
604 673
        static::$DIALLABLE_CHAR_MAPPINGS[static::PLUS_SIGN] = static::PLUS_SIGN;
605 673
        static::$DIALLABLE_CHAR_MAPPINGS['*'] = '*';
606 673
        static::$DIALLABLE_CHAR_MAPPINGS['#'] = '#';
607 673
    }
608
609
    /**
610
     * Used for testing purposes only to reset the PhoneNumberUtil singleton to null.
611
     */
612 678
    public static function resetInstance()
613
    {
614 678
        static::$instance = null;
615 678
    }
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 2
    public static function convertAlphaCharactersInNumber($number)
625
    {
626 2
        if (static::$ALPHA_PHONE_MAPPINGS === null) {
0 ignored issues
show
introduced by
The condition static::ALPHA_PHONE_MAPPINGS === null is always false.
Loading history...
627 1
            static::initAlphaPhoneMappings();
628
        }
629
630 2
        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 41
    protected static function normalizeHelper($number, array $normalizationReplacements, $removeNonMatches)
646
    {
647 41
        $normalizedNumber = '';
648 41
        $strLength = mb_strlen($number, 'UTF-8');
649 41
        for ($i = 0; $i < $strLength; $i++) {
650 41
            $character = mb_substr($number, $i, 1, 'UTF-8');
651 41
            if (isset($normalizationReplacements[mb_strtoupper($character, 'UTF-8')])) {
652 41
                $normalizedNumber .= $normalizationReplacements[mb_strtoupper($character, 'UTF-8')];
653 41
            } elseif (!$removeNonMatches) {
654 2
                $normalizedNumber .= $character;
655
            }
656
            // If neither of the above are true, we remove this character.
657
        }
658 41
        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 61
    public static function formattingRuleHasFirstGroupOnly($nationalPrefixFormattingRule)
669
    {
670 61
        $firstGroupOnlyPrefixPatternMatcher = new Matcher(
671 61
            static::FIRST_GROUP_ONLY_PREFIX_PATTERN,
672
            $nationalPrefixFormattingRule
673
        );
674
675 61
        return mb_strlen($nationalPrefixFormattingRule) === 0
676 61
            || $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 246
    public function getSupportedRegions()
686
    {
687 246
        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 2
    public function getSupportedGlobalNetworkCallingCodes()
697
    {
698 2
        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 1
    public function getSupportedCallingCodes()
711
    {
712 1
        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 5
    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 5
        $possibleLength = $desc->getPossibleLength();
726 5
        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 2
    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 2
        return $desc->hasExampleNumber()
742 2
            || static::descHasPossibleNumberData($desc)
743 2
            || $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 2
    private function getSupportedTypesForMetadata(PhoneMetadata $metadata)
753
    {
754 2
        $types = array();
755 2
        foreach (array_keys(PhoneNumberType::values()) as $type) {
756 2
            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 2
                continue;
760
            }
761
762 2
            if (self::descHasData($this->getNumberDescByType($metadata, $type))) {
763 2
                $types[] = $type;
764
            }
765
        }
766
767 2
        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 1
    public function getSupportedTypesForRegion($regionCode)
781
    {
782 1
        if (!$this->isValidRegionCode($regionCode)) {
783 1
            return array();
784
        }
785 1
        $metadata = $this->getMetadataForRegion($regionCode);
786 1
        return $this->getSupportedTypesForMetadata($metadata);
0 ignored issues
show
Bug introduced by
It seems like $metadata can also be of type null; however, parameter $metadata of libphonenumber\PhoneNumb...ortedTypesForMetadata() does only seem to accept libphonenumber\PhoneMetadata, 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 ignore-type  annotation

786
        return $this->getSupportedTypesForMetadata(/** @scrutinizer ignore-type */ $metadata);
Loading history...
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 1
    public function getSupportedTypesForNonGeoEntity($countryCallingCode)
799
    {
800 1
        $metadata = $this->getMetadataForNonGeographicalRegion($countryCallingCode);
801 1
        if ($metadata === null) {
802 1
            return array();
803
        }
804
805 1
        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 1
    public function getLengthOfGeographicalAreaCode(PhoneNumber $number)
848
    {
849 1
        $metadata = $this->getMetadataForRegion($this->getRegionCodeForNumber($number));
850 1
        if ($metadata === null) {
851 1
            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 1
        if (!$metadata->hasNationalPrefix() && !$number->isItalianLeadingZero()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $number->isItalianLeadingZero() of type boolean|null is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
856 1
            return 0;
857
        }
858
859 1
        $type = $this->getNumberType($number);
860 1
        $countryCallingCode = $number->getCountryCode();
861
862 1
        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 1
            && in_array($countryCallingCode, self::$GEO_MOBILE_COUNTRIES_WITHOUT_MOBILE_AREA_CODES)
867
        ) {
868 1
            return 0;
869
        }
870
871 1
        if (!$this->isNumberGeographical($type, $countryCallingCode)) {
872 1
            return 0;
873
        }
874
875 1
        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 5228
    public function getMetadataForRegion($regionCode)
886
    {
887 5228
        if (!$this->isValidRegionCode($regionCode)) {
888 349
            return null;
889
        }
890
891 5215
        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 5229
    protected function isValidRegionCode($regionCode)
901
    {
902 5229
        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 2293
    public function getRegionCodeForNumber(PhoneNumber $number)
915
    {
916 2293
        $countryCode = $number->getCountryCode();
917 2293
        if (!isset($this->countryCallingCodeToRegionCodeMap[$countryCode])) {
918 4
            return null;
919
        }
920 2292
        $regions = $this->countryCallingCodeToRegionCodeMap[$countryCode];
921 2292
        if (count($regions) == 1) {
922 1724
            return $regions[0];
923
        }
924
925 592
        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 592
    protected function getRegionCodeForNumberFromRegionList(PhoneNumber $number, array $regionCodes)
936
    {
937 592
        $nationalNumber = $this->getNationalSignificantNumber($number);
938 592
        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 592
            $metadata = $this->getMetadataForRegion($regionCode);
942 592
            if ($metadata->hasLeadingDigits()) {
943 286
                $nbMatches = preg_match(
944 286
                    '/' . $metadata->getLeadingDigits() . '/',
945
                    $nationalNumber,
946
                    $matches,
947 286
                    PREG_OFFSET_CAPTURE
948
                );
949 286
                if ($nbMatches > 0 && $matches[0][1] === 0) {
950 286
                    return $regionCode;
951
                }
952 490
            } elseif ($this->getNumberTypeHelper($nationalNumber, $metadata) != PhoneNumberType::UNKNOWN) {
0 ignored issues
show
Bug introduced by
It seems like $metadata can also be of type null; however, parameter $metadata of libphonenumber\PhoneNumb...::getNumberTypeHelper() does only seem to accept libphonenumber\PhoneMetadata, 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 ignore-type  annotation

952
            } elseif ($this->getNumberTypeHelper($nationalNumber, /** @scrutinizer ignore-type */ $metadata) != PhoneNumberType::UNKNOWN) {
Loading history...
953 275
                return $regionCode;
954
            }
955
        }
956 70
        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 2193
    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 2193
        $nationalNumber = '';
970 2193
        if ($number->isItalianLeadingZero() && $number->getNumberOfLeadingZeros() > 0) {
971 82
            $zeros = str_repeat('0', $number->getNumberOfLeadingZeros());
972 82
            $nationalNumber .= $zeros;
973
        }
974 2193
        $nationalNumber .= $number->getNationalNumber();
975 2193
        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 2094
    protected function getNumberTypeHelper($nationalNumber, PhoneMetadata $metadata)
986
    {
987 2094
        if (!$this->isNumberMatchingDesc($nationalNumber, $metadata->getGeneralDesc())) {
988 319
            return PhoneNumberType::UNKNOWN;
989
        }
990 1833
        if ($this->isNumberMatchingDesc($nationalNumber, $metadata->getPremiumRate())) {
991 163
            return PhoneNumberType::PREMIUM_RATE;
992
        }
993 1671
        if ($this->isNumberMatchingDesc($nationalNumber, $metadata->getTollFree())) {
994 197
            return PhoneNumberType::TOLL_FREE;
995
        }
996
997
998 1486
        if ($this->isNumberMatchingDesc($nationalNumber, $metadata->getSharedCost())) {
999 56
            return PhoneNumberType::SHARED_COST;
1000
        }
1001 1430
        if ($this->isNumberMatchingDesc($nationalNumber, $metadata->getVoip())) {
1002 92
            return PhoneNumberType::VOIP;
1003
        }
1004 1342
        if ($this->isNumberMatchingDesc($nationalNumber, $metadata->getPersonalNumber())) {
1005 61
            return PhoneNumberType::PERSONAL_NUMBER;
1006
        }
1007 1281
        if ($this->isNumberMatchingDesc($nationalNumber, $metadata->getPager())) {
1008 25
            return PhoneNumberType::PAGER;
1009
        }
1010 1259
        if ($this->isNumberMatchingDesc($nationalNumber, $metadata->getUan())) {
1011 66
            return PhoneNumberType::UAN;
1012
        }
1013 1196
        if ($this->isNumberMatchingDesc($nationalNumber, $metadata->getVoicemail())) {
1014 15
            return PhoneNumberType::VOICEMAIL;
1015
        }
1016 1182
        $isFixedLine = $this->isNumberMatchingDesc($nationalNumber, $metadata->getFixedLine());
1017 1182
        if ($isFixedLine) {
1018 885
            if ($metadata->getSameMobileAndFixedLinePattern()) {
1019
                return PhoneNumberType::FIXED_LINE_OR_MOBILE;
1020
            }
1021
1022 885
            if ($this->isNumberMatchingDesc($nationalNumber, $metadata->getMobile())) {
1023 112
                return PhoneNumberType::FIXED_LINE_OR_MOBILE;
1024
            }
1025 782
            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 425
        if (!$metadata->getSameMobileAndFixedLinePattern() &&
1030 425
            $this->isNumberMatchingDesc($nationalNumber, $metadata->getMobile())
1031
        ) {
1032 260
            return PhoneNumberType::MOBILE;
1033
        }
1034 190
        return PhoneNumberType::UNKNOWN;
1035
    }
1036
1037
    /**
1038
     * @param string $nationalNumber
1039
     * @param PhoneNumberDesc $numberDesc
1040
     * @return bool
1041
     */
1042 2094
    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 2094
        $actualLength = mb_strlen($nationalNumber);
1048 2094
        $possibleLengths = $numberDesc->getPossibleLength();
1049 2094
        if (count($possibleLengths) > 0 && !in_array($actualLength, $possibleLengths)) {
1050 1692
            return false;
1051
        }
1052
1053 1871
        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 21
    public function isNumberGeographical($phoneNumberObjOrType, $countryCallingCode = null)
1076
    {
1077 21
        if ($phoneNumberObjOrType instanceof PhoneNumber) {
1078 1
            return $this->isNumberGeographical($this->getNumberType($phoneNumberObjOrType), $phoneNumberObjOrType->getCountryCode());
1079
        }
1080
1081 21
        return $phoneNumberObjOrType == PhoneNumberType::FIXED_LINE
1082 17
        || $phoneNumberObjOrType == PhoneNumberType::FIXED_LINE_OR_MOBILE
1083 12
        || (in_array($countryCallingCode, static::$GEO_MOBILE_COUNTRIES)
1084 21
            && $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 1402
    public function getNumberType(PhoneNumber $number)
1094
    {
1095 1402
        $regionCode = $this->getRegionCodeForNumber($number);
1096 1402
        $metadata = $this->getMetadataForRegionOrCallingCode($number->getCountryCode(), $regionCode);
1097 1402
        if ($metadata === null) {
1098 8
            return PhoneNumberType::UNKNOWN;
1099
        }
1100 1401
        $nationalSignificantNumber = $this->getNationalSignificantNumber($number);
1101 1401
        return $this->getNumberTypeHelper($nationalSignificantNumber, $metadata);
1102
    }
1103
1104
    /**
1105
     * @param int $countryCallingCode
1106
     * @param string $regionCode
1107
     * @return null|PhoneMetadata
1108
     */
1109 2146
    protected function getMetadataForRegionOrCallingCode($countryCallingCode, $regionCode)
1110
    {
1111 2146
        return static::REGION_CODE_FOR_NON_GEO_ENTITY === $regionCode ?
1112 2146
            $this->getMetadataForNonGeographicalRegion($countryCallingCode) : $this->getMetadataForRegion($regionCode);
1113
    }
1114
1115
    /**
1116
     * @param int $countryCallingCode
1117
     * @return null|PhoneMetadata
1118
     */
1119 34
    public function getMetadataForNonGeographicalRegion($countryCallingCode)
1120
    {
1121 34
        if (!isset($this->countryCallingCodeToRegionCodeMap[$countryCallingCode])) {
1122 2
            return null;
1123
        }
1124 34
        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 2
    public function getLengthOfNationalDestinationCode(PhoneNumber $number)
1162
    {
1163 2
        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 2
            $copiedProto = clone $number;
1171
        }
1172
1173 2
        $nationalSignificantNumber = $this->format($copiedProto, PhoneNumberFormat::INTERNATIONAL);
1174
1175 2
        $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 2
        if (count($numberGroups) <= 3) {
0 ignored issues
show
Bug introduced by
It seems like $numberGroups can also be of type false; however, parameter $var of count() does only seem to accept Countable|array, 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 ignore-type  annotation

1180
        if (count(/** @scrutinizer ignore-type */ $numberGroups) <= 3) {
Loading history...
1181 1
            return 0;
1182
        }
1183
1184 2
        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 2
            $mobileToken = static::getCountryMobileToken($number->getCountryCode());
1192 2
            if ($mobileToken !== '') {
1193 2
                return mb_strlen($numberGroups[2]) + mb_strlen($numberGroups[3]);
1194
            }
1195
        }
1196 2
        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 344
    public function format(PhoneNumber $number, $numberFormat)
1214
    {
1215 344
        if ($number->getNationalNumber() == 0 && $number->hasRawInput()) {
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing $number->getNationalNumber() of type null|string to 0; this is ambiguous as not only 0 == 0 is true, but null == 0 is true, too. Consider using a strict comparison ===.
Loading history...
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 1
            $rawInput = $number->getRawInput();
1222 1
            if (mb_strlen($rawInput) > 0) {
1223 1
                return $rawInput;
1224
            }
1225
        }
1226
1227 344
        $formattedNumber = '';
1228 344
        $countryCallingCode = $number->getCountryCode();
1229 344
        $nationalSignificantNumber = $this->getNationalSignificantNumber($number);
1230
1231 344
        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 266
            $formattedNumber .= $nationalSignificantNumber;
1235 266
            $this->prefixNumberWithCountryCallingCode($countryCallingCode, PhoneNumberFormat::E164, $formattedNumber);
1236 266
            return $formattedNumber;
1237
        }
1238
1239 95
        if (!$this->hasValidCountryCallingCode($countryCallingCode)) {
1240 1
            $formattedNumber .= $nationalSignificantNumber;
1241 1
            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 95
        $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 95
        $metadata = $this->getMetadataForRegionOrCallingCode($countryCallingCode, $regionCode);
1251 95
        $formattedNumber .= $this->formatNsn($nationalSignificantNumber, $metadata, $numberFormat);
1252 95
        $this->maybeAppendFormattedExtension($number, $metadata, $numberFormat, $formattedNumber);
1253 95
        $this->prefixNumberWithCountryCallingCode($countryCallingCode, $numberFormat, $formattedNumber);
1254 95
        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 345
    protected function prefixNumberWithCountryCallingCode($countryCallingCode, $numberFormat, &$formattedNumber)
1264
    {
1265
        switch ($numberFormat) {
1266 345
            case PhoneNumberFormat::E164:
1267 266
                $formattedNumber = static::PLUS_SIGN . $countryCallingCode . $formattedNumber;
1268 266
                return;
1269 96
            case PhoneNumberFormat::INTERNATIONAL:
1270 20
                $formattedNumber = static::PLUS_SIGN . $countryCallingCode . ' ' . $formattedNumber;
1271 20
                return;
1272 93
            case PhoneNumberFormat::RFC3966:
1273 59
                $formattedNumber = static::RFC3966_PREFIX . static::PLUS_SIGN . $countryCallingCode . '-' . $formattedNumber;
1274 59
                return;
1275 39
            case PhoneNumberFormat::NATIONAL:
1276
            default:
1277 39
                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 166
    protected function hasValidCountryCallingCode($countryCallingCode)
1287
    {
1288 166
        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 525
    public function getRegionCodeForCountryCode($countryCallingCode)
1303
    {
1304 525
        $regionCodes = isset($this->countryCallingCodeToRegionCodeMap[$countryCallingCode]) ? $this->countryCallingCodeToRegionCodeMap[$countryCallingCode] : null;
1305 525
        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 96
    protected function formatNsn($number, PhoneMetadata $metadata, $numberFormat, $carrierCode = null)
1320
    {
1321 96
        $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 96
        $availableFormats = (count($intlNumberFormats) == 0 || $numberFormat == PhoneNumberFormat::NATIONAL)
1325 76
            ? $metadata->numberFormats()
1326 96
            : $metadata->intlNumberFormats();
1327 96
        $formattingPattern = $this->chooseFormattingPatternForNumber($availableFormats, $number);
1328 96
        return ($formattingPattern === null)
1329 8
            ? $number
1330 96
            : $this->formatNsnUsingPattern($number, $formattingPattern, $numberFormat, $carrierCode);
1331
    }
1332
1333
    /**
1334
     * @param NumberFormat[] $availableFormats
1335
     * @param string $nationalNumber
1336
     * @return NumberFormat|null
1337
     */
1338 129
    public function chooseFormattingPatternForNumber(array $availableFormats, $nationalNumber)
1339
    {
1340 129
        foreach ($availableFormats as $numFormat) {
1341 129
            $leadingDigitsPatternMatcher = null;
1342 129
            $size = $numFormat->leadingDigitsPatternSize();
1343
            // We always use the last leading_digits_pattern, as it is the most detailed.
1344 129
            if ($size > 0) {
1345 98
                $leadingDigitsPatternMatcher = new Matcher(
1346 98
                    $numFormat->getLeadingDigitsPattern($size - 1),
1347
                    $nationalNumber
1348
                );
1349
            }
1350 129
            if ($size == 0 || $leadingDigitsPatternMatcher->lookingAt()) {
1351 128
                $m = new Matcher($numFormat->getPattern(), $nationalNumber);
1352 128
                if ($m->matches() > 0) {
1353 128
                    return $numFormat;
1354
                }
1355
            }
1356
        }
1357 9
        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 96
    public function formatNsnUsingPattern(
1370
        $nationalNumber,
1371
        NumberFormat $formattingPattern,
1372
        $numberFormat,
1373
        $carrierCode = null
1374
    ) {
1375 96
        $numberFormatRule = $formattingPattern->getFormat();
1376 96
        $m = new Matcher($formattingPattern->getPattern(), $nationalNumber);
1377 96
        if ($numberFormat === PhoneNumberFormat::NATIONAL &&
1378 96
            $carrierCode !== null && mb_strlen($carrierCode) > 0 &&
1379 96
            mb_strlen($formattingPattern->getDomesticCarrierCodeFormattingRule()) > 0
1380
        ) {
1381
            // Replace the $CC in the formatting rule with the desired carrier code.
1382 2
            $carrierCodeFormattingRule = $formattingPattern->getDomesticCarrierCodeFormattingRule();
1383 2
            $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 2
            $firstGroupMatcher = new Matcher(static::FIRST_GROUP_PATTERN, $numberFormatRule);
1387 2
            $numberFormatRule = $firstGroupMatcher->replaceFirst($carrierCodeFormattingRule);
1388 2
            $formattedNationalNumber = $m->replaceAll($numberFormatRule);
1389
        } else {
1390
            // Use the national prefix formatting rule instead.
1391 96
            $nationalPrefixFormattingRule = $formattingPattern->getNationalPrefixFormattingRule();
1392 96
            if ($numberFormat == PhoneNumberFormat::NATIONAL &&
1393 96
                $nationalPrefixFormattingRule !== null &&
1394 96
                mb_strlen($nationalPrefixFormattingRule) > 0
1395
            ) {
1396 22
                $firstGroupMatcher = new Matcher(static::FIRST_GROUP_PATTERN, $numberFormatRule);
1397 22
                $formattedNationalNumber = $m->replaceAll(
1398 22
                    $firstGroupMatcher->replaceFirst($nationalPrefixFormattingRule)
1399
                );
1400
            } else {
1401 89
                $formattedNationalNumber = $m->replaceAll($numberFormatRule);
1402
            }
1403
        }
1404 96
        if ($numberFormat == PhoneNumberFormat::RFC3966) {
1405
            // Strip any leading punctuation.
1406 59
            $matcher = new Matcher(static::$SEPARATOR_PATTERN, $formattedNationalNumber);
1407 59
            if ($matcher->lookingAt()) {
1408 1
                $formattedNationalNumber = $matcher->replaceFirst('');
1409
            }
1410
            // Replace the rest with a dash between each number group.
1411 59
            $formattedNationalNumber = $matcher->reset($formattedNationalNumber)->replaceAll('-');
1412
        }
1413 96
        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 97
    protected function maybeAppendFormattedExtension(PhoneNumber $number, $metadata, $numberFormat, &$formattedNumber)
1426
    {
1427 97
        if ($number->hasExtension() && mb_strlen($number->getExtension()) > 0) {
1428 13
            if ($numberFormat === PhoneNumberFormat::RFC3966) {
1429 12
                $formattedNumber .= static::RFC3966_EXTN_PREFIX . $number->getExtension();
1430 3
            } elseif (!empty($metadata) && $metadata->hasPreferredExtnPrefix()) {
1431 2
                $formattedNumber .= $metadata->getPreferredExtnPrefix() . $number->getExtension();
1432
            } else {
1433 2
                $formattedNumber .= static::DEFAULT_EXTN_PREFIX . $number->getExtension();
1434
            }
1435
        }
1436 97
    }
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 16
    public static function getCountryMobileToken($countryCallingCode)
1447
    {
1448 16
        if (count(static::$MOBILE_TOKEN_MAPPINGS) === 0) {
1449 1
            static::initMobileTokenMappings();
1450
        }
1451
1452 16
        if (array_key_exists($countryCallingCode, static::$MOBILE_TOKEN_MAPPINGS)) {
1453 5
            return static::$MOBILE_TOKEN_MAPPINGS[$countryCallingCode];
1454
        }
1455 14
        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 1
    public function isAlphaNumber($number)
1469
    {
1470 1
        if (!static::isViablePhoneNumber($number)) {
1471
            // Number is too short, or doesn't match the basic phone number pattern.
1472 1
            return false;
1473
        }
1474 1
        $this->maybeStripExtension($number);
1475 1
        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 3289
    public static function isViablePhoneNumber($number)
1489
    {
1490 3289
        if (static::$VALID_PHONE_NUMBER_PATTERN === null) {
1491 2
            static::initValidPhoneNumberPatterns();
1492
        }
1493
1494 3289
        if (mb_strlen($number) < static::MIN_LENGTH_FOR_NSN) {
1495 25
            return false;
1496
        }
1497
1498 3288
        $validPhoneNumberPattern = static::getValidPhoneNumberPattern();
1499
1500 3288
        $m = preg_match($validPhoneNumberPattern, $number);
1501 3288
        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 3288
    protected static function getValidPhoneNumberPattern()
1510
    {
1511 3288
        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 3283
    protected function maybeStripExtension(&$number)
1522
    {
1523 3283
        $matches = array();
1524 3283
        $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 3283
        if ($find > 0 && static::isViablePhoneNumber(substr($number, 0, $matches[0][1]))) {
1528
            // The numbers are captured into groups in the regular expression.
1529
1530 33
            for ($i = 1, $length = count($matches); $i <= $length; $i++) {
1531 33
                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 33
                    $extension = $matches[$i][0];
1535 33
                    $number = substr($number, 0, $matches[0][1]);
1536 33
                    return $extension;
1537
                }
1538
            }
1539
        }
1540 3259
        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 182
    public function parseAndKeepRawInput($numberToParse, $defaultRegion, PhoneNumber $phoneNumber = null)
1559
    {
1560 182
        if ($phoneNumber === null) {
1561 182
            $phoneNumber = new PhoneNumber();
1562
        }
1563 182
        $this->parseHelper($numberToParse, $defaultRegion, true, true, $phoneNumber);
1564 181
        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 207
    public function findNumbers($text, $defaultRegion, AbstractLeniency $leniency = null, $maxTries = PHP_INT_MAX)
1577
    {
1578 207
        if ($leniency === null) {
1579 18
            $leniency = Leniency::VALID();
1580
        }
1581
1582 207
        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 33
    public function getAsYouTypeFormatter($regionCode)
1592
    {
1593 33
        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 3280
    public static function setItalianLeadingZerosForPhoneNumber($nationalNumber, PhoneNumber $phoneNumber)
1602
    {
1603 3280
        if (strlen($nationalNumber) > 1 && substr($nationalNumber, 0, 1) == '0') {
1604 125
            $phoneNumber->setItalianLeadingZero(true);
1605 125
            $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 125
            while ($numberOfLeadingZeros < (strlen($nationalNumber) - 1) &&
1609 125
                substr($nationalNumber, $numberOfLeadingZeros, 1) == '0') {
1610 20
                $numberOfLeadingZeros++;
1611
            }
1612
1613 125
            if ($numberOfLeadingZeros != 1) {
1614 20
                $phoneNumber->setNumberOfLeadingZeros($numberOfLeadingZeros);
1615
            }
1616
        }
1617 3280
    }
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 3286
    protected function parseHelper($numberToParse, $defaultRegion, $keepRawInput, $checkRegion, PhoneNumber $phoneNumber)
1632
    {
1633 3286
        if ($numberToParse === null) {
0 ignored issues
show
introduced by
The condition $numberToParse === null is always false.
Loading history...
1634 2
            throw new NumberParseException(NumberParseException::NOT_A_NUMBER, 'The phone number supplied was null.');
1635
        }
1636
1637 3285
        $numberToParse = trim($numberToParse);
1638
1639 3285
        if (mb_strlen($numberToParse) > static::MAX_INPUT_STRING_LENGTH) {
1640 1
            throw new NumberParseException(
1641 1
                NumberParseException::TOO_LONG,
1642 1
                'The string supplied was too long to parse.'
1643
            );
1644
        }
1645
1646 3284
        $nationalNumber = '';
1647 3284
        $this->buildNationalNumberForParsing($numberToParse, $nationalNumber);
1648
1649 3284
        if (!static::isViablePhoneNumber($nationalNumber)) {
1650 29
            throw new NumberParseException(
1651 29
                NumberParseException::NOT_A_NUMBER,
1652 29
                '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 3283
        if ($checkRegion && !$this->checkRegionForParsing($nationalNumber, $defaultRegion)) {
1659 7
            throw new NumberParseException(
1660 7
                NumberParseException::INVALID_COUNTRY_CODE,
1661 7
                'Missing or invalid default region.'
1662
            );
1663
        }
1664
1665 3282
        if ($keepRawInput) {
1666 181
            $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 3282
        $extension = $this->maybeStripExtension($nationalNumber);
1671 3282
        if (mb_strlen($extension) > 0) {
1672 32
            $phoneNumber->setExtension($extension);
1673
        }
1674
1675 3282
        $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 3282
        $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 3282
            $countryCode = $this->maybeExtractCountryCode(
1684 3282
                $nationalNumber,
1685
                $regionMetadata,
1686
                $normalizedNationalNumber,
1687
                $keepRawInput,
1688
                $phoneNumber
1689
            );
1690 15
        } catch (NumberParseException $e) {
1691 15
            $matcher = new Matcher(static::$PLUS_CHARS_PATTERN, $nationalNumber);
1692 15
            if ($e->getErrorType() == NumberParseException::INVALID_COUNTRY_CODE && $matcher->lookingAt()) {
1693
                // Strip the plus-char, and try again.
1694 6
                $countryCode = $this->maybeExtractCountryCode(
1695 6
                    substr($nationalNumber, $matcher->end()),
1696
                    $regionMetadata,
1697
                    $normalizedNationalNumber,
1698
                    $keepRawInput,
1699
                    $phoneNumber
1700
                );
1701 6
                if ($countryCode == 0) {
1702 5
                    throw new NumberParseException(
1703 5
                        NumberParseException::INVALID_COUNTRY_CODE,
1704 6
                        'Could not interpret numbers after plus-sign.'
1705
                    );
1706
                }
1707
            } else {
1708 10
                throw new NumberParseException($e->getErrorType(), $e->getMessage(), $e);
1709
            }
1710
        }
1711 3282
        if ($countryCode !== 0) {
1712 350
            $phoneNumberRegion = $this->getRegionCodeForCountryCode($countryCode);
1713 350
            if ($phoneNumberRegion != $defaultRegion) {
1714
                // Metadata cannot be null because the country calling code is valid.
1715 350
                $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 3210
            $normalizedNationalNumber .= static::normalize($nationalNumber);
1722 3210
            if ($defaultRegion !== null) {
0 ignored issues
show
introduced by
The condition $defaultRegion !== null is always true.
Loading history...
1723 3210
                $countryCode = $regionMetadata->getCountryCode();
1724 3210
                $phoneNumber->setCountryCode($countryCode);
1725 3
            } elseif ($keepRawInput) {
1726
                $phoneNumber->clearCountryCodeSource();
1727
            }
1728
        }
1729 3282
        if (mb_strlen($normalizedNationalNumber) < static::MIN_LENGTH_FOR_NSN) {
1730 2
            throw new NumberParseException(
1731 2
                NumberParseException::TOO_SHORT_NSN,
1732 2
                'The string supplied is too short to be a phone number.'
1733
            );
1734
        }
1735 3281
        if ($regionMetadata !== null) {
1736 3281
            $carrierCode = '';
1737 3281
            $potentialNationalNumber = $normalizedNationalNumber;
1738 3281
            $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 3281
            $validationResult = $this->testNumberLength($potentialNationalNumber, $regionMetadata);
1743 3281
            if ($validationResult !== ValidationResult::TOO_SHORT
1744 3281
                && $validationResult !== ValidationResult::IS_POSSIBLE_LOCAL_ONLY
1745 3281
                && $validationResult !== ValidationResult::INVALID_LENGTH) {
1746 2116
                $normalizedNationalNumber = $potentialNationalNumber;
1747 2116
                if ($keepRawInput && mb_strlen($carrierCode) > 0) {
1748 1
                    $phoneNumber->setPreferredDomesticCarrierCode($carrierCode);
1749
                }
1750
            }
1751
        }
1752 3281
        $lengthOfNationalNumber = mb_strlen($normalizedNationalNumber);
1753 3281
        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 3281
        if ($lengthOfNationalNumber > static::MAX_LENGTH_FOR_NSN) {
1760 5
            throw new NumberParseException(
1761 5
                NumberParseException::TOO_LONG,
1762 5
                'The string supplied is too long to be a phone number.'
1763
            );
1764
        }
1765 3280
        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 3280
        if ((int)$normalizedNationalNumber == 0) {
1776 29
            $normalizedNationalNumber = '0';
1777
        } else {
1778 3256
            $normalizedNationalNumber = ltrim($normalizedNationalNumber, '0');
1779
        }
1780
1781 3280
        $phoneNumber->setNationalNumber($normalizedNationalNumber);
1782 3280
    }
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 8
    protected static function copyCoreFieldsOnly(PhoneNumber $phoneNumberIn)
1793
    {
1794 8
        $phoneNumber = new PhoneNumber();
1795 8
        $phoneNumber->setCountryCode($phoneNumberIn->getCountryCode());
1796 8
        $phoneNumber->setNationalNumber($phoneNumberIn->getNationalNumber());
1797 8
        if (mb_strlen($phoneNumberIn->getExtension()) > 0) {
1798 3
            $phoneNumber->setExtension($phoneNumberIn->getExtension());
1799
        }
1800 8
        if ($phoneNumberIn->isItalianLeadingZero()) {
1801 4
            $phoneNumber->setItalianLeadingZero(true);
1802
            // This field is only relevant if there are leading zeros at all.
1803 4
            $phoneNumber->setNumberOfLeadingZeros($phoneNumberIn->getNumberOfLeadingZeros());
1804
        }
1805 8
        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 3284
    protected function buildNationalNumberForParsing($numberToParse, &$nationalNumber)
1815
    {
1816 3284
        $indexOfPhoneContext = strpos($numberToParse, static::RFC3966_PHONE_CONTEXT);
1817 3284
        if ($indexOfPhoneContext !== false) {
1818 6
            $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 6
            if ($phoneContextStart < (strlen($numberToParse) - 1)
1822 6
                && 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 3
                $phoneContextEnd = strpos($numberToParse, ';', $phoneContextStart);
1827 3
                if ($phoneContextEnd > 0) {
1828 1
                    $nationalNumber .= substr($numberToParse, $phoneContextStart, $phoneContextEnd - $phoneContextStart);
1829
                } else {
1830 3
                    $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 6
            $indexOfRfc3966Prefix = strpos($numberToParse, static::RFC3966_PREFIX);
1840 6
            $indexOfNationalNumber = ($indexOfRfc3966Prefix !== false) ? $indexOfRfc3966Prefix + strlen(static::RFC3966_PREFIX) : 0;
1841 6
            $nationalNumber .= substr(
1842 6
                $numberToParse,
1843
                $indexOfNationalNumber,
1844 6
                $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 3284
            $nationalNumber .= static::extractPossibleNumber($numberToParse);
0 ignored issues
show
Bug introduced by
$numberToParse of type string is incompatible with the type integer expected by parameter $number of libphonenumber\PhoneNumb...extractPossibleNumber(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1849
            $nationalNumber .= static::extractPossibleNumber(/** @scrutinizer ignore-type */ $numberToParse);
Loading history...
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 3284
        $indexOfIsdn = strpos($nationalNumber, static::RFC3966_ISDN_SUBADDRESS);
1855 3284
        if ($indexOfIsdn > 0) {
1856 5
            $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 3284
    }
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 3307
    public static function extractPossibleNumber($number)
1880
    {
1881 3307
        if (static::$VALID_START_CHAR_PATTERN === null) {
1882 1
            static::initValidStartCharPattern();
1883
        }
1884
1885 3307
        $matches = array();
1886 3307
        $match = preg_match('/' . static::$VALID_START_CHAR_PATTERN . '/ui', $number, $matches, PREG_OFFSET_CAPTURE);
1887 3307
        if ($match > 0) {
1888 3307
            $number = substr($number, $matches[0][1]);
1889
            // Remove trailing non-alpha non-numerical characters.
1890 3307
            $trailingCharsMatcher = new Matcher(static::$UNWANTED_END_CHAR_PATTERN, $number);
1891 3307
            if ($trailingCharsMatcher->find() && $trailingCharsMatcher->start() > 0) {
1892 2
                $number = substr($number, 0, $trailingCharsMatcher->start());
1893
            }
1894
1895
            // Check for extra numbers at the end.
1896 3307
            $match = preg_match('%' . static::$SECOND_NUMBER_START_PATTERN . '%', $number, $matches, PREG_OFFSET_CAPTURE);
1897 3307
            if ($match > 0) {
1898 1
                $number = substr($number, 0, $matches[0][1]);
1899
            }
1900
1901 3307
            return $number;
1902
        }
1903
1904 6
        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 3283
    protected function checkRegionForParsing($numberToParse, $defaultRegion)
1916
    {
1917 3283
        if (!$this->isValidRegionCode($defaultRegion)) {
1918
            // If the number is null or empty, we can't infer the region.
1919 274
            $plusCharsPatternMatcher = new Matcher(static::$PLUS_CHARS_PATTERN, $numberToParse);
1920 274
            if ($numberToParse === null || mb_strlen($numberToParse) == 0 || !$plusCharsPatternMatcher->lookingAt()) {
1921 7
                return false;
1922
            }
1923
        }
1924 3282
        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 3283
    public function maybeExtractCountryCode(
1960
        $number,
1961
        PhoneMetadata $defaultRegionMetadata = null,
1962
        &$nationalNumber,
1963
        $keepRawInput,
1964
        PhoneNumber $phoneNumber
1965
    ) {
1966 3283
        if (mb_strlen($number) == 0) {
1967
            return 0;
1968
        }
1969 3283
        $fullNumber = $number;
1970
        // Set the default prefix to be something that will never match.
1971 3283
        $possibleCountryIddPrefix = 'NonMatch';
1972 3283
        if ($defaultRegionMetadata !== null) {
1973 3265
            $possibleCountryIddPrefix = $defaultRegionMetadata->getInternationalPrefix();
1974
        }
1975 3283
        $countryCodeSource = $this->maybeStripInternationalPrefixAndNormalize($fullNumber, $possibleCountryIddPrefix);
1976
1977 3283
        if ($keepRawInput) {
1978 182
            $phoneNumber->setCountryCodeSource($countryCodeSource);
1979
        }
1980 3283
        if ($countryCodeSource != CountryCodeSource::FROM_DEFAULT_COUNTRY) {
1981 343
            if (mb_strlen($fullNumber) <= static::MIN_LENGTH_FOR_NSN) {
1982 10
                throw new NumberParseException(
1983 10
                    NumberParseException::TOO_SHORT_AFTER_IDD,
1984 10
                    'Phone number had an IDD, but after this was not long enough to be a viable phone number.'
1985
                );
1986
            }
1987 342
            $potentialCountryCode = $this->extractCountryCode($fullNumber, $nationalNumber);
1988
1989 342
            if ($potentialCountryCode != 0) {
1990 342
                $phoneNumber->setCountryCode($potentialCountryCode);
1991 342
                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 8
            throw new NumberParseException(
1997 8
                NumberParseException::INVALID_COUNTRY_CODE,
1998 8
                'Country calling code supplied was not recognised.'
1999
            );
2000
        }
2001
2002 3221
        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 3221
            $defaultCountryCode = $defaultRegionMetadata->getCountryCode();
2007 3221
            $defaultCountryCodeString = (string)$defaultCountryCode;
2008 3221
            $normalizedNumber = $fullNumber;
2009 3221
            if (strpos($normalizedNumber, $defaultCountryCodeString) === 0) {
2010 106
                $potentialNationalNumber = substr($normalizedNumber, mb_strlen($defaultCountryCodeString));
2011 106
                $generalDesc = $defaultRegionMetadata->getGeneralDesc();
2012
                // Don't need the carrier code.
2013 106
                $carriercode = null;
2014 106
                $this->maybeStripNationalPrefixAndCarrierCode(
2015 106
                    $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 106
                if ((!$this->matcherAPI->matchNationalNumber($fullNumber, $generalDesc, false)
2023 71
                        && $this->matcherAPI->matchNationalNumber($potentialNationalNumber, $generalDesc, false))
2024 106
                    || $this->testNumberLength($fullNumber, $defaultRegionMetadata) === ValidationResult::TOO_LONG
2025
                ) {
2026 24
                    $nationalNumber .= $potentialNationalNumber;
2027 24
                    if ($keepRawInput) {
2028 15
                        $phoneNumber->setCountryCodeSource(CountryCodeSource::FROM_NUMBER_WITHOUT_PLUS_SIGN);
2029
                    }
2030 24
                    $phoneNumber->setCountryCode($defaultCountryCode);
2031 24
                    return $defaultCountryCode;
2032
                }
2033
            }
2034
        }
2035
        // No country calling code present.
2036 3211
        $phoneNumber->setCountryCode(0);
2037 3211
        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 3284
    public function maybeStripInternationalPrefixAndNormalize(&$number, $possibleIddPrefix)
2053
    {
2054 3284
        if (mb_strlen($number) == 0) {
2055
            return CountryCodeSource::FROM_DEFAULT_COUNTRY;
2056
        }
2057 3284
        $matches = array();
2058
        // Check to see if the number begins with one or more plus signs.
2059 3284
        $match = preg_match('/^' . static::$PLUS_CHARS_PATTERN . '/' . static::REGEX_FLAGS, $number, $matches, PREG_OFFSET_CAPTURE);
2060 3284
        if ($match > 0) {
2061 341
            $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 341
            $number = static::normalize($number);
2064 341
            return CountryCodeSource::FROM_NUMBER_WITH_PLUS_SIGN;
2065
        }
2066
        // Attempt to parse the first digits as an international prefix.
2067 3223
        $iddPattern = $possibleIddPrefix;
2068 3223
        $number = static::normalize($number);
2069 3223
        return $this->parsePrefixAsIdd($iddPattern, $number)
2070 19
            ? CountryCodeSource::FROM_NUMBER_WITH_IDD
2071 3223
            : 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 3288
    public static function normalize(&$number)
2092
    {
2093 3288
        if (static::$ALPHA_PHONE_MAPPINGS === null) {
0 ignored issues
show
introduced by
The condition static::ALPHA_PHONE_MAPPINGS === null is always false.
Loading history...
2094 1
            static::initAlphaPhoneMappings();
2095
        }
2096
2097 3288
        $m = new Matcher(static::VALID_ALPHA_PHONE_PATTERN, $number);
2098 3288
        if ($m->matches()) {
2099 9
            return static::normalizeHelper($number, static::$ALPHA_PHONE_MAPPINGS, true);
2100
        }
2101
2102 3286
        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 3307
    public static function normalizeDigitsOnly($number)
2113
    {
2114 3307
        return static::normalizeDigits($number, false /* strip non-digits */);
2115
    }
2116
2117
    /**
2118
     * @param string $number
2119
     * @param bool $keepNonDigits
2120
     * @return string
2121
     */
2122 3340
    public static function normalizeDigits($number, $keepNonDigits)
2123
    {
2124 3340
        $normalizedDigits = '';
2125 3340
        $numberAsArray = preg_split('/(?<!^)(?!$)/u', $number);
2126 3340
        foreach ($numberAsArray as $character) {
2127
            // Check if we are in the unicode number range
2128 3340
            if (array_key_exists($character, static::$numericCharacters)) {
2129 6
                $normalizedDigits .= static::$numericCharacters[$character];
2130 3338
            } elseif (is_numeric($character)) {
2131 3337
                $normalizedDigits .= $character;
2132 172
            } elseif ($keepNonDigits) {
2133 50
                $normalizedDigits .= $character;
2134
            }
2135
        }
2136 3340
        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 3223
    protected function parsePrefixAsIdd($iddPattern, &$number)
2147
    {
2148 3223
        $m = new Matcher($iddPattern, $number);
2149 3223
        if ($m->lookingAt()) {
2150 22
            $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 22
            $digitMatcher = new Matcher(static::$CAPTURING_DIGIT_PATTERN, substr($number, $matchEnd));
2154 22
            if ($digitMatcher->find()) {
2155 22
                $normalizedGroup = static::normalizeDigitsOnly($digitMatcher->group(1));
2156 22
                if ($normalizedGroup == '0') {
2157 7
                    return false;
2158
                }
2159
            }
2160 19
            $number = substr($number, $matchEnd);
2161 19
            return true;
2162
        }
2163 3219
        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 360
    public function extractCountryCode($fullNumber, &$nationalNumber)
2176
    {
2177 360
        if ((mb_strlen($fullNumber) == 0) || ($fullNumber[0] == '0')) {
2178
            // Country codes do not begin with a '0'.
2179 2
            return 0;
2180
        }
2181 360
        $numberLength = mb_strlen($fullNumber);
2182 360
        for ($i = 1; $i <= static::MAX_LENGTH_COUNTRY_CODE && $i <= $numberLength; $i++) {
2183 360
            $potentialCountryCode = (int)substr($fullNumber, 0, $i);
2184 360
            if (isset($this->countryCallingCodeToRegionCodeMap[$potentialCountryCode])) {
2185 360
                $nationalNumber .= substr($fullNumber, $i);
2186 360
                return $potentialCountryCode;
2187
            }
2188
        }
2189 11
        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 3283
    public function maybeStripNationalPrefixAndCarrierCode(&$number, PhoneMetadata $metadata, &$carrierCode)
2202
    {
2203 3283
        $numberLength = mb_strlen($number);
2204 3283
        $possibleNationalPrefix = $metadata->getNationalPrefixForParsing();
2205 3283
        if ($numberLength == 0 || $possibleNationalPrefix === null || mb_strlen($possibleNationalPrefix) == 0) {
2206
            // Early return for numbers of zero length.
2207 1095
            return false;
2208
        }
2209
2210
        // Attempt to parse the first digits as a national prefix.
2211 2198
        $prefixMatcher = new Matcher($possibleNationalPrefix, $number);
2212 2198
        if ($prefixMatcher->lookingAt()) {
2213 174
            $generalDesc = $metadata->getGeneralDesc();
2214
            // Check if the original number is viable.
2215 174
            $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 174
            $numOfGroups = $prefixMatcher->groupCount();
2220 174
            $transformRule = $metadata->getNationalPrefixTransformRule();
2221 174
            if ($transformRule === null
2222 47
                || mb_strlen($transformRule) == 0
2223 174
                || $prefixMatcher->group($numOfGroups - 1) === null
2224
            ) {
2225
                // If the original number was viable, and the resultant number is not, we return.
2226 172
                if ($isViableOriginalNumber &&
2227 68
                    !$this->matcherAPI->matchNationalNumber(
2228 68
                        substr($number, $prefixMatcher->end()),
2229
                        $generalDesc,
2230 172
                        false
2231
                    )) {
2232 19
                    return false;
2233
                }
2234 157
                if ($carrierCode !== null && $numOfGroups > 0 && $prefixMatcher->group($numOfGroups) !== null) {
2235 2
                    $carrierCode .= $prefixMatcher->group(1);
2236
                }
2237
2238 157
                $number = substr($number, $prefixMatcher->end());
2239 157
                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 8
            $transformedNumber = $number;
2245 8
            $transformedNumber = substr_replace(
2246 8
                $transformedNumber,
2247 8
                $prefixMatcher->replaceFirst($transformRule),
2248 8
                0,
2249
                $numberLength
2250
            );
2251 8
            if ($isViableOriginalNumber
2252 8
                && !$this->matcherAPI->matchNationalNumber($transformedNumber, $generalDesc, false)) {
2253
                return false;
2254
            }
2255 8
            if ($carrierCode !== null && $numOfGroups > 1) {
2256
                $carrierCode .= $prefixMatcher->group(1);
2257
            }
2258 8
            $number = substr_replace($number, $transformedNumber, 0, mb_strlen($number));
2259 8
            return true;
2260
        }
2261 2080
        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 4
    public function isPossibleNumberForType(PhoneNumber $number, $type)
2277
    {
2278 4
        $result = $this->isPossibleNumberForTypeWithReason($number, $type);
2279 4
        return $result === ValidationResult::IS_POSSIBLE
2280 4
            || $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 3294
    protected function testNumberLength($number, PhoneMetadata $metadata, $type = PhoneNumberType::UNKNOWN)
2293
    {
2294 3294
        $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 3294
        $possibleLengths = (count($descForType->getPossibleLength()) === 0)
2302 3294
            ? $metadata->getGeneralDesc()->getPossibleLength() : $descForType->getPossibleLength();
2303
2304 3294
        $localLengths = $descForType->getPossibleLengthLocalOnly();
2305
2306 3294
        if ($type === PhoneNumberType::FIXED_LINE_OR_MOBILE) {
2307 3
            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 2
                return $this->testNumberLength($number, $metadata, PhoneNumberType::MOBILE);
2311
            }
2312
2313 3
            $mobileDesc = $this->getNumberDescByType($metadata, PhoneNumberType::MOBILE);
2314 3
            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 1
                $possibleLengths = array_merge(
2319 1
                    $possibleLengths,
2320 1
                    (count($mobileDesc->getPossibleLength()) === 0)
2321 1
                        ? $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 1
                sort($possibleLengths);
2327
2328 1
                if (count($localLengths) === 0) {
2329 1
                    $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 3294
        if ($possibleLengths[0] === -1) {
2342 2
            return ValidationResult::INVALID_LENGTH;
2343
        }
2344
2345 3294
        $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 3294
        if (in_array($actualLength, $localLengths)) {
2351 74
            return ValidationResult::IS_POSSIBLE_LOCAL_ONLY;
2352
        }
2353
2354 3248
        $minimumLength = reset($possibleLengths);
2355 3248
        if ($minimumLength == $actualLength) {
2356 1336
            return ValidationResult::IS_POSSIBLE;
2357
        }
2358
2359 1971
        if ($minimumLength > $actualLength) {
2360 1148
            return ValidationResult::TOO_SHORT;
2361 848
        } elseif (isset($possibleLengths[count($possibleLengths) - 1]) && $possibleLengths[count($possibleLengths) - 1] < $actualLength) {
2362 33
            return ValidationResult::TOO_LONG;
2363
        }
2364
2365
        // We skip the first element; we've already checked it.
2366 834
        array_shift($possibleLengths);
2367 834
        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 9
    public function getRegionCodesForCountryCode($countryCallingCode)
2378
    {
2379 9
        $regionCodes = isset($this->countryCallingCodeToRegionCodeMap[$countryCallingCode]) ? $this->countryCallingCodeToRegionCodeMap[$countryCallingCode] : null;
2380 9
        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 37
    public function getCountryCodeForRegion($regionCode)
2391
    {
2392 37
        if (!$this->isValidRegionCode($regionCode)) {
2393 4
            return 0;
2394
        }
2395 37
        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 2003
    protected function getCountryCodeForValidRegion($regionCode)
2407
    {
2408 2003
        $metadata = $this->getMetadataForRegion($regionCode);
2409 2003
        if ($metadata === null) {
2410
            throw new \InvalidArgumentException('Invalid region code: ' . $regionCode);
2411
        }
2412 2003
        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 1
    public function formatNumberForMobileDialing(PhoneNumber $number, $regionCallingFrom, $withFormatting)
2428
    {
2429 1
        $countryCallingCode = $number->getCountryCode();
2430 1
        if (!$this->hasValidCountryCallingCode($countryCallingCode)) {
2431
            return $number->hasRawInput() ? $number->getRawInput() : '';
2432
        }
2433
2434 1
        $formattedNumber = '';
2435
        // Clear the extension, as that part cannot normally be dialed together with the main number.
2436 1
        $numberNoExt = new PhoneNumber();
2437 1
        $numberNoExt->mergeFrom($number)->clearExtension();
2438 1
        $regionCode = $this->getRegionCodeForCountryCode($countryCallingCode);
2439 1
        $numberType = $this->getNumberType($numberNoExt);
2440 1
        $isValidNumber = ($numberType !== PhoneNumberType::UNKNOWN);
2441 1
        if ($regionCallingFrom == $regionCode) {
2442 1
            $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 1
            if ($regionCode == 'CO' && $numberType == PhoneNumberType::FIXED_LINE) {
2445
                $formattedNumber = $this->formatNationalNumberWithCarrierCode(
2446
                    $numberNoExt,
2447
                    static::COLOMBIA_MOBILE_TO_FIXED_LINE_PREFIX
2448
                );
2449 1
            } 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 1
            } 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 1
                $regionMetadata = $this->getMetadataForRegion($regionCallingFrom);
2464 1
                if ($this->canBeInternationallyDialled($numberNoExt)
2465 1
                    && $this->testNumberLength($this->getNationalSignificantNumber($numberNoExt), $regionMetadata)
0 ignored issues
show
Bug introduced by
It seems like $regionMetadata can also be of type null; however, parameter $metadata of libphonenumber\PhoneNumberUtil::testNumberLength() does only seem to accept libphonenumber\PhoneMetadata, 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 ignore-type  annotation

2465
                    && $this->testNumberLength($this->getNationalSignificantNumber($numberNoExt), /** @scrutinizer ignore-type */ $regionMetadata)
Loading history...
2466 1
                    !== ValidationResult::TOO_SHORT
2467
                ) {
2468 1
                    $formattedNumber = $this->format($numberNoExt, PhoneNumberFormat::INTERNATIONAL);
2469
                } else {
2470 1
                    $formattedNumber = $this->format($numberNoExt, PhoneNumberFormat::NATIONAL);
2471
                }
2472
            } elseif ((
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: ($regionCode == static::...lyDialled($numberNoExt), Probably Intended Meaning: $regionCode == static::R...yDialled($numberNoExt))
Loading history...
2473 1
                $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 1
                        ($regionCode === 'MX' || $regionCode === 'CL' || $regionCode === 'UZ')
2484 1
                        && $isFixedLineOrMobile
2485
                    )
2486 1
            ) && $this->canBeInternationallyDialled($numberNoExt)
2487
            ) {
2488 1
                $formattedNumber = $this->format($numberNoExt, PhoneNumberFormat::INTERNATIONAL);
2489
            } else {
2490 1
                $formattedNumber = $this->format($numberNoExt, PhoneNumberFormat::NATIONAL);
2491
            }
2492 1
        } 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 1
            return $withFormatting ?
2497 1
                $this->format($numberNoExt, PhoneNumberFormat::INTERNATIONAL) :
2498 1
                $this->format($numberNoExt, PhoneNumberFormat::E164);
2499
        }
2500 1
        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 2
    public function formatNationalNumberWithCarrierCode(PhoneNumber $number, $carrierCode)
2515
    {
2516 2
        $countryCallingCode = $number->getCountryCode();
2517 2
        $nationalSignificantNumber = $this->getNationalSignificantNumber($number);
2518 2
        if (!$this->hasValidCountryCallingCode($countryCallingCode)) {
2519 1
            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 2
        $regionCode = $this->getRegionCodeForCountryCode($countryCallingCode);
2526
        // Metadata cannot be null because the country calling code is valid.
2527 2
        $metadata = $this->getMetadataForRegionOrCallingCode($countryCallingCode, $regionCode);
2528
2529 2
        $formattedNumber = $this->formatNsn(
2530 2
            $nationalSignificantNumber,
2531
            $metadata,
2532 2
            PhoneNumberFormat::NATIONAL,
2533
            $carrierCode
2534
        );
2535 2
        $this->maybeAppendFormattedExtension($number, $metadata, PhoneNumberFormat::NATIONAL, $formattedNumber);
2536 2
        $this->prefixNumberWithCountryCallingCode(
2537 2
            $countryCallingCode,
2538 2
            PhoneNumberFormat::NATIONAL,
2539
            $formattedNumber
2540
        );
2541 2
        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 1
    public function formatNationalNumberWithPreferredCarrierCode(PhoneNumber $number, $fallbackCarrierCode)
2562
    {
2563 1
        return $this->formatNationalNumberWithCarrierCode(
2564 1
            $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 1
            mb_strlen($number->getPreferredDomesticCarrierCode()) > 0
2569 1
                ? $number->getPreferredDomesticCarrierCode()
2570 1
                : $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 2
    public function canBeInternationallyDialled(PhoneNumber $number)
2584
    {
2585 2
        $metadata = $this->getMetadataForRegion($this->getRegionCodeForNumber($number));
2586 2
        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 2
            return true;
2590
        }
2591 2
        $nationalSignificantNumber = $this->getNationalSignificantNumber($number);
2592 2
        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 29
    public static function normalizeDiallableCharsOnly($number)
2603
    {
2604 29
        if (count(static::$DIALLABLE_CHAR_MAPPINGS) === 0) {
2605 1
            static::initDiallableCharMappings();
2606
        }
2607
2608 29
        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 1
    public function formatOutOfCountryKeepingAlphaChars(PhoneNumber $number, $regionCallingFrom)
2636
    {
2637 1
        $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 1
        if (mb_strlen($rawInput) == 0) {
2641 1
            return $this->formatOutOfCountryCallingNumber($number, $regionCallingFrom);
2642
        }
2643 1
        $countryCode = $number->getCountryCode();
2644 1
        if (!$this->hasValidCountryCallingCode($countryCode)) {
2645 1
            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 1
        $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 1
        $nationalNumber = $this->getNationalSignificantNumber($number);
2657 1
        if (mb_strlen($nationalNumber) > 3) {
2658 1
            $firstNationalNumberDigit = strpos($rawInput, substr($nationalNumber, 0, 3));
2659 1
            if ($firstNationalNumberDigit !== false) {
2660 1
                $rawInput = substr($rawInput, $firstNationalNumberDigit);
2661
            }
2662
        }
2663 1
        $metadataForRegionCallingFrom = $this->getMetadataForRegion($regionCallingFrom);
2664 1
        if ($countryCode == static::NANPA_COUNTRY_CODE) {
2665 1
            if ($this->isNANPACountry($regionCallingFrom)) {
2666 1
                return $countryCode . ' ' . $rawInput;
2667
            }
2668 1
        } elseif ($metadataForRegionCallingFrom !== null &&
2669 1
            $countryCode == $this->getCountryCodeForValidRegion($regionCallingFrom)
2670
        ) {
2671
            $formattingPattern =
2672 1
                $this->chooseFormattingPatternForNumber(
2673 1
                    $metadataForRegionCallingFrom->numberFormats(),
2674
                    $nationalNumber
2675
                );
2676 1
            if ($formattingPattern === null) {
2677
                // If no pattern above is matched, we format the original input.
2678 1
                return $rawInput;
2679
            }
2680 1
            $newFormat = new NumberFormat();
2681 1
            $newFormat->mergeFrom($formattingPattern);
2682
            // The first group is the first group of digits that the user wrote together.
2683 1
            $newFormat->setPattern("(\\d+)(.*)");
2684
            // Here we just concatenate them back together after the national prefix has been fixed.
2685 1
            $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 1
            return $this->formatNsnUsingPattern($rawInput, $newFormat, PhoneNumberFormat::NATIONAL);
2692
        }
2693 1
        $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 1
        if ($metadataForRegionCallingFrom !== null) {
2698 1
            $internationalPrefix = $metadataForRegionCallingFrom->getInternationalPrefix();
2699 1
            $uniqueInternationalPrefixMatcher = new Matcher(static::SINGLE_INTERNATIONAL_PREFIX, $internationalPrefix);
2700
            $internationalPrefixForFormatting =
2701 1
                $uniqueInternationalPrefixMatcher->matches()
2702 1
                    ? $internationalPrefix
2703 1
                    : $metadataForRegionCallingFrom->getPreferredInternationalPrefix();
2704
        }
2705 1
        $formattedNumber = $rawInput;
2706 1
        $regionCode = $this->getRegionCodeForCountryCode($countryCode);
2707
        // Metadata cannot be null because the country calling code is valid.
2708 1
        $metadataForRegion = $this->getMetadataForRegionOrCallingCode($countryCode, $regionCode);
2709 1
        $this->maybeAppendFormattedExtension(
2710 1
            $number,
2711
            $metadataForRegion,
2712 1
            PhoneNumberFormat::INTERNATIONAL,
2713
            $formattedNumber
2714
        );
2715 1
        if (mb_strlen($internationalPrefixForFormatting) > 0) {
2716 1
            $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 1
            $this->prefixNumberWithCountryCallingCode(
2721 1
                $countryCode,
2722 1
                PhoneNumberFormat::INTERNATIONAL,
2723
                $formattedNumber
2724
            );
2725
        }
2726 1
        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 8
    public function formatOutOfCountryCallingNumber(PhoneNumber $number, $regionCallingFrom)
2747
    {
2748 8
        if (!$this->isValidRegionCode($regionCallingFrom)) {
2749 1
            return $this->format($number, PhoneNumberFormat::INTERNATIONAL);
2750
        }
2751 7
        $countryCallingCode = $number->getCountryCode();
2752 7
        $nationalSignificantNumber = $this->getNationalSignificantNumber($number);
2753 7
        if (!$this->hasValidCountryCallingCode($countryCallingCode)) {
2754
            return $nationalSignificantNumber;
2755
        }
2756 7
        if ($countryCallingCode == static::NANPA_COUNTRY_CODE) {
2757 4
            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 4
                return $countryCallingCode . ' ' . $this->format($number, PhoneNumberFormat::NATIONAL);
2761
            }
2762 6
        } 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 2
            return $this->format($number, PhoneNumberFormat::NATIONAL);
2770
        }
2771
        // Metadata cannot be null because we checked 'isValidRegionCode()' above.
2772 7
        $metadataForRegionCallingFrom = $this->getMetadataForRegion($regionCallingFrom);
2773
2774 7
        $internationalPrefix = $metadataForRegionCallingFrom->getInternationalPrefix();
2775
2776
        // For regions that have multiple international prefixes, the international format of the
2777
        // number is returned, unless there is a preferred international prefix.
2778 7
        $internationalPrefixForFormatting = '';
2779 7
        $uniqueInternationalPrefixMatcher = new Matcher(static::SINGLE_INTERNATIONAL_PREFIX, $internationalPrefix);
2780
2781 7
        if ($uniqueInternationalPrefixMatcher->matches()) {
2782 6
            $internationalPrefixForFormatting = $internationalPrefix;
2783 3
        } elseif ($metadataForRegionCallingFrom->hasPreferredInternationalPrefix()) {
2784 3
            $internationalPrefixForFormatting = $metadataForRegionCallingFrom->getPreferredInternationalPrefix();
2785
        }
2786
2787 7
        $regionCode = $this->getRegionCodeForCountryCode($countryCallingCode);
2788
        // Metadata cannot be null because the country calling code is valid.
2789 7
        $metadataForRegion = $this->getMetadataForRegionOrCallingCode($countryCallingCode, $regionCode);
2790 7
        $formattedNationalNumber = $this->formatNsn(
2791 7
            $nationalSignificantNumber,
2792
            $metadataForRegion,
2793 7
            PhoneNumberFormat::INTERNATIONAL
2794
        );
2795 7
        $formattedNumber = $formattedNationalNumber;
2796 7
        $this->maybeAppendFormattedExtension(
2797 7
            $number,
2798
            $metadataForRegion,
2799 7
            PhoneNumberFormat::INTERNATIONAL,
2800
            $formattedNumber
2801
        );
2802 7
        if (mb_strlen($internationalPrefixForFormatting) > 0) {
2803 7
            $formattedNumber = $internationalPrefixForFormatting . ' ' . $countryCallingCode . ' ' . $formattedNumber;
2804
        } else {
2805 1
            $this->prefixNumberWithCountryCallingCode(
2806 1
                $countryCallingCode,
2807 1
                PhoneNumberFormat::INTERNATIONAL,
2808
                $formattedNumber
2809
            );
2810
        }
2811 7
        return $formattedNumber;
2812
    }
2813
2814
    /**
2815
     * Checks if this is a region under the North American Numbering Plan Administration (NANPA).
2816
     * @param string $regionCode
2817
     * @return boolean true if regionCode is one of the regions under NANPA
2818
     */
2819 5
    public function isNANPACountry($regionCode)
2820
    {
2821 5
        return in_array($regionCode, $this->nanpaRegions);
2822
    }
2823
2824
    /**
2825
     * Formats a phone number using the original phone number format that the number is parsed from.
2826
     * The original format is embedded in the country_code_source field of the PhoneNumber object
2827
     * passed in. If such information is missing, the number will be formatted into the NATIONAL
2828
     * format by default. When we don't have a formatting pattern for the number, the method returns
2829
     * the raw input when it is available.
2830
     *
2831
     * Note this method guarantees no digit will be inserted, removed or modified as a result of
2832
     * formatting.
2833
     *
2834
     * @param PhoneNumber $number the phone number that needs to be formatted in its original number format
2835
     * @param string $regionCallingFrom the region whose IDD needs to be prefixed if the original number
2836
     *     has one
2837
     * @return string the formatted phone number in its original number format
2838
     */
2839 1
    public function formatInOriginalFormat(PhoneNumber $number, $regionCallingFrom)
2840
    {
2841 1
        if ($number->hasRawInput() && !$this->hasFormattingPatternForNumber($number)) {
2842
            // We check if we have the formatting pattern because without that, we might format the number
2843
            // as a group without national prefix.
2844 1
            return $number->getRawInput();
2845
        }
2846 1
        if (!$number->hasCountryCodeSource()) {
2847 1
            return $this->format($number, PhoneNumberFormat::NATIONAL);
2848
        }
2849 1
        switch ($number->getCountryCodeSource()) {
2850 1
            case CountryCodeSource::FROM_NUMBER_WITH_PLUS_SIGN:
2851 1
                $formattedNumber = $this->format($number, PhoneNumberFormat::INTERNATIONAL);
2852 1
                break;
2853 1
            case CountryCodeSource::FROM_NUMBER_WITH_IDD:
2854 1
                $formattedNumber = $this->formatOutOfCountryCallingNumber($number, $regionCallingFrom);
2855 1
                break;
2856 1
            case CountryCodeSource::FROM_NUMBER_WITHOUT_PLUS_SIGN:
2857 1
                $formattedNumber = substr($this->format($number, PhoneNumberFormat::INTERNATIONAL), 1);
2858 1
                break;
2859 1
            case CountryCodeSource::FROM_DEFAULT_COUNTRY:
2860
                // Fall-through to default case.
2861
            default:
2862
2863 1
                $regionCode = $this->getRegionCodeForCountryCode($number->getCountryCode());
2864
                // We strip non-digits from the NDD here, and from the raw input later, so that we can
2865
                // compare them easily.
2866 1
                $nationalPrefix = $this->getNddPrefixForRegion($regionCode, true /* strip non-digits */);
2867 1
                $nationalFormat = $this->format($number, PhoneNumberFormat::NATIONAL);
2868 1
                if ($nationalPrefix === null || mb_strlen($nationalPrefix) == 0) {
2869
                    // If the region doesn't have a national prefix at all, we can safely return the national
2870
                    // format without worrying about a national prefix being added.
2871 1
                    $formattedNumber = $nationalFormat;
2872 1
                    break;
2873
                }
2874
                // Otherwise, we check if the original number was entered with a national prefix.
2875 1
                if ($this->rawInputContainsNationalPrefix(
2876 1
                    $number->getRawInput(),
2877
                    $nationalPrefix,
2878
                    $regionCode
2879
                )
2880
                ) {
2881
                    // If so, we can safely return the national format.
2882 1
                    $formattedNumber = $nationalFormat;
2883 1
                    break;
2884
                }
2885
                // Metadata cannot be null here because getNddPrefixForRegion() (above) returns null if
2886
                // there is no metadata for the region.
2887 1
                $metadata = $this->getMetadataForRegion($regionCode);
2888 1
                $nationalNumber = $this->getNationalSignificantNumber($number);
2889 1
                $formatRule = $this->chooseFormattingPatternForNumber($metadata->numberFormats(), $nationalNumber);
2890
                // The format rule could still be null here if the national number was 0 and there was no
2891
                // raw input (this should not be possible for numbers generated by the phonenumber library
2892
                // as they would also not have a country calling code and we would have exited earlier).
2893 1
                if ($formatRule === null) {
2894
                    $formattedNumber = $nationalFormat;
2895
                    break;
2896
                }
2897
                // When the format we apply to this number doesn't contain national prefix, we can just
2898
                // return the national format.
2899
                // TODO: Refactor the code below with the code in isNationalPrefixPresentIfRequired.
2900 1
                $candidateNationalPrefixRule = $formatRule->getNationalPrefixFormattingRule();
2901
                // We assume that the first-group symbol will never be _before_ the national prefix.
2902 1
                $indexOfFirstGroup = strpos($candidateNationalPrefixRule, '$1');
2903 1
                if ($indexOfFirstGroup <= 0) {
2904 1
                    $formattedNumber = $nationalFormat;
2905 1
                    break;
2906
                }
2907 1
                $candidateNationalPrefixRule = substr($candidateNationalPrefixRule, 0, $indexOfFirstGroup);
2908 1
                $candidateNationalPrefixRule = static::normalizeDigitsOnly($candidateNationalPrefixRule);
2909 1
                if (mb_strlen($candidateNationalPrefixRule) == 0) {
2910
                    // National prefix not used when formatting this number.
2911
                    $formattedNumber = $nationalFormat;
2912
                    break;
2913
                }
2914
                // Otherwise, we need to remove the national prefix from our output.
2915 1
                $numFormatCopy = new NumberFormat();
2916 1
                $numFormatCopy->mergeFrom($formatRule);
2917 1
                $numFormatCopy->clearNationalPrefixFormattingRule();
2918 1
                $numberFormats = array();
2919 1
                $numberFormats[] = $numFormatCopy;
2920 1
                $formattedNumber = $this->formatByPattern($number, PhoneNumberFormat::NATIONAL, $numberFormats);
2921 1
                break;
2922
        }
2923 1
        $rawInput = $number->getRawInput();
2924
        // If no digit is inserted/removed/modified as a result of our formatting, we return the
2925
        // formatted phone number; otherwise we return the raw input the user entered.
2926 1
        if ($formattedNumber !== null && mb_strlen($rawInput) > 0) {
2927 1
            $normalizedFormattedNumber = static::normalizeDiallableCharsOnly($formattedNumber);
2928 1
            $normalizedRawInput = static::normalizeDiallableCharsOnly($rawInput);
2929 1
            if ($normalizedFormattedNumber != $normalizedRawInput) {
2930 1
                $formattedNumber = $rawInput;
2931
            }
2932
        }
2933 1
        return $formattedNumber;
2934
    }
2935
2936
    /**
2937
     * @param PhoneNumber $number
2938
     * @return bool
2939
     */
2940 1
    protected function hasFormattingPatternForNumber(PhoneNumber $number)
2941
    {
2942 1
        $countryCallingCode = $number->getCountryCode();
2943 1
        $phoneNumberRegion = $this->getRegionCodeForCountryCode($countryCallingCode);
2944 1
        $metadata = $this->getMetadataForRegionOrCallingCode($countryCallingCode, $phoneNumberRegion);
2945 1
        if ($metadata === null) {
2946
            return false;
2947
        }
2948 1
        $nationalNumber = $this->getNationalSignificantNumber($number);
2949 1
        $formatRule = $this->chooseFormattingPatternForNumber($metadata->numberFormats(), $nationalNumber);
2950 1
        return $formatRule !== null;
2951
    }
2952
2953
    /**
2954
     * Returns the national dialling prefix for a specific region. For example, this would be 1 for
2955
     * the United States, and 0 for New Zealand. Set stripNonDigits to true to strip symbols like "~"
2956
     * (which indicates a wait for a dialling tone) from the prefix returned. If no national prefix is
2957
     * present, we return null.
2958
     *
2959
     * <p>Warning: Do not use this method for do-your-own formatting - for some regions, the
2960
     * national dialling prefix is used only for certain types of numbers. Use the library's
2961
     * formatting functions to prefix the national prefix when required.
2962
     *
2963
     * @param string $regionCode the region that we want to get the dialling prefix for
2964
     * @param boolean $stripNonDigits true to strip non-digits from the national dialling prefix
2965
     * @return string the dialling prefix for the region denoted by regionCode
2966
     */
2967 28
    public function getNddPrefixForRegion($regionCode, $stripNonDigits)
2968
    {
2969 28
        $metadata = $this->getMetadataForRegion($regionCode);
2970 28
        if ($metadata === null) {
2971 1
            return null;
2972
        }
2973 28
        $nationalPrefix = $metadata->getNationalPrefix();
2974
        // If no national prefix was found, we return null.
2975 28
        if (mb_strlen($nationalPrefix) == 0) {
2976 1
            return null;
2977
        }
2978 28
        if ($stripNonDigits) {
2979
            // Note: if any other non-numeric symbols are ever used in national prefixes, these would have
2980
            // to be removed here as well.
2981 28
            $nationalPrefix = str_replace('~', '', $nationalPrefix);
2982
        }
2983 28
        return $nationalPrefix;
2984
    }
2985
2986
    /**
2987
     * Check if rawInput, which is assumed to be in the national format, has a national prefix. The
2988
     * national prefix is assumed to be in digits-only form.
2989
     * @param string $rawInput
2990
     * @param string $nationalPrefix
2991
     * @param string $regionCode
2992
     * @return bool
2993
     */
2994 1
    protected function rawInputContainsNationalPrefix($rawInput, $nationalPrefix, $regionCode)
2995
    {
2996 1
        $normalizedNationalNumber = static::normalizeDigitsOnly($rawInput);
2997 1
        if (strpos($normalizedNationalNumber, $nationalPrefix) === 0) {
2998
            try {
2999
                // Some Japanese numbers (e.g. 00777123) might be mistaken to contain the national prefix
3000
                // when written without it (e.g. 0777123) if we just do prefix matching. To tackle that, we
3001
                // check the validity of the number if the assumed national prefix is removed (777123 won't
3002
                // be valid in Japan).
3003 1
                return $this->isValidNumber(
3004 1
                    $this->parse(substr($normalizedNationalNumber, mb_strlen($nationalPrefix)), $regionCode)
3005
                );
3006
            } catch (NumberParseException $e) {
3007
                return false;
3008
            }
3009
        }
3010 1
        return false;
3011
    }
3012
3013
    /**
3014
     * Tests whether a phone number matches a valid pattern. Note this doesn't verify the number
3015
     * is actually in use, which is impossible to tell by just looking at a number itself. It only
3016
     * verifies whether the parsed, canonicalised number is valid: not whether a particular series of
3017
     * digits entered by the user is diallable from the region provided when parsing. For example, the
3018
     * number +41 (0) 78 927 2696 can be parsed into a number with country code "41" and national
3019
     * significant number "789272696". This is valid, while the original string is not diallable.
3020
     *
3021
     * @param PhoneNumber $number the phone number that we want to validate
3022
     * @return boolean that indicates whether the number is of a valid pattern
3023
     */
3024 2016
    public function isValidNumber(PhoneNumber $number)
3025
    {
3026 2016
        $regionCode = $this->getRegionCodeForNumber($number);
3027 2016
        return $this->isValidNumberForRegion($number, $regionCode);
3028
    }
3029
3030
    /**
3031
     * Tests whether a phone number is valid for a certain region. Note this doesn't verify the number
3032
     * is actually in use, which is impossible to tell by just looking at a number itself. If the
3033
     * country calling code is not the same as the country calling code for the region, this
3034
     * immediately exits with false. After this, the specific number pattern rules for the region are
3035
     * examined. This is useful for determining for example whether a particular number is valid for
3036
     * Canada, rather than just a valid NANPA number.
3037
     * Warning: In most cases, you want to use {@link #isValidNumber} instead. For example, this
3038
     * method will mark numbers from British Crown dependencies such as the Isle of Man as invalid for
3039
     * the region "GB" (United Kingdom), since it has its own region code, "IM", which may be
3040
     * undesirable.
3041
     *
3042
     * @param PhoneNumber $number the phone number that we want to validate
3043
     * @param string $regionCode the region that we want to validate the phone number for
3044
     * @return boolean that indicates whether the number is of a valid pattern
3045
     */
3046 2022
    public function isValidNumberForRegion(PhoneNumber $number, $regionCode)
3047
    {
3048 2022
        $countryCode = $number->getCountryCode();
3049 2022
        $metadata = $this->getMetadataForRegionOrCallingCode($countryCode, $regionCode);
3050 2022
        if (($metadata === null) ||
3051 1972
            (static::REGION_CODE_FOR_NON_GEO_ENTITY !== $regionCode &&
3052 2022
                $countryCode !== $this->getCountryCodeForValidRegion($regionCode))
3053
        ) {
3054
            // Either the region code was invalid, or the country calling code for this number does not
3055
            // match that of the region code.
3056 64
            return false;
3057
        }
3058 1971
        $nationalSignificantNumber = $this->getNationalSignificantNumber($number);
3059
3060 1971
        return $this->getNumberTypeHelper($nationalSignificantNumber, $metadata) != PhoneNumberType::UNKNOWN;
3061
    }
3062
3063
    /**
3064
     * Parses a string and returns it as a phone number in proto buffer format. The method is quite
3065
     * lenient and looks for a number in the input text (raw input) and does not check whether the
3066
     * string is definitely only a phone number. To do this, it ignores punctuation and white-space,
3067
     * as well as any text before the number (e.g. a leading “Tel: ”) and trims the non-number bits.
3068
     * It will accept a number in any format (E164, national, international etc), assuming it can
3069
     * interpreted with the defaultRegion supplied. It also attempts to convert any alpha characters
3070
     * into digits if it thinks this is a vanity number of the type "1800 MICROSOFT".
3071
     *
3072
     * <p> This method will throw a {@link NumberParseException} if the number is not considered to
3073
     * be a possible number. Note that validation of whether the number is actually a valid number
3074
     * for a particular region is not performed. This can be done separately with {@link #isValidNumber}.
3075
     *
3076
     * <p> Note this method canonicalizes the phone number such that different representations can be
3077
     * easily compared, no matter what form it was originally entered in (e.g. national,
3078
     * international). If you want to record context about the number being parsed, such as the raw
3079
     * input that was entered, how the country code was derived etc. then call {@link
3080
     * #parseAndKeepRawInput} instead.
3081
     *
3082
     * @param string $numberToParse number that we are attempting to parse. This can contain formatting
3083
     *                          such as +, ( and -, as well as a phone number extension.
3084
     * @param string|null $defaultRegion region that we are expecting the number to be from. This is only used
3085
     *                          if the number being parsed is not written in international format.
3086
     *                          The country_code for the number in this case would be stored as that
3087
     *                          of the default region supplied. If the number is guaranteed to
3088
     *                          start with a '+' followed by the country calling code, then
3089
     *                          "ZZ" or null can be supplied.
3090
     * @param PhoneNumber|null $phoneNumber
3091
     * @param bool $keepRawInput
3092
     * @return PhoneNumber a phone number proto buffer filled with the parsed number
3093
     * @throws NumberParseException  if the string is not considered to be a viable phone number (e.g.
3094
     *                               too few or too many digits) or if no default region was supplied
3095
     *                               and the number is not in international format (does not start
3096
     *                               with +)
3097
     */
3098 3120
    public function parse($numberToParse, $defaultRegion = null, PhoneNumber $phoneNumber = null, $keepRawInput = false)
3099
    {
3100 3120
        if ($phoneNumber === null) {
3101 3120
            $phoneNumber = new PhoneNumber();
3102
        }
3103 3120
        $this->parseHelper($numberToParse, $defaultRegion, $keepRawInput, true, $phoneNumber);
3104 3115
        return $phoneNumber;
3105
    }
3106
3107
    /**
3108
     * Formats a phone number in the specified format using client-defined formatting rules. Note that
3109
     * if the phone number has a country calling code of zero or an otherwise invalid country calling
3110
     * code, we cannot work out things like whether there should be a national prefix applied, or how
3111
     * to format extensions, so we return the national significant number with no formatting applied.
3112
     *
3113
     * @param PhoneNumber $number the phone number to be formatted
3114
     * @param int $numberFormat the format the phone number should be formatted into
3115
     * @param array $userDefinedFormats formatting rules specified by clients
3116
     * @return String the formatted phone number
3117
     */
3118 2
    public function formatByPattern(PhoneNumber $number, $numberFormat, array $userDefinedFormats)
3119
    {
3120 2
        $countryCallingCode = $number->getCountryCode();
3121 2
        $nationalSignificantNumber = $this->getNationalSignificantNumber($number);
3122 2
        if (!$this->hasValidCountryCallingCode($countryCallingCode)) {
3123
            return $nationalSignificantNumber;
3124
        }
3125
        // Note getRegionCodeForCountryCode() is used because formatting information for regions which
3126
        // share a country calling code is contained by only one region for performance reasons. For
3127
        // example, for NANPA regions it will be contained in the metadata for US.
3128 2
        $regionCode = $this->getRegionCodeForCountryCode($countryCallingCode);
3129
        // Metadata cannot be null because the country calling code is valid.
3130 2
        $metadata = $this->getMetadataForRegionOrCallingCode($countryCallingCode, $regionCode);
3131
3132 2
        $formattedNumber = '';
3133
3134 2
        $formattingPattern = $this->chooseFormattingPatternForNumber($userDefinedFormats, $nationalSignificantNumber);
3135 2
        if ($formattingPattern === null) {
3136
            // If no pattern above is matched, we format the number as a whole.
3137
            $formattedNumber .= $nationalSignificantNumber;
3138
        } else {
3139 2
            $numFormatCopy = new NumberFormat();
3140
            // Before we do a replacement of the national prefix pattern $NP with the national prefix, we
3141
            // need to copy the rule so that subsequent replacements for different numbers have the
3142
            // appropriate national prefix.
3143 2
            $numFormatCopy->mergeFrom($formattingPattern);
3144 2
            $nationalPrefixFormattingRule = $formattingPattern->getNationalPrefixFormattingRule();
3145 2
            if (mb_strlen($nationalPrefixFormattingRule) > 0) {
3146 1
                $nationalPrefix = $metadata->getNationalPrefix();
3147 1
                if (mb_strlen($nationalPrefix) > 0) {
3148
                    // Replace $NP with national prefix and $FG with the first group ($1).
3149 1
                    $nationalPrefixFormattingRule = str_replace(
3150 1
                        array(static::NP_STRING, static::FG_STRING),
3151 1
                        array($nationalPrefix, '$1'),
3152
                        $nationalPrefixFormattingRule
3153
                    );
3154 1
                    $numFormatCopy->setNationalPrefixFormattingRule($nationalPrefixFormattingRule);
3155
                } else {
3156
                    // We don't want to have a rule for how to format the national prefix if there isn't one.
3157 1
                    $numFormatCopy->clearNationalPrefixFormattingRule();
3158
                }
3159
            }
3160 2
            $formattedNumber .= $this->formatNsnUsingPattern($nationalSignificantNumber, $numFormatCopy, $numberFormat);
3161
        }
3162 2
        $this->maybeAppendFormattedExtension($number, $metadata, $numberFormat, $formattedNumber);
3163 2
        $this->prefixNumberWithCountryCallingCode($countryCallingCode, $numberFormat, $formattedNumber);
3164 2
        return $formattedNumber;
3165
    }
3166
3167
    /**
3168
     * Gets a valid number for the specified region.
3169
     *
3170
     * @param string regionCode  the region for which an example number is needed
0 ignored issues
show
Bug introduced by
The type libphonenumber\regionCode was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
3171
     * @return PhoneNumber a valid fixed-line number for the specified region. Returns null when the metadata
3172
     *    does not contain such information, or the region 001 is passed in. For 001 (representing
3173
     *    non-geographical numbers), call {@link #getExampleNumberForNonGeoEntity} instead.
3174
     */
3175 248
    public function getExampleNumber($regionCode)
3176
    {
3177 248
        return $this->getExampleNumberForType($regionCode, PhoneNumberType::FIXED_LINE);
3178
    }
3179
3180
    /**
3181
     * Gets an invalid number for the specified region. This is useful for unit-testing purposes,
3182
     * where you want to test what will happen with an invalid number. Note that the number that is
3183
     * returned will always be able to be parsed and will have the correct country code. It may also
3184
     * be a valid *short* number/code for this region. Validity checking such numbers is handled with
3185
     * {@link ShortNumberInfo}.
3186
     *
3187
     * @param string $regionCode The region for which an example number is needed
3188
     * @return PhoneNumber|null An invalid number for the specified region. Returns null when an unsupported region
3189
     * or the region 001 (Earth) is passed in.
3190
     */
3191 245
    public function getInvalidExampleNumber($regionCode)
3192
    {
3193 245
        if (!$this->isValidRegionCode($regionCode)) {
3194
            return null;
3195
        }
3196
3197
        // We start off with a valid fixed-line number since every country supports this. Alternatively
3198
        // we could start with a different number type, since fixed-line numbers typically have a wide
3199
        // breadth of valid number lengths and we may have to make it very short before we get an
3200
        // invalid number.
3201
3202 245
        $desc = $this->getNumberDescByType($this->getMetadataForRegion($regionCode), PhoneNumberType::FIXED_LINE);
0 ignored issues
show
Bug introduced by
It seems like $this->getMetadataForRegion($regionCode) can also be of type null; however, parameter $metadata of libphonenumber\PhoneNumb...::getNumberDescByType() does only seem to accept libphonenumber\PhoneMetadata, 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 ignore-type  annotation

3202
        $desc = $this->getNumberDescByType(/** @scrutinizer ignore-type */ $this->getMetadataForRegion($regionCode), PhoneNumberType::FIXED_LINE);
Loading history...
3203
3204 245
        if ($desc->getExampleNumber() == '') {
3205
            // This shouldn't happen; we have a test for this.
3206
            return null;
3207
        }
3208
3209 245
        $exampleNumber = $desc->getExampleNumber();
3210
3211
        // Try and make the number invalid. We do this by changing the length. We try reducing the
3212
        // length of the number, since currently no region has a number that is the same length as
3213
        // MIN_LENGTH_FOR_NSN. This is probably quicker than making the number longer, which is another
3214
        // alternative. We could also use the possible number pattern to extract the possible lengths of
3215
        // the number to make this faster, but this method is only for unit-testing so simplicity is
3216
        // preferred to performance.  We don't want to return a number that can't be parsed, so we check
3217
        // the number is long enough. We try all possible lengths because phone number plans often have
3218
        // overlapping prefixes so the number 123456 might be valid as a fixed-line number, and 12345 as
3219
        // a mobile number. It would be faster to loop in a different order, but we prefer numbers that
3220
        // look closer to real numbers (and it gives us a variety of different lengths for the resulting
3221
        // phone numbers - otherwise they would all be MIN_LENGTH_FOR_NSN digits long.)
3222 245
        for ($phoneNumberLength = mb_strlen($exampleNumber) - 1; $phoneNumberLength >= static::MIN_LENGTH_FOR_NSN; $phoneNumberLength--) {
3223 245
            $numberToTry = mb_substr($exampleNumber, 0, $phoneNumberLength);
3224
            try {
3225 245
                $possiblyValidNumber = $this->parse($numberToTry, $regionCode);
3226 245
                if (!$this->isValidNumber($possiblyValidNumber)) {
3227 245
                    return $possiblyValidNumber;
3228
                }
3229
            } catch (NumberParseException $e) {
3230
                // Shouldn't happen: we have already checked the length, we know example numbers have
3231
                // only valid digits, and we know the region code is fine.
3232
            }
3233
        }
3234
        // We have a test to check that this doesn't happen for any of our supported regions.
3235
        return null;
3236
    }
3237
3238
    /**
3239
     * Gets a valid number for the specified region and number type.
3240
     *
3241
     * @param string|int $regionCodeOrType the region for which an example number is needed
3242
     * @param int $type the PhoneNumberType of number that is needed
3243
     * @return PhoneNumber|null a valid number for the specified region and type. Returns null when the metadata
3244
     *     does not contain such information or if an invalid region or region 001 was entered.
3245
     *     For 001 (representing non-geographical numbers), call
3246
     *     {@link #getExampleNumberForNonGeoEntity} instead.
3247
     *
3248
     * If $regionCodeOrType is the only parameter supplied, then a valid number for the specified number type
3249
     * will be returned that may belong to any country.
3250
     */
3251 3188
    public function getExampleNumberForType($regionCodeOrType, $type = null)
3252
    {
3253 3188
        if ($regionCodeOrType !== null && $type === null) {
3254
            /*
3255
             * Gets a valid number for the specified number type (it may belong to any country).
3256
             */
3257 12
            foreach ($this->getSupportedRegions() as $regionCode) {
3258 12
                $exampleNumber = $this->getExampleNumberForType($regionCode, $regionCodeOrType);
3259 12
                if ($exampleNumber !== null) {
3260 12
                    return $exampleNumber;
3261
                }
3262
            }
3263
3264
            // If there wasn't an example number for a region, try the non-geographical entities.
3265
            foreach ($this->getSupportedGlobalNetworkCallingCodes() as $countryCallingCode) {
3266
                $desc = $this->getNumberDescByType($this->getMetadataForNonGeographicalRegion($countryCallingCode), $regionCodeOrType);
0 ignored issues
show
Bug introduced by
It seems like $this->getMetadataForNon...on($countryCallingCode) can also be of type null; however, parameter $metadata of libphonenumber\PhoneNumb...::getNumberDescByType() does only seem to accept libphonenumber\PhoneMetadata, 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 ignore-type  annotation

3266
                $desc = $this->getNumberDescByType(/** @scrutinizer ignore-type */ $this->getMetadataForNonGeographicalRegion($countryCallingCode), $regionCodeOrType);
Loading history...
3267
                try {
3268
                    if ($desc->getExampleNumber() != '') {
3269
                        return $this->parse('+' . $countryCallingCode . $desc->getExampleNumber(), static::UNKNOWN_REGION);
3270
                    }
3271
                } catch (NumberParseException $e) {
3272
                    // noop
3273
                }
3274
            }
3275
            // There are no example numbers of this type for any country in the library.
3276
            return null;
3277
        }
3278
3279
        // Check the region code is valid.
3280 3188
        if (!$this->isValidRegionCode($regionCodeOrType)) {
3281 1
            return null;
3282
        }
3283 3188
        $desc = $this->getNumberDescByType($this->getMetadataForRegion($regionCodeOrType), $type);
3284
        try {
3285 3188
            if ($desc->hasExampleNumber()) {
3286 3188
                return $this->parse($desc->getExampleNumber(), $regionCodeOrType);
3287
            }
3288
        } catch (NumberParseException $e) {
3289
            // noop
3290
        }
3291 1350
        return null;
3292
    }
3293
3294
    /**
3295
     * @param PhoneMetadata $metadata
3296
     * @param int $type PhoneNumberType
3297
     * @return PhoneNumberDesc
3298
     */
3299 4640
    protected function getNumberDescByType(PhoneMetadata $metadata, $type)
3300
    {
3301
        switch ($type) {
3302 4640
            case PhoneNumberType::PREMIUM_RATE:
3303 251
                return $metadata->getPremiumRate();
3304 4538
            case PhoneNumberType::TOLL_FREE:
3305 251
                return $metadata->getTollFree();
3306 4465
            case PhoneNumberType::MOBILE:
3307 257
                return $metadata->getMobile();
3308 4464
            case PhoneNumberType::FIXED_LINE:
3309 4464
            case PhoneNumberType::FIXED_LINE_OR_MOBILE:
3310 1230
                return $metadata->getFixedLine();
3311 4461
            case PhoneNumberType::SHARED_COST:
3312 248
                return $metadata->getSharedCost();
3313 4269
            case PhoneNumberType::VOIP:
3314 248
                return $metadata->getVoip();
3315 4113
            case PhoneNumberType::PERSONAL_NUMBER:
3316 248
                return $metadata->getPersonalNumber();
3317 3927
            case PhoneNumberType::PAGER:
3318 248
                return $metadata->getPager();
3319 3705
            case PhoneNumberType::UAN:
3320 248
                return $metadata->getUan();
3321 3523
            case PhoneNumberType::VOICEMAIL:
3322 249
                return $metadata->getVoicemail();
3323
            default:
3324 3293
                return $metadata->getGeneralDesc();
3325
        }
3326
    }
3327
3328
    /**
3329
     * Gets a valid number for the specified country calling code for a non-geographical entity.
3330
     *
3331
     * @param int $countryCallingCode the country calling code for a non-geographical entity
3332
     * @return PhoneNumber a valid number for the non-geographical entity. Returns null when the metadata
3333
     *    does not contain such information, or the country calling code passed in does not belong
3334
     *    to a non-geographical entity.
3335
     */
3336 10
    public function getExampleNumberForNonGeoEntity($countryCallingCode)
3337
    {
3338 10
        $metadata = $this->getMetadataForNonGeographicalRegion($countryCallingCode);
3339 10
        if ($metadata !== null) {
3340
            // For geographical entities, fixed-line data is always present. However, for non-geographical
3341
            // entities, this is not the case, so we have to go through different types to find the
3342
            // example number. We don't check fixed-line or personal number since they aren't used by
3343
            // non-geographical entities (if this changes, a unit-test will catch this.)
3344
            /** @var PhoneNumberDesc[] $list */
3345
            $list = array(
3346 10
                $metadata->getMobile(),
3347 10
                $metadata->getTollFree(),
3348 10
                $metadata->getSharedCost(),
3349 10
                $metadata->getVoip(),
3350 10
                $metadata->getVoicemail(),
3351 10
                $metadata->getUan(),
3352 10
                $metadata->getPremiumRate(),
3353
            );
3354 10
            foreach ($list as $desc) {
3355
                try {
3356 10
                    if ($desc !== null && $desc->hasExampleNumber()) {
3357 10
                        return $this->parse('+' . $countryCallingCode . $desc->getExampleNumber(), self::UNKNOWN_REGION);
3358
                    }
3359
                } catch (NumberParseException $e) {
3360
                    // noop
3361
                }
3362
            }
3363
        }
3364
        return null;
3365
    }
3366
3367
3368
    /**
3369
     * Takes two phone numbers and compares them for equality.
3370
     *
3371
     * <p>Returns EXACT_MATCH if the country_code, NSN, presence of a leading zero
3372
     * for Italian numbers and any extension present are the same. Returns NSN_MATCH
3373
     * if either or both has no region specified, and the NSNs and extensions are
3374
     * the same. Returns SHORT_NSN_MATCH if either or both has no region specified,
3375
     * or the region specified is the same, and one NSN could be a shorter version
3376
     * of the other number. This includes the case where one has an extension
3377
     * specified, and the other does not. Returns NO_MATCH otherwise. For example,
3378
     * the numbers +1 345 657 1234 and 657 1234 are a SHORT_NSN_MATCH. The numbers
3379
     * +1 345 657 1234 and 345 657 are a NO_MATCH.
3380
     *
3381
     * @param $firstNumberIn PhoneNumber|string First number to compare. If it is a
3382
     * string it can contain formatting, and can have country calling code specified
3383
     * with + at the start.
3384
     * @param $secondNumberIn PhoneNumber|string Second number to compare. If it is a
3385
     * string it can contain formatting, and can have country calling code specified
3386
     * with + at the start.
3387
     * @throws \InvalidArgumentException
3388
     * @return int {MatchType} NOT_A_NUMBER, NO_MATCH,
3389
     */
3390 8
    public function isNumberMatch($firstNumberIn, $secondNumberIn)
3391
    {
3392 8
        if (is_string($firstNumberIn) && is_string($secondNumberIn)) {
3393
            try {
3394 4
                $firstNumberAsProto = $this->parse($firstNumberIn, static::UNKNOWN_REGION);
3395 4
                return $this->isNumberMatch($firstNumberAsProto, $secondNumberIn);
3396 3
            } catch (NumberParseException $e) {
3397 3
                if ($e->getErrorType() === NumberParseException::INVALID_COUNTRY_CODE) {
3398
                    try {
3399 3
                        $secondNumberAsProto = $this->parse($secondNumberIn, static::UNKNOWN_REGION);
3400 2
                        return $this->isNumberMatch($secondNumberAsProto, $firstNumberIn);
3401 3
                    } catch (NumberParseException $e2) {
3402 3
                        if ($e2->getErrorType() === NumberParseException::INVALID_COUNTRY_CODE) {
3403
                            try {
3404 3
                                $firstNumberProto = new PhoneNumber();
3405 3
                                $secondNumberProto = new PhoneNumber();
3406 3
                                $this->parseHelper($firstNumberIn, null, false, false, $firstNumberProto);
3407 3
                                $this->parseHelper($secondNumberIn, null, false, false, $secondNumberProto);
3408 3
                                return $this->isNumberMatch($firstNumberProto, $secondNumberProto);
3409
                            } catch (NumberParseException $e3) {
3410
                                // Fall through and return MatchType::NOT_A_NUMBER
3411
                            }
3412
                        }
3413
                    }
3414
                }
3415
            }
3416 1
            return MatchType::NOT_A_NUMBER;
3417
        }
3418 8
        if ($firstNumberIn instanceof PhoneNumber && is_string($secondNumberIn)) {
3419
            // First see if the second number has an implicit country calling code, by attempting to parse
3420
            // it.
3421
            try {
3422 4
                $secondNumberAsProto = $this->parse($secondNumberIn, static::UNKNOWN_REGION);
3423 2
                return $this->isNumberMatch($firstNumberIn, $secondNumberAsProto);
3424 3
            } catch (NumberParseException $e) {
3425 3
                if ($e->getErrorType() === NumberParseException::INVALID_COUNTRY_CODE) {
3426
                    // The second number has no country calling code. EXACT_MATCH is no longer possible.
3427
                    // We parse it as if the region was the same as that for the first number, and if
3428
                    // EXACT_MATCH is returned, we replace this with NSN_MATCH.
3429 3
                    $firstNumberRegion = $this->getRegionCodeForCountryCode($firstNumberIn->getCountryCode());
3430
                    try {
3431 3
                        if ($firstNumberRegion != static::UNKNOWN_REGION) {
3432 3
                            $secondNumberWithFirstNumberRegion = $this->parse($secondNumberIn, $firstNumberRegion);
3433 3
                            $match = $this->isNumberMatch($firstNumberIn, $secondNumberWithFirstNumberRegion);
3434 3
                            if ($match === MatchType::EXACT_MATCH) {
3435 1
                                return MatchType::NSN_MATCH;
3436
                            }
3437 2
                            return $match;
3438
                        }
3439
3440
                        // If the first number didn't have a valid country calling code, then we parse the
3441
                        // second number without one as well.
3442 1
                        $secondNumberProto = new PhoneNumber();
3443 1
                        $this->parseHelper($secondNumberIn, null, false, false, $secondNumberProto);
3444 1
                        return $this->isNumberMatch($firstNumberIn, $secondNumberProto);
3445
                    } catch (NumberParseException $e2) {
3446
                        // Fall-through to return NOT_A_NUMBER.
3447
                    }
3448
                }
3449
            }
3450
        }
3451 8
        if ($firstNumberIn instanceof PhoneNumber && $secondNumberIn instanceof PhoneNumber) {
3452
            // We only care about the fields that uniquely define a number, so we copy these across
3453
            // explicitly.
3454 8
            $firstNumber = self::copyCoreFieldsOnly($firstNumberIn);
3455 8
            $secondNumber = self::copyCoreFieldsOnly($secondNumberIn);
3456
3457
            // Early exit if both had extensions and these are different.
3458 8
            if ($firstNumber->hasExtension() && $secondNumber->hasExtension() &&
3459 8
                $firstNumber->getExtension() != $secondNumber->getExtension()
3460
            ) {
3461 1
                return MatchType::NO_MATCH;
3462
            }
3463
3464 8
            $firstNumberCountryCode = $firstNumber->getCountryCode();
3465 8
            $secondNumberCountryCode = $secondNumber->getCountryCode();
3466
            // Both had country_code specified.
3467 8
            if ($firstNumberCountryCode != 0 && $secondNumberCountryCode != 0) {
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing $firstNumberCountryCode of type integer|null to 0; this is ambiguous as not only 0 == 0 is true, but null == 0 is true, too. Consider using a strict comparison ===.
Loading history...
Bug Best Practice introduced by
It seems like you are loosely comparing $secondNumberCountryCode of type integer|null to 0; this is ambiguous as not only 0 == 0 is true, but null == 0 is true, too. Consider using a strict comparison ===.
Loading history...
3468 8
                if ($firstNumber->equals($secondNumber)) {
3469 5
                    return MatchType::EXACT_MATCH;
3470
                }
3471
3472 3
                if ($firstNumberCountryCode == $secondNumberCountryCode &&
3473 3
                    $this->isNationalNumberSuffixOfTheOther($firstNumber, $secondNumber)) {
3474
                    // A SHORT_NSN_MATCH occurs if there is a difference because of the presence or absence of
3475
                    // an 'Italian leading zero', the presence or absence of an extension, or one NSN being a
3476
                    // shorter variant of the other.
3477 2
                    return MatchType::SHORT_NSN_MATCH;
3478
                }
3479
                // This is not a match.
3480 1
                return MatchType::NO_MATCH;
3481
            }
3482
            // Checks cases where one or both country_code fields were not specified. To make equality
3483
            // checks easier, we first set the country_code fields to be equal.
3484 3
            $firstNumber->setCountryCode($secondNumberCountryCode);
3485
            // If all else was the same, then this is an NSN_MATCH.
3486 3
            if ($firstNumber->equals($secondNumber)) {
3487 1
                return MatchType::NSN_MATCH;
3488
            }
3489 3
            if ($this->isNationalNumberSuffixOfTheOther($firstNumber, $secondNumber)) {
3490 2
                return MatchType::SHORT_NSN_MATCH;
3491
            }
3492 1
            return MatchType::NO_MATCH;
3493
        }
3494
        return MatchType::NOT_A_NUMBER;
3495
    }
3496
3497
    /**
3498
     * Returns true when one national number is the suffix of the other or both are the same.
3499
     * @param PhoneNumber $firstNumber
3500
     * @param PhoneNumber $secondNumber
3501
     * @return bool
3502
     */
3503 4
    protected function isNationalNumberSuffixOfTheOther(PhoneNumber $firstNumber, PhoneNumber $secondNumber)
3504
    {
3505 4
        $firstNumberNationalNumber = trim((string)$firstNumber->getNationalNumber());
3506 4
        $secondNumberNationalNumber = trim((string)$secondNumber->getNationalNumber());
3507 4
        return $this->stringEndsWithString($firstNumberNationalNumber, $secondNumberNationalNumber) ||
3508 4
        $this->stringEndsWithString($secondNumberNationalNumber, $firstNumberNationalNumber);
3509
    }
3510
3511
    /**
3512
     * Returns true if a string ends with a given substring, false otherwise.
3513
     *
3514
     * @param string $hayStack
3515
     * @param string $needle
3516
     * @return bool
3517
     */
3518 4
    protected function stringEndsWithString($hayStack, $needle)
3519
    {
3520 4
        $revNeedle = strrev($needle);
3521 4
        $revHayStack = strrev($hayStack);
3522 4
        return strpos($revHayStack, $revNeedle) === 0;
3523
    }
3524
3525
    /**
3526
     * Returns true if the supplied region supports mobile number portability. Returns false for
3527
     * invalid, unknown or regions that don't support mobile number portability.
3528
     *
3529
     * @param string $regionCode the region for which we want to know whether it supports mobile number
3530
     *                    portability or not.
3531
     * @return bool
3532
     */
3533 3
    public function isMobileNumberPortableRegion($regionCode)
3534
    {
3535 3
        $metadata = $this->getMetadataForRegion($regionCode);
3536 3
        if ($metadata === null) {
3537
            return false;
3538
        }
3539
3540 3
        return $metadata->isMobileNumberPortableRegion();
3541
    }
3542
3543
    /**
3544
     * Check whether a phone number is a possible number given a number in the form of a string, and
3545
     * the region where the number could be dialed from. It provides a more lenient check than
3546
     * {@link #isValidNumber}. See {@link #isPossibleNumber(PhoneNumber)} for details.
3547
     *
3548
     * Convenience wrapper around {@link #isPossibleNumberWithReason}. Instead of returning the reason
3549
     * for failure, this method returns a boolean value.
3550
     * For failure, this method returns true if the number is either a possible fully-qualified number
3551
     * (containing the area code and country code), or if the number could be a possible local number
3552
     * (with a country code, but missing an area code). Local numbers are considered possible if they
3553
     * could be possibly dialled in this format: if the area code is needed for a call to connect, the
3554
     * number is not considered possible without it.
3555
     *
3556
     * Note: There are two ways to call this method.
3557
     *
3558
     * isPossibleNumber(PhoneNumber $numberObject)
3559
     * isPossibleNumber(string '+441174960126', string 'GB')
3560
     *
3561
     * @param PhoneNumber|string $number the number that needs to be checked, in the form of a string
3562
     * @param string|null $regionDialingFrom the region that we are expecting the number to be dialed from.
3563
     *     Note this is different from the region where the number belongs.  For example, the number
3564
     *     +1 650 253 0000 is a number that belongs to US. When written in this form, it can be
3565
     *     dialed from any region. When it is written as 00 1 650 253 0000, it can be dialed from any
3566
     *     region which uses an international dialling prefix of 00. When it is written as
3567
     *     650 253 0000, it can only be dialed from within the US, and when written as 253 0000, it
3568
     *     can only be dialed from within a smaller area in the US (Mountain View, CA, to be more
3569
     *     specific).
3570
     * @return boolean true if the number is possible
3571
     */
3572 58
    public function isPossibleNumber($number, $regionDialingFrom = null)
3573
    {
3574 58
        if (is_string($number)) {
3575
            try {
3576 3
                return $this->isPossibleNumber($this->parse($number, $regionDialingFrom));
3577 1
            } catch (NumberParseException $e) {
3578 1
                return false;
3579
            }
3580
        } else {
3581 58
            $result = $this->isPossibleNumberWithReason($number);
3582 58
            return $result === ValidationResult::IS_POSSIBLE
3583 58
                || $result === ValidationResult::IS_POSSIBLE_LOCAL_ONLY;
3584
        }
3585
    }
3586
3587
3588
    /**
3589
     * Check whether a phone number is a possible number. It provides a more lenient check than
3590
     * {@link #isValidNumber} in the following sense:
3591
     * <ol>
3592
     *   <li> It only checks the length of phone numbers. In particular, it doesn't check starting
3593
     *        digits of the number.
3594
     *   <li> It doesn't attempt to figure out the type of the number, but uses general rules which
3595
     *        applies to all types of phone numbers in a region. Therefore, it is much faster than
3596
     *        isValidNumber.
3597
     *   <li> For some numbers (particularly fixed-line), many regions have the concept of area code,
3598
     *        which together with subscriber number constitute the national significant number. It is
3599
     *        sometimes okay to dial only the subscriber number when dialing in the same area. This
3600
     *        function will return IS_POSSIBLE_LOCAL_ONLY if the subscriber-number-only version is
3601
     *        passed in. On the other hand, because isValidNumber validates using information on both
3602
     *        starting digits (for fixed line numbers, that would most likely be area codes) and
3603
     *        length (obviously includes the length of area codes for fixed line numbers), it will
3604
     *        return false for the subscriber-number-only version.
3605
     * </ol>
3606
     * @param PhoneNumber $number the number that needs to be checked
3607
     * @return int a ValidationResult object which indicates whether the number is possible
3608
     */
3609 60
    public function isPossibleNumberWithReason(PhoneNumber $number)
3610
    {
3611 60
        return $this->isPossibleNumberForTypeWithReason($number, PhoneNumberType::UNKNOWN);
3612
    }
3613
3614
    /**
3615
     * Check whether a phone number is a possible number of a particular type. For types that don't
3616
     * exist in a particular region, this will return a result that isn't so useful; it is recommended
3617
     * that you use {@link #getSupportedTypesForRegion} or {@link #getSupportedTypesForNonGeoEntity}
3618
     * respectively before calling this method to determine whether you should call it for this number
3619
     * at all.
3620
     *
3621
     * This provides a more lenient check than {@link #isValidNumber} in the following sense:
3622
     *
3623
     * <ol>
3624
     *   <li> It only checks the length of phone numbers. In particular, it doesn't check starting
3625
     *        digits of the number.
3626
     *   <li> For some numbers (particularly fixed-line), many regions have the concept of area code,
3627
     *        which together with subscriber number constitute the national significant number. It is
3628
     *        sometimes okay to dial only the subscriber number when dialing in the same area. This
3629
     *        function will return IS_POSSIBLE_LOCAL_ONLY if the subscriber-number-only version is
3630
     *        passed in. On the other hand, because isValidNumber validates using information on both
3631
     *        starting digits (for fixed line numbers, that would most likely be area codes) and
3632
     *        length (obviously includes the length of area codes for fixed line numbers), it will
3633
     *        return false for the subscriber-number-only version.
3634
     * </ol>
3635
     *
3636
     * @param PhoneNumber $number the number that needs to be checked
3637
     * @param int $type the PhoneNumberType we are interested in
3638
     * @return int a ValidationResult object which indicates whether the number is possible
3639
     */
3640 69
    public function isPossibleNumberForTypeWithReason(PhoneNumber $number, $type)
3641
    {
3642 69
        $nationalNumber = $this->getNationalSignificantNumber($number);
3643 69
        $countryCode = $number->getCountryCode();
3644
3645
        // Note: For regions that share a country calling code, like NANPA numbers, we just use the
3646
        // rules from the default region (US in this case) since the getRegionCodeForNumber will not
3647
        // work if the number is possible but not valid. There is in fact one country calling code (290)
3648
        // where the possible number pattern differs between various regions (Saint Helena and Tristan
3649
        // da Cuñha), but this is handled by putting all possible lengths for any country with this
3650
        // country calling code in the metadata for the default region in this case.
3651 69
        if (!$this->hasValidCountryCallingCode($countryCode)) {
3652 1
            return ValidationResult::INVALID_COUNTRY_CODE;
3653
        }
3654
3655 69
        $regionCode = $this->getRegionCodeForCountryCode($countryCode);
3656
        // Metadata cannot be null because the country calling code is valid.
3657 69
        $metadata = $this->getMetadataForRegionOrCallingCode($countryCode, $regionCode);
3658 69
        return $this->testNumberLength($nationalNumber, $metadata, $type);
3659
    }
3660
3661
    /**
3662
     * Attempts to extract a valid number from a phone number that is too long to be valid, and resets
3663
     * the PhoneNumber object passed in to that valid version. If no valid number could be extracted,
3664
     * the PhoneNumber object passed in will not be modified.
3665
     *
3666
     * @param PhoneNumber $number a PhoneNumber object which contains a number that is too long to be valid.
3667
     * @return boolean true if a valid phone number can be successfully extracted.
3668
     */
3669 1
    public function truncateTooLongNumber(PhoneNumber $number)
3670
    {
3671 1
        if ($this->isValidNumber($number)) {
3672 1
            return true;
3673
        }
3674 1
        $numberCopy = new PhoneNumber();
3675 1
        $numberCopy->mergeFrom($number);
3676 1
        $nationalNumber = $number->getNationalNumber();
3677
        do {
3678 1
            $nationalNumber = floor($nationalNumber / 10);
3679 1
            $numberCopy->setNationalNumber($nationalNumber);
3680 1
            if ($this->isPossibleNumberWithReason($numberCopy) == ValidationResult::TOO_SHORT || $nationalNumber == 0) {
3681 1
                return false;
3682
            }
3683 1
        } while (!$this->isValidNumber($numberCopy));
3684 1
        $number->setNationalNumber($nationalNumber);
3685 1
        return true;
3686
    }
3687
}
3688