| @@ -1,0 +1,456 @@ | ||
| 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 | + * Defines an abstract adapter to extend the pax data collection. | |
| 16 | + * | |
| 17 | + * @since 1.15.0 (J) - 1.5.0 (WP) | |
| 18 | + */ | |
| 19 | +abstract class VBOCheckinAdapter implements VBOCheckinPaxfields | |
| 20 | +{ | |
| 21 | + /** | |
| 22 | + * The ID of the pax data collector class. | |
| 23 | + * | |
| 24 | + * @var string | |
| 25 | + */ | |
| 26 | + protected $collector_id = ''; | |
| 27 | + | |
| 28 | + /** | |
| 29 | + * Default pax fields MRZ type map. | |
| 30 | + * | |
| 31 | + * @var VBOCheckinPaxfieldsMrzMap | |
| 32 | + */ | |
| 33 | + protected $mrzFieldsMapper = null; | |
| 34 | + | |
| 35 | + /** | |
| 36 | + * Class constructor will define internal properties. | |
| 37 | + * | |
| 38 | + * @since 1.18.6 (J) - 1.8.6 (WP) | |
| 39 | + */ | |
| 40 | + public function __construct() | |
| 41 | + { | |
| 42 | + // get pax data collector base class name | |
| 43 | + $collector_base_class = preg_replace('/^VBOCheckinPaxfields/i', '', strtolower(get_class($this))) ?: 'basic'; | |
| 44 | + | |
| 45 | + // build expected pax fields MRZ type map class name | |
| 46 | + $pax_mrz_class = 'VBOCheckinPaxfieldsMrzMap' . ucfirst($collector_base_class); | |
| 47 | + | |
| 48 | + // define current pax fields MRZ type map | |
| 49 | + if (class_exists($pax_mrz_class)) { | |
| 50 | + // pax data collector provides a dedicated MRZ type map | |
| 51 | + $this->mrzFieldsMapper = new $pax_mrz_class($this->collector_id, $this->listFields()); | |
| 52 | + } else { | |
| 53 | + // use the default (basic) MRZ type map | |
| 54 | + $this->mrzFieldsMapper = new VBOCheckinPaxfieldsMrzMapBasic($this->collector_id, $this->listFields()); | |
| 55 | + } | |
| 56 | + } | |
| 57 | + | |
| 58 | + /** | |
| 59 | + * Tells whether children should be registered. | |
| 60 | + * Children registration is disabled by default. | |
| 61 | + * | |
| 62 | + * @param bool $precheckin true if requested for front-end pre check-in. | |
| 63 | + * | |
| 64 | + * @return bool true to also register the children. | |
| 65 | + * | |
| 66 | + * @since 1.16.3 (J) - 1.6.3 (WP) added $precheckin argument. | |
| 67 | + */ | |
| 68 | + public function registerChildren($precheckin = false) | |
| 69 | + { | |
| 70 | + // disabled by default, unless method gets overridden | |
| 71 | + return false; | |
| 72 | + } | |
| 73 | + | |
| 74 | + /** | |
| 75 | + * Tells whether the check-in driver supports MRZ detection | |
| 76 | + * for the uploaded guest documents (Machine Readable Zone). | |
| 77 | + * | |
| 78 | + * @return bool True if MRZ is supported, or false. | |
| 79 | + * | |
| 80 | + * @since 1.18.6 (J) - 1.8.6 (WP) | |
| 81 | + */ | |
| 82 | + public function supportsMRZDetection() | |
| 83 | + { | |
| 84 | + static $mrzSupportChecked = null; | |
| 85 | + | |
| 86 | + if ($mrzSupportChecked !== null) { | |
| 87 | + // use the cached support check | |
| 88 | + return (bool) $mrzSupportChecked; | |
| 89 | + } | |
| 90 | + | |
| 91 | + // enabled by default, only if the Channel Manager is installed with active AI support | |
| 92 | + $mrzSupportChecked = class_exists('VikChannelManager') && | |
| 93 | + defined('VikChannelManagerConfig::AI') && | |
| 94 | + VikChannelManager::getChannel(VikChannelManagerConfig::AI); | |
| 95 | + | |
| 96 | + // return the support value | |
| 97 | + return $mrzSupportChecked; | |
| 98 | + } | |
| 99 | + | |
| 100 | + /** | |
| 101 | + * Returns the current pax fields MRZ mapper. | |
| 102 | + * | |
| 103 | + * @return VBOCheckinPaxfieldsMrzMap | |
| 104 | + * | |
| 105 | + * @since 1.18.6 (J) - 1.8.6 (WP) | |
| 106 | + */ | |
| 107 | + public function getMRZMapper() | |
| 108 | + { | |
| 109 | + return $this->mrzFieldsMapper; | |
| 110 | + } | |
| 111 | + | |
| 112 | + /** | |
| 113 | + * Returns the instance of the given pax field key. | |
| 114 | + * | |
| 115 | + * @param string $key the field key identifier. | |
| 116 | + * | |
| 117 | + * @return VBOCheckinPaxfield the requested pax field object. | |
| 118 | + */ | |
| 119 | + public function getField($key) | |
| 120 | + { | |
| 121 | + // get all the existing field attributes | |
| 122 | + $attributes = $this->getAttributes(); | |
| 123 | + | |
| 124 | + // create a new instance of the field registry object | |
| 125 | + $pax_field = new VBOCheckinPaxfield(); | |
| 126 | + | |
| 127 | + // inject key and type of field | |
| 128 | + $field_type = (isset($attributes[$key]) ? $attributes[$key] : 'text'); | |
| 129 | + $pax_field->setKey($key); | |
| 130 | + $pax_field->setType($field_type); | |
| 131 | + | |
| 132 | + // return the field registry object | |
| 133 | + return $pax_field; | |
| 134 | + } | |
| 135 | + | |
| 136 | + /** | |
| 137 | + * Attempts to find the first field attribute key from the given type. | |
| 138 | + * | |
| 139 | + * @param string $type The type of pax field. | |
| 140 | + * | |
| 141 | + * @return string Empty string or first field attribute key found for this type. | |
| 142 | + * | |
| 143 | + * @since 1.18.0 (J) - 1.8.0 (WP) | |
| 144 | + */ | |
| 145 | + public function getFieldTypeKey(string $type) | |
| 146 | + { | |
| 147 | + // get all the existing field attributes | |
| 148 | + $attributes = $this->getAttributes(); | |
| 149 | + | |
| 150 | + if (!in_array($type, $attributes)) { | |
| 151 | + // unknown pax field type | |
| 152 | + return ''; | |
| 153 | + } | |
| 154 | + | |
| 155 | + // search for the pax field key from the given type | |
| 156 | + $key = array_search($type, $attributes); | |
| 157 | + | |
| 158 | + if ($key === false) { | |
| 159 | + // pax field type not found | |
| 160 | + return ''; | |
| 161 | + } | |
| 162 | + | |
| 163 | + return (string) $key; | |
| 164 | + } | |
| 165 | + | |
| 166 | + /** | |
| 167 | + * Renders a specific pax field type. | |
| 168 | + * | |
| 169 | + * @param VBOCheckinPaxfield $field the pax field object to render. | |
| 170 | + * | |
| 171 | + * @return string the HTML string to display the field. | |
| 172 | + */ | |
| 173 | + public function render(VBOCheckinPaxfield $field) | |
| 174 | + { | |
| 175 | + // get the field implementor | |
| 176 | + $implementor = $this->getFieldTypeImplementor($field); | |
| 177 | + | |
| 178 | + if ($implementor === null) { | |
| 179 | + // could not access the implementor | |
| 180 | + return ''; | |
| 181 | + } | |
| 182 | + | |
| 183 | + // let the handler render the field | |
| 184 | + return $implementor->render(); | |
| 185 | + } | |
| 186 | + | |
| 187 | + /** | |
| 188 | + * Attempts to return an instance of the field-type implementor object being parsed. | |
| 189 | + * | |
| 190 | + * @param VBOCheckinPaxfield $field the pax field object to render. | |
| 191 | + * | |
| 192 | + * @return null|object the field implementor class of VBOCheckinPaxfieldType or null. | |
| 193 | + */ | |
| 194 | + public function getFieldTypeImplementor(VBOCheckinPaxfield $field) | |
| 195 | + { | |
| 196 | + // get the type of field to render | |
| 197 | + $field_type = $field->getType(); | |
| 198 | + | |
| 199 | + if ($field_type === null) { | |
| 200 | + return null; | |
| 201 | + } | |
| 202 | + | |
| 203 | + if (is_array($field_type)) { | |
| 204 | + // convert field type to "select" string | |
| 205 | + $field_type = 'select'; | |
| 206 | + } | |
| 207 | + | |
| 208 | + if (!is_string($field_type)) { | |
| 209 | + // invalid field type | |
| 210 | + return null; | |
| 211 | + } | |
| 212 | + | |
| 213 | + // compose dinamically the implementor class name | |
| 214 | + $field_class = $this->getFieldTypeClass($field_type); | |
| 215 | + | |
| 216 | + if (!$field_class || !class_exists($field_class)) { | |
| 217 | + // no implementor handler found for this type of field | |
| 218 | + return null; | |
| 219 | + } | |
| 220 | + | |
| 221 | + // return the field handler by passing the pax field object and the data collector id | |
| 222 | + return new $field_class($field, $this->collector_id); | |
| 223 | + } | |
| 224 | + | |
| 225 | + /** | |
| 226 | + * Builds the list of back-end pax fields for the extended collection type. | |
| 227 | + * | |
| 228 | + * @return array the list of pax fields to collect in the back-end. | |
| 229 | + */ | |
| 230 | + public function listFields() | |
| 231 | + { | |
| 232 | + return [$this->getLabels(), $this->getAttributes()]; | |
| 233 | + } | |
| 234 | + | |
| 235 | + /** | |
| 236 | + * Builds the list of front-end (pre-checkin) pax fields for the extended collection type. | |
| 237 | + * Check-in pax fields data collector implementations may override this method, if needed. | |
| 238 | + * | |
| 239 | + * @param array $def_fields list of default pre-checkin field labels and attributes. | |
| 240 | + * | |
| 241 | + * @return array the list of pax fields to collect in the front-end during pre-checkin. | |
| 242 | + * | |
| 243 | + * @since 1.17.2 (J) - 1.7.2 (WP) | |
| 244 | + */ | |
| 245 | + public function listPrecheckinFields(array $def_fields) | |
| 246 | + { | |
| 247 | + // return no labels, nor attributes by default | |
| 248 | + return [ | |
| 249 | + [], | |
| 250 | + [], | |
| 251 | + ]; | |
| 252 | + } | |
| 253 | + | |
| 254 | + /** | |
| 255 | + * Invokes a callback for the extended collection type after the pre-checkin | |
| 256 | + * information have been stored or updated to perform certain actions. | |
| 257 | + * | |
| 258 | + * @param array $data the guest registration data stored. | |
| 259 | + * @param array $booking the booking record involved with the guests registration. | |
| 260 | + * @param array $customer optional customer record associated with the booking. | |
| 261 | + * | |
| 262 | + * @return void | |
| 263 | + * | |
| 264 | + * @since 1.17.5 (J) - 1.7.5 (WP) | |
| 265 | + */ | |
| 266 | + public function onPrecheckinDataStored(array $data, array $booking, array $customer) | |
| 267 | + { | |
| 268 | + // no actions to be performed by default | |
| 269 | + return; | |
| 270 | + } | |
| 271 | + | |
| 272 | + /** | |
| 273 | + * Performs a validation over the guest registration field types for a given reservation. | |
| 274 | + * Those responsible for storing the pre-check-in information should call this method first, | |
| 275 | + * which will invoke the validation method over every registration field type, and then the | |
| 276 | + * driver validation will be automatically called to verify the data submitted. | |
| 277 | + * | |
| 278 | + * @param array $booking The booking record involved with the guests registration. | |
| 279 | + * @param array $booking_rooms The booking room records involved with the guests registration. | |
| 280 | + * @param array $data The guests registration data to validate. | |
| 281 | + * @param bool $precheckin True if validating pre-checkin fields. | |
| 282 | + * | |
| 283 | + * @return void | |
| 284 | + * | |
| 285 | + * @throws Exception | |
| 286 | + * | |
| 287 | + * @uses validateRegistrationFields() | |
| 288 | + * | |
| 289 | + * @since 1.18.0 (J) - 1.8.0 (WP) | |
| 290 | + */ | |
| 291 | + public function validateRegistrationFieldTypes(array $booking, array $booking_rooms, array $data, bool $precheckin = true) | |
| 292 | + { | |
| 293 | + // first off, let the driver perform the validation over the field contents submitted to ensure mandatory values are set | |
| 294 | + $this->validateRegistrationFields($booking, $booking_rooms, $data, $precheckin); | |
| 295 | + | |
| 296 | + // get the current driver's attributes | |
| 297 | + $supported_attributes = $this->getAttributes(); | |
| 298 | + | |
| 299 | + // iterate over all rooms booked to identify the custom registration field types | |
| 300 | + foreach ($booking_rooms as $index => $booking_room) { | |
| 301 | + // count expected room registration guests data | |
| 302 | + $room_adults = $booking_room['adults'] ?? 1; | |
| 303 | + $room_children = $booking_room['children'] ?? 0; | |
| 304 | + $room_guests = $this->registerChildren($precheckin) ? ($room_adults + $room_children) : $room_adults; | |
| 305 | + | |
| 306 | + // scan room guests for the expected room guest registration data | |
| 307 | + for ($g = 1; $g <= $room_guests; $g++) { | |
| 308 | + if (!is_array(($data[$index][$g] ?? null))) { | |
| 309 | + // no registration data available for this room and guest | |
| 310 | + continue; | |
| 311 | + } | |
| 312 | + | |
| 313 | + // iterate over the registration fields | |
| 314 | + foreach ($supported_attributes as $field_type) { | |
| 315 | + if (!is_string($field_type) || !$this->isCustomFieldType($field_type)) { | |
| 316 | + // known field types will not be invoked for triggering a custom validation | |
| 317 | + continue; | |
| 318 | + } | |
| 319 | + | |
| 320 | + // find the first pax field key from the given type | |
| 321 | + $pax_field_key = $this->getFieldTypeKey($field_type); | |
| 322 | + | |
| 323 | + if (!$pax_field_key) { | |
| 324 | + // unknown pax field type | |
| 325 | + continue; | |
| 326 | + } | |
| 327 | + | |
| 328 | + // get an instance of the VBOCheckinPaxfield object | |
| 329 | + $pax_field_obj = $this->getField($pax_field_key); | |
| 330 | + | |
| 331 | + // detect the current type of guest | |
| 332 | + $guest_type = $g > $room_adults ? 'child' : 'adult'; | |
| 333 | + | |
| 334 | + // set object data | |
| 335 | + $pax_field_obj->setGuestType($guest_type) | |
| 336 | + ->setGuestNumber($g) | |
| 337 | + ->setGuestData($data[$index][$g]) | |
| 338 | + ->setRoomIndex($index) | |
| 339 | + ->setBooking($booking) | |
| 340 | + ->setBookingRooms($booking_rooms) | |
| 341 | + ->setRoomGuests($room_adults, $room_children) | |
| 342 | + ->setTotalRooms(count($booking_rooms)); | |
| 343 | + | |
| 344 | + // get the field implementor | |
| 345 | + if ($implementor = $this->getFieldTypeImplementor($pax_field_obj)) { | |
| 346 | + // invoke the registration data validation on the field implementor | |
| 347 | + $implementor->validateGuestRegistrationData(); | |
| 348 | + } | |
| 349 | + } | |
| 350 | + } | |
| 351 | + } | |
| 352 | + | |
| 353 | + // all good | |
| 354 | + return; | |
| 355 | + } | |
| 356 | + | |
| 357 | + /** | |
| 358 | + * Performs a validation over the guest registration fields data for a given reservation. | |
| 359 | + * Custom drivers can override this method to implement their own validation method. | |
| 360 | + * | |
| 361 | + * @param array $booking The booking record involved with the guests registration. | |
| 362 | + * @param array $booking_rooms The booking room records involved with the guests registration. | |
| 363 | + * @param array $data The guests registration data to validate. | |
| 364 | + * @param bool $precheckin True if validating pre-checkin fields. | |
| 365 | + * | |
| 366 | + * @return void | |
| 367 | + * | |
| 368 | + * @throws Exception | |
| 369 | + * | |
| 370 | + * @see validateRegistrationFieldTypes() | |
| 371 | + * | |
| 372 | + * @since 1.17.7 (J) - 1.7.7 (WP) | |
| 373 | + */ | |
| 374 | + public function validateRegistrationFields(array $booking, array $booking_rooms, array $data, bool $precheckin = true) | |
| 375 | + { | |
| 376 | + // no guest fields data validation performed by default | |
| 377 | + return; | |
| 378 | + } | |
| 379 | + | |
| 380 | + /** | |
| 381 | + * Tells whether the given field attribute typr correspond to a custom paxfield type. | |
| 382 | + * | |
| 383 | + * @param string $type The field attribute type. | |
| 384 | + * | |
| 385 | + * @return bool True if this is a custom field type. | |
| 386 | + * | |
| 387 | + * @since 1.18.0 (J) - 1.8.0 (WP) | |
| 388 | + */ | |
| 389 | + protected function isCustomFieldType(string $type) | |
| 390 | + { | |
| 391 | + // get the current driver's attributes | |
| 392 | + $supported_attributes = $this->getAttributes(); | |
| 393 | + | |
| 394 | + if (!in_array($type, $supported_attributes)) { | |
| 395 | + // unknown attribute type for this driver | |
| 396 | + return false; | |
| 397 | + } | |
| 398 | + | |
| 399 | + // declare the known field types that will not require a validation at field-object level | |
| 400 | + $known_types = [ | |
| 401 | + 'calendar', | |
| 402 | + 'country', | |
| 403 | + 'file', | |
| 404 | + 'number', | |
| 405 | + 'select', | |
| 406 | + 'text', | |
| 407 | + 'textarea', | |
| 408 | + ]; | |
| 409 | + | |
| 410 | + return !in_array($type, $known_types); | |
| 411 | + } | |
| 412 | + | |
| 413 | + /** | |
| 414 | + * Composes the field type class name given its type-string. | |
| 415 | + * | |
| 416 | + * @param string $field_type the field type-string identifier. | |
| 417 | + * | |
| 418 | + * @return bool|string the class name to use for the field, or false. | |
| 419 | + */ | |
| 420 | + protected function getFieldTypeClass($field_type) | |
| 421 | + { | |
| 422 | + if (!is_string($field_type) || empty($field_type)) { | |
| 423 | + return false; | |
| 424 | + } | |
| 425 | + | |
| 426 | + // base class name | |
| 427 | + $base_paxf_class = 'VBOCheckinPaxfieldType'; | |
| 428 | + | |
| 429 | + // compose field type class name | |
| 430 | + $field_type = ucwords(str_replace(array('_', '-'), ' ', $field_type)); | |
| 431 | + $field_type = preg_replace("/[^a-zA-Z0-9]/", '', $field_type); | |
| 432 | + | |
| 433 | + return $base_paxf_class . $field_type; | |
| 434 | + } | |
| 435 | + | |
| 436 | + /** | |
| 437 | + * Returns the name of the current pax data driver. | |
| 438 | + * | |
| 439 | + * @return string the name of the driver. | |
| 440 | + */ | |
| 441 | + abstract public function getName(); | |
| 442 | + | |
| 443 | + /** | |
| 444 | + * Builds the list pax fields labels. | |
| 445 | + * | |
| 446 | + * @return array the list of pax fields labels. | |
| 447 | + */ | |
| 448 | + abstract public function getLabels(); | |
| 449 | + | |
| 450 | + /** | |
| 451 | + * Builds the list pax fields attributes. | |
| 452 | + * | |
| 453 | + * @return array the list of pax fields attributes. | |
| 454 | + */ | |
| 455 | + abstract public function getAttributes(); | |
| 456 | +} | |