PluginProbe
VikBooking Hotel Booking Engine & PMS / trunk
VikBooking Hotel Booking Engine & PMS vtrunk
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 1.7.3 All 36 releases
vikbooking / admin / helpers / widgets / check_availability.php

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

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