File "check_availability.php"

Full Path: /home/romayxjt/public_html/wp-content/plugins/vikbooking/admin/helpers/widgets/check_availability.php
File size: 37.54 KB
MIME-type: text/x-php
Charset: utf-8

<?php
/**
 * @package     VikBooking
 * @subpackage  com_vikbooking
 * @author      Alessio Gaggii - e4j - Extensionsforjoomla.com
 * @copyright   Copyright (C) 2018 e4j - Extensionsforjoomla.com. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 * @link        https://vikwp.com
 */

defined('ABSPATH') or die('No script kiddies please!');

/**
 * Class handler for admin widget "check availability".
 * 
 * @since 	1.15.0 (J) - 1.5.0 (WP)
 */
class VikBookingAdminWidgetCheckAvailability extends VikBookingAdminWidget
{
	/**
	 * The instance counter of this widget. Since we do not load individual parameters
	 * for each widget's instance, we use a static counter to determine its settings.
	 *
	 * @var 	int
	 */
	protected static $instance_counter = -1;

	/**
	 * Default number of results per page.
	 * 
	 * @var 	int
	 */
	protected $res_per_page = 3;

	/**
	 * Maximum alternative dates to be displayed.
	 * 
	 * @var 	int
	 */
	protected $max_alternative_dates = 6;

	/**
	 * Maximum alternative parties to be displayed.
	 * 
	 * @var 	int
	 */
	protected $max_alternative_parties = 8;

	/**
	 * Class constructor will define the widget name and identifier.
	 */
	public function __construct()
	{
		// call parent constructor
		parent::__construct();

		$this->widgetName = JText::translate('VBO_W_CHECKAV_TITLE');
		$this->widgetDescr = JText::translate('VBO_W_CHECKAV_DESCR');
		$this->widgetId = basename(__FILE__, '.php');

		// define widget and icon and style name
		$this->widgetIcon = '<i class="' . VikBookingIcons::i('calculator') . '"></i>';
		$this->widgetStyleName = 'blue';
	}

