Create New Item
Item Type
File
Folder
Item Name
Search file in folder and subfolders...
Are you sure want to rename?
File Manager
/
wp-content
/
plugins
/
vikbooking
/
site
:
controller.php
Advanced Search
Upload
New Item
Settings
Back
Back Up
Advanced Editor
Save
<?php /** * @package VikBooking * @subpackage com_vikbooking * @author Alessio Gaggii - e4j - Extensionsforjoomla.com * @copyright Copyright (C) 2018 e4j - Extensionsforjoomla.com. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE * @link https://vikwp.com */ defined('ABSPATH') or die('No script kiddies please!'); jimport('joomla.application.component.controller'); class VikBookingController extends JControllerVikBooking { public function display($cachable = false, $urlparams = array()) { $view = VikRequest::getVar('view', ''); switch ($view) { case 'roomslist': case 'roomdetails': case 'searchdetails': case 'loginregister': case 'orderslist': case 'promotions': case 'availability': case 'packageslist': case 'packagedetails': case 'searchsuggestions': case 'booking': case 'operators': case 'tableaux': case 'precheckin': case 'revstay': case 'tinyurl': VikRequest::setVar('view', $view); break; default: VikRequest::setVar('view', 'vikbooking'); } parent::display(); } public function search() { VikRequest::setVar('view', 'search'); parent::display(); } public function showprc() { VikRequest::setVar('view', 'showprc'); parent::display(); } public function oconfirm() { $requirelogin = VikBooking::requireLogin(); if($requirelogin) { if(VikBooking::userIsLogged()) { VikRequest::setVar('view', 'oconfirm'); } else { VikRequest::setVar('view', 'loginregister'); } } else { VikRequest::setVar('view', 'oconfirm'); } parent::display(); } public function register() { $mainframe = JFactory::getApplication(); $dbo = JFactory::getDBO(); //user data $pname = VikRequest::getString('fname', '', 'request'); $plname = VikRequest::getString('lname', '', 'request'); $pemail = VikRequest::getString('email', '', 'request'); $pusername = VikRequest::getString('username', '', 'request'); $ppassword = VikRequest::getString('password', '', 'request'); $pconfpassword = VikRequest::getString('confpassword', '', 'request'); // //order data $pitemid = VikRequest::getString('Itemid', '', 'request'); $proomid = VikRequest::getVar('roomid', array()); $pdays = VikRequest::getInt('days', '', 'request'); $pcheckin = VikRequest::getInt('checkin', '', 'request'); $pcheckout = VikRequest::getInt('checkout', '', 'request'); $proomsnum = VikRequest::getInt('roomsnum', '', 'request'); $padults = VikRequest::getVar('adults', array()); $pchildren = VikRequest::getVar('children', array()); $rooms = array(); $arrpeople = array(); for($ir = 1; $ir <= $proomsnum; $ir++) { $ind = $ir - 1; if (!empty($proomid[$ind])) { $q = "SELECT * FROM `#__vikbooking_rooms` WHERE `id`='".intval($proomid[$ind])."' AND `avail`='1';"; $dbo->setQuery($q); $dbo->execute(); if ($dbo->getNumRows() > 0) { $takeroom = $dbo->loadAssocList(); $rooms[$ir] = $takeroom[0]; } } if (!empty($padults[$ind])) { $arrpeople[$ir]['adults'] = intval($padults[$ind]); } else { $arrpeople[$ir]['adults'] = 0; } if (!empty($pchildren[$ind])) { $arrpeople[$ir]['children'] = intval($pchildren[$ind]); } else { $arrpeople[$ir]['children'] = 0; } } $prices = array(); foreach($rooms as $num => $r) { $ppriceid = VikRequest::getString('priceid'.$num, '', 'request'); if (!empty($ppriceid)) { $prices[$num] = intval($ppriceid); } } $selopt = array(); $q = "SELECT * FROM `#__vikbooking_optionals` ORDER BY `#__vikbooking_optionals`.`ordering` ASC;"; $dbo->setQuery($q); $dbo->execute(); if ($dbo->getNumRows() > 0) { $optionals = $dbo->loadAssocList(); foreach ($rooms as $num => $r) { foreach ($optionals as $opt) { if (!empty($opt['ageintervals']) && $arrpeople[$num]['children'] > 0) { $tmpvar = VikRequest::getInt('optid'.$num.$opt['id'], []); if (is_array($tmpvar) && $tmpvar) { $optagenames = VikBooking::getOptionIntervalsAges($opt['ageintervals']); $optagepcent = VikBooking::getOptionIntervalsPercentage($opt['ageintervals']); $optageovrct = VikBooking::getOptionIntervalChildOverrides($opt, $arrpeople[$num]['adults'], $arrpeople[$num]['children']); $optorigname = $opt['name']; foreach ($tmpvar as $child_num => $chvar) { $opt['quan'] = $chvar; $opt['chageintv'] = $chvar; //ignore calculation as percetage value to reconstruct the URL $ageintervals_child_string = isset($optageovrct['ageintervals_child' . ($child_num + 1)]) ? $optageovrct['ageintervals_child' . ($child_num + 1)] : $opt['ageintervals']; $optagecosts = VikBooking::getOptionIntervalsCosts($ageintervals_child_string); $opt['cost'] = $optagecosts[($chvar - 1)]; $opt['name'] = $optorigname.' ('.$optagenames[($chvar - 1)].')'; $selopt[$num][] = $opt; } } } else { $tmpvar = VikRequest::getString('optid'.$num.$opt['id'], '', 'request'); if (!empty($tmpvar)) { $opt['quan'] = $tmpvar; $selopt[$num][] = $opt; } } } } } $strpriceid = ""; foreach($prices as $num => $pid) { $strpriceid .= ($num > 1 ? "&" : "")."priceid".$num."=".$pid; } $stroptid = ""; for($ir = 1; $ir <= $proomsnum; $ir++) { if (isset($selopt[$ir]) && is_array($selopt[$ir])) { foreach($selopt[$ir] as $opt) { if (array_key_exists('chageintv', $opt)) { $stroptid .= "&optid".$ir.$opt['id']."[]=".$opt['chageintv']; } else { $stroptid .= "&optid".$ir.$opt['id']."=".$opt['quan']; } } } } $strroomid = ""; foreach ($rooms as $num => $r) { $strroomid .= "&roomid[]=".$r['id']; } $straduchild = ""; foreach ($arrpeople as $indroom => $aduch) { $straduchild .= "&adults[]=".$aduch['adults']; $straduchild .= "&children[]=".$aduch['children']; } $qstring = $strpriceid.$stroptid.$strroomid.$straduchild."&roomsnum=".$proomsnum."&days=".$pdays."&checkin=".$pcheckin."&checkout=".$pcheckout.(!empty($pitemid) ? "&Itemid=".$pitemid : ""); // if (!VikBooking::userIsLogged()) { if (!empty($pname) && !empty($plname) && !empty($pusername) && !empty($pemail) && $ppassword == $pconfpassword) { //save user $newuserid=VikBooking::addJoomlaUser($pname." ".$plname, $pusername, $pemail, $ppassword); if ($newuserid!=false && strlen($newuserid)) { /** * @wponly the return URL should be passed within the $option array of $app->login() */ $redirect_to = JRoute::rewrite('index.php?option=com_vikbooking&task=oconfirm&'.$qstring, false); //registration success $credentials = array('username' => $pusername, 'password' => $ppassword ); //autologin $mainframe->login($credentials, array('redirect' => $redirect_to)); $currentUser = JFactory::getUser(); $currentUser->setLastVisit(time()); $currentUser->set('guest', 0); // $mainframe->redirect($redirect_to); } else { //error while saving new user VikError::raiseWarning('', JText::translate('VBREGERRSAVING')); $mainframe->redirect(JRoute::rewrite('index.php?option=com_vikbooking&view=loginregister&'.$qstring, false)); } } else { //invalid data VikError::raiseWarning('', JText::translate('VBREGERRINSDATA')); $mainframe->redirect(JRoute::rewrite('index.php?option=com_vikbooking&view=loginregister&'.$qstring, false)); } } else { //user is already logged in, proceed $mainframe->redirect(JRoute::rewrite('index.php?option=com_vikbooking&task=oconfirm&'.$qstring, false)); } } public function saveorder() { $dbo = JFactory::getDbo(); $session = JFactory::getSession(); $app = JFactory::getApplication(); $vbo_tn = VikBooking::getTranslator(); // availability helper $av_helper = VikBooking::getAvailabilityInstance(); $prooms = VikRequest::getVar('rooms', array()); $proomindex = VikRequest::getVar('roomindex', array()); $proomsnum = VikRequest::getInt('roomsnum', 0, 'request'); $padults = VikRequest::getVar('adults', array()); $pchildren = VikRequest::getVar('children', array()); $pdays = VikRequest::getInt('days', 0, 'request'); $pcouponcode = VikRequest::getString('couponcode', '', 'request'); $pcheckin = VikRequest::getInt('checkin', 0, 'request'); $pcheckout = VikRequest::getInt('checkout', 0, 'request'); $pprtar = VikRequest::getVar('prtar', array()); $ppriceid = VikRequest::getVar('priceid', array()); $poptionals = VikRequest::getString('optionals', '', 'request'); $ptotdue = VikRequest::getString('totdue', '', 'request'); $pgpayid = VikRequest::getString('gpayid', '', 'request'); $ppkg_id = VikRequest::getInt('pkg_id', '', 'request'); $pnodep = VikRequest::getInt('nodep', '', 'request'); $split_stay = VikRequest::getVar('split_stay', array()); $pitemid = VikRequest::getInt('Itemid', '', 'request'); $validtoken = true; if (VikBooking::tokenForm()) { $validtoken = false; $pviktoken = VikRequest::getString('viktoken', '', 'request'); $sessvbtkn = $session->get('vikbtoken', ''); if (!empty($pviktoken) && $sessvbtkn == $pviktoken) { $session->set('vikbtoken', ''); $validtoken = true; } if (!$validtoken) { $validtoken = JSession::checkToken(); } } if (!$validtoken) { showSelectVb(JText::translate('VBINVALIDTOKEN')); return; } $q = "SELECT * FROM `#__vikbooking_custfields` ORDER BY `#__vikbooking_custfields`.`ordering` ASC;"; $dbo->setQuery($q); $cfields = $dbo->loadAssocList(); $suffdata = true; $useremail = ""; $usercountry = ''; $nominatives = []; $t_first_name = ''; $t_last_name = ''; $phone_number = ''; $fieldflags = []; if ($cfields) { $vbo_tn->translateContents($cfields, '#__vikbooking_custfields'); foreach ($cfields as $cf) { if (intval($cf['required']) == 1 && $cf['type'] != 'separator' && $cf['type'] != 'state') { $tmpcfval = VikRequest::getString('vbf' . $cf['id'], '', 'request'); if (!strlen(str_replace(' ', '', trim($tmpcfval)))) { $suffdata = false; break; } } } //save user email, nominatives, phone number and create custdata array $arrcustdata = []; $arrcfields = []; $emailwasfound = false; foreach ($cfields as $cf) { $user_inp_val = VikRequest::getString('vbf' . $cf['id'], '', 'request'); if (intval($cf['isemail']) == 1 && $emailwasfound == false) { $useremail = trim($user_inp_val); $emailwasfound = true; } if ($cf['isnominative'] == 1) { if (strlen(str_replace(' ', '', trim($user_inp_val)))) { $nominatives[] = $user_inp_val; } } if ($cf['isphone'] == 1) { if (strlen(str_replace(' ', '', trim($user_inp_val)))) { $phone_number = $user_inp_val; } } if (!empty($cf['flag'])) { if (strlen(str_replace(' ', '', trim($user_inp_val)))) { $fieldflags[$cf['flag']] = $user_inp_val; } } if ($cf['type'] != 'separator' && $cf['type'] != 'country' && ( $cf['type'] != 'checkbox' || ($cf['type'] == 'checkbox' && intval($cf['required']) != 1) ) ) { // check the input value to store for the customer raw information string $def_user_inp_val = $user_inp_val; // check for state/province field if ($cf['type'] == 'state' && strlen(str_replace(' ', '', trim($user_inp_val)))) { /** * In order to assign the proper state/province to the customer, * we treat this type of field as if it was a "field flag" type. * * @since 1.16.0 (J) - 1.6.0 (WP) */ $fieldflags['state'] = $user_inp_val; // attempt to save the full state name, not the 2-char code $def_user_inp_val = VBOStateHelper::getFullName($user_inp_val, $usercountry); } $arrcustdata[JText::translate($cf['name'])] = $def_user_inp_val; // store the original input value for this custom field ID $arrcfields[$cf['id']] = $user_inp_val; } elseif ($cf['type'] == 'country') { $countryval = $user_inp_val; if (!empty($countryval) && strstr($countryval, '::') !== false) { $countryparts = explode('::', $countryval); $usercountry = $countryparts[0]; $arrcustdata[JText::translate($cf['name'])] = $countryparts[1]; } else { $arrcustdata[JText::translate($cf['name'])] = ''; } } } } if (!empty($phone_number) && !empty($usercountry)) { $phone_number = VikBooking::checkPhonePrefixCountry($phone_number, $usercountry); } if ($suffdata !== true) { showSelectVb(JText::translate('VBINSUFDATA')); return; } if (count($nominatives) >= 2) { $t_last_name = array_pop($nominatives); $t_first_name = array_pop($nominatives); } $secdiff = $pcheckout - $pcheckin; $daysdiff = $secdiff / 86400; if (is_int($daysdiff)) { if ($daysdiff < 1) { $daysdiff = 1; } } else { if ($daysdiff < 1) { $daysdiff = 1; } else { $sum = floor($daysdiff) * 86400; $newdiff = $secdiff - $sum; $maxhmore = VikBooking::getHoursMoreRb() * 3600; if ($maxhmore >= $newdiff) { $daysdiff = floor($daysdiff); } else { $daysdiff = ceil($daysdiff); } } } if (!VikBooking::dayValidTs($pdays, $pcheckin, $pcheckout) || $pdays != $daysdiff) { showSelectVb(JText::translate('VBINCONGRDATA')); return; } // get check-in and check-out dates information $checkin_info = getdate($pcheckin); $checkout_info = getdate($pcheckout); /** * Check split stay information. * * @since 1.16.0 (J) - 1.6.0 (WP) */ if (!empty($split_stay) && count($split_stay) == count($prooms) && count($split_stay) == $proomsnum && $proomsnum > 1) { // valid split stay request vars received $split_stay_checkins = []; $split_stay_checkouts = []; $split_stay_nights = []; foreach ($split_stay as $sps_k => $split_room) { // calculate and set the exact check-in and check-out timestamps for this split-room $room_checkin = VikBooking::getDateTimestamp($split_room['checkin'], $checkin_info['hours'], $checkin_info['minutes'], $checkin_info['seconds']); $room_checkout = VikBooking::getDateTimestamp($split_room['checkout'], $checkout_info['hours'], $checkout_info['minutes'], $checkout_info['seconds']); $split_stay_checkins[] = $room_checkin; $split_stay_checkouts[] = $room_checkout; // update split stay information $split_room['checkin_ts'] = $room_checkin; $split_room['checkout_ts'] = $room_checkout; $split_room['nights'] = $av_helper->countNightsOfStay($room_checkin, $room_checkout); $split_stay_nights[] = $split_room['nights']; $split_stay[$sps_k] = $split_room; } // validate minimum and maximum stay dates for the split stay if (empty($split_stay_checkins) || empty($split_stay_checkouts)) { // error showSelectVb('Empty stay dates for split stay rooms'); return; } if (array_sum($split_stay_nights) != $daysdiff) { showSelectVb('Invalid sum of total nights for split stay rooms'); return; } if (min($split_stay_checkins) != $pcheckin) { // error showSelectVb('Invalid checkin stay date for split stay rooms'); return; } if (max($split_stay_checkouts) != $pcheckout) { // error showSelectVb('Invalid checkout stay date for split stay rooms'); return; } } else { // unset any possible value as it's invalid $split_stay = []; } $currencyname = VikBooking::getCurrencyName(); $rooms = []; $prices = []; $arrpeople = []; for ($ir = 1; $ir <= $proomsnum; $ir++) { $ind = $ir - 1; if (!empty($prooms[$ind])) { $q = "SELECT * FROM `#__vikbooking_rooms` WHERE `id`=" . (int)$prooms[$ind] . " AND `avail`='1';"; $dbo->setQuery($q); $rdata = $dbo->loadAssoc(); if ($rdata) { $rooms[$ir] = $rdata; } } if (!empty($padults[$ind])) { $arrpeople[$ir]['adults'] = intval($padults[$ind]); } else { $arrpeople[$ir]['adults'] = 0; } if (!empty($pchildren[$ind])) { $arrpeople[$ir]['children'] = intval($pchildren[$ind]); } else { $arrpeople[$ir]['children'] = 0; } $arrpeople[$ir]['pets'] = 0; $prices[$ir] = intval($ppriceid[$ind]); } if (count($rooms) != $proomsnum) { VikError::raiseWarning('', JText::translate('VBROOMNOTFND')); $app->redirect(JRoute::rewrite('index.php?option=com_vikbooking')); exit; } $vbo_tn->translateContents($rooms, '#__vikbooking_rooms'); // package $pkg = []; if (!empty($ppkg_id)) { $pkg = VikBooking::validateRoomPackage($ppkg_id, $rooms, $daysdiff, $pcheckin, $pcheckout); if (!is_array($pkg) || (is_array($pkg) && !(count($pkg) > 0)) ) { if (!is_array($pkg)) { VikError::raiseWarning('', $pkg); } $app->redirect(JRoute::rewrite("index.php?option=com_vikbooking&view=packagedetails&pkgid=".$ppkg_id.(!empty($pitemid) ? "&Itemid=".$pitemid : ""), false)); exit; } } $tars = []; $validfares = true; foreach ($rooms as $num => $r) { if (count($pkg)) { break; } // determine the number of nights of stay and dates to consider $use_los = (int)$daysdiff; $room_checkin = $pcheckin; $room_checkout = $pcheckout; if (!empty($split_stay) && !empty($split_stay[($num - 1)]) && $split_stay[($num - 1)]['idroom'] == $r['id']) { $use_los = (int)$split_stay[($num - 1)]['nights']; $room_checkin = $split_stay[($num - 1)]['checkin_ts']; $room_checkout = $split_stay[($num - 1)]['checkout_ts']; } $q = "SELECT * FROM `#__vikbooking_dispcost` WHERE `idroom`=" . (int)$r['id'] . " AND `days`=" . $use_los . " AND `idprice`=" . $prices[$num]; $dbo->setQuery($q, 0, 1); $dbo->execute(); if (!$dbo->getNumRows()) { $validfares = false; break; } $tar = $dbo->loadAssocList(); // apply seasonal rates $tar = VikBooking::applySeasonsRoom($tar, $room_checkin, $room_checkout); // apply OBP rules $tar = VBORoomHelper::getInstance()->applyOBPRules($tar, $r, $arrpeople[$num]['adults']); // push room tariffs $tars[$num] = $tar; } if ($validfares !== true) { showSelectVb(JText::translate('VBINCONGRDATAREC')); return; } $isdue = 0; $tot_taxes = 0; $tot_city_taxes = 0; $tot_fees = 0; $tot_damage_dep = 0; $rooms_costs_map = []; $is_package = (bool)(count($pkg) > 0); if ($is_package === true) { foreach ($rooms as $num => $r) { $pkg_cost = $pkg['pernight_total'] == 1 ? ($pkg['cost'] * $daysdiff) : $pkg['cost']; $pkg_cost = $pkg['perperson'] == 1 ? ($pkg_cost * ($arrpeople[$num]['adults'] > 0 ? $arrpeople[$num]['adults'] : 1)) : $pkg_cost; $cost_plus_tax = VikBooking::sayPackagePlusIva($pkg_cost, $pkg['idiva']); $isdue += $cost_plus_tax; if ($cost_plus_tax == $pkg_cost) { $cost_minus_tax = VikBooking::sayPackageMinusIva($pkg_cost, $pkg['idiva']); $tot_taxes += ($pkg_cost - $cost_minus_tax); } else { $tot_taxes += ($cost_plus_tax - $pkg_cost); } } } else { foreach ($tars as $num => $tar) { $cost_plus_tax = VikBooking::sayCostPlusIva($tar[0]['cost'], $tar[0]['idprice']); $isdue += $cost_plus_tax; if ($cost_plus_tax == $tar[0]['cost']) { $cost_minus_tax = VikBooking::sayCostMinusIva($tar[0]['cost'], $tar[0]['idprice']); $tot_taxes += ($tar[0]['cost'] - $cost_minus_tax); } else { $tot_taxes += ($cost_plus_tax - $tar[0]['cost']); } $rooms_costs_map[$num] = $tar[0]['cost']; } } /** * Custom check-in/out times due to late check-out/early check-in options. * * @since 1.17.2 (J) - 1.7.2 (WP) */ $custom_checkinout = []; $selopt = []; $optstr = []; $children_age = []; if (!empty($poptionals)) { $stepo = explode(";", $poptionals); foreach ($stepo as $roptkey => $oo) { if (empty($oo)) { continue; } $stept = explode(":", $oo); $rnoid = explode("_", $stept[0]); $room_ind = $rnoid[0] - 1; $q = "SELECT * FROM `#__vikbooking_optionals` WHERE `id`=" . (int)$rnoid[1]; $dbo->setQuery($q, 0, 1); $actopt = $dbo->loadAssocList(); if (!$actopt) { continue; } $vbo_tn->translateContents($actopt, '#__vikbooking_optionals'); // option params $opt_params = !empty($actopt[0]['oparams']) ? json_decode($actopt[0]['oparams'], true) : []; $opt_params = is_array($opt_params) ? $opt_params : []; // determine the number of nights of stay and dates to consider $use_los = (int)$daysdiff; $room_checkin = $pcheckin; $room_checkout = $pcheckout; if (!empty($split_stay) && !empty($split_stay[$room_ind])) { $use_los = (int)$split_stay[$room_ind]['nights']; $room_checkin = $split_stay[$room_ind]['checkin_ts']; $room_checkout = $split_stay[$room_ind]['checkout_ts']; } $chvar = ''; if (!empty($actopt[0]['ageintervals']) && $arrpeople[$rnoid[0]]['children'] > 0 && strstr($stept[1], '-') != false) { $optagenames = VikBooking::getOptionIntervalsAges($actopt[0]['ageintervals']); $optagepcent = VikBooking::getOptionIntervalsPercentage($actopt[0]['ageintervals']); $optageovrct = VikBooking::getOptionIntervalChildOverrides($actopt[0], $arrpeople[$rnoid[0]]['adults'], $arrpeople[$rnoid[0]]['children']); $child_num = VikBooking::getRoomOptionChildNumber($poptionals, $actopt[0]['id'], $roptkey, $arrpeople[$rnoid[0]]['children']); $optagecosts = VikBooking::getOptionIntervalsCosts(isset($optageovrct['ageintervals_child' . ($child_num + 1)]) ? $optageovrct['ageintervals_child' . ($child_num + 1)] : $actopt[0]['ageintervals']); $agestept = explode('-', $stept[1]); $stept[1] = $agestept[0]; $chvar = $agestept[1]; if (array_key_exists(($chvar - 1), $optagepcent) && $optagepcent[($chvar - 1)] == 1) { //percentage value of the adults tariff if ($is_package === true) { $optagecosts[($chvar - 1)] = ($pkg['pernight_total'] == 1 ? ($pkg['cost'] * $daysdiff) : $pkg['cost']) * $optagecosts[($chvar - 1)] / 100; } else { $optagecosts[($chvar - 1)] = $tars[$rnoid[0]][0]['cost'] * $optagecosts[($chvar - 1)] / 100; } } elseif (array_key_exists(($chvar - 1), $optagepcent) && $optagepcent[($chvar - 1)] == 2) { //VBO 1.10 - percentage value of room base cost if ($is_package === true) { $optagecosts[($chvar - 1)] = ($pkg['pernight_total'] == 1 ? ($pkg['cost'] * $daysdiff) : $pkg['cost']) * $optagecosts[($chvar - 1)] / 100; } else { $display_rate = isset($tars[$rnoid[0]][0]['room_base_cost']) ? $tars[$rnoid[0]][0]['room_base_cost'] : $tars[$rnoid[0]][0]['cost']; $optagecosts[($chvar - 1)] = $display_rate * $optagecosts[($chvar - 1)] / 100; } } $actopt[0]['chageintv'] = $chvar; $actopt[0]['name'] .= ' ('.$optagenames[($chvar - 1)].')'; $actopt[0]['quan'] = $stept[1]; $selopt[$rnoid[0]][] = $actopt[0]; $selopt['room'.$rnoid[0]] = $selopt['room'.$rnoid[0]].$actopt[0]['id'].":".$stept[1]."-".$chvar.";"; $realcost = (intval($actopt[0]['perday']) == 1 ? (floatval($optagecosts[($chvar - 1)]) * $use_los * $stept[1]) : (floatval($optagecosts[($chvar - 1)]) * $stept[1])); $children_age[$rnoid[0]][] = array('ageinterval' => $optagenames[($chvar - 1)], 'age' => '', 'cost' => $realcost); } else { $actopt[0]['quan'] = $stept[1]; // VBO 1.11 - options percentage cost of the room total fee if ($is_package === true) { $deftar_basecosts = $pkg['pernight_total'] == 1 ? ($pkg['cost'] * $daysdiff) : $pkg['cost']; } else { $deftar_basecosts = $tars[$rnoid[0]][0]['cost']; } $actopt[0]['cost'] = (int)$actopt[0]['pcentroom'] ? ($deftar_basecosts * $actopt[0]['cost'] / 100) : $actopt[0]['cost']; // $selopt[$rnoid[0]][] = $actopt[0]; if (!isset($selopt['room'.$rnoid[0]])) { $selopt['room'.$rnoid[0]] = ''; } $selopt['room'.$rnoid[0]] .= $actopt[0]['id'] . ":" . $stept[1] . ";"; $realcost = (intval($actopt[0]['perday']) == 1 ? ($actopt[0]['cost'] * $use_los * $stept[1]) : ($actopt[0]['cost'] * $stept[1])); } if (!empty($actopt[0]['maxprice']) && $actopt[0]['maxprice'] > 0 && $realcost > $actopt[0]['maxprice']) { $realcost = $actopt[0]['maxprice']; if (intval($actopt[0]['hmany']) == 1 && intval($stept[1]) > 1) { $realcost = $actopt[0]['maxprice'] * $stept[1]; } } /** * Count pets, if any. * * @since 1.16.2 (J) - 1.6.2 (WP) */ if ($opt_params['pet_fee'] ?? 0) { $tot_pets = 1; if ($actopt[0]['hmany'] > 0 && $stept[1] > 1) { $tot_pets = (int)$stept[1]; } $arrpeople[$rnoid[0]]['pets'] = $tot_pets; } /** * Custom check-in/out times due to late check-out/early check-in options. * * @since 1.17.2 (J) - 1.7.2 (WP) */ if (($opt_params['custom_checkinout'] ?? 0) && (($opt_params['set_checkin'] ?? 0) || ($opt_params['set_checkout'] ?? 0))) { $custom_checkinout = [ ($opt_params['set_checkin'] ?? 0), ($opt_params['set_checkout'] ?? 0), ]; } $realcost = ($actopt[0]['perperson'] == 1 ? ($realcost * $arrpeople[$rnoid[0]]['adults']) : $realcost); /** * Trigger event to allow third party plugins to apply a custom calculation for the option/extra fee or tax. * * @since 1.17.7 (J) - 1.7.7 (WP) */ $use_room_cost = $is_package === true ? $pkg['cost'] : ($tars[$rnoid[0]][0]['cost'] ?? 0); $custom_calc_booking = ['days' => $use_los]; $custom_calc_booking_room = array_merge($arrpeople[$rnoid[0]], ['room_cost' => $use_room_cost]); $custom_calculation = VBOFactory::getPlatform()->getDispatcher()->filter('onCalculateBookingOptionFeeCost', [$realcost, &$actopt[0], $custom_calc_booking, $custom_calc_booking_room]); if ($custom_calculation) { $realcost = (float) $custom_calculation[0]; } $opt_minus_iva = VikBooking::sayOptionalsMinusIva($realcost, $actopt[0]['idiva']); $tmpopr = VikBooking::sayOptionalsPlusIva($realcost, $actopt[0]['idiva']); if ($actopt[0]['is_citytax'] == 1) { $tot_city_taxes += $opt_minus_iva; } elseif ($actopt[0]['is_fee'] == 1) { $tot_fees += $opt_minus_iva; } elseif ($opt_params['damagedep'] ?? 0) { $tot_damage_dep += $opt_minus_iva; } // VBO 1.11 - always calculate the amount of tax no matter if this is already a tax or a fee if ($tmpopr == $realcost) { $tot_taxes += ($realcost - $opt_minus_iva); } else { $tot_taxes += ($tmpopr - $realcost); } // $isdue += $tmpopr; $optstr[$rnoid[0]][] = ($stept[1] > 1 ? $stept[1] . " " : "") . $actopt[0]['name'] . ": " . $tmpopr . " " . $currencyname . "\n"; } } $origtotdue = $isdue; $usedcoupon = false; $strcouponeff = ''; // access current customer $cpin = VikBooking::getCPinIstance(); $customer_details = $cpin->loadCustomerDetails(); // coupon if (strlen($pcouponcode) && $is_package !== true) { $coupon = VikBooking::getCouponInfo($pcouponcode); $valid_customer_coupon = true; if (!empty($coupon) && !empty($coupon['customers'])) { if (empty($customer_details['id']) || !in_array($customer_details['id'], $coupon['customers'])) { $valid_customer_coupon = false; } } if (!empty($coupon) && $valid_customer_coupon) { $coupondateok = true; if (strlen((string)$coupon['datevalid'])) { $dateparts = explode("-", $coupon['datevalid']); $pickinfo = $checkin_info; $dropinfo = $checkout_info; $checkpick = mktime(0, 0, 0, $pickinfo['mon'], $pickinfo['mday'], $pickinfo['year']); $checkdrop = mktime(0, 0, 0, $dropinfo['mon'], $dropinfo['mday'], $dropinfo['year']); if (!($checkpick >= $dateparts[0] && $checkpick <= $dateparts[1] && $checkdrop >= $dateparts[0] && $checkdrop <= $dateparts[1])) { $coupondateok = false; } } if (!empty($coupon['minlos']) && $coupon['minlos'] > $daysdiff) { $coupondateok = false; } if ($coupondateok) { $couponroomok = true; if (!$coupon['allvehicles']) { foreach ($rooms as $num => $r) { if (!(preg_match("/;".$r['id'].";/i", $coupon['idrooms']))) { $couponroomok = false; break; } } } if ($couponroomok) { $coupontotok = true; if (strlen((string)$coupon['mintotord'])) { if ($isdue < $coupon['mintotord']) { $coupontotok = false; } } if ($coupon['maxtotord'] > 0 && $isdue > $coupon['maxtotord']) { $coupontotok = false; } /** * Trigger event to allow third-party plugins to implement additional coupon validations or manipulation. * * @since 1.16.7 (J) - 1.6.7 (WP) */ if ($coupontotok) { $coupon_validation = VBOFactory::getPlatform()->getDispatcher()->filter('onValidateCouponCode', [&$coupon]); if (is_array($coupon_validation) && in_array(false, $coupon_validation, true)) { $coupontotok = false; } } if ($coupontotok) { $usedcoupon = true; if ($coupon['percentot'] == 1) { // percent value $minuscoupon = 100 - $coupon['value']; /** * We allow coupon codes to be applied on the entire reservation or as always just on the total minus mandatory taxes. * * @since 1.13.5 (J) - 1.3.5 (WP) * @since 1.14.3 (J) - 1.4.3 (WP) we also exclude the amount of taxes beside the mandatory fees. * @since 1.16.0 (J) - 1.6.0 (WP) taxes are proportionally calculated when coupon before tax. * @since 1.16.8 (J) - 1.6.8 (WP) with coupon before taxes, discounted amount calculation is an equal subtration. */ $prev_isdue = $isdue; $tot_net = ($isdue - $tot_taxes - $tot_city_taxes - $tot_fees - $tot_damage_dep); $coupondiscount = ($coupon['excludetaxes'] ? $tot_net : $isdue) * $coupon['value'] / 100; $isdue = ($coupon['excludetaxes'] ? $tot_net : $isdue) * $minuscoupon / 100; $tot_taxes = $coupon['excludetaxes'] ? ($tot_taxes * ($tot_net - $coupondiscount) / $tot_net) : $tot_taxes; $isdue += $coupon['excludetaxes'] ? ($tot_taxes + $tot_city_taxes + $tot_fees + $tot_damage_dep) : 0; $coupondiscount = abs($prev_isdue - $isdue); } else { // total value $coupondiscount = $coupon['value']; // isdue : taxes = coupon_discount : x $tax_prop = $tot_taxes * $coupon['value'] / $isdue; $tot_taxes -= $tax_prop; $tot_taxes = $tot_taxes < 0 ? 0 : $tot_taxes; $isdue -= $coupon['value']; $isdue = $isdue < 0 ? 0 : $isdue; } $strcouponeff = $coupon['id'].';'.$coupondiscount.';'.$coupon['code']; } } } } } $strisdue = number_format($isdue, 2) . 'vikbooking'; $ptotdue = number_format($ptotdue, 2) . 'vikbooking'; if ($strisdue != $ptotdue) { showSelectVb(JText::translate('VBINCONGRTOT')); return; } // pay full amount cookie (2 weeks) $nodep_set = !empty($pnodep) ? '1' : '0'; $nodep_time_set = !empty($pnodep) ? (time() + (86400 * 14)) : (time() - (86400 * 14)); $cookie = JFactory::getApplication()->input->cookie; VikRequest::setCookie('vboFA', $nodep_set, $nodep_time_set, '/'); // modify booking $mod_booking = []; $skip_busy_ids = []; $cur_mod = $session->get('vboModBooking', ''); if (is_array($cur_mod) && $cur_mod) { $mod_booking = $cur_mod; $skip_busy_ids = VikBooking::loadBookingBusyIds($mod_booking['id']); } $nowts = time(); $checkts = $nowts; $today_bookings = VikBooking::todayBookings(); if ($today_bookings) { $checkts = mktime(0, 0, 0, date('n'), date('j'), date('Y')); } if (!($checkts <= $pcheckin && $checkts < $pcheckout && $pcheckin < $pcheckout)) { showSelectVb(JText::translate('VBINVALIDDATES')); return; } $roomsavailable = true; foreach ($rooms as $num => $r) { // determine the number of nights of stay and dates to consider $use_los = (int)$daysdiff; $room_checkin = $pcheckin; $room_checkout = $pcheckout; if (!empty($split_stay) && !empty($split_stay[($num - 1)]) && $split_stay[($num - 1)]['idroom'] == $r['id']) { $use_los = (int)$split_stay[($num - 1)]['nights']; $room_checkin = $split_stay[($num - 1)]['checkin_ts']; $room_checkout = $split_stay[($num - 1)]['checkout_ts']; } if (!VikBooking::roomNotLocked($r['id'], $r['units'], $room_checkin, $room_checkout, true, $skip_busy_ids)) { $roomsavailable = false; break; } } if ($roomsavailable !== true) { showSelectVb(JText::translate('VBROOMBOOKEDBYOTHER')); return; } // save in session the checkin and checkout time of the reservation made $session->set('vikbooking_order_checkin', $pcheckin); $session->set('vikbooking_order_checkout', $pcheckout); // handle booking sid and customer information summary string $sid = $mod_booking ? $mod_booking['sid'] : VikBooking::getSecretLink(); $custdata = VikBooking::buildCustData($arrcustdata, "\r\n"); if (VBOPlatformDetection::isWordPress()) { $viklink = JURI::root() . "index.php?option=com_vikbooking&view=booking&sid=" . $sid . "&ts=" . $nowts . (!empty($pnodep) ? "&nodep=".$pnodep : "") . (!empty($pitemid) ? "&Itemid=" . $pitemid : ""); } else { $bestitemid = VikBooking::findProperItemIdType(array('booking')); $viklink = VikBooking::externalroute("index.php?option=com_vikbooking&view=booking&sid=" . $sid . "&ts=" . $nowts . (!empty($pnodep) ? "&nodep=".$pnodep : ""), false, (!empty($bestitemid) ? $bestitemid : null)); } $admail = VikBooking::getAdminMail(); $ftitle = VikBooking::getFrontTitle(); $pricestr = []; if ($is_package === true) { foreach ($rooms as $num => $r) { $pkg_cost = $pkg['pernight_total'] == 1 ? ($pkg['cost'] * $daysdiff) : $pkg['cost']; $pkg_cost = $pkg['perperson'] == 1 ? ($pkg_cost * ($arrpeople[$num]['adults'] > 0 ? $arrpeople[$num]['adults'] : 1)) : $pkg_cost; $cost_plus_tax = VikBooking::sayPackagePlusIva($pkg_cost, $pkg['idiva']); $pricestr[$num] = $pkg['name'].": ".$cost_plus_tax." ".$currencyname; } } else { foreach ($tars as $num => $tar) { $pricestr[$num] = VikBooking::getPriceName($tar[0]['idprice'], $vbo_tn) . ": " . VikBooking::sayCostPlusIva($tar[0]['cost'], $tar[0]['idprice']) . " " . $currencyname . (!empty($tar[0]['attrdata']) ? "\n" . VikBooking::getPriceAttr($tar[0]['idprice'], $vbo_tn) . ": " . $tar[0]['attrdata'] : ""); } } $currentUser = JFactory::getUser(); $langtag = $vbo_tn->current_lang; $vcmchanneldata = $session->get('vcmChannelData', ''); $vcmchanneldata = !empty($vcmchanneldata) && is_array($vcmchanneldata) && count($vcmchanneldata) > 0 ? $vcmchanneldata : ''; // attempt to save customer $cpin->setCustomerExtraInfo($fieldflags); $cpin->saveCustomerDetails($t_first_name, $t_last_name, $useremail, $phone_number, $usercountry, $arrcfields); // collect all room IDs involved $rooms_involved = []; foreach ($rooms as $room_booked) { if (!in_array($room_booked['id'], $rooms_involved)) { $rooms_involved[] = $room_booked['id']; } } $must_payment = $mod_booking ? false : VikBooking::areTherePayments($rooms_involved); $payment = []; if ($must_payment) { $payment = VikBooking::getPayment($pgpayid); } if ($must_payment && empty($payment)) { // error, payment was not selected VikError::raiseWarning('', JText::translate('ERRSELECTPAYMENT')); // build redirect URI values $redirect_uri_vals = [ 'option' => 'com_vikbooking', 'task' => 'oconfirm', ]; foreach ($prices as $num => $pid) { $redirect_uri_vals['priceid' . $num] = $pid; } for ($ir = 1; $ir <= $proomsnum; $ir++) { if (isset($selopt[$ir]) && is_array($selopt[$ir])) { foreach ($selopt[$ir] as $opt) { if (array_key_exists('chageintv', $opt)) { if (!isset($redirect_uri_vals['optid' . $ir . $opt['id']])) { $redirect_uri_vals['optid' . $ir . $opt['id']] = []; } $redirect_uri_vals['optid' . $ir . $opt['id']][] = $opt['chageintv']; } else { $redirect_uri_vals['optid' . $ir . $opt['id']] = $opt['quan']; } } } } $redirect_uri_vals['roomid'] = []; foreach ($rooms as $num => $r) { $redirect_uri_vals['roomid'][] = $r['id']; } $redirect_uri_vals['adults'] = []; $redirect_uri_vals['children'] = []; foreach ($arrpeople as $indroom => $aduch) { $redirect_uri_vals['adults'][] = $aduch['adults']; $redirect_uri_vals['children'][] = $aduch['children']; } $redirect_uri_vals['roomsnum'] = $proomsnum; $redirect_uri_vals['days'] = $pdays; $redirect_uri_vals['checkin'] = $pcheckin; $redirect_uri_vals['checkout'] = $pcheckout; if (!empty($split_stay)) { $redirect_uri_vals['split_stay'] = $split_stay; } $redirect_uri_vals['Itemid'] = !empty($pitemid) ? $pitemid : null; $app->redirect(JRoute::rewrite('index.php?' . http_build_query($redirect_uri_vals), false)); exit; } // turnover seconds $turnover_secs = VikBooking::getHoursRoomAvail() * 3600; $realback = $turnover_secs + $pcheckout; // push data to tracker for conversion $vbo_tracker = VikBooking::getTracker(); $vbo_tracker->pushDates($pcheckin, $pcheckout, $pdays)->pushParty($arrpeople)->pushData('idcustomer', $cpin->getNewCustomerId()); /** * Custom check-in/out times due to late check-out/early check-in options. * * @since 1.17.2 (J) - 1.7.2 (WP) */ if ($custom_checkinout) { // overwrite check-in and/or check-out timestamp(s) if ($custom_checkinout[0] >= 3600) { // overwrite check-in timestamp $time_hours = floor($custom_checkinout[0] / 3600); $time_minutes = floor(($custom_checkinout[0] - ($time_hours * 3600)) / 60); $pcheckin = mktime($time_hours, $time_minutes, 0, $checkin_info['mon'], $checkin_info['mday'], $checkin_info['year']); } if ($custom_checkinout[1] >= 3600) { // overwrite check-out timestamp $time_hours = floor($custom_checkinout[1] / 3600); $time_minutes = floor(($custom_checkinout[1] - ($time_hours * 3600)) / 60); $pcheckout = mktime($time_hours, $time_minutes, 0, $checkout_info['mon'], $checkout_info['mday'], $checkout_info['year']); // overwrite "realback" timestamp as well $realback = $turnover_secs + $pcheckout; } } if (!$mod_booking && ((!empty($payment) && intval($payment['setconfirmed']) == 1) || !$must_payment || ($usedcoupon && $isdue <= 0))) { // 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 $arrbusy = []; foreach ($rooms as $num => $r) { // determine the number of nights of stay and dates to consider $room_checkin = $pcheckin; $room_checkout = $pcheckout; $room_realback = $realback; if (!empty($split_stay) && !empty($split_stay[($num - 1)]) && $split_stay[($num - 1)]['idroom'] == $r['id']) { $room_checkin = $split_stay[($num - 1)]['checkin_ts']; $room_checkout = $split_stay[($num - 1)]['checkout_ts']; $room_realback = $turnover_secs + $room_checkout; } $busy_record = new stdClass; $busy_record->idroom = $r['id']; $busy_record->checkin = $room_checkin; $busy_record->checkout = $room_checkout; $busy_record->realback = $room_realback; $dbo->insertObject('#__vikbooking_busy', $busy_record, 'id'); if (!isset($busy_record->id)) { showSelectVb('Critical error while occupying the rooms. Please try again'); return; } $arrbusy[$num] = $busy_record->id; } // store booking $booking_record = new stdClass; $booking_record->custdata = $custdata; $booking_record->ts = $nowts; $booking_record->status = 'confirmed'; $booking_record->days = $pdays; $booking_record->checkin = $pcheckin; $booking_record->checkout = $pcheckout; $booking_record->custmail = $useremail; $booking_record->sid = $sid; $booking_record->idpayment = !empty($payment) ? ($payment['id'] . '=' . $payment['name']) : null; $booking_record->ujid = $currentUser->id; $booking_record->coupon = $usedcoupon === true ? $strcouponeff : null; $booking_record->roomsnum = count($rooms); $booking_record->total = (float)$isdue; $booking_record->channel = is_array($vcmchanneldata) && !empty($vcmchanneldata['name']) ? $vcmchanneldata['name'] : null; $booking_record->lang = $langtag; $booking_record->country = !empty($usercountry) ? $usercountry : null; $booking_record->tot_taxes = (float)$tot_taxes; $booking_record->tot_city_taxes = (float)$tot_city_taxes; $booking_record->tot_fees = (float)$tot_fees; if ($tot_damage_dep) { $booking_record->tot_damage_dep = (float) $tot_damage_dep; } $booking_record->phone = $phone_number; $booking_record->pkg = $is_package === true ? (int)$pkg['id'] : null; $booking_record->split_stay = !empty($split_stay) ? 1 : 0; $dbo->insertObject('#__vikbooking_orders', $booking_record, 'id'); if (!isset($booking_record->id)) { showSelectVb('Critical error while saving the booking. Please try again'); return; } $neworderid = $booking_record->id; // ConfirmationNumber $confirmnumber = VikBooking::generateConfirmNumber($neworderid, true); // assign room specific unit $set_room_indexes = (VikBooking::autoRoomUnit() || (count($proomindex) == count($rooms))); $room_indexes_usemap = []; $room_indexes_forcemap = []; foreach ($rooms as $num => $r) { $q = "INSERT INTO `#__vikbooking_ordersbusy` (`idorder`,`idbusy`) VALUES(" . (int)$neworderid . ", " . (int)$arrbusy[$num] . ");"; $dbo->setQuery($q); $dbo->execute(); $json_ch_age = ''; if (array_key_exists($num, $children_age)) { $json_ch_age = json_encode($children_age[$num]); } // assign room specific unit $room_indexes = $set_room_indexes === true ? VikBooking::getRoomUnitNumsAvailable(array('id' => $neworderid, 'checkin' => $pcheckin, 'checkout' => $pcheckout), $r['id']) : array(); $use_ind_key = 0; $force_rindex = 0; if ($room_indexes && isset($room_indexes_forcemap[$r['id']])) { // an index for this same room was forced already, reset the values foreach ($room_indexes as $av_key => $av_index) { if (in_array((int)$av_index, $room_indexes_forcemap[$r['id']])) { unset($room_indexes[$av_key]); } } $room_indexes = array_values($room_indexes); } if ($room_indexes) { if (count($proomindex) == count($rooms) && !empty($proomindex[($num - 1)])) { // exact distinctive feature index selected foreach ($room_indexes as $av_index) { if ((int)$av_index == (int)$proomindex[($num - 1)]) { // requested index is available $force_rindex = (int)$proomindex[($num - 1)]; if (isset($room_indexes_forcemap[$r['id']]) && in_array($force_rindex, $room_indexes_forcemap[$r['id']])) { // cannot book the same unit twice $force_rindex = 0; continue; } break; } } if ($force_rindex) { // store the forced index for any possible equal room booked later in the same loop if (!isset($room_indexes_forcemap[$r['id']])) { $room_indexes_forcemap[$r['id']] = []; } array_push($room_indexes_forcemap[$r['id']], $force_rindex); } } if (!array_key_exists($r['id'], $room_indexes_usemap)) { $room_indexes_usemap[$r['id']] = $use_ind_key; } else { $use_ind_key = $room_indexes_usemap[$r['id']]; } if (isset($room_indexes[$use_ind_key])) { $rooms[$num]['roomindex'] = (int)$room_indexes[$use_ind_key]; } } // $pkg_cost = 0; if ($is_package === true) { $pkg_cost = $pkg['pernight_total'] == 1 ? ($pkg['cost'] * $daysdiff) : $pkg['cost']; $pkg_cost = $pkg['perperson'] == 1 ? ($pkg_cost * ($arrpeople[$num]['adults'] > 0 ? $arrpeople[$num]['adults'] : 1)) : $pkg_cost; // $pkg_cost = VikBooking::sayPackagePlusIva($pkg_cost, $pkg['idiva']); } $oroom_record = new stdClass; $oroom_record->idorder = (int)$neworderid; $oroom_record->idroom = (int)$r['id']; $oroom_record->adults = (int)$arrpeople[$num]['adults']; $oroom_record->children = (int)$arrpeople[$num]['children']; $oroom_record->pets = isset($arrpeople[$num]['pets']) ? (int)$arrpeople[$num]['pets'] : 0; $oroom_record->idtar = (int)$tars[$num][0]['id']; $oroom_record->optionals = isset($selopt['room'.$num]) ? $selopt['room'.$num] : null; $oroom_record->childrenage = (!empty($json_ch_age) ? $json_ch_age : null); $oroom_record->t_first_name = $t_first_name; $oroom_record->t_last_name = $t_last_name; $oroom_record->roomindex = null; if ($force_rindex) { $oroom_record->roomindex = $force_rindex; } elseif ($room_indexes && isset($room_indexes[$use_ind_key])) { $oroom_record->roomindex = (int)$room_indexes[$use_ind_key]; } $oroom_record->pkg_id = ($is_package === true ? (int)$pkg['id'] : null); $oroom_record->pkg_name = ($is_package === true ? $pkg['name'] : null); $oroom_record->cust_cost = ($is_package === true ? $pkg_cost : null); $oroom_record->cust_idiva = ($is_package === true ? (int)$pkg['idiva'] : null); $oroom_record->room_cost = (array_key_exists($num, $rooms_costs_map) ? $rooms_costs_map[$num] : null); $dbo->insertObject('#__vikbooking_ordersrooms', $oroom_record, 'id'); if ($room_indexes) { $room_indexes_usemap[$r['id']]++; } } if (!empty($split_stay)) { // save transient on db for split stay information VBOFactory::getConfig()->set('split_stay_' . $neworderid, json_encode($split_stay)); } // customer booking $cpin->saveCustomerBooking($neworderid); if ($usedcoupon === true && $coupon['type'] == 2) { $q = "DELETE FROM `#__vikbooking_coupons` WHERE `id`='".$coupon['id']."';"; $dbo->setQuery($q); $dbo->execute(); } // check if some of the rooms booked have shared calendars VikBooking::updateSharedCalendars($neworderid, array(), $pcheckin, $pcheckout); // send email notification to guest and admin VikBooking::sendBookingEmail($neworderid, ['guest', 'admin']); //SMS VikBooking::sendBookingSMS($neworderid); //Booking History VikBooking::getBookingHistoryInstance()->setBid($neworderid)->store('NC', 'IP: '.VikRequest::getVar('REMOTE_ADDR', '', 'server')); //invoke VikChannelManager if (is_file(VCM_SITE_PATH . DIRECTORY_SEPARATOR . "helpers" . DIRECTORY_SEPARATOR . "synch.vikbooking.php")) { require_once(VCM_SITE_PATH . DIRECTORY_SEPARATOR . "helpers" . DIRECTORY_SEPARATOR . "synch.vikbooking.php"); $vcm = new SynchVikBooking($neworderid); $vcm->setPushType('new')->sendRequest(); } // VBO 1.11 - push data to tracker for conversion $vbo_tracker->pushData('idorder', $neworderid)->closeTrack(); $vbo_tracker->resetTrack(); $app->redirect(JRoute::rewrite("index.php?option=com_vikbooking&view=booking&sid=" . $sid . "&ts=" . $nowts . (!empty($pnodep) ? "&nodep=".$pnodep : "") . (!empty($pitemid) ? "&Itemid=" . $pitemid : ""), false)); } elseif ($mod_booking) { // booking modification statement // get current orders-busy relations $old_busy_ids = []; $q = "SELECT `idbusy` FROM `#__vikbooking_ordersbusy` WHERE `idorder`=".(int)$mod_booking['id'].";"; $dbo->setQuery($q); $getbusy = $dbo->loadAssocList(); if ($getbusy) { foreach ($getbusy as $gbu) { array_push($old_busy_ids, $gbu['idbusy']); } } //remove current busy records if (count($old_busy_ids)) { $q = "DELETE FROM `#__vikbooking_busy` WHERE `id` IN (".implode(', ', $old_busy_ids).");"; $dbo->setQuery($q); $dbo->execute(); } $q = "DELETE FROM `#__vikbooking_ordersbusy` WHERE `idorder`=".(int)$mod_booking['id'].";"; $dbo->setQuery($q); $dbo->execute(); //get current rooms (for VCM and for composing the log) $q = "SELECT `or`.*,`r`.`name`,`r`.`idopt`,`r`.`units`,`r`.`fromadult`,`r`.`toadult` FROM `#__vikbooking_ordersrooms` AS `or`,`#__vikbooking_rooms` AS `r` WHERE `or`.`idorder`=".(int)$mod_booking['id']." AND `or`.`idroom`=`r`.`id` ORDER BY `or`.`id` ASC;"; $dbo->setQuery($q); $dbo->execute(); $old_ordersrooms = $dbo->loadAssocList(); $mod_booking['rooms_info'] = $old_ordersrooms; //remove current rooms $q = "DELETE FROM `#__vikbooking_ordersrooms` WHERE `idorder`=".(int)$mod_booking['id'].";"; $dbo->setQuery($q); $dbo->execute(); //update the booking by creating first the new busy records $arrbusy = []; foreach ($rooms as $num => $r) { $q = "INSERT INTO `#__vikbooking_busy` (`idroom`,`checkin`,`checkout`,`realback`) VALUES(".(int)$r['id'].", ".$dbo->quote($pcheckin).", ".$dbo->quote($pcheckout).", ".$dbo->quote($realback).");"; $dbo->setQuery($q); $dbo->execute(); $lid = $dbo->insertid(); $arrbusy[$num] = $lid; } // assign room specific unit $set_room_indexes = (VikBooking::autoRoomUnit() || (count($proomindex) == count($rooms))); $room_indexes_usemap = []; $room_indexes_forcemap = []; //create the new rooms and orders-busy relations foreach ($rooms as $num => $r) { $q = "INSERT INTO `#__vikbooking_ordersbusy` (`idorder`,`idbusy`) VALUES(".(int)$mod_booking['id'].", ".(int)$arrbusy[$num].");"; $dbo->setQuery($q); $dbo->execute(); $json_ch_age = ''; if (array_key_exists($num, $children_age)) { $json_ch_age = json_encode($children_age[$num]); } // assign room specific unit $room_indexes = $set_room_indexes === true ? VikBooking::getRoomUnitNumsAvailable(array('id' => $mod_booking['id'], 'checkin' => $pcheckin, 'checkout' => $pcheckout), $r['id']) : array(); $use_ind_key = 0; $force_rindex = 0; if ($room_indexes && isset($room_indexes_forcemap[$r['id']])) { // an index for this same room was forced already, reset the values foreach ($room_indexes as $av_key => $av_index) { if (in_array((int)$av_index, $room_indexes_forcemap[$r['id']])) { unset($room_indexes[$av_key]); } } $room_indexes = array_values($room_indexes); } if ($room_indexes) { if (count($proomindex) == count($rooms) && !empty($proomindex[($num - 1)])) { // exact distinctive feature index selected foreach ($room_indexes as $av_index) { if ((int)$av_index == (int)$proomindex[($num - 1)]) { // requested index is available $force_rindex = (int)$proomindex[($num - 1)]; if (isset($room_indexes_forcemap[$r['id']]) && in_array($force_rindex, $room_indexes_forcemap[$r['id']])) { // cannot book the same unit twice $force_rindex = 0; continue; } break; } } if ($force_rindex) { // store the forced index for any possible equal room booked later in the same loop if (!isset($room_indexes_forcemap[$r['id']])) { $room_indexes_forcemap[$r['id']] = []; } array_push($room_indexes_forcemap[$r['id']], $force_rindex); } } if (!array_key_exists($r['id'], $room_indexes_usemap)) { $room_indexes_usemap[$r['id']] = $use_ind_key; } else { $use_ind_key = $room_indexes_usemap[$r['id']]; } if (isset($room_indexes[$use_ind_key])) { $rooms[$num]['roomindex'] = (int)$room_indexes[$use_ind_key]; } } // $pkg_cost = 0; if ($is_package === true) { $pkg_cost = $pkg['pernight_total'] == 1 ? ($pkg['cost'] * $daysdiff) : $pkg['cost']; $pkg_cost = $pkg['perperson'] == 1 ? ($pkg_cost * ($arrpeople[$num]['adults'] > 0 ? $arrpeople[$num]['adults'] : 1)) : $pkg_cost; // $pkg_cost = VikBooking::sayPackagePlusIva($pkg_cost, $pkg['idiva']); } $oroom_record = new stdClass; $oroom_record->idorder = (int)$mod_booking['id']; $oroom_record->idroom = (int)$r['id']; $oroom_record->adults = (int)$arrpeople[$num]['adults']; $oroom_record->children = (int)$arrpeople[$num]['children']; $oroom_record->pets = isset($arrpeople[$num]['pets']) ? (int)$arrpeople[$num]['pets'] : 0; $oroom_record->idtar = (int)$tars[$num][0]['id']; $oroom_record->optionals = isset($selopt['room'.$num]) ? $selopt['room'.$num] : null; $oroom_record->childrenage = (!empty($json_ch_age) ? $json_ch_age : null); $oroom_record->t_first_name = $t_first_name; $oroom_record->t_last_name = $t_last_name; $oroom_record->roomindex = null; if ($force_rindex) { $oroom_record->roomindex = $force_rindex; } elseif ($room_indexes && isset($room_indexes[$use_ind_key])) { $oroom_record->roomindex = (int)$room_indexes[$use_ind_key]; } $oroom_record->pkg_id = ($is_package === true ? (int)$pkg['id'] : null); $oroom_record->pkg_name = ($is_package === true ? $pkg['name'] : null); $oroom_record->cust_cost = ($is_package === true ? $pkg_cost : null); $oroom_record->cust_idiva = ($is_package === true ? (int)$pkg['idiva'] : null); $oroom_record->room_cost = (array_key_exists($num, $rooms_costs_map) ? $rooms_costs_map[$num] : null); $dbo->insertObject('#__vikbooking_ordersrooms', $oroom_record, 'id'); if ($room_indexes) { $room_indexes_usemap[$r['id']]++; } } // update the booking record (do not touch information like sid, confirmnumber, payment method etc..) $logmod = VikBooking::getLogBookingModification($mod_booking); $mod_notes = $logmod.(!empty($mod_booking['adminnotes']) ? "\n\n".$mod_booking['adminnotes'] : ''); // if old total lower than new total, increment paymcount to allow a new payment (if configuration setting enabled) $mod_paymcount = (int)$mod_booking['paymcount']; if ($mod_booking['total'] < $isdue) { $mod_paymcount++; } $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."',`tot_damage_dep`='".$tot_damage_dep."',`phone`=".$dbo->quote($phone_number).",`pkg`=".($is_package === true ? (int)$pkg['id'] : "NULL")." WHERE `id`=".(int)$mod_booking['id'].";"; $dbo->setQuery($q); $dbo->execute(); // remove the coupon used (should never been allowed for modifications) if ($usedcoupon == true && $coupon['type'] == 2) { $q = "DELETE FROM `#__vikbooking_coupons` WHERE `id`=".(int)$coupon['id'].";"; $dbo->setQuery($q); $dbo->execute(); } // unset any previously booked room due to calendar sharing (should not be necessary because busy records have already been purged) VikBooking::cleanSharedCalendarsBusy($mod_booking['id']); // check if some of the rooms booked have shared calendars VikBooking::updateSharedCalendars($mod_booking['id'], array(), $pcheckin, $pcheckout); //send email messages (admin and customer) and invoke SMS send VikBooking::sendBookingEmail($mod_booking['id'], array('guest', 'admin'), true, false, $type = 'modified'); //SMS VikBooking::sendBookingSMS($mod_booking['id']); //Booking History VikBooking::getBookingHistoryInstance()->setBid($mod_booking['id'])->store('MW', $logmod); //invoke VikChannelManager if (is_file(VCM_SITE_PATH . DIRECTORY_SEPARATOR . "helpers" . DIRECTORY_SEPARATOR . "synch.vikbooking.php")) { $vcm_obj = VikBooking::getVcmInvoker(); $vcm_obj->setOids(array($mod_booking['id']))->setSyncType('modify')->setOriginalBooking($mod_booking); $vcm_obj->doSync(); } //unset the session value $session->set('vboModBooking', ''); // VBO 1.11 - push data to tracker for conversion $vbo_tracker->pushData('idorder', $mod_booking['id'])->pushMessage(JText::translate('VBOBOOKINGMODOK'))->closeTrack(); $vbo_tracker->resetTrack(); $app->enqueueMessage(JText::translate('VBOBOOKINGMODOK')); $app->redirect(JRoute::rewrite("index.php?option=com_vikbooking&view=booking&sid=" . $sid . "&ts=" . $nowts . (!empty($pnodep) ? "&nodep=".$pnodep : "") . (!empty($pitemid) ? "&Itemid=" . $pitemid : ""), false)); } else { // booking must have status stand-by and proceed to the payment $booking_record = new stdClass; $booking_record->custdata = $custdata; $booking_record->ts = $nowts; $booking_record->status = 'standby'; $booking_record->days = $pdays; $booking_record->checkin = $pcheckin; $booking_record->checkout = $pcheckout; $booking_record->custmail = $useremail; $booking_record->sid = $sid; $booking_record->idpayment = !empty($payment) ? ($payment['id'] . '=' . $payment['name']) : null; $booking_record->ujid = $currentUser->id; $booking_record->coupon = $usedcoupon === true ? $strcouponeff : null; $booking_record->roomsnum = count($rooms); $booking_record->total = (float)$isdue; $booking_record->channel = is_array($vcmchanneldata) && !empty($vcmchanneldata['name']) ? $vcmchanneldata['name'] : null; $booking_record->lang = $langtag; $booking_record->country = !empty($usercountry) ? $usercountry : null; $booking_record->tot_taxes = (float)$tot_taxes; $booking_record->tot_city_taxes = (float)$tot_city_taxes; $booking_record->tot_fees = (float)$tot_fees; if ($tot_damage_dep) { $booking_record->tot_damage_dep = (float) $tot_damage_dep; } $booking_record->phone = $phone_number; $booking_record->pkg = $is_package === true ? (int)$pkg['id'] : null; $booking_record->split_stay = !empty($split_stay) ? 1 : 0; $dbo->insertObject('#__vikbooking_orders', $booking_record, 'id'); if (!isset($booking_record->id)) { showSelectVb('Critical error while saving the booking. Please try again'); return; } $neworderid = $booking_record->id; $room_indexes_forcemap = []; foreach ($rooms as $num => $r) { $json_ch_age = ''; if (array_key_exists($num, $children_age)) { $json_ch_age = json_encode($children_age[$num]); } $pkg_cost = 0; if ($is_package === true) { $pkg_cost = $pkg['pernight_total'] == 1 ? ($pkg['cost'] * $daysdiff) : $pkg['cost']; $pkg_cost = $pkg['perperson'] == 1 ? ($pkg_cost * ($arrpeople[$num]['adults'] > 0 ? $arrpeople[$num]['adults'] : 1)) : $pkg_cost; // $pkg_cost = VikBooking::sayPackagePlusIva($pkg_cost, $pkg['idiva']); } $oroom_record = new stdClass; $oroom_record->idorder = (int)$neworderid; $oroom_record->idroom = (int)$r['id']; $oroom_record->adults = (int)$arrpeople[$num]['adults']; $oroom_record->children = (int)$arrpeople[$num]['children']; $oroom_record->pets = isset($arrpeople[$num]['pets']) ? (int)$arrpeople[$num]['pets'] : 0; $oroom_record->idtar = (int)$tars[$num][0]['id']; $oroom_record->optionals = isset($selopt['room'.$num]) ? $selopt['room'.$num] : null; $oroom_record->childrenage = (!empty($json_ch_age) ? $json_ch_age : null); $oroom_record->t_first_name = $t_first_name; $oroom_record->t_last_name = $t_last_name; $oroom_record->roomindex = null; if (count($proomindex) == count($rooms) && !empty($proomindex[($num - 1)])) { // check if the sub-unit requested is available if (!isset($room_indexes_forcemap[$r['id']])) { $room_indexes_forcemap[$r['id']] = []; } $room_indexes = VikBooking::getRoomUnitNumsAvailable(array('id' => $neworderid, 'checkin' => $pcheckin, 'checkout' => $pcheckout), $r['id']); $force_rindex = 0; foreach ($room_indexes as $av_index) { if ((int)$av_index == (int)$proomindex[($num - 1)] && !in_array((int)$proomindex[($num - 1)], $room_indexes_forcemap[$r['id']])) { // requested index is available $force_rindex = (int)$proomindex[($num - 1)]; array_push($room_indexes_forcemap[$r['id']], $force_rindex); break; } } if (!empty($force_rindex)) { $oroom_record->roomindex = $force_rindex; } } $oroom_record->pkg_id = ($is_package === true ? (int)$pkg['id'] : null); $oroom_record->pkg_name = ($is_package === true ? $pkg['name'] : null); $oroom_record->cust_cost = ($is_package === true ? $pkg_cost : null); $oroom_record->cust_idiva = ($is_package === true ? (int)$pkg['idiva'] : null); $oroom_record->room_cost = (array_key_exists($num, $rooms_costs_map) ? $rooms_costs_map[$num] : null); $dbo->insertObject('#__vikbooking_ordersrooms', $oroom_record, 'id'); } if ($usedcoupon === true && $coupon['type'] == 2) { $q = "DELETE FROM `#__vikbooking_coupons` WHERE `id`=" . (int)$coupon['id'] . ";"; $dbo->setQuery($q); $dbo->execute(); } // lock rooms waiting to be confirmed $lock_until_ts = VikBooking::getMinutesLock(true); foreach ($rooms as $num => $r) { // determine the number of nights of stay and dates to consider $room_checkin = $pcheckin; $room_checkout = $pcheckout; $room_realback = $realback; if (!empty($split_stay) && !empty($split_stay[($num - 1)]) && $split_stay[($num - 1)]['idroom'] == $r['id']) { $room_checkin = $split_stay[($num - 1)]['checkin_ts']; $room_checkout = $split_stay[($num - 1)]['checkout_ts']; $room_realback = $turnover_secs + $room_checkout; } $tmp_lock_record = new stdClass; $tmp_lock_record->idroom = $r['id']; $tmp_lock_record->checkin = $room_checkin; $tmp_lock_record->checkout = $room_checkout; $tmp_lock_record->until = $lock_until_ts; $tmp_lock_record->realback = $room_realback; $tmp_lock_record->idorder = (int)$neworderid; $dbo->insertObject('#__vikbooking_tmplock', $tmp_lock_record, 'id'); } if (!empty($split_stay)) { // save transient on db for split stay information VBOFactory::getConfig()->set('split_stay_' . $neworderid, json_encode($split_stay)); } // Customer Booking $cpin->saveCustomerBooking($neworderid); // send email notification to guest and admin VikBooking::sendBookingEmail($neworderid, ['guest', 'admin']); //SMS VikBooking::sendBookingSMS($neworderid); //Booking History VikBooking::getBookingHistoryInstance()->setBid($neworderid)->store('NP', 'IP: ' . VikRequest::getVar('REMOTE_ADDR', '', 'server')); /** * Invoke VikChannelManager also in case of pending reservations. * * @since 1.16.5 (J) - 1.6.5 (WP) * * @requires VCM >= 1.8.20 */ if (class_exists('VCMRequestAvailability')) { VikBooking::getVcmInvoker() ->setOids([$neworderid]) ->setSyncType('new') ->doSync(); } // VBO 1.11 - push data to tracker for conversion $vbo_tracker->pushData('idorder', $neworderid)->closeTrack(); $vbo_tracker->resetTrack(); // redirect URI to pending booking details $booking_details_uri = "index.php?option=com_vikbooking&view=booking&sid=" . $sid . "&ts=" . $nowts . (!empty($pnodep) ? "&nodep=".$pnodep : "") . (!empty($pitemid) ? "&Itemid=" . $pitemid : ""); /** * Trigger event to allow third-party plugins to manipulate the redirect URI. * * @since 1.17.2 (J) - 1.7.2 (WP) */ VBOFactory::getPlatform()->getDispatcher()->trigger('onRedirectOrder', [&$booking_details_uri, $neworderid]); // redirect to booking details page $app->redirect(JRoute::rewrite($booking_details_uri, false)); } } public function vieworder() { VikRequest::setVar('view', 'booking'); parent::display(); } public function cancelrequest() { if (!JSession::checkToken()) { // missing CSRF-proof token VBOHttpDocument::getInstance()->close(403, JText::translate('JINVALID_TOKEN')); } $dbo = JFactory::getDbo(); $mainframe = JFactory::getApplication(); $psid = VikRequest::getString('sid', '', 'request'); $pidorder = VikRequest::getString('idorder', '', 'request'); if (!empty($psid) && !empty($pidorder)) { $q = "SELECT * FROM `#__vikbooking_orders` WHERE `id`=".intval($pidorder)." AND `sid`=".$dbo->quote($psid).";"; $dbo->setQuery($q); $order = $dbo->loadAssocList(); if ($order) { $pemail = VikRequest::getString('email', '', 'request'); $preason = VikRequest::getString('reason', '', 'request'); if (!empty($pemail) && !empty($preason)) { $to = VikBooking::getAdminMail(); if(strpos($to, ',') !== false) { $all_recipients = explode(',', $to); foreach ($all_recipients as $k => $v) { if(empty($v)) { unset($all_recipients[$k]); } } if(count($all_recipients) > 0) { $to = $all_recipients; } } //Booking History VikBooking::getBookingHistoryInstance()->setBid($order[0]['id'])->store('CR', $pemail."\n".$preason); // $subject = JText::translate('VBCANCREQUESTEMAILSUBJ') . ' #' . $order[0]['id']; // @wponly we do not need to pass the "best item ID" to externalroute() $uri = VikBooking::externalroute("index.php?option=com_vikbooking&view=booking&sid=" . $order[0]['sid'] . "&ts=" . $order[0]['ts'], false); $msg = JText::sprintf('VBCANCREQUESTEMAILHEAD', $order[0]['id'], $uri)."\n\n".$preason; $vbo_app = VikBooking::getVboApplication(); $adsendermail = VikBooking::getSenderMail(); $vbo_app->sendMail($adsendermail, $adsendermail, $to, $pemail, $subject, $msg, false); $mainframe->enqueueMessage(JText::translate('VBCANCREQUESTMAILSENT')); $mainframe->redirect(JRoute::rewrite("index.php?option=com_vikbooking&view=booking&sid=".$order[0]['sid']."&ts=".$order[0]['ts']."&Itemid=".VikRequest::getString('Itemid', '', 'request'), false)); } else { $mainframe->redirect(JRoute::rewrite("index.php?option=com_vikbooking&view=booking&sid=".$order[0]['sid']."&ts=".$order[0]['ts'], false)); } } else { $mainframe->redirect("index.php"); } } else { $mainframe->redirect("index.php"); } } public function reqinfo() { $proomid = VikRequest::getInt('roomid', '', 'request'); $preqinfotoken = VikRequest::getInt('reqinfotoken', '', 'request'); $pitemid = VikRequest::getInt('Itemid', '', 'request'); $dbo = JFactory::getDBO(); $session = JFactory::getSession(); $mainframe = JFactory::getApplication(); $vbo_app = VikBooking::getVboApplication(); if (!empty($proomid)) { $q = "SELECT `id`,`name` FROM `#__vikbooking_rooms` WHERE `id`=".(int)$proomid.";"; $dbo->setQuery($q); $dbo->execute(); if ($dbo->getNumRows() == 1) { $room = $dbo->loadAssocList(); $goto = JRoute::rewrite('index.php?option=com_vikbooking&view=roomdetails&roomid='.$room[0]['id'].'&Itemid='.$pitemid, false); $preqname = VikRequest::getString('reqname', '', 'request'); $preqemail = VikRequest::getString('reqemail', '', 'request'); $preqmess = VikRequest::getString('reqmess', '', 'request'); if (!empty($preqemail) && !empty($preqmess)) { /** * captcha verification * * @since 1.2.3 */ if ($vbo_app->isCaptcha() && !$vbo_app->reCaptcha('check')) { VikError::raiseWarning('', 'Invalid Captcha'); $mainframe->redirect($goto); exit; } // $sesstoken = $session->get('vboreqinfo'.$room[0]['id'], ''); if((int)$sesstoken == (int)$preqinfotoken) { $session->set('vboreqinfo'.$room[0]['id'], ''); $to = VikBooking::getAdminMail(); if(strpos($to, ',') !== false) { $all_recipients = explode(',', $to); foreach ($all_recipients as $k => $v) { if(empty($v)) { unset($all_recipients[$k]); } } if(count($all_recipients) > 0) { $to = $all_recipients; } } $subject = JText::sprintf('VBOROOMREQINFOSUBJ', $room[0]['name']); $msg = JText::translate('VBOROOMREQINFONAME').": ".$preqname."\n\n".JText::translate('VBOROOMREQINFOEMAIL').": ".$preqemail."\n\n".JText::translate('VBOROOMREQINFOMESS').":\n\n".$preqmess; $adsendermail = VikBooking::getSenderMail(); $vbo_app->sendMail($adsendermail, $adsendermail, $to, $preqemail, $subject, $msg, false); $mainframe->enqueueMessage(JText::translate('VBOROOMREQINFOSENTOK')); } else { VikError::raiseWarning('', JText::translate('VBOROOMREQINFOTKNERR')); } $mainframe->redirect($goto); } else { VikError::raiseWarning('', JText::translate('VBOROOMREQINFOMISSFIELD')); $mainframe->redirect($goto); } } else { $mainframe->redirect("index.php"); } } else { $mainframe->redirect("index.php"); } } public function cron_exec() { if (VBOPlatformDetection::isWordPress()) { // in WordPress it is no more needed to schedule a server cron job VBOHttpDocument::getInstance()->close(406, 'Cron jobs execution is scheduled by WordPress since VikBooking 1.5.10. Please remove any scheduled execution to this end-point.'); } $app = JFactory::getApplication(); $id_cron = $app->input->getUint('cron_id', 0); $key = $app->input->getString('cronkey', ''); $model = VBOMvcModel::getInstance('cronjob'); // dispatch the cron job by injecting the cron key within the // configuration array, in order to make sure that the execution // of the job has been requested by a reliable caller $response = $model->dispatch($id_cron, ['key' => $key]); if ($response === false) { // an error has occurred $error = $model->getError(); if (!$error instanceof Exception) { // wrap error message in an exception for a better ease of use $error = new Exception($error ?: 'Error', 500); } // terminate session with an error VBOHttpDocument::getInstance($app)->close($error->getCode(), $error->getMessage()); } // display response code and teminate the session echo $response; $app->close(); } public function notifypayment() { $app = JFactory::getApplication(); $dbo = JFactory::getDbo(); $session = JFactory::getSession(); $config = VBOFactory::getConfig(); $av_helper = VikBooking::getAvailabilityInstance(); $psid = VikRequest::getString('sid', '', 'request'); $pts = VikRequest::getString('ts', '', 'request'); $nowdf = VikBooking::getDateFormat(); if ($nowdf == "%d/%m/%Y") { $df = 'd/m/Y'; } elseif ($nowdf == "%m/%d/%Y") { $df = 'm/d/Y'; } else { $df = 'Y/m/d'; } if (!strlen($psid) || !strlen($pts)) { VBOHttpDocument::getInstance()->close(500, 'Missing information for fetching the booking'); } $admail = VikBooking::getAdminMail(); $recipient_mail = $admail; if (!is_array($recipient_mail) && strpos($recipient_mail, ',') !== false) { $all_recipients = explode(',', $recipient_mail); foreach ($all_recipients as $k => $v) { if (empty($v)) { unset($all_recipients[$k]); } } if (count($all_recipients) > 0) { $recipient_mail = $all_recipients; } } // load booking details $q = "SELECT * FROM `#__vikbooking_orders` WHERE (`sid`=" . $dbo->quote($psid) . " OR `idorderota`=" . $dbo->quote($psid) . ") AND `ts`=" . $dbo->quote($pts); $dbo->setQuery($q, 0, 1); $row = $dbo->loadAssoc(); if (!$row) { VBOHttpDocument::getInstance()->close(404, 'Booking not found'); } // check if the language in use is the same as the one used during the checkout if (!empty($row['lang'])) { $lang = JFactory::getLanguage(); if ($lang->getTag() != $row['lang']) { $lang->load('com_vikbooking', (VBOPlatformDetection::isWordPress() ? VIKBOOKING_SITE_LANG : JPATH_SITE), $row['lang'], true); if (VBOPlatformDetection::isJoomla()) { $lang->load('joomla', JPATH_SITE, $row['lang'], true); } } } // translator $vbo_tn = VikBooking::getTranslator(); if ($row['status'] == 'confirmed' && !(VikBooking::multiplePayments() && $row['paymcount'] > 0)) { // booking can be paid only if not confirmed or if multiple payments are enabled and payment counter for booking greater than zero VBOHttpDocument::getInstance()->close(409, 'Conflicting and unexpected payment validation for this reservation'); } /** * Check split stay reservation data. * * @since 1.16.0 (J) - 1.6.0 (WP) */ $split_stay = []; if ($row['split_stay'] && $row['status'] != 'confirmed') { // check for transient on DB $split_stay = $config->getArray('split_stay_' . $row['id'], []); } // inject admin email $row['admin_email'] = $admail; // turnover seconds $turnover_secs = VikBooking::getHoursRoomAvail() * 3600; $realback = $turnover_secs + $row['checkout']; $currencyname = VikBooking::getCurrencyName(); $ftitle = VikBooking::getFrontTitle(); $nowts = time(); $rooms = []; $tars = []; $arrpeople = []; $is_package = (bool)(!empty($row['pkg'])); // load booked rooms $q = "SELECT `or`.`id` AS `or_id`,`or`.`idroom`,`or`.`adults`,`or`.`children`,`or`.`idtar`,`or`.`optionals`,`or`.`roomindex`,`or`.`pkg_id`,`or`.`pkg_name`,`or`.`cust_cost`,`or`.`cust_idiva`,`or`.`extracosts`,`or`.`otarplan`,`r`.`id` AS `r_reference_id`,`r`.`name`,`r`.`img`,`r`.`idcarat`,`r`.`fromadult`,`r`.`toadult`,`r`.`params` FROM `#__vikbooking_ordersrooms` AS `or`,`#__vikbooking_rooms` AS `r` WHERE `or`.`idorder`=" . $row['id'] . " AND `or`.`idroom`=`r`.`id` ORDER BY `or`.`id` ASC;"; $dbo->setQuery($q); $orderrooms = $dbo->loadAssocList(); if ($orderrooms) { $vbo_tn->translateContents($orderrooms, '#__vikbooking_rooms', array('id' => 'r_reference_id')); foreach ($orderrooms as $kor => $or) { $num = $kor + 1; $rooms[$num] = $or; $arrpeople[$num]['adults'] = $or['adults']; $arrpeople[$num]['children'] = $or['children']; if ($is_package === true || (!empty($or['cust_cost']) && $or['cust_cost'] > 0.00)) { // package or custom cost set from the back-end continue; } // determine the number of nights of stay and dates to consider $use_los = $row['days']; $room_checkin = $row['checkin']; $room_checkout = $row['checkout']; if (!empty($split_stay) && !empty($split_stay[$kor]) && $split_stay[$kor]['idroom'] == $or['idroom']) { $use_los = (int)$split_stay[$kor]['nights']; $room_checkin = $split_stay[$kor]['checkin_ts']; $room_checkout = $split_stay[$kor]['checkout_ts']; } $q = "SELECT * FROM `#__vikbooking_dispcost` WHERE `id`=" . (int)$or['idtar']; $dbo->setQuery($q, 0, 1); $tar = $dbo->loadAssocList(); if (!$tar) { continue; } $tar = VikBooking::applySeasonsRoom($tar, $room_checkin, $room_checkout); // apply OBP rules $tar = VBORoomHelper::getInstance()->applyOBPRules($tar, $or, $or['adults']); // push tariff $tars[$num] = $tar[0]; } } // inject values $row['order_rooms'] = $orderrooms; $row['fares'] = $tars; // invoke the payment method class $exppay = explode('=', ($row['idpayment'] ?? '')); $payment = VikBooking::getPayment($exppay[0], $vbo_tn); /** * Scan the booking and related rooms for damage deposit payment data. * * @since 1.17.6 (J) - 1.7.6 (WP) */ $damage_deposit_payment = VBORoomHelper::getInstance()->getDamageDepositSplitPayment($row, $orderrooms); if ($app->input->getBool('dd') && !empty($damage_deposit_payment['payment_window']['pay_id'])) { // load the proper payment driver $payment = VikBooking::getPayment($damage_deposit_payment['payment_window']['pay_id']) ?: $payment; } if (!$payment) { VBOHttpDocument::getInstance()->close(500, 'Could not load payment processor for validation.'); } // calculate booking totals $isdue = 0; $tot_taxes = 0; $tot_city_taxes = 0; $tot_fees = 0; $tot_damage_dep = 0; $pricestr = []; $optstr = []; foreach ($orderrooms as $kor => $or) { $num = $kor + 1; // determine the number of nights of stay and dates to consider $use_los = $row['days']; $room_checkin = $row['checkin']; $room_checkout = $row['checkout']; if (!empty($split_stay) && !empty($split_stay[$kor]) && $split_stay[$kor]['idroom'] == $or['idroom']) { $use_los = (int)$split_stay[$kor]['nights']; $room_checkin = $split_stay[$kor]['checkin_ts']; $room_checkout = $split_stay[$kor]['checkout_ts']; } if ($is_package === true || (!empty($or['cust_cost']) && $or['cust_cost'] > 0.00)) { // package cost or cust_cost may not be inclusive of taxes if prices tax included is off $calctar = VikBooking::sayPackagePlusIva($or['cust_cost'], $or['cust_idiva']); $isdue += $calctar; if ($calctar == $or['cust_cost']) { $cost_minus_tax = VikBooking::sayPackageMinusIva($or['cust_cost'], $or['cust_idiva']); $tot_taxes += ($or['cust_cost'] - $cost_minus_tax); } else { $tot_taxes += ($calctar - $or['cust_cost']); } $pricestr[$num] = (!empty($or['pkg_name']) ? $or['pkg_name'] : (!empty($or['otarplan']) ? ucwords($or['otarplan']) : JText::translate('VBOROOMCUSTRATEPLAN'))).": ".$calctar." ".$currencyname; } elseif (array_key_exists($num, $tars) && is_array($tars[$num])) { $calctar = VikBooking::sayCostPlusIva($tars[$num]['cost'], $tars[$num]['idprice']); $tars[$num]['calctar'] = $calctar; $isdue += $calctar; if ($calctar == $tars[$num]['cost']) { $cost_minus_tax = VikBooking::sayCostMinusIva($tars[$num]['cost'], $tars[$num]['idprice']); $tot_taxes += ($tars[$num]['cost'] - $cost_minus_tax); } else { $tot_taxes += ($calctar - $tars[$num]['cost']); } $pricestr[$num] = VikBooking::getPriceName($tars[$num]['idprice'], $vbo_tn) . ": " . $calctar . " " . $currencyname . (!empty($tars[$num]['attrdata']) ? "\n" . VikBooking::getPriceAttr($tars[$num]['idprice'], $vbo_tn) . ": " . $tars[$num]['attrdata'] : ""); } if (!empty($or['optionals'])) { $stepo = explode(";", $or['optionals']); foreach ($stepo as $roptkey => $oo) { if (empty($oo)) { continue; } $stept = explode(":", $oo); $q = "SELECT * FROM `#__vikbooking_optionals` WHERE `id`=" . $dbo->quote($stept[0]) . ";"; $dbo->setQuery($q); $actopt = $dbo->loadAssocList(); if ($actopt) { $vbo_tn->translateContents($actopt, '#__vikbooking_optionals'); // option params $opt_params = !empty($actopt[0]['oparams']) ? json_decode($actopt[0]['oparams'], true) : []; $opt_params = is_array($opt_params) ? $opt_params : []; $chvar = ''; if (!empty($actopt[0]['ageintervals']) && $or['children'] > 0 && strstr($stept[1], '-') != false) { $optagenames = VikBooking::getOptionIntervalsAges($actopt[0]['ageintervals']); $optagepcent = VikBooking::getOptionIntervalsPercentage($actopt[0]['ageintervals']); $optageovrct = VikBooking::getOptionIntervalChildOverrides($actopt[0], $or['adults'], $or['children']); $child_num = VikBooking::getRoomOptionChildNumber($or['optionals'], $actopt[0]['id'], $roptkey, $or['children']); $optagecosts = VikBooking::getOptionIntervalsCosts(isset($optageovrct['ageintervals_child' . ($child_num + 1)]) ? $optageovrct['ageintervals_child' . ($child_num + 1)] : $actopt[0]['ageintervals']); $agestept = explode('-', $stept[1]); $stept[1] = $agestept[0]; $chvar = $agestept[1]; if (array_key_exists(($chvar - 1), $optagepcent) && $optagepcent[($chvar - 1)] == 1) { //percentage value of the adults tariff if ($is_package === true || (!empty($or['cust_cost']) && $or['cust_cost'] > 0.00)) { $optagecosts[($chvar - 1)] = $or['cust_cost'] * $optagecosts[($chvar - 1)] / 100; } else { $optagecosts[($chvar - 1)] = $tars[$num]['cost'] * $optagecosts[($chvar - 1)] / 100; } } elseif (array_key_exists(($chvar - 1), $optagepcent) && $optagepcent[($chvar - 1)] == 2) { //VBO 1.10 - percentage value of room base cost if ($is_package === true || (!empty($or['cust_cost']) && $or['cust_cost'] > 0.00)) { $optagecosts[($chvar - 1)] = $or['cust_cost'] * $optagecosts[($chvar - 1)] / 100; } else { $display_rate = isset($tars[$num]['room_base_cost']) ? $tars[$num]['room_base_cost'] : $tars[$num]['cost']; $optagecosts[($chvar - 1)] = $display_rate * $optagecosts[($chvar - 1)] / 100; } } $actopt[0]['chageintv'] = $chvar; $actopt[0]['name'] .= ' ('.$optagenames[($chvar - 1)].')'; $actopt[0]['quan'] = $stept[1]; $realcost = (intval($actopt[0]['perday']) == 1 ? (floatval($optagecosts[($chvar - 1)]) * $use_los * $stept[1]) : (floatval($optagecosts[($chvar - 1)]) * $stept[1])); } else { $actopt[0]['quan'] = $stept[1]; // VBO 1.11 - options percentage cost of the room total fee if ($is_package === true || (!empty($or['cust_cost']) && $or['cust_cost'] > 0.00)) { $deftar_basecosts = $or['cust_cost']; } else { $deftar_basecosts = $tars[$num]['cost']; } $actopt[0]['cost'] = (int)$actopt[0]['pcentroom'] ? ($deftar_basecosts * $actopt[0]['cost'] / 100) : $actopt[0]['cost']; // $realcost = (intval($actopt[0]['perday']) == 1 ? ($actopt[0]['cost'] * $use_los * $stept[1]) : ($actopt[0]['cost'] * $stept[1])); } if (!empty($actopt[0]['maxprice']) && $actopt[0]['maxprice'] > 0 && $realcost > $actopt[0]['maxprice']) { $realcost = $actopt[0]['maxprice']; if (intval($actopt[0]['hmany']) == 1 && intval($stept[1]) > 1) { $realcost = $actopt[0]['maxprice'] * $stept[1]; } } if ($actopt[0]['perperson'] == 1) { $realcost = $realcost * $or['adults']; } /** * Trigger event to allow third party plugins to apply a custom calculation for the option/extra fee or tax. * * @since 1.17.7 (J) - 1.7.7 (WP) */ $custom_calculation = VBOFactory::getPlatform()->getDispatcher()->filter('onCalculateBookingOptionFeeCost', [$realcost, &$actopt[0], $row, $or]); if ($custom_calculation) { $realcost = (float) $custom_calculation[0]; } $opt_minus_tax = VikBooking::sayOptionalsMinusIva($realcost, $actopt[0]['idiva']); $tmpopr = VikBooking::sayOptionalsPlusIva($realcost, $actopt[0]['idiva']); if ($actopt[0]['is_citytax'] == 1) { $tot_city_taxes += $opt_minus_tax; } elseif ($actopt[0]['is_fee'] == 1) { $tot_fees += $opt_minus_tax; } elseif ($opt_params['damagedep'] ?? 0) { $tot_damage_dep += $opt_minus_tax; } // always calculate the amount of tax no matter if this is already a tax or a fee if ($tmpopr == $realcost) { $tot_taxes += ($realcost - $opt_minus_tax); } else { $tot_taxes += ($tmpopr - $realcost); } // $isdue += $tmpopr; $optstr[$num][] = ($stept[1] > 1 ? $stept[1] . " " : "") . $actopt[0]['name'] . ": " . $tmpopr . " " . $currencyname . "\n"; } } } // custom extra costs if (!empty($or['extracosts'])) { $cur_extra_costs = json_decode($or['extracosts'], true); foreach ($cur_extra_costs as $eck => $ecv) { $ecplustax = !empty($ecv['idtax']) ? VikBooking::sayOptionalsPlusIva($ecv['cost'], $ecv['idtax']) : $ecv['cost']; $isdue += $ecplustax; $optstr[$num][] = $ecv['name'] . ": " . $ecplustax . " " . $currencyname."\n"; } } } // coupon $usedcoupon = false; $origisdue = $isdue; if (strlen($row['coupon']) > 0) { $usedcoupon = true; $expcoupon = explode(";", $row['coupon']); $isdue = $isdue - $expcoupon[1]; } if (empty($row['sid']) && !empty($row['idorderota']) && !empty($row['channel'])) { $row['sid'] = $row['idorderota']; } if (VBOPlatformDetection::isWordPress()) { /** * @wponly The payment gateway is now loaded * using the apposite dispatcher. * * @since 1.0.5 */ JLoader::import('adapter.payment.dispatcher'); $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']; $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']; $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"; $model = JModel::getInstance('vikbooking', 'shortcodes', 'admin'); $itemid = $model->best(array('booking'), (!empty($row['lang']) ? $row['lang'] : null)); $extra_data = []; if ($itemid) { $return_url = str_replace(JUri::root(), '', $return_url); $error_url = str_replace(JUri::root(), '', $error_url); $notify_url = str_replace(JUri::root(), '', $notify_url); $return_url = JRoute::rewrite($return_url . "&Itemid={$itemid}", false); $error_url = JRoute::rewrite($error_url . "&Itemid={$itemid}", false); $notify_url = JRoute::rewrite($notify_url . "&Itemid={$itemid}", false); $extra_data = array( 'return_url' => $return_url, 'error_url' => $error_url, 'notify_url' => $notify_url, ); } $extra_data['transaction_currency'] = VikBooking::getCurrencyCodePp(); $obj = JPaymentDispatcher::getInstance('vikbooking', $payment['file'], array_merge($row, $extra_data), $payment['params']); } else { /** * @joomlaonly The Payment Factory library will invoke the gateway. * Make sure to pass the payment gateway some common variables together with the order record. * * @since 1.14.3 */ require_once VBO_ADMIN_PATH . DIRECTORY_SEPARATOR . 'payments' . DIRECTORY_SEPARATOR . 'libraries' . DIRECTORY_SEPARATOR . 'factory.php'; $bestitemid = VikBooking::findProperItemIdType(array('booking')); $extra_data = array( '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)), '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)), '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), ); $extra_data['transaction_currency'] = VikBooking::getCurrencyCodePp(); $obj = VBOPaymentFactory::getPaymentInstance($payment['file'], array_merge($row, $extra_data), $payment['params']); } $array_result = $obj->validatePayment(); $newpaymentlog = date('c')."\n".$array_result['log']."\n----------\n".$row['paymentlog']; /** * OTA reservations containing PCI-DSS card details may receive additional payments through * the website for upselling or for payments requested. Therefore, the previous card logs * should be appended, not prepended to the current payment logs for the card details. * * @since 1.15.0 (J) - 1.5.0 (WP) */ if (!empty($row['idorderota']) && !empty($row['channel']) && !empty($row['paymentlog'])) { if (stripos($row['paymentlog'], 'card number') !== false && strpos($row['paymentlog'], '*') !== false) { $newpaymentlog = $row['paymentlog'] . "\n----------\n" . date('c') . "\n" . $array_result['log']; } } /** * Ensure the size of the log does not make the query fail. * * @since 1.16.9 (J) - 1.6.9 (WP) */ if (strlen($newpaymentlog) > 65000) { $newpaymentlog = substr($newpaymentlog, 0, 65000) . '...'; } if ($array_result['verified'] == 1) { // valid payment $shouldpay = $isdue; if ($payment['charge'] > 0.00) { if ($payment['ch_disc'] == 1) { // charge if ($payment['val_pcent'] == 1) { // fixed value $shouldpay += $payment['charge']; } else { // percent value $percent_to_pay = $shouldpay * $payment['charge'] / 100; $shouldpay += $percent_to_pay; } } else { // discount if ($payment['val_pcent'] == 1) { // fixed value $shouldpay -= $payment['charge']; } else { // percent value $percent_to_pay = $shouldpay * $payment['charge'] / 100; $shouldpay -= $percent_to_pay; } } } // deposit may be skipped by customer choice $shouldpay_befdep = $shouldpay; if (!VikBooking::payTotal()) { $percentdeposit = VikBooking::getAccPerCent(); if ($percentdeposit > 0) { if (VikBooking::getTypeDeposit() == "fixed") { $shouldpay = $percentdeposit; } else { $shouldpay = $shouldpay * $percentdeposit / 100; } } } // check if a damage deposit was allowed to be paid $shouldpay_dd = $damage_deposit_payment['damagedep_gross'] ?? 0; $shouldpay_befdd = $shouldpay - $shouldpay_dd; // check if the total amount paid is the same as the order total if (isset($array_result['tot_paid'])) { $shouldpay = round($shouldpay, 2); $shouldpay_befdep = round($shouldpay_befdep, 2); $totreceived = round($array_result['tot_paid'], 2); if ($shouldpay != $totreceived && $shouldpay_befdep != $totreceived && $shouldpay_befdd != $totreceived && $shouldpay_dd != $totreceived && $row['paymcount'] == 0) { // the amount paid is different than the order total // fares might have changed or the deposit might be different // Sending just an email to the admin that will check $vbo_app = VikBooking::getVboApplication(); $adsendermail = VikBooking::getSenderMail(); $vbo_app->sendMail($adsendermail, $adsendermail, $recipient_mail, $adsendermail, JText::translate('VBTOTPAYMENTINVALID'), JText::sprintf('VBTOTPAYMENTINVALIDTXT', $row['id'], $totreceived." (".$array_result['tot_paid'].")", $shouldpay), false); } // amount paid should be stored as exclusive of transaction fees/discounts if ($payment['charge'] > 0.00) { if ($payment['ch_disc'] == 1) { // charge if ($payment['val_pcent'] == 1) { // fixed value $array_result['tot_paid'] -= $payment['charge']; } else { // percent value $array_result['tot_paid'] = ($array_result['tot_paid'] / ((100 + $payment['charge']) / 100)); } } else { // discount if ($payment['val_pcent'] == 1) { // fixed value $array_result['tot_paid'] += $payment['charge']; } else { // percent value $array_result['tot_paid'] = $array_result['tot_paid'] * (100 + $payment['charge']) / 100; } } $array_result['tot_paid'] = round($array_result['tot_paid'], 2); } } if ($row['paymcount'] == 0 || $row['status'] == 'standby') { foreach ($orderrooms as $indnum => $r) { $num = $indnum + 1; // determine the number of nights of stay and dates to consider $room_checkin = $row['checkin']; $room_checkout = $row['checkout']; $room_realback = $turnover_secs + $row['checkout']; if (!empty($split_stay) && !empty($split_stay[$indnum]) && $split_stay[$indnum]['idroom'] == $r['idroom']) { $room_checkin = $split_stay[$indnum]['checkin_ts']; $room_checkout = $split_stay[$indnum]['checkout_ts']; $room_realback = $turnover_secs + $split_stay[$indnum]['checkout_ts']; } $busy_record = new stdClass; $busy_record->idroom = $r['idroom']; $busy_record->checkin = $room_checkin; $busy_record->checkout = $room_checkout; $busy_record->realback = $room_realback; $dbo->insertObject('#__vikbooking_busy', $busy_record, 'id'); if (!isset($busy_record->id)) { continue; } $q = "INSERT INTO `#__vikbooking_ordersbusy` (`idorder`,`idbusy`) VALUES(" . (int)$row['id'] . ", " . (int)$busy_record->id . ");"; $dbo->setQuery($q); $dbo->execute(); } } // ConfirmationNumber if ($row['paymcount'] == 0 || $row['status'] == 'standby') { $confirmnumber = VikBooking::generateConfirmNumber($row['id'], true); } // update payable amount in case of up-sells or simply in case of another payment received $new_payable = isset($array_result['tot_paid']) && $array_result['tot_paid'] ? ($row['payable'] - $array_result['tot_paid']) : 0; $new_payable = $new_payable < 0 ? 0 : $new_payable; // update booking record $booking_record = new stdClass; $booking_record->id = $row['id']; $booking_record->status = 'confirmed'; if (isset($array_result['tot_paid']) && $array_result['tot_paid']) { $booking_record->totpaid = ($array_result['tot_paid'] + $row['totpaid']); } $booking_record->paymcount = ($row['paymcount'] + 1); if (!empty($array_result['log'])) { $booking_record->paymentlog = $newpaymentlog; } $booking_record->payable = $new_payable; $dbo->updateObject('#__vikbooking_orders', $booking_record, 'id'); // assign room specific unit $set_room_indexes = VikBooking::autoRoomUnit(); $room_indexes_usemap = []; if ($set_room_indexes === true) { $q = "SELECT `id`,`idroom`,`roomindex` FROM `#__vikbooking_ordersrooms` WHERE `idorder`=".(int)$row['id'].";"; $dbo->setQuery($q); $orooms = $dbo->loadAssocList(); foreach ($orooms as $oroom) { if (!empty($oroom['roomindex'])) { // room specific unit has already been assigned continue; } $room_indexes = VikBooking::getRoomUnitNumsAvailable($row, $oroom['idroom']); $use_ind_key = 0; if ($room_indexes) { if (!array_key_exists($oroom['idroom'], $room_indexes_usemap)) { $room_indexes_usemap[$oroom['idroom']] = $use_ind_key; } else { $use_ind_key = $room_indexes_usemap[$oroom['idroom']]; } $q = "UPDATE `#__vikbooking_ordersrooms` SET `roomindex`=".(int)$room_indexes[$use_ind_key]." WHERE `id`=".(int)$oroom['id'].";"; $dbo->setQuery($q); $dbo->execute(); // update rooms references for the customer email sending function foreach ($rooms as $rnum => $rr) { if ($rr['or_id'] == $oroom['id']) { $rooms[$rnum]['roomindex'] = (int)$room_indexes[$use_ind_key]; break; } } $room_indexes_usemap[$oroom['idroom']]++; } } } // unlock room(s) for other imminent bookings $q = "DELETE FROM `#__vikbooking_tmplock` WHERE `idorder`=" . intval($row['id']) . ";"; $dbo->setQuery($q); $dbo->execute(); // customer booking $q = "SELECT `idcustomer` FROM `#__vikbooking_customers_orders` WHERE `idorder`=".(int)$row['id'].";"; $dbo->setQuery($q); $customer_id = $dbo->loadResult(); if ($customer_id) { $cpin = VikBooking::getCPinIstance(); $cpin->updateBookingCommissions($row['id'], $customer_id); } // check if some of the rooms booked have shared calendars VikBooking::updateSharedCalendars($row['id'], array(), $row['checkin'], $row['checkout']); /** * Trigger event to allow third-party plugins to choose whether payment notifications should be sent * * @since 1.16.10 (J) - 1.6.10 (WP) */ $send_notifications = true; $should_send = VBOFactory::getPlatform()->getDispatcher()->filter('onPaymentReceivedShouldSendNotifications', [$row]); if (is_array($should_send) && in_array(false, $should_send, true)) { $send_notifications = false; } if ($send_notifications) { // send email notification to guest and admin VikBooking::sendBookingEmail($row['id'], array('guest', 'admin')); // SMS VikBooking::sendBookingSMS($row['id']); } /** * Payment gateways may set and return the transaction information * to eventually support a later transaction of type refund. * * @since 1.14 (J) - 1.4.0 (WP) * @since 1.16.2 (J) - 1.6.2 (WP) we attempt to always store the amount paid with this transaction. * @since 1.16.9 (J) - 1.6.9 (WP) we attempt to store the amount of payment processing fees for the transaction. * @since 1.17.6 (J) - 1.7.6 (WP) added support to separate payment for damage deposit. */ $tn_data = $array_result['transaction'] ?? null; if (isset($array_result['tot_paid']) && $array_result['tot_paid']) { // check event data payload to store if (is_array($tn_data)) { // set key $tn_data['amount_paid'] = (float) $array_result['tot_paid']; } elseif (is_object($tn_data)) { // set property $tn_data->amount_paid = (float) $array_result['tot_paid']; } elseif (!$tn_data) { // build an array (we add the payment name because we know there is no other transaction data) $tn_data = [ 'amount_paid' => (float) $array_result['tot_paid'], 'payment_method' => $payment['name'], ]; } } if ($tn_data && isset($array_result['tot_fees']) && $array_result['tot_fees']) { // check event data payload to store if (is_array($tn_data)) { // set key $tn_data['processing_fees'] = (float) $array_result['tot_fees']; } elseif (is_object($tn_data)) { // set property $tn_data->processing_fees = (float) $array_result['tot_fees']; } } if ($app->input->getBool('dd') && ($damage_deposit_payment['damagedep_gross'] ?? null)) { if ($tn_data) { $tn_data = (array) $tn_data; } else { $tn_data = []; } // damage deposit may be authorized without an amount paid returned $tn_data['damage_deposit'] = (float) $damage_deposit_payment['damagedep_gross']; } // Booking History VikBooking::getBookingHistoryInstance()->setBid($row['id'])->setExtraData($tn_data)->store('P' . ($row['paymcount'] > 0 ? 'N' : '0'), $payment['name']); // invoke VikChannelManager if (is_file(VCM_SITE_PATH . DIRECTORY_SEPARATOR . "helpers" . DIRECTORY_SEPARATOR . "synch.vikbooking.php")) { require_once(VCM_SITE_PATH . DIRECTORY_SEPARATOR . "helpers" . DIRECTORY_SEPARATOR . "synch.vikbooking.php"); $vcm = new SynchVikBooking($row['id']); $vcm->setPushType('new')->sendRequest(); } $vcmchanneldata = $session->get('vcmChannelData', ''); if (!empty($vcmchanneldata)) { $session->set('vcmChannelData', ''); } //end invoke VikChannelManager if (method_exists($obj, 'afterValidation')) { $obj->afterValidation(1); } } else { if (empty($array_result['skip_email'])) { $vbo_app = VikBooking::getVboApplication(); $adsendermail = VikBooking::getSenderMail(); $vbo_app->sendMail($adsendermail, $adsendermail, $recipient_mail, $adsendermail, JText::translate('VBPAYMENTNOTVER'), JText::translate('VBSERVRESP') . ":\n\n" . $array_result['log'], false); } if (!empty($array_result['log'])) { $q = "UPDATE `#__vikbooking_orders` SET `paymentlog`=".$dbo->quote($newpaymentlog)." WHERE `id`='" . $row['id'] . "';"; $dbo->setQuery($q); $dbo->execute(); } if (method_exists($obj, 'afterValidation')) { $obj->afterValidation(0); } } } public function currencyconverter() { $session = JFactory::getSession(); $pprices = VikRequest::getVar('prices', array(0)); $pfromsymbol = VikRequest::getString('fromsymbol', '', 'request'); $ptocurrency = VikRequest::getString('tocurrency', '', 'request'); $pfromcurrency = VikRequest::getString('fromcurrency', '', 'request'); $default_cur = !empty($pfromcurrency) ? $pfromcurrency : VikBooking::getCurrencyName(); $response = array(); if (!empty($default_cur) && !empty($pprices) && count($pprices) > 0 && !empty($ptocurrency)) { require_once(VBO_SITE_PATH . DS . "helpers" . DS ."currencyconverter.php"); if ($default_cur != $ptocurrency) { $format = VikBooking::getNumberFormatData(); $converter = new VboCurrencyConverter($default_cur, $ptocurrency, $pprices, explode(':', $format)); $exchanged = $converter->convert(); if (count($exchanged) > 0) { $response = $exchanged; $session->set('vboLastCurrency', $ptocurrency); } else { $conv_error = $converter->getError(); $response['error'] = !empty($conv_error) ? $conv_error : JText::translate('VBERRCURCONVINVALIDDATA'); } } else { $session->set('vboLastCurrency', $ptocurrency); foreach ($pprices as $i => $price) { $response[$i]['symbol'] = $pfromsymbol; $response[$i]['price'] = $price; } } } else { $response['error'] = JText::translate('VBERRCURCONVNODATA'); } if(array_key_exists('error', $response)) { $session->set('vboLastCurrency', $ptocurrency); } echo json_encode($response); exit; } public function signature() { VikRequest::setVar('view', 'signature'); parent::display(); } public function storesignature() { if (!JSession::checkToken()) { // missing CSRF-proof token VBOHttpDocument::getInstance()->close(403, JText::translate('JINVALID_TOKEN')); } $sid = VikRequest::getString('sid', '', 'request'); $ts = VikRequest::getString('ts', '', 'request'); $psignature = VikRequest::getString('signature', '', 'request', VIKREQUEST_ALLOWRAW); $ppad_width = VikRequest::getInt('pad_width', '', 'request'); $ppad_ratio = VikRequest::getInt('pad_ratio', '', 'request'); $pitemid = VikRequest::getInt('Itemid', '', 'request'); $ptmpl = VikRequest::getString('tmpl', '', 'request'); $dbo = JFactory::getDBO(); $mainframe = JFactory::getApplication(); $q = "SELECT * FROM `#__vikbooking_orders` WHERE `ts`=" . $dbo->quote($ts) . " AND `sid`=" . $dbo->quote($sid) . " AND `status`='confirmed';"; $dbo->setQuery($q); $dbo->execute(); if ($dbo->getNumRows() < 1) { VikError::raiseWarning('', 'Booking not found'); $mainframe->redirect('index.php'); exit; } $row = $dbo->loadAssoc(); $tonight = mktime(23, 59, 59, date('n'), date('j'), date('Y')); if ($tonight > $row['checkout']) { VikError::raiseWarning('', 'Check-out date is in the past'); $mainframe->redirect('index.php'); exit; } $customer = array(); $q = "SELECT `c`.*,`co`.`idorder`,`co`.`signature`,`co`.`pax_data`,`co`.`comments` FROM `#__vikbooking_customers` AS `c` LEFT JOIN `#__vikbooking_customers_orders` `co` ON `c`.`id`=`co`.`idcustomer` WHERE `co`.`idorder`=".(int)$row['id'].";"; $dbo->setQuery($q); $dbo->execute(); if ($dbo->getNumRows() > 0) { $customer = $dbo->loadAssoc(); } if (!(count($customer) > 0)) { VikError::raiseWarning('', 'Customer not found'); $mainframe->redirect('index.php'); exit; } //check if the signature has been submitted $signature_data = ''; $cont_type = ''; if (!empty($psignature)) { /** * Implemented safe filtering of base64-encoded signature image * to obtain content and file extension. * * @since 1.15.1 (J) - 1.5.4 (WP) */ if (preg_match("/^data:image\/(png|jpe?g|svg);base64,([A-Za-z0-9\/=+]+)$/", $psignature, $safe_match)) { $signature_data = base64_decode($safe_match[2]); $cont_type = $safe_match[1]; } } $ret_link = JRoute::rewrite('index.php?option=com_vikbooking&task=signature&sid='.$row['sid'].'&ts='.$row['ts'].(!empty($pitemid) ? '&Itemid='.$pitemid : '').($ptmpl == 'component' ? '&tmpl=component' : ''), false); if (empty($signature_data)) { VikError::raiseWarning('', JText::translate('VBOSIGNATUREISEMPTY')); $mainframe->redirect($ret_link); exit; } //write file $sign_fname = $row['id'].'_'.$row['sid'].'_'.$customer['id'].'.'.$cont_type; $filepath = VBO_ADMIN_PATH . DIRECTORY_SEPARATOR . 'resources' . DIRECTORY_SEPARATOR . 'idscans' . DIRECTORY_SEPARATOR . $sign_fname; $fp = fopen($filepath, 'w+'); $bytes = fwrite($fp, $signature_data); fclose($fp); if ($bytes !== false && $bytes > 0) { //update the signature in the DB $q = "UPDATE `#__vikbooking_customers_orders` SET `signature`=".$dbo->quote($sign_fname)." WHERE `idorder`=".(int)$row['id'].";"; $dbo->setQuery($q); $dbo->execute(); $mainframe->enqueueMessage(JText::translate('VBOSIGNATURETHANKS')); //resize image for screens with high resolution if ($ppad_ratio > 1) { $new_width = floor(($ppad_width / 2)); $creativik = new vikResizer(); $creativik->proportionalImage($filepath, $filepath, $new_width, $new_width); } // } else { VikError::raiseWarning('', JText::translate('VBOERRSTORESIGNFILE')); } $mainframe->redirect($ret_link); exit; } public function validatepin() { $cpin = VikBooking::getCPinIstance(); $ppin = VikRequest::getString('pin', '', 'request'); $response = []; $customer = $cpin->getCustomerByPin($ppin); if ($customer) { $response = $customer; $response['success'] = 1; if ($cpin->getCustomerCoupon($customer)) { // set flag indicating that the customer has got dedicated discounts $response['has_discounts'] = 1; } } echo json_encode($response); exit; } public function docancelbooking() { if (!JSession::checkToken()) { // missing CSRF-proof token VBOHttpDocument::getInstance()->close(403, JText::translate('JINVALID_TOKEN')); } $dbo = JFactory::getDbo(); $mainframe = JFactory::getApplication(); $psid = VikRequest::getString('sid', '', 'request'); $pidorder = VikRequest::getString('idorder', '', 'request'); if (!empty($psid) && !empty($pidorder)) { $q = "SELECT * FROM `#__vikbooking_orders` WHERE `id`=".intval($pidorder)." AND `sid`=".$dbo->quote($psid)." AND `status`='confirmed';"; $dbo->setQuery($q); $order = $dbo->loadAssocList(); if ($order) { $pemail = VikRequest::getString('email', '', 'request'); $preason = VikRequest::getString('reason', '', 'request'); if (!empty($pemail) && !empty($preason)) { $to = VikBooking::getAdminMail(); if (strpos($to, ',') !== false) { $all_recipients = explode(',', $to); foreach ($all_recipients as $k => $v) { if (empty($v)) { unset($all_recipients[$k]); } } if (count($all_recipients) > 0) { $to = $all_recipients; } } //check if the booking can be cancelled $days_to_arrival = 0; $is_refundable = 0; $daysadv_refund_arr = array(); $daysadv_refund = 0; $now_info = getdate(); $checkin_info = getdate($order[0]['checkin']); if ($now_info[0] < $checkin_info[0]) { while ($now_info[0] < $checkin_info[0]) { if (!($now_info['mday'] != $checkin_info['mday'] || $now_info['mon'] != $checkin_info['mon'] || $now_info['year'] != $checkin_info['year'])) { break; } $days_to_arrival++; $now_info = getdate(mktime(0, 0, 0, $now_info['mon'], ($now_info['mday'] + 1), $now_info['year'])); } } $tars = array(); $is_package = !empty($order[0]['pkg']) ? true : false; $orderrooms = array(); $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;"; $dbo->setQuery($q); $orderrooms = $dbo->loadAssocList(); if ($orderrooms) { foreach($orderrooms as $kor => $or) { $num = $kor + 1; if($is_package === true || (!empty($or['cust_cost']) && $or['cust_cost'] > 0.00)) { //package or custom cost set from the back-end continue; } $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'] . "';"; $dbo->setQuery($q); $tar = $dbo->loadAssocList(); if ($tar) { $tars[$num] = $tar[0]; } } } foreach ($tars as $num => $tar) { if ($tar['free_cancellation'] < 1) { //if at least one rate plan is non-refundable, the whole reservation cannot be cancelled $is_refundable = 0; $daysadv_refund_arr = array(); break; } $is_refundable = 1; $daysadv_refund_arr[] = $tar['canc_deadline']; } //get the rate plan with the lowest cancellation deadline $daysadv_refund = count($daysadv_refund_arr) > 0 ? min($daysadv_refund_arr) : $daysadv_refund; $resmodcanc = VikBooking::getReservationModCanc(); $resmodcanc = $days_to_arrival < 1 ? 0 : $resmodcanc; $resmodcancmin = VikBooking::getReservationModCancMin(); $canc_allowed = ($resmodcanc > 1 && $resmodcanc != 2 && $is_refundable > 0 && $daysadv_refund <= $days_to_arrival && $days_to_arrival >= $resmodcancmin); if (!$canc_allowed) { VikError::raiseWarning('', JText::translate('VBOERRCANNOTCANCBOOK')); $mainframe->redirect(JRoute::rewrite("index.php?option=com_vikbooking&view=booking&sid=".$order[0]['sid']."&ts=".$order[0]['ts']."&Itemid=".VikRequest::getString('Itemid', '', 'request'), false)); exit; } //make the cancellation in the db and update the administrator notes with the reason specified by the customer $new_adminotes = JText::translate('VBOBOOKCANCELLEDEMAILSUBJ').' ('.$pemail.")\n".$preason."\n\n".$order[0]['adminnotes']; $q = "UPDATE `#__vikbooking_orders` SET `status`='cancelled',`adminnotes`=".$dbo->quote($new_adminotes)." WHERE `id`=".(int)$order[0]['id'].";"; $dbo->setQuery($q); $dbo->execute(); $q = "SELECT * FROM `#__vikbooking_ordersbusy` WHERE `idorder`=".(int)$order[0]['id'].";"; $dbo->setQuery($q); $ordbusy = $dbo->loadAssocList(); if ($ordbusy) { foreach ($ordbusy as $ob) { $q = "DELETE FROM `#__vikbooking_busy` WHERE `id`=".(int)$ob['idbusy'].";"; $dbo->setQuery($q); $dbo->execute(); } } $q = "DELETE FROM `#__vikbooking_ordersbusy` WHERE `idorder`=".(int)$order[0]['id'].";"; $dbo->setQuery($q); $dbo->execute(); if ($order[0]['split_stay']) { // attempt to remove the transient record VBOFactory::getConfig()->remove('split_stay_' . $order[0]['id']); } // Booking History $history_obj = VikBooking::getBookingHistoryInstance()->setBid($order[0]['id']); $history_obj->store('CW', $preason); /** * Check if the amount paid can be refunded. * * @since 1.14 (J) - 1.4.0 (WP) */ $admin_refund_error = ''; $currencysymb = VikBooking::getCurrencySymb(); $payment = VikBooking::getPayment($order[0]['idpayment']); $tn_driver = is_array($payment) ? $payment['file'] : null; // transaction data validation callback $tn_data_callback = function($data) use ($tn_driver) { return (is_object($data) && isset($data->driver) && basename($data->driver, '.php') == basename($tn_driver, '.php')); }; // get previous transactions $prev_tn_data = $history_obj->getEventsWithData(array('P0', 'PN'), $tn_data_callback); if (is_array($prev_tn_data) && count($prev_tn_data) && $order[0]['totpaid'] > 0) { // previous transactions found and total paid > 0 $refund_amount = $order[0]['totpaid']; // push refund information for the payment gateway $order[0]['total_to_refund'] = $refund_amount; $order[0]['transaction'] = $prev_tn_data; $order[0]['refund_reason'] = $preason; // push the transaction currency information $order[0]['transaction_currency'] = VikBooking::getCurrencyCodePp(); /** * @wponly The payment gateway is loaded * through the apposite dispatcher. */ JLoader::import('adapter.payment.dispatcher'); $obj = JPaymentDispatcher::getInstance('vikbooking', $payment['file'], $order[0], $payment['params']); // check if refund is supported by this gateway if (method_exists($obj, 'isRefundSupported') && $obj->isRefundSupported()) { // perform the refund transaction $array_result = $obj->refund(); if ($array_result['verified'] != 1) { // refund failed $admin_refund_error .= "\nRefund transaction failed\n"; // get the refund error message $admin_refund_error .= !empty($array_result['log']) && is_string($array_result['log']) ? $array_result['log'] : ''; } else { // refund was successful /** * The history event extra data will contain the "amount_paid" (refunded). * * @since 1.16.9 (J) - 1.6.9 (WP) */ if (!empty($array_result['tot_paid'])) { // overwrite the requested amount with the returned one $refund_amount = (float)$array_result['tot_paid']; } $history_obj->setExtraData([ 'amount_paid' => $refund_amount, ]); // update total paid, total and refund columns for the booking $booking = new stdClass; $booking->id = $order[0]['id']; if ($order[0]['totpaid'] > 0) { $booking->totpaid = (float)($order[0]['totpaid'] - $refund_amount); } if ($order[0]['total'] > 0) { $booking->total = (float)($order[0]['total'] - $refund_amount); } $booking->refund = (float)$order[0]['refund'] + $refund_amount; // update record in db $dbo->updateObject('#__vikbooking_orders', $booking, 'id'); // store the refund event $event_descr = [ '(' . $payment['name'] . ')', $currencysymb . ' ' . VikBooking::numberFormat($refund_amount), ]; $history_obj->store('RF', implode("\n", $event_descr)); } } } // invoke VikChannelManager if (is_file(VCM_SITE_PATH . DIRECTORY_SEPARATOR . "helpers" . DIRECTORY_SEPARATOR . "synch.vikbooking.php")) { $vcm_obj = VikBooking::getVcmInvoker(); $vcm_obj->setOids(array($order[0]['id']))->setSyncType('cancel'); $vcm_obj->doSync(); } // end invoke VikChannelManager //send email to the administrator $subject = JText::translate('VBOBOOKCANCELLEDEMAILSUBJ'); // @wponly we do not need to pass the "best item id" $uri = VikBooking::externalroute("index.php?option=com_vikbooking&view=booking&sid=" . $order[0]['sid'] . "&ts=" . $order[0]['ts'], false); $msg = JText::sprintf('VBOBOOKCANCELLEDEMAILHEAD', $order[0]['id'], $uri) . "\n\n" . $preason . $admin_refund_error; $vbo_app = VikBooking::getVboApplication(); $adsendermail = VikBooking::getSenderMail(); $vbo_app->sendMail($adsendermail, $adsendermail, $to, $pemail, $subject, $msg, false); // SMS VikBooking::sendBookingSMS($order[0]['id']); // send cancellation email notification to guest VikBooking::sendBookingEmail($order[0]['id'], ['guest']); // go back to the booking details page to show the new status $mainframe->enqueueMessage(JText::translate('VBOBOOKCANCELLEDRESP')); $mainframe->redirect(JRoute::rewrite("index.php?option=com_vikbooking&view=booking&sid=".$order[0]['sid']."&ts=".$order[0]['ts']."&Itemid=".VikRequest::getString('Itemid', '', 'request'), false)); } else { VikError::raiseWarning('', JText::translate('VBOERRMISSDATA')); $mainframe->redirect(JRoute::rewrite("index.php?option=com_vikbooking&view=booking&sid=".$order[0]['sid']."&ts=".$order[0]['ts']."&Itemid=".VikRequest::getString('Itemid', '', 'request'), false)); } } else { $mainframe->redirect("index.php"); } } else { $mainframe->redirect("index.php"); } } public function cancelmodification() { $psid = VikRequest::getString('sid', '', 'request'); $pidorder = VikRequest::getString('id', '', 'request'); $dbo = JFactory::getDBO(); $session = JFactory::getSession(); $mainframe = JFactory::getApplication(); if (!empty($psid) && !empty($pidorder)) { $q = "SELECT * FROM `#__vikbooking_orders` WHERE `id`=".intval($pidorder)." AND `sid`=".$dbo->quote($psid)." AND `status`='confirmed';"; $dbo->setQuery($q); $dbo->execute(); if ($dbo->getNumRows() == 1) { $order = $dbo->loadAssocList(); //unset the session value and redirect $session->set('vboModBooking', ''); $mainframe->redirect(JRoute::rewrite("index.php?option=com_vikbooking&view=booking&sid=".$order[0]['sid']."&ts=".$order[0]['ts'], false)); } else { $mainframe->redirect("index.php"); } } else { $mainframe->redirect("index.php"); } } public function tac_av_l() { require_once(VBO_SITE_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'tac.vikbooking.php'); //Channel Rates Module $pvbomodule = VikRequest::getInt('vbomodule', 0, 'request'); $pshow_tax = VikRequest::getInt('show_tax', 0, 'request'); $pdef_rplan = VikRequest::getInt('def_rplan', 0, 'request'); $pchannels_sel = VikRequest::getVar('channels_sel', array()); $pcheckin = VikRequest::getString('checkin', '', 'request'); $pcheckout = VikRequest::getString('checkout', '', 'request'); if ($pvbomodule > 0 && !empty($pcheckin) && !empty($pcheckout)) { //this is an ajax request, probably made by the module Vik Booking Channel Rates //we need to prepare some variables before calling the method. $start_date = date('Y-m-d', VikBooking::getDateTimestamp($pcheckin, 12, 0)); $end_date = date('Y-m-d', VikBooking::getDateTimestamp($pcheckout, 10, 0)); //set (only some) request variables (the rest is sent via Ajax) VikRequest::setVar('e4jauth', md5('vbo.e4j.vbo')); VikRequest::setVar('req_type', 'hotel_availability'); VikRequest::setVar('start_date', $start_date); VikRequest::setVar('end_date', $end_date); //make call to get the result TACVBO::$getArray = true; $website_rates = TACVBO::tac_av_l([ // always force adults and children to be injected as arguments to avoid request conflicts 'adults' => VikRequest::getVar('adults', array()), 'children' => VikRequest::getVar('children', array()) ?: [0], ]); //validate response if (!is_array($website_rates)) { //error returned echo json_encode(array('e4j.error' => $website_rates)); exit; } if (is_array($website_rates) && isset($website_rates['e4j.error'])) { //another type of error returned echo json_encode($website_rates); exit; } if (is_array($website_rates) && !(count($website_rates) > 0)) { //empty response echo json_encode(array('e4j.error' => 'empty response')); exit; } //get the list of channels connected, filtered by ID $channels_map = VikBooking::getChannelsMap($pchannels_sel); //get the array with the lowest and preferred room rate $best_room_rate = VikBooking::getBestRoomRate($website_rates, $pdef_rplan); //get the charge/discount value for the OTAs rates from the Bulk Rates Cache of VCM $otas_rates_val = VikBooking::getOtasRatesVal($best_room_rate, true); $otas_rmod = ''; $otas_rmodpcent = 0; $otas_rmodval = 0; $otas_rmod_channels = array(); if (!empty($otas_rates_val)) { if (is_array($otas_rates_val)) { $otas_rmod_channels = $otas_rates_val; $use_rates_val = $otas_rates_val[0]; } else { // string $use_rates_val = $otas_rates_val; } $otas_rmod = substr($use_rates_val, 0, 1); // + or - (charge or discount) $otas_rmodpcent = substr($use_rates_val, -1) == '%' ? 1 : 0; $otas_rmodval = (float)($otas_rmodpcent > 0 ? substr($use_rates_val, 1, (strlen($use_rates_val) - 2)) : substr($use_rates_val, 1, (strlen($use_rates_val) - 1))); } if (!count($best_room_rate)) { // nothing to parse echo json_encode(array('e4j.error' => 'no rates')); exit; } // build the response $final_cost = $pshow_tax > 0 ? ($best_room_rate['cost'] + $best_room_rate['taxes']) : $best_room_rate['cost']; $rates_resp = array( 'website' => VikBooking::numberFormat($final_cost) ); if (count($channels_map)) { $rates_resp['channels'] = array(); } foreach ($channels_map as $ch) { $ch_final_cost = $final_cost; /** * Check if an alteration for this channel has been specified. * * @since 1.15.0 (J) - 1.5.0 (WP) */ $use_otas_rmod = $otas_rmod; $use_otas_rmodpcent = $otas_rmodpcent; $use_otas_rmodval = $otas_rmodval; if (is_array($otas_rmod_channels) && isset($otas_rmod_channels[$ch['id']])) { $use_rates_val = $otas_rmod_channels[$ch['id']]; $use_otas_rmod = substr($use_rates_val, 0, 1); //+ or - (charge or discount) $use_otas_rmodpcent = substr($use_rates_val, -1) == '%' ? 1 : 0; $use_otas_rmodval = (float)($use_otas_rmodpcent > 0 ? substr($use_rates_val, 1, (strlen($use_rates_val) - 2)) : substr($use_rates_val, 1, (strlen($use_rates_val) - 1))); } if (!empty($use_otas_rmod)) { if ($use_otas_rmod == '+') { // charge if ($use_otas_rmodpcent > 0) { // percentage $ch_final_cost = $ch_final_cost * (100 + $use_otas_rmodval) / 100; } else { // absolute $ch_final_cost += $use_otas_rmodval * (!empty($best_room_rate['days']) ? $best_room_rate['days'] : 1); } } else { // discount (must be a fool) if ($use_otas_rmodpcent > 0) { // percentage $ch_final_cost = $ch_final_cost / (($use_otas_rmodval / 100) + 1); } else { // absolute $ch_final_cost -= $use_otas_rmodval * (!empty($best_room_rate['days']) ? $best_room_rate['days'] : 1); } } } $rates_resp['channels'][$ch['id']] = VikBooking::numberFormat($ch_final_cost); } // output the response echo json_encode($rates_resp); exit; } // proceed with the standard request (that will exit the process) TACVBO::tac_av_l(); } /** * Front-end authentication for the operators * through their authentication code. * * @since 1.11 */ public function operatorlogin() { /** * Extra security fix for the login form token. * * @since September 8th 2020 */ if (!JFactory::getSession()->checkToken()) { throw new Exception("The security token did not match.", 403); } // $app = JFactory::getApplication(); $pauthcode = VikRequest::getString('authcode', '', 'request'); $pitemid = VikRequest::getInt('Itemid', '', 'request'); /** * We add "&auth=1" to the query string just to avoid caching for a redirect to the same login page URI. * * @since September 9th 2020 */ $goto = JRoute::rewrite('index.php?option=com_vikbooking&view=operators&auth=1'.(!empty($pitemid) ? '&Itemid='.$pitemid : ''), false); if (empty($pauthcode) || !VikBooking::getOperatorInstance()->authOperator($pauthcode)) { // print warning message VikError::raiseWarning('', JText::translate('VBOOPERINVAUTHCODE')); } $app->redirect($goto); } /** * Front-end logout for the operators. * * @since 1.11 */ public function operatorlogout() { $app = JFactory::getApplication(); $pitemid = VikRequest::getInt('Itemid', '', 'request'); $goto = JRoute::rewrite('index.php?option=com_vikbooking&view=operators'.(!empty($pitemid) ? '&Itemid='.$pitemid : ''), false); VikBooking::getOperatorInstance()->logoutOperator(); $app->redirect($goto); } /** * Front-end Pre Check-in submit of the guests details. * * @since 1.12 */ public function storeprecheckin() { if (!JSession::checkToken()) { // missing CSRF-proof token VBOHttpDocument::getInstance()->close(403, JText::translate('JINVALID_TOKEN')); } $dbo = JFactory::getDbo(); $app = JFactory::getApplication(); $sid = VikRequest::getString('sid', '', 'request'); $ts = VikRequest::getString('ts', '', 'request'); $pguests = VikRequest::getVar('guests', array()); $pitemid = VikRequest::getInt('Itemid', 0, 'request'); $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';"; $dbo->setQuery($q); $order = $dbo->loadAssoc(); if (!$order) { throw new Exception('Booking not found', 404); } $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;"; $dbo->setQuery($q); $orderrooms = $dbo->loadAssocList(); if (!$orderrooms) { throw new Exception('No rooms found', 404); } // access the customer record $customer = VikBooking::getCPinInstance()->getCustomerFromBooking($order['id']); $q = "SELECT * FROM `#__vikbooking_customers_orders` WHERE `idorder`=".(int)$order['id'].";"; $dbo->setQuery($q); $custorder = $dbo->loadAssoc(); if (!$custorder) { throw new Exception('No customer found', 404); } // booking details page $goto = JRoute::rewrite('index.php?option=com_vikbooking&view=booking&sid=' . $order['sid'] . '&ts=' . $order['ts'] . (!empty($pitemid) ? '&Itemid=' . $pitemid : ''), false); if (empty($order['sid']) && !empty($order['idorderota'])) { // booking details page for OTA bookings $goto = JRoute::rewrite('index.php?option=com_vikbooking&view=booking&sid=' . $order['idorderota'] . '&ts=' . $order['ts'] . (!empty($pitemid) ? '&Itemid=' . $pitemid : ''), false); } // make sure pre-checkin is allowed $precheckin = VikBooking::precheckinEnabled(); if ($precheckin) { // make sure the limit of days in advance is reflected $precheckin_mind = VikBooking::precheckinMinOffset(); $precheckin_lim_ts = strtotime("+{$precheckin_mind} days 00:00:00"); $precheckin = ($precheckin_lim_ts <= $order['checkin'] || ($precheckin_mind === 1 && time() <= $order['checkin'])); } if (!$precheckin) { // raise error and redirect in case of website or OTA booking VikError::raiseWarning('', 'Pre-checkin not allowed at this time'); $app->redirect($goto); exit; } // build guest details $guests_details = array(); // list of keys for the guests details collected via front-end $front_keys = array(); foreach ($pguests as $ind => $adults) { foreach ($adults as $aduind => $details) { foreach ($details as $detkey => $detval) { if (!in_array($detkey, $front_keys)) { // push the key of the guest details for later comparison array_push($front_keys, $detkey); } if (strlen($detval)) { // push value only if not empty if (!isset($guests_details[$ind])) { $guests_details[$ind] = array(); } if (!isset($guests_details[$ind][$aduind])) { $guests_details[$ind][$aduind] = array(); } $guests_details[$ind][$aduind][$detkey] = $detval; } } } } /** * Compare the current data collected to the back-end pax_data in case there are some * fields dedicated to just the back-end for the admins (like extra_notes), and merge. */ $curpaxdata = json_decode($custorder['pax_data'], true); if (is_array($curpaxdata) && count($curpaxdata)) { foreach ($curpaxdata as $ind => $adults) { if (!isset($guests_details[$ind])) { // current pax data include a room not present, set it to not lose it $guests_details[$ind] = $adults; } foreach ($adults as $aduind => $details) { if (!isset($guests_details[$ind][$aduind])) { // current pax data include a guest not present, set it to not lose it $guests_details[$ind][$aduind] = $details; } foreach ($details as $detkey => $detval) { if (!in_array($detkey, $front_keys)) { // merge this key probably reserved to the back-end $guests_details[$ind][$aduind][$detkey] = $detval; } } } } } // update checkin information $q = "UPDATE `#__vikbooking_customers_orders` SET `pax_data`=".$dbo->quote(json_encode($guests_details))." WHERE `id`=".(int)$custorder['id'].";"; $dbo->setQuery($q); $dbo->execute(); // Booking History VikBooking::getBookingHistoryInstance()->setBid($order['id'])->store('PC'); /** * Invoke the callback on the pax registration driver. * * @since 1.17.5 (J) - 1.7.5 (WP) */ VBOCheckinPax::callbackPrecheckinDataStored( VBOFactory::getConfig()->getString('checkindata', 'basic'), (array) $guests_details, (array) $order, (array) $customer ); // print success message and redirect $app->enqueueMessage(JText::translate('VBOSUBMITPRECHECKINTNKS')); $app->redirect($goto); } /** * Upsell extra services/options. * * @since 1.13 (J) - 1.3.0 (WP) */ public function upsellextras() { $dbo = JFactory::getDbo(); $app = JFactory::getApplication(); $sid = VikRequest::getString('sid', '', 'request'); $ts = VikRequest::getString('ts', '', 'request'); $pitemid = VikRequest::getInt('Itemid', 0, 'request'); $paddopt = VikRequest::getVar('addopt', array()); if (!$paddopt) { throw new Exception('No extra services selected', 404); } $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) . ";"; $dbo->setQuery($q); $order = $dbo->loadAssoc(); if (!$order) { throw new Exception('Booking not found', 404); } $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;"; $dbo->setQuery($q); $orderrooms = $dbo->loadAssocList(); if (!$orderrooms) { throw new Exception('No rooms found', 404); } // availability helper $av_helper = VikBooking::getAvailabilityInstance(); // room stay dates in case of split stay $room_stay_dates = []; if ($order['split_stay']) { if ($order['status'] == 'confirmed') { $room_stay_dates = $av_helper->loadSplitStayBusyRecords($order['id']); } else { $room_stay_dates = VBOFactory::getConfig()->getArray('split_stay_' . $order['id'], []); } } // load all valid and existing options as a security measure $alloptions = []; $q = "SELECT * FROM `#__vikbooking_optionals`;"; $dbo->setQuery($q); $records = $dbo->loadAssocList(); if (!$records) { throw new Exception('No options found', 404); } foreach ($records as $v) { $alloptions[$v['id']] = $v; } /** * Custom check-in/out times due to late check-out/early check-in options. * * @since 1.17.2 (J) - 1.7.2 (WP) */ $custom_checkinout = []; $extras_booked = []; foreach ($orderrooms as $kor => $or) { if (!isset($paddopt[$kor]) || !$paddopt[$kor]) { continue; } // determine proper nights of stay $room_stay_nights = $order['days']; if ($order['split_stay'] && count($room_stay_dates) && isset($room_stay_dates[$kor]) && $room_stay_dates[$kor]['idroom'] == $or['idroom']) { $room_stay_checkin = !empty($room_stay_dates[$kor]['checkin_ts']) ? $room_stay_dates[$kor]['checkin_ts'] : $room_stay_dates[$kor]['checkin']; $room_stay_checkout = !empty($room_stay_dates[$kor]['checkout_ts']) ? $room_stay_dates[$kor]['checkout_ts'] : $room_stay_dates[$kor]['checkout']; $room_stay_nights = $av_helper->countNightsOfStay($room_stay_checkin, $room_stay_checkout); } $extraoptstr = ''; foreach ($paddopt[$kor] as $optid => $quant) { if (strpos($or['optionals'], $optid . ':') === 0 || strpos($or['optionals'], ';' . $optid . ':') > 0) { // this option has already been booked, skip it continue; } if (!isset($alloptions[$optid])) { // this option ID does not exist, skip it continue; } $extraoptstr .= $optid . ':' . (int)$quant . ';'; // option params $opt_params = !empty($alloptions[$optid]['oparams']) ? (array) json_decode($alloptions[$optid]['oparams'], true) : []; /** * Custom check-in/out times due to late check-out/early check-in options. * * @since 1.17.2 (J) - 1.7.2 (WP) */ if (($opt_params['custom_checkinout'] ?? 0) && (($opt_params['set_checkin'] ?? 0) || ($opt_params['set_checkout'] ?? 0))) { $custom_checkinout = [ ($opt_params['set_checkin'] ?? 0), ($opt_params['set_checkout'] ?? 0), ]; } // push option booked $extras_booked[] = [ 'id' => $optid, 'idroom' => $or['idroom'], 'name' => $alloptions[$optid]['name'], 'quant' => $quant, 'room_cost' => (!empty($or['cust_cost']) ? $or['cust_cost'] : $or['room_cost']), 'room_name' => $or['room_name'], 'optcost' => $alloptions[$optid]['cost'], 'adults' => $or['adults'], 'children' => $or['children'], 'nights' => $room_stay_nights, ]; } // update options for this room record $newoptstr = $or['optionals'] . $extraoptstr; $q = "UPDATE `#__vikbooking_ordersrooms` SET `optionals`=" . $dbo->quote($newoptstr) . " WHERE `id`={$or['id']};"; $dbo->setQuery($q); $dbo->execute(); } // increase booking total amount and build event log for the history $currency = VikBooking::getCurrencySymb(); $totrooms = count($orderrooms); $increase = 0; $add_tax = 0; $extraslog = []; foreach ($extras_booked as $extra) { $o = $alloptions[(int)$extra['id']]; if ((int)$o['pcentroom']) { // make sure we have a cost for the room, or we should skip this type of option for "incomplete" bookings if (empty($extra['room_cost'])) { continue; } $o['cost'] = ($extra['room_cost'] * $o['cost'] / 100); } $optcost = intval($o['perday']) == 1 ? ($o['cost'] * $extra['nights']) : $o['cost']; if (!empty($o['maxprice']) && $o['maxprice'] > 0 && $optcost > $o['maxprice']) { $optcost = $o['maxprice']; } if ($o['perperson'] == 1) { $optcost = $optcost * $extra['adults']; } $optcost *= $extra['quant']; /** * Trigger event to allow third party plugins to apply a custom calculation for the option/extra fee or tax. * * @since 1.17.7 (J) - 1.7.7 (WP) */ $custom_calculation = VBOFactory::getPlatform()->getDispatcher()->filter('onCalculateBookingOptionFeeCost', [$optcost, &$o, $order, $extra]); if ($custom_calculation) { $optcost = (float) $custom_calculation[0]; } $floatoptprice = VikBooking::sayOptionalsPlusIva($optcost, $o['idiva']); $netoptprice = VikBooking::sayOptionalsMinusIva($optcost, $o['idiva']); $increase += $floatoptprice; $add_tax += $floatoptprice - $netoptprice; array_push($extraslog, ($totrooms > 1 ? $extra['room_name'] . ': ' : '') . $extra['name'] . ($extra['quant'] > 1 ? ' (x' . $extra['quant'] . ')' : '') . ' ' . $currency . ' ' . VikBooking::numberFormat($floatoptprice)); } $newtotbooking = $order['total'] + $increase; $new_tot_taxes = $order['tot_taxes'] + $add_tax; /** * Important: the 'paymcount' should be increased only if the status is * "confirmed" or no rooms may be occupied when receiving a payment. */ $q = $dbo->getQuery(true) ->update($dbo->qn('#__vikbooking_orders')) ->set($dbo->qn('total') . ' = ' . $dbo->q($newtotbooking)) ->set($dbo->qn('paymcount') . ' = ' . ($order['status'] == 'confirmed' && (int)$order['paymcount'] < 1 ? '1' : $order['paymcount'])) ->set($dbo->qn('tot_taxes') . ' = ' . $dbo->q($new_tot_taxes)) ->set($dbo->qn('payable') . ' = ' . $dbo->q(((float)$order['payable'] + $increase))) ->where($dbo->qn('id') . ' = ' . (int) $order['id']); /** * Custom check-in/out times due to late check-out/early check-in options. * * @since 1.17.2 (J) - 1.7.2 (WP) */ if ($custom_checkinout) { $checkin_info = getdate($order['checkin']); $checkout_info = getdate($order['checkout']); // overwrite check-in and/or check-out timestamp(s) if ($custom_checkinout[0] >= 3600) { // overwrite check-in timestamp $time_hours = floor($custom_checkinout[0] / 3600); $time_minutes = floor(($custom_checkinout[0] - ($time_hours * 3600)) / 60); $new_booking_checkin = mktime($time_hours, $time_minutes, 0, $checkin_info['mon'], $checkin_info['mday'], $checkin_info['year']); // update db record field $q->set($dbo->qn('checkin') . ' = ' . $dbo->q($new_booking_checkin)); } if ($custom_checkinout[1] >= 3600) { // overwrite check-out timestamp $time_hours = floor($custom_checkinout[1] / 3600); $time_minutes = floor(($custom_checkinout[1] - ($time_hours * 3600)) / 60); $new_booking_checkout = mktime($time_hours, $time_minutes, 0, $checkout_info['mon'], $checkout_info['mday'], $checkout_info['year']); // update db record field $q->set($dbo->qn('checkout') . ' = ' . $dbo->q($new_booking_checkout)); } } // update booking record on db $dbo->setQuery($q); $dbo->execute(); // Booking History VikBooking::getBookingHistoryInstance($order['id'])->store('UE', implode("\n", $extraslog)); /** * Trigger event to allow third-party plugins to choose whether upselling notifications should be sent. * * @since 1.17.2 (J) - 1.7.2 (WP) */ $send_notifications = true; $should_send = VBOFactory::getPlatform()->getDispatcher()->filter('onUpsellingReceivedShouldSendNotifications', [$order]); if (is_array($should_send) && in_array(false, $should_send, true)) { $send_notifications = false; } if ($send_notifications) { // send email notification to guest and admin VikBooking::sendBookingEmail($order['id'], array('guest', 'admin')); } $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); $app->enqueueMessage(JText::translate('VBOUPSELLRESULTOK')); $app->redirect($goto); $app->close(); } /** * Submits a new review. * * @since 1.3.0 */ public function sendreview() { $dbo = JFactory::getDbo(); $app = JFactory::getApplication(); $vbo_tn = VikBooking::getTranslator(); $sid = VikRequest::getString('sid', '', 'request'); $ts = VikRequest::getString('ts', '', 'request'); $ratingmess = VikRequest::getString('ratingmess', '', 'request'); $rating = VikRequest::getVar('rating', array(), 'request', 'array'); $pitemid = VikRequest::getInt('Itemid', 0, 'request'); $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';"; $dbo->setQuery($q); $dbo->execute(); if (!$dbo->getNumRows()) { throw new Exception('Booking not found', 404); } $order = $dbo->loadAssoc(); // make sure a review can be left for this booking if (!VikBooking::canBookingBeReviewed($order)) { throw new Exception('Cannot leave a review at this time', 403); } $orderrooms = array(); $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;"; $dbo->setQuery($q); $dbo->execute(); if (!$dbo->getNumRows()) { throw new Exception('No rooms found', 404); } $orderrooms = $dbo->loadAssocList(); // get customer information $customer = VikBooking::getCPinIstance()->getCustomerFromBooking($order['id']); // booking details page $goto = JRoute::rewrite('index.php?option=com_vikbooking&view=booking&sid=' . $order['sid'] . '&ts=' . $order['ts'] . (!empty($pitemid) ? '&Itemid=' . $pitemid : ''), false); if (empty($order['sid']) && !empty($order['idorderota'])) { // booking details page for OTA bookings $goto = JRoute::rewrite('index.php?option=com_vikbooking&view=booking&sid=' . $order['idorderota'] . '&ts=' . $order['ts'] . (!empty($pitemid) ? '&Itemid=' . $pitemid : ''), false); } // reviews settings $gr_approval = VikBooking::guestReviewsApproval(); $gr_type = VikBooking::guestReviewsType(); $gr_services = VikBooking::guestReviewsServices(); $rawservices = $gr_services; $vbo_tn->translateContents($gr_services, '#__vikbooking_greview_service'); // make sure all ratings are not empty if ($gr_type == 'global') { if (empty($rating[0]) || intval($rating[0]) < 1 || intval($rating[0]) > 5) { // no or invalid single-rating received VikError::raiseWarning('', 'Please rate your experience to leave a review.'); $app->redirect($goto); exit; } } else { if (count($gr_services) != count($rating) || !count($rating)) { // something is missing VikError::raiseWarning('', 'Please rate your experience for all services'); $app->redirect($goto); exit; } // make sure all ratings are valid foreach ($rating as $k => $score) { if (empty($score) || intval($score) < 1 || intval($score) > 5) { // no or invalid rating received for this service VikError::raiseWarning('', 'Please rate your experience to leave a review (missing ' . (isset($gr_services[$k]) ? $gr_services[$k]['service_name'] : '-----') . ').'); $app->redirect($goto); exit; } } } // average review score (in base 10) and services map $avg_score = 0; $serv_scores = array(); // gather the information foreach ($rating as $k => $score) { // rating in base 10 $score = ((int)$score * 2); // $avg_score += $score; if ($gr_type == 'service' && isset($gr_services[$k]) && !empty($gr_services[$k]['service_name'])) { $skey = $gr_services[$k]['service_name']; $serv_scores[$skey] = $score; } } // this will be the review_score $avg_score = round(($avg_score / count($rating)), 2); // build review content object $review_content = new stdClass; // creation date $review_content->created_timestamp = date('Y-m-d H:i:s'); // scoring per service (if any) $review_content->scoring = new stdClass; if (count($serv_scores)) { $counter = 0; foreach ($serv_scores as $snametranx => $servscore) { // we build the object with the original names of the services, as they could have been translated $origskey = $rawservices[$counter]['service_name']; $review_content->scoring->{$origskey} = $servscore; $counter++; } } // scoring total value ("review_score" is a protected key) is added no matter of the review type (service/global) $review_content->scoring->review_score = $avg_score; // reviewer information $review_content->reviewer = new stdClass; $customer_name = ''; if ($customer) { $review_content->reviewer->name = $customer['first_name']; $review_content->reviewer->country_code = $customer['country']; $customer_name = $customer['first_name'] . ' ' . $customer['last_name']; } else { $revuname = ''; if (!empty($order['custdata'])) { $uinfos = explode("\n", $order['custdata']); $first_info = explode(':', $uinfos[0]); if (count($first_info) > 1) { unset($first_info[0]); $revuname = implode(':', $first_info); } else { $revuname = trim($first_info[0]); } } $review_content->reviewer->name = $revuname; $review_content->reviewer->country_code = $order['country']; $customer_name = $revuname; } // maximum 2000 chars for the message review to avoid spammers if (!empty($ratingmess) && strlen($ratingmess) > 2000) { $ratingmess = substr($ratingmess, 0, 2000); } // review message $review_content->content = new stdClass; $review_content->content->message = !empty($ratingmess) ? $ratingmess : null; // null reply $review_content->reply = null; // check if multiple accounts to find the property name $property_name = null; $has_multiaccounts = false; $multi_map = array(); $q = "SELECT * FROM `#__vikchannelmanager_roomsxref`;"; $dbo->setQuery($q); $dbo->execute(); if ($dbo->getNumRows()) { $xref_data = $dbo->loadAssocList(); foreach ($xref_data as $xref) { if (empty($xref['prop_params'])) { continue; } if (!isset($multi_map[$xref['idchannel']])) { $multi_map[$xref['idchannel']] = array(); } if (!isset($multi_map[$xref['idchannel']][$xref['prop_params']])) { $multi_map[$xref['idchannel']][$xref['prop_params']] = 0; } $multi_map[$xref['idchannel']][$xref['prop_params']]++; } foreach ($multi_map as $ch_id => $ch_params) { if (count($ch_params) > 1) { $has_multiaccounts = true; break; } } } if ($has_multiaccounts && (int)$order['roomsnum'] === 1) { // find the category name of the room booked (if any, and if one room booked) $q = "SELECT `idcat` FROM `#__vikbooking_rooms` WHERE `id`={$orderrooms[0]['idroom']};"; $dbo->setQuery($q); $dbo->execute(); if ($dbo->getNumRows()) { $allcats = $dbo->loadResult(); if (!empty($allcats)) { $parts = explode(';', $allcats); if (count($parts) === 2) { // just one category, get the name of it $property_name = VikBooking::getCategoryName($parts[0]); } } } if (empty($property_name)) { // category not found, get the room name $property_name = $orderrooms[0]['name']; } } // create record $review_record = new stdClass; $review_record->review_id = -1; $review_record->prop_first_param = null; $review_record->prop_name = $property_name; $review_record->channel = null; $review_record->uniquekey = 0; $review_record->idorder = $order['id']; $review_record->dt = JFactory::getDate()->toSql(true); $review_record->customer_name = $customer_name; $review_record->lang = JFactory::getLanguage()->getTag(); $review_record->score = $avg_score; $review_record->country = $order['country']; $review_record->content = json_encode($review_content); $review_record->published = ($gr_approval == 'auto' ? 1 : 0); // insert review if ($dbo->insertObject('#__vikchannelmanager_otareviews', $review_record, 'id')) { $app->enqueueMessage(JText::translate('VBOTHANKSREVIEWLEFT')); // Booking History VikBooking::getBookingHistoryInstance()->setBid($order['id'])->store('GR'); } else { VikError::raiseWarning('', JText::translate('VBOREVIEWGENERROR')); } // update global score for website and this property (if multiple accounts) $globscore_id = null; $q = "SELECT `id` FROM `#__vikchannelmanager_otascores` WHERE `channel` IS NULL AND " . (is_null($property_name) ? '`prop_name` IS NULL' : '`prop_name`=' . $dbo->quote($property_name)); $dbo->setQuery($q, 0, 1); $dbo->execute(); if ($dbo->getNumRows()) { $globscore_id = $dbo->loadResult(); } // select score for all reviews for the website and this account $services_scores = array(); $services_revscount = array(); $revs_count = 0; $super_tot = 0; $q = "SELECT `score`,`content` FROM `#__vikchannelmanager_otareviews` WHERE `channel` IS NULL AND " . (is_null($property_name) ? '`prop_name` IS NULL' : '`prop_name`=' . $dbo->quote($property_name)) . ";"; $dbo->setQuery($q); $dbo->execute(); if ($dbo->getNumRows()) { $all_scores = $dbo->loadAssocList(); $revs_count += count($all_scores); foreach ($all_scores as $s) { $super_tot += $s['score']; // check if scores were given per service $s['content'] = json_decode($s['content'], true); if (isset($s['content']['scoring']) && count($s['content']['scoring']) > 1) { // review was left for services foreach ($s['content']['scoring'] as $sname => $sval) { if (!isset($services_scores[$sname])) { $services_scores[$sname] = 0; $services_revscount[$sname] = 0; } $services_scores[$sname] += $sval; $services_revscount[$sname]++; } } } } // global average score $revs_count = $revs_count > 0 ? $revs_count : 1; $glob_avg_score = ($super_tot / $revs_count); // services average score $glob_servs_avg_score = array(); foreach ($services_scores as $sname => $val) { $services_revscount[$sname] = isset($services_revscount[$sname]) && $services_revscount[$sname] > 0 ? $services_revscount[$sname] : 1; $glob_servs_avg_score[$sname] = ($val / $services_revscount[$sname]); } // build global score content $glob_score_content = new stdClass; $glob_score_content->review_score = new stdClass; $glob_score_content->review_score->score = $glob_avg_score; $glob_score_content->review_score->review_count = $revs_count; foreach ($glob_servs_avg_score as $sname => $val) { $glob_score_content->{$sname} = new stdClass; $glob_score_content->{$sname}->score = $val; $glob_score_content->{$sname}->review_count = $services_revscount[$sname]; } // build global score object $glob_score_obj = new stdClass; if (!empty($globscore_id)) { $glob_score_obj->id = $globscore_id; } $glob_score_obj->prop_first_param = null; $glob_score_obj->prop_name = $property_name; $glob_score_obj->channel = null; $glob_score_obj->uniquekey = 0; $glob_score_obj->last_updated = JFactory::getDate()->toSql(true); $glob_score_obj->score = round($glob_avg_score, 2); $glob_score_obj->content = json_encode($glob_score_content); // update or create global score if (!empty($globscore_id)) { $dbo->updateObject('#__vikchannelmanager_otascores', $glob_score_obj, 'id'); } else { $dbo->insertObject('#__vikchannelmanager_otascores', $glob_score_obj, 'id'); } // redirect to main view $app->redirect($goto); } /** * AJAX task to get one monthly availability calendar of the room details page. * * @since 1.13.5 */ public function get_avcalendars_data() { $dbo = JFactory::getDbo(); $rid = VikRequest::getInt('rid', 0, 'request'); $direction = VikRequest::getString('direction', 'next', 'request'); $fromdt = VikRequest::getString('fromdt', '', 'request'); $nextdt = VikRequest::getString('nextdt', '', 'request'); $prevdt = VikRequest::getString('prevdt', '', 'request'); // make sure vars are not empty if (empty($rid) || empty($direction) || empty($fromdt) || empty($nextdt) || empty($prevdt)) { /** * Search engines may follow this endpoint, so we have to exit with HTTP status code 200 * * @since 1.16.0 (J) - 1.6.0 (WP) */ VBOHttpDocument::getInstance()->json(['Invalid request variables']); } // date format $vbo_df = VikBooking::getDateFormat(); if ($vbo_df == "%d/%m/%Y") { $vbo_df = 'd/m/Y'; } elseif ($vbo_df == "%m/%d/%Y") { $vbo_df = 'm/d/Y'; } else { $vbo_df = 'Y/m/d'; } // configuration settings $numcalendars = VikBooking::numCalendars(); $showpartlyres = VikBooking::showPartlyReserved(); $showcheckinoutonly = VikBooking::showStatusCheckinoutOnly(); $usepricecal = false; $inonout_allowed = true; $timeopst = VikBooking::getTimeOpenStore(); if (is_array($timeopst)) { if ($timeopst[0] < $timeopst[1]) { // check-in not allowed on a day where there is already a check out (no arrivals/depatures on the same day) $inonout_allowed = false; } } // week-days ordering $firstwday = (int)VikBooking::getFirstWeekDay(); $days_labels = array( JText::translate('VBSUN'), JText::translate('VBMON'), JText::translate('VBTUE'), JText::translate('VBWED'), JText::translate('VBTHU'), JText::translate('VBFRI'), JText::translate('VBSAT') ); $days_indexes = array(); for ($i = 0; $i < 7; $i++) { $days_indexes[$i] = (6 - ($firstwday - $i) + 1) % 7; } // first day timestamp of month to read in case of forward navigation $start_ts = strtotime($fromdt); $start_info = getdate($start_ts); if (!$start_ts || !$start_info) { VBOHttpDocument::getInstance()->close(500, 'Invalid date provided'); } // backward navigation if ($direction == 'prev') { // we need to get the previous month $start_ts = mktime(0, 0, 0, ($start_info['mon'] - 1), 1, $start_info['year']); $start_info = getdate($start_ts); if (!$start_ts || !$start_info) { VBOHttpDocument::getInstance()->close(500, 'Invalid date calculated'); } } // make sure minimum date is respected $min_lim_ts = mktime(0, 0, 0, date('n'), 1, date('Y')); if ($start_ts < $min_lim_ts) { VBOHttpDocument::getInstance()->close(500, 'Dates in the past not allowed'); } // check the current next and prev dates to help the next AJAX navigations $nextnav_ts = strtotime($nextdt); $nextnav_info = getdate($nextnav_ts); if (!$nextnav_ts || !$nextnav_info) { VBOHttpDocument::getInstance()->close(500, 'Invalid next navigation date provided'); } $prevnav_ts = strtotime($prevdt); $prevnav_info = getdate($prevnav_ts); if (!$prevnav_ts || !$prevnav_info) { VBOHttpDocument::getInstance()->close(500, 'Invalid prev navigation date provided'); } // make sure maximum date is respected $max_months_future = 12; $max_date_future = VikBooking::getMaxDateFuture($rid); if (!empty($max_date_future)) { $numlim = (int)substr($max_date_future, 1, (strlen($max_date_future) - 2)); $numlim = $numlim < 1 ? 1 : $numlim; $quantlim = substr($max_date_future, -1, 1); if ($quantlim == 'm' || $quantlim == 'y') { $max_months_future = $numlim * ($quantlim == 'm' ? 1 : 12); $max_ts_future = strtotime("+{$max_months_future} months"); $max_info = getdate($max_ts_future); $max_endts_future = mktime(23, 59, 59, $max_info['mon'], date('t', $max_info[0]), $max_info['year']); if ($start_ts > $max_endts_future) { VBOHttpDocument::getInstance()->close(500, 'Maximum date in the future exceeded'); } } } // get global property closing dates $cal_closing_dates = VikBooking::parseJsClosingDates(); if (count($cal_closing_dates)) { foreach ($cal_closing_dates as $ccdk => $ccdv) { if (!(count($ccdv) == 2)) { continue; } $cal_closing_dates[$ccdk][0] = strtotime($ccdv[0]); $cal_closing_dates[$ccdk][1] = strtotime($ccdv[1]); } } // load room details $q = "SELECT * FROM `#__vikbooking_rooms` WHERE `id`={$rid}"; $dbo->setQuery($q, 0, 1); $dbo->execute(); if (!$dbo->getNumRows()) { VBOHttpDocument::getInstance()->close(404, 'Room not found'); } $room_details = $dbo->loadAssoc(); // get the future busy records $today_ts = mktime(0, 0, 0, date('n'), date('j'), date('Y')); $previousdayclass = ''; $q = "SELECT * FROM `#__vikbooking_busy` WHERE `idroom`={$room_details['id']} AND `checkout`>={$start_ts};"; $dbo->setQuery($q); $busy = $dbo->loadAssocList(); // empty day element $empty_elem = new stdClass; $empty_elem->type = 'placeholder'; $empty_elem->cont = ' '; // build response container $calendars = array(); // prepare calendar object $calendar = new stdClass; $calendar->ts = $start_info[0]; $calendar->mon = $start_info['mon']; $calendar->mday = $start_info['mday']; $calendar->year = $start_info['year']; $calendar->month = VikBooking::sayMonth($start_info['mon']); $calendar->wdays = array(); for ($i = 0; $i < 7; $i++) { $d_ind = ($i + $firstwday) < 7 ? ($i + $firstwday) : ($i + $firstwday - 7); array_push($calendar->wdays, $days_labels[$d_ind]); } // build the calendar rows by looping over the days of this month $calendar->rows = array(); // the row will contain all the placeholders and real days (7 elements at most) $row = array(); $d_count = 0; // first, we push empty dates placeholders for printing the first table cells (cells before the 1st of the month) for ($i = 0, $n = $days_indexes[$start_info['wday']]; $i < $n; $i++, $d_count++) { // push fake-day element (placeholder) array_push($row, $empty_elem); } // start looping $loop_end_month = $start_info['mon']; while ($start_info['mon'] == $loop_end_month) { if ($d_count > 6) { // push the current row array_push($calendar->rows, $row); // start a new row and reset cells counter $row = array(); $d_count = 0; } // build real-day element $elem = new stdClass; $elem->type = 'day'; $elem->cont = $start_info['mday'] < 10 ? "0{$start_info['mday']}" : $start_info['mday']; $elem->dt = date($vbo_df, $start_info[0]); $elem->ymd = date('Y-m-d', $start_info[0]); $elem->ts = $start_info[0]; $elem->class = 'vbtdfree'; $elem->past_class = $start_info[0] < $today_ts ? ' vbtdpast' : ''; // check whether this day has got bookings $totfound = 0; $ischeckinday = false; $ischeckoutday = false; foreach ($busy as $b) { $info_in = getdate($b['checkin']); $checkin_ts = mktime(0, 0, 0, $info_in['mon'], $info_in['mday'], $info_in['year']); $info_out = getdate($b['checkout']); $checkout_ts = mktime(0, 0, 0, $info_out['mon'], $info_out['mday'], $info_out['year']); if ($start_info[0] >= $checkin_ts && $start_info[0] == $checkout_ts) { $ischeckoutday = true; } if ($start_info[0] >= $checkin_ts && $start_info[0] < $checkout_ts) { $totfound++; if ($start_info[0] == $checkin_ts) { $ischeckinday = true; } } } if ($totfound >= $room_details['units']) { $elem->class = "vbtdbusy"; if ($ischeckinday && $showcheckinoutonly && !$usepricecal && $inonout_allowed && $previousdayclass != "vbtdbusy" && $previousdayclass != "vbtdbusy vbtdbusyforcheckin") { $elem->class = "vbtdbusy vbtdbusyforcheckin"; } elseif ($ischeckinday && !$usepricecal && !$inonout_allowed && $previousdayclass != "vbtdbusy" && $previousdayclass != "vbtdbusy vbtdbusyforcheckin") { // check-out not allowed on a day where someone is already checking-in $elem->class = "vbtdbusy"; } } elseif ($totfound > 0) { if ($showpartlyres) { $elem->class = "vbtdwarning"; } } else { if ($ischeckoutday && !$usepricecal && $showcheckinoutonly && $inonout_allowed && !($room_details['units'] > 1)) { $elem->class = "vbtdbusy vbtdbusyforcheckout"; } elseif ($ischeckoutday && !$usepricecal && !$inonout_allowed && !($room_details['units'] > 1)) { $elem->class = "vbtdbusy"; } } // check global closing dates if (count($cal_closing_dates)) { foreach ($cal_closing_dates as $closed_interval) { if ($start_info[0] >= $closed_interval[0] && $start_info[0] <= $closed_interval[1]) { $elem->class = "vbtdbusy"; break; } } } // push cell element and increase counter array_push($row, $elem); $d_count++; // update previous day class $previousdayclass = $elem->class; // go to next day $start_info = getdate(mktime(0, 0, 0, $start_info['mon'], ($start_info['mday'] + 1), $start_info['year'])); } if (count($row)) { // the last row still need to be pushed in the rows for ($i = $d_count; $i <= 6; $i++) { // fill last empty days array_push($row, $empty_elem); } // push ending row array_push($calendar->rows, $row); } // push this month's calendar object array_push($calendars, $calendar); // check whether next request can navigate forward $can_nav_next = false; if ($direction == 'prev') { // we went prev, so we got to be able to go next $can_nav_next = true; } elseif ($direction == 'next' && strtotime("+{$max_months_future} months") > $start_info[0]) { // went next and max future date is still greater than next hypothetical month $can_nav_next = true; } // check whether next request can navigate backward $can_nav_prev = false; if ($direction == 'next') { // we went next, so we got to be able to go prev $can_nav_prev = true; } elseif ($direction == 'prev' && $min_lim_ts < mktime(0, 0, 0, ($start_info['mon'] - 1), 1, $start_info['year'])) { // went prev and first day of today's month is less than (not equal to) the last month just rendered $can_nav_prev = true; } // calculate next and prev dates for the next navigations to help the AJAX request $next_ymd = null; if ($can_nav_next) { if ($direction == 'next') { // next forward navigation will start from the ne month $next_ymd = date('Y-m-d', $start_info[0]); } else { // we add the total number of calendars to the month after the one lastly displayed $next_ymd = date('Y-m-d', strtotime("+" . ($numcalendars - 1) . " months", $start_info[0])); } } $prev_ymd = null; if ($can_nav_prev) { if ($direction == 'prev') { // next backward navigation will start from the last month we just displayed $prev_ymd = date('Y-m-d', mktime(0, 0, 0, ($start_info['mon'] - 1), 1, $start_info['year'])); } else { // we add the total number of calendars to the month after the one lastly displayed $prev_ymd = date('Y-m-d', strtotime("-{$numcalendars} months", $start_info[0])); } } // build response object $response = new stdClass; $response->calendars = $calendars; $response->can_nav_next = $can_nav_next; $response->can_nav_prev = $can_nav_prev; $response->next_ymd = $next_ymd; $response->prev_ymd = $prev_ymd; echo json_encode($response); exit; } /** * AJAX request for adding a new room-day note from the front-end tableaux. * * @return void * * @since 1.13.5 */ public function add_roomdaynote() { $dt = VikRequest::getString('dt', '', 'request'); $idroom = VikRequest::getInt('idroom', 0, 'request'); $subunit = VikRequest::getInt('subunit', 0, 'request'); $type = VikRequest::getString('type', '', 'request'); $type = empty($type) ? 'custom' : $type; $name = VikRequest::getString('name', '', 'request'); $descr = VikRequest::getString('descr', '', 'request'); $cdays = VikRequest::getInt('cdays', 0, 'request'); $cdays = $cdays < 0 ? 0 : $cdays; $cdays = $cdays > 365 ? 365 : $cdays; if (empty($idroom) || empty($dt) || !strtotime($dt)) { echo 'e4j.error.1'; exit; } // we put the operator name in the description (if available) $operator = VikBooking::getOperatorInstance()->getOperatorAccount(); if ($operator !== false) { $oper_signature = "({$operator['first_name']} {$operator['last_name']})"; $descr = empty($descr) ? $oper_signature : $descr . " \n" . $oper_signature; } // reload end date $end_date = $dt; // build critical date object $new_note = array( 'name' => $name, 'type' => $type, 'descr' => $descr, ); // get object $notes = VikBooking::getCriticalDatesInstance(); // store the notes for all consecutive dates for ($i = 0; $i <= $cdays; $i++) { $store_dt = $dt; if ($i > 0) { $dt_info = getdate(strtotime($store_dt)); $store_dt = date('Y-m-d', mktime(0, 0, 0, $dt_info['mon'], ($dt_info['mday'] + $i), $dt_info['year'])); $end_date = $store_dt; } $result = $notes->storeDayNote($new_note, $store_dt, $idroom, $subunit); if (!$result) { echo 'e4j.error.2'; exit; } } // reload all room day notes for this day for the AJAX response $all_notes = $notes->loadRoomDayNotes($dt, $end_date, $idroom, $subunit); if (!$all_notes || !count($all_notes)) { // no notes found even after storing it echo 'e4j.error.3'; exit; } echo json_encode($all_notes); exit; } /** * AJAX endpoint to upload customer documents during the pre-checkin. * * @throws Exception * * @since 1.14 (J) - 1.4.0 (WP) */ public function precheckin_upload_docs() { if (!JSession::checkToken()) { // missing CSRF-proof token VBOHttpDocument::getInstance()->close(403, JText::translate('JINVALID_TOKEN')); } $dbo = JFactory::getDbo(); $app = JFactory::getApplication(); $input = $app->input; // get request values $order_sid = $input->getString('sid', ''); $order_ts = $input->getString('ts', ''); /** * This is a simple file uploading process, but if * we wanted to automatically update the pax_data, * the request vars below would be necessary. */ $room_index = $input->getInt('room_index', 0); $guest_index = $input->getInt('guest_index', 0); $pax_index = $input->getString('pax_index', ''); if (empty($order_sid) || empty($order_ts)) { throw new Exception('Missing booking details', 404); } $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';"; $dbo->setQuery($q); $dbo->execute(); if (!$dbo->getNumRows()) { throw new Exception('Booking not found', 404); } $order = $dbo->loadAssoc(); $customer = array(); $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'].";"; $dbo->setQuery($q); $dbo->execute(); if (!$dbo->getNumRows()) { // one customer must be assigned to this booking for the pre-checkin throw new Exception('No customers associated to this booking', 404); } $customer = $dbo->loadObject(); // make sure pre-checkin is allowed $precheckin = VikBooking::precheckinEnabled(); if ($precheckin) { // make sure the limit of days in advance is reflected $precheckin_mind = VikBooking::precheckinMinOffset(); $precheckin_lim_ts = strtotime("+{$precheckin_mind} days 00:00:00"); $precheckin = ($precheckin_lim_ts <= $order['checkin'] || ($precheckin_mind === 1 && time() <= $order['checkin'])); } if (!$precheckin) { throw new Exception('Pre-checkin not allowed at this time', 403); } // get uploaded files array (use "raw" to avoid filtering the file to upload) $files = $input->files->get('docs', array(), 'raw'); if (!count($files)) { throw new Exception('No files to be uploaded', 500); } if (isset($files['name'])) { // we have a single associative array, we need to push it within a list, // because the upload iterates the $files array $files = array($files); } // fetch documents folder path $dirpath = VBO_CUSTOMERS_PATH . DIRECTORY_SEPARATOR; // check if we have a valid directory if (empty($customer->docsfolder) || !is_dir($dirpath . $customer->docsfolder)) { // randomize string $customer->seed = uniqid(); // create blocks for hashed folder $parts = array( $customer->first_name, $customer->last_name, md5(serialize($customer)), ); // join fetched parts $customer->docsfolder = strtolower(implode('-', array_filter($parts))); if (strlen($customer->docsfolder) < 16) { throw new Exception('Possible security breach. Please specify as many details as possible.', 400); } jimport('joomla.filesystem.folder'); // create a folder for this customer $created = JFolder::create($dirpath . $customer->docsfolder); if (!$created) { throw new Exception(sprintf('Unable to create the folder [%s]', $dirpath . $customer->docsfolder), 403); } unset($customer->seed); // update customer docs folder $record = new stdClass; $record->id = $customer->id; $record->docsfolder = $customer->docsfolder; $dbo->updateObject('#__vikbooking_customers', $record, 'id'); } // prepare the response array of uploaded-file objects $response = array(); $upload_err = null; // compose prefix for all files uploaded (must end with an underscrore for View's compatibility) $file_prefix = str_replace(' ', '-', JText::translate('VBOPRECHECKIN')) . '_'; try { foreach ($files as $file) { // sanitize file name if (!empty($file['name'])) { // replace quotes and pipe, which is the separator in pax_data $file['name'] = str_replace(array("'", '"', '|'), '', $file['name']); } if (empty($file['name'])) { continue; } // always prepend "pre check-in" to the original file name $file['name'] = strtolower($file_prefix . $file['name']); // try to upload the file $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'); // set a valid URL for the uploaded file $result->url = str_replace(DIRECTORY_SEPARATOR, '/', str_replace(VBO_CUSTOMERS_PATH . DIRECTORY_SEPARATOR, VBO_CUSTOMERS_URI, $result->path)); // push uploaded file array_push($response, $result); } } catch (Exception $e) { // do nothing, but catch the error $upload_err = $e; } if (!count($response)) { throw ($upload_err !== null ? $upload_err : (new Exception('No files could actually be uploaded', 500))); } // output the response echo json_encode($response); exit; } /** * AJAX endpoint to submit an inquiry/information request. * * @since 1.15.0 (J) - 1.5.0 (WP) */ public function submit_inquiry() { if (!JSession::checkToken()) { // missing CSRF-proof token VBOHttpDocument::getInstance()->close(403, JText::translate('JINVALID_TOKEN')); } $dbo = JFactory::getDbo(); $app = JFactory::getApplication(); $input = $app->input; // get request values $checkin_dt = $input->getString('checkindate', ''); $checkin_h = $input->getInt('checkinh', 0); $checkin_m = $input->getInt('checkinm', 0); $checkout_dt = $input->getString('checkoutdate', ''); $checkout_h = $input->getInt('checkouth', 0); $checkout_m = $input->getInt('checkoutm', 0); $categories = $input->getString('categories', ''); $roomsnum = $input->getInt('roomsnum', 1); $adults = $input->get('adults', array(), 'int'); $children = $input->get('children', array(), 'int'); $inquiry = $input->get('inquiry', array(), 'raw'); $ulang = $input->getString('ulang', ''); $timeopst = VikBooking::getTimeOpenStore(); if (empty($checkin_h) && empty($checkout_h) && is_array($timeopst)) { $opent = VikBooking::getHoursMinutes($timeopst[0]); $closet = VikBooking::getHoursMinutes($timeopst[1]); $checkin_h = $opent[0]; $checkin_m = $opent[1]; $checkout_h = $closet[0]; $checkout_m = $closet[1]; } // compose the stay dates $checkin_ts = VikBooking::getDateTimestamp($checkin_dt, $checkin_h, $checkin_m); $checkout_ts = VikBooking::getDateTimestamp($checkout_dt, $checkout_h, $checkout_m); if (empty($checkin_dt) || empty($checkout_dt) || empty($checkin_ts) || empty($checkout_ts)) { // invalid dates VBOHttpDocument::getInstance()->close(400, JText::translate('VBINVALIDDATES')); } // validate guests if (!is_array($adults) || !count($adults)) { // invalid adults VBOHttpDocument::getInstance()->close(400, JText::translate('VBINCONGRDATA')); } // prepare customer information $res_custdata = array(); $t_first_name = ''; $t_last_name = ''; $guest_email = ''; $guest_phone = ''; $guest_country = ''; $guest_extras = array(); $guest_custom = array(); foreach ($inquiry as $info_type => $info_vals) { if (empty($info_type) || $info_type == 'checkbox') { // we ignore any checkbox information continue; } foreach ($info_vals as $info_val) { if (!is_scalar($info_val) || !strlen($info_val)) { // empty or invalid field continue; } if ($info_type == 'nominative') { if (empty($t_first_name)) { $t_first_name = $info_val; $res_custdata[JText::translate('VBNAME')] = $info_val; } else { $t_last_name = $info_val; $res_custdata[JText::translate('VBLNAME')] = $info_val; } } elseif ($info_type == 'email') { $guest_email = $info_val; $res_custdata[JText::translate('ORDER_EMAIL')] = $info_val; } elseif ($info_type == 'phone') { $guest_phone = $info_val; $res_custdata[JText::translate('ORDER_PHONE')] = $info_val; } elseif ($info_type == 'country') { $guest_country = $info_val; $res_custdata[JText::translate('ORDER_STATE')] = $info_val; } elseif ($info_type == 'city') { $guest_extras['city'] = $info_val; $res_custdata[JText::translate('ORDER_CITY')] = $info_val; } else { // we treat this as a custom data field $guest_custom[$info_type] = $info_val; // inject reservation custdata string for this value if ($info_type == 'special_requests') { $res_custdata[JText::translate('ORDER_SPREQUESTS')] = $info_val; } else { $readable_ftype = ucwords(str_replace(array('_', '-'), ' ', $info_type)); $res_custdata[$readable_ftype] = $info_val; } } } } // validate fields if (!count($res_custdata)) { // we received no filled information VBOHttpDocument::getInstance()->close(400, JText::translate('VBINCONGRDATA')); } // build the reservation raw-text for the customer data $res_custdata_str = VikBooking::buildCustData($res_custdata, "\n"); // store the customer record as first thing $cpin = VikBooking::getCPinIstance(); $cpin->setCustomerExtraInfo($guest_extras); $cpin->saveCustomerDetails($t_first_name, $t_last_name, $guest_email, $guest_phone, $guest_country, array()); // build customer object for the inquiry reservation $customer = new stdClass; $customer->name = $t_first_name; $customer->lname = $t_last_name; $customer->email = $guest_email; $customer->phone = $guest_phone; $customer->country = $guest_country; $customer->lang = $ulang; $customer->custdata = $res_custdata_str; $customer->adminnotes = isset($guest_custom['special_requests']) ? $guest_custom['special_requests'] : ''; /** * Always append the originally selected stay dates and guest party to the * administrator notes string so that they can be accessed all the times. */ $customer->adminnotes .= !empty($customer->adminnotes) ? "\n" : ''; $customer->adminnotes .= JText::translate('VBPICKUP') . ': ' . $checkin_dt . "\n"; $customer->adminnotes .= JText::translate('VBRETURN') . ': ' . $checkout_dt . "\n"; $customer->adminnotes .= JText::translate('VBFORMADULTS') . ': ' . array_sum($adults) . "\n"; $customer->adminnotes .= JText::translate('VBFORMCHILDREN') . ': ' . array_sum($children) . "\n"; // prepare response object $response = new stdClass; $response->status = 0; $response->error = ''; // invoke availability helper class $av_helper = VikBooking::getAvailabilityInstance(); // turn flag on to ignore restrictions, as this is an inquiry and we must allocate the booking $av_helper->ignoreRestrictions(true); // increase the default number of back and forth days for alternative date suggestions $av_helper->setBackForthDays(90); // set stay dates $av_helper->setStayDates($checkin_dt, $checkout_dt); // set room parties, but we expect to always have one room for the inquiry foreach ($adults as $k => $num_adults) { $num_children = isset($children[$k]) ? $children[$k] : 0; $av_helper->setRoomParty($num_adults, $num_children); } // load available room rates $room_rates = $av_helper->getRates(); // check if availability errors occurred $has_av_error = strlen($av_helper->getError()); $av_error_code = $av_helper->getErrorCode(); // count total fitting records $tot_records = is_array($room_rates) && !$has_av_error ? count($room_rates) : 0; // build history extra data base object $ymd_stay_dates = $av_helper->getStayDates(); $hist_extra_data = new stdClass; $hist_extra_data->checkin_date = $ymd_stay_dates[0]; $hist_extra_data->checkout_date = $ymd_stay_dates[1]; $hist_extra_data->adults = array_sum($adults); $hist_extra_data->children = array_sum($children); if ($tot_records > 0) { // we can create an inquiry pending reservation for a room rate $inquiry_res_id = 0; foreach ($room_rates as $rid => $rates) { foreach ($rates as $room_rplan) { if (!is_array($room_rplan) || !isset($room_rplan['idroom'])) { continue; } // we grab the cheapest room and cheapest rate plan (the first room-rate plan) $inquiry_res_id = $av_helper->createInquiryReservation($room_rplan, $customer); break; } // make sure to create just one inquiry reservation for the cheapest room available if ($inquiry_res_id) { break; } } if ($inquiry_res_id) { // assign booking to customer $cpin->saveCustomerBooking($inquiry_res_id); // send email notification to admin VikBooking::sendBookingEmail($inquiry_res_id, array('admin')); // trigger SMS sending VikBooking::sendBookingSMS($inquiry_res_id); // booking history (set inquiry availability type to 1) $hist_extra_data->av_type = 1; VikBooking::getBookingHistoryInstance()->setBid($inquiry_res_id)->setExtraData($hist_extra_data)->store('IR', $customer->adminnotes); } // update the response status no matter what $response->status = 1; // output response and terminate the request VBOHttpDocument::getInstance()->json($response); } // rely on suggestions in case of no rooms available if (!is_array($room_rates) || $has_av_error) { // try to get the suggestions when no availability list($alternative_dates, $alternative_parties) = $av_helper->findSuggestions(); $inquiry_res_id = 0; $alt_suggestion = ''; if (count($alternative_dates)) { $inquiry_res_id = $av_helper->allocateAltDatesInquiry($alternative_dates, $customer); $alt_suggestion = JText::translate('VBO_ALT_DATES_INQ'); // set inquiry availability type to 2 for alternative dates $hist_extra_data->av_type = 2; } elseif (count($alternative_parties)) { $inquiry_res_id = $av_helper->allocateAltPartyInquiry($alternative_parties, $customer); $alt_suggestion = JText::translate('VBO_ALT_PARTY_INQ'); // set inquiry availability type to 3 for alternative party $hist_extra_data->av_type = 3; } if ($inquiry_res_id) { // assign booking to customer $cpin->saveCustomerBooking($inquiry_res_id); // send email notification to admin VikBooking::sendBookingEmail($inquiry_res_id, array('admin')); // trigger SMS sending VikBooking::sendBookingSMS($inquiry_res_id); // booking history $history_obj = VikBooking::getBookingHistoryInstance()->setBid($inquiry_res_id); // store first history record $history_obj->store('IR', $customer->adminnotes); // store history record mentioning the suggestion used $history_obj->setExtraData($hist_extra_data)->store('IR', $alt_suggestion); } // update the response status no matter what $response->status = 1; // output response and terminate the request VBOHttpDocument::getInstance()->json($response); } /** * If we reach this point it means that no rooms were available and no rooms/dates could be * suggested as an alternative party. Therefore, we allocate the reservation on a "dummy room". */ $all_rooms = $av_helper->loadRooms(); if (count($all_rooms)) { // grab the first "dummy room" $dummy_room_id = key($all_rooms); $room_rplan = array( 'idroom' => $dummy_room_id, ); // create inquiry reservation for a dummy room $inquiry_res_id = $av_helper->createInquiryReservation($room_rplan, $customer); $alt_suggestion = JText::translate('VBO_ALT_DUMMY_INQ'); // set inquiry availability type to 4 for "dummy room" $hist_extra_data->av_type = 4; if ($inquiry_res_id) { // assign booking to customer $cpin->saveCustomerBooking($inquiry_res_id); // send email notification to admin VikBooking::sendBookingEmail($inquiry_res_id, array('admin')); // trigger SMS sending VikBooking::sendBookingSMS($inquiry_res_id); // booking history $history_obj = VikBooking::getBookingHistoryInstance()->setBid($inquiry_res_id); // store first history record $history_obj->store('IR', $customer->adminnotes); // store history record mentioning the suggestion used $history_obj->setExtraData($hist_extra_data)->store('IR', $alt_suggestion); // update the response status $response->status = 1; // output response and terminate the request VBOHttpDocument::getInstance()->json($response); } } // if not even a dummy room could be allocated, it means Vik Booking isn't set up $response->error = $av_helper->explainErrorCode(); if (empty($response->error)) { $response->error = 'No rooms have been configured on this site yet'; } // output response and terminate the request VBOHttpDocument::getInstance()->json($response); } /** * AJAX endpoint to load the states of a given country. * * @return void * * @since 1.16.0 (J) - 1.6.0 (WP) */ public function states_load_from_country() { $dbo = JFactory::getDbo(); $id_country = VikRequest::getInt('id_country', 0, 'request'); $country_3_code = VikRequest::getString('country_3_code', '', 'request'); $country_2_code = VikRequest::getString('country_2_code', '', 'request'); $country_name = VikRequest::getString('country_name', '', 'request'); if (empty($id_country) && empty($country_3_code) && empty($country_2_code) && empty($country_name)) { VBOHttpDocument::getInstance()->close(500, 'Missing country identifier'); } if (!empty($id_country)) { $q = "SELECT * FROM `#__vikbooking_states` WHERE `id_country`=" . $id_country; $dbo->setQuery($q); $dbo->execute(); if (!$dbo->getNumRows()) { // no records found for this country VBOHttpDocument::getInstance()->json([]); } // output the JSON encoded list of states found VBOHttpDocument::getInstance()->json($dbo->loadAssocList()); } // find country ID by name or code $field_name = $dbo->qn('country_name'); $field_value = $country_name; if (!empty($country_3_code)) { $field_name = $dbo->qn('country_3_code'); $field_value = $country_3_code; } if (!empty($country_2_code)) { $field_name = $dbo->qn('country_2_code'); $field_value = $country_2_code; } $q = "SELECT `id` FROM `#__vikbooking_countries` WHERE {$field_name}=" . $dbo->quote($field_value); $dbo->setQuery($q, 0, 1); $dbo->execute(); if (!$dbo->getNumRows()) { // country not found VBOHttpDocument::getInstance()->close(404, sprintf('Country [%s] not found', $field_value)); } $id_country = $dbo->loadResult(); $q = "SELECT * FROM `#__vikbooking_states` WHERE `id_country`=" . $id_country; $dbo->setQuery($q); $dbo->execute(); if (!$dbo->getNumRows()) { // no records found for this country VBOHttpDocument::getInstance()->json([]); } // output the JSON encoded list of states found VBOHttpDocument::getInstance()->json($dbo->loadAssocList()); } /** * AJAX endpoint to perform the room upgrade operation. * * @return void * * @since 1.16.0 (J) - 1.6.0 (WP) */ public function upgrade_room() { $dbo = JFactory::getDbo(); $bid = VikRequest::getInt('bid', 0, 'request'); $sid = VikRequest::getString('sid', '', 'request'); $ts = VikRequest::getString('ts', '', 'request'); $room_index = VikRequest::getInt('room_index', 0, 'request'); $room_id = VikRequest::getInt('room_id', 0, 'request'); if (empty($room_id)) { VBOHttpDocument::getInstance()->close(500, 'Invalid data provided'); } $q = "SELECT * FROM `#__vikbooking_orders` WHERE `id`={$bid} AND (`sid`=" . $dbo->q($sid) . " OR `idorderota`=" . $dbo->q($sid) . ") AND `ts`=" . $dbo->q($ts) . " AND `status`='confirmed'"; $dbo->setQuery($q, 0, 1); $dbo->execute(); if (!$dbo->getNumRows()) { VBOHttpDocument::getInstance()->close(404, JText::translate('VBORDERNOTFOUND')); } $booking = $dbo->loadAssoc(); $booking_rooms = VikBooking::loadOrdersRoomsData($booking['id']); if (!$booking_rooms || !isset($booking_rooms[$room_index])) { VBOHttpDocument::getInstance()->close(404, JText::translate('VBORDERNOTFOUND')); } // access the room helper object $room_helper = VBORoomHelper::getInstance([ 'booking' => $booking, 'rooms' => $booking_rooms, ]); // load the upgrade options for this booking $upgrade_options = $room_helper->getUpgradeOptions(); if (!$upgrade_options || !isset($upgrade_options['upgrade'][$room_index]) || !isset($upgrade_options['upgrade'][$room_index]['r_costs'][$room_id])) { // the room for the upgrade is not available VBOHttpDocument::getInstance()->close(500, 'Could not upgrade to the selected room. Please reload the page and try again'); } $upgrade_room_rate = $upgrade_options['upgrade'][$room_index]['r_costs'][$room_id]; // calculate the cost difference, if any $current_room_cost = $booking_rooms[$room_index]['cust_cost'] > 0 ? (float)$booking_rooms[$room_index]['cust_cost'] : (float)$booking_rooms[$room_index]['room_cost']; $upgrade_room_cost = $current_room_cost; $upgrade_difference = 0; $upgtax_difference = 0; if ($current_room_cost < $upgrade_room_rate['upgrade_cost']) { // we update the total booking amount only if the upgrade room is more expensive $upgrade_room_cost = $upgrade_room_rate['upgrade_cost']; $upgrade_difference = $upgrade_room_rate['upgrade_cost'] - $current_room_cost; // handle taxes $current_tariff_data = VBORoomHelper::getInstance()->getTariffData($booking_rooms[$room_index]['idtar']); if ($current_tariff_data) { // current room tax $current_cost_plus_tax = VikBooking::sayCostPlusIva($current_room_cost, $current_tariff_data['idprice']); $current_cost_minus_tax = VikBooking::sayCostMinusIva($current_room_cost, $current_tariff_data['idprice']); $current_room_tax = $current_cost_plus_tax - $current_cost_minus_tax; // upgrade room tax $upgrade_cost_plus_tax = VikBooking::sayCostPlusIva($upgrade_room_rate['upgrade_cost'], $upgrade_room_rate['idprice']); $upgrade_cost_minus_tax = VikBooking::sayCostMinusIva($upgrade_room_rate['upgrade_cost'], $upgrade_room_rate['idprice']); $upgrade_room_tax = $upgrade_cost_plus_tax - $upgrade_cost_minus_tax; // calculate tax difference $upgtax_difference = $upgrade_room_tax - $current_room_tax; } } // update booking total amounts as first if ($upgrade_difference > 0) { $upd_booking = new stdClass; $upd_booking->id = $booking['id']; $upd_booking->total = (float)($booking['total'] + $upgrade_difference); $upd_booking->tot_taxes = (float)($booking['tot_taxes'] + $upgtax_difference); $dbo->updateObject('#__vikbooking_orders', $upd_booking, 'id'); } // perform the room upgrade (switch) $broom_record = new stdClass; $broom_record->id = $booking_rooms[$room_index]['id']; $broom_record->idorder = $booking_rooms[$room_index]['idorder']; $broom_record->idroom = $room_id; $broom_record->idtar = $upgrade_room_rate['id']; $broom_record->room_cost = $upgrade_room_cost; $dbo->updateObject('#__vikbooking_ordersrooms', $broom_record, ['id', 'idorder']); // Booking History $hist_descr = !empty($booking_rooms[$room_index]['room_name']) ? $booking_rooms[$room_index]['room_name'] . ' ' : ''; $hist_descr .= '-> ' . $upgrade_options['rooms'][$room_id]['name']; VikBooking::getBookingHistoryInstance()->setBid($booking['id'])->store('UR', $hist_descr); // output the JSON encoded successful response VBOHttpDocument::getInstance()->json(['success' => 1]); } /** * End-point used to ping the execution of the scheduled crontab runners. * * @return void * * @since 1.17 (J) - 1.7 (WP) */ public function crontab() { VBOFactory::getCrontabSimulator()->run(); exit; } }