Completed
Push — phoneNumberMatcher ( 6b28c4 )
by Joshua
18:40
created

PhoneNumberMatcherTest   D

Complexity

Total Complexity 85

Size/Duplication

Total Lines 1108
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 7

Importance

Changes 0
Metric Value
wmc 85
lcom 2
cbo 7
dl 0
loc 1108
rs 4.4102
c 0
b 0
f 0

70 Methods

Rating   Name   Duplication   Size   Complexity  
A setUp() 0 8 1
B testContainsMoreThanOneSlashInNationalNumber() 0 44 1
A testFindNationalNumber() 0 23 1
A testFindWithInternationalPrefixes() 0 20 1
A testFindWithLeadingZero() 0 6 1
A testFindNationalNumberArgentina() 0 19 1
A testFindWithXInNumber() 0 12 1
A testFindNumbersMexico() 0 12 1
A testFindNumbersWithPlusWithNoRegion() 0 8 1
B testFindExtensions() 0 28 1
A testFindInterspersedWithSpace() 0 4 1
A testIntermediateParsePositions() 0 18 3
B testFourMatchesInARow() 0 26 1
A testMatchesFoundWithMultipleSpaces() 0 16 1
A testMatchWithSurroundingZipcodes() 0 22 1
A dataLatinLetters() 0 17 1
A testIsLatinLetter() 0 5 1
A testMatchesWithSurroundingLatinChars() 0 15 1
A testMoneyNotSeenAsPhoneNumber() 0 10 1
A testPercentageNotSeenAsPhoneNumber() 0 7 1
A testPhoneNumberWithLeadingOrTrailingMoneyMatches() 0 10 1
B testMatchesWithSurroundingLatinCharsAndLeadingPunctuation() 0 26 1
A testMatchesWithSurroundingChineseChars() 0 10 1
A testMatchesWithSurroundingPunctuation() 0 11 1
A testMatchesMultiplePhoneNumbersSeparatedByPhoneNumberPunctuation() 0 23 1
A testDoesNotMatchMultiplePhoneNumbersSeparatedWithNoWhiteSpace() 0 8 1
A dataImpossibleCases() 0 22 1
A dataPossibleOnlyCases() 0 13 1
B dataValidCases() 0 24 1
A dataStrictGroupingCases() 0 18 1
B dataExactGroupingCases() 0 31 1
A data_testMatchesWithPossibleLeniency() 0 7 1
A testMatchesWithPossibleLeniency() 0 4 1
A testNonMatchesWithPossibleLeniency() 0 4 1
A data_testMatchesWithValidLeniency() 0 6 1
A testMatchesWithValidLeniency() 0 4 1
A data_testNonMatchesWithValidLeniency() 0 5 1
A testNonMatchesWithValidLeniency() 0 4 1
A data_testMatchesWithStrictGroupingLeniency() 0 5 1
A testMatchesWithStrictGroupingLeniency() 0 4 1
A data_testNonMatchesWithStrictGroupLeniency() 0 6 1
A testNonMatchesWithStrictGroupLeniency() 0 4 1
A testMatchesWithExactGroupingLeniency() 0 4 1
A data_testNonMatchesExactGroupLeniency() 0 7 1
A testNonMatchesExactGroupLeniency() 0 4 1
A doTestNumberMatchesForLeniency() 0 10 1
A doTestNumberNonMatchesForLeniency() 0 9 1
B findMatchesInContexts() 0 22 5
A testNonMatchingBracketsAreInvalid() 0 19 1
A testNoMatchIfRegionIsNull() 0 6 1
A testNoMatchInEmptyString() 0 5 1
A testNoMatchIfNoNumber() 0 5 1
B testSequences() 0 24 1
A testNullInput() 0 5 1
A testMaxMatches() 0 17 2
A testMaxMatchesInvalid() 0 15 3
A testMaxMatchesMixed() 0 21 3
A testNonPlusPrefixedNumbersNotFoundForInvalidRegion() 0 8 1
A testEmptyIteration() 0 7 1
A testSingleIteration() 0 12 1
A testDoubleIteration() 0 16 1
A assertMatchProperties() 0 7 1
A assertEqualRange() 0 14 1
A doTestFindInContext() 0 9 2
B findPossibleInContext() 0 33 1
A findValidInContext() 0 16 1
B doTestInContext() 0 24 2
A ensureTermination() 0 14 3
A findNumbersForLeniency() 0 4 1
A hasNoMatches() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like PhoneNumberMatcherTest often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use PhoneNumberMatcherTest, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace libphonenumber\Tests\core;
4
5
use libphonenumber\CountryCodeSource;
6
use libphonenumber\CountryCodeToRegionCodeMapForTesting;
7
use libphonenumber\Leniency;
8
use libphonenumber\PhoneNumber;
9
use libphonenumber\PhoneNumberMatch;
10
use libphonenumber\PhoneNumberMatcher;
11
use libphonenumber\PhoneNumberUtil;
12
use libphonenumber\RegionCode;
13
14
class PhoneNumberMatcherTest extends \PHPUnit_Framework_TestCase
15
{
16
    /**
17
     * @var PhoneNumberUtil
18
     */
19
    protected $phoneUtil;
20
21
    public function setUp()
22
    {
23
        PhoneNumberUtil::resetInstance();
24
        $this->phoneUtil = PhoneNumberUtil::getInstance(
25
            PhoneNumberUtilTest::TEST_META_DATA_FILE_PREFIX,
26
            CountryCodeToRegionCodeMapForTesting::$countryCodeToRegionCodeMapForTesting
27
        );
28
    }
29
30
    public function testContainsMoreThanOneSlashInNationalNumber()
31
    {
32
        // A date should return true.
33
        $number = new PhoneNumber();
34
        $number->setCountryCode(1);
35
        $number->setCountryCodeSource(CountryCodeSource::FROM_DEFAULT_COUNTRY);
36
        $candidate = "1/05/2013";
37
        $this->assertTrue(PhoneNumberMatcher::containsMoreThanOneSlashInNationalNumber($number, $candidate));
38
39
        // Here, the country code source thinks it started with a country calling code, but this is not
40
        // the same as the part before the slash, so it's still true.
41
        $number = new PhoneNumber();
42
        $number->setCountryCodeSource(274);
43
        $number->setCountryCodeSource(CountryCodeSource::FROM_NUMBER_WITHOUT_PLUS_SIGN);
44
        $candidate = "27/4/2013";
45
        $this->assertTrue(PhoneNumberMatcher::containsMoreThanOneSlashInNationalNumber($number, $candidate));
46
47
        // Now it should be false, because the first slash is after the country calling code.
48
        $number = new PhoneNumber();
49
        $number->setCountryCode(49);
50
        $number->setCountryCodeSource(CountryCodeSource::FROM_NUMBER_WITH_PLUS_SIGN);
51
        $candidate = "49/69/2013";
52
        $this->assertFalse(PhoneNumberMatcher::containsMoreThanOneSlashInNationalNumber($number, $candidate));
53
54
        $number = new PhoneNumber();
55
        $number->setCountryCode(49);
56
        $number->setCountryCodeSource(CountryCodeSource::FROM_NUMBER_WITHOUT_PLUS_SIGN);
57
        $candidate = "+49/69/2013";
58
        $this->assertFalse(PhoneNumberMatcher::containsMoreThanOneSlashInNationalNumber($number, $candidate));
59
60
        $candidate = "+ 49/69/2013";
61
        $this->assertFalse(PhoneNumberMatcher::containsMoreThanOneSlashInNationalNumber($number, $candidate));
62
63
        $candidate = "+ 49/69/20/13";
64
        $this->assertTrue(PhoneNumberMatcher::containsMoreThanOneSlashInNationalNumber($number, $candidate));
65
66
        // Here, the first group is not assumed to be the country calling code, even though it is the
67
        // same as it, so this should return true.
68
        $number = new PhoneNumber();
69
        $number->setCountryCode(49);
70
        $number->setCountryCodeSource(CountryCodeSource::FROM_DEFAULT_COUNTRY);
71
        $candidate = "49/69/2013";
72
        $this->assertTrue(PhoneNumberMatcher::containsMoreThanOneSlashInNationalNumber($number, $candidate));
73
    }
74
75
    public function testFindNationalNumber()
76
    {
77
        // same cases as in testParseNationalNumber
78
        $this->doTestFindInContext("033316005", RegionCode::NZ);
79
        // ("33316005", RegionCode.NZ) is omitted since the national prefix is obligatory for these
80
        // types of numbers in New Zealand.
81
        // National prefix attached and some formatting present.
82
        $this->doTestFindInContext("03-331 6005", RegionCode::NZ);
83
        $this->doTestFindInContext("03 331 6005", RegionCode::NZ);
84
        // Testing international prefixes.
85
        // Should strip country code.
86
        $this->doTestFindInContext("0064 3 331 6005", RegionCode::NZ);
87
        // Try again, but this time we have an international number with Region Code US. It should
88
        // recognize the country code and parse accordingly.
89
        $this->doTestFindInContext("01164 3 331 6005", RegionCode::US);
90
        $this->doTestFindInContext("+64 3 331 6005", RegionCode::US);
91
92
        $this->doTestFindInContext("64(0)64123456", RegionCode::NZ);
93
        // Check that using a "/" is fine in a phone number.
94
        // Note that real Polish numbers do *not* start with a 0.
95
        $this->doTestFindInContext("0123/456789", RegionCode::PL);
96
        $this->doTestFindInContext("123-456-7890", RegionCode::US);
97
    }
98
99
    public function testFindWithInternationalPrefixes()
100
    {
101
        $this->doTestFindInContext("+1 (650) 333-6000", RegionCode::NZ);
102
        $this->doTestFindInContext("1-650-333-6000", RegionCode::US);
103
        // Calling the US number from Singapore by using different service providers
104
        // 1st test: calling using SingTel IDD service (IDD is 001)
105
        $this->doTestFindInContext("0011-650-333-6000", RegionCode::SG);
106
        // 2nd test: calling using StarHub IDD service (IDD is 008)
107
        $this->doTestFindInContext("0081-650-333-6000", RegionCode::SG);
108
        // 3rd test: calling using SingTel V019 service (IDD is 019)
109
        $this->doTestFindInContext("0191-650-333-6000", RegionCode::SG);
110
        // Calling the US number from Poland
111
        $this->doTestFindInContext("0~01-650-333-6000", RegionCode::PL);
112
        // Using "++" at the start.
113
        $this->doTestFindInContext("++1 (650) 333-6000", RegionCode::PL);
114
        // Using a full-width plus sign.
115
        $this->doTestFindInContext("\xEF\xBC\x8B1 (650) 333-6000", RegionCode::SG);
116
        // The whole number, including punctuation, is here represented in full-width form.
117
        $this->doTestFindInContext("+1 (650) 333-6000", RegionCode::SG);
118
    }
119
120
    public function testFindWithLeadingZero()
121
    {
122
        $this->doTestFindInContext("+39 02-36618 300", RegionCode::NZ);
123
        $this->doTestFindInContext("02-36618 300", RegionCode::IT);
124
        $this->doTestFindInContext("312 345 678", RegionCode::IT);
125
    }
126
127
    public function testFindNationalNumberArgentina()
128
    {
129
        // Test parsing mobile numbers of Argentina.
130
        $this->doTestFindInContext("+54 9 343 555 1212", RegionCode::AR);
131
        $this->doTestFindInContext("0343 15 555 1212", RegionCode::AR);
132
133
        $this->doTestFindInContext("+54 9 3715 65 4320", RegionCode::AR);
134
        $this->doTestFindInContext("03715 15 65 4320", RegionCode::AR);
135
136
        // Test parsing fixed-line numbers of Argentina.
137
        $this->doTestFindInContext("+54 11 3797 0000", RegionCode::AR);
138
        $this->doTestFindInContext("011 3797 0000", RegionCode::AR);
139
140
        $this->doTestFindInContext("+54 3715 65 4321", RegionCode::AR);
141
        $this->doTestFindInContext("03715 65 4321", RegionCode::AR);
142
143
        $this->doTestFindInContext("+54 23 1234 0000", RegionCode::AR);
144
        $this->doTestFindInContext("023 1234 0000", RegionCode::AR);
145
    }
146
147
    public function testFindWithXInNumber()
148
    {
149
        $this->doTestFindInContext("(0xx) 123456789", RegionCode::AR);
150
        // A case where x denotes both carrier codes and extension symbol.
151
        $this->doTestFindInContext("(0xx) 123456789 x 1234", RegionCode::AR);
152
153
        // This test is intentionally constructed such that the number of digit after xx is larger than
154
        // 7, so that the number won't be mistakenly treated as an extension, as we allow extensions up
155
        // to 7 digits. This assumption is okay for now as all the countries where a carrier selection
156
        // code is written in the form of xx have a national significant number of length larger than 7.
157
        $this->doTestFindInContext("011xx5481429712", RegionCode::US);
158
    }
159
160
    public function testFindNumbersMexico()
161
    {
162
        // Test parsing fixed-line numbers of Mexico.
163
        $this->doTestFindInContext("+52 (449)978-0001", RegionCode::MX);
164
        $this->doTestFindInContext("01 (449)978-0001", RegionCode::MX);
165
        $this->doTestFindInContext("(449)978-0001", RegionCode::MX);
166
167
        // Test parsing mobile numbers of Mexico.
168
        $this->doTestFindInContext("+52 1 33 1234-5678", RegionCode::MX);
169
        $this->doTestFindInContext("044 (33) 1234-5678", RegionCode::MX);
170
        $this->doTestFindInContext("045 33 1234-5678", RegionCode::MX);
171
    }
172
173
    public function testFindNumbersWithPlusWithNoRegion()
174
    {
175
        // RegionCode.ZZ is allowed only if the number starts with a '+' - then the country code can be
176
        // calculated.
177
        $this->doTestFindInContext("+64 3 331 6005", RegionCode::ZZ);
178
        // Null is also allowed for the region code in these cases.
179
        $this->doTestFindInContext("+64 3 331 6005", null);
180
    }
181
182
    public function testFindExtensions()
183
    {
184
        $this->doTestFindInContext("03 331 6005 ext 3456", RegionCode::NZ);
185
        $this->doTestFindInContext("03-3316005x3456", RegionCode::NZ);
186
        $this->doTestFindInContext("03-3316005 int.3456", RegionCode::NZ);
187
        $this->doTestFindInContext("03 3316005 #3456", RegionCode::NZ);
188
        $this->doTestFindInContext("0~0 1800 7493 524", RegionCode::PL);
189
        $this->doTestFindInContext("(1800) 7493.524", RegionCode::US);
190
        // Check that the last instance of an extension token is matched.
191
        $this->doTestFindInContext("0~0 1800 7493 524 ~1234", RegionCode::PL);
192
        // Verifying bug-fix where the last digit of a number was previously omitted if it was a 0 when
193
        // extracting the extension. Also verifying a few different cases of extensions.
194
        $this->doTestFindInContext("+44 2034567890x456", RegionCode::NZ);
195
        $this->doTestFindInContext("+44 2034567890x456", RegionCode::GB);
196
        $this->doTestFindInContext("+44 2034567890 x456", RegionCode::GB);
197
        $this->doTestFindInContext("+44 2034567890 X456", RegionCode::GB);
198
        $this->doTestFindInContext("+44 2034567890 X 456", RegionCode::GB);
199
        $this->doTestFindInContext("+44 2034567890 X  456", RegionCode::GB);
200
        $this->doTestFindInContext("+44 2034567890  X 456", RegionCode::GB);
201
202
        $this->doTestFindInContext("(800) 901-3355 x 7246433", RegionCode::US);
203
        $this->doTestFindInContext("(800) 901-3355 , ext 7246433", RegionCode::US);
204
        $this->doTestFindInContext("(800) 901-3355 ,extension 7246433", RegionCode::US);
205
        // The next test differs from PhoneNumberUtil -> when matching we don't consider a lone comma to
206
        // indicate an extension, although we accept it when parsing.
207
        $this->doTestFindInContext("(800) 901-3355 ,x 7246433", RegionCode::US);
208
        $this->doTestFindInContext("(800) 901-3355 ext: 7246433", RegionCode::US);
209
    }
210
211
    public function testFindInterspersedWithSpace()
212
    {
213
        $this->doTestFindInContext("0 3   3 3 1   6 0 0 5", RegionCode::NZ);
214
    }
215
216
    /**
217
     * Test matching behaviour when starting in the middle of a phone number.
218
     */
219
    public function testIntermediateParsePositions()
220
    {
221
        $text = "Call 033316005  or 032316005!";
222
        //       |    |    |    |    |    |
1 ignored issue
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
223
        //       0    5   10   15   20   25
1 ignored issue
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
224
225
        // Iterate over all possible indices.
226
        for ($i = 0; $i <= 5; $i++) {
227
            $this->assertEqualRange($text, $i, 5, 14);
228
        }
229
        // 7 and 8 digits in a row are still parsed as number.
230
        $this->assertEqualRange($text, 6, 6, 14);
231
        $this->assertEqualRange($text, 7, 7, 14);
232
        // Anything smaller is skipped to the second instance.
233
        for ($i = 8; $i <= 19; $i++) {
234
            $this->assertEqualRange($text, $i, 19, 28);
235
        }
236
    }
237
238
    public function testFourMatchesInARow()
239
    {
240
        $number1 = "415-666-7777";
241
        $number2 = "800-443-1223";
242
        $number3 = "212-443-1223";
243
        $number4 = "650-443-1223";
244
        $text = $number1 . " - " . $number2 . " - " . $number3 . " - " . $number4;
245
246
        $iterator = $this->phoneUtil->findNumbers($text, RegionCode::US);
247
248
        $iterator->next();
249
        $match = $iterator->current();
250
        $this->assertMatchProperties($match, $text, $number1, RegionCode::US);
251
252
        $iterator->next();
253
        $match = $iterator->current();
254
        $this->assertMatchProperties($match, $text, $number2, RegionCode::US);
255
256
        $iterator->next();
257
        $match = $iterator->current();
258
        $this->assertMatchProperties($match, $text, $number3, RegionCode::US);
259
260
        $iterator->next();
261
        $match = $iterator->current();
262
        $this->assertMatchProperties($match, $text, $number4, RegionCode::US);
263
    }
264
265
    public function testMatchesFoundWithMultipleSpaces()
266
    {
267
        $number1 = "(415) 666-7777";
268
        $number2 = "(800) 443-1223";
269
        $text = $number1 . " " . $number2;
270
271
        $iterator = $this->phoneUtil->findNumbers($text, RegionCode::US);
272
273
        $iterator->next();
274
        $match = $iterator->current();
275
        $this->assertMatchProperties($match, $text, $number1, RegionCode::US);
276
277
        $iterator->next();
278
        $match = $iterator->current();
279
        $this->assertMatchProperties($match, $text, $number2, RegionCode::US);
280
    }
281
282
    public function testMatchWithSurroundingZipcodes()
283
    {
284
        $number = "415-666-7777";
285
        $zipPreceding = "My address is CA 34215 - " . $number . " is my number.";
286
287
        $iterator = $this->phoneUtil->findNumbers($zipPreceding, RegionCode::US);
288
289
        $iterator->next();
290
        $match = $iterator->current();
291
        $this->assertMatchProperties($match, $zipPreceding, $number, RegionCode::US);
292
293
        // Now repeat, but this time the phone number has spaces in it. It should still be found.
294
        $number = "(415) 666 7777";
295
296
        $zipFollowing = "My number is " . $number . ". 34215 is my zip-code.";
297
298
        $iterator = $this->phoneUtil->findNumbers($zipFollowing, RegionCode::US);
299
300
        $iterator->next();
301
        $match = $iterator->current();
302
        $this->assertMatchProperties($match, $zipFollowing, $number, RegionCode::US);
303
    }
304
305
    public function dataLatinLetters()
306
    {
307
        return array(
308
            array('c', true),
309
            array('C', true),
310
            array("\xC3\x89", true),
311
            array("\xCC\x81", true), // Combining acute accent
312
            // Punctuation, digits and white-space are not considered "latin letters".
313
            array(':', false),
314
            array('5', false),
315
            array('-', false),
316
            array('.', false),
317
            array(' ', false),
318
            array("\xE6\x88\x91", false),  // Chinese character
319
            array("\xE3\x81\xAE", false),  // Hiragana letter no
320
        );
321
    }
322
323
    /**
324
     * @dataProvider dataLatinLetters
325
     * @param string $letter
326
     * @param string $expectedResult
327
     */
328
    public function testIsLatinLetter($letter, $expectedResult)
329
    {
330
        $this->assertEquals($expectedResult, PhoneNumberMatcher::isLatinLetter($letter),
331
            "{$letter} should return {$expectedResult}");
332
    }
333
334
    public function testMatchesWithSurroundingLatinChars()
335
    {
336
        $possibleOnlyContexts = array();
337
        $possibleOnlyContexts[] = array("abc", "def");
338
        $possibleOnlyContexts[] = array("abc", "");
339
        $possibleOnlyContexts[] = array("", "def");
340
        // Latin capital letter e with an acute accent.
341
        $possibleOnlyContexts[] = array("\xC3\x89", "");
342
        // e with an acute accent decomposed (with combining mark).
343
        $possibleOnlyContexts[] = array("e\xCC\x81", "");
344
345
        // Numbers should not be considered valid, if they are surrounded by Latin characters, but
346
        // should be considered possible.
347
        $this->findMatchesInContexts($possibleOnlyContexts, false, true);
348
    }
349
350
    public function testMoneyNotSeenAsPhoneNumber()
351
    {
352
        $possibleOnlyContexts = array();
353
        $possibleOnlyContexts[] = array("$", "");
354
        $possibleOnlyContexts[] = array("", "$");
355
        $possibleOnlyContexts[] = array("\xC2\xA3", "");  // Pound sign
356
        $possibleOnlyContexts[] = array("\xC2\xA5", "");  // Yen sign
357
358
        $this->findMatchesInContexts($possibleOnlyContexts, false, true);
359
    }
360
361
    public function testPercentageNotSeenAsPhoneNumber()
362
    {
363
        $possibleOnlyContexts = array();
364
        $possibleOnlyContexts[] = array("", "%");
365
        // Numbers followed by % should be dropped
366
        $this->findMatchesInContexts($possibleOnlyContexts, false, true);
367
    }
368
369
    public function testPhoneNumberWithLeadingOrTrailingMoneyMatches()
370
    {
371
        // Because of the space after the 20 (or before the 100) these dollar amounts should not stop
372
        // the actual number from being found.
373
        $contexts = array();
374
        $contexts[] = array('$20 ', '');
375
        $contexts[] = array('', ' 100$');
376
377
        $this->findMatchesInContexts($contexts, true, true);
378
    }
379
380
    public function testMatchesWithSurroundingLatinCharsAndLeadingPunctuation()
381
    {
382
        // Contexts with trailing characters. Leading characters are okay here since the numbers we will
383
        // insert start with punctuation, but trailing characters are still not allowed.
384
        $possibleOnlyContexts = array();
385
        $possibleOnlyContexts[] = array("abc", "def");
386
        $possibleOnlyContexts[] = array("", "def");
387
        $possibleOnlyContexts[] = array("", "\xC3\x89");
388
389
        // Numbers should not be considered valid, if they have trailing Latin characters, but should be
390
        // considered possible.
391
        $numberWithPlus = "+14156667777";
392
        $numberWithBrackets = "(415)6667777";
393
        $this->findMatchesInContexts($possibleOnlyContexts, false, true, RegionCode::US, $numberWithPlus);
394
        $this->findMatchesInContexts($possibleOnlyContexts, false, true, RegionCode::US, $numberWithBrackets);
395
396
        $validContexts = array();
397
        $validContexts[] = array("abc", "");
398
        $validContexts[] = array("\xC3\x89", "");
399
        $validContexts[] = array("\xC3\x89", "."); // Trailing punctuation.
400
        $validContexts[] = array("\xC3\x89", " def"); // Trailing white space.
401
402
        // Numbers should be considered valid, since they start with punctuation.
403
        $this->findMatchesInContexts($validContexts, true, true, RegionCode::US, $numberWithPlus);
404
        $this->findMatchesInContexts($validContexts, true, true, RegionCode::US, $numberWithBrackets);
405
    }
406
407
    public function testMatchesWithSurroundingChineseChars()
408
    {
409
        $validContexts = array();
410
        $validContexts[] = array("我的电话号码是", "");
411
        $validContexts[] = array("", "是我的电话号码");
412
        $validContexts[] = array("请拨打", "我在明天");
413
414
        // Numbers should be considered valid, since they are surrounded by Chinese.
415
        $this->findMatchesInContexts($validContexts, true, true);
416
    }
417
418
    public function testMatchesWithSurroundingPunctuation()
419
    {
420
        $validContexts = array();
421
        $validContexts[] = array("My number-", ""); // At end of text
422
        $validContexts[] = array("", ".Nice day."); // At start of text
423
        $validContexts[] = array("Tel:", "."); // Punctuation surrounds number.
424
        $validContexts[] = array("Tel: ", " on Saturdays."); // White-space is also fine.
425
426
        // Numbers should be considered valid, since they are surrounded by punctuation.
427
        $this->findMatchesInContexts($validContexts, true, true);
428
    }
429
430
    public function testMatchesMultiplePhoneNumbersSeparatedByPhoneNumberPunctuation()
431
    {
432
        $text = "Call 650-253-4561 -- 455-234-3451";
433
        $region = RegionCode::US;
434
435
        $number1 = new PhoneNumber();
436
        $number1->setCountryCode($this->phoneUtil->getCountryCodeForRegion($region));
437
        $number1->setNationalNumber(6502534561);
438
        $match1 = new PhoneNumberMatch(5, "650-253-4561", $number1);
439
440
        $number2 = new PhoneNumber();
441
        $number2->setCountryCode($this->phoneUtil->getCountryCodeForRegion($region));
442
        $number2->setNationalNumber(4552343451);
443
        $match2 = new PhoneNumberMatch(21, "455-234-3451", $number2);
444
445
        $matches = $this->phoneUtil->findNumbers($text, $region);
446
447
        $matches->next();
448
        $this->assertEquals($match1, $matches->current());
449
450
        $matches->next();
451
        $this->assertEquals($match2, $matches->current());
452
    }
453
454
    public function testDoesNotMatchMultiplePhoneNumbersSeparatedWithNoWhiteSpace()
455
    {
456
        // No white-space found between numbers - neither is found.
457
        $text = "Call 650-253-4561--455-234-3451";
458
        $region = RegionCode::US;
459
460
        $this->assertTrue($this->hasNoMatches($this->phoneUtil->findNumbers($text, $region)));
461
    }
462
463
    /**
464
     * Strings with number-like things that shouldn't be found under any level.
465
     * @return array
466
     */
467
    public function dataImpossibleCases()
468
    {
469
        return array(
470
            array("12345", RegionCode::US),
471
            array("23456789", RegionCode::US),
472
            array("234567890112", RegionCode::US),
473
            array("650+253+1234", RegionCode::US),
474
            array("3/10/1984", RegionCode::CA),
475
            array("03/27/2011", RegionCode::US),
476
            array("31/8/2011", RegionCode::US),
477
            array("1/12/2011", RegionCode::US),
478
            array("10/12/82", RegionCode::DE),
479
            array("650x2531234", RegionCode::US),
480
            array("2012-01-02 08:00", RegionCode::US),
481
            array("2012/01/02 08:00", RegionCode::US),
482
            array("20120102 08:00", RegionCode::US),
483
            array("2014-04-12 04:04 PM", RegionCode::US),
484
            array("2014-04-12 &nbsp;04:04 PM", RegionCode::US),
485
            array("2014-04-12 &nbsp;04:04 PM", RegionCode::US),
486
            array("2014-04-12  04:04 PM", RegionCode::US),
487
        );
488
    }
489
490
    /**
491
     * Strings with number-like things that should only be found under "possible".
492
     * @return array
493
     */
494
    public function dataPossibleOnlyCases()
495
    {
496
        return array(
497
            // US numbers cannot start with 7 in the test metadata to be valid.
498
            array("7121115678", RegionCode::US),
499
            // 'X' should not be found in numbers at leniencies stricter than POSSIBLE, unless it represents
500
            // a carrier code or extension.
501
            array("1650 x 253 - 1234", RegionCode::US),
502
            array("650 x 253 - 1234", RegionCode::US),
503
            array("6502531x234", RegionCode::US),
504
            array("(20) 3346 1234", RegionCode::GB),  // Non-optional NP omitted
505
        );
506
    }
507
508
    /**
509
     * Strings with number-like things that should only be found up to and including the "valid"
510
     * leniency level.
511
     * @return array
512
     */
513
    public function dataValidCases()
514
    {
515
        return array(
516
            array("65 02 53 00 00", RegionCode::US),
517
            array("6502 538365", RegionCode::US),
518
            array("650//253-1234", RegionCode::US),  // 2 slashes are illegal at higher levels
519
            array("650/253/1234", RegionCode::US),
520
            array("9002309. 158", RegionCode::US),
521
            array("12 7/8 - 14 12/34 - 5", RegionCode::US),
522
            array("12.1 - 23.71 - 23.45", RegionCode::US),
523
            array("800 234 1 111x1111", RegionCode::US),
524
            array("1979-2011 100", RegionCode::US),
525
            array("+494949-4-94", RegionCode::DE),  // National number in wrong format
526
            array("4156666-777", RegionCode::US),
527
            array("2012-0102 08", RegionCode::US),  // Very strange formatting.
528
            array("2012-01-02 08", RegionCode::US),
529
            // Breakdown assistance number with unexpected formatting.
530
            array("1800-1-0-10 22", RegionCode::AU),
531
            array("030-3-2 23 12 34", RegionCode::DE),
532
            array("03 0 -3 2 23 12 34", RegionCode::DE),
533
            array("(0)3 0 -3 2 23 12 34", RegionCode::DE),
534
            array("0 3 0 -3 2 23 12 34", RegionCode::DE),
535
        );
536
    }
537
538
    /**
539
     * Strings with number-like things that should only be found up to and including the
540
     * "strict_grouping" leniency level.
541
     * @return array
542
     */
543
    public function dataStrictGroupingCases()
544
    {
545
        return array(
546
            array("(415) 6667777", RegionCode::US),
547
            array("415-6667777", RegionCode::US),
548
            // Should be found by strict grouping but not exact grouping, as the last two groups are
549
            // formatted together as a block.
550
            array("0800-2491234", RegionCode::DE),
551
            // Doesn't match any formatting in the test file, but almost matches an alternate format (the
552
            // last two groups have been squashed together here).
553
            array("0900-1 123123", RegionCode::DE),
554
            array("(0)900-1 123123", RegionCode::DE),
555
            array("0 900-1 123123", RegionCode::DE),
556
            // NDC also found as part of the country calling code; this shouldn't ruin the grouping
557
            // expectations.
558
            array("+33 3 34 2312", RegionCode::FR),
559
        );
560
    }
561
562
    /**
563
     * Strings with number-like things that should be found at all levels.
564
     * @return array
565
     */
566
    public function dataExactGroupingCases()
567
    {
568
        return array(
569
            array("4156667777", RegionCode::US),
570
            array("415-666-7777", RegionCode::US),
571
            array("4156667777", RegionCode::US),
572
            array("4156667777 x 123", RegionCode::US),
573
            array("415-666-7777", RegionCode::US),
574
            array("415/666-7777", RegionCode::US),
575
            array("415-666-7777 ext. 503", RegionCode::US),
576
            array("1 415 666 7777 x 123", RegionCode::US),
577
            array("+1 415-666-7777", RegionCode::US),
578
            array("+494949 49", RegionCode::DE),
579
            array("+49-49-34", RegionCode::DE),
580
            array("+49-4931-49", RegionCode::DE),
581
            array("04931-49", RegionCode::DE),  // With National Prefix
582
            array("+49-494949", RegionCode::DE),  // One group with country code
583
            array("+49-494949 ext. 49", RegionCode::DE),
584
            array("+49494949 ext. 49", RegionCode::DE),
585
            array("0494949", RegionCode::DE),
586
            array("0494949 ext. 49", RegionCode::DE),
587
            array("01 (33) 3461 2234", RegionCode::MX),  // Optional NP present
588
            array("(33) 3461 2234", RegionCode::MX),  // Optional NP omitted
589
            array("1800-10-10 22", RegionCode::AU),  // Breakdown assistance number.
590
            // Doesn't match any formatting in the test file, but matches an alternate format exactly.
591
            array("0900-1 123 123", RegionCode::DE),
592
            array("(0)900-1 123 123", RegionCode::DE),
593
            array("0 900-1 123 123", RegionCode::DE),
594
            array("+33 3 34 23 12", RegionCode::FR),
595
        );
596
    }
597
598
    public function data_testMatchesWithPossibleLeniency()
599
    {
600
        return $this->dataStrictGroupingCases()
601
        + $this->dataExactGroupingCases()
602
        + $this->dataValidCases()
603
        + $this->dataPossibleOnlyCases();
604
    }
605
606
    /**
607
     * @param string $rawString
608
     * @param string $region
609
     * @dataProvider data_testMatchesWithPossibleLeniency
610
     */
611
    public function testMatchesWithPossibleLeniency($rawString, $region)
612
    {
613
        $this->doTestNumberMatchesForLeniency($rawString, $region, Leniency::POSSIBLE());
614
    }
615
616
    /**
617
     * @param string $rawString
618
     * @param string $region
619
     * @dataProvider dataImpossibleCases
620
     */
621
    public function testNonMatchesWithPossibleLeniency($rawString, $region)
622
    {
623
        $this->doTestNumberNonMatchesForLeniency($rawString, $region, Leniency::POSSIBLE());
624
    }
625
626
    public function data_testMatchesWithValidLeniency()
627
    {
628
        return $this->dataStrictGroupingCases()
629
        + $this->dataExactGroupingCases()
630
        + $this->dataValidCases();
631
    }
632
633
    /**
634
     * @param string $rawString
635
     * @param string $region
636
     * @dataProvider data_testMatchesWithValidLeniency
637
     */
638
    public function testMatchesWithValidLeniency($rawString, $region)
639
    {
640
        $this->doTestNumberMatchesForLeniency($rawString, $region, Leniency::VALID());
641
    }
642
643
    public function data_testNonMatchesWithValidLeniency()
644
    {
645
        return $this->dataImpossibleCases()
646
            + $this->dataPossibleOnlyCases();
647
    }
648
649
    /**
650
     * @param $rawString
651
     * @param $region
652
     * @dataProvider data_testNonMatchesWithValidLeniency
653
     */
654
    public function testNonMatchesWithValidLeniency($rawString, $region)
655
    {
656
        $this->doTestNumberNonMatchesForLeniency($rawString, $region, Leniency::VALID());
657
    }
658
659
    public function data_testMatchesWithStrictGroupingLeniency()
660
    {
661
        return $this->dataStrictGroupingCases()
662
            + $this->dataExactGroupingCases();
663
    }
664
665
    /**
666
     * @param string $rawString
667
     * @param string $region
668
     * @dataProvider data_testMatchesWithStrictGroupingLeniency
669
     */
670
    public function testMatchesWithStrictGroupingLeniency($rawString, $region)
671
    {
672
        $this->doTestNumberMatchesForLeniency($rawString, $region, Leniency::STRICT_GROUPING());
673
    }
674
675
    public function data_testNonMatchesWithStrictGroupLeniency()
676
    {
677
        return $this->dataImpossibleCases()
678
            + $this->dataPossibleOnlyCases()
679
            + $this->dataValidCases();
680
    }
681
682
    /**
683
     * @param string $rawString
684
     * @param string $region
685
     * @dataProvider data_testNonMatchesWithStrictGroupLeniency
686
     */
687
    public function testNonMatchesWithStrictGroupLeniency($rawString, $region)
688
    {
689
        $this->doTestNumberNonMatchesForLeniency($rawString, $region, Leniency::STRICT_GROUPING());
690
    }
691
692
    /**
693
     * @param string $rawString
694
     * @param string $region
695
     * @dataProvider dataExactGroupingCases
696
     */
697
    public function testMatchesWithExactGroupingLeniency($rawString, $region)
698
    {
699
        $this->doTestNumberMatchesForLeniency($rawString, $region, Leniency::EXACT_GROUPING());
700
    }
701
702
    public function data_testNonMatchesExactGroupLeniency()
703
    {
704
        return $this->dataImpossibleCases()
705
            + $this->dataPossibleOnlyCases()
706
            + $this->dataValidCases()
707
            + $this->dataStrictGroupingCases();
708
    }
709
710
    /**
711
     * @param string $rawString
712
     * @param string $region
713
     * @dataProvider data_testNonMatchesExactGroupLeniency
714
     */
715
    public function testNonMatchesExactGroupLeniency($rawString, $region)
716
    {
717
        $this->doTestNumberNonMatchesForLeniency($rawString, $region, Leniency::EXACT_GROUPING());
718
    }
719
720
    private function doTestNumberMatchesForLeniency($string, $region, Leniency\AbstractLeniency $leniency)
721
    {
722
        $iterator = $this->findNumbersForLeniency($string, $region, $leniency);
723
724
        $iterator->next();
725
        $match = $iterator->current();
726
727
        $this->assertNotNull($match, "No match found in {$string} ({$region}) for leniency {$leniency}");
728
        $this->assertEquals($string, $match->rawString(), "Found wrong match in test {$string} ({$region}). Found {$match->rawString()}");
729
    }
730
731
    private function doTestNumberNonMatchesForLeniency($string, $region, Leniency\AbstractLeniency $leniency)
732
    {
733
        $iterator = $this->findNumbersForLeniency($string, $region, $leniency);
734
735
        $iterator->next();
736
        $match = $iterator->current();
737
738
        $this->assertNull($match, "Match found in {$string} ({$region}) for leniency {$leniency}");
739
    }
740
741
    /**
742
     * Helper method which tests the contexts provided and ensures that:
743
     * -- if isValid is true, they all find a test number inserted in the middle when leniency of
744
     *  matching is set to VALID; else no test number should be extracted at that leniency level
745
     * -- if isPossible is true, they all find a test number inserted in the middle when leniency of
746
     *  matching is set to POSSIBLE; else no test number should be extracted at that leniency level
747
     *
748
     *
749
     * @param array $contexts
750
     * @param bool $isValid
751
     * @param bool $isPossible
752
     * @param string $region
753
     * @param string $number
754
     */
755
    private function findMatchesInContexts($contexts, $isValid, $isPossible, $region = RegionCode::US, $number = "415-666-7777")
756
    {
757
        if ($isValid) {
758
            $this->doTestInContext($number, $region, $contexts, Leniency::VALID());
759
        } else {
760
            foreach ($contexts as $context) {
761
                $text = $context[0] . $number . $context[1];
762
                $this->assertTrue($this->hasNoMatches($this->phoneUtil->findNumbers($text, $region)),
763
                    "Should not have found a number in {$text}");
764
            }
765
        }
766
767
        if ($isPossible) {
768
            $this->doTestInContext($number, $region, $contexts, Leniency::POSSIBLE());
769
        } else {
770
            foreach ($contexts as $context) {
771
                $text = $context[0] . $number . $context[1];
772
                $this->assertTrue($this->hasNoMatches($this->phoneUtil->findNumbers($text, $region)),
773
                    "Should not have found a number in {$text}");
774
            }
775
        }
776
    }
777
778
    public function testNonMatchingBracketsAreInvalid()
779
    {
780
        // The digits up to the ", " form a valid US number, but it shouldn't be matched as one since
781
        // there was a non-matching bracket present.
782
        $this->assertTrue($this->hasNoMatches($this->phoneUtil->findNumbers(
783
            "80.585 [79.964, 81.191]", RegionCode::US)));
784
785
        // The trailing "]" is thrown away before parsing, so the resultant number, while a valid US
786
        // number, does not have matching brackets.
787
        $this->assertTrue($this->hasNoMatches($this->phoneUtil->findNumbers(
788
            "80.585 [79.964]", RegionCode::US)));
789
790
        $this->assertTrue($this->hasNoMatches($this->phoneUtil->findNumbers(
791
            "80.585 ((79.964)", RegionCode::US)));
792
793
        // This case has too many sets of brackets to be valid.
794
        $this->assertTrue($this->hasNoMatches($this->phoneUtil->findNumbers(
795
            "(80).(585) (79).(9)64", RegionCode::US)));
796
    }
797
798
    public function testNoMatchIfRegionIsNull()
799
    {
800
        // Fail on non-international prefix if region code is null.
801
        $this->assertTrue($this->hasNoMatches($this->phoneUtil->findNumbers(
802
            "Random text body - number is 0331 6005, see you there", null)));
803
    }
804
805
    public function testNoMatchInEmptyString()
806
    {
807
        $this->assertTrue($this->hasNoMatches($this->phoneUtil->findNumbers("", RegionCode::US)));
808
        $this->assertTrue($this->hasNoMatches($this->phoneUtil->findNumbers("  ", RegionCode::US)));
809
    }
810
811
    public function testNoMatchIfNoNumber()
812
    {
813
        $this->assertTrue($this->hasNoMatches($this->phoneUtil->findNumbers(
814
            "Random text body - number is foobar, see you there", RegionCode::US)));
815
    }
816
817
    public function testSequences()
818
    {
819
        // Test multiple occurrences.
820
        $text = "Call 033316005  or 032316005!";
821
        $region = RegionCode::NZ;
822
823
        $number1 = new PhoneNumber();
824
        $number1->setCountryCode($this->phoneUtil->getCountryCodeForRegion($region));
825
        $number1->setNationalNumber(33316005);
826
        $match1 = new PhoneNumberMatch(5, "033316005", $number1);
827
828
        $number2 = new PhoneNumber();
829
        $number2->setCountryCode($this->phoneUtil->getCountryCodeForRegion($region));
830
        $number2->setNationalNumber(32316005);
831
        $match2 = new PhoneNumberMatch(19, "032316005", $number2);
832
833
        $matches = $this->phoneUtil->findNumbers($text, $region, Leniency::POSSIBLE());
834
835
        $matches->next();
836
        $this->assertEquals($match1, $matches->current());
837
838
        $matches->next();
839
        $this->assertEquals($match2, $matches->current());
840
    }
841
842
    public function testNullInput()
843
    {
844
        $this->assertTrue($this->hasNoMatches($this->phoneUtil->findNumbers(null, RegionCode::US)));
845
        $this->assertTrue($this->hasNoMatches($this->phoneUtil->findNumbers(null, null)));
846
    }
847
848
    public function testMaxMatches()
849
    {
850
        // Set up text with 100 valid phone numbers.
851
        $numbers = str_repeat("My info: 415-666-7777,", 100);
852
853
        // Matches all 100. Max only applies to failed cases.
854
        $number = $this->phoneUtil->parse("+14156667777", null);
855
        $expected = array_fill(0, 100, $number);
856
857
        $iterable = $this->phoneUtil->findNumbers($numbers, RegionCode::US, Leniency::VALID(), 10);
858
        $actual = array();
859
        foreach ($iterable as $match) {
860
            $actual[] = $match->number();
861
        }
862
863
        $this->assertEquals($expected, $actual);
864
    }
865
866
    public function testMaxMatchesInvalid()
867
    {
868
        // Set up text with 10 invalid phone numbers followed by 100 valid.
869
        $numbers = "";
870
        for ($i = 0; $i < 10; $i++) {
871
            $numbers .= "My address is 949-8945-0";
872
        }
873
        for ($i = 0; $i < 100; $i++) {
874
            $numbers .= "My info: 415-666-7777,";
875
        }
876
877
        $iterable = $this->phoneUtil->findNumbers($numbers, RegionCode::US, Leniency::VALID(), 10);
878
        $iterable->next();
879
        $this->assertNull($iterable->current());
880
    }
881
882
    public function testMaxMatchesMixed()
883
    {
884
        // Set up text with 100 valid numbers inside an invalid number.
885
        $numbers = "";
886
        for ($i = 0; $i < 100; $i++) {
887
            $numbers .= "My info: 415-666-7777 123 fake street";
888
        }
889
890
        // Only matches the first 10 despite there being 100 numbers due to max matches
891
        $number = $this->phoneUtil->parse("+14156667777", null);
892
        $expected = array_fill(0, 10, $number);
893
894
        $iterable = $this->phoneUtil->findNumbers($numbers, RegionCode::US, Leniency::VALID(), 10);
895
896
        $actual = array();
897
        foreach ($iterable as $match) {
898
            $actual[] = $match->number();
899
        }
900
901
        $this->assertEquals($expected, $actual);
902
    }
903
904
    public function testNonPlusPrefixedNumbersNotFoundForInvalidRegion()
905
    {
906
        // Does not start with a "+", we won't match it.
907
        $iterable = $this->phoneUtil->findNumbers("1 456 764 156", RegionCode::ZZ);
908
909
        $iterable->next();
910
        $this->assertFalse($iterable->valid());
911
    }
912
913
    public function testEmptyIteration()
914
    {
915
        $iterable = $this->phoneUtil->findNumbers("", RegionCode::ZZ);
916
917
        $iterable->next();
918
        $this->assertFalse($iterable->valid());
919
    }
920
921
    public function testSingleIteration()
922
    {
923
        $iterable = $this->phoneUtil->findNumbers("+14156667777", RegionCode::ZZ);
924
925
        $iterable->next();
926
        $this->assertTrue($iterable->valid());
927
        $this->assertNotNull($iterable->current());
928
929
        $iterable->next();
930
        $this->assertFalse($iterable->valid());
931
        $this->assertNull($iterable->current());
932
    }
933
934
    public function testDoubleIteration()
935
    {
936
        $iterable = $this->phoneUtil->findNumbers("+14156667777 foobar +14156667777 ", RegionCode::ZZ);
937
938
        $iterable->next();
939
        $this->assertTrue($iterable->valid());
940
        $this->assertNotNull($iterable->current());
941
942
        $iterable->next();
943
        $this->assertTrue($iterable->valid());
944
        $this->assertNotNull($iterable->current());
945
946
        $iterable->next();
947
        $this->assertFalse($iterable->valid());
948
        $this->assertNull($iterable->current());
949
    }
950
951
    /**
952
     * Asserts that the expected match is non-null, and that the raw string and expected
953
     * proto buffer are set appropriately
954
     *
955
     * @param PhoneNumberMatch $match
956
     * @param string $text
957
     * @param string $number
958
     * @param string $region
959
     */
960
    private function assertMatchProperties(PhoneNumberMatch $match, $text, $number, $region)
961
    {
962
        $expectedResult = $this->phoneUtil->parse($number, $region);
963
        $this->assertNotNull($match, "Did not find a number in {$text}; expected {$number}");
964
        $this->assertEquals($expectedResult, $match->number());
965
        $this->assertEquals($number, $match->rawString());
966
    }
967
968
    /**
969
     * Asserts that another number can be found in $text starting at $index, and that its
970
     * corresponding range in $start to $end
971
     *
972
     * @param string $text
973
     * @param int $index
974
     * @param int $start
975
     * @param int $end
976
     */
977
    private function assertEqualRange($text, $index, $start, $end)
978
    {
979
        $sub = mb_substr($text, $index, mb_strlen($text) - $index);
980
        $matches = $this->phoneUtil->findNumbers($sub, RegionCode::NZ, Leniency::POSSIBLE(), PHP_INT_MAX);
981
982
        $matches->next();
983
        $this->assertTrue($matches->valid());
984
985
        $match = $matches->current();
986
987
        $this->assertEquals($start - $index, $match->start());
988
        $this->assertEquals($end - $index, $match->end());
989
        $this->assertEquals(mb_substr($sub, $match->start(), $match->end() - $match->start()), $match->rawString());
990
    }
991
992
    /**
993
     * Tests numbers found by PhoneNumberUtil->findNumbers in various
994
     * textual contexts
995
     *
996
     * @param string $number The number to test
997
     * @param string $defaultCountry The corresponding region code
998
     */
999
    private function doTestFindInContext($number, $defaultCountry)
1000
    {
1001
        $this->findPossibleInContext($number, $defaultCountry);
1002
1003
        $parsed = $this->phoneUtil->parse($number, $defaultCountry);
1004
        if ($this->phoneUtil->isValidNumber($parsed)) {
1005
            $this->findValidInContext($number, $defaultCountry);
1006
        }
1007
    }
1008
1009
    /**
1010
     * Tests valid numbers in contexts that should pass for Leniency::POSSIBLE()
1011
     * @param $number
1012
     * @param $defaultCountry
1013
     */
1014
    private function findPossibleInContext($number, $defaultCountry)
1015
    {
1016
        $contextPairs = array();
1017
        $contextPairs[] = array("", ""); // no content
1018
        $contextPairs[] = array("   ", "\t");  // whitespace only
1019
        $contextPairs[] = array("Hello ", ""); // no context at end
1020
        $contextPairs[] = array("", " to call me!"); // no context at start
1021
        $contextPairs[] = array("Hi there, call ", " to reach me!"); // no context at start
1022
        $contextPairs[] = array("Hi here, call ", " , or don't"); // with commas
1023
        // Three examples without whitespace around the number
1024
        $contextPairs[] = array("Hi call", "");
1025
        $contextPairs[] = array("", "forme");
1026
        $contextPairs[] = array("Hi call", "forme");
1027
        // With other small numbers.
1028
        $contextPairs[] = array("It's cheap! Call ", " before 6:30");
1029
        // With a second number later.
1030
        $contextPairs[] = array("Call ", " or +1800-123-4567!");
1031
        $contextPairs[] = array("Call me on June 2 at", ""); // with a Month-Day date
1032
        // With publication pages
1033
        $contextPairs[] = array("As quoted by Alfonso 12-15 (2009), you may call me at ", "");
1034
        $contextPairs[] = array("As quoted by Alfonso et al. 12-15 (2009), you may call me at ", "");
1035
        // With dates, written in the American style.
1036
        $contextPairs[] = array("As I said on 03/10/2011, you may call be at ", "");
1037
        // With trailing numbers after a comma. The 45 should not be considered an extension
1038
        $contextPairs[] = array("", ", 45 days a year");
1039
        // When matching we don't consider semicolon along with legitimate extension symbol to indicate
1040
        // an extension. The 7246433 should not be considered an extension.
1041
        $contextPairs[] = array("", ";x 7246433");
1042
        // With a postfix stripped off as it looks like the start of another number.
1043
        $contextPairs[] = array("Call ", "/x12 more");
1044
1045
        $this->doTestInContext($number, $defaultCountry, $contextPairs, Leniency::POSSIBLE());
1046
    }
1047
1048
    /**
1049
     * Tests valid numbers in contexts that fail for Leniency::POSSIBLE() but are valid for
1050
     * Leniency::VALID()
1051
     *
1052
     * @param string $number
1053
     * @param string $defaultCountry
1054
     */
1055
    private function findValidInContext($number, $defaultCountry)
1056
    {
1057
        $contextPairs = array();
1058
        // With other small numbers
1059
        $contextPairs[] = array("It's only 9.99! Call ", " to buy");
1060
        // with a number Day.Month.Year date.
1061
        $contextPairs[] = array("Call me on 21.6.1984 at ", "");
1062
        // With a number Month/Day date.
1063
        $contextPairs[] = array("Call me on 06/21 at ", "");
1064
        // With a number Day.Month date.
1065
        $contextPairs[] = array("Call me on 21.6. at ", "");
1066
        // With a number Month/Day/Year date.
1067
        $contextPairs[] = array("Call me on 06/21/84 at ", "");
1068
1069
        $this->doTestInContext($number, $defaultCountry, $contextPairs, Leniency::VALID());
1070
    }
1071
1072
    private function doTestInContext($number, $defaultCountry, $contextPairs, Leniency\AbstractLeniency $leniency)
1073
    {
1074
        foreach ($contextPairs as $context) {
1075
            $prefix = $context[0];
1076
            $text = $prefix . $number . $context[1];
1077
1078
            $start = mb_strlen($prefix);
1079
            $end = $start + mb_strlen($number);
1080
1081
            $iterator = $this->phoneUtil->findNumbers($text, $defaultCountry, $leniency, PHP_INT_MAX);
1082
1083
            $iterator->next();
1084
            $match = $iterator->current();
1085
            $this->assertNotNull($match, "Did not find number in '{$text}'; expected '{$number}'");
1086
1087
            $extracted = mb_substr($text, $match->start(), $match->end() - $match->start());
1088
            $this->assertEquals($start, $match->start(), "Unexpected phone region in '{$text}'; extracted '{$extracted}'");
1089
            $this->assertEquals($end, $match->end(), "Unexpected phone region in '{$text}'; extracted '{$extracted}'");
1090
            $this->assertEquals($number, $extracted);
1091
            $this->assertEquals($extracted, $match->rawString());
1092
1093
            $this->ensureTermination($text, $defaultCountry, $leniency);
1094
        }
1095
    }
1096
1097
    private function ensureTermination($text, $defaultCountry, Leniency\AbstractLeniency $leniency)
1098
    {
1099
1100
        $textLength = mb_strlen($text);
1101
        for ($index = 0; $index <= $textLength; $index++) {
1102
            $sub = mb_substr($text, $index);
1103
            $matches = "";
1104
            // Iterates over all matches.
1105
            foreach ($this->phoneUtil->findNumbers($sub, $defaultCountry, $leniency, PHP_INT_MAX) as $match)
1106
            {
1107
                $matches .= ", " . $match;
1108
            }
1109
        }
1110
    }
1111
1112
    private function findNumbersForLeniency($text, $defaultCountry, Leniency\AbstractLeniency $leniency)
1113
    {
1114
        return $this->phoneUtil->findNumbers($text, $defaultCountry, $leniency, PHP_INT_MAX);
1115
    }
1116
1117
    private function hasNoMatches(PhoneNumberMatcher $match)
1118
    {
1119
        return !$match->valid();
1120
    }
1121
}
1122