Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
5.45% covered (danger)
5.45%
14 / 257
50.00% covered (danger)
50.00%
1 / 2
CRAP
0.00% covered (danger)
0.00%
0 / 1
Country_Code_Utils
5.45% covered (danger)
5.45%
14 / 257
50.00% covered (danger)
50.00%
1 / 2
62.10
0.00% covered (danger)
0.00%
0 / 1
 country_code_to_emoji_flag
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
6
 get_phone_prefix_to_country_map
1.22% covered (danger)
1.22%
3 / 246
0.00% covered (danger)
0.00%
0 / 1
5.86
1<?php
2/**
3 * Country_Code_Utils trait.
4 *
5 * @package automattic/jetpack-forms
6 */
7
8namespace Automattic\Jetpack\Forms\ContactForm;
9
10/**
11 * Trait for country code utilities including emoji flags and phone prefix mapping.
12 *
13 * This trait provides reusable methods for:
14 * - Converting two-letter ISO country codes (e.g., 'US', 'GB', 'DE') into emoji flags
15 * - Mapping international phone prefixes to country codes
16 */
17trait Country_Code_Utils {
18
19    /**
20     * Convert a country code to an emoji flag.
21     *
22     * @param string $country_code The two-letter country code (e.g., 'US', 'GB', 'DE').
23     *
24     * @return string The emoji flag for the country code, or empty string if invalid.
25     */
26    private static function country_code_to_emoji_flag( $country_code ) {
27        if ( empty( $country_code ) || strlen( $country_code ) !== 2 ) {
28            return '';
29        }
30
31        $country_code = strtoupper( $country_code );
32
33        // Convert each letter to a regional indicator symbol.
34        // Regional indicator symbols start at Unicode code point 127462 (🇦)
35        // and correspond to A-Z (ASCII 65-90).
36        $flag = '';
37        for ( $i = 0; $i < 2; $i++ ) {
38            $char = $country_code[ $i ];
39
40            // Check if the character is a valid uppercase letter (A-Z).
41            if ( ord( $char ) < 65 || ord( $char ) > 90 ) {
42                return '';
43            }
44
45            $code_point = 127462 + ( ord( $char ) - 65 );
46
47            // Convert code point to UTF-8 encoded character.
48            $flag .= mb_chr( $code_point, 'UTF-8' );
49        }
50
51        return $flag;
52    }
53
54    /**
55     * Get the mapping of international phone prefixes to ISO country codes.
56     *
57     * The array is sorted by prefix length (longest first) to ensure
58     * more specific prefixes are matched before general ones
59     * (e.g., +1264 Anguilla before +1 USA).
60     *
61     * @return array<string, string> Phone prefix => ISO country code mapping.
62     */
63    private static function get_phone_prefix_to_country_map() {
64        static $sorted_map = null;
65
66        if ( $sorted_map !== null ) {
67            return $sorted_map;
68        }
69
70        /*
71             * This list is synced with the JavaScript country list at:
72             * projects/packages/forms/src/blocks/field-telephone/country-list.js
73             *
74             * Some territories from the JS list are intentionally omitted here because they
75             * share phone prefixes with other countries (e.g., Guernsey, Jersey, and Isle of Man
76             * all share +44 with the UK). Since we can only map one country per prefix for
77             * flag display, we use the primary country for shared prefixes.
78             */
79            $prefix_to_country = array(
80                // 4-digit prefixes (Caribbean and other NANP territories).
81                '+1264' => 'AI', // Anguilla
82                '+1268' => 'AG', // Antigua and Barbuda
83                '+1242' => 'BS', // Bahamas
84                '+1246' => 'BB', // Barbados
85                '+1441' => 'BM', // Bermuda
86                '+1284' => 'VG', // British Virgin Islands
87                '+1345' => 'KY', // Cayman Islands
88                '+1767' => 'DM', // Dominica
89                '+1809' => 'DO', // Dominican Republic
90                '+1829' => 'DO', // Dominican Republic
91                '+1849' => 'DO', // Dominican Republic
92                '+1473' => 'GD', // Grenada
93                '+1671' => 'GU', // Guam
94                '+1876' => 'JM', // Jamaica
95                '+1664' => 'MS', // Montserrat
96                '+1670' => 'MP', // Northern Mariana Islands
97                '+1787' => 'PR', // Puerto Rico
98                '+1939' => 'PR', // Puerto Rico
99                '+1869' => 'KN', // Saint Kitts and Nevis
100                '+1758' => 'LC', // Saint Lucia
101                '+1784' => 'VC', // Saint Vincent and the Grenadines
102                '+1721' => 'SX', // Sint Maarten
103                '+1868' => 'TT', // Trinidad and Tobago
104                '+1649' => 'TC', // Turks and Caicos Islands
105                '+1340' => 'VI', // U.S. Virgin Islands
106                '+1684' => 'AS', // American Samoa
107
108            // 3-digit prefixes.
109                '+355'  => 'AL', // Albania
110                '+213'  => 'DZ', // Algeria
111                '+376'  => 'AD', // Andorra
112                '+244'  => 'AO', // Angola
113                '+246'  => 'IO', // British Indian Ocean Territory
114                '+374'  => 'AM', // Armenia
115                '+297'  => 'AW', // Aruba
116                '+994'  => 'AZ', // Azerbaijan
117                '+973'  => 'BH', // Bahrain
118                '+880'  => 'BD', // Bangladesh
119                '+375'  => 'BY', // Belarus
120                '+501'  => 'BZ', // Belize
121                '+229'  => 'BJ', // Benin
122                '+975'  => 'BT', // Bhutan
123                '+591'  => 'BO', // Bolivia
124                '+387'  => 'BA', // Bosnia and Herzegovina
125                '+267'  => 'BW', // Botswana
126                '+269'  => 'KM', // Comoros
127                '+673'  => 'BN', // Brunei
128                '+359'  => 'BG', // Bulgaria
129                '+226'  => 'BF', // Burkina Faso
130                '+257'  => 'BI', // Burundi
131                '+855'  => 'KH', // Cambodia
132                '+237'  => 'CM', // Cameroon
133                '+238'  => 'CV', // Cape Verde
134                '+236'  => 'CF', // Central African Republic
135                '+235'  => 'TD', // Chad
136                '+682'  => 'CK', // Cook Islands
137                '+506'  => 'CR', // Costa Rica
138                '+385'  => 'HR', // Croatia
139                '+357'  => 'CY', // Cyprus
140                '+420'  => 'CZ', // Czech Republic
141                '+243'  => 'CD', // Democratic Republic of the Congo
142                '+253'  => 'DJ', // Djibouti
143                '+593'  => 'EC', // Ecuador
144                '+503'  => 'SV', // El Salvador
145                '+240'  => 'GQ', // Equatorial Guinea
146                '+291'  => 'ER', // Eritrea
147                '+298'  => 'FO', // Faroe Islands
148                '+372'  => 'EE', // Estonia
149                '+268'  => 'SZ', // Eswatini
150                '+251'  => 'ET', // Ethiopia
151                '+679'  => 'FJ', // Fiji
152                '+358'  => 'FI', // Finland
153                '+594'  => 'GF', // French Guiana
154                '+689'  => 'PF', // French Polynesia
155                '+241'  => 'GA', // Gabon
156                '+220'  => 'GM', // Gambia
157                '+995'  => 'GE', // Georgia
158                '+233'  => 'GH', // Ghana
159                '+350'  => 'GI', // Gibraltar
160                '+299'  => 'GL', // Greenland
161                '+590'  => 'GP', // Guadeloupe
162                '+502'  => 'GT', // Guatemala
163                '+224'  => 'GN', // Guinea
164                '+245'  => 'GW', // Guinea-Bissau
165                '+592'  => 'GY', // Guyana
166                '+509'  => 'HT', // Haiti
167                '+504'  => 'HN', // Honduras
168                '+852'  => 'HK', // Hong Kong
169                '+354'  => 'IS', // Iceland
170                '+964'  => 'IQ', // Iraq
171                '+353'  => 'IE', // Ireland
172                '+972'  => 'IL', // Israel
173                '+225'  => 'CI', // Ivory Coast
174                '+962'  => 'JO', // Jordan
175                '+254'  => 'KE', // Kenya
176                '+686'  => 'KI', // Kiribati
177                '+383'  => 'XK', // Kosovo
178                '+965'  => 'KW', // Kuwait
179                '+996'  => 'KG', // Kyrgyzstan
180                '+856'  => 'LA', // Laos
181                '+371'  => 'LV', // Latvia
182                '+961'  => 'LB', // Lebanon
183                '+266'  => 'LS', // Lesotho
184                '+231'  => 'LR', // Liberia
185                '+218'  => 'LY', // Libya
186                '+423'  => 'LI', // Liechtenstein
187                '+370'  => 'LT', // Lithuania
188                '+352'  => 'LU', // Luxembourg
189                '+853'  => 'MO', // Macau
190                '+261'  => 'MG', // Madagascar
191                '+265'  => 'MW', // Malawi
192                '+960'  => 'MV', // Maldives
193                '+223'  => 'ML', // Mali
194                '+356'  => 'MT', // Malta
195                '+692'  => 'MH', // Marshall Islands
196                '+596'  => 'MQ', // Martinique
197                '+222'  => 'MR', // Mauritania
198                '+230'  => 'MU', // Mauritius
199                '+691'  => 'FM', // Micronesia
200                '+373'  => 'MD', // Moldova
201                '+377'  => 'MC', // Monaco
202                '+976'  => 'MN', // Mongolia
203                '+382'  => 'ME', // Montenegro
204                '+212'  => 'MA', // Morocco
205                '+258'  => 'MZ', // Mozambique
206                '+264'  => 'NA', // Namibia
207                '+674'  => 'NR', // Nauru
208                '+977'  => 'NP', // Nepal
209                '+687'  => 'NC', // New Caledonia
210                '+505'  => 'NI', // Nicaragua
211                '+227'  => 'NE', // Niger
212                '+234'  => 'NG', // Nigeria
213                '+683'  => 'NU', // Niue
214                '+672'  => 'NF', // Norfolk Island
215                '+850'  => 'KP', // North Korea
216                '+389'  => 'MK', // North Macedonia
217                '+968'  => 'OM', // Oman
218                '+680'  => 'PW', // Palau
219                '+970'  => 'PS', // Palestine
220                '+500'  => 'FK', // Falkland Islands
221                '+507'  => 'PA', // Panama
222                '+675'  => 'PG', // Papua New Guinea
223                '+595'  => 'PY', // Paraguay
224                '+974'  => 'QA', // Qatar
225                '+242'  => 'CG', // Republic of the Congo
226                '+262'  => 'RE', // Reunion (also Mayotte)
227                '+250'  => 'RW', // Rwanda
228                '+290'  => 'SH', // Saint Helena
229                '+508'  => 'PM', // Saint Pierre and Miquelon
230                '+685'  => 'WS', // Samoa
231                '+378'  => 'SM', // San Marino
232                '+239'  => 'ST', // Sao Tome and Principe
233                '+966'  => 'SA', // Saudi Arabia
234                '+221'  => 'SN', // Senegal
235                '+381'  => 'RS', // Serbia
236                '+248'  => 'SC', // Seychelles
237                '+232'  => 'SL', // Sierra Leone
238                '+421'  => 'SK', // Slovakia
239                '+386'  => 'SI', // Slovenia
240                '+677'  => 'SB', // Solomon Islands
241                '+252'  => 'SO', // Somalia
242                '+211'  => 'SS', // South Sudan
243                '+249'  => 'SD', // Sudan
244                '+597'  => 'SR', // Suriname
245                '+963'  => 'SY', // Syria
246                '+886'  => 'TW', // Taiwan
247                '+992'  => 'TJ', // Tajikistan
248                '+255'  => 'TZ', // Tanzania
249                '+228'  => 'TG', // Togo
250                '+670'  => 'TL', // Timor-Leste
251                '+690'  => 'TK', // Tokelau
252                '+676'  => 'TO', // Tonga
253                '+216'  => 'TN', // Tunisia
254                '+993'  => 'TM', // Turkmenistan
255                '+688'  => 'TV', // Tuvalu
256                '+256'  => 'UG', // Uganda
257                '+380'  => 'UA', // Ukraine
258                '+971'  => 'AE', // United Arab Emirates
259                '+598'  => 'UY', // Uruguay
260                '+998'  => 'UZ', // Uzbekistan
261                '+678'  => 'VU', // Vanuatu
262                '+379'  => 'VA', // Vatican City
263                '+681'  => 'WF', // Wallis and Futuna
264                '+967'  => 'YE', // Yemen
265                '+260'  => 'ZM', // Zambia
266                '+263'  => 'ZW', // Zimbabwe
267                '+351'  => 'PT', // Portugal
268                '+872'  => 'PN', // Pitcairn Islands
269
270            // 2-digit prefixes.
271                '+93'   => 'AF', // Afghanistan
272                '+54'   => 'AR', // Argentina
273                '+61'   => 'AU', // Australia
274                '+43'   => 'AT', // Austria
275                '+32'   => 'BE', // Belgium
276                '+55'   => 'BR', // Brazil
277                '+56'   => 'CL', // Chile
278                '+86'   => 'CN', // China
279                '+57'   => 'CO', // Colombia
280                '+53'   => 'CU', // Cuba
281                '+45'   => 'DK', // Denmark
282                '+20'   => 'EG', // Egypt
283                '+33'   => 'FR', // France
284                '+49'   => 'DE', // Germany
285                '+30'   => 'GR', // Greece
286                '+36'   => 'HU', // Hungary
287                '+91'   => 'IN', // India
288                '+62'   => 'ID', // Indonesia
289                '+98'   => 'IR', // Iran
290                '+39'   => 'IT', // Italy
291                '+81'   => 'JP', // Japan
292                '+77'   => 'KZ', // Kazakhstan
293                '+82'   => 'KR', // South Korea
294                '+60'   => 'MY', // Malaysia
295                '+52'   => 'MX', // Mexico
296                '+95'   => 'MM', // Myanmar
297                '+31'   => 'NL', // Netherlands
298                '+64'   => 'NZ', // New Zealand
299                '+47'   => 'NO', // Norway
300                '+92'   => 'PK', // Pakistan
301                '+51'   => 'PE', // Peru
302                '+63'   => 'PH', // Philippines
303                '+48'   => 'PL', // Poland
304                '+40'   => 'RO', // Romania
305                '+7'    => 'RU', // Russia (also Kazakhstan +77)
306                '+65'   => 'SG', // Singapore
307                '+27'   => 'ZA', // South Africa
308                '+34'   => 'ES', // Spain
309                '+94'   => 'LK', // Sri Lanka
310                '+46'   => 'SE', // Sweden
311                '+41'   => 'CH', // Switzerland
312                '+66'   => 'TH', // Thailand
313                '+90'   => 'TR', // Turkey
314                '+44'   => 'GB', // United Kingdom
315                '+1'    => 'US', // United States/Canada (NANP)
316                '+58'   => 'VE', // Venezuela
317                '+84'   => 'VN', // Vietnam
318            );
319
320            // Sort by prefix length (longest first) to match most specific prefix.
321            uksort(
322                $prefix_to_country,
323                static function ( $a, $b ) {
324                    return strlen( $b ) - strlen( $a );
325                }
326            );
327
328        $sorted_map = $prefix_to_country;
329
330        return $sorted_map;
331    }
332}