	/**
	 * Custom method for this widget only to load the rates flow records.
	 * The method is called by the admin controller through an AJAX request.
	 * The visibility should be public, it should not exit the process, and
	 * any content sent to output will be returned to the AJAX response.
	 * In this case we return an array because this method requires "return":1.
	 * 
	 * It's the actual rendering of the widget which also allows navigation.
	 */
	public function loadRoomRates()
	{
		$offset = VikRequest::getInt('offset', 0, 'request');
		$length = VikRequest::getInt('length', $this->res_per_page, 'request');
		$wrapper = VikRequest::getString('wrapper', '', 'request');

		$checkin_date = VikRequest::getString('checkin_date', '', 'request');
		$num_nights = VikRequest::getInt('num_nights', 1, 'request');
		$num_adults = VikRequest::getInt('num_adults', 2, 'request');
		$num_children = VikRequest::getInt('num_children', 0, 'request');

		// optional room IDs and guests array to consider
		$room_ids 	  = [];
		$arr_adults   = [];
		$arr_children = [];

		// multitask can inject a booking id
		$bid = VikRequest::getInt('bid', 0, 'request');
		if (!empty($bid)) {
			// get proper loading values for this booking
			list($b_checkin_date, $b_num_nights, $b_num_adults, $b_num_children, $room_ids) = $this->getBookingLoadValues($bid);
			// only check the calculated booking checkin date to determine if loading values was successful
			if (!empty($b_checkin_date)) {
				// overwrite default values
				$checkin_date = $b_checkin_date;
				$num_nights   = $b_num_nights;
				if (is_array($b_num_adults)) {
					// multiple rooms involved
					$num_adults   = array_sum($b_num_adults);
					$num_children = array_sum($b_num_children);
					$arr_adults   = $b_num_adults;
					$arr_children = $b_num_children;
				} else {
					// single room involved
					$num_adults   = $b_num_adults;
					$num_children = $b_num_children;
				}
			}
		}

		if (empty($checkin_date)) {
			// default to today's date
			$checkin_date = date('Y-m-d');
		}
		if ($num_nights < 1) {
			// minimum number of nights is 1
			$num_nights = 1;
		}

		// convert date to timestamp to support custom date formats
		$checkin_ts = VikBooking::getDateTimestamp($checkin_date);
		$checkin_info = getdate($checkin_ts);
		$checkout_ts = mktime(0, 0, 0, $checkin_info['mon'], ($checkin_info['mday'] + $num_nights), $checkin_info['year']);
		$checkout_info = getdate($checkout_ts);
		// build final dates
		$checkin_date = date('Y-m-d', $checkin_ts);
		$checkout_date = date('Y-m-d', $checkout_ts);

		// we make use of the rates flow report helper class
		$report = VikBooking::getReportInstance('rates_flow');
		if (!$report) {
			// display nothing
			return;
		}

		// date formats and separator
		$df = $report->getDateFormat();
		$dtpicker_df = $report->getDateFormat('jui');
		$date_sep = VikBooking::getDateSeparator();

		// currency symbol
		$currency = VikBooking::getCurrencySymb();

		// invoke availability helper class
		$av_helper = VikBooking::getAvailabilityInstance();

		// set stay values
		$av_helper->setStayDates($checkin_date, $checkout_date);
		if (count($arr_adults) > 1) {
			// multiple rooms involved
			foreach ($arr_adults as $gkey => $radults) {
				$av_helper->setRoomParty($radults, (isset($arr_children[$gkey]) ? $arr_children[$gkey] : 0));
			}
		} else {
			// single room involved
			$av_helper->setRoomParty($num_adults, $num_children);
		}

		if ($room_ids) {
			$av_helper->setRoomIds($room_ids);
		}

		// load available room rates (all records, before pagination that will be applied later)
		$room_rates = $av_helper->getRates([
			'wtax' => 1,
		]);

		// load rooms and rate plans
		$all_rooms  = $av_helper->loadRooms();
		$all_rplans = $av_helper->loadRatePlans();

		// check if availability errors occurred
		$has_av_error  = strlen($av_helper->getError());
		$av_error_code = $av_helper->getErrorCode();

		// count total records
		$tot_records = is_array($room_rates) && !$has_av_error ? count($room_rates) : 0;

		// check if a next page can be available
		$has_next_page = (($offset + $length) < $tot_records) ? 1 : 0;

		// splice the array returned to support pagination
		if ($tot_records > $length) {
			$split_room_rates = array();
			$index_offset = 0;
			foreach ($room_rates as $rid => $rates) {
				if ($index_offset >= $offset) {
					$split_room_rates[$rid] = $rates;
				}
				if (count($split_room_rates) >= $length) {
					break;
				}
				$index_offset++;
			}
			$room_rates = $split_room_rates;
			unset($split_room_rates);
		}

		// list of eligible and available room IDs
		$eligible_rids = [];

		// start output buffering
		ob_start();

		// display the information about the dates and room party
		$party_info_parts = array();
		// stay dates information
		$checkin_str = VikBooking::sayWeekDay($checkin_info['wday'], true) . ', ' . date(str_replace('/', $date_sep, $df), $checkin_ts);
		$checkout_str = VikBooking::sayWeekDay($checkout_info['wday'], true) . ', ' . date(str_replace('/', $date_sep, $df), $checkout_ts);
		$party_info_parts[] = $checkin_str . ' - ' . $checkout_str;
		// nights information
		$nights_lbl = $num_nights > 1 ? JText::translate('VBDAYS') : JText::translate('VBDAY');
		$party_info_parts[] = $num_nights . ' ' . $nights_lbl;
		// adults information
		$adults_lbl = $num_adults === 1 ? JText::translate('VBMAILADULT') : JText::translate('VBEDITORDERADULTS');
		$party_info_parts[] = $num_adults . ' ' . $adults_lbl;
		// children information
		$children_lbl = $num_children === 1 ? JText::translate('VBMAILCHILD') : JText::translate('VBEDITORDERCHILDREN');
		if ($num_children > 0) {
			$party_info_parts[] = $num_children . ' ' . $children_lbl;
		}
		?>
		<div class="vbo-widget-checkav-result-party">
			<p><?php echo implode(', ', $party_info_parts); ?></p>
		</div>
		<?php
		
		// check if there is availability
		if (!is_array($room_rates) || !$room_rates || $has_av_error) {
			$explain_err = $av_helper->explainErrorCode();
			$say_error 	 = !empty($explain_err) ? $explain_err : $av_helper->getError();
			$say_error 	 = empty($say_error) ? JText::translate('VBOVERVIEWLEGRED') : $say_error;
			?>
			<p class="err"><?php echo $say_error; ?></p>
			<?php
		} else {
			// parse room rates
			foreach ($room_rates as $rid => $rates) {
				if (!isset($all_rooms[$rid])) {
					// this is to prevent reserved keys from being displayed
					continue;
				}
				// push room ID as eligible and available
				$eligible_rids[] = $rid;
				?>
			<div class="vbo-widget-checkav-result-room-rates">
				<div class="vbo-widget-checkav-result-room-name">
					<span><?php echo $all_rooms[$rid]['name']; ?></span>
				</div>
				<div class="vbo-widget-checkav-result-rates-list">
				<?php
				foreach ($rates as $rplan) {
					$rplan_taxes  = isset($rplan['taxes']) ? $rplan['taxes'] : 0;
					$net_price    = $rplan['cost'] - $rplan_taxes;
					$js_book_args = "'$wrapper', '{$rid}', '{$checkin_date}', '{$checkout_date}', '{$num_adults}', '{$num_children}', '{$rplan['idprice']}'";
					?>
					<div class="vbo-widget-checkav-result-room-rate">
						<div class="vbo-widget-checkav-result-rate-name">
							<span><?php echo isset($all_rplans[$rplan['idprice']]) ? $all_rplans[$rplan['idprice']]['name'] : '?'; ?></span>
						</div>
						<div class="vbo-widget-checkav-result-rate-prices">
						<?php
						if ($net_price != $rplan['cost']) {
							// taxes are greater than zero
							?>
							<span class="vbo-widget-checkav-result-rate-price vbo-widget-checkav-result-rate-net">
								<span><?php echo JText::translate('VBCALCRATESNET'); ?></span>
								<?php echo $currency . ' ' . VikBooking::numberFormat($net_price); ?>
							</span>
							<span class="vbo-widget-checkav-result-rate-price vbo-widget-checkav-result-rate-tax">
								<span><?php echo JText::translate('VBCALCRATESTAX'); ?></span>
								<?php echo $currency . ' ' . VikBooking::numberFormat($rplan_taxes); ?>
							</span>
							<?php
						}
						?>
							<span class="vbo-widget-checkav-result-rate-price vbo-widget-checkav-result-rate-total">
								<span><?php echo JText::translate('VBCALCRATESTOT'); ?></span>
								<?php echo $currency . ' ' . VikBooking::numberFormat($rplan['cost']); ?>
							</span>
							<span class="vbo-widget-checkav-result-rate-price vbo-widget-checkav-result-rate-booknow">
								<button type="button" class="btn btn-primary" onclick="vboWidgetCheckAvailabilityBook(<?php echo $js_book_args; ?>);"><?php echo JText::translate('VBO_BOOKNOW'); ?></button>
							</span>
						</div>
					</div>
					<?php
				}
				?>
				</div>
			</div>
			<?php
			}

			// append navigation
			?>
			<div class="vbo-widget-commands vbo-widget-commands-right">
				<div class="vbo-widget-commands-main">
				<?php
				if ($offset > 0) {
					// show backward navigation button
					?>
					<div class="vbo-widget-command-chevron vbo-widget-command-prev">
						<span class="vbo-widget-command-chevron-prev" onclick="vboWidgetCheckAvailabilityNavigate('<?php echo $wrapper; ?>', -1);"><?php VikBookingIcons::e('chevron-left'); ?></span>
					</div>
					<?php
				}
				if ($has_next_page) {
					// show forward navigation button
					?>
					<div class="vbo-widget-command-chevron vbo-widget-command-next">
						<span class="vbo-widget-command-chevron-next" onclick="vboWidgetCheckAvailabilityNavigate('<?php echo $wrapper; ?>', 1);"><?php VikBookingIcons::e('chevron-right'); ?></span>
					</div>
				<?php
				}
				?>
				</div>
			</div>
			<?php
		}

		// default pool of suggestions
		$alternative_dates 	 = [];
		$alternative_parties = [];
		$split_stay_sols 	 = [];

		if (!is_array($room_rates) || !$room_rates || $has_av_error) {
			// try to get the suggestions when no availability
			list($alternative_dates, $alternative_parties, $split_stay_sols) = $av_helper->findSuggestions();
		} elseif ($eligible_rids) {
			/**
			 * We did return some available rooms, but we still want to calculate if some
			 * split-stay solutions are also available for some rooms that were left unsold.
			 * 
			 * @since 	1.16.3 (J) - 1.6.3 (WP)
			 */
			$parsable_rids = array_diff(array_keys($av_helper->filterPublishedRooms()), $eligible_rids);
			if ($parsable_rids) {
				// try to suggest split stay solutions by using the room IDs that were not available
				$split_stay_sols = $av_helper->findSplitStays($parsable_rids);
			}
		}

		// parse booking split stays
		if ($split_stay_sols) {
			// we've got booking split stays to suggest
			?>
			<p class="info"><?php VikBookingIcons::e('random'); ?> <?php echo JText::translate('VBO_SPLIT_STAYS'); ?></p>
			<div class="vbo-widget-checkav-result-splitstays-wrap">
			<?php
			foreach ($split_stay_sols as $split_stay_sol) {
				?>
				<div class="vbo-widget-checkav-splitstay-rooms">
					<div class="vbo-widget-checkav-splitstay-rooms-inner">
					<?php
					// start args for booking this split stay solution
					$first_rid = $split_stay_sol[0]['idroom'];
					$split_stay_values = [
						'checkin'  	 => $checkin_date, 
						'checkout' 	 => $checkout_date,
						'split_stay' => [],
					];
					// parse rooms for the split stay
					foreach ($split_stay_sol as $split_k => $split_stay_room) {
						// parse stay dates into DateTime objects
						$checkin_dt_obj  = new JDate($split_stay_room['checkin']);
						$checkout_dt_obj = new JDate($split_stay_room['checkout']);
						// push split stay data for booking
						$split_stay_values['split_stay'][] = [
							'idroom'   => $split_stay_room['idroom'],
							'checkin'  => $split_stay_room['checkin'],
							'checkout' => $split_stay_room['checkout'],
							'nights'   => $split_stay_room['nights'],
						];
						?>
						<div class="vbo-widget-checkav-splitstay-room">
							<div class="vbo-widget-checkav-result-room-name">
								<span><?php VikBookingIcons::e(($split_k > 0 ? 'random' : 'arrow-right')); ?> <?php echo $split_stay_room['room_name']; ?></span>
							</div>
							<div class="vbo-widget-checkav-result-altdates-info">
								<div class="vbo-widget-checkav-result-alt-date">
									<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>
									<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>
									<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>
								</div>
							</div>
						</div>
						<?php
					}
					// finalize args for booking this split stay
					$split_stay_qstring = http_build_query($split_stay_values);
					$js_book_args = "'{$first_rid}', '{$num_adults}', '{$num_children}', '{$split_stay_qstring}'";
					?>
					</div>
					<div class="vbo-widget-checkav-splitstay-rooms-book">
						<button type="button" class="btn btn-primary" onclick="vboWidgetCheckAvailabilityBookSplitStay(<?php echo $js_book_args; ?>);"><?php echo JText::translate('VBO_BOOKNOW'); ?></button>
					</div>
				</div>
				<?php
			}
			?>
			</div>
			<?php
		}

		// parse alrernative dates (if any)
		if ($alternative_dates) {
			// we've got some alternative dates to suggest for some rooms
			?>
			<p class="info"><?php echo JText::translate('VBO_ALT_STAY_DATES'); ?></p>
			<div class="vbo-widget-checkav-result-altdates-wrap">
			<?php
			$alt_displayed = 0;
			foreach ($alternative_dates as $arrive_dt => $alt_rooms) {
				$alt_displayed++;
				foreach ($alt_rooms as $rid => $alt_room) {
					if (empty($alt_room['days_av_left'])) {
						continue;
					}
					$sugg_checkin_dt  = null;
					$sugg_checkout_dt = null;
					foreach ($alt_room['days_av_left'] as $dayk => $uleft) {
						if (empty($sugg_checkin_dt)) {
							// grab the first date
							$sugg_checkin_dt = $dayk;
						}
						// always overwrite until last date
						$sugg_checkout_dt = $dayk;
					}
					// increase check-out date by one day (day after last night of stay)
					$sugg_out_info = getdate(strtotime($sugg_checkout_dt));
					$sugg_checkout_dt = date('Y-m-d', mktime(0, 0, 0, $sugg_out_info['mon'], ($sugg_out_info['mday'] + 1), $sugg_out_info['year']));
					// parse stay dates into DateTime objects
					$checkin_dt_obj  = new JDate($sugg_checkin_dt);
					$checkout_dt_obj = new JDate($sugg_checkout_dt);
					// count total nights of stay
					$sug_tot_nights = count($alt_room['days_av_left']);
					// format suggested check-in date for datepicker
					$cal_checkin_dt = date($df, strtotime($sugg_checkin_dt));
					// js arguments
					$js_pick_args = "'$wrapper', '{$rid}', '{$cal_checkin_dt}', '{$sug_tot_nights}', '{$num_adults}', '{$num_children}'";
					?>
				<div class="vbo-widget-checkav-result-room-rates vbo-widget-checkav-result-altdates">
					<div class="vbo-widget-checkav-result-room-name">
						<span><?php echo $alt_room['name']; ?></span>
					</div>
					<div class="vbo-widget-checkav-result-altdates-info">
						<div class="vbo-widget-checkav-result-alt-date">
							<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>
							<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>
						</div>
						<div class="vbo-widget-checkav-result-alt-choose">
							<button type="button" class="btn btn-primary" onclick="vboWidgetCheckAvailabilityPickRoom(<?php echo $js_pick_args; ?>);"><?php echo JText::translate('VBO_SELECT'); ?></button>
						</div>
					</div>
				</div>
					<?php
				}
				// check limit of alternative dates
				if ($alt_displayed >= $this->max_alternative_dates) {
					break;
				}
			}
			?>
			</div>
			<?php
		}

		// parse alrernative parties (if any)
		if ($alternative_parties) {
			// we've got some alternative combinations of rooms to suggest to fit the party
			?>
			<p class="info"><?php echo JText::translate('VBO_ALT_ROOM_PARTIES'); ?></p>
			<div class="vbo-widget-checkav-result-altparties-wrap">
			<?php
			$alt_displayed = 0;
			foreach ($alternative_parties as $arrive_dt => $alt_parties) {
				$alt_displayed++;
				$alt_dates_displayed = false;
				?>
				<div class="vbo-widget-checkav-result-room-rates vbo-widget-checkav-result-altparty">
				<?php
				foreach ($alt_parties as $alt_party) {
					if (empty($alt_party['days_av_left']) || empty($alt_party['guests_allocation'])) {
						continue;
					}
					// room ID
					$rid = $alt_party['id'];
					// count total nights of stay
					$sug_tot_nights = count($alt_party['days_av_left']);
					// party info
					$party_info_parts = array();
					// adults information
					$adults_lbl = $alt_party['guests_allocation']['adults'] == 1 ? JText::translate('VBMAILADULT') : JText::translate('VBEDITORDERADULTS');
					$party_info_parts[] = $alt_party['guests_allocation']['adults'] . ' ' . $adults_lbl;
					// children information
					$children_lbl = isset($alt_party['guests_allocation']['children']) && $alt_party['guests_allocation']['children'] == 1 ? JText::translate('VBMAILCHILD') : JText::translate('VBEDITORDERCHILDREN');
					if (!empty($alt_party['guests_allocation']['children'])) {
						$party_info_parts[] = $alt_party['guests_allocation']['children'] . ' ' . $children_lbl;
					}
					if (!$alt_dates_displayed) {
						// display the stay dates once per alternative party
						$alt_dates_displayed = true;
						// calculate suggested stay dates
						$sugg_checkin_dt  = null;
						$sugg_checkout_dt = null;
						foreach ($alt_party['days_av_left'] as $dayk => $uleft) {
							if (empty($sugg_checkin_dt)) {
								// grab the first date
								$sugg_checkin_dt = $dayk;
							}
							// always overwrite until last date
							$sugg_checkout_dt = $dayk;
						}
						// increase check-out date by one day (day after last night of stay)
						$sugg_out_info = getdate(strtotime($sugg_checkout_dt));
						$sugg_checkout_dt = date('Y-m-d', mktime(0, 0, 0, $sugg_out_info['mon'], ($sugg_out_info['mday'] + 1), $sugg_out_info['year']));
						// parse stay dates into DateTime objects
						$checkin_dt_obj  = new JDate($sugg_checkin_dt);
						$checkout_dt_obj = new JDate($sugg_checkout_dt);
						?>
					<div class="vbo-widget-checkav-result-altparty-dates">
						<div class="vbo-widget-checkav-result-altparty-stay">
							<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>
							<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>
						</div>
					</div>
						<?php
					}
					?>
					<div class="vbo-widget-checkav-result-altparty-room">
						<div class="vbo-widget-checkav-result-room-name">
							<span><?php echo $alt_party['name']; ?></span>
						</div>
						<div class="vbo-widget-checkav-result-alt-guests-rparty">
							<span><?php VikBookingIcons::e('users'); ?> <?php echo implode(', ', $party_info_parts); ?></span>
						</div>
					</div>
					<?php
				}
				?>
				</div>
				<?php
				// check limit of alternative parties
				if ($alt_displayed >= $this->max_alternative_parties) {
					break;
				}
			}
			?>
			</div>
			<?php
		}

		// get the HTML buffer
		$html_content = ob_get_contents();
		ob_end_clean();

		// return an associative array of values
		return array(
			'html' 		  => $html_content,
			'tot_records' => $tot_records,
			'next_page'   => $has_next_page,
		);
	}

