PluginProbe
VikBooking Hotel Booking Engine & PMS / 1.6.4
VikBooking Hotel Booking Engine & PMS v1.6.4
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.4, at admin/helpers/widgets/bookings_calendar.php

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