Completed
Push — master ( d0b6fd...4e563e )
by Joshua
33:29 queued 16:24
created

PhoneNumberUtil::getInstance()   B

Complexity

Conditions 5
Paths 9

Size

Total Lines 19
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 5

Importance

Changes 8
Bugs 2 Features 1
Metric Value
c 8
b 2
f 1
dl 0
loc 19
ccs 14
cts 14
cp 1
rs 8.8571
cc 5
eloc 10
nc 9
nop 4
crap 5
1
<?php
2
3
namespace libphonenumber;
4
5
/**
6
 * Utility for international phone numbers. Functionality includes formatting, parsing and
7
 * validation.
8
 *
9
 * <p>If you use this library, and want to be notified about important changes, please sign up to
10
 * our <a href="http://groups.google.com/group/libphonenumber-discuss/about">mailing list</a>.
11
 *
12
 * NOTE: A lot of methods in this class require Region Code strings. These must be provided using
13
 * ISO 3166-1 two-letter country-code format. These should be in upper-case. The list of the codes
14
 * can be found here:
15
 * http://www.iso.org/iso/country_codes/iso_3166_code_lists/country_names_and_code_elements.htm
16
 *
17
 * @author Shaopeng Jia
18
 * @author Lara Rennie
19
 * @see https://code.google.com/p/libphonenumber/
20
 */
21
class PhoneNumberUtil
22
{
23
    /** Flags to use when compiling regular expressions for phone numbers */
24
    const REGEX_FLAGS = 'ui'; //Unicode and case insensitive
25
    // The minimum and maximum length of the national significant number.
26
    const MIN_LENGTH_FOR_NSN = 2;
27
    // The ITU says the maximum length should be 15, but we have found longer numbers in Germany.
28
    const MAX_LENGTH_FOR_NSN = 17;
29
30
    // We don't allow input strings for parsing to be longer than 250 chars. This prevents malicious
31
    // input from overflowing the regular-expression engine.
32
    const MAX_INPUT_STRING_LENGTH = 250;
33
34
    // The maximum length of the country calling code.
35
    const MAX_LENGTH_COUNTRY_CODE = 3;
36
37
    const REGION_CODE_FOR_NON_GEO_ENTITY = "001";
38
    const META_DATA_FILE_PREFIX = 'PhoneNumberMetadata';
39
    const TEST_META_DATA_FILE_PREFIX = 'PhoneNumberMetadataForTesting';
40
41
    // Region-code for the unknown region.
42
    const UNKNOWN_REGION = "ZZ";
43
44
    const NANPA_COUNTRY_CODE = 1;
45
    /*
46
     * The prefix that needs to be inserted in front of a Colombian landline number when dialed from
47
     * a mobile number in Colombia.
48
     */
49
    const COLOMBIA_MOBILE_TO_FIXED_LINE_PREFIX = "3";
50
    // The PLUS_SIGN signifies the international prefix.
51
    const PLUS_SIGN = '+';
52
    const PLUS_CHARS = '++';
53
    const STAR_SIGN = '*';
54
55
    const RFC3966_EXTN_PREFIX = ";ext=";
56
    const RFC3966_PREFIX = "tel:";
57
    const RFC3966_PHONE_CONTEXT = ";phone-context=";
58
    const RFC3966_ISDN_SUBADDRESS = ";isub=";
59
60
    // We use this pattern to check if the phone number has at least three letters in it - if so, then
61
    // we treat it as a number where some phone-number digits are represented by letters.
62
    const VALID_ALPHA_PHONE_PATTERN = "(?:.*?[A-Za-z]){3}.*";
63
    // We accept alpha characters in phone numbers, ASCII only, upper and lower case.
64
    const VALID_ALPHA = "A-Za-z";
65
66
67
    // Default extension prefix to use when formatting. This will be put in front of any extension
68
    // component of the number, after the main national number is formatted. For example, if you wish
69
    // the default extension formatting to be " extn: 3456", then you should specify " extn: " here
70
    // as the default extension prefix. This can be overridden by region-specific preferences.
71
    const DEFAULT_EXTN_PREFIX = " ext. ";
72
73
    // Regular expression of acceptable punctuation found in phone numbers. This excludes punctuation
74
    // found as a leading character only.
75
    // This consists of dash characters, white space characters, full stops, slashes,
76
    // square brackets, parentheses and tildes. It also includes the letter 'x' as that is found as a
77
    // placeholder for carrier information in some phone numbers. Full-width variants are also
78
    // present.
79
    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";
80
    const DIGITS = "\\p{Nd}";
81
82
    // Pattern that makes it easy to distinguish whether a region has a unique international dialing
83
    // prefix or not. If a region has a unique international prefix (e.g. 011 in USA), it will be
84
    // represented as a string that contains a sequence of ASCII digits. If there are multiple
85
    // available international prefixes in a region, they will be represented as a regex string that
86
    // always contains character(s) other than ASCII digits.
87
    // Note this regex also includes tilde, which signals waiting for the tone.
88
    const UNIQUE_INTERNATIONAL_PREFIX = "[\\d]+(?:[~\xE2\x81\x93\xE2\x88\xBC\xEF\xBD\x9E][\\d]+)?";
89
    const NON_DIGITS_PATTERN = "(\\D+)";
90
91
    // The FIRST_GROUP_PATTERN was originally set to $1 but there are some countries for which the
92
    // first group is not used in the national pattern (e.g. Argentina) so the $1 group does not match
93
    // correctly.  Therefore, we use \d, so that the first group actually used in the pattern will be
94
    // matched.
95
    const FIRST_GROUP_PATTERN = "(\\$\\d)";
96
    const NP_PATTERN = '\\$NP';
97
    const FG_PATTERN = '\\$FG';
98
    const CC_PATTERN = '\\$CC';
99
100
    // A pattern that is used to determine if the national prefix formatting rule has the first group
101
    // only, i.e., does not start with the national prefix. Note that the pattern explicitly allows
102
    // for unbalanced parentheses.
103
    const FIRST_GROUP_ONLY_PREFIX_PATTERN = '\\(?\\$1\\)?';
104
    public static $PLUS_CHARS_PATTERN;
105
    private static $SEPARATOR_PATTERN;
106
    private static $CAPTURING_DIGIT_PATTERN;
107
    private static $VALID_START_CHAR_PATTERN = null;
108
    private static $SECOND_NUMBER_START_PATTERN = "[\\\\/] *x";
109
    private static $UNWANTED_END_CHAR_PATTERN = "[[\\P{N}&&\\P{L}]&&[^#]]+$";
110
    private static $DIALLABLE_CHAR_MAPPINGS = array();
111
    private static $CAPTURING_EXTN_DIGITS;
112
113
    /**
114
     * @var PhoneNumberUtil
115
     */
116
    private static $instance = null;
117
118
    /**
119
     * Only upper-case variants of alpha characters are stored.
120
     * @var array
121
     */
122
    private static $ALPHA_MAPPINGS = array(
123
        'A' => '2',
124
        'B' => '2',
125
        'C' => '2',
126
        'D' => '3',
127
        'E' => '3',
128
        'F' => '3',
129
        'G' => '4',
130
        'H' => '4',
131
        'I' => '4',
132
        'J' => '5',
133
        'K' => '5',
134
        'L' => '5',
135
        'M' => '6',
136
        'N' => '6',
137
        'O' => '6',
138
        'P' => '7',
139
        'Q' => '7',
140
        'R' => '7',
141
        'S' => '7',
142
        'T' => '8',
143
        'U' => '8',
144
        'V' => '8',
145
        'W' => '9',
146
        'X' => '9',
147
        'Y' => '9',
148
        'Z' => '9',
149
    );
150
151
    /**
152
     * Map of country calling codes that use a mobile token before the area code. One example of when
153
     * this is relevant is when determining the length of the national destination code, which should
154
     * be the length of the area code plus the length of the mobile token.
155
     * @var array
156
     */
157
    private static $MOBILE_TOKEN_MAPPINGS;
158
159
    /**
160
     * For performance reasons, amalgamate both into one map.
161
     * @var array
162
     */
163
    private static $ALPHA_PHONE_MAPPINGS;
164
165
    /**
166
     * Separate map of all symbols that we wish to retain when formatting alpha numbers. This
167
     * includes digits, ASCII letters and number grouping symbols such as "-" and " ".
168
     * @var array
169
     */
170
    private static $ALL_PLUS_NUMBER_GROUPING_SYMBOLS;
171
172
    /**
173
     * Simple ASCII digits map used to populate ALPHA_PHONE_MAPPINGS and
174
     * ALL_PLUS_NUMBER_GROUPING_SYMBOLS.
175
     * @var array
176
     */
177
    private static $asciiDigitMappings = array(
178
        '0' => '0',
179
        '1' => '1',
180
        '2' => '2',
181
        '3' => '3',
182
        '4' => '4',
183
        '5' => '5',
184
        '6' => '6',
185
        '7' => '7',
186
        '8' => '8',
187
        '9' => '9',
188
    );
189
190
    /**
191
     * Regexp of all possible ways to write extensions, for use when parsing. This will be run as a
192
     * case-insensitive regexp match. Wide character versions are also provided after each ASCII
193
     * version.
194
     * @var String
195
     */
196
    private static $EXTN_PATTERNS_FOR_PARSING;
197
    private static $EXTN_PATTERN = null;
198
    private static $VALID_PHONE_NUMBER_PATTERN;
199
    private static $MIN_LENGTH_PHONE_NUMBER_PATTERN;
200
    /**
201
     *  Regular expression of viable phone numbers. This is location independent. Checks we have at
202
     * least three leading digits, and only valid punctuation, alpha characters and
203
     * digits in the phone number. Does not include extension data.
204
     * The symbol 'x' is allowed here as valid punctuation since it is often used as a placeholder for
205
     * carrier codes, for example in Brazilian phone numbers. We also allow multiple "+" characters at
206
     * the start.
207
     * Corresponds to the following:
208
     * [digits]{minLengthNsn}|
209
     * plus_sign*(([punctuation]|[star])*[digits]){3,}([punctuation]|[star]|[digits]|[alpha])*
210
     *
211
     * The first reg-ex is to allow short numbers (two digits long) to be parsed if they are entered
212
     * as "15" etc, but only if there is no punctuation in them. The second expression restricts the
213
     * number of digits to three or more, but then allows them to be in international form, and to
214
     * have alpha-characters and punctuation.
215
     *
216
     * Note VALID_PUNCTUATION starts with a -, so must be the first in the range.
217
     * @var string
218
     */
219
    private static $VALID_PHONE_NUMBER;
220
    private static $numericCharacters = array(
221
        "\xef\xbc\x90" => 0,
222
        "\xef\xbc\x91" => 1,
223
        "\xef\xbc\x92" => 2,
224
        "\xef\xbc\x93" => 3,
225
        "\xef\xbc\x94" => 4,
226
        "\xef\xbc\x95" => 5,
227
        "\xef\xbc\x96" => 6,
228
        "\xef\xbc\x97" => 7,
229
        "\xef\xbc\x98" => 8,
230
        "\xef\xbc\x99" => 9,
231
232
        "\xd9\xa0" => 0,
233
        "\xd9\xa1" => 1,
234
        "\xd9\xa2" => 2,
235
        "\xd9\xa3" => 3,
236
        "\xd9\xa4" => 4,
237
        "\xd9\xa5" => 5,
238
        "\xd9\xa6" => 6,
239
        "\xd9\xa7" => 7,
240
        "\xd9\xa8" => 8,
241
        "\xd9\xa9" => 9,
242
243
        "\xdb\xb0" => 0,
244
        "\xdb\xb1" => 1,
245
        "\xdb\xb2" => 2,
246
        "\xdb\xb3" => 3,
247
        "\xdb\xb4" => 4,
248
        "\xdb\xb5" => 5,
249
        "\xdb\xb6" => 6,
250
        "\xdb\xb7" => 7,
251
        "\xdb\xb8" => 8,
252
        "\xdb\xb9" => 9,
253
254
        "\xe1\xa0\x90" => 0,
255
        "\xe1\xa0\x91" => 1,
256
        "\xe1\xa0\x92" => 2,
257
        "\xe1\xa0\x93" => 3,
258
        "\xe1\xa0\x94" => 4,
259
        "\xe1\xa0\x95" => 5,
260
        "\xe1\xa0\x96" => 6,
261
        "\xe1\xa0\x97" => 7,
262
        "\xe1\xa0\x98" => 8,
263
        "\xe1\xa0\x99" => 9,
264
    );
265
266
    /**
267
     * The set of county calling codes that map to the non-geo entity region ("001").
268
     * @var array
269
     */
270
    private $countryCodesForNonGeographicalRegion = array();
271
    /**
272
     * The set of regions the library supports.
273
     * @var array
274
     */
275
    private $supportedRegions = array();
276
277
    /**
278
     * A mapping from a country calling code to the region codes which denote the region represented
279
     * by that country calling code. In the case of multiple regions sharing a calling code, such as
280
     * the NANPA regions, the one indicated with "isMainCountryForCode" in the metadata should be
281
     * first.
282
     * @var array
283
     */
284
    private $countryCallingCodeToRegionCodeMap = array();
285
    /**
286
     * The set of regions that share country calling code 1.
287
     * @var array
288
     */
289
    private $nanpaRegions = array();
290
291
    /**
292
     * @var MetadataSourceInterface
293
     */
294
    private $metadataSource;
295
296
    /**
297
     * This class implements a singleton, so the only constructor is private.
298
     * @param MetadataSourceInterface $metadataSource
299
     * @param $countryCallingCodeToRegionCodeMap
300
     */
301 402
    private function __construct(MetadataSourceInterface $metadataSource, $countryCallingCodeToRegionCodeMap)
302
    {
303 402
        $this->metadataSource = $metadataSource;
304 402
        $this->countryCallingCodeToRegionCodeMap = $countryCallingCodeToRegionCodeMap;
305 402
        $this->init();
306 402
        self::initCapturingExtnDigits();
307 402
        self::initExtnPatterns();
308 402
        self::initExtnPattern();
309 402
        self::$PLUS_CHARS_PATTERN = "[" . self::PLUS_CHARS . "]+";
310 402
        self::$SEPARATOR_PATTERN = "[" . self::VALID_PUNCTUATION . "]+";
311 402
        self::$CAPTURING_DIGIT_PATTERN = "(" . self::DIGITS . ")";
312 402
        self::$VALID_START_CHAR_PATTERN = "[" . self::PLUS_CHARS . self::DIGITS . "]";
313
314 402
        self::$ALPHA_PHONE_MAPPINGS = self::$ALPHA_MAPPINGS + self::$asciiDigitMappings;
315
316 402
        self::$DIALLABLE_CHAR_MAPPINGS = self::$asciiDigitMappings;
317 402
        self::$DIALLABLE_CHAR_MAPPINGS[self::PLUS_SIGN] = self::PLUS_SIGN;
318 402
        self::$DIALLABLE_CHAR_MAPPINGS['*'] = '*';
319
320 402
        self::$ALL_PLUS_NUMBER_GROUPING_SYMBOLS = array();
321
        // Put (lower letter -> upper letter) and (upper letter -> upper letter) mappings.
322 402
        foreach (self::$ALPHA_MAPPINGS as $c => $value) {
323 402
            self::$ALL_PLUS_NUMBER_GROUPING_SYMBOLS[strtolower($c)] = $c;
324 402
            self::$ALL_PLUS_NUMBER_GROUPING_SYMBOLS[$c] = $c;
325 402
        }
326 402
        self::$ALL_PLUS_NUMBER_GROUPING_SYMBOLS += self::$asciiDigitMappings;
327 402
        self::$ALL_PLUS_NUMBER_GROUPING_SYMBOLS["-"] = '-';
328 402
        self::$ALL_PLUS_NUMBER_GROUPING_SYMBOLS["\xEF\xBC\x8D"] = '-';
329 402
        self::$ALL_PLUS_NUMBER_GROUPING_SYMBOLS["\xE2\x80\x90"] = '-';
330 402
        self::$ALL_PLUS_NUMBER_GROUPING_SYMBOLS["\xE2\x80\x91"] = '-';
331 402
        self::$ALL_PLUS_NUMBER_GROUPING_SYMBOLS["\xE2\x80\x92"] = '-';
332 402
        self::$ALL_PLUS_NUMBER_GROUPING_SYMBOLS["\xE2\x80\x93"] = '-';
333 402
        self::$ALL_PLUS_NUMBER_GROUPING_SYMBOLS["\xE2\x80\x94"] = '-';
334 402
        self::$ALL_PLUS_NUMBER_GROUPING_SYMBOLS["\xE2\x80\x95"] = '-';
335 402
        self::$ALL_PLUS_NUMBER_GROUPING_SYMBOLS["\xE2\x88\x92"] = '-';
336 402
        self::$ALL_PLUS_NUMBER_GROUPING_SYMBOLS["/"] = "/";
337 402
        self::$ALL_PLUS_NUMBER_GROUPING_SYMBOLS["\xEF\xBC\x8F"] = "/";
338 402
        self::$ALL_PLUS_NUMBER_GROUPING_SYMBOLS[" "] = " ";
339 402
        self::$ALL_PLUS_NUMBER_GROUPING_SYMBOLS["\xE3\x80\x80"] = " ";
340 402
        self::$ALL_PLUS_NUMBER_GROUPING_SYMBOLS["\xE2\x81\xA0"] = " ";
341 402
        self::$ALL_PLUS_NUMBER_GROUPING_SYMBOLS["."] = ".";
342 402
        self::$ALL_PLUS_NUMBER_GROUPING_SYMBOLS["\xEF\xBC\x8E"] = ".";
343
344
345 402
        self::$MIN_LENGTH_PHONE_NUMBER_PATTERN = "[" . self::DIGITS . "]{" . self::MIN_LENGTH_FOR_NSN . "}";
346 402
        self::$VALID_PHONE_NUMBER = "[" . self::PLUS_CHARS . "]*(?:[" . self::VALID_PUNCTUATION . self::STAR_SIGN . "]*[" . self::DIGITS . "]){3,}[" . self::VALID_PUNCTUATION . self::STAR_SIGN . self::VALID_ALPHA . self::DIGITS . "]*";
347 402
        self::$VALID_PHONE_NUMBER_PATTERN = "%^" . self::$MIN_LENGTH_PHONE_NUMBER_PATTERN . "$|^" . self::$VALID_PHONE_NUMBER . "(?:" . self::$EXTN_PATTERNS_FOR_PARSING . ")?$%" . self::REGEX_FLAGS;
348
349 402
        self::$UNWANTED_END_CHAR_PATTERN = "[^" . self::DIGITS . self::VALID_ALPHA . "#]+$";
350
351 402
        self::$MOBILE_TOKEN_MAPPINGS = array();
352 402
        self::$MOBILE_TOKEN_MAPPINGS['52'] = "1";
353 402
        self::$MOBILE_TOKEN_MAPPINGS['54'] = "9";
354 402
    }
355
356
    /**
357
     * Gets a {@link PhoneNumberUtil} instance to carry out international phone number formatting,
358
     * parsing, or validation. The instance is loaded with phone number metadata for a number of most
359
     * commonly used regions.
360
     *
361
     * <p>The {@link PhoneNumberUtil} is implemented as a singleton. Therefore, calling getInstance
362
     * multiple times will only result in one instance being created.
363
     *
364
     * @param string $baseFileLocation
365
     * @param array|null $countryCallingCodeToRegionCodeMap
366
     * @param MetadataLoaderInterface|null $metadataLoader
367
     * @param MetadataSourceInterface|null $metadataSource
368
     * @return PhoneNumberUtil instance
369
     */
370 4321
    public static function getInstance($baseFileLocation = self::META_DATA_FILE_PREFIX, array $countryCallingCodeToRegionCodeMap = null, MetadataLoaderInterface $metadataLoader = null, MetadataSourceInterface $metadataSource = null)
371
    {
372 4321
        if (self::$instance === null) {
373 402
            if ($countryCallingCodeToRegionCodeMap === null) {
374 267
                $countryCallingCodeToRegionCodeMap = CountryCodeToRegionCodeMap::$countryCodeToRegionCodeMap;
375 267
            }
376
377 402
            if ($metadataLoader === null) {
378 402
                $metadataLoader = new DefaultMetadataLoader();
379 402
            }
380
381 402
            if ($metadataSource === null) {
382 402
                $metadataSource = new MultiFileMetadataSourceImpl($metadataLoader, __DIR__ . '/data/' . $baseFileLocation);
383 402
            }
384
385 402
            self::$instance = new PhoneNumberUtil($metadataSource, $countryCallingCodeToRegionCodeMap);
386 402
        }
387 4321
        return self::$instance;
388
    }
389
390 402
    private function init()
391
    {
392 402
        foreach ($this->countryCallingCodeToRegionCodeMap as $countryCode => $regionCodes) {
393
            // We can assume that if the country calling code maps to the non-geo entity region code then
394
            // that's the only region code it maps to.
395 402
            if (count($regionCodes) == 1 && self::REGION_CODE_FOR_NON_GEO_ENTITY === $regionCodes[0]) {
396
                // This is the subset of all country codes that map to the non-geo entity region code.
397 402
                $this->countryCodesForNonGeographicalRegion[] = $countryCode;
398 402
            } else {
399
                // The supported regions set does not include the "001" non-geo entity region code.
400 402
                $this->supportedRegions = array_merge($this->supportedRegions, $regionCodes);
401
            }
402 402
        }
403
        // If the non-geo entity still got added to the set of supported regions it must be because
404
        // there are entries that list the non-geo entity alongside normal regions (which is wrong).
405
        // If we discover this, remove the non-geo entity from the set of supported regions and log.
406 402
        $idx_region_code_non_geo_entity = array_search(self::REGION_CODE_FOR_NON_GEO_ENTITY, $this->supportedRegions);
407 402
        if ($idx_region_code_non_geo_entity !== false) {
408
            unset($this->supportedRegions[$idx_region_code_non_geo_entity]);
409
        }
410 402
        $this->nanpaRegions = $this->countryCallingCodeToRegionCodeMap[self::NANPA_COUNTRY_CODE];
411 402
    }
412
413 402
    private static function initCapturingExtnDigits()
414
    {
415 402
        self::$CAPTURING_EXTN_DIGITS = "(" . self::DIGITS . "{1,7})";
416 402
    }
417
418 402
    private static function initExtnPatterns()
419
    {
420
        // One-character symbols that can be used to indicate an extension.
421 402
        $singleExtnSymbolsForMatching = "x\xEF\xBD\x98#\xEF\xBC\x83~\xEF\xBD\x9E";
422
        // For parsing, we are slightly more lenient in our interpretation than for matching. Here we
423
        // allow a "comma" as a possible extension indicator. When matching, this is hardly ever used to
424
        // indicate this.
425 402
        $singleExtnSymbolsForParsing = "," . $singleExtnSymbolsForMatching;
426
427 402
        self::$EXTN_PATTERNS_FOR_PARSING = self::createExtnPattern($singleExtnSymbolsForParsing);
428 402
    }
429
430
    // The FIRST_GROUP_PATTERN was originally set to $1 but there are some countries for which the
431
    // first group is not used in the national pattern (e.g. Argentina) so the $1 group does not match
432
    // correctly.  Therefore, we use \d, so that the first group actually used in the pattern will be
433
    // matched.
434
435
    /**
436
     * Helper initialiser method to create the regular-expression pattern to match extensions,
437
     * allowing the one-char extension symbols provided by {@code singleExtnSymbols}.
438
     * @param string $singleExtnSymbols
439
     * @return string
440
     */
441 402
    private static function createExtnPattern($singleExtnSymbols)
442
    {
443
        // There are three regular expressions here. The first covers RFC 3966 format, where the
444
        // extension is added using ";ext=". The second more generic one starts with optional white
445
        // space and ends with an optional full stop (.), followed by zero or more spaces/tabs and then
446
        // the numbers themselves. The other one covers the special case of American numbers where the
447
        // extension is written with a hash at the end, such as "- 503#".
448
        // Note that the only capturing groups should be around the digits that you want to capture as
449
        // part of the extension, or else parsing will fail!
450
        // Canonical-equivalence doesn't seem to be an option with Android java, so we allow two options
451
        // for representing the accented o - the character itself, and one in the unicode decomposed
452
        // form with the combining acute accent.
453 402
        return (self::RFC3966_EXTN_PREFIX . self::$CAPTURING_EXTN_DIGITS . "|" . "[ \xC2\xA0\\t,]*" .
454 402
            "(?:e?xt(?:ensi(?:o\xCC\x81?|\xC3\xB3))?n?|(?:\xEF\xBD\x85)?\xEF\xBD\x98\xEF\xBD\x94(?:\xEF\xBD\x8E)?|" .
455 402
            "[" . $singleExtnSymbols . "]|int|\xEF\xBD\x89\xEF\xBD\x8E\xEF\xBD\x94|anexo)" .
456 402
            "[:\\.\xEF\xBC\x8E]?[ \xC2\xA0\\t,-]*" . self::$CAPTURING_EXTN_DIGITS . "#?|" .
457 402
            "[- ]+(" . self::DIGITS . "{1,5})#");
458
    }
459
460 402
    private static function initExtnPattern()
461
    {
462 402
        self::$EXTN_PATTERN = "/(?:" . self::$EXTN_PATTERNS_FOR_PARSING . ")$/" . self::REGEX_FLAGS;
463 402
    }
464
465
    /**
466
     * Used for testing purposes only to reset the PhoneNumberUtil singleton to null.
467
     */
468 402
    public static function resetInstance()
469
    {
470 402
        self::$instance = null;
471 402
    }
472
473
    /**
474
     * Converts all alpha characters in a number to their respective digits on a keypad, but retains
475
     * existing formatting.
476
     * @param string $number
477
     * @return string
478
     */
479 1
    public static function convertAlphaCharactersInNumber($number)
480
    {
481 1
        return self::normalizeHelper($number, self::$ALPHA_PHONE_MAPPINGS, false);
482
    }
483
484
    /**
485
     * Normalizes a string of characters representing a phone number by replacing all characters found
486
     * in the accompanying map with the values therein, and stripping all other characters if
487
     * removeNonMatches is true.
488
     *
489
     * @param string $number a string of characters representing a phone number
490
     * @param array $normalizationReplacements a mapping of characters to what they should be replaced by in
491
     * the normalized version of the phone number
492
     * @param bool $removeNonMatches indicates whether characters that are not able to be replaced
493
     * should be stripped from the number. If this is false, they will be left unchanged in the number.
494
     * @return string the normalized string version of the phone number
495
     */
496 11
    private static function normalizeHelper($number, array $normalizationReplacements, $removeNonMatches)
497
    {
498 11
        $normalizedNumber = "";
499 11
        $strLength = mb_strlen($number, 'UTF-8');
500 11
        for ($i = 0; $i < $strLength; $i++) {
501 11
            $character = mb_substr($number, $i, 1, 'UTF-8');
502 11
            if (isset($normalizationReplacements[mb_strtoupper($character, 'UTF-8')])) {
503 11
                $normalizedNumber .= $normalizationReplacements[mb_strtoupper($character, 'UTF-8')];
504 11
            } else {
505 11
                if (!$removeNonMatches) {
506 1
                    $normalizedNumber .= $character;
507 1
                }
508
            }
509
            // If neither of the above are true, we remove this character.
510 11
        }
511 11
        return $normalizedNumber;
512
    }
513
514
    /**
515
     * Helper function to check if the national prefix formatting rule has the first group only, i.e.,
516
     * does not start with the national prefix.
517
     * @param string $nationalPrefixFormattingRule
518
     * @return bool
519
     */
520
    public static function formattingRuleHasFirstGroupOnly($nationalPrefixFormattingRule)
521
    {
522
        $m = preg_match(self::FIRST_GROUP_ONLY_PREFIX_PATTERN, $nationalPrefixFormattingRule);
523
        return $m > 0;
524
    }
525
526
    /**
527
     * Convenience method to get a list of what regions the library has metadata for.
528
     * @return array
529
     */
530 234
    public function getSupportedRegions()
531
    {
532 234
        return $this->supportedRegions;
533
    }
534
535
    /**
536
     * Convenience method to get a list of what global network calling codes the library has metadata
537
     * for.
538
     * @return array
539
     */
540 1
    public function getSupportedGlobalNetworkCallingCodes()
541
    {
542 1
        return $this->countryCodesForNonGeographicalRegion;
543
    }
544
545
    /**
546
     * Gets the length of the geographical area code from the {@code nationalNumber} field of the
547
     * PhoneNumber object passed in, so that clients could use it to split a national significant
548
     * number into geographical area code and subscriber number. It works in such a way that the
549
     * resultant subscriber number should be diallable, at least on some devices. An example of how
550
     * this could be used:
551
     *
552
     * <code>
553
     * $phoneUtil = PhoneNumberUtil::getInstance();
554
     * $number = $phoneUtil->parse("16502530000", "US");
555
     * $nationalSignificantNumber = $phoneUtil->getNationalSignificantNumber($number);
556
     *
557
     * $areaCodeLength = $phoneUtil->getLengthOfGeographicalAreaCode($number);
558
     * if ($areaCodeLength > 0)
559
     * {
560
     *     $areaCode = substr($nationalSignificantNumber, 0,$areaCodeLength);
561
     *     $subscriberNumber = substr($nationalSignificantNumber, $areaCodeLength);
562
     * } else {
563
     *     $areaCode = "";
564
     *     $subscriberNumber = $nationalSignificantNumber;
565
     * }
566
     * </code>
567
     *
568
     * N.B.: area code is a very ambiguous concept, so the I18N team generally recommends against
569
     * using it for most purposes, but recommends using the more general {@code nationalNumber}
570
     * instead. Read the following carefully before deciding to use this method:
571
     * <ul>
572
     *  <li> geographical area codes change over time, and this method honors those changes;
573
     *    therefore, it doesn't guarantee the stability of the result it produces.
574
     *  <li> subscriber numbers may not be diallable from all devices (notably mobile devices, which
575
     *    typically requires the full national_number to be dialled in most regions).
576
     *  <li> most non-geographical numbers have no area codes, including numbers from non-geographical
577
     *    entities
578
     *  <li> some geographical numbers have no area codes.
579
     * </ul>
580
     * @param PhoneNumber $number PhoneNumber object for which clients want to know the length of the area code.
581
     * @return int the length of area code of the PhoneNumber object passed in.
582
     */
583 1
    public function getLengthOfGeographicalAreaCode(PhoneNumber $number)
584
    {
585 1
        $metadata = $this->getMetadataForRegion($this->getRegionCodeForNumber($number));
586 1
        if ($metadata === null) {
587 1
            return 0;
588
        }
589
        // If a country doesn't use a national prefix, and this number doesn't have an Italian leading
590
        // zero, we assume it is a closed dialling plan with no area codes.
591 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...
592 1
            return 0;
593
        }
594
595 1
        if (!$this->isNumberGeographical($number)) {
596 1
            return 0;
597
        }
598
599 1
        return $this->getLengthOfNationalDestinationCode($number);
600
    }
