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 / check_availability.php

check_availability.php in VikBooking Hotel Booking Engine & PMS 1.6.5, at admin/helpers/widgets/check_availability.php

1,052 lines 37.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 "check availability".
15 *
16 * @since 1.15.0 (J) - 1.5.0 (WP)
17 */
18 class VikBookingAdminWidgetCheckAvailability 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 results per page.
30 *
31 * @var int
32 */
33 protected $res_per_page = 3;
34
35 /**
36 * Maximum alternative dates to be displayed.
37 *
38 * @var int
39 */
40 protected $max_alternative_dates = 6;
41
42 /**
43 * Maximum alternative parties to be displayed.
44 *
45 * @var int
46 */
47 protected $max_alternative_parties = 8;
48
49 /**
50 * Class constructor will define the widget name and identifier.
51 */
52 public function __construct()
53 {
54 // call parent constructor
55 parent::__construct();
56
57 $this->widgetName = JText::translate('VBO_W_CHECKAV_TITLE');
58 $this->widgetDescr = JText::translate('VBO_W_CHECKAV_DESCR');
59 $this->widgetId = basename(__FILE__, '.php');
60
61 // define widget and icon and style name
62 $this->widgetIcon = '<i class="' . VikBookingIcons::i('calculator') . '"></i>';
63 $this->widgetStyleName = 'blue';
64 }
65
66 /**
67 * Custom method for this widget only to load the rates flow records.
68 * The method is called by the admin controller through an AJAX request.
69 * The visibility should be public, it should not exit the process, and
70 * any content sent to output will be returned to the AJAX response.
71 * In this case we return an array because this method requires "return":1.
72 *
73 * It's the actual rendering of the widget which also allows navigation.
74 */
75 public function loadRoomRates()
76 {
77 $offset = VikRequest::getInt('offset', 0, 'request');
78 $length = VikRequest::getInt('length', $this->res_per_page, 'request');
79 $wrapper = VikRequest::getString('wrapper', '', 'request');
80
81 $checkin_date = VikRequest::getString('checkin_date', '', 'request');
82 $num_nights = VikRequest::getInt('num_nights', 1, 'request');
83 $num_adults = VikRequest::getInt('num_adults', 2, 'request');
84 $num_children = VikRequest::getInt('num_children', 0, 'request');
85
86 // optional room IDs and guests array to consider
87 $room_ids = [];
88 $arr_adults = [];
89 $arr_children = [];
90
91 // multitask can inject a booking id
92 $bid = VikRequest::getInt('bid', 0, 'request');
93 if (!empty($bid)) {
94 // get proper loading values for this booking
95 list($b_checkin_date, $b_num_nights, $b_num_adults, $b_num_children, $room_ids) = $this->getBookingLoadValues($bid);
96 // only check the calculated booking checkin date to determine if loading values was successful
97 if (!empty($b_checkin_date)) {
98 // overwrite default values
99 $checkin_date = $b_checkin_date;
100 $num_nights = $b_num_nights;
101 if (is_array($b_num_adults)) {
102 // multiple rooms involved
103 $num_adults = array_sum($b_num_adults);
104 $num_children = array_sum($b_num_children);
105 $arr_adults = $b_num_adults;
106 $arr_children = $b_num_children;
107 } else {
108 // single room involved
109 $num_adults = $b_num_adults;
110 $num_children = $b_num_children;
111 }
112 }
113 }
114
115 if (empty($checkin_date)) {
116 // default to today's date
117 $checkin_date = date('Y-m-d');
118 }
119 if ($num_nights < 1) {
120 // minimum number of nights is 1
121 $num_nights = 1;
122 }
123
124 // convert date to timestamp to support custom date formats
125 $checkin_ts = VikBooking::getDateTimestamp($checkin_date);
126 $checkin_info = getdate($checkin_ts);
127 $checkout_ts = mktime(0, 0, 0, $checkin_info['mon'], ($checkin_info['mday'] + $num_nights), $checkin_info['year']);
128 $checkout_info = getdate($checkout_ts);
129 // build final dates
130 $checkin_date = date('Y-m-d', $checkin_ts);
131 $checkout_date = date('Y-m-d', $checkout_ts);
132
133 // we make use of the rates flow report helper class
134 $report = VikBooking::getReportInstance('rates_flow');
135 if (!$report) {
136 // display nothing
137 return;
138 }
139
140 // date formats and separator
141 $df = $report->getDateFormat();
142 $dtpicker_df = $report->getDateFormat('jui');
143 $date_sep = VikBooking::getDateSeparator();
144
145 // currency symbol
146 $currency = VikBooking::getCurrencySymb();
147
148 // invoke availability helper class
149 $av_helper = VikBooking::getAvailabilityInstance();
150
151 // set stay values
152 $av_helper->setStayDates($checkin_date, $checkout_date);
153 if (count($arr_adults) > 1) {
154 // multiple rooms involved
155 foreach ($arr_adults as $gkey => $radults) {
156 $av_helper->setRoomParty($radults, (isset($arr_children[$gkey]) ? $arr_children[$gkey] : 0));
157 }
158 } else {
159 // single room involved
160 $av_helper->setRoomParty($num_adults, $num_children);
161 }
162
163 if ($room_ids) {
164 $av_helper->setRoomIds($room_ids);
165 }
166
167 // load available room rates (all records, before pagination that will be applied later)
168 $room_rates = $av_helper->getRates();
169
170 // load rooms and rate plans
171 $all_rooms = $av_helper->loadRooms();
172 $all_rplans = $av_helper->loadRatePlans();
173
174 // check if availability errors occurred
175 $has_av_error = strlen($av_helper->getError());
176 $av_error_code = $av_helper->getErrorCode();
177
178 // count total records
179 $tot_records = is_array($room_rates) && !$has_av_error ? count($room_rates) : 0;
180
181 // check if a next page can be available
182 $has_next_page = (($offset + $length) < $tot_records) ? 1 : 0;
183
184 // splice the array returned to support pagination
185 if ($tot_records > $length) {
186 $split_room_rates = array();
187 $index_offset = 0;
188 foreach ($room_rates as $rid => $rates) {
189 if ($index_offset >= $offset) {
190 $split_room_rates[$rid] = $rates;
191 }
192 if (count($split_room_rates) >= $length) {
193 break;
194 }
195 $index_offset++;
196 }
197 $room_rates = $split_room_rates;
198 unset($split_room_rates);
199 }
200
201 // list of eligible and available room IDs
202 $eligible_rids = [];
203
204 // start output buffering
205 ob_start();
206
207 // display the information about the dates and room party
208 $party_info_parts = array();
209 // stay dates information
210 $checkin_str = VikBooking::sayWeekDay($checkin_info['wday'], true) . ', ' . date(str_replace('/', $date_sep, $df), $checkin_ts);
211 $checkout_str = VikBooking::sayWeekDay($checkout_info['wday'], true) . ', ' . date(str_replace('/', $date_sep, $df), $checkout_ts);
212 $party_info_parts[] = $checkin_str . ' - ' . $checkout_str;
213 // nights information
214 $nights_lbl = $num_nights > 1 ? JText::translate('VBDAYS') : JText::translate('VBDAY');
215 $party_info_parts[] = $num_nights . ' ' . $nights_lbl;
216 // adults information
217 $adults_lbl = $num_adults === 1 ? JText::translate('VBMAILADULT') : JText::translate('VBEDITORDERADULTS');
218 $party_info_parts[] = $num_adults . ' ' . $adults_lbl;
219 // children information
220 $children_lbl = $num_children === 1 ? JText::translate('VBMAILCHILD') : JText::translate('VBEDITORDERCHILDREN');
221 if ($num_children > 0) {
222 $party_info_parts[] = $num_children . ' ' . $children_lbl;
223 }
224 ?>
225 <div class="vbo-widget-checkav-result-party">
226 <p><?php echo implode(', ', $party_info_parts); ?></p>
227 </div>
228 <?php
229
230 // check if there is availability
231 if (!is_array($room_rates) || !$room_rates || $has_av_error) {
232 $explain_err = $av_helper->explainErrorCode();
233 $say_error = !empty($explain_err) ? $explain_err : $av_helper->getError();
234 $say_error = empty($say_error) ? JText::translate('VBOVERVIEWLEGRED') : $say_error;
235 ?>
236 <p class="err"><?php echo $say_error; ?></p>
237 <?php
238 } else {
239 // parse room rates
240 foreach ($room_rates as $rid => $rates) {
241 if (!isset($all_rooms[$rid])) {
242 // this is to prevent reserved keys from being displayed
243 continue;
244 }
245 // push room ID as eligible and available
246 $eligible_rids[] = $rid;
247 ?>
248 <div class="vbo-widget-checkav-result-room-rates">
249 <div class="vbo-widget-checkav-result-room-name">
250 <span><?php echo $all_rooms[$rid]['name']; ?></span>
251 </div>
252 <div class="vbo-widget-checkav-result-rates-list">
253 <?php
254 foreach ($rates as $rplan) {
255 $net_price = $rplan['cost'] - $rplan['taxes'];
256 $js_book_args = "'$wrapper', '{$rid}', '{$checkin_date}', '{$checkout_date}', '{$num_adults}', '{$num_children}', '{$rplan['idprice']}'";
257 ?>
258 <div class="vbo-widget-checkav-result-room-rate">
259 <div class="vbo-widget-checkav-result-rate-name">
260 <span><?php echo isset($all_rplans[$rplan['idprice']]) ? $all_rplans[$rplan['idprice']]['name'] : '?'; ?></span>
261 </div>
262 <div class="vbo-widget-checkav-result-rate-prices">
263 <?php
264 if ($net_price != $rplan['cost']) {
265 // taxes are greater than zero
266 ?>
267 <span class="vbo-widget-checkav-result-rate-price vbo-widget-checkav-result-rate-net">
268 <span><?php echo JText::translate('VBCALCRATESNET'); ?></span>
269 <?php echo $currency . ' ' . VikBooking::numberFormat($net_price); ?>
270 </span>
271 <span class="vbo-widget-checkav-result-rate-price vbo-widget-checkav-result-rate-tax">
272 <span><?php echo JText::translate('VBCALCRATESTAX'); ?></span>
273 <?php echo $currency . ' ' . VikBooking::numberFormat($rplan['taxes']); ?>
274 </span>
275 <?php
276 }
277 ?>
278 <span class="vbo-widget-checkav-result-rate-price vbo-widget-checkav-result-rate-total">
279 <span><?php echo JText::translate('VBCALCRATESTOT'); ?></span>
280 <?php echo $currency . ' ' . VikBooking::numberFormat($rplan['cost']); ?>
281 </span>
282 <span class="vbo-widget-checkav-result-rate-price vbo-widget-checkav-result-rate-booknow">
283 <button type="button" class="btn btn-primary" onclick="vboWidgetCheckAvailabilityBook(<?php echo $js_book_args; ?>);"><?php echo JText::translate('VBO_BOOKNOW'); ?></button>
284 </span>
285 </div>
286 </div>
287 <?php
288 }
289 ?>
290 </div>
291 </div>
292 <?php
293 }
294
295 // append navigation
296 ?>
297 <div class="vbo-widget-commands vbo-widget-commands-right">
298 <div class="vbo-widget-commands-main">
299 <?php
300 if ($offset > 0) {
301 // show backward navigation button
302 ?>
303 <div class="vbo-widget-command-chevron vbo-widget-command-prev">
304 <span class="vbo-widget-command-chevron-prev" onclick="vboWidgetCheckAvailabilityNavigate('<?php echo $wrapper; ?>', -1);"><?php VikBookingIcons::e('chevron-left'); ?></span>
305 </div>
306 <?php
307 }
308 if ($has_next_page) {
309 // show forward navigation button
310 ?>
311 <div class="vbo-widget-command-chevron vbo-widget-command-next">
312 <span class="vbo-widget-command-chevron-next" onclick="vboWidgetCheckAvailabilityNavigate('<?php echo $wrapper; ?>', 1);"><?php VikBookingIcons::e('chevron-right'); ?></span>
313 </div>
314 <?php
315 }
316 ?>
317 </div>
318 </div>
319 <?php
320 }
321
322 // default pool of suggestions
323 $alternative_dates = [];
324 $alternative_parties = [];
325 $split_stay_sols = [];
326
327 if (!is_array($room_rates) || !$room_rates || $has_av_error) {
328 // try to get the suggestions when no availability
329 list($alternative_dates, $alternative_parties, $split_stay_sols) = $av_helper->findSuggestions();
330 } elseif ($eligible_rids) {
331 /**
332 * We did return some available rooms, but we still want to calculate if some
333 * split-stay solutions are also available for some rooms that were left unsold.
334 *
335 * @since 1.16.3 (J) - 1.6.3 (WP)
336 */
337 $parsable_rids = array_diff(array_keys($av_helper->filterPublishedRooms()), $eligible_rids);
338 if ($parsable_rids) {
339 // try to suggest split stay solutions by using the room IDs that were not available
340 $split_stay_sols = $av_helper->findSplitStays($parsable_rids);
341 }
342 }
343
344 // parse booking split stays
345 if ($split_stay_sols) {
346 // we've got booking split stays to suggest
347 ?>
348 <p class="info"><?php VikBookingIcons::e('random'); ?> <?php echo JText::translate('VBO_SPLIT_STAYS'); ?></p>
349 <div class="vbo-widget-checkav-result-splitstays-wrap">
350 <?php
351 foreach ($split_stay_sols as $split_stay_sol) {
352 ?>
353 <div class="vbo-widget-checkav-splitstay-rooms">
354 <div class="vbo-widget-checkav-splitstay-rooms-inner">
355 <?php
356 // start args for booking this split stay solution
357 $first_rid = $split_stay_sol[0]['idroom'];
358 $split_stay_values = [
359 'checkin' => $checkin_date,
360 'checkout' => $checkout_date,
361 'split_stay' => [],
362 ];
363 // parse rooms for the split stay
364 foreach ($split_stay_sol as $split_k => $split_stay_room) {
365 // parse stay dates into DateTime objects
366 $checkin_dt_obj = new JDate($split_stay_room['checkin']);
367 $checkout_dt_obj = new JDate($split_stay_room['checkout']);
368 // push split stay data for booking
369 $split_stay_values['split_stay'][] = [
370 'idroom' => $split_stay_room['idroom'],
371 'checkin' => $split_stay_room['checkin'],
372 'checkout' => $split_stay_room['checkout'],
373 'nights' => $split_stay_room['nights'],
374 ];
375 ?>
376 <div class="vbo-widget-checkav-splitstay-room">
377 <div class="vbo-widget-checkav-result-room-name">
378 <span><?php VikBookingIcons::e(($split_k > 0 ? 'random' : 'arrow-right')); ?> <?php echo $split_stay_room['room_name']; ?></span>
379 </div>
380 <div class="vbo-widget-checkav-result-altdates-info">
381 <div class="vbo-widget-checkav-result-alt-date">
382 <span class="vbo-widget-checkav-result-split-nights"><?php VikBookingIcons::e('moon'); ?> <?php echo $split_stay_room['nights']; ?> <?php echo $split_stay_room['nights'] > 1 ? JText::translate('VBDAYS') : JText::translate('VBDAY'); ?></span>
383 <span class="vbo-widget-checkav-result-alt-date-in"><?php VikBookingIcons::e('plane-arrival'); ?> <?php echo $checkin_dt_obj->format('D, d M Y', true); ?></span>
384 <span class="vbo-widget-checkav-result-alt-date-out"><?php VikBookingIcons::e('plane-departure'); ?> <?php echo $checkout_dt_obj->format('D, d M Y', true); ?></span>
385 </div>
386 </div>
387 </div>
388 <?php
389 }
390 // finalize args for booking this split stay
391 $split_stay_qstring = http_build_query($split_stay_values);
392 $js_book_args = "'{$first_rid}', '{$num_adults}', '{$num_children}', '{$split_stay_qstring}'";
393 ?>
394 </div>
395 <div class="vbo-widget-checkav-splitstay-rooms-book">
396 <button type="button" class="btn btn-primary" onclick="vboWidgetCheckAvailabilityBookSplitStay(<?php echo $js_book_args; ?>);"><?php echo JText::translate('VBO_BOOKNOW'); ?></button>
397 </div>
398 </div>
399 <?php
400 }
401 ?>
402 </div>
403 <?php
404 }
405
406 // parse alrernative dates (if any)
407 if ($alternative_dates) {
408 // we've got some alternative dates to suggest for some rooms
409 ?>
410 <p class="info"><?php echo JText::translate('VBO_ALT_STAY_DATES'); ?></p>
411 <div class="vbo-widget-checkav-result-altdates-wrap">
412 <?php
413 $alt_displayed = 0;
414 foreach ($alternative_dates as $arrive_dt => $alt_rooms) {
415 $alt_displayed++;
416 foreach ($alt_rooms as $rid => $alt_room) {
417 if (empty($alt_room['days_av_left'])) {
418 continue;
419 }
420 $sugg_checkin_dt = null;
421 $sugg_checkout_dt = null;
422 foreach ($alt_room['days_av_left'] as $dayk => $uleft) {
423 if (empty($sugg_checkin_dt)) {
424 // grab the first date
425 $sugg_checkin_dt = $dayk;
426 }
427 // always overwrite until last date
428 $sugg_checkout_dt = $dayk;
429 }
430 // increase check-out date by one day (day after last night of stay)
431 $sugg_out_info = getdate(strtotime($sugg_checkout_dt));
432 $sugg_checkout_dt = date('Y-m-d', mktime(0, 0, 0, $sugg_out_info['mon'], ($sugg_out_info['mday'] + 1), $sugg_out_info['year']));
433 // parse stay dates into DateTime objects
434 $checkin_dt_obj = new JDate($sugg_checkin_dt);
435 $checkout_dt_obj = new JDate($sugg_checkout_dt);
436 // count total nights of stay
437 $sug_tot_nights = count($alt_room['days_av_left']);
438 // format suggested check-in date for datepicker
439 $cal_checkin_dt = date($df, strtotime($sugg_checkin_dt));
440 // js arguments
441 $js_pick_args = "'$wrapper', '{$rid}', '{$cal_checkin_dt}', '{$sug_tot_nights}', '{$num_adults}', '{$num_children}'";
442 ?>
443 <div class="vbo-widget-checkav-result-room-rates vbo-widget-checkav-result-altdates">
444 <div class="vbo-widget-checkav-result-room-name">
445 <span><?php echo $alt_room['name']; ?></span>
446 </div>
447 <div class="vbo-widget-checkav-result-altdates-info">
448 <div class="vbo-widget-checkav-result-alt-date">
449 <span class="vbo-widget-checkav-result-alt-date-in"><?php VikBookingIcons::e('plane-arrival'); ?> <?php echo $checkin_dt_obj->format('D, d M Y', true); ?></span>
450 <span class="vbo-widget-checkav-result-alt-date-out"><?php VikBookingIcons::e('plane-departure'); ?> <?php echo $checkout_dt_obj->format('D, d M Y', true); ?></span>
451 </div>
452 <div class="vbo-widget-checkav-result-alt-choose">
453 <button type="button" class="btn btn-primary" onclick="vboWidgetCheckAvailabilityPickRoom(<?php echo $js_pick_args; ?>);"><?php echo JText::translate('VBO_SELECT'); ?></button>
454 </div>
455 </div>
456 </div>
457 <?php
458 }
459 // check limit of alternative dates
460 if ($alt_displayed >= $this->max_alternative_dates) {
461 break;
462 }
463 }
464 ?>
465 </div>
466 <?php
467 }
468
469 // parse alrernative parties (if any)
470 if ($alternative_parties) {
471 // we've got some alternative combinations of rooms to suggest to fit the party
472 ?>
473 <p class="info"><?php echo JText::translate('VBO_ALT_ROOM_PARTIES'); ?></p>
474 <div class="vbo-widget-checkav-result-altparties-wrap">
475 <?php
476 $alt_displayed = 0;
477 foreach ($alternative_parties as $arrive_dt => $alt_parties) {
478 $alt_displayed++;
479 $alt_dates_displayed = false;
480 ?>
481 <div class="vbo-widget-checkav-result-room-rates vbo-widget-checkav-result-altparty">
482 <?php
483 foreach ($alt_parties as $alt_party) {
484 if (empty($alt_party['days_av_left']) || empty($alt_party['guests_allocation'])) {
485 continue;
486 }
487 // room ID
488 $rid = $alt_party['id'];
489 // count total nights of stay
490 $sug_tot_nights = count($alt_party['days_av_left']);
491 // party info
492 $party_info_parts = array();
493 // adults information
494 $adults_lbl = $alt_party['guests_allocation']['adults'] == 1 ? JText::translate('VBMAILADULT') : JText::translate('VBEDITORDERADULTS');
495 $party_info_parts[] = $alt_party['guests_allocation']['adults'] . ' ' . $adults_lbl;
496 // children information
497 $children_lbl = isset($alt_party['guests_allocation']['children']) && $alt_party['guests_allocation']['children'] == 1 ? JText::translate('VBMAILCHILD') : JText::translate('VBEDITORDERCHILDREN');
498 if (!empty($alt_party['guests_allocation']['children'])) {
499 $party_info_parts[] = $alt_party['guests_allocation']['children'] . ' ' . $children_lbl;
500 }
501 if (!$alt_dates_displayed) {
502 // display the stay dates once per alternative party
503 $alt_dates_displayed = true;
504 // calculate suggested stay dates
505 $sugg_checkin_dt = null;
506 $sugg_checkout_dt = null;
507 foreach ($alt_party['days_av_left'] as $dayk => $uleft) {
508 if (empty($sugg_checkin_dt)) {
509 // grab the first date
510 $sugg_checkin_dt = $dayk;
511 }
512 // always overwrite until last date
513 $sugg_checkout_dt = $dayk;
514 }
515 // increase check-out date by one day (day after last night of stay)
516 $sugg_out_info = getdate(strtotime($sugg_checkout_dt));
517 $sugg_checkout_dt = date('Y-m-d', mktime(0, 0, 0, $sugg_out_info['mon'], ($sugg_out_info['mday'] + 1), $sugg_out_info['year']));
518 // parse stay dates into DateTime objects
519 $checkin_dt_obj = new JDate($sugg_checkin_dt);
520 $checkout_dt_obj = new JDate($sugg_checkout_dt);
521 ?>
522 <div class="vbo-widget-checkav-result-altparty-dates">
523 <div class="vbo-widget-checkav-result-altparty-stay">
524 <span class="vbo-widget-checkav-result-alt-date-in"><?php VikBookingIcons::e('plane-arrival'); ?> <?php echo $checkin_dt_obj->format('D, d M Y', true); ?></span>
525 <span class="vbo-widget-checkav-result-alt-date-out"><?php VikBookingIcons::e('plane-departure'); ?> <?php echo $checkout_dt_obj->format('D, d M Y', true); ?></span>
526 </div>
527 </div>
528 <?php
529 }
530 ?>
531 <div class="vbo-widget-checkav-result-altparty-room">
532 <div class="vbo-widget-checkav-result-room-name">
533 <span><?php echo $alt_party['name']; ?></span>
534 </div>
535 <div class="vbo-widget-checkav-result-alt-guests-rparty">
536 <span><?php VikBookingIcons::e('users'); ?> <?php echo implode(', ', $party_info_parts); ?></span>
537 </div>
538 </div>
539 <?php
540 }
541 ?>
542 </div>
543 <?php
544 // check limit of alternative parties
545 if ($alt_displayed >= $this->max_alternative_parties) {
546 break;
547 }
548 }
549 ?>
550 </div>
551 <?php
552 }
553
554 // get the HTML buffer
555 $html_content = ob_get_contents();
556 ob_end_clean();
557
558 // return an associative array of values
559 return array(
560 'html' => $html_content,
561 'tot_records' => $tot_records,
562 'next_page' => $has_next_page,
563 );
564 }
565
566 /**
567 * Preload the necessary CSS/JS assets.
568 *
569 * @return void
570 */
571 public function preload()
572 {
573 // load assets
574 $this->vbo_app->loadDatePicker();
575 }
576
577 public function render(VBOMultitaskData $data = null)
578 {
579 // increase widget's instance counter
580 static::$instance_counter++;
581
582 // check whether the widget is being rendered via AJAX when adding it through the customizer
583 $is_ajax = $this->isAjaxRendering();
584
585 // generate a unique ID for the sticky notes wrapper instance
586 $wrapper_instance = !$is_ajax ? static::$instance_counter : rand();
587 $wrapper_id = 'vbo-widget-checkav-' . $wrapper_instance;
588
589 // get permissions
590 $vbo_auth_bookings = JFactory::getUser()->authorise('core.vbo.bookings', 'com_vikbooking');
591 if (!$vbo_auth_bookings) {
592 // display nothing
593 return;
594 }
595
596 // we make use of the rates flow report helper class
597 $report = VikBooking::getReportInstance('rates_flow');
598 if (!$report) {
599 // display nothing
600 return;
601 }
602
603 // date formats
604 $df = $report->getDateFormat();
605 $dtpicker_df = $report->getDateFormat('jui');
606
607 // get highest max adults and max children
608 list($max_adults, $max_children) = $this->getMaxestGuests();
609
610 // check multitask data
611 $page_bid = 0;
612 $modal_load_bid = '';
613 if ($data) {
614 $page_bid = $data->getBookingID();
615 if ($page_bid && $data->isModalRendering()) {
616 // immediately load contents according to injected multitask data
617 $modal_load_bid = $page_bid;
618 }
619 }
620
621 ?>
622 <div class="vbo-admin-widget-wrapper">
623 <div class="vbo-admin-widget-head">
624 <h4><?php echo $this->widgetIcon; ?> <span><?php echo $this->widgetName; ?></span></h4>
625 </div>
626 <div id="<?php echo $wrapper_id; ?>" class="vbo-widget-checkav-wrap" data-instance="<?php echo $wrapper_instance; ?>" data-pagebid="<?php echo $page_bid; ?>" data-offset="0" data-length="<?php echo $this->res_per_page; ?>">
627 <div class="vbo-widget-checkav-filters">
628 <div class="vbo-widget-checkav-filters-main">
629 <div class="vbo-widget-checkav-filter vbo-widget-checkav-filter-dpicker">
630 <div class="vbo-field-calendar">
631 <div class="input-append">
632 <input type="text" class="vbo-widget-checkav-checkindt" value="" placeholder="<?php echo htmlspecialchars(JText::translate('VBPICKUPAT')); ?>" />
633 <button type="button" class="btn btn-secondary vbo-widget-checkav-checkindt-trigger"><?php VikBookingIcons::e('calendar'); ?></button>
634 </div>
635 </div>
636 </div>
637 <div class="vbo-widget-checkav-filter vbo-widget-checkav-filter-nights">
638 <label for="vbo-widget-checkav-nights-<?php echo $wrapper_instance; ?>"><?php echo JText::translate('VBDAYS'); ?></label>
639 <input type="number" id="vbo-widget-checkav-nights-<?php echo $wrapper_instance; ?>" class="vbo-widget-checkav-nights" min="1" max="365" step="1" value="1" />
640 </div>
641 </div>
642 <div class="vbo-widget-checkav-filters-secondary">
643 <div class="vbo-widget-checkav-filter vbo-widget-checkav-filter-adults">
644 <label for="vbo-widget-checkav-adults-<?php echo $wrapper_instance; ?>"><?php echo JText::translate('VBEDITORDERADULTS'); ?></label>
645 <input type="number" id="vbo-widget-checkav-adults-<?php echo $wrapper_instance; ?>" class="vbo-widget-checkav-adults" min="0" max="<?php echo $max_adults; ?>" step="1" value="2" />
646 </div>
647 <div class="vbo-widget-checkav-filter vbo-widget-checkav-filter-children">
648 <label for="vbo-widget-checkav-children-<?php echo $wrapper_instance; ?>"><?php echo JText::translate('VBEDITORDERCHILDREN'); ?></label>
649 <input type="number" id="vbo-widget-checkav-children-<?php echo $wrapper_instance; ?>" class="vbo-widget-checkav-children" min="0" max="<?php echo $max_children; ?>" step="1" value="0" />
650 </div>
651 </div>
652 <div class="vbo-widget-checkav-filters-submit">
653 <div class="vbo-widget-checkav-filter vbo-widget-checkav-filter-submit">
654 <button type="button" class="btn vbo-config-btn" onclick="vboWidgetCheckAvailabilityCalc('<?php echo $wrapper_id; ?>');"><?php VikBookingIcons::e('bed'); ?><?php echo JText::translate('VBRATESOVWRATESCALCULATORCALC'); ?></button>
655 </div>
656 </div>
657 </div>
658 <div class="vbo-widget-checkav-results"></div>
659 </div>
660 </div>
661 <?php
662
663 if (static::$instance_counter === 0 || $is_ajax) {
664 /**
665 * Print the JS code only once for all instances of this widget.
666 * The real rendering is made through AJAX, not when the page loads.
667 */
668 ?>
669 <a class="vbo-widget-checkav-basenavuri" href="index.php?option=com_vikbooking&task=calendar&cid[]=%d&checkin=%s&checkout=%s&adults=%d&children=%d&idprice=%d&booknow=1" style="display: none;"></a>
670 <a class="vbo-widget-checkav-splitstayuri" href="index.php?option=com_vikbooking&task=calendar&cid[]=%d&adults=%d&children=%d" style="display: none;"></a>
671
672 <script type="text/javascript">
673
674 /**
675 * Display the loading skeletons.
676 */
677 function vboWidgetCheckAvailabilitySkeletons(wrapper) {
678 var widget_instance = jQuery('#' + wrapper);
679 if (!widget_instance.length) {
680 return false;
681 }
682 widget_instance.find('.vbo-widget-checkav-results').html('');
683 for (var i = 0; i < 1; i++) {
684 var skeleton = '';
685 skeleton += '<div class="vbo-dashboard-guest-activity vbo-dashboard-guest-activity-skeleton">';
686 skeleton += ' <div class="vbo-dashboard-guest-activity-avatar">';
687 skeleton += ' <div class="vbo-skeleton-loading vbo-skeleton-loading-avatar"></div>';
688 skeleton += ' </div>';
689 skeleton += ' <div class="vbo-dashboard-guest-activity-content">';
690 skeleton += ' <div class="vbo-dashboard-guest-activity-content-head">';
691 skeleton += ' <div class="vbo-skeleton-loading vbo-skeleton-loading-title"></div>';
692 skeleton += ' </div>';
693 skeleton += ' <div class="vbo-dashboard-guest-activity-content-info-msg">';
694 skeleton += ' <div class="vbo-skeleton-loading vbo-skeleton-loading-content"></div>';
695 skeleton += ' </div>';
696 skeleton += ' </div>';
697 skeleton += '</div>';
698 // append skeleton
699 jQuery(skeleton).appendTo(widget_instance.find('.vbo-widget-checkav-results'));
700 }
701 }
702
703 /**
704 * Perform the request to load the available room rates.
705 */
706 function vboWidgetCheckAvailabilityLoad(wrapper, checkbid) {
707 var widget_instance = jQuery('#' + wrapper);
708 if (!widget_instance.length) {
709 return false;
710 }
711
712 // get vars for making the request
713 var current_offset = parseInt(widget_instance.attr('data-offset'));
714 var length_per_page = parseInt(widget_instance.attr('data-length'));
715 var checkin_date = widget_instance.find('.vbo-widget-checkav-checkindt').val();
716 var num_nights = widget_instance.find('.vbo-widget-checkav-nights').val();
717 var num_adults = widget_instance.find('.vbo-widget-checkav-adults').val();
718 var num_children = widget_instance.find('.vbo-widget-checkav-children').val();
719
720 var bid = null;
721 if (typeof checkbid !== 'undefined' && checkbid) {
722 bid = checkbid;
723 }
724
725 // the widget method to call
726 var call_method = 'loadRoomRates';
727
728 // make a request to load the available room rates
729 VBOCore.doAjax(
730 "<?php echo $this->getExecWidgetAjaxUri(); ?>",
731 {
732 widget_id: "<?php echo $this->getIdentifier(); ?>",
733 call: call_method,
734 return: 1,
735 offset: current_offset,
736 length: length_per_page,
737 checkin_date: checkin_date,
738 num_nights: num_nights,
739 num_adults: num_adults,
740 num_children: num_children,
741 bid: bid,
742 wrapper: wrapper,
743 tmpl: "component"
744 },
745 function(response) {
746 try {
747 var obj_res = typeof response === 'string' ? JSON.parse(response) : response;
748 if (!obj_res.hasOwnProperty(call_method) || !obj_res[call_method]) {
749 console.error('Unexpected JSON response', obj_res);
750 return false;
751 }
752
753 // replace HTML with new available room rates
754 widget_instance.find('.vbo-widget-checkav-results').html(obj_res[call_method]['html']);
755
756 // check results
757 var tot_records = obj_res[call_method]['tot_records'] || 0;
758 if (!isNaN(tot_records) && parseInt(tot_records) < 1) {
759 // no results can indicate the offset is invalid or too high
760 if (!isNaN(current_offset) && parseInt(current_offset) > 0) {
761 // reset offset to 0
762 widget_instance.attr('data-offset', 0);
763 // show loading skeletons
764 vboWidgetCheckAvailabilitySkeletons(wrapper);
765 // reload the first page
766 vboWidgetCheckAvailabilityLoad(wrapper);
767 }
768 }
769 } catch(err) {
770 console.error('could not parse JSON response', err, response);
771 }
772 },
773 function(error) {
774 // remove the skeleton loading
775 widget_instance.find('.vbo-widget-checkav-results').find('.vbo-dashboard-guest-activity-skeleton').remove();
776 console.error(error);
777 }
778 );
779 }
780
781 /**
782 * Navigate between the various pages of the available room rates.
783 */
784 function vboWidgetCheckAvailabilityNavigate(wrapper, direction) {
785 var widget_instance = jQuery('#' + wrapper);
786 if (!widget_instance.length) {
787 return false;
788 }
789
790 // show loading skeletons
791 vboWidgetCheckAvailabilitySkeletons(wrapper);
792
793 var instance_val = widget_instance.attr('data-instance');
794
795 // current offset
796 var current_offset = parseInt(widget_instance.attr('data-offset'));
797
798 // events per page per type
799 var steps = <?php echo $this->res_per_page; ?>;
800
801 // check direction and update offsets for nav
802 if (direction > 0) {
803 // navigate forward
804 widget_instance.attr('data-offset', (current_offset + steps));
805 } else {
806 // navigate backward
807 var new_offset = current_offset - steps;
808 new_offset = new_offset >= 0 ? new_offset : 0;
809 widget_instance.attr('data-offset', new_offset);
810 }
811
812 // launch navigation
813 vboWidgetCheckAvailabilityLoad(wrapper);
814 }
815
816 /**
817 * Calculate the available room rates.
818 */
819 function vboWidgetCheckAvailabilityCalc(wrapper) {
820 var widget_instance = jQuery('#' + wrapper);
821 if (!widget_instance.length) {
822 return false;
823 }
824
825 // show loading skeletons
826 vboWidgetCheckAvailabilitySkeletons(wrapper);
827
828 // always reset offset to the first page
829 widget_instance.attr('data-offset', 0);
830
831 // load data
832 vboWidgetCheckAvailabilityLoad(wrapper);
833 }
834
835 /**
836 * Triggers when the multitask panel opens.
837 */
838 function vboWidgetCheckAvailabilityMultitaskOpen(wrapper) {
839 var widget_instance = jQuery('#' + wrapper);
840 if (!widget_instance.length) {
841 return false;
842 }
843
844 // check if a booking ID was set for this page
845 var page_bid = widget_instance.attr('data-pagebid');
846 if (!page_bid || page_bid < 1) {
847 return false;
848 }
849
850 // show loading skeletons
851 vboWidgetCheckAvailabilitySkeletons(wrapper);
852
853 // always reset offset to the first page
854 widget_instance.attr('data-offset', 0);
855
856 // load data by injecting the current booking ID
857 vboWidgetCheckAvailabilityLoad(wrapper, page_bid);
858 }
859
860 /**
861 * Book a room rate.
862 */
863 function vboWidgetCheckAvailabilityBook(wrapper, rid, din, dout, adults, children, rpid) {
864 var open_url = jQuery('.vbo-widget-checkav-basenavuri').first().attr('href');
865 open_url = open_url.replace('%d', rid);
866 open_url = open_url.replace('%s', din);
867 open_url = open_url.replace('%s', dout);
868 open_url = open_url.replace('%d', adults);
869 open_url = open_url.replace('%d', children);
870 open_url = open_url.replace('%d', rpid);
871 // navigate
872 document.location.href = open_url;
873 }
874
875 /**
876 * Book a split stay.
877 */
878 function vboWidgetCheckAvailabilityBookSplitStay(rid, adults, children, split_qstring) {
879 var open_url = jQuery('.vbo-widget-checkav-splitstayuri').first().attr('href');
880 open_url = open_url.replace('%d', rid);
881 open_url = open_url.replace('%d', adults);
882 open_url = open_url.replace('%d', children);
883 open_url += '&' + split_qstring;
884 // navigate
885 document.location.href = open_url;
886 }
887
888 /**
889 * Pick alternative stay dates suggested for calculation.
890 */
891 function vboWidgetCheckAvailabilityPickRoom(wrapper, rid, din, nights, adults, children) {
892 var widget_instance = jQuery('#' + wrapper);
893 if (!widget_instance.length) {
894 return false;
895 }
896
897 // show loading skeletons
898 vboWidgetCheckAvailabilitySkeletons(wrapper);
899
900 // always reset offset to the first page
901 widget_instance.attr('data-offset', 0);
902
903 // populate check-in date
904 widget_instance.find('.vbo-widget-checkav-checkindt').datepicker('setDate', din);
905
906 // set number of nights, adults and children
907 widget_instance.find('.vbo-widget-checkav-nights').val(nights);
908 widget_instance.find('.vbo-widget-checkav-adults').val(adults);
909 widget_instance.find('.vbo-widget-checkav-children').val(children);
910
911 // load data (we ignore the room ID passed to display all options available)
912 vboWidgetCheckAvailabilityLoad(wrapper);
913 }
914
915 </script>
916 <?php
917 }
918 ?>
919
920 <script type="text/javascript">
921
922 jQuery(function() {
923
924 // render datepicker calendar for dates navigation
925 jQuery('#<?php echo $wrapper_id; ?>').find('.vbo-widget-checkav-checkindt').datepicker({
926 minDate: 0,
927 maxDate: "+3y",
928 yearRange: "<?php echo date('Y'); ?>:<?php echo (date('Y') + 3); ?>",
929 changeMonth: true,
930 changeYear: true,
931 dateFormat: "<?php echo $dtpicker_df; ?>"
932 });
933
934 // triggering for datepicker calendar icon
935 jQuery('#<?php echo $wrapper_id; ?>').find('.vbo-widget-checkav-checkindt-trigger').click(function() {
936 var jdp = jQuery(this).parent().find('input.hasDatepicker');
937 if (jdp.length) {
938 jdp.focus();
939 }
940 });
941
942 // subscribe to the multitask-panel-open event
943 document.addEventListener(VBOCore.multitask_open_event, function() {
944 vboWidgetCheckAvailabilityMultitaskOpen('<?php echo $wrapper_id; ?>');
945 });
946
947 <?php
948 if ($modal_load_bid) {
949 // immediately fire the adaptive rendering according to multitask data
950 ?>
951 vboWidgetCheckAvailabilityMultitaskOpen('<?php echo $wrapper_id; ?>');
952 <?php
953 }
954 ?>
955
956 });
957
958 </script>
959
960 <?php
961 }
962
963 /**
964 * Returns the maximum number of adults and children as a sum from all rooms.
965 * Internal method for this widget only.
966 *
967 * @return array two values, respectively for highest adults and children.
968 */
969 protected function getMaxestGuests()
970 {
971 $maxest = array(1, 1);
972
973 $dbo = JFactory::getDbo();
974
975 $q = "SELECT SUM(`toadult`) AS `max_adults`, SUM(`tochild`) AS `max_children` FROM `#__vikbooking_rooms`;";
976 $dbo->setQuery($q);
977 $record = $dbo->loadAssoc();
978 if ($record) {
979 $maxest = array($record['max_adults'], $record['max_children']);
980 }
981
982 return $maxest;
983 }
984
985 /**
986 * In case the multitask panel injects a booking ID, this method
987 * prepares the proper values to use for loading room rates.
988 *
989 * @param int $bid the booking id.
990 * @param bool $multi_rooms whether to return array of guests
991 * when multiple rooms booked.
992 *
993 * @return array list of values to use for loading.
994 */
995 protected function getBookingLoadValues($bid, $multi_rooms = true)
996 {
997 // set default values
998 $checkin_date = '';
999 $num_nights = 0;
1000 $num_adults = 0;
1001 $num_children = 0;
1002 $room_ids = [];
1003
1004 if (empty($bid)) {
1005 // do nothing
1006 return [$checkin_date, $num_nights, $num_adults, $num_children, $room_ids];
1007 }
1008
1009 // get booking details
1010 $booking = VikBooking::getBookingInfoFromID($bid);
1011
1012 if (!is_array($booking) || !count($booking)) {
1013 // do nothing
1014 return [$checkin_date, $num_nights, $num_adults, $num_children, $room_ids];
1015 }
1016
1017 // get reservation rooms
1018 $book_rooms = VikBooking::loadOrdersRoomsData($booking['id']);
1019
1020 if (!is_array($book_rooms) || !count($book_rooms)) {
1021 // do nothing
1022 return [$checkin_date, $num_nights, $num_adults, $num_children, $room_ids];
1023 }
1024
1025 // build proper values for this reservation
1026 $checkin_date = date('Y-m-d', $booking['checkin']);
1027 $num_nights = $booking['days'];
1028 $arr_adults = [];
1029 $arr_children = [];
1030 foreach ($book_rooms as $roomres) {
1031 $num_adults += $roomres['adults'];
1032 $num_children += $roomres['children'];
1033 $arr_adults[] = $roomres['adults'];
1034 $arr_children[] = $roomres['children'];
1035 $room_ids[] = $roomres['idroom'];
1036 if ($booking['split_stay']) {
1037 // do not sum the guests in case of split stay
1038 $num_adults = $roomres['adults'];
1039 $num_children = $roomres['children'];
1040 }
1041 }
1042
1043 if (count($book_rooms) > 1 && $multi_rooms === true && !$booking['split_stay']) {
1044 // return the adults and children as array rather than as integers
1045 $num_adults = $arr_adults;
1046 $num_children = $arr_children;
1047 }
1048
1049 return [$checkin_date, $num_nights, $num_adults, $num_children, $room_ids];
1050 }
1051 }
1052