| 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 |
jimport('joomla.application.component.view'); |
| 14 |
|
| 15 |
class VikbookingViewSearchsuggestions extends JViewVikBooking |
| 16 |
{ |
| 17 |
public function display($tpl = null) |
| 18 |
{ |
| 19 |
$dbo = JFactory::getDbo(); |
| 20 |
$vbo_tn = VikBooking::getTranslator(); |
| 21 |
|
| 22 |
// invoke availability helper class and define settings |
| 23 |
$av_helper = VikBooking::getAvailabilityInstance() |
| 24 |
->setBackForthDays(14) |
| 25 |
->setIsFrontBooking(true); |
| 26 |
|
| 27 |
$code = VikRequest::getInt('code', 0, 'request'); |
| 28 |
$fromts = VikRequest::getInt('fromts', 0, 'request'); |
| 29 |
$tots = VikRequest::getInt('tots', 0, 'request'); |
| 30 |
$party = $padults = VikRequest::getVar('party', array()); |
| 31 |
$pcategories = VikRequest::getString('categories', '', 'request'); |
| 32 |
|
| 33 |
// set stay values |
| 34 |
$checkin_date = date('Y-m-d', $fromts); |
| 35 |
$checkout_date = date('Y-m-d', $tots); |
| 36 |
$av_helper->setStayDates($checkin_date, $checkout_date); |
| 37 |
|
| 38 |
// count nights of stay |
| 39 |
$daysdiff = $av_helper->countNightsOfStay(); |
| 40 |
|
| 41 |
// get how many guests were asked in the party |
| 42 |
$party_guests = [ |
| 43 |
'adults' => 0, |
| 44 |
'children' => 0, |
| 45 |
]; |
| 46 |
foreach ($party as $guests) { |
| 47 |
// set party involved for this room |
| 48 |
$av_helper->setRoomParty((isset($guests['adults']) ? (int)$guests['adults'] : 0), (isset($guests['children']) ? (int)$guests['children'] : 0)); |
| 49 |
if (isset($guests['adults'])) { |
| 50 |
$party_guests['adults'] += (int)$guests['adults']; |
| 51 |
} |
| 52 |
if (isset($guests['children'])) { |
| 53 |
$party_guests['children'] += (int)$guests['children']; |
| 54 |
} |
| 55 |
} |
| 56 |
|
| 57 |
// get nights requested |
| 58 |
$info_from = getdate($fromts); |
| 59 |
$nights_requested = [ |
| 60 |
mktime(0, 0, 0, $info_from['mon'], $info_from['mday'], $info_from['year']), |
| 61 |
]; |
| 62 |
for ($n = 1; $n < $daysdiff; $n++) { |
| 63 |
$nights_requested[] = mktime(0, 0, 0, $info_from['mon'], ($info_from['mday'] + $n), $info_from['year']); |
| 64 |
} |
| 65 |
|
| 66 |
// get rooms IDs available |
| 67 |
$rooms_info = []; |
| 68 |
$q = "SELECT * FROM `#__vikbooking_rooms` WHERE `avail`=1 ORDER BY `name` ASC;"; |
| 69 |
$dbo->setQuery($q); |
| 70 |
$dbo->execute(); |
| 71 |
if ($dbo->getNumRows() > 0) { |
| 72 |
$getrooms = $dbo->loadAssocList(); |
| 73 |
$vbo_tn->translateContents($getrooms, '#__vikbooking_rooms'); |
| 74 |
foreach ($getrooms as $r) { |
| 75 |
/** |
| 76 |
* If requested, we apply the same category filter used for the search. |
| 77 |
* |
| 78 |
* @since 1.14 (J) - 1.4.0 (WP) |
| 79 |
*/ |
| 80 |
if (!empty($pcategories) && $pcategories != 'all' && !empty($r['idcat'])) { |
| 81 |
$room_cats = explode(';', $r['idcat']); |
| 82 |
if (!empty($room_cats[0]) && !in_array($pcategories, $room_cats)) { |
| 83 |
// room has got categories, but the filtered one is not available, so skip this room |
| 84 |
continue; |
| 85 |
} |
| 86 |
} |
| 87 |
|
| 88 |
// push room |
| 89 |
$rooms_info[$r['id']] = $r; |
| 90 |
} |
| 91 |
} |
| 92 |
|
| 93 |
// set rooms |
| 94 |
$av_helper->setRooms($rooms_info); |
| 95 |
|
| 96 |
// check for turnover day between bookings |
| 97 |
$inonout_allowed = true; |
| 98 |
$timeopst = VikBooking::getTimeOpenStore(); |
| 99 |
if (is_array($timeopst)) { |
| 100 |
if ($timeopst[0] < $timeopst[1]) { |
| 101 |
// check-in not allowed on a day where there is already a check out (no arrivals/depatures on the same day) |
| 102 |
$inonout_allowed = false; |
| 103 |
} |
| 104 |
} |
| 105 |
|
| 106 |
// get closest timestamps to the requested dates (14 days before and after the requested dates, if not in the past) |
| 107 |
$info_from = getdate($fromts); |
| 108 |
// get nights requested |
| 109 |
$nights_requested = []; |
| 110 |
$nights_requested[] = mktime(0, 0, 0, $info_from['mon'], $info_from['mday'], $info_from['year']); |
| 111 |
for ($n = 1; $n < $daysdiff; $n++) { |
| 112 |
$nights_requested[] = mktime(0, 0, 0, $info_from['mon'], ($info_from['mday'] + $n), $info_from['year']); |
| 113 |
} |
| 114 |
// count suggested dates back/forth |
| 115 |
$info_to = getdate($tots); |
| 116 |
$lim_past_ts = mktime($info_from['hours'], $info_from['minutes'], $info_from['seconds'], date('n'), ((int)date('j') + VikBooking::getMinDaysAdvance()), date('Y')); |
| 117 |
$sug_from_ts = mktime($info_from['hours'], $info_from['minutes'], $info_from['seconds'], $info_from['mon'], ($info_from['mday'] - 14), $info_from['year']); |
| 118 |
$sug_from_ts = $sug_from_ts < $lim_past_ts ? $lim_past_ts : $sug_from_ts; |
| 119 |
$sug_to_ts = mktime($info_to['hours'], $info_to['minutes'], $info_to['seconds'], $info_to['mon'], ($info_to['mday'] + 14), $info_to['year']); |
| 120 |
$sug_to_ts = $sug_to_ts < $sug_from_ts ? $sug_from_ts : $sug_to_ts; |
| 121 |
|
| 122 |
// get days for suggestions |
| 123 |
$groupdays = []; |
| 124 |
$sug_start_info = getdate($sug_from_ts); |
| 125 |
$sug_from_midnight = mktime(0, 0, 0, $sug_start_info['mon'], $sug_start_info['mday'], $sug_start_info['year']); |
| 126 |
$sug_start_info = getdate($sug_from_midnight); |
| 127 |
while ($sug_start_info[0] <= $sug_to_ts) { |
| 128 |
array_push($groupdays, $sug_start_info[0]); |
| 129 |
$sug_start_info = getdate(mktime(0, 0, 0, $sug_start_info['mon'], ($sug_start_info['mday'] + 1), $sug_start_info['year'])); |
| 130 |
} |
| 131 |
|
| 132 |
// build suggestions array of dates with some availability |
| 133 |
$suggestions = []; |
| 134 |
$fully_booked = []; |
| 135 |
$allbusy = VikBooking::loadBusyRecords(array_keys($rooms_info), $sug_from_ts, strtotime('+1 day', $sug_to_ts)); |
| 136 |
foreach ($rooms_info as $rid => $room) { |
| 137 |
foreach ($groupdays as $gday) { |
| 138 |
$day_key = date('Y-m-d', $gday); |
| 139 |
$bfound = 0; |
| 140 |
if (!isset($allbusy[$rid])) { |
| 141 |
$allbusy[$rid] = []; |
| 142 |
} |
| 143 |
foreach ($allbusy[$rid] as $bu) { |
| 144 |
$busy_info_in = getdate($bu['checkin']); |
| 145 |
$busy_info_out = getdate($bu['checkout']); |
| 146 |
$busy_in_ts = mktime(0, 0, 0, $busy_info_in['mon'], $busy_info_in['mday'], $busy_info_in['year']); |
| 147 |
$busy_out_ts = mktime(0, 0, 0, $busy_info_out['mon'], $busy_info_out['mday'], $busy_info_out['year']); |
| 148 |
if ($gday >= $busy_in_ts && $gday == $busy_out_ts && !$inonout_allowed && $room['units'] < 2) { |
| 149 |
// check-ins on check-outs not allowed |
| 150 |
$bfound++; |
| 151 |
if ($bfound >= $room['units']) { |
| 152 |
break; |
| 153 |
} |
| 154 |
} |
| 155 |
if ($gday >= $busy_in_ts && $gday < $busy_out_ts) { |
| 156 |
$bfound++; |
| 157 |
if ($bfound >= $room['units']) { |
| 158 |
break; |
| 159 |
} |
| 160 |
} |
| 161 |
} |
| 162 |
if ($bfound < $room['units']) { |
| 163 |
if (!isset($suggestions[$day_key])) { |
| 164 |
$suggestions[$day_key] = []; |
| 165 |
} |
| 166 |
$room_day = $room; |
| 167 |
$room_day['units_left'] = $room['units'] - $bfound; |
| 168 |
$suggestions[$day_key] = $suggestions[$day_key] + array($rid => $room_day); |
| 169 |
} else { |
| 170 |
if (!in_array($room['id'], $fully_booked)) { |
| 171 |
// push fully booked room ID |
| 172 |
$fully_booked[] = $room['id']; |
| 173 |
} |
| 174 |
} |
| 175 |
} |
| 176 |
} |
| 177 |
|
| 178 |
// build the solutions array with keys=checkin, values=all rooms suited for the requested number of nights |
| 179 |
$solutions = []; |
| 180 |
if ($suggestions) { |
| 181 |
//get all rooms available for the number of nights requested in the suggestions array of dates |
| 182 |
foreach ($suggestions as $kday => $rooms) { |
| 183 |
$day_ts_info = getdate(strtotime($kday)); |
| 184 |
foreach ($rooms as $rid => $room) { |
| 185 |
$suitable = true; |
| 186 |
$room_days_av_left = array($kday => $room['units_left']); |
| 187 |
for ($i = 1; $i < $daysdiff; $i++) { |
| 188 |
$next_night = mktime(0, 0, 0, $day_ts_info['mon'], ($day_ts_info['mday'] + $i), $day_ts_info['year']); |
| 189 |
$next_night_dt = date('Y-m-d', $next_night); |
| 190 |
if (!isset($suggestions[$next_night_dt]) || !isset($suggestions[$next_night_dt][$rid])) { |
| 191 |
$suitable = false; |
| 192 |
break; |
| 193 |
} |
| 194 |
$room_days_av_left[$next_night_dt] = $suggestions[$next_night_dt][$rid]['units_left']; |
| 195 |
} |
| 196 |
if ($suitable === true) { |
| 197 |
if (!isset($solutions[$day_ts_info[0]])) { |
| 198 |
$solutions[$day_ts_info[0]] = []; |
| 199 |
} |
| 200 |
unset($room['units_left']); |
| 201 |
$room['days_av_left'] = $room_days_av_left; |
| 202 |
$solutions[$day_ts_info[0]] = $solutions[$day_ts_info[0]] + array($rid => $room); |
| 203 |
} |
| 204 |
} |
| 205 |
} |
| 206 |
} |
| 207 |
|
| 208 |
if ($solutions) { |
| 209 |
// sort the solutions by the closest checkin date to the one requested |
| 210 |
$sortmap = []; |
| 211 |
foreach ($solutions as $kdayts => $solution) { |
| 212 |
$sortmap[$kdayts] = $kdayts > $fromts ? ($kdayts - $fromts) : ($fromts - $kdayts); |
| 213 |
} |
| 214 |
asort($sortmap); |
| 215 |
$sorted = []; |
| 216 |
foreach ($sortmap as $kdayts => $v) { |
| 217 |
$sorted[$kdayts] = $solutions[$kdayts]; |
| 218 |
} |
| 219 |
$solutions = $sorted; |
| 220 |
|
| 221 |
/** |
| 222 |
* Load rooms involved in the final alternative solutions in order |
| 223 |
* to validate global/room-level restrictions and closing dates. |
| 224 |
* |
| 225 |
* @since 1.15.4 (J) - 1.5.10 (WP) |
| 226 |
*/ |
| 227 |
$rooms_involved = []; |
| 228 |
foreach ($solutions as $kdayts => $roomsol) { |
| 229 |
foreach (array_keys($roomsol) as $rid) { |
| 230 |
if (!in_array($rid, $rooms_involved)) { |
| 231 |
$rooms_involved[] = $rid; |
| 232 |
} |
| 233 |
} |
| 234 |
} |
| 235 |
|
| 236 |
// load restrictions for all rooms involved |
| 237 |
$all_restrictions = VikBooking::loadRestrictions(true, $rooms_involved); |
| 238 |
$glob_restrictions = VikBooking::globalRestrictions($all_restrictions); |
| 239 |
$room_level_restr = []; |
| 240 |
|
| 241 |
foreach ($solutions as $kdayts => $roomsol) { |
| 242 |
// build suggested stay dates |
| 243 |
$sug_in = getdate($kdayts); |
| 244 |
$sug_out = getdate(mktime(0, 0, 0, $sug_in['mon'], ($sug_in['mday'] + $daysdiff), $sug_in['year'])); |
| 245 |
// validate global restrictions |
| 246 |
if (VikBooking::validateRoomRestriction($glob_restrictions, $sug_in, $sug_out, $daysdiff)) { |
| 247 |
// global restrictions apply over this stay |
| 248 |
unset($solutions[$kdayts]); |
| 249 |
continue; |
| 250 |
} |
| 251 |
// validate closing dates and the affected stay components |
| 252 |
if ($closed_components = VikBooking::validateClosingDates($sug_in[0], $sug_out[0], 'Y-m-d', true)) { |
| 253 |
// global closing dates apply over this stay |
| 254 |
unset($solutions[$kdayts]); |
| 255 |
if (($closed_components['checkin'] ?? false) || ($closed_components['stay'] ?? false)) { |
| 256 |
// this day should not be a suggestion because the check-in date is fully closed |
| 257 |
unset($suggestions[date('Y-m-d', $kdayts)]); |
| 258 |
} |
| 259 |
continue; |
| 260 |
} |
| 261 |
// validate restrictions at room level |
| 262 |
foreach ($roomsol as $rid => $rdata) { |
| 263 |
if (!isset($room_level_restr[$rid])) { |
| 264 |
// load restrictions at room level |
| 265 |
$room_level_restr[$rid] = VikBooking::roomRestrictions($rid, $all_restrictions); |
| 266 |
} |
| 267 |
if (VikBooking::validateRoomRestriction($room_level_restr[$rid], $sug_in, $sug_out, $daysdiff)) { |
| 268 |
// room-level restrictions apply over this stay |
| 269 |
unset($solutions[$kdayts][$rid]); |
| 270 |
if (!count($solutions[$kdayts])) { |
| 271 |
// unset the entire suggested date |
| 272 |
unset($solutions[$kdayts]); |
| 273 |
break; |
| 274 |
} |
| 275 |
continue; |
| 276 |
} |
| 277 |
} |
| 278 |
} |
| 279 |
} |
| 280 |
|
| 281 |
// this array should only be parsed in case of error codes 2 and 3. It is not populated in case of error code 1 |
| 282 |
$booking_solutions = []; |
| 283 |
|
| 284 |
/** |
| 285 |
* In case of error code 1, we try to check for some split stay solutions. |
| 286 |
* |
| 287 |
* @since 1.16.0 (J) - 1.6.0 (WP) |
| 288 |
* @since 1.18.6 (J) - 1.8.6 (WP) calculate split stay solutions prior to error code parsing. |
| 289 |
*/ |
| 290 |
$split_stay_solutions = $code === 1 ? $av_helper->findSplitStays($fully_booked, $allbusy) : []; |
| 291 |
|
| 292 |
// parse the solutions depending on the error code to give the right booking solutions |
| 293 |
if ($solutions) { |
| 294 |
switch ($code) { |
| 295 |
case 1: |
| 296 |
// no availability for the dates requested (there are rates defined for this number of nights and rooms compatible with the party requested) |
| 297 |
// the solution is to suggest another date for check-in/out when there is availability for the requested party |
| 298 |
|
| 299 |
// check if the party requested is suited for the current solutions and unset the ones we can't suggest |
| 300 |
foreach ($solutions as $kdayts => $solution) { |
| 301 |
$party_solutions = []; |
| 302 |
foreach ($party as $nump => $guests) { |
| 303 |
foreach ($solution as $rid => $roomsol) { |
| 304 |
if (($roomsol['fromadult'] <= $guests['adults'] && $roomsol['toadult'] >= $guests['adults']) && |
| 305 |
($roomsol['fromchild'] <= $guests['children'] && $roomsol['tochild'] >= $guests['children']) && |
| 306 |
($roomsol['mintotpeople'] <= ($guests['adults'] + $guests['children'])) && |
| 307 |
($roomsol['totpeople'] >= ($guests['adults'] + $guests['children']))) { |
| 308 |
// this room-solution can fit this party |
| 309 |
$sol_min_rooms = min($roomsol['days_av_left']); |
| 310 |
$solutions[$kdayts][$rid]['minrooms'] = $sol_min_rooms; |
| 311 |
if (!isset($party_solutions[$nump])) { |
| 312 |
$party_solutions[$nump] = []; |
| 313 |
} |
| 314 |
$party_solutions[$nump] = $party_solutions[$nump] + array($rid => $solutions[$kdayts][$rid]); |
| 315 |
} |
| 316 |
} |
| 317 |
} |
| 318 |
if (count($party_solutions) != count($party)) { |
| 319 |
// in this type of error-code we can't suggest other booking solutions for a specific day, so we unset this solution that doesn't fit the party |
| 320 |
unset($solutions[$kdayts]); |
| 321 |
continue; |
| 322 |
} |
| 323 |
|
| 324 |
// check if the rooms-solutions have enough units to be used |
| 325 |
$rooms_usage = []; |
| 326 |
$rooms_avail = []; |
| 327 |
foreach ($party_solutions as $nump => $party_sol) { |
| 328 |
foreach ($party_sol as $rid => $roomsol) { |
| 329 |
if (!isset($rooms_avail[$rid])) { |
| 330 |
$rooms_avail[$rid] = $roomsol['minrooms']; |
| 331 |
} |
| 332 |
if (!isset($rooms_usage[$rid])) { |
| 333 |
$rooms_usage[$rid] = 0; |
| 334 |
} |
| 335 |
$rooms_usage[$rid]++; |
| 336 |
} |
| 337 |
} |
| 338 |
foreach ($rooms_usage as $rid => $usage) { |
| 339 |
if ($usage > $rooms_avail[$rid]) { |
| 340 |
// this room has been suggested too many times for its remaining availability on this day. Remove it until it's okay |
| 341 |
while ($usage > $rooms_avail[$rid]) { |
| 342 |
foreach ($party_solutions as $nump => $party_sol) { |
| 343 |
if (isset($party_sol[$rid])) { |
| 344 |
$usage--; |
| 345 |
unset($party_solutions[$nump][$rid]); |
| 346 |
if (!(count($party_solutions[$nump]) > 0)) { |
| 347 |
// this party has no more solutions |
| 348 |
unset($party_solutions[$nump]); |
| 349 |
} |
| 350 |
if (!($usage > $rooms_avail[$rid])) { |
| 351 |
break; |
| 352 |
} |
| 353 |
} |
| 354 |
} |
| 355 |
} |
| 356 |
} |
| 357 |
} |
| 358 |
if (count($party_solutions) != count($party)) { |
| 359 |
// in this type of error-code we can't suggest other booking solutions for a specific day, so we unset this solution that doesn't fit the party |
| 360 |
unset($solutions[$kdayts]); |
| 361 |
continue; |
| 362 |
} |
| 363 |
} |
| 364 |
|
| 365 |
break; |
| 366 |
case 2: |
| 367 |
case 3: |
| 368 |
// (error code 2) query failed for fetching the rooms (no rates defined for this number of nights or no rooms for the party requested) |
| 369 |
// the solution is to suggest another combination of party for booking (as long as there are rates for the requested number of nights and the number of guests can fit in the available rooms). |
| 370 |
// (error code 3) not enough rooms available for the party requested |
| 371 |
// the solution to suggest is the same as for the error code 2. |
| 372 |
|
| 373 |
// unset the rooms in the solutions that have no rates for this number of nights as they are useless. |
| 374 |
foreach ($solutions as $kdayts => $solution) { |
| 375 |
$rooms_with_rates = []; |
| 376 |
$q = "SELECT `p`.`idroom` FROM `#__vikbooking_dispcost` AS `p` WHERE `p`.`idroom` IN (".implode(', ', array_keys($solution)).") AND `p`.`days`=".(int)$daysdiff." GROUP BY `p`.`idroom`;"; |
| 377 |
$dbo->setQuery($q); |
| 378 |
$getrooms = $dbo->loadAssocList(); |
| 379 |
foreach ($getrooms as $r) { |
| 380 |
array_push($rooms_with_rates, (int)$r['idroom']); |
| 381 |
} |
| 382 |
foreach ($solution as $rid => $roomsol) { |
| 383 |
if (!in_array($rid, $rooms_with_rates)) { |
| 384 |
unset($solutions[$kdayts][$rid]); |
| 385 |
continue; |
| 386 |
} |
| 387 |
$solutions[$kdayts][$rid]['minrooms'] = min($roomsol['days_av_left']); |
| 388 |
} |
| 389 |
if (!(count($solutions[$kdayts]))) { |
| 390 |
unset($solutions[$kdayts]); |
| 391 |
} |
| 392 |
} |
| 393 |
if (!(count($solutions))) { |
| 394 |
break; |
| 395 |
} |
| 396 |
|
| 397 |
// build the booking solution to suggest, for example, to book some rooms more or less. |
| 398 |
// Example: 1 room for 8 adults can be booked as 3 double rooms (3 adults, 3 adults, 2 adults = 8 adults) |
| 399 |
// Example: 2 quadruple rooms for 4 adults each, can be booked as 4 double rooms, with 2 adults each. |
| 400 |
|
| 401 |
// sort rooms-solutions by 'max-adults' (toadult), 'max-guests' (totpeople), 'max-children' (tochild), in a Descending order |
| 402 |
foreach ($solutions as $kdayts => $solution) { |
| 403 |
// with this sorting, we will have the bigger rooms on top to quickly fit the party requested |
| 404 |
uasort($solutions[$kdayts], function($a, $b) { |
| 405 |
if ($a['toadult'] == $b['toadult']) { |
| 406 |
if ($a['totpeople'] == $b['totpeople']) { |
| 407 |
return $a['tochild'] > $b['tochild'] ? -1 : 1; |
| 408 |
} |
| 409 |
return $a['totpeople'] > $b['totpeople'] ? -1 : 1; |
| 410 |
} |
| 411 |
return $a['toadult'] > $b['toadult'] ? -1 : 1; |
| 412 |
}); |
| 413 |
} |
| 414 |
|
| 415 |
// check if the rooms of each solution can fit the number of guests requested, unset the solution otherwise |
| 416 |
foreach ($solutions as $kdayts => $solution) { |
| 417 |
$solution_guests = array( |
| 418 |
'adults' => 0, |
| 419 |
'children' => 0 |
| 420 |
); |
| 421 |
foreach ($solution as $rid => $roomsol) { |
| 422 |
//check if this solution of rooms can allocate all the guests requested |
| 423 |
if ($roomsol['totpeople'] < ($roomsol['toadult'] + $roomsol['tochild']) && !$party_guests['children']) { |
| 424 |
// in case of no children requested, we ignore them to avoid adjusting the room capacity |
| 425 |
$roomsol['tochild'] = 0; |
| 426 |
} |
| 427 |
if ($roomsol['totpeople'] < ($roomsol['toadult'] + $roomsol['tochild'])) { |
| 428 |
// the sum of the max_adults and max_children exceeds the max_guests: lower the adults we can take first (if party children > 0), then the children, until sum=max_guests |
| 429 |
while (($roomsol['toadult'] > 0 || $roomsol['tochild'] > 0)) { |
| 430 |
if (!$party_guests['children'] && $roomsol['totpeople'] == $roomsol['toadult']) { |
| 431 |
/** |
| 432 |
* When no children requested in the party, we cannot under-utilize rooms. |
| 433 |
* Break the loop without lowering the 'toadult'. |
| 434 |
* @since 1.10 - August 2018 |
| 435 |
*/ |
| 436 |
$roomsol['tochild'] = 0; |
| 437 |
break; |
| 438 |
} |
| 439 |
if ($party_guests['children'] && $solution_guests['children'] >= $party_guests['children']) { |
| 440 |
/** |
| 441 |
* If all the children requested were allocated in other solutions, |
| 442 |
* we should not under-utilize rooms by reduing the number of adults. |
| 443 |
* @since 1.10 - August 2018 |
| 444 |
*/ |
| 445 |
break; |
| 446 |
} |
| 447 |
if ($roomsol['toadult'] > 0 && $party_guests['children'] > 0 && !($roomsol['tochild'] > $party_guests['children'])) { |
| 448 |
//we lower first the adults that we put in this room, only if there are children in the party and if the children in the party are more than the 'max_children' of this room |
| 449 |
$roomsol['toadult']--; |
| 450 |
if ($roomsol['totpeople'] >= ($roomsol['toadult'] + $roomsol['tochild'])) { |
| 451 |
break; |
| 452 |
} |
| 453 |
} |
| 454 |
if ($roomsol['tochild'] > 0) { |
| 455 |
// if the max_guests is still greater than the sum of adults+children we take, take out one child |
| 456 |
$roomsol['tochild']--; |
| 457 |
if ($roomsol['totpeople'] >= ($roomsol['toadult'] + $roomsol['tochild'])) { |
| 458 |
break; |
| 459 |
} |
| 460 |
} |
| 461 |
if ($roomsol['toadult'] > 0) { |
| 462 |
// if even at this point we still have a high sum of guests to take compared to the max_guests, take out again one adult |
| 463 |
$roomsol['toadult']--; |
| 464 |
if ($roomsol['totpeople'] >= ($roomsol['toadult'] + $roomsol['tochild'])) { |
| 465 |
break; |
| 466 |
} |
| 467 |
} |
| 468 |
} |
| 469 |
} |
| 470 |
$solution_guests['adults'] += $roomsol['toadult'] * $roomsol['minrooms']; |
| 471 |
$solution_guests['children'] += $roomsol['tochild'] * $roomsol['minrooms']; |
| 472 |
// update 'max_adults' and 'max_children' for this solution (for later guests allocation in $booking_solution) |
| 473 |
$solution[$rid]['toadult'] = $roomsol['toadult']; |
| 474 |
$solution[$rid]['tochild'] = $roomsol['tochild']; |
| 475 |
} |
| 476 |
$solutions[$kdayts] = $solution; |
| 477 |
if ($solution_guests['adults'] < $party_guests['adults'] || $solution_guests['children'] < $party_guests['children']) { |
| 478 |
// the guests we can allocate with the solution of this day are not enough: unset the solution |
| 479 |
unset($solutions[$kdayts]); |
| 480 |
continue; |
| 481 |
} |
| 482 |
// if we get to this point we can suggest a booking solution for the party requested, but in different rooms |
| 483 |
if (!isset($booking_solutions[$kdayts])) { |
| 484 |
$booking_solutions[$kdayts] = []; |
| 485 |
} |
| 486 |
// re-loop over the rooms in this solution to build the booking solution for this day |
| 487 |
$guests_allocated = array( |
| 488 |
'adults' => 0, |
| 489 |
'children' => 0 |
| 490 |
); |
| 491 |
|
| 492 |
/** |
| 493 |
* The rooms available for an alternative booking solutions have been sorted by capacity |
| 494 |
* in a descending order to quickly fit the guest party requested. However, if a smaller |
| 495 |
* and cheaper room was capable of fitting all guests, we should opt for this solution. |
| 496 |
* |
| 497 |
* @since 1.15.4 (J) - 1.5.9 (WP) |
| 498 |
*/ |
| 499 |
$smaller_fit_found = false; |
| 500 |
$smaller_solutions = array_reverse($solution, true); |
| 501 |
foreach ($smaller_solutions as $rid => $roomsol) { |
| 502 |
if ($party_guests['adults'] > 0 && $party_guests['adults'] > $roomsol['toadult']) { |
| 503 |
// too many adults requested for this small room |
| 504 |
continue; |
| 505 |
} |
| 506 |
if ($party_guests['children'] > 0 && $party_guests['children'] > $roomsol['tochild']) { |
| 507 |
// too many children requested for this small room |
| 508 |
continue; |
| 509 |
} |
| 510 |
if (($party_guests['adults'] + $party_guests['children']) > $roomsol['totpeople']) { |
| 511 |
// too many guests requested for this small room |
| 512 |
continue; |
| 513 |
} |
| 514 |
// we've got a fitting room which could be smaller |
| 515 |
$roomsol['guests_allocation'] = [ |
| 516 |
'adults' => $party_guests['adults'], |
| 517 |
'children' => $party_guests['children'], |
| 518 |
]; |
| 519 |
array_push($booking_solutions[$kdayts], $roomsol); |
| 520 |
// turn flag on and break the loop |
| 521 |
$smaller_fit_found = true; |
| 522 |
break; |
| 523 |
} |
| 524 |
if ($smaller_fit_found) { |
| 525 |
// no need to parse the rooms from the largest to the smallest |
| 526 |
continue; |
| 527 |
} |
| 528 |
|
| 529 |
foreach ($solution as $rid => $roomsol) { |
| 530 |
// fullfil all the units of this room |
| 531 |
for ($units_taken = 0; $units_taken < $roomsol['minrooms']; $units_taken++) { |
| 532 |
$current_allocation = array( |
| 533 |
'adults' => 0, |
| 534 |
'children' => 0 |
| 535 |
); |
| 536 |
if ($guests_allocated['adults'] < $party_guests['adults']) { |
| 537 |
$humans_taken = $roomsol['toadult']; |
| 538 |
$missing_humans = $party_guests['adults'] - $guests_allocated['adults']; |
| 539 |
$humans_taken = $humans_taken > $missing_humans ? $missing_humans : $humans_taken; |
| 540 |
$current_allocation['adults'] = $humans_taken; |
| 541 |
$guests_allocated['adults'] += $humans_taken; |
| 542 |
} |
| 543 |
if ($guests_allocated['children'] < $party_guests['children']) { |
| 544 |
$humans_taken = $roomsol['tochild']; |
| 545 |
$missing_humans = $party_guests['children'] - $guests_allocated['children']; |
| 546 |
$humans_taken = $humans_taken > $missing_humans ? $missing_humans : $humans_taken; |
| 547 |
$current_allocation['children'] = $humans_taken; |
| 548 |
$guests_allocated['children'] += $humans_taken; |
| 549 |
} |
| 550 |
$roomsol['guests_allocation'] = $current_allocation; |
| 551 |
array_push($booking_solutions[$kdayts], $roomsol); |
| 552 |
if ($guests_allocated['adults'] >= $party_guests['adults'] && $guests_allocated['children'] >= $party_guests['children']) { |
| 553 |
//we have allocated all guests, exit the for-loop |
| 554 |
break; |
| 555 |
} |
| 556 |
} |
| 557 |
if ($guests_allocated['adults'] >= $party_guests['adults'] && $guests_allocated['children'] >= $party_guests['children']) { |
| 558 |
// we have allocated all guests with this solution, no need to loop over other rooms available in this day. |
| 559 |
break; |
| 560 |
} |
| 561 |
} |
| 562 |
} |
| 563 |
|
| 564 |
break; |
| 565 |
default: |
| 566 |
break; |
| 567 |
} |
| 568 |
} |
| 569 |
|
| 570 |
$this->code = $code; |
| 571 |
$this->nights = $daysdiff; |
| 572 |
$this->checkin = $fromts; |
| 573 |
$this->checkout = $tots; |
| 574 |
$this->nights_requested = $nights_requested; |
| 575 |
$this->party = $party; |
| 576 |
$this->party_guests = $party_guests; |
| 577 |
$this->suggestions = $suggestions; |
| 578 |
$this->solutions = $solutions; |
| 579 |
$this->booking_solutions = $booking_solutions; |
| 580 |
$this->sug_from_ts = $sug_from_ts; |
| 581 |
$this->sug_to_ts = $sug_to_ts; |
| 582 |
$this->vbo_tn = $vbo_tn; |
| 583 |
$this->split_stay_solutions = $split_stay_solutions; |
| 584 |
//theme |
| 585 |
$theme = VikBooking::getTheme(); |
| 586 |
if ($theme != 'default') { |
| 587 |
$thdir = VBO_SITE_PATH.DS.'themes'.DS.$theme.DS.'searchsuggestions'; |
| 588 |
if (is_dir($thdir)) { |
| 589 |
$this->_setPath('template', $thdir.DS); |
| 590 |
} |
| 591 |
} |
| 592 |
|
| 593 |
/** |
| 594 |
* |
| 595 |
* This view is displayed via Ajax, so the output must be buffered and echoed as JSON-encoded, then exit the process. |
| 596 |
* This is to avoid printing meta data values of the head. However, we need to receive the request var getjson=1 for bc. |
| 597 |
* |
| 598 |
* @since 1.13 (J) - 1.3.1 (WP) |
| 599 |
*/ |
| 600 |
$getjson = VikRequest::getInt('getjson', 0, 'request'); |
| 601 |
if ($getjson) { |
| 602 |
ob_start(); |
| 603 |
parent::display($tpl); |
| 604 |
$ajax_buffer = ob_get_contents(); |
| 605 |
ob_end_clean(); |
| 606 |
echo json_encode(array($ajax_buffer)); |
| 607 |
exit; |
| 608 |
} |
| 609 |
|
| 610 |
parent::display($tpl); |
| 611 |
} |
| 612 |
} |
| 613 |
|