PluginProbe
VikBooking Hotel Booking Engine & PMS / 1.6.6
VikBooking Hotel Booking Engine & PMS v1.6.6
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 / admin / helpers / widgets / bookings_calendar.php

bookings_calendar.php in VikBooking Hotel Booking Engine & PMS 1.6.6, at admin/helpers/widgets/bookings_calendar.php

2,601 lines 94.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 com_vikbooking
5 * @author Alessio Gaggii - e4j - Extensionsforjoomla.com
6 * @copyright Copyright (C) 2018 e4j - Extensionsforjoomla.com. 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 /**
14 * Class handler for admin widget "bookings calendar".
15 *
16 * @since 1.15.0 (J) - 1.5.0 (WP)
17 */
18 class VikBookingAdminWidgetBookingsCalendar extends VikBookingAdminWidget
19 {
20 /**
21 * The instance counter of this widget. Since we do not load individual parameters
22 * for each widget's instance, we use a static counter to determine its settings.
23 *
24 * @var int
25 */
26 protected static $instance_counter = -1;
27
28 /**
29 * Default number of bookings per page.
30 *
31 * @var int
32 */
33 protected $bookings_per_page = 6;
34
35 /**
36 * Class constructor will define the widget name and identifier.
37 */
38 public function __construct()
39 {
40 // call parent constructor
41 parent::__construct();
42
43 $this->widgetName = JText::translate('VBO_W_BOOKSCAL_TITLE');
44 $this->widgetDescr = JText::translate('VBO_W_BOOKSCAL_DESCR');
45 $this->widgetId = basename(__FILE__, '.php');
46
47 // define widget and icon and style name
48 $this->widgetIcon = '<i class="' . VikBookingIcons::i('calendar') . '"></i>';
49 $this->widgetStyleName = 'brown';
50 }
51
52 /**
53 * Custom method for this widget only to load the bookings calendar.
54 * The method is called by the admin controller through an AJAX request.
55 * The visibility should be public, it should not exit the process, and
56 * any content sent to output will be returned to the AJAX response.
57 * In this case we return an array because this method requires "return":1.
58 */
59 public function loadBookingsCalendar()
60 {
61 // get today's date
62 $today_ymd = date('Y-m-d');
63
64 $offset = VikRequest::getString('offset', $today_ymd, 'request');
65 $wrapper = VikRequest::getString('wrapper', '', 'request');
66 $room_id = VikRequest::getInt('room_id', 0, 'request');
67 $date_dir = VikRequest::getInt('date_dir', 0, 'request');
68 $bid = VikRequest::getInt('bid', 0, 'request');
69 $bid = !empty($date_dir) ? 0 : $bid;
70
71 // the booking ID can be passed in case of multitask data for this page
72 if (!empty($bid)) {
73 // load the availability of the month when booking starts
74 $booking_info = VikBooking::getBookingInfoFromID($bid);
75 if ($booking_info) {
76 // force the offset to use as start date
77 $offset = date('Y-m-d', $booking_info['checkin']);
78 }
79 }
80
81 // calculate date timestamps interval
82 $now_info = getdate(strtotime($offset));
83 if ($date_dir > 0) {
84 // next month from current offset
85 $from_ts = mktime(0, 0, 0, ($now_info['mon'] + 1), 1, $now_info['year']);
86 $to_ts = mktime(23, 59, 59, ($now_info['mon'] + 1), date('t', $from_ts), $now_info['year']);
87 } elseif ($date_dir < 0) {
88 // prev month from current offset
89 $from_ts = mktime(0, 0, 0, ($now_info['mon'] - 1), 1, $now_info['year']);
90 $to_ts = mktime(23, 59, 59, ($now_info['mon'] - 1), date('t', $from_ts), $now_info['year']);
91 } else {
92 // no navigation, use current offset
93 $from_ts = mktime(0, 0, 0, $now_info['mon'], 1, $now_info['year']);
94 $to_ts = mktime(23, 59, 59, $now_info['mon'], date('t', $now_info[0]), $now_info['year']);
95 }
96
97 // build week days list according to settings
98 $firstwday = (int)VikBooking::getFirstWeekDay();
99 $days_labels = array(
100 JText::translate('VBSUN'),
101 JText::translate('VBMON'),
102 JText::translate('VBTUE'),
103 JText::translate('VBWED'),
104 JText::translate('VBTHU'),
105 JText::translate('VBFRI'),
106 JText::translate('VBSAT'),
107 );
108 $days_indexes = [];
109 for ($i = 0; $i < 7; $i++) {
110 $days_indexes[$i] = (6 - ($firstwday - $i) + 1) % 7;
111 }
112
113 // currency symbol
114 $currencysymb = VikBooking::getCurrencySymb();
115
116 // start looping from the first day of the current month
117 $info_arr = getdate($from_ts);
118
119 // build period name
120 $period_date = VikBooking::sayMonth($info_arr['mon']) . ' ' . $info_arr['year'];
121
122 // invoke availability helper class
123 $av_helper = VikBooking::getAvailabilityInstance();
124
125 // get all rooms and tax rates
126 $all_rooms = $av_helper->loadRooms();
127 $tax_rates = $av_helper->getTaxRates();
128
129 // count maximum units available depending on filter
130 $tot_rooms = 1;
131 if (!empty($room_id) && isset($all_rooms[$room_id])) {
132 // use the units of the filtered room
133 $max_units = $all_rooms[$room_id]['units'];
134 } else {
135 // sum all room units
136 $max_units = 0;
137 $tot_rooms = count($all_rooms);
138 foreach ($all_rooms as $rid => $room) {
139 $max_units += $room['units'];
140 }
141 }
142
143 // build "search name"
144 if (!empty($room_id) && isset($all_rooms[$room_id])) {
145 $search_name = $all_rooms[$room_id]['name'];
146 } else {
147 $search_name = preg_replace("/[^A-Za-z0-9 ]/", '', JText::translate('VBOSTATSALLROOMS'));
148 }
149
150 // load busy records
151 $room_filter = !empty($room_id) ? array($room_id) : array();
152 $busy_records = VikBooking::loadBusyRecords($room_filter, $from_ts, $to_ts);
153
154 // load festivities or room-day notes
155 $festivities = [];
156 $rday_notes = [];
157 if (!empty($room_id) && isset($all_rooms[$room_id])) {
158 // load room-day notes for the given room id
159 $rday_notes = VikBooking::getCriticalDatesInstance()->loadRoomDayNotes(date('Y-m-d', $from_ts), date('Y-m-d', $to_ts), $room_id);
160 } else {
161 // load festivities when no specific room filter set
162 $fests = VikBooking::getFestivitiesInstance();
163 if ($fests->shouldCheckFestivities()) {
164 $fests->storeNextFestivities();
165 }
166 $festivities = $fests->loadFestDates(date('Y-m-d', $from_ts), date('Y-m-d', $to_ts));
167 }
168
169 // date format
170 $dtpicker_df = $this->getDateFormat('jui');
171
172 // start output buffering
173 ob_start();
174
175 // generate calendar
176 $d_count = 0;
177 $mon_lim = $info_arr['mon'];
178 $next_offset = date('Y-m-d', $from_ts);
179
180 ?>
181 <div class="vbo-widget-booskcal-mday-wrap" style="display: none;">
182 <div class="vbo-widget-booskcal-mday-head">
183 <a class="vbo-widget-booskcal-mday-back" href="JavaScript: void(0);" onclick="vboWidgetBooksCalMonth('<?php echo $wrapper; ?>');"><?php VikBookingIcons::e('chevron-left'); ?> <?php echo $period_date; ?></a>
184 <span class="vbo-widget-booskcal-mday-name"></span>
185 </div>
186 <div class="vbo-dashboard-guests-latest vbo-widget-booskcal-mday-list" data-ymd="" data-offset="0" data-length="<?php echo $this->bookings_per_page; ?>"></div>
187 </div>
188
189 <div class="vbo-widget-booskcal-newbook-wrap" data-ymd="" data-roomid="" style="display: none;">
190 <div class="vbo-widget-booskcal-newbook-head">
191 <a class="vbo-widget-booskcal-newbook-back" href="JavaScript: void(0);" onclick="vboWidgetBooksCalMonth('<?php echo $wrapper; ?>');"><?php VikBookingIcons::e('chevron-left'); ?> <?php echo JText::translate('VBANNULLA'); ?></a>
192 <span class="vbo-widget-booskcal-newbook-name"><?php echo JText::translate('VBQUICKBOOK'); ?></span>
193 </div>
194 <div class="vbo-widget-booskcal-newbook-cont">
195 <div class="vbo-admin-container vbo-admin-container-full vbo-admin-container-compact">
196 <div class="vbo-params-wrap">
197 <div class="vbo-params-container">
198
199 <div class="vbo-param-container">
200 <div class="vbo-param-label"><?php echo JText::translate('VBPICKUPAT'); ?></div>
201 <div class="vbo-param-setting">
202 <div class="vbo-field-calendar">
203 <div class="input-append">
204 <input type="text" class="vbo-widget-bookscal-checkindt" value="" autocomplete="off" />
205 <button type="button" class="btn btn-secondary vbo-widget-bookscal-checkindt-trigger"><?php VikBookingIcons::e('calendar'); ?></button>
206 </div>
207 </div>
208 </div>
209 </div>
210
211 <div class="vbo-param-container">
212 <div class="vbo-param-label"><?php echo JText::translate('VBRELEASEAT'); ?></div>
213 <div class="vbo-param-setting">
214 <div class="vbo-field-calendar">
215 <div class="input-append">
216 <input type="text" class="vbo-widget-bookscal-checkoutdt" value="" autocomplete="off" />
217 <button type="button" class="btn btn-secondary vbo-widget-bookscal-checkoutdt-trigger"><?php VikBookingIcons::e('calendar'); ?></button>
218 </div>
219 </div>
220 </div>
221 </div>
222
223 <div class="vbo-param-container vbo-toggle-small">
224 <div class="vbo-param-label"><?php echo JText::translate('VBSUBMCLOSEROOM'); ?></div>
225 <div class="vbo-param-setting">
226 <?php echo $this->vbo_app->printYesNoButtons('closeroom', JText::translate('VBYES'), JText::translate('VBNO'), 0, 1, 0, "vboWidgetBooksCalClosure('{$wrapper}');"); ?>
227 </div>
228 </div>
229
230 <div class="vbo-param-container" data-noclosure="1">
231 <div class="vbo-param-label"><?php echo JText::translate('VBOCUSTOMER'); ?></div>
232 <div class="vbo-param-setting">
233 <span class="vbo-assign-customer" onclick="vboWidgetBooksCalAssignCustomer('<?php echo $wrapper; ?>');">
234 <?php VikBookingIcons::e('user-circle'); ?>
235 <span><?php echo JText::translate('VBFILLCUSTFIELDS'); ?></span>
236 </span>
237 <input type="hidden" value="" class="vbo-widget-bookscal-custid" />
238 <input type="hidden" value="" class="vbo-widget-bookscal-custmail" />
239 <input type="hidden" value="" class="vbo-widget-bookscal-custdata" />
240 <input type="hidden" value="" class="vbo-widget-bookscal-country" />
241 <input type="hidden" value="" class="vbo-widget-bookscal-state" />
242 <input type="hidden" value="" class="vbo-widget-bookscal-phone" />
243 <input type="hidden" value="" class="vbo-widget-bookscal-tfname" />
244 <input type="hidden" value="" class="vbo-widget-bookscal-tlname" />
245 <input type="hidden" value="" class="vbo-widget-bookscal-roomcost" />
246 <input type="hidden" value="" class="vbo-widget-bookscal-idprice" />
247 </div>
248 </div>
249
250 <div class="vbo-param-container" data-noclosure="1">
251 <div class="vbo-param-label"><?php echo JText::translate('VBPVIEWROOMSEVEN'); ?></div>
252 <div class="vbo-param-setting">
253 <input type="number" class="vbo-input-number-small vbo-widget-bookscal-units" value="1" min="1" max="99" onchange="vboWidgetBooksCalGetWebsiteRates('<?php echo $wrapper; ?>');" />
254 </div>
255 </div>
256
257 <div class="vbo-param-container" data-noclosure="1">
258 <div class="vbo-param-label"><?php echo JText::translate('VBPVIEWORDERSPEOPLE'); ?></div>
259 <div class="vbo-param-setting">
260 <span class="vbo-quickres-aduchi-wrap">
261 <span class="vbo-quickres-aduchi-inlbl"><?php echo JText::translate('VBEDITORDERADULTS'); ?></span>
262 <input type="number" class="vbo-input-number-small vbo-widget-bookscal-adults" value="2" min="0" max="99" onchange="vboWidgetBooksCalGetWebsiteRates('<?php echo $wrapper; ?>');" />
263 </span>
264 <span class="vbo-quickres-aduchi-wrap">
265 <span class="vbo-quickres-aduchi-inlbl"><?php echo JText::translate('VBEDITORDERCHILDREN'); ?></span>
266 <input type="number" class="vbo-input-number-small vbo-widget-bookscal-children" value="0" min="0" max="99" />
267 </span>
268 </div>
269 </div>
270
271 <div class="vbo-param-container vbo-website-rates-row" data-noclosure="1" data-unavailable="1" style="display: none;">
272 <div class="vbo-param-label"><?php echo JText::translate('VBOWEBSITERATES'); ?></div>
273 <div class="vbo-param-setting">
274 <div class="vbo-website-rates-cont"></div>
275 </div>
276 </div>
277
278 <div class="vbo-param-container vbo-row-custcost" data-noclosure="1">
279 <div class="vbo-param-label"><?php echo JText::translate('VBOROOMCUSTRATEPLANADD'); ?></div>
280 <div class="vbo-param-setting">
281 <div class="vbo-calendar-costs-wrapper">
282 <?php echo $currencysymb; ?> <input type="number" class="vbo-widget-bookscal-custcost" value="" step="any" min="0" onfocus="vboWidgetBooksCalFocusTaxes('<?php echo $wrapper; ?>');" />
283 <?php
284 if ($tax_rates) {
285 ?>
286 <select class="vbo-widget-bookscal-taxid" style="display: none;">
287 <option value=""><?php echo JText::translate('VBNEWOPTFOUR'); ?></option>
288 <?php
289 foreach ($tax_rates as $kiv => $iv) {
290 ?>
291 <option value="<?php echo $iv['id']; ?>"<?php echo $kiv < 1 ? ' selected="selected"' : ''; ?>><?php echo empty($iv['name']) ? "{$iv['aliq']}%" : "{$iv['name']} - {$iv['aliq']}%"; ?></option>
292 <?php
293 }
294 ?>
295 </select>
296 <?php
297 }
298 ?>
299 </div>
300 </div>
301 </div>
302
303 <div class="vbo-param-container vbo-param-confirm-btn">
304 <div class="vbo-param-label"></div>
305 <div class="vbo-param-setting">
306 <button type="button" class="btn btn-success vbo-btn-wide" onclick="vboWidgetBooksCalSaveBooking('<?php echo $wrapper; ?>');"><?php VikBookingIcons::e('save'); ?> <?php echo JText::translate('VBSAVE'); ?></button>
307 </div>
308 </div>
309
310 </div>
311 </div>
312 </div>
313 </div>
314 </div>
315
316 <table class="vbadmincaltable vbo-widget-booskcal-calendar-table">
317 <tbody>
318 <tr class="vbadmincaltrmdays">
319 <?php
320 // display week days in the proper order
321 for ($i = 0; $i < 7; $i++) {
322 $d_ind = ($i + $firstwday) < 7 ? ($i + $firstwday) : ($i + $firstwday - 7);
323 ?>
324 <td class="vbo-widget-booskcal-cell-wday"><?php echo $days_labels[$d_ind]; ?></td>
325 <?php
326 }
327 ?>
328 </tr>
329 <tr>
330 <?php
331 // display empty cells until the first week-day of the month
332 for ($i = 0, $n = $days_indexes[$info_arr['wday']]; $i < $n; $i++, $d_count++) {
333 ?>
334 <td class="vbo-widget-booskcal-cell-mday vbo-widget-booskcal-cell-empty">&nbsp;</td>
335 <?php
336 }
337 // display month days
338 while ($info_arr['mon'] == $mon_lim) {
339 if ($d_count > 6) {
340 $d_count = 0;
341 // close current row and open a new one
342 echo "\n</tr>\n<tr>\n";
343 }
344 // count units booked on this day
345 $tot_units_booked = 0;
346 $cell_classes = [];
347 $cell_bids = [];
348 foreach ($busy_records as $rid => $rbusy) {
349 foreach ($rbusy as $b) {
350 $tmpone = getdate($b['checkin']);
351 $ritts = mktime(0, 0, 0, $tmpone['mon'], $tmpone['mday'], $tmpone['year']);
352 $tmptwo = getdate($b['checkout']);
353 $conts = mktime(0, 0, 0, $tmptwo['mon'], $tmptwo['mday'], $tmptwo['year']);
354 if ($info_arr[0] >= $ritts && $info_arr[0] < $conts) {
355 // increase units booked
356 $tot_units_booked++;
357 if ($tot_rooms === 1) {
358 if (!empty($b['closure'])) {
359 // hightlight that this was a closure
360 $cell_classes[] = 'busy-closure';
361 } elseif (!empty($b['sharedcal'])) {
362 // hightlight that this was a reflection from a shared calendar
363 $cell_classes[] = 'busy-sharedcalendar';
364 }
365 }
366 // check if we can push the booking ID involved
367 if (!empty($b['idorder'])) {
368 $cell_bids[] = $b['idorder'];
369 }
370 }
371 }
372 }
373 // check status for this day
374 if ($tot_units_booked > 0) {
375 if ($tot_units_booked < $max_units) {
376 // prepend the "partially-busy" class
377 array_unshift($cell_classes, 'vbo-partially');
378 }
379 // prepend the "busy" cell class so that this will be first
380 array_unshift($cell_classes, 'busy');
381 } else {
382 // set the "free" cell class
383 $cell_classes[] = 'free';
384 }
385 // set ymd values
386 $cell_ymd = date('Y-m-d', $info_arr[0]);
387 if ($cell_ymd == $today_ymd) {
388 // set the "today" cell class
389 $cell_classes[] = 'is-today';
390 }
391 $cell_day_read = VikBooking::sayWeekDay($info_arr['wday']) . ' ' . $info_arr['mday'];
392
393 // count values for this day
394 $has_fests = isset($festivities[$cell_ymd]);
395 $rdnotes_key = $cell_ymd . '_' . $room_id . '_0';
396 $has_rdnotes = isset($rday_notes[$rdnotes_key]);
397
398 ?>
399 <td class="vbo-widget-booskcal-cell-mday <?php echo implode(' ', $cell_classes); ?>" onclick="vboWidgetBooksCalMday('<?php echo $wrapper; ?>', this);" data-bids="<?php echo implode(',', array_unique($cell_bids)); ?>" data-ymd="<?php echo $cell_ymd; ?>" data-dayread="<?php echo htmlspecialchars($cell_day_read); ?>">
400 <span class="vbo-widget-booskcal-mday-val"><?php echo $info_arr['mday']; ?></span>
401 <?php
402 if ($has_fests || $has_rdnotes) {
403 ?>
404 <div class="vbo-widget-booskcal-mday-info">
405 <?php
406 if ($has_fests) {
407 ?>
408 <span class="vbo-widget-booskcal-mday-fests"><?php VikBookingIcons::e('birthday-cake'); ?></span>
409 <?php
410 }
411 if ($has_rdnotes) {
412 ?>
413 <span class="vbo-widget-booskcal-mday-rdnotes"><?php VikBookingIcons::e('sticky-note'); ?></span>
414 <?php
415 }
416 ?>
417 </div>
418 <?php
419 }
420 ?>
421 </td>
422 <?php
423 $dayts = mktime(0, 0, 0, $info_arr['mon'], ($info_arr['mday'] + 1), $info_arr['year']);
424 $info_arr = getdate($dayts);
425 $d_count++;
426 }
427 // add empty cells until the end of the row
428 for ($i = $d_count; $i <= 6; $i++) {
429 ?>
430 <td class="vbo-widget-booskcal-cell-mday vbo-widget-booskcal-cell-empty">&nbsp;</td>
431 <?php
432 }
433 ?>
434 </tr>
435 </tbody>
436 </table>
437
438 <script type="text/javascript">
439
440 // render datepicker calendar for dates navigation
441 jQuery('#<?php echo $wrapper; ?>').find('.vbo-widget-bookscal-checkindt, .vbo-widget-bookscal-checkoutdt').datepicker({
442 minDate: "-1m",
443 maxDate: "+3y",
444 yearRange: "<?php echo date('Y'); ?>:<?php echo (date('Y') + 3); ?>",
445 changeMonth: true,
446 changeYear: true,
447 dateFormat: "<?php echo $dtpicker_df; ?>",
448 onSelect: function(selectedDate) {
449 if (!selectedDate) {
450 return;
451 }
452 if (jQuery(this).hasClass('vbo-widget-bookscal-checkindt')) {
453 let nowstart = jQuery(this).datepicker('getDate');
454 let nowstartdate = new Date(nowstart.getTime());
455 jQuery('.vbo-widget-bookscal-checkoutdt').datepicker('option', {minDate: nowstartdate});
456 }
457 // calculate website rates
458 vboWidgetBooksCalGetWebsiteRates('<?php echo $wrapper; ?>');
459 }
460 });
461
462 // triggering for datepicker calendar icon
463 jQuery('#<?php echo $wrapper; ?>').find('.vbo-widget-bookscal-checkindt-trigger, .vbo-widget-bookscal-checkoutdt-trigger').click(function() {
464 var jdp = jQuery(this).parent().find('input.hasDatepicker');
465 if (jdp.length) {
466 jdp.focus();
467 }
468 });
469
470 // check room units
471 var vbo_bookscal_roomfilt = jQuery('#<?php echo $wrapper; ?>').find('.vbo-booskcal-roomid');
472 if (vbo_bookscal_roomfilt.val()) {
473 var vbo_bookscal_room_units = vbo_bookscal_roomfilt.find('option:selected').attr('data-units');
474 if (vbo_bookscal_room_units > 1) {
475 jQuery('#<?php echo $wrapper; ?>').find('.vbo-widget-bookscal-units').attr('max', vbo_bookscal_room_units);
476 } else {
477 jQuery('#<?php echo $wrapper; ?>').find('.vbo-widget-bookscal-units').closest('[data-noclosure="1"]').hide();
478 }
479 } else {
480 jQuery('#<?php echo $wrapper; ?>').find('.vbo-widget-bookscal-units').closest('[data-noclosure="1"]').hide();
481 }
482
483 <?php
484 // check if a specific day was requested through the widget options
485 $force_day = $this->getOption('day', '');
486 if ($force_day && preg_match("/^[0-9]{4}-[0-9]{2}-[0-9]{2}$/", $force_day)) {
487 // trigger the click event over the requested day
488 ?>
489 setTimeout(() => {
490 jQuery('.vbo-widget-booskcal-cell-mday[data-ymd="<?php echo $force_day; ?>"]').trigger('click');
491 }, 100);
492 <?php
493 }
494 ?>
495
496 </script>
497 <?php
498
499 // get the HTML buffer
500 $html_content = ob_get_contents();
501 ob_end_clean();
502
503 // return an associative array of values
504 return array(
505 'html' => $html_content,
506 'offset' => $next_offset,
507 'search_name' => $search_name,
508 'period_date' => $period_date,
509 );
510 }
511
512 /**
513 * Custom method for this widget only to load the bookings of a month-day.
514 * The method is called by the admin controller through an AJAX request.
515 * The visibility should be public, it should not exit the process, and
516 * any content sent to output will be returned to the AJAX response.
517 * In this case we return an array because this method requires "return":1.
518 */
519 public function loadMdayBookings()
520 {
521 // get today's date
522 $today_ymd = date('Y-m-d');
523
524 $page_offset = VikRequest::getInt('page_offset', 0, 'request');
525 $page_length = VikRequest::getInt('page_length', $this->bookings_per_page, 'request');
526 $ymd = VikRequest::getString('ymd', $today_ymd, 'request');
527 $wrapper = VikRequest::getString('wrapper', '', 'request');
528 $room_id = VikRequest::getInt('room_id', 0, 'request');
529
530 // calculate date timestamps interval for the given day
531 $day_info = getdate(strtotime($ymd));
532 $from_ts = mktime(0, 0, 0, $day_info['mon'], $day_info['mday'], $day_info['year']);
533 $to_ts = mktime(23, 59, 59, $day_info['mon'], $day_info['mday'], $day_info['year']);
534
535 // load busy records
536 $room_filter = !empty($room_id) ? array($room_id) : array();
537 $busy_records = VikBooking::loadBusyRecords($room_filter, $from_ts, $to_ts);
538
539 // gather all bookings touching this day
540 $booking_ids = [];
541 foreach ($busy_records as $rid => $rbusy) {
542 foreach ($rbusy as $b) {
543 $tmpone = getdate($b['checkin']);
544 $ritts = mktime(0, 0, 0, $tmpone['mon'], $tmpone['mday'], $tmpone['year']);
545 $tmptwo = getdate($b['checkout']);
546 $conts = mktime(0, 0, 0, $tmptwo['mon'], $tmptwo['mday'], $tmptwo['year']);
547 if ($from_ts >= $ritts && $from_ts < $conts) {
548 if (empty($b['idorder']) || in_array($b['idorder'], $booking_ids)) {
549 continue;
550 }
551 array_push($booking_ids, $b['idorder']);
552 }
553 }
554 }
555
556 // invoke availability helper class
557 $av_helper = VikBooking::getAvailabilityInstance();
558
559 // collect booking information
560 $booking_details = [];
561 foreach ($booking_ids as $bid) {
562 $booking = $av_helper->getBookingDetails($bid);
563 if (!is_array($booking) || !$booking) {
564 continue;
565 }
566 $booking_details[$bid] = $booking;
567 }
568
569 // check if a next page can be available
570 $tot_bookings = count($booking_details);
571 $has_next_page = ($tot_bookings > ($page_length + $page_offset));
572
573 // slice the records, if needed
574 if ($tot_bookings > $page_length) {
575 $booking_details = array_slice($booking_details, $page_offset, $page_length, true);
576 }
577
578 // load cancellations for today (only if a room ID filter is set)
579 $cancellations = [];
580 if (!empty($room_id)) {
581 $cancellations = $this->loadCancellations($room_id, $ymd);
582 }
583
584 // load festivities or room-day notes
585 $festivities = [];
586 $rday_notes = [];
587 if (!empty($room_id)) {
588 // load room-day notes for the given room id
589 $rday_notes = VikBooking::getCriticalDatesInstance()->loadRoomDayNotes(date('Y-m-d', $from_ts), date('Y-m-d', $to_ts), $room_id);
590 } else {
591 // load festivities when no specific room filter set
592 $festivities = VikBooking::getFestivitiesInstance()->loadFestDates(date('Y-m-d', $from_ts), date('Y-m-d', $to_ts));
593 }
594
595 // start output buffering
596 ob_start();
597
598 if ($festivities) {
599 // display the festivities for this day
600 ?>
601 <div class="vbo-widget-booskcal-events vbo-widget-booskcal-fests">
602 <?php
603 foreach ($festivities as $fest_ymd => $fest) {
604 if (empty($fest['festinfo']) || !is_array($fest['festinfo'])) {
605 continue;
606 }
607 foreach ($fest['festinfo'] as $fest_info) {
608 if (!is_object($fest_info) || empty($fest_info->trans_name)) {
609 continue;
610 }
611 ?>
612 <div class="vbo-widget-booskcal-event vbo-widget-booskcal-fest">
613 <strong><?php echo $fest_info->trans_name; ?></strong>
614 <?php
615 if (!empty($fest_info->descr)) {
616 ?>
617 <div><?php echo nl2br($fest_info->descr); ?></div>
618 <?php
619 }
620 ?>
621 </div>
622 <?php
623 }
624 }
625 ?>
626 </div>
627 <?php
628 }
629
630 if ($rday_notes) {
631 // display the room-day notes for this day
632 ?>
633 <div class="vbo-widget-booskcal-events vbo-widget-booskcal-rdaynotes">
634 <?php
635 foreach ($rday_notes as $rday_note) {
636 if (empty($rday_note['info']) || !is_array($rday_note['info'])) {
637 continue;
638 }
639 foreach ($rday_note['info'] as $note_info) {
640 if (!is_object($note_info) || empty($note_info->name)) {
641 continue;
642 }
643 ?>
644 <div class="vbo-widget-booskcal-event vbo-widget-booskcal-rdaynote">
645 <strong><?php echo $note_info->name; ?></strong>
646 <?php
647 if (!empty($note_info->descr)) {
648 ?>
649 <div><?php echo nl2br($note_info->descr); ?></div>
650 <?php
651 }
652 ?>
653 </div>
654 <?php
655 }
656 }
657 ?>
658 </div>
659 <?php
660 }
661
662 if (!$booking_details && !$cancellations) {
663 ?>
664 <p class="info"><?php echo JText::translate('VBNOORDERSFOUND'); ?></p>
665 <?php
666 } else {
667 if (!$booking_details) {
668 ?>
669 <p class="info"><?php echo JText::translate('VBNOORDERSFOUND'); ?></p>
670 <?php
671 }
672
673 // merge confirmed bookings with cancellations (if any)
674 $booking_details = array_merge($booking_details, $cancellations);
675
676 // display all bookings of this day
677 $canc_separator = false;
678 foreach ($booking_details as $booking) {
679 // get channel logo and other details
680 $ch_logo_obj = VikBooking::getVcmChannelsLogo($booking['channel'], true);
681 $channel_logo = is_object($ch_logo_obj) ? $ch_logo_obj->getSmallLogoURL() : '';
682 $nights_lbl = $booking['days'] > 1 ? JText::translate('VBDAYS') : JText::translate('VBDAY');
683 $rooms_lbl = !empty($booking['roomsnum']) && $booking['roomsnum'] > 1 ? ', ' . $booking['roomsnum'] . ' ' . JText::translate('VBPVIEWORDERSTHREE') : '';
684 // compose customer name
685 $customer_name = !empty($booking['customer_fullname']) ? $booking['customer_fullname'] : '';
686 if ($booking['closure'] > 0 || !strcasecmp($booking['custdata'], JText::translate('VBDBTEXTROOMCLOSED'))) {
687 $customer_name = '<span class="vbordersroomclosed"><i class="' . VikBookingIcons::i('ban') . '"></i> ' . JText::translate('VBDBTEXTROOMCLOSED') . '</span>';
688 }
689 if (empty($customer_name)) {
690 $customer_name = VikBooking::getFirstCustDataField($booking['custdata']);
691 }
692 // customer country flag
693 $customer_country = '';
694 $customer_cflag = '';
695 if (!empty($booking['customer_country'])) {
696 $customer_country = $booking['customer_country'];
697 } elseif (!empty($booking['country'])) {
698 $customer_country = $booking['country'];
699 }
700 if ($customer_country && is_file(VBO_ADMIN_PATH . DIRECTORY_SEPARATOR . 'resources' . DIRECTORY_SEPARATOR . 'countries' . DIRECTORY_SEPARATOR . $customer_country . '.png')) {
701 $customer_cflag = '<img src="'.VBO_ADMIN_URI.'resources/countries/' . $customer_country . '.png'.'" title="' . htmlspecialchars($customer_country) . '" class="vbo-country-flag vbo-country-flag-left"/>';
702 }
703
704 // check for cancellations
705 $elem_style = $booking['status'] == 'cancelled' ? 'display: none;' : '';
706 if ($booking['status'] == 'cancelled' && !$canc_separator) {
707 // display the button to show the cancellations
708 $canc_separator = true;
709 ?>
710 <div class="vbo-bookings-status-separator">
711 <button type="button" class="btn btn-small btn-secondary" onclick="vboWidgetBooksCalCancToggle('<?php echo $wrapper; ?>');"><?php echo JText::translate('VBO_SHOW_CANCELLATIONS'); ?></button>
712 </div>
713 <?php
714 }
715
716 ?>
717 <div class="vbo-dashboard-guest-activity vbo-widget-booskcal-reservation" data-type="<?php echo $booking['status']; ?>" data-resid="<?php echo $booking['id']; ?>" style="<?php echo $elem_style; ?>">
718 <div class="vbo-dashboard-guest-activity-avatar">
719 <?php
720 if (!empty($channel_logo)) {
721 // channel logo has got the highest priority
722 ?>
723 <img class="vbo-dashboard-guest-activity-avatar-profile" src="<?php echo $channel_logo; ?>" />
724 <?php
725 } elseif (!empty($booking['pic'])) {
726 // customer profile picture
727 ?>
728 <img class="vbo-dashboard-guest-activity-avatar-profile" src="<?php echo strpos($booking['pic'], 'http') === 0 ? $booking['pic'] : VBO_SITE_URI . 'resources/uploads/' . $booking['pic']; ?>" />
729 <?php
730 } else {
731 // we use an icon as fallback
732 VikBookingIcons::e('hotel', 'vbo-dashboard-guest-activity-avatar-icon');
733 }
734 ?>
735 </div>
736 <div class="vbo-dashboard-guest-activity-content">
737 <div class="vbo-dashboard-guest-activity-content-head">
738 <div class="vbo-dashboard-guest-activity-content-info-details">
739 <h4><?php echo $customer_name . $customer_cflag; ?></h4>
740 <div class="vbo-dashboard-guest-activity-content-info-icon">
741 <?php
742 if ($booking['status'] == 'cancelled') {
743 ?>
744 <span class="badge badge-danger"><?php echo JText::translate('VBCANCELLED'); ?></span>
745 <?php
746 }
747 ?>
748 <span><?php VikBookingIcons::e('plane-arrival'); ?> <?php echo date(str_replace("/", $this->datesep, $this->df), $booking['checkin']); ?> - <?php echo $booking['days'] . ' ' . $nights_lbl . $rooms_lbl; ?></span>
749 </div>
750 </div>
751 <div class="vbo-dashboard-guest-activity-content-info-date">
752 <span>
753 <span class="label label-info"><?php echo $booking['id']; ?></span>
754 </span>
755 <span><?php echo date(str_replace("/", $this->datesep, $this->df) . ' H:i', $booking['ts']); ?></span>
756 </div>
757 </div>
758 </div>
759 </div>
760 <?php
761 }
762
763 // append navigation
764 ?>
765 <div class="vbo-widget-commands vbo-widget-commands-right">
766 <div class="vbo-widget-commands-main">
767 <?php
768 if ($page_offset > 0) {
769 // show backward navigation button
770 ?>
771 <div class="vbo-widget-command-chevron vbo-widget-command-prev">
772 <span class="vbo-widget-command-chevron-prev" onclick="vboWidgetBooksCalMdayNavigate('<?php echo $wrapper; ?>', -1);"><?php VikBookingIcons::e('chevron-left'); ?></span>
773 </div>
774 <?php
775 }
776 if ($has_next_page) {
777 // show forward navigation button
778 ?>
779 <div class="vbo-widget-command-chevron vbo-widget-command-next">
780 <span class="vbo-widget-command-chevron-next" onclick="vboWidgetBooksCalMdayNavigate('<?php echo $wrapper; ?>', 1);"><?php VikBookingIcons::e('chevron-right'); ?></span>
781 </div>
782 <?php
783 }
784 ?>
785 </div>
786 </div>
787 <?php
788
789 // append JS code
790 ?>
791 <script type="text/javascript">
792
793 setTimeout(() => {
794 jQuery('#<?php echo $wrapper; ?>').find('.vbo-widget-booskcal-reservation').on('click', function(e) {
795 let bid = jQuery(this).attr('data-resid');
796 let target = jQuery(e.target);
797 if (target.is('img.vbo-dashboard-guest-activity-avatar-profile') || target.is('i.vbo-dashboard-guest-activity-avatar-icon') || target.is('span.label-info')) {
798 // render booking details within the widget booking details
799 VBOCore.handleDisplayWidgetNotification({widget_id: 'booking_details'}, {
800 bid: bid,
801 modal_options: {
802 /**
803 * Overwrite modal options for rendering the admin widget.
804 * We need to use a different suffix in case this current widget was
805 * also rendered within a modal, or it would get dismissed in favour
806 * of the newly opened admin widget.
807 */
808 suffix: 'widget_modal_inner_booking_details',
809 },
810 });
811 } else {
812 // navigate to booking details page
813 vboWidgetBooksCalOpenBooking(bid);
814 }
815 });
816 }, 300);
817
818 </script>
819 <?php
820 }
821
822 // get the HTML buffer
823 $html_content = ob_get_contents();
824 ob_end_clean();
825
826 // return an associative array of values
827 return array(
828 'html' => $html_content,
829 'tot_bookings' => $tot_bookings,
830 'next_page' => (int)$has_next_page,
831 );
832 }
833
834 /**
835 * Custom method for this widget only to load the custom fields for the new booking.
836 * The method is called by the admin controller through an AJAX request.
837 * The visibility should be public, and an array is requested to be returned.
838 *
839 * @return array
840 *
841 * @since 1.16.0 (J) - 1.6.0 (WP)
842 */
843 public function displayCustomerFilling()
844 {
845 $dbo = JFactory::getDbo();
846
847 $wrapper = VikRequest::getString('wrapper', '', 'request');
848
849 // load custom fields and countries
850 $all_countries = [];
851 $q = "SELECT * FROM `#__vikbooking_custfields` ORDER BY `#__vikbooking_custfields`.`ordering` ASC;";
852 $dbo->setQuery($q);
853 $all_cfields = $dbo->loadAssocList();
854 if ($all_cfields) {
855 $q = "SELECT * FROM `#__vikbooking_countries` ORDER BY `#__vikbooking_countries`.`country_name` ASC;";
856 $dbo->setQuery($q);
857 $all_countries = $dbo->loadAssocList();
858 }
859
860 // start output buffering
861 ob_start();
862
863 ?>
864 <div class="vbo-calendar-cfields-filler" data-wrapper="<?php echo $wrapper; ?>">
865 <div class="vbo-calendar-cfields-topcont">
866 <div class="vbo-calendar-cfields-search">
867 <label for="vbo-searchcust<?php echo $wrapper; ?>"><?php echo JText::translate('VBOSEARCHEXISTCUST'); ?></label>
868 <span id="vbo-searchcust-loading<?php echo $wrapper; ?>" class="vbo-searchcust-loading">
869 <i class="vboicn-hour-glass"></i>
870 </span>
871 <input type="text" id="vbo-searchcust<?php echo $wrapper; ?>" class="vbo-searchcust" autocomplete="off" value="" placeholder="<?php echo htmlspecialchars(JText::translate('VBOSEARCHCUSTBY')); ?>" size="35" />
872 <div id="vbo-searchcust-res<?php echo $wrapper; ?>" class="vbo-searchcust-res"></div>
873 </div>
874 </div>
875 <div class="vbo-calendar-cfields-inner vbo-widget-bookscal-cfields-inner">
876 <input type="hidden" value="" id="vbo-widget-bookscal-cfield-custid<?php echo $wrapper; ?>" />
877 <?php
878 foreach ($all_cfields as $cfield) {
879 if ($cfield['type'] == 'text' && $cfield['isphone'] == 1) {
880 ?>
881 <div class="vbo-calendar-cfield-entry">
882 <label for="cfield<?php echo $cfield['id'] . $wrapper; ?>"><?php echo JText::translate($cfield['name']); ?></label>
883 <span>
884 <?php
885 echo $this->vbo_app->printPhoneInputField(
886 [
887 'id' => 'cfield' . $cfield['id'] . $wrapper,
888 'class' => 'vbo-calendar-cfield-phone',
889 'data-isemail' => '0',
890 'data-isnominative' => '0',
891 'data-isphone' => '1'
892 ],
893 [
894 'fullNumberOnBlur' => true
895 ],
896 $load_assets = false
897 );
898 ?>
899 </span>
900 </div>
901 <?php
902 } elseif ($cfield['type'] == 'text') {
903 ?>
904 <div class="vbo-calendar-cfield-entry">
905 <label for="cfield<?php echo $cfield['id'] . $wrapper; ?>"><?php echo JText::translate($cfield['name']); ?></label>
906 <span>
907 <input type="text" id="cfield<?php echo $cfield['id'] . $wrapper; ?>" data-isemail="<?php echo ($cfield['isemail'] == 1 ? '1' : '0'); ?>" data-isnominative="<?php echo ($cfield['isnominative'] == 1 ? '1' : '0'); ?>" data-isphone="0" value="" size="35"/>
908 </span>
909 </div>
910 <?php
911 } elseif ($cfield['type'] == 'textarea') {
912 ?>
913 <div class="vbo-calendar-cfield-entry">
914 <label for="cfield<?php echo $cfield['id'] . $wrapper; ?>"><?php echo JText::translate($cfield['name']); ?></label>
915 <span>
916 <textarea id="cfield<?php echo $cfield['id'] . $wrapper; ?>" rows="4" cols="35"></textarea>
917 </span>
918 </div>
919 <?php
920 } elseif ($cfield['type'] == 'country') {
921 ?>
922 <div class="vbo-calendar-cfield-entry">
923 <label for="cfield<?php echo $cfield['id'] . $wrapper; ?>"><?php echo JText::translate($cfield['name']); ?></label>
924 <span>
925 <select id="cfield<?php echo $cfield['id'] . $wrapper; ?>" class="vbo-calendar-cfield-country" onchange="vboWidgetBooksCalChangeCountry(this, '<?php echo $wrapper; ?>');">
926 <option value=""> </option>
927 <?php
928 foreach ($all_countries as $country) {
929 ?>
930 <option value="<?php echo $country['country_name']; ?>" data-ccode="<?php echo $country['country_3_code']; ?>" data-c2code="<?php echo $country['country_2_code']; ?>"><?php echo $country['country_name']; ?></option>
931 <?php
932 }
933 ?>
934 </select>
935 </span>
936 </div>
937 <?php
938 } elseif ($cfield['type'] == 'state') {
939 ?>
940 <div class="vbo-calendar-cfield-entry">
941 <label for="cfield<?php echo $cfield['id'] . $wrapper; ?>"><?php echo JText::translate($cfield['name']); ?></label>
942 <span>
943 <select id="cfield<?php echo $cfield['id'] . $wrapper; ?>" class="vbo-calendar-cfield-state">
944 <option value="">-----</option>
945 </select>
946 </span>
947 </div>
948 <?php
949 }
950 }
951 ?>
952 </div>
953 </div>
954
955 <script type="text/javascript">
956
957 /**
958 * Pool of customers previous information collected.
959 */
960 window['customers_search_vals<?php echo $wrapper; ?>'] = {};
961
962 /**
963 * Search among the customers for the given keyword.
964 */
965 function vboWidgetBooksCalCustomerSearch(words) {
966 jQuery('#vbo-searchcust-res<?php echo $wrapper; ?>').hide().html('');
967 jQuery('#vbo-searchcust-loading<?php echo $wrapper; ?>').show();
968
969 VBOCore.doAjax(
970 "<?php echo VikBooking::ajaxUrl('index.php?option=com_vikbooking&task=searchcustomer'); ?>",
971 {
972 kw: words,
973 selector: 'vbo-widget-custsearchres-entry',
974 no_script: 1,
975 tmpl: "component"
976 },
977 (cont) => {
978 // parse the response
979 if (cont) {
980 // populate values
981 var obj_res = typeof cont === 'string' ? JSON.parse(cont) : cont;
982 window['customers_search_vals<?php echo $wrapper; ?>'] = obj_res[0];
983 jQuery('#vbo-searchcust-res<?php echo $wrapper; ?>').html(obj_res[1]);
984
985 setTimeout(() => {
986 // de-register listeners
987 jQuery('#vbo-searchcust<?php echo $wrapper; ?>').off('keydown');
988 jQuery('#vbo-searchcust-res<?php echo $wrapper; ?>').find('.vbo-widget-custsearchres-entry').off('hover');
989
990 // register listeners
991 var vboCust = jQuery('#vbo-searchcust-res<?php echo $wrapper; ?>').find('.vbo-widget-custsearchres-entry');
992 var vboCustSelected = null;
993 jQuery('#vbo-searchcust<?php echo $wrapper; ?>').keydown(function(e) {
994 if (e.which === 40) {
995 if (vboCustSelected) {
996 vboCustSelected.removeClass('vbo-widget-custsearchres-entry-highligthed');
997 next = vboCustSelected.next();
998 if (next.length > 0) {
999 vboCustSelected = next.addClass('vbo-widget-custsearchres-entry-highligthed');
1000 } else {
1001 vboCustSelected = vboCust.eq(0).addClass('vbo-widget-custsearchres-entry-highligthed');
1002 }
1003 } else {
1004 vboCustSelected = vboCust.eq(0).addClass('vbo-widget-custsearchres-entry-highligthed');
1005 }
1006 } else if (e.which === 38) {
1007 if (vboCustSelected) {
1008 vboCustSelected.removeClass('vbo-widget-custsearchres-entry-highligthed');
1009 next = vboCustSelected.prev();
1010 if (next.length > 0) {
1011 vboCustSelected = next.addClass('vbo-widget-custsearchres-entry-highligthed');
1012 } else {
1013 vboCustSelected = vboCust.last().addClass('vbo-widget-custsearchres-entry-highligthed');
1014 }
1015 } else {
1016 vboCustSelected = vboCust.last().addClass('vbo-widget-custsearchres-entry-highligthed');
1017 }
1018 } else if (e.which === 13) {
1019 if (vboCustSelected) {
1020 vboCustSelected.trigger('click');
1021 }
1022 }
1023 });
1024
1025 jQuery('#vbo-searchcust-res<?php echo $wrapper; ?>').find('.vbo-widget-custsearchres-entry').hover(function() {
1026 if (vboCustSelected) {
1027 vboCustSelected.removeClass('vbo-widget-custsearchres-entry-highligthed');
1028 vboCustSelected = null;
1029 }
1030 vboCustSelected = jQuery(this).addClass('vbo-widget-custsearchres-entry-highligthed');
1031 }, function() {
1032 if (vboCustSelected) {
1033 vboCustSelected.removeClass('vbo-widget-custsearchres-entry-highligthed');
1034 vboCustSelected = null;
1035 }
1036 jQuery(this).removeClass('vbo-widget-custsearchres-entry-highligthed');
1037 });
1038 }, 300);
1039 } else {
1040 window['customers_search_vals<?php echo $wrapper; ?>'] = {};
1041 jQuery("#vbo-searchcust-res<?php echo $wrapper; ?>").html("----");
1042 }
1043 jQuery("#vbo-searchcust-res<?php echo $wrapper; ?>").show();
1044 jQuery("#vbo-searchcust-loading<?php echo $wrapper; ?>").hide();
1045 },
1046 (error) => {
1047 jQuery("#vbo-searchcust-loading<?php echo $wrapper; ?>").hide();
1048 console.error(error);
1049 alert(error.responseText);
1050 }
1051 );
1052 }
1053
1054 /**
1055 * Register keyup event listener with a debounce technique for searching customers.
1056 */
1057 document.getElementById('vbo-searchcust<?php echo $wrapper; ?>').addEventListener('keyup', VBOCore.debounceEvent((e) => {
1058 var keywords = jQuery("#vbo-searchcust<?php echo $wrapper; ?>").val();
1059 var chars = keywords ? keywords.length : '';
1060 // we prefer e.key, as e.keyCode is deprecated
1061 var key_pressed = e.key || e.keyCode;
1062 var rgx = new RegExp(/^[A-Za-z0-9]$/);
1063 if (chars > 1 && key_pressed) {
1064 if (key_pressed == 'Enter' || key_pressed == 13 || rgx.test(key_pressed)) {
1065 vboWidgetBooksCalCustomerSearch(keywords);
1066 }
1067 } else {
1068 if (jQuery("#vbo-searchcust-res<?php echo $wrapper; ?>").is(":visible")) {
1069 jQuery("#vbo-searchcust-res<?php echo $wrapper; ?>").hide();
1070 }
1071 }
1072 }, 600));
1073
1074 /**
1075 * Register the click event over the customer search results.
1076 */
1077 jQuery(document).on('click', '#vbo-searchcust-res<?php echo $wrapper; ?> .vbo-widget-custsearchres-entry', function() {
1078 var custid = jQuery(this).attr('data-custid');
1079 var custemail = jQuery(this).attr('data-email');
1080 var custphone = jQuery(this).attr('data-phone');
1081 var custcountry = jQuery(this).attr('data-country');
1082 var custfirstname = jQuery(this).attr('data-firstname');
1083 var custlastname = jQuery(this).attr('data-lastname');
1084
1085 // set customer ID
1086 jQuery('#vbo-widget-bookscal-cfield-custid<?php echo $wrapper; ?>').val(custid);
1087
1088 // check previous custom fields
1089 if (window['customers_search_vals<?php echo $wrapper; ?>'].hasOwnProperty(custid)) {
1090 jQuery.each(window['customers_search_vals<?php echo $wrapper; ?>'][custid], function(cfid, cfval) {
1091 var fill_field = jQuery('#cfield' + cfid + '<?php echo $wrapper; ?>');
1092 if (fill_field.length) {
1093 // set previous value
1094 fill_field.val(cfval);
1095 }
1096 });
1097 }
1098
1099 // always populate basic information on custom fields
1100 var fields_wrap = jQuery('.vbo-calendar-cfields-filler[data-wrapper="<?php echo $wrapper; ?>"]');
1101
1102 if (custcountry.length) {
1103 if (custcountry.length > 3) {
1104 fields_wrap.find('select.vbo-calendar-cfield-country').val(custcountry);
1105 } else {
1106 var country_opt = fields_wrap.find('select.vbo-calendar-cfield-country').find('option[data-ccode="' + custcountry + '"]');
1107 if (country_opt.length) {
1108 fields_wrap.find('select.vbo-calendar-cfield-country').val(country_opt.attr('value'));
1109 }
1110 }
1111 }
1112 fields_wrap.find('input[data-isnominative="1"]').each(function(k, v) {
1113 if (k == 0) {
1114 jQuery(this).val(custfirstname);
1115 return true;
1116 }
1117 if (k == 1) {
1118 jQuery(this).val(custlastname);
1119 return true;
1120 }
1121 return false;
1122 });
1123 fields_wrap.find('input[data-isemail="1"]').val(custemail);
1124 fields_wrap.find('input[data-isphone="1"]').val(custphone);
1125
1126 // set customer upon the selection just made
1127 vboWidgetBooksCalSetCustomer('<?php echo $wrapper; ?>');
1128 });
1129
1130 // focus search customer input field
1131 setTimeout(() => {
1132 jQuery('#vbo-searchcust<?php echo $wrapper; ?>').focus();
1133 }, 500);
1134
1135 </script>
1136 <?php
1137
1138 // get the HTML buffer
1139 $html_content = ob_get_contents();
1140 ob_end_clean();
1141
1142 // return an associative array of values
1143 return [
1144 'html' => $html_content,
1145 ];
1146 }
1147
1148 /**
1149 * Custom method for this widget only to save a new booking with the input values.
1150 * The method is called by the admin controller through an AJAX request.
1151 * The visibility should be public, and an array is requested to be returned.
1152 *
1153 * @return array
1154 *
1155 * @since 1.16.0 (J) - 1.6.0 (WP)
1156 */
1157 public function saveBooking()
1158 {
1159 $wrapper = VikRequest::getString('wrapper', '', 'request');
1160 $forcebooking = VikRequest::getInt('forcebooking', 0, 'request');
1161 $room_id = VikRequest::getInt('room_id', 0, 'request');
1162 $checkin = VikRequest::getString('checkin', '', 'request');
1163 $checkout = VikRequest::getString('checkout', '', 'request');
1164 $closure = VikRequest::getInt('closure', 0, 'request');
1165 $units_closed = VikRequest::getInt('units_closed', 0, 'request');
1166 $units = VikRequest::getInt('units', 1, 'request');
1167 $adults = VikRequest::getInt('adults', 2, 'request');
1168 $children = VikRequest::getInt('children', 0, 'request');
1169 $cust_id = VikRequest::getInt('cust_id', 0, 'request');
1170 $cust_email = VikRequest::getString('cust_email', '', 'request');
1171 $cust_data = VikRequest::getString('cust_data', '', 'request');
1172 $cust_country = VikRequest::getString('cust_country', '', 'request');
1173 $cust_state = VikRequest::getString('cust_state', '', 'request');
1174 $cust_phone = VikRequest::getString('cust_phone', '', 'request');
1175 $cust_tfname = VikRequest::getString('cust_tfname', '', 'request');
1176 $cust_tlname = VikRequest::getString('cust_tlname', '', 'request');
1177 $roomcost = VikRequest::getFloat('roomcost', 0, 'request');
1178 $idprice = VikRequest::getInt('idprice', 0, 'request');
1179 $cust_roomcost = VikRequest::getFloat('cust_roomcost', 0, 'request');
1180 $taxid = VikRequest::getInt('taxid', 0, 'request');
1181
1182 if (empty($room_id) || empty($checkin) || empty($checkout)) {
1183 VBOHttpDocument::getInstance()->close(500, JText::translate('VBO_PLEASE_FILL_FIELDS'));
1184 }
1185
1186 // invoke the reservation model and inject values
1187 $model_res = VBOModelReservation::getInstance([
1188 'force_booking' => $forcebooking,
1189 'set_closed' => $closure,
1190 'units_closed' => $units_closed,
1191 'status' => 'confirmed',
1192 'num_rooms' => $units,
1193 'adults' => $adults,
1194 'children' => $children,
1195 ])->setCustomer([
1196 'id' => $cust_id,
1197 'first_name' => $cust_tfname,
1198 'last_name' => $cust_tlname,
1199 'data' => $cust_data,
1200 'email' => $cust_email,
1201 'country' => $cust_country,
1202 'state' => $cust_state,
1203 'phone' => $cust_phone,
1204 ])->setRoom([
1205 'id' => $room_id,
1206 'cust_cost' => $cust_roomcost,
1207 'room_cost' => $roomcost,
1208 'id_price' => $idprice,
1209 'id_tax' => $taxid,
1210 ]);
1211
1212 // calculate proper check-in and check-out timestamps
1213 list($hcheckin, $mcheckin, $hcheckout, $mcheckout) = $model_res->loadCheckinOutTimes();
1214
1215 // get final stay timestamps
1216 $checkin_ts = VikBooking::getDateTimestamp($checkin, $hcheckin, $mcheckin);
1217 $checkout_ts = VikBooking::getDateTimestamp($checkout, $hcheckout, $mcheckout);
1218
1219 if (!$checkin_ts || !$checkout_ts || $checkin_ts >= $checkout_ts) {
1220 VBOHttpDocument::getInstance()->close(500, JText::translate('ERRINVDATESEASON'));
1221 }
1222
1223 // set stay dates
1224 $model_res->set('checkin', $checkin_ts);
1225 $model_res->set('checkout', $checkout_ts);
1226
1227 // store the reservation
1228 $model_res->create();
1229
1230 // get the new booking ID
1231 $res_id = $model_res->getNewBookingID();
1232 if (!$res_id) {
1233 VBOHttpDocument::getInstance()->close(500, $model_res->getError());
1234 }
1235
1236 return [
1237 'new_booking_id' => $res_id,
1238 'vcm_action' => $model_res->getChannelManagerAction(),
1239 ];
1240 }
1241
1242 /**
1243 * Preload the necessary CSS/JS assets.
1244 *
1245 * @return void
1246 */
1247 public function preload()
1248 {
1249 // load assets
1250 $this->vbo_app->loadDatePicker();
1251 $this->vbo_app->loadPhoneInputFieldAssets();
1252
1253 // JS lang def
1254 JText::script('VBFILLCUSTFIELDS');
1255 JText::script('VBO_PLEASE_FILL_FIELDS');
1256 JText::script('VBDASHUPRESONE');
1257 JText::script('VBANNULLA');
1258 JText::script('VBAPPLY');
1259 JText::script('VBDBTEXTROOMCLOSED');
1260 JText::script('VBO_MARK_UNITS_CLOSED');
1261 }
1262
1263 /**
1264 * Main method to invoke the widget. Contents will be loaded
1265 * through AJAX requests, not via PHP when the page loads.
1266 *
1267 * @param VBOMultitaskData $data
1268 *
1269 * @return void
1270 */
1271 public function render(VBOMultitaskData $data = null)
1272 {
1273 // increase widget's instance counter
1274 static::$instance_counter++;
1275
1276 // check whether the widget is being rendered via AJAX when adding it through the customizer
1277 $is_ajax = $this->isAjaxRendering();
1278
1279 // generate a unique ID for the sticky notes wrapper instance
1280 $wrapper_instance = !$is_ajax ? static::$instance_counter : rand();
1281 $wrapper_id = 'vbo-widget-booskcal-' . $wrapper_instance;
1282
1283 // get permissions
1284 $vbo_auth_bookings = JFactory::getUser()->authorise('core.vbo.bookings', 'com_vikbooking');
1285 if (!$vbo_auth_bookings) {
1286 // display nothing
1287 return;
1288 }
1289
1290 // invoke availability helper class
1291 $av_helper = VikBooking::getAvailabilityInstance();
1292
1293 // get all rooms
1294 $all_rooms = $av_helper->loadRooms();
1295
1296 // check pricing tax configuration
1297 $prices_vat_included = (int)VikBooking::ivaInclusa();
1298
1299 /**
1300 * This widget can make use of the Select2 jQuery plugin, but we do not preload it in
1301 * order to save resources. If any other widget has preloaded Select2, JS will detect it.
1302 */
1303 $use_nice_select = 0;
1304 if (count($all_rooms) > 1) {
1305 // turn flag on
1306 $use_nice_select = 1;
1307 }
1308
1309 // default dates and values
1310 $now_info = getdate();
1311 $from_ts = mktime(0, 0, 0, $now_info['mon'], 1, $now_info['year']);
1312
1313 // build week days list according to settings
1314 $firstwday = (int)VikBooking::getFirstWeekDay();
1315 $days_labels = array(
1316 JText::translate('VBSUN'),
1317 JText::translate('VBMON'),
1318 JText::translate('VBTUE'),
1319 JText::translate('VBWED'),
1320 JText::translate('VBTHU'),
1321 JText::translate('VBFRI'),
1322 JText::translate('VBSAT'),
1323 );
1324 $days_indexes = [];
1325 for ($i = 0; $i < 7; $i++) {
1326 $days_indexes[$i] = (6 - ($firstwday - $i) + 1) % 7;
1327 }
1328
1329 // check multitask data
1330 $page_bid = 0;
1331 $load_room = 0;
1332 $modal_load_bid = '';
1333 $js_modal_id = '';
1334 if ($data) {
1335 // access Multitask data
1336 $page_bid = $data->getBookingID() ?: $this->options()->fetchBookingId();
1337 $is_modal_rendering = $data->isModalRendering();
1338 if ($page_bid && $is_modal_rendering) {
1339 // immediately load contents according to injected multitask data
1340 $modal_load_bid = $page_bid;
1341 }
1342 if ($is_modal_rendering) {
1343 // get modal JS identifier
1344 $js_modal_id = $data->getModalJsIdentifier();
1345 }
1346
1347 // check for a custom date (Y-m-d)
1348 $option_offset = $this->getOption('offset', '');
1349 if ($option_offset && preg_match("/^[0-9]{4}-[0-9]{2}-[0-9]{2}$/", $option_offset)) {
1350 // overwrite values
1351 $now_info = getdate(strtotime($option_offset));
1352 $from_ts = mktime(0, 0, 0, $now_info['mon'], 1, $now_info['year']);
1353 }
1354
1355 // check if a specific room ID was set
1356 $load_room = $this->getOption('id_room', 0);
1357 }
1358
1359 // default labels
1360 $search_name = preg_replace("/[^A-Za-z0-9 ]/", '', JText::translate('VBOSTATSALLROOMS'));
1361 $period_date = VikBooking::sayMonth($now_info['mon']) . ' ' . $now_info['year'];
1362
1363 // start looping from the first day of the current month
1364 $info_arr = getdate($from_ts);
1365
1366 // week days counter
1367 $d_count = 0;
1368 $mon_lim = $info_arr['mon'];
1369
1370 ?>
1371 <div id="<?php echo $wrapper_id; ?>" class="vbo-admin-widget-wrapper" data-instance="<?php echo $wrapper_instance; ?>" data-pagebid="<?php echo $page_bid; ?>" data-offset="<?php echo date('Y-m-d', $from_ts); ?>">
1372 <div class="vbo-admin-widget-head">
1373 <div class="vbo-admin-widget-head-inline">
1374 <h4><?php echo $this->widgetIcon; ?> <span><?php echo $this->widgetName; ?></span></h4>
1375 <div class="vbo-admin-widget-head-commands">
1376
1377 <div class="vbo-reportwidget-commands">
1378 <div class="vbo-reportwidget-commands-main">
1379 <div class="vbo-reportwidget-command-dates">
1380 <div class="vbo-reportwidget-period-name"><?php echo $search_name; ?></div>
1381 <div class="vbo-reportwidget-period-date"><?php echo $period_date; ?></div>
1382 </div>
1383 <div class="vbo-reportwidget-command-chevron vbo-reportwidget-command-prev">
1384 <span class="vbo-widget-booskcal-dt-prev" onclick="vboWidgetBookCalsMonthNav('<?php echo $wrapper_id; ?>', -1);"><?php VikBookingIcons::e('chevron-left'); ?></span>
1385 </div>
1386 <div class="vbo-reportwidget-command-chevron vbo-reportwidget-command-next">
1387 <span class="vbo-widget-booskcal-dt-next" onclick="vboWidgetBookCalsMonthNav('<?php echo $wrapper_id; ?>', 1);"><?php VikBookingIcons::e('chevron-right'); ?></span>
1388 </div>
1389 </div>
1390 </div>
1391
1392 </div>
1393 </div>
1394 </div>
1395 <div class="vbo-widget-booskcal-wrap">
1396 <div class="vbo-widget-booskcal-inner">
1397
1398 <div class="vbo-widget-booskcal-top-wrap">
1399 <div class="vbo-widget-booskcal-filter">
1400 <select class="vbo-booskcal-roomid" onchange="vboWidgetBookCalsSetRoom('<?php echo $wrapper_id; ?>', this.value);">
1401 <option data-units="0"></option>
1402 <?php
1403 foreach ($all_rooms as $rid => $room) {
1404 ?>
1405 <option value="<?php echo $rid; ?>" data-units="<?php echo $room['units']; ?>"<?php echo $rid == $load_room ? ' selected="selected"' : ''; ?>><?php echo $room['name']; ?></option>
1406 <?php
1407 }
1408 ?>
1409 </select>
1410 </div>
1411 <div class="vbo-widget-booskcal-newbook">
1412 <button type="button" class="btn btn-success vbo-widget-booskcal-newbook-start" onclick="vboWidgetBookCalsNewBooking('<?php echo $wrapper_id; ?>');"><?php VikBookingIcons::e('plus-circle'); ?> <?php echo JText::translate('VBO_NEW_BOOKING'); ?></button>
1413 <button type="button" class="btn btn-success vbo-widget-booskcal-newbook-id" data-bookingid="" onclick="vboWidgetBookCalsOpenNewBooking('<?php echo $wrapper_id; ?>');" style="display: none;"><?php VikBookingIcons::e('check-circle'); ?> <span></span></button>
1414 </div>
1415 </div>
1416
1417 <div class="vbo-widget-booskcal-calendar">
1418
1419 <table class="vbadmincaltable vbo-widget-booskcal-calendar-table">
1420 <tbody>
1421 <tr class="vbadmincaltrmdays">
1422 <?php
1423 // display week days in the proper order
1424 for ($i = 0; $i < 7; $i++) {
1425 $d_ind = ($i + $firstwday) < 7 ? ($i + $firstwday) : ($i + $firstwday - 7);
1426 ?>
1427 <td class="vbo-widget-booskcal-cell-wday"><?php echo $days_labels[$d_ind]; ?></td>
1428 <?php
1429 }
1430 ?>
1431 </tr>
1432 <tr>
1433 <?php
1434 // display empty cells until the first week-day of the month
1435 for ($i = 0, $n = $days_indexes[$info_arr['wday']]; $i < $n; $i++, $d_count++) {
1436 ?>
1437 <td class="vbo-widget-booskcal-cell-mday vbo-widget-booskcal-cell-empty">&nbsp;</td>
1438 <?php
1439 }
1440 // display month days
1441 while ($info_arr['mon'] == $mon_lim) {
1442 if ($d_count > 6) {
1443 $d_count = 0;
1444 // close current row and open a new one
1445 echo "\n</tr>\n<tr>\n";
1446 }
1447 ?>
1448 <td class="vbo-widget-booskcal-cell-mday">
1449 <span class="vbo-widget-booskcal-mday-val"><?php echo $info_arr['mday']; ?></span>
1450 </td>
1451 <?php
1452 $dayts = mktime(0, 0, 0, $info_arr['mon'], ($info_arr['mday'] + 1), $info_arr['year']);
1453 $info_arr = getdate($dayts);
1454 $d_count++;
1455 }
1456 // add empty cells until the end of the row
1457 for ($i = $d_count; $i <= 6; $i++) {
1458 ?>
1459 <td class="vbo-widget-booskcal-cell-mday vbo-widget-booskcal-cell-empty">&nbsp;</td>
1460 <?php
1461 }
1462 ?>
1463 </tr>
1464 </tbody>
1465 </table>
1466
1467 </div>
1468
1469 </div>
1470 </div>
1471 </div>
1472 <?php
1473
1474 if (static::$instance_counter === 0 || $is_ajax) {
1475 /**
1476 * Print the JS code only once for all instances of this widget.
1477 * The real rendering is made through AJAX, not when the page loads.
1478 */
1479 ?>
1480 <a class="vbo-widget-bookscal-basenavuri" href="<?php echo VBOFactory::getPlatform()->getUri()->admin('index.php?option=com_vikbooking&task=editorder&cid[]=%d', $xhtml = false); ?>" style="display: none;"></a>
1481
1482 <script type="text/javascript">
1483
1484 /**
1485 * Open the booking details page for the clicked reservation.
1486 */
1487 function vboWidgetBooksCalOpenBooking(id) {
1488 var open_url = jQuery('.vbo-widget-bookscal-basenavuri').first().attr('href');
1489 open_url = open_url.replace('%d', id);
1490 // navigate in a new tab
1491 window.open(open_url, '_blank');
1492 }
1493
1494 /**
1495 * Display the loading skeletons.
1496 */
1497 function vboWidgetBooksCalSkeletons(wrapper) {
1498 var widget_instance = jQuery('#' + wrapper);
1499 if (!widget_instance.length) {
1500 return false;
1501 }
1502
1503 var skelton_html = '<div class="vbo-skeleton-loading vbo-skeleton-loading-mday-cell"></div>';
1504 widget_instance.find('.vbo-widget-booskcal-calendar').find('.vbo-widget-booskcal-cell-mday').attr('class', 'vbo-widget-booskcal-cell-mday').html(skelton_html);
1505 }
1506
1507 /**
1508 * Perform the request to load the bookings calendar.
1509 */
1510 function vboWidgetBooksCalLoad(wrapper, dates_direction, page_bid) {
1511 var widget_instance = jQuery('#' + wrapper);
1512 if (!widget_instance.length) {
1513 return false;
1514 }
1515
1516 // check if a navigation of dates was requested (0 = no dates nav)
1517 if (typeof dates_direction === 'undefined') {
1518 dates_direction = 0;
1519 }
1520
1521 // check if multitask data passed a booking ID for the current page
1522 var force_bid = 0;
1523 if (typeof page_bid !== 'undefined' && page_bid && !isNaN(page_bid)) {
1524 force_bid = page_bid;
1525 }
1526
1527 // get vars for making the request
1528 var current_offset = widget_instance.attr('data-offset');
1529 var room_id = widget_instance.find('.vbo-booskcal-roomid').val();
1530
1531 // gather options, if any
1532 var options = vbo_widget_books_cal_options_oo || {};
1533 if (vbo_widget_books_cal_options_oo) {
1534 // multitask data options should be used only once ("oo")
1535 vbo_widget_books_cal_options_oo = null;
1536 }
1537
1538 // the widget method to call
1539 var call_method = 'loadBookingsCalendar';
1540
1541 // make a request to load the bookings calendar
1542 VBOCore.doAjax(
1543 "<?php echo $this->getExecWidgetAjaxUri(); ?>",
1544 {
1545 widget_id: "<?php echo $this->getIdentifier(); ?>",
1546 call: call_method,
1547 return: 1,
1548 bid: force_bid,
1549 offset: current_offset,
1550 room_id: room_id,
1551 date_dir: dates_direction,
1552 wrapper: wrapper,
1553 _options: options,
1554 tmpl: "component"
1555 },
1556 (response) => {
1557 try {
1558 var obj_res = typeof response === 'string' ? JSON.parse(response) : response;
1559 if (!obj_res.hasOwnProperty(call_method)) {
1560 console.error('Unexpected JSON response', obj_res);
1561 return false;
1562 }
1563
1564 // set new offset for navigation
1565 widget_instance.attr('data-offset', obj_res[call_method]['offset']);
1566
1567 // update search name and month
1568 widget_instance.find('.vbo-reportwidget-period-name').text(obj_res[call_method]['search_name']);
1569 widget_instance.find('.vbo-reportwidget-period-date').text(obj_res[call_method]['period_date']);
1570
1571 // replace HTML with new bookings calendar
1572 widget_instance.find('.vbo-widget-booskcal-calendar').html(obj_res[call_method]['html']);
1573 } catch(err) {
1574 console.error('could not parse JSON response', err, response);
1575 }
1576 },
1577 (error) => {
1578 // remove the skeleton loading
1579 widget_instance.find('.vbo-widget-booskcal-calendar').find('.vbo-skeleton-loading').remove();
1580 console.error(error);
1581 }
1582 );
1583 }
1584
1585 /**
1586 * Perform the request to load the month-day reservations.
1587 */
1588 function vboWidgetBooksCalGetMdayRes(wrapper, ymd) {
1589 var widget_instance = jQuery('#' + wrapper);
1590 if (!widget_instance.length || !ymd) {
1591 return false;
1592 }
1593
1594 // get vars for making the request
1595 var room_id = widget_instance.find('.vbo-booskcal-roomid').val();
1596 var page_offset = widget_instance.find('.vbo-widget-booskcal-mday-list').attr('data-offset');
1597 var page_length = widget_instance.find('.vbo-widget-booskcal-mday-list').attr('data-length');
1598
1599 // the widget method to call
1600 var call_method = 'loadMdayBookings';
1601
1602 // make a request to load the bookings calendar
1603 VBOCore.doAjax(
1604 "<?php echo $this->getExecWidgetAjaxUri(); ?>",
1605 {
1606 widget_id: "<?php echo $this->getIdentifier(); ?>",
1607 call: call_method,
1608 return: 1,
1609 page_offset: page_offset,
1610 page_length: page_length,
1611 ymd: ymd,
1612 room_id: room_id,
1613 wrapper: wrapper,
1614 tmpl: "component"
1615 },
1616 (response) => {
1617 try {
1618 var obj_res = typeof response === 'string' ? JSON.parse(response) : response;
1619 if (!obj_res.hasOwnProperty(call_method)) {
1620 console.error('Unexpected JSON response', obj_res);
1621 return false;
1622 }
1623 // replace HTML with month-day reservations
1624 widget_instance.find('.vbo-widget-booskcal-mday-list').html(obj_res[call_method]['html']);
1625 } catch(err) {
1626 console.error('could not parse JSON response', err, response);
1627 }
1628 },
1629 (error) => {
1630 // remove the skeleton loading
1631 widget_instance.find('.vbo-widget-booskcal-mday-list').html('');
1632 console.error(error);
1633 }
1634 );
1635 }
1636
1637 /**
1638 * Navigate between the months and load the bookings calendar.
1639 */
1640 function vboWidgetBookCalsMonthNav(wrapper, direction) {
1641 var widget_instance = jQuery('#' + wrapper);
1642 if (!widget_instance.length) {
1643 return false;
1644 }
1645
1646 // show loading skeletons
1647 vboWidgetBooksCalSkeletons(wrapper);
1648
1649 // launch dates navigation and load records
1650 vboWidgetBooksCalLoad(wrapper, direction);
1651 }
1652
1653 /**
1654 * Change room calendar.
1655 */
1656 function vboWidgetBookCalsSetRoom(wrapper, rid) {
1657 var widget_instance = jQuery('#' + wrapper);
1658 if (!widget_instance.length) {
1659 return false;
1660 }
1661
1662 // check if we are creating a new booking
1663 if (widget_instance.find('.vbo-widget-booskcal-newbook-wrap').is(':visible')) {
1664 // check room units
1665 var vbo_bookscal_roomfilt = widget_instance.find('.vbo-booskcal-roomid');
1666 if (rid) {
1667 // manage units depending on selected room total units
1668 var vbo_bookscal_room_units = vbo_bookscal_roomfilt.find('option:selected').attr('data-units');
1669 if (vbo_bookscal_room_units > 1) {
1670 widget_instance.find('.vbo-widget-bookscal-units').attr('max', vbo_bookscal_room_units).val('1').closest('[data-noclosure="1"]').show();
1671 } else {
1672 widget_instance.find('.vbo-widget-bookscal-units').val('1').closest('[data-noclosure="1"]').hide();
1673 }
1674 } else {
1675 // hide units when no room is selected
1676 vbo_bookscal_roomfilt.closest('[data-noclosure="1"]').hide();
1677 }
1678
1679 // check the closure status
1680 if (widget_instance.find('input[name="closeroom"]').prop('checked')) {
1681 // reset values
1682 widget_instance.find('.vbo-widget-bookscal-units').val('1');
1683 widget_instance.find('[data-noclosure="1"]').hide();
1684 }
1685
1686 // attempt to update the website rates
1687 vboWidgetBooksCalGetWebsiteRates(wrapper);
1688
1689 // do nothing else when adding a new booking
1690 return;
1691 }
1692
1693 // show loading skeletons
1694 vboWidgetBooksCalSkeletons(wrapper);
1695
1696 // let the records be loaded for this new room filter
1697 vboWidgetBooksCalLoad(wrapper, 0);
1698 }
1699
1700 /**
1701 * Generate the HTML skeleton string to the month-day reservations.
1702 */
1703 function vboWidgetBooksCalMdaySkeleton() {
1704 var monthday_loading = '';
1705 monthday_loading += '<div class="vbo-dashboard-guest-activity vbo-dashboard-guest-activity-skeleton">';
1706 monthday_loading += ' <div class="vbo-dashboard-guest-activity-avatar">';
1707 monthday_loading += ' <div class="vbo-skeleton-loading vbo-skeleton-loading-avatar"></div>';
1708 monthday_loading += ' </div>';
1709 monthday_loading += ' <div class="vbo-dashboard-guest-activity-content">';
1710 monthday_loading += ' <div class="vbo-dashboard-guest-activity-content-head">';
1711 monthday_loading += ' <div class="vbo-skeleton-loading vbo-skeleton-loading-title"></div>';
1712 monthday_loading += ' </div>';
1713 monthday_loading += ' <div class="vbo-dashboard-guest-activity-content-subhead">';
1714 monthday_loading += ' <div class="vbo-skeleton-loading vbo-skeleton-loading-subtitle"></div>';
1715 monthday_loading += ' </div>';
1716 monthday_loading += ' <div class="vbo-dashboard-guest-activity-content-info-msg">';
1717 monthday_loading += ' <div class="vbo-skeleton-loading vbo-skeleton-loading-content"></div>';
1718 monthday_loading += ' </div>';
1719 monthday_loading += ' </div>';
1720 monthday_loading += '</div>';
1721
1722 return monthday_loading;
1723 }
1724
1725 /**
1726 * Enter the month-day view mode from monthly view.
1727 */
1728 function vboWidgetBooksCalMday(wrapper, element) {
1729 var widget_instance = jQuery('#' + wrapper);
1730 if (!widget_instance.length) {
1731 return false;
1732 }
1733
1734 // get cell element
1735 var cell = jQuery(element);
1736 if (!cell || !cell.length) {
1737 return false;
1738 }
1739
1740 // set month-day title
1741 var day_read = cell.attr('data-dayread');
1742 widget_instance.find('.vbo-widget-booskcal-mday-name').text(day_read);
1743
1744 // get cell ymd value
1745 var day_ymd = cell.attr('data-ymd');
1746
1747 // always update the proper ymd day
1748 widget_instance.find('.vbo-widget-booskcal-mday-list').attr('data-ymd', day_ymd);
1749
1750 // get pre-loaded booking ids
1751 var tot_day_res = 0;
1752 var day_bids = cell.attr('data-bids');
1753 if (day_bids && day_bids.length) {
1754 tot_day_res = day_bids.split(',').length;
1755 }
1756
1757 // populate loading skeletons for month-day bookings
1758 var monthday_loading = vboWidgetBooksCalMdaySkeleton();
1759 if (tot_day_res > 1) {
1760 // double up the loading skeletons
1761 monthday_loading = monthday_loading + monthday_loading;
1762 }
1763 widget_instance.find('.vbo-widget-booskcal-mday-list').html(monthday_loading);
1764
1765 // toggle elements
1766 widget_instance.find('.vbo-widget-booskcal-calendar-table').hide();
1767 widget_instance.find('.vbo-widget-booskcal-newbook-wrap').hide();
1768 widget_instance.find('.vbo-widget-booskcal-mday-wrap').show();
1769
1770 // launch month-day bookings retrieval
1771 vboWidgetBooksCalGetMdayRes(wrapper, day_ymd);
1772 }
1773
1774 /**
1775 * Navigate between the various pages of the month-day bookings.
1776 */
1777 function vboWidgetBooksCalMdayNavigate(wrapper, direction) {
1778 var widget_instance = jQuery('#' + wrapper);
1779 if (!widget_instance.length) {
1780 return false;
1781 }
1782
1783 // get bookings container
1784 var bookings_list = widget_instance.find('.vbo-widget-booskcal-mday-list');
1785
1786 // show loading skeletons
1787 var monthday_loading = vboWidgetBooksCalMdaySkeleton();
1788 bookings_list.html(monthday_loading + monthday_loading);
1789
1790 // get current offset and length (MUST be numbers, not strings)
1791 var current_offset = parseInt(bookings_list.attr('data-offset'));
1792 var current_length = parseInt(bookings_list.attr('data-length'));
1793 var day_ymd = bookings_list.attr('data-ymd');
1794
1795 // check direction and update offsets for nav
1796 if (direction > 0) {
1797 // navigate forward
1798 bookings_list.attr('data-offset', (current_offset + current_length));
1799 } else {
1800 // navigate backward
1801 var new_offset = current_offset - current_length;
1802 new_offset = new_offset >= 0 ? new_offset : 0;
1803 bookings_list.attr('data-offset', new_offset);
1804 }
1805
1806 // launch month-day bookings retrieval
1807 vboWidgetBooksCalGetMdayRes(wrapper, day_ymd);
1808 }
1809
1810 /**
1811 * Go back to the montly/month-day view from the month-day/new-booking view.
1812 */
1813 function vboWidgetBooksCalMonth(wrapper) {
1814 var widget_instance = jQuery('#' + wrapper);
1815 if (!widget_instance.length) {
1816 return false;
1817 }
1818
1819 var current_ymd = '';
1820 var current_rid = '';
1821 if (widget_instance.find('.vbo-widget-booskcal-newbook-wrap').is(':visible')) {
1822 current_ymd = widget_instance.find('.vbo-widget-booskcal-newbook-wrap').attr('data-ymd');
1823 current_rid = widget_instance.find('.vbo-widget-booskcal-newbook-wrap').attr('data-roomid');
1824 if (current_rid != widget_instance.find('.vbo-booskcal-roomid').val()) {
1825 // going back from the new-booking view detected a change of the room filter, so we do a reset
1826
1827 // show loading skeletons
1828 vboWidgetBooksCalSkeletons(wrapper);
1829
1830 // let the records be loaded for this new room filter
1831 vboWidgetBooksCalLoad(wrapper, 0);
1832
1833 // do not proceed
1834 return;
1835 }
1836 }
1837
1838 // toggle elements depending on the current view
1839 widget_instance.find('.vbo-widget-booskcal-newbook-wrap').hide().attr('data-ymd', '');
1840 if (current_ymd) {
1841 // back to the month-day view
1842 widget_instance.find('.vbo-widget-booskcal-calendar-table').hide();
1843 widget_instance.find('.vbo-widget-booskcal-mday-wrap').show();
1844 } else {
1845 // back to the monthly view
1846 widget_instance.find('.vbo-widget-booskcal-mday-wrap').hide();
1847 widget_instance.find('.vbo-widget-booskcal-mday-list').html('').attr('data-ymd', '').attr('data-offset', '0');
1848 widget_instance.find('.vbo-widget-booskcal-calendar-table').show();
1849 }
1850 }
1851
1852 /**
1853 * Display the new booking interface by hiding the other elements.
1854 */
1855 function vboWidgetBookCalsNewBooking(wrapper) {
1856 var widget_instance = jQuery('#' + wrapper);
1857 if (!widget_instance.length) {
1858 return false;
1859 }
1860
1861 var current_ymd = '';
1862 var current_rid = widget_instance.find('.vbo-booskcal-roomid').val();
1863 if (widget_instance.find('.vbo-widget-booskcal-mday-wrap').is(':visible')) {
1864 current_ymd = widget_instance.find('.vbo-widget-booskcal-mday-list').attr('data-ymd');
1865 }
1866
1867 // toggle elements
1868 widget_instance.find('.vbo-widget-booskcal-calendar-table').hide();
1869 widget_instance.find('.vbo-widget-booskcal-mday-wrap').hide();
1870 widget_instance.find('.vbo-widget-booskcal-newbook-wrap').show().attr('data-ymd', current_ymd).attr('data-roomid', current_rid);
1871
1872 if (current_ymd) {
1873 // set the check-in date
1874 var ymd_parts = current_ymd.split('-');
1875 widget_instance.find('.vbo-widget-bookscal-checkindt').datepicker('setDate', new Date(ymd_parts[0], parseInt(ymd_parts[1]) - 1, parseInt(ymd_parts[2], 0, 0, 0)));
1876 // trigger the onSelect function in datepicker
1877 jQuery('.ui-datepicker-current-day').click();
1878 }
1879 }
1880
1881 /**
1882 * Toggle the room-closure status when creating a new booking.
1883 */
1884 function vboWidgetBooksCalClosure(wrapper) {
1885 var widget_instance = jQuery('#' + wrapper);
1886 if (!widget_instance.length) {
1887 return false;
1888 }
1889
1890 if (widget_instance.find('input[name="closeroom"]').prop('checked')) {
1891 widget_instance.find('.vbo-param-container[data-noclosure="1"]').hide();
1892 } else {
1893 widget_instance.find('.vbo-param-container[data-noclosure="1"]').show();
1894 // check selected room
1895 var vbo_bookscal_roomfilt = widget_instance.find('.vbo-booskcal-roomid');
1896 if (vbo_bookscal_roomfilt.val()) {
1897 var vbo_bookscal_room_units = vbo_bookscal_roomfilt.find('option:selected').attr('data-units');
1898 if (vbo_bookscal_room_units < 2) {
1899 widget_instance.find('.vbo-widget-bookscal-units').closest('[data-noclosure="1"]').hide();
1900 }
1901 } else {
1902 widget_instance.find('.vbo-widget-bookscal-units').closest('[data-noclosure="1"]').hide();
1903 }
1904 // hide website rates if not available
1905 widget_instance.find('.vbo-param-container[data-unavailable="1"]').hide();
1906 }
1907 }
1908
1909 /**
1910 * Open the modal to assign an existing or a new customer to the booking.
1911 */
1912 function vboWidgetBooksCalAssignCustomer(wrapper) {
1913 var widget_instance = jQuery('#' + wrapper);
1914 if (!widget_instance.length) {
1915 return false;
1916 }
1917
1918 var modal_body = VBOCore.displayModal({
1919 extra_class: 'vbo-modal-rounded vbo-modal-tall',
1920 title: Joomla.JText._('VBFILLCUSTFIELDS'),
1921 footer_left: '<button type="button" class="btn" onclick="VBOCore.emitEvent(\'vbo-widget-booskcal-assigncustomer-dismiss\');">' + Joomla.JText._('VBANNULLA') + '</button>',
1922 footer_right: '<button type="button" class="btn btn-success" onclick="vboWidgetBooksCalSetCustomer(\'' + wrapper + '\');">' + Joomla.JText._('VBAPPLY') + '</button>',
1923 dismiss_event: 'vbo-widget-booskcal-assigncustomer-dismiss',
1924 loading_event: 'vbo-widget-booskcal-assigncustomer-loading',
1925 onDismiss: (e) => {
1926 if (!e || !e.detail) {
1927 // no event data received, maybe the modal was simply dismissed
1928 return;
1929 }
1930
1931 // parse data received within the dismiss event
1932 try {
1933 let customer_data = JSON.parse(e.detail);
1934
1935 // set values received
1936 widget_instance.find('.vbo-widget-bookscal-custid').val((customer_data['id'] || ''));
1937 widget_instance.find('.vbo-widget-bookscal-custmail').val((customer_data['email'] || ''));
1938 widget_instance.find('.vbo-widget-bookscal-custdata').val((customer_data['data'] || ''));
1939 widget_instance.find('.vbo-widget-bookscal-country').val((customer_data['country'] || ''));
1940 widget_instance.find('.vbo-widget-bookscal-state').val((customer_data['state'] || ''));
1941 widget_instance.find('.vbo-widget-bookscal-phone').val((customer_data['phone'] || ''));
1942
1943 let tot_names = customer_data['nominatives'] ? customer_data['nominatives'].length : 0;
1944 if (tot_names > 1) {
1945 widget_instance.find('.vbo-widget-bookscal-tfname').val(customer_data['nominatives'][0]);
1946 widget_instance.find('.vbo-widget-bookscal-tlname').val(customer_data['nominatives'][1]);
1947 }
1948 if (tot_names) {
1949 widget_instance.find('.vbo-assign-customer').find('span').text(customer_data['nominatives'].join(' '));
1950 } else {
1951 widget_instance.find('.vbo-assign-customer').find('span').text(Joomla.JText._('VBFILLCUSTFIELDS'));
1952 }
1953 } catch(e) {
1954 // log the error and abort
1955 console.error(e);
1956 return;
1957 }
1958 },
1959 });
1960
1961 // start loading
1962 VBOCore.emitEvent('vbo-widget-booskcal-assigncustomer-loading');
1963
1964 // the widget method to call
1965 var call_method = 'displayCustomerFilling';
1966
1967 // make a request to load the bookings calendar
1968 VBOCore.doAjax(
1969 "<?php echo $this->getExecWidgetAjaxUri(); ?>",
1970 {
1971 widget_id: "<?php echo $this->getIdentifier(); ?>",
1972 call: call_method,
1973 return: 1,
1974 wrapper: wrapper,
1975 tmpl: "component"
1976 },
1977 (response) => {
1978 // stop loading
1979 VBOCore.emitEvent('vbo-widget-booskcal-assigncustomer-loading');
1980 try {
1981 var obj_res = typeof response === 'string' ? JSON.parse(response) : response;
1982 if (!obj_res.hasOwnProperty(call_method)) {
1983 // dismiss modal and abort
1984 VBOCore.emitEvent('vbo-widget-booskcal-assigncustomer-dismiss');
1985 console.error('Unexpected JSON response', obj_res);
1986 return false;
1987 }
1988
1989 // append modal content
1990 modal_body.append(obj_res[call_method]['html']);
1991 } catch(err) {
1992 console.error('could not parse JSON response', err, response);
1993 }
1994 },
1995 (error) => {
1996 // stop loading
1997 VBOCore.emitEvent('vbo-widget-booskcal-assigncustomer-loading');
1998 // display error
1999 alert(error.responseText);
2000 // dismiss modal
2001 VBOCore.emitEvent('vbo-widget-booskcal-assigncustomer-dismiss');
2002 }
2003 );
2004 }
2005
2006 /**
2007 * Applies the provided customer information when creating a new booking.
2008 */
2009 function vboWidgetBooksCalSetCustomer(wrapper) {
2010 var widget_instance = jQuery('#' + wrapper);
2011 if (!widget_instance.length) {
2012 return false;
2013 }
2014
2015 var cfields_cont = '';
2016 var cfield_vals = {
2017 id: jQuery('#vbo-widget-bookscal-cfield-custid' + wrapper).val(),
2018 email: '',
2019 phone: '',
2020 nominatives: [],
2021 country: '',
2022 state: '',
2023 data: '',
2024 };
2025
2026 jQuery('.vbo-calendar-cfields-filler[data-wrapper="' + wrapper + '"]').find('.vbo-calendar-cfield-entry').each(function() {
2027 var cfield_entry = jQuery(this);
2028 var cfield_name = cfield_entry.find('label').text();
2029 var cfield_input = cfield_entry.find('span').find('input');
2030 var cfield_textarea = cfield_entry.find('span').find('textarea');
2031 var cfield_select = cfield_entry.find('span').find('select.vbo-calendar-cfield-country');
2032 var cfield_state = cfield_entry.find('span').find('select.vbo-calendar-cfield-state');
2033 var cfield_cont = '';
2034 if (cfield_input.length) {
2035 cfield_cont = cfield_input.val();
2036 if (cfield_input.attr('data-isemail') == '1' && cfield_cont && cfield_cont.length) {
2037 cfield_vals['email'] = cfield_cont;
2038 }
2039 if (cfield_input.attr('data-isphone') == '1') {
2040 cfield_vals['phone'] = cfield_cont;
2041 }
2042 if (cfield_input.attr('data-isnominative') == '1') {
2043 cfield_vals['nominatives'].push(cfield_cont);
2044 }
2045 } else if (cfield_textarea.length) {
2046 cfield_cont = cfield_textarea.val();
2047 } else if (cfield_select.length) {
2048 cfield_cont = cfield_select.val();
2049 if (cfield_cont && cfield_cont.length) {
2050 var country_code = jQuery('option:selected', cfield_select).attr('data-ccode');
2051 if (country_code && country_code.length) {
2052 cfield_vals['country'] = country_code;
2053 }
2054 }
2055 } else if (cfield_state.length) {
2056 cfield_cont = cfield_state.val();
2057 cfield_vals['state'] = cfield_cont;
2058 }
2059 if (cfield_cont && cfield_cont.length) {
2060 cfields_cont += cfield_name + ": " + cfield_cont + "\r\n";
2061 }
2062 });
2063
2064 if (!cfields_cont.length) {
2065 // empty information
2066 alert(Joomla.JText._('VBO_PLEASE_FILL_FIELDS'));
2067
2068 // do not proceed
2069 return false;
2070 }
2071
2072 // clean up last new lines
2073 cfields_cont = cfields_cont.replace(/\r\n+$/, "");
2074
2075 // set raw customer data string
2076 cfield_vals['data'] = cfields_cont;
2077
2078 // dimiss the modal by injecting the customer object information
2079 VBOCore.emitEvent('vbo-widget-booskcal-assigncustomer-dismiss', JSON.stringify(cfield_vals));
2080 }
2081
2082 /**
2083 * Reloads a list of states according to the given country.
2084 */
2085 function vboWidgetBooksCalReloadStates(country_3_code, wrapper) {
2086 var states_elem = jQuery('.vbo-calendar-cfields-filler[data-wrapper="' + wrapper + '"]').find('select.vbo-calendar-cfield-state');
2087
2088 if (!states_elem.length || !country_3_code || !country_3_code.length) {
2089 return;
2090 }
2091
2092 // unset the current states/provinces
2093 states_elem.html('');
2094
2095 // make a request to load the states/provinces of the selected country
2096 VBOCore.doAjax(
2097 "<?php echo VikBooking::ajaxUrl('index.php?option=com_vikbooking&task=states.load_from_country'); ?>",
2098 {
2099 country_3_code: country_3_code,
2100 tmpl: "component"
2101 },
2102 (response) => {
2103 try {
2104 var obj_res = typeof response === 'string' ? JSON.parse(response) : response;
2105 if (!obj_res) {
2106 console.error('Unexpected JSON response', obj_res);
2107 return false;
2108 }
2109
2110 // append empty value
2111 states_elem.append('<option value="">-----</option>');
2112
2113 for (var i = 0; i < obj_res.length; i++) {
2114 // append state
2115 states_elem.append('<option value="' + obj_res[i]['state_2_code'] + '">' + obj_res[i]['state_name'] + '</option>');
2116 }
2117 } catch(err) {
2118 console.error('could not parse JSON response', err, response);
2119 }
2120 },
2121 (error) => {
2122 console.error(error);
2123 }
2124 );
2125 }
2126
2127 /**
2128 * Triggers when a custom field of type country is changed.
2129 */
2130 function vboWidgetBooksCalChangeCountry(elem, wrapper) {
2131 var sel_country = jQuery(elem).find('option:selected');
2132 if (!sel_country.length) {
2133 return false;
2134 }
2135
2136 // trigger event for phone number
2137 jQuery('.vbo-calendar-cfield-phone').trigger('vboupdatephonenumber', sel_country.attr('data-c2code'));
2138
2139 // reload state/province
2140 vboWidgetBooksCalReloadStates(sel_country.attr('data-ccode'), wrapper);
2141 }
2142
2143 /**
2144 * Calculates the website rates according to the input values.
2145 */
2146 function vboWidgetBooksCalGetWebsiteRates(wrapper) {
2147 var widget_instance = jQuery('#' + wrapper);
2148 if (!widget_instance.length) {
2149 return false;
2150 }
2151
2152 // unset previously selected rates, if any
2153 widget_instance.find('.vbo-widget-bookscal-roomcost').val('');
2154 widget_instance.find('.vbo-widget-bookscal-idprice').val('');
2155 widget_instance.find('.vbo-widget-bookscal-custcost').val('').attr('readonly', false);
2156 widget_instance.find('.vbo-cal-wbrate-wrap').removeClass('vbo-cal-wbrate-wrap-selected');
2157
2158 // gather values
2159 var is_closing = widget_instance.find('input[name="closeroom"]').prop('checked');
2160 var room_id = widget_instance.find('.vbo-booskcal-roomid').val();
2161 var checkinfdate = widget_instance.find('.vbo-widget-bookscal-checkindt').val();
2162 var checkoutfdate = widget_instance.find('.vbo-widget-bookscal-checkoutdt').val();
2163 var adults = widget_instance.find('.vbo-widget-bookscal-adults').val();
2164 var children = widget_instance.find('.vbo-widget-bookscal-children').val();
2165 var units = widget_instance.find('.vbo-widget-bookscal-units').val();
2166
2167 if (is_closing || !room_id || !checkinfdate || !checkoutfdate) {
2168 // do not proceed
2169 widget_instance.find('.vbo-website-rates-row').hide().attr('data-unavailable', '1');
2170 return;
2171 }
2172
2173 // tax settings
2174 var vbo_tax_included = <?php echo $prices_vat_included; ?>;
2175
2176 // make the request
2177 VBOCore.doAjax(
2178 "<?php echo VikBooking::ajaxUrl('index.php?option=com_vikbooking&task=calc_rates'); ?>",
2179 {
2180 id_room: room_id,
2181 checkinfdate: checkinfdate,
2182 checkoutfdate: checkoutfdate,
2183 num_nights: 0,
2184 num_adults: adults,
2185 num_children: children,
2186 units: units,
2187 only_rates: 1,
2188 tmpl: "component"
2189 },
2190 (resp) => {
2191 try {
2192 obj_res = typeof resp === 'string' ? JSON.parse(resp) : resp;
2193 if (!obj_res[0].hasOwnProperty('idprice')) {
2194 widget_instance.find('.vbo-website-rates-row').hide().attr('data-unavailable', '1');
2195 console.log(resp);
2196 return false;
2197 }
2198
2199 // display the rates obtained
2200 var wrhtml = "";
2201 for (var i in obj_res) {
2202 if (!obj_res.hasOwnProperty(i)) {
2203 continue;
2204 }
2205 if (!vbo_tax_included && obj_res[i].hasOwnProperty('net') && obj_res[i].hasOwnProperty('fnet')) {
2206 obj_res[i]['tot'] = obj_res[i]['net'];
2207 obj_res[i]['ftot'] = obj_res[i]['fnet'];
2208 }
2209 wrhtml += "<div class=\"vbo-cal-wbrate-wrap\" onclick=\"vboWidgetBooksCalSelWebsiteRate(this, '" + wrapper + "');\">";
2210 wrhtml += " <div class=\"vbo-cal-wbrate-inner\">";
2211 wrhtml += " <span class=\"vbo-cal-wbrate-name\" data-idprice=\"" + obj_res[i]['idprice'] + "\">" + obj_res[i]['name'] + "</span>";
2212 wrhtml += " <span class=\"vbo-cal-wbrate-cost\" data-cost=\"" + obj_res[i]['tot'] + "\">" + obj_res[i]['ftot'] + "</span>";
2213 wrhtml += " </div>";
2214 wrhtml += "</div>";
2215 }
2216 widget_instance.find('.vbo-website-rates-cont').html(wrhtml);
2217 widget_instance.find('.vbo-website-rates-row').fadeIn().attr('data-unavailable', '0');
2218 } catch(err) {
2219 widget_instance.find('.vbo-website-rates-row').hide().attr('data-unavailable', '1');
2220 console.error("could not parse JSON response", resp);
2221 return false;
2222 }
2223 },
2224 (error) => {
2225 widget_instance.find('.vbo-website-rates-row').hide().attr('data-unavailable', '1');
2226 console.error("Error calculating the rates", error);
2227 }
2228 );
2229 }
2230
2231 /**
2232 * Attempts to display the tax rates drop down for the custom rate.
2233 */
2234 function vboWidgetBooksCalFocusTaxes(wrapper) {
2235 var widget_instance = jQuery('#' + wrapper);
2236 if (!widget_instance.length) {
2237 return false;
2238 }
2239
2240 var tax_rates_sel = widget_instance.find('.vbo-widget-bookscal-taxid');
2241 if (tax_rates_sel.length) {
2242 tax_rates_sel.show();
2243 }
2244 }
2245
2246 /**
2247 * Select a website rate plan.
2248 */
2249 function vboWidgetBooksCalSelWebsiteRate(elem, wrapper) {
2250 var widget_instance = jQuery('#' + wrapper);
2251 if (!widget_instance.length) {
2252 return false;
2253 }
2254
2255 var rate = jQuery(elem);
2256 var idprice = rate.find('.vbo-cal-wbrate-name').attr('data-idprice');
2257 var cost = rate.find('.vbo-cal-wbrate-cost').attr('data-cost');
2258 var prev_idprice = widget_instance.find('.vbo-widget-bookscal-idprice').val();
2259
2260 // reset all selected classes
2261 widget_instance.find('.vbo-cal-wbrate-wrap').removeClass('vbo-cal-wbrate-wrap-selected');
2262 if (prev_idprice && prev_idprice == idprice) {
2263 // rate plan has been de-selected
2264 widget_instance.find('.vbo-widget-bookscal-idprice').val("");
2265 widget_instance.find('.vbo-widget-bookscal-roomcost').val("");
2266 widget_instance.find('.vbo-widget-bookscal-custcost').attr('readonly', false);
2267 } else {
2268 // rate plan has been selected
2269 rate.addClass('vbo-cal-wbrate-wrap-selected');
2270 widget_instance.find('.vbo-widget-bookscal-idprice').val(idprice);
2271 widget_instance.find('.vbo-widget-bookscal-roomcost').val(cost);
2272 widget_instance.find('.vbo-widget-bookscal-custcost').attr('readonly', true);
2273 }
2274 }
2275
2276 /**
2277 * Save a new booking.
2278 */
2279 function vboWidgetBooksCalSaveBooking(wrapper) {
2280 var widget_instance = jQuery('#' + wrapper);
2281 if (!widget_instance.length) {
2282 return false;
2283 }
2284
2285 // gather all the necessary information
2286 var room_id = widget_instance.find('.vbo-booskcal-roomid').val();
2287 var checkin = widget_instance.find('.vbo-widget-bookscal-checkindt').val();
2288 var checkout = widget_instance.find('.vbo-widget-bookscal-checkoutdt').val();
2289 var closure = widget_instance.find('input[name="closeroom"]').prop('checked') ? 1 : 0;
2290 var units = widget_instance.find('.vbo-widget-bookscal-units').val();
2291 var adults = widget_instance.find('.vbo-widget-bookscal-adults').val();
2292 var children = widget_instance.find('.vbo-widget-bookscal-children').val();
2293 var cust_id = widget_instance.find('.vbo-widget-bookscal-custid').val();
2294 var cust_email = widget_instance.find('.vbo-widget-bookscal-custmail').val();
2295 var cust_data = widget_instance.find('.vbo-widget-bookscal-custdata').val();
2296 var cust_country = widget_instance.find('.vbo-widget-bookscal-country').val();
2297 var cust_state = widget_instance.find('.vbo-widget-bookscal-state').val();
2298 var cust_phone = widget_instance.find('.vbo-widget-bookscal-phone').val();
2299 var cust_tfname = widget_instance.find('.vbo-widget-bookscal-tfname').val();
2300 var cust_tlname = widget_instance.find('.vbo-widget-bookscal-tlname').val();
2301 var roomcost = widget_instance.find('.vbo-widget-bookscal-roomcost').val();
2302 var idprice = widget_instance.find('.vbo-widget-bookscal-idprice').val();
2303 var cust_roomcost = widget_instance.find('.vbo-widget-bookscal-custcost').val();
2304 var taxid = widget_instance.find('.vbo-widget-bookscal-taxid').val();
2305
2306 if (!room_id || !checkin || !checkout) {
2307 // missing information
2308 alert(Joomla.JText._('VBO_PLEASE_FILL_FIELDS'));
2309
2310 // abort
2311 return false;
2312 }
2313
2314 // check if suggesting to mark the rooms as closed is necessary
2315 var set_units_closed = 0;
2316 var current_room_units = 0;
2317 var vbo_bookscal_roomfilt = widget_instance.find('.vbo-booskcal-roomid');
2318 if (vbo_bookscal_roomfilt.val()) {
2319 var current_room_units = vbo_bookscal_roomfilt.find('option:selected').attr('data-units');
2320 }
2321 if (!cust_id && !cust_data && !closure && current_room_units > 1) {
2322 // no closure, no customer email, multiple units, ask if the units should be marked as closed
2323 if (confirm(Joomla.JText._('VBO_MARK_UNITS_CLOSED'))) {
2324 set_units_closed = 1;
2325 cust_data = Joomla.JText._('VBDBTEXTROOMCLOSED');
2326 }
2327 }
2328
2329 if (!set_units_closed && !cust_id && !cust_data && !closure) {
2330 // missing information
2331 alert(Joomla.JText._('VBO_PLEASE_FILL_FIELDS'));
2332
2333 // abort
2334 return false;
2335 }
2336
2337 // loading content
2338 var loading_content = '<div class="vbo-modal-overlay-content-backdrop"><div class="vbo-modal-overlay-content-backdrop-body">' + VBOCore.options.default_loading_body + '</div></div>';
2339
2340 // show loading
2341 widget_instance.find('.vbo-widget-booskcal-newbook-cont').prepend(loading_content);
2342
2343 // the widget method to call
2344 var call_method = 'saveBooking';
2345
2346 // make a request to save the booking
2347 VBOCore.doAjax(
2348 "<?php echo $this->getExecWidgetAjaxUri(); ?>",
2349 {
2350 widget_id: "<?php echo $this->getIdentifier(); ?>",
2351 call: call_method,
2352 return: 1,
2353 room_id: room_id,
2354 checkin: checkin,
2355 checkout: checkout,
2356 closure: closure,
2357 units_closed: set_units_closed,
2358 units: units,
2359 adults: adults,
2360 children: children,
2361 cust_id: cust_id,
2362 cust_email: cust_email,
2363 cust_data: cust_data,
2364 cust_country: cust_country,
2365 cust_state: cust_state,
2366 cust_phone: cust_phone,
2367 cust_tfname: cust_tfname,
2368 cust_tlname: cust_tlname,
2369 roomcost: roomcost,
2370 idprice: idprice,
2371 cust_roomcost: cust_roomcost,
2372 taxid: taxid,
2373 wrapper: wrapper,
2374 tmpl: "component"
2375 },
2376 (response) => {
2377 // stop loading
2378 widget_instance.find('.vbo-modal-overlay-content-backdrop').remove();
2379 try {
2380 var obj_res = typeof response === 'string' ? JSON.parse(response) : response;
2381 if (!obj_res.hasOwnProperty(call_method)) {
2382 console.error('Unexpected JSON response', obj_res);
2383 return false;
2384 }
2385
2386 // show last booking created
2387 widget_instance.find('.vbo-widget-booskcal-newbook-start').hide();
2388 widget_instance.find('.vbo-widget-booskcal-newbook-id').attr('data-bookingid', obj_res[call_method]['new_booking_id']).show().find('span').text(Joomla.JText._('VBDASHUPRESONE') + ': ' + obj_res[call_method]['new_booking_id']);
2389
2390 // check if should suggest to run VCM
2391 if (obj_res[call_method]['vcm_action']) {
2392 widget_instance.append('<p class="info" onclick="jQuery(this).remove();">' + obj_res[call_method]['vcm_action'] + '</p>');
2393 }
2394
2395 // register the last booking ID created
2396 vbo_widget_books_cal_last_new_bid = obj_res[call_method]['new_booking_id'];
2397
2398 // reload bookings calendar
2399
2400 // show loading skeletons
2401 vboWidgetBooksCalSkeletons(wrapper);
2402
2403 // let the records be loaded for this new room filter
2404 vboWidgetBooksCalLoad(wrapper, 0);
2405 } catch(err) {
2406 console.error('could not parse JSON response', err, response);
2407 }
2408 },
2409 (error) => {
2410 // stop loading
2411 widget_instance.find('.vbo-modal-overlay-content-backdrop').remove();
2412 // display error
2413 console.error(error);
2414 alert(error.responseText);
2415 }
2416 );
2417 }
2418
2419 /**
2420 * Triggers when the newly created booking button is clicked.
2421 */
2422 function vboWidgetBookCalsOpenNewBooking(wrapper) {
2423 var widget_instance = jQuery('#' + wrapper);
2424 if (!widget_instance.length) {
2425 return false;
2426 }
2427
2428 var bid = widget_instance.find('.vbo-widget-booskcal-newbook-id').attr('data-bookingid');
2429 // open the booking
2430 vboWidgetBooksCalOpenBooking(bid);
2431
2432 // switch back to the regular button to create a new booking
2433 widget_instance.find('.vbo-widget-booskcal-newbook-id').hide();
2434 widget_instance.find('.vbo-widget-booskcal-newbook-start').show();
2435 }
2436
2437 /**
2438 * Toggles the cancelled reservations.
2439 */
2440 function vboWidgetBooksCalCancToggle(wrapper) {
2441 var widget_instance = jQuery('#' + wrapper);
2442 if (!widget_instance.length) {
2443 return false;
2444 }
2445
2446 widget_instance.find('[data-type="cancelled"]').toggle();
2447 }
2448
2449 /**
2450 * Triggers when the multitask panel opens.
2451 */
2452 function vboWidgetBooksCalMultitaskOpen(wrapper) {
2453 var widget_instance = jQuery('#' + wrapper);
2454 if (!widget_instance.length) {
2455 return false;
2456 }
2457
2458 // check if a booking ID was set for this page
2459 var page_bid = widget_instance.attr('data-pagebid');
2460 if (!page_bid || page_bid < 1) {
2461 return false;
2462 }
2463
2464 // show loading skeletons
2465 vboWidgetBooksCalSkeletons(wrapper);
2466
2467 // load data by injecting the current booking ID
2468 vboWidgetBooksCalLoad(wrapper, 0, page_bid);
2469 }
2470
2471 </script>
2472 <?php
2473 }
2474 ?>
2475
2476 <script type="text/javascript">
2477
2478 // store the lastly created booking ID
2479 var vbo_widget_books_cal_last_new_bid = null;
2480
2481 // store widget options, if any
2482 var vbo_widget_books_cal_options_oo = <?php echo json_encode($this->getOptions()); ?>;
2483
2484 jQuery(function() {
2485
2486 // when document is ready, load bookings calendar for this widget's instance
2487 vboWidgetBooksCalLoad('<?php echo $wrapper_id; ?>', 0, '<?php echo $modal_load_bid; ?>');
2488
2489 // convert the select to a Select2 element
2490 if (typeof jQuery.fn.select2 !== 'undefined' && <?php echo $use_nice_select; ?>) {
2491 jQuery('#<?php echo $wrapper_id; ?>').find('select.vbo-booskcal-roomid').select2({
2492 width: "100%",
2493 placeholder: "<?php echo htmlspecialchars(VikBooking::strTrimLiteral(JText::translate('VBOREPORTSROOMFILT'))); ?>",
2494 allowClear: true
2495 });
2496 }
2497
2498 // subscribe to the multitask-panel-open event
2499 document.addEventListener(VBOCore.multitask_open_event, function() {
2500 vboWidgetBooksCalMultitaskOpen('<?php echo $wrapper_id; ?>');
2501 });
2502
2503 // subscribe to the multitask-panel-close event to emit the event for the lastly created booking ID
2504 document.addEventListener(VBOCore.multitask_close_event, function() {
2505 if (vbo_widget_books_cal_last_new_bid) {
2506 // emit the event with data for anyone who is listening to it
2507 VBOCore.emitEvent('vbo_new_booking_created', {
2508 bid: vbo_widget_books_cal_last_new_bid
2509 });
2510 }
2511 });
2512
2513 <?php
2514 if ($js_modal_id) {
2515 // widget can be dismissed through the modal
2516 ?>
2517 // subscribe to the modal-dismissed event to emit the event for the lastly created booking ID
2518 document.addEventListener(VBOCore.widget_modal_dismissed + '<?php echo $js_modal_id; ?>', function() {
2519 if (vbo_widget_books_cal_last_new_bid) {
2520 // emit the event with data for anyone who is listening to it
2521 VBOCore.emitEvent('vbo_new_booking_created', {
2522 bid: vbo_widget_books_cal_last_new_bid
2523 });
2524 }
2525 });
2526 <?php
2527 }
2528 ?>
2529
2530 });
2531
2532 </script>
2533
2534 <?php
2535 }
2536
2537 /**
2538 * Helper method to load the booking cancellations for the given room and date.
2539 *
2540 * @param int $room_id the Vik Booking room ID.
2541 * @param string $ymd the current calendar date in Y-m-d format.
2542 *
2543 * @return array list of involved booking cancellations.
2544 *
2545 * @since 1.16.1 (J) - 1.6.1 (WP)
2546 */
2547 protected function loadCancellations($room_id, $ymd)
2548 {
2549 $dbo = JFactory::getDbo();
2550
2551 if (empty($room_id) || empty($ymd)) {
2552 return [];
2553 }
2554
2555 $stay_date_info = getdate(strtotime($ymd));
2556 $lim_ts_to = mktime(23, 59, 59, $stay_date_info['mon'], $stay_date_info['mday'], $stay_date_info['year']);
2557
2558 $q = $dbo->getQuery(true);
2559
2560 $q->select($dbo->qn('o') . '.*');
2561 $q->from($dbo->qn('#__vikbooking_orders', 'o'));
2562 $q->leftjoin($dbo->qn('#__vikbooking_ordersrooms', 'or') . ' ON ' . $dbo->qn('o.id') . ' = ' . $dbo->qn('or.idorder'));
2563 $q->where($dbo->qn('o.status') . ' = ' . $dbo->q('cancelled'));
2564 $q->where($dbo->qn('o.checkin') . ' <= ' . $lim_ts_to);
2565 $q->where($dbo->qn('o.checkout') . ' > ' . $lim_ts_to);
2566 $q->where($dbo->qn('or.idroom') . ' = ' . (int)$room_id);
2567 $q->group($dbo->qn('o.id'));
2568 $q->order($dbo->qn('o.id') . ' ASC');
2569
2570 $dbo->setQuery($q);
2571 $cancellations = $dbo->loadAssocList();
2572
2573 // join the customer information with a separate query as this is faster than joining two more tables at once
2574 foreach ($cancellations as &$canc_book) {
2575 $q = $dbo->getQuery(true);
2576
2577 $q->select($dbo->qn('co.idcustomer'));
2578 $q->select('CONCAT_WS(" ", ' . $dbo->qn('c.first_name') . ', ' . $dbo->qn('c.last_name') . ') AS ' . $dbo->qn('customer_fullname'));
2579 $q->select($dbo->qn('c.country', 'customer_country'));
2580 $q->select($dbo->qn('c.pic'));
2581 $q->from($dbo->qn('#__vikbooking_customers_orders', 'co'));
2582 $q->leftjoin($dbo->qn('#__vikbooking_customers', 'c') . ' ON ' . $dbo->qn('c.id') . ' = ' . $dbo->qn('co.idcustomer'));
2583 $q->where($dbo->qn('co.idorder') . ' = ' . (int)$canc_book['id']);
2584
2585 $dbo->setQuery($q);
2586 $customer_data = $dbo->loadAssoc();
2587
2588 if ($customer_data) {
2589 // merge properties
2590 $canc_book = array_merge($canc_book, $customer_data);
2591 }
2592 }
2593
2594 // unset last reference
2595 unset($canc_book);
2596
2597 // return the list of cancellation bookings
2598 return $cancellations;
2599 }
2600 }
2601