601
602
    /**
603
     * Returns the metadata for the given region code or {@code null} if the region code is invalid
604
     * or unknown.
605
     * @param string $regionCode
606
     * @return PhoneMetadata
607
     */
608 4050
    public function getMetadataForRegion($regionCode)
609
    {
610 4050
        if (!$this->isValidRegionCode($regionCode)) {
611 282
            return null;
612
        }
613
614 4037
        return $this->metadataSource->getMetadataForRegion($regionCode);
615
    }
616
617
    /**
618
     * Helper function to check region code is not unknown or null.
619
     * @param string $regionCode
620
     * @return bool
621
     */
622 4050
    private function isValidRegionCode($regionCode)
623
    {
624 4050
        return $regionCode !== null && in_array($regionCode, $this->supportedRegions);
625
    }
626
627
    /**
628
     * Returns the region where a phone number is from. This could be used for geocoding at the region
629
     * level.
630
     *
631
     * @param PhoneNumber $number the phone number whose origin we want to know
632
     * @return null|string  the region where the phone number is from, or null if no region matches this calling
633
     * code
634
     */
635 1892
    public function getRegionCodeForNumber(PhoneNumber $number)
636
    {
637 1892
        $countryCode = $number->getCountryCode();
638 1892
        if (!isset($this->countryCallingCodeToRegionCodeMap[$countryCode])) {
639 4
            return null;
640
        }
641 1891
        $regions = $this->countryCallingCodeToRegionCodeMap[$countryCode];
642 1891
        if (count($regions) == 1) {
643 1445
            return $regions[0];
644
        } else {
645 467
            return $this->getRegionCodeForNumberFromRegionList($number, $regions);
646
        }
647
    }
648
649
    /**
650
     * @param PhoneNumber $number
651
     * @param array $regionCodes
652
     * @return null|string
653
     */
654 467
    private function getRegionCodeForNumberFromRegionList(PhoneNumber $number, array $regionCodes)
655
    {
656 467
        $nationalNumber = $this->getNationalSignificantNumber($number);
657 467
        foreach ($regionCodes as $regionCode) {
658
            // If leadingDigits is present, use this. Otherwise, do full validation.
659
            // Metadata cannot be null because the region codes come from the country calling code map.
660 467
            $metadata = $this->getMetadataForRegion($regionCode);
661 467
            if ($metadata->hasLeadingDigits()) {
662 144
                $nbMatches = preg_match(
663 144
                    '/' . $metadata->getLeadingDigits() . '/',
664 144
                    $nationalNumber,
665 144
                    $matches,
666
                    PREG_OFFSET_CAPTURE
667 144
                );
668 144
                if ($nbMatches > 0 && $matches[0][1] === 0) {
669 139
                    return $regionCode;
670
                }
671 458
            } else if ($this->getNumberTypeHelper($nationalNumber, $metadata) != PhoneNumberType::UNKNOWN) {
0 ignored issues
show
Bug introduced by
It seems like $metadata defined by $this->getMetadataForRegion($regionCode) on line 660 can be null; however, libphonenumber\PhoneNumb...::getNumberTypeHelper() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
672 323
                return $regionCode;
673
            }
674 204
        }
675 12
        return null;
676
    }
677
678
    /**
679
     * Gets the national significant number of the a phone number. Note a national significant number
680
     * doesn't contain a national prefix or any formatting.
681
     *
682
     * @param PhoneNumber $number the phone number for which the national significant number is needed
683
     * @return string the national significant number of the PhoneNumber object passed in
684
     */
685 1736
    public function getNationalSignificantNumber(PhoneNumber $number)
686
    {
687
        // If leading zero(s) have been set, we prefix this now. Note this is not a national prefix.
688 1736
        $nationalNumber = '';
689 1736
        if ($number->isItalianLeadingZero()) {
690 38
            $zeros = str_repeat('0', $number->getNumberOfLeadingZeros());
691 38
            $nationalNumber .= $zeros;
692 38
        }
693 1736
        $nationalNumber .= $number->getNationalNumber();
694 1736
        return $nationalNumber;
695
    }
696
697
    /**
698
     * @param string $nationalNumber
699
     * @param PhoneMetadata $metadata
700
     * @return int PhoneNumberType constant
701
     */
702 1679
    private function getNumberTypeHelper($nationalNumber, PhoneMetadata $metadata)
703
    {
704 1679
        if (!$this->isNumberMatchingDesc($nationalNumber, $metadata->getGeneralDesc())) {
705 34
            return PhoneNumberType::UNKNOWN;
706
        }
707 1676
        if ($this->isNumberMatchingDesc($nationalNumber, $metadata->getPremiumRate())) {
708 148
            return PhoneNumberType::PREMIUM_RATE;
709
        }
710 1529
        if ($this->isNumberMatchingDesc($nationalNumber, $metadata->getTollFree())) {
711 179
            return PhoneNumberType::TOLL_FREE;
712
        }
713
714
715 1359
        if ($this->isNumberMatchingDesc($nationalNumber, $metadata->getSharedCost())) {
716 63
            return PhoneNumberType::SHARED_COST;
717
        }
718 1296
        if ($this->isNumberMatchingDesc($nationalNumber, $metadata->getVoip())) {
719 76
            return PhoneNumberType::VOIP;
720
        }
721 1223
        if ($this->isNumberMatchingDesc($nationalNumber, $metadata->getPersonalNumber())) {
722 63
            return PhoneNumberType::PERSONAL_NUMBER;
723
        }
724 1160
        if ($this->isNumberMatchingDesc($nationalNumber, $metadata->getPager())) {
725 25
            return PhoneNumberType::PAGER;
726
        }
727 1137
        if ($this->isNumberMatchingDesc($nationalNumber, $metadata->getUan())) {
728 56
            return PhoneNumberType::UAN;
729
        }
730 1083
        if ($this->isNumberMatchingDesc($nationalNumber, $metadata->getVoicemail())) {
731 11
            return PhoneNumberType::VOICEMAIL;
732
        }
733 1072
        $isFixedLine = $this->isNumberMatchingDesc($nationalNumber, $metadata->getFixedLine());
734 1072
        if ($isFixedLine) {
735 791
            if ($metadata->isSameMobileAndFixedLinePattern()) {
736
                return PhoneNumberType::FIXED_LINE_OR_MOBILE;
737 791
            } else if ($this->isNumberMatchingDesc($nationalNumber, $metadata->getMobile())) {
738 57
                return PhoneNumberType::FIXED_LINE_OR_MOBILE;
739
            }
740 743
            return PhoneNumberType::FIXED_LINE;
741
        }
742
        // Otherwise, test to see if the number is mobile. Only do this if certain that the patterns for
743
        // mobile and fixed line aren't the same.
744 403
        if (!$metadata->isSameMobileAndFixedLinePattern() &&
745 403
            $this->isNumberMatchingDesc($nationalNumber, $metadata->getMobile())
746 403
        ) {
747 253
            return PhoneNumberType::MOBILE;
748
        }
749 173
        return PhoneNumberType::UNKNOWN;
750
    }
751
752
    /**
753
     * @param string $nationalNumber
754
     * @param PhoneNumberDesc $numberDesc
755
     * @return bool
756
     */
757 1700
    public function isNumberMatchingDesc($nationalNumber, PhoneNumberDesc $numberDesc)
758
    {
759 1700
        $nationalNumberPatternMatcher = new Matcher($numberDesc->getNationalNumberPattern(), $nationalNumber);
760
761 1700
        return $this->isNumberPossibleForDesc($nationalNumber, $numberDesc) && $nationalNumberPatternMatcher->matches();
762
    }
763
764
    /**
765
     *
766
     * Helper method to check whether a number is too short to be a regular length phone number in a
767
     * region.
768
     *
769
     * @param PhoneMetadata $regionMetadata
770
     * @param string $number
771
     * @return bool
772
     */
773 2391
    private function isShorterThanPossibleNormalNumber(PhoneMetadata $regionMetadata, $number)
774
    {
775 2391
        $possibleNumberPattern = $regionMetadata->getGeneralDesc()->getPossibleNumberPattern();
776 2391
        return ($this->testNumberLengthAgainstPattern($possibleNumberPattern, $number) === ValidationResult::TOO_SHORT);
777
    }
778
779
    /**
780
     * @param string $nationalNumber
781
     * @param PhoneNumberDesc $numberDesc
782
     * @return bool
783
     */
784 1700
    public function isNumberPossibleForDesc($nationalNumber, PhoneNumberDesc $numberDesc)
785
    {
786 1700
        $possibleNumberPatternMatcher = new Matcher($numberDesc->getPossibleNumberPattern(), $nationalNumber);
787
788 1700
        return $possibleNumberPatternMatcher->matches();
789
    }
790
791
    /**
792
     * Tests whether a phone number has a geographical association. It checks if the number is
793
     * associated to a certain region in the country where it belongs to. Note that this doesn't
794
     * verify if the number is actually in use.
795
     * @param PhoneNumber $phoneNumber
796
     * @return bool
797
     */
798 2
    public function isNumberGeographical(PhoneNumber $phoneNumber)
799
    {
800 2
        $numberType = $this->getNumberType($phoneNumber);
801
        // TODO: Include mobile phone numbers from countries like Indonesia, which has some
802
        // mobile numbers that are geographical.
803 2
        return $numberType == PhoneNumberType::FIXED_LINE || $numberType == PhoneNumberType::FIXED_LINE_OR_MOBILE;
804
    }
805
806
    /**
807
     * Gets the type of a phone number.
808
     * @param PhoneNumber $number the number the phone number that we want to know the type
809
     * @return int PhoneNumberType the type of the phone number
810
     */
811 1357
    public function getNumberType(PhoneNumber $number)
812
    {
813 1357
        $regionCode = $this->getRegionCodeForNumber($number);
814 1357
        $metadata = $this->getMetadataForRegionOrCallingCode($number->getCountryCode(), $regionCode);
815 1357
        if ($metadata === null) {
816 8
            return PhoneNumberType::UNKNOWN;
817
        }
818 1356
        $nationalSignificantNumber = $this->getNationalSignificantNumber($number);
819 1356
        return $this->getNumberTypeHelper($nationalSignificantNumber, $metadata);
820
    }
