PluginProbe
VikBooking Hotel Booking Engine & PMS / 1.8.16
VikBooking Hotel Booking Engine & PMS v1.8.16
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
← All changes | admin/helpers/widgets/check_availability.php +1054 -0 1.6.2 → 1.8.16 View file →
@@ -1,0 +1,1054 @@
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 +}