PluginProbe
VikBooking Hotel Booking Engine & PMS / 1.8.7
VikBooking Hotel Booking Engine & PMS v1.8.7
1.8.16 1.8.15 1.8.14 1.8.13 1.8.12 1.8.11 1.8.10 1.8.9 1.8.6 1.8.7 1.8.8 trunk 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.6.5 1.6.6 1.6.7 1.6.8 1.6.9 1.7.0 1.7.1 1.7.2 All 37 releases
vikbooking / admin / helpers / src / booking / registry.php

registry.php in VikBooking Hotel Booking Engine & PMS 1.8.7, at admin/helpers/src/booking/registry.php

950 lines 28.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * @package VikBooking
4 * @subpackage core
5 * @author E4J s.r.l.
6 * @copyright Copyright (C) 2025 E4J s.r.l. All Rights Reserved.
7 * @license http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
8 * @link https://vikwp.com
9 */
10
11 // No direct access
12 defined('ABSPATH') or die('No script kiddies please!');
13
14 /**
15 * Booking registry implementation.
16 *
17 * @since 1.18.4 (J) - 1.8.4 (WP)
18 */
19 class VBOBookingRegistry
20 {
21 /**
22 * @var array
23 */
24 protected $registry = [];
25
26 /**
27 * @var array
28 */
29 protected $bookingRooms = [];
30
31 /**
32 * @var array
33 */
34 protected $previousBooking = [];
35
36 /**
37 * @var int
38 */
39 protected $currentRoomIndex = 0;
40
41 /**
42 * @var int
43 */
44 protected $currentRoomNumber = 0;
45
46 /**
47 * @var array
48 */
49 protected $dacData = [];
50
51 /**
52 * @var array
53 */
54 protected $roomDetails = [];
55
56 /**
57 * @var ?array
58 */
59 protected $customer = null;
60
61 /**
62 * Proxy to construct the object.
63 *
64 * @param array $options Associative list of booking information to bind.
65 * @param array $rooms Associative list of booking rooms to bind.
66 * @param array $previous Associative list of previous booking information to bind.
67 *
68 * @return VBOBookingRegistry
69 */
70 public static function getInstance(array $options, array $rooms = [], array $previous = [])
71 {
72 return new static($options, $rooms, $previous);
73 }
74
75 /**
76 * Class constructor.
77 *
78 * @param array $options Associative list of booking information to bind.
79 * @param array $rooms Associative list of booking rooms to bind.
80 * @param array $previous Associative list of previous booking information to bind.
81 *
82 * @throws Exception
83 */
84 public function __construct(array $options, array $rooms = [], array $previous = [])
85 {
86 // ensure we have enough booking details
87 if ($options === ['id' => $options['id'] ?? 0]) {
88 // load full booking details
89 $options = VikBooking::getBookingInfoFromID($options['id']);
90 }
91
92 if (empty($options['id'])) {
93 throw new Exception('Missing booking ID.', 500);
94 }
95
96 // bind booking options to internal registry
97 $this->bind($options);
98
99 if (!$rooms) {
100 // load booking rooms
101 $rooms = VikBooking::loadOrdersRoomsData($this->getID());
102 }
103
104 if (!$rooms) {
105 throw new Exception('No booking rooms found.', 404);
106 }
107
108 // bind booking rooms
109 $this->bookingRooms = $rooms;
110
111 // bind previous booking information in case of alteration
112 $this->previousBooking = $previous;
113 }
114
115 /**
116 * Binds the given options onto the internal booking registry.
117 *
118 * @param array $options The booking options to bind.
119 *
120 * @return void
121 */
122 public function bind(array $options)
123 {
124 $this->registry = array_merge($this->registry, $options);
125 }
126
127 /**
128 * Returns the current booking ID.
129 *
130 * @return int
131 */
132 public function getID()
133 {
134 return (int) $this->getProperty('id', 0);
135 }
136
137 /**
138 * Returns the number of nights of stay for the current booking ID.
139 *
140 * @param bool $roomLevel True to calculate the nights at room-level.
141 *
142 * @return int
143 */
144 public function getTotalNights(bool $roomLevel = false)
145 {
146 if ($roomLevel) {
147 // get the stay timestamps at room-level
148 $stayTimestamps = $this->getStayTimestamps(true);
149
150 // return the room-level nights of stay
151 return VikBooking::getAvailabilityInstance()->countNightsOfStay($stayTimestamps[0], $stayTimestamps[1]) ?: 1;
152 }
153
154 // return the booking nights of stay
155 return (int) $this->getProperty('days', 1);
156 }
157
158 /**
159 * Tells whether the booking is actually a closure reservation.
160 *
161 * @return bool
162 */
163 public function isClosure()
164 {
165 return (bool) $this->getProperty('closure', 0);
166 }
167
168 /**
169 * Tells whether the booking status is confirmed.
170 *
171 * @return bool
172 */
173 public function isConfirmed()
174 {
175 return $this->getProperty('status', '') == 'confirmed';
176 }
177
178 /**
179 * Tells whether the booking status is pending (stand-by).
180 *
181 * @return bool
182 */
183 public function isPending()
184 {
185 return $this->getProperty('status', '') == 'standby';
186 }
187
188 /**
189 * Tells whether the booking status is cancelled.
190 *
191 * @return bool
192 */
193 public function isCancelled()
194 {
195 return $this->getProperty('status', '') == 'cancelled';
196 }
197
198 /**
199 * Tells whether the booking is flagged as overbooking.
200 *
201 * @return bool
202 */
203 public function isOverbooking()
204 {
205 return $this->getProperty('type', '') == 'overbooking';
206 }
207
208 /**
209 * Returns the requested registry property name.
210 *
211 * @param string $name The registry property to fetch.
212 * @param mixed $default The default value to return.
213 *
214 * @return mixed
215 */
216 public function getProperty(string $name, $default = null)
217 {
218 return $this->registry[$name] ?? $default;
219 }
220
221 /**
222 * Sets a value for the requested registry property name.
223 *
224 * @param string $name The registry property to set.
225 * @param mixed $value The value to set.
226 *
227 * @return self
228 */
229 public function setProperty(string $name, $value)
230 {
231 $this->registry[$name] = $value;
232
233 return $this;
234 }
235
236 /**
237 * Returns the requested previous booking property name.
238 *
239 * @param string $name The previous booking property to fetch.
240 * @param mixed $default The default value to return.
241 *
242 * @return mixed
243 */
244 public function getPreviousProperty(string $name, $default = null)
245 {
246 return $this->previousBooking[$name] ?? $default;
247 }
248
249 /**
250 * Returns the booking data.
251 *
252 * @return array
253 */
254 public function getData()
255 {
256 return $this->registry;
257 }
258
259 /**
260 * Returns the whole or provider-alias DAC data-registry.
261 *
262 * @param ?string $providerAlias Optional DAC provider alias identifier.
263 *
264 * @return array
265 */
266 public function getDACData(?string $providerAlias = null)
267 {
268 if ($providerAlias) {
269 return $this->dacData[$providerAlias] ?? [];
270 }
271
272 return $this->dacData;
273 }
274
275 /**
276 * Returns the requested DAC data-registry property name.
277 *
278 * @param string $providerAlias The DAC provider alias identifier.
279 * @param string $name The DAC registry property to fetch.
280 * @param mixed $default The default value to return.
281 *
282 * @return mixed
283 */
284 public function getDACProperty(string $providerAlias, string $name, $default = null)
285 {
286 return $this->dacData[$providerAlias][$name] ?? $default;
287 }
288
289 /**
290 * Sets a value within the DAC data-registry for a given property name.
291 *
292 * @param string $providerAlias The DAC provider alias identifier.
293 * @param string $name The DAC registry property to set.
294 * @param mixed $value The property value to set.
295 *
296 * @return self
297 */
298 public function setDACProperty(string $providerAlias, string $name, $value)
299 {
300 if (!isset($this->dacData[$providerAlias])) {
301 // start a new container
302 $this->dacData[$providerAlias] = [];
303 }
304
305 // set the value for the requested property name
306 $this->dacData[$providerAlias][$name] = $value;
307
308 return $this;
309 }
310
311 /**
312 * Returns the booking rooms data.
313 *
314 * @return array
315 */
316 public function getRooms()
317 {
318 return $this->bookingRooms;
319 }
320
321 /**
322 * Returns the booked listing IDs.
323 *
324 * @param bool $unique True to only get a unique list.
325 *
326 * @return array List of booked room ID integers, unique or not.
327 */
328 public function getBookedListingIds(bool $unique = true)
329 {
330 if (!$unique) {
331 return array_map('intval', array_column($this->bookingRooms, 'idroom'));
332 }
333
334 return array_values(array_unique(array_map('intval', array_column($this->bookingRooms, 'idroom'))));
335 }
336
337 /**
338 * Returns a list of booked listing IDs and subunits (0 if no subunit).
339 *
340 * @param bool $unique True to only get a unique list.
341 *
342 * @return array Linear array of strings as "roomid-subunit".
343 *
344 * @since 1.18.7 (J) - 1.8.7 (WP)
345 */
346 public function getBookedListingSubunits(bool $unique = true)
347 {
348 $bookedListingSubunits = [];
349
350 foreach ($this->bookingRooms as $bookingRoom) {
351 // push booked listing ID and related subunit number
352 $bookedListingSubunits[] = sprintf('%d-%d', $bookingRoom['idroom'], (int) ($bookingRoom['roomindex'] ?? 0));
353 }
354
355 if ($unique) {
356 // remove duplicate entries on listing-level with no subunits
357 $bookedListingSubunits = array_values(array_unique($bookedListingSubunits));
358 }
359
360 return $bookedListingSubunits;
361 }
362
363 /**
364 * Counts the number of total adults, either at booking or room level.
365 *
366 * @param bool $roomLevel True to count the adults of the current room.
367 *
368 * @return int
369 *
370 * @since 1.18.7 (J) - 1.8.7 (WP)
371 */
372 public function countTotalAdults(bool $roomLevel = false)
373 {
374 if ($roomLevel) {
375 foreach ($this->bookingRooms as $index => $bookingRoom) {
376 if ($index == $this->getCurrentRoomIndex()) {
377 return (int) ($bookingRoom['adults'] ?? 0);
378 }
379 }
380 }
381
382 return array_sum(array_map('intval', array_column($this->bookingRooms, 'adults')));
383 }
384
385 /**
386 * Counts the number of total children, either at booking or room level.
387 *
388 * @param bool $roomLevel True to count the children of the current room.
389 *
390 * @return int
391 *
392 * @since 1.18.7 (J) - 1.8.7 (WP)
393 */
394 public function countTotalChildren(bool $roomLevel = false)
395 {
396 if ($roomLevel) {
397 foreach ($this->bookingRooms as $index => $bookingRoom) {
398 if ($index == $this->getCurrentRoomIndex()) {
399 return (int) ($bookingRoom['children'] ?? 0);
400 }
401 }
402 }
403
404 return array_sum(array_map('intval', array_column($this->bookingRooms, 'children')));
405 }
406
407 /**
408 * Counts the number of total pets, either at booking or room level.
409 *
410 * @param bool $roomLevel True to count the pets of the current room.
411 *
412 * @return int
413 *
414 * @since 1.18.7 (J) - 1.8.7 (WP)
415 */
416 public function countTotalPets(bool $roomLevel = false)
417 {
418 if ($roomLevel) {
419 foreach ($this->bookingRooms as $index => $bookingRoom) {
420 if ($index == $this->getCurrentRoomIndex()) {
421 return (int) ($bookingRoom['pets'] ?? 0);
422 }
423 }
424 }
425
426 return array_sum(array_map('intval', array_column($this->bookingRooms, 'pets')));
427 }
428
429 /**
430 * Counts the number of total guests, either at booking or room level.
431 *
432 * @param bool $roomLevel True to count the guests of the current room.
433 *
434 * @return int
435 *
436 * @since 1.18.7 (J) - 1.8.7 (WP)
437 */
438 public function countTotalGuests(bool $roomLevel = false)
439 {
440 return $this->countTotalAdults($roomLevel) + $this->countTotalChildren($roomLevel);
441 }
442
443 /**
444 * Returns the previous booking data.
445 *
446 * @return array
447 */
448 public function getPrevious()
449 {
450 return $this->previousBooking;
451 }
452
453 /**
454 * Gets the current room index.
455 *
456 * @return int
457 */
458 public function getCurrentRoomIndex()
459 {
460 return $this->currentRoomIndex;
461 }
462
463 /**
464 * Sets the current room index.
465 *
466 * @param int $index The current room index.
467 *
468 * @return void
469 */
470 public function setCurrentRoomIndex(int $index)
471 {
472 $this->currentRoomIndex = $index;
473 }
474
475 /**
476 * Gets the current room number (1-based index).
477 *
478 * @return int
479 *
480 * @since 1.18.7 (J) - 1.8.7 (WP)
481 */
482 public function getCurrentRoomNumber()
483 {
484 return $this->currentRoomNumber;
485 }
486
487 /**
488 * Sets the current room number (1-based index).
489 *
490 * @param int $number The current room number.
491 *
492 * @return void
493 *
494 * @since 1.18.7 (J) - 1.8.7 (WP)
495 */
496 public function setCurrentRoomNumber(int $number)
497 {
498 $this->currentRoomNumber = $number;
499 }
500
501 /**
502 * Gets the room ID from the current room index set.
503 *
504 * @return int
505 *
506 * @since 1.18.7 (J) - 1.8.7 (WP) added argument $roomLevel.
507 */
508 public function getCurrentRoomID()
509 {
510 foreach ($this->getRooms() as $index => $bookingRoom) {
511 if ($index == $this->currentRoomIndex) {
512 return (int) $bookingRoom['idroom'];
513 }
514 }
515
516 return (int) (($this->getRooms()[0]['idroom'] ?? 0) ?: 0);
517 }
518
519 /**
520 * Returns the details for the given room ID or for all rooms set.
521 *
522 * @param ?int $listingId Optional listing ID to get.
523 *
524 * @return array
525 *
526 * @since 1.18.7 (J) - 1.8.7 (WP)
527 */
528 public function getRoomDetails(?int $listingId = null)
529 {
530 if ($listingId && !($this->roomDetails[$listingId] ?? [])) {
531 // obtain the information for the requested listing ID
532 $listingDetails = VikBooking::getRoomInfo($listingId, ['name', 'img', 'units', 'params'], true);
533 if ($listingDetails) {
534 // cache value
535 $this->setRoomDetails($listingId, $listingDetails);
536 }
537 }
538
539 if (!$listingId) {
540 return $this->roomDetails;
541 }
542
543 return $this->roomDetails[$listingId] ?? [];
544 }
545
546 /**
547 * Sets the details for the given room ID.
548 *
549 * @param int $listingId The listing ID to update.
550 * @param array $data The details data to set.
551 *
552 * @return self
553 *
554 * @since 1.18.7 (J) - 1.8.7 (WP)
555 */
556 public function setRoomDetails(int $listingId, array $data)
557 {
558 $this->roomDetails[$listingId] = $data;
559
560 return $this;
561 }
562
563 /**
564 * Gets the booking customer details.
565 *
566 * @return array
567 *
568 * @since 1.18.7 (J) - 1.8.7 (WP)
569 */
570 public function getCustomer()
571 {
572 if ($this->customer === null) {
573 // load and cache customer details
574 $this->customer = VikBooking::getCPinInstance()->getCustomerFromBooking($this->getID());
575 }
576
577 return $this->customer;
578 }
579
580 /**
581 * Sets the booking customer details.
582 *
583 * @param array $customer Raw customer details.
584 *
585 * @return self
586 *
587 * @since 1.18.7 (J) - 1.8.7 (WP)
588 */
589 public function setCustomer(array $customer)
590 {
591 $this->customer = $customer;
592
593 return $this;
594 }
595
596 /**
597 * Gets the booking customer data: nominative, logo, provenience name.
598 *
599 * @return array Numeric list of booking-customer data value strings.
600 *
601 * @since 1.18.7 (J) - 1.8.7 (WP)
602 */
603 public function getBookingCustomerData()
604 {
605 // starting values
606 $customer_nominative = '';
607 $booking_avatar_src = '';
608 $booking_avatar_alt = '';
609
610 if ($this->customer === null) {
611 // attempt to load customer details first
612 $this->getCustomer();
613 }
614
615 if (!empty($this->customer['first_name']) || !empty($this->customer['last_name'])) {
616 // check if we need to display a profile picture or a channel logo
617 if (!empty($this->customer['pic'])) {
618 // customer profile picture
619 $booking_avatar_src = strpos($this->customer['pic'], 'http') === 0 ? $this->customer['pic'] : VBO_SITE_URI . 'resources/uploads/' . $this->customer['pic'];
620 $booking_avatar_alt = basename($booking_avatar_src);
621 } elseif ($this->getProperty('idorderota') && $this->getProperty('channel')) {
622 // channel logo
623 $logo_helper = VikBooking::getVcmChannelsLogo($this->getProperty('channel'), $get_istance = true);
624 if ($logo_helper !== false) {
625 $booking_avatar_src = $logo_helper->getSmallLogoURL();
626 $booking_avatar_alt = $logo_helper->provenience;
627 }
628 }
629
630 if (!empty($booking_avatar_src)) {
631 // make sure the alt attribute is not too long in case of broken images
632 $booking_avatar_alt = !empty($booking_avatar_alt) && strlen($booking_avatar_alt) > 15 ? '...' . substr($booking_avatar_alt, -12) : $booking_avatar_alt;
633 }
634
635 // customer name
636 $customer_fullname = trim($this->customer['first_name'] . ' ' . $this->customer['last_name']);
637 if (strlen($customer_fullname) > 26) {
638 if (function_exists('mb_substr')) {
639 $customer_fullname = trim(mb_substr($customer_fullname, 0, 26, 'UTF-8')) . '..';
640 } else {
641 $customer_fullname = trim(substr($customer_fullname, 0, 26)) . '..';
642 }
643 }
644 $customer_nominative = $customer_fullname;
645 } else {
646 // parse the customer data string
647 $custdata_parts = explode("\n", (string) $this->getProperty('custdata'));
648 $enoughinfo = false;
649 if (count($custdata_parts) > 2 && strpos($custdata_parts[0], ':') !== false && strpos($custdata_parts[1], ':') !== false) {
650 // get the first two fields
651 $custvalues = array();
652 foreach ($custdata_parts as $custdet) {
653 if (strlen($custdet) < 1) {
654 continue;
655 }
656 $custdet_parts = explode(':', $custdet);
657 if (count($custdet_parts) >= 2) {
658 unset($custdet_parts[0]);
659 array_push($custvalues, trim(implode(':', $custdet_parts)));
660 }
661 if (count($custvalues) > 1) {
662 break;
663 }
664 }
665 if (count($custvalues) > 1) {
666 $enoughinfo = true;
667 $customer_nominative = trim(implode(' ', $custvalues));
668 if (strlen($customer_nominative) > 26) {
669 if (function_exists('mb_substr')) {
670 $customer_nominative = trim(mb_substr($customer_nominative, 0, 26, 'UTF-8')) . '..';
671 } else {
672 $customer_nominative = trim(substr($customer_nominative, 0, 26)) . '..';
673 }
674 }
675 if ($this->getProperty('idorderota') && $this->getProperty('channel')) {
676 // add support for the channel logo for the imported OTA reservations with no customer record
677 $logo_helper = VikBooking::getVcmChannelsLogo($this->getProperty('channel'), $get_istance = true);
678 if ($logo_helper !== false) {
679 $booking_avatar_src = $logo_helper->getSmallLogoURL();
680 $booking_avatar_alt = $logo_helper->provenience;
681 // make sure the alt attribute is not too long in case of broken images
682 $booking_avatar_alt = !empty($booking_avatar_alt) && strlen($booking_avatar_alt) > 15 ? '...' . substr($booking_avatar_alt, -12) : $booking_avatar_alt;
683 }
684 }
685 }
686 }
687 if (!$enoughinfo) {
688 $customer_nominative = '#' . $this->getID();
689 }
690 }
691
692 return [
693 $customer_nominative,
694 $booking_avatar_src,
695 $booking_avatar_alt,
696 ];
697 }
698
699 /**
700 * Returns the booking (or current room booking record) stay timestamps.
701 *
702 * @param bool $roomLevel True to return the stay timestamps at booking room level.
703 *
704 * @return array
705 *
706 * @since 1.18.7 (J) - 1.8.7 (WP) added argument $roomLevel.
707 */
708 public function getStayTimestamps(bool $roomLevel = false)
709 {
710 if ($roomLevel) {
711 // current room index signature for the stay timestamps
712 $roomSignature = '_stay_timestamps' . $this->getCurrentRoomIndex();
713
714 if ($cachedTimestamps = $this->getProperty($roomSignature)) {
715 // return the cached stay timestamps for the current room index
716 return $cachedTimestamps;
717 }
718
719 // load room occupied records
720 $room_stay_dates = VikBooking::getAvailabilityInstance(true)->loadSplitStayBusyRecords($this->getID());
721
722 // default room-level stay timestamps
723 $roomLevelCheckin = $this->getProperty('checkin');
724 $roomLevelCheckout = $this->getProperty('checkout');
725
726 // split-stay booking or booked rooms for modified stay nights should rely on occupied records
727 if ($room_stay_dates[$this->getCurrentRoomIndex()] ?? []) {
728 // set booking-room-level stay timestamps
729 $roomLevelCheckin = $room_stay_dates[$this->getCurrentRoomIndex()]['checkin'];
730 $roomLevelCheckout = $room_stay_dates[$this->getCurrentRoomIndex()]['checkout'];
731 }
732
733 // build room-level stay timestamps list
734 $roomLevelStayList = [
735 $roomLevelCheckin,
736 $roomLevelCheckout,
737 ];
738
739 // cache stay timestamps for the current room index
740 $this->setProperty($roomSignature, $roomLevelStayList);
741
742 // return booking-room-level stay timestamps, if different than global stay dates
743 return $roomLevelStayList;
744 }
745
746 // global reservation stay timestamps
747 return [
748 $this->getProperty('checkin'),
749 $this->getProperty('checkout'),
750 ];
751 }
752
753 /**
754 * Builds and returns the iterable date period interval for the nights of stay.
755 *
756 * @param string $duration The interval specification used for DateInterval::__construct().
757 * @param int $from_ts Optional from date period timestamp.
758 * @param int $to_ts Optional to date period timestamp.
759 *
760 * @return DatePeriod
761 */
762 public function buildStayPeriodInterval(string $duration = 'P1D', int $from_ts = 0, int $to_ts = 0)
763 {
764 if (empty($from_ts)) {
765 $from_ts = $this->getProperty('checkin');
766 }
767
768 if (empty($to_ts)) {
769 $to_ts = $this->getProperty('checkout');
770 }
771
772 // local timezone
773 $tz = new DateTimezone(date_default_timezone_get());
774
775 // get date bounds
776 $from_bound = new DateTime(date('Y-m-d H:i:s', $from_ts), $tz);
777 $to_bound = new DateTime(date('Y-m-d H:i:s', $to_ts), $tz);
778
779 // build iterable dates interval (period)
780 $date_range = new DatePeriod(
781 // start date included by default in the result set
782 $from_bound,
783 // interval between recurrences within the period
784 new DateInterval($duration),
785 // end date (check-out) excluded by default from the result set
786 $to_bound
787 );
788
789 return $date_range;
790 }
791
792 /**
793 * Returns the iterable date period range of dates for the nights of stay.
794 *
795 * @return DatePeriod
796 */
797 public function getStayPeriod()
798 {
799 if (($this->registry['stay_date_period'] ?? null) instanceof DatePeriod) {
800 // return cached value
801 return $this->registry['stay_date_period'];
802 }
803
804 // build iterable dates interval (period)
805 $date_range = $this->buildStayPeriodInterval('P1D');
806
807 // cache value
808 $this->bind(['stay_date_period' => $date_range]);
809
810 return $date_range;
811 }
812
813 /**
814 * Attempts to detect changes between the current and previous bookings.
815 *
816 * @param bool $roomLevel True to also detect alterations at room-level.
817 *
818 * @return bool False if no changes were actually proved, true otherwise.
819 *
820 * @since 1.18.7 (J) - 1.8.7 (WP) added argument $roomLevel.
821 */
822 public function detectAlterations(bool $roomLevel = false)
823 {
824 if ($this->getProperty('checkin') != $this->getPreviousProperty('checkin')) {
825 return true;
826 }
827
828 if ($this->getProperty('checkout') != $this->getPreviousProperty('checkout')) {
829 return true;
830 }
831
832 if ($this->getProperty('days') != $this->getPreviousProperty('days')) {
833 return true;
834 }
835
836 if ($this->getProperty('roomsnum') != $this->getPreviousProperty('roomsnum')) {
837 return true;
838 }
839
840 // get the rooms booked with the current reservation
841 $current_room_ids = array_column($this->getRooms(), 'idroom');
842 if (!$current_room_ids && is_array($this->getProperty('rooms_info'))) {
843 $current_room_ids = array_column($this->getProperty('rooms_info'), 'idroom');
844 }
845
846 // get the rooms booked with the previous reservation
847 $previous_room_ids = array_column((array) $this->getPreviousProperty('rooms_info', []), 'idroom');
848
849 // map and sort both room lists
850 $current_room_ids = array_map('intval', $current_room_ids);
851 $previous_room_ids = array_map('intval', $previous_room_ids);
852 sort($current_room_ids);
853 sort($previous_room_ids);
854
855 if (!$current_room_ids || !$previous_room_ids || $current_room_ids != $previous_room_ids) {
856 return true;
857 }
858
859 // attempt to also detect changes at room-level
860 if ($roomLevel) {
861 // check subunits
862 $current_room_indexes = array_map('intval', array_column($this->getRooms(), 'roomindex'));
863 $previous_room_indexes = array_map('intval', array_column((array) $this->getPreviousProperty('rooms_info', []), 'roomindex'));
864 sort($current_room_indexes);
865 sort($previous_room_indexes);
866 // ensure room subunits information is available for current and previous booking
867 if ($current_room_indexes && $previous_room_indexes && $current_room_indexes != $previous_room_indexes) {
868 // room subunits alteration detected
869 return true;
870 }
871 }
872
873 // no significant changes to stay dates or listings could be proved
874 return false;
875 }
876
877 /**
878 * Tells if the current date and time is between the stay dates.
879 *
880 * @return bool
881 */
882 public function isStaying()
883 {
884 $now = time();
885
886 return $this->getProperty('checkin', 0) <= $now && $now <= $this->getProperty('checkout', 0);
887 }
888
889 /**
890 * Tells if the booking arrival date is today.
891 *
892 * @return bool
893 */
894 public function isArrivingToday()
895 {
896 return date('Y-m-d', $this->getProperty('checkin', 0)) === date('Y-m-d');
897 }
898
899 /**
900 * Tells if the booking departure date is today.
901 *
902 * @return bool
903 */
904 public function isDepartingToday()
905 {
906 return date('Y-m-d', $this->getProperty('checkout', 0)) === date('Y-m-d');
907 }
908
909 /**
910 * Tells if the booking arrival date and time is in the future.
911 *
912 * @return bool
913 */
914 public function isFuture()
915 {
916 return $this->getProperty('checkin', 0) > time();
917 }
918
919 /**
920 * Tells if the booking departure date and time is in the past.
921 *
922 * @return bool
923 */
924 public function isPast()
925 {
926 return $this->getProperty('checkout', 0) < time();
927 }
928
929 /**
930 * Tells if the booking went through the pre-checkin process.
931 *
932 * @return bool
933 *
934 * @since 1.18.6 (J) - 1.8.6 (WP)
935 */
936 public function hasPreCheckedIn()
937 {
938 // check registry protected value
939 $pre_checkin = $this->getProperty('_precheckin');
940
941 if (!is_int($pre_checkin)) {
942 // fetch the actual status and cache it internally
943 $pre_checkin = (int) boolval(VikBooking::getBookingHistoryInstance($this->getID())->hasEvent('PC'));
944 $this->registry['_precheckin'] = $pre_checkin;
945 }
946
947 return (bool) $pre_checkin;
948 }
949 }
950