PluginProbe
VikBooking Hotel Booking Engine & PMS / 1.8.16
VikBooking Hotel Booking Engine & PMS v1.8.16
1.8.16 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 All 37 releases
vikbooking / site / views / precheckin / tmpl / default.php

default.php in VikBooking Hotel Booking Engine & PMS 1.8.16, at site/views/precheckin/tmpl/default.php

910 lines 34.0 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 com_vikbooking
5 * @author Alessio Gaggii - E4J srl
6 * @copyright Copyright (C) 2026 E4J srl. All rights reserved.
7 * @license GNU General Public License version 2 or later; see LICENSE
8 * @link https://vikwp.com
9 */
10
11 defined('ABSPATH') or die('No script kiddies please!');
12
13 $document = JFactory::getDocument();
14 // load jQuery lib e jQuery UI
15 if (VikBooking::loadJquery()) {
16 JHtml::fetch('jquery.framework', true, true);
17 }
18 $document->addStyleSheet(VBO_SITE_URI.'resources/jquery-ui.min.css');
19 JHtml::fetch('script', VBO_SITE_URI.'resources/jquery-ui.min.js');
20
21 // load VBO core JS
22 VikBooking::getVboApplication()->loadCoreJS();
23
24 $datesep = VikBooking::getDateSeparator();
25 $nowdf = VikBooking::getDateFormat();
26 if ($nowdf == "%d/%m/%Y") {
27 $df = 'd/m/Y';
28 $juidf = 'dd/mm/yy';
29 } elseif ($nowdf == "%m/%d/%Y") {
30 $df = 'm/d/Y';
31 $juidf = 'mm/dd/yy';
32 } else {
33 $df = 'Y/m/d';
34 $juidf = 'yy/mm/dd';
35 }
36 $wdays_map = array(
37 JText::translate('VBWEEKDAYZERO'),
38 JText::translate('VBWEEKDAYONE'),
39 JText::translate('VBWEEKDAYTWO'),
40 JText::translate('VBWEEKDAYTHREE'),
41 JText::translate('VBWEEKDAYFOUR'),
42 JText::translate('VBWEEKDAYFIVE'),
43 JText::translate('VBWEEKDAYSIX')
44 );
45 $ts_info = getdate($this->order['ts']);
46 $checkin_info = getdate($this->order['checkin']);
47 $checkout_info = getdate($this->order['checkout']);
48 $pitemid = VikRequest::getInt('Itemid', 0, 'request');
49
50 // compose the calculation for pax_data
51 list($pax_fields, $pax_fields_attributes) = VikBooking::getPaxFields(true);
52 $pax_data = array();
53 $count_pax_data = 1;
54 if (!empty($this->customer['pax_data'])) {
55 $pax_data = json_decode($this->customer['pax_data'], true);
56 }
57
58 $arrpeople = array();
59 foreach ($this->orderrooms as $ind => $or) {
60 $num = $ind + 1;
61 $arrpeople[$num]['adults'] = $or['adults'];
62 $arrpeople[$num]['children'] = $or['children'];
63 $arrpeople[$num]['children_age'] = $or['childrenage'];
64 $arrpeople[$num]['t_first_name'] = $or['t_first_name'];
65 $arrpeople[$num]['t_last_name'] = $or['t_last_name'];
66 }
67
68 // countries list
69 $all_countries = VikBooking::getCountriesArray();
70
71 // access pax fields registration object
72 $pax_fields_obj = VBOCheckinPax::getInstance();
73
74 // load langs for JS
75 JText::script('VBO_UPLOAD_FAILED');
76 JText::script('VBO_REMOVEF_CONFIRM');
77 JText::script('VBO_PRECHECKIN_TOAST_HELP');
78 JText::script('VBO_PLEASE_WAIT');
79 ?>
80
81 <h3 class="vbo-booking-details-intro"><?php echo JText::sprintf('VBOYOURBOOKCONFAT', VikBooking::getFrontTitle()); ?></h3>
82
83 <div class="vbo-booking-details-topcontainer vbo-booking-details-precheckin">
84
85 <div class="vbo-booking-details-head vbo-booking-details-head-confirmed">
86 <h4><?php echo JText::translate('VBOPRECHECKIN'); ?></h4>
87 </div>
88
89 <div class="vbo-booking-details-midcontainer">
90 <div class="vbo-booking-details-bookinfos">
91 <span class="vbvordudatatitle"><?php echo JText::translate('VBORDERDETAILS'); ?></span>
92 <div class="vbo-booking-details-bookinfo">
93 <span class="vbo-booking-details-bookinfo-lbl"><?php echo JText::translate('VBORDEREDON'); ?></span>
94 <span class="vbo-booking-details-bookinfo-val"><?php echo $wdays_map[$ts_info['wday']].', '.date(str_replace("/", $datesep, $df).' H:i', $this->order['ts']); ?></span>
95 </div>
96 <div class="vbo-booking-details-bookinfo">
97 <span class="vbo-booking-details-bookinfo-lbl"><?php echo JText::translate('VBCONFIRMNUMB'); ?></span>
98 <span class="vbo-booking-details-bookinfo-val"><?php echo $this->order['confirmnumber']; ?></span>
99 </div>
100 <div class="vbo-booking-details-bookinfo">
101 <span class="vbo-booking-details-bookinfo-lbl"><?php echo JText::translate('VBDAL'); ?></span>
102 <span class="vbo-booking-details-bookinfo-val"><?php echo $wdays_map[$checkin_info['wday']].', '.date(str_replace("/", $datesep, $df).' H:i', $this->order['checkin']); ?></span>
103 </div>
104 <div class="vbo-booking-details-bookinfo">
105 <span class="vbo-booking-details-bookinfo-lbl"><?php echo JText::translate('VBAL'); ?></span>
106 <span class="vbo-booking-details-bookinfo-val"><?php echo $wdays_map[$checkout_info['wday']].', '.date(str_replace("/", $datesep, $df).' H:i', $this->order['checkout']); ?></span>
107 </div>
108 <div class="vbo-booking-details-bookinfo">
109 <span class="vbo-booking-details-bookinfo-lbl"><?php echo JText::translate('VBDAYS'); ?></span>
110 <span class="vbo-booking-details-bookinfo-val"><?php echo $this->order['days']; ?></span>
111 </div>
112 </div>
113 </div>
114
115 <div class="vbo-precheckin-container">
116 <div class="info vbo-precheckin-disclaimer"><?php echo JText::translate('VBO_PRECHECKIN_DISCLAIMER'); ?></div>
117 <form action="<?php echo JRoute::rewrite('index.php?option=com_vikbooking'.(!empty($pitemid) ? '&Itemid='.$pitemid : '')); ?>" method="post" id="vbo-precheckin-main-form">
118 <input type="hidden" name="option" value="com_vikbooking" />
119 <input type="hidden" name="task" value="storeprecheckin" />
120 <input type="hidden" name="sid" value="<?php echo empty($this->order['sid']) && !empty($this->order['idorderota']) ? $this->order['idorderota'] : $this->order['sid']; ?>" />
121 <input type="hidden" name="ts" value="<?php echo $this->order['ts']; ?>" />
122 <input type="hidden" name="Itemid" value="<?php echo $pitemid; ?>" />
123 <?php
124 /**
125 * Tell whether the pax fields driver supports MRZ detection for uploaded documents (IDs).
126 *
127 * @since 1.18.6 (J) - 1.8.6 (WP)
128 */
129 $mrzDetectionSupport = $pax_fields_obj->supportsMRZDetection();
130
131 // iterate all rooms within the reservation
132 foreach ($this->orderrooms as $ind => $or) {
133 $num = $ind + 1;
134 // total guests details available
135 $count_pax_data = $num < 2 ? $count_pax_data : 0;
136 if (isset($pax_data[$ind])) {
137 $count_pax_data = count($pax_data[$ind]);
138 }
139
140 // calculate the number of guests to register depending on current driver settings for pre-checkin (plugin only)
141 $guests_to_register = $pax_fields_obj->registerChildren($precheckin = true) ? ($arrpeople[$num]['adults'] + $arrpeople[$num]['children']) : $arrpeople[$num]['adults'];
142 ?>
143 <div class="vbo-precheckin-room-wrapper">
144 <div class="vbo-precheckin-room-wrap <?php echo $count_pax_data >= $guests_to_register ? 'vbo-precheckin-guestscount-complete' : 'vbo-precheckin-guestscount-incomplete'; ?>">
145 <div class="vbo-precheckin-room-head">
146 <span><?php echo $or['name']; ?></span>
147 </div>
148 <div class="vbo-precheckin-adults-cont">
149 <?php
150 for ($g = 1; $g <= $guests_to_register; $g++) {
151 $current_guest = array();
152 if (count($pax_data) && isset($pax_data[$ind]) && isset($pax_data[$ind][$g])) {
153 $current_guest = $pax_data[$ind][$g];
154 } elseif ($ind < 1 && $g == 1) {
155 $current_guest = $this->customer;
156 }
157 ?>
158 <div class="vbo-precheckin-adult-wrap">
159 <div class="vbo-precheckin-adult-num">
160 <span><?php echo JText::sprintf('VBOGUESTNUM', $g); ?></span>
161 </div>
162 <div class="vbo-precheckin-guest-fields">
163 <?php
164 /**
165 * Overrides tip: to add and collect custom details from each guest it is possible to push more
166 * pairs of key-values to $pax_fields. For example, a custom value could be pushed like this:
167 *
168 * $pax_fields['custom_field_key'] = 'Custom Field Name';
169 * $pax_fields_attributes['custom_field_key'] = 'text';
170 *
171 * @see the best way to customize the guest fields is to use the apposite plugin hooks/events.
172 */
173 $pax_field_ind = 1;
174 foreach ($pax_fields as $paxk => $paxv) {
175 // determine the pax field rendering type
176
177 if (isset($pax_fields_attributes[$paxk]) && is_array($pax_fields_attributes[$paxk])) {
178 // select with choices
179 $field_rendering_type = 'select';
180 } elseif (isset($pax_fields_attributes[$paxk]) && $pax_fields_attributes[$paxk] == 'country') {
181 // field of type country
182 $field_rendering_type = 'country';
183 } elseif (isset($pax_fields_attributes[$paxk]) && $pax_fields_attributes[$paxk] == 'calendar') {
184 // datepicker calendar
185 $field_rendering_type = 'calendar';
186 } elseif (isset($pax_fields_attributes[$paxk]) && $pax_fields_attributes[$paxk] == 'file') {
187 // file upload (multiple files allowed)
188 $field_rendering_type = 'file';
189 } elseif (isset($pax_fields_attributes[$paxk]) && $pax_fields_attributes[$paxk] == 'text') {
190 // text
191 $field_rendering_type = 'text';
192 } else {
193 // custom pax-field type
194 $field_rendering_type = 'paxfield';
195 }
196
197 // the pax-field HTML string, if any
198 $pax_field_html = '';
199
200 // run pre-flight check to ensure the paxfield is compatible with the current guest
201 if ($field_rendering_type === 'paxfield') {
202 /**
203 * Attempt to render a custom field previously installed through a third party plugin.
204 * If no such field type is found, the object will default to the "text" type.
205 *
206 * @since 1.16.3 (J) - 1.6.3 (WP)
207 */
208
209 // get an instance of the VBOCheckinPaxfield object
210 $pax_field_obj = $pax_fields_obj->getField($paxk);
211
212 // detect the current type of guest
213 $guest_type = $g > $arrpeople[$num]['adults'] ? 'child' : 'adult';
214
215 // set object data
216 $pax_field_obj->setGuestType($guest_type)
217 ->setFieldNumber($pax_field_ind)
218 ->setGuestNumber($g)
219 ->setGuestData($current_guest)
220 ->setRoomIndex($ind)
221 ->setBooking($this->order)
222 ->setBookingRooms($this->orderrooms)
223 ->setRoomGuests($arrpeople[$num]['adults'], $arrpeople[$num]['children'])
224 ->setTotalRooms(count($arrpeople));
225
226 // render input field
227 $pax_field_html = $pax_fields_obj->render($pax_field_obj);
228 if (empty($pax_field_html)) {
229 // nothing to display, increase the counter and continue
230 $pax_field_ind++;
231 continue;
232 }
233 }
234
235 // open field container by determining the classes to set
236 $fieldClasses = [];
237 if (($pax_fields_attributes[$paxk] ?? '') === 'file') {
238 $fieldClasses[] = 'vbo-precheckin-guest-detail-files';
239 if ($mrzDetectionSupport) {
240 $fieldClasses[] = 'vbo-precheckin-guest-files-mrz';
241 }
242 }
243 ?><div class="vbo-precheckin-guest-detail<?php echo $fieldClasses ? ' ' . implode(' ', $fieldClasses) : ''; ?>" data-rindex="<?php echo $ind; ?>" data-gindex="<?php echo $g; ?>" data-paxk="<?php echo JHtml::fetch('esc_attr', $paxk); ?>">
244 <label for="vbo-room-pax-field-<?php echo $ind . '-' . $g . '-' . $pax_field_ind; ?>"><?php echo $paxv; ?></label>
245 <?php
246
247 if ($field_rendering_type === 'paxfield') {
248 // render the custom pax-field input element
249
250 // access the implementor object
251 $implementor = $pax_fields_obj->getFieldTypeImplementor($pax_field_obj);
252
253 // check if a particular CSS class has been defined
254 $field_container_class = $implementor->getContainerClassAttr();
255 $field_container_class = !empty($field_container_class) ? " $field_container_class" : '';
256
257 // display the custom field type
258 ?>
259 <div class="vbo-precheckin-custom-field<?php echo $field_container_class; ?>">
260 <?php echo $pax_field_html; ?>
261 </div>
262 <?php
263 } elseif ($field_rendering_type === 'select') {
264 // select with choices
265 ?>
266 <select id="vbo-room-pax-field-<?php echo $ind . '-' . $g . '-' . $pax_field_ind; ?>" name="guests[<?php echo $ind; ?>][<?php echo $g; ?>][<?php echo $paxk; ?>]">
267 <option></option>
268 <?php
269 $opt_ind = 1;
270 foreach ($pax_fields_attributes[$paxk] as $attrk => $attrv) {
271 $paxv_selected = isset($current_guest[$paxk]) && ($current_guest[$paxk] == $attrv || (!is_numeric($attrk) && $current_guest[$paxk] == $attrk));
272 if (!$paxv_selected && isset($current_guest[$paxk]) && is_numeric($current_guest[$paxk])) {
273 /**
274 * It is safe to compare such select tags, which usually refer to "gender",
275 * to even numeric values in order to attempt to pre-select the current value.
276 */
277 $paxv_selected = ($opt_ind == $current_guest[$paxk]);
278 }
279 ?>
280 <option value="<?php echo !is_numeric($attrk) ? $attrk : $attrv; ?>"<?php echo ($paxv_selected ? ' selected="selected"' : ''); ?>><?php echo $attrv; ?></option>
281 <?php
282 $opt_ind++;
283 }
284 ?>
285 </select>
286 <?php
287 } elseif ($field_rendering_type === 'country') {
288 // field of type country
289 echo VikBooking::getCountriesSelect('guests['.$ind.']['.$g.']['.$paxk.']', $all_countries, (string) ($current_guest[$paxk] ?? ''), (string) $paxv, 'vbo-room-pax-field-' . $ind . '-' . $g . '-' . $pax_field_ind);
290 } elseif ($field_rendering_type === 'calendar') {
291 // datepicker calendar
292 ?>
293 <input type="text" autocomplete="off" data-gind="<?php echo $g; ?>" class="vbo-paxfield vbo-paxfield-datepicker" id="vbo-room-pax-field-<?php echo $ind . '-' . $g . '-' . $pax_field_ind; ?>" name="guests[<?php echo $ind; ?>][<?php echo $g; ?>][<?php echo $paxk; ?>]" value="<?php echo (isset($current_guest[$paxk]) ? $this->escape($current_guest[$paxk]) : ''); ?>" readonly />
294 <?php
295 } elseif ($field_rendering_type === 'file') {
296 // file upload (multiple files allowed)
297 ?>
298 <input type="hidden" id="vbo-paxfield-curfiles-<?php echo $ind . '-' . $g . '-' . $paxk; ?>" name="guests[<?php echo $ind; ?>][<?php echo $g; ?>][<?php echo $paxk; ?>]" value="<?php echo (isset($current_guest[$paxk]) ? $this->escape($current_guest[$paxk]) : ''); ?>" />
299
300 <div class="vbo-paxfield-upload-wrapper">
301 <?php
302 if ($mrzDetectionSupport) {
303 // display the proper instructions for guidance
304 ?>
305 <div class="vbo-paxfield-upload-instructions">
306 <span><?php VikBookingIcons::e('info-circle'); ?> <?php echo JText::translate('VBO_DOCS_UPLOAD_MRZ_HELP'); ?></span>
307 </div>
308 <?php
309 }
310 ?>
311
312 <div class="vbo-paxfield-upload-container">
313 <button type="button" class="btn vbo-pref-color-btn-secondary vbo-precheckin-uploadfile" data-roomi="<?php echo $ind; ?>" data-guesti="<?php echo $g; ?>" data-paxk="<?php echo JHtml::fetch('esc_attr', $paxk); ?>"><?php VikBookingIcons::e('camera'); ?> <?php echo JText::translate('VBOUPSELLADD'); ?></button>
314 <div class="vbo-paxfield-upload-progress-wrap" id="vbo-paxfield-upload-progress-<?php echo $ind . '-' . $g . '-' . $paxk; ?>" style="display: none;">
315 <div class="vbo-paxfield-upload-progress">&nbsp;</div>
316 </div>
317 </div>
318
319 <div class="vbo-paxfield vbo-paxfield-files" id="vbo-paxfield-files-<?php echo $ind . '-' . $g . '-' . $paxk; ?>" data-gselector="<?php echo $ind . '-' . $g . '-' . $paxk; ?>">
320 <?php
321 if (isset($current_guest[$paxk]) && !empty($current_guest[$paxk])) {
322 $guest_files = explode('|', $current_guest[$paxk]);
323 foreach ($guest_files as $gfk => $guest_file) {
324 if (empty($guest_file) || strpos($guest_file, 'http') !== 0) {
325 continue;
326 }
327 $furl_segments = explode('/', $guest_file);
328 $guest_fname = $furl_segments[(count($furl_segments) - 1)];
329 $read_fname = substr($guest_fname, (strpos($guest_fname, '_') + 1));
330 ?>
331 <div class="vbo-paxfield-file-uploaded">
332 <span class="vbo-paxfield-file-uploaded-rm"><?php VikBookingIcons::e('times-circle'); ?></span>
333 <a href="<?php echo JHtml::fetch('esc_attr', $guest_file); ?>" target="_blank">
334 <?php VikBookingIcons::e('image'); ?>
335 <span><?php echo htmlspecialchars($read_fname); ?></span>
336 </a>
337 </div>
338 <?php
339 }
340 }
341 ?>
342 </div>
343
344 </div>
345 <?php
346 } else {
347 // text
348 ?>
349 <input type="text" autocomplete="off" data-gind="<?php echo $g; ?>" class="vbo-paxfield" id="vbo-room-pax-field-<?php echo $ind . '-' . $g . '-' . $pax_field_ind; ?>" name="guests[<?php echo $ind; ?>][<?php echo $g; ?>][<?php echo $paxk; ?>]" value="<?php echo (isset($current_guest[$paxk]) ? $this->escape($current_guest[$paxk]) : ''); ?>" />
350 <?php
351 }
352
353 // close field container
354 ?>
355 </div><?php
356
357 // increase field counter
358 $pax_field_ind++;
359 }
360 ?>
361 </div>
362 </div>
363 <?php
364 }
365 ?>
366 </div>
367 </div>
368 </div>
369 <?php
370 }
371 ?>
372 <div class="vbo-precheckin-submit">
373 <button type="submit" class="btn btn-large vbo-pref-color-btn"><?php echo JText::translate('VBOSUBMITPRECHECKIN'); ?></button>
374 </div>
375 <?php echo JHtml::fetch('form.token'); ?>
376 </form>
377 </div>
378
379 </div>
380
381 <input type="file" id="vbo-precheckin-upload-field" accept="image/*,.pdf" multiple="multiple" style="display: none;" />
382
383 <script type="text/javascript">
384 /**
385 * Declare global scope variables
386 */
387 var vbo_prechin_current_room = null,
388 vbo_prechin_current_guest = null,
389 vbo_prechin_current_paxk = null,
390 vbo_typingtoast_displayed = false,
391 vbo_typingtoast_changes = 0;
392
393 const mrzDetection = <?php echo (int) $mrzDetectionSupport; ?>;
394
395 /**
396 * Displays a toast message
397 */
398 function vboPresentToast(content, duration, clickcallback) {
399 // remove any other previous toast from the document
400 jQuery('.vbo-toast-message').remove();
401 // build toast
402 var toast = jQuery('<div>').addClass('vbo-toast-message vbo-toast-message-presented');
403 // onclick function
404 var onclickfunc = function() {
405 // hide toast when clicked
406 jQuery(this).removeClass('vbo-toast-message-presented').addClass('vbo-toast-message-dimissed');
407 };
408 if (typeof clickcallback === 'function') {
409 onclickfunc = function() {
410 // launch callback
411 clickcallback.call(this);
412 // hide toast either way
413 jQuery(this).removeClass('vbo-toast-message-presented').addClass('vbo-toast-message-dimissed');
414 };
415 }
416 // register click event on toast
417 toast.on('click', onclickfunc);
418 // build toast content
419 var inner = jQuery('<div>').addClass('vbo-toast-message-content');
420 toast.append(inner.append(content));
421 // present toast
422 jQuery('body').append(toast);
423 // set timeout for auto-dismiss
424 if (typeof duration === 'undefined') {
425 duration = 4000;
426 }
427 if (!isNaN(duration) && duration > 0) {
428 // if duration NaN or <= 0, the toast won't be dismissed automatically
429 setTimeout(function() {
430 jQuery('.vbo-toast-message').removeClass('vbo-toast-message-presented').addClass('vbo-toast-message-dimissed');
431 }, duration);
432 }
433 }
434
435 /**
436 * Some older smartphones or tablets may not support files uploading
437 */
438 function vboIsUploadSupported() {
439 if (!navigator || !navigator.userAgent) {
440 return false;
441 }
442 if (navigator.userAgent.match(/(Android (1.0|1.1|1.5|1.6|2.0|2.1))|(Windows Phone (OS 7|8.0))|(XBLWP)|(ZuneWP)|(w(eb)?OSBrowser)|(webOS)|(Kindle\/(1.0|2.0|2.5|3.0))/)) {
443 return false;
444 } else {
445 return true;
446 }
447 }
448
449 /**
450 * Checks wether a jQuery XHR response object was due to a connection error.
451 * Property readyState 0 = Network Error (UNSENT), 4 = HTTP error (DONE).
452 * Property responseText may not be set in some browsers.
453 * This is what to check to determine if a connection error occurred.
454 */
455 function vboIsConnectionLostError(err) {
456 if (!err || !err.hasOwnProperty('status')) {
457 return false;
458 }
459
460 return (
461 err.statusText == 'error'
462 && err.status == 0
463 && (err.readyState == 0 || err.readyState == 4)
464 && (!err.hasOwnProperty('responseText') || err.responseText == '')
465 );
466 }
467
468 /**
469 * Ensures AJAX requests that fail due to connection errors are retried automatically.
470 * This function is made specifically to work with AJAX uploads.
471 */
472 function vboDoAjaxUpload(url, data, success, failure, progress, attempt) {
473 var VBO_AJAX_MAX_ATTEMPTS = 3;
474
475 if (attempt === undefined) {
476 attempt = 1;
477 }
478
479 var settings = {
480 type: 'post',
481 contentType: false,
482 processData: false,
483 cache: false,
484 };
485
486 // register event for upload progress
487 settings.xhr = function() {
488 var xhrobj = jQuery.ajaxSettings.xhr();
489 if (xhrobj.upload) {
490 // attach progress event
491 xhrobj.upload.addEventListener('progress', function(event) {
492 // calculate percentage
493 var percent = 0;
494 var position = event.loaded || event.position;
495 var total = event.total;
496 if (event.lengthComputable) {
497 percent = Math.ceil(position / total * 100);
498 }
499 // trigger callback
500 progress(percent);
501 }, false);
502 }
503 return xhrobj;
504 };
505
506 if (typeof url === 'object') {
507 // configuration object passed
508 Object.assign(settings, url);
509 } else {
510 // use the default settings
511 settings.url = url;
512 }
513
514 // set request data
515 settings.data = data;
516
517 return jQuery.ajax(
518 settings
519 ).done(function(resp) {
520 if (success !== undefined) {
521 // launch success callback function
522 success(resp);
523 }
524 }).fail(function(err) {
525 /**
526 * If the error is caused by a site connection lost, and if the number
527 * of retries is lower than max attempts, retry the same AJAX request.
528 */
529 if (attempt < VBO_AJAX_MAX_ATTEMPTS && vboIsConnectionLostError(err)) {
530 // delay the retry by half second
531 setTimeout(function() {
532 // relaunch same request and increase number of attempts
533 console.log('Retrying previous AJAX request');
534 vboDoAjaxUpload(url, data, success, failure, progress, (attempt + 1));
535 }, 500);
536 } else {
537 // launch the failure callback otherwise
538 if (failure !== undefined) {
539 failure(err);
540 }
541 }
542
543 // always log the error in console
544 console.log('AJAX request failed' + (err.status == 500 ? ' (' + err.responseText + ')' : ''), err);
545 });
546 }
547
548 /**
549 * Updates the progress bar for the current uploading process.
550 * If MRZ is supported, the upload progress will reach 100% before
551 * the request is actually completed.
552 */
553 function vboUploadSetProgress(selector_suffix, progress, mrzSupported) {
554 progress = Math.max(0, progress);
555 progress = Math.min(100, progress);
556
557 const progress_wrap = document.querySelector('#vbo-paxfield-upload-progress-' + selector_suffix);
558 if (!progress_wrap) {
559 return;
560 }
561
562 const progressEl = progress_wrap.querySelector('.vbo-paxfield-upload-progress');
563
564 // update progress element width
565 progressEl.style.width = progress + '%';
566
567 // update progress element content
568 if (progress >= 99 && mrzSupported) {
569 // display status message for analysing documents through MRZ
570 progressEl.textContent = <?php echo json_encode(JText::translate('VBO_ANALYSING_DOCS')); ?> + '...';
571 } else {
572 // update progress content
573 progressEl.textContent = progress + '%';
574 }
575 }
576
577 /**
578 * Uploads the selected document(s)
579 */
580 function vboUploadDocuments(files) {
581 // create form data object
582 const formData = new FormData();
583
584 // set booking and pre-checkin information
585 formData.append('sid', '<?php echo empty($this->order['sid']) && !empty($this->order['idorderota']) ? $this->order['idorderota'] : $this->order['sid']; ?>');
586 formData.append('ts', '<?php echo $this->order['ts']; ?>');
587 formData.append('room_index', vbo_prechin_current_room);
588 formData.append('guest_index', vbo_prechin_current_guest);
589 formData.append('pax_index', vbo_prechin_current_paxk);
590
591 if (mrzDetection) {
592 formData.append('mrz', 1);
593 }
594
595 // selector suffix is composed of room, guest and pax field indexes
596 let selector_suffix = vbo_prechin_current_room + '-' + vbo_prechin_current_guest + '-' + vbo_prechin_current_paxk;
597
598 // iterate files selected and append them to the form data
599 for (let i = 0; i < files.length; i++) {
600 formData.append('docs[]', files[i]);
601 }
602
603 // display progress wrap
604 jQuery('#vbo-paxfield-upload-progress-' + selector_suffix).show();
605
606 // AJAX request to upload the files selected
607 vboDoAjaxUpload(
608 // url
609 '<?php echo VikBooking::ajaxUrl(JRoute::rewrite('index.php?option=com_vikbooking&task=precheckin_upload_docs&tmpl=component'.(!empty($pitemid) ? '&Itemid='.$pitemid : ''), false)); ?>',
610 // form data
611 formData,
612 // success callback
613 (response) => {
614 // hide progress wrap
615 jQuery('#vbo-paxfield-upload-progress-' + selector_suffix).hide();
616 // unset progress
617 vboUploadSetProgress(selector_suffix, 0);
618
619 // parse response
620 try {
621 const obj_res = typeof response === 'string' ? JSON.parse(response) : response;
622 const uploaded_urls = [];
623
624 for (let i in (obj_res.uploads || [])) {
625 if (!obj_res.uploads.hasOwnProperty(i) || !obj_res.uploads[i].hasOwnProperty('url')) {
626 continue;
627 }
628 uploaded_urls.push(obj_res.uploads[i]['url']);
629 }
630
631 if (!uploaded_urls.length) {
632 console.error('no valid URLs returned', response);
633 return false;
634 }
635
636 // update hidden field
637 let hidden_inp = jQuery('#vbo-paxfield-curfiles-' + selector_suffix);
638 let current_guest_files = hidden_inp.val().split('|');
639 if (!current_guest_files.length || !current_guest_files[0].length) {
640 current_guest_files = [];
641 }
642
643 // merge current files with new ones uploaded
644 let new_guest_files = current_guest_files.concat(uploaded_urls);
645
646 // update hidden input field to contain all files
647 hidden_inp.val(new_guest_files.join('|'));
648
649 // display links for the newly uploaded files
650 let uploaded_content = '';
651 for (let i = 0; i < uploaded_urls.length; i++) {
652 let furl_segments = uploaded_urls[i].split('/');
653 let guest_fname = furl_segments[(furl_segments.length - 1)];
654 let read_fname = guest_fname.substr((guest_fname.indexOf('_') + 1));
655
656 uploaded_content += '<div class="vbo-paxfield-file-uploaded">';
657 uploaded_content += ' <span class="vbo-paxfield-file-uploaded-rm"><?php VikBookingIcons::e('times-circle'); ?></span>';
658 uploaded_content += ' <a href="' + uploaded_urls[i] + '" target="_blank">';
659 uploaded_content += ' <?php VikBookingIcons::e('image'); ?>';
660 uploaded_content += ' <span>' + read_fname + '</span>';
661 uploaded_content += ' </a>';
662 uploaded_content += '</div>';
663 }
664
665 // append the new content
666 jQuery('#vbo-paxfield-files-' + selector_suffix).append(uploaded_content);
667
668 // check if some fields should be populated from MRZ data detection
669 if (mrzDetection && obj_res?.mrz?.data) {
670 // iterate MRZ data properties map
671 obj_res.mrz.data.forEach((propertyMap) => {
672 if (!propertyMap?.id) {
673 // unexpected property format
674 return;
675 }
676
677 // set the value to apply to field
678 let propertyValue = propertyMap?.value || '';
679
680 // select field container
681 let fieldContainer = document.querySelector('.vbo-precheckin-guest-detail[data-rindex="' + vbo_prechin_current_room + '"][data-gindex="' + vbo_prechin_current_guest + '"][data-paxk="' + propertyMap.id + '"]');
682 if (!fieldContainer) {
683 // no matching element found in DOM
684 return;
685 }
686
687 // select input element
688 let paxFieldElement = fieldContainer.querySelector('input[name*="' + propertyMap.id + '"], textarea[name*="' + propertyMap.id + '"], select[name*="' + propertyMap.id + '"]');
689 if (!paxFieldElement) {
690 // no matching input element found in DOM
691 return;
692 }
693
694 // set field value
695 if (paxFieldElement.matches('input[type="checkbox"]')) {
696 // checkbox input field
697 paxFieldElement.checked = propertyValue ? true : false;
698 } else {
699 // input field accepts value property
700 paxFieldElement.value = propertyValue;
701 }
702
703 // attempt to trigger the change event on the field element
704 paxFieldElement.dispatchEvent(new Event('change'));
705 });
706 }
707
708 // display toast message
709 vboPresentToast(Joomla.JText._('VBO_PRECHECKIN_TOAST_HELP'), 4000, function() {
710 jQuery('html,body').animate({scrollTop: jQuery('.vbo-precheckin-submit').offset().top - 100}, {duration: 400});
711 });
712 } catch(err) {
713 console.error('could not parse JSON response for uploading documents', err, response);
714 }
715 },
716 // failure callback
717 (error) => {
718 alert(Joomla.JText._('VBO_UPLOAD_FAILED'));
719 // hide progress wrap
720 jQuery('#vbo-paxfield-upload-progress-' + selector_suffix).hide();
721 // unset progress
722 vboUploadSetProgress(selector_suffix, 0);
723
724 console.error(error);
725 },
726 // progress callback
727 (progress) => {
728 // update progress bar
729 vboUploadSetProgress(selector_suffix, progress, mrzDetection);
730 }
731 );
732 }
733
734 /**
735 * Declare functions for DOM ready
736 */
737 jQuery(function() {
738
739 /**
740 * Datepicker for birth date
741 */
742 jQuery('.vbo-paxfield-datepicker').datepicker({
743 dateFormat: "<?php echo $juidf; ?>",
744 changeMonth: true,
745 changeYear: true,
746 yearRange: "<?php echo (date('Y') - 100) . ':' . date('Y'); ?>",
747 maxDate: "0d",
748 });
749
750 /**
751 * Click event on file-upload button
752 */
753 jQuery('.vbo-precheckin-uploadfile').click(function() {
754 var elem = jQuery(this);
755
756 var room_index = elem.data('roomi'),
757 guest_index = elem.data('guesti'),
758 pax_index = elem.data('paxk');
759
760 // check if device supports file upload
761 if (!vboIsUploadSupported()) {
762 alert('Your device may not support files uploading');
763 return false;
764 }
765
766 // update global vars
767 vbo_prechin_current_room = room_index;
768 vbo_prechin_current_guest = guest_index;
769 vbo_prechin_current_paxk = pax_index;
770
771 // trigger the click event on the hidden input field for the files upload
772 jQuery('#vbo-precheckin-upload-field').trigger('click');
773 });
774
775 /**
776 * Change event on global file-upload hidden field
777 */
778 jQuery('#vbo-precheckin-upload-field').on('change', function(e) {
779 // get files selected
780 var files = jQuery(this)[0].files;
781
782 if (!files || !files.length) {
783 console.error('no files selected for upload');
784 return false;
785 }
786
787 // upload selected files
788 vboUploadDocuments(files);
789
790 // make the input value empty
791 jQuery(this).val(null);
792 });
793
794 /**
795 * Click event on the button to remove an uploaded file
796 */
797 jQuery(document.body).on('click', '.vbo-paxfield-file-uploaded-rm', function() {
798 var file_container = jQuery(this).closest('.vbo-paxfield-file-uploaded');
799 if (!file_container.length) {
800 return false;
801 }
802 var file_url = file_container.find('a').attr('href');
803 var files_selector = file_container.closest('.vbo-paxfield-files').attr('data-gselector');
804 if (confirm(Joomla.JText._('VBO_REMOVEF_CONFIRM'))) {
805 var pax_elem = jQuery('#vbo-paxfield-curfiles-' + files_selector);
806 var pax_urls = pax_elem.val();
807 if (pax_urls.indexOf(file_url + '|') >= 0) {
808 pax_urls = pax_urls.replace(file_url + '|', '');
809 } else if (pax_urls.indexOf('|' + file_url) >= 0) {
810 pax_urls = pax_urls.replace('|' + file_url, '');
811 } else {
812 pax_urls = pax_urls.replace(file_url, '');
813 }
814 // update hidden input value
815 pax_elem.val(pax_urls);
816 // remove the file container
817 file_container.remove();
818 // display toast message
819 vboPresentToast(Joomla.JText._('VBO_PRECHECKIN_TOAST_HELP'), 4000, function() {
820 jQuery('html,body').animate({scrollTop: jQuery('.vbo-precheckin-submit').offset().top - 100}, {duration: 400});
821 });
822 }
823 });
824
825 /**
826 * Change event on input fields to trigger the toast message
827 */
828 jQuery('.vbo-precheckin-room-wrapper').find('input[type="text"], select').on('change', function() {
829 // if toast displayed and clicked, unset listener
830 if (vbo_typingtoast_displayed === true) {
831 jQuery('.vbo-precheckin-room-wrapper').find('input[type="text"], select').off('change');
832 }
833 // increase counter
834 vbo_typingtoast_changes++;
835 if (vbo_typingtoast_changes > 2) {
836 // reset counter
837 vbo_typingtoast_changes = 0;
838 // display toast message
839 vboPresentToast(Joomla.JText._('VBO_PRECHECKIN_TOAST_HELP'), 4000, function() {
840 jQuery('html,body').animate({scrollTop: jQuery('.vbo-precheckin-submit').offset().top - 100}, {duration: 400});
841 // if toast is clicked, we never display it again
842 vbo_typingtoast_displayed = true;
843 });
844 }
845 });
846
847 /**
848 * Pre-submit form data validation.
849 */
850 jQuery('#vbo-precheckin-main-form').on('submit', function(e) {
851 // prevent immediate data submission
852 e.preventDefault();
853
854 // disable submit button to avoid duplicate submissions
855 jQuery(this).find('button[type="submit"]').prop('disabled', true);
856
857 try {
858 // build form data for fields validation
859 const validateForm = new FormData;
860
861 // scan all form entries
862 const pcForm = new FormData(document.querySelector('#vbo-precheckin-main-form'));
863 for (const pair of pcForm.entries()) {
864 if (!pair[0] || pair[0].indexOf('guests') !== 0) {
865 // we only want to validate the form "guests" fields
866 continue;
867 }
868 // append field and related value for validation
869 validateForm.append(pair[0], pair[1]);
870 }
871
872 // append booking values for identification
873 validateForm.append('sid', '<?php echo empty($this->order['sid']) && !empty($this->order['idorderota']) ? $this->order['idorderota'] : $this->order['sid']; ?>');
874 validateForm.append('ts', '<?php echo $this->order['ts']; ?>');
875
876 // build query parameters for validation
877 let qpValidation = new URLSearchParams(validateForm).toString();
878
879 // display toast message
880 vboPresentToast(Joomla.JText._('VBO_PLEASE_WAIT'), 5000);
881
882 // validate guest registration fields
883 VBOCore.doAjax(
884 "<?php echo VikBooking::ajaxUrl('index.php?option=com_vikbooking&task=checkin.validatePrecheckinFields'); ?>",
885 qpValidation,
886 (response) => {
887 // unregister form submit event
888 jQuery('#vbo-precheckin-main-form').off('submit');
889 // propagate the form submission in case of success
890 jQuery('#vbo-precheckin-main-form').submit();
891 },
892 (error) => {
893 // display error
894 alert(error.responseText || 'Could not validate fields');
895 // enable submit button to allow re-validation
896 jQuery(this).find('button[type="submit"]').prop('disabled', false);
897 }
898 );
899 } catch(error) {
900 console.error(error);
901 // unregister form submit event
902 jQuery('#vbo-precheckin-main-form').off('submit');
903 // submit the form without fields validation
904 jQuery('#vbo-precheckin-main-form').submit();
905 }
906 });
907
908 });
909 </script>
910