821
822
    /**
823
     * @param int $countryCallingCode
824
     * @param string $regionCode
825
     * @return PhoneMetadata
826
     */
827 1666
    private function getMetadataForRegionOrCallingCode($countryCallingCode, $regionCode)
828
    {
829 1666
        return self::REGION_CODE_FOR_NON_GEO_ENTITY === $regionCode ?
830 1666
            $this->getMetadataForNonGeographicalRegion($countryCallingCode) : $this->getMetadataForRegion($regionCode);
831
    }
832
833
    /**
834
     * @param int $countryCallingCode
835
     * @return PhoneMetadata
836
     */
837 31
    public function getMetadataForNonGeographicalRegion($countryCallingCode)
838
    {
839 31
        if (!isset($this->countryCallingCodeToRegionCodeMap[$countryCallingCode])) {
840 1
            return null;
841
        }
842 31
        return $this->metadataSource->getMetadataForNonGeographicalRegion($countryCallingCode);
843
    }
844
845
    /**
846
     * Gets the length of the national destination code (NDC) from the PhoneNumber object passed in,
847
     * so that clients could use it to split a national significant number into NDC and subscriber
848
     * number. The NDC of a phone number is normally the first group of digit(s) right after the
849
     * country calling code when the number is formatted in the international format, if there is a
850
     * subscriber number part that follows. An example of how this could be used:
851
     *
852
     * <code>
853
     * $phoneUtil = PhoneNumberUtil::getInstance();
854
     * $number = $phoneUtil->parse("18002530000", "US");
855
     * $nationalSignificantNumber = $phoneUtil->getNationalSignificantNumber($number);
856
     *
857
     * $nationalDestinationCodeLength = $phoneUtil->getLengthOfNationalDestinationCode($number);
858
     * if ($nationalDestinationCodeLength > 0) {
859
     *     $nationalDestinationCode = substr($nationalSignificantNumber, 0, $nationalDestinationCodeLength);
860
     *     $subscriberNumber = substr($nationalSignificantNumber, $nationalDestinationCodeLength);
861
     * } else {
862
     *     $nationalDestinationCode = "";
863
     *     $subscriberNumber = $nationalSignificantNumber;
864
     * }
865
     * </code>
866
     *
867
     * Refer to the unit tests to see the difference between this function and
868
     * {@link #getLengthOfGeographicalAreaCode}.
869
     *
870
     * @param PhoneNumber $number the PhoneNumber object for which clients want to know the length of the NDC.
871
     * @return int the length of NDC of the PhoneNumber object passed in.
872
     */
873 2
    public function getLengthOfNationalDestinationCode(PhoneNumber $number)
874
    {
875 2
        if ($number->hasExtension()) {
876
            // We don't want to alter the proto given to us, but we don't want to include the extension
877
            // when we format it, so we copy it and clear the extension here.
878
            $copiedProto = new PhoneNumber();
879
            $copiedProto->mergeFrom($number);
880
            $copiedProto->clearExtension();
881
        } else {
882 2
            $copiedProto = clone $number;
883
        }
884
885 2
        $nationalSignificantNumber = $this->format($copiedProto, PhoneNumberFormat::INTERNATIONAL);
886
887 2
        $numberGroups = preg_split('/' . self::NON_DIGITS_PATTERN . '/', $nationalSignificantNumber);
888
889
        // The pattern will start with "+COUNTRY_CODE " so the first group will always be the empty
890
        // string (before the + symbol) and the second group will be the country calling code. The third
891
        // group will be area code if it is not the last group.
892 2
        if (count($numberGroups) <= 3) {
893 1
            return 0;
894
        }
895
896 2
        if ($this->getNumberType($number) == PhoneNumberType::MOBILE) {
897
            // For example Argentinian mobile numbers, when formatted in the international format, are in
898
            // the form of +54 9 NDC XXXX.... As a result, we take the length of the third group (NDC) and
899
            // add the length of the second group (which is the mobile token), which also forms part of
900
            // the national significant number. This assumes that the mobile token is always formatted
901
            // separately from the rest of the phone number.
902
903 1
            $mobileToken = self::getCountryMobileToken($number->getCountryCode());
904 1
            if ($mobileToken !== "") {
905 1
                return mb_strlen($numberGroups[2]) + mb_strlen($numberGroups[3]);
906
            }
907 1
        }
908 2
        return mb_strlen($numberGroups[2]);
909
    }
910
911
    /**
912
     * Formats a phone number in the specified format using default rules. Note that this does not
913
     * promise to produce a phone number that the user can dial from where they are - although we do
914
     * format in either 'national' or 'international' format depending on what the client asks for, we
915
     * do not currently support a more abbreviated format, such as for users in the same "area" who
916
     * could potentially dial the number without area code. Note that if the phone number has a
917
     * country calling code of 0 or an otherwise invalid country calling code, we cannot work out
918
     * which formatting rules to apply so we return the national significant number with no formatting
919
     * applied.
920
     *
921
     * @param PhoneNumber $number the phone number to be formatted
922
     * @param int $numberFormat the PhoneNumberFormat the phone number should be formatted into
923
     * @return string the formatted phone number
924
     */
925 289
    public function format(PhoneNumber $number, $numberFormat)
926
    {
927 289
        if ($number->getNationalNumber() == 0 && $number->hasRawInput()) {
928
            // Unparseable numbers that kept their raw input just use that.
929
            // This is the only case where a number can be formatted as E164 without a
930
            // leading '+' symbol (but the original number wasn't parseable anyway).
931
            // TODO: Consider removing the 'if' above so that unparseable
932
            // strings without raw input format to the empty string instead of "+00"
933 1
            $rawInput = $number->getRawInput();
934 1
            if (mb_strlen($rawInput) > 0) {
935 1
                return $rawInput;
936
            }
937
        }
938 289
        $metadata = null;
939 289
        $formattedNumber = "";
940 289
        $countryCallingCode = $number->getCountryCode();
941 289
        $nationalSignificantNumber = $this->getNationalSignificantNumber($number);
942 289
        if ($numberFormat == PhoneNumberFormat::E164) {
943
            // Early exit for E164 case (even if the country calling code is invalid) since no formatting
944
            // of the national number needs to be applied. Extensions are not formatted.
945 265
            $formattedNumber .= $nationalSignificantNumber;
946 265
            $this->prefixNumberWithCountryCallingCode($countryCallingCode, PhoneNumberFormat::E164, $formattedNumber);
947 289
        } elseif (!$this->hasValidCountryCallingCode($countryCallingCode)) {
948 1
            $formattedNumber .= $nationalSignificantNumber;
949 1
        } else {
950
            // Note getRegionCodeForCountryCode() is used because formatting information for regions which
951
            // share a country calling code is contained by only one region for performance reasons. For
952
            // example, for NANPA regions it will be contained in the metadata for US.
953 41
            $regionCode = $this->getRegionCodeForCountryCode($countryCallingCode);
954
            // Metadata cannot be null because the country calling code is valid (which means that the
955
            // region code cannot be ZZ and must be one of our supported region codes).
956 41
            $metadata = $this->getMetadataForRegionOrCallingCode($countryCallingCode, $regionCode);
957 41
            $formattedNumber .= $this->formatNsn($nationalSignificantNumber, $metadata, $numberFormat);
0 ignored issues
show
Bug introduced by
It seems like $metadata defined by $this->getMetadataForReg...llingCode, $regionCode) on line 956 can be null; however, libphonenumber\PhoneNumberUtil::formatNsn() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
958 41
            $this->prefixNumberWithCountryCallingCode($countryCallingCode, $numberFormat, $formattedNumber);
959
        }
960 289
        $this->maybeAppendFormattedExtension($number, $metadata, $numberFormat, $formattedNumber);
961 289
        return $formattedNumber;
962
    }
963
964
    /**
965
     * A helper function that is used by format and formatByPattern.
966
     * @param int $countryCallingCode
967
     * @param int $numberFormat PhoneNumberFormat
968
     * @param string $formattedNumber
969
     */
970 290
    private function prefixNumberWithCountryCallingCode($countryCallingCode, $numberFormat, &$formattedNumber)
971
    {
972
        switch ($numberFormat) {
973 290
            case PhoneNumberFormat::E164:
974 265
                $formattedNumber = self::PLUS_SIGN . $countryCallingCode . $formattedNumber;
975 265
                return;
976 42
            case PhoneNumberFormat::INTERNATIONAL:
977 19
                $formattedNumber = self::PLUS_SIGN . $countryCallingCode . " " . $formattedNumber;
978 19
                return;
979 39
            case PhoneNumberFormat::RFC3966:
980 4
                $formattedNumber = self::RFC3966_PREFIX . self::PLUS_SIGN . $countryCallingCode . "-" . $formattedNumber;
981 4
                return;
982 39
            case PhoneNumberFormat::NATIONAL:
983 39
            default:
984 39
                return;
985
        }
986
    }
987
988
    /**
989
     * Helper function to check the country calling code is valid.
990
     * @param int $countryCallingCode
991
     * @return bool
992
     */
993 47
    private function hasValidCountryCallingCode($countryCallingCode)
994
    {
995 47
        return isset($this->countryCallingCodeToRegionCodeMap[$countryCallingCode]);
996
    }
997
998
    /**
999
     * Returns the region code that matches the specific country calling code. In the case of no
1000
     * region code being found, ZZ will be returned. In the case of multiple regions, the one
1001
     * designated in the metadata as the "main" region for this calling code will be returned. If the
1002
     * countryCallingCode entered is valid but doesn't match a specific region (such as in the case of
1003
     * non-geographical calling codes like 800) the value "001" will be returned (corresponding to
1004
     * the value for World in the UN M.49 schema).
1005
     *
1006
     * @param int $countryCallingCode
1007
     * @return string
1008
     */
1009 332
    public function getRegionCodeForCountryCode($countryCallingCode)
1010
    {
1011 332
        $regionCodes = isset($this->countryCallingCodeToRegionCodeMap[$countryCallingCode]) ? $this->countryCallingCodeToRegionCodeMap[$countryCallingCode] : null;
1012 332
        return $regionCodes === null ? self::UNKNOWN_REGION : $regionCodes[0];
1013
    }
1014
1015
    /**
1016
     * Note in some regions, the national number can be written in two completely different ways
1017
     * depending on whether it forms part of the NATIONAL format or INTERNATIONAL format. The
1018
     * numberFormat parameter here is used to specify which format to use for those cases. If a
1019
     * carrierCode is specified, this will be inserted into the formatted string to replace $CC.
1020
     * @param string $number
1021
     * @param PhoneMetadata $metadata
1022
     * @param int $numberFormat PhoneNumberFormat
1023
     * @param null|string $carrierCode
1024
     * @return string
1025
     */
1026 42
    private function formatNsn($number, PhoneMetadata $metadata, $numberFormat, $carrierCode = null)
1027
    {
1028 42
        $intlNumberFormats = $metadata->intlNumberFormats();
1029
        // When the intlNumberFormats exists, we use that to format national number for the
1030
        // INTERNATIONAL format instead of using the numberDesc.numberFormats.
1031 42
        $availableFormats = (count($intlNumberFormats) == 0 || $numberFormat == PhoneNumberFormat::NATIONAL)
1032 42
            ? $metadata->numberFormats()
1033 42
            : $metadata->intlNumberFormats();
1034 42
        $formattingPattern = $this->chooseFormattingPatternForNumber($availableFormats, $number);
1035 42
        return ($formattingPattern === null)
1036 42
            ? $number
1037 42
            : $this->formatNsnUsingPattern($number, $formattingPattern, $numberFormat, $carrierCode);
1038
    }
1039
1040
    /**
1041
     * @param NumberFormat[] $availableFormats
1042
     * @param string $nationalNumber
1043
     * @return NumberFormat|null
1044
     */
1045 43
    public function chooseFormattingPatternForNumber(array $availableFormats, $nationalNumber)
1046
    {
1047 43
        foreach ($availableFormats as $numFormat) {
1048 43
            $leadingDigitsPatternMatcher = null;
1049 43
            $size = $numFormat->leadingDigitsPatternSize();
1050
            // We always use the last leading_digits_pattern, as it is the most detailed.
1051 43
            if ($size > 0) {
1052 38
                $leadingDigitsPatternMatcher = new Matcher(
1053 38
                    $numFormat->getLeadingDigitsPattern($size - 1),
1054
                    $nationalNumber
1055 38
                );
1056 38
            }
1057 43
            if ($size == 0 || $leadingDigitsPatternMatcher->lookingAt()) {
1058 42
                $m = new Matcher($numFormat->getPattern(), $nationalNumber);
1059 42
                if ($m->matches() > 0) {
1060 42
                    return $numFormat;
1061
                }
1062 12
            }
1063 32
        }
1064 9
        return null;
1065
    }
1066
1067
    /**
1068
     * Note that carrierCode is optional - if null or an empty string, no carrier code replacement
1069
     * will take place.
1070
     * @param string $nationalNumber
1071
     * @param NumberFormat $formattingPattern
1072
     * @param int $numberFormat PhoneNumberFormat
1073
     * @param null|string $carrierCode
1074
     * @return string
1075
     */
1076 42
    private function formatNsnUsingPattern(
1077
        $nationalNumber,
1078
        NumberFormat $formattingPattern,
1079
        $numberFormat,
1080
        $carrierCode = null
1081
    ) {
1082 42
        $numberFormatRule = $formattingPattern->getFormat();
1083 42
        $m = new Matcher($formattingPattern->getPattern(), $nationalNumber);
1084 42
        if ($numberFormat === PhoneNumberFormat::NATIONAL &&
1085 42
            $carrierCode !== null && mb_strlen($carrierCode) > 0 &&
1086 2
            mb_strlen($formattingPattern->getDomesticCarrierCodeFormattingRule()) > 0
1087 42
        ) {
1088
            // Replace the $CC in the formatting rule with the desired carrier code.
1089 2
            $carrierCodeFormattingRule = $formattingPattern->getDomesticCarrierCodeFormattingRule();
1090 2
            $ccPatternMatcher = new Matcher(self::CC_PATTERN, $carrierCodeFormattingRule);
1091 2
            $carrierCodeFormattingRule = $ccPatternMatcher->replaceFirst($carrierCode);
1092
            // Now replace the $FG in the formatting rule with the first group and the carrier code
1093
            // combined in the appropriate way.
1094 2
            $firstGroupMatcher = new Matcher(self::FIRST_GROUP_PATTERN, $numberFormatRule);
1095 2
            $numberFormatRule = $firstGroupMatcher->replaceFirst($carrierCodeFormattingRule);
1096 2
            $formattedNationalNumber = $m->replaceAll($numberFormatRule);
1097 2
        } else {
1098
            // Use the national prefix formatting rule instead.
1099 42
            $nationalPrefixFormattingRule = $formattingPattern->getNationalPrefixFormattingRule();
1100 42
            if ($numberFormat == PhoneNumberFormat::NATIONAL &&
1101 42
                $nationalPrefixFormattingRule !== null &&
1102 37
                mb_strlen($nationalPrefixFormattingRule) > 0
1103 42
            ) {
1104 22
                $firstGroupMatcher = new Matcher(self::FIRST_GROUP_PATTERN, $numberFormatRule);
1105 22
                $formattedNationalNumber = $m->replaceAll(
1106 22
                    $firstGroupMatcher->replaceFirst($nationalPrefixFormattingRule)
1107 22
                );
1108 22
            } else {
1109 34
                $formattedNationalNumber = $m->replaceAll($numberFormatRule);
1110
            }
1111
1112
        }
1113 42
        if ($numberFormat == PhoneNumberFormat::RFC3966) {
1114
            // Strip any leading punctuation.
1115 4
            $matcher = new Matcher(self::$SEPARATOR_PATTERN, $formattedNationalNumber);
1116 4
            if ($matcher->lookingAt()) {
1117 1
                $formattedNationalNumber = $matcher->replaceFirst("");
1118 1
            }
1119
            // Replace the rest with a dash between each number group.
1120 4
            $formattedNationalNumber = $matcher->reset($formattedNationalNumber)->replaceAll("-");
1121 4
        }
1122 42
        return $formattedNationalNumber;
1123
    }
1124
1125
    /**
1126
     * Appends the formatted extension of a phone number to formattedNumber, if the phone number had
1127
     * an extension specified.
1128
     *
1129
     * @param PhoneNumber $number
1130
     * @param PhoneMetadata|null $metadata
1131
     * @param int $numberFormat PhoneNumberFormat
1132
     * @param string $formattedNumber
1133
     */
1134 291
    private function maybeAppendFormattedExtension(PhoneNumber $number, $metadata, $numberFormat, &$formattedNumber)
1135
    {
1136 291
        if ($number->hasExtension() && mb_strlen($number->getExtension()) > 0) {
1137 2
            if ($numberFormat === PhoneNumberFormat::RFC3966) {
1138 1
                $formattedNumber .= self::RFC3966_EXTN_PREFIX . $number->getExtension();
1139 1
            } else {
1140 2
                if (!empty($metadata) && $metadata->hasPreferredExtnPrefix()) {
1141 1
                    $formattedNumber .= $metadata->getPreferredExtnPrefix() . $number->getExtension();
1142 1
                } else {
1143 2
                    $formattedNumber .= self::DEFAULT_EXTN_PREFIX . $number->getExtension();
1144
                }
1145
            }
1146 2
        }
1147 291
    }
1148
1149
    /**
1150
     * Returns the mobile token for the provided country calling code if it has one, otherwise
1151
     * returns an empty string. A mobile token is a number inserted before the area code when dialing
1152
     * a mobile number from that country from abroad.
1153
     *
1154
     * @param int $countryCallingCode the country calling code for which we want the mobile token
1155
     * @return string the mobile token, as a string, for the given country calling code
1156
     */
1157 15
    public static function getCountryMobileToken($countryCallingCode)
1158
    {
1159 15
        if (array_key_exists($countryCallingCode, self::$MOBILE_TOKEN_MAPPINGS)) {
1160 3
            return self::$MOBILE_TOKEN_MAPPINGS[$countryCallingCode];
1161
        }
1162 14
        return "";
1163
    }
1164
1165
    /**
1166
     * Checks if the number is a valid vanity (alpha) number such as 800 MICROSOFT. A valid vanity
1167
     * number will start with at least 3 digits and will have three or more alpha characters. This
1168
     * does not do region-specific checks - to work out if this number is actually valid for a region,
1169
     * it should be parsed and methods such as {@link #isPossibleNumberWithReason} and
1170
     * {@link #isValidNumber} should be used.
1171
     *
1172
     * @param string $number the number that needs to be checked
1173
     * @return bool true if the number is a valid vanity number
1174
     */
1175 1
    public function isAlphaNumber($number)
1176
    {
1177 1
        if (!$this->isViablePhoneNumber($number)) {
1178
            // Number is too short, or doesn't match the basic phone number pattern.
1179 1
            return false;
1180
        }
1181 1
        $this->maybeStripExtension($number);
1182 1
        return (bool)preg_match('/' . self::VALID_ALPHA_PHONE_PATTERN . '/' . self::REGEX_FLAGS, $number);
1183
    }
1184
1185
    /**
1186
     * Checks to see if the string of characters could possibly be a phone number at all. At the
1187
     * moment, checks to see that the string begins with at least 2 digits, ignoring any punctuation
1188
     * commonly found in phone numbers.
1189
     * This method does not require the number to be normalized in advance - but does assume that
1190
     * leading non-number symbols have been removed, such as by the method extractPossibleNumber.
1191
     *
1192
     * @param string $number to be checked for viability as a phone number
1193
     * @return boolean true if the number could be a phone number of some sort, otherwise false
1194
     */
1195 2395
    public static function isViablePhoneNumber($number)
1196
    {
1197 2395
        if (mb_strlen($number) < self::MIN_LENGTH_FOR_NSN) {
1198 4
            return false;
1199
        }
1200
1201 2395
        $validPhoneNumberPattern = self::getValidPhoneNumberPattern();
1202
1203 2395
        $m = preg_match($validPhoneNumberPattern, $number);
1204 2395
        return $m > 0;
1205
    }
1206
1207
    /**
1208
     * We append optionally the extension pattern to the end here, as a valid phone number may
1209
     * have an extension prefix appended, followed by 1 or more digits.
1210
     * @return string
1211
     */
1212 2395
    private static function getValidPhoneNumberPattern()
1213
    {
1214 2395
        return self::$VALID_PHONE_NUMBER_PATTERN;
1215
    }
1216
1217
    /**
1218
     * Strips any extension (as in, the part of the number dialled after the call is connected,
1219
     * usually indicated with extn, ext, x or similar) from the end of the number, and returns it.
1220
     *
1221
     * @param string $number the non-normalized telephone number that we wish to strip the extension from
1222
     * @return string the phone extension
1223
     */
