PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.16
Yatra – Travel Booking & Tour Operator Software v3.0.16
3.0.16 3.0.15 3.0.14 3.0.14.1 3.0.14.2 3.0.12 3.0.13 3.0.11 3.0.10 3.0.9 3.0.8 3.0.7 3.0.6 3.0.5 3.0.5.1 3.0.4 3.0.3 3.0.2.9 3.0.2.7 3.0.2.8 3.0.2.6 trunk 1.0.0 2.0.0 2.0.1 All 84 releases
← All changes | app/Helpers/FormatHelper.php +474 -83 3.0.5.1 → 3.0.16 View file →
@@ -22,90 +22,107 @@
22 22
23 23 /**
24 24 * Country codes to names mapping
25 25 */
26 - private const COUNTRY_NAMES = [
27 - 'AF' => 'Afghanistan', 'AL' => 'Albania', 'DZ' => 'Algeria', 'AD' => 'Andorra',
28 - 'AO' => 'Angola', 'AG' => 'Antigua and Barbuda', 'AR' => 'Argentina', 'AM' => 'Armenia',
29 - 'AU' => 'Australia', 'AT' => 'Austria', 'AZ' => 'Azerbaijan', 'BS' => 'Bahamas',
30 - 'BH' => 'Bahrain', 'BD' => 'Bangladesh', 'BB' => 'Barbados', 'BY' => 'Belarus',
31 - 'BE' => 'Belgium', 'BZ' => 'Belize', 'BJ' => 'Benin', 'BT' => 'Bhutan',
32 - 'BO' => 'Bolivia', 'BA' => 'Bosnia and Herzegovina', 'BW' => 'Botswana', 'BR' => 'Brazil',
33 - 'BN' => 'Brunei', 'BG' => 'Bulgaria', 'BF' => 'Burkina Faso', 'BI' => 'Burundi',
34 - 'KH' => 'Cambodia', 'CM' => 'Cameroon', 'CA' => 'Canada', 'CV' => 'Cape Verde',
35 - 'CF' => 'Central African Republic', 'TD' => 'Chad', 'CL' => 'Chile', 'CN' => 'China',
36 - 'CO' => 'Colombia', 'KM' => 'Comoros', 'CG' => 'Congo', 'CD' => 'DR Congo',
37 - 'CR' => 'Costa Rica', 'CI' => 'Ivory Coast', 'HR' => 'Croatia', 'CU' => 'Cuba',
38 - 'CY' => 'Cyprus', 'CZ' => 'Czech Republic', 'DK' => 'Denmark', 'DJ' => 'Djibouti',
39 - 'DM' => 'Dominica', 'DO' => 'Dominican Republic', 'EC' => 'Ecuador', 'EG' => 'Egypt',
40 - 'SV' => 'El Salvador', 'GQ' => 'Equatorial Guinea', 'ER' => 'Eritrea', 'EE' => 'Estonia',
41 - 'SZ' => 'Eswatini', 'ET' => 'Ethiopia', 'FJ' => 'Fiji', 'FI' => 'Finland',
42 - 'FR' => 'France', 'GA' => 'Gabon', 'GM' => 'Gambia', 'GE' => 'Georgia',
43 - 'DE' => 'Germany', 'GH' => 'Ghana', 'GR' => 'Greece', 'GD' => 'Grenada',
44 - 'GT' => 'Guatemala', 'GN' => 'Guinea', 'GW' => 'Guinea-Bissau', 'GY' => 'Guyana',
45 - 'HT' => 'Haiti', 'HN' => 'Honduras', 'HU' => 'Hungary', 'IS' => 'Iceland',
46 - 'IN' => 'India', 'ID' => 'Indonesia', 'IR' => 'Iran', 'IQ' => 'Iraq',
47 - 'IE' => 'Ireland', 'IL' => 'Israel', 'IT' => 'Italy', 'JM' => 'Jamaica',
48 - 'JP' => 'Japan', 'JO' => 'Jordan', 'KZ' => 'Kazakhstan', 'KE' => 'Kenya',
49 - 'KI' => 'Kiribati', 'KP' => 'North Korea', 'KR' => 'South Korea', 'KW' => 'Kuwait',
50 - 'KG' => 'Kyrgyzstan', 'LA' => 'Laos', 'LV' => 'Latvia', 'LB' => 'Lebanon',
51 - 'LS' => 'Lesotho', 'LR' => 'Liberia', 'LY' => 'Libya', 'LI' => 'Liechtenstein',
52 - 'LT' => 'Lithuania', 'LU' => 'Luxembourg', 'MG' => 'Madagascar', 'MW' => 'Malawi',
53 - 'MY' => 'Malaysia', 'MV' => 'Maldives', 'ML' => 'Mali', 'MT' => 'Malta',
54 - 'MH' => 'Marshall Islands', 'MR' => 'Mauritania', 'MU' => 'Mauritius', 'MX' => 'Mexico',
55 - 'FM' => 'Micronesia', 'MD' => 'Moldova', 'MC' => 'Monaco', 'MN' => 'Mongolia',
56 - 'ME' => 'Montenegro', 'MA' => 'Morocco', 'MZ' => 'Mozambique', 'MM' => 'Myanmar',
57 - 'NA' => 'Namibia', 'NR' => 'Nauru', 'NP' => 'Nepal', 'NL' => 'Netherlands',
58 - 'NZ' => 'New Zealand', 'NI' => 'Nicaragua', 'NE' => 'Niger', 'NG' => 'Nigeria',
59 - 'MK' => 'North Macedonia', 'NO' => 'Norway', 'OM' => 'Oman', 'PK' => 'Pakistan',
60 - 'PW' => 'Palau', 'PS' => 'Palestine', 'PA' => 'Panama', 'PG' => 'Papua New Guinea',
61 - 'PY' => 'Paraguay', 'PE' => 'Peru', 'PH' => 'Philippines', 'PL' => 'Poland',
62 - 'PT' => 'Portugal', 'QA' => 'Qatar', 'RO' => 'Romania', 'RU' => 'Russia',
63 - 'RW' => 'Rwanda', 'KN' => 'Saint Kitts and Nevis', 'LC' => 'Saint Lucia',
64 - 'VC' => 'Saint Vincent and the Grenadines', 'WS' => 'Samoa', 'SM' => 'San Marino',
65 - 'ST' => 'Sao Tome and Principe', 'SA' => 'Saudi Arabia', 'SN' => 'Senegal',
66 - 'RS' => 'Serbia', 'SC' => 'Seychelles', 'SL' => 'Sierra Leone', 'SG' => 'Singapore',
67 - 'SK' => 'Slovakia', 'SI' => 'Slovenia', 'SB' => 'Solomon Islands', 'SO' => 'Somalia',
68 - 'ZA' => 'South Africa', 'SS' => 'South Sudan', 'ES' => 'Spain', 'LK' => 'Sri Lanka',
69 - 'SD' => 'Sudan', 'SR' => 'Suriname', 'SE' => 'Sweden', 'CH' => 'Switzerland',
70 - 'SY' => 'Syria', 'TW' => 'Taiwan', 'TJ' => 'Tajikistan', 'TZ' => 'Tanzania',
71 - 'TH' => 'Thailand', 'TL' => 'Timor-Leste', 'TG' => 'Togo', 'TO' => 'Tonga',
72 - 'TT' => 'Trinidad and Tobago', 'TN' => 'Tunisia', 'TR' => 'Turkey', 'TM' => 'Turkmenistan',
73 - 'TV' => 'Tuvalu', 'UG' => 'Uganda', 'UA' => 'Ukraine', 'AE' => 'United Arab Emirates',
74 - 'GB' => 'United Kingdom', 'US' => 'United States', 'UY' => 'Uruguay', 'UZ' => 'Uzbekistan',
75 - 'VU' => 'Vanuatu', 'VA' => 'Vatican City', 'VE' => 'Venezuela', 'VN' => 'Vietnam',
76 - 'YE' => 'Yemen', 'ZM' => 'Zambia', 'ZW' => 'Zimbabwe',
26 + /**
27 + * Country code => translated display name.
28 + *
29 + * Names are wrapped in __() at the definition so every consumer (booking
30 + * form, admin, confirmation) shows localized names, and so all of them are
31 + * extractable into the translation catalog. Cached per request.
32 + */
33 + private static ?array $countryNamesCache = null;
34 +
35 + private static function countryNames(): array
36 + {
37 + if (self::$countryNamesCache !== null) {
38 + return self::$countryNamesCache;
39 + }
40 +
41 + return self::$countryNamesCache = [
42 +
43 + 'AF' => __('Afghanistan', 'yatra'), 'AL' => __('Albania', 'yatra'), 'DZ' => __('Algeria', 'yatra'), 'AD' => __('Andorra', 'yatra'),
44 + 'AO' => __('Angola', 'yatra'), 'AG' => __('Antigua and Barbuda', 'yatra'), 'AR' => __('Argentina', 'yatra'), 'AM' => __('Armenia', 'yatra'),
45 + 'AU' => __('Australia', 'yatra'), 'AT' => __('Austria', 'yatra'), 'AZ' => __('Azerbaijan', 'yatra'), 'BS' => __('Bahamas', 'yatra'),
46 + 'BH' => __('Bahrain', 'yatra'), 'BD' => __('Bangladesh', 'yatra'), 'BB' => __('Barbados', 'yatra'), 'BY' => __('Belarus', 'yatra'),
47 + 'BE' => __('Belgium', 'yatra'), 'BZ' => __('Belize', 'yatra'), 'BJ' => __('Benin', 'yatra'), 'BT' => __('Bhutan', 'yatra'),
48 + 'BO' => __('Bolivia', 'yatra'), 'BA' => __('Bosnia and Herzegovina', 'yatra'), 'BW' => __('Botswana', 'yatra'), 'BR' => __('Brazil', 'yatra'),
49 + 'BN' => __('Brunei', 'yatra'), 'BG' => __('Bulgaria', 'yatra'), 'BF' => __('Burkina Faso', 'yatra'), 'BI' => __('Burundi', 'yatra'),
50 + 'KH' => __('Cambodia', 'yatra'), 'CM' => __('Cameroon', 'yatra'), 'CA' => __('Canada', 'yatra'), 'CV' => __('Cape Verde', 'yatra'),
51 + 'CF' => __('Central African Republic', 'yatra'), 'TD' => __('Chad', 'yatra'), 'CL' => __('Chile', 'yatra'), 'CN' => __('China', 'yatra'),
52 + 'CO' => __('Colombia', 'yatra'), 'KM' => __('Comoros', 'yatra'), 'CG' => __('Congo', 'yatra'), 'CD' => __('DR Congo', 'yatra'),
53 + 'CR' => __('Costa Rica', 'yatra'), 'CI' => __('Ivory Coast', 'yatra'), 'HR' => __('Croatia', 'yatra'), 'CU' => __('Cuba', 'yatra'),
54 + 'CY' => __('Cyprus', 'yatra'), 'CZ' => __('Czech Republic', 'yatra'), 'DK' => __('Denmark', 'yatra'), 'DJ' => __('Djibouti', 'yatra'),
55 + 'DM' => __('Dominica', 'yatra'), 'DO' => __('Dominican Republic', 'yatra'), 'EC' => __('Ecuador', 'yatra'), 'EG' => __('Egypt', 'yatra'),
56 + 'SV' => __('El Salvador', 'yatra'), 'GQ' => __('Equatorial Guinea', 'yatra'), 'ER' => __('Eritrea', 'yatra'), 'EE' => __('Estonia', 'yatra'),
57 + 'SZ' => __('Eswatini', 'yatra'), 'ET' => __('Ethiopia', 'yatra'), 'FJ' => __('Fiji', 'yatra'), 'FI' => __('Finland', 'yatra'),
58 + 'FR' => __('France', 'yatra'), 'GA' => __('Gabon', 'yatra'), 'GM' => __('Gambia', 'yatra'), 'GE' => __('Georgia', 'yatra'),
59 + 'DE' => __('Germany', 'yatra'), 'GH' => __('Ghana', 'yatra'), 'GR' => __('Greece', 'yatra'), 'GD' => __('Grenada', 'yatra'),
60 + 'GT' => __('Guatemala', 'yatra'), 'GN' => __('Guinea', 'yatra'), 'GW' => __('Guinea-Bissau', 'yatra'), 'GY' => __('Guyana', 'yatra'),
61 + 'HT' => __('Haiti', 'yatra'), 'HN' => __('Honduras', 'yatra'), 'HU' => __('Hungary', 'yatra'), 'IS' => __('Iceland', 'yatra'),
62 + 'IN' => __('India', 'yatra'), 'ID' => __('Indonesia', 'yatra'), 'IR' => __('Iran', 'yatra'), 'IQ' => __('Iraq', 'yatra'),
63 + 'IE' => __('Ireland', 'yatra'), 'IL' => __('Israel', 'yatra'), 'IT' => __('Italy', 'yatra'), 'JM' => __('Jamaica', 'yatra'),
64 + 'JP' => __('Japan', 'yatra'), 'JO' => __('Jordan', 'yatra'), 'KZ' => __('Kazakhstan', 'yatra'), 'KE' => __('Kenya', 'yatra'),
65 + 'KI' => __('Kiribati', 'yatra'), 'KP' => __('North Korea', 'yatra'), 'KR' => __('South Korea', 'yatra'), 'KW' => __('Kuwait', 'yatra'),
66 + 'KG' => __('Kyrgyzstan', 'yatra'), 'LA' => __('Laos', 'yatra'), 'LV' => __('Latvia', 'yatra'), 'LB' => __('Lebanon', 'yatra'),
67 + 'LS' => __('Lesotho', 'yatra'), 'LR' => __('Liberia', 'yatra'), 'LY' => __('Libya', 'yatra'), 'LI' => __('Liechtenstein', 'yatra'),
68 + 'LT' => __('Lithuania', 'yatra'), 'LU' => __('Luxembourg', 'yatra'), 'MG' => __('Madagascar', 'yatra'), 'MW' => __('Malawi', 'yatra'),
69 + 'MY' => __('Malaysia', 'yatra'), 'MV' => __('Maldives', 'yatra'), 'ML' => __('Mali', 'yatra'), 'MT' => __('Malta', 'yatra'),
70 + 'MH' => __('Marshall Islands', 'yatra'), 'MR' => __('Mauritania', 'yatra'), 'MU' => __('Mauritius', 'yatra'), 'MX' => __('Mexico', 'yatra'),
71 + 'FM' => __('Micronesia', 'yatra'), 'MD' => __('Moldova', 'yatra'), 'MC' => __('Monaco', 'yatra'), 'MN' => __('Mongolia', 'yatra'),
72 + 'ME' => __('Montenegro', 'yatra'), 'MA' => __('Morocco', 'yatra'), 'MZ' => __('Mozambique', 'yatra'), 'MM' => __('Myanmar', 'yatra'),
73 + 'NA' => __('Namibia', 'yatra'), 'NR' => __('Nauru', 'yatra'), 'NP' => __('Nepal', 'yatra'), 'NL' => __('Netherlands', 'yatra'),
74 + 'NZ' => __('New Zealand', 'yatra'), 'NI' => __('Nicaragua', 'yatra'), 'NE' => __('Niger', 'yatra'), 'NG' => __('Nigeria', 'yatra'),
75 + 'MK' => __('North Macedonia', 'yatra'), 'NO' => __('Norway', 'yatra'), 'OM' => __('Oman', 'yatra'), 'PK' => __('Pakistan', 'yatra'),
76 + 'PW' => __('Palau', 'yatra'), 'PS' => __('Palestine', 'yatra'), 'PA' => __('Panama', 'yatra'), 'PG' => __('Papua New Guinea', 'yatra'),
77 + 'PY' => __('Paraguay', 'yatra'), 'PE' => __('Peru', 'yatra'), 'PH' => __('Philippines', 'yatra'), 'PL' => __('Poland', 'yatra'),
78 + 'PT' => __('Portugal', 'yatra'), 'QA' => __('Qatar', 'yatra'), 'RO' => __('Romania', 'yatra'), 'RU' => __('Russia', 'yatra'),
79 + 'RW' => __('Rwanda', 'yatra'), 'KN' => __('Saint Kitts and Nevis', 'yatra'), 'LC' => __('Saint Lucia', 'yatra'),
80 + 'VC' => __('Saint Vincent and the Grenadines', 'yatra'), 'WS' => __('Samoa', 'yatra'), 'SM' => __('San Marino', 'yatra'),
81 + 'ST' => __('Sao Tome and Principe', 'yatra'), 'SA' => __('Saudi Arabia', 'yatra'), 'SN' => __('Senegal', 'yatra'),
82 + 'RS' => __('Serbia', 'yatra'), 'SC' => __('Seychelles', 'yatra'), 'SL' => __('Sierra Leone', 'yatra'), 'SG' => __('Singapore', 'yatra'),
83 + 'SK' => __('Slovakia', 'yatra'), 'SI' => __('Slovenia', 'yatra'), 'SB' => __('Solomon Islands', 'yatra'), 'SO' => __('Somalia', 'yatra'),
84 + 'ZA' => __('South Africa', 'yatra'), 'SS' => __('South Sudan', 'yatra'), 'ES' => __('Spain', 'yatra'), 'LK' => __('Sri Lanka', 'yatra'),
85 + 'SD' => __('Sudan', 'yatra'), 'SR' => __('Suriname', 'yatra'), 'SE' => __('Sweden', 'yatra'), 'CH' => __('Switzerland', 'yatra'),
86 + 'SY' => __('Syria', 'yatra'), 'TW' => __('Taiwan', 'yatra'), 'TJ' => __('Tajikistan', 'yatra'), 'TZ' => __('Tanzania', 'yatra'),
87 + 'TH' => __('Thailand', 'yatra'), 'TL' => __('Timor-Leste', 'yatra'), 'TG' => __('Togo', 'yatra'), 'TO' => __('Tonga', 'yatra'),
88 + 'TT' => __('Trinidad and Tobago', 'yatra'), 'TN' => __('Tunisia', 'yatra'), 'TR' => __('Turkey', 'yatra'), 'TM' => __('Turkmenistan', 'yatra'),
89 + 'TV' => __('Tuvalu', 'yatra'), 'UG' => __('Uganda', 'yatra'), 'UA' => __('Ukraine', 'yatra'), 'AE' => __('United Arab Emirates', 'yatra'),
90 + 'GB' => __('United Kingdom', 'yatra'), 'US' => __('United States', 'yatra'), 'UY' => __('Uruguay', 'yatra'), 'UZ' => __('Uzbekistan', 'yatra'),
91 + 'VU' => __('Vanuatu', 'yatra'), 'VA' => __('Vatican City', 'yatra'), 'VE' => __('Venezuela', 'yatra'), 'VN' => __('Vietnam', 'yatra'),
92 + 'YE' => __('Yemen', 'yatra'), 'ZM' => __('Zambia', 'yatra'), 'ZW' => __('Zimbabwe', 'yatra'),
77 93 // ISO-3166-1 territories and special regions added so customers
78 94 // from common tourism markets (Hong Kong, Puerto Rico, Greenland,
79 95 // Gibraltar, Faroe Islands, etc.) can select their location.
80 - 'HK' => 'Hong Kong', 'MO' => 'Macao', 'PR' => 'Puerto Rico',
81 - 'GI' => 'Gibraltar', 'GL' => 'Greenland', 'FO' => 'Faroe Islands',
82 - 'GG' => 'Guernsey', 'IM' => 'Isle of Man', 'JE' => 'Jersey',
83 - 'AX' => 'Aland Islands', 'SJ' => 'Svalbard and Jan Mayen',
84 - 'BM' => 'Bermuda', 'KY' => 'Cayman Islands', 'AI' => 'Anguilla',
85 - 'AW' => 'Aruba', 'CW' => 'Curacao', 'SX' => 'Sint Maarten',
86 - 'BQ' => 'Bonaire, Sint Eustatius and Saba',
87 - 'MS' => 'Montserrat', 'VG' => 'British Virgin Islands',
88 - 'TC' => 'Turks and Caicos Islands',
89 - 'BL' => 'Saint Barthelemy', 'MF' => 'Saint Martin (French)',
90 - 'PM' => 'Saint Pierre and Miquelon',
91 - 'SH' => 'Saint Helena, Ascension and Tristan da Cunha',
92 - 'FK' => 'Falkland Islands',
93 - 'GS' => 'South Georgia and the South Sandwich Islands',
94 - 'PF' => 'French Polynesia', 'NC' => 'New Caledonia',
95 - 'WF' => 'Wallis and Futuna', 'CK' => 'Cook Islands',
96 - 'NU' => 'Niue', 'TK' => 'Tokelau',
97 - 'GP' => 'Guadeloupe', 'MQ' => 'Martinique',
98 - 'RE' => 'Reunion', 'YT' => 'Mayotte',
99 - 'AS' => 'American Samoa', 'GU' => 'Guam',
100 - 'MP' => 'Northern Mariana Islands', 'VI' => 'U.S. Virgin Islands',
101 - 'UM' => 'U.S. Minor Outlying Islands',
102 - 'TF' => 'French Southern Territories',
103 - 'IO' => 'British Indian Ocean Territory', 'BV' => 'Bouvet Island',
104 - 'HM' => 'Heard Island and McDonald Islands',
105 - 'AQ' => 'Antarctica', 'PN' => 'Pitcairn',
106 - 'EH' => 'Western Sahara', 'XK' => 'Kosovo',
107 - ];
96 + 'HK' => __('Hong Kong', 'yatra'), 'MO' => __('Macao', 'yatra'), 'PR' => __('Puerto Rico', 'yatra'),
97 + 'GI' => __('Gibraltar', 'yatra'), 'GL' => __('Greenland', 'yatra'), 'FO' => __('Faroe Islands', 'yatra'),
98 + 'GG' => __('Guernsey', 'yatra'), 'IM' => __('Isle of Man', 'yatra'), 'JE' => __('Jersey', 'yatra'),
99 + 'AX' => __('Aland Islands', 'yatra'), 'SJ' => __('Svalbard and Jan Mayen', 'yatra'),
100 + 'BM' => __('Bermuda', 'yatra'), 'KY' => __('Cayman Islands', 'yatra'), 'AI' => __('Anguilla', 'yatra'),
101 + 'AW' => __('Aruba', 'yatra'), 'CW' => __('Curacao', 'yatra'), 'SX' => __('Sint Maarten', 'yatra'),
102 + 'BQ' => __('Bonaire, Sint Eustatius and Saba', 'yatra'),
103 + 'MS' => __('Montserrat', 'yatra'), 'VG' => __('British Virgin Islands', 'yatra'),
104 + 'TC' => __('Turks and Caicos Islands', 'yatra'),
105 + 'BL' => __('Saint Barthelemy', 'yatra'), 'MF' => __('Saint Martin (French)', 'yatra'),
106 + 'PM' => __('Saint Pierre and Miquelon', 'yatra'),
107 + 'SH' => __('Saint Helena, Ascension and Tristan da Cunha', 'yatra'),
108 + 'FK' => __('Falkland Islands', 'yatra'),
109 + 'GS' => __('South Georgia and the South Sandwich Islands', 'yatra'),
110 + 'PF' => __('French Polynesia', 'yatra'), 'NC' => __('New Caledonia', 'yatra'),
111 + 'WF' => __('Wallis and Futuna', 'yatra'), 'CK' => __('Cook Islands', 'yatra'),
112 + 'NU' => __('Niue', 'yatra'), 'TK' => __('Tokelau', 'yatra'),
113 + 'GP' => __('Guadeloupe', 'yatra'), 'MQ' => __('Martinique', 'yatra'),
114 + 'RE' => __('Reunion', 'yatra'), 'YT' => __('Mayotte', 'yatra'),
115 + 'AS' => __('American Samoa', 'yatra'), 'GU' => __('Guam', 'yatra'),
116 + 'MP' => __('Northern Mariana Islands', 'yatra'), 'VI' => __('U.S. Virgin Islands', 'yatra'),
117 + 'UM' => __('U.S. Minor Outlying Islands', 'yatra'),
118 + 'TF' => __('French Southern Territories', 'yatra'),
119 + 'IO' => __('British Indian Ocean Territory', 'yatra'), 'BV' => __('Bouvet Island', 'yatra'),
120 + 'HM' => __('Heard Island and McDonald Islands', 'yatra'),
121 + 'AQ' => __('Antarctica', 'yatra'), 'PN' => __('Pitcairn', 'yatra'),
122 + 'EH' => __('Western Sahara', 'yatra'), 'XK' => __('Kosovo', 'yatra'),
123 + ];
124 + }
108 125
109 126 /**
110 127 * Format price with currency
111 128 *
@@ -283,12 +300,138 @@
283 300 */
284 301 public static function getCountryName(string $code): string
285 302 {
286 303 $code = strtoupper($code);
287 - return self::COUNTRY_NAMES[$code] ?? $code;
304 + return self::countryNames()[$code] ?? $code;
288 305 }
289 306
290 307 /**
308 + * Build a customer's postal address as display lines, for invoices and any
309 + * other document that has to show where the customer actually is.
310 + *
311 + * Two sources, in order of authority:
312 + * 1. The booking's own contact data — what the customer entered at the time
313 + * of purchase, which is what an invoice should reflect.
314 + * 2. The linked customer record, which carries the structured city / state /
315 + * postal code / country that the booking form does not always collect.
316 + *
317 + * @param object|array|null $booking Booking (or payment joined to one) carrying
318 + * contact_data / contact_country / customer_id.
319 + * @return string[] Non-empty address lines, ready to render one per line.
320 + */
321 + public static function customerAddressLines($booking): array
322 + {
323 + $get = static function ($source, string $key) {
324 + if (is_array($source)) {
325 + return $source[$key] ?? null;
326 + }
327 + if (is_object($source)) {
328 + return $source->$key ?? null;
329 + }
330 + return null;
331 + };
332 +
333 + $parts = ['address' => '', 'city' => '', 'state' => '', 'postal_code' => '', 'country' => ''];
334 +
335 + // 1. What the customer typed when booking.
336 + $contactData = $get($booking, 'contact_data');
337 + if (is_string($contactData) && $contactData !== '') {
338 + $contactData = json_decode($contactData, true);
339 + }
340 + if (is_array($contactData)) {
341 + foreach (array_keys($parts) as $key) {
342 + if (!empty($contactData[$key]) && is_scalar($contactData[$key])) {
343 + $parts[$key] = trim((string) $contactData[$key]);
344 + }
345 + }
346 + // The booking form stores the postcode under either name.
347 + if ($parts['postal_code'] === '' && !empty($contactData['zip'])) {
348 + $parts['postal_code'] = trim((string) $contactData['zip']);
349 + }
350 + }
351 +
352 + if ($parts['country'] === '') {
353 + $parts['country'] = trim((string) ($get($booking, 'contact_country') ?? ''));
354 + }
355 +
356 + // 2. Fill the gaps from the customer record.
357 + $customerId = (int) ($get($booking, 'customer_id') ?? 0);
358 + if ($customerId > 0 && in_array('', $parts, true)) {
359 + $customer = (new \Yatra\Repositories\CustomerRepository())->find($customerId);
360 +
361 + if ($customer) {
362 + foreach (array_keys($parts) as $key) {
363 + if ($parts[$key] === '' && !empty($customer->$key)) {
364 + $parts[$key] = trim((string) $customer->$key);
365 + }
366 + }
367 + }
368 + }
369 +
370 + // "City, State 12345" reads as one line; street and country get their own.
371 + $locality = trim(implode(', ', array_filter([$parts['city'], $parts['state']])));
372 + if ($parts['postal_code'] !== '') {
373 + $locality = trim($locality . ' ' . $parts['postal_code']);
374 + }
375 +
376 + $country = $parts['country'] !== '' ? self::getCountryName($parts['country']) : '';
377 +
378 + return array_values(array_filter([$parts['address'], $locality, $country], static function ($line) {
379 + return $line !== '';
380 + }));
381 + }
382 +
383 + /**
384 + * The operator's own business address, one entry per line.
385 + *
386 + * Settings hold the address in separate fields (street, city, state, postcode,
387 + * country) but documents only ever printed `company_address` — so everything
388 + * after the street silently vanished and the address looked truncated at its
389 + * final line. Composed the same way as {@see customerAddressLines()} so both
390 + * sides of a document read identically.
391 + *
392 + * @return list<string>
393 + */
394 + public static function companyAddressLines(): array
395 + {
396 + $get = static function (string $key): string {
397 + return trim((string) \Yatra\Services\SettingsService::get($key, ''));
398 + };
399 +
400 + $street = $get('company_address');
401 + $city = $get('company_city');
402 + $state = $get('company_state');
403 + $zip = $get('company_zip');
404 + $countryRaw = $get('company_country');
405 +
406 + // "City, State 12345" reads as one line; street and country get their own.
407 + $locality = trim(implode(', ', array_filter([$city, $state])));
408 + if ($zip !== '') {
409 + $locality = trim($locality . ' ' . $zip);
410 + }
411 +
412 + $country = $countryRaw !== '' ? self::getCountryName($countryRaw) : '';
413 +
414 + // The street field may itself carry line breaks (an operator pasting a full
415 + // address), so keep those as separate lines rather than one run-on entry.
416 + $lines = [];
417 + foreach (preg_split('/\R/', $street) ?: [] as $streetLine) {
418 + $streetLine = trim($streetLine);
419 + if ($streetLine !== '') {
420 + $lines[] = $streetLine;
421 + }
422 + }
423 +
424 + foreach ([$locality, $country] as $line) {
425 + if ($line !== '') {
426 + $lines[] = $line;
427 + }
428 + }
429 +
430 + return array_values($lines);
431 + }
432 +
433 + /**
291 434 * Canonical country list — single source of truth used by every
292 435 * country / nationality dropdown in both Free and Pro plugins.
293 436 *
294 437 * Returns the full ISO-3166-1 alpha-2 set (sovereign states +
@@ -306,9 +449,9 @@
306 449 * @return array<string, string> [ISO-3166 code => display name]
307 450 */
308 451 public static function getCountries(): array
309 452 {
310 - $countries = self::COUNTRY_NAMES;
453 + $countries = self::countryNames();
311 454
312 455 // Stable alphabetical sort by display name. Operators expect
313 456 // "Argentina" before "Australia" before "Austria" — code-order
314 457 // (AR/AU/AT) is computer-friendly but not human-friendly.
@@ -323,8 +466,246 @@
323 466 return $countries;
324 467 }
325 468
326 469 /**
470 + * International dialing codes, keyed by ISO-3166-1 alpha-2 (digits only,
471 + * no leading "+"). Aligned to the {@see countryNames()} set. Multiple
472 + * countries legitimately share a code (all NANP countries use "1", RU/KZ
473 + * use "7", GB/GG/IM/JE use "44") — {@see dialingCodePriority()} resolves
474 + * which one an incoming "+code" auto-selects.
475 + *
476 + * @return array<string, string> [ISO code => dialing code]
477 + */
478 + private static function dialingCodes(): array
479 + {
480 + return [
481 + 'AF' => '93', 'AL' => '355', 'DZ' => '213', 'AD' => '376', 'AO' => '244', 'AI' => '1', 'AQ' => '672',
482 + 'AG' => '1', 'AR' => '54', 'AM' => '374', 'AW' => '297', 'AU' => '61', 'AT' => '43', 'AZ' => '994',
483 + 'BS' => '1', 'BH' => '973', 'BD' => '880', 'BB' => '1', 'BY' => '375', 'BE' => '32', 'BZ' => '501',
484 + 'BJ' => '229', 'BM' => '1', 'BT' => '975', 'BO' => '591', 'BQ' => '599', 'BA' => '387', 'BW' => '267',
485 + 'BR' => '55', 'IO' => '246', 'BN' => '673', 'BG' => '359', 'BF' => '226', 'BI' => '257', 'KH' => '855',
486 + 'CM' => '237', 'CA' => '1', 'CV' => '238', 'KY' => '1', 'CF' => '236', 'TD' => '235', 'CL' => '56',
487 + 'CN' => '86', 'CO' => '57', 'KM' => '269', 'CG' => '242', 'CD' => '243', 'CK' => '682', 'CR' => '506',
488 + 'CI' => '225', 'HR' => '385', 'CU' => '53', 'CW' => '599', 'CY' => '357', 'CZ' => '420', 'DK' => '45',
489 + 'DJ' => '253', 'DM' => '1', 'DO' => '1', 'EC' => '593', 'EG' => '20', 'SV' => '503', 'GQ' => '240',
490 + 'ER' => '291', 'EE' => '372', 'SZ' => '268', 'ET' => '251', 'FK' => '500', 'FO' => '298', 'FJ' => '679',
491 + 'FI' => '358', 'FR' => '33', 'GF' => '594', 'PF' => '689', 'GA' => '241', 'GM' => '220', 'GE' => '995',
492 + 'DE' => '49', 'GH' => '233', 'GI' => '350', 'GR' => '30', 'GL' => '299', 'GD' => '1', 'GP' => '590',
493 + 'GU' => '1', 'GT' => '502', 'GG' => '44', 'GN' => '224', 'GW' => '245', 'GY' => '592', 'HT' => '509',
494 + 'HN' => '504', 'HK' => '852', 'HU' => '36', 'IS' => '354', 'IN' => '91', 'ID' => '62', 'IR' => '98',
495 + 'IQ' => '964', 'IE' => '353', 'IM' => '44', 'IL' => '972', 'IT' => '39', 'JM' => '1', 'JP' => '81',
496 + 'JE' => '44', 'JO' => '962', 'KZ' => '7', 'KE' => '254', 'KI' => '686', 'KP' => '850', 'KR' => '82',
497 + 'KW' => '965', 'KG' => '996', 'LA' => '856', 'LV' => '371', 'LB' => '961', 'LS' => '266', 'LR' => '231',
498 + 'LY' => '218', 'LI' => '423', 'LT' => '370', 'LU' => '352', 'MO' => '853', 'MK' => '389', 'MG' => '261',
499 + 'MW' => '265', 'MY' => '60', 'MV' => '960', 'ML' => '223', 'MT' => '356', 'MH' => '692', 'MQ' => '596',
500 + 'MR' => '222', 'MU' => '230', 'YT' => '262', 'MX' => '52', 'FM' => '691', 'MD' => '373', 'MC' => '377',
501 + 'MN' => '976', 'ME' => '382', 'MS' => '1', 'MA' => '212', 'MZ' => '258', 'MM' => '95', 'NA' => '264',
502 + 'NR' => '674', 'NP' => '977', 'NL' => '31', 'NC' => '687', 'NZ' => '64', 'NI' => '505', 'NE' => '227',
503 + 'NG' => '234', 'NU' => '683', 'NF' => '672', 'MP' => '1', 'NO' => '47', 'OM' => '968', 'PK' => '92',
504 + 'PW' => '680', 'PS' => '970', 'PA' => '507', 'PG' => '675', 'PY' => '595', 'PE' => '51', 'PH' => '63',
505 + 'PN' => '64', 'PL' => '48', 'PT' => '351', 'PR' => '1', 'QA' => '974', 'RE' => '262', 'RO' => '40',
506 + 'RU' => '7', 'RW' => '250', 'BL' => '590', 'SH' => '290', 'KN' => '1', 'LC' => '1', 'MF' => '590',
507 + 'PM' => '508', 'VC' => '1', 'WS' => '685', 'SM' => '378', 'ST' => '239', 'SA' => '966', 'SN' => '221',
508 + 'RS' => '381', 'SC' => '248', 'SL' => '232', 'SG' => '65', 'SX' => '1', 'SK' => '421', 'SI' => '386',
509 + 'SB' => '677', 'SO' => '252', 'ZA' => '27', 'GS' => '500', 'SS' => '211', 'ES' => '34', 'LK' => '94',
510 + 'SD' => '249', 'SR' => '597', 'SJ' => '47', 'SE' => '46', 'CH' => '41', 'SY' => '963', 'TW' => '886',
511 + 'TJ' => '992', 'TZ' => '255', 'TH' => '66', 'TL' => '670', 'TG' => '228', 'TK' => '690', 'TO' => '676',
512 + 'TT' => '1', 'TN' => '216', 'TR' => '90', 'TM' => '993', 'TC' => '1', 'TV' => '688', 'UG' => '256',
513 + 'UA' => '380', 'AE' => '971', 'GB' => '44', 'US' => '1', 'UY' => '598', 'UZ' => '998', 'VU' => '678',
514 + 'VA' => '39', 'VE' => '58', 'VN' => '84', 'VG' => '1', 'VI' => '1', 'WF' => '681', 'EH' => '212',
515 + 'YE' => '967', 'ZM' => '260', 'ZW' => '263', 'XK' => '383', 'AS' => '1', 'AX' => '358',
516 + 'UM' => '1', 'CX' => '61', 'CC' => '61', 'TA' => '290',
517 + ];
518 + }
519 +
520 + /**
521 + * Preferred country for a dialing code that several countries share. When
522 + * an operator types a raw "+<code><number>" we must pick one country to
523 + * auto-select; this is the conventional primary (e.g. "1" => US, not one of
524 + * the ~20 other NANP countries). Codes not listed here fall back to the
525 + * first ISO that declares them.
526 + *
527 + * @return array<string, string> [dialing code => primary ISO]
528 + */
529 + private static function dialingCodePriority(): array
530 + {
531 + return [
532 + '1' => 'US', '7' => 'RU', '39' => 'IT', '44' => 'GB', '47' => 'NO', '61' => 'AU',
533 + '212' => 'MA', '262' => 'RE', '290' => 'SH', '358' => 'FI', '500' => 'FK',
534 + '590' => 'GP', '599' => 'CW', '64' => 'NZ', '672' => 'AQ',
535 + ];
536 + }
537 +
538 + /**
539 + * Preferred ISO per shared dialing code (dial => ISO), for the front-end
540 + * detector to resolve which country a "+code" auto-selects. Mirrors the
541 + * server-side {@see dialingCodePriority()}.
542 + *
543 + * @return array<string, string>
544 + */
545 + public static function getPhonePriority(): array
546 + {
547 + return self::dialingCodePriority();
548 + }
549 +
550 + /**
551 + * Dialing code (digits only, no "+") for one ISO code, or '' if unknown.
552 + */
553 + public static function getDialingCode(string $iso): string
554 + {
555 + $codes = self::dialingCodes();
556 + return $codes[strtoupper($iso)] ?? '';
557 + }
558 +
559 + /**
560 + * Country list for the phone-number field: ISO code, display name and
561 + * dialing code, sorted by display name. Filterable via
562 + * `yatra_phone_countries` (e.g. to pin popular countries to the top).
563 + *
564 + * @return array<int, array{iso:string, name:string, dial:string}>
565 + */
566 + public static function getPhoneCountries(): array
567 + {
568 + $names = self::getCountries(); // [iso => name], already sorted/filtered
569 + $dial = self::dialingCodes();
570 + $out = [];
571 + foreach ($names as $iso => $name) {
572 + $iso = strtoupper((string) $iso);
573 + if (!isset($dial[$iso]) || $dial[$iso] === '') {
574 + continue;
575 + }
576 + $out[] = ['iso' => $iso, 'name' => (string) $name, 'dial' => $dial[$iso]];
577 + }
578 + if (\function_exists('apply_filters')) {
579 + $filtered = apply_filters('yatra_phone_countries', $out);
580 + if (\is_array($filtered) && $filtered !== []) {
581 + return $filtered;
582 + }
583 + }
584 + return $out;
585 + }
586 +
587 + /**
588 + * Detect the country/dialing-code from a raw phone value. Only a value that
589 + * begins with "+" is treated as international; anything else returns null so
590 + * an existing bare national number is left untouched (backward compatible).
591 + *
592 + * Uses longest-prefix matching so "+9779806015400" → NP (977), not a shorter
593 + * false match, then applies {@see dialingCodePriority()} for shared codes.
594 + *
595 + * @return array{iso:string, dial:string, national:string}|null
596 + */
597 + public static function detectPhoneCountry(string $raw): ?array
598 + {
599 + $raw = trim($raw);
600 + if ($raw === '' || $raw[0] !== '+') {
601 + return null;
602 + }
603 + $digits = preg_replace('/\D+/', '', $raw);
604 + if ($digits === '' || $digits === null) {
605 + return null;
606 + }
607 + $codes = self::dialingCodes();
608 + $priority = self::dialingCodePriority();
609 + $maxLen = min(4, strlen($digits));
610 + for ($len = $maxLen; $len >= 1; $len--) {
611 + $prefix = substr($digits, 0, $len);
612 + $matches = array_keys($codes, $prefix, true);
613 + if ($matches === []) {
614 + continue;
615 + }
616 + $iso = $priority[$prefix] ?? (string) $matches[0];
617 + return [
618 + 'iso' => $iso,
619 + 'dial' => $prefix,
620 + 'national' => substr($digits, $len),
621 + ];
622 + }
623 + return null;
624 + }
625 +
626 + /**
627 + * Default country pre-selected in the phone field. Priority: the
628 + * `default_phone_country` setting → the site locale suffix (en_US → US,
629 + * ne_NP → NP) → "US". Filterable via `yatra_default_phone_country`.
630 + */
631 + public static function getDefaultPhoneCountry(): string
632 + {
633 + $codes = self::dialingCodes();
634 + $default = '';
635 +
636 + if (\class_exists(SettingsService::class)) {
637 + $setting = strtoupper((string) SettingsService::getString('default_phone_country', ''));
638 + if ($setting !== '' && isset($codes[$setting])) {
639 + $default = $setting;
640 + }
641 + }
642 + if ($default === '' && \function_exists('get_locale')) {
643 + if (preg_match('/_([A-Z]{2})$/', (string) get_locale(), $m) && isset($codes[$m[1]])) {
644 + $default = $m[1];
645 + }
646 + }
647 + if ($default === '') {
648 + $default = 'US';
649 + }
650 + if (\function_exists('apply_filters')) {
651 + $default = (string) apply_filters('yatra_default_phone_country', $default);
652 + }
653 + return isset($codes[$default]) ? $default : 'US';
654 + }
655 +
656 + /**
657 + * Combine a national number with a country (ISO) into the stored value.
658 + *
659 + * - Empty number → '' (nothing to store).
660 + * - Already-international ("+...") → returned unchanged (a pasted full number
661 + * or the no-JS fallback where the customer typed the "+code" themselves).
662 + * - National number + known ISO → "+<dial><digits>" (spaces/dashes stripped).
663 + * - No/unknown ISO → the number left as typed (legacy behavior, backward
664 + * compatible — nothing is invented).
665 + */
666 + public static function combineInternationalPhone(string $number, string $iso): string
667 + {
668 + $number = trim($number);
669 + if ($number === '') {
670 + return '';
671 + }
672 + if ($number[0] === '+') {
673 + return $number;
674 + }
675 + $iso = strtoupper(trim($iso));
676 + $dial = $iso !== '' ? self::getDialingCode($iso) : '';
677 + if ($dial === '') {
678 + return $number;
679 + }
680 + $digits = preg_replace('/\D+/', '', $number);
681 + if ($digits === '' || $digits === null) {
682 + return $number;
683 + }
684 + return '+' . $dial . $digits;
685 + }
686 +
687 + /**
688 + * Format a stored phone value for human display. A value saved with a
689 + * country code ("+9779806015400") is shown as "+977 9806015400"; a legacy
690 + * bare value is returned unchanged (never corrupted). Optionally accepts a
691 + * known ISO to disambiguate shared codes.
692 + */
693 + public static function formatPhoneForDisplay(string $stored, string $iso = ''): string
694 + {
695 + $stored = trim($stored);
696 + if ($stored === '') {
697 + return '';
698 + }
699 + $detected = self::detectPhoneCountry($stored);
700 + if ($detected === null) {
701 + return $stored; // legacy / national-only value — leave as-is
702 + }
703 + $national = $detected['national'] !== '' ? ' ' . $detected['national'] : '';
704 + return '+' . $detected['dial'] . $national;
705 + }
706 +
707 + /**
327 708 * Format phone number for display
328 709 *
329 710 * @param string $phone Phone number
330 711 * @return string Formatted phone
@@ -419,10 +800,20 @@
419 800 */
420 801 public static function formatRatingStars(float $rating, bool $showNumber = false): string
421 802 {
422 803 $rating = max(0, min(5, $rating));
423 - $fullStars = (int) floor($rating);
424 - $hasHalfStar = ($rating - $fullStars) >= 0.5;
804 +
805 + // Rounds to the nearest half star (see yatra_rating_star_parts), so this
806 + // renderer agrees with the confirmation page and reviews block.
807 + if (function_exists('yatra_rating_star_parts')) {
808 + $parts = yatra_rating_star_parts($rating);
809 + $fullStars = $parts['full'];
810 + $hasHalfStar = $parts['half'];
811 + } else {
812 + $halves = (int) round($rating * 2);
813 + $fullStars = intdiv($halves, 2);
814 + $hasHalfStar = ($halves % 2) === 1;
815 + }
425 816
426 817 $html = '<span class="yatra-rating-stars">';
427 818
428 819 for ($i = 1; $i <= 5; $i++) {