| @@ -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 | |
| @@ -943,8 +1024,31 @@ | ||
| 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 | |
| 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 | + | |
| 947 | 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) { |
| @@ -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'); |
| @@ -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'); |
| @@ -1482,9 +1637,20 @@ | ||
| 1482 | 1637 | // VBO 1.11 - push data to tracker for conversion |
| 1483 | 1638 | $vbo_tracker->pushData('idorder', $neworderid)->closeTrack(); |
| 1484 | 1639 | $vbo_tracker->resetTrack(); |
| 1485 | 1640 | |
| 1486 | - $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)); | |
| 1487 | 1653 | } |
| 1488 | 1654 | } |
| 1489 | 1655 | |
| 1490 | 1656 | public function vieworder() |
| @@ -1494,18 +1660,24 @@ | ||
| 1494 | 1660 | } |
| 1495 | 1661 | |
| 1496 | 1662 | public function cancelrequest() |
| 1497 | 1663 | { |
| 1664 | + if (!JSession::checkToken()) { | |
| 1665 | + // missing CSRF-proof token | |
| 1666 | + VBOHttpDocument::getInstance()->close(403, JText::translate('JINVALID_TOKEN')); | |
| 1667 | + } | |
| 1668 | + | |
| 1498 | 1669 | $dbo = JFactory::getDbo(); |
| 1499 | 1670 | $mainframe = JFactory::getApplication(); |
| 1671 | + | |
| 1500 | 1672 | $psid = VikRequest::getString('sid', '', 'request'); |
| 1501 | 1673 | $pidorder = VikRequest::getString('idorder', '', 'request'); |
| 1674 | + | |
| 1502 | 1675 | if (!empty($psid) && !empty($pidorder)) { |
| 1503 | 1676 | $q = "SELECT * FROM `#__vikbooking_orders` WHERE `id`=".intval($pidorder)." AND `sid`=".$dbo->quote($psid).";"; |
| 1504 | 1677 | $dbo->setQuery($q); |
| 1505 | - $dbo->execute(); | |
| 1506 | - if ($dbo->getNumRows() == 1) { | |
| 1507 | - $order = $dbo->loadAssocList(); | |
| 1678 | + $order = $dbo->loadAssocList(); | |
| 1679 | + if ($order) { | |
| 1508 | 1680 | $pemail = VikRequest::getString('email', '', 'request'); |
| 1509 | 1681 | $preason = VikRequest::getString('reason', '', 'request'); |
| 1510 | 1682 | if (!empty($pemail) && !empty($preason)) { |
| 1511 | 1683 | $to = VikBooking::getAdminMail(); |
| @@ -1651,10 +1823,13 @@ | ||
| 1651 | 1823 | } |
| 1652 | 1824 | |
| 1653 | 1825 | public function notifypayment() |
| 1654 | 1826 | { |
| 1827 | + $app = JFactory::getApplication(); | |
| 1655 | 1828 | $dbo = JFactory::getDbo(); |
| 1656 | 1829 | |
| 1830 | + $session = JFactory::getSession(); | |
| 1831 | + | |
| 1657 | 1832 | $config = VBOFactory::getConfig(); |
| 1658 | 1833 | $av_helper = VikBooking::getAvailabilityInstance(); |
| 1659 | 1834 | |
| 1660 | 1835 | $psid = VikRequest::getString('sid', '', 'request'); |
| @@ -1686,18 +1861,16 @@ | ||
| 1686 | 1861 | $recipient_mail = $all_recipients; |
| 1687 | 1862 | } |
| 1688 | 1863 | } |
| 1689 | 1864 | |
| 1865 | + // load booking details | |
| 1690 | 1866 | $q = "SELECT * FROM `#__vikbooking_orders` WHERE (`sid`=" . $dbo->quote($psid) . " OR `idorderota`=" . $dbo->quote($psid) . ") AND `ts`=" . $dbo->quote($pts); |
| 1691 | 1867 | $dbo->setQuery($q, 0, 1); |
| 1692 | - $dbo->execute(); | |
| 1693 | - if (!$dbo->getNumRows()) { | |
| 1868 | + $row = $dbo->loadAssoc(); | |
| 1869 | + if (!$row) { | |
| 1694 | 1870 | VBOHttpDocument::getInstance()->close(404, 'Booking not found'); |
| 1695 | 1871 | } |
| 1696 | 1872 | |
| 1697 | - // load booking details | |
| 1698 | - $row = $dbo->loadAssoc(); | |
| 1699 | - | |
| 1700 | 1873 | // check if the language in use is the same as the one used during the checkout |
| 1701 | 1874 | if (!empty($row['lang'])) { |
| 1702 | 1875 | $lang = JFactory::getLanguage(); |
| 1703 | 1876 | if ($lang->getTag() != $row['lang']) { |
| @@ -1712,9 +1885,12 @@ | ||
| 1712 | 1885 | $vbo_tn = VikBooking::getTranslator(); |
| 1713 | 1886 | |
| 1714 | 1887 | if ($row['status'] == 'confirmed' && !(VikBooking::multiplePayments() && $row['paymcount'] > 0)) { |
| 1715 | 1888 | // booking can be paid only if not confirmed or if multiple payments are enabled and payment counter for booking greater than zero |
| 1716 | - 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 | + } | |
| 1717 | 1893 | } |
| 1718 | 1894 | |
| 1719 | 1895 | /** |
| 1720 | 1896 | * Check split stay reservation data. |
| @@ -1789,12 +1965,34 @@ | ||
| 1789 | 1965 | // inject values |
| 1790 | 1966 | $row['order_rooms'] = $orderrooms; |
| 1791 | 1967 | $row['fares'] = $tars; |
| 1792 | 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 | |
| 1793 | 1990 | $isdue = 0; |
| 1794 | 1991 | $tot_taxes = 0; |
| 1795 | 1992 | $tot_city_taxes = 0; |
| 1796 | 1993 | $tot_fees = 0; |
| 1994 | + $tot_damage_dep = 0; | |
| 1797 | 1995 | $pricestr = []; |
| 1798 | 1996 | $optstr = []; |
| 1799 | 1997 | foreach ($orderrooms as $kor => $or) { |
| 1800 | 1998 | $num = $kor + 1; |
| @@ -1843,8 +2041,13 @@ | ||
| 1843 | 2041 | $dbo->setQuery($q); |
| 1844 | 2042 | $actopt = $dbo->loadAssocList(); |
| 1845 | 2043 | if ($actopt) { |
| 1846 | 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 | + | |
| 1847 | 2050 | $chvar = ''; |
| 1848 | 2051 | if (!empty($actopt[0]['ageintervals']) && $or['children'] > 0 && strstr($stept[1], '-') != false) { |
| 1849 | 2052 | $optagenames = VikBooking::getOptionIntervalsAges($actopt[0]['ageintervals']); |
| 1850 | 2053 | $optagepcent = VikBooking::getOptionIntervalsPercentage($actopt[0]['ageintervals']); |
| @@ -1894,8 +2097,19 @@ | ||
| 1894 | 2097 | } |
| 1895 | 2098 | if ($actopt[0]['perperson'] == 1) { |
| 1896 | 2099 | $realcost = $realcost * $or['adults']; |
| 1897 | 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 | + | |
| 1898 | 2112 | $opt_minus_tax = VikBooking::sayOptionalsMinusIva($realcost, $actopt[0]['idiva']); |
| 1899 | 2113 | $tmpopr = VikBooking::sayOptionalsPlusIva($realcost, $actopt[0]['idiva']); |
| 1900 | 2114 | if ($actopt[0]['is_citytax'] == 1) { |
| 1901 | 2115 | $tot_city_taxes += $opt_minus_tax; |
| @@ -1900,10 +2114,12 @@ | ||
| 1900 | 2114 | if ($actopt[0]['is_citytax'] == 1) { |
| 1901 | 2115 | $tot_city_taxes += $opt_minus_tax; |
| 1902 | 2116 | } elseif ($actopt[0]['is_fee'] == 1) { |
| 1903 | 2117 | $tot_fees += $opt_minus_tax; |
| 2118 | + } elseif ($opt_params['damagedep'] ?? 0) { | |
| 2119 | + $tot_damage_dep += $opt_minus_tax; | |
| 1904 | 2120 | } |
| 1905 | - // 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 | |
| 1906 | 2122 | if ($tmpopr == $realcost) { |
| 1907 | 2123 | $tot_taxes += ($realcost - $opt_minus_tax); |
| 1908 | 2124 | } else { |
| 1909 | 2125 | $tot_taxes += ($tmpopr - $realcost); |
| @@ -1934,16 +2150,19 @@ | ||
| 1934 | 2150 | $expcoupon = explode(";", $row['coupon']); |
| 1935 | 2151 | $isdue = $isdue - $expcoupon[1]; |
| 1936 | 2152 | } |
| 1937 | 2153 | |
| 1938 | - // invoke the payment method class | |
| 1939 | - $exppay = explode('=', $row['idpayment']); | |
| 1940 | - $payment = VikBooking::getPayment($exppay[0], $vbo_tn); | |
| 1941 | - | |
| 1942 | 2154 | if (empty($row['sid']) && !empty($row['idorderota']) && !empty($row['channel'])) { |
| 1943 | 2155 | $row['sid'] = $row['idorderota']; |
| 1944 | 2156 | } |
| 1945 | 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 | + | |
| 1946 | 2165 | if (VBOPlatformDetection::isWordPress()) { |
| 1947 | 2166 | /** |
| 1948 | 2167 | * @wponly The payment gateway is now loaded |
| 1949 | 2168 | * using the apposite dispatcher. |
| @@ -1952,9 +2171,9 @@ | ||
| 1952 | 2171 | */ |
| 1953 | 2172 | JLoader::import('adapter.payment.dispatcher'); |
| 1954 | 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']; |
| 1955 | 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']; |
| 1956 | - $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"; | |
| 1957 | 2176 | $model = JModel::getInstance('vikbooking', 'shortcodes', 'admin'); |
| 1958 | 2177 | $itemid = $model->best(array('booking'), (!empty($row['lang']) ? $row['lang'] : null)); |
| 1959 | 2178 | $extra_data = []; |
| 1960 | 2179 | if ($itemid) { |
| @@ -1985,9 +2204,9 @@ | ||
| 1985 | 2204 | $bestitemid = VikBooking::findProperItemIdType(array('booking')); |
| 1986 | 2205 | $extra_data = array( |
| 1987 | 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)), |
| 1988 | 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)), |
| 1989 | - '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), | |
| 1990 | 2209 | ); |
| 1991 | 2210 | $extra_data['transaction_currency'] = VikBooking::getCurrencyCodePp(); |
| 1992 | 2211 | |
| 1993 | 2212 | $obj = VBOPaymentFactory::getPaymentInstance($payment['file'], array_merge($row, $extra_data), $payment['params']); |
| @@ -1992,9 +2211,20 @@ | ||
| 1992 | 2211 | |
| 1993 | 2212 | $obj = VBOPaymentFactory::getPaymentInstance($payment['file'], array_merge($row, $extra_data), $payment['params']); |
| 1994 | 2213 | } |
| 1995 | 2214 | |
| 1996 | - $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 | |
| 1997 | 2227 | $newpaymentlog = date('c')."\n".$array_result['log']."\n----------\n".$row['paymentlog']; |
| 1998 | 2228 | |
| 1999 | 2229 | /** |
| 2000 | 2230 | * OTA reservations containing PCI-DSS card details may receive additional payments through |
| @@ -2008,11 +2238,21 @@ | ||
| 2008 | 2238 | $newpaymentlog = $row['paymentlog'] . "\n----------\n" . date('c') . "\n" . $array_result['log']; |
| 2009 | 2239 | } |
| 2010 | 2240 | } |
| 2011 | 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 | + | |
| 2012 | 2251 | if ($array_result['verified'] == 1) { |
| 2013 | 2252 | // valid payment |
| 2014 | 2253 | $shouldpay = $isdue; |
| 2254 | + | |
| 2015 | 2255 | if ($payment['charge'] > 0.00) { |
| 2016 | 2256 | if ($payment['ch_disc'] == 1) { |
| 2017 | 2257 | // charge |
| 2018 | 2258 | if ($payment['val_pcent'] == 1) { |
| @@ -2034,39 +2274,60 @@ | ||
| 2034 | 2274 | $shouldpay -= $percent_to_pay; |
| 2035 | 2275 | } |
| 2036 | 2276 | } |
| 2037 | 2277 | } |
| 2038 | - //deposit may be skipped by customer choice | |
| 2278 | + | |
| 2279 | + // deposit may be skipped by customer choice | |
| 2039 | 2280 | $shouldpay_befdep = $shouldpay; |
| 2040 | - // | |
| 2281 | + $shouldpay_befdep_no_dd = $shouldpay - ($damage_deposit_payment['damagedep_gross'] ?? 0); | |
| 2282 | + | |
| 2041 | 2283 | if (!VikBooking::payTotal()) { |
| 2042 | 2284 | $percentdeposit = VikBooking::getAccPerCent(); |
| 2043 | 2285 | if ($percentdeposit > 0) { |
| 2044 | 2286 | if (VikBooking::getTypeDeposit() == "fixed") { |
| 2045 | 2287 | $shouldpay = $percentdeposit; |
| 2288 | + $shouldpay_befdep_no_dd = $percentdeposit; | |
| 2046 | 2289 | } else { |
| 2047 | 2290 | $shouldpay = $shouldpay * $percentdeposit / 100; |
| 2291 | + $shouldpay_befdep_no_dd = $shouldpay_befdep_no_dd * $percentdeposit / 100;; | |
| 2048 | 2292 | } |
| 2049 | 2293 | } |
| 2050 | 2294 | } |
| 2051 | - //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 | |
| 2052 | 2301 | if (isset($array_result['tot_paid'])) { |
| 2053 | 2302 | $shouldpay = round($shouldpay, 2); |
| 2054 | 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); | |
| 2055 | 2306 | $totreceived = round($array_result['tot_paid'], 2); |
| 2056 | - if ($shouldpay != $totreceived && $shouldpay_befdep != $totreceived && $row['paymcount'] == 0) { | |
| 2057 | - //the amount paid is different than the order total | |
| 2058 | - //fares might have changed or the deposit might be different | |
| 2059 | - //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 | |
| 2060 | 2311 | $vbo_app = VikBooking::getVboApplication(); |
| 2061 | 2312 | $adsendermail = VikBooking::getSenderMail(); |
| 2062 | - $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 | + ); | |
| 2063 | 2327 | } |
| 2064 | - /** | |
| 2065 | - * We store the amount paid before applying the charge for the transaction. | |
| 2066 | - * | |
| 2067 | - * @since 1.3.0 | |
| 2068 | - */ | |
| 2328 | + | |
| 2329 | + // amount paid should be stored as exclusive of transaction fees/discounts | |
| 2069 | 2330 | if ($payment['charge'] > 0.00) { |
| 2070 | 2331 | if ($payment['ch_disc'] == 1) { |
| 2071 | 2332 | // charge |
| 2072 | 2333 | if ($payment['val_pcent'] == 1) { |
| @@ -2130,12 +2391,29 @@ | ||
| 2130 | 2391 | // update payable amount in case of up-sells or simply in case of another payment received |
| 2131 | 2392 | $new_payable = isset($array_result['tot_paid']) && $array_result['tot_paid'] ? ($row['payable'] - $array_result['tot_paid']) : 0; |
| 2132 | 2393 | $new_payable = $new_payable < 0 ? 0 : $new_payable; |
| 2133 | 2394 | |
| 2134 | - $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'] . "';"; | |
| 2135 | - $dbo->setQuery($q); | |
| 2136 | - $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; | |
| 2137 | 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 | + | |
| 2138 | 2416 | // assign room specific unit |
| 2139 | 2417 | $set_room_indexes = VikBooking::autoRoomUnit(); |
| 2140 | 2418 | $room_indexes_usemap = []; |
| 2141 | 2419 | if ($set_room_indexes === true) { |
| @@ -2140,9 +2418,8 @@ | ||
| 2140 | 2418 | $room_indexes_usemap = []; |
| 2141 | 2419 | if ($set_room_indexes === true) { |
| 2142 | 2420 | $q = "SELECT `id`,`idroom`,`roomindex` FROM `#__vikbooking_ordersrooms` WHERE `idorder`=".(int)$row['id'].";"; |
| 2143 | 2421 | $dbo->setQuery($q); |
| 2144 | - $dbo->execute(); | |
| 2145 | 2422 | $orooms = $dbo->loadAssocList(); |
| 2146 | 2423 | foreach ($orooms as $oroom) { |
| 2147 | 2424 | if (!empty($oroom['roomindex'])) { |
| 2148 | 2425 | // room specific unit has already been assigned |
| @@ -2175,8 +2452,13 @@ | ||
| 2175 | 2452 | $q = "DELETE FROM `#__vikbooking_tmplock` WHERE `idorder`=" . intval($row['id']) . ";"; |
| 2176 | 2453 | $dbo->setQuery($q); |
| 2177 | 2454 | $dbo->execute(); |
| 2178 | 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 | + | |
| 2179 | 2461 | // customer booking |
| 2180 | 2462 | $q = "SELECT `idcustomer` FROM `#__vikbooking_customers_orders` WHERE `idorder`=".(int)$row['id'].";"; |
| 2181 | 2463 | $dbo->setQuery($q); |
| 2182 | 2464 | $customer_id = $dbo->loadResult(); |
| @@ -2187,14 +2469,27 @@ | ||
| 2187 | 2469 | |
| 2188 | 2470 | // check if some of the rooms booked have shared calendars |
| 2189 | 2471 | VikBooking::updateSharedCalendars($row['id'], array(), $row['checkin'], $row['checkout']); |
| 2190 | 2472 | |
| 2191 | - // send email notification to guest and admin | |
| 2192 | - 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 | + } | |
| 2193 | 2483 | |
| 2194 | - // SMS | |
| 2195 | - VikBooking::sendBookingSMS($row['id']); | |
| 2484 | + if ($send_notifications) { | |
| 2485 | + // send email notification to guest and admin | |
| 2486 | + VikBooking::sendBookingEmail($row['id'], array('guest', 'admin')); | |
| 2196 | 2487 | |
| 2488 | + // SMS | |
| 2489 | + VikBooking::sendBookingSMS($row['id']); | |
| 2490 | + } | |
| 2491 | + | |
| 2197 | 2492 | /** |
| 2198 | 2493 | * Payment gateways may set and return the transaction information |
| 2199 | 2494 | * to eventually support a later transaction of type refund. |
| 2200 | 2495 | * |
| @@ -2199,26 +2494,47 @@ | ||
| 2199 | 2494 | * to eventually support a later transaction of type refund. |
| 2200 | 2495 | * |
| 2201 | 2496 | * @since 1.14 (J) - 1.4.0 (WP) |
| 2202 | 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. | |
| 2203 | 2500 | */ |
| 2204 | - $tn_data = isset($array_result['transaction']) ? $array_result['transaction'] : null; | |
| 2501 | + $tn_data = $array_result['transaction'] ?? null; | |
| 2205 | 2502 | if (isset($array_result['tot_paid']) && $array_result['tot_paid']) { |
| 2206 | 2503 | // check event data payload to store |
| 2207 | 2504 | if (is_array($tn_data)) { |
| 2208 | 2505 | // set key |
| 2209 | - $tn_data['amount_paid'] = (float)$array_result['tot_paid']; | |
| 2506 | + $tn_data['amount_paid'] = (float) $array_result['tot_paid']; | |
| 2210 | 2507 | } elseif (is_object($tn_data)) { |
| 2211 | 2508 | // set property |
| 2212 | - $tn_data->amount_paid = (float)$array_result['tot_paid']; | |
| 2509 | + $tn_data->amount_paid = (float) $array_result['tot_paid']; | |
| 2213 | 2510 | } elseif (!$tn_data) { |
| 2214 | 2511 | // build an array (we add the payment name because we know there is no other transaction data) |
| 2215 | 2512 | $tn_data = [ |
| 2216 | - 'amount_paid' => (float)$array_result['tot_paid'], | |
| 2513 | + 'amount_paid' => (float) $array_result['tot_paid'], | |
| 2217 | 2514 | 'payment_method' => $payment['name'], |
| 2218 | 2515 | ]; |
| 2219 | 2516 | } |
| 2220 | 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 | + } | |
| 2221 | 2537 | |
| 2222 | 2538 | // Booking History |
| 2223 | 2539 | VikBooking::getBookingHistoryInstance()->setBid($row['id'])->setExtraData($tn_data)->store('P' . ($row['paymcount'] > 0 ? 'N' : '0'), $payment['name']); |
| 2224 | 2540 | |
| @@ -2227,9 +2543,8 @@ | ||
| 2227 | 2543 | require_once(VCM_SITE_PATH . DIRECTORY_SEPARATOR . "helpers" . DIRECTORY_SEPARATOR . "synch.vikbooking.php"); |
| 2228 | 2544 | $vcm = new SynchVikBooking($row['id']); |
| 2229 | 2545 | $vcm->setPushType('new')->sendRequest(); |
| 2230 | 2546 | } |
| 2231 | - $session = JFactory::getSession(); | |
| 2232 | 2547 | $vcmchanneldata = $session->get('vcmChannelData', ''); |
| 2233 | 2548 | if (!empty($vcmchanneldata)) { |
| 2234 | 2549 | $session->set('vcmChannelData', ''); |
| 2235 | 2550 | } |
| @@ -2252,11 +2567,16 @@ | ||
| 2252 | 2567 | $obj->afterValidation(0); |
| 2253 | 2568 | } |
| 2254 | 2569 | } |
| 2255 | 2570 | } |
| 2256 | - | |
| 2571 | + | |
| 2257 | 2572 | public function currencyconverter() |
| 2258 | 2573 | { |
| 2574 | + if (!JSession::checkToken()) { | |
| 2575 | + // missing CSRF-proof token | |
| 2576 | + VBOHttpDocument::getInstance()->close(403, JText::translate('JINVALID_TOKEN')); | |
| 2577 | + } | |
| 2578 | + | |
| 2259 | 2579 | $session = JFactory::getSession(); |
| 2260 | 2580 | $pprices = VikRequest::getVar('prices', array(0)); |
| 2261 | 2581 | $pfromsymbol = VikRequest::getString('fromsymbol', '', 'request'); |
| 2262 | 2582 | $ptocurrency = VikRequest::getString('tocurrency', '', 'request'); |
| @@ -2300,8 +2620,13 @@ | ||
| 2300 | 2620 | } |
| 2301 | 2621 | |
| 2302 | 2622 | public function storesignature() |
| 2303 | 2623 | { |
| 2624 | + if (!JSession::checkToken()) { | |
| 2625 | + // missing CSRF-proof token | |
| 2626 | + VBOHttpDocument::getInstance()->close(403, JText::translate('JINVALID_TOKEN')); | |
| 2627 | + } | |
| 2628 | + | |
| 2304 | 2629 | $sid = VikRequest::getString('sid', '', 'request'); |
| 2305 | 2630 | $ts = VikRequest::getString('ts', '', 'request'); |
| 2306 | 2631 | $psignature = VikRequest::getString('signature', '', 'request', VIKREQUEST_ALLOWRAW); |
| 2307 | 2632 | $ppad_width = VikRequest::getInt('pad_width', '', 'request'); |
| @@ -2385,8 +2710,13 @@ | ||
| 2385 | 2710 | } |
| 2386 | 2711 | |
| 2387 | 2712 | public function validatepin() |
| 2388 | 2713 | { |
| 2714 | + if (!JSession::checkToken()) { | |
| 2715 | + // missing CSRF-proof token | |
| 2716 | + VBOHttpDocument::getInstance()->close(403, JText::translate('JINVALID_TOKEN')); | |
| 2717 | + } | |
| 2718 | + | |
| 2389 | 2719 | $cpin = VikBooking::getCPinIstance(); |
| 2390 | 2720 | |
| 2391 | 2721 | $ppin = VikRequest::getString('pin', '', 'request'); |
| 2392 | 2722 | |
| @@ -2392,9 +2722,9 @@ | ||
| 2392 | 2722 | |
| 2393 | 2723 | $response = []; |
| 2394 | 2724 | |
| 2395 | 2725 | $customer = $cpin->getCustomerByPin($ppin); |
| 2396 | - if (count($customer)) { | |
| 2726 | + if ($customer) { | |
| 2397 | 2727 | $response = $customer; |
| 2398 | 2728 | $response['success'] = 1; |
| 2399 | 2729 | |
| 2400 | 2730 | if ($cpin->getCustomerCoupon($customer)) { |
| @@ -2408,18 +2738,24 @@ | ||
| 2408 | 2738 | } |
| 2409 | 2739 | |
| 2410 | 2740 | public function docancelbooking() |
| 2411 | 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 | + | |
| 2412 | 2750 | $psid = VikRequest::getString('sid', '', 'request'); |
| 2413 | 2751 | $pidorder = VikRequest::getString('idorder', '', 'request'); |
| 2414 | - $dbo = JFactory::getDBO(); | |
| 2415 | - $mainframe = JFactory::getApplication(); | |
| 2752 | + | |
| 2416 | 2753 | if (!empty($psid) && !empty($pidorder)) { |
| 2417 | 2754 | $q = "SELECT * FROM `#__vikbooking_orders` WHERE `id`=".intval($pidorder)." AND `sid`=".$dbo->quote($psid)." AND `status`='confirmed';"; |
| 2418 | 2755 | $dbo->setQuery($q); |
| 2419 | - $dbo->execute(); | |
| 2420 | - if ($dbo->getNumRows() == 1) { | |
| 2421 | - $order = $dbo->loadAssocList(); | |
| 2756 | + $order = $dbo->loadAssocList(); | |
| 2757 | + if ($order) { | |
| 2422 | 2758 | $pemail = VikRequest::getString('email', '', 'request'); |
| 2423 | 2759 | $preason = VikRequest::getString('reason', '', 'request'); |
| 2424 | 2760 | if (!empty($pemail) && !empty($preason)) { |
| 2425 | 2761 | $to = VikBooking::getAdminMail(); |
| @@ -2454,11 +2790,10 @@ | ||
| 2454 | 2790 | $is_package = !empty($order[0]['pkg']) ? true : false; |
| 2455 | 2791 | $orderrooms = array(); |
| 2456 | 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;"; |
| 2457 | 2793 | $dbo->setQuery($q); |
| 2458 | - $dbo->execute(); | |
| 2459 | - if ($dbo->getNumRows() > 0) { | |
| 2460 | - $orderrooms = $dbo->loadAssocList(); | |
| 2794 | + $orderrooms = $dbo->loadAssocList(); | |
| 2795 | + if ($orderrooms) { | |
| 2461 | 2796 | foreach($orderrooms as $kor => $or) { |
| 2462 | 2797 | $num = $kor + 1; |
| 2463 | 2798 | if($is_package === true || (!empty($or['cust_cost']) && $or['cust_cost'] > 0.00)) { |
| 2464 | 2799 | //package or custom cost set from the back-end |
| @@ -2465,11 +2800,10 @@ | ||
| 2465 | 2800 | continue; |
| 2466 | 2801 | } |
| 2467 | 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'] . "';"; |
| 2468 | 2803 | $dbo->setQuery($q); |
| 2469 | - $dbo->execute(); | |
| 2470 | - if ($dbo->getNumRows() == 1) { | |
| 2471 | - $tar = $dbo->loadAssocList(); | |
| 2804 | + $tar = $dbo->loadAssocList(); | |
| 2805 | + if ($tar) { | |
| 2472 | 2806 | $tars[$num] = $tar[0]; |
| 2473 | 2807 | } |
| 2474 | 2808 | } |
| 2475 | 2809 | } |
| @@ -2551,8 +2885,15 @@ | ||
| 2551 | 2885 | // push the transaction currency information |
| 2552 | 2886 | $order[0]['transaction_currency'] = VikBooking::getCurrencyCodePp(); |
| 2553 | 2887 | |
| 2554 | 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 | + /** | |
| 2555 | 2896 | * @wponly The payment gateway is loaded |
| 2556 | 2897 | * through the apposite dispatcher. |
| 2557 | 2898 | */ |
| 2558 | 2899 | JLoader::import('adapter.payment.dispatcher'); |
| @@ -2570,16 +2911,29 @@ | ||
| 2570 | 2911 | $admin_refund_error .= !empty($array_result['log']) && is_string($array_result['log']) ? $array_result['log'] : ''; |
| 2571 | 2912 | } else { |
| 2572 | 2913 | // refund was successful |
| 2573 | 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 | + | |
| 2574 | 2928 | // update total paid, total and refund columns for the booking |
| 2575 | 2929 | $booking = new stdClass; |
| 2576 | 2930 | $booking->id = $order[0]['id']; |
| 2577 | 2931 | if ($order[0]['totpaid'] > 0) { |
| 2578 | - $booking->totpaid = $order[0]['totpaid'] - $refund_amount; | |
| 2932 | + $booking->totpaid = (float)($order[0]['totpaid'] - $refund_amount); | |
| 2579 | 2933 | } |
| 2580 | 2934 | if ($order[0]['total'] > 0) { |
| 2581 | - $booking->total = $order[0]['total'] - $refund_amount; | |
| 2935 | + $booking->total = (float)($order[0]['total'] - $refund_amount); | |
| 2582 | 2936 | } |
| 2583 | 2937 | $booking->refund = (float)$order[0]['refund'] + $refund_amount; |
| 2584 | 2938 | // update record in db |
| 2585 | 2939 | $dbo->updateObject('#__vikbooking_orders', $booking, 'id'); |
| @@ -2584,12 +2938,12 @@ | ||
| 2584 | 2938 | // update record in db |
| 2585 | 2939 | $dbo->updateObject('#__vikbooking_orders', $booking, 'id'); |
| 2586 | 2940 | |
| 2587 | 2941 | // store the refund event |
| 2588 | - $event_descr = array( | |
| 2942 | + $event_descr = [ | |
| 2589 | 2943 | '(' . $payment['name'] . ')', |
| 2590 | 2944 | $currencysymb . ' ' . VikBooking::numberFormat($refund_amount), |
| 2591 | - ); | |
| 2945 | + ]; | |
| 2592 | 2946 | $history_obj->store('RF', implode("\n", $event_descr)); |
| 2593 | 2947 | } |
| 2594 | 2948 | } |
| 2595 | 2949 | } |
| @@ -2678,9 +3032,13 @@ | ||
| 2678 | 3032 | VikRequest::setVar('start_date', $start_date); |
| 2679 | 3033 | VikRequest::setVar('end_date', $end_date); |
| 2680 | 3034 | //make call to get the result |
| 2681 | 3035 | TACVBO::$getArray = true; |
| 2682 | - $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 | + ]); | |
| 2683 | 3041 | //validate response |
| 2684 | 3042 | if (!is_array($website_rates)) { |
| 2685 | 3043 | //error returned |
| 2686 | 3044 | echo json_encode(array('e4j.error' => $website_rates)); |
| @@ -2791,16 +3149,14 @@ | ||
| 2791 | 3149 | */ |
| 2792 | 3150 | public function operatorlogin() |
| 2793 | 3151 | { |
| 2794 | 3152 | /** |
| 2795 | - * Extra security fix for the login form token. | |
| 2796 | - * | |
| 2797 | - * @since September 8th 2020 | |
| 3153 | + * Secure the login form token. | |
| 2798 | 3154 | */ |
| 2799 | - if (!JFactory::getSession()->checkToken()) { | |
| 2800 | - 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); | |
| 2801 | 3158 | } |
| 2802 | - // | |
| 2803 | 3159 | |
| 2804 | 3160 | $app = JFactory::getApplication(); |
| 2805 | 3161 | $pauthcode = VikRequest::getString('authcode', '', 'request'); |
| 2806 | 3162 | $pitemid = VikRequest::getInt('Itemid', '', 'request'); |
| @@ -2841,39 +3197,44 @@ | ||
| 2841 | 3197 | * @since 1.12 |
| 2842 | 3198 | */ |
| 2843 | 3199 | public function storeprecheckin() |
| 2844 | 3200 | { |
| 3201 | + if (!JSession::checkToken()) { | |
| 3202 | + // missing CSRF-proof token | |
| 3203 | + VBOHttpDocument::getInstance()->close(403, JText::translate('JINVALID_TOKEN')); | |
| 3204 | + } | |
| 3205 | + | |
| 2845 | 3206 | $dbo = JFactory::getDbo(); |
| 2846 | 3207 | $app = JFactory::getApplication(); |
| 2847 | - $sid = VikRequest::getString('sid', '', 'request'); | |
| 2848 | - $ts = VikRequest::getString('ts', '', 'request'); | |
| 2849 | - $pguests = VikRequest::getVar('guests', array()); | |
| 2850 | - $pitemid = VikRequest::getInt('Itemid', 0, 'request'); | |
| 2851 | 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 | + | |
| 2852 | 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';"; |
| 2853 | 3215 | $dbo->setQuery($q); |
| 2854 | - $dbo->execute(); | |
| 2855 | - if (!$dbo->getNumRows()) { | |
| 3216 | + $order = $dbo->loadAssoc(); | |
| 3217 | + if (!$order) { | |
| 2856 | 3218 | throw new Exception('Booking not found', 404); |
| 2857 | 3219 | } |
| 2858 | - $order = $dbo->loadAssoc(); | |
| 2859 | 3220 | |
| 2860 | - $orderrooms = array(); | |
| 2861 | 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;"; |
| 2862 | 3222 | $dbo->setQuery($q); |
| 2863 | - $dbo->execute(); | |
| 2864 | - if (!$dbo->getNumRows()) { | |
| 3223 | + $orderrooms = $dbo->loadAssocList(); | |
| 3224 | + if (!$orderrooms) { | |
| 2865 | 3225 | throw new Exception('No rooms found', 404); |
| 2866 | 3226 | } |
| 2867 | - $orderrooms = $dbo->loadAssocList(); | |
| 2868 | 3227 | |
| 3228 | + // access the customer record | |
| 3229 | + $customer = VikBooking::getCPinInstance()->getCustomerFromBooking($order['id']); | |
| 3230 | + | |
| 2869 | 3231 | $q = "SELECT * FROM `#__vikbooking_customers_orders` WHERE `idorder`=".(int)$order['id'].";"; |
| 2870 | 3232 | $dbo->setQuery($q); |
| 2871 | - $dbo->execute(); | |
| 2872 | - if (!$dbo->getNumRows()) { | |
| 3233 | + $custorder = $dbo->loadAssoc(); | |
| 3234 | + if (!$custorder) { | |
| 2873 | 3235 | throw new Exception('No customer found', 404); |
| 2874 | 3236 | } |
| 2875 | - $custorder = $dbo->loadAssoc(); | |
| 2876 | 3237 | |
| 2877 | 3238 | // booking details page |
| 2878 | 3239 | $goto = JRoute::rewrite('index.php?option=com_vikbooking&view=booking&sid=' . $order['sid'] . '&ts=' . $order['ts'] . (!empty($pitemid) ? '&Itemid=' . $pitemid : ''), false); |
| 2879 | 3240 | if (empty($order['sid']) && !empty($order['idorderota'])) { |
| @@ -2885,10 +3246,16 @@ | ||
| 2885 | 3246 | $precheckin = VikBooking::precheckinEnabled(); |
| 2886 | 3247 | if ($precheckin) { |
| 2887 | 3248 | // make sure the limit of days in advance is reflected |
| 2888 | 3249 | $precheckin_mind = VikBooking::precheckinMinOffset(); |
| 2889 | - $precheckin_lim_ts = strtotime("+{$precheckin_mind} days 00:00:00"); | |
| 2890 | - $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 | + } | |
| 2891 | 3258 | } |
| 2892 | 3259 | if (!$precheckin) { |
| 2893 | 3260 | // raise error and redirect in case of website or OTA booking |
| 2894 | 3261 | VikError::raiseWarning('', 'Pre-checkin not allowed at this time'); |
| @@ -2904,13 +3271,18 @@ | ||
| 2904 | 3271 | |
| 2905 | 3272 | foreach ($pguests as $ind => $adults) { |
| 2906 | 3273 | foreach ($adults as $aduind => $details) { |
| 2907 | 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 | + } | |
| 2908 | 3280 | if (!in_array($detkey, $front_keys)) { |
| 2909 | 3281 | // push the key of the guest details for later comparison |
| 2910 | 3282 | array_push($front_keys, $detkey); |
| 2911 | 3283 | } |
| 2912 | - if (strlen($detval)) { | |
| 3284 | + if (is_scalar($detval) && strlen($detval)) { | |
| 2913 | 3285 | // push value only if not empty |
| 2914 | 3286 | if (!isset($guests_details[$ind])) { |
| 2915 | 3287 | $guests_details[$ind] = array(); |
| 2916 | 3288 | } |
| @@ -2939,8 +3311,13 @@ | ||
| 2939 | 3311 | // current pax data include a guest not present, set it to not lose it |
| 2940 | 3312 | $guests_details[$ind][$aduind] = $details; |
| 2941 | 3313 | } |
| 2942 | 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 | + } | |
| 2943 | 3320 | if (!in_array($detkey, $front_keys)) { |
| 2944 | 3321 | // merge this key probably reserved to the back-end |
| 2945 | 3322 | $guests_details[$ind][$aduind][$detkey] = $detval; |
| 2946 | 3323 | } |
| @@ -2956,8 +3333,20 @@ | ||
| 2956 | 3333 | |
| 2957 | 3334 | // Booking History |
| 2958 | 3335 | VikBooking::getBookingHistoryInstance()->setBid($order['id'])->store('PC'); |
| 2959 | 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 | + | |
| 2960 | 3349 | // print success message and redirect |
| 2961 | 3350 | $app->enqueueMessage(JText::translate('VBOSUBMITPRECHECKINTNKS')); |
| 2962 | 3351 | $app->redirect($goto); |
| 2963 | 3352 | } |
| @@ -2975,28 +3364,27 @@ | ||
| 2975 | 3364 | $ts = VikRequest::getString('ts', '', 'request'); |
| 2976 | 3365 | $pitemid = VikRequest::getInt('Itemid', 0, 'request'); |
| 2977 | 3366 | $paddopt = VikRequest::getVar('addopt', array()); |
| 2978 | 3367 | |
| 2979 | - if (!count($paddopt)) { | |
| 3368 | + if (!$paddopt) { | |
| 2980 | 3369 | throw new Exception('No extra services selected', 404); |
| 2981 | 3370 | } |
| 2982 | 3371 | |
| 3372 | + // find the involved reservation, direct or OTA | |
| 2983 | 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) . ";"; |
| 2984 | 3374 | $dbo->setQuery($q); |
| 2985 | - $dbo->execute(); | |
| 2986 | - if (!$dbo->getNumRows()) { | |
| 3375 | + $order = $dbo->loadAssoc(); | |
| 3376 | + if (!$order) { | |
| 2987 | 3377 | throw new Exception('Booking not found', 404); |
| 2988 | 3378 | } |
| 2989 | - $order = $dbo->loadAssoc(); | |
| 2990 | 3379 | |
| 2991 | - $orderrooms = []; | |
| 2992 | - $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;"; | |
| 2993 | 3382 | $dbo->setQuery($q); |
| 2994 | - $dbo->execute(); | |
| 2995 | - if (!$dbo->getNumRows()) { | |
| 3383 | + $orderrooms = $dbo->loadAssocList(); | |
| 3384 | + if (!$orderrooms) { | |
| 2996 | 3385 | throw new Exception('No rooms found', 404); |
| 2997 | 3386 | } |
| 2998 | - $orderrooms = $dbo->loadAssocList(); | |
| 2999 | 3387 | |
| 3000 | 3388 | // availability helper |
| 3001 | 3389 | $av_helper = VikBooking::getAvailabilityInstance(); |
| 3002 | 3390 | |
| @@ -3013,20 +3401,56 @@ | ||
| 3013 | 3401 | // load all valid and existing options as a security measure |
| 3014 | 3402 | $alloptions = []; |
| 3015 | 3403 | $q = "SELECT * FROM `#__vikbooking_optionals`;"; |
| 3016 | 3404 | $dbo->setQuery($q); |
| 3017 | - $dbo->execute(); | |
| 3018 | - if (!$dbo->getNumRows()) { | |
| 3405 | + $records = $dbo->loadAssocList(); | |
| 3406 | + if (!$records) { | |
| 3019 | 3407 | throw new Exception('No options found', 404); |
| 3020 | 3408 | } |
| 3021 | - $records = $dbo->loadAssocList(); | |
| 3022 | 3409 | foreach ($records as $v) { |
| 3023 | - $alloptions[(int)$v['id']] = $v; | |
| 3410 | + $alloptions[$v['id']] = $v; | |
| 3024 | 3411 | } |
| 3025 | 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 | |
| 3026 | 3450 | $extras_booked = []; |
| 3027 | 3451 | foreach ($orderrooms as $kor => $or) { |
| 3028 | - if (!isset($paddopt[$kor]) || !count($paddopt[$kor])) { | |
| 3452 | + if (!isset($paddopt[$kor]) || !$paddopt[$kor]) { | |
| 3029 | 3453 | continue; |
| 3030 | 3454 | } |
| 3031 | 3455 | |
| 3032 | 3456 | // determine proper nights of stay |
| @@ -3042,27 +3466,65 @@ | ||
| 3042 | 3466 | if (strpos($or['optionals'], $optid . ':') === 0 || strpos($or['optionals'], ';' . $optid . ':') > 0) { |
| 3043 | 3467 | // this option has already been booked, skip it |
| 3044 | 3468 | continue; |
| 3045 | 3469 | } |
| 3046 | - if (!isset($alloptions[(int)$optid])) { | |
| 3470 | + if (!isset($alloptions[$optid])) { | |
| 3047 | 3471 | // this option ID does not exist, skip it |
| 3048 | 3472 | continue; |
| 3049 | 3473 | } |
| 3050 | 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 | + | |
| 3051 | 3512 | // push option booked |
| 3052 | - array_push($extras_booked, array( | |
| 3053 | - 'id' => $optid, | |
| 3054 | - 'idroom' => $or['idroom'], | |
| 3055 | - 'name' => $alloptions[(int)$optid]['name'], | |
| 3056 | - 'quant' => $quant, | |
| 3513 | + $extras_booked[] = [ | |
| 3514 | + 'id' => $optid, | |
| 3515 | + 'idroom' => $or['idroom'], | |
| 3516 | + 'name' => $alloptions[$optid]['name'], | |
| 3517 | + 'quant' => $quant, | |
| 3057 | 3518 | 'room_cost' => (!empty($or['cust_cost']) ? $or['cust_cost'] : $or['room_cost']), |
| 3058 | 3519 | 'room_name' => $or['room_name'], |
| 3059 | - 'optcost' => $alloptions[(int)$optid]['cost'], | |
| 3060 | - 'adults' => $or['adults'], | |
| 3061 | - 'children' => $or['children'], | |
| 3062 | - 'nights' => $room_stay_nights, | |
| 3063 | - )); | |
| 3520 | + 'optcost' => $alloptions[$optid]['cost'], | |
| 3521 | + 'adults' => $or['adults'], | |
| 3522 | + 'children' => $or['children'], | |
| 3523 | + 'nights' => $room_stay_nights, | |
| 3524 | + ]; | |
| 3064 | 3525 | } |
| 3526 | + | |
| 3065 | 3527 | // update options for this room record |
| 3066 | 3528 | $newoptstr = $or['optionals'] . $extraoptstr; |
| 3067 | 3529 | $q = "UPDATE `#__vikbooking_ordersrooms` SET `optionals`=" . $dbo->quote($newoptstr) . " WHERE `id`={$or['id']};"; |
| 3068 | 3530 | $dbo->setQuery($q); |
| @@ -3072,8 +3534,10 @@ | ||
| 3072 | 3534 | // increase booking total amount and build event log for the history |
| 3073 | 3535 | $currency = VikBooking::getCurrencySymb(); |
| 3074 | 3536 | $totrooms = count($orderrooms); |
| 3075 | 3537 | $increase = 0; |
| 3538 | + $future_dd = 0; | |
| 3539 | + $add_tax = 0; | |
| 3076 | 3540 | $extraslog = []; |
| 3077 | 3541 | foreach ($extras_booked as $extra) { |
| 3078 | 3542 | $o = $alloptions[(int)$extra['id']]; |
| 3079 | 3543 | if ((int)$o['pcentroom']) { |
| @@ -3090,31 +3554,100 @@ | ||
| 3090 | 3554 | if ($o['perperson'] == 1) { |
| 3091 | 3555 | $optcost = $optcost * $extra['adults']; |
| 3092 | 3556 | } |
| 3093 | 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 | + | |
| 3094 | 3569 | $floatoptprice = VikBooking::sayOptionalsPlusIva($optcost, $o['idiva']); |
| 3570 | + $netoptprice = VikBooking::sayOptionalsMinusIva($optcost, $o['idiva']); | |
| 3095 | 3571 | $increase += $floatoptprice; |
| 3572 | + $add_tax += $floatoptprice - $netoptprice; | |
| 3096 | 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 | + } | |
| 3097 | 3579 | } |
| 3098 | 3580 | |
| 3099 | 3581 | $newtotbooking = $order['total'] + $increase; |
| 3582 | + $new_tot_taxes = $order['tot_taxes'] + $add_tax; | |
| 3583 | + | |
| 3100 | 3584 | /** |
| 3101 | 3585 | * Important: the 'paymcount' should be increased only if the status is |
| 3102 | 3586 | * "confirmed" or no rooms may be occupied when receiving a payment. |
| 3103 | 3587 | */ |
| 3104 | - $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 | |
| 3105 | 3624 | $dbo->setQuery($q); |
| 3106 | 3625 | $dbo->execute(); |
| 3107 | 3626 | |
| 3108 | 3627 | // Booking History |
| 3109 | - VikBooking::getBookingHistoryInstance()->setBid($order['id'])->store('UE', implode("\n", $extraslog)); | |
| 3628 | + VikBooking::getBookingHistoryInstance($order['id'])->store('UE', implode("\n", $extraslog)); | |
| 3110 | 3629 | |
| 3111 | - // send email notification to guest and admin | |
| 3112 | - 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 | + } | |
| 3113 | 3640 | |
| 3641 | + if ($send_notifications) { | |
| 3642 | + // send email notification to guest and admin | |
| 3643 | + VikBooking::sendBookingEmail($order['id'], array('guest', 'admin')); | |
| 3644 | + } | |
| 3645 | + | |
| 3114 | 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); |
| 3115 | 3647 | $app->enqueueMessage(JText::translate('VBOUPSELLRESULTOK')); |
| 3116 | 3648 | $app->redirect($goto); |
| 3649 | + $app->close(); | |
| 3117 | 3650 | } |
| 3118 | 3651 | |
| 3119 | 3652 | /** |
| 3120 | 3653 | * Submits a new review. |
| @@ -3237,12 +3770,12 @@ | ||
| 3237 | 3770 | |
| 3238 | 3771 | // reviewer information |
| 3239 | 3772 | $review_content->reviewer = new stdClass; |
| 3240 | 3773 | $customer_name = ''; |
| 3241 | - if (count($customer)) { | |
| 3774 | + if ($customer) { | |
| 3242 | 3775 | $review_content->reviewer->name = $customer['first_name']; |
| 3243 | 3776 | $review_content->reviewer->country_code = $customer['country']; |
| 3244 | - $customer_name = $customer['first_name'] . ' ' . $customer['last_name'];; | |
| 3777 | + $customer_name = $customer['first_name'] . ' ' . $customer['last_name']; | |
| 3245 | 3778 | } else { |
| 3246 | 3779 | $revuname = ''; |
| 3247 | 3780 | if (!empty($order['custdata'])) { |
| 3248 | 3781 | $uinfos = explode("\n", $order['custdata']); |
| @@ -3763,8 +4296,13 @@ | ||
| 3763 | 4296 | * @since 1.13.5 |
| 3764 | 4297 | */ |
| 3765 | 4298 | public function add_roomdaynote() |
| 3766 | 4299 | { |
| 4300 | + if (!JSession::checkToken()) { | |
| 4301 | + // missing CSRF-proof token | |
| 4302 | + VBOHttpDocument::getInstance()->close(403, JText::translate('JINVALID_TOKEN')); | |
| 4303 | + } | |
| 4304 | + | |
| 3767 | 4305 | $dt = VikRequest::getString('dt', '', 'request'); |
| 3768 | 4306 | $idroom = VikRequest::getInt('idroom', 0, 'request'); |
| 3769 | 4307 | $subunit = VikRequest::getInt('subunit', 0, 'request'); |
| 3770 | 4308 | $type = VikRequest::getString('type', '', 'request'); |
| @@ -3829,21 +4367,26 @@ | ||
| 3829 | 4367 | |
| 3830 | 4368 | /** |
| 3831 | 4369 | * AJAX endpoint to upload customer documents during the pre-checkin. |
| 3832 | 4370 | * |
| 3833 | - * @throws Exception | |
| 3834 | - * | |
| 3835 | 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. | |
| 3836 | 4373 | */ |
| 3837 | 4374 | public function precheckin_upload_docs() |
| 3838 | 4375 | { |
| 3839 | - $dbo = JFactory::getDbo(); | |
| 3840 | - $app = JFactory::getApplication(); | |
| 3841 | - $input = $app->input; | |
| 4376 | + $app = JFactory::getApplication(); | |
| 4377 | + $dbo = JFactory::getDbo(); | |
| 4378 | + $input = $app->input; | |
| 3842 | 4379 | |
| 3843 | - // get request values | |
| 3844 | - $order_sid = $input->getString('sid', ''); | |
| 3845 | - $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); | |
| 3846 | 4389 | /** |
| 3847 | 4390 | * This is a simple file uploading process, but if |
| 3848 | 4391 | * we wanted to automatically update the pax_data, |
| 3849 | 4392 | * the request vars below would be necessary. |
| @@ -3849,31 +4392,29 @@ | ||
| 3849 | 4392 | * the request vars below would be necessary. |
| 3850 | 4393 | */ |
| 3851 | 4394 | $room_index = $input->getInt('room_index', 0); |
| 3852 | 4395 | $guest_index = $input->getInt('guest_index', 0); |
| 3853 | - $pax_index = $input->getString('pax_index', ''); | |
| 4396 | + $pax_index = $input->getString('pax_index', ''); | |
| 3854 | 4397 | |
| 3855 | 4398 | if (empty($order_sid) || empty($order_ts)) { |
| 3856 | - throw new Exception('Missing booking details', 404); | |
| 4399 | + VBOHttpDocument::getInstance($app)->close(404, 'Missing booking details'); | |
| 3857 | 4400 | } |
| 3858 | 4401 | |
| 3859 | 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';"; |
| 3860 | 4403 | $dbo->setQuery($q); |
| 3861 | - $dbo->execute(); | |
| 3862 | - if (!$dbo->getNumRows()) { | |
| 3863 | - throw new Exception('Booking not found', 404); | |
| 4404 | + $order = $dbo->loadAssoc(); | |
| 4405 | + if (!$order) { | |
| 4406 | + VBOHttpDocument::getInstance($app)->close(404, 'Booking not found'); | |
| 3864 | 4407 | } |
| 3865 | - $order = $dbo->loadAssoc(); | |
| 3866 | 4408 | |
| 3867 | 4409 | $customer = array(); |
| 3868 | 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'].";"; |
| 3869 | 4411 | $dbo->setQuery($q); |
| 3870 | - $dbo->execute(); | |
| 3871 | - if (!$dbo->getNumRows()) { | |
| 4412 | + $customer = $dbo->loadObject(); | |
| 4413 | + if (!$customer) { | |
| 3872 | 4414 | // one customer must be assigned to this booking for the pre-checkin |
| 3873 | - throw new Exception('No customers associated to this booking', 404); | |
| 4415 | + VBOHttpDocument::getInstance($app)->close(404, 'No customers associated to this booking'); | |
| 3874 | 4416 | } |
| 3875 | - $customer = $dbo->loadObject(); | |
| 3876 | 4417 | |
| 3877 | 4418 | // make sure pre-checkin is allowed |
| 3878 | 4419 | $precheckin = VikBooking::precheckinEnabled(); |
| 3879 | 4420 | if ($precheckin) { |
| @@ -3878,25 +4419,31 @@ | ||
| 3878 | 4419 | $precheckin = VikBooking::precheckinEnabled(); |
| 3879 | 4420 | if ($precheckin) { |
| 3880 | 4421 | // make sure the limit of days in advance is reflected |
| 3881 | 4422 | $precheckin_mind = VikBooking::precheckinMinOffset(); |
| 3882 | - $precheckin_lim_ts = strtotime("+{$precheckin_mind} days 00:00:00"); | |
| 3883 | - $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 | + } | |
| 3884 | 4431 | } |
| 3885 | 4432 | if (!$precheckin) { |
| 3886 | - throw new Exception('Pre-checkin not allowed at this time', 403); | |
| 4433 | + VBOHttpDocument::getInstance($app)->close(403, 'Pre-checkin not allowed at this time'); | |
| 3887 | 4434 | } |
| 3888 | 4435 | |
| 3889 | 4436 | // get uploaded files array (use "raw" to avoid filtering the file to upload) |
| 3890 | 4437 | $files = $input->files->get('docs', array(), 'raw'); |
| 3891 | - if (!count($files)) { | |
| 3892 | - throw new Exception('No files to be uploaded', 500); | |
| 4438 | + if (!$files) { | |
| 4439 | + VBOHttpDocument::getInstance($app)->close(500, 'No files to be uploaded'); | |
| 3893 | 4440 | } |
| 3894 | 4441 | |
| 3895 | 4442 | if (isset($files['name'])) { |
| 3896 | 4443 | // we have a single associative array, we need to push it within a list, |
| 3897 | 4444 | // because the upload iterates the $files array |
| 3898 | - $files = array($files); | |
| 4445 | + $files = [$files]; | |
| 3899 | 4446 | } |
| 3900 | 4447 | |
| 3901 | 4448 | // fetch documents folder path |
| 3902 | 4449 | $dirpath = VBO_CUSTOMERS_PATH . DIRECTORY_SEPARATOR; |
| @@ -3906,28 +4453,26 @@ | ||
| 3906 | 4453 | // randomize string |
| 3907 | 4454 | $customer->seed = uniqid(); |
| 3908 | 4455 | |
| 3909 | 4456 | // create blocks for hashed folder |
| 3910 | - $parts = array( | |
| 4457 | + $parts = [ | |
| 3911 | 4458 | $customer->first_name, |
| 3912 | 4459 | $customer->last_name, |
| 3913 | 4460 | md5(serialize($customer)), |
| 3914 | - ); | |
| 4461 | + ]; | |
| 3915 | 4462 | |
| 3916 | 4463 | // join fetched parts |
| 3917 | 4464 | $customer->docsfolder = strtolower(implode('-', array_filter($parts))); |
| 3918 | 4465 | |
| 3919 | 4466 | if (strlen($customer->docsfolder) < 16) { |
| 3920 | - 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.'); | |
| 3921 | 4468 | } |
| 3922 | 4469 | |
| 3923 | - jimport('joomla.filesystem.folder'); | |
| 3924 | - | |
| 3925 | 4470 | // create a folder for this customer |
| 3926 | 4471 | $created = JFolder::create($dirpath . $customer->docsfolder); |
| 3927 | 4472 | |
| 3928 | 4473 | if (!$created) { |
| 3929 | - 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)); | |
| 3930 | 4475 | } |
| 3931 | 4476 | |
| 3932 | 4477 | unset($customer->seed); |
| 3933 | 4478 | |
| @@ -3937,10 +4482,12 @@ | ||
| 3937 | 4482 | $record->docsfolder = $customer->docsfolder; |
| 3938 | 4483 | $dbo->updateObject('#__vikbooking_customers', $record, 'id'); |
| 3939 | 4484 | } |
| 3940 | 4485 | |
| 3941 | - // prepare the response array of uploaded-file objects | |
| 3942 | - $response = array(); | |
| 4486 | + // prepare the response with the uploaded-file objects | |
| 4487 | + $response = [ | |
| 4488 | + 'uploads' => [], | |
| 4489 | + ]; | |
| 3943 | 4490 | $upload_err = null; |
| 3944 | 4491 | |
| 3945 | 4492 | // compose prefix for all files uploaded (must end with an underscrore for View's compatibility) |
| 3946 | 4493 | $file_prefix = str_replace(' ', '-', JText::translate('VBOPRECHECKIN')) . '_'; |
| @@ -3958,13 +4505,13 @@ | ||
| 3958 | 4505 | } |
| 3959 | 4506 | // always prepend "pre check-in" to the original file name |
| 3960 | 4507 | $file['name'] = strtolower($file_prefix . $file['name']); |
| 3961 | 4508 | // try to upload the file |
| 3962 | - $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'); | |
| 3963 | 4510 | // set a valid URL for the uploaded file |
| 3964 | 4511 | $result->url = str_replace(DIRECTORY_SEPARATOR, '/', str_replace(VBO_CUSTOMERS_PATH . DIRECTORY_SEPARATOR, VBO_CUSTOMERS_URI, $result->path)); |
| 3965 | 4512 | // push uploaded file |
| 3966 | - array_push($response, $result); | |
| 4513 | + array_push($response['uploads'], $result); | |
| 3967 | 4514 | } |
| 3968 | 4515 | |
| 3969 | 4516 | } catch (Exception $e) { |
| 3970 | 4517 | // do nothing, but catch the error |
| @@ -3970,15 +4517,56 @@ | ||
| 3970 | 4517 | // do nothing, but catch the error |
| 3971 | 4518 | $upload_err = $e; |
| 3972 | 4519 | } |
| 3973 | 4520 | |
| 3974 | - if (!count($response)) { | |
| 3975 | - 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'); | |
| 3976 | 4527 | } |
| 3977 | 4528 | |
| 3978 | - // output the response | |
| 3979 | - echo json_encode($response); | |
| 3980 | - 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); | |
| 3981 | 4569 | } |
| 3982 | 4570 | |
| 3983 | 4571 | /** |
| 3984 | 4572 | * AJAX endpoint to submit an inquiry/information request. |
| @@ -4004,11 +4592,11 @@ | ||
| 4004 | 4592 | $checkout_h = $input->getInt('checkouth', 0); |
| 4005 | 4593 | $checkout_m = $input->getInt('checkoutm', 0); |
| 4006 | 4594 | $categories = $input->getString('categories', ''); |
| 4007 | 4595 | $roomsnum = $input->getInt('roomsnum', 1); |
| 4008 | - $adults = $input->get('adults', array(), 'int'); | |
| 4009 | - $children = $input->get('children', array(), 'int'); | |
| 4010 | - $inquiry = $input->get('inquiry', array(), 'raw'); | |
| 4596 | + $adults = $input->get('adults', [], 'int'); | |
| 4597 | + $children = $input->get('children', [], 'int'); | |
| 4598 | + $inquiry = $input->get('inquiry', [], 'array'); | |
| 4011 | 4599 | $ulang = $input->getString('ulang', ''); |
| 4012 | 4600 | |
| 4013 | 4601 | $timeopst = VikBooking::getTimeOpenStore(); |
| 4014 | 4602 | if (empty($checkin_h) && empty($checkout_h) && is_array($timeopst)) { |
| @@ -4053,8 +4641,10 @@ | ||
| 4053 | 4641 | if (!is_scalar($info_val) || !strlen($info_val)) { |
| 4054 | 4642 | // empty or invalid field |
| 4055 | 4643 | continue; |
| 4056 | 4644 | } |
| 4645 | + // accept only text from submitted value | |
| 4646 | + $info_val = strip_tags($info_val); | |
| 4057 | 4647 | if ($info_type == 'nominative') { |
| 4058 | 4648 | if (empty($t_first_name)) { |
| 4059 | 4649 | $t_first_name = $info_val; |
| 4060 | 4650 | $res_custdata[JText::translate('VBNAME')] = $info_val; |
| @@ -4447,10 +5037,10 @@ | ||
| 4447 | 5037 | // update booking total amounts as first |
| 4448 | 5038 | if ($upgrade_difference > 0) { |
| 4449 | 5039 | $upd_booking = new stdClass; |
| 4450 | 5040 | $upd_booking->id = $booking['id']; |
| 4451 | - $upd_booking->total = $booking['total'] + $upgrade_difference; | |
| 4452 | - $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); | |
| 4453 | 5043 | |
| 4454 | 5044 | $dbo->updateObject('#__vikbooking_orders', $upd_booking, 'id'); |
| 4455 | 5045 | } |
| 4456 | 5046 | |
| @@ -4470,6 +5060,153 @@ | ||
| 4470 | 5060 | VikBooking::getBookingHistoryInstance()->setBid($booking['id'])->store('UR', $hist_descr); |
| 4471 | 5061 | |
| 4472 | 5062 | // output the JSON encoded successful response |
| 4473 | 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(); | |
| 4474 | 5211 | } |
| 4475 | 5212 | } |