1224 2392
    private function maybeStripExtension(&$number)
1225
    {
1226 2392
        $matches = array();
1227 2392
        $find = preg_match(self::$EXTN_PATTERN, $number, $matches, PREG_OFFSET_CAPTURE);
1228
        // If we find a potential extension, and the number preceding this is a viable number, we assume
1229
        // it is an extension.
1230 2392
        if ($find > 0 && $this->isViablePhoneNumber(substr($number, 0, $matches[0][1]))) {
1231
            // The numbers are captured into groups in the regular expression.
1232
1233 5
            for ($i = 1, $length = count($matches); $i <= $length; $i++) {
1234 5
                if ($matches[$i][0] != "") {
1235
                    // We go through the capturing groups until we find one that captured some digits. If none
1236
                    // did, then we will return the empty string.
1237 5
                    $extension = $matches[$i][0];
1238 5
                    $number = substr($number, 0, $matches[0][1]);
1239 5
                    return $extension;
1240
                }
1241 5
            }
1242
        }
1243 2392
        return "";
1244
    }
1245
1246
    /**
1247
     * Parses a string and returns it in proto buffer format. This method differs from {@link #parse}
1248
     * in that it always populates the raw_input field of the protocol buffer with numberToParse as
1249
     * well as the country_code_source field.
1250
     *
1251
     * @param string $numberToParse number that we are attempting to parse. This can contain formatting
1252
     *                                  such as +, ( and -, as well as a phone number extension. It can also
1253
     *                                  be provided in RFC3966 format.
1254
     * @param string $defaultRegion region that we are expecting the number to be from. This is only used
1255
     *                                  if the number being parsed is not written in international format.
1256
     *                                  The country calling code for the number in this case would be stored
1257
     *                                  as that of the default region supplied.
1258
     * @param PhoneNumber $phoneNumber
1259
     * @return PhoneNumber              a phone number proto buffer filled with the parsed number
1260
     */
1261 3
    public function parseAndKeepRawInput($numberToParse, $defaultRegion, PhoneNumber $phoneNumber = null)
1262
    {
1263 3
        if ($phoneNumber === null) {
1264 3
            $phoneNumber = new PhoneNumber();
1265 3
        }
1266 3
        $this->parseHelper($numberToParse, $defaultRegion, true, true, $phoneNumber);
1267 3
        return $phoneNumber;
1268
    }
1269
1270
    /**
1271
     * A helper function to set the values related to leading zeros in a PhoneNumber.
1272
     * @param string $nationalNumber
1273
     * @param PhoneNumber $phoneNumber
1274
     */
1275 2389
    public static function setItalianLeadingZerosForPhoneNumber($nationalNumber, PhoneNumber $phoneNumber)
1276
    {
1277 2389
        if (strlen($nationalNumber) > 1 && substr($nationalNumber, 0, 1) == '0') {
1278 43
            $phoneNumber->setItalianLeadingZero(true);
1279 43
            $numberOfLeadingZeros = 1;
1280
            // Note that if the national number is all "0"s, the last "0" is not counted as a leading
1281
            // zero.
1282 43
            while ($numberOfLeadingZeros < (strlen($nationalNumber) - 1) &&
1283 43
                substr($nationalNumber, $numberOfLeadingZeros, 1) == '0') {
1284 4
                $numberOfLeadingZeros++;
1285 4
            }
1286
1287 43
            if ($numberOfLeadingZeros != 1) {
1288 4
                $phoneNumber->setNumberOfLeadingZeros($numberOfLeadingZeros);
1289 4
            }
1290 43
        }
1291 2389
    }
1292
1293
    /**
1294
     * Parses a string and fills up the phoneNumber. This method is the same as the public
1295
     * parse() method, with the exception that it allows the default region to be null, for use by
1296
     * isNumberMatch(). checkRegion should be set to false if it is permitted for the default region
1297
     * to be null or unknown ("ZZ").
1298
     * @param string $numberToParse
1299
     * @param string $defaultRegion
1300
     * @param bool $keepRawInput
1301
     * @param bool $checkRegion
1302
     * @param PhoneNumber $phoneNumber
1303
     * @throws NumberParseException
1304
     */
1305 2394
    private function parseHelper($numberToParse, $defaultRegion, $keepRawInput, $checkRegion, PhoneNumber $phoneNumber)
1306
    {
1307 2394
        if ($numberToParse === null) {
1308 2
            throw new NumberParseException(NumberParseException::NOT_A_NUMBER, "The phone number supplied was null.");
1309
        }
1310
1311 2393
        $numberToParse = trim($numberToParse);
1312
1313 2393
        if (mb_strlen($numberToParse) > self::MAX_INPUT_STRING_LENGTH) {
1314 1
            throw new NumberParseException(
1315 1
                NumberParseException::TOO_LONG,
1316
                "The string supplied was too long to parse."
1317 1
            );
1318
        }
1319
1320 2392
        $nationalNumber = '';
1321 2392
        $this->buildNationalNumberForParsing($numberToParse, $nationalNumber);
1322
1323 2392
        if (!$this->isViablePhoneNumber($nationalNumber)) {
1324 4
            throw new NumberParseException(
1325 4
                NumberParseException::NOT_A_NUMBER,
1326
                "The string supplied did not seem to be a phone number."
1327 4
            );
1328
        }
1329
1330
        // Check the region supplied is valid, or that the extracted number starts with some sort of +
1331
        // sign so the number's region can be determined.
1332 2391
        if ($checkRegion && !$this->checkRegionForParsing($nationalNumber, $defaultRegion)) {
1333 5
            throw new NumberParseException(
1334 5
                NumberParseException::INVALID_COUNTRY_CODE,
1335
                "Missing or invalid default region."
1336 5
            );
1337
        }
1338
1339 2391
        if ($keepRawInput) {
1340 3
            $phoneNumber->setRawInput($numberToParse);
1341 3
        }
1342
        // Attempt to parse extension first, since it doesn't require region-specific data and we want
1343
        // to have the non-normalised number here.
1344 2391
        $extension = $this->maybeStripExtension($nationalNumber);
1345 2391
        if (mb_strlen($extension) > 0) {
1346 4
            $phoneNumber->setExtension($extension);
1347 4
        }
1348
1349 2391
        $regionMetadata = $this->getMetadataForRegion($defaultRegion);
1350
        // Check to see if the number is given in international format so we know whether this number is
1351
        // from the default region or not.
1352 2391
        $normalizedNationalNumber = "";
1353
        try {
1354
            // TODO: This method should really just take in the string buffer that has already
1355
            // been created, and just remove the prefix, rather than taking in a string and then
1356
            // outputting a string buffer.
1357 2391
            $countryCode = $this->maybeExtractCountryCode(
1358 2391
                $nationalNumber,
1359 2391
                $regionMetadata,
1360 2391
                $normalizedNationalNumber,
1361 2391
                $keepRawInput,
1362
                $phoneNumber
1363 2391
            );
1364 2391
        } catch (NumberParseException $e) {
1365 2
            $matcher = new Matcher(self::$PLUS_CHARS_PATTERN, $nationalNumber);
1366 2
            if ($e->getErrorType() == NumberParseException::INVALID_COUNTRY_CODE && $matcher->lookingAt()) {
1367
                // Strip the plus-char, and try again.
1368 2
                $countryCode = $this->maybeExtractCountryCode(
1369 2
                    substr($nationalNumber, $matcher->end()),
1370 2
                    $regionMetadata,
1371 2
                    $normalizedNationalNumber,
1372 2
                    $keepRawInput,
1373
                    $phoneNumber
1374 2
                );
1375 2
                if ($countryCode == 0) {
1376 1
                    throw new NumberParseException(
1377 1
                        NumberParseException::INVALID_COUNTRY_CODE,
1378
                        "Could not interpret numbers after plus-sign."
1379 1
                    );
1380
                }
1381 1
            } else {
1382 1
                throw new NumberParseException($e->getErrorType(), $e->getMessage(), $e);
1383
            }
1384
        }
1385 2391
        if ($countryCode !== 0) {
1386 284
            $phoneNumberRegion = $this->getRegionCodeForCountryCode($countryCode);
1387 284
            if ($phoneNumberRegion != $defaultRegion) {
1388
                // Metadata cannot be null because the country calling code is valid.
1389 271
                $regionMetadata = $this->getMetadataForRegionOrCallingCode($countryCode, $phoneNumberRegion);
1390 271
            }
1391 284
        } else {
1392
            // If no extracted country calling code, use the region supplied instead. The national number
1393
            // is just the normalized version of the number we were given to parse.
1394
1395 2365
            $normalizedNationalNumber .= $this->normalize($nationalNumber);
1396 2365
            if ($defaultRegion !== null) {
1397 2365
                $countryCode = $regionMetadata->getCountryCode();
1398 2365
                $phoneNumber->setCountryCode($countryCode);
1399 2365
            } else if ($keepRawInput) {
1400
                $phoneNumber->clearCountryCodeSource();
1401
            }
1402
        }
1403 2391
        if (mb_strlen($normalizedNationalNumber) < self::MIN_LENGTH_FOR_NSN) {
1404 2
            throw new NumberParseException(
1405 2
                NumberParseException::TOO_SHORT_NSN,
1406
                "The string supplied is too short to be a phone number."
1407 2
            );
1408
        }
1409 2390
        if ($regionMetadata !== null) {
1410 2390
            $carrierCode = "";
1411 2390
            $potentialNationalNumber = $normalizedNationalNumber;
1412 2390
            $this->maybeStripNationalPrefixAndCarrierCode($potentialNationalNumber, $regionMetadata, $carrierCode);
1413
            // We require that the NSN remaining after stripping the national prefix and carrier code be
1414
            // of a possible length for the region. Otherwise, we don't do the stripping, since the
1415
            // original number could be a valid short number.
1416 2390
            if (!$this->isShorterThanPossibleNormalNumber($regionMetadata, $potentialNationalNumber)) {
1417 1918
                $normalizedNationalNumber = $potentialNationalNumber;
1418 1918
                if ($keepRawInput) {
1419 3
                    $phoneNumber->setPreferredDomesticCarrierCode($carrierCode);
1420 3
                }
1421 1918
            }
1422 2390
        }
1423 2390
        $lengthOfNationalNumber = mb_strlen($normalizedNationalNumber);
1424 2390
        if ($lengthOfNationalNumber < self::MIN_LENGTH_FOR_NSN) {
1425
            throw new NumberParseException(
1426
                NumberParseException::TOO_SHORT_NSN,
1427
                "The string supplied is too short to be a phone number."
1428
            );
1429
        }
1430 2390
        if ($lengthOfNationalNumber > self::MAX_LENGTH_FOR_NSN) {
1431 1
            throw new NumberParseException(
1432 1
                NumberParseException::TOO_LONG,
1433
                "The string supplied is too long to be a phone number."
1434 1
            );
1435
        }
1436 2389
        $this->setItalianLeadingZerosForPhoneNumber($normalizedNationalNumber, $phoneNumber);
1437
1438
1439
        /*
1440
         * We have to store the National Number as a string instead of a "long" as Google do
1441
         *
1442
         * Since PHP doesn't always support 64 bit INTs, this was a float, but that had issues
1443
         * with long numbers.
1444
         *
1445
         * We have to remove the leading zeroes ourself though
1446
         */
1447 2389
        if ((int)$normalizedNationalNumber == 0) {
1448 3
            $normalizedNationalNumber = "0";
1449 3
        } else {
1450 2387
            $normalizedNationalNumber = ltrim($normalizedNationalNumber, '0');
1451
        }
1452
1453 2389
        $phoneNumber->setNationalNumber($normalizedNationalNumber);
1454 2389
    }
1455
1456
    /**
1457
     * Converts numberToParse to a form that we can parse and write it to nationalNumber if it is
1458
     * written in RFC3966; otherwise extract a possible number out of it and write to nationalNumber.
1459
     * @param string $numberToParse
1460
     * @param string $nationalNumber
1461
     */
1462 2392
    private function buildNationalNumberForParsing($numberToParse, &$nationalNumber)
1463
    {
1464 2392
        $indexOfPhoneContext = strpos($numberToParse, self::RFC3966_PHONE_CONTEXT);
1465 2392
        if ($indexOfPhoneContext > 0) {
1466 6
            $phoneContextStart = $indexOfPhoneContext + mb_strlen(self::RFC3966_PHONE_CONTEXT);
1467
            // If the phone context contains a phone number prefix, we need to capture it, whereas domains
1468
            // will be ignored.
1469 6
            if (substr($numberToParse, $phoneContextStart, 1) == self::PLUS_SIGN) {
1470
                // Additional parameters might follow the phone context. If so, we will remove them here
1471
                // because the parameters after phone context are not important for parsing the
1472
                // phone number.
1473 3
                $phoneContextEnd = strpos($numberToParse, ';', $phoneContextStart);
1474 3
                if ($phoneContextEnd > 0) {
1475 1
                    $nationalNumber .= substr($numberToParse, $phoneContextStart, $phoneContextEnd - $phoneContextStart);
1476 1
                } else {
1477 3
                    $nationalNumber .= substr($numberToParse, $phoneContextStart);
1478
                }
1479 3
            }
1480
1481
            // Now append everything between the "tel:" prefix and the phone-context. This should include
1482
            // the national number, an optional extension or isdn-subaddress component. Note we also
1483
            // handle the case when "tel:" is missing, as we have seen in some of the phone number inputs.
1484
            // In that case, we append everything from the beginning.
1485
1486 6
            $indexOfRfc3966Prefix = strpos($numberToParse, self::RFC3966_PREFIX);
1487 6
            $indexOfNationalNumber = ($indexOfRfc3966Prefix !== false) ? $indexOfRfc3966Prefix + strlen(self::RFC3966_PREFIX) : 0;
1488 6
            $nationalNumber .= substr($numberToParse, $indexOfNationalNumber, ($indexOfPhoneContext - $indexOfNationalNumber));
1489 6
        } else {
1490
            // Extract a possible number from the string passed in (this strips leading characters that
1491
            // could not be the start of a phone number.)
1492 2392
            $nationalNumber .= $this->extractPossibleNumber($numberToParse);
1493
        }
1494
1495
        // Delete the isdn-subaddress and everything after it if it is present. Note extension won't
1496
        // appear at the same time with isdn-subaddress according to paragraph 5.3 of the RFC3966 spec,
1497 2392
        $indexOfIsdn = strpos($nationalNumber, self::RFC3966_ISDN_SUBADDRESS);
1498 2392
        if ($indexOfIsdn > 0) {
1499 5
            $nationalNumber = substr($nationalNumber, 0, $indexOfIsdn);
1500 5
        }
1501
        // If both phone context and isdn-subaddress are absent but other parameters are present, the
1502
        // parameters are left in nationalNumber. This is because we are concerned about deleting
1503
        // content from a potential number string when there is no strong evidence that the number is
1504
        // actually written in RFC3966.
1505 2392
    }
1506
1507
    /**
1508
     * Attempts to extract a possible number from the string passed in. This currently strips all
1509
     * leading characters that cannot be used to start a phone number. Characters that can be used to
1510
     * start a phone number are defined in the VALID_START_CHAR_PATTERN. If none of these characters
1511
     * are found in the number passed in, an empty string is returned. This function also attempts to
1512
     * strip off any alternative extensions or endings if two or more are present, such as in the case
1513
     * of: (530) 583-6985 x302/x2303. The second extension here makes this actually two phone numbers,
1514
     * (530) 583-6985 x302 and (530) 583-6985 x2303. We remove the second extension so that the first
1515
     * number is parsed correctly.
1516
     *
1517
     * @param int $number the string that might contain a phone number
1518
     * @return string the number, stripped of any non-phone-number prefix (such as "Tel:") or an empty
1519
     *                string if no character used to start phone numbers (such as + or any digit) is
1520
     *                found in the number
1521
     */
1522 2430
    public static function extractPossibleNumber($number)
1523
    {
1524 2430
        $matches = array();
1525 2430
        $match = preg_match('/' . self::$VALID_START_CHAR_PATTERN . '/ui', $number, $matches, PREG_OFFSET_CAPTURE);
1526 2430
        if ($match > 0) {
1527 2430
            $number = substr($number, $matches[0][1]);
1528
            // Remove trailing non-alpha non-numerical characters.
1529 2430
            $trailingCharsMatcher = new Matcher(self::$UNWANTED_END_CHAR_PATTERN, $number);
1530 2430
            if ($trailingCharsMatcher->find() && $trailingCharsMatcher->start() > 0) {
1531 2
                $number = substr($number, 0, $trailingCharsMatcher->start());
1532 2
            }
1533
1534
            // Check for extra numbers at the end.
1535 2430
            $match = preg_match('%' . self::$SECOND_NUMBER_START_PATTERN . '%', $number, $matches, PREG_OFFSET_CAPTURE);
1536 2430
            if ($match > 0) {
1537 1
                $number = substr($number, 0, $matches[0][1]);
1538 1
            }
1539
1540 2430
            return $number;
1541
        } else {
1542 4
            return "";
1543
        }
1544
    }
1545
1546
    /**
1547
     * Checks to see that the region code used is valid, or if it is not valid, that the number to
1548
     * parse starts with a + symbol so that we can attempt to infer the region from the number.
1549
     * Returns false if it cannot use the region provided and the region cannot be inferred.
1550
     * @param string $numberToParse
1551
     * @param string $defaultRegion
1552
     * @return bool
1553
     */
1554 2391
    private function checkRegionForParsing($numberToParse, $defaultRegion)
1555
    {
1556 2391
        if (!$this->isValidRegionCode($defaultRegion)) {
1557
            // If the number is null or empty, we can't infer the region.
1558 265
            $plusCharsPatternMatcher = new Matcher(self::$PLUS_CHARS_PATTERN, $numberToParse);
1559 265
            if ($numberToParse === null || mb_strlen($numberToParse) == 0 || !$plusCharsPatternMatcher->lookingAt()) {
1560 5
                return false;
1561
            }
1562 263
        }
1563 2391
        return true;
1564
    }
1565
1566
    /**
1567
     * Tries to extract a country calling code from a number. This method will return zero if no
1568
     * country calling code is considered to be present. Country calling codes are extracted in the
1569
     * following ways:
1570
     * <ul>
1571
     *  <li> by stripping the international dialing prefix of the region the person is dialing from,
1572
     *       if this is present in the number, and looking at the next digits
1573
     *  <li> by stripping the '+' sign if present and then looking at the next digits
1574
     *  <li> by comparing the start of the number and the country calling code of the default region.
1575
     *       If the number is not considered possible for the numbering plan of the default region
1576
     *       initially, but starts with the country calling code of this region, validation will be
1577
     *       reattempted after stripping this country calling code. If this number is considered a
1578
     *       possible number, then the first digits will be considered the country calling code and
1579
     *       removed as such.
1580
     * </ul>
1581
     * It will throw a NumberParseException if the number starts with a '+' but the country calling
1582
     * code supplied after this does not match that of any known region.
1583
     *
1584
     * @param string $number non-normalized telephone number that we wish to extract a country calling
1585
     *     code from - may begin with '+'
1586
     * @param PhoneMetadata $defaultRegionMetadata metadata about the region this number may be from
1587
     * @param string $nationalNumber a string buffer to store the national significant number in, in the case
1588
     *     that a country calling code was extracted. The number is appended to any existing contents.
1589
     *     If no country calling code was extracted, this will be left unchanged.
1590
     * @param bool $keepRawInput true if the country_code_source and preferred_carrier_code fields of
1591
     *     phoneNumber should be populated.
1592
     * @param PhoneNumber $phoneNumber the PhoneNumber object where the country_code and country_code_source need
1593
     *     to be populated. Note the country_code is always populated, whereas country_code_source is
1594
     *     only populated when keepCountryCodeSource is true.
1595
     * @return int the country calling code extracted or 0 if none could be extracted
1596
     * @throws NumberParseException
1597
     */