	/**
	 * Preload the necessary CSS/JS assets.
	 * 
	 * @return 	void
	 */
	public function preload()
	{
		// load assets
		$this->vbo_app->loadDatePicker();
	}

	public function render(?VBOMultitaskData $data = null)
	{
		// increase widget's instance counter
		static::$instance_counter++;

		// check whether the widget is being rendered via AJAX when adding it through the customizer
		$is_ajax = $this->isAjaxRendering();

		// generate a unique ID for the sticky notes wrapper instance
		$wrapper_instance = !$is_ajax ? static::$instance_counter : rand();
		$wrapper_id = 'vbo-widget-checkav-' . $wrapper_instance;

		// get permissions
		$vbo_auth_bookings = JFactory::getUser()->authorise('core.vbo.bookings', 'com_vikbooking');
		if (!$vbo_auth_bookings) {
			// display nothing
			return;
		}

		// we make use of the rates flow report helper class
		$report = VikBooking::getReportInstance('rates_flow');
		if (!$report) {
			// display nothing
			return;
		}

		// date formats
		$df = $report->getDateFormat();
		$dtpicker_df = $report->getDateFormat('jui');

		// get highest max adults and max children
		list($max_adults, $max_children) = $this->getMaxestGuests();

		// check multitask data
		$page_bid = 0;
		$modal_load_bid = '';
		if ($data) {
			$page_bid = $data->getBookingID();
			if ($page_bid && $data->isModalRendering()) {
				// immediately load contents according to injected multitask data
				$modal_load_bid = $page_bid;
			}
		}

		?>
		<div class="vbo-admin-widget-wrapper">
			<div class="vbo-admin-widget-head">
				<h4><?php echo $this->widgetIcon; ?> <span><?php echo $this->widgetName; ?></span></h4>
			</div>
			<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; ?>">
				<div class="vbo-widget-checkav-filters">
					<div class="vbo-widget-checkav-filters-main">
						<div class="vbo-widget-checkav-filter vbo-widget-checkav-filter-dpicker">
							<div class="vbo-field-calendar">
								<div class="input-append">
									<input type="text" class="vbo-widget-checkav-checkindt" value="" placeholder="<?php echo htmlspecialchars(JText::translate('VBPICKUPAT')); ?>" />
									<button type="button" class="btn btn-secondary vbo-widget-checkav-checkindt-trigger"><?php VikBookingIcons::e('calendar'); ?></button>
								</div>
							</div>
						</div>
						<div class="vbo-widget-checkav-filter vbo-widget-checkav-filter-nights">
							<label for="vbo-widget-checkav-nights-<?php echo $wrapper_instance; ?>"><?php echo JText::translate('VBDAYS'); ?></label>
							<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" />
						</div>
					</div>
					<div class="vbo-widget-checkav-filters-secondary">
						<div class="vbo-widget-checkav-filter vbo-widget-checkav-filter-adults">
							<label for="vbo-widget-checkav-adults-<?php echo $wrapper_instance; ?>"><?php echo JText::translate('VBEDITORDERADULTS'); ?></label>
							<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" />
						</div>
						<div class="vbo-widget-checkav-filter vbo-widget-checkav-filter-children">
							<label for="vbo-widget-checkav-children-<?php echo $wrapper_instance; ?>"><?php echo JText::translate('VBEDITORDERCHILDREN'); ?></label>
							<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" />
						</div>
					</div>
					<div class="vbo-widget-checkav-filters-submit">
						<div class="vbo-widget-checkav-filter vbo-widget-checkav-filter-submit">
							<button type="button" class="btn vbo-config-btn" onclick="vboWidgetCheckAvailabilityCalc('<?php echo $wrapper_id; ?>');"><?php VikBookingIcons::e('bed'); ?><?php echo JText::translate('VBRATESOVWRATESCALCULATORCALC'); ?></button>
						</div>
					</div>
				</div>
				<div class="vbo-widget-checkav-results"></div>
			</div>
		</div>
		<?php

		if (static::$instance_counter === 0 || $is_ajax) {
			/**
			 * Print the JS code only once for all instances of this widget.
			 * The real rendering is made through AJAX, not when the page loads.
			 */
			?>
		<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>
		<a class="vbo-widget-checkav-splitstayuri" href="index.php?option=com_vikbooking&task=calendar&cid[]=%d&adults=%d&children=%d" style="display: none;"></a>

		<script type="text/javascript">

			/**
			 * Display the loading skeletons.
			 */
			function vboWidgetCheckAvailabilitySkeletons(wrapper) {
				var widget_instance = jQuery('#' + wrapper);
				if (!widget_instance.length) {
					return false;
				}
				widget_instance.find('.vbo-widget-checkav-results').html('');
				for (var i = 0; i < 1; i++) {
					var skeleton = '';
					skeleton += '<div class="vbo-dashboard-guest-activity vbo-dashboard-guest-activity-skeleton">';
					skeleton += '	<div class="vbo-dashboard-guest-activity-avatar">';
					skeleton += '		<div class="vbo-skeleton-loading vbo-skeleton-loading-avatar"></div>';
					skeleton += '	</div>';
					skeleton += '	<div class="vbo-dashboard-guest-activity-content">';
					skeleton += '		<div class="vbo-dashboard-guest-activity-content-head">';
					skeleton += '			<div class="vbo-skeleton-loading vbo-skeleton-loading-title"></div>';
					skeleton += '		</div>';
					skeleton += '		<div class="vbo-dashboard-guest-activity-content-info-msg">';
					skeleton += '			<div class="vbo-skeleton-loading vbo-skeleton-loading-content"></div>';
					skeleton += '		</div>';
					skeleton += '	</div>';
					skeleton += '</div>';
					// append skeleton
					jQuery(skeleton).appendTo(widget_instance.find('.vbo-widget-checkav-results'));
				}
			}

			/**
			 * Perform the request to load the available room rates.
			 */
			function vboWidgetCheckAvailabilityLoad(wrapper, checkbid) {
				var widget_instance = jQuery('#' + wrapper);
				if (!widget_instance.length) {
					return false;
				}

				// get vars for making the request
				var current_offset  = parseInt(widget_instance.attr('data-offset'));
				var length_per_page = parseInt(widget_instance.attr('data-length'));
				var checkin_date 	= widget_instance.find('.vbo-widget-checkav-checkindt').val();
				var num_nights 		= widget_instance.find('.vbo-widget-checkav-nights').val();
				var num_adults 		= widget_instance.find('.vbo-widget-checkav-adults').val();
				var num_children 	= widget_instance.find('.vbo-widget-checkav-children').val();

				var bid = null;
				if (typeof checkbid !== 'undefined' && checkbid) {
					bid = checkbid;
				}

				// the widget method to call
				var call_method = 'loadRoomRates';

				// make a request to load the available room rates
				VBOCore.doAjax(
					"<?php echo $this->getExecWidgetAjaxUri(); ?>",
					{
						widget_id: "<?php echo $this->getIdentifier(); ?>",
						call: call_method,
						return: 1,
						offset: current_offset,
						length: length_per_page,
						checkin_date: checkin_date,
						num_nights: num_nights,
						num_adults: num_adults,
						num_children: num_children,
						bid: bid,
						wrapper: wrapper,
						tmpl: "component"
					},
					function(response) {
						try {
							var obj_res = typeof response === 'string' ? JSON.parse(response) : response;
							if (!obj_res.hasOwnProperty(call_method) || !obj_res[call_method]) {
								console.error('Unexpected JSON response', obj_res);
								return false;
							}

							// replace HTML with new available room rates
							widget_instance.find('.vbo-widget-checkav-results').html(obj_res[call_method]['html']);
							
							// check results
							var tot_records = obj_res[call_method]['tot_records'] || 0;
							if (!isNaN(tot_records) && parseInt(tot_records) < 1) {
								// no results can indicate the offset is invalid or too high
								if (!isNaN(current_offset) && parseInt(current_offset) > 0) {
									// reset offset to 0
									widget_instance.attr('data-offset', 0);
									// show loading skeletons
									vboWidgetCheckAvailabilitySkeletons(wrapper);
									// reload the first page
									vboWidgetCheckAvailabilityLoad(wrapper);
								}
							}
						} catch(err) {
							console.error('could not parse JSON response', err, response);
						}
					},
					function(error) {
						// remove the skeleton loading
						widget_instance.find('.vbo-widget-checkav-results').find('.vbo-dashboard-guest-activity-skeleton').remove();
						console.error(error);
					}
				);
			}

			/**
			 * Navigate between the various pages of the available room rates.
			 */
			function vboWidgetCheckAvailabilityNavigate(wrapper, direction) {
				var widget_instance = jQuery('#' + wrapper);
				if (!widget_instance.length) {
					return false;
				}

				// show loading skeletons
				vboWidgetCheckAvailabilitySkeletons(wrapper);

				var instance_val = widget_instance.attr('data-instance');

				// current offset
				var current_offset = parseInt(widget_instance.attr('data-offset'));

				// events per page per type
				var steps = <?php echo $this->res_per_page; ?>;

				// check direction and update offsets for nav
				if (direction > 0) {
					// navigate forward
					widget_instance.attr('data-offset', (current_offset + steps));
				} else {
					// navigate backward
					var new_offset = current_offset - steps;
					new_offset = new_offset >= 0 ? new_offset : 0;
					widget_instance.attr('data-offset', new_offset);
				}

				// launch navigation
				vboWidgetCheckAvailabilityLoad(wrapper);
			}

			/**
			 * Calculate the available room rates.
			 */
			function vboWidgetCheckAvailabilityCalc(wrapper) {
				var widget_instance = jQuery('#' + wrapper);
				if (!widget_instance.length) {
					return false;
				}

				// show loading skeletons
				vboWidgetCheckAvailabilitySkeletons(wrapper);

				// always reset offset to the first page
				widget_instance.attr('data-offset', 0);

				// load data
				vboWidgetCheckAvailabilityLoad(wrapper);
			}

			/**
			 * Triggers when the multitask panel opens.
			 */
			function vboWidgetCheckAvailabilityMultitaskOpen(wrapper) {
				var widget_instance = jQuery('#' + wrapper);
				if (!widget_instance.length) {
					return false;
				}

				// check if a booking ID was set for this page
				var page_bid = widget_instance.attr('data-pagebid');
				if (!page_bid || page_bid < 1) {
					return false;
				}

				// show loading skeletons
				vboWidgetCheckAvailabilitySkeletons(wrapper);

				// always reset offset to the first page
				widget_instance.attr('data-offset', 0);

				// load data by injecting the current booking ID
				vboWidgetCheckAvailabilityLoad(wrapper, page_bid);
			}

			/**
			 * Book a room rate.
			 */
			function vboWidgetCheckAvailabilityBook(wrapper, rid, din, dout, adults, children, rpid) {
				var open_url = jQuery('.vbo-widget-checkav-basenavuri').first().attr('href');
				open_url = open_url.replace('%d', rid);
				open_url = open_url.replace('%s', din);
				open_url = open_url.replace('%s', dout);
				open_url = open_url.replace('%d', adults);
				open_url = open_url.replace('%d', children);
				open_url = open_url.replace('%d', rpid);
				// navigate
				document.location.href = open_url;
			}

			/**
			 * Book a split stay.
			 */
			function vboWidgetCheckAvailabilityBookSplitStay(rid, adults, children, split_qstring) {
				var open_url = jQuery('.vbo-widget-checkav-splitstayuri').first().attr('href');
				open_url = open_url.replace('%d', rid);
				open_url = open_url.replace('%d', adults);
				open_url = open_url.replace('%d', children);
				open_url += '&' + split_qstring;
				// navigate
				document.location.href = open_url;
			}

			/**
			 * Pick alternative stay dates suggested for calculation.
			 */
			function vboWidgetCheckAvailabilityPickRoom(wrapper, rid, din, nights, adults, children) {
				var widget_instance = jQuery('#' + wrapper);
				if (!widget_instance.length) {
					return false;
				}

				// show loading skeletons
				vboWidgetCheckAvailabilitySkeletons(wrapper);

				// always reset offset to the first page
				widget_instance.attr('data-offset', 0);

				// populate check-in date
				widget_instance.find('.vbo-widget-checkav-checkindt').datepicker('setDate', din);

				// set number of nights, adults and children
				widget_instance.find('.vbo-widget-checkav-nights').val(nights);
				widget_instance.find('.vbo-widget-checkav-adults').val(adults);
				widget_instance.find('.vbo-widget-checkav-children').val(children);

				// load data (we ignore the room ID passed to display all options available)
				vboWidgetCheckAvailabilityLoad(wrapper);
			}

		</script>
			<?php
		}
		?>

		<script type="text/javascript">

			jQuery(function() {

				// render datepicker calendar for dates navigation
				jQuery('#<?php echo $wrapper_id; ?>').find('.vbo-widget-checkav-checkindt').datepicker({
					minDate: 0,
					maxDate: "+3y",
					yearRange: "<?php echo date('Y'); ?>:<?php echo (date('Y') + 3); ?>",
					changeMonth: true,
					changeYear: true,
					dateFormat: "<?php echo $dtpicker_df; ?>"
				});

				// triggering for datepicker calendar icon
				jQuery('#<?php echo $wrapper_id; ?>').find('.vbo-widget-checkav-checkindt-trigger').click(function() {
					var jdp = jQuery(this).parent().find('input.hasDatepicker');
					if (jdp.length) {
						jdp.focus();
					}
				});

				// subscribe to the multitask-panel-open event
				document.addEventListener(VBOCore.multitask_open_event, function() {
					vboWidgetCheckAvailabilityMultitaskOpen('<?php echo $wrapper_id; ?>');
				});

			<?php
			if ($modal_load_bid) {
				// immediately fire the adaptive rendering according to multitask data
				?>
				vboWidgetCheckAvailabilityMultitaskOpen('<?php echo $wrapper_id; ?>');
				<?php
			}
			?>

			});
			
		</script>

		<?php
	}

