PluginProbe
VikBooking Hotel Booking Engine & PMS / 1.6.9
VikBooking Hotel Booking Engine & PMS v1.6.9
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.9, at admin/helpers/widgets/check_availability.php

1,053 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 $rplan_taxes = isset($rplan['taxes']) ? $rplan['taxes'] : 0;
256 $net_price = $rplan['cost'] - $rplan_taxes;
257 $js_book_args = "'$wrapper', '{$rid}', '{$checkin_date}', '{$checkout_date}', '{$num_adults}', '{$num_children}', '{$rplan['idprice']}'";
258 ?>
259 <div class="vbo-widget-checkav-result-room-rate">
260 <div class="vbo-widget-checkav-result-rate-name">
261 <span><?php echo isset($all_rplans[$rplan['idprice']]) ? $all_rplans[$rplan['idprice']]['name'] : '?'; ?></span>
262 </div>
263 <div class="vbo-widget-checkav-result-rate-prices">
264 <?php
265 if ($net_price != $rplan['cost']) {
266 // taxes are greater than zero
267 ?>
268 <span class="vbo-widget-checkav-result-rate-price vbo-widget-checkav-result-rate-net">
269 <span><?php echo JText::translate('VBCALCRATESNET'); ?></span>
270 <?php echo $currency . ' ' . VikBooking::numberFormat($net_price); ?>
271 </span>
272 <span class="vbo-widget-checkav-result-rate-price vbo-widget-checkav-result-rate-tax">
273 <span><?php echo JText::translate('VBCALCRATESTAX'); ?></span>
274 <?php echo $currency . ' ' . VikBooking::numberFormat($rplan_taxes); ?>
275 </span>
276 <?php
277 }
278 ?>
279 <span class="vbo-widget-checkav-result-rate-price vbo-widget-checkav-result-rate-total">
280 <span><?php echo JText::translate('VBCALCRATESTOT'); ?></span>
281 <?php echo $currency . ' ' . VikBooking::numberFormat($rplan['cost']); ?>
282 </span>
283 <span class="vbo-widget-checkav-result-rate-price vbo-widget-checkav-result-rate-booknow">
284 <button type="button" class="btn btn-primary" onclick="vboWidgetCheckAvailabilityBook(<?php echo $js_book_args; ?>);"><?php echo JText::translate('VBO_BOOKNOW'); ?></button>
285 </span>
286 </div>
287 </div>
288 <?php
289 }
290 ?>
291 </div>
292 </div>
293 <?php
294 }
295
296 // append navigation
297 ?>
298 <div class="vbo-widget-commands vbo-widget-commands-right">
299 <div class="vbo-widget-commands-main">
300 <?php
301 if ($offset > 0) {
302 // show backward navigation button
303 ?>
304 <div class="vbo-widget-command-chevron vbo-widget-command-prev">
305 <span class="vbo-widget-command-chevron-prev" onclick="vboWidgetCheckAvailabilityNavigate('<?php echo $wrapper; ?>', -1);"><?php VikBookingIcons::e('chevron-left'); ?></span>
306 </div>
307 <?php
308 }
309 if ($has_next_page) {
310 // show forward navigation button
311 ?>
312 <div class="vbo-widget-command-chevron vbo-widget-command-next">
313 <span class="vbo-widget-command-chevron-next" onclick="vboWidgetCheckAvailabilityNavigate('<?php echo $wrapper; ?>', 1);"><?php VikBookingIcons::e('chevron-right'); ?></span>
314 </div>
315 <?php
316 }
317 ?>
318 </div>
319 </div>
320 <?php
321 }
322
323 // default pool of suggestions
324 $alternative_dates = [];
325 $alternative_parties = [];
326 $split_stay_sols = [];
327
328 if (!is_array($room_rates) || !$room_rates || $has_av_error) {
329 // try to get the suggestions when no availability
330 list($alternative_dates, $alternative_parties, $split_stay_sols) = $av_helper->findSuggestions();
331 } elseif ($eligible_rids) {
332 /**
333 * We did return some available rooms, but we still want to calculate if some
334 * split-stay solutions are also available for some rooms that were left unsold.
335 *
336 * @since 1.16.3 (J) - 1.6.3 (WP)
337 */
338 $parsable_rids = array_diff(array_keys($av_helper->filterPublishedRooms()), $eligible_rids);
339 if ($parsable_rids) {
340 // try to suggest split stay solutions by using the room IDs that were not available
341 $split_stay_sols = $av_helper->findSplitStays($parsable_rids);
342 }
343 }
344
345 // parse booking split stays
346 if ($split_stay_sols) {
347 // we've got booking split stays to suggest
348 ?>
349 <p class="info"><?php VikBookingIcons::e('random'); ?> <?php echo JText::translate('VBO_SPLIT_STAYS'); ?></p>
350 <div class="vbo-widget-checkav-result-splitstays-wrap">
351 <?php
352 foreach ($split_stay_sols as $split_stay_sol) {
353 ?>
354 <div class="vbo-widget-checkav-splitstay-rooms">
355 <div class="vbo-widget-checkav-splitstay-rooms-inner">
356 <?php
357 // start args for booking this split stay solution
358 $first_rid = $split_stay_sol[0]['idroom'];
359 $split_stay_values = [
360 'checkin' => $checkin_date,
361 'checkout' => $checkout_date,
362 'split_stay' => [],
363 ];
364 // parse rooms for the split stay
365 foreach ($split_stay_sol as $split_k => $split_stay_room) {
366 // parse stay dates into DateTime objects
367 $checkin_dt_obj = new JDate($split_stay_room['checkin']);
368 $checkout_dt_obj = new JDate($split_stay_room['checkout']);
369 // push split stay data for booking
370 $split_stay_values['split_stay'][] = [
371 'idroom' => $split_stay_room['idroom'],
372 'checkin' => $split_stay_room['checkin'],
373 'checkout' => $split_stay_room['checkout'],
374 'nights' => $split_stay_room['nights'],
375 ];
376 ?>
377 <div class="vbo-widget-checkav-splitstay-room">
378 <div class="vbo-widget-checkav-result-room-name">
379 <span><?php VikBookingIcons::e(($split_k > 0 ? 'random' : 'arrow-right')); ?> <?php echo $split_stay_room['room_name']; ?></span>
380 </div>
381 <div class="vbo-widget-checkav-result-altdates-info">
382 <div class="vbo-widget-checkav-result-alt-date">
383 <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>
384 <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>
385 <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>
386 </div>
387 </div>
388 </div>
389 <?php
390 }
391 // finalize args for booking this split stay
392 $split_stay_qstring = http_build_query($split_stay_values);
393 $js_book_args = "'{$first_rid}', '{$num_adults}', '{$num_children}', '{$split_stay_qstring}'";
394 ?>
395 </div>
396 <div class="vbo-widget-checkav-splitstay-rooms-book">
397 <button type="button" class="btn btn-primary" onclick="vboWidgetCheckAvailabilityBookSplitStay(<?php echo $js_book_args; ?>);"><?php echo JText::translate('VBO_BOOKNOW'); ?></button>
398 </div>
399 </div>
400 <?php
401 }
402 ?>
403 </div>
404 <?php
405 }
406
407 // parse alrernative dates (if any)
408 if ($alternative_dates) {
409 // we've got some alternative dates to suggest for some rooms
410 ?>
411 <p class="info"><?php echo JText::translate('VBO_ALT_STAY_DATES'); ?></p>
412 <div class="vbo-widget-checkav-result-altdates-wrap">
413 <?php
414 $alt_displayed = 0;
415 foreach ($alternative_dates as $arrive_dt => $alt_rooms) {
416 $alt_displayed++;
417 foreach ($alt_rooms as $rid => $alt_room) {
418 if (empty($alt_room['days_av_left'])) {
419 continue;
420 }
421 $sugg_checkin_dt = null;
422 $sugg_checkout_dt = null;
423 foreach ($alt_room['days_av_left'] as $dayk => $uleft) {
424 if (empty($sugg_checkin_dt)) {
425 // grab the first date
426 $sugg_checkin_dt = $dayk;
427 }
428 // always overwrite until last date
429 $sugg_checkout_dt = $dayk;
430 }
431 // increase check-out date by one day (day after last night of stay)
432 $sugg_out_info = getdate(strtotime($sugg_checkout_dt));
433 $sugg_checkout_dt = date('Y-m-d', mktime(0, 0, 0, $sugg_out_info['mon'], ($sugg_out_info['mday'] + 1), $sugg_out_info['year']));
434 // parse stay dates into DateTime objects
435 $checkin_dt_obj = new JDate($sugg_checkin_dt);
436 $checkout_dt_obj = new JDate($sugg_checkout_dt);
437 // count total nights of stay
438 $sug_tot_nights = count($alt_room['days_av_left']);
439 // format suggested check-in date for datepicker
440 $cal_checkin_dt = date($df, strtotime($sugg_checkin_dt));
441 // js arguments
442 $js_pick_args = "'$wrapper', '{$rid}', '{$cal_checkin_dt}', '{$sug_tot_nights}', '{$num_adults}', '{$num_children}'";
443 ?>
444 <div class="vbo-widget-checkav-result-room-rates vbo-widget-checkav-result-altdates">
445 <div class="vbo-widget-checkav-result-room-name">
446 <span><?php echo $alt_room['name']; ?></span>
447 </div>
448 <div class="vbo-widget-checkav-result-altdates-info">
449 <div class="vbo-widget-checkav-result-alt-date">
450 <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>
451 <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>
452 </div>
453 <div class="vbo-widget-checkav-result-alt-choose">
454 <button type="button" class="btn btn-primary" onclick="vboWidgetCheckAvailabilityPickRoom(<?php echo $js_pick_args; ?>);"><?php echo JText::translate('VBO_SELECT'); ?></button>
455 </div>
456 </div>
457 </div>
458 <?php
459 }
460 // check limit of alternative dates
461 if ($alt_displayed >= $this->max_alternative_dates) {
462 break;
463 }
464 }
465 ?>
466 </div>
467 <?php
468 }
469
470 // parse alrernative parties (if any)
471 if ($alternative_parties) {
472 // we've got some alternative combinations of rooms to suggest to fit the party
473 ?>
474 <p class="info"><?php echo JText::translate('VBO_ALT_ROOM_PARTIES'); ?></p>
475 <div class="vbo-widget-checkav-result-altparties-wrap">
476 <?php
477 $alt_displayed = 0;
478 foreach ($alternative_parties as $arrive_dt => $alt_parties) {
479 $alt_displayed++;
480 $alt_dates_displayed = false;
481 ?>
482 <div class="vbo-widget-checkav-result-room-rates vbo-widget-checkav-result-altparty">
483 <?php
484 foreach ($alt_parties as $alt_party) {
485 if (empty($alt_party['days_av_left']) || empty($alt_party['guests_allocation'])) {
486 continue;
487 }
488 // room ID
489 $rid = $alt_party['id'];
490 // count total nights of stay
491 $sug_tot_nights = count($alt_party['days_av_left']);
492 // party info
493 $party_info_parts = array();
494 // adults information
495 $adults_lbl = $alt_party['guests_allocation']['adults'] == 1 ? JText::translate('VBMAILADULT') : JText::translate('VBEDITORDERADULTS');
496 $party_info_parts[] = $alt_party['guests_allocation']['adults'] . ' ' . $adults_lbl;
497 // children information
498 $children_lbl = isset($alt_party['guests_allocation']['children']) && $alt_party['guests_allocation']['children'] == 1 ? JText::translate('VBMAILCHILD') : JText::translate('VBEDITORDERCHILDREN');
499 if (!empty($alt_party['guests_allocation']['children'])) {
500 $party_info_parts[] = $alt_party['guests_allocation']['children'] . ' ' . $children_lbl;
501 }
502 if (!$alt_dates_displayed) {
503 // display the stay dates once per alternative party
504 $alt_dates_displayed = true;
505 // calculate suggested stay dates
506 $sugg_checkin_dt = null;
507 $sugg_checkout_dt = null;
508 foreach ($alt_party['days_av_left'] as $dayk => $uleft) {
509 if (empty($sugg_checkin_dt)) {
510 // grab the first date
511 $sugg_checkin_dt = $dayk;
512 }
513 // always overwrite until last date
514 $sugg_checkout_dt = $dayk;
515 }
516 // increase check-out date by one day (day after last night of stay)
517 $sugg_out_info = getdate(strtotime($sugg_checkout_dt));
518 $sugg_checkout_dt = date('Y-m-d', mktime(0, 0, 0, $sugg_out_info['mon'], ($sugg_out_info['mday'] + 1), $sugg_out_info['year']));
519 // parse stay dates into DateTime objects
520 $checkin_dt_obj = new JDate($sugg_checkin_dt);
521 $checkout_dt_obj = new JDate($sugg_checkout_dt);
522 ?>
523 <div class="vbo-widget-checkav-result-altparty-dates">
524 <div class="vbo-widget-checkav-result-altparty-stay">
525 <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>
526 <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>
527 </div>
528 </div>
529 <?php
530 }
531 ?>
532 <div class="vbo-widget-checkav-result-altparty-room">
533 <div class="vbo-widget-checkav-result-room-name">
534 <span><?php echo $alt_party['name']; ?></span>
535 </div>
536 <div class="vbo-widget-checkav-result-alt-guests-rparty">
537 <span><?php VikBookingIcons::e('users'); ?> <?php echo implode(', ', $party_info_parts); ?></span>
538 </div>
539 </div>
540 <?php
541 }
542 ?>
543 </div>
544 <?php
545 // check limit of alternative parties
546 if ($alt_displayed >= $this->max_alternative_parties) {
547 break;
548 }
549 }
550 ?>
551 </div>
552 <?php
553 }
554
555 // get the HTML buffer
556 $html_content = ob_get_contents();
557 ob_end_clean();
558
559 // return an associative array of values
560 return array(
561 'html' => $html_content,
562 'tot_records' => $tot_records,
563 'next_page' => $has_next_page,
564 );
565 }
566
567 /**
568 * Preload the necessary CSS/JS assets.
569 *
570 * @return void
571 */
572 public function preload()
573 {
574 // load assets
575 $this->vbo_app->loadDatePicker();
576 }
577
578 public function render(VBOMultitaskData $data = null)
579 {
580 // increase widget's instance counter
581 static::$instance_counter++;
582
583 // check whether the widget is being rendered via AJAX when adding it through the customizer
584 $is_ajax = $this->isAjaxRendering();
585
586 // generate a unique ID for the sticky notes wrapper instance
587 $wrapper_instance = !$is_ajax ? static::$instance_counter : rand();
588 $wrapper_id = 'vbo-widget-checkav-' . $wrapper_instance;
589
590 // get permissions
591 $vbo_auth_bookings = JFactory::getUser()->authorise('core.vbo.bookings', 'com_vikbooking');
592 if (!$vbo_auth_bookings) {
593 // display nothing
594 return;
595 }
596
597 // we make use of the rates flow report helper class
598 $report = VikBooking::getReportInstance('rates_flow');
599 if (!$report) {
600 // display nothing
601 return;
602 }
603
604 // date formats
605 $df = $report->getDateFormat();
606 $dtpicker_df = $report->getDateFormat('jui');
607
608 // get highest max adults and max children
609 list($max_adults, $max_children) = $this->getMaxestGuests();
610
611 // check multitask data
612 $page_bid = 0;
613 $modal_load_bid = '';
614 if ($data) {
615 $page_bid = $data->getBookingID();
616 if ($page_bid && $data->isModalRendering()) {
617 // immediately load contents according to injected multitask data
618 $modal_load_bid = $page_bid;
619 }
620 }
621
622 ?>
623 <div class="vbo-admin-widget-wrapper">
624 <div class="vbo-admin-widget-head">
625 <h4><?php echo $this->widgetIcon; ?> <span><?php echo $this->widgetName; ?></span></h4>
626 </div>
627 <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; ?>">
628 <div class="vbo-widget-checkav-filters">
629 <div class="vbo-widget-checkav-filters-main">
630 <div class="vbo-widget-checkav-filter vbo-widget-checkav-filter-dpicker">
631 <div class="vbo-field-calendar">
632 <div class="input-append">
633 <input type="text" class="vbo-widget-checkav-checkindt" value="" placeholder="<?php echo htmlspecialchars(JText::translate('VBPICKUPAT')); ?>" />
634 <button type="button" class="btn btn-secondary vbo-widget-checkav-checkindt-trigger"><?php VikBookingIcons::e('calendar'); ?></button>
635 </div>
636 </div>
637 </div>
638 <div class="vbo-widget-checkav-filter vbo-widget-checkav-filter-nights">
639 <label for="vbo-widget-checkav-nights-<?php echo $wrapper_instance; ?>"><?php echo JText::translate('VBDAYS'); ?></label>
640 <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" />
641 </div>
642 </div>
643 <div class="vbo-widget-checkav-filters-secondary">
644 <div class="vbo-widget-checkav-filter vbo-widget-checkav-filter-adults">
645 <label for="vbo-widget-checkav-adults-<?php echo $wrapper_instance; ?>"><?php echo JText::translate('VBEDITORDERADULTS'); ?></label>
646 <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" />
647 </div>
648 <div class="vbo-widget-checkav-filter vbo-widget-checkav-filter-children">
649 <label for="vbo-widget-checkav-children-<?php echo $wrapper_instance; ?>"><?php echo JText::translate('VBEDITORDERCHILDREN'); ?></label>
650 <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" />
651 </div>
652 </div>
653 <div class="vbo-widget-checkav-filters-submit">
654 <div class="vbo-widget-checkav-filter vbo-widget-checkav-filter-submit">
655 <button type="button" class="btn vbo-config-btn" onclick="vboWidgetCheckAvailabilityCalc('<?php echo $wrapper_id; ?>');"><?php VikBookingIcons::e('bed'); ?><?php echo JText::translate('VBRATESOVWRATESCALCULATORCALC'); ?></button>
656 </div>
657 </div>
658 </div>
659 <div class="vbo-widget-checkav-results"></div>
660 </div>
661 </div>
662 <?php
663
664 if (static::$instance_counter === 0 || $is_ajax) {
665 /**
666 * Print the JS code only once for all instances of this widget.
667 * The real rendering is made through AJAX, not when the page loads.
668 */
669 ?>
670 <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>
671 <a class="vbo-widget-checkav-splitstayuri" href="index.php?option=com_vikbooking&task=calendar&cid[]=%d&adults=%d&children=%d" style="display: none;"></a>
672
673 <script type="text/javascript">
674
675 /**
676 * Display the loading skeletons.
677 */
678 function vboWidgetCheckAvailabilitySkeletons(wrapper) {
679 var widget_instance = jQuery('#' + wrapper);
680 if (!widget_instance.length) {
681 return false;
682 }
683 widget_instance.find('.vbo-widget-checkav-results').html('');
684 for (var i = 0; i < 1; i++) {
685 var skeleton = '';
686 skeleton += '<div class="vbo-dashboard-guest-activity vbo-dashboard-guest-activity-skeleton">';
687 skeleton += ' <div class="vbo-dashboard-guest-activity-avatar">';
688 skeleton += ' <div class="vbo-skeleton-loading vbo-skeleton-loading-avatar"></div>';
689 skeleton += ' </div>';
690 skeleton += ' <div class="vbo-dashboard-guest-activity-content">';
691 skeleton += ' <div class="vbo-dashboard-guest-activity-content-head">';
692 skeleton += ' <div class="vbo-skeleton-loading vbo-skeleton-loading-title"></div>';
693 skeleton += ' </div>';
694 skeleton += ' <div class="vbo-dashboard-guest-activity-content-info-msg">';
695 skeleton += ' <div class="vbo-skeleton-loading vbo-skeleton-loading-content"></div>';
696 skeleton += ' </div>';
697 skeleton += ' </div>';
698 skeleton += '</div>';
699 // append skeleton
700 jQuery(skeleton).appendTo(widget_instance.find('.vbo-widget-checkav-results'));
701 }
702 }
703
704 /**
705 * Perform the request to load the available room rates.
706 */
707 function vboWidgetCheckAvailabilityLoad(wrapper, checkbid) {
708 var widget_instance = jQuery('#' + wrapper);
709 if (!widget_instance.length) {
710 return false;
711 }
712
713 // get vars for making the request
714 var current_offset = parseInt(widget_instance.attr('data-offset'));
715 var length_per_page = parseInt(widget_instance.attr('data-length'));
716 var checkin_date = widget_instance.find('.vbo-widget-checkav-checkindt').val();
717 var num_nights = widget_instance.find('.vbo-widget-checkav-nights').val();
718 var num_adults = widget_instance.find('.vbo-widget-checkav-adults').val();
719 var num_children = widget_instance.find('.vbo-widget-checkav-children').val();
720
721 var bid = null;
722 if (typeof checkbid !== 'undefined' && checkbid) {
723 bid = checkbid;
724 }
725
726 // the widget method to call
727 var call_method = 'loadRoomRates';
728
729 // make a request to load the available room rates
730 VBOCore.doAjax(
731 "<?php echo $this->getExecWidgetAjaxUri(); ?>",
732 {
733 widget_id: "<?php echo $this->getIdentifier(); ?>",
734 call: call_method,
735 return: 1,
736 offset: current_offset,
737 length: length_per_page,
738 checkin_date: checkin_date,
739 num_nights: num_nights,
740 num_adults: num_adults,
741 num_children: num_children,
742 bid: bid,
743 wrapper: wrapper,
744 tmpl: "component"
745 },
746 function(response) {
747 try {
748 var obj_res = typeof response === 'string' ? JSON.parse(response) : response;
749 if (!obj_res.hasOwnProperty(call_method) || !obj_res[call_method]) {
750 console.error('Unexpected JSON response', obj_res);
751 return false;
752 }
753
754 // replace HTML with new available room rates
755 widget_instance.find('.vbo-widget-checkav-results').html(obj_res[call_method]['html']);
756
757 // check results
758 var tot_records = obj_res[call_method]['tot_records'] || 0;
759 if (!isNaN(tot_records) && parseInt(tot_records) < 1) {
760 // no results can indicate the offset is invalid or too high
761 if (!isNaN(current_offset) && parseInt(current_offset) > 0) {
762 // reset offset to 0
763 widget_instance.attr('data-offset', 0);
764 // show loading skeletons
765 vboWidgetCheckAvailabilitySkeletons(wrapper);
766 // reload the first page
767 vboWidgetCheckAvailabilityLoad(wrapper);
768 }
769 }
770 } catch(err) {
771 console.error('could not parse JSON response', err, response);
772 }
773 },
774 function(error) {
775 // remove the skeleton loading
776 widget_instance.find('.vbo-widget-checkav-results').find('.vbo-dashboard-guest-activity-skeleton').remove();
777 console.error(error);
778 }
779 );
780 }
781
782 /**
783 * Navigate between the various pages of the available room rates.
784 */
785 function vboWidgetCheckAvailabilityNavigate(wrapper, direction) {
786 var widget_instance = jQuery('#' + wrapper);
787 if (!widget_instance.length) {
788 return false;
789 }
790
791 // show loading skeletons
792 vboWidgetCheckAvailabilitySkeletons(wrapper);
793
794 var instance_val = widget_instance.attr('data-instance');
795
796 // current offset
797 var current_offset = parseInt(widget_instance.attr('data-offset'));
798
799 // events per page per type
800 var steps = <?php echo $this->res_per_page; ?>;
801
802 // check direction and update offsets for nav
803 if (direction > 0) {
804 // navigate forward
805 widget_instance.attr('data-offset', (current_offset + steps));
806 } else {
807 // navigate backward
808 var new_offset = current_offset - steps;
809 new_offset = new_offset >= 0 ? new_offset : 0;
810 widget_instance.attr('data-offset', new_offset);
811 }
812
813 // launch navigation
814 vboWidgetCheckAvailabilityLoad(wrapper);
815 }
816
817 /**
818 * Calculate the available room rates.
819 */
820 function vboWidgetCheckAvailabilityCalc(wrapper) {
821 var widget_instance = jQuery('#' + wrapper);
822 if (!widget_instance.length) {
823 return false;
824 }
825
826 // show loading skeletons
827 vboWidgetCheckAvailabilitySkeletons(wrapper);
828
829 // always reset offset to the first page
830 widget_instance.attr('data-offset', 0);
831
832 // load data
833 vboWidgetCheckAvailabilityLoad(wrapper);
834 }
835
836 /**
837 * Triggers when the multitask panel opens.
838 */
839 function vboWidgetCheckAvailabilityMultitaskOpen(wrapper) {
840 var widget_instance = jQuery('#' + wrapper);
841 if (!widget_instance.length) {
842 return false;
843 }
844
845 // check if a booking ID was set for this page
846 var page_bid = widget_instance.attr('data-pagebid');
847 if (!page_bid || page_bid < 1) {
848 return false;
849 }
850
851 // show loading skeletons
852 vboWidgetCheckAvailabilitySkeletons(wrapper);
853
854 // always reset offset to the first page
855 widget_instance.attr('data-offset', 0);
856
857 // load data by injecting the current booking ID
858 vboWidgetCheckAvailabilityLoad(wrapper, page_bid);
859 }
860
861 /**
862 * Book a room rate.
863 */
864 function vboWidgetCheckAvailabilityBook(wrapper, rid, din, dout, adults, children, rpid) {
865 var open_url = jQuery('.vbo-widget-checkav-basenavuri').first().attr('href');
866 open_url = open_url.replace('%d', rid);
867 open_url = open_url.replace('%s', din);
868 open_url = open_url.replace('%s', dout);
869 open_url = open_url.replace('%d', adults);
870 open_url = open_url.replace('%d', children);
871 open_url = open_url.replace('%d', rpid);
872 // navigate
873 document.location.href = open_url;
874 }
875
876 /**
877 * Book a split stay.
878 */
879 function vboWidgetCheckAvailabilityBookSplitStay(rid, adults, children, split_qstring) {
880 var open_url = jQuery('.vbo-widget-checkav-splitstayuri').first().attr('href');
881 open_url = open_url.replace('%d', rid);
882 open_url = open_url.replace('%d', adults);
883 open_url = open_url.replace('%d', children);
884 open_url += '&' + split_qstring;
885 // navigate
886 document.location.href = open_url;
887 }
888
889 /**
890 * Pick alternative stay dates suggested for calculation.
891 */
892 function vboWidgetCheckAvailabilityPickRoom(wrapper, rid, din, nights, adults, children) {
893 var widget_instance = jQuery('#' + wrapper);
894 if (!widget_instance.length) {
895 return false;
896 }
897
898 // show loading skeletons
899 vboWidgetCheckAvailabilitySkeletons(wrapper);
900
901 // always reset offset to the first page
902 widget_instance.attr('data-offset', 0);
903
904 // populate check-in date
905 widget_instance.find('.vbo-widget-checkav-checkindt').datepicker('setDate', din);
906
907 // set number of nights, adults and children
908 widget_instance.find('.vbo-widget-checkav-nights').val(nights);
909 widget_instance.find('.vbo-widget-checkav-adults').val(adults);
910 widget_instance.find('.vbo-widget-checkav-children').val(children);
911
912 // load data (we ignore the room ID passed to display all options available)
913 vboWidgetCheckAvailabilityLoad(wrapper);
914 }
915
916 </script>
917 <?php
918 }
919 ?>
920
921 <script type="text/javascript">
922
923 jQuery(function() {
924
925 // render datepicker calendar for dates navigation
926 jQuery('#<?php echo $wrapper_id; ?>').find('.vbo-widget-checkav-checkindt').datepicker({
927 minDate: 0,
928 maxDate: "+3y",
929 yearRange: "<?php echo date('Y'); ?>:<?php echo (date('Y') + 3); ?>",
930 changeMonth: true,
931 changeYear: true,
932 dateFormat: "<?php echo $dtpicker_df; ?>"
933 });
934
935 // triggering for datepicker calendar icon
936 jQuery('#<?php echo $wrapper_id; ?>').find('.vbo-widget-checkav-checkindt-trigger').click(function() {
937 var jdp = jQuery(this).parent().find('input.hasDatepicker');
938 if (jdp.length) {
939 jdp.focus();
940 }
941 });
942
943 // subscribe to the multitask-panel-open event
944 document.addEventListener(VBOCore.multitask_open_event, function() {
945 vboWidgetCheckAvailabilityMultitaskOpen('<?php echo $wrapper_id; ?>');
946 });
947
948 <?php
949 if ($modal_load_bid) {
950 // immediately fire the adaptive rendering according to multitask data
951 ?>
952 vboWidgetCheckAvailabilityMultitaskOpen('<?php echo $wrapper_id; ?>');
953 <?php
954 }
955 ?>
956
957 });
958
959 </script>
960
961 <?php
962 }
963
964 /**
965 * Returns the maximum number of adults and children as a sum from all rooms.
966 * Internal method for this widget only.
967 *
968 * @return array two values, respectively for highest adults and children.
969 */
970 protected function getMaxestGuests()
971 {
972 $maxest = array(1, 1);
973
974 $dbo = JFactory::getDbo();
975
976 $q = "SELECT SUM(`toadult`) AS `max_adults`, SUM(`tochild`) AS `max_children` FROM `#__vikbooking_rooms`;";
977 $dbo->setQuery($q);
978 $record = $dbo->loadAssoc();
979 if ($record) {
980 $maxest = array($record['max_adults'], $record['max_children']);
981 }
982
983 return $maxest;
984 }
985
986 /**
987 * In case the multitask panel injects a booking ID, this method
988 * prepares the proper values to use for loading room rates.
989 *
990 * @param int $bid the booking id.
991 * @param bool $multi_rooms whether to return array of guests
992 * when multiple rooms booked.
993 *
994 * @return array list of values to use for loading.
995 */
996 protected function getBookingLoadValues($bid, $multi_rooms = true)
997 {
998 // set default values
999 $checkin_date = '';
1000 $num_nights = 0;
1001 $num_adults = 0;
1002 $num_children = 0;
1003 $room_ids = [];
1004
1005 if (empty($bid)) {
1006 // do nothing
1007 return [$checkin_date, $num_nights, $num_adults, $num_children, $room_ids];
1008 }
1009
1010 // get booking details
1011 $booking = VikBooking::getBookingInfoFromID($bid);
1012
1013 if (!is_array($booking) || !count($booking)) {
1014 // do nothing
1015 return [$checkin_date, $num_nights, $num_adults, $num_children, $room_ids];
1016 }
1017
1018 // get reservation rooms
1019 $book_rooms = VikBooking::loadOrdersRoomsData($booking['id']);
1020
1021 if (!is_array($book_rooms) || !count($book_rooms)) {
1022 // do nothing
1023 return [$checkin_date, $num_nights, $num_adults, $num_children, $room_ids];
1024 }
1025
1026 // build proper values for this reservation
1027 $checkin_date = date('Y-m-d', $booking['checkin']);
1028 $num_nights = $booking['days'];
1029 $arr_adults = [];
1030 $arr_children = [];
1031 foreach ($book_rooms as $roomres) {
1032 $num_adults += $roomres['adults'];
1033 $num_children += $roomres['children'];
1034 $arr_adults[] = $roomres['adults'];
1035 $arr_children[] = $roomres['children'];
1036 $room_ids[] = $roomres['idroom'];
1037 if ($booking['split_stay']) {
1038 // do not sum the guests in case of split stay
1039 $num_adults = $roomres['adults'];
1040 $num_children = $roomres['children'];
1041 }
1042 }
1043
1044 if (count($book_rooms) > 1 && $multi_rooms === true && !$booking['split_stay']) {
1045 // return the adults and children as array rather than as integers
1046 $num_adults = $arr_adults;
1047 $num_children = $arr_children;
1048 }
1049
1050 return [$checkin_date, $num_nights, $num_adults, $num_children, $room_ids];
1051 }
1052 }
1053