1598 2392
    public function maybeExtractCountryCode(
1599
        $number,
1600
        PhoneMetadata $defaultRegionMetadata = null,
1601
        &$nationalNumber,
1602
        $keepRawInput,
1603
        PhoneNumber $phoneNumber
1604
    ) {
1605 2392
        if (mb_strlen($number) == 0) {
1606
            return 0;
1607
        }
1608 2392
        $fullNumber = $number;
1609
        // Set the default prefix to be something that will never match.
1610 2392
        $possibleCountryIddPrefix = "NonMatch";
1611 2392
        if ($defaultRegionMetadata !== null) {
1612 2379
            $possibleCountryIddPrefix = $defaultRegionMetadata->getInternationalPrefix();
1613 2379
        }
1614 2392
        $countryCodeSource = $this->maybeStripInternationalPrefixAndNormalize($fullNumber, $possibleCountryIddPrefix);
1615
1616 2392
        if ($keepRawInput) {
1617 4
            $phoneNumber->setCountryCodeSource($countryCodeSource);
1618 4
        }
1619 2392
        if ($countryCodeSource != CountryCodeSource::FROM_DEFAULT_COUNTRY) {
1620 281
            if (mb_strlen($fullNumber) <= self::MIN_LENGTH_FOR_NSN) {
1621 1
                throw new NumberParseException(
1622 1
                    NumberParseException::TOO_SHORT_AFTER_IDD,
1623
                    "Phone number had an IDD, but after this was not long enough to be a viable phone number."
1624 1
                );
1625
            }
1626 281
            $potentialCountryCode = $this->extractCountryCode($fullNumber, $nationalNumber);
1627
1628 281
            if ($potentialCountryCode != 0) {
1629 281
                $phoneNumber->setCountryCode($potentialCountryCode);
1630 281
                return $potentialCountryCode;
1631
            }
1632
1633
            // If this fails, they must be using a strange country calling code that we don't recognize,
1634
            // or that doesn't exist.
1635 3
            throw new NumberParseException(
1636 3
                NumberParseException::INVALID_COUNTRY_CODE,
1637
                "Country calling code supplied was not recognised."
1638 3
            );
1639 2372
        } else if ($defaultRegionMetadata !== null) {
1640
            // Check to see if the number starts with the country calling code for the default region. If
1641
            // so, we remove the country calling code, and do some checks on the validity of the number
1642
            // before and after.
1643 2372
            $defaultCountryCode = $defaultRegionMetadata->getCountryCode();
1644 2372
            $defaultCountryCodeString = (string)$defaultCountryCode;
1645 2372
            $normalizedNumber = (string)$fullNumber;
1646 2372
            if (strpos($normalizedNumber, $defaultCountryCodeString) === 0) {
1647 50
                $potentialNationalNumber = substr($normalizedNumber, mb_strlen($defaultCountryCodeString));
1648 50
                $generalDesc = $defaultRegionMetadata->getGeneralDesc();
1649 50
                $validNumberPattern = $generalDesc->getNationalNumberPattern();
1650
                // Don't need the carrier code.
1651 50
                $carriercode = null;
1652 50
                $this->maybeStripNationalPrefixAndCarrierCode(
1653 50
                    $potentialNationalNumber,
1654 50
                    $defaultRegionMetadata,
1655
                    $carriercode
1656 50
                );
1657 50
                $possibleNumberPattern = $generalDesc->getPossibleNumberPattern();
1658
                // If the number was not valid before but is valid now, or if it was too long before, we
1659
                // consider the number with the country calling code stripped to be a better result and
1660
                // keep that instead.
1661 50
                if ((preg_match('/^(' . $validNumberPattern . ')$/x', $fullNumber) == 0 &&
1662 14
                        preg_match('/^(' . $validNumberPattern . ')$/x', $potentialNationalNumber) > 0) ||
1663 41
                    $this->testNumberLengthAgainstPattern($possibleNumberPattern, (string)$fullNumber)
1664
                    == ValidationResult::TOO_LONG
1665 50
                ) {
1666 12
                    $nationalNumber .= $potentialNationalNumber;
1667 12
                    if ($keepRawInput) {
1668 3
                        $phoneNumber->setCountryCodeSource(CountryCodeSource::FROM_NUMBER_WITHOUT_PLUS_SIGN);
1669 3
                    }
1670 12
                    $phoneNumber->setCountryCode($defaultCountryCode);
1671 12
                    return $defaultCountryCode;
1672
                }
1673 40
            }
1674 2366
        }
1675
        // No country calling code present.
1676 2366
        $phoneNumber->setCountryCode(0);
1677 2366
        return 0;
1678
    }
1679
1680
    /**
1681
     * Strips any international prefix (such as +, 00, 011) present in the number provided, normalizes
1682
     * the resulting number, and indicates if an international prefix was present.
1683
     *
1684
     * @param string $number the non-normalized telephone number that we wish to strip any international
1685
     *     dialing prefix from.
1686
     * @param string $possibleIddPrefix string the international direct dialing prefix from the region we
1687
     *     think this number may be dialed in
1688
     * @return int the corresponding CountryCodeSource if an international dialing prefix could be
1689
     *     removed from the number, otherwise CountryCodeSource.FROM_DEFAULT_COUNTRY if the number did
1690
     *     not seem to be in international format.
1691
     */
1692 2393
    public function maybeStripInternationalPrefixAndNormalize(&$number, $possibleIddPrefix)
1693
    {
1694 2393
        if (mb_strlen($number) == 0) {
1695
            return CountryCodeSource::FROM_DEFAULT_COUNTRY;
1696
        }
1697 2393
        $matches = array();
1698
        // Check to see if the number begins with one or more plus signs.
1699 2393
        $match = preg_match('/^' . self::$PLUS_CHARS_PATTERN . '/' . self::REGEX_FLAGS, $number, $matches, PREG_OFFSET_CAPTURE);
1700 2393
        if ($match > 0) {
1701 280
            $number = mb_substr($number, $matches[0][1] + mb_strlen($matches[0][0]));
1702
            // Can now normalize the rest of the number since we've consumed the "+" sign at the start.
1703 280
            $number = $this->normalize($number);
1704 280
            return CountryCodeSource::FROM_NUMBER_WITH_PLUS_SIGN;
1705
        }
1706
        // Attempt to parse the first digits as an international prefix.
1707 2374
        $iddPattern = $possibleIddPrefix;
1708 2374
        $number = $this->normalize($number);
1709 2374
        return $this->parsePrefixAsIdd($iddPattern, $number)
1710 2374
            ? CountryCodeSource::FROM_NUMBER_WITH_IDD
1711 2374
            : CountryCodeSource::FROM_DEFAULT_COUNTRY;
1712
    }
1713
1714
    /**
1715
     * Normalizes a string of characters representing a phone number. This performs
1716
     * the following conversions:
1717
     *   Punctuation is stripped.
1718
     *   For ALPHA/VANITY numbers:
1719
     *   Letters are converted to their numeric representation on a telephone
1720
     *       keypad. The keypad used here is the one defined in ITU Recommendation
1721
     *       E.161. This is only done if there are 3 or more letters in the number,
1722
     *       to lessen the risk that such letters are typos.
1723
     *   For other numbers:
1724
     *   Wide-ascii digits are converted to normal ASCII (European) digits.
1725
     *   Arabic-Indic numerals are converted to European numerals.
1726
     *   Spurious alpha characters are stripped.
1727
     *
1728
     * @param string $number a string of characters representing a phone number.
1729
     * @return string the normalized string version of the phone number.
1730
     */
1731 2396
    public static function normalize(&$number)
1732
    {
1733 2396
        $m = new Matcher(self::VALID_ALPHA_PHONE_PATTERN, $number);
1734 2396
        if ($m->matches()) {
1735 6
            return self::normalizeHelper($number, self::$ALPHA_PHONE_MAPPINGS, true);
1736
        } else {
1737 2395
            return self::normalizeDigitsOnly($number);
1738
        }
1739
    }
1740
1741
    /**
1742
     * Normalizes a string of characters representing a phone number. This converts wide-ascii and
1743
     * arabic-indic numerals to European numerals, and strips punctuation and alpha characters.
1744
     *
1745
     * @param $number string  a string of characters representing a phone number
1746
     * @return string the normalized string version of the phone number
1747
     */
1748 2429
    public static function normalizeDigitsOnly($number)
1749
    {
1750 2429
        return self::normalizeDigits($number, false /* strip non-digits */);
1751
    }
1752
1753
    /**
1754
     * @param string $number
1755
     * @param bool $keepNonDigits
1756
     * @return string
1757
     */
1758 2429
    public static function normalizeDigits($number, $keepNonDigits)
1759
    {
1760 2429
        $normalizedDigits = "";
1761 2429
        $numberAsArray = preg_split('/(?<!^)(?!$)/u', $number);
1762 2429
        foreach ($numberAsArray as $character) {
1763 2429
            if (is_numeric($character)) {
1764 2429
                $normalizedDigits .= $character;
1765 2429
            } elseif ($keepNonDigits) {
1766
                $normalizedDigits .= $character;
1767
            }
1768
            // If neither of the above are true, we remove this character.
1769
1770
            // Check if we are in the unicode number range
1771 2429
            if (array_key_exists($character, self::$numericCharacters)) {
1772 2
                $normalizedDigits .= self::$numericCharacters[$character];
1773 2
            }
1774 2429
        }
1775 2429
        return $normalizedDigits;
1776
    }
1777
1778
    /**
1779
     * Strips the IDD from the start of the number if present. Helper function used by
1780
     * maybeStripInternationalPrefixAndNormalize.
1781
     * @param string $iddPattern
1782
     * @param string $number
1783
     * @return bool
1784
     */
1785 2374
    private function parsePrefixAsIdd($iddPattern, &$number)
1786
    {
1787 2374
        $m = new Matcher($iddPattern, $number);
1788 2374
        if ($m->lookingAt()) {
1789 12
            $matchEnd = $m->end();
1790
            // Only strip this if the first digit after the match is not a 0, since country calling codes
1791
            // cannot begin with 0.
1792 12
            $digitMatcher = new Matcher(self::$CAPTURING_DIGIT_PATTERN, substr($number, $matchEnd));
1793 12
            if ($digitMatcher->find()) {
1794 12
                $normalizedGroup = $this->normalizeDigitsOnly($digitMatcher->group(1));
1795 12
                if ($normalizedGroup == "0") {
1796 4
                    return false;
1797
                }
1798 10
            }
1799 10
            $number = substr($number, $matchEnd);
1800 10
            return true;
1801
        }
1802 2371
        return false;
1803
    }
1804
1805
    /**
1806
     * Extracts country calling code from fullNumber, returns it and places the remaining number in  nationalNumber.
1807
     * It assumes that the leading plus sign or IDD has already been removed.
1808
     * Returns 0 if fullNumber doesn't start with a valid country calling code, and leaves nationalNumber unmodified.
1809
     * @param string $fullNumber
1810
     * @param string $nationalNumber
1811
     * @return int
1812
     */
1813 281
    private function extractCountryCode(&$fullNumber, &$nationalNumber)
1814
    {
1815 281
        if ((mb_strlen($fullNumber) == 0) || ($fullNumber[0] == '0')) {
1816
            // Country codes do not begin with a '0'.
1817 2
            return 0;
1818
        }
1819 281
        $numberLength = mb_strlen($fullNumber);
1820 281
        for ($i = 1; $i <= self::MAX_LENGTH_COUNTRY_CODE && $i <= $numberLength; $i++) {
1821 281
            $potentialCountryCode = (int)substr($fullNumber, 0, $i);
1822 281
            if (isset($this->countryCallingCodeToRegionCodeMap[$potentialCountryCode])) {
1823 281
                $nationalNumber .= substr($fullNumber, $i);
1824 281
                return $potentialCountryCode;
1825
            }
1826 252
        }
1827 2
        return 0;
1828
    }
1829
1830
    /**
1831
     * Strips any national prefix (such as 0, 1) present in the number provided.
1832
     *
1833
     * @param string $number the normalized telephone number that we wish to strip any national
1834
     *     dialing prefix from
1835
     * @param PhoneMetadata $metadata the metadata for the region that we think this number is from
1836
     * @param string $carrierCode a place to insert the carrier code if one is extracted
1837
     * @return bool true if a national prefix or carrier code (or both) could be extracted.
1838
     */
1839 2392
    public function maybeStripNationalPrefixAndCarrierCode(&$number, PhoneMetadata $metadata, &$carrierCode)
1840
    {
1841 2392
        $numberLength = mb_strlen($number);
1842 2392
        $possibleNationalPrefix = $metadata->getNationalPrefixForParsing();
1843 2392
        if ($numberLength == 0 || $possibleNationalPrefix === null || mb_strlen($possibleNationalPrefix) == 0) {
1844
            // Early return for numbers of zero length.
1845 848
            return false;
1846
        }
1847
1848
        // Attempt to parse the first digits as a national prefix.
1849 1553
        $prefixMatcher = new Matcher($possibleNationalPrefix, $number);
1850 1553
        if ($prefixMatcher->lookingAt()) {
1851 65
            $nationalNumberRule = $metadata->getGeneralDesc()->getNationalNumberPattern();
1852
            // Check if the original number is viable.
1853 65
            $nationalNumberRuleMatcher = new Matcher($nationalNumberRule, $number);
1854 65
            $isViableOriginalNumber = $nationalNumberRuleMatcher->matches();
1855
            // $prefixMatcher->group($numOfGroups) === null implies nothing was captured by the capturing
1856
            // groups in $possibleNationalPrefix; therefore, no transformation is necessary, and we just
1857
            // remove the national prefix
1858 65
            $numOfGroups = $prefixMatcher->groupCount();
1859 65
            $transformRule = $metadata->getNationalPrefixTransformRule();
1860
            if ($transformRule === null
1861 65
                || mb_strlen($transformRule) == 0
1862 22
                || $prefixMatcher->group($numOfGroups - 1) === null
1863 65
            ) {
1864
                // If the original number was viable, and the resultant number is not, we return.
1865 60
                $matcher = new Matcher($nationalNumberRule, substr($number, $prefixMatcher->end()));
1866 60
                if ($isViableOriginalNumber && !$matcher->matches()) {
1867 13
                    return false;
1868
                }
1869 50
                if ($carrierCode !== null && $numOfGroups > 0 && $prefixMatcher->group($numOfGroups) !== null) {
1870 2
                    $carrierCode .= $prefixMatcher->group(1);
1871 2
                }
1872
1873 50
                $number = substr($number, $prefixMatcher->end());
1874 50
                return true;
1875
            } else {
1876
                // Check that the resultant number is still viable. If not, return. Check this by copying
1877
                // the string and making the transformation on the copy first.
1878 9
                $transformedNumber = $number;
1879 9
                $transformedNumber = substr_replace(
1880 9
                    $transformedNumber,
1881 9
                    $prefixMatcher->replaceFirst($transformRule),
1882 9
                    0,
1883
                    $numberLength
1884 9
                );
1885 9
                $matcher = new Matcher($nationalNumberRule, $transformedNumber);
1886 9
                if ($isViableOriginalNumber && !$matcher->matches()) {
1887
                    return false;
1888
                }
1889 9
                if ($carrierCode !== null && $numOfGroups > 1) {
1890
                    $carrierCode .= $prefixMatcher->group(1);
1891
                }
1892 9
                $number = substr_replace($number, $transformedNumber, 0, mb_strlen($number));
1893 9
                return true;
1894
            }
1895
        }
1896 1503
        return false;
1897
    }
1898
1899
    /**
1900
     * Helper method to check a number against a particular pattern and determine whether it matches,
1901
     * or is too short or too long. Currently, if a number pattern suggests that numbers of length 7
1902
     * and 10 are possible, and a number in between these possible lengths is entered, such as of
1903
     * length 8, this will return TOO_LONG.
1904
     * @param string $numberPattern
1905
     * @param string $number
1906
     * @return int ValidationResult
1907
     */
1908 2394
    private function testNumberLengthAgainstPattern($numberPattern, $number)
1909
    {
1910 2394
        $numberMatcher = new Matcher($numberPattern, $number);
1911 2394
        if ($numberMatcher->matches()) {
1912 1913
            return ValidationResult::IS_POSSIBLE;
1913
        }
1914 506
        if ($numberMatcher->lookingAt()) {
1915 17
            return ValidationResult::TOO_LONG;
1916
        } else {
1917 497
            return ValidationResult::TOO_SHORT;
1918
        }
1919
    }
1920
1921
    /**
1922
     * Returns a list with the region codes that match the specific country calling code. For
1923
     * non-geographical country calling codes, the region code 001 is returned. Also, in the case
1924
     * of no region code being found, an empty list is returned.
1925
     * @param int $countryCallingCode
1926
     * @return array|null
1927
     */
1928 9
    public function getRegionCodesForCountryCode($countryCallingCode)
1929
    {
1930 9
        $regionCodes = isset($this->countryCallingCodeToRegionCodeMap[$countryCallingCode]) ? $this->countryCallingCodeToRegionCodeMap[$countryCallingCode] : null;
1931 9
        return $regionCodes === null ? array() : $regionCodes;
1932
    }
1933
1934
    /**
1935
     * Returns the country calling code for a specific region. For example, this would be 1 for the
1936
     * United States, and 64 for New Zealand. Assumes the region is already valid.
1937
     *
1938
     * @param string $regionCode the region that we want to get the country calling code for
1939
     * @return int the country calling code for the region denoted by regionCode
1940
     */
1941 2
    public function getCountryCodeForRegion($regionCode)
1942
    {
1943 2
        if (!$this->isValidRegionCode($regionCode)) {
1944 1
            return 0;
1945
        }
1946 2
        return $this->getCountryCodeForValidRegion($regionCode);
1947
    }
1948
1949
    /**
1950
     * Returns the country calling code for a specific region. For example, this would be 1 for the
1951
     * United States, and 64 for New Zealand. Assumes the region is already valid.
1952
     *
1953
     * @param string $regionCode the region that we want to get the country calling code for
1954
     * @return int the country calling code for the region denoted by regionCode
1955
     * @throws \InvalidArgumentException if the region is invalid
1956
     */
1957 1592
    private function getCountryCodeForValidRegion($regionCode)
1958
    {
1959 1592
        $metadata = $this->getMetadataForRegion($regionCode);
1960 1592
        if ($metadata === null) {
1961
            throw new \InvalidArgumentException("Invalid region code: " . $regionCode);
1962
        }
1963 1592
        return $metadata->getCountryCode();
1964
    }
1965
1966
    /**
1967
     * Returns a number formatted in such a way that it can be dialed from a mobile phone in a
1968
     * specific region. If the number cannot be reached from the region (e.g. some countries block
1969
     * toll-free numbers from being called outside of the country), the method returns an empty
1970
     * string.
1971
     *
1972
     * @param PhoneNumber $number the phone number to be formatted
1973
     * @param string $regionCallingFrom the region where the call is being placed
1974
     * @param boolean $withFormatting whether the number should be returned with formatting symbols, such as
1975
     *     spaces and dashes.
1976
     * @return string the formatted phone number
1977
     */
1978 1
    public function formatNumberForMobileDialing(PhoneNumber $number, $regionCallingFrom, $withFormatting)