	/**
	 * Returns the maximum number of adults and children as a sum from all rooms.
	 * Internal method for this widget only.
	 * 
	 * @return 	array 	two values, respectively for highest adults and children.
	 */
	protected function getMaxestGuests()
	{
		$maxest = array(1, 1);

		$dbo = JFactory::getDbo();

		$q = "SELECT SUM(`toadult`) AS `max_adults`, SUM(`tochild`) AS `max_children` FROM `#__vikbooking_rooms`;";
		$dbo->setQuery($q);
		$record = $dbo->loadAssoc();
		if ($record) {
			$maxest = array($record['max_adults'], $record['max_children']);
		}

		return $maxest;
	}

	/**
	 * In case the multitask panel injects a booking ID, this method
	 * prepares the proper values to use for loading room rates.
	 * 
	 * @param 	int 	$bid 			the booking id.
	 * @param 	bool 	$multi_rooms 	whether to return array of guests
	 *									when multiple rooms booked.
	 * 
	 * @return 	array 	list of values to use for loading.
	 */
	protected function getBookingLoadValues($bid, $multi_rooms = true)
	{
		// set default values
		$checkin_date = '';
		$num_nights   = 0;
		$num_adults   = 0;
		$num_children = 0;
		$room_ids	  = [];

		if (empty($bid)) {
			// do nothing
			return [$checkin_date, $num_nights, $num_adults, $num_children, $room_ids];
		}

		// get booking details
		$booking = VikBooking::getBookingInfoFromID($bid);

		if (!is_array($booking) || !count($booking)) {
			// do nothing
			return [$checkin_date, $num_nights, $num_adults, $num_children, $room_ids];
		}

		// get reservation rooms
		$book_rooms = VikBooking::loadOrdersRoomsData($booking['id']);

		if (!is_array($book_rooms) || !count($book_rooms)) {
			// do nothing
			return [$checkin_date, $num_nights, $num_adults, $num_children, $room_ids];
		}

		// build proper values for this reservation
		$checkin_date = date('Y-m-d', $booking['checkin']);
		$num_nights   = $booking['days'];
		$arr_adults   = [];
		$arr_children = [];
		foreach ($book_rooms as $roomres) {
			$num_adults 	+= $roomres['adults'];
			$num_children 	+= $roomres['children'];
			$arr_adults[] 	= $roomres['adults'];
			$arr_children[] = $roomres['children'];
			$room_ids[] 	= $roomres['idroom'];
			if ($booking['split_stay']) {
				// do not sum the guests in case of split stay
				$num_adults   = $roomres['adults'];
				$num_children = $roomres['children'];
			}
		}

		if (count($book_rooms) > 1 && $multi_rooms === true && !$booking['split_stay']) {
			// return the adults and children as array rather than as integers
			$num_adults = $arr_adults;
			$num_children = $arr_children;
		}

		return [$checkin_date, $num_nights, $num_adults, $num_children, $room_ids];
	}
}