File Coverage

blib/lib/Number/Phone/UK.pm
Criterion Covered Total %
statement 162 165 99.3
branch 53 58 93.1
condition 15 18 83.3
subroutine 32 32 100.0
pod 9 9 100.0
total 271 282 97.1


line stmt bran cond sub pod time code
1             package Number::Phone::UK;
2              
3 15     15   6615 use strict;
  15         38  
  15         488  
4              
5 15     15   104 use Scalar::Util 'blessed';
  15         45  
  15         749  
6 15     15   716 use Number::Phone::UK::Data;
  15         51  
  15         511  
7              
8 15     15   110 use base 'Number::Phone';
  15         48  
  15         19753  
9              
10             our $VERSION = '1.71';
11              
12             my $cache = {};
13              
14             =head1 NAME
15              
16             Number::Phone::UK - UK-specific methods for Number::Phone
17              
18             =head1 SYNOPSIS
19              
20             use Number::Phone;
21              
22             $daves_phone = Number::Phone->new('+44 1234 567890');
23              
24             =cut
25              
26             sub new {
27 338     338 1 2746 my $class = shift;
28 338         627 my $number = shift;
29              
30 338         1078 $number = '+44'._clean_number($number);
31 338 100       1161 if(is_valid($number)) {
32 301         895 $number =~ s/^0/+44/;
33 301         880 my $target_class = $class->_get_class(_clean_number($number));
34 301 100       1304 return undef if($class ne $target_class);
35 295         4408 return bless(\$number, $target_class);
36 38         242 } else { return undef; }
37             }
38              
39             =head1 DATABASE
40              
41             Number::Phone::UK uses a large database, access via L. This
42             database lives in a file, and normally only the little bits of it that you access will
43             ever get loaded into memory. This means, however, that creating Number::Phone::UK objects
44             almost always involves disk access and so is slow compared to data for some other
45             countries. There are two ways to avoid this slowness.
46              
47             First, if you don't need all the functionality you can use L.
48              
49             Second, if you can accept slow startup - eg when your server starts - then you can call
50             C<< Number::Phone::UK::Data->slurp() >> from your code, which will pull the entire database
51             into memory. This will take a few minutes, and on a 64-bit machine will consume of the
52             order of 200MB of memory.
53              
54             The database uses L. This apparently has some problems if you connect to it,
55             C, and then try to access the database from multiple processes. We attempt to
56             work around this by re-connecting to the database after forking. This is, of course,
57             not a problem if you C the database before forking.
58              
59             =head1 METHODS
60              
61             The following methods from Number::Phone are overridden:
62              
63             =over 4
64              
65             =item new
66              
67             The constructor, you should never have to call this yourself. To create an
68             object the canonical incantation is C<< Number::Phone->new('+44 ...') >>.
69              
70             =item data_source
71              
72             Returns a string telling where and when the data that drives this class was last updated, looking something like:
73              
74             "OFCOM at Wed Sep 30 10:37:39 2020 UTC"
75              
76             The current value of this is also documented in L.
77              
78             =item is_valid
79              
80             The number is valid within the national numbering scheme. It may or may
81             not yet be allocated, or it may be reserved. Any number which returns
82             true for any of the following methods will also be valid.
83              
84             =cut
85              
86             sub _get_class {
87 300     301   643 my $class = shift;
88 300         644 my $number = shift;
89 300         949 foreach my $prefix (_prefixes($number)) {
90 2835 100       4953454 if(exists(Number::Phone::UK::Data::db()->{subclass}->{$prefix})) {
91 43 50       127456 return $class if(Number::Phone::UK::Data::db()->{subclass}->{$prefix} eq '');
92              
93 43         124747 my $desired_subclass = Number::Phone::UK::Data::db()->{subclass}->{$prefix};
94 43         124540 my $subclass = "Number::Phone::UK::$desired_subclass";
95 43     4   5034 eval "use $subclass";
  4     4   41  
  4     3   10  
  4     3   46  
  4     3   37  
  4     3   14  
  4     3   38  
  3     3   37  
  3     2   7  
  3     2   28  
  3     2   31  
  3     2   7  
  3         37  
  3         33  
  3         9  
  3         37  
  3         37  
  3         14  
  3         30  
  3         35  
  3         9  
  3         45  
  3         30  
  3         11  
  3         30  
  2         20  
  2         9  
  2         25  
  2         18  
  2         7  
  2         22  
  2         17  
  2         5  
  2         21  
  2         19  
  2         5  
  2         19  
96 43         287 return $subclass;
97             }
98             }
99 257         485265 return $class;
100             }
101              
102             sub _clean_number {
103 1211     1212   2031 my $clean = shift;
104 1211         3425 $clean =~ s/[^0-9+]//g; # strip non-digits/plusses
105 1211         4762 $clean =~ s/^\+44//; # remove leading +44
106 1211         2475 $clean =~ s/^0//; # kill leading zero
107 1211         3955 return $clean;
108             }
109              
110             sub _prefixes {
111 794     794   1321 my $number = shift;
112 794         2387 map { substr($number, 0, $_) } reverse(1..length($number));
  7858         15765  
113             }
114              
115             sub is_valid {
116 365     365 1 2408 my $number = shift;
117              
118             # If called as an object method, it *must* be valid otherwise the
119             # object would never have been instantiated.
120             # If called as a subroutine, that's the constructor doing its thang.
121 365 100       1532 return 1 if(blessed($number));
122              
123             # otherwise we have to validate
124              
125             # if we've seen this number before, use cached result
126 337 100       1882 return 1 if($cache->{$number}->{is_valid});
127              
128             # assume it's OK unless proven otherwise
129 202         632 $cache->{$number}->{is_valid} = 1;
130              
131 202         430 my $cleaned_number = _clean_number($number);
132              
133 202         666 my @prefixes = _prefixes($cleaned_number);
134              
135             # quickly check length
136 202 100 100     1450 return $cache->{$number}->{is_valid} = 0 if(length($cleaned_number) < 7 || length($cleaned_number) > 10);
137              
138             # 04 and 06 are invalid, only 05[56] are valid
139 185 100       803 return $cache->{$number}->{is_valid} = 0 if($cleaned_number =~ /^(4|5[01234789]|6)/);
140              
141             # slightly more rigourous length check for some unallocated geographic numbers
142             # 07, 02x and 011x are always ten digits
143 179 100 100     990 return $cache->{$number}->{is_valid} = 0 if($cleaned_number =~ /^([27]|11)/ && length($cleaned_number) != 10);
144              
145             my $telco_and_length_code =
146             (
147 165         332817 map { Number::Phone::UK::Data::db()->{telco_and_length}->{$_} }
148 171         496 grep { exists(Number::Phone::UK::Data::db()->{telco_and_length}->{$_}) }
  1684         3068701  
149             @prefixes
150             )[0];
151              
152 171         367375 $cache->{$number}->{is_allocated} = 0;
153 171 100 100     1002 if(
    100          
154             # if we've got a telco, we've been allocated
155             $telco_and_length_code &&
156             Number::Phone::UK::Data::db()->{telco_format}->{$telco_and_length_code}->{telco}
157             ) {
158 110         336638 $cache->{$number}->{is_allocated} = 1;
159 110         351 $cache->{$number}->{operator} = Number::Phone::UK::Data::db()->{telco_format}->{$telco_and_length_code}->{telco};
160             $cache->{$number}->{format} = Number::Phone::UK::Data::db()->{telco_format}->{$telco_and_length_code}->{format}
161 110         299812 } elsif($telco_and_length_code) {
162             # if not we might still have a format, eg for Protected numbers
163             $cache->{$number}->{format} = Number::Phone::UK::Data::db()->{telco_format}->{$telco_and_length_code}->{format}
164 55         128368 }
165              
166 171 100 66     410528 if($cache->{$number}->{format} && $cache->{$number}->{format} =~ /\+/) {
167 165         10636 my($arealength, $subscriberlength) = split(/\+/, $cache->{$number}->{format});
168             # for hateful mixed thing
169 165 100       836 my @subscriberlengths = ($subscriberlength =~ m{/}) ? split(/\//, $subscriberlength) : ($subscriberlength);
170 165         1347 $subscriberlength =~ s/^(\d+).*/$1/; # for hateful mixed thing
171 165         806 $cache->{$number}->{areacode} = substr($cleaned_number, 0, $arealength);
172 165         618 $cache->{$number}->{subscriber} = substr($cleaned_number, $arealength);
173             $cache->{$number}->{areaname} = (
174             map {
175 88         157808 Number::Phone::UK::Data::db()->{areanames}->{$_}
176 165         471 } grep { Number::Phone::UK::Data::db()->{areanames}->{$_} } @prefixes
  1624         2626744  
177             )[0];
178 165 100       307819 if(!grep { length($cache->{$number}->{subscriber}) == $_ } @subscriberlengths) {
  183         1234  
179             # number wrong length!
180 6         42 $cache->{$number} = { is_valid => 0 };
181 6         36 return 0;
182             }
183             }
184              
185 165         856 return $cache->{$number}->{is_valid};
186             }
187              
188             # now define the is_* methods that we over-ride
189             sub is_fixed_line {
190 30 100   30 1 863 return 0 if(is_mobile(@_));
191 16         62 return undef;
192             }
193              
194             sub is_drama {
195 80     80 1 374 my $self = shift;
196              
197 80         158 my $num = _clean_number(${$self});
  80         236  
198              
199 80         1363 my @drama_numbers = (
200             # Leeds, Sheffield, Nottingham, Leicester, Bristol, Reading
201             qr/^11[3-8]4960[0-9]{3}$/,
202             # Birmingham, Edinburgh, Glasgow, Liverpool, Manchester
203             qr/^1[2-6]14960[0-9]{3}$/,
204             # London
205             qr/^2079460[0-9]{3}$/,
206             # Tyneside/Durham/Sunderland
207             qr/^1914980[0-9]{3}$/,
208             # Northern Ireland
209             qr/^2896496[0-9]{3}$/,
210             # Cardiff
211             qr/^2920180[0-9]{3}$/,
212             # No area
213             qr/^1632960[0-9]{3}$/,
214             # Mobile
215             qr/^7700900[0-9]{3}$/,
216             # Freephone
217             qr/^8081570[0-9]{3}$/,
218             # Premium Rate
219             qr/^9098790[0-9]{3}$/,
220             # UK Wide
221             qr/^3069990[0-9]{3}$/,
222             );
223              
224 80         243 foreach my $d (@drama_numbers) {
225 552 100       2351 return 1 if ($num =~ $d);
226             }
227              
228 32         308 return 0;
229             }
230              
231             foreach my $is (qw(
232             geographic network_service tollfree corporate
233             personal pager mobile specialrate adult allocated ipphone
234             )) {
235 15     15   132 no strict 'refs';
  15         37  
  15         3628  
236             *{__PACKAGE__."::is_$is"} = sub {
237 540     540   62969 my $self = shift;
238 540 100       924 if(!exists($cache->{${$self}}->{"is_$is"})) {
  540         2272  
239 288         478470 $cache->{${$self}}->{"is_$is"} =
240             grep {
241             Number::Phone::UK::Data::db()->{
242             { geographic => 'geo_prefices',
243             network_service => 'network_svc_prefices',
244             tollfree => 'free_prefices',
245             corporate => 'corporate_prefices',
246             personal => 'personal_prefices',
247             pager => 'pager_prefices',
248             mobile => 'mobile_prefices',
249             specialrate => 'special_prefices',
250             adult => 'adult_prefices',
251             ipphone => 'ip_prefices'
252             }->{$is}
253 2876         4341756 }->{$_}
254 288         520 } _prefixes(_clean_number(${$self}));
  288         674  
255             }
256 540         1524 $cache->{${$self}}->{"is_$is"};
  540         2915  
257             }
258             }
259              
260             # define the other methods
261              
262             foreach my $method (qw(operator areacode areaname subscriber)) {
263 15     15   140 no strict 'refs';
  15         889  
  15         9365  
264             *{__PACKAGE__."::$method"} = sub {
265 359     359   667 my $self = shift;
266 359         554 return $cache->{${$self}}->{$method};
  359         1900  
267             }
268             }
269              
270             =item is_allocated
271              
272             The number has been allocated to a telco for use. It may or may not yet
273             be in use or may be reserved.
274              
275             =item is_drama
276              
277             The number is intended for use in fiction. OFCOM has allocated numerous small
278             ranges for this purpose. These numbers will not be allocated to real customers.
279             See L
280             for the authoritative source.
281              
282             =item is_geographic
283              
284             The number refers to a geographic area.
285              
286             =item is_fixed_line
287              
288             The number, when in use, can only refer to a fixed line.
289              
290             (we can't tell whether a number is a fixed line, but we can tell that
291             some are *not*).
292              
293             =item is_mobile
294              
295             The number, when in use, can only refer to a mobile phone.
296              
297             =item is_pager
298              
299             The number, when in use, can only refer to a pager.
300              
301             =item is_tollfree
302              
303             Callers will not be charged for calls to this number under normal circumstances.
304              
305             =item is_specialrate
306              
307             The number, when in use, attracts special rates. For instance, national
308             dialling at local rates, or premium rates for services.
309              
310             =item is_adult
311              
312             The number, when in use, goes to a service of an adult nature, such as porn.
313              
314             =item is_personal
315              
316             The number, when in use, goes to an individual person.
317              
318             =item is_corporate
319              
320             The number, when in use, goes to a business.
321              
322             =item is_ipphone
323              
324             The number, when in use, is terminated using VoIP.
325              
326             =item is_network_service
327              
328             The number is some kind of network service such as a human operator, directory
329             enquiries, emergency services etc
330              
331             =item country_code
332              
333             Returns 44.
334              
335             =cut
336              
337 94     94 1 388 sub country_code { 44; }
338              
339             =item regulator
340              
341             Returns informational text.
342              
343             =cut
344              
345 2     2 1 15 sub regulator { 'OFCOM, http://www.ofcom.org.uk/'; }
346              
347             =item areacode
348              
349             Return the area code - if applicable - for the number. If not applicable,
350             returns undef.
351              
352             =item areaname
353              
354             Return the area name - if applicable - for the number, or undef.
355              
356             =item location
357              
358             For geographic numbers, this returns the location of the exchange to which
359             that number is assigned, if available. Otherwise returns undef.
360              
361             =cut
362              
363             sub location {
364 6     6 1 3641 my $self = shift;
365              
366 6 100       31 return undef unless($self->is_geographic());
367              
368 4         10 my $cleaned_number = _clean_number(${$self});
  4         19  
369              
370 4         17 my @prefixes = _prefixes($cleaned_number);
371              
372             # uncoverable branch true
373 4 50       24 if(!$ENV{TESTINGKILLTHEWABBIT}) {
374 0         0 eval "require Number::Phone::UK::DetailedLocations"; # uncoverable statement
375             }
376 4 100       63 require Number::Phone::UK::Exchanges if(!$Number::Phone::UK::Exchanges::db);
377              
378 4         581 foreach(@prefixes) {
379 28 100       110 if(exists($Number::Phone::UK::Exchanges::db->{exchg_prefices}->{$_})) {
380             return [
381             $Number::Phone::UK::Exchanges::db->{exchg_positions}->{$Number::Phone::UK::Exchanges::db->{exchg_prefices}->{$_}}->{lat},
382             $Number::Phone::UK::Exchanges::db->{exchg_positions}->{$Number::Phone::UK::Exchanges::db->{exchg_prefices}->{$_}}->{long}
383 4         43 ];
384             }
385             }
386             # may become coverable if I ever test the location of a number
387             # in an areacode that wasn't in the data dump I got years ago
388 0         0 return undef; # uncoverable statement
389             }
390              
391             =item subscriber
392              
393             Return the subscriber part of the number
394              
395             =item operator
396              
397             Return the name of the telco operating this number, in an appropriate
398             character set and with optional details such as their web site or phone
399             number.
400              
401             =item format
402              
403             Return a sanely formatted version of the number, complete with IDD code, eg
404             for the UK number (0208) 771-2924 it would return +44 20 8771 2924.
405              
406             =cut
407              
408             sub format {
409 63     63 1 876 my $self = shift;
410 63         210 my $r;
411              
412 63 100 66     253 if($self->areacode()) { # if there's an areacode ...
    100 66        
    50          
    0          
413 38         118 $r = '+'.country_code().' '.$self->areacode().' ';
414 38 100       124 if( length($self->subscriber()) == 7) { $r .= substr($self->subscriber(), 0, 3).' '.substr($self->subscriber(), 3) }
  2 100       7  
415 11         32 elsif(length($self->subscriber()) == 8) { $r .= substr($self->subscriber(), 0, 4).' '.substr($self->subscriber(), 4) }
416 25         126 else { $r .= $self->subscriber() }
417             } elsif($self->subscriber && $self->subscriber =~ /^7/) { # mobiles/pagers don't have areacodes but should be formatted as if they do
418 9         46 $r = '+'.country_code().
419             ' '.substr($self->subscriber(), 0, 4).
420             ' '.substr($self->subscriber(), 4);
421             } elsif(!$self->is_allocated() || !$cache->{${self}}->{format}) { # if not allocated or no format
422 16         62 $r = '+'.country_code().' '.substr(${$self}, 3)
  16         52  
423             } elsif($self->subscriber()) { # if there's a subscriber ...
424 0         0 $r = '+'.country_code().' '.$self->subscriber
425             }
426 63         498 return $r;
427             }
428              
429             =item intra_country_dial_to
430              
431             Within the UK numbering plan you can *always* dial 0xxxx xxxxxx
432             for intra-country calls. In most places the leading 0$areacode is
433             optional but in some it is required (see eg
434             L) and over time this
435             will apply to more areas.
436              
437             =cut
438              
439             sub intra_country_dial_to {
440 10     10 1 24 my $from = shift;
441 10         19 my $to = shift;
442              
443 10 100       43 die if(!$to->is_allocated());
444 9 100       38 return '0'.($to->areacode() ? $to->areacode() : '').$to->subscriber();
445             }
446              
447             =item country
448              
449             If the number is_international, return the two-letter ISO country code.
450              
451             NYI
452              
453             =back
454              
455             =head1 LIMITATIONS/BUGS/FEEDBACK
456              
457             The results are only as up-to-date as the data included from OFCOM's
458             official documentation of number range allocations.
459              
460             No attempt is made to deal with number portability.
461              
462             Please report bugs at L, including, if possible, a test case.
463              
464             I welcome feedback from users.
465              
466             =head1 LICENCE
467              
468             You may use, modify and distribute this software under the same terms as
469             perl itself.
470              
471             =head1 AUTHOR
472              
473             David Cantrell Edavid@cantrell.org.ukE
474              
475             Copyright 2023
476              
477             =cut
478              
479             1;