1979
    {
1980 1
        $countryCallingCode = $number->getCountryCode();
1981 1
        if (!$this->hasValidCountryCallingCode($countryCallingCode)) {
1982
            return $number->hasRawInput() ? $number->getRawInput() : "";
1983
        }
1984
1985 1
        $formattedNumber = "";
1986
        // Clear the extension, as that part cannot normally be dialed together with the main number.
1987 1
        $numberNoExt = new PhoneNumber();
1988 1
        $numberNoExt->mergeFrom($number)->clearExtension();
1989 1
        $regionCode = $this->getRegionCodeForCountryCode($countryCallingCode);
1990 1
        $numberType = $this->getNumberType($numberNoExt);
1991 1
        $isValidNumber = ($numberType !== PhoneNumberType::UNKNOWN);
1992 1
        if ($regionCallingFrom == $regionCode) {
1993 1
            $isFixedLineOrMobile = ($numberType == PhoneNumberType::FIXED_LINE) || ($numberType == PhoneNumberType::MOBILE) || ($numberType == PhoneNumberType::FIXED_LINE_OR_MOBILE);
1994
            // Carrier codes may be needed in some countries. We handle this here.
1995 1
            if ($regionCode == "CO" && $numberType == PhoneNumberType::FIXED_LINE) {
1996
                $formattedNumber = $this->formatNationalNumberWithCarrierCode(
1997
                    $numberNoExt,
1998
                    self::COLOMBIA_MOBILE_TO_FIXED_LINE_PREFIX
1999
                );
2000 1
            } elseif ($regionCode == "BR" && $isFixedLineOrMobile) {
2001
                // Brazilian fixed line and mobile numbers need to be dialed with a carrier code when
2002
                // called within Brazil. Without that, most of the carriers won't connect the call.
2003
                // Because of that, we return an empty string here.
2004
                $formattedNumber = $numberNoExt->hasPreferredDomesticCarrierCode(
2005
                ) ? $this->formatNationalNumberWithCarrierCode($numberNoExt, "") : "";
2006 1
            } elseif ($isValidNumber && $regionCode == "HU") {
2007
                // The national format for HU numbers doesn't contain the national prefix, because that is
2008
                // how numbers are normally written down. However, the national prefix is obligatory when
2009
                // dialing from a mobile phone, except for short numbers. As a result, we add it back here
2010
                // if it is a valid regular length phone number.
2011 1
                $formattedNumber = $this->getNddPrefixForRegion(
2012 1
                        $regionCode,
2013
                        true /* strip non-digits */
2014 1
                    ) . " " . $this->format($numberNoExt, PhoneNumberFormat::NATIONAL);
2015 1
            } elseif ($countryCallingCode === self::NANPA_COUNTRY_CODE) {
2016
                // For NANPA countries, we output international format for numbers that can be dialed
2017
                // internationally, since that always works, except for numbers which might potentially be
2018
                // short numbers, which are always dialled in national format.
2019 1
                $regionMetadata = $this->getMetadataForRegion($regionCallingFrom);
2020 1
                if ($this->canBeInternationallyDialled($numberNoExt) &&
2021 1
                    !$this->isShorterThanPossibleNormalNumber(
2022 1
                        $regionMetadata,
0 ignored issues
show
Bug introduced by
It seems like $regionMetadata defined by $this->getMetadataForRegion($regionCallingFrom) on line 2019 can be null; however, libphonenumber\PhoneNumb...nPossibleNormalNumber() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
2023 1
                        $this->getNationalSignificantNumber($numberNoExt)
2024 1
                    )
2025 1
                ) {
2026 1
                    $formattedNumber = $this->format($numberNoExt, PhoneNumberFormat::INTERNATIONAL);
2027 1
                } else {
2028 1
                    $formattedNumber = $this->format($numberNoExt, PhoneNumberFormat::NATIONAL);
2029
                }
2030 1
            } else {
2031
                // For non-geographical countries, Mexican and Chilean fixed line and mobile numbers, we
2032
                // output international format for numbers that can be dialed internationally as that always
2033
                // works.
2034 1
                if (($regionCode == self::REGION_CODE_FOR_NON_GEO_ENTITY ||
2035
                        // MX fixed line and mobile numbers should always be formatted in international format,
2036
                        // even when dialed within MX. For national format to work, a carrier code needs to be
2037
                        // used, and the correct carrier code depends on if the caller and callee are from the
2038
                        // same local area. It is trickier to get that to work correctly than using
2039
                        // international format, which is tested to work fine on all carriers.
2040
                        // CL fixed line numbers need the national prefix when dialing in the national format,
2041
                        // but don't have it when used for display. The reverse is true for mobile numbers.
2042
                        // As a result, we output them in the international format to make it work.
2043 1
                        (($regionCode == "MX" || $regionCode == "CL") && $isFixedLineOrMobile)) && $this->canBeInternationallyDialled(
2044
                        $numberNoExt
2045 1
                    )
2046 1
                ) {
2047 1
                    $formattedNumber = $this->format($numberNoExt, PhoneNumberFormat::INTERNATIONAL);
2048 1
                } else {
2049 1
                    $formattedNumber = $this->format($numberNoExt, PhoneNumberFormat::NATIONAL);
2050
                }
2051
            }
2052 1
        } elseif ($isValidNumber && $this->canBeInternationallyDialled($numberNoExt)) {
2053
            // We assume that short numbers are not diallable from outside their region, so if a number
2054
            // is not a valid regular length phone number, we treat it as if it cannot be internationally
2055
            // dialled.
2056
            return $withFormatting ?
2057 1
                $this->format($numberNoExt, PhoneNumberFormat::INTERNATIONAL) :
2058 1
                $this->format($numberNoExt, PhoneNumberFormat::E164);
2059
        }
2060 1
        return $withFormatting ? $formattedNumber : $this->normalizeDiallableCharsOnly($formattedNumber);
2061
    }
2062
2063
    /**
2064
     * Formats a phone number in national format for dialing using the carrier as specified in the
2065
     * {@code carrierCode}. The {@code carrierCode} will always be used regardless of whether the
2066
     * phone number already has a preferred domestic carrier code stored. If {@code carrierCode}
2067
     * contains an empty string, returns the number in national format without any carrier code.
2068
     *
2069
     * @param PhoneNumber $number the phone number to be formatted
2070
     * @param string $carrierCode the carrier selection code to be used
2071
     * @return string the formatted phone number in national format for dialing using the carrier as
2072
     * specified in the {@code carrierCode}
2073
     */
2074 2
    public function formatNationalNumberWithCarrierCode(PhoneNumber $number, $carrierCode)
2075
    {
2076 2
        $countryCallingCode = $number->getCountryCode();
2077 2
        $nationalSignificantNumber = $this->getNationalSignificantNumber($number);
2078 2
        if (!$this->hasValidCountryCallingCode($countryCallingCode)) {
2079 1
            return $nationalSignificantNumber;
2080
        }
2081
2082
        // Note getRegionCodeForCountryCode() is used because formatting information for regions which
2083
        // share a country calling code is contained by only one region for performance reasons. For
2084
        // example, for NANPA regions it will be contained in the metadata for US.
2085 2
        $regionCode = $this->getRegionCodeForCountryCode($countryCallingCode);
2086
        // Metadata cannot be null because the country calling code is valid.
2087 2
        $metadata = $this->getMetadataForRegionOrCallingCode($countryCallingCode, $regionCode);
2088
2089 2
        $formattedNumber = $this->formatNsn(
2090 2
            $nationalSignificantNumber,
2091 2
            $metadata,
0 ignored issues
show
Bug introduced by
It seems like $metadata defined by $this->getMetadataForReg...llingCode, $regionCode) on line 2087 can be null; however, libphonenumber\PhoneNumberUtil::formatNsn() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
2092 2
            PhoneNumberFormat::NATIONAL,
2093
            $carrierCode
2094 2
        );
2095 2
        $this->maybeAppendFormattedExtension($number, $metadata, PhoneNumberFormat::NATIONAL, $formattedNumber);
2096 2
        $this->prefixNumberWithCountryCallingCode(
2097 2
            $countryCallingCode,
2098 2
            PhoneNumberFormat::NATIONAL,
2099
            $formattedNumber
2100 2
        );
2101 2
        return $formattedNumber;
2102
    }
2103
2104
    /**
2105
     * Formats a phone number in national format for dialing using the carrier as specified in the
2106
     * preferredDomesticCarrierCode field of the PhoneNumber object passed in. If that is missing,
2107
     * use the {@code fallbackCarrierCode} passed in instead. If there is no
2108
     * {@code preferredDomesticCarrierCode}, and the {@code fallbackCarrierCode} contains an empty
2109
     * string, return the number in national format without any carrier code.
2110
     *
2111
     * <p>Use {@link #formatNationalNumberWithCarrierCode} instead if the carrier code passed in
2112
     * should take precedence over the number's {@code preferredDomesticCarrierCode} when formatting.
2113
     *
2114
     * @param PhoneNumber $number the phone number to be formatted
2115
     * @param string $fallbackCarrierCode the carrier selection code to be used, if none is found in the
2116
     *     phone number itself
2117
     * @return string the formatted phone number in national format for dialing using the number's
2118
     *     {@code preferredDomesticCarrierCode}, or the {@code fallbackCarrierCode} passed in if
2119
     *     none is found
2120
     */
2121 1
    public function formatNationalNumberWithPreferredCarrierCode(PhoneNumber $number, $fallbackCarrierCode)
2122
    {
2123 1
        return $this->formatNationalNumberWithCarrierCode(
2124 1
            $number,
2125 1
            $number->hasPreferredDomesticCarrierCode()
2126 1
                ? $number->getPreferredDomesticCarrierCode()
2127 1
                : $fallbackCarrierCode
2128 1
        );
2129
    }
2130
2131
    /**
2132
     * Returns true if the number can be dialled from outside the region, or unknown. If the number
2133
     * can only be dialled from within the region, returns false. Does not check the number is a valid
2134
     * number.
2135
     * TODO: Make this method public when we have enough metadata to make it worthwhile.
2136
     *
2137
     * @param PhoneNumber $number the phone-number for which we want to know whether it is diallable from outside the region
2138
     * @return bool
2139
     */
2140 29
    public function canBeInternationallyDialled(PhoneNumber $number)
2141
    {
2142 29
        $metadata = $this->getMetadataForRegion($this->getRegionCodeForNumber($number));
2143 29
        if ($metadata === null) {
2144
            // Note numbers belonging to non-geographical entities (e.g. +800 numbers) are always
2145
            // internationally diallable, and will be caught here.
2146 2
            return true;
2147
        }
2148 29
        $nationalSignificantNumber = $this->getNationalSignificantNumber($number);
2149 29
        return !$this->isNumberMatchingDesc($nationalSignificantNumber, $metadata->getNoInternationalDialling());
2150
    }
2151
2152
    /**
2153
     * Normalizes a string of characters representing a phone number. This strips all characters which
2154
     * are not diallable on a mobile phone keypad (including all non-ASCII digits).
2155
     *
2156
     * @param string $number a string of characters representing a phone number
2157
     * @return string the normalized string version of the phone number
2158
     */
2159 3
    public static function normalizeDiallableCharsOnly($number)
2160
    {
2161 3
        return self::normalizeHelper($number, self::$DIALLABLE_CHAR_MAPPINGS, true /* remove non matches */);
2162
    }
2163
2164
    /**
2165
     * Formats a phone number for out-of-country dialing purposes.
2166
     *
2167
     * Note that in this version, if the number was entered originally using alpha characters and
2168
     * this version of the number is stored in raw_input, this representation of the number will be
2169
     * used rather than the digit representation. Grouping information, as specified by characters
2170
     * such as "-" and " ", will be retained.
2171
     *
2172
     * <p><b>Caveats:</b></p>
2173
     * <ul>
2174
     *  <li> This will not produce good results if the country calling code is both present in the raw
2175
     *       input _and_ is the start of the national number. This is not a problem in the regions
2176
     *       which typically use alpha numbers.
2177
     *  <li> This will also not produce good results if the raw input has any grouping information
2178
     *       within the first three digits of the national number, and if the function needs to strip
2179
     *       preceding digits/words in the raw input before these digits. Normally people group the
2180
     *       first three digits together so this is not a huge problem - and will be fixed if it
2181
     *       proves to be so.
2182
     * </ul>
2183
     *
2184
     * @param PhoneNumber $number the phone number that needs to be formatted
2185
     * @param String $regionCallingFrom the region where the call is being placed
2186
     * @return String the formatted phone number
2187
     */
2188 1
    public function formatOutOfCountryKeepingAlphaChars(PhoneNumber $number, $regionCallingFrom)
2189
    {
2190 1
        $rawInput = $number->getRawInput();
2191
        // If there is no raw input, then we can't keep alpha characters because there aren't any.
2192
        // In this case, we return formatOutOfCountryCallingNumber.
2193 1
        if (mb_strlen($rawInput) == 0) {
2194 1
            return $this->formatOutOfCountryCallingNumber($number, $regionCallingFrom);
2195
        }
2196 1
        $countryCode = $number->getCountryCode();
2197 1
        if (!$this->hasValidCountryCallingCode($countryCode)) {
2198 1
            return $rawInput;
2199
        }
2200
        // Strip any prefix such as country calling code, IDD, that was present. We do this by comparing
2201
        // the number in raw_input with the parsed number.
2202
        // To do this, first we normalize punctuation. We retain number grouping symbols such as " "
2203
        // only.
2204 1
        $rawInput = $this->normalizeHelper($rawInput, self::$ALL_PLUS_NUMBER_GROUPING_SYMBOLS, true);
2205
        // Now we trim everything before the first three digits in the parsed number. We choose three
2206
        // because all valid alpha numbers have 3 digits at the start - if it does not, then we don't
2207
        // trim anything at all. Similarly, if the national number was less than three digits, we don't
2208
        // trim anything at all.
2209 1
        $nationalNumber = $this->getNationalSignificantNumber($number);
2210 1
        if (mb_strlen($nationalNumber) > 3) {
2211 1
            $firstNationalNumberDigit = strpos($rawInput, substr($nationalNumber, 0, 3));
2212 1
            if ($firstNationalNumberDigit !== false) {
2213 1
                $rawInput = substr($rawInput, $firstNationalNumberDigit);
2214 1
            }
2215 1
        }
2216 1
        $metadataForRegionCallingFrom = $this->getMetadataForRegion($regionCallingFrom);
2217 1
        if ($countryCode == self::NANPA_COUNTRY_CODE) {
2218 1
            if ($this->isNANPACountry($regionCallingFrom)) {
2219 1
                return $countryCode . " " . $rawInput;
2220
            }
2221 1
        } else if ($metadataForRegionCallingFrom !== null &&
2222 1
            $countryCode == $this->getCountryCodeForValidRegion($regionCallingFrom)
2223 1
        ) {
2224
            $formattingPattern =
2225 1
                $this->chooseFormattingPatternForNumber(
2226 1
                    $metadataForRegionCallingFrom->numberFormats(),
2227
                    $nationalNumber
2228 1
                );
2229 1
            if ($formattingPattern === null) {
2230
                // If no pattern above is matched, we format the original input.
2231 1
                return $rawInput;
2232
            }
2233 1
            $newFormat = new NumberFormat();
2234 1
            $newFormat->mergeFrom($formattingPattern);
2235
            // The first group is the first group of digits that the user wrote together.
2236 1
            $newFormat->setPattern("(\\d+)(.*)");
2237
            // Here we just concatenate them back together after the national prefix has been fixed.
2238 1
            $newFormat->setFormat("$1$2");
2239
            // Now we format using this pattern instead of the default pattern, but with the national
2240
            // prefix prefixed if necessary.
2241
            // This will not work in the cases where the pattern (and not the leading digits) decide
2242
            // whether a national prefix needs to be used, since we have overridden the pattern to match
2243
            // anything, but that is not the case in the metadata to date.
2244 1
            return $this->formatNsnUsingPattern($rawInput, $newFormat, PhoneNumberFormat::NATIONAL);
2245
        }
2246 1
        $internationalPrefixForFormatting = "";
2247
        // If an unsupported region-calling-from is entered, or a country with multiple international
2248
        // prefixes, the international format of the number is returned, unless there is a preferred
2249
        // international prefix.
2250 1
        if ($metadataForRegionCallingFrom !== null) {
2251 1
            $internationalPrefix = $metadataForRegionCallingFrom->getInternationalPrefix();
2252 1
            $uniqueInternationalPrefixMatcher = new Matcher(self::UNIQUE_INTERNATIONAL_PREFIX, $internationalPrefix);
2253
            $internationalPrefixForFormatting =
2254 1
                $uniqueInternationalPrefixMatcher->matches()
2255 1
                    ? $internationalPrefix
2256 1
                    : $metadataForRegionCallingFrom->getPreferredInternationalPrefix();
2257 1
        }
2258 1
        $formattedNumber = $rawInput;
2259 1
        $regionCode = $this->getRegionCodeForCountryCode($countryCode);
2260
        // Metadata cannot be null because the country calling code is valid.
2261 1
        $metadataForRegion = $this->getMetadataForRegionOrCallingCode($countryCode, $regionCode);
2262 1
        $this->maybeAppendFormattedExtension(
2263 1
            $number,
2264 1
            $metadataForRegion,
2265 1
            PhoneNumberFormat::INTERNATIONAL,
2266
            $formattedNumber
2267 1
        );
2268 1
        if (mb_strlen($internationalPrefixForFormatting) > 0) {
2269 1
            $formattedNumber = $internationalPrefixForFormatting . " " . $countryCode . " " . $formattedNumber;
2270 1
        } else {
2271
            // Invalid region entered as country-calling-from (so no metadata was found for it) or the
2272
            // region chosen has multiple international dialling prefixes.
2273 1
            $this->prefixNumberWithCountryCallingCode(
2274 1
                $countryCode,
2275 1
                PhoneNumberFormat::INTERNATIONAL,
2276
                $formattedNumber
2277 1
            );
2278
        }
2279 1
        return $formattedNumber;
2280
    }
2281
2282
    /**
2283
     * Formats a phone number for out-of-country dialing purposes. If no regionCallingFrom is
2284
     * supplied, we format the number in its INTERNATIONAL format. If the country calling code is the
2285
     * same as that of the region where the number is from, then NATIONAL formatting will be applied.
2286
     *
2287
     * <p>If the number itself has a country calling code of zero or an otherwise invalid country
2288
     * calling code, then we return the number with no formatting applied.
2289
     *
2290
     * <p>Note this function takes care of the case for calling inside of NANPA and between Russia and
2291
     * Kazakhstan (who share the same country calling code). In those cases, no international prefix
2292
     * is used. For regions which have multiple international prefixes, the number in its
2293
     * INTERNATIONAL format will be returned instead.
2294
     *
2295
     * @param PhoneNumber $number the phone number to be formatted
2296
     * @param string $regionCallingFrom the region where the call is being placed
2297
     * @return string  the formatted phone number
2298
     */
2299 8
    public function formatOutOfCountryCallingNumber(PhoneNumber $number, $regionCallingFrom)
2300
    {
2301 8
        if (!$this->isValidRegionCode($regionCallingFrom)) {
2302 1
            return $this->format($number, PhoneNumberFormat::INTERNATIONAL);
2303
        }
2304 7
        $countryCallingCode = $number->getCountryCode();
2305 7
        $nationalSignificantNumber = $this->getNationalSignificantNumber($number);
2306 7
        if (!$this->hasValidCountryCallingCode($countryCallingCode)) {
2307
            return $nationalSignificantNumber;
2308
        }
2309 7
        if ($countryCallingCode == self::NANPA_COUNTRY_CODE) {
2310 4
            if ($this->isNANPACountry($regionCallingFrom)) {
2311
                // For NANPA regions, return the national format for these regions but prefix it with the
2312
                // country calling code.
2313 1
                return $countryCallingCode . " " . $this->format($number, PhoneNumberFormat::NATIONAL);
2314
            }
2315 7
        } else if ($countryCallingCode == $this->getCountryCodeForValidRegion($regionCallingFrom)) {
2316
            // If regions share a country calling code, the country calling code need not be dialled.
2317
            // This also applies when dialling within a region, so this if clause covers both these cases.
2318
            // Technically this is the case for dialling from La Reunion to other overseas departments of
2319
            // France (French Guiana, Martinique, Guadeloupe), but not vice versa - so we don't cover this
2320
            // edge case for now and for those cases return the version including country calling code.
2321
            // Details here: http://www.petitfute.com/voyage/225-info-pratiques-reunion
2322 2
            return $this->format($number, PhoneNumberFormat::NATIONAL);
2323
        }
2324
        // Metadata cannot be null because we checked 'isValidRegionCode()' above.
2325 7
        $metadataForRegionCallingFrom = $this->getMetadataForRegion($regionCallingFrom);
2326
2327 7
        $internationalPrefix = $metadataForRegionCallingFrom->getInternationalPrefix();
2328
2329
        // For regions that have multiple international prefixes, the international format of the
2330
        // number is returned, unless there is a preferred international prefix.
2331 7
        $internationalPrefixForFormatting = "";
2332 7
        $uniqueInternationalPrefixMatcher = new Matcher(self::UNIQUE_INTERNATIONAL_PREFIX, $internationalPrefix);
2333
2334 7
        if ($uniqueInternationalPrefixMatcher->matches()) {
2335 6
            $internationalPrefixForFormatting = $internationalPrefix;
2336 7
        } else if ($metadataForRegionCallingFrom->hasPreferredInternationalPrefix()) {
2337 3
            $internationalPrefixForFormatting = $metadataForRegionCallingFrom->getPreferredInternationalPrefix();
2338 3
        }
2339
2340 7
        $regionCode = $this->getRegionCodeForCountryCode($countryCallingCode);
2341
        // Metadata cannot be null because the country calling code is valid.
2342 7
        $metadataForRegion = $this->getMetadataForRegionOrCallingCode($countryCallingCode, $regionCode);
2343 7
        $formattedNationalNumber = $this->formatNsn(
2344 7
            $nationalSignificantNumber,
2345 7
            $metadataForRegion,
0 ignored issues
show
Bug introduced by
It seems like $metadataForRegion defined by $this->getMetadataForReg...llingCode, $regionCode) on line 2342 can be null; however, libphonenumber\PhoneNumberUtil::formatNsn() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
2346
            PhoneNumberFormat::INTERNATIONAL
2347 7
        );
2348 7
        $formattedNumber = $formattedNationalNumber;
2349 7
        $this->maybeAppendFormattedExtension(
2350 7
            $number,
2351 7
            $metadataForRegion,
2352 7
            PhoneNumberFormat::INTERNATIONAL,
2353
            $formattedNumber
2354 7
        );
