| @@ -32,8 +32,11 @@ | ||
| 32 | 32 | case 'operators': |
| 33 | 33 | case 'tableaux': |
| 34 | 34 | case 'precheckin': |
| 35 | 35 | case 'revstay': |
| 36 | + case 'chat': | |
| 37 | + case 'tinyurl': | |
| 38 | + case 'quote': | |
| 36 | 39 | VikRequest::setVar('view', $view); |
| 37 | 40 | break; |
| 38 | 41 | default: |
| 39 | 42 | VikRequest::setVar('view', 'vikbooking'); |
| @@ -300,8 +303,12 @@ | ||
| 300 | 303 | foreach ($cfields as $cf) { |
| 301 | 304 | $user_inp_val = VikRequest::getString('vbf' . $cf['id'], '', 'request'); |
| 302 | 305 | if (intval($cf['isemail']) == 1 && $emailwasfound == false) { |
| 303 | 306 | $useremail = trim($user_inp_val); |
| 307 | + if (!preg_match('/^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/', $useremail)) { | |
| 308 | + showSelectVb('Invalid email address provided.'); | |
| 309 | + return; | |
| 310 | + } | |
| 304 | 311 | $emailwasfound = true; |
| 305 | 312 | } |
| 306 | 313 | if ($cf['isnominative'] == 1) { |
| 307 | 314 | if (strlen(str_replace(' ', '', trim($user_inp_val)))) { |
| @@ -533,8 +540,9 @@ | ||
| 533 | 540 | $isdue = 0; |
| 534 | 541 | $tot_taxes = 0; |
| 535 | 542 | $tot_city_taxes = 0; |
| 536 | 543 | $tot_fees = 0; |
| 544 | + $tot_damage_dep = 0; | |
| 537 | 545 | $rooms_costs_map = []; |
| 538 | 546 | $is_package = (bool)(count($pkg) > 0); |
| 539 | 547 | if ($is_package === true) { |
| 540 | 548 | foreach ($rooms as $num => $r) { |
| @@ -562,8 +570,37 @@ | ||
| 562 | 570 | $rooms_costs_map[$num] = $tar[0]['cost']; |
| 563 | 571 | } |
| 564 | 572 | } |
| 565 | 573 | |
| 574 | + /** | |
| 575 | + * Custom check-in/out times due to late check-out/early check-in options or custom listing settings. | |
| 576 | + * | |
| 577 | + * @since 1.17.2 (J) - 1.7.2 (WP) | |
| 578 | + * @since 1.18.3 (J) - 1.8.3 (WP) added support to check-in/out times at listing-level. | |
| 579 | + */ | |
| 580 | + $custom_checkinout = []; | |
| 581 | + if (count($rooms) === 1) { | |
| 582 | + $listing_custom_checkin = VikBooking::getRoomParam('checkin', ($rooms[(key($rooms))]['params'] ?? '')); | |
| 583 | + $listing_custom_checkout = VikBooking::getRoomParam('checkout', ($rooms[(key($rooms))]['params'] ?? '')); | |
| 584 | + if ($listing_custom_checkin) { | |
| 585 | + // set listing-level check-in time in seconds | |
| 586 | + $listing_custom_checkin_parts = explode(':', $listing_custom_checkin); | |
| 587 | + $listing_custom_checkin = (intval($listing_custom_checkin_parts[0]) * 3600) + (intval($listing_custom_checkin_parts[1]) * 60); | |
| 588 | + } | |
| 589 | + if ($listing_custom_checkout) { | |
| 590 | + // set listing-level check-out time in seconds | |
| 591 | + $listing_custom_checkout_parts = explode(':', $listing_custom_checkout); | |
| 592 | + $listing_custom_checkout = (intval($listing_custom_checkout_parts[0]) * 3600) + (intval($listing_custom_checkout_parts[1]) * 60); | |
| 593 | + } | |
| 594 | + if ($listing_custom_checkin || $listing_custom_checkout) { | |
| 595 | + // set listing-level check-in/out times in seconds | |
| 596 | + $custom_checkinout = [ | |
| 597 | + (int) $listing_custom_checkin, | |
| 598 | + (int) $listing_custom_checkout, | |
| 599 | + ]; | |
| 600 | + } | |
| 601 | + } | |
| 602 | + | |
| 566 | 603 | $selopt = []; |
| 567 | 604 | $optstr = []; |
| 568 | 605 | $children_age = []; |
| 569 | 606 | if (!empty($poptionals)) { |
| @@ -659,9 +696,9 @@ | ||
| 659 | 696 | * Count pets, if any. |
| 660 | 697 | * |
| 661 | 698 | * @since 1.16.2 (J) - 1.6.2 (WP) |
| 662 | 699 | */ |
| 663 | - if ($opt_params && isset($opt_params['pet_fee']) && $opt_params['pet_fee']) { | |
| 700 | + if ($opt_params['pet_fee'] ?? 0) { | |
| 664 | 701 | $tot_pets = 1; |
| 665 | 702 | if ($actopt[0]['hmany'] > 0 && $stept[1] > 1) { |
| 666 | 703 | $tot_pets = (int)$stept[1]; |
| 667 | 704 | } |
| @@ -667,9 +704,35 @@ | ||
| 667 | 704 | } |
| 668 | 705 | $arrpeople[$rnoid[0]]['pets'] = $tot_pets; |
| 669 | 706 | } |
| 670 | 707 | |
| 708 | + /** | |
| 709 | + * Custom check-in/out times due to late check-out/early check-in options. | |
| 710 | + * | |
| 711 | + * @since 1.17.2 (J) - 1.7.2 (WP) | |
| 712 | + */ | |
| 713 | + if (($opt_params['custom_checkinout'] ?? 0) && (($opt_params['set_checkin'] ?? 0) || ($opt_params['set_checkout'] ?? 0))) { | |
| 714 | + $custom_checkinout = [ | |
| 715 | + ($opt_params['set_checkin'] ?? 0), | |
| 716 | + ($opt_params['set_checkout'] ?? 0), | |
| 717 | + ]; | |
| 718 | + } | |
| 719 | + | |
| 671 | 720 | $realcost = ($actopt[0]['perperson'] == 1 ? ($realcost * $arrpeople[$rnoid[0]]['adults']) : $realcost); |
| 721 | + | |
| 722 | + /** | |
| 723 | + * Trigger event to allow third party plugins to apply a custom calculation for the option/extra fee or tax. | |
| 724 | + * | |
| 725 | + * @since 1.17.7 (J) - 1.7.7 (WP) | |
| 726 | + */ | |
| 727 | + $use_room_cost = $is_package === true ? $pkg['cost'] : ($tars[$rnoid[0]][0]['cost'] ?? 0); | |
| 728 | + $custom_calc_booking = ['days' => $use_los]; | |
| 729 | + $custom_calc_booking_room = array_merge($arrpeople[$rnoid[0]], ['room_cost' => $use_room_cost]); | |
| 730 | + $custom_calculation = VBOFactory::getPlatform()->getDispatcher()->filter('onCalculateBookingOptionFeeCost', [$realcost, &$actopt[0], $custom_calc_booking, $custom_calc_booking_room]); | |
| 731 | + if ($custom_calculation) { | |
| 732 | + $realcost = (float) $custom_calculation[0]; | |
| 733 | + } | |
| 734 | + | |
| 672 | 735 | $opt_minus_iva = VikBooking::sayOptionalsMinusIva($realcost, $actopt[0]['idiva']); |
| 673 | 736 | $tmpopr = VikBooking::sayOptionalsPlusIva($realcost, $actopt[0]['idiva']); |
| 674 | 737 | if ($actopt[0]['is_citytax'] == 1) { |
| 675 | 738 | $tot_city_taxes += $opt_minus_iva; |
| @@ -674,8 +737,10 @@ | ||
| 674 | 737 | if ($actopt[0]['is_citytax'] == 1) { |
| 675 | 738 | $tot_city_taxes += $opt_minus_iva; |
| 676 | 739 | } elseif ($actopt[0]['is_fee'] == 1) { |
| 677 | 740 | $tot_fees += $opt_minus_iva; |
| 741 | + } elseif ($opt_params['damagedep'] ?? 0) { | |
| 742 | + $tot_damage_dep += $opt_minus_iva; | |
| 678 | 743 | } |
| 679 | 744 | // VBO 1.11 - always calculate the amount of tax no matter if this is already a tax or a fee |
| 680 | 745 | if ($tmpopr == $realcost) { |
| 681 | 746 | $tot_taxes += ($realcost - $opt_minus_iva); |
| @@ -708,10 +773,10 @@ | ||
| 708 | 773 | if (!empty($coupon) && $valid_customer_coupon) { |
| 709 | 774 | $coupondateok = true; |
| 710 | 775 | if (strlen((string)$coupon['datevalid'])) { |
| 711 | 776 | $dateparts = explode("-", $coupon['datevalid']); |
| 712 | - $pickinfo = getdate($pcheckin); | |
| 713 | - $dropinfo = getdate($pcheckout); | |
| 777 | + $pickinfo = $checkin_info; | |
| 778 | + $dropinfo = $checkout_info; | |
| 714 | 779 | $checkpick = mktime(0, 0, 0, $pickinfo['mon'], $pickinfo['mday'], $pickinfo['year']); |
| 715 | 780 | $checkdrop = mktime(0, 0, 0, $dropinfo['mon'], $dropinfo['mday'], $dropinfo['year']); |
| 716 | 781 | if (!($checkpick >= $dateparts[0] && $checkpick <= $dateparts[1] && $checkdrop >= $dateparts[0] && $checkdrop <= $dateparts[1])) { |
| 717 | 782 | $coupondateok = false; |
| @@ -739,9 +804,22 @@ | ||
| 739 | 804 | } |
| 740 | 805 | if ($coupon['maxtotord'] > 0 && $isdue > $coupon['maxtotord']) { |
| 741 | 806 | $coupontotok = false; |
| 742 | 807 | } |
| 808 | + | |
| 809 | + /** | |
| 810 | + * Trigger event to allow third-party plugins to implement additional coupon validations or manipulation. | |
| 811 | + * | |
| 812 | + * @since 1.16.7 (J) - 1.6.7 (WP) | |
| 813 | + */ | |
| 743 | 814 | if ($coupontotok) { |
| 815 | + $coupon_validation = VBOFactory::getPlatform()->getDispatcher()->filter('onValidateCouponCode', [&$coupon]); | |
| 816 | + if (is_array($coupon_validation) && in_array(false, $coupon_validation, true)) { | |
| 817 | + $coupontotok = false; | |
| 818 | + } | |
| 819 | + } | |
| 820 | + | |
| 821 | + if ($coupontotok) { | |
| 744 | 822 | $usedcoupon = true; |
| 745 | 823 | if ($coupon['percentot'] == 1) { |
| 746 | 824 | // percent value |
| 747 | 825 | $minuscoupon = 100 - $coupon['value']; |
| @@ -750,14 +828,17 @@ | ||
| 750 | 828 | * |
| 751 | 829 | * @since 1.13.5 (J) - 1.3.5 (WP) |
| 752 | 830 | * @since 1.14.3 (J) - 1.4.3 (WP) we also exclude the amount of taxes beside the mandatory fees. |
| 753 | 831 | * @since 1.16.0 (J) - 1.6.0 (WP) taxes are proportionally calculated when coupon before tax. |
| 832 | + * @since 1.16.8 (J) - 1.6.8 (WP) with coupon before taxes, discounted amount calculation is an equal subtration. | |
| 754 | 833 | */ |
| 755 | - $tot_net = ($isdue - $tot_taxes - $tot_city_taxes - $tot_fees); | |
| 834 | + $prev_isdue = $isdue; | |
| 835 | + $tot_net = ($isdue - $tot_taxes - $tot_city_taxes - $tot_fees - $tot_damage_dep); | |
| 756 | 836 | $coupondiscount = ($coupon['excludetaxes'] ? $tot_net : $isdue) * $coupon['value'] / 100; |
| 757 | 837 | $isdue = ($coupon['excludetaxes'] ? $tot_net : $isdue) * $minuscoupon / 100; |
| 758 | - $tot_taxes = $coupon['excludetaxes'] ? ($tot_taxes * ($tot_net - $coupondiscount) / $tot_net) : ($tot_taxes * $minuscoupon / 100); | |
| 759 | - $isdue += $coupon['excludetaxes'] ? ($tot_taxes + $tot_city_taxes + $tot_fees) : 0; | |
| 838 | + $tot_taxes = $coupon['excludetaxes'] ? ($tot_taxes * ($tot_net - $coupondiscount) / $tot_net) : $tot_taxes; | |
| 839 | + $isdue += $coupon['excludetaxes'] ? ($tot_taxes + $tot_city_taxes + $tot_fees + $tot_damage_dep) : 0; | |
| 840 | + $coupondiscount = abs($prev_isdue - $isdue); | |
| 760 | 841 | } else { |
| 761 | 842 | // total value |
| 762 | 843 | $coupondiscount = $coupon['value']; |
| 763 | 844 | // isdue : taxes = coupon_discount : x |
| @@ -775,9 +856,9 @@ | ||
| 775 | 856 | } |
| 776 | 857 | |
| 777 | 858 | $strisdue = number_format($isdue, 2) . 'vikbooking'; |
| 778 | 859 | $ptotdue = number_format($ptotdue, 2) . 'vikbooking'; |
| 779 | - if ($strisdue != $ptotdue) { | |
| 860 | + if ($strisdue != $ptotdue && abs(round($isdue, 2) - round((float) $ptotdue, 2)) <= 0.01) { | |
| 780 | 861 | showSelectVb(JText::translate('VBINCONGRTOT')); |
| 781 | 862 | return; |
| 782 | 863 | } |
| 783 | 864 | |
| @@ -790,9 +871,9 @@ | ||
| 790 | 871 | // modify booking |
| 791 | 872 | $mod_booking = []; |
| 792 | 873 | $skip_busy_ids = []; |
| 793 | 874 | $cur_mod = $session->get('vboModBooking', ''); |
| 794 | - if (is_array($cur_mod) && count($cur_mod)) { | |
| 875 | + if (is_array($cur_mod) && $cur_mod) { | |
| 795 | 876 | $mod_booking = $cur_mod; |
| 796 | 877 | $skip_busy_ids = VikBooking::loadBookingBusyIds($mod_booking['id']); |
| 797 | 878 | } |
| 798 | 879 | |
| @@ -833,9 +914,9 @@ | ||
| 833 | 914 | $session->set('vikbooking_order_checkin', $pcheckin); |
| 834 | 915 | $session->set('vikbooking_order_checkout', $pcheckout); |
| 835 | 916 | |
| 836 | 917 | // handle booking sid and customer information summary string |
| 837 | - $sid = count($mod_booking) ? $mod_booking['sid'] : VikBooking::getSecretLink(); | |
| 918 | + $sid = $mod_booking ? $mod_booking['sid'] : VikBooking::getSecretLink(); | |
| 838 | 919 | $custdata = VikBooking::buildCustData($arrcustdata, "\r\n"); |
| 839 | 920 | |
| 840 | 921 | if (VBOPlatformDetection::isWordPress()) { |
| 841 | 922 | $viklink = JURI::root() . "index.php?option=com_vikbooking&view=booking&sid=" . $sid . "&ts=" . $nowts . (!empty($pnodep) ? "&nodep=".$pnodep : "") . (!empty($pitemid) ? "&Itemid=" . $pitemid : ""); |
| @@ -876,9 +957,9 @@ | ||
| 876 | 957 | $rooms_involved[] = $room_booked['id']; |
| 877 | 958 | } |
| 878 | 959 | } |
| 879 | 960 | |
| 880 | - $must_payment = count($mod_booking) ? false : VikBooking::areTherePayments($rooms_involved); | |
| 961 | + $must_payment = $mod_booking ? false : VikBooking::areTherePayments($rooms_involved); | |
| 881 | 962 | $payment = []; |
| 882 | 963 | if ($must_payment) { |
| 883 | 964 | $payment = VikBooking::getPayment($pgpayid); |
| 884 | 965 | } |
| @@ -943,9 +1024,32 @@ | ||
| 943 | 1024 | // push data to tracker for conversion |
| 944 | 1025 | $vbo_tracker = VikBooking::getTracker(); |
| 945 | 1026 | $vbo_tracker->pushDates($pcheckin, $pcheckout, $pdays)->pushParty($arrpeople)->pushData('idcustomer', $cpin->getNewCustomerId()); |
| 946 | 1027 | |
| 947 | - if (!(count($mod_booking) > 0) && ((!empty($payment) && intval($payment['setconfirmed']) == 1) || !$must_payment || ($usedcoupon && $isdue <= 0))) { | |
| 1028 | + /** | |
| 1029 | + * Custom check-in/out times due to late check-out/early check-in options. | |
| 1030 | + * | |
| 1031 | + * @since 1.17.2 (J) - 1.7.2 (WP) | |
| 1032 | + */ | |
| 1033 | + if ($custom_checkinout) { | |
| 1034 | + // overwrite check-in and/or check-out timestamp(s) | |
| 1035 | + if ($custom_checkinout[0] >= 3600) { | |
| 1036 | + // overwrite check-in timestamp | |
| 1037 | + $time_hours = floor($custom_checkinout[0] / 3600); | |
| 1038 | + $time_minutes = floor(($custom_checkinout[0] - ($time_hours * 3600)) / 60); | |
| 1039 | + $pcheckin = mktime($time_hours, $time_minutes, 0, $checkin_info['mon'], $checkin_info['mday'], $checkin_info['year']); | |
| 1040 | + } | |
| 1041 | + if ($custom_checkinout[1] >= 3600) { | |
| 1042 | + // overwrite check-out timestamp | |
| 1043 | + $time_hours = floor($custom_checkinout[1] / 3600); | |
| 1044 | + $time_minutes = floor(($custom_checkinout[1] - ($time_hours * 3600)) / 60); | |
| 1045 | + $pcheckout = mktime($time_hours, $time_minutes, 0, $checkout_info['mon'], $checkout_info['mday'], $checkout_info['year']); | |
| 1046 | + // overwrite "realback" timestamp as well | |
| 1047 | + $realback = $turnover_secs + $pcheckout; | |
| 1048 | + } | |
| 1049 | + } | |
| 1050 | + | |
| 1051 | + if (!$mod_booking && ((!empty($payment) && intval($payment['setconfirmed']) == 1) || !$must_payment || ($usedcoupon && $isdue <= 0))) { | |
| 948 | 1052 | // we enter this statement to set the booking to Confirmed when: no booking modification and, payment selected sets status to confirmed or no payments enabled or 100% coupon |
| 949 | 1053 | $arrbusy = []; |
| 950 | 1054 | foreach ($rooms as $num => $r) { |
| 951 | 1055 | // determine the number of nights of stay and dates to consider |
| @@ -987,19 +1091,29 @@ | ||
| 987 | 1091 | $booking_record->idpayment = !empty($payment) ? ($payment['id'] . '=' . $payment['name']) : null; |
| 988 | 1092 | $booking_record->ujid = $currentUser->id; |
| 989 | 1093 | $booking_record->coupon = $usedcoupon === true ? $strcouponeff : null; |
| 990 | 1094 | $booking_record->roomsnum = count($rooms); |
| 991 | - $booking_record->total = $isdue; | |
| 1095 | + $booking_record->total = (float)$isdue; | |
| 992 | 1096 | $booking_record->channel = is_array($vcmchanneldata) && !empty($vcmchanneldata['name']) ? $vcmchanneldata['name'] : null; |
| 993 | 1097 | $booking_record->lang = $langtag; |
| 994 | 1098 | $booking_record->country = !empty($usercountry) ? $usercountry : null; |
| 995 | - $booking_record->tot_taxes = $tot_taxes; | |
| 996 | - $booking_record->tot_city_taxes = $tot_city_taxes; | |
| 997 | - $booking_record->tot_fees = $tot_fees; | |
| 1099 | + $booking_record->tot_taxes = (float)$tot_taxes; | |
| 1100 | + $booking_record->tot_city_taxes = (float)$tot_city_taxes; | |
| 1101 | + $booking_record->tot_fees = (float)$tot_fees; | |
| 1102 | + if ($tot_damage_dep) { | |
| 1103 | + $booking_record->tot_damage_dep = (float) $tot_damage_dep; | |
| 1104 | + } | |
| 998 | 1105 | $booking_record->phone = $phone_number; |
| 999 | 1106 | $booking_record->pkg = $is_package === true ? (int)$pkg['id'] : null; |
| 1000 | 1107 | $booking_record->split_stay = !empty($split_stay) ? 1 : 0; |
| 1001 | 1108 | |
| 1109 | + /** | |
| 1110 | + * Trigger event to allow third party plugins to overwrite any booking property before it gets created. | |
| 1111 | + * | |
| 1112 | + * @since 1.18.3 (J) - 1.8.3 (WP) | |
| 1113 | + */ | |
| 1114 | + VBOFactory::getPlatform()->getDispatcher()->trigger('onBeforeCreateBookingRecord', [$booking_record, $rooms, $tars, $selopt, $arrpeople]); | |
| 1115 | + | |
| 1002 | 1116 | $dbo->insertObject('#__vikbooking_orders', $booking_record, 'id'); |
| 1003 | 1117 | |
| 1004 | 1118 | if (!isset($booking_record->id)) { |
| 1005 | 1119 | showSelectVb('Critical error while saving the booking. Please try again'); |
| @@ -1026,9 +1140,9 @@ | ||
| 1026 | 1140 | // assign room specific unit |
| 1027 | 1141 | $room_indexes = $set_room_indexes === true ? VikBooking::getRoomUnitNumsAvailable(array('id' => $neworderid, 'checkin' => $pcheckin, 'checkout' => $pcheckout), $r['id']) : array(); |
| 1028 | 1142 | $use_ind_key = 0; |
| 1029 | 1143 | $force_rindex = 0; |
| 1030 | - if (count($room_indexes) && isset($room_indexes_forcemap[$r['id']])) { | |
| 1144 | + if ($room_indexes && isset($room_indexes_forcemap[$r['id']])) { | |
| 1031 | 1145 | // an index for this same room was forced already, reset the values |
| 1032 | 1146 | foreach ($room_indexes as $av_key => $av_index) { |
| 1033 | 1147 | if (in_array((int)$av_index, $room_indexes_forcemap[$r['id']])) { |
| 1034 | 1148 | unset($room_indexes[$av_key]); |
| @@ -1035,9 +1149,9 @@ | ||
| 1035 | 1149 | } |
| 1036 | 1150 | } |
| 1037 | 1151 | $room_indexes = array_values($room_indexes); |
| 1038 | 1152 | } |
| 1039 | - if (count($room_indexes)) { | |
| 1153 | + if ($room_indexes) { | |
| 1040 | 1154 | if (count($proomindex) == count($rooms) && !empty($proomindex[($num - 1)])) { |
| 1041 | 1155 | // exact distinctive feature index selected |
| 1042 | 1156 | foreach ($room_indexes as $av_index) { |
| 1043 | 1157 | if ((int)$av_index == (int)$proomindex[($num - 1)]) { |
| @@ -1089,9 +1203,9 @@ | ||
| 1089 | 1203 | $oroom_record->t_last_name = $t_last_name; |
| 1090 | 1204 | $oroom_record->roomindex = null; |
| 1091 | 1205 | if ($force_rindex) { |
| 1092 | 1206 | $oroom_record->roomindex = $force_rindex; |
| 1093 | - } elseif (count($room_indexes) && isset($room_indexes[$use_ind_key])) { | |
| 1207 | + } elseif ($room_indexes && isset($room_indexes[$use_ind_key])) { | |
| 1094 | 1208 | $oroom_record->roomindex = (int)$room_indexes[$use_ind_key]; |
| 1095 | 1209 | } |
| 1096 | 1210 | $oroom_record->pkg_id = ($is_package === true ? (int)$pkg['id'] : null); |
| 1097 | 1211 | $oroom_record->pkg_name = ($is_package === true ? $pkg['name'] : null); |
| @@ -1100,9 +1214,9 @@ | ||
| 1100 | 1214 | $oroom_record->room_cost = (array_key_exists($num, $rooms_costs_map) ? $rooms_costs_map[$num] : null); |
| 1101 | 1215 | |
| 1102 | 1216 | $dbo->insertObject('#__vikbooking_ordersrooms', $oroom_record, 'id'); |
| 1103 | 1217 | |
| 1104 | - if (count($room_indexes)) { | |
| 1218 | + if ($room_indexes) { | |
| 1105 | 1219 | $room_indexes_usemap[$r['id']]++; |
| 1106 | 1220 | } |
| 1107 | 1221 | } |
| 1108 | 1222 | |
| @@ -1134,9 +1248,9 @@ | ||
| 1134 | 1248 | |
| 1135 | 1249 | //invoke VikChannelManager |
| 1136 | 1250 | if (is_file(VCM_SITE_PATH . DIRECTORY_SEPARATOR . "helpers" . DIRECTORY_SEPARATOR . "synch.vikbooking.php")) { |
| 1137 | 1251 | require_once(VCM_SITE_PATH . DIRECTORY_SEPARATOR . "helpers" . DIRECTORY_SEPARATOR . "synch.vikbooking.php"); |
| 1138 | - $vcm = new synchVikBooking($neworderid); | |
| 1252 | + $vcm = new SynchVikBooking($neworderid); | |
| 1139 | 1253 | $vcm->setPushType('new')->sendRequest(); |
| 1140 | 1254 | } |
| 1141 | 1255 | |
| 1142 | 1256 | // VBO 1.11 - push data to tracker for conversion |
| @@ -1143,9 +1257,9 @@ | ||
| 1143 | 1257 | $vbo_tracker->pushData('idorder', $neworderid)->closeTrack(); |
| 1144 | 1258 | $vbo_tracker->resetTrack(); |
| 1145 | 1259 | |
| 1146 | 1260 | $app->redirect(JRoute::rewrite("index.php?option=com_vikbooking&view=booking&sid=" . $sid . "&ts=" . $nowts . (!empty($pnodep) ? "&nodep=".$pnodep : "") . (!empty($pitemid) ? "&Itemid=" . $pitemid : ""), false)); |
| 1147 | - } elseif (count($mod_booking) > 0) { | |
| 1261 | + } elseif ($mod_booking) { | |
| 1148 | 1262 | // booking modification statement |
| 1149 | 1263 | // get current orders-busy relations |
| 1150 | 1264 | $old_busy_ids = []; |
| 1151 | 1265 | $q = "SELECT `idbusy` FROM `#__vikbooking_ordersbusy` WHERE `idorder`=".(int)$mod_booking['id'].";"; |
| @@ -1200,9 +1314,9 @@ | ||
| 1200 | 1314 | // assign room specific unit |
| 1201 | 1315 | $room_indexes = $set_room_indexes === true ? VikBooking::getRoomUnitNumsAvailable(array('id' => $mod_booking['id'], 'checkin' => $pcheckin, 'checkout' => $pcheckout), $r['id']) : array(); |
| 1202 | 1316 | $use_ind_key = 0; |
| 1203 | 1317 | $force_rindex = 0; |
| 1204 | - if (count($room_indexes) && isset($room_indexes_forcemap[$r['id']])) { | |
| 1318 | + if ($room_indexes && isset($room_indexes_forcemap[$r['id']])) { | |
| 1205 | 1319 | // an index for this same room was forced already, reset the values |
| 1206 | 1320 | foreach ($room_indexes as $av_key => $av_index) { |
| 1207 | 1321 | if (in_array((int)$av_index, $room_indexes_forcemap[$r['id']])) { |
| 1208 | 1322 | unset($room_indexes[$av_key]); |
| @@ -1209,9 +1323,9 @@ | ||
| 1209 | 1323 | } |
| 1210 | 1324 | } |
| 1211 | 1325 | $room_indexes = array_values($room_indexes); |
| 1212 | 1326 | } |
| 1213 | - if (count($room_indexes)) { | |
| 1327 | + if ($room_indexes) { | |
| 1214 | 1328 | if (count($proomindex) == count($rooms) && !empty($proomindex[($num - 1)])) { |
| 1215 | 1329 | // exact distinctive feature index selected |
| 1216 | 1330 | foreach ($room_indexes as $av_index) { |
| 1217 | 1331 | if ((int)$av_index == (int)$proomindex[($num - 1)]) { |
| @@ -1263,9 +1377,9 @@ | ||
| 1263 | 1377 | $oroom_record->t_last_name = $t_last_name; |
| 1264 | 1378 | $oroom_record->roomindex = null; |
| 1265 | 1379 | if ($force_rindex) { |
| 1266 | 1380 | $oroom_record->roomindex = $force_rindex; |
| 1267 | - } elseif (count($room_indexes) && isset($room_indexes[$use_ind_key])) { | |
| 1381 | + } elseif ($room_indexes && isset($room_indexes[$use_ind_key])) { | |
| 1268 | 1382 | $oroom_record->roomindex = (int)$room_indexes[$use_ind_key]; |
| 1269 | 1383 | } |
| 1270 | 1384 | $oroom_record->pkg_id = ($is_package === true ? (int)$pkg['id'] : null); |
| 1271 | 1385 | $oroom_record->pkg_name = ($is_package === true ? $pkg['name'] : null); |
| @@ -1274,9 +1388,9 @@ | ||
| 1274 | 1388 | $oroom_record->room_cost = (array_key_exists($num, $rooms_costs_map) ? $rooms_costs_map[$num] : null); |
| 1275 | 1389 | |
| 1276 | 1390 | $dbo->insertObject('#__vikbooking_ordersrooms', $oroom_record, 'id'); |
| 1277 | 1391 | |
| 1278 | - if (count($room_indexes)) { | |
| 1392 | + if ($room_indexes) { | |
| 1279 | 1393 | $room_indexes_usemap[$r['id']]++; |
| 1280 | 1394 | } |
| 1281 | 1395 | } |
| 1282 | 1396 | |
| @@ -1288,12 +1402,43 @@ | ||
| 1288 | 1402 | if ($mod_booking['total'] < $isdue) { |
| 1289 | 1403 | $mod_paymcount++; |
| 1290 | 1404 | } |
| 1291 | 1405 | |
| 1292 | - $q = "UPDATE `#__vikbooking_orders` SET `custdata`=".$dbo->quote($custdata).",`ts`='".$nowts."',`days`=".$dbo->quote($pdays).",`checkin`=".$dbo->quote($pcheckin).",`checkout`=".$dbo->quote($pcheckout).",`custmail`=".$dbo->quote($useremail).",`ujid`='".$currentUser->id."',`coupon`=".($usedcoupon === true ? $dbo->quote($strcouponeff) : "NULL").",`roomsnum`='".count($rooms)."',`total`='".$isdue."',`channel`=".(is_array($vcmchanneldata) ? $dbo->quote($vcmchanneldata['name']) : (!empty($mod_booking['channel']) ? $dbo->quote($mod_booking['channel']) : 'NULL')).",`paymcount`=".$mod_paymcount.",`adminnotes`=".$dbo->quote($mod_notes).",`lang`=".$dbo->quote($langtag).",`country`=".(!empty($usercountry) ? $dbo->quote($usercountry) : 'NULL').",`tot_taxes`='".$tot_taxes."',`tot_city_taxes`='".$tot_city_taxes."',`tot_fees`='".$tot_fees."',`phone`=".$dbo->quote($phone_number).",`pkg`=".($is_package === true ? (int)$pkg['id'] : "NULL")." WHERE `id`=".(int)$mod_booking['id'].";"; | |
| 1406 | + $q = $dbo->getQuery(true) | |
| 1407 | + ->update($dbo->qn('#__vikbooking_orders')) | |
| 1408 | + ->set($dbo->qn('custdata') . ' = ' . $dbo->q($custdata)) | |
| 1409 | + ->set($dbo->qn('ts') . ' = ' . $nowts) | |
| 1410 | + ->set($dbo->qn('days') . ' = ' . $pdays) | |
| 1411 | + ->set($dbo->qn('checkin') . ' = ' . $dbo->q($pcheckin)) | |
| 1412 | + ->set($dbo->qn('checkout') . ' = ' . $dbo->q($pcheckout)) | |
| 1413 | + ->set($dbo->qn('custmail') . ' = ' . $dbo->q($useremail)) | |
| 1414 | + ->set($dbo->qn('ujid') . ' = ' . (int) $currentUser->id) | |
| 1415 | + ->set($dbo->qn('coupon') . ' = ' . ($usedcoupon === true ? $dbo->q($strcouponeff) : 'NULL')) | |
| 1416 | + ->set($dbo->qn('roomsnum') . ' = ' . count($rooms)) | |
| 1417 | + ->set($dbo->qn('total') . ' = ' . $isdue) | |
| 1418 | + ->set($dbo->qn('channel') . ' = ' . (is_array($vcmchanneldata) ? $dbo->q($vcmchanneldata['name']) : (!empty($mod_booking['channel']) ? $dbo->q($mod_booking['channel']) : 'NULL'))) | |
| 1419 | + ->set($dbo->qn('paymcount') . ' = ' . $mod_paymcount) | |
| 1420 | + ->set($dbo->qn('adminnotes') . ' = ' . $dbo->q($mod_notes)) | |
| 1421 | + ->set($dbo->qn('lang') . ' = ' . $dbo->q($langtag)) | |
| 1422 | + ->set($dbo->qn('country') . ' = ' . (!empty($usercountry) ? $dbo->q($usercountry) : 'NULL')) | |
| 1423 | + ->set($dbo->qn('tot_taxes') . ' = ' . $tot_taxes) | |
| 1424 | + ->set($dbo->qn('tot_city_taxes') . ' = ' . $tot_city_taxes) | |
| 1425 | + ->set($dbo->qn('tot_fees') . ' = ' . $tot_fees) | |
| 1426 | + ->set($dbo->qn('tot_damage_dep') . ' = ' . $tot_damage_dep) | |
| 1427 | + ->set($dbo->qn('phone') . ' = ' . $dbo->q($phone_number)) | |
| 1428 | + ->set($dbo->qn('pkg') . ' = ' . ($is_package === true ? (int) $pkg['id'] : 'NULL')) | |
| 1429 | + ->where($dbo->qn('id') . ' = ' . (int) $mod_booking['id']); | |
| 1430 | + | |
| 1293 | 1431 | $dbo->setQuery($q); |
| 1294 | 1432 | $dbo->execute(); |
| 1295 | 1433 | |
| 1434 | + /** | |
| 1435 | + * Trigger event to allow third party plugins to run after the booking is modified. | |
| 1436 | + * | |
| 1437 | + * @since 1.18.3 (J) - 1.8.3 (WP) | |
| 1438 | + */ | |
| 1439 | + VBOFactory::getPlatform()->getDispatcher()->trigger('onAfterModifyBookingRecord', [$mod_booking, $rooms, $tars, $selopt, $arrpeople]); | |
| 1440 | + | |
| 1296 | 1441 | // remove the coupon used (should never been allowed for modifications) |
| 1297 | 1442 | if ($usedcoupon == true && $coupon['type'] == 2) { |
| 1298 | 1443 | $q = "DELETE FROM `#__vikbooking_coupons` WHERE `id`=".(int)$coupon['id'].";"; |
| 1299 | 1444 | $dbo->setQuery($q); |
| @@ -1305,9 +1450,9 @@ | ||
| 1305 | 1450 | // check if some of the rooms booked have shared calendars |
| 1306 | 1451 | VikBooking::updateSharedCalendars($mod_booking['id'], array(), $pcheckin, $pcheckout); |
| 1307 | 1452 | |
| 1308 | 1453 | //send email messages (admin and customer) and invoke SMS send |
| 1309 | - VikBooking::sendBookingEmail($mod_booking['id'], array('guest', 'admin')); | |
| 1454 | + VikBooking::sendBookingEmail($mod_booking['id'], array('guest', 'admin'), true, false, $type = 'modified'); | |
| 1310 | 1455 | |
| 1311 | 1456 | //SMS |
| 1312 | 1457 | VikBooking::sendBookingSMS($mod_booking['id']); |
| 1313 | 1458 | |
| @@ -1344,19 +1489,29 @@ | ||
| 1344 | 1489 | $booking_record->idpayment = !empty($payment) ? ($payment['id'] . '=' . $payment['name']) : null; |
| 1345 | 1490 | $booking_record->ujid = $currentUser->id; |
| 1346 | 1491 | $booking_record->coupon = $usedcoupon === true ? $strcouponeff : null; |
| 1347 | 1492 | $booking_record->roomsnum = count($rooms); |
| 1348 | - $booking_record->total = $isdue; | |
| 1493 | + $booking_record->total = (float)$isdue; | |
| 1349 | 1494 | $booking_record->channel = is_array($vcmchanneldata) && !empty($vcmchanneldata['name']) ? $vcmchanneldata['name'] : null; |
| 1350 | 1495 | $booking_record->lang = $langtag; |
| 1351 | 1496 | $booking_record->country = !empty($usercountry) ? $usercountry : null; |
| 1352 | - $booking_record->tot_taxes = $tot_taxes; | |
| 1353 | - $booking_record->tot_city_taxes = $tot_city_taxes; | |
| 1354 | - $booking_record->tot_fees = $tot_fees; | |
| 1497 | + $booking_record->tot_taxes = (float)$tot_taxes; | |
| 1498 | + $booking_record->tot_city_taxes = (float)$tot_city_taxes; | |
| 1499 | + $booking_record->tot_fees = (float)$tot_fees; | |
| 1500 | + if ($tot_damage_dep) { | |
| 1501 | + $booking_record->tot_damage_dep = (float) $tot_damage_dep; | |
| 1502 | + } | |
| 1355 | 1503 | $booking_record->phone = $phone_number; |
| 1356 | 1504 | $booking_record->pkg = $is_package === true ? (int)$pkg['id'] : null; |
| 1357 | 1505 | $booking_record->split_stay = !empty($split_stay) ? 1 : 0; |
| 1358 | 1506 | |
| 1507 | + /** | |
| 1508 | + * Trigger event to allow third party plugins to overwrite any booking property before it gets created. | |
| 1509 | + * | |
| 1510 | + * @since 1.18.3 (J) - 1.8.3 (WP) | |
| 1511 | + */ | |
| 1512 | + VBOFactory::getPlatform()->getDispatcher()->trigger('onBeforeCreateBookingRecord', [$booking_record, $rooms, $tars, $selopt, $arrpeople]); | |
| 1513 | + | |
| 1359 | 1514 | $dbo->insertObject('#__vikbooking_orders', $booking_record, 'id'); |
| 1360 | 1515 | |
| 1361 | 1516 | if (!isset($booking_record->id)) { |
| 1362 | 1517 | showSelectVb('Critical error while saving the booking. Please try again'); |
| @@ -1462,15 +1617,40 @@ | ||
| 1462 | 1617 | //SMS |
| 1463 | 1618 | VikBooking::sendBookingSMS($neworderid); |
| 1464 | 1619 | |
| 1465 | 1620 | //Booking History |
| 1466 | - VikBooking::getBookingHistoryInstance()->setBid($neworderid)->store('NP', 'IP: '.VikRequest::getVar('REMOTE_ADDR', '', 'server')); | |
| 1621 | + VikBooking::getBookingHistoryInstance()->setBid($neworderid)->store('NP', 'IP: ' . VikRequest::getVar('REMOTE_ADDR', '', 'server')); | |
| 1467 | 1622 | |
| 1623 | + /** | |
| 1624 | + * Invoke VikChannelManager also in case of pending reservations. | |
| 1625 | + * | |
| 1626 | + * @since 1.16.5 (J) - 1.6.5 (WP) | |
| 1627 | + * | |
| 1628 | + * @requires VCM >= 1.8.20 | |
| 1629 | + */ | |
| 1630 | + if (class_exists('VCMRequestAvailability')) { | |
| 1631 | + VikBooking::getVcmInvoker() | |
| 1632 | + ->setOids([$neworderid]) | |
| 1633 | + ->setSyncType('new') | |
| 1634 | + ->doSync(); | |
| 1635 | + } | |
| 1636 | + | |
| 1468 | 1637 | // VBO 1.11 - push data to tracker for conversion |
| 1469 | 1638 | $vbo_tracker->pushData('idorder', $neworderid)->closeTrack(); |
| 1470 | 1639 | $vbo_tracker->resetTrack(); |
| 1471 | 1640 | |
| 1472 | - $app->redirect(JRoute::rewrite("index.php?option=com_vikbooking&view=booking&sid=" . $sid . "&ts=" . $nowts . (!empty($pnodep) ? "&nodep=".$pnodep : "") . (!empty($pitemid) ? "&Itemid=" . $pitemid : ""), false)); | |
| 1641 | + // redirect URI to pending booking details | |
| 1642 | + $booking_details_uri = "index.php?option=com_vikbooking&view=booking&sid=" . $sid . "&ts=" . $nowts . (!empty($pnodep) ? "&nodep=".$pnodep : "") . (!empty($pitemid) ? "&Itemid=" . $pitemid : ""); | |
| 1643 | + | |
| 1644 | + /** | |
| 1645 | + * Trigger event to allow third-party plugins to manipulate the redirect URI. | |
| 1646 | + * | |
| 1647 | + * @since 1.17.2 (J) - 1.7.2 (WP) | |
| 1648 | + */ | |
| 1649 | + VBOFactory::getPlatform()->getDispatcher()->trigger('onRedirectOrder', [&$booking_details_uri, $neworderid]); | |
| 1650 | + | |
| 1651 | + // redirect to booking details page | |
| 1652 | + $app->redirect(JRoute::rewrite($booking_details_uri, false)); | |
| 1473 | 1653 | } |
| 1474 | 1654 | } |
| 1475 | 1655 | |
| 1476 | 1656 | public function vieworder() |
| @@ -1480,18 +1660,24 @@ | ||
| 1480 | 1660 | } |
| 1481 | 1661 | |
| 1482 | 1662 | public function cancelrequest() |
| 1483 | 1663 | { |
| 1664 | + if (!JSession::checkToken()) { | |
| 1665 | + // missing CSRF-proof token | |
| 1666 | + VBOHttpDocument::getInstance()->close(403, JText::translate('JINVALID_TOKEN')); | |
| 1667 | + } | |
| 1668 | + | |
| 1484 | 1669 | $dbo = JFactory::getDbo(); |
| 1485 | 1670 | $mainframe = JFactory::getApplication(); |
| 1671 | + | |
| 1486 | 1672 | $psid = VikRequest::getString('sid', '', 'request'); |
| 1487 | 1673 | $pidorder = VikRequest::getString('idorder', '', 'request'); |
| 1674 | + | |
| 1488 | 1675 | if (!empty($psid) && !empty($pidorder)) { |
| 1489 | 1676 | $q = "SELECT * FROM `#__vikbooking_orders` WHERE `id`=".intval($pidorder)." AND `sid`=".$dbo->quote($psid).";"; |
| 1490 | 1677 | $dbo->setQuery($q); |
| 1491 | - $dbo->execute(); | |
| 1492 | - if ($dbo->getNumRows() == 1) { | |
| 1493 | - $order = $dbo->loadAssocList(); | |
| 1678 | + $order = $dbo->loadAssocList(); | |
| 1679 | + if ($order) { | |
| 1494 | 1680 | $pemail = VikRequest::getString('email', '', 'request'); |
| 1495 | 1681 | $preason = VikRequest::getString('reason', '', 'request'); |
| 1496 | 1682 | if (!empty($pemail) && !empty($preason)) { |
| 1497 | 1683 | $to = VikBooking::getAdminMail(); |
| @@ -1597,9 +1783,9 @@ | ||
| 1597 | 1783 | } |
| 1598 | 1784 | |
| 1599 | 1785 | public function cron_exec() |
| 1600 | 1786 | { |
| 1601 | - if (defined('ABSPATH')) | |
| 1787 | + if (VBOPlatformDetection::isWordPress()) | |
| 1602 | 1788 | { |
| 1603 | 1789 | // in WordPress it is no more needed to schedule a server cron job |
| 1604 | 1790 | VBOHttpDocument::getInstance()->close(406, 'Cron jobs execution is scheduled by WordPress since VikBooking 1.5.10. Please remove any scheduled execution to this end-point.'); |
| 1605 | 1791 | } |
| @@ -1637,10 +1823,13 @@ | ||
| 1637 | 1823 | } |
| 1638 | 1824 | |
| 1639 | 1825 | public function notifypayment() |
| 1640 | 1826 | { |
| 1827 | + $app = JFactory::getApplication(); | |
| 1641 | 1828 | $dbo = JFactory::getDbo(); |
| 1642 | 1829 | |
| 1830 | + $session = JFactory::getSession(); | |
| 1831 | + | |
| 1643 | 1832 | $config = VBOFactory::getConfig(); |
| 1644 | 1833 | $av_helper = VikBooking::getAvailabilityInstance(); |
| 1645 | 1834 | |
| 1646 | 1835 | $psid = VikRequest::getString('sid', '', 'request'); |
| @@ -1672,18 +1861,16 @@ | ||
| 1672 | 1861 | $recipient_mail = $all_recipients; |
| 1673 | 1862 | } |
| 1674 | 1863 | } |
| 1675 | 1864 | |
| 1865 | + // load booking details | |
| 1676 | 1866 | $q = "SELECT * FROM `#__vikbooking_orders` WHERE (`sid`=" . $dbo->quote($psid) . " OR `idorderota`=" . $dbo->quote($psid) . ") AND `ts`=" . $dbo->quote($pts); |
| 1677 | 1867 | $dbo->setQuery($q, 0, 1); |
| 1678 | - $dbo->execute(); | |
| 1679 | - if (!$dbo->getNumRows()) { | |
| 1868 | + $row = $dbo->loadAssoc(); | |
| 1869 | + if (!$row) { | |
| 1680 | 1870 | VBOHttpDocument::getInstance()->close(404, 'Booking not found'); |
| 1681 | 1871 | } |
| 1682 | 1872 | |
| 1683 | - // load booking details | |
| 1684 | - $row = $dbo->loadAssoc(); | |
| 1685 | - | |
| 1686 | 1873 | // check if the language in use is the same as the one used during the checkout |
| 1687 | 1874 | if (!empty($row['lang'])) { |
| 1688 | 1875 | $lang = JFactory::getLanguage(); |
| 1689 | 1876 | if ($lang->getTag() != $row['lang']) { |
| @@ -1698,9 +1885,12 @@ | ||
| 1698 | 1885 | $vbo_tn = VikBooking::getTranslator(); |
| 1699 | 1886 | |
| 1700 | 1887 | if ($row['status'] == 'confirmed' && !(VikBooking::multiplePayments() && $row['paymcount'] > 0)) { |
| 1701 | 1888 | // booking can be paid only if not confirmed or if multiple payments are enabled and payment counter for booking greater than zero |
| 1702 | - VBOHttpDocument::getInstance()->close(409, 'Conflicting and unexpected payment validation for this reservation'); | |
| 1889 | + if (empty($row['idorderota']) || !empty($row['totpaid'])) { | |
| 1890 | + // accept only the first payment for an OTA reservation | |
| 1891 | + VBOHttpDocument::getInstance()->close(409, 'Conflicting and unexpected payment validation for this reservation'); | |
| 1892 | + } | |
| 1703 | 1893 | } |
| 1704 | 1894 | |
| 1705 | 1895 | /** |
| 1706 | 1896 | * Check split stay reservation data. |
| @@ -1731,11 +1921,10 @@ | ||
| 1731 | 1921 | |
| 1732 | 1922 | // load booked rooms |
| 1733 | 1923 | $q = "SELECT `or`.`id` AS `or_id`,`or`.`idroom`,`or`.`adults`,`or`.`children`,`or`.`idtar`,`or`.`optionals`,`or`.`roomindex`,`or`.`pkg_id`,`or`.`pkg_name`,`or`.`cust_cost`,`or`.`cust_idiva`,`or`.`extracosts`,`or`.`otarplan`,`r`.`id` AS `r_reference_id`,`r`.`name`,`r`.`img`,`r`.`idcarat`,`r`.`fromadult`,`r`.`toadult`,`r`.`params` FROM `#__vikbooking_ordersrooms` AS `or`,`#__vikbooking_rooms` AS `r` WHERE `or`.`idorder`=" . $row['id'] . " AND `or`.`idroom`=`r`.`id` ORDER BY `or`.`id` ASC;"; |
| 1734 | 1924 | $dbo->setQuery($q); |
| 1735 | - $dbo->execute(); | |
| 1736 | - if ($dbo->getNumRows() > 0) { | |
| 1737 | - $orderrooms = $dbo->loadAssocList(); | |
| 1925 | + $orderrooms = $dbo->loadAssocList(); | |
| 1926 | + if ($orderrooms) { | |
| 1738 | 1927 | $vbo_tn->translateContents($orderrooms, '#__vikbooking_rooms', array('id' => 'r_reference_id')); |
| 1739 | 1928 | foreach ($orderrooms as $kor => $or) { |
| 1740 | 1929 | $num = $kor + 1; |
| 1741 | 1930 | $rooms[$num] = $or; |
| @@ -1757,13 +1946,13 @@ | ||
| 1757 | 1946 | } |
| 1758 | 1947 | |
| 1759 | 1948 | $q = "SELECT * FROM `#__vikbooking_dispcost` WHERE `id`=" . (int)$or['idtar']; |
| 1760 | 1949 | $dbo->setQuery($q, 0, 1); |
| 1761 | - $dbo->execute(); | |
| 1762 | - if (!$dbo->getNumRows()) { | |
| 1950 | + $tar = $dbo->loadAssocList(); | |
| 1951 | + if (!$tar) { | |
| 1763 | 1952 | continue; |
| 1764 | 1953 | } |
| 1765 | - $tar = $dbo->loadAssocList(); | |
| 1954 | + | |
| 1766 | 1955 | $tar = VikBooking::applySeasonsRoom($tar, $room_checkin, $room_checkout); |
| 1767 | 1956 | |
| 1768 | 1957 | // apply OBP rules |
| 1769 | 1958 | $tar = VBORoomHelper::getInstance()->applyOBPRules($tar, $or, $or['adults']); |
| @@ -1776,12 +1965,34 @@ | ||
| 1776 | 1965 | // inject values |
| 1777 | 1966 | $row['order_rooms'] = $orderrooms; |
| 1778 | 1967 | $row['fares'] = $tars; |
| 1779 | 1968 | |
| 1969 | + // invoke the payment method class | |
| 1970 | + $exppay = explode('=', ($row['idpayment'] ?? '')); | |
| 1971 | + $payment = VikBooking::getPayment($exppay[0], $vbo_tn); | |
| 1972 | + | |
| 1973 | + /** | |
| 1974 | + * Scan the booking and related rooms for damage deposit payment data. | |
| 1975 | + * | |
| 1976 | + * @since 1.17.6 (J) - 1.7.6 (WP) | |
| 1977 | + */ | |
| 1978 | + $damage_deposit_payment = VBORoomHelper::getInstance()->getDamageDepositSplitPayment($row, $orderrooms); | |
| 1979 | + | |
| 1980 | + if ($app->input->getBool('dd') && !empty($damage_deposit_payment['payment_window']['pay_id'])) { | |
| 1981 | + // load the proper payment driver | |
| 1982 | + $payment = VikBooking::getPayment($damage_deposit_payment['payment_window']['pay_id']) ?: $payment; | |
| 1983 | + } | |
| 1984 | + | |
| 1985 | + if (!$payment) { | |
| 1986 | + VBOHttpDocument::getInstance()->close(500, 'Could not load payment processor for validation.'); | |
| 1987 | + } | |
| 1988 | + | |
| 1989 | + // calculate booking totals | |
| 1780 | 1990 | $isdue = 0; |
| 1781 | 1991 | $tot_taxes = 0; |
| 1782 | 1992 | $tot_city_taxes = 0; |
| 1783 | 1993 | $tot_fees = 0; |
| 1994 | + $tot_damage_dep = 0; | |
| 1784 | 1995 | $pricestr = []; |
| 1785 | 1996 | $optstr = []; |
| 1786 | 1997 | foreach ($orderrooms as $kor => $or) { |
| 1787 | 1998 | $num = $kor + 1; |
| @@ -1827,12 +2038,16 @@ | ||
| 1827 | 2038 | } |
| 1828 | 2039 | $stept = explode(":", $oo); |
| 1829 | 2040 | $q = "SELECT * FROM `#__vikbooking_optionals` WHERE `id`=" . $dbo->quote($stept[0]) . ";"; |
| 1830 | 2041 | $dbo->setQuery($q); |
| 1831 | - $dbo->execute(); | |
| 1832 | - if ($dbo->getNumRows() == 1) { | |
| 1833 | - $actopt = $dbo->loadAssocList(); | |
| 2042 | + $actopt = $dbo->loadAssocList(); | |
| 2043 | + if ($actopt) { | |
| 1834 | 2044 | $vbo_tn->translateContents($actopt, '#__vikbooking_optionals'); |
| 2045 | + | |
| 2046 | + // option params | |
| 2047 | + $opt_params = !empty($actopt[0]['oparams']) ? json_decode($actopt[0]['oparams'], true) : []; | |
| 2048 | + $opt_params = is_array($opt_params) ? $opt_params : []; | |
| 2049 | + | |
| 1835 | 2050 | $chvar = ''; |
| 1836 | 2051 | if (!empty($actopt[0]['ageintervals']) && $or['children'] > 0 && strstr($stept[1], '-') != false) { |
| 1837 | 2052 | $optagenames = VikBooking::getOptionIntervalsAges($actopt[0]['ageintervals']); |
| 1838 | 2053 | $optagepcent = VikBooking::getOptionIntervalsPercentage($actopt[0]['ageintervals']); |
| @@ -1882,8 +2097,19 @@ | ||
| 1882 | 2097 | } |
| 1883 | 2098 | if ($actopt[0]['perperson'] == 1) { |
| 1884 | 2099 | $realcost = $realcost * $or['adults']; |
| 1885 | 2100 | } |
| 2101 | + | |
| 2102 | + /** | |
| 2103 | + * Trigger event to allow third party plugins to apply a custom calculation for the option/extra fee or tax. | |
| 2104 | + * | |
| 2105 | + * @since 1.17.7 (J) - 1.7.7 (WP) | |
| 2106 | + */ | |
| 2107 | + $custom_calculation = VBOFactory::getPlatform()->getDispatcher()->filter('onCalculateBookingOptionFeeCost', [$realcost, &$actopt[0], $row, $or]); | |
| 2108 | + if ($custom_calculation) { | |
| 2109 | + $realcost = (float) $custom_calculation[0]; | |
| 2110 | + } | |
| 2111 | + | |
| 1886 | 2112 | $opt_minus_tax = VikBooking::sayOptionalsMinusIva($realcost, $actopt[0]['idiva']); |
| 1887 | 2113 | $tmpopr = VikBooking::sayOptionalsPlusIva($realcost, $actopt[0]['idiva']); |
| 1888 | 2114 | if ($actopt[0]['is_citytax'] == 1) { |
| 1889 | 2115 | $tot_city_taxes += $opt_minus_tax; |
| @@ -1888,10 +2114,12 @@ | ||
| 1888 | 2114 | if ($actopt[0]['is_citytax'] == 1) { |
| 1889 | 2115 | $tot_city_taxes += $opt_minus_tax; |
| 1890 | 2116 | } elseif ($actopt[0]['is_fee'] == 1) { |
| 1891 | 2117 | $tot_fees += $opt_minus_tax; |
| 2118 | + } elseif ($opt_params['damagedep'] ?? 0) { | |
| 2119 | + $tot_damage_dep += $opt_minus_tax; | |
| 1892 | 2120 | } |
| 1893 | - // VBO 1.11 - always calculate the amount of tax no matter if this is already a tax or a fee | |
| 2121 | + // always calculate the amount of tax no matter if this is already a tax or a fee | |
| 1894 | 2122 | if ($tmpopr == $realcost) { |
| 1895 | 2123 | $tot_taxes += ($realcost - $opt_minus_tax); |
| 1896 | 2124 | } else { |
| 1897 | 2125 | $tot_taxes += ($tmpopr - $realcost); |
| @@ -1922,16 +2150,19 @@ | ||
| 1922 | 2150 | $expcoupon = explode(";", $row['coupon']); |
| 1923 | 2151 | $isdue = $isdue - $expcoupon[1]; |
| 1924 | 2152 | } |
| 1925 | 2153 | |
| 1926 | - // invoke the payment method class | |
| 1927 | - $exppay = explode('=', $row['idpayment']); | |
| 1928 | - $payment = VikBooking::getPayment($exppay[0], $vbo_tn); | |
| 1929 | - | |
| 1930 | 2154 | if (empty($row['sid']) && !empty($row['idorderota']) && !empty($row['channel'])) { |
| 1931 | 2155 | $row['sid'] = $row['idorderota']; |
| 1932 | 2156 | } |
| 1933 | 2157 | |
| 2158 | + /** | |
| 2159 | + * Trigger event to allow third-party plugins to manipulate the transaction data. | |
| 2160 | + * | |
| 2161 | + * @since 1.18.5 (J) - 1.8.5 (WP) | |
| 2162 | + */ | |
| 2163 | + VBOFactory::getPlatform()->getDispatcher()->trigger('onInitPaymentTransaction', [&$row, &$payment['params'], []]); | |
| 2164 | + | |
| 1934 | 2165 | if (VBOPlatformDetection::isWordPress()) { |
| 1935 | 2166 | /** |
| 1936 | 2167 | * @wponly The payment gateway is now loaded |
| 1937 | 2168 | * using the apposite dispatcher. |
| @@ -1940,9 +2171,9 @@ | ||
| 1940 | 2171 | */ |
| 1941 | 2172 | JLoader::import('adapter.payment.dispatcher'); |
| 1942 | 2173 | $return_url = JUri::root() . "index.php?option=com_vikbooking&view=booking&sid=" . (!empty($row['idorderota']) && !empty($row['channel']) ? $row['idorderota'] : $row['sid']) . "&ts=" . $row['ts']; |
| 1943 | 2174 | $error_url = JUri::root() . "index.php?option=com_vikbooking&view=booking&sid=" . (!empty($row['idorderota']) && !empty($row['channel']) ? $row['idorderota'] : $row['sid']) . "&ts=" . $row['ts']; |
| 1944 | - $notify_url = JUri::root() . "index.php?option=com_vikbooking&task=notifypayment&sid=" . (!empty($row['idorderota']) && !empty($row['channel']) ? $row['idorderota'] : $row['sid']) . "&ts=" . $row['ts']."&tmpl=component"; | |
| 2175 | + $notify_url = JUri::root() . "index.php?option=com_vikbooking&task=notifypayment" . ($app->input->getBool('dd') ? '&dd=1' : '') . "&sid=" . (!empty($row['idorderota']) && !empty($row['channel']) ? $row['idorderota'] : $row['sid']) . "&ts=" . $row['ts'] . "&tmpl=component"; | |
| 1945 | 2176 | $model = JModel::getInstance('vikbooking', 'shortcodes', 'admin'); |
| 1946 | 2177 | $itemid = $model->best(array('booking'), (!empty($row['lang']) ? $row['lang'] : null)); |
| 1947 | 2178 | $extra_data = []; |
| 1948 | 2179 | if ($itemid) { |
| @@ -1973,9 +2204,9 @@ | ||
| 1973 | 2204 | $bestitemid = VikBooking::findProperItemIdType(array('booking')); |
| 1974 | 2205 | $extra_data = array( |
| 1975 | 2206 | 'return_url' => VikBooking::externalroute("index.php?option=com_vikbooking&view=booking&sid=" . (!empty($row['idorderota']) && !empty($row['channel']) ? $row['idorderota'] : $row['sid']) . "&ts=" . $row['ts'], false, (!empty($bestitemid) ? $bestitemid : null)), |
| 1976 | 2207 | 'error_url' => VikBooking::externalroute("index.php?option=com_vikbooking&view=booking&sid=" . (!empty($row['idorderota']) && !empty($row['channel']) ? $row['idorderota'] : $row['sid']) . "&ts=" . $row['ts'], false, (!empty($bestitemid) ? $bestitemid : null)), |
| 1977 | - 'notify_url' => VikBooking::externalroute("index.php?option=com_vikbooking&task=notifypayment&sid=" . (!empty($row['idorderota']) && !empty($row['channel']) ? $row['idorderota'] : $row['sid']) . "&ts=" . $row['ts']."&tmpl=component", false, null), | |
| 2208 | + 'notify_url' => VikBooking::externalroute("index.php?option=com_vikbooking&task=notifypayment" . ($app->input->getBool('dd') ? '&dd=1' : '') . "&sid=" . (!empty($row['idorderota']) && !empty($row['channel']) ? $row['idorderota'] : $row['sid']) . "&ts=" . $row['ts'] . "&tmpl=component", false, null), | |
| 1978 | 2209 | ); |
| 1979 | 2210 | $extra_data['transaction_currency'] = VikBooking::getCurrencyCodePp(); |
| 1980 | 2211 | |
| 1981 | 2212 | $obj = VBOPaymentFactory::getPaymentInstance($payment['file'], array_merge($row, $extra_data), $payment['params']); |
| @@ -1980,9 +2211,20 @@ | ||
| 1980 | 2211 | |
| 1981 | 2212 | $obj = VBOPaymentFactory::getPaymentInstance($payment['file'], array_merge($row, $extra_data), $payment['params']); |
| 1982 | 2213 | } |
| 1983 | 2214 | |
| 1984 | - $array_result = $obj->validatePayment(); | |
| 2215 | + try { | |
| 2216 | + // let the gateway validate the payment transaction | |
| 2217 | + $array_result = $obj->validatePayment(); | |
| 2218 | + } catch (Throwable $e) { | |
| 2219 | + // silently catch the error and set the log | |
| 2220 | + $array_result = [ | |
| 2221 | + 'verified' => 0, | |
| 2222 | + 'log' => $e->getMessage() ?: 'Transaction error exception.', | |
| 2223 | + ]; | |
| 2224 | + } | |
| 2225 | + | |
| 2226 | + // build payment log | |
| 1985 | 2227 | $newpaymentlog = date('c')."\n".$array_result['log']."\n----------\n".$row['paymentlog']; |
| 1986 | 2228 | |
| 1987 | 2229 | /** |
| 1988 | 2230 | * OTA reservations containing PCI-DSS card details may receive additional payments through |
| @@ -1996,11 +2238,21 @@ | ||
| 1996 | 2238 | $newpaymentlog = $row['paymentlog'] . "\n----------\n" . date('c') . "\n" . $array_result['log']; |
| 1997 | 2239 | } |
| 1998 | 2240 | } |
| 1999 | 2241 | |
| 2242 | + /** | |
| 2243 | + * Ensure the size of the log does not make the query fail. | |
| 2244 | + * | |
| 2245 | + * @since 1.16.9 (J) - 1.6.9 (WP) | |
| 2246 | + */ | |
| 2247 | + if (strlen($newpaymentlog) > 55000) { | |
| 2248 | + $newpaymentlog = substr($newpaymentlog, 0, 55000) . '...'; | |
| 2249 | + } | |
| 2250 | + | |
| 2000 | 2251 | if ($array_result['verified'] == 1) { |
| 2001 | 2252 | // valid payment |
| 2002 | 2253 | $shouldpay = $isdue; |
| 2254 | + | |
| 2003 | 2255 | if ($payment['charge'] > 0.00) { |
| 2004 | 2256 | if ($payment['ch_disc'] == 1) { |
| 2005 | 2257 | // charge |
| 2006 | 2258 | if ($payment['val_pcent'] == 1) { |
| @@ -2022,39 +2274,60 @@ | ||
| 2022 | 2274 | $shouldpay -= $percent_to_pay; |
| 2023 | 2275 | } |
| 2024 | 2276 | } |
| 2025 | 2277 | } |
| 2026 | - //deposit may be skipped by customer choice | |
| 2278 | + | |
| 2279 | + // deposit may be skipped by customer choice | |
| 2027 | 2280 | $shouldpay_befdep = $shouldpay; |
| 2028 | - // | |
| 2281 | + $shouldpay_befdep_no_dd = $shouldpay - ($damage_deposit_payment['damagedep_gross'] ?? 0); | |
| 2282 | + | |
| 2029 | 2283 | if (!VikBooking::payTotal()) { |
| 2030 | 2284 | $percentdeposit = VikBooking::getAccPerCent(); |
| 2031 | 2285 | if ($percentdeposit > 0) { |
| 2032 | 2286 | if (VikBooking::getTypeDeposit() == "fixed") { |
| 2033 | 2287 | $shouldpay = $percentdeposit; |
| 2288 | + $shouldpay_befdep_no_dd = $percentdeposit; | |
| 2034 | 2289 | } else { |
| 2035 | 2290 | $shouldpay = $shouldpay * $percentdeposit / 100; |
| 2291 | + $shouldpay_befdep_no_dd = $shouldpay_befdep_no_dd * $percentdeposit / 100;; | |
| 2036 | 2292 | } |
| 2037 | 2293 | } |
| 2038 | 2294 | } |
| 2039 | - //check if the total amount paid is the same as the order total | |
| 2295 | + | |
| 2296 | + // check if a damage deposit was allowed to be paid | |
| 2297 | + $shouldpay_dd = $damage_deposit_payment['damagedep_gross'] ?? 0; | |
| 2298 | + $shouldpay_befdd = $shouldpay - $shouldpay_dd; | |
| 2299 | + | |
| 2300 | + // check if the total amount paid is the same as the order total | |
| 2040 | 2301 | if (isset($array_result['tot_paid'])) { |
| 2041 | 2302 | $shouldpay = round($shouldpay, 2); |
| 2042 | 2303 | $shouldpay_befdep = round($shouldpay_befdep, 2); |
| 2304 | + $shouldpay_befdep_no_dd = round($shouldpay_befdep_no_dd, 2); | |
| 2305 | + $shouldpay_less_damagedep = round(($row['total'] - $row['tot_damage_dep']), 2); | |
| 2043 | 2306 | $totreceived = round($array_result['tot_paid'], 2); |
| 2044 | - if ($shouldpay != $totreceived && $shouldpay_befdep != $totreceived && $row['paymcount'] == 0) { | |
| 2045 | - //the amount paid is different than the order total | |
| 2046 | - //fares might have changed or the deposit might be different | |
| 2047 | - //Sending just an email to the admin that will check | |
| 2307 | + if ($shouldpay != $totreceived && $shouldpay_befdep != $totreceived && $shouldpay_befdep_no_dd != $totreceived && $shouldpay_befdd != $totreceived && $shouldpay_less_damagedep != $totreceived && $shouldpay_dd != $totreceived && $row['paymcount'] == 0) { | |
| 2308 | + // the amount paid is different than the order total | |
| 2309 | + // fares might have changed or the deposit might be different | |
| 2310 | + // Sending just an email to the admin that will check | |
| 2048 | 2311 | $vbo_app = VikBooking::getVboApplication(); |
| 2049 | 2312 | $adsendermail = VikBooking::getSenderMail(); |
| 2050 | - $vbo_app->sendMail($adsendermail, $adsendermail, $recipient_mail, $adsendermail, JText::translate('VBTOTPAYMENTINVALID'), JText::sprintf('VBTOTPAYMENTINVALIDTXT', $row['id'], $totreceived." (".$array_result['tot_paid'].")", $shouldpay), false); | |
| 2313 | + $vbo_app->sendMail( | |
| 2314 | + $adsendermail, | |
| 2315 | + $adsendermail, | |
| 2316 | + $recipient_mail, | |
| 2317 | + $adsendermail, | |
| 2318 | + JText::translate('VBTOTPAYMENTINVALID'), | |
| 2319 | + JText::sprintf( | |
| 2320 | + 'VBTOTPAYMENTINVALIDTXT', | |
| 2321 | + $row['id'], | |
| 2322 | + $totreceived . " (" . $array_result['tot_paid'] . ")", | |
| 2323 | + $shouldpay | |
| 2324 | + ), | |
| 2325 | + false | |
| 2326 | + ); | |
| 2051 | 2327 | } |
| 2052 | - /** | |
| 2053 | - * We store the amount paid before applying the charge for the transaction. | |
| 2054 | - * | |
| 2055 | - * @since 1.3.0 | |
| 2056 | - */ | |
| 2328 | + | |
| 2329 | + // amount paid should be stored as exclusive of transaction fees/discounts | |
| 2057 | 2330 | if ($payment['charge'] > 0.00) { |
| 2058 | 2331 | if ($payment['ch_disc'] == 1) { |
| 2059 | 2332 | // charge |
| 2060 | 2333 | if ($payment['val_pcent'] == 1) { |
| @@ -2118,12 +2391,29 @@ | ||
| 2118 | 2391 | // update payable amount in case of up-sells or simply in case of another payment received |
| 2119 | 2392 | $new_payable = isset($array_result['tot_paid']) && $array_result['tot_paid'] ? ($row['payable'] - $array_result['tot_paid']) : 0; |
| 2120 | 2393 | $new_payable = $new_payable < 0 ? 0 : $new_payable; |
| 2121 | 2394 | |
| 2122 | - $q = "UPDATE `#__vikbooking_orders` SET `status`='confirmed'" . (isset($array_result['tot_paid']) && $array_result['tot_paid'] ? ", `totpaid`='" . ($array_result['tot_paid'] + $row['totpaid']) . "', `paymcount`=".($row['paymcount'] + 1) : "") . (!empty($array_result['log']) ? ", `paymentlog`=".$dbo->quote($newpaymentlog) : "") . ", `payable`=" . $dbo->quote($new_payable) . " WHERE `id`='" . $row['id'] . "';"; | |
| 2123 | - $dbo->setQuery($q); | |
| 2124 | - $dbo->execute(); | |
| 2395 | + // update booking record | |
| 2396 | + $booking_record = new stdClass; | |
| 2397 | + $booking_record->id = $row['id']; | |
| 2398 | + $booking_record->status = 'confirmed'; | |
| 2399 | + if (isset($array_result['tot_paid']) && $array_result['tot_paid']) { | |
| 2400 | + $booking_record->totpaid = ($array_result['tot_paid'] + $row['totpaid']); | |
| 2401 | + } | |
| 2402 | + $booking_record->paymcount = ($row['paymcount'] + 1); | |
| 2403 | + if (!empty($array_result['log'])) { | |
| 2404 | + $booking_record->paymentlog = $newpaymentlog; | |
| 2405 | + } | |
| 2406 | + $booking_record->payable = $new_payable; | |
| 2125 | 2407 | |
| 2408 | + // update booking record | |
| 2409 | + if (!$dbo->updateObject('#__vikbooking_orders', $booking_record, 'id')) { | |
| 2410 | + // payments log content may break the update | |
| 2411 | + unset($booking_record->paymentlog); | |
| 2412 | + // try once more | |
| 2413 | + $dbo->updateObject('#__vikbooking_orders', $booking_record, 'id'); | |
| 2414 | + } | |
| 2415 | + | |
| 2126 | 2416 | // assign room specific unit |
| 2127 | 2417 | $set_room_indexes = VikBooking::autoRoomUnit(); |
| 2128 | 2418 | $room_indexes_usemap = []; |
| 2129 | 2419 | if ($set_room_indexes === true) { |
| @@ -2128,10 +2418,9 @@ | ||
| 2128 | 2418 | $room_indexes_usemap = []; |
| 2129 | 2419 | if ($set_room_indexes === true) { |
| 2130 | 2420 | $q = "SELECT `id`,`idroom`,`roomindex` FROM `#__vikbooking_ordersrooms` WHERE `idorder`=".(int)$row['id'].";"; |
| 2131 | 2421 | $dbo->setQuery($q); |
| 2132 | - $dbo->execute(); | |
| 2133 | - $orooms = $dbo->getNumRows() ? $dbo->loadAssocList() : array(); | |
| 2422 | + $orooms = $dbo->loadAssocList(); | |
| 2134 | 2423 | foreach ($orooms as $oroom) { |
| 2135 | 2424 | if (!empty($oroom['roomindex'])) { |
| 2136 | 2425 | // room specific unit has already been assigned |
| 2137 | 2426 | continue; |
| @@ -2137,9 +2426,9 @@ | ||
| 2137 | 2426 | continue; |
| 2138 | 2427 | } |
| 2139 | 2428 | $room_indexes = VikBooking::getRoomUnitNumsAvailable($row, $oroom['idroom']); |
| 2140 | 2429 | $use_ind_key = 0; |
| 2141 | - if (count($room_indexes)) { | |
| 2430 | + if ($room_indexes) { | |
| 2142 | 2431 | if (!array_key_exists($oroom['idroom'], $room_indexes_usemap)) { |
| 2143 | 2432 | $room_indexes_usemap[$oroom['idroom']] = $use_ind_key; |
| 2144 | 2433 | } else { |
| 2145 | 2434 | $use_ind_key = $room_indexes_usemap[$oroom['idroom']]; |
| @@ -2163,14 +2452,18 @@ | ||
| 2163 | 2452 | $q = "DELETE FROM `#__vikbooking_tmplock` WHERE `idorder`=" . intval($row['id']) . ";"; |
| 2164 | 2453 | $dbo->setQuery($q); |
| 2165 | 2454 | $dbo->execute(); |
| 2166 | 2455 | |
| 2456 | + if (!empty($row['idquote'])) { | |
| 2457 | + // let the quote model handle the release of other locked room records (OTAs included), if any | |
| 2458 | + VBOMvcModel::getInstance('quote')->releaseUnconfirmedSolutions((int) $row['idquote'], (int) $row['id']); | |
| 2459 | + } | |
| 2460 | + | |
| 2167 | 2461 | // customer booking |
| 2168 | 2462 | $q = "SELECT `idcustomer` FROM `#__vikbooking_customers_orders` WHERE `idorder`=".(int)$row['id'].";"; |
| 2169 | 2463 | $dbo->setQuery($q); |
| 2170 | - $dbo->execute(); | |
| 2171 | - if ($dbo->getNumRows() > 0) { | |
| 2172 | - $customer_id = $dbo->loadResult(); | |
| 2464 | + $customer_id = $dbo->loadResult(); | |
| 2465 | + if ($customer_id) { | |
| 2173 | 2466 | $cpin = VikBooking::getCPinIstance(); |
| 2174 | 2467 | $cpin->updateBookingCommissions($row['id'], $customer_id); |
| 2175 | 2468 | } |
| 2176 | 2469 | |
| @@ -2176,14 +2469,27 @@ | ||
| 2176 | 2469 | |
| 2177 | 2470 | // check if some of the rooms booked have shared calendars |
| 2178 | 2471 | VikBooking::updateSharedCalendars($row['id'], array(), $row['checkin'], $row['checkout']); |
| 2179 | 2472 | |
| 2180 | - // send email notification to guest and admin | |
| 2181 | - VikBooking::sendBookingEmail($row['id'], array('guest', 'admin')); | |
| 2473 | + /** | |
| 2474 | + * Trigger event to allow third-party plugins to choose whether payment notifications should be sent | |
| 2475 | + * | |
| 2476 | + * @since 1.16.10 (J) - 1.6.10 (WP) | |
| 2477 | + */ | |
| 2478 | + $send_notifications = true; | |
| 2479 | + $should_send = VBOFactory::getPlatform()->getDispatcher()->filter('onPaymentReceivedShouldSendNotifications', [$row]); | |
| 2480 | + if (is_array($should_send) && in_array(false, $should_send, true)) { | |
| 2481 | + $send_notifications = false; | |
| 2482 | + } | |
| 2182 | 2483 | |
| 2183 | - // SMS | |
| 2184 | - VikBooking::sendBookingSMS($row['id']); | |
| 2484 | + if ($send_notifications) { | |
| 2485 | + // send email notification to guest and admin | |
| 2486 | + VikBooking::sendBookingEmail($row['id'], array('guest', 'admin')); | |
| 2185 | 2487 | |
| 2488 | + // SMS | |
| 2489 | + VikBooking::sendBookingSMS($row['id']); | |
| 2490 | + } | |
| 2491 | + | |
| 2186 | 2492 | /** |
| 2187 | 2493 | * Payment gateways may set and return the transaction information |
| 2188 | 2494 | * to eventually support a later transaction of type refund. |
| 2189 | 2495 | * |
| @@ -2188,26 +2494,47 @@ | ||
| 2188 | 2494 | * to eventually support a later transaction of type refund. |
| 2189 | 2495 | * |
| 2190 | 2496 | * @since 1.14 (J) - 1.4.0 (WP) |
| 2191 | 2497 | * @since 1.16.2 (J) - 1.6.2 (WP) we attempt to always store the amount paid with this transaction. |
| 2498 | + * @since 1.16.9 (J) - 1.6.9 (WP) we attempt to store the amount of payment processing fees for the transaction. | |
| 2499 | + * @since 1.17.6 (J) - 1.7.6 (WP) added support to separate payment for damage deposit. | |
| 2192 | 2500 | */ |
| 2193 | - $tn_data = isset($array_result['transaction']) ? $array_result['transaction'] : null; | |
| 2501 | + $tn_data = $array_result['transaction'] ?? null; | |
| 2194 | 2502 | if (isset($array_result['tot_paid']) && $array_result['tot_paid']) { |
| 2195 | 2503 | // check event data payload to store |
| 2196 | 2504 | if (is_array($tn_data)) { |
| 2197 | 2505 | // set key |
| 2198 | - $tn_data['amount_paid'] = (float)$array_result['tot_paid']; | |
| 2506 | + $tn_data['amount_paid'] = (float) $array_result['tot_paid']; | |
| 2199 | 2507 | } elseif (is_object($tn_data)) { |
| 2200 | 2508 | // set property |
| 2201 | - $tn_data->amount_paid = (float)$array_result['tot_paid']; | |
| 2509 | + $tn_data->amount_paid = (float) $array_result['tot_paid']; | |
| 2202 | 2510 | } elseif (!$tn_data) { |
| 2203 | 2511 | // build an array (we add the payment name because we know there is no other transaction data) |
| 2204 | 2512 | $tn_data = [ |
| 2205 | - 'amount_paid' => (float)$array_result['tot_paid'], | |
| 2513 | + 'amount_paid' => (float) $array_result['tot_paid'], | |
| 2206 | 2514 | 'payment_method' => $payment['name'], |
| 2207 | 2515 | ]; |
| 2208 | 2516 | } |
| 2209 | 2517 | } |
| 2518 | + if ($tn_data && isset($array_result['tot_fees']) && $array_result['tot_fees']) { | |
| 2519 | + // check event data payload to store | |
| 2520 | + if (is_array($tn_data)) { | |
| 2521 | + // set key | |
| 2522 | + $tn_data['processing_fees'] = (float) $array_result['tot_fees']; | |
| 2523 | + } elseif (is_object($tn_data)) { | |
| 2524 | + // set property | |
| 2525 | + $tn_data->processing_fees = (float) $array_result['tot_fees']; | |
| 2526 | + } | |
| 2527 | + } | |
| 2528 | + if ($app->input->getBool('dd') && ($damage_deposit_payment['damagedep_gross'] ?? null)) { | |
| 2529 | + if ($tn_data) { | |
| 2530 | + $tn_data = (array) $tn_data; | |
| 2531 | + } else { | |
| 2532 | + $tn_data = []; | |
| 2533 | + } | |
| 2534 | + // damage deposit may be authorized without an amount paid returned | |
| 2535 | + $tn_data['damage_deposit'] = (float) $damage_deposit_payment['damagedep_gross']; | |
| 2536 | + } | |
| 2210 | 2537 | |
| 2211 | 2538 | // Booking History |
| 2212 | 2539 | VikBooking::getBookingHistoryInstance()->setBid($row['id'])->setExtraData($tn_data)->store('P' . ($row['paymcount'] > 0 ? 'N' : '0'), $payment['name']); |
| 2213 | 2540 | |
| @@ -2213,12 +2540,11 @@ | ||
| 2213 | 2540 | |
| 2214 | 2541 | // invoke VikChannelManager |
| 2215 | 2542 | if (is_file(VCM_SITE_PATH . DIRECTORY_SEPARATOR . "helpers" . DIRECTORY_SEPARATOR . "synch.vikbooking.php")) { |
| 2216 | 2543 | require_once(VCM_SITE_PATH . DIRECTORY_SEPARATOR . "helpers" . DIRECTORY_SEPARATOR . "synch.vikbooking.php"); |
| 2217 | - $vcm = new synchVikBooking($row['id']); | |
| 2544 | + $vcm = new SynchVikBooking($row['id']); | |
| 2218 | 2545 | $vcm->setPushType('new')->sendRequest(); |
| 2219 | 2546 | } |
| 2220 | - $session = JFactory::getSession(); | |
| 2221 | 2547 | $vcmchanneldata = $session->get('vcmChannelData', ''); |
| 2222 | 2548 | if (!empty($vcmchanneldata)) { |
| 2223 | 2549 | $session->set('vcmChannelData', ''); |
| 2224 | 2550 | } |
| @@ -2241,11 +2567,16 @@ | ||
| 2241 | 2567 | $obj->afterValidation(0); |
| 2242 | 2568 | } |
| 2243 | 2569 | } |
| 2244 | 2570 | } |
| 2245 | - | |
| 2571 | + | |
| 2246 | 2572 | public function currencyconverter() |
| 2247 | 2573 | { |
| 2574 | + if (!JSession::checkToken()) { | |
| 2575 | + // missing CSRF-proof token | |
| 2576 | + VBOHttpDocument::getInstance()->close(403, JText::translate('JINVALID_TOKEN')); | |
| 2577 | + } | |
| 2578 | + | |
| 2248 | 2579 | $session = JFactory::getSession(); |
| 2249 | 2580 | $pprices = VikRequest::getVar('prices', array(0)); |
| 2250 | 2581 | $pfromsymbol = VikRequest::getString('fromsymbol', '', 'request'); |
| 2251 | 2582 | $ptocurrency = VikRequest::getString('tocurrency', '', 'request'); |
| @@ -2289,8 +2620,13 @@ | ||
| 2289 | 2620 | } |
| 2290 | 2621 | |
| 2291 | 2622 | public function storesignature() |
| 2292 | 2623 | { |
| 2624 | + if (!JSession::checkToken()) { | |
| 2625 | + // missing CSRF-proof token | |
| 2626 | + VBOHttpDocument::getInstance()->close(403, JText::translate('JINVALID_TOKEN')); | |
| 2627 | + } | |
| 2628 | + | |
| 2293 | 2629 | $sid = VikRequest::getString('sid', '', 'request'); |
| 2294 | 2630 | $ts = VikRequest::getString('ts', '', 'request'); |
| 2295 | 2631 | $psignature = VikRequest::getString('signature', '', 'request', VIKREQUEST_ALLOWRAW); |
| 2296 | 2632 | $ppad_width = VikRequest::getInt('pad_width', '', 'request'); |
| @@ -2374,8 +2710,13 @@ | ||
| 2374 | 2710 | } |
| 2375 | 2711 | |
| 2376 | 2712 | public function validatepin() |
| 2377 | 2713 | { |
| 2714 | + if (!JSession::checkToken()) { | |
| 2715 | + // missing CSRF-proof token | |
| 2716 | + VBOHttpDocument::getInstance()->close(403, JText::translate('JINVALID_TOKEN')); | |
| 2717 | + } | |
| 2718 | + | |
| 2378 | 2719 | $cpin = VikBooking::getCPinIstance(); |
| 2379 | 2720 | |
| 2380 | 2721 | $ppin = VikRequest::getString('pin', '', 'request'); |
| 2381 | 2722 | |
| @@ -2381,9 +2722,9 @@ | ||
| 2381 | 2722 | |
| 2382 | 2723 | $response = []; |
| 2383 | 2724 | |
| 2384 | 2725 | $customer = $cpin->getCustomerByPin($ppin); |
| 2385 | - if (count($customer)) { | |
| 2726 | + if ($customer) { | |
| 2386 | 2727 | $response = $customer; |
| 2387 | 2728 | $response['success'] = 1; |
| 2388 | 2729 | |
| 2389 | 2730 | if ($cpin->getCustomerCoupon($customer)) { |
| @@ -2397,18 +2738,24 @@ | ||
| 2397 | 2738 | } |
| 2398 | 2739 | |
| 2399 | 2740 | public function docancelbooking() |
| 2400 | 2741 | { |
| 2742 | + if (!JSession::checkToken()) { | |
| 2743 | + // missing CSRF-proof token | |
| 2744 | + VBOHttpDocument::getInstance()->close(403, JText::translate('JINVALID_TOKEN')); | |
| 2745 | + } | |
| 2746 | + | |
| 2747 | + $dbo = JFactory::getDbo(); | |
| 2748 | + $mainframe = JFactory::getApplication(); | |
| 2749 | + | |
| 2401 | 2750 | $psid = VikRequest::getString('sid', '', 'request'); |
| 2402 | 2751 | $pidorder = VikRequest::getString('idorder', '', 'request'); |
| 2403 | - $dbo = JFactory::getDBO(); | |
| 2404 | - $mainframe = JFactory::getApplication(); | |
| 2752 | + | |
| 2405 | 2753 | if (!empty($psid) && !empty($pidorder)) { |
| 2406 | 2754 | $q = "SELECT * FROM `#__vikbooking_orders` WHERE `id`=".intval($pidorder)." AND `sid`=".$dbo->quote($psid)." AND `status`='confirmed';"; |
| 2407 | 2755 | $dbo->setQuery($q); |
| 2408 | - $dbo->execute(); | |
| 2409 | - if ($dbo->getNumRows() == 1) { | |
| 2410 | - $order = $dbo->loadAssocList(); | |
| 2756 | + $order = $dbo->loadAssocList(); | |
| 2757 | + if ($order) { | |
| 2411 | 2758 | $pemail = VikRequest::getString('email', '', 'request'); |
| 2412 | 2759 | $preason = VikRequest::getString('reason', '', 'request'); |
| 2413 | 2760 | if (!empty($pemail) && !empty($preason)) { |
| 2414 | 2761 | $to = VikBooking::getAdminMail(); |
| @@ -2443,11 +2790,10 @@ | ||
| 2443 | 2790 | $is_package = !empty($order[0]['pkg']) ? true : false; |
| 2444 | 2791 | $orderrooms = array(); |
| 2445 | 2792 | $q = "SELECT `or`.`idroom`,`or`.`adults`,`or`.`children`,`or`.`idtar`,`or`.`optionals`,`or`.`roomindex`,`or`.`pkg_id`,`or`.`pkg_name`,`or`.`cust_cost`,`or`.`cust_idiva`,`or`.`extracosts`,`or`.`room_cost`,`or`.`otarplan`,`r`.`id` AS `r_reference_id`,`r`.`name`,`r`.`img`,`r`.`idcarat`,`r`.`fromadult`,`r`.`toadult` FROM `#__vikbooking_ordersrooms` AS `or`,`#__vikbooking_rooms` AS `r` WHERE `or`.`idorder`='".$order[0]['id']."' AND `or`.`idroom`=`r`.`id` ORDER BY `or`.`id` ASC;"; |
| 2446 | 2793 | $dbo->setQuery($q); |
| 2447 | - $dbo->execute(); | |
| 2448 | - if ($dbo->getNumRows() > 0) { | |
| 2449 | - $orderrooms = $dbo->loadAssocList(); | |
| 2794 | + $orderrooms = $dbo->loadAssocList(); | |
| 2795 | + if ($orderrooms) { | |
| 2450 | 2796 | foreach($orderrooms as $kor => $or) { |
| 2451 | 2797 | $num = $kor + 1; |
| 2452 | 2798 | if($is_package === true || (!empty($or['cust_cost']) && $or['cust_cost'] > 0.00)) { |
| 2453 | 2799 | //package or custom cost set from the back-end |
| @@ -2454,11 +2800,10 @@ | ||
| 2454 | 2800 | continue; |
| 2455 | 2801 | } |
| 2456 | 2802 | $q = "SELECT `t`.*,`p`.`name`,`p`.`free_cancellation`,`p`.`canc_deadline`,`p`.`canc_policy` FROM `#__vikbooking_dispcost` AS `t` LEFT JOIN `#__vikbooking_prices` AS `p` ON `t`.`idprice`=`p`.`id` WHERE `t`.`id`='" . $or['idtar'] . "';"; |
| 2457 | 2803 | $dbo->setQuery($q); |
| 2458 | - $dbo->execute(); | |
| 2459 | - if ($dbo->getNumRows() == 1) { | |
| 2460 | - $tar = $dbo->loadAssocList(); | |
| 2804 | + $tar = $dbo->loadAssocList(); | |
| 2805 | + if ($tar) { | |
| 2461 | 2806 | $tars[$num] = $tar[0]; |
| 2462 | 2807 | } |
| 2463 | 2808 | } |
| 2464 | 2809 | } |
| @@ -2540,8 +2885,15 @@ | ||
| 2540 | 2885 | // push the transaction currency information |
| 2541 | 2886 | $order[0]['transaction_currency'] = VikBooking::getCurrencyCodePp(); |
| 2542 | 2887 | |
| 2543 | 2888 | /** |
| 2889 | + * Trigger event to allow third-party plugins to manipulate the transaction data. | |
| 2890 | + * | |
| 2891 | + * @since 1.18.5 (J) - 1.8.5 (WP) | |
| 2892 | + */ | |
| 2893 | + VBOFactory::getPlatform()->getDispatcher()->trigger('onInitRefundTransaction', [&$order[0], &$payment['params']]); | |
| 2894 | + | |
| 2895 | + /** | |
| 2544 | 2896 | * @wponly The payment gateway is loaded |
| 2545 | 2897 | * through the apposite dispatcher. |
| 2546 | 2898 | */ |
| 2547 | 2899 | JLoader::import('adapter.payment.dispatcher'); |
| @@ -2559,16 +2911,29 @@ | ||
| 2559 | 2911 | $admin_refund_error .= !empty($array_result['log']) && is_string($array_result['log']) ? $array_result['log'] : ''; |
| 2560 | 2912 | } else { |
| 2561 | 2913 | // refund was successful |
| 2562 | 2914 | |
| 2915 | + /** | |
| 2916 | + * The history event extra data will contain the "amount_paid" (refunded). | |
| 2917 | + * | |
| 2918 | + * @since 1.16.9 (J) - 1.6.9 (WP) | |
| 2919 | + */ | |
| 2920 | + if (!empty($array_result['tot_paid'])) { | |
| 2921 | + // overwrite the requested amount with the returned one | |
| 2922 | + $refund_amount = (float)$array_result['tot_paid']; | |
| 2923 | + } | |
| 2924 | + $history_obj->setExtraData([ | |
| 2925 | + 'amount_paid' => $refund_amount, | |
| 2926 | + ]); | |
| 2927 | + | |
| 2563 | 2928 | // update total paid, total and refund columns for the booking |
| 2564 | 2929 | $booking = new stdClass; |
| 2565 | 2930 | $booking->id = $order[0]['id']; |
| 2566 | 2931 | if ($order[0]['totpaid'] > 0) { |
| 2567 | - $booking->totpaid = $order[0]['totpaid'] - $refund_amount; | |
| 2932 | + $booking->totpaid = (float)($order[0]['totpaid'] - $refund_amount); | |
| 2568 | 2933 | } |
| 2569 | 2934 | if ($order[0]['total'] > 0) { |
| 2570 | - $booking->total = $order[0]['total'] - $refund_amount; | |
| 2935 | + $booking->total = (float)($order[0]['total'] - $refund_amount); | |
| 2571 | 2936 | } |
| 2572 | 2937 | $booking->refund = (float)$order[0]['refund'] + $refund_amount; |
| 2573 | 2938 | // update record in db |
| 2574 | 2939 | $dbo->updateObject('#__vikbooking_orders', $booking, 'id'); |
| @@ -2573,12 +2938,12 @@ | ||
| 2573 | 2938 | // update record in db |
| 2574 | 2939 | $dbo->updateObject('#__vikbooking_orders', $booking, 'id'); |
| 2575 | 2940 | |
| 2576 | 2941 | // store the refund event |
| 2577 | - $event_descr = array( | |
| 2942 | + $event_descr = [ | |
| 2578 | 2943 | '(' . $payment['name'] . ')', |
| 2579 | 2944 | $currencysymb . ' ' . VikBooking::numberFormat($refund_amount), |
| 2580 | - ); | |
| 2945 | + ]; | |
| 2581 | 2946 | $history_obj->store('RF', implode("\n", $event_descr)); |
| 2582 | 2947 | } |
| 2583 | 2948 | } |
| 2584 | 2949 | } |
| @@ -2667,9 +3032,13 @@ | ||
| 2667 | 3032 | VikRequest::setVar('start_date', $start_date); |
| 2668 | 3033 | VikRequest::setVar('end_date', $end_date); |
| 2669 | 3034 | //make call to get the result |
| 2670 | 3035 | TACVBO::$getArray = true; |
| 2671 | - $website_rates = TACVBO::tac_av_l(); | |
| 3036 | + $website_rates = TACVBO::tac_av_l([ | |
| 3037 | + // always force adults and children to be injected as arguments to avoid request conflicts | |
| 3038 | + 'adults' => VikRequest::getVar('adults', array()), | |
| 3039 | + 'children' => VikRequest::getVar('children', array()) ?: [0], | |
| 3040 | + ]); | |
| 2672 | 3041 | //validate response |
| 2673 | 3042 | if (!is_array($website_rates)) { |
| 2674 | 3043 | //error returned |
| 2675 | 3044 | echo json_encode(array('e4j.error' => $website_rates)); |
| @@ -2780,16 +3149,14 @@ | ||
| 2780 | 3149 | */ |
| 2781 | 3150 | public function operatorlogin() |
| 2782 | 3151 | { |
| 2783 | 3152 | /** |
| 2784 | - * Extra security fix for the login form token. | |
| 2785 | - * | |
| 2786 | - * @since September 8th 2020 | |
| 3153 | + * Secure the login form token. | |
| 2787 | 3154 | */ |
| 2788 | - if (!JFactory::getSession()->checkToken()) { | |
| 2789 | - throw new Exception("The security token did not match.", 403); | |
| 3155 | + if (!JSession::checkToken()) { | |
| 3156 | + // missing CSRF-proof token | |
| 3157 | + throw new Exception('The security token did not match.', 403); | |
| 2790 | 3158 | } |
| 2791 | - // | |
| 2792 | 3159 | |
| 2793 | 3160 | $app = JFactory::getApplication(); |
| 2794 | 3161 | $pauthcode = VikRequest::getString('authcode', '', 'request'); |
| 2795 | 3162 | $pitemid = VikRequest::getInt('Itemid', '', 'request'); |
| @@ -2830,39 +3197,44 @@ | ||
| 2830 | 3197 | * @since 1.12 |
| 2831 | 3198 | */ |
| 2832 | 3199 | public function storeprecheckin() |
| 2833 | 3200 | { |
| 3201 | + if (!JSession::checkToken()) { | |
| 3202 | + // missing CSRF-proof token | |
| 3203 | + VBOHttpDocument::getInstance()->close(403, JText::translate('JINVALID_TOKEN')); | |
| 3204 | + } | |
| 3205 | + | |
| 2834 | 3206 | $dbo = JFactory::getDbo(); |
| 2835 | 3207 | $app = JFactory::getApplication(); |
| 2836 | - $sid = VikRequest::getString('sid', '', 'request'); | |
| 2837 | - $ts = VikRequest::getString('ts', '', 'request'); | |
| 2838 | - $pguests = VikRequest::getVar('guests', array()); | |
| 2839 | - $pitemid = VikRequest::getInt('Itemid', 0, 'request'); | |
| 2840 | 3208 | |
| 3209 | + $sid = $app->input->getString('sid', ''); | |
| 3210 | + $ts = $app->input->getString('ts', ''); | |
| 3211 | + $pguests = $app->input->get('guests', [], 'array'); | |
| 3212 | + $pitemid = $app->input->getInt('Itemid', 0); | |
| 3213 | + | |
| 2841 | 3214 | $q = "SELECT `o`.* FROM `#__vikbooking_orders` AS `o` WHERE (`o`.`sid`=" . $dbo->quote($sid) . " OR `o`.`idorderota`=" . $dbo->quote($sid) . ") AND `o`.`ts`=" . $dbo->quote($ts) . " AND `o`.`status`='confirmed';"; |
| 2842 | 3215 | $dbo->setQuery($q); |
| 2843 | - $dbo->execute(); | |
| 2844 | - if (!$dbo->getNumRows()) { | |
| 3216 | + $order = $dbo->loadAssoc(); | |
| 3217 | + if (!$order) { | |
| 2845 | 3218 | throw new Exception('Booking not found', 404); |
| 2846 | 3219 | } |
| 2847 | - $order = $dbo->loadAssoc(); | |
| 2848 | 3220 | |
| 2849 | - $orderrooms = array(); | |
| 2850 | 3221 | $q = "SELECT `or`.`idroom`,`or`.`adults`,`or`.`children`,`or`.`idtar`,`or`.`optionals`,`or`.`childrenage`,`or`.`t_first_name`,`or`.`t_last_name`,`or`.`roomindex`,`or`.`pkg_id`,`or`.`pkg_name`,`or`.`cust_cost`,`or`.`cust_idiva`,`or`.`extracosts`,`or`.`room_cost`,`or`.`otarplan`,`r`.`id` AS `r_reference_id`,`r`.`name`,`r`.`img`,`r`.`idcarat`,`r`.`fromadult`,`r`.`toadult` FROM `#__vikbooking_ordersrooms` AS `or`,`#__vikbooking_rooms` AS `r` WHERE `or`.`idorder`=".(int)$order['id']." AND `or`.`idroom`=`r`.`id` ORDER BY `or`.`id` ASC;"; |
| 2851 | 3222 | $dbo->setQuery($q); |
| 2852 | - $dbo->execute(); | |
| 2853 | - if (!$dbo->getNumRows()) { | |
| 3223 | + $orderrooms = $dbo->loadAssocList(); | |
| 3224 | + if (!$orderrooms) { | |
| 2854 | 3225 | throw new Exception('No rooms found', 404); |
| 2855 | 3226 | } |
| 2856 | - $orderrooms = $dbo->loadAssocList(); | |
| 2857 | 3227 | |
| 3228 | + // access the customer record | |
| 3229 | + $customer = VikBooking::getCPinInstance()->getCustomerFromBooking($order['id']); | |
| 3230 | + | |
| 2858 | 3231 | $q = "SELECT * FROM `#__vikbooking_customers_orders` WHERE `idorder`=".(int)$order['id'].";"; |
| 2859 | 3232 | $dbo->setQuery($q); |
| 2860 | - $dbo->execute(); | |
| 2861 | - if (!$dbo->getNumRows()) { | |
| 3233 | + $custorder = $dbo->loadAssoc(); | |
| 3234 | + if (!$custorder) { | |
| 2862 | 3235 | throw new Exception('No customer found', 404); |
| 2863 | 3236 | } |
| 2864 | - $custorder = $dbo->loadAssoc(); | |
| 2865 | 3237 | |
| 2866 | 3238 | // booking details page |
| 2867 | 3239 | $goto = JRoute::rewrite('index.php?option=com_vikbooking&view=booking&sid=' . $order['sid'] . '&ts=' . $order['ts'] . (!empty($pitemid) ? '&Itemid=' . $pitemid : ''), false); |
| 2868 | 3240 | if (empty($order['sid']) && !empty($order['idorderota'])) { |
| @@ -2874,10 +3246,16 @@ | ||
| 2874 | 3246 | $precheckin = VikBooking::precheckinEnabled(); |
| 2875 | 3247 | if ($precheckin) { |
| 2876 | 3248 | // make sure the limit of days in advance is reflected |
| 2877 | 3249 | $precheckin_mind = VikBooking::precheckinMinOffset(); |
| 2878 | - $precheckin_lim_ts = strtotime("+{$precheckin_mind} days 00:00:00"); | |
| 2879 | - $precheckin = ($precheckin_lim_ts <= $order['checkin'] || ($precheckin_mind === 1 && time() <= $order['checkin'])); | |
| 3250 | + if ($precheckin_mind < 0) { | |
| 3251 | + // validation made prior to check-out date and time | |
| 3252 | + $precheckin = time() <= strtotime("{$precheckin_mind} days 23:59:59", $order['checkout']); | |
| 3253 | + } else { | |
| 3254 | + // classic validation prior to check-in date and time | |
| 3255 | + $precheckin_lim_ts = strtotime("+{$precheckin_mind} days 00:00:00"); | |
| 3256 | + $precheckin = ($precheckin_lim_ts <= $order['checkin'] || ($precheckin_mind === 1 && time() <= $order['checkin'])); | |
| 3257 | + } | |
| 2880 | 3258 | } |
| 2881 | 3259 | if (!$precheckin) { |
| 2882 | 3260 | // raise error and redirect in case of website or OTA booking |
| 2883 | 3261 | VikError::raiseWarning('', 'Pre-checkin not allowed at this time'); |
| @@ -2893,13 +3271,18 @@ | ||
| 2893 | 3271 | |
| 2894 | 3272 | foreach ($pguests as $ind => $adults) { |
| 2895 | 3273 | foreach ($adults as $aduind => $details) { |
| 2896 | 3274 | foreach ($details as $detkey => $detval) { |
| 3275 | + // accept no raw HTML tags as keys | |
| 3276 | + $detkey = htmlspecialchars(strip_tags($detkey)); | |
| 3277 | + if (!$detkey) { | |
| 3278 | + continue; | |
| 3279 | + } | |
| 2897 | 3280 | if (!in_array($detkey, $front_keys)) { |
| 2898 | 3281 | // push the key of the guest details for later comparison |
| 2899 | 3282 | array_push($front_keys, $detkey); |
| 2900 | 3283 | } |
| 2901 | - if (strlen($detval)) { | |
| 3284 | + if (is_scalar($detval) && strlen($detval)) { | |
| 2902 | 3285 | // push value only if not empty |
| 2903 | 3286 | if (!isset($guests_details[$ind])) { |
| 2904 | 3287 | $guests_details[$ind] = array(); |
| 2905 | 3288 | } |
| @@ -2928,8 +3311,13 @@ | ||
| 2928 | 3311 | // current pax data include a guest not present, set it to not lose it |
| 2929 | 3312 | $guests_details[$ind][$aduind] = $details; |
| 2930 | 3313 | } |
| 2931 | 3314 | foreach ($details as $detkey => $detval) { |
| 3315 | + // accept no raw HTML tags as keys | |
| 3316 | + $detkey = htmlspecialchars(strip_tags($detkey)); | |
| 3317 | + if (!$detkey) { | |
| 3318 | + continue; | |
| 3319 | + } | |
| 2932 | 3320 | if (!in_array($detkey, $front_keys)) { |
| 2933 | 3321 | // merge this key probably reserved to the back-end |
| 2934 | 3322 | $guests_details[$ind][$aduind][$detkey] = $detval; |
| 2935 | 3323 | } |
| @@ -2945,8 +3333,20 @@ | ||
| 2945 | 3333 | |
| 2946 | 3334 | // Booking History |
| 2947 | 3335 | VikBooking::getBookingHistoryInstance()->setBid($order['id'])->store('PC'); |
| 2948 | 3336 | |
| 3337 | + /** | |
| 3338 | + * Invoke the callback on the pax registration driver. | |
| 3339 | + * | |
| 3340 | + * @since 1.17.5 (J) - 1.7.5 (WP) | |
| 3341 | + */ | |
| 3342 | + VBOCheckinPax::callbackPrecheckinDataStored( | |
| 3343 | + VBOFactory::getConfig()->getString('checkindata', 'basic'), | |
| 3344 | + (array) $guests_details, | |
| 3345 | + (array) $order, | |
| 3346 | + (array) $customer | |
| 3347 | + ); | |
| 3348 | + | |
| 2949 | 3349 | // print success message and redirect |
| 2950 | 3350 | $app->enqueueMessage(JText::translate('VBOSUBMITPRECHECKINTNKS')); |
| 2951 | 3351 | $app->redirect($goto); |
| 2952 | 3352 | } |
| @@ -2964,28 +3364,27 @@ | ||
| 2964 | 3364 | $ts = VikRequest::getString('ts', '', 'request'); |
| 2965 | 3365 | $pitemid = VikRequest::getInt('Itemid', 0, 'request'); |
| 2966 | 3366 | $paddopt = VikRequest::getVar('addopt', array()); |
| 2967 | 3367 | |
| 2968 | - if (!count($paddopt)) { | |
| 3368 | + if (!$paddopt) { | |
| 2969 | 3369 | throw new Exception('No extra services selected', 404); |
| 2970 | 3370 | } |
| 2971 | 3371 | |
| 3372 | + // find the involved reservation, direct or OTA | |
| 2972 | 3373 | $q = "SELECT `o`.* FROM `#__vikbooking_orders` AS `o` WHERE (`o`.`sid`=" . $dbo->quote($sid) . " OR `o`.`idorderota`=" . $dbo->quote($sid) . ") AND `o`.`ts`=" . $dbo->quote($ts) . ";"; |
| 2973 | 3374 | $dbo->setQuery($q); |
| 2974 | - $dbo->execute(); | |
| 2975 | - if (!$dbo->getNumRows()) { | |
| 3375 | + $order = $dbo->loadAssoc(); | |
| 3376 | + if (!$order) { | |
| 2976 | 3377 | throw new Exception('Booking not found', 404); |
| 2977 | 3378 | } |
| 2978 | - $order = $dbo->loadAssoc(); | |
| 2979 | 3379 | |
| 2980 | - $orderrooms = []; | |
| 2981 | - $q = "SELECT `or`.*,`r`.`name` AS `room_name` FROM `#__vikbooking_ordersrooms` AS `or`,`#__vikbooking_rooms` AS `r` WHERE `or`.`idorder`=".(int)$order['id']." AND `or`.`idroom`=`r`.`id` ORDER BY `or`.`id` ASC;"; | |
| 3380 | + // obtain the involved rooms (include room "params") | |
| 3381 | + $q = "SELECT `or`.*,`r`.`name` AS `room_name`,`r`.`params` AS `room_params` FROM `#__vikbooking_ordersrooms` AS `or`,`#__vikbooking_rooms` AS `r` WHERE `or`.`idorder`=".(int)$order['id']." AND `or`.`idroom`=`r`.`id` ORDER BY `or`.`id` ASC;"; | |
| 2982 | 3382 | $dbo->setQuery($q); |
| 2983 | - $dbo->execute(); | |
| 2984 | - if (!$dbo->getNumRows()) { | |
| 3383 | + $orderrooms = $dbo->loadAssocList(); | |
| 3384 | + if (!$orderrooms) { | |
| 2985 | 3385 | throw new Exception('No rooms found', 404); |
| 2986 | 3386 | } |
| 2987 | - $orderrooms = $dbo->loadAssocList(); | |
| 2988 | 3387 | |
| 2989 | 3388 | // availability helper |
| 2990 | 3389 | $av_helper = VikBooking::getAvailabilityInstance(); |
| 2991 | 3390 | |
| @@ -3002,20 +3401,56 @@ | ||
| 3002 | 3401 | // load all valid and existing options as a security measure |
| 3003 | 3402 | $alloptions = []; |
| 3004 | 3403 | $q = "SELECT * FROM `#__vikbooking_optionals`;"; |
| 3005 | 3404 | $dbo->setQuery($q); |
| 3006 | - $dbo->execute(); | |
| 3007 | - if (!$dbo->getNumRows()) { | |
| 3405 | + $records = $dbo->loadAssocList(); | |
| 3406 | + if (!$records) { | |
| 3008 | 3407 | throw new Exception('No options found', 404); |
| 3009 | 3408 | } |
| 3010 | - $records = $dbo->loadAssocList(); | |
| 3011 | 3409 | foreach ($records as $v) { |
| 3012 | - $alloptions[(int)$v['id']] = $v; | |
| 3410 | + $alloptions[$v['id']] = $v; | |
| 3013 | 3411 | } |
| 3014 | 3412 | |
| 3413 | + /** | |
| 3414 | + * Custom check-in/out times due to late check-out/early check-in options or custom listing settings. | |
| 3415 | + * | |
| 3416 | + * @since 1.17.2 (J) - 1.7.2 (WP) | |
| 3417 | + * @since 1.18.3 (J) - 1.8.3 (WP) added support to check-in/out times at listing-level. | |
| 3418 | + */ | |
| 3419 | + $custom_checkinout = []; | |
| 3420 | + if (count($orderrooms) === 1) { | |
| 3421 | + $listing_custom_checkin = VikBooking::getRoomParam('checkin', ($orderrooms[(key($orderrooms))]['room_params'] ?? '')); | |
| 3422 | + $listing_custom_checkout = VikBooking::getRoomParam('checkout', ($orderrooms[(key($orderrooms))]['room_params'] ?? '')); | |
| 3423 | + if ($listing_custom_checkin) { | |
| 3424 | + // set listing-level check-in time in seconds | |
| 3425 | + $listing_custom_checkin_parts = explode(':', $listing_custom_checkin); | |
| 3426 | + $listing_custom_checkin = (intval($listing_custom_checkin_parts[0]) * 3600) + (intval($listing_custom_checkin_parts[1]) * 60); | |
| 3427 | + } | |
| 3428 | + if ($listing_custom_checkout) { | |
| 3429 | + // set listing-level check-out time in seconds | |
| 3430 | + $listing_custom_checkout_parts = explode(':', $listing_custom_checkout); | |
| 3431 | + $listing_custom_checkout = (intval($listing_custom_checkout_parts[0]) * 3600) + (intval($listing_custom_checkout_parts[1]) * 60); | |
| 3432 | + } | |
| 3433 | + if ($listing_custom_checkin || $listing_custom_checkout) { | |
| 3434 | + // set listing-level check-in/out times in seconds | |
| 3435 | + $custom_checkinout = [ | |
| 3436 | + (int) $listing_custom_checkin, | |
| 3437 | + (int) $listing_custom_checkout, | |
| 3438 | + ]; | |
| 3439 | + } | |
| 3440 | + } | |
| 3441 | + | |
| 3442 | + /** | |
| 3443 | + * Gather the damage deposit options to be paid separately. | |
| 3444 | + * | |
| 3445 | + * @since 1.18.6 (J) - 1.8.6 (WP) | |
| 3446 | + */ | |
| 3447 | + $separate_dd_options = []; | |
| 3448 | + | |
| 3449 | + // build the extras booked | |
| 3015 | 3450 | $extras_booked = []; |
| 3016 | 3451 | foreach ($orderrooms as $kor => $or) { |
| 3017 | - if (!isset($paddopt[$kor]) || !count($paddopt[$kor])) { | |
| 3452 | + if (!isset($paddopt[$kor]) || !$paddopt[$kor]) { | |
| 3018 | 3453 | continue; |
| 3019 | 3454 | } |
| 3020 | 3455 | |
| 3021 | 3456 | // determine proper nights of stay |
| @@ -3031,27 +3466,65 @@ | ||
| 3031 | 3466 | if (strpos($or['optionals'], $optid . ':') === 0 || strpos($or['optionals'], ';' . $optid . ':') > 0) { |
| 3032 | 3467 | // this option has already been booked, skip it |
| 3033 | 3468 | continue; |
| 3034 | 3469 | } |
| 3035 | - if (!isset($alloptions[(int)$optid])) { | |
| 3470 | + if (!isset($alloptions[$optid])) { | |
| 3036 | 3471 | // this option ID does not exist, skip it |
| 3037 | 3472 | continue; |
| 3038 | 3473 | } |
| 3039 | 3474 | $extraoptstr .= $optid . ':' . (int)$quant . ';'; |
| 3475 | + | |
| 3476 | + // option params | |
| 3477 | + $opt_params = !empty($alloptions[$optid]['oparams']) ? (array) json_decode($alloptions[$optid]['oparams'], true) : []; | |
| 3478 | + | |
| 3479 | + /** | |
| 3480 | + * Custom check-in/out times due to late check-out/early check-in options. | |
| 3481 | + * | |
| 3482 | + * @since 1.17.2 (J) - 1.7.2 (WP) | |
| 3483 | + */ | |
| 3484 | + if (($opt_params['custom_checkinout'] ?? 0) && (($opt_params['set_checkin'] ?? 0) || ($opt_params['set_checkout'] ?? 0))) { | |
| 3485 | + $custom_checkinout = [ | |
| 3486 | + ($opt_params['set_checkin'] ?? 0), | |
| 3487 | + ($opt_params['set_checkout'] ?? 0), | |
| 3488 | + ]; | |
| 3489 | + } | |
| 3490 | + | |
| 3491 | + /** | |
| 3492 | + * Check if this is a damage deposit with a separate payment window. | |
| 3493 | + * | |
| 3494 | + * @since 1.18.6 (J) - 1.8.6 (WP) | |
| 3495 | + */ | |
| 3496 | + if (($opt_params['damagedep'] ?? 0) && !empty($opt_params['damagedep_settings']['paywhen'])) { | |
| 3497 | + // damage deposit option with separate payment defined | |
| 3498 | + $future_payable_dd = true; | |
| 3499 | + if (!empty($opt_params['damagedep_settings']['bmaxlos']) && ($order['days'] ?? 1) > $opt_params['damagedep_settings']['bmaxlos']) { | |
| 3500 | + // maximum nights of stay validation failed | |
| 3501 | + $future_payable_dd = false; | |
| 3502 | + } elseif (empty($opt_params['damagedep_settings']['payid'])) { | |
| 3503 | + // separate payment method ID validation failed | |
| 3504 | + $future_payable_dd = false; | |
| 3505 | + } | |
| 3506 | + if ($future_payable_dd === true) { | |
| 3507 | + // push damage deposit option ID to be paid separately | |
| 3508 | + $separate_dd_options[] = (int) $optid; | |
| 3509 | + } | |
| 3510 | + } | |
| 3511 | + | |
| 3040 | 3512 | // push option booked |
| 3041 | - array_push($extras_booked, array( | |
| 3042 | - 'id' => $optid, | |
| 3043 | - 'idroom' => $or['idroom'], | |
| 3044 | - 'name' => $alloptions[(int)$optid]['name'], | |
| 3045 | - 'quant' => $quant, | |
| 3513 | + $extras_booked[] = [ | |
| 3514 | + 'id' => $optid, | |
| 3515 | + 'idroom' => $or['idroom'], | |
| 3516 | + 'name' => $alloptions[$optid]['name'], | |
| 3517 | + 'quant' => $quant, | |
| 3046 | 3518 | 'room_cost' => (!empty($or['cust_cost']) ? $or['cust_cost'] : $or['room_cost']), |
| 3047 | 3519 | 'room_name' => $or['room_name'], |
| 3048 | - 'optcost' => $alloptions[(int)$optid]['cost'], | |
| 3049 | - 'adults' => $or['adults'], | |
| 3050 | - 'children' => $or['children'], | |
| 3051 | - 'nights' => $room_stay_nights, | |
| 3052 | - )); | |
| 3520 | + 'optcost' => $alloptions[$optid]['cost'], | |
| 3521 | + 'adults' => $or['adults'], | |
| 3522 | + 'children' => $or['children'], | |
| 3523 | + 'nights' => $room_stay_nights, | |
| 3524 | + ]; | |
| 3053 | 3525 | } |
| 3526 | + | |
| 3054 | 3527 | // update options for this room record |
| 3055 | 3528 | $newoptstr = $or['optionals'] . $extraoptstr; |
| 3056 | 3529 | $q = "UPDATE `#__vikbooking_ordersrooms` SET `optionals`=" . $dbo->quote($newoptstr) . " WHERE `id`={$or['id']};"; |
| 3057 | 3530 | $dbo->setQuery($q); |
| @@ -3061,8 +3534,10 @@ | ||
| 3061 | 3534 | // increase booking total amount and build event log for the history |
| 3062 | 3535 | $currency = VikBooking::getCurrencySymb(); |
| 3063 | 3536 | $totrooms = count($orderrooms); |
| 3064 | 3537 | $increase = 0; |
| 3538 | + $future_dd = 0; | |
| 3539 | + $add_tax = 0; | |
| 3065 | 3540 | $extraslog = []; |
| 3066 | 3541 | foreach ($extras_booked as $extra) { |
| 3067 | 3542 | $o = $alloptions[(int)$extra['id']]; |
| 3068 | 3543 | if ((int)$o['pcentroom']) { |
| @@ -3079,31 +3554,100 @@ | ||
| 3079 | 3554 | if ($o['perperson'] == 1) { |
| 3080 | 3555 | $optcost = $optcost * $extra['adults']; |
| 3081 | 3556 | } |
| 3082 | 3557 | $optcost *= $extra['quant']; |
| 3558 | + | |
| 3559 | + /** | |
| 3560 | + * Trigger event to allow third party plugins to apply a custom calculation for the option/extra fee or tax. | |
| 3561 | + * | |
| 3562 | + * @since 1.17.7 (J) - 1.7.7 (WP) | |
| 3563 | + */ | |
| 3564 | + $custom_calculation = VBOFactory::getPlatform()->getDispatcher()->filter('onCalculateBookingOptionFeeCost', [$optcost, &$o, $order, $extra]); | |
| 3565 | + if ($custom_calculation) { | |
| 3566 | + $optcost = (float) $custom_calculation[0]; | |
| 3567 | + } | |
| 3568 | + | |
| 3083 | 3569 | $floatoptprice = VikBooking::sayOptionalsPlusIva($optcost, $o['idiva']); |
| 3570 | + $netoptprice = VikBooking::sayOptionalsMinusIva($optcost, $o['idiva']); | |
| 3084 | 3571 | $increase += $floatoptprice; |
| 3572 | + $add_tax += $floatoptprice - $netoptprice; | |
| 3085 | 3573 | array_push($extraslog, ($totrooms > 1 ? $extra['room_name'] . ': ' : '') . $extra['name'] . ($extra['quant'] > 1 ? ' (x' . $extra['quant'] . ')' : '') . ' ' . $currency . ' ' . VikBooking::numberFormat($floatoptprice)); |
| 3574 | + | |
| 3575 | + if (in_array((int) $extra['id'], $separate_dd_options)) { | |
| 3576 | + // this is a damage deposit option that will be paid separately | |
| 3577 | + $future_dd += $floatoptprice; | |
| 3578 | + } | |
| 3086 | 3579 | } |
| 3087 | 3580 | |
| 3088 | 3581 | $newtotbooking = $order['total'] + $increase; |
| 3582 | + $new_tot_taxes = $order['tot_taxes'] + $add_tax; | |
| 3583 | + | |
| 3089 | 3584 | /** |
| 3090 | 3585 | * Important: the 'paymcount' should be increased only if the status is |
| 3091 | 3586 | * "confirmed" or no rooms may be occupied when receiving a payment. |
| 3092 | 3587 | */ |
| 3093 | - $q = "UPDATE `#__vikbooking_orders` SET `total`=" . $dbo->quote($newtotbooking) . ", `paymcount`=" . ($order['status'] == 'confirmed' && (int)$order['paymcount'] < 1 ? '1' : $order['paymcount']) . ", `payable`=" . $dbo->quote(((float)$order['payable'] + $increase)) . " WHERE `id`={$order['id']};"; | |
| 3588 | + $q = $dbo->getQuery(true) | |
| 3589 | + ->update($dbo->qn('#__vikbooking_orders')) | |
| 3590 | + ->set($dbo->qn('total') . ' = ' . $dbo->q($newtotbooking)) | |
| 3591 | + ->set($dbo->qn('paymcount') . ' = ' . ($order['status'] == 'confirmed' && (int)$order['paymcount'] < 1 ? '1' : $order['paymcount'])) | |
| 3592 | + ->set($dbo->qn('tot_taxes') . ' = ' . $dbo->q($new_tot_taxes)) | |
| 3593 | + ->set($dbo->qn('payable') . ' = ' . $dbo->q(((float)$order['payable'] + $increase - $future_dd))) | |
| 3594 | + ->where($dbo->qn('id') . ' = ' . (int) $order['id']); | |
| 3595 | + | |
| 3596 | + /** | |
| 3597 | + * Custom check-in/out times due to late check-out/early check-in options. | |
| 3598 | + * | |
| 3599 | + * @since 1.17.2 (J) - 1.7.2 (WP) | |
| 3600 | + */ | |
| 3601 | + if ($custom_checkinout) { | |
| 3602 | + $checkin_info = getdate($order['checkin']); | |
| 3603 | + $checkout_info = getdate($order['checkout']); | |
| 3604 | + // overwrite check-in and/or check-out timestamp(s) | |
| 3605 | + if ($custom_checkinout[0] >= 3600) { | |
| 3606 | + // overwrite check-in timestamp | |
| 3607 | + $time_hours = floor($custom_checkinout[0] / 3600); | |
| 3608 | + $time_minutes = floor(($custom_checkinout[0] - ($time_hours * 3600)) / 60); | |
| 3609 | + $new_booking_checkin = mktime($time_hours, $time_minutes, 0, $checkin_info['mon'], $checkin_info['mday'], $checkin_info['year']); | |
| 3610 | + // update db record field | |
| 3611 | + $q->set($dbo->qn('checkin') . ' = ' . $dbo->q($new_booking_checkin)); | |
| 3612 | + } | |
| 3613 | + if ($custom_checkinout[1] >= 3600) { | |
| 3614 | + // overwrite check-out timestamp | |
| 3615 | + $time_hours = floor($custom_checkinout[1] / 3600); | |
| 3616 | + $time_minutes = floor(($custom_checkinout[1] - ($time_hours * 3600)) / 60); | |
| 3617 | + $new_booking_checkout = mktime($time_hours, $time_minutes, 0, $checkout_info['mon'], $checkout_info['mday'], $checkout_info['year']); | |
| 3618 | + // update db record field | |
| 3619 | + $q->set($dbo->qn('checkout') . ' = ' . $dbo->q($new_booking_checkout)); | |
| 3620 | + } | |
| 3621 | + } | |
| 3622 | + | |
| 3623 | + // update booking record on db | |
| 3094 | 3624 | $dbo->setQuery($q); |
| 3095 | 3625 | $dbo->execute(); |
| 3096 | 3626 | |
| 3097 | 3627 | // Booking History |
| 3098 | - VikBooking::getBookingHistoryInstance()->setBid($order['id'])->store('UE', implode("\n", $extraslog)); | |
| 3628 | + VikBooking::getBookingHistoryInstance($order['id'])->store('UE', implode("\n", $extraslog)); | |
| 3099 | 3629 | |
| 3100 | - // send email notification to guest and admin | |
| 3101 | - VikBooking::sendBookingEmail($order['id'], array('guest', 'admin')); | |
| 3630 | + /** | |
| 3631 | + * Trigger event to allow third-party plugins to choose whether upselling notifications should be sent. | |
| 3632 | + * | |
| 3633 | + * @since 1.17.2 (J) - 1.7.2 (WP) | |
| 3634 | + */ | |
| 3635 | + $send_notifications = true; | |
| 3636 | + $should_send = VBOFactory::getPlatform()->getDispatcher()->filter('onUpsellingReceivedShouldSendNotifications', [$order]); | |
| 3637 | + if (is_array($should_send) && in_array(false, $should_send, true)) { | |
| 3638 | + $send_notifications = false; | |
| 3639 | + } | |
| 3102 | 3640 | |
| 3641 | + if ($send_notifications) { | |
| 3642 | + // send email notification to guest and admin | |
| 3643 | + VikBooking::sendBookingEmail($order['id'], array('guest', 'admin')); | |
| 3644 | + } | |
| 3645 | + | |
| 3103 | 3646 | $goto = JRoute::rewrite('index.php?option=com_vikbooking&view=booking&sid=' . (empty($order['sid']) && !empty($order['idorderota']) ? $order['idorderota'] : $order['sid']) . '&ts=' . $order['ts'] . (!empty($pitemid) ? '&Itemid=' . $pitemid : ''), false); |
| 3104 | 3647 | $app->enqueueMessage(JText::translate('VBOUPSELLRESULTOK')); |
| 3105 | 3648 | $app->redirect($goto); |
| 3649 | + $app->close(); | |
| 3106 | 3650 | } |
| 3107 | 3651 | |
| 3108 | 3652 | /** |
| 3109 | 3653 | * Submits a new review. |
| @@ -3226,12 +3770,12 @@ | ||
| 3226 | 3770 | |
| 3227 | 3771 | // reviewer information |
| 3228 | 3772 | $review_content->reviewer = new stdClass; |
| 3229 | 3773 | $customer_name = ''; |
| 3230 | - if (count($customer)) { | |
| 3774 | + if ($customer) { | |
| 3231 | 3775 | $review_content->reviewer->name = $customer['first_name']; |
| 3232 | 3776 | $review_content->reviewer->country_code = $customer['country']; |
| 3233 | - $customer_name = $customer['first_name'] . ' ' . $customer['last_name'];; | |
| 3777 | + $customer_name = $customer['first_name'] . ' ' . $customer['last_name']; | |
| 3234 | 3778 | } else { |
| 3235 | 3779 | $revuname = ''; |
| 3236 | 3780 | if (!empty($order['custdata'])) { |
| 3237 | 3781 | $uinfos = explode("\n", $order['custdata']); |
| @@ -3609,10 +4153,11 @@ | ||
| 3609 | 4153 | // build real-day element |
| 3610 | 4154 | $elem = new stdClass; |
| 3611 | 4155 | $elem->type = 'day'; |
| 3612 | 4156 | $elem->cont = $start_info['mday'] < 10 ? "0{$start_info['mday']}" : $start_info['mday']; |
| 3613 | - $elem->dt = date($vbo_df, $start_info[0]); | |
| 3614 | - $elem->ts = $start_info[0]; | |
| 4157 | + $elem->dt = date($vbo_df, $start_info[0]); | |
| 4158 | + $elem->ymd = date('Y-m-d', $start_info[0]); | |
| 4159 | + $elem->ts = $start_info[0]; | |
| 3615 | 4160 | $elem->class = 'vbtdfree'; |
| 3616 | 4161 | $elem->past_class = $start_info[0] < $today_ts ? ' vbtdpast' : ''; |
| 3617 | 4162 | |
| 3618 | 4163 | // check whether this day has got bookings |
| @@ -3751,8 +4296,13 @@ | ||
| 3751 | 4296 | * @since 1.13.5 |
| 3752 | 4297 | */ |
| 3753 | 4298 | public function add_roomdaynote() |
| 3754 | 4299 | { |
| 4300 | + if (!JSession::checkToken()) { | |
| 4301 | + // missing CSRF-proof token | |
| 4302 | + VBOHttpDocument::getInstance()->close(403, JText::translate('JINVALID_TOKEN')); | |
| 4303 | + } | |
| 4304 | + | |
| 3755 | 4305 | $dt = VikRequest::getString('dt', '', 'request'); |
| 3756 | 4306 | $idroom = VikRequest::getInt('idroom', 0, 'request'); |
| 3757 | 4307 | $subunit = VikRequest::getInt('subunit', 0, 'request'); |
| 3758 | 4308 | $type = VikRequest::getString('type', '', 'request'); |
| @@ -3817,21 +4367,26 @@ | ||
| 3817 | 4367 | |
| 3818 | 4368 | /** |
| 3819 | 4369 | * AJAX endpoint to upload customer documents during the pre-checkin. |
| 3820 | 4370 | * |
| 3821 | - * @throws Exception | |
| 3822 | - * | |
| 3823 | 4371 | * @since 1.14 (J) - 1.4.0 (WP) |
| 4372 | + * @since 1.18.6 (J) - 1.8.6 (WP) added support for MRZ detection through Channel Manager. | |
| 3824 | 4373 | */ |
| 3825 | 4374 | public function precheckin_upload_docs() |
| 3826 | 4375 | { |
| 3827 | - $dbo = JFactory::getDbo(); | |
| 3828 | - $app = JFactory::getApplication(); | |
| 3829 | - $input = $app->input; | |
| 4376 | + $app = JFactory::getApplication(); | |
| 4377 | + $dbo = JFactory::getDbo(); | |
| 4378 | + $input = $app->input; | |
| 3830 | 4379 | |
| 3831 | - // get request values | |
| 3832 | - $order_sid = $input->getString('sid', ''); | |
| 3833 | - $order_ts = $input->getString('ts', ''); | |
| 4380 | + if (!JSession::checkToken()) { | |
| 4381 | + // missing CSRF-proof token | |
| 4382 | + VBOHttpDocument::getInstance($app)->close(403, JText::translate('JINVALID_TOKEN')); | |
| 4383 | + } | |
| 4384 | + | |
| 4385 | + // gather request values | |
| 4386 | + $order_sid = $input->getString('sid', ''); | |
| 4387 | + $order_ts = $input->getString('ts', ''); | |
| 4388 | + $use_mrz = $input->getBool('mrz', false); | |
| 3834 | 4389 | /** |
| 3835 | 4390 | * This is a simple file uploading process, but if |
| 3836 | 4391 | * we wanted to automatically update the pax_data, |
| 3837 | 4392 | * the request vars below would be necessary. |
| @@ -3837,31 +4392,29 @@ | ||
| 3837 | 4392 | * the request vars below would be necessary. |
| 3838 | 4393 | */ |
| 3839 | 4394 | $room_index = $input->getInt('room_index', 0); |
| 3840 | 4395 | $guest_index = $input->getInt('guest_index', 0); |
| 3841 | - $pax_index = $input->getString('pax_index', ''); | |
| 4396 | + $pax_index = $input->getString('pax_index', ''); | |
| 3842 | 4397 | |
| 3843 | 4398 | if (empty($order_sid) || empty($order_ts)) { |
| 3844 | - throw new Exception('Missing booking details', 404); | |
| 4399 | + VBOHttpDocument::getInstance($app)->close(404, 'Missing booking details'); | |
| 3845 | 4400 | } |
| 3846 | 4401 | |
| 3847 | 4402 | $q = "SELECT `o`.*,(SELECT SUM(`or`.`adults`) FROM `#__vikbooking_ordersrooms` AS `or` WHERE `or`.`idorder`=`o`.`id`) AS `tot_adults` FROM `#__vikbooking_orders` AS `o` WHERE (`o`.`sid`=" . $dbo->quote($order_sid) . " OR `o`.`idorderota`=" . $dbo->quote($order_sid) . ") AND `o`.`ts`=" . $dbo->quote($order_ts) . " AND `o`.`status`='confirmed';"; |
| 3848 | 4403 | $dbo->setQuery($q); |
| 3849 | - $dbo->execute(); | |
| 3850 | - if (!$dbo->getNumRows()) { | |
| 3851 | - throw new Exception('Booking not found', 404); | |
| 4404 | + $order = $dbo->loadAssoc(); | |
| 4405 | + if (!$order) { | |
| 4406 | + VBOHttpDocument::getInstance($app)->close(404, 'Booking not found'); | |
| 3852 | 4407 | } |
| 3853 | - $order = $dbo->loadAssoc(); | |
| 3854 | 4408 | |
| 3855 | 4409 | $customer = array(); |
| 3856 | 4410 | $q = "SELECT `c`.*,`co`.`idorder`,`co`.`signature`,`co`.`pax_data`,`co`.`comments`,`co`.`checkindoc` FROM `#__vikbooking_customers` AS `c` LEFT JOIN `#__vikbooking_customers_orders` `co` ON `c`.`id`=`co`.`idcustomer` WHERE `co`.`idorder`=".$order['id'].";"; |
| 3857 | 4411 | $dbo->setQuery($q); |
| 3858 | - $dbo->execute(); | |
| 3859 | - if (!$dbo->getNumRows()) { | |
| 4412 | + $customer = $dbo->loadObject(); | |
| 4413 | + if (!$customer) { | |
| 3860 | 4414 | // one customer must be assigned to this booking for the pre-checkin |
| 3861 | - throw new Exception('No customers associated to this booking', 404); | |
| 4415 | + VBOHttpDocument::getInstance($app)->close(404, 'No customers associated to this booking'); | |
| 3862 | 4416 | } |
| 3863 | - $customer = $dbo->loadObject(); | |
| 3864 | 4417 | |
| 3865 | 4418 | // make sure pre-checkin is allowed |
| 3866 | 4419 | $precheckin = VikBooking::precheckinEnabled(); |
| 3867 | 4420 | if ($precheckin) { |
| @@ -3866,25 +4419,31 @@ | ||
| 3866 | 4419 | $precheckin = VikBooking::precheckinEnabled(); |
| 3867 | 4420 | if ($precheckin) { |
| 3868 | 4421 | // make sure the limit of days in advance is reflected |
| 3869 | 4422 | $precheckin_mind = VikBooking::precheckinMinOffset(); |
| 3870 | - $precheckin_lim_ts = strtotime("+{$precheckin_mind} days 00:00:00"); | |
| 3871 | - $precheckin = ($precheckin_lim_ts <= $order['checkin'] || ($precheckin_mind === 1 && time() <= $order['checkin'])); | |
| 4423 | + if ($precheckin_mind < 0) { | |
| 4424 | + // validation made prior to check-out date and time | |
| 4425 | + $precheckin = time() <= strtotime("{$precheckin_mind} days 23:59:59", $order['checkout']); | |
| 4426 | + } else { | |
| 4427 | + // classic validation prior to check-in date and time | |
| 4428 | + $precheckin_lim_ts = strtotime("+{$precheckin_mind} days 00:00:00"); | |
| 4429 | + $precheckin = ($precheckin_lim_ts <= $order['checkin'] || ($precheckin_mind === 1 && time() <= $order['checkin'])); | |
| 4430 | + } | |
| 3872 | 4431 | } |
| 3873 | 4432 | if (!$precheckin) { |
| 3874 | - throw new Exception('Pre-checkin not allowed at this time', 403); | |
| 4433 | + VBOHttpDocument::getInstance($app)->close(403, 'Pre-checkin not allowed at this time'); | |
| 3875 | 4434 | } |
| 3876 | 4435 | |
| 3877 | 4436 | // get uploaded files array (use "raw" to avoid filtering the file to upload) |
| 3878 | 4437 | $files = $input->files->get('docs', array(), 'raw'); |
| 3879 | - if (!count($files)) { | |
| 3880 | - throw new Exception('No files to be uploaded', 500); | |
| 4438 | + if (!$files) { | |
| 4439 | + VBOHttpDocument::getInstance($app)->close(500, 'No files to be uploaded'); | |
| 3881 | 4440 | } |
| 3882 | 4441 | |
| 3883 | 4442 | if (isset($files['name'])) { |
| 3884 | 4443 | // we have a single associative array, we need to push it within a list, |
| 3885 | 4444 | // because the upload iterates the $files array |
| 3886 | - $files = array($files); | |
| 4445 | + $files = [$files]; | |
| 3887 | 4446 | } |
| 3888 | 4447 | |
| 3889 | 4448 | // fetch documents folder path |
| 3890 | 4449 | $dirpath = VBO_CUSTOMERS_PATH . DIRECTORY_SEPARATOR; |
| @@ -3894,28 +4453,26 @@ | ||
| 3894 | 4453 | // randomize string |
| 3895 | 4454 | $customer->seed = uniqid(); |
| 3896 | 4455 | |
| 3897 | 4456 | // create blocks for hashed folder |
| 3898 | - $parts = array( | |
| 4457 | + $parts = [ | |
| 3899 | 4458 | $customer->first_name, |
| 3900 | 4459 | $customer->last_name, |
| 3901 | 4460 | md5(serialize($customer)), |
| 3902 | - ); | |
| 4461 | + ]; | |
| 3903 | 4462 | |
| 3904 | 4463 | // join fetched parts |
| 3905 | 4464 | $customer->docsfolder = strtolower(implode('-', array_filter($parts))); |
| 3906 | 4465 | |
| 3907 | 4466 | if (strlen($customer->docsfolder) < 16) { |
| 3908 | - throw new Exception('Possible security breach. Please specify as many details as possible.', 400); | |
| 4467 | + VBOHttpDocument::getInstance($app)->close(400, 'Possible security breach. Please specify as many details as possible.'); | |
| 3909 | 4468 | } |
| 3910 | 4469 | |
| 3911 | - jimport('joomla.filesystem.folder'); | |
| 3912 | - | |
| 3913 | 4470 | // create a folder for this customer |
| 3914 | 4471 | $created = JFolder::create($dirpath . $customer->docsfolder); |
| 3915 | 4472 | |
| 3916 | 4473 | if (!$created) { |
| 3917 | - throw new Exception(sprintf('Unable to create the folder [%s]', $dirpath . $customer->docsfolder), 403); | |
| 4474 | + VBOHttpDocument::getInstance($app)->close(403, sprintf('Unable to create the folder [%s]', $dirpath . $customer->docsfolder)); | |
| 3918 | 4475 | } |
| 3919 | 4476 | |
| 3920 | 4477 | unset($customer->seed); |
| 3921 | 4478 | |
| @@ -3925,10 +4482,12 @@ | ||
| 3925 | 4482 | $record->docsfolder = $customer->docsfolder; |
| 3926 | 4483 | $dbo->updateObject('#__vikbooking_customers', $record, 'id'); |
| 3927 | 4484 | } |
| 3928 | 4485 | |
| 3929 | - // prepare the response array of uploaded-file objects | |
| 3930 | - $response = array(); | |
| 4486 | + // prepare the response with the uploaded-file objects | |
| 4487 | + $response = [ | |
| 4488 | + 'uploads' => [], | |
| 4489 | + ]; | |
| 3931 | 4490 | $upload_err = null; |
| 3932 | 4491 | |
| 3933 | 4492 | // compose prefix for all files uploaded (must end with an underscrore for View's compatibility) |
| 3934 | 4493 | $file_prefix = str_replace(' ', '-', JText::translate('VBOPRECHECKIN')) . '_'; |
| @@ -3946,13 +4505,13 @@ | ||
| 3946 | 4505 | } |
| 3947 | 4506 | // always prepend "pre check-in" to the original file name |
| 3948 | 4507 | $file['name'] = strtolower($file_prefix . $file['name']); |
| 3949 | 4508 | // try to upload the file |
| 3950 | - $result = VikBooking::uploadFileFromRequest($file, $dirpath . $customer->docsfolder, "/(image\/.+)|(application\/(zip|pdf|msword|vnd.*?))|(text\/(plain|markdown|csv))$/i"); | |
| 4509 | + $result = VikBooking::uploadFileFromRequest($file, $dirpath . $customer->docsfolder, 'png,jpg,jpeg,bmp,heic,zip,rar,pdf,doc,docx,rtf,odt,pages,xls,xlsx,csv,ods,numbers,txt,md'); | |
| 3951 | 4510 | // set a valid URL for the uploaded file |
| 3952 | 4511 | $result->url = str_replace(DIRECTORY_SEPARATOR, '/', str_replace(VBO_CUSTOMERS_PATH . DIRECTORY_SEPARATOR, VBO_CUSTOMERS_URI, $result->path)); |
| 3953 | 4512 | // push uploaded file |
| 3954 | - array_push($response, $result); | |
| 4513 | + array_push($response['uploads'], $result); | |
| 3955 | 4514 | } |
| 3956 | 4515 | |
| 3957 | 4516 | } catch (Exception $e) { |
| 3958 | 4517 | // do nothing, but catch the error |
| @@ -3958,15 +4517,56 @@ | ||
| 3958 | 4517 | // do nothing, but catch the error |
| 3959 | 4518 | $upload_err = $e; |
| 3960 | 4519 | } |
| 3961 | 4520 | |
| 3962 | - if (!count($response)) { | |
| 3963 | - throw ($upload_err !== null ? $upload_err : (new Exception('No files could actually be uploaded', 500))); | |
| 4521 | + if (!$response['uploads']) { | |
| 4522 | + // raise an error | |
| 4523 | + if ($upload_err instanceof Exception) { | |
| 4524 | + VBOHttpDocument::getInstance($app)->close($upload_err->getCode() ?: 500, $upload_err->getMessage()); | |
| 4525 | + } | |
| 4526 | + VBOHttpDocument::getInstance($app)->close(500, 'No files could actually be uploaded'); | |
| 3964 | 4527 | } |
| 3965 | 4528 | |
| 3966 | - // output the response | |
| 3967 | - echo json_encode($response); | |
| 3968 | - exit; | |
| 4529 | + if ($use_mrz) { | |
| 4530 | + // file upload completed, detect MRZ codes from uploaded documents | |
| 4531 | + $mrzImageUrls = array_values(array_filter(array_map(function($uploaded) { | |
| 4532 | + return $uploaded->url ?? ''; | |
| 4533 | + }, $response['uploads']))); | |
| 4534 | + | |
| 4535 | + // set response properties for MRZ detection result | |
| 4536 | + $response['mrz'] = [ | |
| 4537 | + 'data' => null, | |
| 4538 | + 'raw' => null, | |
| 4539 | + 'verified' => null, | |
| 4540 | + 'error' => null, | |
| 4541 | + ]; | |
| 4542 | + | |
| 4543 | + try { | |
| 4544 | + // let the AI model service analyse the uploaded documents | |
| 4545 | + $mrzResult = (new VCMAiModelService)->mrzDetection($mrzImageUrls); | |
| 4546 | + | |
| 4547 | + // let the current pax-fields data collector parse the extracted MRZ data | |
| 4548 | + $mrzMappedFields = VBOCheckinPax::getInstance() | |
| 4549 | + ->getMRZMapper() | |
| 4550 | + ->setVerified((bool) ($mrzResult->valid ?? false)) | |
| 4551 | + ->mapDetectedProperties((array) ($mrzResult->data ?? null)) | |
| 4552 | + ->getMappedFields(); | |
| 4553 | + | |
| 4554 | + // set MRZ validation result within the response | |
| 4555 | + $response['mrz']['data'] = $mrzMappedFields; | |
| 4556 | + $response['mrz']['raw'] = ($mrzResult->data ?? null); | |
| 4557 | + $response['mrz']['verified'] = (bool) $mrzResult->valid ?? false; | |
| 4558 | + } catch (Exception $e) { | |
| 4559 | + // catch the error message | |
| 4560 | + $response['mrz']['error'] = $e->getMessage(); | |
| 4561 | + } catch (Throwable $e) { | |
| 4562 | + // catch the PHP error | |
| 4563 | + $response['mrz']['error'] = sprintf('Fatal error caught: %s', $e->getMessage()); | |
| 4564 | + } | |
| 4565 | + } | |
| 4566 | + | |
| 4567 | + // send response to output | |
| 4568 | + VBOHttpDocument::getInstance($app)->json($response); | |
| 3969 | 4569 | } |
| 3970 | 4570 | |
| 3971 | 4571 | /** |
| 3972 | 4572 | * AJAX endpoint to submit an inquiry/information request. |
| @@ -3992,11 +4592,11 @@ | ||
| 3992 | 4592 | $checkout_h = $input->getInt('checkouth', 0); |
| 3993 | 4593 | $checkout_m = $input->getInt('checkoutm', 0); |
| 3994 | 4594 | $categories = $input->getString('categories', ''); |
| 3995 | 4595 | $roomsnum = $input->getInt('roomsnum', 1); |
| 3996 | - $adults = $input->get('adults', array(), 'int'); | |
| 3997 | - $children = $input->get('children', array(), 'int'); | |
| 3998 | - $inquiry = $input->get('inquiry', array(), 'raw'); | |
| 4596 | + $adults = $input->get('adults', [], 'int'); | |
| 4597 | + $children = $input->get('children', [], 'int'); | |
| 4598 | + $inquiry = $input->get('inquiry', [], 'array'); | |
| 3999 | 4599 | $ulang = $input->getString('ulang', ''); |
| 4000 | 4600 | |
| 4001 | 4601 | $timeopst = VikBooking::getTimeOpenStore(); |
| 4002 | 4602 | if (empty($checkin_h) && empty($checkout_h) && is_array($timeopst)) { |
| @@ -4041,8 +4641,10 @@ | ||
| 4041 | 4641 | if (!is_scalar($info_val) || !strlen($info_val)) { |
| 4042 | 4642 | // empty or invalid field |
| 4043 | 4643 | continue; |
| 4044 | 4644 | } |
| 4645 | + // accept only text from submitted value | |
| 4646 | + $info_val = strip_tags($info_val); | |
| 4045 | 4647 | if ($info_type == 'nominative') { |
| 4046 | 4648 | if (empty($t_first_name)) { |
| 4047 | 4649 | $t_first_name = $info_val; |
| 4048 | 4650 | $res_custdata[JText::translate('VBNAME')] = $info_val; |
| @@ -4435,10 +5037,10 @@ | ||
| 4435 | 5037 | // update booking total amounts as first |
| 4436 | 5038 | if ($upgrade_difference > 0) { |
| 4437 | 5039 | $upd_booking = new stdClass; |
| 4438 | 5040 | $upd_booking->id = $booking['id']; |
| 4439 | - $upd_booking->total = $booking['total'] + $upgrade_difference; | |
| 4440 | - $upd_booking->tot_taxes = $booking['tot_taxes'] + $upgtax_difference; | |
| 5041 | + $upd_booking->total = (float)($booking['total'] + $upgrade_difference); | |
| 5042 | + $upd_booking->tot_taxes = (float)($booking['tot_taxes'] + $upgtax_difference); | |
| 4441 | 5043 | |
| 4442 | 5044 | $dbo->updateObject('#__vikbooking_orders', $upd_booking, 'id'); |
| 4443 | 5045 | } |
| 4444 | 5046 | |
| @@ -4458,6 +5060,153 @@ | ||
| 4458 | 5060 | VikBooking::getBookingHistoryInstance()->setBid($booking['id'])->store('UR', $hist_descr); |
| 4459 | 5061 | |
| 4460 | 5062 | // output the JSON encoded successful response |
| 4461 | 5063 | VBOHttpDocument::getInstance()->json(['success' => 1]); |
| 5064 | + } | |
| 5065 | + | |
| 5066 | + /** | |
| 5067 | + * End-point used to ping the execution of the scheduled crontab runners. | |
| 5068 | + * | |
| 5069 | + * @return void | |
| 5070 | + * | |
| 5071 | + * @since 1.17 (J) - 1.7 (WP) | |
| 5072 | + */ | |
| 5073 | + public function crontab() | |
| 5074 | + { | |
| 5075 | + VBOFactory::getCrontabSimulator()->run(); | |
| 5076 | + exit; | |
| 5077 | + } | |
| 5078 | + | |
| 5079 | + /** | |
| 5080 | + * End-point used to route at runtime the link to the requested room details page. | |
| 5081 | + * Originally introduced to improve landing page URL accuracy with Google VR. | |
| 5082 | + * | |
| 5083 | + * @return void | |
| 5084 | + * | |
| 5085 | + * @since 1.18.3 (J) - 1.8.3 (WP) | |
| 5086 | + */ | |
| 5087 | + public function route_listing_details() | |
| 5088 | + { | |
| 5089 | + $app = JFactory::getApplication(); | |
| 5090 | + | |
| 5091 | + $listing_id = $app->input->getUint('listing_id', 0); | |
| 5092 | + | |
| 5093 | + if (!$listing_id) { | |
| 5094 | + VBOHttpDocument::getInstance($app)->close(400, 'Missing listing ID.'); | |
| 5095 | + } | |
| 5096 | + | |
| 5097 | + // make sure the requested listing exists | |
| 5098 | + $listing_data = VikBooking::getRoomInfo($listing_id); | |
| 5099 | + | |
| 5100 | + if (!$listing_data) { | |
| 5101 | + VBOHttpDocument::getInstance($app)->close(404, 'Listing not found.'); | |
| 5102 | + } | |
| 5103 | + | |
| 5104 | + if (empty($listing_data['avail'])) { | |
| 5105 | + VBOHttpDocument::getInstance($app)->close(404, 'Listing currently unavailable.'); | |
| 5106 | + } | |
| 5107 | + | |
| 5108 | + // get user country and locale (preferred language), if available, to find the best language | |
| 5109 | + $user_country = $app->input->getString('country', ''); | |
| 5110 | + $user_lang = $app->input->getString('ulang', ''); | |
| 5111 | + $user_country = empty($user_country) && !empty($user_lang) ? $user_lang : $user_country; | |
| 5112 | + $best_lang = (!empty($user_country) || !empty($user_lang)) && class_exists('VikChannelManager') ? VikChannelManager::guessBookingLangFromCountry($user_country, $user_lang) : ''; | |
| 5113 | + | |
| 5114 | + /** | |
| 5115 | + * Adjust best language in case the default website lang is different than English and | |
| 5116 | + * the visitor speaks a foreign language. This way we make English the best language. | |
| 5117 | + * I.E. Website default lang is IT, visitor's lang is DE, we make it land on EN because DE isn't available. | |
| 5118 | + */ | |
| 5119 | + $current_lang = JFactory::getLanguage()->getTag(); | |
| 5120 | + if (empty($best_lang) && !empty($user_lang) && substr(strtolower($user_lang), 0, 2) != 'en' && substr(strtolower($current_lang), 0, 2) != 'en') { | |
| 5121 | + // check if English is available | |
| 5122 | + foreach (VikBooking::getVboApplication()->getKnownLanguages() as $ltag => $ldet) { | |
| 5123 | + if (substr(strtolower($ltag), 0, 2) == 'en') { | |
| 5124 | + // grab this English-esque language | |
| 5125 | + $best_lang = $ltag; | |
| 5126 | + break; | |
| 5127 | + } | |
| 5128 | + } | |
| 5129 | + } | |
| 5130 | + | |
| 5131 | + // build query arguments for routing | |
| 5132 | + $route_query_args = [ | |
| 5133 | + 'option' => 'com_vikbooking', | |
| 5134 | + 'view' => 'roomdetails', | |
| 5135 | + 'roomid' => $listing_id, | |
| 5136 | + ]; | |
| 5137 | + | |
| 5138 | + // route the best URI (if possible) | |
| 5139 | + try { | |
| 5140 | + // find the best page id for the URL according to the CMS | |
| 5141 | + $itemid = null; | |
| 5142 | + | |
| 5143 | + if (VBOPlatformDetection::isWordPress()) { | |
| 5144 | + /** | |
| 5145 | + * @wponly route the best Shortcode for the booking process ("room details" or "search form"). | |
| 5146 | + * the best booking language is passed over the model to find the best shortcode. | |
| 5147 | + */ | |
| 5148 | + $model = JModel::getInstance('vikbooking', 'shortcodes', 'admin'); | |
| 5149 | + | |
| 5150 | + // grab all Shortcodes and parse them to find the best one for this room, if any | |
| 5151 | + $shortcodes = $model->all(); | |
| 5152 | + foreach ($shortcodes as $shortcode) { | |
| 5153 | + if ($shortcode->type != 'roomdetails' || empty($shortcode->post_id)) { | |
| 5154 | + continue; | |
| 5155 | + } | |
| 5156 | + $page_params = json_decode($shortcode->json); | |
| 5157 | + if (!is_object($page_params) || !isset($page_params->roomid) || (int) $page_params->roomid != $listing_id) { | |
| 5158 | + continue; | |
| 5159 | + } | |
| 5160 | + if (!empty($best_lang) && $shortcode->lang == $best_lang) { | |
| 5161 | + // always give higher priority to the exact lang | |
| 5162 | + $itemid = $shortcode->post_id; | |
| 5163 | + } | |
| 5164 | + if (empty($itemid)) { | |
| 5165 | + // in case no perfect lang found, use this post id for this room-type | |
| 5166 | + $itemid = $shortcode->post_id; | |
| 5167 | + } | |
| 5168 | + } | |
| 5169 | + | |
| 5170 | + if (empty($itemid)) { | |
| 5171 | + // default to the best shortcode for this lang of type "search form" | |
| 5172 | + $itemid = $model->best(['vikbooking'], $best_lang); | |
| 5173 | + if (!$itemid) { | |
| 5174 | + // if we really get to this point, try to fetch the first non-empty shortcode, if any | |
| 5175 | + $shortcodes = $model->all($columns = 'post_id', $full = true); | |
| 5176 | + if ($shortcodes) { | |
| 5177 | + // grab the very first active shortcode, no matter what's the type of it | |
| 5178 | + $itemid = $shortcodes[0]->post_id; | |
| 5179 | + } | |
| 5180 | + } | |
| 5181 | + } | |
| 5182 | + } else { | |
| 5183 | + /** | |
| 5184 | + * @joomlaonly inject the language to the query arguments list before routing | |
| 5185 | + */ | |
| 5186 | + if (!empty($best_lang)) { | |
| 5187 | + $route_query_args['lang'] = $best_lang; | |
| 5188 | + } | |
| 5189 | + | |
| 5190 | + if (class_exists('VCMFactory')) { | |
| 5191 | + $best_menuitem_id = VCMFactory::getPlatform()->getPagerouter()->findProperPageId(['roomdetails'], ['roomid' => $room_type_id, 'lang' => $best_lang]); | |
| 5192 | + | |
| 5193 | + if ($best_menuitem_id) { | |
| 5194 | + $itemid = $best_menuitem_id; | |
| 5195 | + } else { | |
| 5196 | + $itemid = VCMFactory::getPlatform()->getPagerouter()->findProperPageId(['vikbooking'], $best_lang); | |
| 5197 | + } | |
| 5198 | + } | |
| 5199 | + } | |
| 5200 | + | |
| 5201 | + // route final URL with all arguments | |
| 5202 | + $routed_url = VikBooking::externalroute($route_query_args, false, $itemid); | |
| 5203 | + } catch (Throwable $e) { | |
| 5204 | + // raise an error | |
| 5205 | + VBOHttpDocument::getInstance($app)->close(500, 'Could not route to the requested page.'); | |
| 5206 | + } | |
| 5207 | + | |
| 5208 | + // redirect the request to the proper page | |
| 5209 | + $app->redirect($routed_url); | |
| 5210 | + $app->close(); | |
| 4462 | 5211 | } |
| 4463 | 5212 | } |