| @@ -67,8 +67,15 @@ | ||
| 67 | 67 | */ |
| 68 | 68 | protected $ignore_restrictions = false; |
| 69 | 69 | |
| 70 | 70 | /** |
| 71 | + * Whether to ignore rooms availability. | |
| 72 | + * | |
| 73 | + * @var bool | |
| 74 | + */ | |
| 75 | + protected $ignore_availability = false; | |
| 76 | + | |
| 77 | + /** | |
| 71 | 78 | * Whether check-ins on check-outs are allowed. |
| 72 | 79 | * |
| 73 | 80 | * @var bool |
| 74 | 81 | */ |
| @@ -147,20 +154,28 @@ | ||
| 147 | 154 | // load dependencies |
| 148 | 155 | if (!class_exists('TACVBO')) { |
| 149 | 156 | require_once VBO_SITE_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'tac.vikbooking.php'; |
| 150 | 157 | } |
| 158 | + | |
| 159 | + /** | |
| 160 | + * Set default nights transfer ratio. | |
| 161 | + * | |
| 162 | + * @since 1.18.5 (J) - 1.8.5 (WP) | |
| 163 | + */ | |
| 164 | + $this->setNightsTransfersRatio(); | |
| 151 | 165 | } |
| 152 | 166 | |
| 153 | 167 | /** |
| 154 | - * Returns the global object, either | |
| 155 | - * a new instance or the existing instance | |
| 156 | - * if the class was already instantiated. | |
| 168 | + * Returns the global object, either a new instance or the existing instance | |
| 169 | + * if the class was already instantiated, unless a new instance is requested. | |
| 170 | + * | |
| 171 | + * @param bool $anew True for forcing a new instance. | |
| 157 | 172 | * |
| 158 | - * @return self A new instance of the class. | |
| 173 | + * @return VikBookingAvailability | |
| 159 | 174 | */ |
| 160 | - public static function getInstance() | |
| 175 | + public static function getInstance($anew = false) | |
| 161 | 176 | { |
| 162 | - if (is_null(static::$instance)) { | |
| 177 | + if (is_null(static::$instance) || $anew) { | |
| 163 | 178 | static::$instance = new static(); |
| 164 | 179 | } |
| 165 | 180 | |
| 166 | 181 | return static::$instance; |
| @@ -246,9 +261,10 @@ | ||
| 246 | 261 | return 'Missing or invalid request options.'; |
| 247 | 262 | case 2: |
| 248 | 263 | return 'Invalid request authentication.'; |
| 249 | 264 | case 3: |
| 250 | - return JText::translate('VBO_AV_ECODE_3'); | |
| 265 | + $expl = JText::translate('VBO_AV_ECODE_3'); | |
| 266 | + return $expl != 'VBO_AV_ECODE_3' ? $expl : 'No rooms found for the given party.'; | |
| 251 | 267 | case 4: |
| 252 | 268 | return 'Not compliant with the booking restrictions.'; |
| 253 | 269 | case 5: |
| 254 | 270 | return 'Not compliant with the global closing dates.'; |
| @@ -254,25 +270,105 @@ | ||
| 254 | 270 | return 'Not compliant with the global closing dates.'; |
| 255 | 271 | case 6: |
| 256 | 272 | return 'No rates defined for the given length of stay.'; |
| 257 | 273 | case 7: |
| 258 | - return JText::translate('VBO_AV_ECODE_7'); | |
| 274 | + $expl = JText::translate('VBO_AV_ECODE_7'); | |
| 275 | + return $expl != 'VBO_AV_ECODE_7' ? $expl : 'No availability for the dates requested.'; | |
| 259 | 276 | case 8: |
| 260 | 277 | return 'No rooms available due to room or rate plan restrictions.'; |
| 261 | 278 | default: |
| 262 | 279 | return 'Unknown error code.'; |
| 263 | 280 | } |
| 264 | - | |
| 265 | 281 | } |
| 266 | 282 | |
| 267 | 283 | /** |
| 284 | + * Returns a list of room IDs from the given category IDs. | |
| 285 | + * | |
| 286 | + * @param array $category_ids List of category IDs to filter. | |
| 287 | + * | |
| 288 | + * @return array List of involved and unique room IDs. | |
| 289 | + * | |
| 290 | + * @since 1.17.6 (J) - 1.7.6 (WP) | |
| 291 | + */ | |
| 292 | + public function filterRoomCategories(array $category_ids) | |
| 293 | + { | |
| 294 | + $category_ids = array_filter(array_map('abs', array_map('intval', $category_ids))); | |
| 295 | + | |
| 296 | + if (!$category_ids) { | |
| 297 | + return []; | |
| 298 | + } | |
| 299 | + | |
| 300 | + $dbo = JFactory::getDbo(); | |
| 301 | + | |
| 302 | + $q = $dbo->getQuery(true) | |
| 303 | + ->select($dbo->qn('id')) | |
| 304 | + ->from($dbo->qn('#__vikbooking_rooms')); | |
| 305 | + | |
| 306 | + foreach ($category_ids as $cat_id) { | |
| 307 | + $q->where([ | |
| 308 | + $dbo->qn('idcat') . ' = ' . $dbo->q($cat_id . ';'), | |
| 309 | + $dbo->qn('idcat') . ' LIKE ' . $dbo->q($cat_id . ';%'), | |
| 310 | + $dbo->qn('idcat') . ' LIKE ' . $dbo->q('%;' . $cat_id . ';%'), | |
| 311 | + $dbo->qn('idcat') . ' LIKE ' . $dbo->q('%;' . $cat_id . ';'), | |
| 312 | + ], 'OR'); | |
| 313 | + } | |
| 314 | + | |
| 315 | + $dbo->setQuery($q); | |
| 316 | + | |
| 317 | + return array_values(array_unique(array_map('intval', $dbo->loadColumn()))); | |
| 318 | + } | |
| 319 | + | |
| 320 | + /** | |
| 321 | + * Loads a list of room categories. | |
| 322 | + * | |
| 323 | + * @param bool $public True for excluding the "private" categories. | |
| 324 | + * | |
| 325 | + * @return array The associative list of categories. | |
| 326 | + * | |
| 327 | + * @since 1.17.6 (J) - 1.7.6 (WP) | |
| 328 | + * @since 1.18.3 (J) - 1.8.3 (WP) added argument $public. | |
| 329 | + */ | |
| 330 | + public function loadRoomCategories($public = false) | |
| 331 | + { | |
| 332 | + $dbo = JFactory::getDbo(); | |
| 333 | + | |
| 334 | + $dbo->setQuery( | |
| 335 | + $dbo->getQuery(true) | |
| 336 | + ->select([ | |
| 337 | + $dbo->qn('id'), | |
| 338 | + $dbo->qn('name'), | |
| 339 | + ]) | |
| 340 | + ->from($dbo->qn('#__vikbooking_categories')) | |
| 341 | + ->order($dbo->qn('name') . ' ASC') | |
| 342 | + ); | |
| 343 | + | |
| 344 | + $categories = $dbo->loadAssocList(); | |
| 345 | + | |
| 346 | + if ($public) { | |
| 347 | + $categories = array_values(array_filter($categories, function($category) { | |
| 348 | + return substr((string) $category['name'], 0, 1) !== '_'; | |
| 349 | + })); | |
| 350 | + } | |
| 351 | + | |
| 352 | + return $categories; | |
| 353 | + } | |
| 354 | + | |
| 355 | + /** | |
| 268 | 356 | * Loads all rooms in VBO and maps them into an associative array. |
| 269 | 357 | * |
| 270 | - * @return array the associative list of rooms. | |
| 358 | + * @param array $ids optional list of IDs to load. | |
| 359 | + * @param int $max optional rooms limit to fetch. | |
| 360 | + * @param bool $anew true to avoid object caching. | |
| 361 | + * | |
| 362 | + * @return array the associative list of rooms. | |
| 363 | + * | |
| 364 | + * @since 1.16.10 (J) - 1.6.10 (WP) added arguments $ids, $max. | |
| 365 | + * @since 1.17.5 (J) - 1.7.5 (WP) added argument $anew. | |
| 366 | + * @since 1.18.6 (J) - 1.8.6 (WP) added support to negative IDs for category IDs. | |
| 271 | 367 | */ |
| 272 | - public function loadRooms() | |
| 368 | + public function loadRooms(array $ids = [], $max = 0, $anew = false) | |
| 273 | 369 | { |
| 274 | - if ($this->all_rooms) { | |
| 370 | + if ($this->all_rooms && !$anew) { | |
| 275 | 371 | // return previously cached array if available |
| 276 | 372 | return $this->all_rooms; |
| 277 | 373 | } |
| 278 | 374 | |
| @@ -277,16 +373,56 @@ | ||
| 277 | 373 | } |
| 278 | 374 | |
| 279 | 375 | $dbo = JFactory::getDbo(); |
| 280 | 376 | |
| 281 | - $q = "SELECT `id`, `name`, `img`, `idcat`, `avail`, `units`, `fromadult`, `toadult`, | |
| 282 | - `fromchild`, `tochild`, `totpeople`, `mintotpeople` FROM `#__vikbooking_rooms` | |
| 283 | - ORDER BY `avail` DESC, `name` ASC;"; | |
| 284 | - $dbo->setQuery($q); | |
| 377 | + $q = $dbo->getQuery(true) | |
| 378 | + ->select([ | |
| 379 | + $dbo->qn('id'), | |
| 380 | + $dbo->qn('name'), | |
| 381 | + $dbo->qn('img'), | |
| 382 | + $dbo->qn('idcat'), | |
| 383 | + $dbo->qn('avail'), | |
| 384 | + $dbo->qn('units'), | |
| 385 | + $dbo->qn('fromadult'), | |
| 386 | + $dbo->qn('toadult'), | |
| 387 | + $dbo->qn('fromchild'), | |
| 388 | + $dbo->qn('tochild'), | |
| 389 | + $dbo->qn('totpeople'), | |
| 390 | + $dbo->qn('mintotpeople'), | |
| 391 | + ]) | |
| 392 | + ->from($dbo->qn('#__vikbooking_rooms')); | |
| 393 | + | |
| 394 | + if ($ids) { | |
| 395 | + // cast all IDs to signed integers | |
| 396 | + $ids = array_map('intval', $ids); | |
| 397 | + | |
| 398 | + // normalize listing IDs to support (negative) category IDs | |
| 399 | + $categoryIds = array_filter($ids, function($id) { | |
| 400 | + return $id < 0; | |
| 401 | + }); | |
| 402 | + | |
| 403 | + if ($categoryIds) { | |
| 404 | + // filter out negative IDs | |
| 405 | + $ids = array_values(array_filter($ids, function($id) { | |
| 406 | + return $id > 0; | |
| 407 | + })); | |
| 408 | + | |
| 409 | + // include the listing IDs from category IDs, if any | |
| 410 | + $ids = array_values(array_unique(array_merge($ids, $this->filterRoomCategories($categoryIds)))); | |
| 411 | + } | |
| 412 | + | |
| 413 | + // set query clause | |
| 414 | + $q->where($dbo->qn('id') . ' IN (' . implode(', ', $ids) . ')'); | |
| 415 | + } | |
| 416 | + | |
| 417 | + $q->order($dbo->qn('avail') . ' DESC'); | |
| 418 | + $q->order($dbo->qn('name') . ' ASC'); | |
| 419 | + | |
| 420 | + $dbo->setQuery($q, 0, $max); | |
| 285 | 421 | $room_rows = $dbo->loadAssocList(); |
| 286 | 422 | |
| 287 | 423 | if (!$room_rows) { |
| 288 | - return $this->all_rooms; | |
| 424 | + return $anew ? [] : $this->all_rooms; | |
| 289 | 425 | } |
| 290 | 426 | |
| 291 | 427 | if ($this->isFrontBooking()) { |
| 292 | 428 | // apply translations on rooms |
| @@ -293,13 +429,19 @@ | ||
| 293 | 429 | $vbo_tn = VikBooking::getTranslator(); |
| 294 | 430 | $vbo_tn->translateContents($room_rows, '#__vikbooking_rooms'); |
| 295 | 431 | } |
| 296 | 432 | |
| 433 | + $assoc_rooms = []; | |
| 297 | 434 | foreach ($room_rows as $room) { |
| 298 | - $this->all_rooms[$room['id']] = $room; | |
| 435 | + $assoc_rooms[$room['id']] = $room; | |
| 299 | 436 | } |
| 300 | 437 | |
| 301 | - return $this->all_rooms; | |
| 438 | + if (!$anew) { | |
| 439 | + // cache room records | |
| 440 | + $this->all_rooms = $assoc_rooms; | |
| 441 | + } | |
| 442 | + | |
| 443 | + return $anew ? $assoc_rooms : $this->all_rooms; | |
| 302 | 444 | } |
| 303 | 445 | |
| 304 | 446 | /** |
| 305 | 447 | * Sets the current rooms as an associative array of information. The |
| @@ -325,9 +467,9 @@ | ||
| 325 | 467 | { |
| 326 | 468 | $rooms = $this->loadRooms(); |
| 327 | 469 | |
| 328 | 470 | foreach ($rooms as $k => $room) { |
| 329 | - if (!$room['avail']) { | |
| 471 | + if (!($room['avail'] ?? 1)) { | |
| 330 | 472 | unset($rooms[$k]); |
| 331 | 473 | } |
| 332 | 474 | } |
| 333 | 475 | |
| @@ -334,15 +476,56 @@ | ||
| 334 | 476 | return $rooms; |
| 335 | 477 | } |
| 336 | 478 | |
| 337 | 479 | /** |
| 480 | + * Finds a room (or multiple rooms) by name. | |
| 481 | + * | |
| 482 | + * @param string $name The room name to look for. | |
| 483 | + * @param bool $all True to return a list of matches. | |
| 484 | + * | |
| 485 | + * @return array | |
| 486 | + * | |
| 487 | + * @since 1.16.10 (J) - 1.6.10 (WP) | |
| 488 | + * @since 1.18.17 (J) - 1.8.17 (WP) added argument $all. | |
| 489 | + */ | |
| 490 | + public function getRoomByName(string $name, bool $all = false) | |
| 491 | + { | |
| 492 | + $dbo = JFactory::getDbo(); | |
| 493 | + | |
| 494 | + $q = $dbo->getQuery(true) | |
| 495 | + ->select('*') | |
| 496 | + ->from($dbo->qn('#__vikbooking_rooms')) | |
| 497 | + ->order($dbo->qn('avail') . ' DESC') | |
| 498 | + ->order($dbo->qn('name') . ' ASC'); | |
| 499 | + | |
| 500 | + foreach (array_filter(preg_split("/[\s\-_.,]+/", $name)) as $nm_part) { | |
| 501 | + $q->where($dbo->qn('name') . ' LIKE ' . $dbo->q("%{$nm_part}%")); | |
| 502 | + } | |
| 503 | + | |
| 504 | + if ($all) { | |
| 505 | + // match all records | |
| 506 | + $dbo->setQuery($q); | |
| 507 | + return $dbo->loadAssocList(); | |
| 508 | + } | |
| 509 | + | |
| 510 | + $dbo->setQuery($q, 0, 1); | |
| 511 | + $record = $dbo->loadAssoc(); | |
| 512 | + | |
| 513 | + return $record ?? []; | |
| 514 | + } | |
| 515 | + | |
| 516 | + /** | |
| 338 | 517 | * Loads all rate plans in VBO and maps them into an associative array. |
| 339 | 518 | * |
| 519 | + * @param bool $no_cache True to avoid internal caching. | |
| 520 | + * | |
| 340 | 521 | * @return array the associative list of rate plans. |
| 522 | + * | |
| 523 | + * @since 1.17.6 (J) - 1.7.6 (WP) added $no_cache argument. | |
| 341 | 524 | */ |
| 342 | - public function loadRatePlans() | |
| 525 | + public function loadRatePlans($no_cache = false) | |
| 343 | 526 | { |
| 344 | - if (count($this->all_rplans)) { | |
| 527 | + if (!$no_cache && $this->all_rplans) { | |
| 345 | 528 | // return previously cached array if available |
| 346 | 529 | return $this->all_rplans; |
| 347 | 530 | } |
| 348 | 531 | |
| @@ -348,27 +531,452 @@ | ||
| 348 | 531 | |
| 349 | 532 | $dbo = JFactory::getDbo(); |
| 350 | 533 | |
| 351 | 534 | $rate_plans = []; |
| 535 | + $derived_rplans = []; | |
| 352 | 536 | |
| 353 | 537 | $q = "SELECT * FROM `#__vikbooking_prices` ORDER BY `name` ASC;"; |
| 354 | 538 | $dbo->setQuery($q); |
| 355 | 539 | $rplan_rows = $dbo->loadAssocList(); |
| 356 | 540 | |
| 357 | - if ($rplan_rows) { | |
| 358 | - foreach ($rplan_rows as $rplan) { | |
| 359 | - $rate_plans[$rplan['id']] = $rplan; | |
| 541 | + foreach ($rplan_rows as $rplan) { | |
| 542 | + if (!empty($rplan['derived_id']) && !empty($rplan['derived_data'])) { | |
| 543 | + // decode the derived data information | |
| 544 | + $rplan['derived_data'] = json_decode($rplan['derived_data'], true); | |
| 545 | + // add rate plan ID | |
| 546 | + $derived_rplans[] = $rplan['id']; | |
| 360 | 547 | } |
| 361 | - // sort rate plans | |
| 362 | - $rate_plans = VikBooking::sortRatePlans($rate_plans, true); | |
| 548 | + $rate_plans[$rplan['id']] = $rplan; | |
| 363 | 549 | } |
| 364 | 550 | |
| 365 | - $this->all_rplans = $rate_plans; | |
| 551 | + // add the information about the parent rate plans, if any | |
| 552 | + foreach ($rate_plans as $rplan_id => $rplan_data) { | |
| 553 | + if (in_array($rplan_id, $derived_rplans) && isset($rate_plans[$rplan_data['derived_id']])) { | |
| 554 | + // set parent rate details | |
| 555 | + $rate_plans[$rplan_id]['parent_rate_id'] = $rate_plans[$rplan_data['derived_id']]['id']; | |
| 556 | + $rate_plans[$rplan_id]['parent_rate_name'] = $rate_plans[$rplan_data['derived_id']]['name']; | |
| 557 | + } | |
| 558 | + } | |
| 366 | 559 | |
| 560 | + // sort rate plans | |
| 561 | + $rate_plans = VikBooking::sortRatePlans($rate_plans, true); | |
| 562 | + | |
| 563 | + if (!$no_cache) { | |
| 564 | + $this->all_rplans = $rate_plans; | |
| 565 | + } | |
| 566 | + | |
| 367 | 567 | return $rate_plans; |
| 368 | 568 | } |
| 369 | 569 | |
| 370 | 570 | /** |
| 571 | + * Returns a list of rate plans derived from the given parent rate plan ID. | |
| 572 | + * | |
| 573 | + * @param int $parent_rate_id The parent rate plan ID. | |
| 574 | + * @param ?array $all_rate_plans Optional cached rate plans list. | |
| 575 | + * | |
| 576 | + * @return array | |
| 577 | + * | |
| 578 | + * @since 1.16.10 (J) - 1.6.10 (WP) | |
| 579 | + * @since 1.18.5 (J) - 1.8.5 (WP) added argument $all_rate_plans. | |
| 580 | + */ | |
| 581 | + public function getDerivedRatePlans(int $parent_rate_id, ?array $all_rate_plans = null) | |
| 582 | + { | |
| 583 | + $derived_rplans = []; | |
| 584 | + | |
| 585 | + foreach (($all_rate_plans ?: $this->loadRatePlans()) as $rp_id => $rplan) { | |
| 586 | + if ($rplan['derived_id'] && $rplan['derived_id'] == $parent_rate_id && $rplan['derived_data']) { | |
| 587 | + // this rate plan is derived from the given parent rate plan | |
| 588 | + $derived_rplans[] = $rplan; | |
| 589 | + } | |
| 590 | + } | |
| 591 | + | |
| 592 | + return $derived_rplans; | |
| 593 | + } | |
| 594 | + | |
| 595 | + /** | |
| 596 | + * Returns the orphan dates for the given or set rooms and dates. | |
| 597 | + * | |
| 598 | + * @param array $options List of fetching options. | |
| 599 | + * | |
| 600 | + * @return array Associative list of rooms orphan dates. | |
| 601 | + * | |
| 602 | + * @since 1.17.7 (J) - 1.7.7 (WP) | |
| 603 | + */ | |
| 604 | + public function getOrphanDates(array $options = []) | |
| 605 | + { | |
| 606 | + // flag to indicate whether room-level restrictions were loaded | |
| 607 | + $use_room_level_restr = false; | |
| 608 | + | |
| 609 | + // load and set rooms and restrictions | |
| 610 | + if ($options['room_ids'] ?? []) { | |
| 611 | + // force the requested rooms to be loaded | |
| 612 | + $this->loadRooms((array) $options['room_ids']); | |
| 613 | + | |
| 614 | + // load restrictions with room filters | |
| 615 | + $all_restrictions = VikBooking::loadRestrictions(true, (array) $options['room_ids']); | |
| 616 | + | |
| 617 | + // turn flag on for room-level restrictions being used | |
| 618 | + $use_room_level_restr = true; | |
| 619 | + } else { | |
| 620 | + // load restrictions with no filters | |
| 621 | + $all_restrictions = VikBooking::loadRestrictions(false); | |
| 622 | + } | |
| 623 | + | |
| 624 | + // default minimum stay | |
| 625 | + $def_min_stay = VikBooking::getDefaultNightsCalendar(); | |
| 626 | + | |
| 627 | + // figure out dates range | |
| 628 | + if (($current_stay_dates = $this->getStayDates()) && !($options['from_date'] ?? null)) { | |
| 629 | + // populate current stay dates | |
| 630 | + $options['from_date'] = $current_stay_dates[0]; | |
| 631 | + $options['to_date'] = $current_stay_dates[1]; | |
| 632 | + } else { | |
| 633 | + // check given fetching options | |
| 634 | + if (!($options['from_date'] ?? null)) { | |
| 635 | + // default to today's date | |
| 636 | + $options['from_date'] = date('Y-m-d'); | |
| 637 | + } | |
| 638 | + | |
| 639 | + if (!($options['to_date'] ?? null)) { | |
| 640 | + // default to next week's date | |
| 641 | + $options['to_date'] = date('Y-m-d', strtotime('+1 week')); | |
| 642 | + } | |
| 643 | + } | |
| 644 | + | |
| 645 | + // calculate the highest minimum stay for a better accuracy | |
| 646 | + $all_min_los = [($def_min_stay > 1 ? $def_min_stay : 0)]; | |
| 647 | + foreach ($all_restrictions as $index => $restrs) { | |
| 648 | + $all_min_los = array_merge($all_min_los, array_column($restrs, 'minlos')); | |
| 649 | + } | |
| 650 | + $max_min_los = max(array_map('intval', $all_min_los)); | |
| 651 | + | |
| 652 | + if (!$max_min_los) { | |
| 653 | + // no minimum stay restrictions to apply, hence no orphan dates | |
| 654 | + return []; | |
| 655 | + } | |
| 656 | + | |
| 657 | + // calculate past limit timestamp for today at midnight | |
| 658 | + $lim_past_ts = strtotime(date('Y-m-d')); | |
| 659 | + | |
| 660 | + // always fetch a wider availability window for better accuracy | |
| 661 | + $fetch_from_date = date('Y-m-d', strtotime("-{$max_min_los} days", strtotime($options['from_date']))); | |
| 662 | + $fetch_to_date = date('Y-m-d', strtotime("+{$max_min_los} days", strtotime($options['to_date']))); | |
| 663 | + if (strtotime($fetch_from_date) < $lim_past_ts) { | |
| 664 | + // start fetching the inventory from today's date (past dates not accepted) | |
| 665 | + $fetch_from_date = date('Y-m-d'); | |
| 666 | + } | |
| 667 | + | |
| 668 | + // always set stay dates | |
| 669 | + $this->setStayDates($fetch_from_date, $fetch_to_date); | |
| 670 | + | |
| 671 | + // obtain the availability inventory by ignoring restrictions | |
| 672 | + $ari = $this->getInventory(false); | |
| 673 | + | |
| 674 | + if (!$ari) { | |
| 675 | + return []; | |
| 676 | + } | |
| 677 | + | |
| 678 | + // build orphan dates pool | |
| 679 | + $orphans_pool = []; | |
| 680 | + | |
| 681 | + // UTC timezone | |
| 682 | + $utc_tz = new DateTimezone('UTC'); | |
| 683 | + | |
| 684 | + // get date bounds | |
| 685 | + $from_bound = new DateTime($options['from_date'], $utc_tz); | |
| 686 | + $to_bound = new DateTime($options['to_date'], $utc_tz); | |
| 687 | + | |
| 688 | + // build iterable dates interval (period) | |
| 689 | + $date_range = new DatePeriod( | |
| 690 | + // start date included by default in the result set | |
| 691 | + $from_bound, | |
| 692 | + // interval between recurrences within the period | |
| 693 | + new DateInterval('P1D'), | |
| 694 | + // end date excluded by default from the result set | |
| 695 | + $to_bound->modify('+1 day') | |
| 696 | + ); | |
| 697 | + | |
| 698 | + // scan rooms availability inventory | |
| 699 | + foreach ($ari as $idroom => $room_ari) { | |
| 700 | + // load proper room-level restrictions if not done already | |
| 701 | + $all_restrictions = $use_room_level_restr ? $all_restrictions : VikBooking::loadRestrictions(true, [$idroom]); | |
| 702 | + | |
| 703 | + // iterate the dates interval | |
| 704 | + foreach ($date_range as $dt) { | |
| 705 | + // calculate date values | |
| 706 | + $day_key = $dt->format('Y-m-d'); | |
| 707 | + $day_now_ts = strtotime($day_key); | |
| 708 | + $day_after_ts = strtotime('+1 day', $day_now_ts); | |
| 709 | + | |
| 710 | + // parse room restrictions for the current inventory day and room to get the minimum stay | |
| 711 | + $restr = VikBooking::parseSeasonRestrictions($day_now_ts, $day_after_ts, 1, $all_restrictions); | |
| 712 | + $minimum_stay = (int) ($restr['minlos'] ?? $def_min_stay); | |
| 713 | + | |
| 714 | + if ($minimum_stay < 2) { | |
| 715 | + // no real minimum stay restriction detected for this day | |
| 716 | + continue; | |
| 717 | + } | |
| 718 | + | |
| 719 | + // scan rooms availability | |
| 720 | + foreach ($room_ari['inventory'] as $keypoint => $inventory) { | |
| 721 | + if ($inventory['day'] != $day_key) { | |
| 722 | + // not the date-key point we are looking for | |
| 723 | + continue; | |
| 724 | + } | |
| 725 | + | |
| 726 | + // tell whether the listing is available on the current date-key point | |
| 727 | + $is_available = (bool) ($inventory['units_to_sell'] ?? $inventory['available'] ?? 0); | |
| 728 | + if (!$is_available) { | |
| 729 | + // this day is fully booked, hence no orphan dates | |
| 730 | + continue 2; | |
| 731 | + } | |
| 732 | + | |
| 733 | + // scan the availability for the next minimum stay days from the current day-key point | |
| 734 | + for ($d = 0; $d < $minimum_stay; $d++) { | |
| 735 | + // build next date-key point value (start from current day-key point as it counts as one available night of stay) | |
| 736 | + $next_keypoint = $keypoint + $d; | |
| 737 | + | |
| 738 | + if (!isset($room_ari['inventory'][$next_keypoint])) { | |
| 739 | + // no more data available | |
| 740 | + break; | |
| 741 | + } | |
| 742 | + | |
| 743 | + // tell whether the listing will be available on this future date-key point (0th day will always be available) | |
| 744 | + $will_be_available = (bool) ($room_ari['inventory'][$next_keypoint]['units_to_sell'] ?? $room_ari['inventory'][$next_keypoint]['available'] ?? 0); | |
| 745 | + | |
| 746 | + if (!$will_be_available) { | |
| 747 | + /** | |
| 748 | + * Probable orphan date found for bookings on days ahead. Ensure this | |
| 749 | + * is truly an orphan date by checking the days before. If staying | |
| 750 | + * on this (available) night is allowed, then this should not be | |
| 751 | + * considered as an orphan date, even though arriving is not allowed. | |
| 752 | + */ | |
| 753 | + if (!($options['orphans_checkin'] ?? false)) { | |
| 754 | + // check previous dates before saying it's an orphan date | |
| 755 | + for ($backd = ($keypoint - 1), $distance = 1; $backd >= 0; $backd--, $distance++) { | |
| 756 | + if (!isset($room_ari['inventory'][$backd])) { | |
| 757 | + // nothing to do, let it be an orphan date | |
| 758 | + break; | |
| 759 | + } | |
| 760 | + | |
| 761 | + // tell whether the listing was available on this previous date-key point | |
| 762 | + $was_available = (bool) ($room_ari['inventory'][$backd]['units_to_sell'] ?? $room_ari['inventory'][$backd]['available'] ?? 0); | |
| 763 | + | |
| 764 | + if (!$was_available) { | |
| 765 | + // nothing to do, let it be an orphan date | |
| 766 | + break; | |
| 767 | + } | |
| 768 | + | |
| 769 | + // calculate past date values | |
| 770 | + $past_day_now_ts = strtotime($room_ari['inventory'][$backd]['day']); | |
| 771 | + $past_day_after_ts = strtotime('+1 day', $past_day_now_ts); | |
| 772 | + | |
| 773 | + // parse room restrictions for the current past day and room to get the minimum stay | |
| 774 | + $restr = VikBooking::parseSeasonRestrictions($past_day_now_ts, $past_day_after_ts, 1, $all_restrictions); | |
| 775 | + $minimum_stay = (int) ($restr['minlos'] ?? $def_min_stay); | |
| 776 | + | |
| 777 | + if ($minimum_stay <= $distance) { | |
| 778 | + // free day with a minimum stay that allows to stay on the presumed orphan date | |
| 779 | + // break the cycles for the presumed orphan date-key point, because it's not truly an orphan | |
| 780 | + break 3; | |
| 781 | + } | |
| 782 | + } | |
| 783 | + } | |
| 784 | + | |
| 785 | + // orphan date found | |
| 786 | + if (!isset($orphans_pool[$idroom])) { | |
| 787 | + // start container | |
| 788 | + $orphans_pool[$idroom] = [ | |
| 789 | + 'room_name' => $room_ari['room_name'], | |
| 790 | + 'orphans' => [], | |
| 791 | + ]; | |
| 792 | + } | |
| 793 | + | |
| 794 | + // push orphan date information | |
| 795 | + $orphans_pool[$idroom]['orphans'][] = [ | |
| 796 | + 'day' => $day_key, | |
| 797 | + 'current_min_stay_nights' => $minimum_stay, | |
| 798 | + 'max_min_stay_nights_allowed' => $d, | |
| 799 | + ]; | |
| 800 | + | |
| 801 | + // break the cycles for this date-key point | |
| 802 | + break 2; | |
| 803 | + } | |
| 804 | + } | |
| 805 | + } | |
| 806 | + } | |
| 807 | + } | |
| 808 | + | |
| 809 | + // return the associative list of room orphan dates found, if any | |
| 810 | + return $orphans_pool; | |
| 811 | + } | |
| 812 | + | |
| 813 | + /** | |
| 814 | + * Returns the inventory for the rooms and dates set. | |
| 815 | + * | |
| 816 | + * @param bool $restrictions Whether to include booking restrictions data. | |
| 817 | + * | |
| 818 | + * @return array Associative list of rooms availability inventory. | |
| 819 | + * | |
| 820 | + * @since 1.16.10 (J) - 1.6.10 (WP) | |
| 821 | + */ | |
| 822 | + public function getInventory($restrictions = false) | |
| 823 | + { | |
| 824 | + $stay_ts = $this->getStayDates(true); | |
| 825 | + | |
| 826 | + if (!$stay_ts) { | |
| 827 | + $this->setError('No dates provided'); | |
| 828 | + return []; | |
| 829 | + } | |
| 830 | + | |
| 831 | + if ($stay_ts[0] > $stay_ts[1]) { | |
| 832 | + $this->setError('Invalid dates provided'); | |
| 833 | + return []; | |
| 834 | + } | |
| 835 | + | |
| 836 | + $info_from = getdate($stay_ts[0]); | |
| 837 | + | |
| 838 | + $room_ids = array_column($this->loadRooms(), 'id'); | |
| 839 | + $room_names = array_column($this->loadRooms(), 'name'); | |
| 840 | + $room_units = array_column($this->loadRooms(), 'units'); | |
| 841 | + | |
| 842 | + if (!$room_ids) { | |
| 843 | + $this->setError('No rooms provided'); | |
| 844 | + return []; | |
| 845 | + } | |
| 846 | + | |
| 847 | + // load busy records | |
| 848 | + $busy_records = VikBooking::loadBusyRecords($room_ids, $stay_ts[0], $stay_ts[1]); | |
| 849 | + | |
| 850 | + // room restrictions associative list | |
| 851 | + $room_restrictions = []; | |
| 852 | + | |
| 853 | + // global minimum stay restriction | |
| 854 | + $glob_minlos = VikBooking::getDefaultNightsCalendar(); | |
| 855 | + $glob_minlos = $glob_minlos < 1 ? 1 : (int) $glob_minlos; | |
| 856 | + | |
| 857 | + // inventory pool | |
| 858 | + $ari = []; | |
| 859 | + foreach ($room_ids as $room_index => $room_id) { | |
| 860 | + $ari[$room_id] = [ | |
| 861 | + 'room_name' => $room_names[$room_index], | |
| 862 | + 'inventory' => [], | |
| 863 | + ]; | |
| 864 | + } | |
| 865 | + | |
| 866 | + // loop through all dates in the range | |
| 867 | + while ($info_from[0] <= $stay_ts[1]) { | |
| 868 | + // build day after timestamp | |
| 869 | + $day_after_ts = mktime(0, 0, 0, $info_from['mon'], $info_from['mday'] + 1, $info_from['year']); | |
| 870 | + | |
| 871 | + // scan the units booked for each requested room | |
| 872 | + foreach ($room_ids as $room_index => $room_id) { | |
| 873 | + $units_booked = 0; | |
| 874 | + // scan all the occupied records of the current room | |
| 875 | + foreach (($busy_records[$room_id] ?? []) as $busy) { | |
| 876 | + $info_in = getdate($busy['checkin']); | |
| 877 | + $info_out = getdate($busy['checkout']); | |
| 878 | + $in_ts = mktime(0, 0, 0, $info_in['mon'], $info_in['mday'], $info_in['year']); | |
| 879 | + $out_ts = mktime(0, 0, 0, $info_out['mon'], $info_out['mday'], $info_out['year']); | |
| 880 | + if ($info_from[0] >= $in_ts && $info_from[0] < $out_ts) { | |
| 881 | + // increase room units booked | |
| 882 | + $units_booked++; | |
| 883 | + } | |
| 884 | + } | |
| 885 | + | |
| 886 | + // count units left | |
| 887 | + $units_left = $units_booked >= $room_units[$room_index] ? 0 : ($room_units[$room_index] - $units_booked); | |
| 888 | + | |
| 889 | + // set room inventory date | |
| 890 | + $inventory_day = [ | |
| 891 | + 'day' => date('Y-m-d', $info_from[0]), | |
| 892 | + ]; | |
| 893 | + | |
| 894 | + if ($room_units[$room_index] == 1) { | |
| 895 | + // single-unit listing inventory structure | |
| 896 | + $inventory_day['available'] = (bool) $units_left; | |
| 897 | + } else { | |
| 898 | + // multi-unit room-type inventory structure | |
| 899 | + $inventory_day['units_booked'] = $units_booked; | |
| 900 | + $inventory_day['units_to_sell'] = $units_left; | |
| 901 | + } | |
| 902 | + | |
| 903 | + // check for room-level restrictions | |
| 904 | + if ($restrictions) { | |
| 905 | + if (!isset($room_restrictions[$room_id])) { | |
| 906 | + // load room restrictions only once | |
| 907 | + $room_restrictions[$room_id] = VikBooking::loadRestrictions(true, [$room_id]); | |
| 908 | + } | |
| 909 | + | |
| 910 | + // parse room restrictions for the current inventory day | |
| 911 | + $restr = VikBooking::parseSeasonRestrictions($info_from[0], $day_after_ts, 1, $room_restrictions[$room_id]); | |
| 912 | + if ($restr) { | |
| 913 | + $inventory_day['restrictions'] = [ | |
| 914 | + 'min_los' => (int) $restr['minlos'], | |
| 915 | + ]; | |
| 916 | + if (($restr['maxlos'] ?? 0)) { | |
| 917 | + $inventory_day['restrictions']['max_los'] = (int) $restr['maxlos']; | |
| 918 | + } | |
| 919 | + if (!empty($restr['cta'])) { | |
| 920 | + $inventory_day['restrictions']['closed_to_arrival'] = array_map(function($wday) { | |
| 921 | + switch ($wday) { | |
| 922 | + case '1': | |
| 923 | + return 'Monday'; | |
| 924 | + case '2': | |
| 925 | + return 'Tuesday'; | |
| 926 | + case '3': | |
| 927 | + return 'Wednesday'; | |
| 928 | + case '4': | |
| 929 | + return 'Thursday'; | |
| 930 | + case '5': | |
| 931 | + return 'Friday'; | |
| 932 | + case '6': | |
| 933 | + return 'Saturday'; | |
| 934 | + default: | |
| 935 | + return 'Sunday'; | |
| 936 | + } | |
| 937 | + }, $restr['cta']); | |
| 938 | + } | |
| 939 | + if (!empty($restr['ctd'])) { | |
| 940 | + $inventory_day['restrictions']['closed_to_departure'] = array_map(function($wday) { | |
| 941 | + switch ($wday) { | |
| 942 | + case '1': | |
| 943 | + return 'Monday'; | |
| 944 | + case '2': | |
| 945 | + return 'Tuesday'; | |
| 946 | + case '3': | |
| 947 | + return 'Wednesday'; | |
| 948 | + case '4': | |
| 949 | + return 'Thursday'; | |
| 950 | + case '5': | |
| 951 | + return 'Friday'; | |
| 952 | + case '6': | |
| 953 | + return 'Saturday'; | |
| 954 | + default: | |
| 955 | + return 'Sunday'; | |
| 956 | + } | |
| 957 | + }, $restr['ctd']); | |
| 958 | + } | |
| 959 | + } else { | |
| 960 | + // set the global minimum stay restriction | |
| 961 | + $inventory_day['restrictions'] = [ | |
| 962 | + 'min_los' => $glob_minlos, | |
| 963 | + ]; | |
| 964 | + } | |
| 965 | + } | |
| 966 | + | |
| 967 | + // push room-day inventory | |
| 968 | + $ari[$room_id]['inventory'][] = $inventory_day; | |
| 969 | + } | |
| 970 | + | |
| 971 | + // go to next date | |
| 972 | + $info_from = getdate($day_after_ts); | |
| 973 | + } | |
| 974 | + | |
| 975 | + return $ari; | |
| 976 | + } | |
| 977 | + | |
| 978 | + /** | |
| 371 | 979 | * Gets the available room rates for the specified dates, party and rooms. |
| 372 | 980 | * |
| 373 | 981 | * @param array $params optional list of options to be forced for TACVBO. |
| 374 | 982 | * |
| @@ -375,23 +983,29 @@ | ||
| 375 | 983 | * @return mixed boolean false in case of errors or array result of TACVBO class. |
| 376 | 984 | */ |
| 377 | 985 | public function getRates($params = []) |
| 378 | 986 | { |
| 379 | - if (!count($this->stay_dates)) { | |
| 987 | + // reset errors to their initial values | |
| 988 | + $this->error = ''; | |
| 989 | + $this->errorCode = 0; | |
| 990 | + | |
| 991 | + if (!$this->stay_dates) { | |
| 380 | 992 | $this->setError('No dates provided'); |
| 381 | 993 | return false; |
| 382 | 994 | } |
| 383 | 995 | |
| 384 | - if (!count($this->room_parties)) { | |
| 996 | + if (!$this->room_parties) { | |
| 385 | 997 | $this->setError('No room party provided'); |
| 386 | 998 | return false; |
| 387 | 999 | } |
| 388 | 1000 | |
| 389 | 1001 | // count injected rooms, if any |
| 390 | - $tot_rooms = count($this->room_ids); | |
| 1002 | + $tot_rooms = (int) (($params['rooms_count'] ?? 0) ?: count($this->room_ids)); | |
| 391 | 1003 | |
| 392 | 1004 | // build options array for TACVBO |
| 393 | 1005 | $options = [ |
| 1006 | + 'hash' => md5('vbo.e4j.vbo'), | |
| 1007 | + 'req_type' => 'hotel_availability', | |
| 394 | 1008 | 'start_date' => $this->stay_dates[0], |
| 395 | 1009 | 'end_date' => $this->stay_dates[1], |
| 396 | 1010 | 'nights' => $this->countNightsOfStay(), |
| 397 | 1011 | 'num_rooms' => ($tot_rooms > 0 ? $tot_rooms : 1), |
| @@ -397,8 +1011,9 @@ | ||
| 397 | 1011 | 'num_rooms' => ($tot_rooms > 0 ? $tot_rooms : 1), |
| 398 | 1012 | 'adults' => [$this->getPartyGuests('adults', 0)], |
| 399 | 1013 | 'children' => [$this->getPartyGuests('children', 0)], |
| 400 | 1014 | 'only_rates' => 1, |
| 1015 | + 'wtax' => $params['wtax'] ?? null, | |
| 401 | 1016 | ]; |
| 402 | 1017 | |
| 403 | 1018 | if ($tot_rooms > 1 && count($this->room_parties) == $tot_rooms) { |
| 404 | 1019 | // re-build list of adults and children |
| @@ -409,8 +1024,29 @@ | ||
| 409 | 1024 | $options['children'][] = $this->getPartyGuests('children', $i); |
| 410 | 1025 | } |
| 411 | 1026 | } |
| 412 | 1027 | |
| 1028 | + // check for implicit settings | |
| 1029 | + if (!empty($params['max_rooms_limit'])) { | |
| 1030 | + // set rooms maximum limit | |
| 1031 | + TACVBO::$maxRoomsLimit = (int) $params['max_rooms_limit']; | |
| 1032 | + // unset implicit setting | |
| 1033 | + unset($params['max_rooms_limit']); | |
| 1034 | + } else { | |
| 1035 | + // reset to initial value for subsequent calls | |
| 1036 | + TACVBO::$maxRoomsLimit = 0; | |
| 1037 | + } | |
| 1038 | + | |
| 1039 | + if (is_array(($params['forced_room_ids'] ?? null))) { | |
| 1040 | + // set allowed room IDs | |
| 1041 | + TACVBO::$forcedRoomIds = $params['forced_room_ids']; | |
| 1042 | + // unset implicit setting | |
| 1043 | + unset($params['forced_room_ids']); | |
| 1044 | + } else { | |
| 1045 | + // reset to initial value for subsequent calls | |
| 1046 | + TACVBO::$forcedRoomIds = []; | |
| 1047 | + } | |
| 1048 | + | |
| 413 | 1049 | // merge default options with params, if any |
| 414 | 1050 | $options = array_merge($options, $params); |
| 415 | 1051 | |
| 416 | 1052 | // invoke TACVBO class by injecting the options |
| @@ -415,8 +1051,9 @@ | ||
| 415 | 1051 | |
| 416 | 1052 | // invoke TACVBO class by injecting the options |
| 417 | 1053 | TACVBO::$getArray = true; |
| 418 | 1054 | TACVBO::$ignoreRestrictions = $this->ignore_restrictions; |
| 1055 | + TACVBO::$ignoreAvailability = $this->ignore_availability; | |
| 419 | 1056 | $website_rates = TACVBO::tac_av_l($options); |
| 420 | 1057 | |
| 421 | 1058 | // store the error code occurred (if any) |
| 422 | 1059 | $this->errorCode = TACVBO::getErrorCode(); |
| @@ -440,9 +1077,9 @@ | ||
| 440 | 1077 | } |
| 441 | 1078 | |
| 442 | 1079 | // optional filter by room IDs will be applied on this flow |
| 443 | 1080 | $found_rids = array_keys($website_rates); |
| 444 | - $unwanted_rids = $tot_rooms ? array_diff($found_rids, $this->room_ids) : []; | |
| 1081 | + $unwanted_rids = $this->room_ids ? array_diff($found_rids, $this->room_ids) : []; | |
| 445 | 1082 | foreach ($unwanted_rids as $rid) { |
| 446 | 1083 | unset($website_rates[$rid]); |
| 447 | 1084 | } |
| 448 | 1085 | |
| @@ -449,8 +1086,20 @@ | ||
| 449 | 1086 | return $website_rates; |
| 450 | 1087 | } |
| 451 | 1088 | |
| 452 | 1089 | /** |
| 1090 | + * Returns the fully booked linear array of room IDs. | |
| 1091 | + * | |
| 1092 | + * @return array | |
| 1093 | + * | |
| 1094 | + * @since 1.18.8 (J) - 1.8.8 (WP) | |
| 1095 | + */ | |
| 1096 | + public function getFullyBooked() | |
| 1097 | + { | |
| 1098 | + return (array) $this->fully_booked; | |
| 1099 | + } | |
| 1100 | + | |
| 1101 | + /** | |
| 453 | 1102 | * Finds the available suggestions in case of no availability previously occurred |
| 454 | 1103 | * while getting the rates. This method should be called after getRates() so that |
| 455 | 1104 | * a valid errorCode to be analized will be available, unless code is forced. |
| 456 | 1105 | * Suggestions can include closest booking dates and/or different room-guest parties. |
| @@ -509,15 +1158,15 @@ | ||
| 509 | 1158 | * we build a list of available solutions for booking split stays on the same dates. The |
| 510 | 1159 | * visibility should be public so that other Views could use just this method. |
| 511 | 1160 | * |
| 512 | 1161 | * @param array $room_ids list of unavailable, yet compatible, room IDs. |
| 513 | - * @param array $busy_list optional list of busy records for the involved dates. | |
| 1162 | + * @param ?array $busy_list optional list of busy records for the involved dates. | |
| 514 | 1163 | * |
| 515 | 1164 | * @return array associative list of available split stays, or empty array. |
| 516 | 1165 | * |
| 517 | 1166 | * @since 1.16.0 (J) - 1.6.0 (WP) |
| 518 | 1167 | */ |
| 519 | - public function findSplitStays(array $room_ids = [], array $busy_list = null) | |
| 1168 | + public function findSplitStays(array $room_ids = [], ?array $busy_list = null) | |
| 520 | 1169 | { |
| 521 | 1170 | if (!$room_ids) { |
| 522 | 1171 | return []; |
| 523 | 1172 | } |
| @@ -618,15 +1267,15 @@ | ||
| 618 | 1267 | } |
| 619 | 1268 | } |
| 620 | 1269 | } |
| 621 | 1270 | |
| 622 | - if (!count($avroom_nights)) { | |
| 1271 | + if (!$avroom_nights) { | |
| 623 | 1272 | // no rooms available at all, there's no way to do anything |
| 624 | 1273 | return []; |
| 625 | 1274 | } |
| 626 | 1275 | |
| 627 | 1276 | // make sure all nights requested can be satisfied by at least one room |
| 628 | - foreach ($groupdays as $gday) { | |
| 1277 | + foreach ($groupdays as $gday_index => $gday) { | |
| 629 | 1278 | $day_key = date('Y-m-d', $gday); |
| 630 | 1279 | $day_av = false; |
| 631 | 1280 | foreach ($avroom_nights as $rid => $av_nights) { |
| 632 | 1281 | if (in_array($day_key, $av_nights)) { |
| @@ -634,10 +1283,11 @@ | ||
| 634 | 1283 | $day_av = true; |
| 635 | 1284 | break; |
| 636 | 1285 | } |
| 637 | 1286 | } |
| 638 | - if (!$day_av) { | |
| 639 | - // this night is not available in any room, unable to proceed | |
| 1287 | + // ensure there's availability for this day, or permit if it's the check-out day | |
| 1288 | + if (!$day_av && !($gday_index === (count($groupdays) - 1) && $this->inonout_allowed)) { | |
| 1289 | + // this night of stay is not available in any room, unable to proceed | |
| 640 | 1290 | return []; |
| 641 | 1291 | } |
| 642 | 1292 | } |
| 643 | 1293 | |
| @@ -835,9 +1485,9 @@ | ||
| 835 | 1485 | } |
| 836 | 1486 | |
| 837 | 1487 | // apply nights/transfers ratio limit (unless disabled) |
| 838 | 1488 | $nights_transfers_ratio = $this->getNightsTransfersRatio(); |
| 839 | - if ($nights_transfers_ratio > 0 && $nights_transfers_ratio < 100) { | |
| 1489 | + if ($nights_transfers_ratio > 0 && $nights_transfers_ratio <= 100) { | |
| 840 | 1490 | // count and apply limits |
| 841 | 1491 | foreach ($split_stay_sols as $k => $split_stay_sol) { |
| 842 | 1492 | // count nights and transfers |
| 843 | 1493 | $split_stay_transfers = count($split_stay_sol) - 1; |
| @@ -852,11 +1502,14 @@ | ||
| 852 | 1502 | unset($split_stay_sols[$k]); |
| 853 | 1503 | $altered_sols = true; |
| 854 | 1504 | } |
| 855 | 1505 | } |
| 1506 | + } else { | |
| 1507 | + // split stays have been disabled | |
| 1508 | + $split_stay_sols = []; | |
| 856 | 1509 | } |
| 857 | 1510 | |
| 858 | - if ($altered_sols && count($split_stay_sols)) { | |
| 1511 | + if ($altered_sols && $split_stay_sols) { | |
| 859 | 1512 | // restore the array keys |
| 860 | 1513 | $split_stay_sols = array_values($split_stay_sols); |
| 861 | 1514 | } |
| 862 | 1515 | |
| @@ -1727,13 +2380,13 @@ | ||
| 1727 | 2380 | return $this; |
| 1728 | 2381 | } |
| 1729 | 2382 | |
| 1730 | 2383 | /** |
| 1731 | - * Returns the current stay date or timestamps. | |
| 2384 | + * Returns the current stay dates or timestamps. | |
| 1732 | 2385 | * |
| 1733 | 2386 | * @param bool $ts whether to get the date timestamps. |
| 1734 | 2387 | * |
| 1735 | - * @return array the current stay date timestamps. | |
| 2388 | + * @return array the current stay dates or timestamps. | |
| 1736 | 2389 | */ |
| 1737 | 2390 | public function getStayDates($ts = false) |
| 1738 | 2391 | { |
| 1739 | 2392 | return $ts ? $this->stay_ts : $this->stay_dates; |
| @@ -1790,8 +2443,26 @@ | ||
| 1790 | 2443 | return $this->ignore_restrictions; |
| 1791 | 2444 | } |
| 1792 | 2445 | |
| 1793 | 2446 | /** |
| 2447 | + * Sets and returns the flag to ignore the rooms availability. | |
| 2448 | + * | |
| 2449 | + * @param bool $set the boolean status to set. | |
| 2450 | + * | |
| 2451 | + * @return bool the current ignore status. | |
| 2452 | + * | |
| 2453 | + * @since 1.16.10 (J) - 1.6.10 (WP) | |
| 2454 | + */ | |
| 2455 | + public function ignoreAvailability($set = null) | |
| 2456 | + { | |
| 2457 | + if (is_bool($set)) { | |
| 2458 | + $this->ignore_availability = $set; | |
| 2459 | + } | |
| 2460 | + | |
| 2461 | + return $this->ignore_availability; | |
| 2462 | + } | |
| 2463 | + | |
| 2464 | + /** | |
| 1794 | 2465 | * Takes the first payment ID "string", if available. |
| 1795 | 2466 | * |
| 1796 | 2467 | * @return string|null |
| 1797 | 2468 | */ |
| @@ -1830,23 +2501,28 @@ | ||
| 1830 | 2501 | * @since 1.16.0 (J) - 1.6.0 (WP) |
| 1831 | 2502 | */ |
| 1832 | 2503 | public function getDefaultNightsTransfersRatio() |
| 1833 | 2504 | { |
| 1834 | - $config = VBOFactory::getConfig(); | |
| 2505 | + static $transferRatio = null; | |
| 1835 | 2506 | |
| 1836 | - return (int)$config->get('split_stay_ratio', 50); | |
| 2507 | + if ($transferRatio === null) { | |
| 2508 | + $config = VBOFactory::getConfig(); | |
| 2509 | + $transferRatio = (int) $config->get('split_stay_ratio', 50); | |
| 2510 | + } | |
| 2511 | + | |
| 2512 | + return $transferRatio; | |
| 1837 | 2513 | } |
| 1838 | 2514 | |
| 1839 | 2515 | /** |
| 1840 | 2516 | * Sets the nights/transfers ratio for split stays. Use it to start applying limits. |
| 1841 | 2517 | * |
| 1842 | - * @param mixed $ratio integer value, or the default config setting will be applied. | |
| 2518 | + * @param ?int $ratio Optional ratio integer value, or default value will apply. | |
| 1843 | 2519 | * |
| 1844 | 2520 | * @return self |
| 1845 | 2521 | * |
| 1846 | 2522 | * @since 1.16.0 (J) - 1.6.0 (WP) |
| 1847 | 2523 | */ |
| 1848 | - public function setNightsTransfersRatio($ratio = null) | |
| 2524 | + public function setNightsTransfersRatio(?int $ratio = null) | |
| 1849 | 2525 | { |
| 1850 | 2526 | if (is_int($ratio)) { |
| 1851 | 2527 | $this->nights_transfers_ratio = $ratio; |
| 1852 | 2528 | } else { |
| @@ -1864,9 +2540,9 @@ | ||
| 1864 | 2540 | * @since 1.16.0 (J) - 1.6.0 (WP) |
| 1865 | 2541 | */ |
| 1866 | 2542 | public function isFrontBooking() |
| 1867 | 2543 | { |
| 1868 | - return (bool)$this->is_front_booking; | |
| 2544 | + return (bool) $this->is_front_booking; | |
| 1869 | 2545 | } |
| 1870 | 2546 | |
| 1871 | 2547 | /** |
| 1872 | 2548 | * Toggles the flag to behave for the front-end booking process. |