2355 7
        if (mb_strlen($internationalPrefixForFormatting) > 0) {
2356 7
            $formattedNumber = $internationalPrefixForFormatting . " " . $countryCallingCode . " " . $formattedNumber;
2357 7
        } else {
2358 1
            $this->prefixNumberWithCountryCallingCode(
2359 1
                $countryCallingCode,
2360 1
                PhoneNumberFormat::INTERNATIONAL,
2361
                $formattedNumber
2362 1
            );
2363
        }
2364 7
        return $formattedNumber;
2365
    }
2366
2367
    /**
2368
     * Checks if this is a region under the North American Numbering Plan Administration (NANPA).
2369
     * @param string $regionCode
2370
     * @return boolean true if regionCode is one of the regions under NANPA
2371
     */
2372 5
    public function isNANPACountry($regionCode)
2373
    {
2374 5
        return in_array($regionCode, $this->nanpaRegions);
2375
    }
2376
2377
    /**
2378
     * Formats a phone number using the original phone number format that the number is parsed from.
2379
     * The original format is embedded in the country_code_source field of the PhoneNumber object
2380
     * passed in. If such information is missing, the number will be formatted into the NATIONAL
2381
     * format by default. When the number contains a leading zero and this is unexpected for this
2382
     * country, or we don't have a formatting pattern for the number, the method returns the raw input
2383
     * when it is available.
2384
     *
2385
     * Note this method guarantees no digit will be inserted, removed or modified as a result of
2386
     * formatting.
2387
     *
2388
     * @param PhoneNumber $number the phone number that needs to be formatted in its original number format
2389
     * @param string $regionCallingFrom the region whose IDD needs to be prefixed if the original number
2390
     *     has one
2391
     * @return string the formatted phone number in its original number format
2392
     */
2393 1
    public function formatInOriginalFormat(PhoneNumber $number, $regionCallingFrom)
