PluginProbe
VikBooking Hotel Booking Engine & PMS / 1.8.15
VikBooking Hotel Booking Engine & PMS v1.8.15
1.8.15 1.8.14 1.8.13 1.8.12 1.8.11 1.8.10 1.8.9 1.8.6 1.8.7 1.8.8 trunk 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.6.5 1.6.6 1.6.7 1.6.8 1.6.9 1.7.0 1.7.1 1.7.2 1.7.3 All 36 releases
vikbooking / admin / helpers / src / checkin / paxfields / italy.php

italy.php in VikBooking Hotel Booking Engine & PMS 1.8.15, at admin/helpers/src/checkin/paxfields/italy.php

545 lines 17.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * @package VikBooking
4 * @subpackage core
5 * @author Alessio Gaggii - E4J s.r.l.
6 * @copyright Copyright (C) 2022 E4J s.r.l. All Rights Reserved.
7 * @license http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
8 * @link https://vikwp.com
9 */
10
11 // No direct access
12 defined('ABSPATH') or die('No script kiddies please!');
13
14 /**
15 * Helper class to support custom pax fields data collection types for Italy.
16 *
17 * @since 1.15.0 (J) - 1.5.0 (WP)
18 */
19 final class VBOCheckinPaxfieldsItaly extends VBOCheckinAdapter
20 {
21 /**
22 * The ID of this pax data collector class.
23 *
24 * @var string
25 */
26 protected $collector_id = 'italy';
27
28 /**
29 * Returns the name of the current pax data driver.
30 *
31 * @return string the name of this driver.
32 */
33 public function getName()
34 {
35 return '"Italia"';
36 }
37
38 /**
39 * Tells whether children should be registered.
40 *
41 * @override this driver requires children to be registered.
42 *
43 * @param bool $precheckin true if requested for front-end pre check-in.
44 *
45 * @return bool true to also register the children.
46 *
47 * @since 1.16.3 (J) - 1.6.3 (WP) added $precheckin argument.
48 * @since 1.17.6 (J) - 1.7.6 (WP) children are always registered.
49 */
50 public function registerChildren($precheckin = false)
51 {
52 // children are registered during both back-end and pre-check-in
53 return true;
54 }
55
56 /**
57 * Returns the list of field labels. The count and keys
58 * of the labels should match with the attributes.
59 *
60 * @see All fields ending with "_b" stand for "birth",
61 * all fields ending with "_s" stand for "stay",
62 * all fields ending with "_c" stand for "citizenship".
63 * Keep the ending char intact as some fields act differently
64 * depending on the number and type of guest we are parsing.
65 *
66 * @return array associative list of field labels.
67 */
68 public function getLabels()
69 {
70 return [
71 'guest_type' => JText::translate('VBO_GUEST_TYPE'),
72 'first_name' => JText::translate('VBCUSTOMERFIRSTNAME'),
73 'last_name' => JText::translate('VBCUSTOMERLASTNAME'),
74 'gender' => JText::translate('VBOCUSTGENDER'),
75 'date_birth' => JText::translate('ORDER_DBIRTH'),
76 'country_b' => JText::translate('VBO_BIRTH_STATE'),
77 'comune_b' => JText::translate('VBO_BIRTH_MUNICIPALITY'),
78 'province_b' => JText::translate('VBO_STATE_PROVINCE'),
79 'zip_b' => JText::translate('ORDER_ZIP'),
80 'country_s' => JText::translate('VBO_STAY_STATE'),
81 'comune_s' => JText::translate('VBO_STAY_MUNICIPALITY'),
82 'province_s' => JText::translate('VBO_STATE_PROVINCE'),
83 'zip_s' => JText::translate('ORDER_ZIP'),
84 'country_c' => JText::translate('VBOCUSTNATIONALITY'),
85 'doctype' => JText::translate('VBOCUSTDOCTYPE'),
86 'docnum' => JText::translate('VBOCUSTDOCNUM'),
87 'docplace' => JText::translate('VBO_ID_ISSUE_PLACE'),
88 'extranotes' => JText::translate('VBOGUESTEXTRANOTES'),
89 ];
90 }
91
92 /**
93 * Returns the list of field attributes. The count and keys
94 * of the attributes should match with the labels.
95 *
96 * @see All fields ending with "_b" stand for "birth",
97 * all fields ending with "_s" stand for "stay",
98 * all fields ending with "_c" stand for "citizenship".
99 * Keep the ending char intact as some fields act differently
100 * depending on the number and type of guest we are parsing.
101 *
102 * @return array associative list of field attributes.
103 */
104 public function getAttributes()
105 {
106 return [
107 'guest_type' => 'italy_guesttype',
108 'first_name' => 'text',
109 'last_name' => 'text',
110 'gender' => 'italy_gender',
111 'date_birth' => 'calendar',
112 'country_b' => 'italy_country',
113 'comune_b' => 'italy_comune',
114 'province_b' => 'italy_province',
115 'zip_b' => 'italy_cap',
116 'country_s' => 'italy_country',
117 'comune_s' => 'italy_comune',
118 'province_s' => 'italy_province',
119 'zip_s' => 'italy_cap',
120 'country_c' => 'italy_country',
121 'doctype' => 'italy_doctype',
122 'docnum' => 'italy_docnum',
123 'docplace' => 'italy_country',
124 'extranotes' => 'textarea',
125 ];
126 }
127
128 /**
129 * @inheritDoc
130 *
131 * @since 1.17.2 (J) - 1.7.2 (WP)
132 */
133 public function listPrecheckinFields(array $def_fields)
134 {
135 // use the same fields for the back-end guests registration
136 $labels = $this->getLabels();
137 $attributes = $this->getAttributes();
138
139 // for pre-checkin we keep any default field of type "file" for uploading IDs
140 foreach (($def_fields[1] ?? []) as $field_key => $field_type) {
141 if (!is_string($field_type)) {
142 // not looking for a list of options
143 continue;
144 }
145 if (!strcasecmp($field_type, 'file') && ($def_fields[0][$field_key] ?? null)) {
146 // append or prepend this pax field of type "file" for uploading IDs
147 if ($this->supportsMRZDetection()) {
148 // when MRZ detection is supported, prepend the pax field of type file
149 $labels = [$field_key => $def_fields[0][$field_key]] + $labels;
150 $attributes = [$field_key => $field_type] + $attributes;
151 } else {
152 // append pax field of type file when no MRZ support
153 $labels[$field_key] = $def_fields[0][$field_key];
154 $attributes[$field_key] = $field_type;
155 }
156
157 // break the loop once we've found the desired field type
158 break;
159 }
160 }
161
162 // return the list of pre-checkin pax fields
163 return [$labels, $attributes];
164 }
165
166 /**
167 * @inheritDoc
168 * @override
169 *
170 * @since 1.17.7 (J) - 1.7.7 (WP)
171 */
172 public function validateRegistrationFields(array $booking, array $booking_rooms, array $data, bool $precheckin = true)
173 {
174 if (!$precheckin) {
175 // no validation needed during back-end registration
176 return;
177 }
178
179 // get all field labels
180 $labels = $this->getLabels();
181
182 // list of mandatory fields
183 $mandatory_gfields = [
184 'guest_type',
185 'first_name',
186 'last_name',
187 'gender',
188 'date_birth',
189 'country_b',
190 'country_c',
191 ];
192
193 // list of mandatory fields for the main guest
194 $mandatory_main_gfields = [
195 'doctype',
196 'docnum',
197 'docplace',
198 ];
199
200 // list of mandatory fields for any guest in case the country of birth ("country_b") is Italy
201 $mandatory_italy_gfields = [
202 'comune_b',
203 'province_b',
204 ];
205
206 // iterate over all rooms booked
207 foreach ($booking_rooms as $index => $booking_room) {
208 if (!is_array(($data[$index] ?? null)) || !$data[$index]) {
209 throw new Exception(sprintf('Missing guests registration data for room #%d.', ($index + 1)), 500);
210 }
211
212 // count expected room registration guests data
213 $room_adults = $booking_room['adults'] ?? 1;
214 $room_children = $booking_room['children'] ?? 0;
215 $room_guests = $this->registerChildren($precheckin) ? ($room_adults + $room_children) : $room_adults;
216
217 if ($room_guests > count($data[$index])) {
218 throw new Exception(sprintf('Please fill the information for all guests (%d) for room #%d.', $room_guests, ($index + 1)), 500);
219 }
220
221 // scan room guests for the expected room guest registration data
222 for ($g = 1; $g <= $room_guests; $g++) {
223 if (!is_array(($data[$index][$g] ?? null))) {
224 throw new Exception(sprintf('Please fill the information for the guests #%d for room #%d.', $g, ($index + 1)), 500);
225 }
226
227 // build guest mandatory fields
228 $mand_fields = $mandatory_gfields;
229
230 if ($g === 1) {
231 // merge main guest-level mandatory fields for validation
232 $mand_fields = array_merge($mand_fields, $mandatory_main_gfields);
233 }
234
235 // check if we need to validate additional fields for this guest in case it's Italian
236 if (!empty($data[$index][$g]['country_b']) && is_scalar($data[$index][$g]['country_b'])) {
237 if ($data[$index][$g]['country_b'] == '100000100' || !strcasecmp(substr((string) $data[$index][$g]['country_b'], 0, 2), 'IT')) {
238 // the guest is Italian, merge mandatory fields for Italian guests
239 $mand_fields = array_merge($mand_fields, $mandatory_italy_gfields);
240 }
241 }
242
243 // ensure no mandatory field is empty
244 foreach ($mand_fields as $mand_gfield) {
245 if (!is_string($data[$index][$g][$mand_gfield] ?? null) || !strlen($data[$index][$g][$mand_gfield])) {
246 throw new Exception(JText::sprintf('VBO_MISSING_GFIELD_REGISTRATION', ($labels[$mand_gfield] ?? $mand_gfield)), 500);
247 }
248 }
249 }
250 }
251
252 // map of guest-type codes
253 $guest_type_codes_map = [
254 // main guest "Head of Family" expects other guests with equal last name to be "Family Member"
255 17 => 19,
256 // main guest "Head of Group" expects other guests with equal last name to be "Group Member"
257 18 => 20,
258 ];
259
260 // map of guest-type codes and related translations
261 $guest_type_codes_map_tn = [
262 // main guest
263 17 => JText::translate('VBO_FAMILY_HEAD'),
264 18 => JText::translate('VBO_GROUP_HEAD'),
265 // additional guests
266 19 => JText::translate('VBO_FAMILY_MEMBER'),
267 20 => JText::translate('VBO_GROUP_MEMBER'),
268 ];
269
270 // fields are sufficient, prevent transmission errors with invalid guest-type codes
271 foreach ($booking_rooms as $index => $booking_room) {
272 // count expected room registration guests data
273 $room_adults = $booking_room['adults'] ?? 1;
274 $room_children = $booking_room['children'] ?? 0;
275 $room_guests = $this->registerChildren($precheckin) ? ($room_adults + $room_children) : $room_adults;
276
277 if ($room_guests < 2) {
278 // no validation needed over guest-type code
279 continue;
280 }
281
282 // identify the main guest type code and last name
283 $main_guest_type = (int) ($data[$index][1]['guest_type'] ?? 0);
284 $main_guest_lname = $data[$index][1]['last_name'] ?? '';
285 if (!in_array($main_guest_type, [17, 18]) || empty($main_guest_lname)) {
286 // expected 17 or 18, respectively for "Head of Family" or "Head of Group"
287 continue;
288 }
289
290 // scan room guests by excluding the main one
291 for ($g = 2; $g <= $room_guests; $g++) {
292 if ($main_guest_lname == ($data[$index][$g]['last_name'] ?? '')) {
293 // this guest has got the same last name as the main one
294 if ($guest_type_codes_map[$main_guest_type] != ($data[$index][$g]['guest_type'] ?? 0)) {
295 // expected additional guest type code differs from the selected value
296 throw new Exception(sprintf('The guest #%d for room #%d has got the same last name as the main guest, but their guest-type codes do not match.', $g, ($index + 1)), 500);
297 }
298 }
299 }
300
301 // if the main guest is "Head of Family" then all guests are expected to be "Family Members", otherwise
302 // if the main guest is "Head of Group" then all guests are expected to be "Group Members" or validation will fail
303 for ($g = 2; $g <= $room_guests; $g++) {
304 if ($guest_type_codes_map[$main_guest_type] != ($data[$index][$g]['guest_type'] ?? 0)) {
305 // invalid guest type selected according to the main guest type
306 $main_guest_type_tn = $guest_type_codes_map_tn[($data[$index][1]['guest_type'] ?? 0)] ?? '';
307 $guest_type_tn = $guest_type_codes_map_tn[($data[$index][$g]['guest_type'] ?? 0)] ?? '';
308 $guest_type_exp_code = ($data[$index][$g]['guest_type'] ?? 0) == 19 ? 20 : (($data[$index][$g]['guest_type'] ?? 0) == 20 ? 19 : 0);
309 $guest_type_exp_tn = $guest_type_codes_map_tn[$guest_type_exp_code] ?? '';
310 throw new Exception(sprintf('Invalid type "%s" for the guest #%d in room #%d. "%s" is expected when the main guest is the "%s".', $guest_type_tn, $g, ($index + 1), $guest_type_exp_tn, $main_guest_type_tn), 500);
311 }
312 }
313 }
314 }
315
316 /**
317 * Parses the file Nazioni.csv and returns an associative
318 * array with the code and name of the "Nazione" (country).
319 *
320 * Public and internal method, not part of the interface.
321 * Useful for all the implementor fields that need it.
322 *
323 * @param bool $normalize Whether to normalize the country names into localised values.
324 *
325 * @return array
326 *
327 * @since 1.17.2 (J) - 1.7.2 (WP) added argument $normalize and related behaviour.
328 */
329 public function loadNazioni($normalize = true)
330 {
331 // always try access the registry instance data
332 $collect_registry = VBOCheckinPax::getInstanceData();
333
334 // check if the registry instance of this collection type has cached values
335 if ($collect_registry) {
336 $prev_country_codes = $collect_registry->get('nazioni');
337 if (is_array($prev_country_codes) && $prev_country_codes) {
338 // another field of this collection must have loaded already the "Nazioni"
339 return $prev_country_codes;
340 }
341 }
342
343 // container
344 $country_codes = [];
345
346 $csv = VBO_ADMIN_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'report' . DIRECTORY_SEPARATOR . 'Nazioni.csv';
347 $rows = file($csv);
348 foreach ($rows as $row) {
349 if (empty($row)) {
350 continue;
351 }
352
353 $v = explode(';', $row);
354 if (count($v) != 3) {
355 continue;
356 }
357
358 // remove unwanted chars
359 $v[0] = trim($v[0]);
360
361 if (!isset($country_codes[$v[0]])) {
362 $country_codes[$v[0]] = [];
363 }
364
365 // push values
366 $country_codes[$v[0]]['name'] = trim($v[1]);
367 $country_codes[$v[0]]['three_code'] = trim($v[2]);
368 }
369
370 /**
371 * Apply translations based on country 3-char ISO code, if not in Italian or denied.
372 *
373 * @since 1.17.2 (J) - 1.7.2 (WP)
374 */
375 if ($normalize && strtolower(substr(JFactory::getLanguage()->getTag(), 0, 2)) !== 'it') {
376 // get the translated countries list
377 $tn_countries = VikBooking::getCountriesArray();
378
379 $country_codes = array_map(function($country_vals) use ($tn_countries) {
380 if (!($tn_countries[$country_vals['three_code']] ?? null)) {
381 // unknown country
382 return $country_vals;
383 }
384
385 // replace country name
386 $country_vals['name'] = $tn_countries[$country_vals['three_code']]['country_name'];
387
388 // return the modified country values array
389 return $country_vals;
390 }, $country_codes);
391 }
392
393 // try to cache the country codes for other fields that may need them later
394 if ($collect_registry) {
395 // cache array for other fields
396 $collect_registry->set('nazioni', $country_codes);
397 }
398
399 return $country_codes;
400 }
401
402 /**
403 * Parses the file Comuni.csv and returns two associative
404 * arrays: one for the Comuni and one for the Province.
405 * Every line of the CSV is composed of: Codice, Comune, Provincia.
406 *
407 * Public and internal method, not part of the interface.
408 * Useful for all the implementor fields that need it.
409 *
410 * @return array
411 */
412 public function loadComuniProvince()
413 {
414 // always try access the registry instance data
415 $collect_registry = VBOCheckinPax::getInstanceData();
416
417 // check if the registry instance of this collection type has cached values
418 if ($collect_registry) {
419 $prev_comprov_codes = $collect_registry->get('comuni_province');
420 if (is_array($prev_comprov_codes) && $prev_comprov_codes) {
421 // another field of this collection must have loaded already the "Comuni"
422 return $prev_comprov_codes;
423 }
424 }
425
426 // container
427 $comprov_codes = [
428 'comuni' => [],
429 'province' => [],
430 ];
431
432 $csv = VBO_ADMIN_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'report' . DIRECTORY_SEPARATOR . 'Comuni.csv';
433 $rows = file($csv);
434 foreach ($rows as $row) {
435 if (empty($row)) {
436 continue;
437 }
438
439 $v = explode(';', $row);
440 if (count($v) != 3) {
441 continue;
442 }
443
444 // trim values
445 $v[0] = trim($v[0]);
446 $v[1] = trim($v[1]);
447 $v[2] = trim($v[2]);
448
449 if (!isset($comprov_codes['comuni'][$v[0]])) {
450 $comprov_codes['comuni'][$v[0]] = [];
451 }
452
453 // push values
454 $comprov_codes['comuni'][$v[0]]['name'] = $v[1];
455 $comprov_codes['comuni'][$v[0]]['province'] = $v[2];
456 $comprov_codes['province'][$v[2]] = $v[2];
457 }
458
459 // try to cache the comuni-province codes for other fields that may need them later
460 if ($collect_registry) {
461 // cache array for other fields
462 $collect_registry->set('comuni_province', $comprov_codes);
463 }
464
465 return $comprov_codes;
466 }
467
468 /**
469 * Parses the file Documenti.csv and returns an associative
470 * array with the code and name of the Documento.
471 * Every line of the CSV is composed of: Codice, Documento.
472 *
473 * Public and internal method, not part of the interface.
474 * Useful for all the implementor fields that need it.
475 *
476 * @param bool $normalize Whether to display values relevant to non-italian guests.
477 *
478 * @return array
479 *
480 * @since 1.17.2 (J) - 1.7.2 (WP) added argument $normalize and related behaviour.
481 */
482 public function loadDocumenti($normalize = false)
483 {
484 // always try access the registry instance data
485 $collect_registry = VBOCheckinPax::getInstanceData();
486
487 // check if the registry instance of this collection type has cached values
488 if ($collect_registry) {
489 $prev_documenti = $collect_registry->get('documenti');
490 if (is_array($prev_documenti) && $prev_documenti) {
491 // another field of this collection must have loaded already the "Comuni"
492 return $prev_documenti;
493 }
494 }
495
496 // container
497 $documenti = [];
498
499 $csv = VBO_ADMIN_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'report' . DIRECTORY_SEPARATOR . 'Documenti.csv';
500 $rows = file($csv);
501 foreach ($rows as $row) {
502 if (empty($row)) {
503 continue;
504 }
505
506 $v = explode(';', $row);
507 if (count($v) != 2) {
508 continue;
509 }
510
511 // trim values
512 $v[0] = trim($v[0]);
513 $v[1] = trim($v[1]);
514
515 // push values
516 $documenti[$v[0]] = $v[1];
517 }
518
519 if ($normalize === true) {
520 // we expect these values to be displayed to non-italian guests
521 $known_documents = [
522 'PASOR' => JText::translate('VBO_PASSPORT'),
523 'PATEN' => JText::translate('VBO_DRIVING_LICENSE'),
524 'IDENT' => JText::translate('VBO_IDENTITY_DOCUMENT'),
525 ];
526
527 // filter documents by foreign types
528 $documenti = array_filter($documenti, function($doc_type) use ($known_documents) {
529 return isset($known_documents[$doc_type]);
530 }, ARRAY_FILTER_USE_KEY);
531
532 // normalize known document names
533 $documenti = array_merge($documenti, $known_documents);
534 }
535
536 // try to cache the documenti codes for other fields that may need them later
537 if ($collect_registry) {
538 // cache array for other fields
539 $collect_registry->set('documenti', $documenti);
540 }
541
542 return $documenti;
543 }
544 }
545