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

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