PhoneNumberUtil.php 181 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780
  1. <?php
  2. namespace libphonenumber;
  3. use libphonenumber\Leniency\AbstractLeniency;
  4. /**
  5. * Utility for international phone numbers. Functionality includes formatting, parsing and
  6. * validation.
  7. *
  8. * <p>If you use this library, and want to be notified about important changes, please sign up to
  9. * our <a href="http://groups.google.com/group/libphonenumber-discuss/about">mailing list</a>.
  10. *
  11. * NOTE: A lot of methods in this class require Region Code strings. These must be provided using
  12. * CLDR two-letter region-code format. These should be in upper-case. The list of the codes
  13. * can be found here:
  14. * http://www.unicode.org/cldr/charts/30/supplemental/territory_information.html
  15. *
  16. * @author Shaopeng Jia
  17. * @see https://github.com/google/libphonenumber
  18. */
  19. class PhoneNumberUtil
  20. {
  21. /** Flags to use when compiling regular expressions for phone numbers */
  22. const REGEX_FLAGS = 'ui'; //Unicode and case insensitive
  23. // The minimum and maximum length of the national significant number.
  24. const MIN_LENGTH_FOR_NSN = 2;
  25. // The ITU says the maximum length should be 15, but we have found longer numbers in Germany.
  26. const MAX_LENGTH_FOR_NSN = 17;
  27. // We don't allow input strings for parsing to be longer than 250 chars. This prevents malicious
  28. // input from overflowing the regular-expression engine.
  29. const MAX_INPUT_STRING_LENGTH = 250;
  30. // The maximum length of the country calling code.
  31. const MAX_LENGTH_COUNTRY_CODE = 3;
  32. const REGION_CODE_FOR_NON_GEO_ENTITY = '001';
  33. // Region-code for the unknown region.
  34. const UNKNOWN_REGION = 'ZZ';
  35. const NANPA_COUNTRY_CODE = 1;
  36. // The PLUS_SIGN signifies the international prefix.
  37. const PLUS_SIGN = '+';
  38. const PLUS_CHARS = '++';
  39. const STAR_SIGN = '*';
  40. const RFC3966_EXTN_PREFIX = ';ext=';
  41. const RFC3966_PREFIX = 'tel:';
  42. const RFC3966_PHONE_CONTEXT = ';phone-context=';
  43. const RFC3966_ISDN_SUBADDRESS = ';isub=';
  44. // We use this pattern to check if the phone number has at least three letters in it - if so, then
  45. // we treat it as a number where some phone-number digits are represented by letters.
  46. const VALID_ALPHA_PHONE_PATTERN = '(?:.*?[A-Za-z]){3}.*';
  47. // We accept alpha characters in phone numbers, ASCII only, upper and lower case.
  48. const VALID_ALPHA = 'A-Za-z';
  49. // Default extension prefix to use when formatting. This will be put in front of any extension
  50. // component of the number, after the main national number is formatted. For example, if you wish
  51. // the default extension formatting to be " extn: 3456", then you should specify " extn: " here
  52. // as the default extension prefix. This can be overridden by region-specific preferences.
  53. const DEFAULT_EXTN_PREFIX = ' ext. ';
  54. // Regular expression of acceptable punctuation found in phone numbers, used to find numbers in
  55. // text and to decide what is a viable phone number. This excludes diallable characters.
  56. // This consists of dash characters, white space characters, full stops, slashes,
  57. // square brackets, parentheses and tildes. It also includes the letter 'x' as that is found as a
  58. // placeholder for carrier information in some phone numbers. Full-width variants are also
  59. // present.
  60. const VALID_PUNCTUATION = "-x\xE2\x80\x90-\xE2\x80\x95\xE2\x88\x92\xE3\x83\xBC\xEF\xBC\x8D-\xEF\xBC\x8F \xC2\xA0\xC2\xAD\xE2\x80\x8B\xE2\x81\xA0\xE3\x80\x80()\xEF\xBC\x88\xEF\xBC\x89\xEF\xBC\xBB\xEF\xBC\xBD.\\[\\]/~\xE2\x81\x93\xE2\x88\xBC";
  61. const DIGITS = "\\p{Nd}";
  62. // Pattern that makes it easy to distinguish whether a region has a single international dialing
  63. // prefix or not. If a region has a single international prefix (e.g. 011 in USA), it will be
  64. // represented as a string that contains a sequence of ASCII digits, and possible a tilde, which
  65. // signals waiting for the tone. If there are multiple available international prefixes in a
  66. // region, they will be represented as a regex string that always contains one or more characters
  67. // that are not ASCII digits or a tilde.
  68. const SINGLE_INTERNATIONAL_PREFIX = "[\\d]+(?:[~\xE2\x81\x93\xE2\x88\xBC\xEF\xBD\x9E][\\d]+)?";
  69. const NON_DIGITS_PATTERN = "(\\D+)";
  70. // The FIRST_GROUP_PATTERN was originally set to $1 but there are some countries for which the
  71. // first group is not used in the national pattern (e.g. Argentina) so the $1 group does not match
  72. // correctly. Therefore, we use \d, so that the first group actually used in the pattern will be
  73. // matched.
  74. const FIRST_GROUP_PATTERN = "(\\$\\d)";
  75. // Constants used in the formatting rules to represent the national prefix, first group and
  76. // carrier code respectively.
  77. const NP_STRING = '$NP';
  78. const FG_STRING = '$FG';
  79. const CC_STRING = '$CC';
  80. // A pattern that is used to determine if the national prefix formatting rule has the first group
  81. // only, i.e, does not start with the national prefix. Note that the pattern explicitly allows
  82. // for unbalanced parentheses.
  83. const FIRST_GROUP_ONLY_PREFIX_PATTERN = '\\(?\\$1\\)?';
  84. public static $PLUS_CHARS_PATTERN;
  85. protected static $SEPARATOR_PATTERN;
  86. protected static $CAPTURING_DIGIT_PATTERN;
  87. protected static $VALID_START_CHAR_PATTERN;
  88. public static $SECOND_NUMBER_START_PATTERN = '[\\\\/] *x';
  89. public static $UNWANTED_END_CHAR_PATTERN = "[[\\P{N}&&\\P{L}]&&[^#]]+$";
  90. protected static $DIALLABLE_CHAR_MAPPINGS = array();
  91. protected static $CAPTURING_EXTN_DIGITS;
  92. /**
  93. * @var PhoneNumberUtil
  94. */
  95. protected static $instance;
  96. /**
  97. * Only upper-case variants of alpha characters are stored.
  98. *
  99. * @var array
  100. */
  101. protected static $ALPHA_MAPPINGS = array(
  102. 'A' => '2',
  103. 'B' => '2',
  104. 'C' => '2',
  105. 'D' => '3',
  106. 'E' => '3',
  107. 'F' => '3',
  108. 'G' => '4',
  109. 'H' => '4',
  110. 'I' => '4',
  111. 'J' => '5',
  112. 'K' => '5',
  113. 'L' => '5',
  114. 'M' => '6',
  115. 'N' => '6',
  116. 'O' => '6',
  117. 'P' => '7',
  118. 'Q' => '7',
  119. 'R' => '7',
  120. 'S' => '7',
  121. 'T' => '8',
  122. 'U' => '8',
  123. 'V' => '8',
  124. 'W' => '9',
  125. 'X' => '9',
  126. 'Y' => '9',
  127. 'Z' => '9',
  128. );
  129. /**
  130. * Map of country calling codes that use a mobile token before the area code. One example of when
  131. * this is relevant is when determining the length of the national destination code, which should
  132. * be the length of the area code plus the length of the mobile token.
  133. *
  134. * @var array
  135. */
  136. protected static $MOBILE_TOKEN_MAPPINGS = array();
  137. /**
  138. * Set of country codes that have geographically assigned mobile numbers (see GEO_MOBILE_COUNTRIES
  139. * below) which are not based on *area codes*. For example, in China mobile numbers start with a
  140. * carrier indicator, and beyond that are geographically assigned: this carrier indicator is not
  141. * considered to be an area code.
  142. *
  143. * @var array
  144. */
  145. protected static $GEO_MOBILE_COUNTRIES_WITHOUT_MOBILE_AREA_CODES;
  146. /**
  147. * Set of country codes that doesn't have national prefix, but it has area codes.
  148. *
  149. * @var array
  150. */
  151. protected static $COUNTRIES_WITHOUT_NATIONAL_PREFIX_WITH_AREA_CODES;
  152. /**
  153. * Set of country calling codes that have geographically assigned mobile numbers. This may not be
  154. * complete; we add calling codes case by case, as we find geographical mobile numbers or hear
  155. * from user reports. Note that countries like the US, where we can't distinguish between
  156. * fixed-line or mobile numbers, are not listed here, since we consider FIXED_LINE_OR_MOBILE to be
  157. * a possibly geographically-related type anyway (like FIXED_LINE).
  158. *
  159. * @var array
  160. */
  161. protected static $GEO_MOBILE_COUNTRIES;
  162. /**
  163. * For performance reasons, amalgamate both into one map.
  164. *
  165. * @var array
  166. */
  167. protected static $ALPHA_PHONE_MAPPINGS;
  168. /**
  169. * Separate map of all symbols that we wish to retain when formatting alpha numbers. This
  170. * includes digits, ASCII letters and number grouping symbols such as "-" and " ".
  171. *
  172. * @var array
  173. */
  174. protected static $ALL_PLUS_NUMBER_GROUPING_SYMBOLS;
  175. /**
  176. * Simple ASCII digits map used to populate ALPHA_PHONE_MAPPINGS and
  177. * ALL_PLUS_NUMBER_GROUPING_SYMBOLS.
  178. *
  179. * @var array
  180. */
  181. protected static $asciiDigitMappings = array(
  182. '0' => '0',
  183. '1' => '1',
  184. '2' => '2',
  185. '3' => '3',
  186. '4' => '4',
  187. '5' => '5',
  188. '6' => '6',
  189. '7' => '7',
  190. '8' => '8',
  191. '9' => '9',
  192. );
  193. /**
  194. * Regexp of all possible ways to write extensions, for use when parsing. This will be run as a
  195. * case-insensitive regexp match. Wide character versions are also provided after each ASCII
  196. * version.
  197. *
  198. * @var String
  199. */
  200. protected static $EXTN_PATTERNS_FOR_PARSING;
  201. /**
  202. * @var string
  203. * @internal
  204. */
  205. public static $EXTN_PATTERNS_FOR_MATCHING;
  206. // Regular expression of valid global-number-digits for the phone-context parameter, following the
  207. // syntax defined in RFC3966.
  208. protected static $RFC3966_VISUAL_SEPARATOR = "[\\-\\.\\(\\)]?";
  209. protected static $RFC3966_PHONE_DIGIT;
  210. protected static $RFC3966_GLOBAL_NUMBER_DIGITS;
  211. // Regular expression of valid domainname for the phone-context parameter, following the syntax
  212. // defined in RFC3966.
  213. protected static $ALPHANUM;
  214. protected static $RFC3966_DOMAINLABEL;
  215. protected static $RFC3966_TOPLABEL;
  216. protected static $RFC3966_DOMAINNAME;
  217. protected static $EXTN_PATTERN;
  218. protected static $VALID_PHONE_NUMBER_PATTERN;
  219. protected static $MIN_LENGTH_PHONE_NUMBER_PATTERN;
  220. /**
  221. * Regular expression of viable phone numbers. This is location independent. Checks we have at
  222. * least three leading digits, and only valid punctuation, alpha characters and
  223. * digits in the phone number. Does not include extension data.
  224. * The symbol 'x' is allowed here as valid punctuation since it is often used as a placeholder for
  225. * carrier codes, for example in Brazilian phone numbers. We also allow multiple "+" characters at
  226. * the start.
  227. * Corresponds to the following:
  228. * [digits]{minLengthNsn}|
  229. * plus_sign*(([punctuation]|[star])*[digits]){3,}([punctuation]|[star]|[digits]|[alpha])*
  230. *
  231. * The first reg-ex is to allow short numbers (two digits long) to be parsed if they are entered
  232. * as "15" etc, but only if there is no punctuation in them. The second expression restricts the
  233. * number of digits to three or more, but then allows them to be in international form, and to
  234. * have alpha-characters and punctuation.
  235. *
  236. * Note VALID_PUNCTUATION starts with a -, so must be the first in the range.
  237. *
  238. * @var string
  239. */
  240. protected static $VALID_PHONE_NUMBER;
  241. protected static $numericCharacters = array(
  242. "\xef\xbc\x90" => 0,
  243. "\xef\xbc\x91" => 1,
  244. "\xef\xbc\x92" => 2,
  245. "\xef\xbc\x93" => 3,
  246. "\xef\xbc\x94" => 4,
  247. "\xef\xbc\x95" => 5,
  248. "\xef\xbc\x96" => 6,
  249. "\xef\xbc\x97" => 7,
  250. "\xef\xbc\x98" => 8,
  251. "\xef\xbc\x99" => 9,
  252. "\xd9\xa0" => 0,
  253. "\xd9\xa1" => 1,
  254. "\xd9\xa2" => 2,
  255. "\xd9\xa3" => 3,
  256. "\xd9\xa4" => 4,
  257. "\xd9\xa5" => 5,
  258. "\xd9\xa6" => 6,
  259. "\xd9\xa7" => 7,
  260. "\xd9\xa8" => 8,
  261. "\xd9\xa9" => 9,
  262. "\xdb\xb0" => 0,
  263. "\xdb\xb1" => 1,
  264. "\xdb\xb2" => 2,
  265. "\xdb\xb3" => 3,
  266. "\xdb\xb4" => 4,
  267. "\xdb\xb5" => 5,
  268. "\xdb\xb6" => 6,
  269. "\xdb\xb7" => 7,
  270. "\xdb\xb8" => 8,
  271. "\xdb\xb9" => 9,
  272. "\xe1\xa0\x90" => 0,
  273. "\xe1\xa0\x91" => 1,
  274. "\xe1\xa0\x92" => 2,
  275. "\xe1\xa0\x93" => 3,
  276. "\xe1\xa0\x94" => 4,
  277. "\xe1\xa0\x95" => 5,
  278. "\xe1\xa0\x96" => 6,
  279. "\xe1\xa0\x97" => 7,
  280. "\xe1\xa0\x98" => 8,
  281. "\xe1\xa0\x99" => 9,
  282. );
  283. /**
  284. * The set of county calling codes that map to the non-geo entity region ("001").
  285. *
  286. * @var array
  287. */
  288. protected $countryCodesForNonGeographicalRegion = array();
  289. /**
  290. * The set of regions the library supports.
  291. *
  292. * @var array
  293. */
  294. protected $supportedRegions = array();
  295. /**
  296. * A mapping from a country calling code to the region codes which denote the region represented
  297. * by that country calling code. In the case of multiple regions sharing a calling code, such as
  298. * the NANPA regions, the one indicated with "isMainCountryForCode" in the metadata should be
  299. * first.
  300. *
  301. * @var array
  302. */
  303. protected $countryCallingCodeToRegionCodeMap = array();
  304. /**
  305. * The set of regions that share country calling code 1.
  306. *
  307. * @var array
  308. */
  309. protected $nanpaRegions = array();
  310. /**
  311. * @var MetadataSourceInterface
  312. */
  313. protected $metadataSource;
  314. /**
  315. * @var MatcherAPIInterface
  316. */
  317. protected $matcherAPI;
  318. /**
  319. * This class implements a singleton, so the only constructor is protected.
  320. * @param MetadataSourceInterface $metadataSource
  321. * @param $countryCallingCodeToRegionCodeMap
  322. */
  323. protected function __construct(MetadataSourceInterface $metadataSource, $countryCallingCodeToRegionCodeMap)
  324. {
  325. $this->metadataSource = $metadataSource;
  326. $this->countryCallingCodeToRegionCodeMap = $countryCallingCodeToRegionCodeMap;
  327. $this->init();
  328. $this->matcherAPI = RegexBasedMatcher::create();
  329. static::initExtnPatterns();
  330. static::initExtnPattern();
  331. static::initRFC3966Patterns();
  332. static::$PLUS_CHARS_PATTERN = '[' . static::PLUS_CHARS . ']+';
  333. static::$SEPARATOR_PATTERN = '[' . static::VALID_PUNCTUATION . ']+';
  334. static::$CAPTURING_DIGIT_PATTERN = '(' . static::DIGITS . ')';
  335. static::initValidStartCharPattern();
  336. static::initAlphaPhoneMappings();
  337. static::initDiallableCharMappings();
  338. static::$ALL_PLUS_NUMBER_GROUPING_SYMBOLS = array();
  339. // Put (lower letter -> upper letter) and (upper letter -> upper letter) mappings.
  340. foreach (static::$ALPHA_MAPPINGS as $c => $value) {
  341. static::$ALL_PLUS_NUMBER_GROUPING_SYMBOLS[strtolower($c)] = $c;
  342. static::$ALL_PLUS_NUMBER_GROUPING_SYMBOLS[$c] = $c;
  343. }
  344. static::$ALL_PLUS_NUMBER_GROUPING_SYMBOLS += static::$asciiDigitMappings;
  345. static::$ALL_PLUS_NUMBER_GROUPING_SYMBOLS['-'] = '-';
  346. static::$ALL_PLUS_NUMBER_GROUPING_SYMBOLS["\xEF\xBC\x8D"] = '-';
  347. static::$ALL_PLUS_NUMBER_GROUPING_SYMBOLS["\xE2\x80\x90"] = '-';
  348. static::$ALL_PLUS_NUMBER_GROUPING_SYMBOLS["\xE2\x80\x91"] = '-';
  349. static::$ALL_PLUS_NUMBER_GROUPING_SYMBOLS["\xE2\x80\x92"] = '-';
  350. static::$ALL_PLUS_NUMBER_GROUPING_SYMBOLS["\xE2\x80\x93"] = '-';
  351. static::$ALL_PLUS_NUMBER_GROUPING_SYMBOLS["\xE2\x80\x94"] = '-';
  352. static::$ALL_PLUS_NUMBER_GROUPING_SYMBOLS["\xE2\x80\x95"] = '-';
  353. static::$ALL_PLUS_NUMBER_GROUPING_SYMBOLS["\xE2\x88\x92"] = '-';
  354. static::$ALL_PLUS_NUMBER_GROUPING_SYMBOLS['/'] = '/';
  355. static::$ALL_PLUS_NUMBER_GROUPING_SYMBOLS["\xEF\xBC\x8F"] = '/';
  356. static::$ALL_PLUS_NUMBER_GROUPING_SYMBOLS[' '] = ' ';
  357. static::$ALL_PLUS_NUMBER_GROUPING_SYMBOLS["\xE3\x80\x80"] = ' ';
  358. static::$ALL_PLUS_NUMBER_GROUPING_SYMBOLS["\xE2\x81\xA0"] = ' ';
  359. static::$ALL_PLUS_NUMBER_GROUPING_SYMBOLS['.'] = '.';
  360. static::$ALL_PLUS_NUMBER_GROUPING_SYMBOLS["\xEF\xBC\x8E"] = '.';
  361. static::initValidPhoneNumberPatterns();
  362. static::$UNWANTED_END_CHAR_PATTERN = '[^' . static::DIGITS . static::VALID_ALPHA . '#]+$';
  363. static::initMobileTokenMappings();
  364. static::$GEO_MOBILE_COUNTRIES_WITHOUT_MOBILE_AREA_CODES = array();
  365. static::$GEO_MOBILE_COUNTRIES_WITHOUT_MOBILE_AREA_CODES[] = 86; // China
  366. static::$COUNTRIES_WITHOUT_NATIONAL_PREFIX_WITH_AREA_CODES = array();
  367. static::$COUNTRIES_WITHOUT_NATIONAL_PREFIX_WITH_AREA_CODES[] = 52; // Mexico
  368. static::$GEO_MOBILE_COUNTRIES = array();
  369. static::$GEO_MOBILE_COUNTRIES[] = 52; // Mexico
  370. static::$GEO_MOBILE_COUNTRIES[] = 54; // Argentina
  371. static::$GEO_MOBILE_COUNTRIES[] = 55; // Brazil
  372. static::$GEO_MOBILE_COUNTRIES[] = 62; // Indonesia: some prefixes only (fixed CMDA wireless)
  373. static::$GEO_MOBILE_COUNTRIES = array_merge(static::$GEO_MOBILE_COUNTRIES, static::$GEO_MOBILE_COUNTRIES_WITHOUT_MOBILE_AREA_CODES);
  374. }
  375. /**
  376. * Gets a {@link PhoneNumberUtil} instance to carry out international phone number formatting,
  377. * parsing or validation. The instance is loaded with phone number metadata for a number of most
  378. * commonly used regions.
  379. *
  380. * <p>The {@link PhoneNumberUtil} is implemented as a singleton. Therefore calling getInstance
  381. * multiple times will only result in one instance being created.
  382. *
  383. * @param string|null $baseFileLocation
  384. * @param array|null $countryCallingCodeToRegionCodeMap
  385. * @param MetadataLoaderInterface|null $metadataLoader
  386. * @param MetadataSourceInterface|null $metadataSource
  387. * @return PhoneNumberUtil instance
  388. */
  389. public static function getInstance($baseFileLocation = null, array $countryCallingCodeToRegionCodeMap = null, MetadataLoaderInterface $metadataLoader = null, MetadataSourceInterface $metadataSource = null)
  390. {
  391. if (static::$instance === null) {
  392. if ($countryCallingCodeToRegionCodeMap === null) {
  393. $countryCallingCodeToRegionCodeMap = CountryCodeToRegionCodeMap::$countryCodeToRegionCodeMap;
  394. }
  395. if ($metadataLoader === null) {
  396. $metadataLoader = new DefaultMetadataLoader();
  397. }
  398. if ($metadataSource === null) {
  399. if ($baseFileLocation === null) {
  400. $baseFileLocation = __DIR__ . '/data/PhoneNumberMetadata';
  401. }
  402. $metadataSource = new MultiFileMetadataSourceImpl($metadataLoader, $baseFileLocation);
  403. }
  404. static::$instance = new static($metadataSource, $countryCallingCodeToRegionCodeMap);
  405. }
  406. return static::$instance;
  407. }
  408. protected function init()
  409. {
  410. $supportedRegions = array(array());
  411. foreach ($this->countryCallingCodeToRegionCodeMap as $countryCode => $regionCodes) {
  412. // We can assume that if the country calling code maps to the non-geo entity region code then
  413. // that's the only region code it maps to.
  414. if (count($regionCodes) === 1 && static::REGION_CODE_FOR_NON_GEO_ENTITY === $regionCodes[0]) {
  415. // This is the subset of all country codes that map to the non-geo entity region code.
  416. $this->countryCodesForNonGeographicalRegion[] = $countryCode;
  417. } else {
  418. // The supported regions set does not include the "001" non-geo entity region code.
  419. $supportedRegions[] = $regionCodes;
  420. }
  421. }
  422. $this->supportedRegions = call_user_func_array('array_merge', $supportedRegions);
  423. // If the non-geo entity still got added to the set of supported regions it must be because
  424. // there are entries that list the non-geo entity alongside normal regions (which is wrong).
  425. // If we discover this, remove the non-geo entity from the set of supported regions and log.
  426. $idx_region_code_non_geo_entity = array_search(static::REGION_CODE_FOR_NON_GEO_ENTITY, $this->supportedRegions);
  427. if ($idx_region_code_non_geo_entity !== false) {
  428. unset($this->supportedRegions[$idx_region_code_non_geo_entity]);
  429. }
  430. $this->nanpaRegions = $this->countryCallingCodeToRegionCodeMap[static::NANPA_COUNTRY_CODE];
  431. }
  432. /**
  433. * @internal
  434. */
  435. public static function initExtnPatterns()
  436. {
  437. static::$EXTN_PATTERNS_FOR_PARSING = static::createExtnPattern(true);
  438. static::$EXTN_PATTERNS_FOR_MATCHING = static::createExtnPattern(false);
  439. }
  440. /**
  441. * Helper method for constructing regular expressions for parsing. Creates an expression that
  442. * captures up to maxLength digits.
  443. * @param int $maxLength
  444. * @return string
  445. */
  446. protected static function extnDigits($maxLength)
  447. {
  448. return '(' . self::DIGITS . '{1,' . $maxLength . '})';
  449. }
  450. /**
  451. * Helper initialiser method to create the regular-expression pattern to match extensions.
  452. * Note that there are currently six capturing groups for the extension itself. If this number is
  453. * changed, MaybeStripExtension needs to be updated.
  454. *
  455. * @param boolean $forParsing
  456. * @return string
  457. */
  458. protected static function createExtnPattern($forParsing)
  459. {
  460. // We cap the maximum length of an extension based on the ambiguity of the way the extension is
  461. // prefixed. As per ITU, the officially allowed length for extensions is actually 40, but we
  462. // don't support this since we haven't seen real examples and this introduces many false
  463. // interpretations as the extension labels are not standardized.
  464. $extLimitAfterExplicitLabel = 20;
  465. $extLimitAfterLikelyLabel = 15;
  466. $extLimitAfterAmbiguousChar = 9;
  467. $extLimitWhenNotSure = 6;
  468. $possibleSeparatorsBetweenNumberAndExtLabel = "[ \xC2\xA0\\t,]*";
  469. // Optional full stop (.) or colon, followed by zero or more spaces/tabs/commas.
  470. $possibleCharsAfterExtLabel = "[:\\.\xEf\xBC\x8E]?[ \xC2\xA0\\t,-]*";
  471. $optionalExtnSuffix = "#?";
  472. // Here the extension is called out in more explicit way, i.e mentioning it obvious patterns
  473. // like "ext.". Canonical-equivalence doesn't seem to be an option with Android java, so we
  474. // allow two options for representing the accented o - the character itself, and one in the
  475. // unicode decomposed form with the combining acute accent.
  476. $explicitExtLabels = "(?:e?xt(?:ensi(?:o\xCC\x81?|\xC3\xB3))?n?|\xEF\xBD\x85?\xEF\xBD\x98\xEF\xBD\x94\xEF\xBD\x8E?|\xD0\xB4\xD0\xBE\xD0\xB1|anexo)";
  477. // One-character symbols that can be used to indicate an extension, and less commonly used
  478. // or more ambiguous extension labels.
  479. $ambiguousExtLabels = "(?:[x\xEF\xBD\x98#\xEF\xBC\x83~\xEF\xBD\x9E]|int|\xEF\xBD\x89\xEF\xBD\x8E\xEF\xBD\x94)";
  480. // When extension is not separated clearly.
  481. $ambiguousSeparator = "[- ]+";
  482. $rfcExtn = static::RFC3966_EXTN_PREFIX . static::extnDigits($extLimitAfterExplicitLabel);
  483. $explicitExtn = $possibleSeparatorsBetweenNumberAndExtLabel . $explicitExtLabels
  484. . $possibleCharsAfterExtLabel . static::extnDigits($extLimitAfterExplicitLabel)
  485. . $optionalExtnSuffix;
  486. $ambiguousExtn = $possibleSeparatorsBetweenNumberAndExtLabel . $ambiguousExtLabels
  487. . $possibleCharsAfterExtLabel . static::extnDigits($extLimitAfterAmbiguousChar) . $optionalExtnSuffix;
  488. $americanStyleExtnWithSuffix = $ambiguousSeparator . static::extnDigits($extLimitWhenNotSure) . "#";
  489. // The first regular expression covers RFC 3966 format, where the extension is added using
  490. // ";ext=". The second more generic where extension is mentioned with explicit labels like
  491. // "ext:". In both the above cases we allow more numbers in extension than any other extension
  492. // labels. The third one captures when single character extension labels or less commonly used
  493. // labels are used. In such cases we capture fewer extension digits in order to reduce the
  494. // chance of falsely interpreting two numbers beside each other as a number + extension. The
  495. // fourth one covers the special case of American numbers where the extension is written with a
  496. // hash at the end, such as "- 503#".
  497. $extensionPattern =
  498. $rfcExtn . "|"
  499. . $explicitExtn . "|"
  500. . $ambiguousExtn . "|"
  501. . $americanStyleExtnWithSuffix;
  502. // Additional pattern that is supported when parsing extensions, not when matching.
  503. if ($forParsing) {
  504. // This is same as possibleSeparatorsBetweenNumberAndExtLabel, but not matching comma as
  505. // extension label may have it.
  506. $possibleSeparatorsNumberExtLabelNoComma = "[ \xC2\xA0\\t]*";
  507. // ",," is commonly used for auto dialling the extension when connected. First comma is matched
  508. // through possibleSeparatorsBetweenNumberAndExtLabel, so we do not repeat it here. Semi-colon
  509. // works in Iphone and Android also to pop up a button with the extension number following.
  510. $autoDiallingAndExtLabelsFound = "(?:,{2}|;)";
  511. $autoDiallingExtn = $possibleSeparatorsNumberExtLabelNoComma
  512. . $autoDiallingAndExtLabelsFound . $possibleCharsAfterExtLabel
  513. . static::extnDigits($extLimitAfterLikelyLabel) . $optionalExtnSuffix;
  514. $onlyCommasExtn = $possibleSeparatorsNumberExtLabelNoComma
  515. . '(?:,)+' . $possibleCharsAfterExtLabel . static::extnDigits($extLimitAfterAmbiguousChar)
  516. . $optionalExtnSuffix;
  517. // Here the first pattern is exclusively for extension autodialling formats which are used
  518. // when dialling and in this case we accept longer extensions. However, the second pattern
  519. // is more liberal on the number of commas that acts as extension labels, so we have a strict
  520. // cap on the number of digits in such extensions.
  521. return $extensionPattern . "|"
  522. . $autoDiallingExtn . "|"
  523. . $onlyCommasExtn;
  524. }
  525. return $extensionPattern;
  526. }
  527. protected static function initExtnPattern()
  528. {
  529. static::$EXTN_PATTERN = '/(?:' . static::$EXTN_PATTERNS_FOR_PARSING . ')$/' . static::REGEX_FLAGS;
  530. }
  531. protected static function initRFC3966Patterns()
  532. {
  533. static::$RFC3966_PHONE_DIGIT = '(' . static::DIGITS . '|' . static::$RFC3966_VISUAL_SEPARATOR . ')';
  534. static::$RFC3966_GLOBAL_NUMBER_DIGITS = "^\\" . static::PLUS_SIGN . static::$RFC3966_PHONE_DIGIT . "*" . static::DIGITS . static::$RFC3966_PHONE_DIGIT . "*$";
  535. static::$ALPHANUM = static::VALID_ALPHA . static::DIGITS;
  536. static::$RFC3966_DOMAINLABEL = '[' . static::$ALPHANUM . "]+((\\-)*[" . static::$ALPHANUM . "])*";
  537. static::$RFC3966_TOPLABEL = '[' . static::VALID_ALPHA . "]+((\\-)*[" . static::$ALPHANUM . "])*";
  538. static::$RFC3966_DOMAINNAME = "^(" . static::$RFC3966_DOMAINLABEL . "\\.)*" . static::$RFC3966_TOPLABEL . "\\.?$";
  539. }
  540. protected static function initValidPhoneNumberPatterns()
  541. {
  542. static::initExtnPatterns();
  543. static::$MIN_LENGTH_PHONE_NUMBER_PATTERN = '[' . static::DIGITS . ']{' . static::MIN_LENGTH_FOR_NSN . '}';
  544. static::$VALID_PHONE_NUMBER = '[' . static::PLUS_CHARS . ']*(?:[' . static::VALID_PUNCTUATION . static::STAR_SIGN . ']*[' . static::DIGITS . ']){3,}[' . static::VALID_PUNCTUATION . static::STAR_SIGN . static::VALID_ALPHA . static::DIGITS . ']*';
  545. static::$VALID_PHONE_NUMBER_PATTERN = '%^' . static::$MIN_LENGTH_PHONE_NUMBER_PATTERN . '$|^' . static::$VALID_PHONE_NUMBER . '(?:' . static::$EXTN_PATTERNS_FOR_PARSING . ')?$%' . static::REGEX_FLAGS;
  546. }
  547. protected static function initAlphaPhoneMappings()
  548. {
  549. static::$ALPHA_PHONE_MAPPINGS = static::$ALPHA_MAPPINGS + static::$asciiDigitMappings;
  550. }
  551. protected static function initValidStartCharPattern()
  552. {
  553. static::$VALID_START_CHAR_PATTERN = '[' . static::PLUS_CHARS . static::DIGITS . ']';
  554. }
  555. protected static function initMobileTokenMappings()
  556. {
  557. static::$MOBILE_TOKEN_MAPPINGS = array();
  558. static::$MOBILE_TOKEN_MAPPINGS['54'] = '9';
  559. }
  560. protected static function initDiallableCharMappings()
  561. {
  562. static::$DIALLABLE_CHAR_MAPPINGS = static::$asciiDigitMappings;
  563. static::$DIALLABLE_CHAR_MAPPINGS[static::PLUS_SIGN] = static::PLUS_SIGN;
  564. static::$DIALLABLE_CHAR_MAPPINGS['*'] = '*';
  565. static::$DIALLABLE_CHAR_MAPPINGS['#'] = '#';
  566. }
  567. /**
  568. * Used for testing purposes only to reset the PhoneNumberUtil singleton to null.
  569. */
  570. public static function resetInstance()
  571. {
  572. static::$instance = null;
  573. }
  574. /**
  575. * Converts all alpha characters in a number to their respective digits on a keypad, but retains
  576. * existing formatting.
  577. *
  578. * @param string $number
  579. * @return string
  580. */
  581. public static function convertAlphaCharactersInNumber($number)
  582. {
  583. if (static::$ALPHA_PHONE_MAPPINGS === null) {
  584. static::initAlphaPhoneMappings();
  585. }
  586. return static::normalizeHelper($number, static::$ALPHA_PHONE_MAPPINGS, false);
  587. }
  588. /**
  589. * Normalizes a string of characters representing a phone number by replacing all characters found
  590. * in the accompanying map with the values therein, and stripping all other characters if
  591. * removeNonMatches is true.
  592. *
  593. * @param string $number a string of characters representing a phone number
  594. * @param array $normalizationReplacements a mapping of characters to what they should be replaced by in
  595. * the normalized version of the phone number.
  596. * @param bool $removeNonMatches indicates whether characters that are not able to be replaced.
  597. * should be stripped from the number. If this is false, they will be left unchanged in the number.
  598. * @return string the normalized string version of the phone number.
  599. */
  600. protected static function normalizeHelper($number, array $normalizationReplacements, $removeNonMatches)
  601. {
  602. $normalizedNumber = '';
  603. $strLength = mb_strlen($number, 'UTF-8');
  604. for ($i = 0; $i < $strLength; $i++) {
  605. $character = mb_substr($number, $i, 1, 'UTF-8');
  606. if (isset($normalizationReplacements[mb_strtoupper($character, 'UTF-8')])) {
  607. $normalizedNumber .= $normalizationReplacements[mb_strtoupper($character, 'UTF-8')];
  608. } elseif (!$removeNonMatches) {
  609. $normalizedNumber .= $character;
  610. }
  611. // If neither of the above are true, we remove this character.
  612. }
  613. return $normalizedNumber;
  614. }
  615. /**
  616. * Helper function to check if the national prefix formatting rule has the first group only, i.e.,
  617. * does not start with the national prefix.
  618. *
  619. * @param string $nationalPrefixFormattingRule
  620. * @return bool
  621. */
  622. public static function formattingRuleHasFirstGroupOnly($nationalPrefixFormattingRule)
  623. {
  624. $firstGroupOnlyPrefixPatternMatcher = new Matcher(
  625. static::FIRST_GROUP_ONLY_PREFIX_PATTERN,
  626. $nationalPrefixFormattingRule
  627. );
  628. return $nationalPrefixFormattingRule === ''
  629. || $firstGroupOnlyPrefixPatternMatcher->matches();
  630. }
  631. /**
  632. * Returns all regions the library has metadata for.
  633. *
  634. * @return array An unordered array of the two-letter region codes for every geographical region the
  635. * library supports
  636. */
  637. public function getSupportedRegions()
  638. {
  639. return $this->supportedRegions;
  640. }
  641. /**
  642. * Returns all global network calling codes the library has metadata for.
  643. *
  644. * @return array An unordered array of the country calling codes for every non-geographical entity
  645. * the library supports
  646. */
  647. public function getSupportedGlobalNetworkCallingCodes()
  648. {
  649. return $this->countryCodesForNonGeographicalRegion;
  650. }
  651. /**
  652. * Returns all country calling codes the library has metadata for, covering both non-geographical
  653. * entities (global network calling codes) and those used for geographical entities. The could be
  654. * used to populate a drop-down box of country calling codes for a phone-number widget, for
  655. * instance.
  656. *
  657. * @return array An unordered array of the country calling codes for every geographical and
  658. * non-geographical entity the library supports
  659. */
  660. public function getSupportedCallingCodes()
  661. {
  662. return array_keys($this->countryCallingCodeToRegionCodeMap);
  663. }
  664. /**
  665. * Returns true if there is any possible number data set for a particular PhoneNumberDesc.
  666. *
  667. * @param PhoneNumberDesc $desc
  668. * @return bool
  669. */
  670. protected static function descHasPossibleNumberData(PhoneNumberDesc $desc)
  671. {
  672. // If this is empty, it means numbers of this type inherit from the "general desc" -> the value
  673. // '-1' means that no numbers exist for this type.
  674. $possibleLength = $desc->getPossibleLength();
  675. return count($possibleLength) != 1 || $possibleLength[0] != -1;
  676. }
  677. /**
  678. * Returns true if there is any data set for a particular PhoneNumberDesc.
  679. *
  680. * @param PhoneNumberDesc $desc
  681. * @return bool
  682. */
  683. protected static function descHasData(PhoneNumberDesc $desc)
  684. {
  685. // Checking most properties since we don't know what's present, since a custom build may have
  686. // stripped just one of them (e.g. liteBuild strips exampleNumber). We don't bother checking the
  687. // possibleLengthsLocalOnly, since if this is the only thing that's present we don't really
  688. // support the type at all: no type-specific methods will work with only this data.
  689. return $desc->hasExampleNumber()
  690. || static::descHasPossibleNumberData($desc)
  691. || $desc->hasNationalNumberPattern();
  692. }
  693. /**
  694. * Returns the types we have metadata for based on the PhoneMetadata object passed in.
  695. *
  696. * @param PhoneMetadata $metadata
  697. * @return array
  698. */
  699. private function getSupportedTypesForMetadata(PhoneMetadata $metadata)
  700. {
  701. $types = array();
  702. foreach (array_keys(PhoneNumberType::values()) as $type) {
  703. if ($type === PhoneNumberType::FIXED_LINE_OR_MOBILE || $type === PhoneNumberType::UNKNOWN) {
  704. // Never return FIXED_LINE_OR_MOBILE (it is a convenience type, and represents that a
  705. // particular number type can't be determined) or UNKNOWN (the non-type).
  706. continue;
  707. }
  708. if (self::descHasData($this->getNumberDescByType($metadata, $type))) {
  709. $types[] = $type;
  710. }
  711. }
  712. return $types;
  713. }
  714. /**
  715. * Returns the types for a given region which the library has metadata for. Will not include
  716. * FIXED_LINE_OR_MOBILE (if the numbers in this region could be classified as FIXED_LINE_OR_MOBILE,
  717. * both FIXED_LINE and MOBILE would be present) and UNKNOWN.
  718. *
  719. * No types will be returned for invalid or unknown region codes.
  720. *
  721. * @param string $regionCode
  722. * @return array
  723. */
  724. public function getSupportedTypesForRegion($regionCode)
  725. {
  726. if (!$this->isValidRegionCode($regionCode)) {
  727. return array();
  728. }
  729. $metadata = $this->getMetadataForRegion($regionCode);
  730. return $this->getSupportedTypesForMetadata($metadata);
  731. }
  732. /**
  733. * Returns the types for a country-code belonging to a non-geographical entity which the library
  734. * has metadata for. Will not include FIXED_LINE_OR_MOBILE (if numbers for this non-geographical
  735. * entity could be classified as FIXED_LINE_OR_MOBILE, both FIXED_LINE and MOBILE would be
  736. * present) and UNKNOWN.
  737. *
  738. * @param int $countryCallingCode
  739. * @return array
  740. */
  741. public function getSupportedTypesForNonGeoEntity($countryCallingCode)
  742. {
  743. $metadata = $this->getMetadataForNonGeographicalRegion($countryCallingCode);
  744. if ($metadata === null) {
  745. return array();
  746. }
  747. return $this->getSupportedTypesForMetadata($metadata);
  748. }
  749. /**
  750. * Gets the length of the geographical area code from the {@code nationalNumber} field of the
  751. * PhoneNumber object passed in, so that clients could use it to split a national significant
  752. * number into geographical area code and subscriber number. It works in such a way that the
  753. * resultant subscriber number should be diallable, at least on some devices. An example of how
  754. * this could be used:
  755. *
  756. * <code>
  757. * $phoneUtil = PhoneNumberUtil::getInstance();
  758. * $number = $phoneUtil->parse("16502530000", "US");
  759. * $nationalSignificantNumber = $phoneUtil->getNationalSignificantNumber($number);
  760. *
  761. * $areaCodeLength = $phoneUtil->getLengthOfGeographicalAreaCode($number);
  762. * if ($areaCodeLength > 0)
  763. * {
  764. * $areaCode = substr($nationalSignificantNumber, 0,$areaCodeLength);
  765. * $subscriberNumber = substr($nationalSignificantNumber, $areaCodeLength);
  766. * } else {
  767. * $areaCode = "";
  768. * $subscriberNumber = $nationalSignificantNumber;
  769. * }
  770. * </code>
  771. *
  772. * N.B.: area code is a very ambiguous concept, so the I18N team generally recommends against
  773. * using it for most purposes, but recommends using the more general {@code nationalNumber}
  774. * instead. Read the following carefully before deciding to use this method:
  775. * <ul>
  776. * <li> geographical area codes change over time, and this method honors those changes;
  777. * therefore, it doesn't guarantee the stability of the result it produces.
  778. * <li> subscriber numbers may not be diallable from all devices (notably mobile devices, which
  779. * typically requires the full national_number to be dialled in most regions).
  780. * <li> most non-geographical numbers have no area codes, including numbers from non-geographical
  781. * entities
  782. * <li> some geographical numbers have no area codes.
  783. * </ul>
  784. *
  785. * @param PhoneNumber $number PhoneNumber object for which clients want to know the length of the area code.
  786. * @return int the length of area code of the PhoneNumber object passed in.
  787. */
  788. public function getLengthOfGeographicalAreaCode(PhoneNumber $number)
  789. {
  790. $metadata = $this->getMetadataForRegion($this->getRegionCodeForNumber($number));
  791. if ($metadata === null) {
  792. return 0;
  793. }
  794. $countryCallingCode = $number->getCountryCode();
  795. // If a country doesn't use a national prefix, and this number doesn't have an Italian leading
  796. // zero, we assume it is a closed dialling plan with no area codes.
  797. // Note:this is our general assumption, but there are exceptions which are tracked in
  798. // COUNTRIES_WITHOUT_NATIONAL_PREFIX_WITH_AREA_CODES.
  799. if (!$metadata->hasNationalPrefix() && !$number->isItalianLeadingZero() && !in_array($countryCallingCode, self::$COUNTRIES_WITHOUT_NATIONAL_PREFIX_WITH_AREA_CODES)) {
  800. return 0;
  801. }
  802. $type = $this->getNumberType($number);
  803. if ($type === PhoneNumberType::MOBILE
  804. // Note this is a rough heuristic; it doesn't cover Indonesia well, for example, where area
  805. // codes are present for some mobile phones but not for others. We have no better way of
  806. // representing this in the metadata at this point.
  807. && in_array($countryCallingCode, self::$GEO_MOBILE_COUNTRIES_WITHOUT_MOBILE_AREA_CODES)
  808. ) {
  809. return 0;
  810. }
  811. if (!$this->isNumberGeographical($type, $countryCallingCode)) {
  812. return 0;
  813. }
  814. return $this->getLengthOfNationalDestinationCode($number);
  815. }
  816. /**
  817. * Returns the metadata for the given region code or {@code null} if the region code is invalid
  818. * or unknown.
  819. *
  820. * @param string $regionCode
  821. * @return null|PhoneMetadata
  822. */
  823. public function getMetadataForRegion($regionCode)
  824. {
  825. if (!$this->isValidRegionCode($regionCode)) {
  826. return null;
  827. }
  828. return $this->metadataSource->getMetadataForRegion($regionCode);
  829. }
  830. /**
  831. * Helper function to check region code is not unknown or null.
  832. *
  833. * @param string $regionCode
  834. * @return bool
  835. */
  836. protected function isValidRegionCode($regionCode)
  837. {
  838. return $regionCode !== null && !is_numeric($regionCode) && in_array(strtoupper($regionCode), $this->supportedRegions);
  839. }
  840. /**
  841. * Returns the region where a phone number is from. This could be used for geocoding at the region
  842. * level. Only guarantees correct results for valid, full numbers (not short-codes, or invalid
  843. * numbers).
  844. *
  845. * @param PhoneNumber $number the phone number whose origin we want to know
  846. * @return null|string the region where the phone number is from, or null if no region matches this calling
  847. * code
  848. */
  849. public function getRegionCodeForNumber(PhoneNumber $number)
  850. {
  851. $countryCode = $number->getCountryCode();
  852. if (!isset($this->countryCallingCodeToRegionCodeMap[$countryCode])) {
  853. return null;
  854. }
  855. $regions = $this->countryCallingCodeToRegionCodeMap[$countryCode];
  856. if (count($regions) == 1) {
  857. return $regions[0];
  858. }
  859. return $this->getRegionCodeForNumberFromRegionList($number, $regions);
  860. }
  861. /**
  862. * Returns the region code for a number from the list of region codes passing in.
  863. *
  864. * @param PhoneNumber $number
  865. * @param array $regionCodes
  866. * @return null|string
  867. */
  868. protected function getRegionCodeForNumberFromRegionList(PhoneNumber $number, array $regionCodes)
  869. {
  870. $nationalNumber = $this->getNationalSignificantNumber($number);
  871. foreach ($regionCodes as $regionCode) {
  872. // If leadingDigits is present, use this. Otherwise, do full validation.
  873. // Metadata cannot be null because the region codes come from the country calling code map.
  874. $metadata = $this->getMetadataForRegion($regionCode);
  875. if ($metadata->hasLeadingDigits()) {
  876. $nbMatches = preg_match(
  877. '/' . $metadata->getLeadingDigits() . '/',
  878. $nationalNumber,
  879. $matches,
  880. PREG_OFFSET_CAPTURE
  881. );
  882. if ($nbMatches > 0 && $matches[0][1] === 0) {
  883. return $regionCode;
  884. }
  885. } elseif ($this->getNumberTypeHelper($nationalNumber, $metadata) != PhoneNumberType::UNKNOWN) {
  886. return $regionCode;
  887. }
  888. }
  889. return null;
  890. }
  891. /**
  892. * Gets the national significant number of the a phone number. Note a national significant number
  893. * doesn't contain a national prefix or any formatting.
  894. *
  895. * @param PhoneNumber $number the phone number for which the national significant number is needed
  896. * @return string the national significant number of the PhoneNumber object passed in
  897. */
  898. public function getNationalSignificantNumber(PhoneNumber $number)
  899. {
  900. // If leading zero(s) have been set, we prefix this now. Note this is not a national prefix.
  901. $nationalNumber = '';
  902. if ($number->isItalianLeadingZero() && $number->getNumberOfLeadingZeros() > 0) {
  903. $zeros = str_repeat('0', $number->getNumberOfLeadingZeros());
  904. $nationalNumber .= $zeros;
  905. }
  906. $nationalNumber .= $number->getNationalNumber();
  907. return $nationalNumber;
  908. }
  909. /**
  910. * Returns the type of number passed in i.e Toll free, premium.
  911. *
  912. * @param string $nationalNumber
  913. * @param PhoneMetadata $metadata
  914. * @return int PhoneNumberType constant
  915. */
  916. protected function getNumberTypeHelper($nationalNumber, PhoneMetadata $metadata)
  917. {
  918. if (!$this->isNumberMatchingDesc($nationalNumber, $metadata->getGeneralDesc())) {
  919. return PhoneNumberType::UNKNOWN;
  920. }
  921. if ($this->isNumberMatchingDesc($nationalNumber, $metadata->getPremiumRate())) {
  922. return PhoneNumberType::PREMIUM_RATE;
  923. }
  924. if ($this->isNumberMatchingDesc($nationalNumber, $metadata->getTollFree())) {
  925. return PhoneNumberType::TOLL_FREE;
  926. }
  927. if ($this->isNumberMatchingDesc($nationalNumber, $metadata->getSharedCost())) {
  928. return PhoneNumberType::SHARED_COST;
  929. }
  930. if ($this->isNumberMatchingDesc($nationalNumber, $metadata->getVoip())) {
  931. return PhoneNumberType::VOIP;
  932. }
  933. if ($this->isNumberMatchingDesc($nationalNumber, $metadata->getPersonalNumber())) {
  934. return PhoneNumberType::PERSONAL_NUMBER;
  935. }
  936. if ($this->isNumberMatchingDesc($nationalNumber, $metadata->getPager())) {
  937. return PhoneNumberType::PAGER;
  938. }
  939. if ($this->isNumberMatchingDesc($nationalNumber, $metadata->getUan())) {
  940. return PhoneNumberType::UAN;
  941. }
  942. if ($this->isNumberMatchingDesc($nationalNumber, $metadata->getVoicemail())) {
  943. return PhoneNumberType::VOICEMAIL;
  944. }
  945. $isFixedLine = $this->isNumberMatchingDesc($nationalNumber, $metadata->getFixedLine());
  946. if ($isFixedLine) {
  947. if ($metadata->getSameMobileAndFixedLinePattern()) {
  948. return PhoneNumberType::FIXED_LINE_OR_MOBILE;
  949. }
  950. if ($this->isNumberMatchingDesc($nationalNumber, $metadata->getMobile())) {
  951. return PhoneNumberType::FIXED_LINE_OR_MOBILE;
  952. }
  953. return PhoneNumberType::FIXED_LINE;
  954. }
  955. // Otherwise, test to see if the number is mobile. Only do this if certain that the patterns for
  956. // mobile and fixed line aren't the same.
  957. if (!$metadata->getSameMobileAndFixedLinePattern() &&
  958. $this->isNumberMatchingDesc($nationalNumber, $metadata->getMobile())
  959. ) {
  960. return PhoneNumberType::MOBILE;
  961. }
  962. return PhoneNumberType::UNKNOWN;
  963. }
  964. /**
  965. * @param string $nationalNumber
  966. * @param PhoneNumberDesc $numberDesc
  967. * @return bool
  968. */
  969. public function isNumberMatchingDesc($nationalNumber, PhoneNumberDesc $numberDesc)
  970. {
  971. // Check if any possible number lengths are present; if so, we use them to avoid checking the
  972. // validation pattern if they don't match. If they are absent, this means they match the general
  973. // description, which we have already checked before checking a specific number type.
  974. $actualLength = mb_strlen($nationalNumber);
  975. $possibleLengths = $numberDesc->getPossibleLength();
  976. if (count($possibleLengths) > 0 && !in_array($actualLength, $possibleLengths)) {
  977. return false;
  978. }
  979. return $this->matcherAPI->matchNationalNumber($nationalNumber, $numberDesc, false);
  980. }
  981. /**
  982. * isNumberGeographical(PhoneNumber)
  983. *
  984. * Tests whether a phone number has a geographical association. It checks if the number is
  985. * associated with a certain region in the country to which it belongs. Note that this doesn't
  986. * verify if the number is actually in use.
  987. *
  988. * isNumberGeographical(PhoneNumberType, $countryCallingCode)
  989. *
  990. * Tests whether a phone number has a geographical association, as represented by its type and the
  991. * country it belongs to.
  992. *
  993. * This version exists since calculating the phone number type is expensive; if we have already
  994. * done this, we don't want to do it again.
  995. *
  996. * @param PhoneNumber|int $phoneNumberObjOrType A PhoneNumber object, or a PhoneNumberType integer
  997. * @param int|null $countryCallingCode Used when passing a PhoneNumberType
  998. * @return bool
  999. */
  1000. public function isNumberGeographical($phoneNumberObjOrType, $countryCallingCode = null)
  1001. {
  1002. if ($phoneNumberObjOrType instanceof PhoneNumber) {
  1003. return $this->isNumberGeographical($this->getNumberType($phoneNumberObjOrType), $phoneNumberObjOrType->getCountryCode());
  1004. }
  1005. return $phoneNumberObjOrType == PhoneNumberType::FIXED_LINE
  1006. || $phoneNumberObjOrType == PhoneNumberType::FIXED_LINE_OR_MOBILE
  1007. || (in_array($countryCallingCode, static::$GEO_MOBILE_COUNTRIES)
  1008. && $phoneNumberObjOrType == PhoneNumberType::MOBILE);
  1009. }
  1010. /**
  1011. * Gets the type of a valid phone number.
  1012. *
  1013. * @param PhoneNumber $number the number the phone number that we want to know the type
  1014. * @return int PhoneNumberType the type of the phone number, or UNKNOWN if it is invalid
  1015. */
  1016. public function getNumberType(PhoneNumber $number)
  1017. {
  1018. $regionCode = $this->getRegionCodeForNumber($number);
  1019. $metadata = $this->getMetadataForRegionOrCallingCode($number->getCountryCode(), $regionCode);
  1020. if ($metadata === null) {
  1021. return PhoneNumberType::UNKNOWN;
  1022. }
  1023. $nationalSignificantNumber = $this->getNationalSignificantNumber($number);
  1024. return $this->getNumberTypeHelper($nationalSignificantNumber, $metadata);
  1025. }
  1026. /**
  1027. * @param int $countryCallingCode
  1028. * @param string $regionCode
  1029. * @return null|PhoneMetadata
  1030. */
  1031. protected function getMetadataForRegionOrCallingCode($countryCallingCode, $regionCode)
  1032. {
  1033. return static::REGION_CODE_FOR_NON_GEO_ENTITY === $regionCode ?
  1034. $this->getMetadataForNonGeographicalRegion($countryCallingCode) : $this->getMetadataForRegion($regionCode);
  1035. }
  1036. /**
  1037. * @param int $countryCallingCode
  1038. * @return null|PhoneMetadata
  1039. */
  1040. public function getMetadataForNonGeographicalRegion($countryCallingCode)
  1041. {
  1042. if (!isset($this->countryCallingCodeToRegionCodeMap[$countryCallingCode])) {
  1043. return null;
  1044. }
  1045. return $this->metadataSource->getMetadataForNonGeographicalRegion($countryCallingCode);
  1046. }
  1047. /**
  1048. * Gets the length of the national destination code (NDC) from the PhoneNumber object passed in,
  1049. * so that clients could use it to split a national significant number into NDC and subscriber
  1050. * number. The NDC of a phone number is normally the first group of digit(s) right after the
  1051. * country calling code when the number is formatted in the international format, if there is a
  1052. * subscriber number part that follows.
  1053. *
  1054. * follows.
  1055. *
  1056. * N.B.: similar to an area code, not all numbers have an NDC!
  1057. *
  1058. * An example of how this could be used:
  1059. *
  1060. * <code>
  1061. * $phoneUtil = PhoneNumberUtil::getInstance();
  1062. * $number = $phoneUtil->parse("18002530000", "US");
  1063. * $nationalSignificantNumber = $phoneUtil->getNationalSignificantNumber($number);
  1064. *
  1065. * $nationalDestinationCodeLength = $phoneUtil->getLengthOfNationalDestinationCode($number);
  1066. * if ($nationalDestinationCodeLength > 0) {
  1067. * $nationalDestinationCode = substr($nationalSignificantNumber, 0, $nationalDestinationCodeLength);
  1068. * $subscriberNumber = substr($nationalSignificantNumber, $nationalDestinationCodeLength);
  1069. * } else {
  1070. * $nationalDestinationCode = "";
  1071. * $subscriberNumber = $nationalSignificantNumber;
  1072. * }
  1073. * </code>
  1074. *
  1075. * Refer to the unit tests to see the difference between this function and
  1076. * {@link #getLengthOfGeographicalAreaCode}.
  1077. *
  1078. * @param PhoneNumber $number the PhoneNumber object for which clients want to know the length of the NDC.
  1079. * @return int the length of NDC of the PhoneNumber object passed in, which could be zero
  1080. */
  1081. public function getLengthOfNationalDestinationCode(PhoneNumber $number)
  1082. {
  1083. if ($number->hasExtension()) {
  1084. // We don't want to alter the proto given to us, but we don't want to include the extension
  1085. // when we format it, so we copy it and clear the extension here.
  1086. $copiedProto = new PhoneNumber();
  1087. $copiedProto->mergeFrom($number);
  1088. $copiedProto->clearExtension();
  1089. } else {
  1090. $copiedProto = clone $number;
  1091. }
  1092. $nationalSignificantNumber = $this->format($copiedProto, PhoneNumberFormat::INTERNATIONAL);
  1093. $numberGroups = preg_split('/' . static::NON_DIGITS_PATTERN . '/', $nationalSignificantNumber);
  1094. // The pattern will start with "+COUNTRY_CODE " so the first group will always be the empty
  1095. // string (before the + symbol) and the second group will be the country calling code. The third
  1096. // group will be area code if it is not the last group.
  1097. if (count($numberGroups) <= 3) {
  1098. return 0;
  1099. }
  1100. if ($this->getNumberType($number) == PhoneNumberType::MOBILE) {
  1101. // For example Argentinian mobile numbers, when formatted in the international format, are in
  1102. // the form of +54 9 NDC XXXX.... As a result, we take the length of the third group (NDC) and
  1103. // add the length of the second group (which is the mobile token), which also forms part of
  1104. // the national significant number. This assumes that the mobile token is always formatted
  1105. // separately from the rest of the phone number.
  1106. $mobileToken = static::getCountryMobileToken($number->getCountryCode());
  1107. if ($mobileToken !== '') {
  1108. return mb_strlen($numberGroups[2]) + mb_strlen($numberGroups[3]);
  1109. }
  1110. }
  1111. return mb_strlen($numberGroups[2]);
  1112. }
  1113. /**
  1114. * Formats a phone number in the specified format using default rules. Note that this does not
  1115. * promise to produce a phone number that the user can dial from where they are - although we do
  1116. * format in either 'national' or 'international' format depending on what the client asks for, we
  1117. * do not currently support a more abbreviated format, such as for users in the same "area" who
  1118. * could potentially dial the number without area code. Note that if the phone number has a
  1119. * country calling code of 0 or an otherwise invalid country calling code, we cannot work out
  1120. * which formatting rules to apply so we return the national significant number with no formatting
  1121. * applied.
  1122. *
  1123. * @param PhoneNumber $number the phone number to be formatted
  1124. * @param int $numberFormat the PhoneNumberFormat the phone number should be formatted into
  1125. * @return string the formatted phone number
  1126. */
  1127. public function format(PhoneNumber $number, $numberFormat)
  1128. {
  1129. if ($number->getNationalNumber() == 0 && $number->hasRawInput()) {
  1130. // Unparsable numbers that kept their raw input just use that.
  1131. // This is the only case where a number can be formatted as E164 without a
  1132. // leading '+' symbol (but the original number wasn't parseable anyway).
  1133. // TODO: Consider removing the 'if' above so that unparsable
  1134. // strings without raw input format to the empty string instead of "+00"
  1135. $rawInput = $number->getRawInput();
  1136. if ($rawInput !== '') {
  1137. return $rawInput;
  1138. }
  1139. }
  1140. $formattedNumber = '';
  1141. $countryCallingCode = $number->getCountryCode();
  1142. $nationalSignificantNumber = $this->getNationalSignificantNumber($number);
  1143. if ($numberFormat == PhoneNumberFormat::E164) {
  1144. // Early exit for E164 case (even if the country calling code is invalid) since no formatting
  1145. // of the national number needs to be applied. Extensions are not formatted.
  1146. $formattedNumber .= $nationalSignificantNumber;
  1147. $this->prefixNumberWithCountryCallingCode($countryCallingCode, PhoneNumberFormat::E164, $formattedNumber);
  1148. return $formattedNumber;
  1149. }
  1150. if (!$this->hasValidCountryCallingCode($countryCallingCode)) {
  1151. $formattedNumber .= $nationalSignificantNumber;
  1152. return $formattedNumber;
  1153. }
  1154. // Note getRegionCodeForCountryCode() is used because formatting information for regions which
  1155. // share a country calling code is contained by only one region for performance reasons. For
  1156. // example, for NANPA regions it will be contained in the metadata for US.
  1157. $regionCode = $this->getRegionCodeForCountryCode($countryCallingCode);
  1158. // Metadata cannot be null because the country calling code is valid (which means that the
  1159. // region code cannot be ZZ and must be one of our supported region codes).
  1160. $metadata = $this->getMetadataForRegionOrCallingCode($countryCallingCode, $regionCode);
  1161. $formattedNumber .= $this->formatNsn($nationalSignificantNumber, $metadata, $numberFormat);
  1162. $this->maybeAppendFormattedExtension($number, $metadata, $numberFormat, $formattedNumber);
  1163. $this->prefixNumberWithCountryCallingCode($countryCallingCode, $numberFormat, $formattedNumber);
  1164. return $formattedNumber;
  1165. }
  1166. /**
  1167. * A helper function that is used by format and formatByPattern.
  1168. * @param int $countryCallingCode
  1169. * @param int $numberFormat PhoneNumberFormat
  1170. * @param string $formattedNumber
  1171. */
  1172. protected function prefixNumberWithCountryCallingCode($countryCallingCode, $numberFormat, &$formattedNumber)
  1173. {
  1174. switch ($numberFormat) {
  1175. case PhoneNumberFormat::E164:
  1176. $formattedNumber = static::PLUS_SIGN . $countryCallingCode . $formattedNumber;
  1177. return;
  1178. case PhoneNumberFormat::INTERNATIONAL:
  1179. $formattedNumber = static::PLUS_SIGN . $countryCallingCode . ' ' . $formattedNumber;
  1180. return;
  1181. case PhoneNumberFormat::RFC3966:
  1182. $formattedNumber = static::RFC3966_PREFIX . static::PLUS_SIGN . $countryCallingCode . '-' . $formattedNumber;
  1183. return;
  1184. case PhoneNumberFormat::NATIONAL:
  1185. default:
  1186. return;
  1187. }
  1188. }
  1189. /**
  1190. * Helper function to check the country calling code is valid.
  1191. * @param int $countryCallingCode
  1192. * @return bool
  1193. */
  1194. protected function hasValidCountryCallingCode($countryCallingCode)
  1195. {
  1196. return isset($this->countryCallingCodeToRegionCodeMap[$countryCallingCode]);
  1197. }
  1198. /**
  1199. * Returns the region code that matches the specific country calling code. In the case of no
  1200. * region code being found, ZZ will be returned. In the case of multiple regions, the one
  1201. * designated in the metadata as the "main" region for this calling code will be returned. If the
  1202. * countryCallingCode entered is valid but doesn't match a specific region (such as in the case of
  1203. * non-geographical calling codes like 800) the value "001" will be returned (corresponding to
  1204. * the value for World in the UN M.49 schema).
  1205. *
  1206. * @param int $countryCallingCode
  1207. * @return string
  1208. */
  1209. public function getRegionCodeForCountryCode($countryCallingCode)
  1210. {
  1211. $regionCodes = isset($this->countryCallingCodeToRegionCodeMap[$countryCallingCode]) ? $this->countryCallingCodeToRegionCodeMap[$countryCallingCode] : null;
  1212. return $regionCodes === null ? static::UNKNOWN_REGION : $regionCodes[0];
  1213. }
  1214. /**
  1215. * Note in some regions, the national number can be written in two completely different ways
  1216. * depending on whether it forms part of the NATIONAL format or INTERNATIONAL format. The
  1217. * numberFormat parameter here is used to specify which format to use for those cases. If a
  1218. * carrierCode is specified, this will be inserted into the formatted string to replace $CC.
  1219. * @param string $number
  1220. * @param PhoneMetadata $metadata
  1221. * @param int $numberFormat PhoneNumberFormat
  1222. * @param null|string $carrierCode
  1223. * @return string
  1224. */
  1225. protected function formatNsn($number, PhoneMetadata $metadata, $numberFormat, $carrierCode = null)
  1226. {
  1227. $intlNumberFormats = $metadata->intlNumberFormats();
  1228. // When the intlNumberFormats exists, we use that to format national number for the
  1229. // INTERNATIONAL format instead of using the numberDesc.numberFormats.
  1230. $availableFormats = (count($intlNumberFormats) == 0 || $numberFormat == PhoneNumberFormat::NATIONAL)
  1231. ? $metadata->numberFormats()
  1232. : $metadata->intlNumberFormats();
  1233. $formattingPattern = $this->chooseFormattingPatternForNumber($availableFormats, $number);
  1234. return ($formattingPattern === null)
  1235. ? $number
  1236. : $this->formatNsnUsingPattern($number, $formattingPattern, $numberFormat, $carrierCode);
  1237. }
  1238. /**
  1239. * @param NumberFormat[] $availableFormats
  1240. * @param string $nationalNumber
  1241. * @return NumberFormat|null
  1242. */
  1243. public function chooseFormattingPatternForNumber(array $availableFormats, $nationalNumber)
  1244. {
  1245. foreach ($availableFormats as $numFormat) {
  1246. $leadingDigitsPatternMatcher = null;
  1247. $size = $numFormat->leadingDigitsPatternSize();
  1248. // We always use the last leading_digits_pattern, as it is the most detailed.
  1249. if ($size > 0) {
  1250. $leadingDigitsPatternMatcher = new Matcher(
  1251. $numFormat->getLeadingDigitsPattern($size - 1),
  1252. $nationalNumber
  1253. );
  1254. }
  1255. if ($size == 0 || $leadingDigitsPatternMatcher->lookingAt()) {
  1256. $m = new Matcher($numFormat->getPattern(), $nationalNumber);
  1257. if ($m->matches() > 0) {
  1258. return $numFormat;
  1259. }
  1260. }
  1261. }
  1262. return null;
  1263. }
  1264. /**
  1265. * Note that carrierCode is optional - if null or an empty string, no carrier code replacement
  1266. * will take place.
  1267. * @param string $nationalNumber
  1268. * @param NumberFormat $formattingPattern
  1269. * @param int $numberFormat PhoneNumberFormat
  1270. * @param null|string $carrierCode
  1271. * @return string
  1272. */
  1273. public function formatNsnUsingPattern(
  1274. $nationalNumber,
  1275. NumberFormat $formattingPattern,
  1276. $numberFormat,
  1277. $carrierCode = null
  1278. ) {
  1279. $numberFormatRule = $formattingPattern->getFormat();
  1280. $m = new Matcher($formattingPattern->getPattern(), $nationalNumber);
  1281. if ($numberFormat === PhoneNumberFormat::NATIONAL &&
  1282. $carrierCode !== null && $carrierCode !== '' &&
  1283. $formattingPattern->getDomesticCarrierCodeFormattingRule() !== ''
  1284. ) {
  1285. // Replace the $CC in the formatting rule with the desired carrier code.
  1286. $carrierCodeFormattingRule = $formattingPattern->getDomesticCarrierCodeFormattingRule();
  1287. $carrierCodeFormattingRule = str_replace(static::CC_STRING, $carrierCode, $carrierCodeFormattingRule);
  1288. // Now replace the $FG in the formatting rule with the first group and the carrier code
  1289. // combined in the appropriate way.
  1290. $firstGroupMatcher = new Matcher(static::FIRST_GROUP_PATTERN, $numberFormatRule);
  1291. $numberFormatRule = $firstGroupMatcher->replaceFirst($carrierCodeFormattingRule);
  1292. $formattedNationalNumber = $m->replaceAll($numberFormatRule);
  1293. } else {
  1294. // Use the national prefix formatting rule instead.
  1295. $nationalPrefixFormattingRule = $formattingPattern->getNationalPrefixFormattingRule();
  1296. if ($numberFormat == PhoneNumberFormat::NATIONAL &&
  1297. $nationalPrefixFormattingRule !== null &&
  1298. mb_strlen($nationalPrefixFormattingRule) > 0
  1299. ) {
  1300. $firstGroupMatcher = new Matcher(static::FIRST_GROUP_PATTERN, $numberFormatRule);
  1301. $formattedNationalNumber = $m->replaceAll(
  1302. $firstGroupMatcher->replaceFirst($nationalPrefixFormattingRule)
  1303. );
  1304. } else {
  1305. $formattedNationalNumber = $m->replaceAll($numberFormatRule);
  1306. }
  1307. }
  1308. if ($numberFormat == PhoneNumberFormat::RFC3966) {
  1309. // Strip any leading punctuation.
  1310. $matcher = new Matcher(static::$SEPARATOR_PATTERN, $formattedNationalNumber);
  1311. if ($matcher->lookingAt()) {
  1312. $formattedNationalNumber = $matcher->replaceFirst('');
  1313. }
  1314. // Replace the rest with a dash between each number group.
  1315. $formattedNationalNumber = $matcher->reset($formattedNationalNumber)->replaceAll('-');
  1316. }
  1317. return $formattedNationalNumber;
  1318. }
  1319. /**
  1320. * Appends the formatted extension of a phone number to formattedNumber, if the phone number had
  1321. * an extension specified.
  1322. *
  1323. * @param PhoneNumber $number
  1324. * @param PhoneMetadata|null $metadata
  1325. * @param int $numberFormat PhoneNumberFormat
  1326. * @param string $formattedNumber
  1327. */
  1328. protected function maybeAppendFormattedExtension(PhoneNumber $number, $metadata, $numberFormat, &$formattedNumber)
  1329. {
  1330. if ($number->hasExtension() && mb_strlen($number->getExtension()) > 0) {
  1331. if ($numberFormat === PhoneNumberFormat::RFC3966) {
  1332. $formattedNumber .= static::RFC3966_EXTN_PREFIX . $number->getExtension();
  1333. } elseif (!empty($metadata) && $metadata->hasPreferredExtnPrefix()) {
  1334. $formattedNumber .= $metadata->getPreferredExtnPrefix() . $number->getExtension();
  1335. } else {
  1336. $formattedNumber .= static::DEFAULT_EXTN_PREFIX . $number->getExtension();
  1337. }
  1338. }
  1339. }
  1340. /**
  1341. * Returns the mobile token for the provided country calling code if it has one, otherwise
  1342. * returns an empty string. A mobile token is a number inserted before the area code when dialing
  1343. * a mobile number from that country from abroad.
  1344. *
  1345. * @param int $countryCallingCode the country calling code for which we want the mobile token
  1346. * @return string the mobile token, as a string, for the given country calling code
  1347. */
  1348. public static function getCountryMobileToken($countryCallingCode)
  1349. {
  1350. if (count(static::$MOBILE_TOKEN_MAPPINGS) === 0) {
  1351. static::initMobileTokenMappings();
  1352. }
  1353. if (array_key_exists($countryCallingCode, static::$MOBILE_TOKEN_MAPPINGS)) {
  1354. return static::$MOBILE_TOKEN_MAPPINGS[$countryCallingCode];
  1355. }
  1356. return '';
  1357. }
  1358. /**
  1359. * Checks if the number is a valid vanity (alpha) number such as 800 MICROSOFT. A valid vanity
  1360. * number will start with at least 3 digits and will have three or more alpha characters. This
  1361. * does not do region-specific checks - to work out if this number is actually valid for a region,
  1362. * it should be parsed and methods such as {@link #isPossibleNumberWithReason} and
  1363. * {@link #isValidNumber} should be used.
  1364. *
  1365. * @param string $number the number that needs to be checked
  1366. * @return bool true if the number is a valid vanity number
  1367. */
  1368. public function isAlphaNumber($number)
  1369. {
  1370. if (!static::isViablePhoneNumber($number)) {
  1371. // Number is too short, or doesn't match the basic phone number pattern.
  1372. return false;
  1373. }
  1374. $this->maybeStripExtension($number);
  1375. return (bool)preg_match('/' . static::VALID_ALPHA_PHONE_PATTERN . '/' . static::REGEX_FLAGS, $number);
  1376. }
  1377. /**
  1378. * Checks to see if the string of characters could possibly be a phone number at all. At the
  1379. * moment, checks to see that the string begins with at least 2 digits, ignoring any punctuation
  1380. * commonly found in phone numbers.
  1381. * This method does not require the number to be normalized in advance - but does assume that
  1382. * leading non-number symbols have been removed, such as by the method extractPossibleNumber.
  1383. *
  1384. * @param string $number to be checked for viability as a phone number
  1385. * @return boolean true if the number could be a phone number of some sort, otherwise false
  1386. */
  1387. public static function isViablePhoneNumber($number)
  1388. {
  1389. if (static::$VALID_PHONE_NUMBER_PATTERN === null) {
  1390. static::initValidPhoneNumberPatterns();
  1391. }
  1392. if (mb_strlen($number) < static::MIN_LENGTH_FOR_NSN) {
  1393. return false;
  1394. }
  1395. $validPhoneNumberPattern = static::getValidPhoneNumberPattern();
  1396. $m = preg_match($validPhoneNumberPattern, $number);
  1397. return $m > 0;
  1398. }
  1399. /**
  1400. * We append optionally the extension pattern to the end here, as a valid phone number may
  1401. * have an extension prefix appended, followed by 1 or more digits.
  1402. * @return string
  1403. */
  1404. protected static function getValidPhoneNumberPattern()
  1405. {
  1406. return static::$VALID_PHONE_NUMBER_PATTERN;
  1407. }
  1408. /**
  1409. * Strips any extension (as in, the part of the number dialled after the call is connected,
  1410. * usually indicated with extn, ext, x or similar) from the end of the number, and returns it.
  1411. *
  1412. * @param string $number the non-normalized telephone number that we wish to strip the extension from
  1413. * @return string the phone extension
  1414. */
  1415. protected function maybeStripExtension(&$number)
  1416. {
  1417. $matches = array();
  1418. $find = preg_match(static::$EXTN_PATTERN, $number, $matches, PREG_OFFSET_CAPTURE);
  1419. // If we find a potential extension, and the number preceding this is a viable number, we assume
  1420. // it is an extension.
  1421. if ($find > 0 && static::isViablePhoneNumber(substr($number, 0, $matches[0][1]))) {
  1422. // The numbers are captured into groups in the regular expression.
  1423. for ($i = 1, $length = count($matches); $i <= $length; $i++) {
  1424. if ($matches[$i][0] != '') {
  1425. // We go through the capturing groups until we find one that captured some digits. If none
  1426. // did, then we will return the empty string.
  1427. $extension = $matches[$i][0];
  1428. $number = substr($number, 0, $matches[0][1]);
  1429. return $extension;
  1430. }
  1431. }
  1432. }
  1433. return '';
  1434. }
  1435. /**
  1436. * Parses a string and returns it in proto buffer format. This method differs from {@link #parse}
  1437. * in that it always populates the raw_input field of the protocol buffer with numberToParse as
  1438. * well as the country_code_source field.
  1439. *
  1440. * @param string $numberToParse number that we are attempting to parse. This can contain formatting
  1441. * such as +, ( and -, as well as a phone number extension. It can also
  1442. * be provided in RFC3966 format.
  1443. * @param string $defaultRegion region that we are expecting the number to be from. This is only used
  1444. * if the number being parsed is not written in international format.
  1445. * The country calling code for the number in this case would be stored
  1446. * as that of the default region supplied.
  1447. * @param PhoneNumber $phoneNumber
  1448. * @return PhoneNumber a phone number proto buffer filled with the parsed number
  1449. */
  1450. public function parseAndKeepRawInput($numberToParse, $defaultRegion, PhoneNumber $phoneNumber = null)
  1451. {
  1452. if ($phoneNumber === null) {
  1453. $phoneNumber = new PhoneNumber();
  1454. }
  1455. $this->parseHelper($numberToParse, $defaultRegion, true, true, $phoneNumber);
  1456. return $phoneNumber;
  1457. }
  1458. /**
  1459. * Returns an iterable over all PhoneNumberMatches in $text
  1460. *
  1461. * @param string $text
  1462. * @param string $defaultRegion
  1463. * @param AbstractLeniency $leniency Defaults to Leniency::VALID()
  1464. * @param int $maxTries Defaults to PHP_INT_MAX
  1465. * @return PhoneNumberMatcher
  1466. */
  1467. public function findNumbers($text, $defaultRegion, AbstractLeniency $leniency = null, $maxTries = PHP_INT_MAX)
  1468. {
  1469. if ($leniency === null) {
  1470. $leniency = Leniency::VALID();
  1471. }
  1472. return new PhoneNumberMatcher($this, $text, $defaultRegion, $leniency, $maxTries);
  1473. }
  1474. /**
  1475. * Gets an AsYouTypeFormatter for the specific region.
  1476. *
  1477. * @param string $regionCode The region where the phone number is being entered.
  1478. * @return AsYouTypeFormatter
  1479. */
  1480. public function getAsYouTypeFormatter($regionCode)
  1481. {
  1482. return new AsYouTypeFormatter($regionCode);
  1483. }
  1484. /**
  1485. * A helper function to set the values related to leading zeros in a PhoneNumber.
  1486. * @param string $nationalNumber
  1487. * @param PhoneNumber $phoneNumber
  1488. */
  1489. public static function setItalianLeadingZerosForPhoneNumber($nationalNumber, PhoneNumber $phoneNumber)
  1490. {
  1491. if (strlen($nationalNumber) > 1 && substr($nationalNumber, 0, 1) == '0') {
  1492. $phoneNumber->setItalianLeadingZero(true);
  1493. $numberOfLeadingZeros = 1;
  1494. // Note that if the national number is all "0"s, the last "0" is not counted as a leading
  1495. // zero.
  1496. while ($numberOfLeadingZeros < (strlen($nationalNumber) - 1) &&
  1497. substr($nationalNumber, $numberOfLeadingZeros, 1) == '0') {
  1498. $numberOfLeadingZeros++;
  1499. }
  1500. if ($numberOfLeadingZeros != 1) {
  1501. $phoneNumber->setNumberOfLeadingZeros($numberOfLeadingZeros);
  1502. }
  1503. }
  1504. }
  1505. /**
  1506. * Parses a string and fills up the phoneNumber. This method is the same as the public
  1507. * parse() method, with the exception that it allows the default region to be null, for use by
  1508. * isNumberMatch(). checkRegion should be set to false if it is permitted for the default region
  1509. * to be null or unknown ("ZZ").
  1510. * @param string $numberToParse
  1511. * @param string $defaultRegion
  1512. * @param bool $keepRawInput
  1513. * @param bool $checkRegion
  1514. * @param PhoneNumber $phoneNumber
  1515. * @throws NumberParseException
  1516. */
  1517. protected function parseHelper($numberToParse, $defaultRegion, $keepRawInput, $checkRegion, PhoneNumber $phoneNumber)
  1518. {
  1519. if ($numberToParse === null) {
  1520. throw new NumberParseException(NumberParseException::NOT_A_NUMBER, 'The phone number supplied was null.');
  1521. }
  1522. $numberToParse = trim($numberToParse);
  1523. if (mb_strlen($numberToParse) > static::MAX_INPUT_STRING_LENGTH) {
  1524. throw new NumberParseException(
  1525. NumberParseException::TOO_LONG,
  1526. 'The string supplied was too long to parse.'
  1527. );
  1528. }
  1529. $nationalNumber = '';
  1530. $this->buildNationalNumberForParsing($numberToParse, $nationalNumber);
  1531. if (!static::isViablePhoneNumber($nationalNumber)) {
  1532. throw new NumberParseException(
  1533. NumberParseException::NOT_A_NUMBER,
  1534. 'The string supplied did not seem to be a phone number.'
  1535. );
  1536. }
  1537. // Check the region supplied is valid, or that the extracted number starts with some sort of +
  1538. // sign so the number's region can be determined.
  1539. if ($checkRegion && !$this->checkRegionForParsing($nationalNumber, $defaultRegion)) {
  1540. throw new NumberParseException(
  1541. NumberParseException::INVALID_COUNTRY_CODE,
  1542. 'Missing or invalid default region.'
  1543. );
  1544. }
  1545. if ($keepRawInput) {
  1546. $phoneNumber->setRawInput($numberToParse);
  1547. }
  1548. // Attempt to parse extension first, since it doesn't require region-specific data and we want
  1549. // to have the non-normalised number here.
  1550. $extension = $this->maybeStripExtension($nationalNumber);
  1551. if ($extension !== '') {
  1552. $phoneNumber->setExtension($extension);
  1553. }
  1554. $regionMetadata = $this->getMetadataForRegion($defaultRegion);
  1555. // Check to see if the number is given in international format so we know whether this number is
  1556. // from the default region or not.
  1557. $normalizedNationalNumber = '';
  1558. try {
  1559. // TODO: This method should really just take in the string buffer that has already
  1560. // been created, and just remove the prefix, rather than taking in a string and then
  1561. // outputting a string buffer.
  1562. $countryCode = $this->maybeExtractCountryCode(
  1563. $nationalNumber,
  1564. $regionMetadata,
  1565. $normalizedNationalNumber,
  1566. $keepRawInput,
  1567. $phoneNumber
  1568. );
  1569. } catch (NumberParseException $e) {
  1570. $matcher = new Matcher(static::$PLUS_CHARS_PATTERN, $nationalNumber);
  1571. if ($e->getErrorType() == NumberParseException::INVALID_COUNTRY_CODE && $matcher->lookingAt()) {
  1572. // Strip the plus-char, and try again.
  1573. $countryCode = $this->maybeExtractCountryCode(
  1574. substr($nationalNumber, $matcher->end()),
  1575. $regionMetadata,
  1576. $normalizedNationalNumber,
  1577. $keepRawInput,
  1578. $phoneNumber
  1579. );
  1580. if ($countryCode == 0) {
  1581. throw new NumberParseException(
  1582. NumberParseException::INVALID_COUNTRY_CODE,
  1583. 'Could not interpret numbers after plus-sign.'
  1584. );
  1585. }
  1586. } else {
  1587. throw new NumberParseException($e->getErrorType(), $e->getMessage(), $e);
  1588. }
  1589. }
  1590. if ($countryCode !== 0) {
  1591. $phoneNumberRegion = $this->getRegionCodeForCountryCode($countryCode);
  1592. if ($phoneNumberRegion != $defaultRegion) {
  1593. // Metadata cannot be null because the country calling code is valid.
  1594. $regionMetadata = $this->getMetadataForRegionOrCallingCode($countryCode, $phoneNumberRegion);
  1595. }
  1596. } else {
  1597. // If no extracted country calling code, use the region supplied instead. The national number
  1598. // is just the normalized version of the number we were given to parse.
  1599. $normalizedNationalNumber .= static::normalize($nationalNumber);
  1600. if ($defaultRegion !== null) {
  1601. $countryCode = $regionMetadata->getCountryCode();
  1602. $phoneNumber->setCountryCode($countryCode);
  1603. } elseif ($keepRawInput) {
  1604. $phoneNumber->clearCountryCodeSource();
  1605. }
  1606. }
  1607. if (mb_strlen($normalizedNationalNumber) < static::MIN_LENGTH_FOR_NSN) {
  1608. throw new NumberParseException(
  1609. NumberParseException::TOO_SHORT_NSN,
  1610. 'The string supplied is too short to be a phone number.'
  1611. );
  1612. }
  1613. if ($regionMetadata !== null) {
  1614. $carrierCode = '';
  1615. $potentialNationalNumber = $normalizedNationalNumber;
  1616. $this->maybeStripNationalPrefixAndCarrierCode($potentialNationalNumber, $regionMetadata, $carrierCode);
  1617. // We require that the NSN remaining after stripping the national prefix and carrier code be
  1618. // long enough to be a possible length for the region. Otherwise, we don't do the stripping,
  1619. // since the original number could be a valid short number.
  1620. $validationResult = $this->testNumberLength($potentialNationalNumber, $regionMetadata);
  1621. if ($validationResult !== ValidationResult::TOO_SHORT
  1622. && $validationResult !== ValidationResult::IS_POSSIBLE_LOCAL_ONLY
  1623. && $validationResult !== ValidationResult::INVALID_LENGTH) {
  1624. $normalizedNationalNumber = $potentialNationalNumber;
  1625. if ($keepRawInput && $carrierCode !== '') {
  1626. $phoneNumber->setPreferredDomesticCarrierCode($carrierCode);
  1627. }
  1628. }
  1629. }
  1630. $lengthOfNationalNumber = mb_strlen($normalizedNationalNumber);
  1631. if ($lengthOfNationalNumber < static::MIN_LENGTH_FOR_NSN) {
  1632. throw new NumberParseException(
  1633. NumberParseException::TOO_SHORT_NSN,
  1634. 'The string supplied is too short to be a phone number.'
  1635. );
  1636. }
  1637. if ($lengthOfNationalNumber > static::MAX_LENGTH_FOR_NSN) {
  1638. throw new NumberParseException(
  1639. NumberParseException::TOO_LONG,
  1640. 'The string supplied is too long to be a phone number.'
  1641. );
  1642. }
  1643. static::setItalianLeadingZerosForPhoneNumber($normalizedNationalNumber, $phoneNumber);
  1644. /*
  1645. * We have to store the National Number as a string instead of a "long" as Google do
  1646. *
  1647. * Since PHP doesn't always support 64 bit INTs, this was a float, but that had issues
  1648. * with long numbers.
  1649. *
  1650. * We have to remove the leading zeroes ourself though
  1651. */
  1652. if ((int)$normalizedNationalNumber == 0) {
  1653. $normalizedNationalNumber = '0';
  1654. } else {
  1655. $normalizedNationalNumber = ltrim($normalizedNationalNumber, '0');
  1656. }
  1657. $phoneNumber->setNationalNumber($normalizedNationalNumber);
  1658. }
  1659. /**
  1660. * Extracts the value of the phone-context parameter of numberToExtractFrom where the index of
  1661. * ";phone-context=" is the parameter indexOfPhoneContext, following the syntax defined in
  1662. * RFC3966.
  1663. *
  1664. * @param string $numberToExtractFrom
  1665. * @param int|false $indexOfPhoneContext
  1666. * @return string|null the extracted string (possibly empty), or null if no phone-context parameter is found.
  1667. */
  1668. protected function extractPhoneContext($numberToExtractFrom, $indexOfPhoneContext)
  1669. {
  1670. // If no phone-context parameter is present
  1671. if ($indexOfPhoneContext === false) {
  1672. return null;
  1673. }
  1674. $phoneContextStart = $indexOfPhoneContext + strlen(static::RFC3966_PHONE_CONTEXT);
  1675. // If phone-context parameter is empty
  1676. if ($phoneContextStart >= mb_strlen($numberToExtractFrom)) {
  1677. return '';
  1678. }
  1679. $phoneContextEnd = strpos($numberToExtractFrom, ';', $phoneContextStart);
  1680. // If phone-context is not the last parameter
  1681. if ($phoneContextEnd !== false) {
  1682. return substr($numberToExtractFrom, $phoneContextStart, $phoneContextEnd - $phoneContextStart);
  1683. }
  1684. return substr($numberToExtractFrom, $phoneContextStart);
  1685. }
  1686. /**
  1687. * Returns whether the value of phoneContext follows the syntax defined in RFC3966.
  1688. *
  1689. * @param string|null $phoneContext
  1690. * @return bool
  1691. */
  1692. protected function isPhoneContextValid($phoneContext)
  1693. {
  1694. if ($phoneContext === null) {
  1695. return true;
  1696. }
  1697. if ($phoneContext === '') {
  1698. return false;
  1699. }
  1700. $numberDigitsPattern = '/' . static::$RFC3966_GLOBAL_NUMBER_DIGITS . '/' . static::REGEX_FLAGS;
  1701. $domainNamePattern = '/' . static::$RFC3966_DOMAINNAME . '/' . static::REGEX_FLAGS;
  1702. // Does phone-context value match pattern of global-number-digits or domainname
  1703. return preg_match($numberDigitsPattern, $phoneContext) || preg_match($domainNamePattern, $phoneContext);
  1704. }
  1705. /**
  1706. * Returns a new phone number containing only the fields needed to uniquely identify a phone
  1707. * number, rather than any fields that capture the context in which the phone number was created.
  1708. * These fields correspond to those set in parse() rather than parseAndKeepRawInput()
  1709. *
  1710. * @param PhoneNumber $phoneNumberIn
  1711. * @return PhoneNumber
  1712. */
  1713. protected static function copyCoreFieldsOnly(PhoneNumber $phoneNumberIn)
  1714. {
  1715. $phoneNumber = new PhoneNumber();
  1716. $phoneNumber->setCountryCode($phoneNumberIn->getCountryCode());
  1717. $phoneNumber->setNationalNumber($phoneNumberIn->getNationalNumber());
  1718. if ($phoneNumberIn->getExtension() != '') {
  1719. $phoneNumber->setExtension($phoneNumberIn->getExtension());
  1720. }
  1721. if ($phoneNumberIn->isItalianLeadingZero()) {
  1722. $phoneNumber->setItalianLeadingZero(true);
  1723. // This field is only relevant if there are leading zeros at all.
  1724. $phoneNumber->setNumberOfLeadingZeros($phoneNumberIn->getNumberOfLeadingZeros());
  1725. }
  1726. return $phoneNumber;
  1727. }
  1728. /**
  1729. * Converts numberToParse to a form that we can parse and write it to nationalNumber if it is
  1730. * written in RFC3966; otherwise extract a possible number out of it and write to nationalNumber.
  1731. * @param string $numberToParse
  1732. * @param string $nationalNumber
  1733. * @throws NumberParseException
  1734. */
  1735. protected function buildNationalNumberForParsing($numberToParse, &$nationalNumber)
  1736. {
  1737. $indexOfPhoneContext = strpos($numberToParse, static::RFC3966_PHONE_CONTEXT);
  1738. $phoneContext = $this->extractPhoneContext($numberToParse, $indexOfPhoneContext);
  1739. if (!$this->isPhoneContextValid($phoneContext)) {
  1740. throw new NumberParseException(NumberParseException::NOT_A_NUMBER, 'The phone-context valid is invalid.');
  1741. }
  1742. if ($phoneContext !== null) {
  1743. // If the phone context contains a phone number prefix, we need to capture it, whereas domains
  1744. // will be ignored.
  1745. if (strpos($phoneContext, self::PLUS_SIGN) === 0) {
  1746. // Additional parameters might follow the phone context. If so, we will remove them here
  1747. // because the parameters after phone context are not important for parsing the phone
  1748. // number.
  1749. $nationalNumber .= $phoneContext;
  1750. }
  1751. // Now append everything between the "tel:" prefix and the phone-context. This should include
  1752. // the national number, an optional extension or isdn-subaddress component. Note we also
  1753. // handle the case when "tel:" is missing, as we have seen in some of the phone number inputs.
  1754. // In that case, we append everything from the beginning.
  1755. $indexOfRfc3966Prefix = strpos($numberToParse, static::RFC3966_PREFIX);
  1756. $indexOfNationalNumber = ($indexOfRfc3966Prefix !== false) ? $indexOfRfc3966Prefix + strlen(static::RFC3966_PREFIX) : 0;
  1757. $nationalNumber .= substr(
  1758. $numberToParse,
  1759. $indexOfNationalNumber,
  1760. $indexOfPhoneContext - $indexOfNationalNumber
  1761. );
  1762. } else {
  1763. // Extract a possible number from the string passed in (this strips leading characters that
  1764. // could not be the start of a phone number.)
  1765. $nationalNumber .= static::extractPossibleNumber($numberToParse);
  1766. }
  1767. // Delete the isdn-subaddress and everything after it if it is present. Note extension won't
  1768. // appear at the same time with isdn-subaddress according to paragraph 5.3 of the RFC3966 spec,
  1769. $indexOfIsdn = strpos($nationalNumber, static::RFC3966_ISDN_SUBADDRESS);
  1770. if ($indexOfIsdn > 0) {
  1771. $nationalNumber = substr($nationalNumber, 0, $indexOfIsdn);
  1772. }
  1773. // If both phone context and isdn-subaddress are absent but other parameters are present, the
  1774. // parameters are left in nationalNumber. This is because we are concerned about deleting
  1775. // content from a potential number string when there is no strong evidence that the number is
  1776. // actually written in RFC3966.
  1777. }
  1778. /**
  1779. * Attempts to extract a possible number from the string passed in. This currently strips all
  1780. * leading characters that cannot be used to start a phone number. Characters that can be used to
  1781. * start a phone number are defined in the VALID_START_CHAR_PATTERN. If none of these characters
  1782. * are found in the number passed in, an empty string is returned. This function also attempts to
  1783. * strip off any alternative extensions or endings if two or more are present, such as in the case
  1784. * of: (530) 583-6985 x302/x2303. The second extension here makes this actually two phone numbers,
  1785. * (530) 583-6985 x302 and (530) 583-6985 x2303. We remove the second extension so that the first
  1786. * number is parsed correctly.
  1787. *
  1788. * @param string $number the string that might contain a phone number
  1789. * @return string the number, stripped of any non-phone-number prefix (such as "Tel:") or an empty
  1790. * string if no character used to start phone numbers (such as + or any digit) is
  1791. * found in the number
  1792. */
  1793. public static function extractPossibleNumber($number)
  1794. {
  1795. if (static::$VALID_START_CHAR_PATTERN === null) {
  1796. static::initValidStartCharPattern();
  1797. }
  1798. $matches = array();
  1799. $match = preg_match('/' . static::$VALID_START_CHAR_PATTERN . '/ui', $number, $matches, PREG_OFFSET_CAPTURE);
  1800. if ($match > 0) {
  1801. $number = substr($number, $matches[0][1]);
  1802. // Remove trailing non-alpha non-numerical characters.
  1803. $trailingCharsMatcher = new Matcher(static::$UNWANTED_END_CHAR_PATTERN, $number);
  1804. if ($trailingCharsMatcher->find() && $trailingCharsMatcher->start() > 0) {
  1805. $number = substr($number, 0, $trailingCharsMatcher->start());
  1806. }
  1807. // Check for extra numbers at the end.
  1808. $match = preg_match('%' . static::$SECOND_NUMBER_START_PATTERN . '%', $number, $matches, PREG_OFFSET_CAPTURE);
  1809. if ($match > 0) {
  1810. $number = substr($number, 0, $matches[0][1]);
  1811. }
  1812. return $number;
  1813. }
  1814. return '';
  1815. }
  1816. /**
  1817. * Checks to see that the region code used is valid, or if it is not valid, that the number to
  1818. * parse starts with a + symbol so that we can attempt to infer the region from the number.
  1819. * Returns false if it cannot use the region provided and the region cannot be inferred.
  1820. * @param string $numberToParse
  1821. * @param string $defaultRegion
  1822. * @return bool
  1823. */
  1824. protected function checkRegionForParsing($numberToParse, $defaultRegion)
  1825. {
  1826. if (!$this->isValidRegionCode($defaultRegion)) {
  1827. // If the number is null or empty, we can't infer the region.
  1828. $plusCharsPatternMatcher = new Matcher(static::$PLUS_CHARS_PATTERN, $numberToParse);
  1829. if ($numberToParse === null || $numberToParse === '' || !$plusCharsPatternMatcher->lookingAt()) {
  1830. return false;
  1831. }
  1832. }
  1833. return true;
  1834. }
  1835. /**
  1836. * Tries to extract a country calling code from a number. This method will return zero if no
  1837. * country calling code is considered to be present. Country calling codes are extracted in the
  1838. * following ways:
  1839. * <ul>
  1840. * <li> by stripping the international dialing prefix of the region the person is dialing from,
  1841. * if this is present in the number, and looking at the next digits
  1842. * <li> by stripping the '+' sign if present and then looking at the next digits
  1843. * <li> by comparing the start of the number and the country calling code of the default region.
  1844. * If the number is not considered possible for the numbering plan of the default region
  1845. * initially, but starts with the country calling code of this region, validation will be
  1846. * reattempted after stripping this country calling code. If this number is considered a
  1847. * possible number, then the first digits will be considered the country calling code and
  1848. * removed as such.
  1849. * </ul>
  1850. * It will throw a NumberParseException if the number starts with a '+' but the country calling
  1851. * code supplied after this does not match that of any known region.
  1852. *
  1853. * @param string $number non-normalized telephone number that we wish to extract a country calling
  1854. * code from - may begin with '+'
  1855. * @param PhoneMetadata $defaultRegionMetadata metadata about the region this number may be from
  1856. * @param string $nationalNumber a string buffer to store the national significant number in, in the case
  1857. * that a country calling code was extracted. The number is appended to any existing contents.
  1858. * If no country calling code was extracted, this will be left unchanged.
  1859. * @param bool $keepRawInput true if the country_code_source and preferred_carrier_code fields of
  1860. * phoneNumber should be populated.
  1861. * @param PhoneNumber $phoneNumber the PhoneNumber object where the country_code and country_code_source need
  1862. * to be populated. Note the country_code is always populated, whereas country_code_source is
  1863. * only populated when keepCountryCodeSource is true.
  1864. * @return int the country calling code extracted or 0 if none could be extracted
  1865. * @throws NumberParseException
  1866. */
  1867. public function maybeExtractCountryCode(
  1868. $number,
  1869. PhoneMetadata $defaultRegionMetadata = null,
  1870. &$nationalNumber,
  1871. $keepRawInput,
  1872. PhoneNumber $phoneNumber
  1873. ) {
  1874. if ($number === '') {
  1875. return 0;
  1876. }
  1877. $fullNumber = $number;
  1878. // Set the default prefix to be something that will never match.
  1879. $possibleCountryIddPrefix = 'NonMatch';
  1880. if ($defaultRegionMetadata !== null) {
  1881. $possibleCountryIddPrefix = $defaultRegionMetadata->getInternationalPrefix();
  1882. }
  1883. $countryCodeSource = $this->maybeStripInternationalPrefixAndNormalize($fullNumber, $possibleCountryIddPrefix);
  1884. if ($keepRawInput) {
  1885. $phoneNumber->setCountryCodeSource($countryCodeSource);
  1886. }
  1887. if ($countryCodeSource != CountryCodeSource::FROM_DEFAULT_COUNTRY) {
  1888. if (mb_strlen($fullNumber) <= static::MIN_LENGTH_FOR_NSN) {
  1889. throw new NumberParseException(
  1890. NumberParseException::TOO_SHORT_AFTER_IDD,
  1891. 'Phone number had an IDD, but after this was not long enough to be a viable phone number.'
  1892. );
  1893. }
  1894. $potentialCountryCode = $this->extractCountryCode($fullNumber, $nationalNumber);
  1895. if ($potentialCountryCode != 0) {
  1896. $phoneNumber->setCountryCode($potentialCountryCode);
  1897. return $potentialCountryCode;
  1898. }
  1899. // If this fails, they must be using a strange country calling code that we don't recognize,
  1900. // or that doesn't exist.
  1901. throw new NumberParseException(
  1902. NumberParseException::INVALID_COUNTRY_CODE,
  1903. 'Country calling code supplied was not recognised.'
  1904. );
  1905. }
  1906. if ($defaultRegionMetadata !== null) {
  1907. // Check to see if the number starts with the country calling code for the default region. If
  1908. // so, we remove the country calling code, and do some checks on the validity of the number
  1909. // before and after.
  1910. $defaultCountryCode = $defaultRegionMetadata->getCountryCode();
  1911. $defaultCountryCodeString = (string)$defaultCountryCode;
  1912. $normalizedNumber = $fullNumber;
  1913. if (strpos($normalizedNumber, $defaultCountryCodeString) === 0) {
  1914. $potentialNationalNumber = substr($normalizedNumber, mb_strlen($defaultCountryCodeString));
  1915. $generalDesc = $defaultRegionMetadata->getGeneralDesc();
  1916. // Don't need the carrier code.
  1917. $carriercode = null;
  1918. $this->maybeStripNationalPrefixAndCarrierCode(
  1919. $potentialNationalNumber,
  1920. $defaultRegionMetadata,
  1921. $carriercode
  1922. );
  1923. // If the number was not valid before but is valid now, or if it was too long before, we
  1924. // consider the number with the country calling code stripped to be a better result and
  1925. // keep that instead.
  1926. if ((!$this->matcherAPI->matchNationalNumber($fullNumber, $generalDesc, false)
  1927. && $this->matcherAPI->matchNationalNumber($potentialNationalNumber, $generalDesc, false))
  1928. || $this->testNumberLength($fullNumber, $defaultRegionMetadata) === ValidationResult::TOO_LONG
  1929. ) {
  1930. $nationalNumber .= $potentialNationalNumber;
  1931. if ($keepRawInput) {
  1932. $phoneNumber->setCountryCodeSource(CountryCodeSource::FROM_NUMBER_WITHOUT_PLUS_SIGN);
  1933. }
  1934. $phoneNumber->setCountryCode($defaultCountryCode);
  1935. return $defaultCountryCode;
  1936. }
  1937. }
  1938. }
  1939. // No country calling code present.
  1940. $phoneNumber->setCountryCode(0);
  1941. return 0;
  1942. }
  1943. /**
  1944. * Strips any international prefix (such as +, 00, 011) present in the number provided, normalizes
  1945. * the resulting number, and indicates if an international prefix was present.
  1946. *
  1947. * @param string $number the non-normalized telephone number that we wish to strip any international
  1948. * dialing prefix from.
  1949. * @param string $possibleIddPrefix string the international direct dialing prefix from the region we
  1950. * think this number may be dialed in
  1951. * @return int the corresponding CountryCodeSource if an international dialing prefix could be
  1952. * removed from the number, otherwise CountryCodeSource.FROM_DEFAULT_COUNTRY if the number did
  1953. * not seem to be in international format.
  1954. */
  1955. public function maybeStripInternationalPrefixAndNormalize(&$number, $possibleIddPrefix)
  1956. {
  1957. if ($number === '') {
  1958. return CountryCodeSource::FROM_DEFAULT_COUNTRY;
  1959. }
  1960. $matches = array();
  1961. // Check to see if the number begins with one or more plus signs.
  1962. $match = preg_match('/^' . static::$PLUS_CHARS_PATTERN . '/' . static::REGEX_FLAGS, $number, $matches, PREG_OFFSET_CAPTURE);
  1963. if ($match > 0) {
  1964. $number = mb_substr($number, $matches[0][1] + mb_strlen($matches[0][0]));
  1965. // Can now normalize the rest of the number since we've consumed the "+" sign at the start.
  1966. $number = static::normalize($number);
  1967. return CountryCodeSource::FROM_NUMBER_WITH_PLUS_SIGN;
  1968. }
  1969. // Attempt to parse the first digits as an international prefix.
  1970. $iddPattern = $possibleIddPrefix;
  1971. $number = static::normalize($number);
  1972. return $this->parsePrefixAsIdd($iddPattern, $number)
  1973. ? CountryCodeSource::FROM_NUMBER_WITH_IDD
  1974. : CountryCodeSource::FROM_DEFAULT_COUNTRY;
  1975. }
  1976. /**
  1977. * Normalizes a string of characters representing a phone number. This performs
  1978. * the following conversions:
  1979. * Punctuation is stripped.
  1980. * For ALPHA/VANITY numbers:
  1981. * Letters are converted to their numeric representation on a telephone
  1982. * keypad. The keypad used here is the one defined in ITU Recommendation
  1983. * E.161. This is only done if there are 3 or more letters in the number,
  1984. * to lessen the risk that such letters are typos.
  1985. * For other numbers:
  1986. * - Wide-ascii digits are converted to normal ASCII (European) digits.
  1987. * - Arabic-Indic numerals are converted to European numerals.
  1988. * - Spurious alpha characters are stripped.
  1989. *
  1990. * @param string $number a string of characters representing a phone number.
  1991. * @return string the normalized string version of the phone number.
  1992. */
  1993. public static function normalize(&$number)
  1994. {
  1995. if (static::$ALPHA_PHONE_MAPPINGS === null) {
  1996. static::initAlphaPhoneMappings();
  1997. }
  1998. $m = new Matcher(static::VALID_ALPHA_PHONE_PATTERN, $number);
  1999. if ($m->matches()) {
  2000. return static::normalizeHelper($number, static::$ALPHA_PHONE_MAPPINGS, true);
  2001. }
  2002. return static::normalizeDigitsOnly($number);
  2003. }
  2004. /**
  2005. * Normalizes a string of characters representing a phone number. This converts wide-ascii and
  2006. * arabic-indic numerals to European numerals, and strips punctuation and alpha characters.
  2007. *
  2008. * @param $number string a string of characters representing a phone number
  2009. * @return string the normalized string version of the phone number
  2010. */
  2011. public static function normalizeDigitsOnly($number)
  2012. {
  2013. return static::normalizeDigits($number, false /* strip non-digits */);
  2014. }
  2015. /**
  2016. * @param string $number
  2017. * @param bool $keepNonDigits
  2018. * @return string
  2019. */
  2020. public static function normalizeDigits($number, $keepNonDigits)
  2021. {
  2022. $normalizedDigits = '';
  2023. $numberAsArray = preg_split('/(?<!^)(?!$)/u', $number);
  2024. foreach ($numberAsArray as $character) {
  2025. // Check if we are in the unicode number range
  2026. if (array_key_exists($character, static::$numericCharacters)) {
  2027. $normalizedDigits .= static::$numericCharacters[$character];
  2028. } elseif (is_numeric($character)) {
  2029. $normalizedDigits .= $character;
  2030. } elseif ($keepNonDigits) {
  2031. $normalizedDigits .= $character;
  2032. }
  2033. }
  2034. return $normalizedDigits;
  2035. }
  2036. /**
  2037. * Strips the IDD from the start of the number if present. Helper function used by
  2038. * maybeStripInternationalPrefixAndNormalize.
  2039. * @param string $iddPattern
  2040. * @param string $number
  2041. * @return bool
  2042. */
  2043. protected function parsePrefixAsIdd($iddPattern, &$number)
  2044. {
  2045. $m = new Matcher($iddPattern, $number);
  2046. if ($m->lookingAt()) {
  2047. $matchEnd = $m->end();
  2048. // Only strip this if the first digit after the match is not a 0, since country calling codes
  2049. // cannot begin with 0.
  2050. $digitMatcher = new Matcher(static::$CAPTURING_DIGIT_PATTERN, substr($number, $matchEnd));
  2051. if ($digitMatcher->find()) {
  2052. $normalizedGroup = static::normalizeDigitsOnly($digitMatcher->group(1));
  2053. if ($normalizedGroup == '0') {
  2054. return false;
  2055. }
  2056. }
  2057. $number = substr($number, $matchEnd);
  2058. return true;
  2059. }
  2060. return false;
  2061. }
  2062. /**
  2063. * Extracts country calling code from fullNumber, returns it and places the remaining number in nationalNumber.
  2064. * It assumes that the leading plus sign or IDD has already been removed.
  2065. * Returns 0 if fullNumber doesn't start with a valid country calling code, and leaves nationalNumber unmodified.
  2066. * @param string $fullNumber
  2067. * @param string $nationalNumber
  2068. * @return int
  2069. * @internal
  2070. */
  2071. public function extractCountryCode($fullNumber, &$nationalNumber)
  2072. {
  2073. if (($fullNumber === '') || ($fullNumber[0] == '0')) {
  2074. // Country codes do not begin with a '0'.
  2075. return 0;
  2076. }
  2077. $numberLength = mb_strlen($fullNumber);
  2078. for ($i = 1; $i <= static::MAX_LENGTH_COUNTRY_CODE && $i <= $numberLength; $i++) {
  2079. $potentialCountryCode = (int)substr($fullNumber, 0, $i);
  2080. if (isset($this->countryCallingCodeToRegionCodeMap[$potentialCountryCode])) {
  2081. $nationalNumber .= substr($fullNumber, $i);
  2082. return $potentialCountryCode;
  2083. }
  2084. }
  2085. return 0;
  2086. }
  2087. /**
  2088. * Strips any national prefix (such as 0, 1) present in the number provided.
  2089. *
  2090. * @param string $number the normalized telephone number that we wish to strip any national
  2091. * dialing prefix from
  2092. * @param PhoneMetadata $metadata the metadata for the region that we think this number is from
  2093. * @param string $carrierCode a place to insert the carrier code if one is extracted
  2094. * @return bool true if a national prefix or carrier code (or both) could be extracted.
  2095. */
  2096. public function maybeStripNationalPrefixAndCarrierCode(&$number, PhoneMetadata $metadata, &$carrierCode)
  2097. {
  2098. $numberLength = mb_strlen($number);
  2099. $possibleNationalPrefix = $metadata->getNationalPrefixForParsing();
  2100. if ($numberLength == 0 || $possibleNationalPrefix === null || $possibleNationalPrefix === '') {
  2101. // Early return for numbers of zero length.
  2102. return false;
  2103. }
  2104. // Attempt to parse the first digits as a national prefix.
  2105. $prefixMatcher = new Matcher($possibleNationalPrefix, $number);
  2106. if ($prefixMatcher->lookingAt()) {
  2107. $generalDesc = $metadata->getGeneralDesc();
  2108. // Check if the original number is viable.
  2109. $isViableOriginalNumber = $this->matcherAPI->matchNationalNumber($number, $generalDesc, false);
  2110. // $prefixMatcher->group($numOfGroups) === null implies nothing was captured by the capturing
  2111. // groups in $possibleNationalPrefix; therefore, no transformation is necessary, and we just
  2112. // remove the national prefix
  2113. $numOfGroups = $prefixMatcher->groupCount();
  2114. $transformRule = $metadata->getNationalPrefixTransformRule();
  2115. if ($transformRule === null
  2116. || $transformRule === ''
  2117. || $prefixMatcher->group($numOfGroups - 1) === null
  2118. ) {
  2119. // If the original number was viable, and the resultant number is not, we return.
  2120. if ($isViableOriginalNumber &&
  2121. !$this->matcherAPI->matchNationalNumber(
  2122. substr($number, $prefixMatcher->end()),
  2123. $generalDesc,
  2124. false
  2125. )) {
  2126. return false;
  2127. }
  2128. if ($carrierCode !== null && $numOfGroups > 0 && $prefixMatcher->group($numOfGroups) !== null) {
  2129. $carrierCode .= $prefixMatcher->group(1);
  2130. }
  2131. $number = substr($number, $prefixMatcher->end());
  2132. return true;
  2133. }
  2134. // Check that the resultant number is still viable. If not, return. Check this by copying
  2135. // the string and making the transformation on the copy first.
  2136. $transformedNumber = $number;
  2137. $transformedNumber = substr_replace(
  2138. $transformedNumber,
  2139. $prefixMatcher->replaceFirst($transformRule),
  2140. 0,
  2141. $numberLength
  2142. );
  2143. if ($isViableOriginalNumber
  2144. && !$this->matcherAPI->matchNationalNumber($transformedNumber, $generalDesc, false)) {
  2145. return false;
  2146. }
  2147. if ($carrierCode !== null && $numOfGroups > 1) {
  2148. $carrierCode .= $prefixMatcher->group(1);
  2149. }
  2150. $number = substr_replace($number, $transformedNumber, 0, mb_strlen($number));
  2151. return true;
  2152. }
  2153. return false;
  2154. }
  2155. /**
  2156. * Convenience wrapper around isPossibleNumberForTypeWithReason. Instead of returning the reason
  2157. * for failure, this method returns true if the number is either a possible fully-qualified
  2158. * number (containing the area code and country code), or if the number could be a possible local
  2159. * number (with a country code, but missing an area code). Local numbers are considered possible
  2160. * if they could be possibly dialled in this format: if the area code is needed for a call to
  2161. * connect, the number is not considered possible without it.
  2162. *
  2163. * @param PhoneNumber $number The number that needs to be checked
  2164. * @param int $type PhoneNumberType The type we are interested in
  2165. * @return bool true if the number is possible for this particular type
  2166. */
  2167. public function isPossibleNumberForType(PhoneNumber $number, $type)
  2168. {
  2169. $result = $this->isPossibleNumberForTypeWithReason($number, $type);
  2170. return $result === ValidationResult::IS_POSSIBLE
  2171. || $result === ValidationResult::IS_POSSIBLE_LOCAL_ONLY;
  2172. }
  2173. /**
  2174. * Helper method to check a number against possible lengths for this number type, and determine
  2175. * whether it matches, or is too short or too long.
  2176. *
  2177. * @param string $number
  2178. * @param PhoneMetadata $metadata
  2179. * @param int $type PhoneNumberType
  2180. * @return int ValidationResult
  2181. */
  2182. protected function testNumberLength($number, PhoneMetadata $metadata, $type = PhoneNumberType::UNKNOWN)
  2183. {
  2184. $descForType = $this->getNumberDescByType($metadata, $type);
  2185. // There should always be "possibleLengths" set for every element. This is declared in the XML
  2186. // schema which is verified by PhoneNumberMetadataSchemaTest.
  2187. // For size efficiency, where a sub-description (e.g. fixed-line) has the same possibleLengths
  2188. // as the parent, this is missing, so we fall back to the general desc (where no numbers of the
  2189. // type exist at all, there is one possible length (-1) which is guaranteed not to match the
  2190. // length of any real phone number).
  2191. $possibleLengths = (count($descForType->getPossibleLength()) === 0)
  2192. ? $metadata->getGeneralDesc()->getPossibleLength() : $descForType->getPossibleLength();
  2193. $localLengths = $descForType->getPossibleLengthLocalOnly();
  2194. if ($type === PhoneNumberType::FIXED_LINE_OR_MOBILE) {
  2195. if (!static::descHasPossibleNumberData($this->getNumberDescByType($metadata, PhoneNumberType::FIXED_LINE))) {
  2196. // The rate case has been encountered where no fixedLine data is available (true for some
  2197. // non-geographical entities), so we just check mobile.
  2198. return $this->testNumberLength($number, $metadata, PhoneNumberType::MOBILE);
  2199. }
  2200. $mobileDesc = $this->getNumberDescByType($metadata, PhoneNumberType::MOBILE);
  2201. if (static::descHasPossibleNumberData($mobileDesc)) {
  2202. // Note that when adding the possible lengths from mobile, we have to again check they
  2203. // aren't empty since if they are this indicates they are the same as the general desc and
  2204. // should be obtained from there.
  2205. $possibleLengths = array_merge(
  2206. $possibleLengths,
  2207. (count($mobileDesc->getPossibleLength()) === 0)
  2208. ? $metadata->getGeneralDesc()->getPossibleLength() : $mobileDesc->getPossibleLength()
  2209. );
  2210. // The current list is sorted; we need to merge in the new list and re-sort (duplicates
  2211. // are okay). Sorting isn't so expensive because the lists are very small.
  2212. sort($possibleLengths);
  2213. if (count($localLengths) === 0) {
  2214. $localLengths = $mobileDesc->getPossibleLengthLocalOnly();
  2215. } else {
  2216. $localLengths = array_merge($localLengths, $mobileDesc->getPossibleLengthLocalOnly());
  2217. sort($localLengths);
  2218. }
  2219. }
  2220. }
  2221. // If the type is not supported at all (indicated by the possible lengths containing -1 at this
  2222. // point) we return invalid length.
  2223. if ($possibleLengths[0] === -1) {
  2224. return ValidationResult::INVALID_LENGTH;
  2225. }
  2226. $actualLength = mb_strlen($number);
  2227. // This is safe because there is never an overlap between the possible lengths and the local-only
  2228. // lengths; this is checked at build time.
  2229. if (in_array($actualLength, $localLengths)) {
  2230. return ValidationResult::IS_POSSIBLE_LOCAL_ONLY;
  2231. }
  2232. $minimumLength = reset($possibleLengths);
  2233. if ($minimumLength == $actualLength) {
  2234. return ValidationResult::IS_POSSIBLE;
  2235. }
  2236. if ($minimumLength > $actualLength) {
  2237. return ValidationResult::TOO_SHORT;
  2238. } elseif (isset($possibleLengths[count($possibleLengths) - 1]) && $possibleLengths[count($possibleLengths) - 1] < $actualLength) {
  2239. return ValidationResult::TOO_LONG;
  2240. }
  2241. // We skip the first element; we've already checked it.
  2242. array_shift($possibleLengths);
  2243. return in_array($actualLength, $possibleLengths) ? ValidationResult::IS_POSSIBLE : ValidationResult::INVALID_LENGTH;
  2244. }
  2245. /**
  2246. * Returns a list with the region codes that match the specific country calling code. For
  2247. * non-geographical country calling codes, the region code 001 is returned. Also, in the case
  2248. * of no region code being found, an empty list is returned.
  2249. * @param int $countryCallingCode
  2250. * @return array
  2251. */
  2252. public function getRegionCodesForCountryCode($countryCallingCode)
  2253. {
  2254. $regionCodes = isset($this->countryCallingCodeToRegionCodeMap[$countryCallingCode]) ? $this->countryCallingCodeToRegionCodeMap[$countryCallingCode] : null;
  2255. return $regionCodes === null ? array() : $regionCodes;
  2256. }
  2257. /**
  2258. * Returns the country calling code for a specific region. For example, this would be 1 for the
  2259. * United States, and 64 for New Zealand. Assumes the region is already valid.
  2260. *
  2261. * @param string $regionCode the region that we want to get the country calling code for
  2262. * @return int the country calling code for the region denoted by regionCode
  2263. */
  2264. public function getCountryCodeForRegion($regionCode)
  2265. {
  2266. if (!$this->isValidRegionCode($regionCode)) {
  2267. return 0;
  2268. }
  2269. return $this->getCountryCodeForValidRegion($regionCode);
  2270. }
  2271. /**
  2272. * Returns the country calling code for a specific region. For example, this would be 1 for the
  2273. * United States, and 64 for New Zealand. Assumes the region is already valid.
  2274. *
  2275. * @param string $regionCode the region that we want to get the country calling code for
  2276. * @return int the country calling code for the region denoted by regionCode
  2277. * @throws \InvalidArgumentException if the region is invalid
  2278. */
  2279. protected function getCountryCodeForValidRegion($regionCode)
  2280. {
  2281. $metadata = $this->getMetadataForRegion($regionCode);
  2282. if ($metadata === null) {
  2283. throw new \InvalidArgumentException('Invalid region code: ' . $regionCode);
  2284. }
  2285. return $metadata->getCountryCode();
  2286. }
  2287. /**
  2288. * Returns a number formatted in such a way that it can be dialed from a mobile phone in a
  2289. * specific region. If the number cannot be reached from the region (e.g. some countries block
  2290. * toll-free numbers from being called outside of the country), the method returns an empty
  2291. * string.
  2292. *
  2293. * @param PhoneNumber $number the phone number to be formatted
  2294. * @param string $regionCallingFrom the region where the call is being placed
  2295. * @param boolean $withFormatting whether the number should be returned with formatting symbols, such as
  2296. * spaces and dashes.
  2297. * @return string the formatted phone number
  2298. */
  2299. public function formatNumberForMobileDialing(PhoneNumber $number, $regionCallingFrom, $withFormatting)
  2300. {
  2301. $countryCallingCode = $number->getCountryCode();
  2302. if (!$this->hasValidCountryCallingCode($countryCallingCode)) {
  2303. return $number->hasRawInput() ? $number->getRawInput() : '';
  2304. }
  2305. $formattedNumber = '';
  2306. // Clear the extension, as that part cannot normally be dialed together with the main number.
  2307. $numberNoExt = new PhoneNumber();
  2308. $numberNoExt->mergeFrom($number)->clearExtension();
  2309. $regionCode = $this->getRegionCodeForCountryCode($countryCallingCode);
  2310. $numberType = $this->getNumberType($numberNoExt);
  2311. $isValidNumber = ($numberType !== PhoneNumberType::UNKNOWN);
  2312. if (strtoupper($regionCallingFrom) === $regionCode) {
  2313. $isFixedLineOrMobile = ($numberType == PhoneNumberType::FIXED_LINE || $numberType == PhoneNumberType::MOBILE || $numberType == PhoneNumberType::FIXED_LINE_OR_MOBILE);
  2314. // Carrier codes may be needed in some countries. We handle this here.
  2315. if ($regionCode === 'BR' && $isFixedLineOrMobile) {
  2316. // Historically, we set this to an empty string when parsing with raw input if none was
  2317. // found in the input string. However, this doesn't result in a number we can dial. For this
  2318. // reason, we treat the empty string the same as if it isn't set at all.
  2319. $formattedNumber = $numberNoExt->getPreferredDomesticCarrierCode() !== ''
  2320. ? $this->formatNationalNumberWithPreferredCarrierCode($numberNoExt, '')
  2321. // Brazilian fixed line and mobile numbers need to be dialed with a carrier code when
  2322. // called within Brazil. Without that, most of the carriers won't connect the call.
  2323. // Because of that, we return an empty string here.
  2324. : '';
  2325. } elseif ($countryCallingCode === static::NANPA_COUNTRY_CODE) {
  2326. // For NANPA countries, we output international format for numbers that can be dialed
  2327. // internationally, since that always works, except for numbers which might potentially be
  2328. // short numbers, which are always dialled in national format.
  2329. $regionMetadata = $this->getMetadataForRegion($regionCallingFrom);
  2330. if ($this->canBeInternationallyDialled($numberNoExt)
  2331. && $this->testNumberLength($this->getNationalSignificantNumber($numberNoExt), $regionMetadata)
  2332. !== ValidationResult::TOO_SHORT
  2333. ) {
  2334. $formattedNumber = $this->format($numberNoExt, PhoneNumberFormat::INTERNATIONAL);
  2335. } else {
  2336. $formattedNumber = $this->format($numberNoExt, PhoneNumberFormat::NATIONAL);
  2337. }
  2338. } elseif ((
  2339. $regionCode == static::REGION_CODE_FOR_NON_GEO_ENTITY ||
  2340. // MX fixed line and mobile numbers should always be formatted in international format,
  2341. // even when dialed within MX. For national format to work, a carrier code needs to be
  2342. // used, and the correct carrier code depends on if the caller and callee are from the
  2343. // same local area. It is trickier to get that to work correctly than using
  2344. // international format, which is tested to work fine on all carriers.
  2345. // CL fixed line numbers need the national prefix when dialing in the national format,
  2346. // but don't have it when used for display. The reverse is true for mobile numbers.
  2347. // As a result, we output them in the international format to make it work.
  2348. (
  2349. ($regionCode === 'MX' || $regionCode === 'CL' || $regionCode === 'UZ')
  2350. && $isFixedLineOrMobile
  2351. )
  2352. ) && $this->canBeInternationallyDialled($numberNoExt)
  2353. ) {
  2354. $formattedNumber = $this->format($numberNoExt, PhoneNumberFormat::INTERNATIONAL);
  2355. } else {
  2356. $formattedNumber = $this->format($numberNoExt, PhoneNumberFormat::NATIONAL);
  2357. }
  2358. } elseif ($isValidNumber && $this->canBeInternationallyDialled($numberNoExt)) {
  2359. // We assume that short numbers are not diallable from outside their region, so if a number
  2360. // is not a valid regular length phone number, we treat it as if it cannot be internationally
  2361. // dialled.
  2362. return $withFormatting ?
  2363. $this->format($numberNoExt, PhoneNumberFormat::INTERNATIONAL) :
  2364. $this->format($numberNoExt, PhoneNumberFormat::E164);
  2365. }
  2366. return $withFormatting ? $formattedNumber : static::normalizeDiallableCharsOnly($formattedNumber);
  2367. }
  2368. /**
  2369. * Formats a phone number in national format for dialing using the carrier as specified in the
  2370. * {@code carrierCode}. The {@code carrierCode} will always be used regardless of whether the
  2371. * phone number already has a preferred domestic carrier code stored. If {@code carrierCode}
  2372. * contains an empty string, returns the number in national format without any carrier code.
  2373. *
  2374. * @param PhoneNumber $number the phone number to be formatted
  2375. * @param string $carrierCode the carrier selection code to be used
  2376. * @return string the formatted phone number in national format for dialing using the carrier as
  2377. * specified in the {@code carrierCode}
  2378. */
  2379. public function formatNationalNumberWithCarrierCode(PhoneNumber $number, $carrierCode)
  2380. {
  2381. $countryCallingCode = $number->getCountryCode();
  2382. $nationalSignificantNumber = $this->getNationalSignificantNumber($number);
  2383. if (!$this->hasValidCountryCallingCode($countryCallingCode)) {
  2384. return $nationalSignificantNumber;
  2385. }
  2386. // Note getRegionCodeForCountryCode() is used because formatting information for regions which
  2387. // share a country calling code is contained by only one region for performance reasons. For
  2388. // example, for NANPA regions it will be contained in the metadata for US.
  2389. $regionCode = $this->getRegionCodeForCountryCode($countryCallingCode);
  2390. // Metadata cannot be null because the country calling code is valid.
  2391. $metadata = $this->getMetadataForRegionOrCallingCode($countryCallingCode, $regionCode);
  2392. $formattedNumber = $this->formatNsn(
  2393. $nationalSignificantNumber,
  2394. $metadata,
  2395. PhoneNumberFormat::NATIONAL,
  2396. $carrierCode
  2397. );
  2398. $this->maybeAppendFormattedExtension($number, $metadata, PhoneNumberFormat::NATIONAL, $formattedNumber);
  2399. $this->prefixNumberWithCountryCallingCode(
  2400. $countryCallingCode,
  2401. PhoneNumberFormat::NATIONAL,
  2402. $formattedNumber
  2403. );
  2404. return $formattedNumber;
  2405. }
  2406. /**
  2407. * Formats a phone number in national format for dialing using the carrier as specified in the
  2408. * preferredDomesticCarrierCode field of the PhoneNumber object passed in. If that is missing,
  2409. * use the {@code fallbackCarrierCode} passed in instead. If there is no
  2410. * {@code preferredDomesticCarrierCode}, and the {@code fallbackCarrierCode} contains an empty
  2411. * string, return the number in national format without any carrier code.
  2412. *
  2413. * <p>Use {@link #formatNationalNumberWithCarrierCode} instead if the carrier code passed in
  2414. * should take precedence over the number's {@code preferredDomesticCarrierCode} when formatting.
  2415. *
  2416. * @param PhoneNumber $number the phone number to be formatted
  2417. * @param string $fallbackCarrierCode the carrier selection code to be used, if none is found in the
  2418. * phone number itself
  2419. * @return string the formatted phone number in national format for dialing using the number's
  2420. * {@code preferredDomesticCarrierCode}, or the {@code fallbackCarrierCode} passed in if
  2421. * none is found
  2422. */
  2423. public function formatNationalNumberWithPreferredCarrierCode(PhoneNumber $number, $fallbackCarrierCode)
  2424. {
  2425. return $this->formatNationalNumberWithCarrierCode(
  2426. $number,
  2427. // Historically, we set this to an empty string when parsing with raw input if none was
  2428. // found in the input string. However, this doesn't result in a number we can dial. For this
  2429. // reason, we treat the empty string the same as if it isn't set at all.
  2430. $number->getPreferredDomesticCarrierCode() != ''
  2431. ? $number->getPreferredDomesticCarrierCode()
  2432. : $fallbackCarrierCode
  2433. );
  2434. }
  2435. /**
  2436. * Returns true if the number can be dialled from outside the region, or unknown. If the number
  2437. * can only be dialled from within the region, returns false. Does not check the number is a valid
  2438. * number. Note that, at the moment, this method does not handle short numbers (which are
  2439. * currently all presumed to not be diallable from outside their country).
  2440. *
  2441. * @param PhoneNumber $number the phone-number for which we want to know whether it is diallable from outside the region
  2442. * @return bool
  2443. */
  2444. public function canBeInternationallyDialled(PhoneNumber $number)
  2445. {
  2446. $metadata = $this->getMetadataForRegion($this->getRegionCodeForNumber($number));
  2447. if ($metadata === null) {
  2448. // Note numbers belonging to non-geographical entities (e.g. +800 numbers) are always
  2449. // internationally diallable, and will be caught here.
  2450. return true;
  2451. }
  2452. $nationalSignificantNumber = $this->getNationalSignificantNumber($number);
  2453. return !$this->isNumberMatchingDesc($nationalSignificantNumber, $metadata->getNoInternationalDialling());
  2454. }
  2455. /**
  2456. * Normalizes a string of characters representing a phone number. This strips all characters which
  2457. * are not diallable on a mobile phone keypad (including all non-ASCII digits).
  2458. *
  2459. * @param string $number a string of characters representing a phone number
  2460. * @return string the normalized string version of the phone number
  2461. */
  2462. public static function normalizeDiallableCharsOnly($number)
  2463. {
  2464. if (count(static::$DIALLABLE_CHAR_MAPPINGS) === 0) {
  2465. static::initDiallableCharMappings();
  2466. }
  2467. return static::normalizeHelper($number, static::$DIALLABLE_CHAR_MAPPINGS, true /* remove non matches */);
  2468. }
  2469. /**
  2470. * Formats a phone number for out-of-country dialing purposes.
  2471. *
  2472. * Note that in this version, if the number was entered originally using alpha characters and
  2473. * this version of the number is stored in raw_input, this representation of the number will be
  2474. * used rather than the digit representation. Grouping information, as specified by characters
  2475. * such as "-" and " ", will be retained.
  2476. *
  2477. * <p><b>Caveats:</b></p>
  2478. * <ul>
  2479. * <li> This will not produce good results if the country calling code is both present in the raw
  2480. * input _and_ is the start of the national number. This is not a problem in the regions
  2481. * which typically use alpha numbers.
  2482. * <li> This will also not produce good results if the raw input has any grouping information
  2483. * within the first three digits of the national number, and if the function needs to strip
  2484. * preceding digits/words in the raw input before these digits. Normally people group the
  2485. * first three digits together so this is not a huge problem - and will be fixed if it
  2486. * proves to be so.
  2487. * </ul>
  2488. *
  2489. * @param PhoneNumber $number the phone number that needs to be formatted
  2490. * @param String $regionCallingFrom the region where the call is being placed
  2491. * @return String the formatted phone number
  2492. */
  2493. public function formatOutOfCountryKeepingAlphaChars(PhoneNumber $number, $regionCallingFrom)
  2494. {
  2495. $rawInput = $number->getRawInput();
  2496. // If there is no raw input, then we can't keep alpha characters because there aren't any.
  2497. // In this case, we return formatOutOfCountryCallingNumber.
  2498. if ($rawInput === null || $rawInput === '') {
  2499. return $this->formatOutOfCountryCallingNumber($number, $regionCallingFrom);
  2500. }
  2501. $countryCode = $number->getCountryCode();
  2502. if (!$this->hasValidCountryCallingCode($countryCode)) {
  2503. return $rawInput;
  2504. }
  2505. // Strip any prefix such as country calling code, IDD, that was present. We do this by comparing
  2506. // the number in raw_input with the parsed number.
  2507. // To do this, first we normalize punctuation. We retain number grouping symbols such as " "
  2508. // only.
  2509. $rawInput = self::normalizeHelper($rawInput, static::$ALL_PLUS_NUMBER_GROUPING_SYMBOLS, true);
  2510. // Now we trim everything before the first three digits in the parsed number. We choose three
  2511. // because all valid alpha numbers have 3 digits at the start - if it does not, then we don't
  2512. // trim anything at all. Similarly, if the national number was less than three digits, we don't
  2513. // trim anything at all.
  2514. $nationalNumber = $this->getNationalSignificantNumber($number);
  2515. if (mb_strlen($nationalNumber) > 3) {
  2516. $firstNationalNumberDigit = strpos($rawInput, substr($nationalNumber, 0, 3));
  2517. if ($firstNationalNumberDigit !== false) {
  2518. $rawInput = substr($rawInput, $firstNationalNumberDigit);
  2519. }
  2520. }
  2521. $metadataForRegionCallingFrom = $this->getMetadataForRegion($regionCallingFrom);
  2522. if ($countryCode == static::NANPA_COUNTRY_CODE) {
  2523. if ($this->isNANPACountry($regionCallingFrom)) {
  2524. return $countryCode . ' ' . $rawInput;
  2525. }
  2526. } elseif ($metadataForRegionCallingFrom !== null &&
  2527. $countryCode == $this->getCountryCodeForValidRegion($regionCallingFrom)
  2528. ) {
  2529. $formattingPattern =
  2530. $this->chooseFormattingPatternForNumber(
  2531. $metadataForRegionCallingFrom->numberFormats(),
  2532. $nationalNumber
  2533. );
  2534. if ($formattingPattern === null) {
  2535. // If no pattern above is matched, we format the original input.
  2536. return $rawInput;
  2537. }
  2538. $newFormat = new NumberFormat();
  2539. $newFormat->mergeFrom($formattingPattern);
  2540. // The first group is the first group of digits that the user wrote together.
  2541. $newFormat->setPattern("(\\d+)(.*)");
  2542. // Here we just concatenate them back together after the national prefix has been fixed.
  2543. $newFormat->setFormat('$1$2');
  2544. // Now we format using this pattern instead of the default pattern, but with the national
  2545. // prefix prefixed if necessary.
  2546. // This will not work in the cases where the pattern (and not the leading digits) decide
  2547. // whether a national prefix needs to be used, since we have overridden the pattern to match
  2548. // anything, but that is not the case in the metadata to date.
  2549. return $this->formatNsnUsingPattern($rawInput, $newFormat, PhoneNumberFormat::NATIONAL);
  2550. }
  2551. $internationalPrefixForFormatting = '';
  2552. // If an unsupported region-calling-from is entered, or a country with multiple international
  2553. // prefixes, the international format of the number is returned, unless there is a preferred
  2554. // international prefix.
  2555. if ($metadataForRegionCallingFrom !== null) {
  2556. $internationalPrefix = $metadataForRegionCallingFrom->getInternationalPrefix();
  2557. $uniqueInternationalPrefixMatcher = new Matcher(static::SINGLE_INTERNATIONAL_PREFIX, $internationalPrefix);
  2558. $internationalPrefixForFormatting =
  2559. $uniqueInternationalPrefixMatcher->matches()
  2560. ? $internationalPrefix
  2561. : $metadataForRegionCallingFrom->getPreferredInternationalPrefix();
  2562. }
  2563. $formattedNumber = $rawInput;
  2564. $regionCode = $this->getRegionCodeForCountryCode($countryCode);
  2565. // Metadata cannot be null because the country calling code is valid.
  2566. $metadataForRegion = $this->getMetadataForRegionOrCallingCode($countryCode, $regionCode);
  2567. $this->maybeAppendFormattedExtension(
  2568. $number,
  2569. $metadataForRegion,
  2570. PhoneNumberFormat::INTERNATIONAL,
  2571. $formattedNumber
  2572. );
  2573. if ($internationalPrefixForFormatting != '') {
  2574. $formattedNumber = $internationalPrefixForFormatting . ' ' . $countryCode . ' ' . $formattedNumber;
  2575. } else {
  2576. // Invalid region entered as country-calling-from (so no metadata was found for it) or the
  2577. // region chosen has multiple international dialling prefixes.
  2578. $this->prefixNumberWithCountryCallingCode(
  2579. $countryCode,
  2580. PhoneNumberFormat::INTERNATIONAL,
  2581. $formattedNumber
  2582. );
  2583. }
  2584. return $formattedNumber;
  2585. }
  2586. /**
  2587. * Formats a phone number for out-of-country dialing purposes. If no regionCallingFrom is
  2588. * supplied, we format the number in its INTERNATIONAL format. If the country calling code is the
  2589. * same as that of the region where the number is from, then NATIONAL formatting will be applied.
  2590. *
  2591. * <p>If the number itself has a country calling code of zero or an otherwise invalid country
  2592. * calling code, then we return the number with no formatting applied.
  2593. *
  2594. * <p>Note this function takes care of the case for calling inside of NANPA and between Russia and
  2595. * Kazakhstan (who share the same country calling code). In those cases, no international prefix
  2596. * is used. For regions which have multiple international prefixes, the number in its
  2597. * INTERNATIONAL format will be returned instead.
  2598. *
  2599. * @param PhoneNumber $number the phone number to be formatted
  2600. * @param string $regionCallingFrom the region where the call is being placed
  2601. * @return string the formatted phone number
  2602. */
  2603. public function formatOutOfCountryCallingNumber(PhoneNumber $number, $regionCallingFrom)
  2604. {
  2605. if (!$this->isValidRegionCode($regionCallingFrom)) {
  2606. return $this->format($number, PhoneNumberFormat::INTERNATIONAL);
  2607. }
  2608. $countryCallingCode = $number->getCountryCode();
  2609. $nationalSignificantNumber = $this->getNationalSignificantNumber($number);
  2610. if (!$this->hasValidCountryCallingCode($countryCallingCode)) {
  2611. return $nationalSignificantNumber;
  2612. }
  2613. if ($countryCallingCode == static::NANPA_COUNTRY_CODE) {
  2614. if ($this->isNANPACountry($regionCallingFrom)) {
  2615. // For NANPA regions, return the national format for these regions but prefix it with the
  2616. // country calling code.
  2617. return $countryCallingCode . ' ' . $this->format($number, PhoneNumberFormat::NATIONAL);
  2618. }
  2619. } elseif ($countryCallingCode == $this->getCountryCodeForValidRegion($regionCallingFrom)) {
  2620. // If regions share a country calling code, the country calling code need not be dialled.
  2621. // This also applies when dialling within a region, so this if clause covers both these cases.
  2622. // Technically this is the case for dialling from La Reunion to other overseas departments of
  2623. // France (French Guiana, Martinique, Guadeloupe), but not vice versa - so we don't cover this
  2624. // edge case for now and for those cases return the version including country calling code.
  2625. // Details here: http://www.petitfute.com/voyage/225-info-pratiques-reunion
  2626. return $this->format($number, PhoneNumberFormat::NATIONAL);
  2627. }
  2628. // Metadata cannot be null because we checked 'isValidRegionCode()' above.
  2629. /** @var PhoneMetadata $metadataForRegionCallingFrom */
  2630. $metadataForRegionCallingFrom = $this->getMetadataForRegion($regionCallingFrom);
  2631. $internationalPrefix = $metadataForRegionCallingFrom->getInternationalPrefix();
  2632. // In general, if there is a preferred international prefix, use that. Otherwise, for regions
  2633. // that have multiple international prefixes, the international format of the number is
  2634. // returned since we would not know which one to use.
  2635. $internationalPrefixForFormatting = '';
  2636. if ($metadataForRegionCallingFrom->hasPreferredInternationalPrefix()) {
  2637. $internationalPrefixForFormatting = $metadataForRegionCallingFrom->getPreferredInternationalPrefix();
  2638. } else {
  2639. $uniqueInternationalPrefixMatcher = new Matcher(static::SINGLE_INTERNATIONAL_PREFIX, $internationalPrefix);
  2640. if ($uniqueInternationalPrefixMatcher->matches()) {
  2641. $internationalPrefixForFormatting = $internationalPrefix;
  2642. }
  2643. }
  2644. $regionCode = $this->getRegionCodeForCountryCode($countryCallingCode);
  2645. // Metadata cannot be null because the country calling code is valid.
  2646. /** @var PhoneMetadata $metadataForRegion */
  2647. $metadataForRegion = $this->getMetadataForRegionOrCallingCode($countryCallingCode, $regionCode);
  2648. $formattedNationalNumber = $this->formatNsn(
  2649. $nationalSignificantNumber,
  2650. $metadataForRegion,
  2651. PhoneNumberFormat::INTERNATIONAL
  2652. );
  2653. $formattedNumber = $formattedNationalNumber;
  2654. $this->maybeAppendFormattedExtension(
  2655. $number,
  2656. $metadataForRegion,
  2657. PhoneNumberFormat::INTERNATIONAL,
  2658. $formattedNumber
  2659. );
  2660. if ($internationalPrefixForFormatting !== '') {
  2661. $formattedNumber = $internationalPrefixForFormatting . ' ' . $countryCallingCode . ' ' . $formattedNumber;
  2662. } else {
  2663. $this->prefixNumberWithCountryCallingCode(
  2664. $countryCallingCode,
  2665. PhoneNumberFormat::INTERNATIONAL,
  2666. $formattedNumber
  2667. );
  2668. }
  2669. return $formattedNumber;
  2670. }
  2671. /**
  2672. * Checks if this is a region under the North American Numbering Plan Administration (NANPA).
  2673. * @param string $regionCode
  2674. * @return boolean true if regionCode is one of the regions under NANPA
  2675. */
  2676. public function isNANPACountry($regionCode)
  2677. {
  2678. return in_array(strtoupper((string)$regionCode), $this->nanpaRegions);
  2679. }
  2680. /**
  2681. * Formats a phone number using the original phone number format (e.g. INTERNATIONAL or NATIONAL)
  2682. * that the number is parsed from, provided that the number has been parsed with
  2683. * parseAndKeepRawInput. Otherwise the number will be formatted in NATIONAL format.
  2684. *
  2685. * The original format is embedded in the country_code_source field of the PhoneNumber object
  2686. * passed in, which is only set when parsing keeps the raw input. When we don't have a formatting
  2687. * pattern for the number, the method falls back to returning the raw input.
  2688. *
  2689. * Note this method guarantees no digit will be inserted, removed or modified as a result of
  2690. * formatting.
  2691. *
  2692. * @param PhoneNumber $number the phone number that needs to be formatted in its original number format
  2693. * @param string $regionCallingFrom the region whose IDD needs to be prefixed if the original number
  2694. * has one
  2695. * @return string the formatted phone number in its original number format
  2696. */
  2697. public function formatInOriginalFormat(PhoneNumber $number, $regionCallingFrom)
  2698. {
  2699. if ($number->hasRawInput() && !$this->hasFormattingPatternForNumber($number)) {
  2700. // We check if we have the formatting pattern because without that, we might format the number
  2701. // as a group without national prefix.
  2702. return $number->getRawInput();
  2703. }
  2704. if (!$number->hasCountryCodeSource()) {
  2705. return $this->format($number, PhoneNumberFormat::NATIONAL);
  2706. }
  2707. switch ($number->getCountryCodeSource()) {
  2708. case CountryCodeSource::FROM_NUMBER_WITH_PLUS_SIGN:
  2709. $formattedNumber = $this->format($number, PhoneNumberFormat::INTERNATIONAL);
  2710. break;
  2711. case CountryCodeSource::FROM_NUMBER_WITH_IDD:
  2712. $formattedNumber = $this->formatOutOfCountryCallingNumber($number, $regionCallingFrom);
  2713. break;
  2714. case CountryCodeSource::FROM_NUMBER_WITHOUT_PLUS_SIGN:
  2715. $formattedNumber = substr($this->format($number, PhoneNumberFormat::INTERNATIONAL), 1);
  2716. break;
  2717. case CountryCodeSource::FROM_DEFAULT_COUNTRY:
  2718. // Fall-through to default case.
  2719. default:
  2720. $regionCode = $this->getRegionCodeForCountryCode($number->getCountryCode());
  2721. // We strip non-digits from the NDD here, and from the raw input later, so that we can
  2722. // compare them easily.
  2723. $nationalPrefix = $this->getNddPrefixForRegion($regionCode, true /* strip non-digits */);
  2724. $nationalFormat = $this->format($number, PhoneNumberFormat::NATIONAL);
  2725. if ($nationalPrefix === null || $nationalPrefix === '') {
  2726. // If the region doesn't have a national prefix at all, we can safely return the national
  2727. // format without worrying about a national prefix being added.
  2728. $formattedNumber = $nationalFormat;
  2729. break;
  2730. }
  2731. // Otherwise, we check if the original number was entered with a national prefix.
  2732. if ($this->rawInputContainsNationalPrefix(
  2733. $number->getRawInput(),
  2734. $nationalPrefix,
  2735. $regionCode
  2736. )
  2737. ) {
  2738. // If so, we can safely return the national format.
  2739. $formattedNumber = $nationalFormat;
  2740. break;
  2741. }
  2742. // Metadata cannot be null here because getNddPrefixForRegion() (above) returns null if
  2743. // there is no metadata for the region.
  2744. $metadata = $this->getMetadataForRegion($regionCode);
  2745. $nationalNumber = $this->getNationalSignificantNumber($number);
  2746. $formatRule = $this->chooseFormattingPatternForNumber($metadata->numberFormats(), $nationalNumber);
  2747. // The format rule could still be null here if the national number was 0 and there was no
  2748. // raw input (this should not be possible for numbers generated by the phonenumber library
  2749. // as they would also not have a country calling code and we would have exited earlier).
  2750. if ($formatRule === null) {
  2751. $formattedNumber = $nationalFormat;
  2752. break;
  2753. }
  2754. // When the format we apply to this number doesn't contain national prefix, we can just
  2755. // return the national format.
  2756. // TODO: Refactor the code below with the code in isNationalPrefixPresentIfRequired.
  2757. $candidateNationalPrefixRule = $formatRule->getNationalPrefixFormattingRule();
  2758. // We assume that the first-group symbol will never be _before_ the national prefix.
  2759. $indexOfFirstGroup = strpos($candidateNationalPrefixRule, '$1');
  2760. if ($indexOfFirstGroup <= 0) {
  2761. $formattedNumber = $nationalFormat;
  2762. break;
  2763. }
  2764. $candidateNationalPrefixRule = substr($candidateNationalPrefixRule, 0, $indexOfFirstGroup);
  2765. $candidateNationalPrefixRule = static::normalizeDigitsOnly($candidateNationalPrefixRule);
  2766. if ($candidateNationalPrefixRule === '') {
  2767. // National prefix not used when formatting this number.
  2768. $formattedNumber = $nationalFormat;
  2769. break;
  2770. }
  2771. // Otherwise, we need to remove the national prefix from our output.
  2772. $numFormatCopy = new NumberFormat();
  2773. $numFormatCopy->mergeFrom($formatRule);
  2774. $numFormatCopy->clearNationalPrefixFormattingRule();
  2775. $numberFormats = array();
  2776. $numberFormats[] = $numFormatCopy;
  2777. $formattedNumber = $this->formatByPattern($number, PhoneNumberFormat::NATIONAL, $numberFormats);
  2778. break;
  2779. }
  2780. $rawInput = $number->getRawInput();
  2781. // If no digit is inserted/removed/modified as a result of our formatting, we return the
  2782. // formatted phone number; otherwise we return the raw input the user entered.
  2783. if ($formattedNumber !== null && mb_strlen($rawInput) > 0) {
  2784. $normalizedFormattedNumber = static::normalizeDiallableCharsOnly($formattedNumber);
  2785. $normalizedRawInput = static::normalizeDiallableCharsOnly($rawInput);
  2786. if ($normalizedFormattedNumber != $normalizedRawInput) {
  2787. $formattedNumber = $rawInput;
  2788. }
  2789. }
  2790. return $formattedNumber;
  2791. }
  2792. /**
  2793. * @param PhoneNumber $number
  2794. * @return bool
  2795. */
  2796. protected function hasFormattingPatternForNumber(PhoneNumber $number)
  2797. {
  2798. $countryCallingCode = $number->getCountryCode();
  2799. $phoneNumberRegion = $this->getRegionCodeForCountryCode($countryCallingCode);
  2800. $metadata = $this->getMetadataForRegionOrCallingCode($countryCallingCode, $phoneNumberRegion);
  2801. if ($metadata === null) {
  2802. return false;
  2803. }
  2804. $nationalNumber = $this->getNationalSignificantNumber($number);
  2805. $formatRule = $this->chooseFormattingPatternForNumber($metadata->numberFormats(), $nationalNumber);
  2806. return $formatRule !== null;
  2807. }
  2808. /**
  2809. * Returns the national dialling prefix for a specific region. For example, this would be 1 for
  2810. * the United States, and 0 for New Zealand. Set stripNonDigits to true to strip symbols like "~"
  2811. * (which indicates a wait for a dialling tone) from the prefix returned. If no national prefix is
  2812. * present, we return null.
  2813. *
  2814. * <p>Warning: Do not use this method for do-your-own formatting - for some regions, the
  2815. * national dialling prefix is used only for certain types of numbers. Use the library's
  2816. * formatting functions to prefix the national prefix when required.
  2817. *
  2818. * @param string $regionCode the region that we want to get the dialling prefix for
  2819. * @param boolean $stripNonDigits true to strip non-digits from the national dialling prefix
  2820. * @return string the dialling prefix for the region denoted by regionCode
  2821. */
  2822. public function getNddPrefixForRegion($regionCode, $stripNonDigits)
  2823. {
  2824. $metadata = $this->getMetadataForRegion($regionCode);
  2825. if ($metadata === null) {
  2826. return null;
  2827. }
  2828. $nationalPrefix = $metadata->getNationalPrefix();
  2829. // If no national prefix was found, we return null.
  2830. if ($nationalPrefix == '') {
  2831. return null;
  2832. }
  2833. if ($stripNonDigits) {
  2834. // Note: if any other non-numeric symbols are ever used in national prefixes, these would have
  2835. // to be removed here as well.
  2836. $nationalPrefix = str_replace('~', '', $nationalPrefix);
  2837. }
  2838. return $nationalPrefix;
  2839. }
  2840. /**
  2841. * Check if rawInput, which is assumed to be in the national format, has a national prefix. The
  2842. * national prefix is assumed to be in digits-only form.
  2843. * @param string $rawInput
  2844. * @param string $nationalPrefix
  2845. * @param string $regionCode
  2846. * @return bool
  2847. */
  2848. protected function rawInputContainsNationalPrefix($rawInput, $nationalPrefix, $regionCode)
  2849. {
  2850. $normalizedNationalNumber = static::normalizeDigitsOnly($rawInput);
  2851. if (strpos($normalizedNationalNumber, $nationalPrefix) === 0) {
  2852. try {
  2853. // Some Japanese numbers (e.g. 00777123) might be mistaken to contain the national prefix
  2854. // when written without it (e.g. 0777123) if we just do prefix matching. To tackle that, we
  2855. // check the validity of the number if the assumed national prefix is removed (777123 won't
  2856. // be valid in Japan).
  2857. return $this->isValidNumber(
  2858. $this->parse(substr($normalizedNationalNumber, mb_strlen($nationalPrefix)), $regionCode)
  2859. );
  2860. } catch (NumberParseException $e) {
  2861. return false;
  2862. }
  2863. }
  2864. return false;
  2865. }
  2866. /**
  2867. * Tests whether a phone number matches a valid pattern. Note this doesn't verify the number
  2868. * is actually in use, which is impossible to tell by just looking at a number itself. It only
  2869. * verifies whether the parsed, canonicalised number is valid: not whether a particular series of
  2870. * digits entered by the user is diallable from the region provided when parsing. For example, the
  2871. * number +41 (0) 78 927 2696 can be parsed into a number with country code "41" and national
  2872. * significant number "789272696". This is valid, while the original string is not diallable.
  2873. *
  2874. * @param PhoneNumber $number the phone number that we want to validate
  2875. * @return boolean that indicates whether the number is of a valid pattern
  2876. */
  2877. public function isValidNumber(PhoneNumber $number)
  2878. {
  2879. $regionCode = $this->getRegionCodeForNumber($number);
  2880. return $this->isValidNumberForRegion($number, $regionCode);
  2881. }
  2882. /**
  2883. * Tests whether a phone number is valid for a certain region. Note this doesn't verify the number
  2884. * is actually in use, which is impossible to tell by just looking at a number itself. If the
  2885. * country calling code is not the same as the country calling code for the region, this
  2886. * immediately exits with false. After this, the specific number pattern rules for the region are
  2887. * examined. This is useful for determining for example whether a particular number is valid for
  2888. * Canada, rather than just a valid NANPA number.
  2889. * Warning: In most cases, you want to use {@link #isValidNumber} instead. For example, this
  2890. * method will mark numbers from British Crown dependencies such as the Isle of Man as invalid for
  2891. * the region "GB" (United Kingdom), since it has its own region code, "IM", which may be
  2892. * undesirable.
  2893. *
  2894. * @param PhoneNumber $number the phone number that we want to validate
  2895. * @param string $regionCode the region that we want to validate the phone number for
  2896. * @return boolean that indicates whether the number is of a valid pattern
  2897. */
  2898. public function isValidNumberForRegion(PhoneNumber $number, $regionCode)
  2899. {
  2900. $countryCode = $number->getCountryCode();
  2901. $metadata = $this->getMetadataForRegionOrCallingCode($countryCode, $regionCode);
  2902. if (($metadata === null) ||
  2903. (static::REGION_CODE_FOR_NON_GEO_ENTITY !== $regionCode &&
  2904. $countryCode !== $this->getCountryCodeForValidRegion($regionCode))
  2905. ) {
  2906. // Either the region code was invalid, or the country calling code for this number does not
  2907. // match that of the region code.
  2908. return false;
  2909. }
  2910. $nationalSignificantNumber = $this->getNationalSignificantNumber($number);
  2911. return $this->getNumberTypeHelper($nationalSignificantNumber, $metadata) != PhoneNumberType::UNKNOWN;
  2912. }
  2913. /**
  2914. * Parses a string and returns it as a phone number in proto buffer format. The method is quite
  2915. * lenient and looks for a number in the input text (raw input) and does not check whether the
  2916. * string is definitely only a phone number. To do this, it ignores punctuation and white-space,
  2917. * as well as any text before the number (e.g. a leading “Tel: ”) and trims the non-number bits.
  2918. * It will accept a number in any format (E164, national, international etc), assuming it can
  2919. * interpreted with the defaultRegion supplied. It also attempts to convert any alpha characters
  2920. * into digits if it thinks this is a vanity number of the type "1800 MICROSOFT".
  2921. *
  2922. * <p> This method will throw a {@link NumberParseException} if the number is not considered to
  2923. * be a possible number. Note that validation of whether the number is actually a valid number
  2924. * for a particular region is not performed. This can be done separately with {@link #isValidNumber}.
  2925. *
  2926. * <p> Note this method canonicalizes the phone number such that different representations can be
  2927. * easily compared, no matter what form it was originally entered in (e.g. national,
  2928. * international). If you want to record context about the number being parsed, such as the raw
  2929. * input that was entered, how the country code was derived etc. then call {@link
  2930. * #parseAndKeepRawInput} instead.
  2931. *
  2932. * @param string $numberToParse number that we are attempting to parse. This can contain formatting
  2933. * such as +, ( and -, as well as a phone number extension.
  2934. * @param string|null $defaultRegion region that we are expecting the number to be from. This is only used
  2935. * if the number being parsed is not written in international format.
  2936. * The country_code for the number in this case would be stored as that
  2937. * of the default region supplied. If the number is guaranteed to
  2938. * start with a '+' followed by the country calling code, then
  2939. * "ZZ" or null can be supplied.
  2940. * @param PhoneNumber|null $phoneNumber
  2941. * @param bool $keepRawInput
  2942. * @return PhoneNumber a phone number proto buffer filled with the parsed number
  2943. * @throws NumberParseException if the string is not considered to be a viable phone number (e.g.
  2944. * too few or too many digits) or if no default region was supplied
  2945. * and the number is not in international format (does not start
  2946. * with +)
  2947. */
  2948. public function parse($numberToParse, $defaultRegion = null, PhoneNumber $phoneNumber = null, $keepRawInput = false)
  2949. {
  2950. if ($phoneNumber === null) {
  2951. $phoneNumber = new PhoneNumber();
  2952. }
  2953. $this->parseHelper($numberToParse, $defaultRegion, $keepRawInput, true, $phoneNumber);
  2954. return $phoneNumber;
  2955. }
  2956. /**
  2957. * Formats a phone number in the specified format using client-defined formatting rules. Note that
  2958. * if the phone number has a country calling code of zero or an otherwise invalid country calling
  2959. * code, we cannot work out things like whether there should be a national prefix applied, or how
  2960. * to format extensions, so we return the national significant number with no formatting applied.
  2961. *
  2962. * @param PhoneNumber $number the phone number to be formatted
  2963. * @param int $numberFormat the format the phone number should be formatted into
  2964. * @param array $userDefinedFormats formatting rules specified by clients
  2965. * @return String the formatted phone number
  2966. */
  2967. public function formatByPattern(PhoneNumber $number, $numberFormat, array $userDefinedFormats)
  2968. {
  2969. $countryCallingCode = $number->getCountryCode();
  2970. $nationalSignificantNumber = $this->getNationalSignificantNumber($number);
  2971. if (!$this->hasValidCountryCallingCode($countryCallingCode)) {
  2972. return $nationalSignificantNumber;
  2973. }
  2974. // Note getRegionCodeForCountryCode() is used because formatting information for regions which
  2975. // share a country calling code is contained by only one region for performance reasons. For
  2976. // example, for NANPA regions it will be contained in the metadata for US.
  2977. $regionCode = $this->getRegionCodeForCountryCode($countryCallingCode);
  2978. // Metadata cannot be null because the country calling code is valid.
  2979. $metadata = $this->getMetadataForRegionOrCallingCode($countryCallingCode, $regionCode);
  2980. $formattedNumber = '';
  2981. $formattingPattern = $this->chooseFormattingPatternForNumber($userDefinedFormats, $nationalSignificantNumber);
  2982. if ($formattingPattern === null) {
  2983. // If no pattern above is matched, we format the number as a whole.
  2984. $formattedNumber .= $nationalSignificantNumber;
  2985. } else {
  2986. $numFormatCopy = new NumberFormat();
  2987. // Before we do a replacement of the national prefix pattern $NP with the national prefix, we
  2988. // need to copy the rule so that subsequent replacements for different numbers have the
  2989. // appropriate national prefix.
  2990. $numFormatCopy->mergeFrom($formattingPattern);
  2991. $nationalPrefixFormattingRule = $formattingPattern->getNationalPrefixFormattingRule();
  2992. if ($nationalPrefixFormattingRule !== '') {
  2993. $nationalPrefix = $metadata->getNationalPrefix();
  2994. if ($nationalPrefix != '') {
  2995. // Replace $NP with national prefix and $FG with the first group ($1).
  2996. $nationalPrefixFormattingRule = str_replace(
  2997. array(static::NP_STRING, static::FG_STRING),
  2998. array($nationalPrefix, '$1'),
  2999. $nationalPrefixFormattingRule
  3000. );
  3001. $numFormatCopy->setNationalPrefixFormattingRule($nationalPrefixFormattingRule);
  3002. } else {
  3003. // We don't want to have a rule for how to format the national prefix if there isn't one.
  3004. $numFormatCopy->clearNationalPrefixFormattingRule();
  3005. }
  3006. }
  3007. $formattedNumber .= $this->formatNsnUsingPattern($nationalSignificantNumber, $numFormatCopy, $numberFormat);
  3008. }
  3009. $this->maybeAppendFormattedExtension($number, $metadata, $numberFormat, $formattedNumber);
  3010. $this->prefixNumberWithCountryCallingCode($countryCallingCode, $numberFormat, $formattedNumber);
  3011. return $formattedNumber;
  3012. }
  3013. /**
  3014. * Gets a valid number for the specified region.
  3015. *
  3016. * @param string regionCode the region for which an example number is needed
  3017. * @return PhoneNumber a valid fixed-line number for the specified region. Returns null when the metadata
  3018. * does not contain such information, or the region 001 is passed in. For 001 (representing
  3019. * non-geographical numbers), call {@link #getExampleNumberForNonGeoEntity} instead.
  3020. */
  3021. public function getExampleNumber($regionCode)
  3022. {
  3023. return $this->getExampleNumberForType($regionCode, PhoneNumberType::FIXED_LINE);
  3024. }
  3025. /**
  3026. * Gets an invalid number for the specified region. This is useful for unit-testing purposes,
  3027. * where you want to test what will happen with an invalid number. Note that the number that is
  3028. * returned will always be able to be parsed and will have the correct country code. It may also
  3029. * be a valid *short* number/code for this region. Validity checking such numbers is handled with
  3030. * {@link ShortNumberInfo}.
  3031. *
  3032. * @param string $regionCode The region for which an example number is needed
  3033. * @return PhoneNumber|null An invalid number for the specified region. Returns null when an unsupported region
  3034. * or the region 001 (Earth) is passed in.
  3035. */
  3036. public function getInvalidExampleNumber($regionCode)
  3037. {
  3038. if (!$this->isValidRegionCode($regionCode)) {
  3039. return null;
  3040. }
  3041. // We start off with a valid fixed-line number since every country supports this. Alternatively
  3042. // we could start with a different number type, since fixed-line numbers typically have a wide
  3043. // breadth of valid number lengths and we may have to make it very short before we get an
  3044. // invalid number.
  3045. $desc = $this->getNumberDescByType($this->getMetadataForRegion($regionCode), PhoneNumberType::FIXED_LINE);
  3046. if ($desc->getExampleNumber() == '') {
  3047. // This shouldn't happen; we have a test for this.
  3048. return null;
  3049. }
  3050. $exampleNumber = $desc->getExampleNumber();
  3051. // Try and make the number invalid. We do this by changing the length. We try reducing the
  3052. // length of the number, since currently no region has a number that is the same length as
  3053. // MIN_LENGTH_FOR_NSN. This is probably quicker than making the number longer, which is another
  3054. // alternative. We could also use the possible number pattern to extract the possible lengths of
  3055. // the number to make this faster, but this method is only for unit-testing so simplicity is
  3056. // preferred to performance. We don't want to return a number that can't be parsed, so we check
  3057. // the number is long enough. We try all possible lengths because phone number plans often have
  3058. // overlapping prefixes so the number 123456 might be valid as a fixed-line number, and 12345 as
  3059. // a mobile number. It would be faster to loop in a different order, but we prefer numbers that
  3060. // look closer to real numbers (and it gives us a variety of different lengths for the resulting
  3061. // phone numbers - otherwise they would all be MIN_LENGTH_FOR_NSN digits long.)
  3062. for ($phoneNumberLength = mb_strlen($exampleNumber) - 1; $phoneNumberLength >= static::MIN_LENGTH_FOR_NSN; $phoneNumberLength--) {
  3063. $numberToTry = mb_substr($exampleNumber, 0, $phoneNumberLength);
  3064. try {
  3065. $possiblyValidNumber = $this->parse($numberToTry, $regionCode);
  3066. if (!$this->isValidNumber($possiblyValidNumber)) {
  3067. return $possiblyValidNumber;
  3068. }
  3069. } catch (NumberParseException $e) {
  3070. // Shouldn't happen: we have already checked the length, we know example numbers have
  3071. // only valid digits, and we know the region code is fine.
  3072. }
  3073. }
  3074. // We have a test to check that this doesn't happen for any of our supported regions.
  3075. return null;
  3076. }
  3077. /**
  3078. * Gets a valid number for the specified region and number type.
  3079. *
  3080. * @param string|int $regionCodeOrType the region for which an example number is needed
  3081. * @param int $type the PhoneNumberType of number that is needed
  3082. * @return PhoneNumber|null a valid number for the specified region and type. Returns null when the metadata
  3083. * does not contain such information or if an invalid region or region 001 was entered.
  3084. * For 001 (representing non-geographical numbers), call
  3085. * {@link #getExampleNumberForNonGeoEntity} instead.
  3086. *
  3087. * If $regionCodeOrType is the only parameter supplied, then a valid number for the specified number type
  3088. * will be returned that may belong to any country.
  3089. */
  3090. public function getExampleNumberForType($regionCodeOrType, $type = null)
  3091. {
  3092. if ($regionCodeOrType !== null && $type === null) {
  3093. /*
  3094. * Gets a valid number for the specified number type (it may belong to any country).
  3095. */
  3096. foreach ($this->getSupportedRegions() as $regionCode) {
  3097. $exampleNumber = $this->getExampleNumberForType($regionCode, $regionCodeOrType);
  3098. if ($exampleNumber !== null) {
  3099. return $exampleNumber;
  3100. }
  3101. }
  3102. // If there wasn't an example number for a region, try the non-geographical entities.
  3103. foreach ($this->getSupportedGlobalNetworkCallingCodes() as $countryCallingCode) {
  3104. $desc = $this->getNumberDescByType($this->getMetadataForNonGeographicalRegion($countryCallingCode), $regionCodeOrType);
  3105. try {
  3106. if ($desc->getExampleNumber() != '') {
  3107. return $this->parse('+' . $countryCallingCode . $desc->getExampleNumber(), static::UNKNOWN_REGION);
  3108. }
  3109. } catch (NumberParseException $e) {
  3110. // noop
  3111. }
  3112. }
  3113. // There are no example numbers of this type for any country in the library.
  3114. return null;
  3115. }
  3116. // Check the region code is valid.
  3117. if (!$this->isValidRegionCode($regionCodeOrType)) {
  3118. return null;
  3119. }
  3120. $desc = $this->getNumberDescByType($this->getMetadataForRegion($regionCodeOrType), $type);
  3121. try {
  3122. if ($desc->hasExampleNumber()) {
  3123. return $this->parse($desc->getExampleNumber(), $regionCodeOrType);
  3124. }
  3125. } catch (NumberParseException $e) {
  3126. // noop
  3127. }
  3128. return null;
  3129. }
  3130. /**
  3131. * @param PhoneMetadata $metadata
  3132. * @param int $type PhoneNumberType
  3133. * @return PhoneNumberDesc
  3134. */
  3135. protected function getNumberDescByType(PhoneMetadata $metadata, $type)
  3136. {
  3137. switch ($type) {
  3138. case PhoneNumberType::PREMIUM_RATE:
  3139. return $metadata->getPremiumRate();
  3140. case PhoneNumberType::TOLL_FREE:
  3141. return $metadata->getTollFree();
  3142. case PhoneNumberType::MOBILE:
  3143. return $metadata->getMobile();
  3144. case PhoneNumberType::FIXED_LINE:
  3145. case PhoneNumberType::FIXED_LINE_OR_MOBILE:
  3146. return $metadata->getFixedLine();
  3147. case PhoneNumberType::SHARED_COST:
  3148. return $metadata->getSharedCost();
  3149. case PhoneNumberType::VOIP:
  3150. return $metadata->getVoip();
  3151. case PhoneNumberType::PERSONAL_NUMBER:
  3152. return $metadata->getPersonalNumber();
  3153. case PhoneNumberType::PAGER:
  3154. return $metadata->getPager();
  3155. case PhoneNumberType::UAN:
  3156. return $metadata->getUan();
  3157. case PhoneNumberType::VOICEMAIL:
  3158. return $metadata->getVoicemail();
  3159. default:
  3160. return $metadata->getGeneralDesc();
  3161. }
  3162. }
  3163. /**
  3164. * Gets a valid number for the specified country calling code for a non-geographical entity.
  3165. *
  3166. * @param int $countryCallingCode the country calling code for a non-geographical entity
  3167. * @return PhoneNumber a valid number for the non-geographical entity. Returns null when the metadata
  3168. * does not contain such information, or the country calling code passed in does not belong
  3169. * to a non-geographical entity.
  3170. */
  3171. public function getExampleNumberForNonGeoEntity($countryCallingCode)
  3172. {
  3173. $metadata = $this->getMetadataForNonGeographicalRegion($countryCallingCode);
  3174. if ($metadata !== null) {
  3175. // For geographical entities, fixed-line data is always present. However, for non-geographical
  3176. // entities, this is not the case, so we have to go through different types to find the
  3177. // example number. We don't check fixed-line or personal number since they aren't used by
  3178. // non-geographical entities (if this changes, a unit-test will catch this.)
  3179. /** @var PhoneNumberDesc[] $list */
  3180. $list = array(
  3181. $metadata->getMobile(),
  3182. $metadata->getTollFree(),
  3183. $metadata->getSharedCost(),
  3184. $metadata->getVoip(),
  3185. $metadata->getVoicemail(),
  3186. $metadata->getUan(),
  3187. $metadata->getPremiumRate(),
  3188. );
  3189. foreach ($list as $desc) {
  3190. try {
  3191. if ($desc !== null && $desc->hasExampleNumber()) {
  3192. return $this->parse('+' . $countryCallingCode . $desc->getExampleNumber(), self::UNKNOWN_REGION);
  3193. }
  3194. } catch (NumberParseException $e) {
  3195. // noop
  3196. }
  3197. }
  3198. }
  3199. return null;
  3200. }
  3201. /**
  3202. * Takes two phone numbers and compares them for equality.
  3203. *
  3204. * <p>Returns EXACT_MATCH if the country_code, NSN, presence of a leading zero
  3205. * for Italian numbers and any extension present are the same. Returns NSN_MATCH
  3206. * if either or both has no region specified, and the NSNs and extensions are
  3207. * the same. Returns SHORT_NSN_MATCH if either or both has no region specified,
  3208. * or the region specified is the same, and one NSN could be a shorter version
  3209. * of the other number. This includes the case where one has an extension
  3210. * specified, and the other does not. Returns NO_MATCH otherwise. For example,
  3211. * the numbers +1 345 657 1234 and 657 1234 are a SHORT_NSN_MATCH. The numbers
  3212. * +1 345 657 1234 and 345 657 are a NO_MATCH.
  3213. *
  3214. * @param $firstNumberIn PhoneNumber|string First number to compare. If it is a
  3215. * string it can contain formatting, and can have country calling code specified
  3216. * with + at the start.
  3217. * @param $secondNumberIn PhoneNumber|string Second number to compare. If it is a
  3218. * string it can contain formatting, and can have country calling code specified
  3219. * with + at the start.
  3220. * @throws \InvalidArgumentException
  3221. * @return int {MatchType} NOT_A_NUMBER, NO_MATCH,
  3222. */
  3223. public function isNumberMatch($firstNumberIn, $secondNumberIn)
  3224. {
  3225. if (is_string($firstNumberIn) && is_string($secondNumberIn)) {
  3226. try {
  3227. $firstNumberAsProto = $this->parse($firstNumberIn, static::UNKNOWN_REGION);
  3228. return $this->isNumberMatch($firstNumberAsProto, $secondNumberIn);
  3229. } catch (NumberParseException $e) {
  3230. if ($e->getErrorType() === NumberParseException::INVALID_COUNTRY_CODE) {
  3231. try {
  3232. $secondNumberAsProto = $this->parse($secondNumberIn, static::UNKNOWN_REGION);
  3233. return $this->isNumberMatch($secondNumberAsProto, $firstNumberIn);
  3234. } catch (NumberParseException $e2) {
  3235. if ($e2->getErrorType() === NumberParseException::INVALID_COUNTRY_CODE) {
  3236. try {
  3237. $firstNumberProto = new PhoneNumber();
  3238. $secondNumberProto = new PhoneNumber();
  3239. $this->parseHelper($firstNumberIn, null, false, false, $firstNumberProto);
  3240. $this->parseHelper($secondNumberIn, null, false, false, $secondNumberProto);
  3241. return $this->isNumberMatch($firstNumberProto, $secondNumberProto);
  3242. } catch (NumberParseException $e3) {
  3243. // Fall through and return MatchType::NOT_A_NUMBER
  3244. }
  3245. }
  3246. }
  3247. }
  3248. }
  3249. return MatchType::NOT_A_NUMBER;
  3250. }
  3251. if ($firstNumberIn instanceof PhoneNumber && is_string($secondNumberIn)) {
  3252. // First see if the second number has an implicit country calling code, by attempting to parse
  3253. // it.
  3254. try {
  3255. $secondNumberAsProto = $this->parse($secondNumberIn, static::UNKNOWN_REGION);
  3256. return $this->isNumberMatch($firstNumberIn, $secondNumberAsProto);
  3257. } catch (NumberParseException $e) {
  3258. if ($e->getErrorType() === NumberParseException::INVALID_COUNTRY_CODE) {
  3259. // The second number has no country calling code. EXACT_MATCH is no longer possible.
  3260. // We parse it as if the region was the same as that for the first number, and if
  3261. // EXACT_MATCH is returned, we replace this with NSN_MATCH.
  3262. $firstNumberRegion = $this->getRegionCodeForCountryCode($firstNumberIn->getCountryCode());
  3263. try {
  3264. if ($firstNumberRegion != static::UNKNOWN_REGION) {
  3265. $secondNumberWithFirstNumberRegion = $this->parse($secondNumberIn, $firstNumberRegion);
  3266. $match = $this->isNumberMatch($firstNumberIn, $secondNumberWithFirstNumberRegion);
  3267. if ($match === MatchType::EXACT_MATCH) {
  3268. return MatchType::NSN_MATCH;
  3269. }
  3270. return $match;
  3271. }
  3272. // If the first number didn't have a valid country calling code, then we parse the
  3273. // second number without one as well.
  3274. $secondNumberProto = new PhoneNumber();
  3275. $this->parseHelper($secondNumberIn, null, false, false, $secondNumberProto);
  3276. return $this->isNumberMatch($firstNumberIn, $secondNumberProto);
  3277. } catch (NumberParseException $e2) {
  3278. // Fall-through to return NOT_A_NUMBER.
  3279. }
  3280. }
  3281. }
  3282. }
  3283. if ($firstNumberIn instanceof PhoneNumber && $secondNumberIn instanceof PhoneNumber) {
  3284. // We only care about the fields that uniquely define a number, so we copy these across
  3285. // explicitly.
  3286. $firstNumber = self::copyCoreFieldsOnly($firstNumberIn);
  3287. $secondNumber = self::copyCoreFieldsOnly($secondNumberIn);
  3288. // Early exit if both had extensions and these are different.
  3289. if ($firstNumber->hasExtension() && $secondNumber->hasExtension() &&
  3290. $firstNumber->getExtension() != $secondNumber->getExtension()
  3291. ) {
  3292. return MatchType::NO_MATCH;
  3293. }
  3294. $firstNumberCountryCode = $firstNumber->getCountryCode();
  3295. $secondNumberCountryCode = $secondNumber->getCountryCode();
  3296. // Both had country_code specified.
  3297. if ($firstNumberCountryCode != 0 && $secondNumberCountryCode != 0) {
  3298. if ($firstNumber->equals($secondNumber)) {
  3299. return MatchType::EXACT_MATCH;
  3300. }
  3301. if ($firstNumberCountryCode == $secondNumberCountryCode &&
  3302. $this->isNationalNumberSuffixOfTheOther($firstNumber, $secondNumber)) {
  3303. // A SHORT_NSN_MATCH occurs if there is a difference because of the presence or absence of
  3304. // an 'Italian leading zero', the presence or absence of an extension, or one NSN being a
  3305. // shorter variant of the other.
  3306. return MatchType::SHORT_NSN_MATCH;
  3307. }
  3308. // This is not a match.
  3309. return MatchType::NO_MATCH;
  3310. }
  3311. // Checks cases where one or both country_code fields were not specified. To make equality
  3312. // checks easier, we first set the country_code fields to be equal.
  3313. $firstNumber->setCountryCode($secondNumberCountryCode);
  3314. // If all else was the same, then this is an NSN_MATCH.
  3315. if ($firstNumber->equals($secondNumber)) {
  3316. return MatchType::NSN_MATCH;
  3317. }
  3318. if ($this->isNationalNumberSuffixOfTheOther($firstNumber, $secondNumber)) {
  3319. return MatchType::SHORT_NSN_MATCH;
  3320. }
  3321. return MatchType::NO_MATCH;
  3322. }
  3323. return MatchType::NOT_A_NUMBER;
  3324. }
  3325. /**
  3326. * Returns true when one national number is the suffix of the other or both are the same.
  3327. * @param PhoneNumber $firstNumber
  3328. * @param PhoneNumber $secondNumber
  3329. * @return bool
  3330. */
  3331. protected function isNationalNumberSuffixOfTheOther(PhoneNumber $firstNumber, PhoneNumber $secondNumber)
  3332. {
  3333. $firstNumberNationalNumber = trim((string)$firstNumber->getNationalNumber());
  3334. $secondNumberNationalNumber = trim((string)$secondNumber->getNationalNumber());
  3335. return $this->stringEndsWithString($firstNumberNationalNumber, $secondNumberNationalNumber) ||
  3336. $this->stringEndsWithString($secondNumberNationalNumber, $firstNumberNationalNumber);
  3337. }
  3338. /**
  3339. * Returns true if a string ends with a given substring, false otherwise.
  3340. *
  3341. * @param string $hayStack
  3342. * @param string $needle
  3343. * @return bool
  3344. */
  3345. protected function stringEndsWithString($hayStack, $needle)
  3346. {
  3347. $revNeedle = strrev($needle);
  3348. $revHayStack = strrev($hayStack);
  3349. return strpos($revHayStack, $revNeedle) === 0;
  3350. }
  3351. /**
  3352. * Returns true if the supplied region supports mobile number portability. Returns false for
  3353. * invalid, unknown or regions that don't support mobile number portability.
  3354. *
  3355. * @param string $regionCode the region for which we want to know whether it supports mobile number
  3356. * portability or not.
  3357. * @return bool
  3358. */
  3359. public function isMobileNumberPortableRegion($regionCode)
  3360. {
  3361. $metadata = $this->getMetadataForRegion($regionCode);
  3362. if ($metadata === null) {
  3363. return false;
  3364. }
  3365. return $metadata->isMobileNumberPortableRegion();
  3366. }
  3367. /**
  3368. * Check whether a phone number is a possible number given a number in the form of a string, and
  3369. * the region where the number could be dialed from. It provides a more lenient check than
  3370. * {@link #isValidNumber}. See {@link #isPossibleNumber(PhoneNumber)} for details.
  3371. *
  3372. * Convenience wrapper around {@link #isPossibleNumberWithReason}. Instead of returning the reason
  3373. * for failure, this method returns a boolean value.
  3374. * For failure, this method returns true if the number is either a possible fully-qualified number
  3375. * (containing the area code and country code), or if the number could be a possible local number
  3376. * (with a country code, but missing an area code). Local numbers are considered possible if they
  3377. * could be possibly dialled in this format: if the area code is needed for a call to connect, the
  3378. * number is not considered possible without it.
  3379. *
  3380. * Note: There are two ways to call this method.
  3381. *
  3382. * isPossibleNumber(PhoneNumber $numberObject)
  3383. * isPossibleNumber(string '+441174960126', string 'GB')
  3384. *
  3385. * @param PhoneNumber|string $number the number that needs to be checked, in the form of a string
  3386. * @param string|null $regionDialingFrom the region that we are expecting the number to be dialed from.
  3387. * Note this is different from the region where the number belongs. For example, the number
  3388. * +1 650 253 0000 is a number that belongs to US. When written in this form, it can be
  3389. * dialed from any region. When it is written as 00 1 650 253 0000, it can be dialed from any
  3390. * region which uses an international dialling prefix of 00. When it is written as
  3391. * 650 253 0000, it can only be dialed from within the US, and when written as 253 0000, it
  3392. * can only be dialed from within a smaller area in the US (Mountain View, CA, to be more
  3393. * specific).
  3394. * @return boolean true if the number is possible
  3395. */
  3396. public function isPossibleNumber($number, $regionDialingFrom = null)
  3397. {
  3398. if (is_string($number)) {
  3399. try {
  3400. return $this->isPossibleNumber($this->parse($number, $regionDialingFrom));
  3401. } catch (NumberParseException $e) {
  3402. return false;
  3403. }
  3404. } else {
  3405. $result = $this->isPossibleNumberWithReason($number);
  3406. return $result === ValidationResult::IS_POSSIBLE
  3407. || $result === ValidationResult::IS_POSSIBLE_LOCAL_ONLY;
  3408. }
  3409. }
  3410. /**
  3411. * Check whether a phone number is a possible number. It provides a more lenient check than
  3412. * {@link #isValidNumber} in the following sense:
  3413. * <ol>
  3414. * <li> It only checks the length of phone numbers. In particular, it doesn't check starting
  3415. * digits of the number.
  3416. * <li> It doesn't attempt to figure out the type of the number, but uses general rules which
  3417. * applies to all types of phone numbers in a region. Therefore, it is much faster than
  3418. * isValidNumber.
  3419. * <li> For some numbers (particularly fixed-line), many regions have the concept of area code,
  3420. * which together with subscriber number constitute the national significant number. It is
  3421. * sometimes okay to dial only the subscriber number when dialing in the same area. This
  3422. * function will return IS_POSSIBLE_LOCAL_ONLY if the subscriber-number-only version is
  3423. * passed in. On the other hand, because isValidNumber validates using information on both
  3424. * starting digits (for fixed line numbers, that would most likely be area codes) and
  3425. * length (obviously includes the length of area codes for fixed line numbers), it will
  3426. * return false for the subscriber-number-only version.
  3427. * </ol>
  3428. * @param PhoneNumber $number the number that needs to be checked
  3429. * @return int a ValidationResult object which indicates whether the number is possible
  3430. */
  3431. public function isPossibleNumberWithReason(PhoneNumber $number)
  3432. {
  3433. return $this->isPossibleNumberForTypeWithReason($number, PhoneNumberType::UNKNOWN);
  3434. }
  3435. /**
  3436. * Check whether a phone number is a possible number of a particular type. For types that don't
  3437. * exist in a particular region, this will return a result that isn't so useful; it is recommended
  3438. * that you use {@link #getSupportedTypesForRegion} or {@link #getSupportedTypesForNonGeoEntity}
  3439. * respectively before calling this method to determine whether you should call it for this number
  3440. * at all.
  3441. *
  3442. * This provides a more lenient check than {@link #isValidNumber} in the following sense:
  3443. *
  3444. * <ol>
  3445. * <li> It only checks the length of phone numbers. In particular, it doesn't check starting
  3446. * digits of the number.
  3447. * <li> For some numbers (particularly fixed-line), many regions have the concept of area code,
  3448. * which together with subscriber number constitute the national significant number. It is
  3449. * sometimes okay to dial only the subscriber number when dialing in the same area. This
  3450. * function will return IS_POSSIBLE_LOCAL_ONLY if the subscriber-number-only version is
  3451. * passed in. On the other hand, because isValidNumber validates using information on both
  3452. * starting digits (for fixed line numbers, that would most likely be area codes) and
  3453. * length (obviously includes the length of area codes for fixed line numbers), it will
  3454. * return false for the subscriber-number-only version.
  3455. * </ol>
  3456. *
  3457. * @param PhoneNumber $number the number that needs to be checked
  3458. * @param int $type the PhoneNumberType we are interested in
  3459. * @return int a ValidationResult object which indicates whether the number is possible
  3460. */
  3461. public function isPossibleNumberForTypeWithReason(PhoneNumber $number, $type)
  3462. {
  3463. $nationalNumber = $this->getNationalSignificantNumber($number);
  3464. $countryCode = $number->getCountryCode();
  3465. // Note: For regions that share a country calling code, like NANPA numbers, we just use the
  3466. // rules from the default region (US in this case) since the getRegionCodeForNumber will not
  3467. // work if the number is possible but not valid. There is in fact one country calling code (290)
  3468. // where the possible number pattern differs between various regions (Saint Helena and Tristan
  3469. // da Cuñha), but this is handled by putting all possible lengths for any country with this
  3470. // country calling code in the metadata for the default region in this case.
  3471. if (!$this->hasValidCountryCallingCode($countryCode)) {
  3472. return ValidationResult::INVALID_COUNTRY_CODE;
  3473. }
  3474. $regionCode = $this->getRegionCodeForCountryCode($countryCode);
  3475. // Metadata cannot be null because the country calling code is valid.
  3476. $metadata = $this->getMetadataForRegionOrCallingCode($countryCode, $regionCode);
  3477. return $this->testNumberLength($nationalNumber, $metadata, $type);
  3478. }
  3479. /**
  3480. * Attempts to extract a valid number from a phone number that is too long to be valid, and resets
  3481. * the PhoneNumber object passed in to that valid version. If no valid number could be extracted,
  3482. * the PhoneNumber object passed in will not be modified.
  3483. *
  3484. * @param PhoneNumber $number a PhoneNumber object which contains a number that is too long to be valid.
  3485. * @return boolean true if a valid phone number can be successfully extracted.
  3486. */
  3487. public function truncateTooLongNumber(PhoneNumber $number)
  3488. {
  3489. if ($this->isValidNumber($number)) {
  3490. return true;
  3491. }
  3492. $numberCopy = new PhoneNumber();
  3493. $numberCopy->mergeFrom($number);
  3494. $nationalNumber = $number->getNationalNumber();
  3495. do {
  3496. $nationalNumber = floor($nationalNumber / 10);
  3497. $numberCopy->setNationalNumber($nationalNumber);
  3498. if ($this->isPossibleNumberWithReason($numberCopy) == ValidationResult::TOO_SHORT || $nationalNumber == 0) {
  3499. return false;
  3500. }
  3501. } while (!$this->isValidNumber($numberCopy));
  3502. $number->setNationalNumber($nationalNumber);
  3503. return true;
  3504. }
  3505. }