| @@ -37,8 +37,13 @@ | ||
| 37 | 37 | */ |
| 38 | 38 | protected $allRooms = []; |
| 39 | 39 | |
| 40 | 40 | /** |
| 41 | + * @var ?VBOBookingRegistry | |
| 42 | + */ | |
| 43 | + protected ?VBOBookingRegistry $prevBookingRegistry = null; | |
| 44 | + | |
| 45 | + /** | |
| 41 | 46 | * Proxy for immediately getting the object and bind data. |
| 42 | 47 | * |
| 43 | 48 | * @param array|object $data optional data to bind. |
| 44 | 49 | * @param boolean $anew true for forcing a new instance. |
| @@ -444,20 +449,31 @@ | ||
| 444 | 449 | return $special_requests; |
| 445 | 450 | } |
| 446 | 451 | |
| 447 | 452 | /** |
| 448 | - * Creates a new reservation record after having constructed the | |
| 449 | - * object by properly injecting all the necessary booking information. | |
| 453 | + * Runs before a reservation is created or updated. | |
| 450 | 454 | * |
| 455 | + * @param ?int $bookingId Optional reservation ID being updated. | |
| 456 | + * | |
| 451 | 457 | * @return bool |
| 458 | + * | |
| 459 | + * @since 1.18.11 (J) - 1.8.11 (WP) | |
| 452 | 460 | */ |
| 453 | - public function create() | |
| 461 | + protected function preflight(?int $bookingId = null) | |
| 454 | 462 | { |
| 455 | - if (!$this->canCreate()) { | |
| 456 | - $this->setError('Forbidden'); | |
| 463 | + // if updating a reservation, fetch the previous details | |
| 464 | + try { | |
| 465 | + $prevBookingRegistry = $bookingId ? VBOBookingRegistry::getInstance(['id' => $bookingId]) : null; | |
| 466 | + } catch (Exception $e) { | |
| 467 | + $this->setError($e->getMessage()); | |
| 457 | 468 | return false; |
| 458 | 469 | } |
| 459 | 470 | |
| 471 | + if ($prevBookingRegistry) { | |
| 472 | + // set booking registry snapshot prior to update | |
| 473 | + $this->prevBookingRegistry = $prevBookingRegistry; | |
| 474 | + } | |
| 475 | + | |
| 460 | 476 | // availability helper |
| 461 | 477 | $av_helper = VikBooking::getAvailabilityInstance(true); |
| 462 | 478 | |
| 463 | 479 | // validate mandatory fields |
| @@ -534,8 +550,33 @@ | ||
| 534 | 550 | |
| 535 | 551 | // fetch and apply turnover time before doing anything else |
| 536 | 552 | $this->applyTurnover(); |
| 537 | 553 | |
| 554 | + return true; | |
| 555 | + } | |
| 556 | + | |
| 557 | + /** | |
| 558 | + * Creates a new reservation record after having constructed the | |
| 559 | + * object by properly injecting all the necessary booking information. | |
| 560 | + * | |
| 561 | + * @return bool | |
| 562 | + */ | |
| 563 | + public function create() | |
| 564 | + { | |
| 565 | + if (!$this->canCreate()) { | |
| 566 | + $this->setError('Forbidden'); | |
| 567 | + return false; | |
| 568 | + } | |
| 569 | + | |
| 570 | + /** | |
| 571 | + * Run preflight to ensure data integrity. | |
| 572 | + * | |
| 573 | + * @since 1.18.11 (J) - 1.8.11 (WP) | |
| 574 | + */ | |
| 575 | + if (!$this->preflight()) { | |
| 576 | + return false; | |
| 577 | + } | |
| 578 | + | |
| 538 | 579 | // get pool of rooms involved |
| 539 | 580 | $rooms_pool = $this->getRoomsPool(); |
| 540 | 581 | if (!$rooms_pool) { |
| 541 | 582 | if ($this->getError() === false) { |
| @@ -576,8 +617,80 @@ | ||
| 576 | 617 | return true; |
| 577 | 618 | } |
| 578 | 619 | |
| 579 | 620 | /** |
| 621 | + * Updates a reservation record after having constructed the | |
| 622 | + * object by properly injecting all the necessary booking information. | |
| 623 | + * Unlike the modify() method, this method follows the create() pattern. | |
| 624 | + * | |
| 625 | + * @return bool | |
| 626 | + * | |
| 627 | + * @since 1.18.11 (J) - 1.8.11 (WP) | |
| 628 | + */ | |
| 629 | + public function update() | |
| 630 | + { | |
| 631 | + // access booking ID | |
| 632 | + $bookingId = (int) $this->get('booking_id', 0); | |
| 633 | + | |
| 634 | + if (!$bookingId) { | |
| 635 | + $this->setError('Missing booking ID to update.'); | |
| 636 | + return false; | |
| 637 | + } | |
| 638 | + | |
| 639 | + /** | |
| 640 | + * Run preflight to ensure data integrity. | |
| 641 | + */ | |
| 642 | + if (!$this->preflight($bookingId)) { | |
| 643 | + return false; | |
| 644 | + } | |
| 645 | + | |
| 646 | + // get pool of rooms involved | |
| 647 | + $rooms_pool = $this->getRoomsPool(); | |
| 648 | + if (!$rooms_pool) { | |
| 649 | + if ($this->getError() === false) { | |
| 650 | + // set generic error if not set already | |
| 651 | + $this->setError('No rooms involved in the reservation'); | |
| 652 | + } | |
| 653 | + return false; | |
| 654 | + } | |
| 655 | + | |
| 656 | + try { | |
| 657 | + // check if the rooms are available for modification | |
| 658 | + $rooms_available = $this->bookingModifiable($bookingId, $this->get('checkin'), $this->get('checkout')); | |
| 659 | + if (!$rooms_available && !$this->get('force_booking', 0) && !$this->get('set_closed', 0)) { | |
| 660 | + // no forcing, no closure and room(s) fully booked means we have an error | |
| 661 | + if (!$this->getError()) { | |
| 662 | + $this->setError(JText::translate('VBBOOKNOTMADE')); | |
| 663 | + } | |
| 664 | + return false; | |
| 665 | + } | |
| 666 | + } catch (Exception $e) { | |
| 667 | + $this->setError($e->getMessage()); | |
| 668 | + return false; | |
| 669 | + } | |
| 670 | + | |
| 671 | + // detect if we are forcing the reservation | |
| 672 | + $this->detectForcedReason($rooms_available); | |
| 673 | + | |
| 674 | + // store or update the customer information | |
| 675 | + $this->storeCustomer(); | |
| 676 | + | |
| 677 | + // calculate total amount and total tax | |
| 678 | + $this->calculateTotal(); | |
| 679 | + | |
| 680 | + // update booking and room-booking records | |
| 681 | + if (!$this->storeReservationRecords($rooms_pool, $bookingId)) { | |
| 682 | + if ($this->getError() === false) { | |
| 683 | + // set generic error if not set already | |
| 684 | + $this->setError('Could not update the reservation'); | |
| 685 | + } | |
| 686 | + return false; | |
| 687 | + } | |
| 688 | + | |
| 689 | + return true; | |
| 690 | + } | |
| 691 | + | |
| 692 | + /** | |
| 580 | 693 | * Searches for bookings according to specified filters. |
| 581 | 694 | * |
| 582 | 695 | * @return array |
| 583 | 696 | * |
| @@ -642,12 +755,21 @@ | ||
| 642 | 755 | $q->where($dbo->qn('o.custmail') . ' = ' . $dbo->q($filters['email'])); |
| 643 | 756 | } |
| 644 | 757 | |
| 645 | 758 | if (($filters['phone'] ?? null)) { |
| 646 | - $q->where(sprintf('REPLACE(%s, \' \', \'\') LIKE REPLACE(%s, \' \', \'\')', | |
| 647 | - $dbo->qn('o.phone'), | |
| 648 | - $dbo->q('%' . $filters['phone']) | |
| 649 | - )); | |
| 759 | + $q->where( | |
| 760 | + sprintf( | |
| 761 | + '(%s OR %s)', | |
| 762 | + sprintf('REPLACE(%s, \' \', \'\') LIKE REPLACE(%s, \' \', \'\')', | |
| 763 | + $dbo->qn('o.phone'), | |
| 764 | + $dbo->q('%' . $filters['phone']) | |
| 765 | + ), | |
| 766 | + sprintf('REPLACE(%s, \' \', \'\') LIKE REPLACE(%s, \' \', \'\')', | |
| 767 | + $dbo->qn('o.phone_alias'), | |
| 768 | + $dbo->q('%' . $filters['phone_alias']) | |
| 769 | + ) | |
| 770 | + ) | |
| 771 | + ); | |
| 650 | 772 | } |
| 651 | 773 | |
| 652 | 774 | if (($filters['date_range']['type'] ?? null) && (($filters['date_range']['start'] ?? null) || ($filters['date_range']['end'] ?? null))) { |
| 653 | 775 | // search by date range |
| @@ -951,12 +1073,95 @@ | ||
| 951 | 1073 | return (bool) $dbo->getAffectedRows(); |
| 952 | 1074 | } |
| 953 | 1075 | |
| 954 | 1076 | /** |
| 1077 | + * Updates the registration status for the provided booking. | |
| 1078 | + * | |
| 1079 | + * @param int $status Registration status to set. | |
| 1080 | + * @param ?int $bookingId Optional booking ID to update. | |
| 1081 | + * | |
| 1082 | + * @return bool | |
| 1083 | + * | |
| 1084 | + * @throws Exception | |
| 1085 | + * | |
| 1086 | + * @since 1.18.15 (J) - 1.8.15 (WP) | |
| 1087 | + */ | |
| 1088 | + public function updateRegistration(int $status, ?int $bookingId = null) | |
| 1089 | + { | |
| 1090 | + $dbo = JFactory::getDbo(); | |
| 1091 | + | |
| 1092 | + if (!$bookingId) { | |
| 1093 | + // get the internal booking record | |
| 1094 | + $booking = $this->getBooking(); | |
| 1095 | + $bookingId = $booking['id'] ?? null; | |
| 1096 | + } | |
| 1097 | + | |
| 1098 | + if (empty($bookingId)) { | |
| 1099 | + throw new Exception('Missing booking ID.', 400); | |
| 1100 | + } | |
| 1101 | + | |
| 1102 | + $knownStatuses = [-1, 0, 1, 2]; | |
| 1103 | + | |
| 1104 | + if (!in_array($status, $knownStatuses, true)) { | |
| 1105 | + throw new InvalidArgumentException('Invalid registration status.', 400); | |
| 1106 | + } | |
| 1107 | + | |
| 1108 | + // update booking record | |
| 1109 | + $dbo->setQuery( | |
| 1110 | + $dbo->getQuery(true) | |
| 1111 | + ->update($dbo->qn('#__vikbooking_orders')) | |
| 1112 | + ->set($dbo->qn('checked') . ' = ' . $status) | |
| 1113 | + ->where($dbo->qn('id') . ' = ' . $bookingId) | |
| 1114 | + ); | |
| 1115 | + $dbo->execute(); | |
| 1116 | + | |
| 1117 | + return (bool) $dbo->getAffectedRows(); | |
| 1118 | + } | |
| 1119 | + | |
| 1120 | + /** | |
| 1121 | + * Updates the booking-level phone number or alias phone number (masked phone). | |
| 1122 | + * | |
| 1123 | + * @param ?string $phone The phone number value to set, even null to unset. | |
| 1124 | + * @param bool $isAlias Whether to update the real or alias phone number. | |
| 1125 | + * @param ?int $bookingId Optional booking ID to update. | |
| 1126 | + * | |
| 1127 | + * @return bool | |
| 1128 | + * | |
| 1129 | + * @throws Exception | |
| 1130 | + * | |
| 1131 | + * @since 1.18.16 (J) - 1.8.16 (WP) | |
| 1132 | + */ | |
| 1133 | + public function updatePhoneNumber(?string $phone, bool $isAlias = false, ?int $bookingId = null) | |
| 1134 | + { | |
| 1135 | + $dbo = JFactory::getDbo(); | |
| 1136 | + | |
| 1137 | + if (!$bookingId) { | |
| 1138 | + // get the internal booking record | |
| 1139 | + $booking = $this->getBooking(); | |
| 1140 | + $bookingId = $booking['id'] ?? null; | |
| 1141 | + } | |
| 1142 | + | |
| 1143 | + if (empty($bookingId)) { | |
| 1144 | + throw new Exception('Missing booking ID.', 400); | |
| 1145 | + } | |
| 1146 | + | |
| 1147 | + $dbo->setQuery( | |
| 1148 | + $dbo->getQuery(true) | |
| 1149 | + ->update('#__vikbooking_orders') | |
| 1150 | + ->set($dbo->qn($isAlias ? 'phone_alias' : 'phone') . ' = ' . ($phone ? $dbo->q($phone) : 'NULL')) | |
| 1151 | + ->where($dbo->qn('id') . ' = ' . (int) $bookingId) | |
| 1152 | + ); | |
| 1153 | + $dbo->execute(); | |
| 1154 | + | |
| 1155 | + return (bool) $dbo->getAffectedRows(); | |
| 1156 | + } | |
| 1157 | + | |
| 1158 | + /** | |
| 955 | 1159 | * Modifies the requested booking ID according to the provided options. |
| 956 | 1160 | * This method does not support all rate plan options like for the creation of a |
| 957 | 1161 | * new booking. This is a method for making quick updates concerning a room switch, |
| 958 | 1162 | * a change of stay dates, new booking total amount, guests, add extra services etc.. |
| 1163 | + * For modifying a reservation with the same data as for the creation, see update(). | |
| 959 | 1164 | * |
| 960 | 1165 | * @param array $options List of details to perform the modification. |
| 961 | 1166 | * |
| 962 | 1167 | * @return bool |
| @@ -1400,17 +1605,19 @@ | ||
| 1400 | 1605 | return true; |
| 1401 | 1606 | } |
| 1402 | 1607 | |
| 1403 | 1608 | /** |
| 1404 | - * Deletes the requested booking ID. | |
| 1609 | + * Deletes the requested booking ID and related records. | |
| 1405 | 1610 | * |
| 1406 | - * @param array $options List of details to perform the cancellation. | |
| 1611 | + * @param array $options List of details to perform the cancellation. | |
| 1612 | + * @param bool $reCreating True if the booking is being re-created (updated). | |
| 1407 | 1613 | * |
| 1408 | 1614 | * @return bool |
| 1409 | 1615 | * |
| 1410 | 1616 | * @since 1.16.10 (J) - 1.6.10 (WP) |
| 1617 | + * @since 1.18.11 (J) - 1.8.11 (WP) added argument $reCreating. | |
| 1411 | 1618 | */ |
| 1412 | - public function delete(array $options) | |
| 1619 | + public function delete(array $options, bool $reCreating = false) | |
| 1413 | 1620 | { |
| 1414 | 1621 | $dbo = JFactory::getDbo(); |
| 1415 | 1622 | |
| 1416 | 1623 | $booking_id = $options['booking_id'] ?? 0; |
| @@ -1416,9 +1623,13 @@ | ||
| 1416 | 1623 | $booking_id = $options['booking_id'] ?? 0; |
| 1417 | 1624 | $canc_reason = $options['cancellation_reason'] ?? ''; |
| 1418 | 1625 | $purge_remove = $options['purge_remove'] ?? false; |
| 1419 | 1626 | |
| 1420 | - $booking = VikBooking::getBookingInfoFromID($booking_id); | |
| 1627 | + if ($reCreating && $this->prevBookingRegistry && $this->prevBookingRegistry->getID() == $booking_id) { | |
| 1628 | + $booking = $this->prevBookingRegistry->getData(); | |
| 1629 | + } else { | |
| 1630 | + $booking = VikBooking::getBookingInfoFromID($booking_id); | |
| 1631 | + } | |
| 1421 | 1632 | |
| 1422 | 1633 | if (!$booking) { |
| 1423 | 1634 | $this->setError('Booking not found.'); |
| 1424 | 1635 | return false; |
| @@ -1423,14 +1634,14 @@ | ||
| 1423 | 1634 | $this->setError('Booking not found.'); |
| 1424 | 1635 | return false; |
| 1425 | 1636 | } |
| 1426 | 1637 | |
| 1427 | - if ($booking['status'] === 'cancelled' && !$purge_remove) { | |
| 1638 | + if (!$reCreating && $booking['status'] === 'cancelled' && !$purge_remove) { | |
| 1428 | 1639 | $this->setError(sprintf('Booking ID %d is already cancelled.', $booking['id'])); |
| 1429 | 1640 | return false; |
| 1430 | 1641 | } |
| 1431 | 1642 | |
| 1432 | - if (class_exists('VCMFeesCancellation')) { | |
| 1643 | + if (!$reCreating && class_exists('VCMFeesCancellation')) { | |
| 1433 | 1644 | // let VCM detect if there are any constraints for the cancellation |
| 1434 | 1645 | $canc_denied = VCMFeesCancellation::getInstance($booking, $anew = true)->isBookingConstrained(); |
| 1435 | 1646 | if ($canc_denied) { |
| 1436 | 1647 | // set error message |
| @@ -1446,19 +1657,21 @@ | ||
| 1446 | 1657 | // whether OTAs should be notified |
| 1447 | 1658 | $notify_otas = false; |
| 1448 | 1659 | |
| 1449 | 1660 | if ($booking['status'] != 'cancelled') { |
| 1450 | - // update status to cancelled | |
| 1451 | - $q = $dbo->getQuery(true) | |
| 1452 | - ->update($dbo->qn('#__vikbooking_orders')) | |
| 1453 | - ->set($dbo->qn('status') . ' = ' . $dbo->q('cancelled')) | |
| 1454 | - ->where($dbo->qn('id') . ' = ' . (int) $booking['id']); | |
| 1455 | - if (!empty($canc_reason)) { | |
| 1456 | - $set_canc_reason = (!empty($booking['adminnotes']) ? $booking['adminnotes'] . "\n" : '') . $canc_reason; | |
| 1457 | - $q->set($dbo->qn('adminnotes') . ' = ' . $dbo->q($set_canc_reason)); | |
| 1661 | + if (!$reCreating) { | |
| 1662 | + // update status to cancelled | |
| 1663 | + $q = $dbo->getQuery(true) | |
| 1664 | + ->update($dbo->qn('#__vikbooking_orders')) | |
| 1665 | + ->set($dbo->qn('status') . ' = ' . $dbo->q('cancelled')) | |
| 1666 | + ->where($dbo->qn('id') . ' = ' . (int) $booking['id']); | |
| 1667 | + if (!empty($canc_reason)) { | |
| 1668 | + $set_canc_reason = (!empty($booking['adminnotes']) ? $booking['adminnotes'] . "\n" : '') . $canc_reason; | |
| 1669 | + $q->set($dbo->qn('adminnotes') . ' = ' . $dbo->q($set_canc_reason)); | |
| 1670 | + } | |
| 1671 | + $dbo->setQuery($q); | |
| 1672 | + $dbo->execute(); | |
| 1458 | 1673 | } |
| 1459 | - $dbo->setQuery($q); | |
| 1460 | - $dbo->execute(); | |
| 1461 | 1674 | |
| 1462 | 1675 | // delete temporarily locked records, if any |
| 1463 | 1676 | $dbo->setQuery( |
| 1464 | 1677 | $dbo->getQuery(true) |
| @@ -1466,26 +1679,28 @@ | ||
| 1466 | 1679 | ->where($dbo->qn('idorder') . ' = ' . (int) $booking['id']) |
| 1467 | 1680 | ); |
| 1468 | 1681 | $dbo->execute(); |
| 1469 | 1682 | |
| 1470 | - if ($booking['status'] == 'confirmed') { | |
| 1683 | + if (!$reCreating && $booking['status'] == 'confirmed') { | |
| 1471 | 1684 | // turn flag on |
| 1472 | 1685 | $notify_otas = true; |
| 1473 | 1686 | } |
| 1474 | 1687 | |
| 1475 | - // access history object | |
| 1476 | - $history_obj = VikBooking::getBookingHistoryInstance($booking['id']); | |
| 1688 | + if (!$reCreating) { | |
| 1689 | + // access history object | |
| 1690 | + $history_obj = VikBooking::getBookingHistoryInstance($booking['id']); | |
| 1477 | 1691 | |
| 1478 | - $caller_id = $now_user->name ? "({$now_user->name})" : ''; | |
| 1479 | - if ($this->getCaller()) { | |
| 1480 | - $caller_id = '(' . $this->getCaller() . ')'; | |
| 1481 | - if ($this->getHistoryData()) { | |
| 1482 | - $history_obj->setExtraData($this->getHistoryData()); | |
| 1692 | + $caller_id = $now_user->name ? "({$now_user->name})" : ''; | |
| 1693 | + if ($this->getCaller()) { | |
| 1694 | + $caller_id = '(' . $this->getCaller() . ')'; | |
| 1695 | + if ($this->getHistoryData()) { | |
| 1696 | + $history_obj->setExtraData($this->getHistoryData()); | |
| 1697 | + } | |
| 1483 | 1698 | } |
| 1699 | + | |
| 1700 | + // update Booking History | |
| 1701 | + $history_obj->store('CB', $caller_id); | |
| 1484 | 1702 | } |
| 1485 | - | |
| 1486 | - // update Booking History | |
| 1487 | - $history_obj->store('CB', $caller_id); | |
| 1488 | 1703 | } |
| 1489 | 1704 | |
| 1490 | 1705 | /** |
| 1491 | 1706 | * In case of pending bookings being cancelled, schedule the release through VCM. |
| @@ -1522,9 +1737,9 @@ | ||
| 1522 | 1737 | ); |
| 1523 | 1738 | $dbo->execute(); |
| 1524 | 1739 | |
| 1525 | 1740 | // check for purge removal |
| 1526 | - if ($booking['status'] === 'cancelled' && $purge_remove) { | |
| 1741 | + if (!$reCreating && $booking['status'] === 'cancelled' && $purge_remove) { | |
| 1527 | 1742 | // delete booking-customer relation |
| 1528 | 1743 | $dbo->setQuery( |
| 1529 | 1744 | $dbo->getQuery(true) |
| 1530 | 1745 | ->delete($dbo->qn('#__vikbooking_customers_orders')) |
| @@ -1561,9 +1776,26 @@ | ||
| 1561 | 1776 | VBOFactory::getConfig()->remove('split_stay_' . $booking['id']); |
| 1562 | 1777 | } |
| 1563 | 1778 | } |
| 1564 | 1779 | |
| 1565 | - if ($notify_otas) { | |
| 1780 | + if ($reCreating === true) { | |
| 1781 | + // when updating a booking record, get rid of all rooms previously assigned | |
| 1782 | + | |
| 1783 | + // delete booking-room relations | |
| 1784 | + $dbo->setQuery( | |
| 1785 | + $dbo->getQuery(true) | |
| 1786 | + ->delete($dbo->qn('#__vikbooking_ordersrooms')) | |
| 1787 | + ->where($dbo->qn('idorder') . ' = ' . (int) $booking['id']) | |
| 1788 | + ); | |
| 1789 | + $dbo->execute(); | |
| 1790 | + | |
| 1791 | + // in case of split stay booking, remove the transient | |
| 1792 | + if ($booking['split_stay']) { | |
| 1793 | + VBOFactory::getConfig()->remove('split_stay_' . $booking['id']); | |
| 1794 | + } | |
| 1795 | + } | |
| 1796 | + | |
| 1797 | + if (!$reCreating && $notify_otas) { | |
| 1566 | 1798 | $vcm_autosync = VikBooking::vcmAutoUpdate(); |
| 1567 | 1799 | if ($vcm_autosync > 0) { |
| 1568 | 1800 | $vcm_obj = VikBooking::getVcmInvoker(); |
| 1569 | 1801 | $vcm_obj->setOids([$booking['id']])->setSyncType('cancel'); |
| @@ -1664,8 +1896,11 @@ | ||
| 1664 | 1896 | $units_minus_oper[$or['idroom']] = 0; |
| 1665 | 1897 | } |
| 1666 | 1898 | } |
| 1667 | 1899 | |
| 1900 | + // list of room-units pool | |
| 1901 | + $roomUnits = []; | |
| 1902 | + | |
| 1668 | 1903 | // check availability for each room involved |
| 1669 | 1904 | foreach ($roomBooking as $ind => $or) { |
| 1670 | 1905 | // determine proper values for this room |
| 1671 | 1906 | $room_stay_checkin = $booking['checkin']; |
| @@ -1681,8 +1916,11 @@ | ||
| 1681 | 1916 | |
| 1682 | 1917 | // get room record |
| 1683 | 1918 | $room_record = VikBooking::getRoomInfo($or['idroom']); |
| 1684 | 1919 | |
| 1920 | + // set room (total) units | |
| 1921 | + $roomUnits[$or['idroom']] = (int) ($room_record['units'] ?? 1); | |
| 1922 | + | |
| 1685 | 1923 | // check if the room is available |
| 1686 | 1924 | if (!VikBooking::roomBookable($or['idroom'], (($room_record['units'] ?? 0) - $units_minus_oper[$or['idroom']]), $room_stay_checkin, $room_stay_checkout)) { |
| 1687 | 1925 | $allbook = false; |
| 1688 | 1926 | $notavail[] = $room_record['name'] ?? '?'; |
| @@ -1706,26 +1944,31 @@ | ||
| 1706 | 1944 | $room_stay_checkout = $room_stay_dates[$ind]['checkout_ts'] ?: $room_stay_dates[$ind]['checkout']; |
| 1707 | 1945 | $room_stay_realback = $turnover_secs + $room_stay_checkout; |
| 1708 | 1946 | } |
| 1709 | 1947 | |
| 1710 | - // build busy record | |
| 1711 | - $busy_record = new stdClass; | |
| 1712 | - $busy_record->idroom = (int) $or['idroom']; | |
| 1713 | - $busy_record->checkin = (int) $room_stay_checkin; | |
| 1714 | - $busy_record->checkout = (int) $room_stay_checkout; | |
| 1715 | - $busy_record->realback = (int) $room_stay_realback; | |
| 1948 | + // determine the room units to occupy (to support closure re-confirmations) | |
| 1949 | + $bookUnitsCount = (int) (intval($booking['closure'] ?? 0) ? ($roomUnits[$or['idroom']] ?? 1) : 1); | |
| 1716 | 1950 | |
| 1717 | - // store busy record and obtain the newly created ID | |
| 1718 | - $dbo->insertObject('#__vikbooking_busy', $busy_record, 'id'); | |
| 1719 | - $lid = $busy_record->id ?? 0; | |
| 1951 | + for ($bi = 1; $bi <= $bookUnitsCount; $bi++) { | |
| 1952 | + // build busy record | |
| 1953 | + $busy_record = new stdClass; | |
| 1954 | + $busy_record->idroom = (int) $or['idroom']; | |
| 1955 | + $busy_record->checkin = (int) $room_stay_checkin; | |
| 1956 | + $busy_record->checkout = (int) $room_stay_checkout; | |
| 1957 | + $busy_record->realback = (int) $room_stay_realback; | |
| 1720 | 1958 | |
| 1721 | - // build busy relation record | |
| 1722 | - $obusy_record = new stdClass; | |
| 1723 | - $obusy_record->idorder = (int) $booking['id']; | |
| 1724 | - $obusy_record->idbusy = (int) $lid; | |
| 1959 | + // store busy record and obtain the newly created ID | |
| 1960 | + $dbo->insertObject('#__vikbooking_busy', $busy_record, 'id'); | |
| 1961 | + $lid = $busy_record->id ?? 0; | |
| 1725 | 1962 | |
| 1726 | - // store busy relation record | |
| 1727 | - $dbo->insertObject('#__vikbooking_ordersbusy', $obusy_record, 'id'); | |
| 1963 | + // build busy relation record | |
| 1964 | + $obusy_record = new stdClass; | |
| 1965 | + $obusy_record->idorder = (int) $booking['id']; | |
| 1966 | + $obusy_record->idbusy = (int) $lid; | |
| 1967 | + | |
| 1968 | + // store busy relation record | |
| 1969 | + $dbo->insertObject('#__vikbooking_ordersbusy', $obusy_record, 'id'); | |
| 1970 | + } | |
| 1728 | 1971 | } |
| 1729 | 1972 | |
| 1730 | 1973 | // delete temporarily locked records, if any |
| 1731 | 1974 | $dbo->setQuery( |
| @@ -1971,10 +2214,15 @@ | ||
| 1971 | 2214 | * @since 1.18.2 (J) - 1.8.2 (WP) |
| 1972 | 2215 | */ |
| 1973 | 2216 | public function bookingModifiable(int $booking_id, $new_checkin, $new_checkout) |
| 1974 | 2217 | { |
| 1975 | - // load all booking rooms | |
| 1976 | - $booking_rooms = VikBooking::loadOrdersRoomsData($booking_id); | |
| 2218 | + // attempt to access previous booking registry to reduce queries | |
| 2219 | + if ($this->prevBookingRegistry && $this->prevBookingRegistry->getID() == $booking_id) { | |
| 2220 | + $booking_rooms = $this->prevBookingRegistry->getRooms(); | |
| 2221 | + } else { | |
| 2222 | + // load all booking rooms | |
| 2223 | + $booking_rooms = VikBooking::loadOrdersRoomsData($booking_id); | |
| 2224 | + } | |
| 1977 | 2225 | |
| 1978 | 2226 | if (!$booking_rooms) { |
| 1979 | 2227 | throw new Exception('Could not find any rooms booked within the reservation.', 500); |
| 1980 | 2228 | } |
| @@ -2004,8 +2252,36 @@ | ||
| 2004 | 2252 | foreach ($booking_rooms as $booking_room) { |
| 2005 | 2253 | $rooms_units_counter[$booking_room['idroom']] = ($rooms_counter[$booking_room['idroom']] ?? -1) + 1; |
| 2006 | 2254 | } |
| 2007 | 2255 | |
| 2256 | + // check if the booking modification involves other rooms | |
| 2257 | + $roomsPool = $this->getRoomsPool(); | |
| 2258 | + $oldRoomIds = array_map('intval', array_column($booking_rooms, 'idroom')); | |
| 2259 | + $newRoomIds = array_values(array_unique(array_map('intval', array_column($roomsPool, 'id')))); | |
| 2260 | + $diffIds = array_diff($newRoomIds, $oldRoomIds); | |
| 2261 | + foreach ($diffIds as $newId) { | |
| 2262 | + $unitsRequested = 0; | |
| 2263 | + $totUnits = 0; | |
| 2264 | + foreach ($roomsPool as $roomData) { | |
| 2265 | + if ($newId == $roomData['id']) { | |
| 2266 | + $unitsRequested++; | |
| 2267 | + $totUnits = (int) ($roomData['units'] ?? 1); | |
| 2268 | + } | |
| 2269 | + } | |
| 2270 | + if (!$unitsRequested) { | |
| 2271 | + continue; | |
| 2272 | + } | |
| 2273 | + | |
| 2274 | + // update units counter map for multiple units requested | |
| 2275 | + $rooms_units_counter[$newId] = $unitsRequested - 1; | |
| 2276 | + | |
| 2277 | + // push additional room for availability validation | |
| 2278 | + $booking_rooms[] = [ | |
| 2279 | + 'idroom' => $newId, | |
| 2280 | + 'tot_units' => $totUnits, | |
| 2281 | + ]; | |
| 2282 | + } | |
| 2283 | + | |
| 2008 | 2284 | // check the availability for each room booked |
| 2009 | 2285 | $rooms_parsed = []; |
| 2010 | 2286 | foreach ($booking_rooms as $booking_room) { |
| 2011 | 2287 | if (in_array($booking_room['idroom'], $rooms_parsed)) { |
| @@ -2733,8 +3009,11 @@ | ||
| 2733 | 3009 | $email = !empty($inj_customer['email']) ? $inj_customer['email'] : ''; |
| 2734 | 3010 | $country = !empty($inj_customer['country']) ? $inj_customer['country'] : ''; |
| 2735 | 3011 | $phone = !empty($inj_customer['phone']) ? $inj_customer['phone'] : ''; |
| 2736 | 3012 | $gender = !empty($inj_customer['gender']) ? $inj_customer['gender'] : ''; |
| 3013 | + $address = !empty($inj_customer['address']) ? $inj_customer['address'] : ''; | |
| 3014 | + $city = !empty($inj_customer['city']) ? $inj_customer['city'] : ''; | |
| 3015 | + $zip = !empty($inj_customer['zip']) ? $inj_customer['zip'] : ''; | |
| 2737 | 3016 | |
| 2738 | 3017 | // custom fields |
| 2739 | 3018 | $q = "SELECT * FROM `#__vikbooking_custfields` ORDER BY `ordering` ASC;"; |
| 2740 | 3019 | $dbo->setQuery($q); |
| @@ -2770,8 +3049,23 @@ | ||
| 2770 | 3049 | // inject the customer gender value |
| 2771 | 3050 | $customer_extrainfo['gender'] = strtoupper($gender); |
| 2772 | 3051 | } |
| 2773 | 3052 | |
| 3053 | + if (!empty($address)) { | |
| 3054 | + // inject extra info | |
| 3055 | + $customer_extrainfo['address'] = strlen((string) $address) > 200 ? substr($address, 0, 200) : $address; | |
| 3056 | + } | |
| 3057 | + | |
| 3058 | + if (!empty($city)) { | |
| 3059 | + // inject extra info | |
| 3060 | + $customer_extrainfo['city'] = strlen((string) $city) > 50 ? substr($city, 0, 50) : $city; | |
| 3061 | + } | |
| 3062 | + | |
| 3063 | + if (!empty($zip)) { | |
| 3064 | + // inject extra info | |
| 3065 | + $customer_extrainfo['zip'] = strlen((string) $zip) > 10 ? substr($zip, 0, 10) : $zip; | |
| 3066 | + } | |
| 3067 | + | |
| 2774 | 3068 | $cpin = VikBooking::getCPinInstance(); |
| 2775 | 3069 | $cpin->is_admin = true; |
| 2776 | 3070 | $cpin->setCustomerExtraInfo($customer_extrainfo); |
| 2777 | 3071 | $cpin->saveCustomerDetails($first_name, $last_name, $email, $phone, $country, $customer_cfields); |
| @@ -2920,9 +3214,9 @@ | ||
| 2920 | 3214 | // total and taxes should be multiplied by the number of rooms booked when using a website rate plan |
| 2921 | 3215 | if ($set_closed) { |
| 2922 | 3216 | $roomTotalValue *= $roomDetails['units']; |
| 2923 | 3217 | $roomTotalTax *= $roomDetails['units']; |
| 2924 | - } elseif ($num_rooms > 1 && $num_rooms <= $roomDetails['units']) { | |
| 3218 | + } elseif (!$this->isMultiRoom() && $num_rooms > 1 && $num_rooms <= $roomDetails['units']) { | |
| 2925 | 3219 | $roomTotalValue *= $num_rooms; |
| 2926 | 3220 | $roomTotalTax *= $num_rooms; |
| 2927 | 3221 | } |
| 2928 | 3222 | } |
| @@ -3035,17 +3329,25 @@ | ||
| 3035 | 3329 | ]; |
| 3036 | 3330 | } |
| 3037 | 3331 | |
| 3038 | 3332 | /** |
| 3039 | - * Stores the booking and room-booking records. | |
| 3040 | - * If no errors, the newly generated booking id is set. | |
| 3333 | + * Stores (or updates) the booking and room-booking records. | |
| 3334 | + * If no errors, the newly generated or updated booking id is set. | |
| 3041 | 3335 | * |
| 3042 | - * @param array $rooms_pool list of rooms involved. | |
| 3336 | + * @param array $rooms_pool List of rooms involved. | |
| 3337 | + * @param ?int $bookingId The booking ID being updated, if any. | |
| 3043 | 3338 | * |
| 3044 | 3339 | * @return bool |
| 3340 | + * | |
| 3341 | + * @since 1.18.11 (J) - 1.8.11 (WP) added $bookingId argument to support updates. | |
| 3045 | 3342 | */ |
| 3046 | - protected function storeReservationRecords(array $rooms_pool) | |
| 3343 | + protected function storeReservationRecords(array $rooms_pool, ?int $bookingId = null) | |
| 3047 | 3344 | { |
| 3345 | + if ($bookingId && !$this->prevBookingRegistry) { | |
| 3346 | + $this->setError('Missing previous booking registry for update.'); | |
| 3347 | + return false; | |
| 3348 | + } | |
| 3349 | + | |
| 3048 | 3350 | $dbo = JFactory::getDbo(); |
| 3049 | 3351 | |
| 3050 | 3352 | // access properties |
| 3051 | 3353 | $set_closed = $this->get('set_closed', 0); |
| @@ -3080,17 +3382,22 @@ | ||
| 3080 | 3382 | $set_taxes = (float) $this->get('_total_tax', 0); |
| 3081 | 3383 | $set_city_tax = (float) $this->get('_total_city_tax', 0); |
| 3082 | 3384 | $set_fees = (float) $this->get('_total_fees', 0); |
| 3083 | 3385 | |
| 3084 | - // booking creation date | |
| 3085 | - $now_ts = time(); | |
| 3086 | - if ($created_on = $this->get('created_on')) { | |
| 3087 | - if (is_int($created_on)) { | |
| 3088 | - // timestamp expected | |
| 3089 | - $now_ts = $created_on; | |
| 3090 | - } elseif (preg_match('/^[0-9]{4}\-[0-9]{2}\-[0-9]{2}/', (string) $created_on)) { | |
| 3091 | - // date in military format expected, with or without the time | |
| 3092 | - $now_ts = strtotime($created_on); | |
| 3386 | + if ($bookingId) { | |
| 3387 | + // updating a booking should not change the creation date | |
| 3388 | + $now_ts = $this->prevBookingRegistry->getProperty('ts', time()); | |
| 3389 | + } else { | |
| 3390 | + // booking creation date | |
| 3391 | + $now_ts = time(); | |
| 3392 | + if ($created_on = $this->get('created_on')) { | |
| 3393 | + if (is_int($created_on)) { | |
| 3394 | + // timestamp expected | |
| 3395 | + $now_ts = $created_on; | |
| 3396 | + } elseif (preg_match('/^[0-9]{4}\-[0-9]{2}\-[0-9]{2}/', (string) $created_on)) { | |
| 3397 | + // date in military format expected, with or without the time | |
| 3398 | + $now_ts = strtotime($created_on); | |
| 3399 | + } | |
| 3093 | 3400 | } |
| 3094 | 3401 | } |
| 3095 | 3402 | |
| 3096 | 3403 | // stay dates |
| @@ -3108,8 +3415,12 @@ | ||
| 3108 | 3415 | $customer_data = ($inj_customer['data'] ?? null) ?: ''; |
| 3109 | 3416 | $customer_email = ($inj_customer['email'] ?? null) ?: ''; |
| 3110 | 3417 | $country_code = ($inj_customer['country'] ?? null) ?: ''; |
| 3111 | 3418 | $phone_number = ($inj_customer['phone'] ?? null) ?: ''; |
| 3419 | + $phone_alias = ($inj_customer['phone_alias'] ?? null) ?: ''; | |
| 3420 | + $address = ($inj_customer['address'] ?? null) ?: ''; | |
| 3421 | + $city = ($inj_customer['city'] ?? null) ?: ''; | |
| 3422 | + $zip = ($inj_customer['zip'] ?? null) ?: ''; | |
| 3112 | 3423 | |
| 3113 | 3424 | if ($set_closed) { |
| 3114 | 3425 | // get the possibly custom "customer notes" |
| 3115 | 3426 | $customer_notes = $customer_data; |
| @@ -3131,14 +3442,25 @@ | ||
| 3131 | 3442 | } |
| 3132 | 3443 | if ($customer_email) { |
| 3133 | 3444 | $customer_data .= "eMail: {$customer_email}\n"; |
| 3134 | 3445 | } |
| 3446 | + if ($phone_number) { | |
| 3447 | + $customer_data .= "Phone: {$phone_number}\n"; | |
| 3448 | + } elseif ($phone_alias) { | |
| 3449 | + $customer_data .= "Phone-alias: {$phone_alias}\n"; | |
| 3450 | + } | |
| 3135 | 3451 | if ($country_code) { |
| 3136 | 3452 | $customer_data .= "Country: {$country_code}\n"; |
| 3137 | 3453 | } |
| 3138 | - if ($phone_number) { | |
| 3139 | - $customer_data .= "Phone: {$phone_number}\n"; | |
| 3454 | + if ($address) { | |
| 3455 | + $customer_data .= "Address: {$address}\n"; | |
| 3140 | 3456 | } |
| 3457 | + if ($city) { | |
| 3458 | + $customer_data .= "City: {$city}\n"; | |
| 3459 | + } | |
| 3460 | + if ($zip) { | |
| 3461 | + $customer_data .= "Zip: {$zip}\n"; | |
| 3462 | + } | |
| 3141 | 3463 | $customer_data = rtrim($customer_data, "\n"); |
| 3142 | 3464 | } |
| 3143 | 3465 | |
| 3144 | 3466 | // ensure the country code is in the right format |
| @@ -3151,10 +3473,10 @@ | ||
| 3151 | 3473 | // this looks like an unknown country name |
| 3152 | 3474 | $country_code = ''; |
| 3153 | 3475 | } |
| 3154 | 3476 | |
| 3155 | - // generate booking SID | |
| 3156 | - $sid = VikBooking::getSecretLink(); | |
| 3477 | + // generate booking SID, unless we are updating | |
| 3478 | + $sid = !$bookingId ? VikBooking::getSecretLink() : null; | |
| 3157 | 3479 | |
| 3158 | 3480 | // assign room specific unit |
| 3159 | 3481 | $set_room_indexes = !$set_closed ? VikBooking::autoRoomUnit() : false; |
| 3160 | 3482 | $num_rooms = $num_rooms > 0 ? $num_rooms : 1; |
| @@ -3205,12 +3527,28 @@ | ||
| 3205 | 3527 | // attempt to get the default payment method |
| 3206 | 3528 | if (!$paymentmeth && ($auto_paymeth || $status == 'standby')) { |
| 3207 | 3529 | // get the default payment method, if any |
| 3208 | 3530 | $paymentmeth = $this->getDefaultPaymentMethod($auto_paymeth); |
| 3531 | + } elseif ($paymentmeth && is_int($paymentmeth)) { | |
| 3532 | + // convert the payment integer ID into a string with the actual record name | |
| 3533 | + $paymentmeth = $this->getPaymentMethodValue($paymentmeth); | |
| 3209 | 3534 | } |
| 3210 | 3535 | |
| 3536 | + if ($bookingId) { | |
| 3537 | + // when updating, delete all room booking records involved | |
| 3538 | + // to allow the re-generation of the updated records | |
| 3539 | + $this->delete(['booking_id' => $bookingId], $reCreating = true); | |
| 3540 | + } | |
| 3541 | + | |
| 3211 | 3542 | // prepare booking record |
| 3212 | 3543 | $booking = new stdClass; |
| 3544 | + | |
| 3545 | + if ($bookingId) { | |
| 3546 | + // inject current booking ID for update | |
| 3547 | + $booking->id = $bookingId; | |
| 3548 | + } | |
| 3549 | + | |
| 3550 | + // set booking record properties | |
| 3213 | 3551 | $booking->custdata = $customer_data; |
| 3214 | 3552 | $booking->ts = $now_ts; |
| 3215 | 3553 | $booking->status = $status; |
| 3216 | 3554 | $booking->days = $daysdiff; |
| @@ -3231,8 +3569,9 @@ | ||
| 3231 | 3569 | $booking->tot_city_taxes = $set_city_tax ?: ($this->get('tot_city_taxes') ? ((float) $this->get('tot_city_taxes')) : null); |
| 3232 | 3570 | $booking->tot_fees = $set_fees ?: ($this->get('tot_fees') ? ((float) $this->get('tot_fees')) : null); |
| 3233 | 3571 | $booking->tot_damage_dep = $this->get('tot_damage_dep') ? ((float) $this->get('tot_damage_dep')) : null; |
| 3234 | 3572 | $booking->phone = $phone_number; |
| 3573 | + $booking->phone_alias = $phone_alias ?: null; | |
| 3235 | 3574 | $booking->cmms = $this->get('cmms') ? ((float) $this->get('cmms')) : null; |
| 3236 | 3575 | $booking->closure = ($status == 'standby' ? 0 : ($set_closed || $units_closed ? 1 : 0)); |
| 3237 | 3576 | $booking->payable = $this->get('payable') ? ((float) $this->get('payable')) : null; |
| 3238 | 3577 | $booking->refund = $this->get('refund') ? ((float) $this->get('refund')) : null; |
| @@ -3294,17 +3633,22 @@ | ||
| 3294 | 3633 | return false; |
| 3295 | 3634 | } |
| 3296 | 3635 | } |
| 3297 | 3636 | |
| 3298 | - // store booking record | |
| 3299 | - $dbo->insertObject('#__vikbooking_orders', $booking, 'id'); | |
| 3637 | + if ($bookingId) { | |
| 3638 | + // update booking record | |
| 3639 | + $dbo->updateObject('#__vikbooking_orders', $booking, 'id'); | |
| 3640 | + } else { | |
| 3641 | + // store booking record | |
| 3642 | + $dbo->insertObject('#__vikbooking_orders', $booking, 'id'); | |
| 3643 | + } | |
| 3300 | 3644 | |
| 3301 | 3645 | if (empty($booking->id)) { |
| 3302 | - $this->setError('Could not store the reservation record'); | |
| 3646 | + $this->setError('Could not save reservation record.'); | |
| 3303 | 3647 | return false; |
| 3304 | 3648 | } |
| 3305 | 3649 | |
| 3306 | - // get the newly generated booking ID | |
| 3650 | + // get the newly generated (or just updated) booking ID | |
| 3307 | 3651 | $newoid = $booking->id; |
| 3308 | 3652 | |
| 3309 | 3653 | // set the new booking ID |
| 3310 | 3654 | $this->setNewBookingID($newoid); |
| @@ -3317,10 +3661,12 @@ | ||
| 3317 | 3661 | if ($status == 'confirmed') { |
| 3318 | 3662 | // check if some of the rooms booked have shared calendars |
| 3319 | 3663 | VikBooking::updateSharedCalendars($newoid, [$room['id']], $checkin_ts, $checkout_ts); |
| 3320 | 3664 | |
| 3321 | - // generate and set confirmation number | |
| 3322 | - $confirmnumber = VikBooking::generateConfirmNumber($newoid, true); | |
| 3665 | + if (!$bookingId) { | |
| 3666 | + // generate and set confirmation number for the newly created confirmed reservation | |
| 3667 | + $confirmnumber = VikBooking::generateConfirmNumber($newoid, true); | |
| 3668 | + } | |
| 3323 | 3669 | |
| 3324 | 3670 | // store busy record-booking relations |
| 3325 | 3671 | foreach ($insertedbusy as $lid) { |
| 3326 | 3672 | $obusy_record = new stdClass; |
| @@ -3332,10 +3678,10 @@ | ||
| 3332 | 3678 | } |
| 3333 | 3679 | } |
| 3334 | 3680 | |
| 3335 | 3681 | // store room booking records |
| 3682 | + $room_indexes_usemap = []; | |
| 3336 | 3683 | foreach ($rooms_pool as $rind => $nowroom) { |
| 3337 | - $room_indexes_usemap = []; | |
| 3338 | 3684 | for ($r = 1; $r <= $or_forend; $r++) { |
| 3339 | 3685 | // determine room-level stay dates |
| 3340 | 3686 | $room_stay_checkin = $nowroom['checkin_ts'] ?? $roomsData[$rind]['checkin'] ?? $checkin_ts; |
| 3341 | 3687 | $room_stay_checkout = $nowroom['checkout_ts'] ?? $roomsData[$rind]['checkout'] ?? $checkout_ts; |
| @@ -3390,10 +3736,10 @@ | ||
| 3390 | 3736 | } |
| 3391 | 3737 | } |
| 3392 | 3738 | |
| 3393 | 3739 | // room guests |
| 3394 | - $room_adults = (int) (isset($adults_map[$r]) && empty($split_stay_data) ? $adults_map[$r] : ($roomsData[$rind]['adults'] ?? $adults)); | |
| 3395 | - $room_children = (int) (isset($children_map[$r]) && empty($split_stay_data) ? $children_map[$r] : ($roomsData[$rind]['children'] ?? $children)); | |
| 3740 | + $room_adults = (int) ($roomsData[$rind]['adults'] ?? (isset($adults_map[$r]) && empty($split_stay_data) ? $adults_map[$r] : $adults)); | |
| 3741 | + $room_children = (int) ($roomsData[$rind]['children'] ?? (isset($children_map[$r]) && empty($split_stay_data) ? $children_map[$r] : $children)); | |
| 3396 | 3742 | $use_children_age = (array) ($roomsData[$rind]['children_age'] ?? $children_age); |
| 3397 | 3743 | |
| 3398 | 3744 | // number of pets per room |
| 3399 | 3745 | $room_pets = (int) ($roomsData[$rind]['pets'] ?? 0); |
| @@ -3544,10 +3890,11 @@ | ||
| 3544 | 3890 | $cpin->setNewCustomerId($customer_id); |
| 3545 | 3891 | } |
| 3546 | 3892 | $cpin->saveCustomerBooking($newoid); |
| 3547 | 3893 | |
| 3548 | - // Booking History | |
| 3894 | + // handle booking history | |
| 3549 | 3895 | $history_obj = VikBooking::getBookingHistoryInstance($newoid); |
| 3896 | + | |
| 3550 | 3897 | $forced_reason = !empty($forced_reason) ? " {$forced_reason}" : $forced_reason; |
| 3551 | 3898 | $caller_id = $now_user->name ? "({$now_user->name})" : ''; |
| 3552 | 3899 | if ($this->getCaller()) { |
| 3553 | 3900 | $caller_id = '(' . $this->getCaller() . ')'; |
| @@ -3558,27 +3905,62 @@ | ||
| 3558 | 3905 | if ($this->get('idquote')) { |
| 3559 | 3906 | // mention the quote ID in the history description |
| 3560 | 3907 | $forced_reason .= sprintf(' %s #%d', JText::translate('VBO_BTYPE_QUOTE'), (int) $this->get('idquote')); |
| 3561 | 3908 | } |
| 3562 | - $history_obj->store('NB', trim($caller_id . $forced_reason)); | |
| 3563 | 3909 | |
| 3910 | + // tell whether alterations were detected, in case of booking update | |
| 3911 | + $alterationsDetected = false; | |
| 3912 | + | |
| 3913 | + if ($bookingId) { | |
| 3914 | + // booking updated event | |
| 3915 | + $historyPrevBooking = $this->prevBookingRegistry->getData(); | |
| 3916 | + $historyPrevBooking['rooms_info'] = $this->prevBookingRegistry->getRooms(); | |
| 3917 | + $history_obj | |
| 3918 | + ->setPrevBooking($historyPrevBooking) | |
| 3919 | + ->store('MB', trim($caller_id . $forced_reason) . ' ' . VikBooking::getLogBookingModification($historyPrevBooking)); | |
| 3920 | + | |
| 3921 | + // construct a new booking registry, and inject the booking snapshot prior updating | |
| 3922 | + $currentRegistry = VBOBookingRegistry::getInstance(['id' => $bookingId], [], $historyPrevBooking); | |
| 3923 | + // detect alterations (include room-level) | |
| 3924 | + $alterationsDetected = $currentRegistry->detectAlterations(true); | |
| 3925 | + } else { | |
| 3926 | + // new booking event | |
| 3927 | + $history_obj->store('NB', trim($caller_id . $forced_reason)); | |
| 3928 | + } | |
| 3929 | + | |
| 3564 | 3930 | if ($status == 'confirmed' || ($status == 'standby' && class_exists('VCMRequestAvailability'))) { |
| 3565 | 3931 | // Invoke Channel Manager |
| 3566 | 3932 | $vcm_autosync = VikBooking::vcmAutoUpdate(); |
| 3567 | 3933 | if ($vcm_autosync > 0) { |
| 3568 | 3934 | $vcm_obj = VikBooking::getVcmInvoker(); |
| 3569 | - $vcm_obj->setOids([$newoid])->setSyncType('new'); | |
| 3570 | - $sync_result = $vcm_obj->doSync(); | |
| 3571 | - if ($sync_result === false) { | |
| 3572 | - // set error message | |
| 3573 | - $vcm_err = $vcm_obj->getError(); | |
| 3574 | - $this->setError(JText::translate('VBCHANNELMANAGERRESULTKO') . (!empty($vcm_err) ? ' - ' . $vcm_err : '')); | |
| 3575 | 3935 | |
| 3576 | - // return true because the booking was actually stored | |
| 3577 | - return true; | |
| 3936 | + // tell if sync is actually needed | |
| 3937 | + $sync_needed = true; | |
| 3938 | + if ($bookingId && $alterationsDetected === false) { | |
| 3939 | + // do not waste connections when nothing sensitive was updated | |
| 3940 | + $sync_needed = false; | |
| 3578 | 3941 | } |
| 3579 | - } elseif (is_file(VCM_SITE_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'synch.vikbooking.php')) { | |
| 3580 | - // set the necessary action to invoke VCM | |
| 3942 | + | |
| 3943 | + if ($sync_needed) { | |
| 3944 | + if ($bookingId) { | |
| 3945 | + // sync for an updated booking, after something was truly modified | |
| 3946 | + $vcm_obj->setOids([$bookingId])->setSyncType('modify')->setOriginalBooking($historyPrevBooking ?? []); | |
| 3947 | + } else { | |
| 3948 | + // always sync for a new booking | |
| 3949 | + $vcm_obj->setOids([$newoid])->setSyncType('new'); | |
| 3950 | + } | |
| 3951 | + | |
| 3952 | + // run operation | |
| 3953 | + $sync_result = $vcm_obj->doSync(); | |
| 3954 | + | |
| 3955 | + if ($sync_result === false) { | |
| 3956 | + // set error message, but do NOT return false at this point | |
| 3957 | + $vcm_err = $vcm_obj->getError(); | |
| 3958 | + $this->setError(JText::translate('VBCHANNELMANAGERRESULTKO') . (!empty($vcm_err) ? ' - ' . $vcm_err : '')); | |
| 3959 | + } | |
| 3960 | + } | |
| 3961 | + } elseif (!$bookingId && is_file(VCM_SITE_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'synch.vikbooking.php')) { | |
| 3962 | + // set the necessary action to invoke VCM (only when creating a new booking) | |
| 3581 | 3963 | $vcm_sync_url = 'index.php?option=com_vikbooking&task=invoke_vcm&stype=new&cid[]=' . $newoid . '&returl=' . urlencode('index.php?option=com_vikbooking&task=calendar&cid[]=' . $room['id']); |
| 3582 | 3964 | |
| 3583 | 3965 | $this->setChannelManagerAction(JText::translate('VBCHANNELMANAGERINVOKEASK') . ' <button type="button" class="btn btn-primary" onclick="document.location.href=\'' . $vcm_sync_url . '\';">' . JText::translate('VBCHANNELMANAGERSENDRQ') . '</button>'); |
| 3584 | 3966 | } |
| @@ -3585,13 +3967,13 @@ | ||
| 3585 | 3967 | } |
| 3586 | 3968 | |
| 3587 | 3969 | if (VikBooking::isAdmin()) { |
| 3588 | 3970 | /** |
| 3589 | - * Trigger event to allow third party plugins to intercept the admin new booking event. | |
| 3971 | + * Trigger event to allow third party plugins to intercept the admin new/updated booking event. | |
| 3590 | 3972 | * |
| 3591 | 3973 | * @since 1.16.8 (J) - 1.6.8 (WP) |
| 3592 | 3974 | */ |
| 3593 | - VBOFactory::getPlatform()->getDispatcher()->trigger('onAfterCreateNewBookingAdmin', [$newoid]); | |
| 3975 | + VBOFactory::getPlatform()->getDispatcher()->trigger($bookingId ? 'onAfterUpdateBookingAdmin' : 'onAfterCreateNewBookingAdmin', [$newoid]); | |
| 3594 | 3976 | } |
| 3595 | 3977 | |
| 3596 | 3978 | return true; |
| 3597 | 3979 | } |
| @@ -3637,6 +4019,27 @@ | ||
| 3637 | 4019 | } |
| 3638 | 4020 | |
| 3639 | 4021 | // nothing was found |
| 3640 | 4022 | return ''; |
| 4023 | + } | |
| 4024 | + | |
| 4025 | + /** | |
| 4026 | + * Given a payment method record ID, returns the string | |
| 4027 | + * in the format for saving it along the reservation. | |
| 4028 | + * | |
| 4029 | + * @param int $id The record ID. | |
| 4030 | + * | |
| 4031 | + * @return ?string ID=Name calculated value, or null. | |
| 4032 | + * | |
| 4033 | + * @since 1.18.15 (J) - 1.8.15 (WP) | |
| 4034 | + */ | |
| 4035 | + protected function getPaymentMethodValue(int $id) | |
| 4036 | + { | |
| 4037 | + $record = VBOMvcModel::getInstance('payment')->getItem($id); | |
| 4038 | + | |
| 4039 | + if ($record) { | |
| 4040 | + return sprintf('%s=%s', $record->id, $record->name); | |
| 4041 | + } | |
| 4042 | + | |
| 4043 | + return null; | |
| 3641 | 4044 | } |
| 3642 | 4045 | } |