diff --git a/include/phone.gb.inc b/include/phone.gb.inc index b398d49..20c642c 100644 --- a/include/phone.gb.inc +++ b/include/phone.gb.inc @@ -4,140 +4,377 @@ * @file * CCK Field for British phone numbers. */ - function phone_gb_metadata() { // These strings are translated using t() on output. return array( - 'error' => '"%value" is not a valid British (UK) phone number
British Phone numbers should .... ', + 'label' => 'Phone Numbers - Great Britain - England (UK)', + 'error' => '"%value" is not a valid British (UK) phone number
British Phone numbers should contain 9 or 10 digits after
the 0 trunk code or +44 country code.', ); } +/* + Accepts: + 020 7000 7000 + 0207 000 7000 + 02070 007 000 #4567 + 0117 455 7777 + 01174 557 777 + 0175 061 8888 + 01750 618 888 + 01750 62555 + 01697 355 555 + 01697 73555 + 02030007000 + 02070008888 + 01215557777 + 01174007777 + 01750615777 + 0175062555 + 0169773555 + 01946755555 + 07788555777 + 07010001000 + 08005557777 + 0800555888 + (020) 7000 7000 + (0207) 000 7000 + (01174) 557 777 #333 + (01970) 234567 #0001 + (0169) 772444 + +44 207 000 7000 + +44 2070 007000 + +44 16973 55555 + +44 1697 355 555 + +441174 008 888 #333 + +44 203 000 8000 #555 + +441970234567 + +44(0)1970234567 + +44 1970 123 456 + +44 (0)1970 234 456 + +441174008888 + +441174008888#333 + +44 (0) 203 000 8000 + +44(1970)234567 + (+44) 1697 355 555 + (+44 16973) 55555 #888 + (+441970)234567 + (+44) 2070 007 000 + (+44 20) 3000 8000 + (+44) (0) 207 000 7000 + 00 44 207 000 7000 + 00 44 16977 3555 + 00442030007777#555 + 00 (44) 1697 73555 + (00 44) 203 000 8000 + 011 44 207 000 7000 + 011 (44) 20 7000 7000 + 011 44 1697 73555 + 011 44 (0)203 000 8000#222 + "Be liberal in what you accept and strict in what you return." + + Rejects: + 06750 618888 + 06750 62555 + 060 5000 7000 + 00 33 20 00 70 00 + 01 69 77 35 55 + 05342030007777#555 + 02230007777 + 062300077 + 06230007777 + 0623000777788 + (0197) 0123456 #01 + +44 01970 123 456 +*/ + /** - * Verifies that $phonenumber is a valid eleven-digit United Kingdom phone number - * - * Regular expression adapted from Amos Hurd's regex at RegExLib.com + * Verifies that $phonenumber is a United Kingdom phone number in a valid + * number range. Rejects numbers that are too long or too short or are in + * a non-valid range. Accepts a wide range of input formats and a number + * of different prefixes. + * created by @g1smd * * @param string $phonenumber - * @return boolean Returns boolean FALSE if the phone number is not valid. + * @return boolean Returns boolean FALSE if the phone number is not valid */ - function valid_gb_phone_number($phonenumber) { + // Check if number entered matches a valid format + if (!valid_gb_phone_pattern($phonenumber)) { + return FALSE; + } else { + // Extract number parts: prefix, NSN, extension + $phonenumberPartsArray = (extract_gb_phone_parts($phonenumber)); + if (empty($phonenumberPartsArray)) { + return FALSE; + } else { + $phonenumberNSN = $phonenumberPartsArray['NSN']; + // Check if NSN entered is in a valid range + if (!valid_gb_phone_range($phonenumberNSN)) { + return FALSE; + } else { + return TRUE; + } + } + } +} - /* - Accepts: - +441970123456 - +44(0)1970123456 - +44 1970 123 456 - +44 (0)1970 123 456 - (01970) 123456 #0001 - Rejects: - (+441970)123456 - +44(1970)123456 - +44 01970 123 456 - (0197) 0123456 #01 - */ - $regex = "/ - ( - (^\+44\s?(\(0\))?\d{4}|^\(?0\d{4}\)?){1}\s?\d{3}\s?\d{3} # 4 digit area code with optional +44 internationalisation or not, optional spaces and brackets. - | - (^\+44\s?(\(0\))?\d{3}|^\(?0\d{3}\)?){1}\s?\d{3}\s?\d{4} # 3 digit area code with optional +44 internationalisation or not, optional spaces and brackets. - | - (^\+44\s?(\(0\))?\d{2}|^\(?0\d{2}\)?){1}\s?\d{4}\s?\d{4} # 2 digit area code with optional +44 internationalisation or not, optional spaces and brackets. - | - (^\+44\s?(\(0\))?\d{1}|^\(?0\d{1}\)?){1}\s?\d{5}\s?\d{5} # 1 digit area code with optional +44 internationalisation or not, optional spaces and brackets. - ) - (\s?\#\d*)? # optional extension number shown with a hash divider - /x"; - - if (!preg_match($regex, $phonenumber)) { - return FALSE; - } - else - { - return TRUE; - } +/** + * Convert a valid United Kingdom phone number into standard +44 1970 123456 #001 + * international format. Accepts a wide range of input formats and prefixes and + * re-formats the number taking into account the required 2+8, 3+7, 4+6, 4+5, 5+5, + * 5+4 and 3+6 formats by number range. + * created by @g1smd + * + * @param $phonenumber must be a valid nine or ten-digit number (with optional extension) + * @return string $phonenumber + */ +function format_gb_phone_number($phonenumber, $field = FALSE) { + $phonenumberPrefix = $phonenumberNSNraw = $phonenumberNSNformatted = $phonenumberExtension = ''; + + // Extract optional country prefix, NSN, and optional extension. + $phonenumberPartsArray = extract_gb_phone_parts($phonenumber); + if (!empty($phonenumberPartsArray)) { + $phonenumberNSN = $phonenumberPartsArray['NSN']; + if ($phonenumberNSN == null) { + return $phonenumber; + } + // Grab only the NSN part for formatting + // NSN part might include spaces, hyphens or ')' and will need to be removed + $arrayReplace = array(")", "-", " "); + $phonenumberNSN = trim(str_replace($arrayReplace, "", $phonenumberNSN)); + // Format NSN part of GB number + $phonenumberNSNformatted = format_gb_nsn($phonenumberNSN); + + // Set prefix as 44 or 0 + if (isset($phonenumberPartsArray['prefix']) && $phonenumberPartsArray['prefix'] != null) { + $phonenumberPrefix = $phonenumberPartsArray['prefix']; + // } else { + // $phonenumberPrefix = "0"; + } + + // Extract extension + $phonenumberHasExtension = false; + $phonenumberExtension = null; + if (isset($phonenumberPartsArray['extension']) && $phonenumberPartsArray['extension'] != null) { + $phonenumberHasExtension = true; + $phonenumberExtension = " " . trim($phonenumberPartsArray['extension']); + } + + // Add prefix back on to NSN + if ($field['phone_country_code']) { + // Use formatting from form selector + $phonenumber = '+44 ' . $phonenumberNSNformatted; + } else { + // Use formatting from type-in + $phonenumber = $phonenumberPrefix . $phonenumberNSNformatted; + } + + // Add extension back on to number + if ($phonenumberHasExtension) { + $phonenumber .= $phonenumberExtension; + } + } + return $phonenumber; } /** - * Convert a valid United Kingdom phone number into standard +44 (0)1970 123 456 #001 international format + * Verifies that $phonenumber uses a valid UK phone number input pattern. + * Pattern matches any number entered as 2+8, 3+7, 4+6, 4+5, 5+5, 5+4, 3+6 + * with or without spaces, with a variety of prefixes and optional extension. + * RegEx patterns are based on + * http://www.aa-asterisk.org.uk/index.php/Number_format and + * http://www.aa-asterisk.org.uk/index.php/Regular_Expressions_for_Validating_and_Formatting_UK_Telephone_Numbers + * created by @g1smd * - * @param $phonenumber must be a valid eleven-digit number (with optional extension) + * Regular expression is adapted from Amos Hurd's RegEx at RegExLib.com + * Reformulated by @g1smd to remove multiple inefficiencies in pattern and + * include various valid number formats that the original pattern omitted. * + * @param string $phonenumber + * @return boolean Returns boolean FALSE if the phone number is not valid. */ -function format_gb_phone_number($phonenumber, $field = FALSE) { +function valid_gb_phone_pattern($phonenumber) { + // RegEx to define valid formats for GB numbers + $patternRegExGB = '/^'; + $patternRegExGB .= '(?:'; + $patternRegExGB .= '(?:\(?(?:0(?:0|11)\)?\s?\(?|\+)44\)?\s?(?:\(?0\)?\s?)?)'; # leading 00, 011 or + before 44 with optional (0); parentheses and spaces optional + $patternRegExGB .= '|'; + $patternRegExGB .= '(?:\(?0)'; # leading (0, 0 + $patternRegExGB .= ')'; + $patternRegExGB .= '(?:'; + $patternRegExGB .= '(?:\d{5}\)?\s?\d{4,5})'; # [5+4]/[5+5] + $patternRegExGB .= '|'; + $patternRegExGB .= '(?:\d{4}\)?\s?(?:\d{5}|\d{3}\s?\d{3}))'; # [4+5]/[4+6] + $patternRegExGB .= '|'; + $patternRegExGB .= '(?:\d{3}\)?\s?\d{3}\s?\d{3,4})'; # [3+6]/[3+7] + $patternRegExGB .= '|'; + $patternRegExGB .= '(?:\d{2}\)?\s?\d{4}\s?\d{4})'; # [2+8] + $patternRegExGB .= ')'; + $patternRegExGB .= '(?:'; + $patternRegExGB .= '(\s?\#\d+)?'; # optional extension number shown with a hash divider + $patternRegExGB .= ')'; + $patternRegExGB .= '$/x'; - $area = $number = $extension = ''; - - //If we already have the formatting we want just return - if (preg_match( - "/ - ( - \+44\s\(0\)\d{4}\s\d{3}\s\d{3} # 4 digit area code - | - \+44\s\(0\)\d{3}\s\d{3}\s\d{4} # 3 digit area code - | - \+44\s\(0\)\d{2}\s\d{4}\s\d{4} # 2 digit area code - ) - (\s\#\d*)? - /",$phonenumber)) { - return $phonenumber; +// Test number entered for matching format + if (!preg_match($patternRegExGB, $phonenumber)) { + return FALSE; + } else { + return TRUE; } - else { - //Simplify to 10 digit number and clean up ready for international reformat. - $phonenumber = preg_replace("/^(\+44)?\s?(\(?0\)?)?/","",$phonenumber); - $phonenumber = preg_replace("/\(/","",$phonenumber); - $phonenumber = preg_replace("/\(0/","",$phonenumber); - $phonenumber = preg_replace("/\)/","",$phonenumber); - - //If there are some spaces in the number assume some level of preformatting - if(preg_match("/ /",$phonenumber)) { - $regex = "/ - # 4 digit area code. - ( - (\d{4}) # capture 4 digit area code - \s+ # ignore required separator to make a distinction with other area codes - (\d{3}) # capture first set of numbers in the local number - \s* # ignore optional separator - (\d{3}) # capture second set of numbers in the local number - | - # 3 digit area code. - (\d{3}) # capture 3 digit area code - \s+ # ignore required seperator - (\d{3}) # capture first set of numbers in the local number - \s* # ignore possible boundary - (\d{4}) # capture second set of numbers in the local number - | - # 2 digit area code. - (\d{2}) # capture 2 digit area code - \s+ # ignore required boundary to make a distinction with other area codes - (\d{4}) # capture first set of numbers in the local number - \s* # ignore possible boundary - (\d{4}) # capture second set of numbers in the local number - ) - # capture the optional extension number - (\s*\#)? - (\d{4}|\d{3})? - /x"; - preg_match($regex, $phonenumber, $matches); - $area = $matches[2].$matches[5].$matches[8]; - $number = $matches[3].$matches[6].$matches[9].' '.$matches[4].$matches[7].$matches[10]; - $extension = $matches[12]; +} + +/** + * Extract parts from GB phone number: prefix, NSN and optional extension. + * Accepts a wide range of input formats and prefixes. This function also + * cleans up the NSN part by removing spaces, hyphens and brackets. Returned + * prefix is either +44 with space or a 0 without space. + * created by @g1smd + * + * @param string $phonenumber must be a valid UK phone number (with optional extension) + * @return array $phonenumberPartsArray Returns prefix, NSN and extension in array. + */ +function extract_gb_phone_parts($phonenumber) { + // RegEx to extract number parts: 44 prefix ($2), NSN ($3), extension ($4) + $patternGBnumberparts = '/^'; + $patternGBnumberparts .= '(\(?(?:0(?:0|11)\)?\s?\(?|\+)(44)\)?\s?)?\(?0?(?:\)\s?)?'; # country or trunk prefix + $patternGBnumberparts .= '('; + $patternGBnumberparts .= '[1-9]\d{1,4}\)?[\d\s]+'; # NSN + $patternGBnumberparts .= ')'; + $patternGBnumberparts .= '(\#\d+)?'; # optional extension + $patternGBnumberparts .= '$/x'; + if (preg_match($patternGBnumberparts, $phonenumber, $matches)) { + + // Extract NSN part of GB number + if (ISSET($matches['3'])) { + $phonenumberNSNraw = $matches['3']; + // Trim NSN and remove space, hyphen or ')' if present + $arrayReplace = array(")", "-", " "); + $phonenumberNSN = trim(str_replace($arrayReplace, "", $phonenumberNSNraw)); + + // Extract 44 prefix if present and set prefix as 0 or as +44 and space + if (ISSET($matches['2']) && $matches['2'] == '44') { + $phonenumberPrefix = '+44 '; + } else { + $phonenumberPrefix = '0'; + } + + // Extract extension + $phonenumberExtension = null; + if (ISSET($matches['4'])) { + $phonenumberExtension = ' ' . $matches['4']; + } } - //If there are no spaces in the number assume 4 digit area code. - else { - preg_match("/(\d{4})(\d{3})(\d{3})\#?(\d*)?/",$phonenumber, $matches); - $area = $matches[1]; - $number = $matches[2].' '.$matches[3]; - $extension = $matches[4]; +// } else { + } + + $phonenumberPartsArray = array( + 'NSN' => $phonenumberNSN, + 'prefix' => $phonenumberPrefix, + 'extension' => $phonenumberExtension, + ); + + return $phonenumberPartsArray; +} + +/** + * Verifies that $phonenumberNSN is a valid UK phone number range by initial + * digits and length. Tests the NSN part for length and number range. Based on + * http://www.aa-asterisk.org.uk/index.php/Number_format and + * http://www.aa-asterisk.org.uk/index.php/Regular_Expressions_for_Validating_and_Formatting_UK_Telephone_Numbers + * created by @g1smd + * + * @param string $phonenumberNSN + * @return boolean Returns boolean FALSE if the phone number is not valid. + */ +function valid_gb_phone_range($phonenumberNSN) { + // RegEx to define valid ranges for NSN by initial digits and length + $patternGBvalidrange = '/^'; + $patternGBvalidrange .= '('; + $patternGBvalidrange .= '(1[1-9]|2[03489]|3[0347]|5[56]|7[04-9]|8[047]|9[018])\d{8}'; + $patternGBvalidrange .= '|'; + $patternGBvalidrange .= '(1[2-9]\d|[58]00)\d{6}'; + $patternGBvalidrange .= '|'; + $patternGBvalidrange .= '8(001111|45464\d)'; + $patternGBvalidrange .= ')'; + $patternGBvalidrange .= '$/x'; + // Test NSN to see if it matches a valid number range + if (preg_match($patternGBvalidrange, $phonenumberNSN)) { + return TRUE; + } else { + return FALSE; + } +} + +/** + * Format GB phone numbers in correct format per number range. Based on + * http://www.aa-asterisk.org.uk/index.php/Number_format and + * http://www.aa-asterisk.org.uk/index.php/Regular_Expressions_for_Validating_and_Formatting_UK_Telephone_Numbers + * created by @g1smd + * + * @param string $phonenumberNSN Must be the 10 or 9 digit NSN part of the number. + * @return string $phonenumberNSN Returns correctly formatted NSN by length and range. + */ +function format_gb_nsn($phonenumberNSN) { + // Trim NSN + $phonenumberNSN = trim($phonenumberNSN); + // Find NSN string length + $phonenumberNSNLength = strlen($phonenumberNSN); + // RegEx patterns to define formatting by length and initial digits + // [2+8] 2d, 55, 56, 70, 76 (not 7624) + $pattern28 = '/^(?:2|5[56]|7(?:0|6(?:[013-9]|2[0-35-9])))/'; + $capture28 = '/^(\d{2})(\d{4})(\d{4})$/'; + // [3+7] 11d, 1d1, 3dd, 80d, 84d, 87d, 9dd + $pattern37 = '/^(?:1(?:1|\d1)|3|8(?:0[08]|4[2-5]|7[0-3])|9[018])/'; + $capture37 = '/^(\d{3})(\d{3})(\d{4})$/'; + // [5+5] 1dddd (12 areas) + $pattern55 = '/^(?:1(?:3873|5(?:242|39[456])|697[347]|768[347]|9467))/'; + $capture55 = '/^(\d{5})(\d{5})$/'; + // [5+4] 1ddd (1 area) + $pattern54 = '/^(?:16977[23])/'; + $capture54 = '/^(\d{5})(\d{4})$/'; + // [4+6] 1ddd, 7ddd (inc 7624) (not 70, 76) + $pattern46 = '/^(?:1|7(?:[1-5789]|624))/'; + $capture46 = '/^(\d{4})(\d{6})$/'; + // [4+5] 1ddd (40 areas) + $pattern45 = '/^(?:1(?:2(?:0[48]|54|76|9[78])|3(?:6[34]|8[46])|4(?:04|20|6[01]|8[08])|5(?:27|6[26])|6(?:06|29|35|47|59|95)|7(?:26|44|50)|8(?:27|37|84)|9(?:0[05]|35|49|63|95)))/'; + $capture45 = '/^(\d{4})(\d{5})$/'; + // [3+6] 500, 800 + $pattern36 = '/^([58]00)/'; + $capture36 = '/^(\d{3})(\d{6})$/'; + // Format numbers by leading digits and length + if ($phonenumberNSNLength == 10 && preg_match($pattern28, $phonenumberNSN)) { + if (preg_match($capture28, $phonenumberNSN, $matches)) { + $phonenumberNSN = $matches['1'] . " " . $matches['2'] . " " . $matches['3']; } - - if ($field['phone_country_code']) { - $phonenumber = '+44 (0)'.$area.' '.$number; - } - else { - $phonenumber = '0'.$area.' '.$number; - } - $phonenumber .= (empty($extension))?'':" #$extension"; - } - return $phonenumber; + } else if ($phonenumberNSNLength == 10 && preg_match($pattern37, $phonenumberNSN)) { + if (preg_match($capture37, $phonenumberNSN, $matches)) { + $phonenumberNSN = $matches['1'] . " " . $matches['2'] . " " . $matches['3']; + } + } else if ($phonenumberNSNLength == 10 && preg_match($pattern55, $phonenumberNSN)) { + if (preg_match($capture55, $phonenumberNSN, $matches)) { + $phonenumberNSN = $matches['1'] . " " . $matches['2']; + } + } else if ($phonenumberNSNLength == 9 && preg_match($pattern54, $phonenumberNSN)) { + if (preg_match($capture54, $phonenumberNSN, $matches)) { + $phonenumberNSN = $matches['1'] . " " . $matches['2']; + } + } else if ($phonenumberNSNLength == 10 && preg_match($pattern46, $phonenumberNSN)) { + if (preg_match($capture46, $phonenumberNSN, $matches)) { + $phonenumberNSN = $matches['1'] . " " . $matches['2']; + } + } else if ($phonenumberNSNLength == 9 && preg_match($pattern45, $phonenumberNSN)) { + if (preg_match($capture45, $phonenumberNSN, $matches)) { + $phonenumberNSN = $matches['1'] . " " . $matches['2']; + } + } else if ($phonenumberNSNLength == 9 && preg_match($pattern36, $phonenumberNSN)) { + if (preg_match($capture36, $phonenumberNSN, $matches)) { + $phonenumberNSN = $matches['1'] . " " . $matches['2']; + } + } else if ($phonenumberNSNLength > 5) { + // Default format for non-valid numbers (shouldn't ever get here) + if (preg_match("/^(\d)(\d{4})(\d*)$/", $phonenumberNSN, $matches)) { + $phonenumberNSN = $matches['1'] . " " . $matches['2'] . " " . $matches['3']; + } + } + return $phonenumberNSN; }