2394
    {
2395 1
        if ($number->hasRawInput() &&
2396 1
            ($this->hasUnexpectedItalianLeadingZero($number) || !$this->hasFormattingPatternForNumber($number))
2397 1
        ) {
2398
            // We check if we have the formatting pattern because without that, we might format the number
2399
            // as a group without national prefix.
2400 1
            return $number->getRawInput();
2401
        }
2402 1
        if (!$number->hasCountryCodeSource()) {
2403 1
            return $this->format($number, PhoneNumberFormat::NATIONAL);
2404
        }
2405 1
        switch ($number->getCountryCodeSource()) {
2406 1
            case CountryCodeSource::FROM_NUMBER_WITH_PLUS_SIGN:
2407 1
                $formattedNumber = $this->format($number, PhoneNumberFormat::INTERNATIONAL);
2408 1
                break;
2409 1
            case CountryCodeSource::FROM_NUMBER_WITH_IDD:
2410 1
                $formattedNumber = $this->formatOutOfCountryCallingNumber($number, $regionCallingFrom);
2411 1
                break;
2412 1
            case CountryCodeSource::FROM_NUMBER_WITHOUT_PLUS_SIGN:
2413 1
                $formattedNumber = substr($this->format($number, PhoneNumberFormat::INTERNATIONAL), 1);
2414 1
                break;
2415 1
            case CountryCodeSource::FROM_DEFAULT_COUNTRY:
2416
                // Fall-through to default case.
2417 1
            default:
0 ignored issues
show
Coding Style introduced by
The default body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a default statement must start on the line immediately following the statement.

switch ($expr) {
    default:
        doSomething(); //right
        break;
}


switch ($expr) {
    default:

        doSomething(); //wrong
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
2418
2419 1
                $regionCode = $this->getRegionCodeForCountryCode($number->getCountryCode());
2420
                // We strip non-digits from the NDD here, and from the raw input later, so that we can
2421
                // compare them easily.
2422 1
                $nationalPrefix = $this->getNddPrefixForRegion($regionCode, true /* strip non-digits */);
2423 1
                $nationalFormat = $this->format($number, PhoneNumberFormat::NATIONAL);
2424 1
                if ($nationalPrefix === null || mb_strlen($nationalPrefix) == 0) {
2425
                    // If the region doesn't have a national prefix at all, we can safely return the national
2426
                    // format without worrying about a national prefix being added.
2427 1
                    $formattedNumber = $nationalFormat;
2428 1
                    break;
2429
                }
2430
                // Otherwise, we check if the original number was entered with a national prefix.
2431 1
                if ($this->rawInputContainsNationalPrefix(
2432 1
                    $number->getRawInput(),
2433 1
                    $nationalPrefix,
2434
                    $regionCode
2435 1
                )
2436 1
                ) {
2437
                    // If so, we can safely return the national format.
2438 1
                    $formattedNumber = $nationalFormat;
2439 1
                    break;
2440
                }
2441
                // Metadata cannot be null here because getNddPrefixForRegion() (above) returns null if
2442
                // there is no metadata for the region.
2443 1
                $metadata = $this->getMetadataForRegion($regionCode);
2444 1
                $nationalNumber = $this->getNationalSignificantNumber($number);
2445 1
                $formatRule = $this->chooseFormattingPatternForNumber($metadata->numberFormats(), $nationalNumber);
2446
                // The format rule could still be null here if the national number was 0 and there was no
2447
                // raw input (this should not be possible for numbers generated by the phonenumber library
2448
                // as they would also not have a country calling code and we would have exited earlier).
2449 1
                if ($formatRule === null) {
2450
                    $formattedNumber = $nationalFormat;
2451
                    break;
2452
                }
2453
                // When the format we apply to this number doesn't contain national prefix, we can just
2454
                // return the national format.
2455
                // TODO: Refactor the code below with the code in isNationalPrefixPresentIfRequired.
2456 1
                $candidateNationalPrefixRule = $formatRule->getNationalPrefixFormattingRule();
2457
                // We assume that the first-group symbol will never be _before_ the national prefix.
2458 1
                $indexOfFirstGroup = strpos($candidateNationalPrefixRule, '$1');
2459 1
                if ($indexOfFirstGroup <= 0) {
2460 1
                    $formattedNumber = $nationalFormat;
2461 1
                    break;
2462
                }
2463 1
                $candidateNationalPrefixRule = substr($candidateNationalPrefixRule, 0, $indexOfFirstGroup);
2464 1
                $candidateNationalPrefixRule = $this->normalizeDigitsOnly($candidateNationalPrefixRule);
2465 1
                if (mb_strlen($candidateNationalPrefixRule) == 0) {
2466
                    // National prefix not used when formatting this number.
2467
                    $formattedNumber = $nationalFormat;
2468
                    break;
2469
                }
2470
                // Otherwise, we need to remove the national prefix from our output.
2471 1
                $numFormatCopy = new NumberFormat();
2472 1
                $numFormatCopy->mergeFrom($formatRule);
2473 1
                $numFormatCopy->clearNationalPrefixFormattingRule();
2474 1
                $numberFormats = array();
2475 1
                $numberFormats[] = $numFormatCopy;
2476 1
                $formattedNumber = $this->formatByPattern($number, PhoneNumberFormat::NATIONAL, $numberFormats);
2477 1
                break;
2478 1
        }
2479 1
        $rawInput = $number->getRawInput();
2480
        // If no digit is inserted/removed/modified as a result of our formatting, we return the
2481
        // formatted phone number; otherwise we return the raw input the user entered.
2482 1
        if ($formattedNumber !== null && mb_strlen($rawInput) > 0) {
2483 1
            $normalizedFormattedNumber = $this->normalizeDiallableCharsOnly($formattedNumber);
2484 1
            $normalizedRawInput = $this->normalizeDiallableCharsOnly($rawInput);
2485 1
            if ($normalizedFormattedNumber != $normalizedRawInput) {
2486 1
                $formattedNumber = $rawInput;
2487 1
            }
2488 1
        }
2489 1
        return $formattedNumber;
2490
    }
2491
2492
    /**
2493
     * Returns true if a number is from a region whose national significant number couldn't contain a
2494
     * leading zero, but has the italian_leading_zero field set to true.
2495
     * @param PhoneNumber $number
2496
     * @return bool
2497
     */
2498 1
    private function hasUnexpectedItalianLeadingZero(PhoneNumber $number)
2499
    {
2500 1
        return $number->isItalianLeadingZero() && !$this->isLeadingZeroPossible($number->getCountryCode());
2501
    }
2502
2503
    /**
2504
     * Checks whether the country calling code is from a region whose national significant number
2505
     * could contain a leading zero. An example of such a region is Italy. Returns false if no
2506
     * metadata for the country is found.
2507
     * @param int $countryCallingCode
2508
     * @return bool
2509
     */
2510 2
    public function isLeadingZeroPossible($countryCallingCode)
2511
    {
2512 2
        $mainMetadataForCallingCode = $this->getMetadataForRegionOrCallingCode(
2513 2
            $countryCallingCode,
2514 2
            $this->getRegionCodeForCountryCode($countryCallingCode)
2515 2
        );
2516 2
        if ($mainMetadataForCallingCode === null) {
2517 1
            return false;
2518
        }
2519 2
        return (bool)$mainMetadataForCallingCode->isLeadingZeroPossible();
2520
    }
2521
2522
    /**
2523
     * @param PhoneNumber $number
2524
     * @return bool
2525
     */
2526 1
    private function hasFormattingPatternForNumber(PhoneNumber $number)
2527
    {
2528 1
        $countryCallingCode = $number->getCountryCode();
2529 1
        $phoneNumberRegion = $this->getRegionCodeForCountryCode($countryCallingCode);
2530 1
        $metadata = $this->getMetadataForRegionOrCallingCode($countryCallingCode, $phoneNumberRegion);
2531 1
        if ($metadata === null) {
2532
            return false;
2533
        }
2534 1
        $nationalNumber = $this->getNationalSignificantNumber($number);
2535 1
        $formatRule = $this->chooseFormattingPatternForNumber($metadata->numberFormats(), $nationalNumber);
2536 1
        return $formatRule !== null;
2537
    }
2538
2539
    /**
2540
     * Returns the national dialling prefix for a specific region. For example, this would be 1 for
2541
     * the United States, and 0 for New Zealand. Set stripNonDigits to true to strip symbols like "~"
2542
     * (which indicates a wait for a dialling tone) from the prefix returned. If no national prefix is
2543
     * present, we return null.
2544
     *
2545
     * <p>Warning: Do not use this method for do-your-own formatting - for some regions, the
2546
     * national dialling prefix is used only for certain types of numbers. Use the library's
2547
     * formatting functions to prefix the national prefix when required.
2548
     *
2549
     * @param string $regionCode the region that we want to get the dialling prefix for
2550
     * @param boolean $stripNonDigits true to strip non-digits from the national dialling prefix
2551
     * @return string the dialling prefix for the region denoted by regionCode
2552
     */
2553 3
    public function getNddPrefixForRegion($regionCode, $stripNonDigits)
2554
    {
2555 3
        $metadata = $this->getMetadataForRegion($regionCode);
2556 3
        if ($metadata === null) {
2557 1
            return null;
2558
        }
2559 3
        $nationalPrefix = $metadata->getNationalPrefix();
2560
        // If no national prefix was found, we return null.
2561 3
        if (mb_strlen($nationalPrefix) == 0) {
2562 1
            return null;
2563
        }
2564 3
        if ($stripNonDigits) {
2565
            // Note: if any other non-numeric symbols are ever used in national prefixes, these would have
2566
            // to be removed here as well.
2567 3
            $nationalPrefix = str_replace("~", "", $nationalPrefix);
2568 3
        }
2569 3
        return $nationalPrefix;
2570
    }
2571
2572
    /**
2573
     * Check if rawInput, which is assumed to be in the national format, has a national prefix. The
2574
     * national prefix is assumed to be in digits-only form.
2575
     * @param string $rawInput
2576
     * @param string $nationalPrefix
2577
     * @param string $regionCode
2578
     * @return bool
2579
     */
2580 1
    private function rawInputContainsNationalPrefix($rawInput, $nationalPrefix, $regionCode)
2581
    {
2582 1
        $normalizedNationalNumber = $this->normalizeDigitsOnly($rawInput);
2583 1
        if (strpos($normalizedNationalNumber, $nationalPrefix) === 0) {
2584
            try {
2585
                // Some Japanese numbers (e.g. 00777123) might be mistaken to contain the national prefix
2586
                // when written without it (e.g. 0777123) if we just do prefix matching. To tackle that, we
2587
                // check the validity of the number if the assumed national prefix is removed (777123 won't
2588
                // be valid in Japan).
2589 1
                return $this->isValidNumber(
2590 1
                    $this->parse(substr($normalizedNationalNumber, mb_strlen($nationalPrefix)), $regionCode)
2591 1
                );
2592
            } catch (NumberParseException $e) {
2593
                return false;
2594
            }
2595
        }
2596 1
        return false;
2597
    }
2598
2599
    /**
2600
     * Tests whether a phone number matches a valid pattern. Note this doesn't verify the number
2601
     * is actually in use, which is impossible to tell by just looking at a number itself.
2602
     *
2603
     * @param PhoneNumber $number the phone number that we want to validate
2604
     * @return boolean that indicates whether the number is of a valid pattern
2605
     */
2606 1591
    public function isValidNumber(PhoneNumber $number)
2607
    {
2608 1591
        $regionCode = $this->getRegionCodeForNumber($number);
2609 1591
        return $this->isValidNumberForRegion($number, $regionCode);
2610
    }
2611
2612
    /**
2613
     * Tests whether a phone number is valid for a certain region. Note this doesn't verify the number
2614
     * is actually in use, which is impossible to tell by just looking at a number itself. If the
2615
     * country calling code is not the same as the country calling code for the region, this
2616
     * immediately exits with false. After this, the specific number pattern rules for the region are
2617
     * examined. This is useful for determining for example whether a particular number is valid for
2618
     * Canada, rather than just a valid NANPA number.
2619
     * Warning: In most cases, you want to use {@link #isValidNumber} instead. For example, this
2620
     * method will mark numbers from British Crown dependencies such as the Isle of Man as invalid for
2621
     * the region "GB" (United Kingdom), since it has its own region code, "IM", which may be
2622
     * undesirable.
2623
     *
2624
     * @param PhoneNumber $number the phone number that we want to validate
2625
     * @param string $regionCode the region that we want to validate the phone number for
2626
     * @return boolean that indicates whether the number is of a valid pattern
2627
     */
2628 1597
    public function isValidNumberForRegion(PhoneNumber $number, $regionCode)
2629
    {
2630 1597
        $countryCode = $number->getCountryCode();
2631 1597
        $metadata = $this->getMetadataForRegionOrCallingCode($countryCode, $regionCode);
2632 1597
        if (($metadata === null) ||
2633 1595
            (self::REGION_CODE_FOR_NON_GEO_ENTITY !== $regionCode &&
2634 1586
                $countryCode !== $this->getCountryCodeForValidRegion($regionCode))
2635 1597
        ) {
2636
            // Either the region code was invalid, or the country calling code for this number does not
2637
            // match that of the region code.
2638 6
            return false;
2639
        }
2640 1594
        $nationalSignificantNumber = $this->getNationalSignificantNumber($number);
2641
2642 1594
        return $this->getNumberTypeHelper($nationalSignificantNumber, $metadata) != PhoneNumberType::UNKNOWN;
2643
    }
2644
2645
    /**
2646
     * Parses a string and returns it in proto buffer format. This method will throw a
2647
     * {@link NumberParseException} if the number is not considered to be
2648
     * a possible number. Note that validation of whether the number is actually a valid number for a
2649
     * particular region is not performed. This can be done separately with {@link #isValidNumber}.
2650
     *
2651
     * @param string $numberToParse number that we are attempting to parse. This can contain formatting
2652
     *                          such as +, ( and -, as well as a phone number extension.
2653
     * @param string $defaultRegion region that we are expecting the number to be from. This is only used
2654
     *                          if the number being parsed is not written in international format.
2655
     *                          The country_code for the number in this case would be stored as that
2656
     *                          of the default region supplied. If the number is guaranteed to
2657
     *                          start with a '+' followed by the country calling code, then
2658
     *                          "ZZ" or null can be supplied.
2659
     * @param PhoneNumber|null $phoneNumber
2660
     * @param bool $keepRawInput
2661
     * @return PhoneNumber a phone number proto buffer filled with the parsed number
2662
     * @throws NumberParseException  if the string is not considered to be a viable phone number or if
2663
     *                               no default region was supplied and the number is not in
2664
     *                               international format (does not start with +)
2665
     */
2666 2393
    public function parse($numberToParse, $defaultRegion, PhoneNumber $phoneNumber = null, $keepRawInput = false)
2667
    {
2668 2393
        if ($phoneNumber === null) {
2669 2393
            $phoneNumber = new PhoneNumber();
2670 2393
        }
2671 2393
        $this->parseHelper($numberToParse, $defaultRegion, $keepRawInput, true, $phoneNumber);
2672 2388
        return $phoneNumber;
2673
    }
2674
2675
    /**
2676
     * Formats a phone number in the specified format using client-defined formatting rules. Note that
2677
     * if the phone number has a country calling code of zero or an otherwise invalid country calling
2678
     * code, we cannot work out things like whether there should be a national prefix applied, or how
2679
     * to format extensions, so we return the national significant number with no formatting applied.
2680
     *
2681
     * @param PhoneNumber $number the phone number to be formatted
2682
     * @param int $numberFormat the format the phone number should be formatted into
2683
     * @param array $userDefinedFormats formatting rules specified by clients
2684
     * @return String the formatted phone number
2685
     */
2686 2
    public function formatByPattern(PhoneNumber $number, $numberFormat, array $userDefinedFormats)
2687
    {
2688 2
        $countryCallingCode = $number->getCountryCode();
2689 2
        $nationalSignificantNumber = $this->getNationalSignificantNumber($number);
2690 2
        if (!$this->hasValidCountryCallingCode($countryCallingCode)) {
2691
            return $nationalSignificantNumber;
2692
        }
2693
        // Note getRegionCodeForCountryCode() is used because formatting information for regions which
2694
        // share a country calling code is contained by only one region for performance reasons. For
2695
        // example, for NANPA regions it will be contained in the metadata for US.
2696 2
        $regionCode = $this->getRegionCodeForCountryCode($countryCallingCode);
2697
        // Metadata cannot be null because the country calling code is valid
2698 2
        $metadata = $this->getMetadataForRegionOrCallingCode($countryCallingCode, $regionCode);
2699
2700 2
        $formattedNumber = "";
2701
2702 2
        $formattingPattern = $this->chooseFormattingPatternForNumber($userDefinedFormats, $nationalSignificantNumber);
2703 2
        if ($formattingPattern === null) {
2704
            // If no pattern above is matched, we format the number as a whole.
2705
            $formattedNumber .= $nationalSignificantNumber;
2706
        } else {
2707 2
            $numFormatCopy = new NumberFormat();
2708
            // Before we do a replacement of the national prefix pattern $NP with the national prefix, we
2709
            // need to copy the rule so that subsequent replacements for different numbers have the
2710
            // appropriate national prefix.
2711 2
            $numFormatCopy->mergeFrom($formattingPattern);
2712 2
            $nationalPrefixFormattingRule = $formattingPattern->getNationalPrefixFormattingRule();
2713 2
            if (mb_strlen($nationalPrefixFormattingRule) > 0) {
2714 1
                $nationalPrefix = $metadata->getNationalPrefix();
2715 1
                if (mb_strlen($nationalPrefix) > 0) {
2716
                    // Replace $NP with national prefix and $FG with the first group ($1).
2717 1
                    $npPatternMatcher = new Matcher(self::NP_PATTERN, $nationalPrefixFormattingRule);
2718 1
                    $nationalPrefixFormattingRule = $npPatternMatcher->replaceFirst($nationalPrefix);
2719 1
                    $fgPatternMatcher = new Matcher(self::FG_PATTERN, $nationalPrefixFormattingRule);
2720 1
                    $nationalPrefixFormattingRule = $fgPatternMatcher->replaceFirst("\\$1");
2721 1
                    $numFormatCopy->setNationalPrefixFormattingRule($nationalPrefixFormattingRule);
2722 1
                } else {
2723
                    // We don't want to have a rule for how to format the national prefix if there isn't one.
2724 1
                    $numFormatCopy->clearNationalPrefixFormattingRule();
2725
                }
2726 1
            }
2727 2
            $formattedNumber .= $this->formatNsnUsingPattern($nationalSignificantNumber, $numFormatCopy, $numberFormat);
2728
        }
2729 2
        $this->maybeAppendFormattedExtension($number, $metadata, $numberFormat, $formattedNumber);
2730 2
        $this->prefixNumberWithCountryCallingCode($countryCallingCode, $numberFormat, $formattedNumber);
2731 2
        return $formattedNumber;
2732
    }
2733
2734
    /**
2735
     * Gets a valid number for the specified region.
2736
     *
2737
     * @param string regionCode  the region for which an example number is needed
2738
     * @return PhoneNumber a valid fixed-line number for the specified region. Returns null when the metadata
2739
     *    does not contain such information, or the region 001 is passed in. For 001 (representing
2740
     *    non-geographical numbers), call {@link #getExampleNumberForNonGeoEntity} instead.
2741
     */
2742 247
    public function getExampleNumber($regionCode)
2743
    {
2744 247
        return $this->getExampleNumberForType($regionCode, PhoneNumberType::FIXED_LINE);
2745
    }
2746
2747
    /**
2748
     * Gets a valid number for the specified region and number type.
2749
     *
2750
     * @param string $regionCode the region for which an example number is needed
2751
     * @param int $type the PhoneNumberType of number that is needed
2752
     * @return PhoneNumber a valid number for the specified region and type. Returns null when the metadata
2753
     *     does not contain such information or if an invalid region or region 001 was entered.
2754
     *     For 001 (representing non-geographical numbers), call
2755
     *     {@link #getExampleNumberForNonGeoEntity} instead.
2756
     */
2757 3164
    public function getExampleNumberForType($regionCode, $type)
2758
    {
2759
        // Check the region code is valid.
2760 3164
        if (!$this->isValidRegionCode($regionCode)) {
2761 1
            return null;
2762
        }
2763 3164
        $desc = $this->getNumberDescByType($this->getMetadataForRegion($regionCode), $type);
0 ignored issues
show
Bug introduced by
It seems like $this->getMetadataForRegion($regionCode) can be null; however, getNumberDescByType() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
2764
        try {
2765 3164
            if ($desc->hasExampleNumber()) {
2766 1788
                return $this->parse($desc->getExampleNumber(), $regionCode);
2767
            }
2768 1377
        } catch (NumberParseException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
2769
        }
2770 1377
        return null;
2771
    }
2772
2773
    /**
2774
     * @param PhoneMetadata $metadata
2775
     * @param int $type PhoneNumberType
2776
     * @return PhoneNumberDesc
2777
     */
2778 3164
    private function getNumberDescByType(PhoneMetadata $metadata, $type)
2779
    {
2780
        switch ($type) {
2781 3164
            case PhoneNumberType::PREMIUM_RATE:
2782 244
                return $metadata->getPremiumRate();
2783 2920
            case PhoneNumberType::TOLL_FREE:
2784 244
                return $metadata->getTollFree();
2785 2676
            case PhoneNumberType::MOBILE:
2786 245
                return $metadata->getMobile();
2787 2432
            case PhoneNumberType::FIXED_LINE:
2788 2432
            case PhoneNumberType::FIXED_LINE_OR_MOBILE:
2789 968
                return $metadata->getFixedLine();
2790 1464
            case PhoneNumberType::SHARED_COST:
2791 244
                return $metadata->getSharedCost();
2792 1220
            case PhoneNumberType::VOIP:
2793 244
                return $metadata->getVoip();
2794 976
            case PhoneNumberType::PERSONAL_NUMBER:
2795 244
                return $metadata->getPersonalNumber();
2796 732
            case PhoneNumberType::PAGER:
2797 244
                return $metadata->getPager();
2798 488
            case PhoneNumberType::UAN:
2799 244
                return $metadata->getUan();
2800 244
            case PhoneNumberType::VOICEMAIL:
2801 244
                return $metadata->getVoicemail();
2802
            default:
2803
                return $metadata->getGeneralDesc();
2804
        }
2805
    }
2806
2807
    /**
2808
     * Gets a valid number for the specified country calling code for a non-geographical entity.
2809
     *
2810
     * @param int $countryCallingCode the country calling code for a non-geographical entity
2811
     * @return PhoneNumber a valid number for the non-geographical entity. Returns null when the metadata
2812
     *    does not contain such information, or the country calling code passed in does not belong
2813
     *    to a non-geographical entity.
2814
     */
2815 10
    public function getExampleNumberForNonGeoEntity($countryCallingCode)
2816
    {
2817 10
        $metadata = $this->getMetadataForNonGeographicalRegion($countryCallingCode);
2818 10
        if ($metadata !== null) {
2819 10
            $desc = $metadata->getGeneralDesc();
2820
            try {
2821 10
                if ($desc->hasExampleNumber()) {
2822 10
                    return $this->parse("+" . $countryCallingCode . $desc->getExampleNumber(), "ZZ");
2823
                }
2824
            } catch (NumberParseException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
2825
            }
2826
        }
2827
        return null;
2828
    }
2829
2830
2831
    /**
2832
     * Takes two phone numbers and compares them for equality.
2833
     *
2834
     * <p>Returns EXACT_MATCH if the country_code, NSN, presence of a leading zero
2835
     * for Italian numbers and any extension present are the same. Returns NSN_MATCH
2836
     * if either or both has no region specified, and the NSNs and extensions are
2837
     * the same. Returns SHORT_NSN_MATCH if either or both has no region specified,
2838
     * or the region specified is the same, and one NSN could be a shorter version
2839
     * of the other number. This includes the case where one has an extension
2840
     * specified, and the other does not. Returns NO_MATCH otherwise. For example,
2841
     * the numbers +1 345 657 1234 and 657 1234 are a SHORT_NSN_MATCH. The numbers
2842
     * +1 345 657 1234 and 345 657 are a NO_MATCH.
2843
     *
2844
     * @param $firstNumberIn PhoneNumber|string First number to compare. If it is a
2845
     * string it can contain formatting, and can have country calling code specified
2846
     * with + at the start.
2847
     * @param $secondNumberIn PhoneNumber|string Second number to compare. If it is a
2848
     * string it can contain formatting, and can have country calling code specified
2849
     * with + at the start.
2850
     * @throws \InvalidArgumentException
2851
     * @return int {MatchType} NOT_A_NUMBER, NO_MATCH,
2852
     */
2853 4
    public function isNumberMatch($firstNumberIn, $secondNumberIn)
2854
    {
2855 4
        if (is_string($firstNumberIn) && is_string($secondNumberIn)) {
2856
            try {
2857 4
                $firstNumberAsProto = $this->parse($firstNumberIn, self::UNKNOWN_REGION);
2858 4
                return $this->isNumberMatch($firstNumberAsProto, $secondNumberIn);
2859 3
            } catch (NumberParseException $e) {
2860 3
                if ($e->getErrorType() === NumberParseException::INVALID_COUNTRY_CODE) {
2861
                    try {
2862 3
                        $secondNumberAsProto = $this->parse($secondNumberIn, self::UNKNOWN_REGION);
2863 2
                        return $this->isNumberMatch($secondNumberAsProto, $firstNumberIn);
2864 3
                    } catch (NumberParseException $e2) {
2865 3
                        if ($e2->getErrorType() === NumberParseException::INVALID_COUNTRY_CODE) {
2866
                            try {
2867 3
                                $firstNumberProto = new PhoneNumber();
2868 3
                                $secondNumberProto = new PhoneNumber();
2869 3
                                $this->parseHelper($firstNumberIn, null, false, false, $firstNumberProto);
2870 3
                                $this->parseHelper($secondNumberIn, null, false, false, $secondNumberProto);
2871 3
                                return $this->isNumberMatch($firstNumberProto, $secondNumberProto);
2872
                            } catch (NumberParseException $e3) {
2873
                                // Fall through and return MatchType::NOT_A_NUMBER
2874
                            }
2875
                        }
2876
                    }
2877
                }
2878
            }
2879 1
            return MatchType::NOT_A_NUMBER;
2880
        }
2881 4
        if ($firstNumberIn instanceof PhoneNumber && is_string($secondNumberIn)) {
2882
            // First see if the second number has an implicit country calling code, by attempting to parse
2883
            // it.
2884
            try {
2885 4
                $secondNumberAsProto = $this->parse($secondNumberIn, self::UNKNOWN_REGION);
2886 2
                return $this->isNumberMatch($firstNumberIn, $secondNumberAsProto);
2887 3
            } catch (NumberParseException $e) {
2888 3
                if ($e->getErrorType() === NumberParseException::INVALID_COUNTRY_CODE) {
2889
                    // The second number has no country calling code. EXACT_MATCH is no longer possible.
2890
                    // We parse it as if the region was the same as that for the first number, and if
2891
                    // EXACT_MATCH is returned, we replace this with NSN_MATCH.
2892 3
                    $firstNumberRegion = $this->getRegionCodeForCountryCode($firstNumberIn->getCountryCode());
2893
                    try {
2894 3
                        if ($firstNumberRegion != self::UNKNOWN_REGION) {
2895 3
                            $secondNumberWithFirstNumberRegion = $this->parse($secondNumberIn, $firstNumberRegion);
2896 3
                            $match = $this->isNumberMatch($firstNumberIn, $secondNumberWithFirstNumberRegion);
2897 3
                            if ($match === MatchType::EXACT_MATCH) {
2898 1
                                return MatchType::NSN_MATCH;
2899
                            }
2900 2
                            return $match;
2901
                        } else {
2902
                            // If the first number didn't have a valid country calling code, then we parse the
2903
                            // second number without one as well.
2904 1
                            $secondNumberProto = new PhoneNumber();
2905 1
                            $this->parseHelper($secondNumberIn, null, false, false, $secondNumberProto);
2906 1
                            return $this->isNumberMatch($firstNumberIn, $secondNumberProto);
2907
                        }
2908
                    } catch (NumberParseException $e2) {
2909
                        // Fall-through to return NOT_A_NUMBER.
2910
                    }
2911
                }
2912
            }
2913
        }
2914 4
        if ($firstNumberIn instanceof PhoneNumber && $secondNumberIn instanceof PhoneNumber) {
2915
            // Make copies of the phone number so that the numbers passed in are not edited.
2916 4
            $firstNumber = new PhoneNumber();
2917 4
            $firstNumber->mergeFrom($firstNumberIn);
2918 4
            $secondNumber = new PhoneNumber();
2919 4
            $secondNumber->mergeFrom($secondNumberIn);
2920
2921
            // First clear raw_input, country_code_source and preferred_domestic_carrier_code fields and any
2922
            // empty-string extensions so that we can use the proto-buffer equality method.
2923 4
            $firstNumber->clearRawInput();
2924 4
            $firstNumber->clearCountryCodeSource();
2925 4
            $firstNumber->clearPreferredDomesticCarrierCode();
2926 4
            $secondNumber->clearRawInput();
2927 4
            $secondNumber->clearCountryCodeSource();
2928 4
            $secondNumber->clearPreferredDomesticCarrierCode();
2929 4
            if ($firstNumber->hasExtension() && mb_strlen($firstNumber->getExtension()) === 0) {
2930 1
                $firstNumber->clearExtension();
2931 1
            }
2932
2933 4
            if ($secondNumber->hasExtension() && mb_strlen($secondNumber->getExtension()) === 0) {
2934 1
                $secondNumber->clearExtension();
2935 1
            }
2936
2937
            // Early exit if both had extensions and these are different.
2938 4
            if ($firstNumber->hasExtension() && $secondNumber->hasExtension() &&
2939 2
                $firstNumber->getExtension() != $secondNumber->getExtension()
2940 4
            ) {
2941 1
                return MatchType::NO_MATCH;
2942
            }
2943
2944 4
            $firstNumberCountryCode = $firstNumber->getCountryCode();
2945 4
            $secondNumberCountryCode = $secondNumber->getCountryCode();
2946
            // Both had country_code specified.
2947 4
            if ($firstNumberCountryCode != 0 && $secondNumberCountryCode != 0) {
0 ignored issues
show
Bug 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 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...
2948 4
                if ($firstNumber->equals($secondNumber)) {
2949 2
                    return MatchType::EXACT_MATCH;
2950 2
                } elseif ($firstNumberCountryCode == $secondNumberCountryCode &&
2951 1
                    $this->isNationalNumberSuffixOfTheOther($firstNumber, $secondNumber)
2952 2
                ) {
2953
                    // A SHORT_NSN_MATCH occurs if there is a difference because of the presence or absence of
2954
                    // an 'Italian leading zero', the presence or absence of an extension, or one NSN being a
2955
                    // shorter variant of the other.
2956 1
                    return MatchType::SHORT_NSN_MATCH;
2957
                }
2958
                // This is not a match.
2959 1
                return MatchType::NO_MATCH;
2960
            }
2961
            // Checks cases where one or both country_code fields were not specified. To make equality
2962
            // checks easier, we first set the country_code fields to be equal.
2963 3
            $firstNumber->setCountryCode($secondNumberCountryCode);
2964
            // If all else was the same, then this is an NSN_MATCH.
2965 3
            if ($firstNumber->equals($secondNumber)) {
2966 1
                return MatchType::NSN_MATCH;
2967
            }
2968 3
            if ($this->isNationalNumberSuffixOfTheOther($firstNumber, $secondNumber)) {
2969 2
                return MatchType::SHORT_NSN_MATCH;
2970
            }
2971 1
            return MatchType::NO_MATCH;
2972
        }
2973
        return MatchType::NOT_A_NUMBER;
2974
    }
2975
2976
    /**
2977
     * Returns true when one national number is the suffix of the other or both are the same.
2978
     * @param PhoneNumber $firstNumber
2979
     * @param PhoneNumber $secondNumber
2980
     * @return bool
2981
     */
2982 3
    private function isNationalNumberSuffixOfTheOther(PhoneNumber $firstNumber, PhoneNumber $secondNumber)
2983
    {
2984 3
        $firstNumberNationalNumber = trim((string)$firstNumber->getNationalNumber());
2985 3
        $secondNumberNationalNumber = trim((string)$secondNumber->getNationalNumber());
2986 3
        return $this->stringEndsWithString($firstNumberNationalNumber, $secondNumberNationalNumber) ||
2987 3
        $this->stringEndsWithString($secondNumberNationalNumber, $firstNumberNationalNumber);
2988
    }
2989
2990 3
    private function stringEndsWithString($hayStack, $needle)
2991
    {
2992 3
        $revNeedle = strrev($needle);
2993 3
        $revHayStack = strrev($hayStack);
2994 3
        return strpos($revHayStack, $revNeedle) === 0;
2995
    }
2996
2997
    /**
2998
     * Returns true if the supplied region supports mobile number portability. Returns false for
2999
     * invalid, unknown or regions that don't support mobile number portability.
3000
     *
3001
     * @param string $regionCode the region for which we want to know whether it supports mobile number
3002
     *                    portability or not.
3003
     * @return bool
3004
     */
3005 3
    public function isMobileNumberPortableRegion($regionCode)
3006
    {
3007 3
        $metadata = $this->getMetadataForRegion($regionCode);
3008 3
        if ($metadata === null) {
3009
            return false;
3010
        }
3011
3012 3
        return $metadata->isMobileNumberPortableRegion();
3013
    }
3014
3015
    /**
3016
     * Check whether a phone number is a possible number given a number in the form of a string, and
3017
     * the region where the number could be dialed from. It provides a more lenient check than
3018
     * {@link #isValidNumber}. See {@link #isPossibleNumber(PhoneNumber)} for details.
3019
     *
3020
     * <p>This method first parses the number, then invokes {@link #isPossibleNumber(PhoneNumber)}
3021
     * with the resultant PhoneNumber object.
3022
     *
3023
     * @param PhoneNumber|string $number the number that needs to be checked, in the form of a string
3024
     * @param string $regionDialingFrom the region that we are expecting the number to be dialed from.
3025
     *     Note this is different from the region where the number belongs.  For example, the number
3026
     *     +1 650 253 0000 is a number that belongs to US. When written in this form, it can be
3027
     *     dialed from any region. When it is written as 00 1 650 253 0000, it can be dialed from any
3028
     *     region which uses an international dialling prefix of 00. When it is written as
3029
     *     650 253 0000, it can only be dialed from within the US, and when written as 253 0000, it
3030
     *     can only be dialed from within a smaller area in the US (Mountain View, CA, to be more
3031
     *     specific).
3032
     * @return boolean true if the number is possible
3033
     */
3034 2
    public function isPossibleNumber($number, $regionDialingFrom = null)
3035
    {
3036 2
        if ($regionDialingFrom !== null && is_string($number)) {
3037
            try {
3038 2
                return $this->isPossibleNumberWithReason(
3039 2
                    $this->parse($number, $regionDialingFrom)
3040 2
                ) === ValidationResult::IS_POSSIBLE;
3041 1
            } catch (NumberParseException $e) {
3042 1
                return false;
3043
            }
3044
        } else {
3045 2
            return $this->isPossibleNumberWithReason($number) === ValidationResult::IS_POSSIBLE;
0 ignored issues
show
Bug introduced by
It seems like $number defined by parameter $number on line 3034 can also be of type string; however, libphonenumber\PhoneNumb...sibleNumberWithReason() does only seem to accept object<libphonenumber\PhoneNumber>, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
3046
        }
3047
    }
3048
3049
3050
    /**
3051
     * Check whether a phone number is a possible number. It provides a more lenient check than
3052
     * {@link #isValidNumber} in the following sense:
3053
     * <ol>
3054
     * <li> It only checks the length of phone numbers. In particular, it doesn't check starting
3055
     *      digits of the number.
3056
     * <li> It doesn't attempt to figure out the type of the number, but uses general rules which
3057
     *      applies to all types of phone numbers in a region. Therefore, it is much faster than
3058
     *      isValidNumber.
3059
     * <li> For fixed line numbers, many regions have the concept of area code, which together with
3060
     *      subscriber number constitute the national significant number. It is sometimes okay to dial
3061
     *      the subscriber number only when dialing in the same area. This function will return
3062
     *      true if the subscriber-number-only version is passed in. On the other hand, because
3063
     *      isValidNumber validates using information on both starting digits (for fixed line
3064
     *      numbers, that would most likely be area codes) and length (obviously includes the
3065
     *      length of area codes for fixed line numbers), it will return false for the
3066
     *      subscriber-number-only version.
3067
     * </ol>
3068
     * @param PhoneNumber $number the number that needs to be checked
3069
     * @return int a ValidationResult object which indicates whether the number is possible
3070
     */
3071 4
    public function isPossibleNumberWithReason(PhoneNumber $number)
3072
    {
3073 4
        $nationalNumber = $this->getNationalSignificantNumber($number);
3074 4
        $countryCode = $number->getCountryCode();
3075
        // Note: For Russian Fed and NANPA numbers, we just use the rules from the default region (US or
3076
        // Russia) since the getRegionCodeForNumber will not work if the number is possible but not
3077
        // valid. This would need to be revisited if the possible number pattern ever differed between
3078
        // various regions within those plans.
3079 4
        if (!$this->hasValidCountryCallingCode($countryCode)) {
3080 1
            return ValidationResult::INVALID_COUNTRY_CODE;
3081
        }
3082
3083 4
        $regionCode = $this->getRegionCodeForCountryCode($countryCode);
3084
        // Metadata cannot be null because the country calling code is valid.
3085 4
        $metadata = $this->getMetadataForRegionOrCallingCode($countryCode, $regionCode);
3086
3087 4
        $possibleNumberPattern = $metadata->getGeneralDesc()->getPossibleNumberPattern();
3088 4
        return $this->testNumberLengthAgainstPattern($possibleNumberPattern, $nationalNumber);
3089
    }
3090
3091
    /**
3092
     * Attempts to extract a valid number from a phone number that is too long to be valid, and resets
3093
     * the PhoneNumber object passed in to that valid version. If no valid number could be extracted,
3094
     * the PhoneNumber object passed in will not be modified.
3095
     * @param PhoneNumber $number a PhoneNumber object which contains a number that is too long to be valid.
3096
     * @return boolean true if a valid phone number can be successfully extracted.
3097
     */
3098 1
    public function truncateTooLongNumber(PhoneNumber $number)
3099
    {
3100 1
        if ($this->isValidNumber($number)) {
3101 1
            return true;
3102
        }
3103 1
        $numberCopy = new PhoneNumber();
3104 1
        $numberCopy->mergeFrom($number);
3105 1
        $nationalNumber = $number->getNationalNumber();
3106
        do {
3107 1
            $nationalNumber = floor($nationalNumber / 10);
3108 1
            $numberCopy->setNationalNumber($nationalNumber);
3109 1
            if ($this->isPossibleNumberWithReason($numberCopy) == ValidationResult::TOO_SHORT || $nationalNumber == 0) {
3110 1
                return false;
3111
            }
3112 1
        } while (!$this->isValidNumber($numberCopy));
3113 1
        $number->setNationalNumber($nationalNumber);
3114 1
        return true;
3115
    }
3116
}
3117