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

controller.php in VikBooking Hotel Booking Engine & PMS 1.7.1, at site/controller.php

4,581 lines 175.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * @package VikBooking
4 * @subpackage com_vikbooking
5 * @author Alessio Gaggii - e4j - Extensionsforjoomla.com
6 * @copyright Copyright (C) 2018 e4j - Extensionsforjoomla.com. All rights reserved.
7 * @license GNU General Public License version 2 or later; see LICENSE
8 * @link https://vikwp.com
9 */
10
11 defined('ABSPATH') or die('No script kiddies please!');
12
13 jimport('joomla.application.component.controller');
14
15 class VikBookingController extends JControllerVikBooking
16 {
17 public function display($cachable = false, $urlparams = array())
18 {
19 $view = VikRequest::getVar('view', '');
20 switch ($view) {
21 case 'roomslist':
22 case 'roomdetails':
23 case 'searchdetails':
24 case 'loginregister':
25 case 'orderslist':
26 case 'promotions':
27 case 'availability':
28 case 'packageslist':
29 case 'packagedetails':
30 case 'searchsuggestions':
31 case 'booking':
32 case 'operators':
33 case 'tableaux':
34 case 'precheckin':
35 case 'revstay':
36 case 'tinyurl':
37 VikRequest::setVar('view', $view);
38 break;
39 default:
40 VikRequest::setVar('view', 'vikbooking');
41 }
42 parent::display();
43 }
44
45 public function search()
46 {
47 VikRequest::setVar('view', 'search');
48 parent::display();
49 }
50
51 public function showprc()
52 {
53 VikRequest::setVar('view', 'showprc');
54 parent::display();
55 }
56
57 public function oconfirm()
58 {
59 $requirelogin = VikBooking::requireLogin();
60 if($requirelogin) {
61 if(VikBooking::userIsLogged()) {
62 VikRequest::setVar('view', 'oconfirm');
63 } else {
64 VikRequest::setVar('view', 'loginregister');
65 }
66 } else {
67 VikRequest::setVar('view', 'oconfirm');
68 }
69 parent::display();
70 }
71
72 public function register()
73 {
74 $mainframe = JFactory::getApplication();
75 $dbo = JFactory::getDBO();
76
77 //user data
78 $pname = VikRequest::getString('fname', '', 'request');
79 $plname = VikRequest::getString('lname', '', 'request');
80 $pemail = VikRequest::getString('email', '', 'request');
81 $pusername = VikRequest::getString('username', '', 'request');
82 $ppassword = VikRequest::getString('password', '', 'request');
83 $pconfpassword = VikRequest::getString('confpassword', '', 'request');
84 //
85 //order data
86 $pitemid = VikRequest::getString('Itemid', '', 'request');
87 $proomid = VikRequest::getVar('roomid', array());
88 $pdays = VikRequest::getInt('days', '', 'request');
89 $pcheckin = VikRequest::getInt('checkin', '', 'request');
90 $pcheckout = VikRequest::getInt('checkout', '', 'request');
91 $proomsnum = VikRequest::getInt('roomsnum', '', 'request');
92 $padults = VikRequest::getVar('adults', array());
93 $pchildren = VikRequest::getVar('children', array());
94 $rooms = array();
95 $arrpeople = array();
96 for($ir = 1; $ir <= $proomsnum; $ir++) {
97 $ind = $ir - 1;
98 if (!empty($proomid[$ind])) {
99 $q = "SELECT * FROM `#__vikbooking_rooms` WHERE `id`='".intval($proomid[$ind])."' AND `avail`='1';";
100 $dbo->setQuery($q);
101 $dbo->execute();
102 if ($dbo->getNumRows() > 0) {
103 $takeroom = $dbo->loadAssocList();
104 $rooms[$ir] = $takeroom[0];
105 }
106 }
107 if (!empty($padults[$ind])) {
108 $arrpeople[$ir]['adults'] = intval($padults[$ind]);
109 } else {
110 $arrpeople[$ir]['adults'] = 0;
111 }
112 if (!empty($pchildren[$ind])) {
113 $arrpeople[$ir]['children'] = intval($pchildren[$ind]);
114 } else {
115 $arrpeople[$ir]['children'] = 0;
116 }
117 }
118 $prices = array();
119 foreach($rooms as $num => $r) {
120 $ppriceid = VikRequest::getString('priceid'.$num, '', 'request');
121 if (!empty($ppriceid)) {
122 $prices[$num] = intval($ppriceid);
123 }
124 }
125 $selopt = array();
126 $q = "SELECT * FROM `#__vikbooking_optionals` ORDER BY `#__vikbooking_optionals`.`ordering` ASC;";
127 $dbo->setQuery($q);
128 $dbo->execute();
129 if ($dbo->getNumRows() > 0) {
130 $optionals = $dbo->loadAssocList();
131 foreach ($rooms as $num => $r) {
132 foreach ($optionals as $opt) {
133 if (!empty($opt['ageintervals']) && $arrpeople[$num]['children'] > 0) {
134 $tmpvar = VikRequest::getInt('optid'.$num.$opt['id'], []);
135 if (is_array($tmpvar) && $tmpvar) {
136 $optagenames = VikBooking::getOptionIntervalsAges($opt['ageintervals']);
137 $optagepcent = VikBooking::getOptionIntervalsPercentage($opt['ageintervals']);
138 $optageovrct = VikBooking::getOptionIntervalChildOverrides($opt, $arrpeople[$num]['adults'], $arrpeople[$num]['children']);
139 $optorigname = $opt['name'];
140 foreach ($tmpvar as $child_num => $chvar) {
141 $opt['quan'] = $chvar;
142 $opt['chageintv'] = $chvar;
143 //ignore calculation as percetage value to reconstruct the URL
144 $ageintervals_child_string = isset($optageovrct['ageintervals_child' . ($child_num + 1)]) ? $optageovrct['ageintervals_child' . ($child_num + 1)] : $opt['ageintervals'];
145 $optagecosts = VikBooking::getOptionIntervalsCosts($ageintervals_child_string);
146 $opt['cost'] = $optagecosts[($chvar - 1)];
147 $opt['name'] = $optorigname.' ('.$optagenames[($chvar - 1)].')';
148 $selopt[$num][] = $opt;
149 }
150 }
151 } else {
152 $tmpvar = VikRequest::getString('optid'.$num.$opt['id'], '', 'request');
153 if (!empty($tmpvar)) {
154 $opt['quan'] = $tmpvar;
155 $selopt[$num][] = $opt;
156 }
157 }
158 }
159 }
160 }
161 $strpriceid = "";
162 foreach($prices as $num => $pid) {
163 $strpriceid .= ($num > 1 ? "&" : "")."priceid".$num."=".$pid;
164 }
165 $stroptid = "";
166 for($ir = 1; $ir <= $proomsnum; $ir++) {
167 if (isset($selopt[$ir]) && is_array($selopt[$ir])) {
168 foreach($selopt[$ir] as $opt) {
169 if (array_key_exists('chageintv', $opt)) {
170 $stroptid .= "&optid".$ir.$opt['id']."[]=".$opt['chageintv'];
171 } else {
172 $stroptid .= "&optid".$ir.$opt['id']."=".$opt['quan'];
173 }
174 }
175 }
176 }
177 $strroomid = "";
178 foreach ($rooms as $num => $r) {
179 $strroomid .= "&roomid[]=".$r['id'];
180 }
181 $straduchild = "";
182 foreach ($arrpeople as $indroom => $aduch) {
183 $straduchild .= "&adults[]=".$aduch['adults'];
184 $straduchild .= "&children[]=".$aduch['children'];
185 }
186
187 $qstring = $strpriceid.$stroptid.$strroomid.$straduchild."&roomsnum=".$proomsnum."&days=".$pdays."&checkin=".$pcheckin."&checkout=".$pcheckout.(!empty($pitemid) ? "&Itemid=".$pitemid : "");
188 //
189 if (!VikBooking::userIsLogged()) {
190 if (!empty($pname) && !empty($plname) && !empty($pusername) && !empty($pemail) && $ppassword == $pconfpassword) {
191 //save user
192 $newuserid=VikBooking::addJoomlaUser($pname." ".$plname, $pusername, $pemail, $ppassword);
193
194 if ($newuserid!=false && strlen($newuserid)) {
195
196 /**
197 * @wponly the return URL should be passed within the $option array of $app->login()
198 */
199 $redirect_to = JRoute::rewrite('index.php?option=com_vikbooking&task=oconfirm&'.$qstring, false);
200
201 //registration success
202 $credentials = array('username' => $pusername, 'password' => $ppassword );
203 //autologin
204 $mainframe->login($credentials, array('redirect' => $redirect_to));
205 $currentUser = JFactory::getUser();
206 $currentUser->setLastVisit(time());
207 $currentUser->set('guest', 0);
208 //
209 $mainframe->redirect($redirect_to);
210 } else {
211 //error while saving new user
212 VikError::raiseWarning('', JText::translate('VBREGERRSAVING'));
213 $mainframe->redirect(JRoute::rewrite('index.php?option=com_vikbooking&view=loginregister&'.$qstring, false));
214 }
215 } else {
216 //invalid data
217 VikError::raiseWarning('', JText::translate('VBREGERRINSDATA'));
218 $mainframe->redirect(JRoute::rewrite('index.php?option=com_vikbooking&view=loginregister&'.$qstring, false));
219 }
220 } else {
221 //user is already logged in, proceed
222 $mainframe->redirect(JRoute::rewrite('index.php?option=com_vikbooking&task=oconfirm&'.$qstring, false));
223 }
224 }
225
226 public function saveorder()
227 {
228 $dbo = JFactory::getDbo();
229 $session = JFactory::getSession();
230 $app = JFactory::getApplication();
231 $vbo_tn = VikBooking::getTranslator();
232
233 // availability helper
234 $av_helper = VikBooking::getAvailabilityInstance();
235
236 $prooms = VikRequest::getVar('rooms', array());
237 $proomindex = VikRequest::getVar('roomindex', array());
238 $proomsnum = VikRequest::getInt('roomsnum', 0, 'request');
239 $padults = VikRequest::getVar('adults', array());
240 $pchildren = VikRequest::getVar('children', array());
241 $pdays = VikRequest::getInt('days', 0, 'request');
242 $pcouponcode = VikRequest::getString('couponcode', '', 'request');
243 $pcheckin = VikRequest::getInt('checkin', 0, 'request');
244 $pcheckout = VikRequest::getInt('checkout', 0, 'request');
245 $pprtar = VikRequest::getVar('prtar', array());
246 $ppriceid = VikRequest::getVar('priceid', array());
247 $poptionals = VikRequest::getString('optionals', '', 'request');
248 $ptotdue = VikRequest::getString('totdue', '', 'request');
249 $pgpayid = VikRequest::getString('gpayid', '', 'request');
250 $ppkg_id = VikRequest::getInt('pkg_id', '', 'request');
251 $pnodep = VikRequest::getInt('nodep', '', 'request');
252 $split_stay = VikRequest::getVar('split_stay', array());
253 $pitemid = VikRequest::getInt('Itemid', '', 'request');
254
255 $validtoken = true;
256 if (VikBooking::tokenForm()) {
257 $validtoken = false;
258 $pviktoken = VikRequest::getString('viktoken', '', 'request');
259 $sessvbtkn = $session->get('vikbtoken', '');
260 if (!empty($pviktoken) && $sessvbtkn == $pviktoken) {
261 $session->set('vikbtoken', '');
262 $validtoken = true;
263 }
264 if (!$validtoken) {
265 $validtoken = JSession::checkToken();
266 }
267 }
268
269 if (!$validtoken) {
270 showSelectVb(JText::translate('VBINVALIDTOKEN'));
271 return;
272 }
273
274 $q = "SELECT * FROM `#__vikbooking_custfields` ORDER BY `#__vikbooking_custfields`.`ordering` ASC;";
275 $dbo->setQuery($q);
276 $cfields = $dbo->loadAssocList();
277
278 $suffdata = true;
279 $useremail = "";
280 $usercountry = '';
281 $nominatives = [];
282 $t_first_name = '';
283 $t_last_name = '';
284 $phone_number = '';
285 $fieldflags = [];
286 if ($cfields) {
287 $vbo_tn->translateContents($cfields, '#__vikbooking_custfields');
288 foreach ($cfields as $cf) {
289 if (intval($cf['required']) == 1 && $cf['type'] != 'separator' && $cf['type'] != 'state') {
290 $tmpcfval = VikRequest::getString('vbf' . $cf['id'], '', 'request');
291 if (!strlen(str_replace(' ', '', trim($tmpcfval)))) {
292 $suffdata = false;
293 break;
294 }
295 }
296 }
297 //save user email, nominatives, phone number and create custdata array
298 $arrcustdata = [];
299 $arrcfields = [];
300 $emailwasfound = false;
301 foreach ($cfields as $cf) {
302 $user_inp_val = VikRequest::getString('vbf' . $cf['id'], '', 'request');
303 if (intval($cf['isemail']) == 1 && $emailwasfound == false) {
304 $useremail = trim($user_inp_val);
305 $emailwasfound = true;
306 }
307 if ($cf['isnominative'] == 1) {
308 if (strlen(str_replace(' ', '', trim($user_inp_val)))) {
309 $nominatives[] = $user_inp_val;
310 }
311 }
312 if ($cf['isphone'] == 1) {
313 if (strlen(str_replace(' ', '', trim($user_inp_val)))) {
314 $phone_number = $user_inp_val;
315 }
316 }
317 if (!empty($cf['flag'])) {
318 if (strlen(str_replace(' ', '', trim($user_inp_val)))) {
319 $fieldflags[$cf['flag']] = $user_inp_val;
320 }
321 }
322 if ($cf['type'] != 'separator' && $cf['type'] != 'country' && ( $cf['type'] != 'checkbox' || ($cf['type'] == 'checkbox' && intval($cf['required']) != 1) ) ) {
323 // check the input value to store for the customer raw information string
324 $def_user_inp_val = $user_inp_val;
325 // check for state/province field
326 if ($cf['type'] == 'state' && strlen(str_replace(' ', '', trim($user_inp_val)))) {
327 /**
328 * In order to assign the proper state/province to the customer,
329 * we treat this type of field as if it was a "field flag" type.
330 *
331 * @since 1.16.0 (J) - 1.6.0 (WP)
332 */
333 $fieldflags['state'] = $user_inp_val;
334
335 // attempt to save the full state name, not the 2-char code
336 $def_user_inp_val = VBOStateHelper::getFullName($user_inp_val, $usercountry);
337 }
338 $arrcustdata[JText::translate($cf['name'])] = $def_user_inp_val;
339
340 // store the original input value for this custom field ID
341 $arrcfields[$cf['id']] = $user_inp_val;
342 } elseif ($cf['type'] == 'country') {
343 $countryval = $user_inp_val;
344 if (!empty($countryval) && strstr($countryval, '::') !== false) {
345 $countryparts = explode('::', $countryval);
346 $usercountry = $countryparts[0];
347 $arrcustdata[JText::translate($cf['name'])] = $countryparts[1];
348 } else {
349 $arrcustdata[JText::translate($cf['name'])] = '';
350 }
351 }
352 }
353 }
354 if (!empty($phone_number) && !empty($usercountry)) {
355 $phone_number = VikBooking::checkPhonePrefixCountry($phone_number, $usercountry);
356 }
357
358 if ($suffdata !== true) {
359 showSelectVb(JText::translate('VBINSUFDATA'));
360 return;
361 }
362
363 if (count($nominatives) >= 2) {
364 $t_last_name = array_pop($nominatives);
365 $t_first_name = array_pop($nominatives);
366 }
367
368 $secdiff = $pcheckout - $pcheckin;
369 $daysdiff = $secdiff / 86400;
370 if (is_int($daysdiff)) {
371 if ($daysdiff < 1) {
372 $daysdiff = 1;
373 }
374 } else {
375 if ($daysdiff < 1) {
376 $daysdiff = 1;
377 } else {
378 $sum = floor($daysdiff) * 86400;
379 $newdiff = $secdiff - $sum;
380 $maxhmore = VikBooking::getHoursMoreRb() * 3600;
381 if ($maxhmore >= $newdiff) {
382 $daysdiff = floor($daysdiff);
383 } else {
384 $daysdiff = ceil($daysdiff);
385 }
386 }
387 }
388
389 if (!VikBooking::dayValidTs($pdays, $pcheckin, $pcheckout) || $pdays != $daysdiff) {
390 showSelectVb(JText::translate('VBINCONGRDATA'));
391 return;
392 }
393
394 // get check-in and check-out dates information
395 $checkin_info = getdate($pcheckin);
396 $checkout_info = getdate($pcheckout);
397
398 /**
399 * Check split stay information.
400 *
401 * @since 1.16.0 (J) - 1.6.0 (WP)
402 */
403 if (!empty($split_stay) && count($split_stay) == count($prooms) && count($split_stay) == $proomsnum && $proomsnum > 1) {
404 // valid split stay request vars received
405 $split_stay_checkins = [];
406 $split_stay_checkouts = [];
407 $split_stay_nights = [];
408 foreach ($split_stay as $sps_k => $split_room) {
409 // calculate and set the exact check-in and check-out timestamps for this split-room
410 $room_checkin = VikBooking::getDateTimestamp($split_room['checkin'], $checkin_info['hours'], $checkin_info['minutes'], $checkin_info['seconds']);
411 $room_checkout = VikBooking::getDateTimestamp($split_room['checkout'], $checkout_info['hours'], $checkout_info['minutes'], $checkout_info['seconds']);
412 $split_stay_checkins[] = $room_checkin;
413 $split_stay_checkouts[] = $room_checkout;
414 // update split stay information
415 $split_room['checkin_ts'] = $room_checkin;
416 $split_room['checkout_ts'] = $room_checkout;
417 $split_room['nights'] = $av_helper->countNightsOfStay($room_checkin, $room_checkout);
418 $split_stay_nights[] = $split_room['nights'];
419 $split_stay[$sps_k] = $split_room;
420 }
421 // validate minimum and maximum stay dates for the split stay
422 if (empty($split_stay_checkins) || empty($split_stay_checkouts)) {
423 // error
424 showSelectVb('Empty stay dates for split stay rooms');
425 return;
426 }
427 if (array_sum($split_stay_nights) != $daysdiff) {
428 showSelectVb('Invalid sum of total nights for split stay rooms');
429 return;
430 }
431 if (min($split_stay_checkins) != $pcheckin) {
432 // error
433 showSelectVb('Invalid checkin stay date for split stay rooms');
434 return;
435 }
436 if (max($split_stay_checkouts) != $pcheckout) {
437 // error
438 showSelectVb('Invalid checkout stay date for split stay rooms');
439 return;
440 }
441 } else {
442 // unset any possible value as it's invalid
443 $split_stay = [];
444 }
445
446 $currencyname = VikBooking::getCurrencyName();
447 $rooms = [];
448 $prices = [];
449 $arrpeople = [];
450 for ($ir = 1; $ir <= $proomsnum; $ir++) {
451 $ind = $ir - 1;
452 if (!empty($prooms[$ind])) {
453 $q = "SELECT * FROM `#__vikbooking_rooms` WHERE `id`=" . (int)$prooms[$ind] . " AND `avail`='1';";
454 $dbo->setQuery($q);
455 $rdata = $dbo->loadAssoc();
456 if ($rdata) {
457 $rooms[$ir] = $rdata;
458 }
459 }
460 if (!empty($padults[$ind])) {
461 $arrpeople[$ir]['adults'] = intval($padults[$ind]);
462 } else {
463 $arrpeople[$ir]['adults'] = 0;
464 }
465 if (!empty($pchildren[$ind])) {
466 $arrpeople[$ir]['children'] = intval($pchildren[$ind]);
467 } else {
468 $arrpeople[$ir]['children'] = 0;
469 }
470 $arrpeople[$ir]['pets'] = 0;
471 $prices[$ir] = intval($ppriceid[$ind]);
472 }
473 if (count($rooms) != $proomsnum) {
474 VikError::raiseWarning('', JText::translate('VBROOMNOTFND'));
475 $app->redirect(JRoute::rewrite('index.php?option=com_vikbooking'));
476 exit;
477 }
478 $vbo_tn->translateContents($rooms, '#__vikbooking_rooms');
479
480 // package
481 $pkg = [];
482 if (!empty($ppkg_id)) {
483 $pkg = VikBooking::validateRoomPackage($ppkg_id, $rooms, $daysdiff, $pcheckin, $pcheckout);
484 if (!is_array($pkg) || (is_array($pkg) && !(count($pkg) > 0)) ) {
485 if (!is_array($pkg)) {
486 VikError::raiseWarning('', $pkg);
487 }
488 $app->redirect(JRoute::rewrite("index.php?option=com_vikbooking&view=packagedetails&pkgid=".$ppkg_id.(!empty($pitemid) ? "&Itemid=".$pitemid : ""), false));
489 exit;
490 }
491 }
492
493 $tars = [];
494 $validfares = true;
495 foreach ($rooms as $num => $r) {
496 if (count($pkg)) {
497 break;
498 }
499
500 // determine the number of nights of stay and dates to consider
501 $use_los = (int)$daysdiff;
502 $room_checkin = $pcheckin;
503 $room_checkout = $pcheckout;
504 if (!empty($split_stay) && !empty($split_stay[($num - 1)]) && $split_stay[($num - 1)]['idroom'] == $r['id']) {
505 $use_los = (int)$split_stay[($num - 1)]['nights'];
506 $room_checkin = $split_stay[($num - 1)]['checkin_ts'];
507 $room_checkout = $split_stay[($num - 1)]['checkout_ts'];
508 }
509
510 $q = "SELECT * FROM `#__vikbooking_dispcost` WHERE `idroom`=" . (int)$r['id'] . " AND `days`=" . $use_los . " AND `idprice`=" . $prices[$num];
511 $dbo->setQuery($q, 0, 1);
512 $dbo->execute();
513 if (!$dbo->getNumRows()) {
514 $validfares = false;
515 break;
516 }
517 $tar = $dbo->loadAssocList();
518
519 // apply seasonal rates
520 $tar = VikBooking::applySeasonsRoom($tar, $room_checkin, $room_checkout);
521
522 // apply OBP rules
523 $tar = VBORoomHelper::getInstance()->applyOBPRules($tar, $r, $arrpeople[$num]['adults']);
524
525 // push room tariffs
526 $tars[$num] = $tar;
527 }
528
529 if ($validfares !== true) {
530 showSelectVb(JText::translate('VBINCONGRDATAREC'));
531 return;
532 }
533
534 $isdue = 0;
535 $tot_taxes = 0;
536 $tot_city_taxes = 0;
537 $tot_fees = 0;
538 $rooms_costs_map = [];
539 $is_package = (bool)(count($pkg) > 0);
540 if ($is_package === true) {
541 foreach ($rooms as $num => $r) {
542 $pkg_cost = $pkg['pernight_total'] == 1 ? ($pkg['cost'] * $daysdiff) : $pkg['cost'];
543 $pkg_cost = $pkg['perperson'] == 1 ? ($pkg_cost * ($arrpeople[$num]['adults'] > 0 ? $arrpeople[$num]['adults'] : 1)) : $pkg_cost;
544 $cost_plus_tax = VikBooking::sayPackagePlusIva($pkg_cost, $pkg['idiva']);
545 $isdue += $cost_plus_tax;
546 if ($cost_plus_tax == $pkg_cost) {
547 $cost_minus_tax = VikBooking::sayPackageMinusIva($pkg_cost, $pkg['idiva']);
548 $tot_taxes += ($pkg_cost - $cost_minus_tax);
549 } else {
550 $tot_taxes += ($cost_plus_tax - $pkg_cost);
551 }
552 }
553 } else {
554 foreach ($tars as $num => $tar) {
555 $cost_plus_tax = VikBooking::sayCostPlusIva($tar[0]['cost'], $tar[0]['idprice']);
556 $isdue += $cost_plus_tax;
557 if ($cost_plus_tax == $tar[0]['cost']) {
558 $cost_minus_tax = VikBooking::sayCostMinusIva($tar[0]['cost'], $tar[0]['idprice']);
559 $tot_taxes += ($tar[0]['cost'] - $cost_minus_tax);
560 } else {
561 $tot_taxes += ($cost_plus_tax - $tar[0]['cost']);
562 }
563 $rooms_costs_map[$num] = $tar[0]['cost'];
564 }
565 }
566
567 $selopt = [];
568 $optstr = [];
569 $children_age = [];
570 if (!empty($poptionals)) {
571 $stepo = explode(";", $poptionals);
572 foreach ($stepo as $roptkey => $oo) {
573 if (empty($oo)) {
574 continue;
575 }
576 $stept = explode(":", $oo);
577 $rnoid = explode("_", $stept[0]);
578 $room_ind = $rnoid[0] - 1;
579
580 $q = "SELECT * FROM `#__vikbooking_optionals` WHERE `id`=" . (int)$rnoid[1];
581 $dbo->setQuery($q, 0, 1);
582 $actopt = $dbo->loadAssocList();
583 if (!$actopt) {
584 continue;
585 }
586 $vbo_tn->translateContents($actopt, '#__vikbooking_optionals');
587
588 // option params
589 $opt_params = !empty($actopt[0]['oparams']) ? json_decode($actopt[0]['oparams'], true) : [];
590 $opt_params = is_array($opt_params) ? $opt_params : [];
591
592 // determine the number of nights of stay and dates to consider
593 $use_los = (int)$daysdiff;
594 $room_checkin = $pcheckin;
595 $room_checkout = $pcheckout;
596 if (!empty($split_stay) && !empty($split_stay[$room_ind])) {
597 $use_los = (int)$split_stay[$room_ind]['nights'];
598 $room_checkin = $split_stay[$room_ind]['checkin_ts'];
599 $room_checkout = $split_stay[$room_ind]['checkout_ts'];
600 }
601
602 $chvar = '';
603 if (!empty($actopt[0]['ageintervals']) && $arrpeople[$rnoid[0]]['children'] > 0 && strstr($stept[1], '-') != false) {
604 $optagenames = VikBooking::getOptionIntervalsAges($actopt[0]['ageintervals']);
605 $optagepcent = VikBooking::getOptionIntervalsPercentage($actopt[0]['ageintervals']);
606 $optageovrct = VikBooking::getOptionIntervalChildOverrides($actopt[0], $arrpeople[$rnoid[0]]['adults'], $arrpeople[$rnoid[0]]['children']);
607 $child_num = VikBooking::getRoomOptionChildNumber($poptionals, $actopt[0]['id'], $roptkey, $arrpeople[$rnoid[0]]['children']);
608 $optagecosts = VikBooking::getOptionIntervalsCosts(isset($optageovrct['ageintervals_child' . ($child_num + 1)]) ? $optageovrct['ageintervals_child' . ($child_num + 1)] : $actopt[0]['ageintervals']);
609 $agestept = explode('-', $stept[1]);
610 $stept[1] = $agestept[0];
611 $chvar = $agestept[1];
612 if (array_key_exists(($chvar - 1), $optagepcent) && $optagepcent[($chvar - 1)] == 1) {
613 //percentage value of the adults tariff
614 if ($is_package === true) {
615 $optagecosts[($chvar - 1)] = ($pkg['pernight_total'] == 1 ? ($pkg['cost'] * $daysdiff) : $pkg['cost']) * $optagecosts[($chvar - 1)] / 100;
616 } else {
617 $optagecosts[($chvar - 1)] = $tars[$rnoid[0]][0]['cost'] * $optagecosts[($chvar - 1)] / 100;
618 }
619 } elseif (array_key_exists(($chvar - 1), $optagepcent) && $optagepcent[($chvar - 1)] == 2) {
620 //VBO 1.10 - percentage value of room base cost
621 if ($is_package === true) {
622 $optagecosts[($chvar - 1)] = ($pkg['pernight_total'] == 1 ? ($pkg['cost'] * $daysdiff) : $pkg['cost']) * $optagecosts[($chvar - 1)] / 100;
623 } else {
624 $display_rate = isset($tars[$rnoid[0]][0]['room_base_cost']) ? $tars[$rnoid[0]][0]['room_base_cost'] : $tars[$rnoid[0]][0]['cost'];
625 $optagecosts[($chvar - 1)] = $display_rate * $optagecosts[($chvar - 1)] / 100;
626 }
627 }
628 $actopt[0]['chageintv'] = $chvar;
629 $actopt[0]['name'] .= ' ('.$optagenames[($chvar - 1)].')';
630 $actopt[0]['quan'] = $stept[1];
631 $selopt[$rnoid[0]][] = $actopt[0];
632 $selopt['room'.$rnoid[0]] = $selopt['room'.$rnoid[0]].$actopt[0]['id'].":".$stept[1]."-".$chvar.";";
633 $realcost = (intval($actopt[0]['perday']) == 1 ? (floatval($optagecosts[($chvar - 1)]) * $use_los * $stept[1]) : (floatval($optagecosts[($chvar - 1)]) * $stept[1]));
634 $children_age[$rnoid[0]][] = array('ageinterval' => $optagenames[($chvar - 1)], 'age' => '', 'cost' => $realcost);
635 } else {
636 $actopt[0]['quan'] = $stept[1];
637 // VBO 1.11 - options percentage cost of the room total fee
638 if ($is_package === true) {
639 $deftar_basecosts = $pkg['pernight_total'] == 1 ? ($pkg['cost'] * $daysdiff) : $pkg['cost'];
640 } else {
641 $deftar_basecosts = $tars[$rnoid[0]][0]['cost'];
642 }
643 $actopt[0]['cost'] = (int)$actopt[0]['pcentroom'] ? ($deftar_basecosts * $actopt[0]['cost'] / 100) : $actopt[0]['cost'];
644 //
645 $selopt[$rnoid[0]][] = $actopt[0];
646 if (!isset($selopt['room'.$rnoid[0]])) {
647 $selopt['room'.$rnoid[0]] = '';
648 }
649 $selopt['room'.$rnoid[0]] .= $actopt[0]['id'] . ":" . $stept[1] . ";";
650 $realcost = (intval($actopt[0]['perday']) == 1 ? ($actopt[0]['cost'] * $use_los * $stept[1]) : ($actopt[0]['cost'] * $stept[1]));
651 }
652 if (!empty($actopt[0]['maxprice']) && $actopt[0]['maxprice'] > 0 && $realcost > $actopt[0]['maxprice']) {
653 $realcost = $actopt[0]['maxprice'];
654 if (intval($actopt[0]['hmany']) == 1 && intval($stept[1]) > 1) {
655 $realcost = $actopt[0]['maxprice'] * $stept[1];
656 }
657 }
658
659 /**
660 * Count pets, if any.
661 *
662 * @since 1.16.2 (J) - 1.6.2 (WP)
663 */
664 if ($opt_params && isset($opt_params['pet_fee']) && $opt_params['pet_fee']) {
665 $tot_pets = 1;
666 if ($actopt[0]['hmany'] > 0 && $stept[1] > 1) {
667 $tot_pets = (int)$stept[1];
668 }
669 $arrpeople[$rnoid[0]]['pets'] = $tot_pets;
670 }
671
672 $realcost = ($actopt[0]['perperson'] == 1 ? ($realcost * $arrpeople[$rnoid[0]]['adults']) : $realcost);
673 $opt_minus_iva = VikBooking::sayOptionalsMinusIva($realcost, $actopt[0]['idiva']);
674 $tmpopr = VikBooking::sayOptionalsPlusIva($realcost, $actopt[0]['idiva']);
675 if ($actopt[0]['is_citytax'] == 1) {
676 $tot_city_taxes += $opt_minus_iva;
677 } elseif ($actopt[0]['is_fee'] == 1) {
678 $tot_fees += $opt_minus_iva;
679 }
680 // VBO 1.11 - always calculate the amount of tax no matter if this is already a tax or a fee
681 if ($tmpopr == $realcost) {
682 $tot_taxes += ($realcost - $opt_minus_iva);
683 } else {
684 $tot_taxes += ($tmpopr - $realcost);
685 }
686 //
687 $isdue += $tmpopr;
688 $optstr[$rnoid[0]][] = ($stept[1] > 1 ? $stept[1] . " " : "") . $actopt[0]['name'] . ": " . $tmpopr . " " . $currencyname . "\n";
689 }
690 }
691
692 $origtotdue = $isdue;
693 $usedcoupon = false;
694 $strcouponeff = '';
695
696 // access current customer
697 $cpin = VikBooking::getCPinIstance();
698 $customer_details = $cpin->loadCustomerDetails();
699
700 // coupon
701 if (strlen($pcouponcode) && $is_package !== true) {
702 $coupon = VikBooking::getCouponInfo($pcouponcode);
703 $valid_customer_coupon = true;
704 if (!empty($coupon) && !empty($coupon['customers'])) {
705 if (empty($customer_details['id']) || !in_array($customer_details['id'], $coupon['customers'])) {
706 $valid_customer_coupon = false;
707 }
708 }
709 if (!empty($coupon) && $valid_customer_coupon) {
710 $coupondateok = true;
711 if (strlen((string)$coupon['datevalid'])) {
712 $dateparts = explode("-", $coupon['datevalid']);
713 $pickinfo = getdate($pcheckin);
714 $dropinfo = getdate($pcheckout);
715 $checkpick = mktime(0, 0, 0, $pickinfo['mon'], $pickinfo['mday'], $pickinfo['year']);
716 $checkdrop = mktime(0, 0, 0, $dropinfo['mon'], $dropinfo['mday'], $dropinfo['year']);
717 if (!($checkpick >= $dateparts[0] && $checkpick <= $dateparts[1] && $checkdrop >= $dateparts[0] && $checkdrop <= $dateparts[1])) {
718 $coupondateok = false;
719 }
720 }
721 if (!empty($coupon['minlos']) && $coupon['minlos'] > $daysdiff) {
722 $coupondateok = false;
723 }
724 if ($coupondateok) {
725 $couponroomok = true;
726 if (!$coupon['allvehicles']) {
727 foreach ($rooms as $num => $r) {
728 if (!(preg_match("/;".$r['id'].";/i", $coupon['idrooms']))) {
729 $couponroomok = false;
730 break;
731 }
732 }
733 }
734 if ($couponroomok) {
735 $coupontotok = true;
736 if (strlen((string)$coupon['mintotord'])) {
737 if ($isdue < $coupon['mintotord']) {
738 $coupontotok = false;
739 }
740 }
741 if ($coupon['maxtotord'] > 0 && $isdue > $coupon['maxtotord']) {
742 $coupontotok = false;
743 }
744
745 /**
746 * Trigger event to allow third-party plugins to implement additional coupon validations or manipulation.
747 *
748 * @since 1.16.7 (J) - 1.6.7 (WP)
749 */
750 if ($coupontotok) {
751 $coupon_validation = VBOFactory::getPlatform()->getDispatcher()->filter('onValidateCouponCode', [&$coupon]);
752 if (is_array($coupon_validation) && in_array(false, $coupon_validation, true)) {
753 $coupontotok = false;
754 }
755 }
756
757 if ($coupontotok) {
758 $usedcoupon = true;
759 if ($coupon['percentot'] == 1) {
760 // percent value
761 $minuscoupon = 100 - $coupon['value'];
762 /**
763 * We allow coupon codes to be applied on the entire reservation or as always just on the total minus mandatory taxes.
764 *
765 * @since 1.13.5 (J) - 1.3.5 (WP)
766 * @since 1.14.3 (J) - 1.4.3 (WP) we also exclude the amount of taxes beside the mandatory fees.
767 * @since 1.16.0 (J) - 1.6.0 (WP) taxes are proportionally calculated when coupon before tax.
768 * @since 1.16.8 (J) - 1.6.8 (WP) with coupon before taxes, discounted amount calculation is an equal subtration.
769 */
770 $prev_isdue = $isdue;
771 $tot_net = ($isdue - $tot_taxes - $tot_city_taxes - $tot_fees);
772 $coupondiscount = ($coupon['excludetaxes'] ? $tot_net : $isdue) * $coupon['value'] / 100;
773 $isdue = ($coupon['excludetaxes'] ? $tot_net : $isdue) * $minuscoupon / 100;
774 $tot_taxes = $coupon['excludetaxes'] ? ($tot_taxes * ($tot_net - $coupondiscount) / $tot_net) : $tot_taxes;
775 $isdue += $coupon['excludetaxes'] ? ($tot_taxes + $tot_city_taxes + $tot_fees) : 0;
776 $coupondiscount = abs($prev_isdue - $isdue);
777 } else {
778 // total value
779 $coupondiscount = $coupon['value'];
780 // isdue : taxes = coupon_discount : x
781 $tax_prop = $tot_taxes * $coupon['value'] / $isdue;
782 $tot_taxes -= $tax_prop;
783 $tot_taxes = $tot_taxes < 0 ? 0 : $tot_taxes;
784 $isdue -= $coupon['value'];
785 $isdue = $isdue < 0 ? 0 : $isdue;
786 }
787 $strcouponeff = $coupon['id'].';'.$coupondiscount.';'.$coupon['code'];
788 }
789 }
790 }
791 }
792 }
793
794 $strisdue = number_format($isdue, 2) . 'vikbooking';
795 $ptotdue = number_format($ptotdue, 2) . 'vikbooking';
796 if ($strisdue != $ptotdue) {
797 showSelectVb(JText::translate('VBINCONGRTOT'));
798 return;
799 }
800
801 // pay full amount cookie (2 weeks)
802 $nodep_set = !empty($pnodep) ? '1' : '0';
803 $nodep_time_set = !empty($pnodep) ? (time() + (86400 * 14)) : (time() - (86400 * 14));
804 $cookie = JFactory::getApplication()->input->cookie;
805 VikRequest::setCookie('vboFA', $nodep_set, $nodep_time_set, '/');
806
807 // modify booking
808 $mod_booking = [];
809 $skip_busy_ids = [];
810 $cur_mod = $session->get('vboModBooking', '');
811 if (is_array($cur_mod) && $cur_mod) {
812 $mod_booking = $cur_mod;
813 $skip_busy_ids = VikBooking::loadBookingBusyIds($mod_booking['id']);
814 }
815
816 $nowts = time();
817 $checkts = $nowts;
818 $today_bookings = VikBooking::todayBookings();
819 if ($today_bookings) {
820 $checkts = mktime(0, 0, 0, date('n'), date('j'), date('Y'));
821 }
822 if (!($checkts <= $pcheckin && $checkts < $pcheckout && $pcheckin < $pcheckout)) {
823 showSelectVb(JText::translate('VBINVALIDDATES'));
824 return;
825 }
826
827 $roomsavailable = true;
828 foreach ($rooms as $num => $r) {
829 // determine the number of nights of stay and dates to consider
830 $use_los = (int)$daysdiff;
831 $room_checkin = $pcheckin;
832 $room_checkout = $pcheckout;
833 if (!empty($split_stay) && !empty($split_stay[($num - 1)]) && $split_stay[($num - 1)]['idroom'] == $r['id']) {
834 $use_los = (int)$split_stay[($num - 1)]['nights'];
835 $room_checkin = $split_stay[($num - 1)]['checkin_ts'];
836 $room_checkout = $split_stay[($num - 1)]['checkout_ts'];
837 }
838
839 if (!VikBooking::roomNotLocked($r['id'], $r['units'], $room_checkin, $room_checkout, true, $skip_busy_ids)) {
840 $roomsavailable = false;
841 break;
842 }
843 }
844 if ($roomsavailable !== true) {
845 showSelectVb(JText::translate('VBROOMBOOKEDBYOTHER'));
846 return;
847 }
848
849 // save in session the checkin and checkout time of the reservation made
850 $session->set('vikbooking_order_checkin', $pcheckin);
851 $session->set('vikbooking_order_checkout', $pcheckout);
852
853 // handle booking sid and customer information summary string
854 $sid = $mod_booking ? $mod_booking['sid'] : VikBooking::getSecretLink();
855 $custdata = VikBooking::buildCustData($arrcustdata, "\r\n");
856
857 if (VBOPlatformDetection::isWordPress()) {
858 $viklink = JURI::root() . "index.php?option=com_vikbooking&view=booking&sid=" . $sid . "&ts=" . $nowts . (!empty($pnodep) ? "&nodep=".$pnodep : "") . (!empty($pitemid) ? "&Itemid=" . $pitemid : "");
859 } else {
860 $bestitemid = VikBooking::findProperItemIdType(array('booking'));
861 $viklink = VikBooking::externalroute("index.php?option=com_vikbooking&view=booking&sid=" . $sid . "&ts=" . $nowts . (!empty($pnodep) ? "&nodep=".$pnodep : ""), false, (!empty($bestitemid) ? $bestitemid : null));
862 }
863
864 $admail = VikBooking::getAdminMail();
865 $ftitle = VikBooking::getFrontTitle();
866 $pricestr = [];
867 if ($is_package === true) {
868 foreach ($rooms as $num => $r) {
869 $pkg_cost = $pkg['pernight_total'] == 1 ? ($pkg['cost'] * $daysdiff) : $pkg['cost'];
870 $pkg_cost = $pkg['perperson'] == 1 ? ($pkg_cost * ($arrpeople[$num]['adults'] > 0 ? $arrpeople[$num]['adults'] : 1)) : $pkg_cost;
871 $cost_plus_tax = VikBooking::sayPackagePlusIva($pkg_cost, $pkg['idiva']);
872 $pricestr[$num] = $pkg['name'].": ".$cost_plus_tax." ".$currencyname;
873 }
874 } else {
875 foreach ($tars as $num => $tar) {
876 $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'] : "");
877 }
878 }
879
880 $currentUser = JFactory::getUser();
881 $langtag = $vbo_tn->current_lang;
882 $vcmchanneldata = $session->get('vcmChannelData', '');
883 $vcmchanneldata = !empty($vcmchanneldata) && is_array($vcmchanneldata) && count($vcmchanneldata) > 0 ? $vcmchanneldata : '';
884
885 // attempt to save customer
886 $cpin->setCustomerExtraInfo($fieldflags);
887 $cpin->saveCustomerDetails($t_first_name, $t_last_name, $useremail, $phone_number, $usercountry, $arrcfields);
888
889 // collect all room IDs involved
890 $rooms_involved = [];
891 foreach ($rooms as $room_booked) {
892 if (!in_array($room_booked['id'], $rooms_involved)) {
893 $rooms_involved[] = $room_booked['id'];
894 }
895 }
896
897 $must_payment = $mod_booking ? false : VikBooking::areTherePayments($rooms_involved);
898 $payment = [];
899 if ($must_payment) {
900 $payment = VikBooking::getPayment($pgpayid);
901 }
902 if ($must_payment && empty($payment)) {
903 // error, payment was not selected
904 VikError::raiseWarning('', JText::translate('ERRSELECTPAYMENT'));
905
906 // build redirect URI values
907 $redirect_uri_vals = [
908 'option' => 'com_vikbooking',
909 'task' => 'oconfirm',
910 ];
911
912 foreach ($prices as $num => $pid) {
913 $redirect_uri_vals['priceid' . $num] = $pid;
914 }
915
916 for ($ir = 1; $ir <= $proomsnum; $ir++) {
917 if (isset($selopt[$ir]) && is_array($selopt[$ir])) {
918 foreach ($selopt[$ir] as $opt) {
919 if (array_key_exists('chageintv', $opt)) {
920 if (!isset($redirect_uri_vals['optid' . $ir . $opt['id']])) {
921 $redirect_uri_vals['optid' . $ir . $opt['id']] = [];
922 }
923 $redirect_uri_vals['optid' . $ir . $opt['id']][] = $opt['chageintv'];
924 } else {
925 $redirect_uri_vals['optid' . $ir . $opt['id']] = $opt['quan'];
926 }
927 }
928 }
929 }
930
931 $redirect_uri_vals['roomid'] = [];
932 foreach ($rooms as $num => $r) {
933 $redirect_uri_vals['roomid'][] = $r['id'];
934 }
935
936 $redirect_uri_vals['adults'] = [];
937 $redirect_uri_vals['children'] = [];
938 foreach ($arrpeople as $indroom => $aduch) {
939 $redirect_uri_vals['adults'][] = $aduch['adults'];
940 $redirect_uri_vals['children'][] = $aduch['children'];
941 }
942
943 $redirect_uri_vals['roomsnum'] = $proomsnum;
944 $redirect_uri_vals['days'] = $pdays;
945 $redirect_uri_vals['checkin'] = $pcheckin;
946 $redirect_uri_vals['checkout'] = $pcheckout;
947 if (!empty($split_stay)) {
948 $redirect_uri_vals['split_stay'] = $split_stay;
949 }
950 $redirect_uri_vals['Itemid'] = !empty($pitemid) ? $pitemid : null;
951
952 $app->redirect(JRoute::rewrite('index.php?' . http_build_query($redirect_uri_vals), false));
953 exit;
954 }
955
956 // turnover seconds
957 $turnover_secs = VikBooking::getHoursRoomAvail() * 3600;
958 $realback = $turnover_secs + $pcheckout;
959
960 // push data to tracker for conversion
961 $vbo_tracker = VikBooking::getTracker();
962 $vbo_tracker->pushDates($pcheckin, $pcheckout, $pdays)->pushParty($arrpeople)->pushData('idcustomer', $cpin->getNewCustomerId());
963
964 if (!$mod_booking && ((!empty($payment) && intval($payment['setconfirmed']) == 1) || !$must_payment || ($usedcoupon && $isdue <= 0))) {
965 // 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
966 $arrbusy = [];
967 foreach ($rooms as $num => $r) {
968 // determine the number of nights of stay and dates to consider
969 $room_checkin = $pcheckin;
970 $room_checkout = $pcheckout;
971 $room_realback = $realback;
972 if (!empty($split_stay) && !empty($split_stay[($num - 1)]) && $split_stay[($num - 1)]['idroom'] == $r['id']) {
973 $room_checkin = $split_stay[($num - 1)]['checkin_ts'];
974 $room_checkout = $split_stay[($num - 1)]['checkout_ts'];
975 $room_realback = $turnover_secs + $room_checkout;
976 }
977
978 $busy_record = new stdClass;
979 $busy_record->idroom = $r['id'];
980 $busy_record->checkin = $room_checkin;
981 $busy_record->checkout = $room_checkout;
982 $busy_record->realback = $room_realback;
983
984 $dbo->insertObject('#__vikbooking_busy', $busy_record, 'id');
985
986 if (!isset($busy_record->id)) {
987 showSelectVb('Critical error while occupying the rooms. Please try again');
988 return;
989 }
990
991 $arrbusy[$num] = $busy_record->id;
992 }
993
994 // store booking
995 $booking_record = new stdClass;
996 $booking_record->custdata = $custdata;
997 $booking_record->ts = $nowts;
998 $booking_record->status = 'confirmed';
999 $booking_record->days = $pdays;
1000 $booking_record->checkin = $pcheckin;
1001 $booking_record->checkout = $pcheckout;
1002 $booking_record->custmail = $useremail;
1003 $booking_record->sid = $sid;
1004 $booking_record->idpayment = !empty($payment) ? ($payment['id'] . '=' . $payment['name']) : null;
1005 $booking_record->ujid = $currentUser->id;
1006 $booking_record->coupon = $usedcoupon === true ? $strcouponeff : null;
1007 $booking_record->roomsnum = count($rooms);
1008 $booking_record->total = (float)$isdue;
1009 $booking_record->channel = is_array($vcmchanneldata) && !empty($vcmchanneldata['name']) ? $vcmchanneldata['name'] : null;
1010 $booking_record->lang = $langtag;
1011 $booking_record->country = !empty($usercountry) ? $usercountry : null;
1012 $booking_record->tot_taxes = (float)$tot_taxes;
1013 $booking_record->tot_city_taxes = (float)$tot_city_taxes;
1014 $booking_record->tot_fees = (float)$tot_fees;
1015 $booking_record->phone = $phone_number;
1016 $booking_record->pkg = $is_package === true ? (int)$pkg['id'] : null;
1017 $booking_record->split_stay = !empty($split_stay) ? 1 : 0;
1018
1019 $dbo->insertObject('#__vikbooking_orders', $booking_record, 'id');
1020
1021 if (!isset($booking_record->id)) {
1022 showSelectVb('Critical error while saving the booking. Please try again');
1023 return;
1024 }
1025 $neworderid = $booking_record->id;
1026
1027 // ConfirmationNumber
1028 $confirmnumber = VikBooking::generateConfirmNumber($neworderid, true);
1029
1030 // assign room specific unit
1031 $set_room_indexes = (VikBooking::autoRoomUnit() || (count($proomindex) == count($rooms)));
1032 $room_indexes_usemap = [];
1033 $room_indexes_forcemap = [];
1034
1035 foreach ($rooms as $num => $r) {
1036 $q = "INSERT INTO `#__vikbooking_ordersbusy` (`idorder`,`idbusy`) VALUES(" . (int)$neworderid . ", " . (int)$arrbusy[$num] . ");";
1037 $dbo->setQuery($q);
1038 $dbo->execute();
1039 $json_ch_age = '';
1040 if (array_key_exists($num, $children_age)) {
1041 $json_ch_age = json_encode($children_age[$num]);
1042 }
1043 // assign room specific unit
1044 $room_indexes = $set_room_indexes === true ? VikBooking::getRoomUnitNumsAvailable(array('id' => $neworderid, 'checkin' => $pcheckin, 'checkout' => $pcheckout), $r['id']) : array();
1045 $use_ind_key = 0;
1046 $force_rindex = 0;
1047 if ($room_indexes && isset($room_indexes_forcemap[$r['id']])) {
1048 // an index for this same room was forced already, reset the values
1049 foreach ($room_indexes as $av_key => $av_index) {
1050 if (in_array((int)$av_index, $room_indexes_forcemap[$r['id']])) {
1051 unset($room_indexes[$av_key]);
1052 }
1053 }
1054 $room_indexes = array_values($room_indexes);
1055 }
1056 if ($room_indexes) {
1057 if (count($proomindex) == count($rooms) && !empty($proomindex[($num - 1)])) {
1058 // exact distinctive feature index selected
1059 foreach ($room_indexes as $av_index) {
1060 if ((int)$av_index == (int)$proomindex[($num - 1)]) {
1061 // requested index is available
1062 $force_rindex = (int)$proomindex[($num - 1)];
1063 if (isset($room_indexes_forcemap[$r['id']]) && in_array($force_rindex, $room_indexes_forcemap[$r['id']])) {
1064 // cannot book the same unit twice
1065 $force_rindex = 0;
1066 continue;
1067 }
1068 break;
1069 }
1070 }
1071 if ($force_rindex) {
1072 // store the forced index for any possible equal room booked later in the same loop
1073 if (!isset($room_indexes_forcemap[$r['id']])) {
1074 $room_indexes_forcemap[$r['id']] = [];
1075 }
1076 array_push($room_indexes_forcemap[$r['id']], $force_rindex);
1077 }
1078 }
1079 if (!array_key_exists($r['id'], $room_indexes_usemap)) {
1080 $room_indexes_usemap[$r['id']] = $use_ind_key;
1081 } else {
1082 $use_ind_key = $room_indexes_usemap[$r['id']];
1083 }
1084 if (isset($room_indexes[$use_ind_key])) {
1085 $rooms[$num]['roomindex'] = (int)$room_indexes[$use_ind_key];
1086 }
1087 }
1088 //
1089 $pkg_cost = 0;
1090 if ($is_package === true) {
1091 $pkg_cost = $pkg['pernight_total'] == 1 ? ($pkg['cost'] * $daysdiff) : $pkg['cost'];
1092 $pkg_cost = $pkg['perperson'] == 1 ? ($pkg_cost * ($arrpeople[$num]['adults'] > 0 ? $arrpeople[$num]['adults'] : 1)) : $pkg_cost;
1093 // $pkg_cost = VikBooking::sayPackagePlusIva($pkg_cost, $pkg['idiva']);
1094 }
1095
1096 $oroom_record = new stdClass;
1097 $oroom_record->idorder = (int)$neworderid;
1098 $oroom_record->idroom = (int)$r['id'];
1099 $oroom_record->adults = (int)$arrpeople[$num]['adults'];
1100 $oroom_record->children = (int)$arrpeople[$num]['children'];
1101 $oroom_record->pets = isset($arrpeople[$num]['pets']) ? (int)$arrpeople[$num]['pets'] : 0;
1102 $oroom_record->idtar = (int)$tars[$num][0]['id'];
1103 $oroom_record->optionals = isset($selopt['room'.$num]) ? $selopt['room'.$num] : null;
1104 $oroom_record->childrenage = (!empty($json_ch_age) ? $json_ch_age : null);
1105 $oroom_record->t_first_name = $t_first_name;
1106 $oroom_record->t_last_name = $t_last_name;
1107 $oroom_record->roomindex = null;
1108 if ($force_rindex) {
1109 $oroom_record->roomindex = $force_rindex;
1110 } elseif ($room_indexes && isset($room_indexes[$use_ind_key])) {
1111 $oroom_record->roomindex = (int)$room_indexes[$use_ind_key];
1112 }
1113 $oroom_record->pkg_id = ($is_package === true ? (int)$pkg['id'] : null);
1114 $oroom_record->pkg_name = ($is_package === true ? $pkg['name'] : null);
1115 $oroom_record->cust_cost = ($is_package === true ? $pkg_cost : null);
1116 $oroom_record->cust_idiva = ($is_package === true ? (int)$pkg['idiva'] : null);
1117 $oroom_record->room_cost = (array_key_exists($num, $rooms_costs_map) ? $rooms_costs_map[$num] : null);
1118
1119 $dbo->insertObject('#__vikbooking_ordersrooms', $oroom_record, 'id');
1120
1121 if ($room_indexes) {
1122 $room_indexes_usemap[$r['id']]++;
1123 }
1124 }
1125
1126 if (!empty($split_stay)) {
1127 // save transient on db for split stay information
1128 VBOFactory::getConfig()->set('split_stay_' . $neworderid, json_encode($split_stay));
1129 }
1130
1131 // customer booking
1132 $cpin->saveCustomerBooking($neworderid);
1133
1134 if ($usedcoupon === true && $coupon['type'] == 2) {
1135 $q = "DELETE FROM `#__vikbooking_coupons` WHERE `id`='".$coupon['id']."';";
1136 $dbo->setQuery($q);
1137 $dbo->execute();
1138 }
1139
1140 // check if some of the rooms booked have shared calendars
1141 VikBooking::updateSharedCalendars($neworderid, array(), $pcheckin, $pcheckout);
1142
1143 // send email notification to guest and admin
1144 VikBooking::sendBookingEmail($neworderid, ['guest', 'admin']);
1145
1146 //SMS
1147 VikBooking::sendBookingSMS($neworderid);
1148
1149 //Booking History
1150 VikBooking::getBookingHistoryInstance()->setBid($neworderid)->store('NC', 'IP: '.VikRequest::getVar('REMOTE_ADDR', '', 'server'));
1151
1152 //invoke VikChannelManager
1153 if (is_file(VCM_SITE_PATH . DIRECTORY_SEPARATOR . "helpers" . DIRECTORY_SEPARATOR . "synch.vikbooking.php")) {
1154 require_once(VCM_SITE_PATH . DIRECTORY_SEPARATOR . "helpers" . DIRECTORY_SEPARATOR . "synch.vikbooking.php");
1155 $vcm = new SynchVikBooking($neworderid);
1156 $vcm->setPushType('new')->sendRequest();
1157 }
1158
1159 // VBO 1.11 - push data to tracker for conversion
1160 $vbo_tracker->pushData('idorder', $neworderid)->closeTrack();
1161 $vbo_tracker->resetTrack();
1162
1163 $app->redirect(JRoute::rewrite("index.php?option=com_vikbooking&view=booking&sid=" . $sid . "&ts=" . $nowts . (!empty($pnodep) ? "&nodep=".$pnodep : "") . (!empty($pitemid) ? "&Itemid=" . $pitemid : ""), false));
1164 } elseif ($mod_booking) {
1165 // booking modification statement
1166 // get current orders-busy relations
1167 $old_busy_ids = [];
1168 $q = "SELECT `idbusy` FROM `#__vikbooking_ordersbusy` WHERE `idorder`=".(int)$mod_booking['id'].";";
1169 $dbo->setQuery($q);
1170 $getbusy = $dbo->loadAssocList();
1171 if ($getbusy) {
1172 foreach ($getbusy as $gbu) {
1173 array_push($old_busy_ids, $gbu['idbusy']);
1174 }
1175 }
1176 //remove current busy records
1177 if (count($old_busy_ids)) {
1178 $q = "DELETE FROM `#__vikbooking_busy` WHERE `id` IN (".implode(', ', $old_busy_ids).");";
1179 $dbo->setQuery($q);
1180 $dbo->execute();
1181 }
1182 $q = "DELETE FROM `#__vikbooking_ordersbusy` WHERE `idorder`=".(int)$mod_booking['id'].";";
1183 $dbo->setQuery($q);
1184 $dbo->execute();
1185 //get current rooms (for VCM and for composing the log)
1186 $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;";
1187 $dbo->setQuery($q);
1188 $dbo->execute();
1189 $old_ordersrooms = $dbo->loadAssocList();
1190 $mod_booking['rooms_info'] = $old_ordersrooms;
1191 //remove current rooms
1192 $q = "DELETE FROM `#__vikbooking_ordersrooms` WHERE `idorder`=".(int)$mod_booking['id'].";";
1193 $dbo->setQuery($q);
1194 $dbo->execute();
1195 //update the booking by creating first the new busy records
1196 $arrbusy = [];
1197 foreach ($rooms as $num => $r) {
1198 $q = "INSERT INTO `#__vikbooking_busy` (`idroom`,`checkin`,`checkout`,`realback`) VALUES(".(int)$r['id'].", ".$dbo->quote($pcheckin).", ".$dbo->quote($pcheckout).", ".$dbo->quote($realback).");";
1199 $dbo->setQuery($q);
1200 $dbo->execute();
1201 $lid = $dbo->insertid();
1202 $arrbusy[$num] = $lid;
1203 }
1204 // assign room specific unit
1205 $set_room_indexes = (VikBooking::autoRoomUnit() || (count($proomindex) == count($rooms)));
1206 $room_indexes_usemap = [];
1207 $room_indexes_forcemap = [];
1208 //create the new rooms and orders-busy relations
1209 foreach ($rooms as $num => $r) {
1210 $q = "INSERT INTO `#__vikbooking_ordersbusy` (`idorder`,`idbusy`) VALUES(".(int)$mod_booking['id'].", ".(int)$arrbusy[$num].");";
1211 $dbo->setQuery($q);
1212 $dbo->execute();
1213 $json_ch_age = '';
1214 if (array_key_exists($num, $children_age)) {
1215 $json_ch_age = json_encode($children_age[$num]);
1216 }
1217 // assign room specific unit
1218 $room_indexes = $set_room_indexes === true ? VikBooking::getRoomUnitNumsAvailable(array('id' => $mod_booking['id'], 'checkin' => $pcheckin, 'checkout' => $pcheckout), $r['id']) : array();
1219 $use_ind_key = 0;
1220 $force_rindex = 0;
1221 if ($room_indexes && isset($room_indexes_forcemap[$r['id']])) {
1222 // an index for this same room was forced already, reset the values
1223 foreach ($room_indexes as $av_key => $av_index) {
1224 if (in_array((int)$av_index, $room_indexes_forcemap[$r['id']])) {
1225 unset($room_indexes[$av_key]);
1226 }
1227 }
1228 $room_indexes = array_values($room_indexes);
1229 }
1230 if ($room_indexes) {
1231 if (count($proomindex) == count($rooms) && !empty($proomindex[($num - 1)])) {
1232 // exact distinctive feature index selected
1233 foreach ($room_indexes as $av_index) {
1234 if ((int)$av_index == (int)$proomindex[($num - 1)]) {
1235 // requested index is available
1236 $force_rindex = (int)$proomindex[($num - 1)];
1237 if (isset($room_indexes_forcemap[$r['id']]) && in_array($force_rindex, $room_indexes_forcemap[$r['id']])) {
1238 // cannot book the same unit twice
1239 $force_rindex = 0;
1240 continue;
1241 }
1242 break;
1243 }
1244 }
1245 if ($force_rindex) {
1246 // store the forced index for any possible equal room booked later in the same loop
1247 if (!isset($room_indexes_forcemap[$r['id']])) {
1248 $room_indexes_forcemap[$r['id']] = [];
1249 }
1250 array_push($room_indexes_forcemap[$r['id']], $force_rindex);
1251 }
1252 }
1253 if (!array_key_exists($r['id'], $room_indexes_usemap)) {
1254 $room_indexes_usemap[$r['id']] = $use_ind_key;
1255 } else {
1256 $use_ind_key = $room_indexes_usemap[$r['id']];
1257 }
1258 if (isset($room_indexes[$use_ind_key])) {
1259 $rooms[$num]['roomindex'] = (int)$room_indexes[$use_ind_key];
1260 }
1261 }
1262 //
1263 $pkg_cost = 0;
1264 if ($is_package === true) {
1265 $pkg_cost = $pkg['pernight_total'] == 1 ? ($pkg['cost'] * $daysdiff) : $pkg['cost'];
1266 $pkg_cost = $pkg['perperson'] == 1 ? ($pkg_cost * ($arrpeople[$num]['adults'] > 0 ? $arrpeople[$num]['adults'] : 1)) : $pkg_cost;
1267 // $pkg_cost = VikBooking::sayPackagePlusIva($pkg_cost, $pkg['idiva']);
1268 }
1269
1270 $oroom_record = new stdClass;
1271 $oroom_record->idorder = (int)$mod_booking['id'];
1272 $oroom_record->idroom = (int)$r['id'];
1273 $oroom_record->adults = (int)$arrpeople[$num]['adults'];
1274 $oroom_record->children = (int)$arrpeople[$num]['children'];
1275 $oroom_record->pets = isset($arrpeople[$num]['pets']) ? (int)$arrpeople[$num]['pets'] : 0;
1276 $oroom_record->idtar = (int)$tars[$num][0]['id'];
1277 $oroom_record->optionals = isset($selopt['room'.$num]) ? $selopt['room'.$num] : null;
1278 $oroom_record->childrenage = (!empty($json_ch_age) ? $json_ch_age : null);
1279 $oroom_record->t_first_name = $t_first_name;
1280 $oroom_record->t_last_name = $t_last_name;
1281 $oroom_record->roomindex = null;
1282 if ($force_rindex) {
1283 $oroom_record->roomindex = $force_rindex;
1284 } elseif ($room_indexes && isset($room_indexes[$use_ind_key])) {
1285 $oroom_record->roomindex = (int)$room_indexes[$use_ind_key];
1286 }
1287 $oroom_record->pkg_id = ($is_package === true ? (int)$pkg['id'] : null);
1288 $oroom_record->pkg_name = ($is_package === true ? $pkg['name'] : null);
1289 $oroom_record->cust_cost = ($is_package === true ? $pkg_cost : null);
1290 $oroom_record->cust_idiva = ($is_package === true ? (int)$pkg['idiva'] : null);
1291 $oroom_record->room_cost = (array_key_exists($num, $rooms_costs_map) ? $rooms_costs_map[$num] : null);
1292
1293 $dbo->insertObject('#__vikbooking_ordersrooms', $oroom_record, 'id');
1294
1295 if ($room_indexes) {
1296 $room_indexes_usemap[$r['id']]++;
1297 }
1298 }
1299
1300 // update the booking record (do not touch information like sid, confirmnumber, payment method etc..)
1301 $logmod = VikBooking::getLogBookingModification($mod_booking);
1302 $mod_notes = $logmod.(!empty($mod_booking['adminnotes']) ? "\n\n".$mod_booking['adminnotes'] : '');
1303 // if old total lower than new total, increment paymcount to allow a new payment (if configuration setting enabled)
1304 $mod_paymcount = (int)$mod_booking['paymcount'];
1305 if ($mod_booking['total'] < $isdue) {
1306 $mod_paymcount++;
1307 }
1308
1309 $q = "UPDATE `#__vikbooking_orders` SET `custdata`=".$dbo->quote($custdata).",`ts`='".$nowts."',`days`=".$dbo->quote($pdays).",`checkin`=".$dbo->quote($pcheckin).",`checkout`=".$dbo->quote($pcheckout).",`custmail`=".$dbo->quote($useremail).",`ujid`='".$currentUser->id."',`coupon`=".($usedcoupon === true ? $dbo->quote($strcouponeff) : "NULL").",`roomsnum`='".count($rooms)."',`total`='".$isdue."',`channel`=".(is_array($vcmchanneldata) ? $dbo->quote($vcmchanneldata['name']) : (!empty($mod_booking['channel']) ? $dbo->quote($mod_booking['channel']) : 'NULL')).",`paymcount`=".$mod_paymcount.",`adminnotes`=".$dbo->quote($mod_notes).",`lang`=".$dbo->quote($langtag).",`country`=".(!empty($usercountry) ? $dbo->quote($usercountry) : 'NULL').",`tot_taxes`='".$tot_taxes."',`tot_city_taxes`='".$tot_city_taxes."',`tot_fees`='".$tot_fees."',`phone`=".$dbo->quote($phone_number).",`pkg`=".($is_package === true ? (int)$pkg['id'] : "NULL")." WHERE `id`=".(int)$mod_booking['id'].";";
1310 $dbo->setQuery($q);
1311 $dbo->execute();
1312
1313 // remove the coupon used (should never been allowed for modifications)
1314 if ($usedcoupon == true && $coupon['type'] == 2) {
1315 $q = "DELETE FROM `#__vikbooking_coupons` WHERE `id`=".(int)$coupon['id'].";";
1316 $dbo->setQuery($q);
1317 $dbo->execute();
1318 }
1319
1320 // unset any previously booked room due to calendar sharing (should not be necessary because busy records have already been purged)
1321 VikBooking::cleanSharedCalendarsBusy($mod_booking['id']);
1322 // check if some of the rooms booked have shared calendars
1323 VikBooking::updateSharedCalendars($mod_booking['id'], array(), $pcheckin, $pcheckout);
1324
1325 //send email messages (admin and customer) and invoke SMS send
1326 VikBooking::sendBookingEmail($mod_booking['id'], array('guest', 'admin'), true, false, $type = 'modified');
1327
1328 //SMS
1329 VikBooking::sendBookingSMS($mod_booking['id']);
1330
1331 //Booking History
1332 VikBooking::getBookingHistoryInstance()->setBid($mod_booking['id'])->store('MW', $logmod);
1333
1334 //invoke VikChannelManager
1335 if (is_file(VCM_SITE_PATH . DIRECTORY_SEPARATOR . "helpers" . DIRECTORY_SEPARATOR . "synch.vikbooking.php")) {
1336 $vcm_obj = VikBooking::getVcmInvoker();
1337 $vcm_obj->setOids(array($mod_booking['id']))->setSyncType('modify')->setOriginalBooking($mod_booking);
1338 $vcm_obj->doSync();
1339 }
1340
1341 //unset the session value
1342 $session->set('vboModBooking', '');
1343
1344 // VBO 1.11 - push data to tracker for conversion
1345 $vbo_tracker->pushData('idorder', $mod_booking['id'])->pushMessage(JText::translate('VBOBOOKINGMODOK'))->closeTrack();
1346 $vbo_tracker->resetTrack();
1347
1348 $app->enqueueMessage(JText::translate('VBOBOOKINGMODOK'));
1349 $app->redirect(JRoute::rewrite("index.php?option=com_vikbooking&view=booking&sid=" . $sid . "&ts=" . $nowts . (!empty($pnodep) ? "&nodep=".$pnodep : "") . (!empty($pitemid) ? "&Itemid=" . $pitemid : ""), false));
1350 } else {
1351 // booking must have status stand-by and proceed to the payment
1352 $booking_record = new stdClass;
1353 $booking_record->custdata = $custdata;
1354 $booking_record->ts = $nowts;
1355 $booking_record->status = 'standby';
1356 $booking_record->days = $pdays;
1357 $booking_record->checkin = $pcheckin;
1358 $booking_record->checkout = $pcheckout;
1359 $booking_record->custmail = $useremail;
1360 $booking_record->sid = $sid;
1361 $booking_record->idpayment = !empty($payment) ? ($payment['id'] . '=' . $payment['name']) : null;
1362 $booking_record->ujid = $currentUser->id;
1363 $booking_record->coupon = $usedcoupon === true ? $strcouponeff : null;
1364 $booking_record->roomsnum = count($rooms);
1365 $booking_record->total = (float)$isdue;
1366 $booking_record->channel = is_array($vcmchanneldata) && !empty($vcmchanneldata['name']) ? $vcmchanneldata['name'] : null;
1367 $booking_record->lang = $langtag;
1368 $booking_record->country = !empty($usercountry) ? $usercountry : null;
1369 $booking_record->tot_taxes = (float)$tot_taxes;
1370 $booking_record->tot_city_taxes = (float)$tot_city_taxes;
1371 $booking_record->tot_fees = (float)$tot_fees;
1372 $booking_record->phone = $phone_number;
1373 $booking_record->pkg = $is_package === true ? (int)$pkg['id'] : null;
1374 $booking_record->split_stay = !empty($split_stay) ? 1 : 0;
1375
1376 $dbo->insertObject('#__vikbooking_orders', $booking_record, 'id');
1377
1378 if (!isset($booking_record->id)) {
1379 showSelectVb('Critical error while saving the booking. Please try again');
1380 return;
1381 }
1382 $neworderid = $booking_record->id;
1383
1384 $room_indexes_forcemap = [];
1385 foreach ($rooms as $num => $r) {
1386 $json_ch_age = '';
1387 if (array_key_exists($num, $children_age)) {
1388 $json_ch_age = json_encode($children_age[$num]);
1389 }
1390
1391 $pkg_cost = 0;
1392 if ($is_package === true) {
1393 $pkg_cost = $pkg['pernight_total'] == 1 ? ($pkg['cost'] * $daysdiff) : $pkg['cost'];
1394 $pkg_cost = $pkg['perperson'] == 1 ? ($pkg_cost * ($arrpeople[$num]['adults'] > 0 ? $arrpeople[$num]['adults'] : 1)) : $pkg_cost;
1395 // $pkg_cost = VikBooking::sayPackagePlusIva($pkg_cost, $pkg['idiva']);
1396 }
1397
1398 $oroom_record = new stdClass;
1399 $oroom_record->idorder = (int)$neworderid;
1400 $oroom_record->idroom = (int)$r['id'];
1401 $oroom_record->adults = (int)$arrpeople[$num]['adults'];
1402 $oroom_record->children = (int)$arrpeople[$num]['children'];
1403 $oroom_record->pets = isset($arrpeople[$num]['pets']) ? (int)$arrpeople[$num]['pets'] : 0;
1404 $oroom_record->idtar = (int)$tars[$num][0]['id'];
1405 $oroom_record->optionals = isset($selopt['room'.$num]) ? $selopt['room'.$num] : null;
1406 $oroom_record->childrenage = (!empty($json_ch_age) ? $json_ch_age : null);
1407 $oroom_record->t_first_name = $t_first_name;
1408 $oroom_record->t_last_name = $t_last_name;
1409 $oroom_record->roomindex = null;
1410 if (count($proomindex) == count($rooms) && !empty($proomindex[($num - 1)])) {
1411 // check if the sub-unit requested is available
1412 if (!isset($room_indexes_forcemap[$r['id']])) {
1413 $room_indexes_forcemap[$r['id']] = [];
1414 }
1415 $room_indexes = VikBooking::getRoomUnitNumsAvailable(array('id' => $neworderid, 'checkin' => $pcheckin, 'checkout' => $pcheckout), $r['id']);
1416 $force_rindex = 0;
1417 foreach ($room_indexes as $av_index) {
1418 if ((int)$av_index == (int)$proomindex[($num - 1)] && !in_array((int)$proomindex[($num - 1)], $room_indexes_forcemap[$r['id']])) {
1419 // requested index is available
1420 $force_rindex = (int)$proomindex[($num - 1)];
1421 array_push($room_indexes_forcemap[$r['id']], $force_rindex);
1422 break;
1423 }
1424 }
1425 if (!empty($force_rindex)) {
1426 $oroom_record->roomindex = $force_rindex;
1427 }
1428 }
1429 $oroom_record->pkg_id = ($is_package === true ? (int)$pkg['id'] : null);
1430 $oroom_record->pkg_name = ($is_package === true ? $pkg['name'] : null);
1431 $oroom_record->cust_cost = ($is_package === true ? $pkg_cost : null);
1432 $oroom_record->cust_idiva = ($is_package === true ? (int)$pkg['idiva'] : null);
1433 $oroom_record->room_cost = (array_key_exists($num, $rooms_costs_map) ? $rooms_costs_map[$num] : null);
1434
1435 $dbo->insertObject('#__vikbooking_ordersrooms', $oroom_record, 'id');
1436 }
1437
1438 if ($usedcoupon === true && $coupon['type'] == 2) {
1439 $q = "DELETE FROM `#__vikbooking_coupons` WHERE `id`=" . (int)$coupon['id'] . ";";
1440 $dbo->setQuery($q);
1441 $dbo->execute();
1442 }
1443
1444 // lock rooms waiting to be confirmed
1445 $lock_until_ts = VikBooking::getMinutesLock(true);
1446 foreach ($rooms as $num => $r) {
1447 // determine the number of nights of stay and dates to consider
1448 $room_checkin = $pcheckin;
1449 $room_checkout = $pcheckout;
1450 $room_realback = $realback;
1451 if (!empty($split_stay) && !empty($split_stay[($num - 1)]) && $split_stay[($num - 1)]['idroom'] == $r['id']) {
1452 $room_checkin = $split_stay[($num - 1)]['checkin_ts'];
1453 $room_checkout = $split_stay[($num - 1)]['checkout_ts'];
1454 $room_realback = $turnover_secs + $room_checkout;
1455 }
1456
1457 $tmp_lock_record = new stdClass;
1458 $tmp_lock_record->idroom = $r['id'];
1459 $tmp_lock_record->checkin = $room_checkin;
1460 $tmp_lock_record->checkout = $room_checkout;
1461 $tmp_lock_record->until = $lock_until_ts;
1462 $tmp_lock_record->realback = $room_realback;
1463 $tmp_lock_record->idorder = (int)$neworderid;
1464
1465 $dbo->insertObject('#__vikbooking_tmplock', $tmp_lock_record, 'id');
1466 }
1467
1468 if (!empty($split_stay)) {
1469 // save transient on db for split stay information
1470 VBOFactory::getConfig()->set('split_stay_' . $neworderid, json_encode($split_stay));
1471 }
1472
1473 // Customer Booking
1474 $cpin->saveCustomerBooking($neworderid);
1475
1476 // send email notification to guest and admin
1477 VikBooking::sendBookingEmail($neworderid, ['guest', 'admin']);
1478
1479 //SMS
1480 VikBooking::sendBookingSMS($neworderid);
1481
1482 //Booking History
1483 VikBooking::getBookingHistoryInstance()->setBid($neworderid)->store('NP', 'IP: ' . VikRequest::getVar('REMOTE_ADDR', '', 'server'));
1484
1485 /**
1486 * Invoke VikChannelManager also in case of pending reservations.
1487 *
1488 * @since 1.16.5 (J) - 1.6.5 (WP)
1489 *
1490 * @requires VCM >= 1.8.20
1491 */
1492 if (class_exists('VCMRequestAvailability')) {
1493 VikBooking::getVcmInvoker()
1494 ->setOids([$neworderid])
1495 ->setSyncType('new')
1496 ->doSync();
1497 }
1498
1499 // VBO 1.11 - push data to tracker for conversion
1500 $vbo_tracker->pushData('idorder', $neworderid)->closeTrack();
1501 $vbo_tracker->resetTrack();
1502
1503 $app->redirect(JRoute::rewrite("index.php?option=com_vikbooking&view=booking&sid=" . $sid . "&ts=" . $nowts . (!empty($pnodep) ? "&nodep=".$pnodep : "") . (!empty($pitemid) ? "&Itemid=" . $pitemid : ""), false));
1504 }
1505 }
1506
1507 public function vieworder()
1508 {
1509 VikRequest::setVar('view', 'booking');
1510 parent::display();
1511 }
1512
1513 public function cancelrequest()
1514 {
1515 if (!JSession::checkToken()) {
1516 // missing CSRF-proof token
1517 VBOHttpDocument::getInstance()->close(403, JText::translate('JINVALID_TOKEN'));
1518 }
1519
1520 $dbo = JFactory::getDbo();
1521 $mainframe = JFactory::getApplication();
1522
1523 $psid = VikRequest::getString('sid', '', 'request');
1524 $pidorder = VikRequest::getString('idorder', '', 'request');
1525
1526 if (!empty($psid) && !empty($pidorder)) {
1527 $q = "SELECT * FROM `#__vikbooking_orders` WHERE `id`=".intval($pidorder)." AND `sid`=".$dbo->quote($psid).";";
1528 $dbo->setQuery($q);
1529 $order = $dbo->loadAssocList();
1530 if ($order) {
1531 $pemail = VikRequest::getString('email', '', 'request');
1532 $preason = VikRequest::getString('reason', '', 'request');
1533 if (!empty($pemail) && !empty($preason)) {
1534 $to = VikBooking::getAdminMail();
1535 if(strpos($to, ',') !== false) {
1536 $all_recipients = explode(',', $to);
1537 foreach ($all_recipients as $k => $v) {
1538 if(empty($v)) {
1539 unset($all_recipients[$k]);
1540 }
1541 }
1542 if(count($all_recipients) > 0) {
1543 $to = $all_recipients;
1544 }
1545 }
1546 //Booking History
1547 VikBooking::getBookingHistoryInstance()->setBid($order[0]['id'])->store('CR', $pemail."\n".$preason);
1548 //
1549 $subject = JText::translate('VBCANCREQUESTEMAILSUBJ') . ' #' . $order[0]['id'];
1550 // @wponly we do not need to pass the "best item ID" to externalroute()
1551 $uri = VikBooking::externalroute("index.php?option=com_vikbooking&view=booking&sid=" . $order[0]['sid'] . "&ts=" . $order[0]['ts'], false);
1552 $msg = JText::sprintf('VBCANCREQUESTEMAILHEAD', $order[0]['id'], $uri)."\n\n".$preason;
1553 $vbo_app = VikBooking::getVboApplication();
1554 $adsendermail = VikBooking::getSenderMail();
1555 $vbo_app->sendMail($adsendermail, $adsendermail, $to, $pemail, $subject, $msg, false);
1556 $mainframe->enqueueMessage(JText::translate('VBCANCREQUESTMAILSENT'));
1557 $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));
1558 } else {
1559 $mainframe->redirect(JRoute::rewrite("index.php?option=com_vikbooking&view=booking&sid=".$order[0]['sid']."&ts=".$order[0]['ts'], false));
1560 }
1561 } else {
1562 $mainframe->redirect("index.php");
1563 }
1564 } else {
1565 $mainframe->redirect("index.php");
1566 }
1567 }
1568
1569 public function reqinfo()
1570 {
1571 $proomid = VikRequest::getInt('roomid', '', 'request');
1572 $preqinfotoken = VikRequest::getInt('reqinfotoken', '', 'request');
1573 $pitemid = VikRequest::getInt('Itemid', '', 'request');
1574 $dbo = JFactory::getDBO();
1575 $session = JFactory::getSession();
1576 $mainframe = JFactory::getApplication();
1577 $vbo_app = VikBooking::getVboApplication();
1578 if (!empty($proomid)) {
1579 $q = "SELECT `id`,`name` FROM `#__vikbooking_rooms` WHERE `id`=".(int)$proomid.";";
1580 $dbo->setQuery($q);
1581 $dbo->execute();
1582 if ($dbo->getNumRows() == 1) {
1583 $room = $dbo->loadAssocList();
1584 $goto = JRoute::rewrite('index.php?option=com_vikbooking&view=roomdetails&roomid='.$room[0]['id'].'&Itemid='.$pitemid, false);
1585 $preqname = VikRequest::getString('reqname', '', 'request');
1586 $preqemail = VikRequest::getString('reqemail', '', 'request');
1587 $preqmess = VikRequest::getString('reqmess', '', 'request');
1588 if (!empty($preqemail) && !empty($preqmess)) {
1589 /**
1590 * captcha verification
1591 *
1592 * @since 1.2.3
1593 */
1594 if ($vbo_app->isCaptcha() && !$vbo_app->reCaptcha('check')) {
1595 VikError::raiseWarning('', 'Invalid Captcha');
1596 $mainframe->redirect($goto);
1597 exit;
1598 }
1599 //
1600 $sesstoken = $session->get('vboreqinfo'.$room[0]['id'], '');
1601 if((int)$sesstoken == (int)$preqinfotoken) {
1602 $session->set('vboreqinfo'.$room[0]['id'], '');
1603 $to = VikBooking::getAdminMail();
1604 if(strpos($to, ',') !== false) {
1605 $all_recipients = explode(',', $to);
1606 foreach ($all_recipients as $k => $v) {
1607 if(empty($v)) {
1608 unset($all_recipients[$k]);
1609 }
1610 }
1611 if(count($all_recipients) > 0) {
1612 $to = $all_recipients;
1613 }
1614 }
1615 $subject = JText::sprintf('VBOROOMREQINFOSUBJ', $room[0]['name']);
1616 $msg = JText::translate('VBOROOMREQINFONAME').": ".$preqname."\n\n".JText::translate('VBOROOMREQINFOEMAIL').": ".$preqemail."\n\n".JText::translate('VBOROOMREQINFOMESS').":\n\n".$preqmess;
1617 $adsendermail = VikBooking::getSenderMail();
1618 $vbo_app->sendMail($adsendermail, $adsendermail, $to, $preqemail, $subject, $msg, false);
1619 $mainframe->enqueueMessage(JText::translate('VBOROOMREQINFOSENTOK'));
1620 } else {
1621 VikError::raiseWarning('', JText::translate('VBOROOMREQINFOTKNERR'));
1622 }
1623 $mainframe->redirect($goto);
1624 } else {
1625 VikError::raiseWarning('', JText::translate('VBOROOMREQINFOMISSFIELD'));
1626 $mainframe->redirect($goto);
1627 }
1628 } else {
1629 $mainframe->redirect("index.php");
1630 }
1631 } else {
1632 $mainframe->redirect("index.php");
1633 }
1634 }
1635
1636 public function cron_exec()
1637 {
1638 if (VBOPlatformDetection::isWordPress())
1639 {
1640 // in WordPress it is no more needed to schedule a server cron job
1641 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.');
1642 }
1643
1644 $app = JFactory::getApplication();
1645
1646 $id_cron = $app->input->getUint('cron_id', 0);
1647 $key = $app->input->getString('cronkey', '');
1648
1649 $model = VBOMvcModel::getInstance('cronjob');
1650
1651 // dispatch the cron job by injecting the cron key within the
1652 // configuration array, in order to make sure that the execution
1653 // of the job has been requested by a reliable caller
1654 $response = $model->dispatch($id_cron, ['key' => $key]);
1655
1656 if ($response === false)
1657 {
1658 // an error has occurred
1659 $error = $model->getError();
1660
1661 if (!$error instanceof Exception)
1662 {
1663 // wrap error message in an exception for a better ease of use
1664 $error = new Exception($error ?: 'Error', 500);
1665 }
1666
1667 // terminate session with an error
1668 VBOHttpDocument::getInstance($app)->close($error->getCode(), $error->getMessage());
1669 }
1670
1671 // display response code and teminate the session
1672 echo $response;
1673 $app->close();
1674 }
1675
1676 public function notifypayment()
1677 {
1678 $dbo = JFactory::getDbo();
1679
1680 $config = VBOFactory::getConfig();
1681 $av_helper = VikBooking::getAvailabilityInstance();
1682
1683 $psid = VikRequest::getString('sid', '', 'request');
1684 $pts = VikRequest::getString('ts', '', 'request');
1685
1686 $nowdf = VikBooking::getDateFormat();
1687 if ($nowdf == "%d/%m/%Y") {
1688 $df = 'd/m/Y';
1689 } elseif ($nowdf == "%m/%d/%Y") {
1690 $df = 'm/d/Y';
1691 } else {
1692 $df = 'Y/m/d';
1693 }
1694
1695 if (!strlen($psid) || !strlen($pts)) {
1696 VBOHttpDocument::getInstance()->close(500, 'Missing information for fetching the booking');
1697 }
1698
1699 $admail = VikBooking::getAdminMail();
1700 $recipient_mail = $admail;
1701 if (!is_array($recipient_mail) && strpos($recipient_mail, ',') !== false) {
1702 $all_recipients = explode(',', $recipient_mail);
1703 foreach ($all_recipients as $k => $v) {
1704 if (empty($v)) {
1705 unset($all_recipients[$k]);
1706 }
1707 }
1708 if (count($all_recipients) > 0) {
1709 $recipient_mail = $all_recipients;
1710 }
1711 }
1712
1713 $q = "SELECT * FROM `#__vikbooking_orders` WHERE (`sid`=" . $dbo->quote($psid) . " OR `idorderota`=" . $dbo->quote($psid) . ") AND `ts`=" . $dbo->quote($pts);
1714 $dbo->setQuery($q, 0, 1);
1715 $dbo->execute();
1716 if (!$dbo->getNumRows()) {
1717 VBOHttpDocument::getInstance()->close(404, 'Booking not found');
1718 }
1719
1720 // load booking details
1721 $row = $dbo->loadAssoc();
1722
1723 // check if the language in use is the same as the one used during the checkout
1724 if (!empty($row['lang'])) {
1725 $lang = JFactory::getLanguage();
1726 if ($lang->getTag() != $row['lang']) {
1727 $lang->load('com_vikbooking', (VBOPlatformDetection::isWordPress() ? VIKBOOKING_SITE_LANG : JPATH_SITE), $row['lang'], true);
1728 if (VBOPlatformDetection::isJoomla()) {
1729 $lang->load('joomla', JPATH_SITE, $row['lang'], true);
1730 }
1731 }
1732 }
1733
1734 // translator
1735 $vbo_tn = VikBooking::getTranslator();
1736
1737 if ($row['status'] == 'confirmed' && !(VikBooking::multiplePayments() && $row['paymcount'] > 0)) {
1738 // booking can be paid only if not confirmed or if multiple payments are enabled and payment counter for booking greater than zero
1739 VBOHttpDocument::getInstance()->close(409, 'Conflicting and unexpected payment validation for this reservation');
1740 }
1741
1742 /**
1743 * Check split stay reservation data.
1744 *
1745 * @since 1.16.0 (J) - 1.6.0 (WP)
1746 */
1747 $split_stay = [];
1748 if ($row['split_stay'] && $row['status'] != 'confirmed') {
1749 // check for transient on DB
1750 $split_stay = $config->getArray('split_stay_' . $row['id'], []);
1751 }
1752
1753 // inject admin email
1754 $row['admin_email'] = $admail;
1755
1756 // turnover seconds
1757 $turnover_secs = VikBooking::getHoursRoomAvail() * 3600;
1758 $realback = $turnover_secs + $row['checkout'];
1759
1760 $currencyname = VikBooking::getCurrencyName();
1761 $ftitle = VikBooking::getFrontTitle();
1762 $nowts = time();
1763
1764 $rooms = [];
1765 $tars = [];
1766 $arrpeople = [];
1767 $is_package = (bool)(!empty($row['pkg']));
1768
1769 // load booked rooms
1770 $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;";
1771 $dbo->setQuery($q);
1772 $orderrooms = $dbo->loadAssocList();
1773 if ($orderrooms) {
1774 $vbo_tn->translateContents($orderrooms, '#__vikbooking_rooms', array('id' => 'r_reference_id'));
1775 foreach ($orderrooms as $kor => $or) {
1776 $num = $kor + 1;
1777 $rooms[$num] = $or;
1778 $arrpeople[$num]['adults'] = $or['adults'];
1779 $arrpeople[$num]['children'] = $or['children'];
1780 if ($is_package === true || (!empty($or['cust_cost']) && $or['cust_cost'] > 0.00)) {
1781 // package or custom cost set from the back-end
1782 continue;
1783 }
1784
1785 // determine the number of nights of stay and dates to consider
1786 $use_los = $row['days'];
1787 $room_checkin = $row['checkin'];
1788 $room_checkout = $row['checkout'];
1789 if (!empty($split_stay) && !empty($split_stay[$kor]) && $split_stay[$kor]['idroom'] == $or['idroom']) {
1790 $use_los = (int)$split_stay[$kor]['nights'];
1791 $room_checkin = $split_stay[$kor]['checkin_ts'];
1792 $room_checkout = $split_stay[$kor]['checkout_ts'];
1793 }
1794
1795 $q = "SELECT * FROM `#__vikbooking_dispcost` WHERE `id`=" . (int)$or['idtar'];
1796 $dbo->setQuery($q, 0, 1);
1797 $tar = $dbo->loadAssocList();
1798 if (!$tar) {
1799 continue;
1800 }
1801
1802 $tar = VikBooking::applySeasonsRoom($tar, $room_checkin, $room_checkout);
1803
1804 // apply OBP rules
1805 $tar = VBORoomHelper::getInstance()->applyOBPRules($tar, $or, $or['adults']);
1806
1807 // push tariff
1808 $tars[$num] = $tar[0];
1809 }
1810 }
1811
1812 // inject values
1813 $row['order_rooms'] = $orderrooms;
1814 $row['fares'] = $tars;
1815
1816 $isdue = 0;
1817 $tot_taxes = 0;
1818 $tot_city_taxes = 0;
1819 $tot_fees = 0;
1820 $pricestr = [];
1821 $optstr = [];
1822 foreach ($orderrooms as $kor => $or) {
1823 $num = $kor + 1;
1824
1825 // determine the number of nights of stay and dates to consider
1826 $use_los = $row['days'];
1827 $room_checkin = $row['checkin'];
1828 $room_checkout = $row['checkout'];
1829 if (!empty($split_stay) && !empty($split_stay[$kor]) && $split_stay[$kor]['idroom'] == $or['idroom']) {
1830 $use_los = (int)$split_stay[$kor]['nights'];
1831 $room_checkin = $split_stay[$kor]['checkin_ts'];
1832 $room_checkout = $split_stay[$kor]['checkout_ts'];
1833 }
1834
1835 if ($is_package === true || (!empty($or['cust_cost']) && $or['cust_cost'] > 0.00)) {
1836 // package cost or cust_cost may not be inclusive of taxes if prices tax included is off
1837 $calctar = VikBooking::sayPackagePlusIva($or['cust_cost'], $or['cust_idiva']);
1838 $isdue += $calctar;
1839 if ($calctar == $or['cust_cost']) {
1840 $cost_minus_tax = VikBooking::sayPackageMinusIva($or['cust_cost'], $or['cust_idiva']);
1841 $tot_taxes += ($or['cust_cost'] - $cost_minus_tax);
1842 } else {
1843 $tot_taxes += ($calctar - $or['cust_cost']);
1844 }
1845 $pricestr[$num] = (!empty($or['pkg_name']) ? $or['pkg_name'] : (!empty($or['otarplan']) ? ucwords($or['otarplan']) : JText::translate('VBOROOMCUSTRATEPLAN'))).": ".$calctar." ".$currencyname;
1846 } elseif (array_key_exists($num, $tars) && is_array($tars[$num])) {
1847 $calctar = VikBooking::sayCostPlusIva($tars[$num]['cost'], $tars[$num]['idprice']);
1848 $tars[$num]['calctar'] = $calctar;
1849 $isdue += $calctar;
1850 if ($calctar == $tars[$num]['cost']) {
1851 $cost_minus_tax = VikBooking::sayCostMinusIva($tars[$num]['cost'], $tars[$num]['idprice']);
1852 $tot_taxes += ($tars[$num]['cost'] - $cost_minus_tax);
1853 } else {
1854 $tot_taxes += ($calctar - $tars[$num]['cost']);
1855 }
1856 $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'] : "");
1857 }
1858 if (!empty($or['optionals'])) {
1859 $stepo = explode(";", $or['optionals']);
1860 foreach ($stepo as $roptkey => $oo) {
1861 if (empty($oo)) {
1862 continue;
1863 }
1864 $stept = explode(":", $oo);
1865 $q = "SELECT * FROM `#__vikbooking_optionals` WHERE `id`=" . $dbo->quote($stept[0]) . ";";
1866 $dbo->setQuery($q);
1867 $actopt = $dbo->loadAssocList();
1868 if ($actopt) {
1869 $vbo_tn->translateContents($actopt, '#__vikbooking_optionals');
1870 $chvar = '';
1871 if (!empty($actopt[0]['ageintervals']) && $or['children'] > 0 && strstr($stept[1], '-') != false) {
1872 $optagenames = VikBooking::getOptionIntervalsAges($actopt[0]['ageintervals']);
1873 $optagepcent = VikBooking::getOptionIntervalsPercentage($actopt[0]['ageintervals']);
1874 $optageovrct = VikBooking::getOptionIntervalChildOverrides($actopt[0], $or['adults'], $or['children']);
1875 $child_num = VikBooking::getRoomOptionChildNumber($or['optionals'], $actopt[0]['id'], $roptkey, $or['children']);
1876 $optagecosts = VikBooking::getOptionIntervalsCosts(isset($optageovrct['ageintervals_child' . ($child_num + 1)]) ? $optageovrct['ageintervals_child' . ($child_num + 1)] : $actopt[0]['ageintervals']);
1877 $agestept = explode('-', $stept[1]);
1878 $stept[1] = $agestept[0];
1879 $chvar = $agestept[1];
1880 if (array_key_exists(($chvar - 1), $optagepcent) && $optagepcent[($chvar - 1)] == 1) {
1881 //percentage value of the adults tariff
1882 if ($is_package === true || (!empty($or['cust_cost']) && $or['cust_cost'] > 0.00)) {
1883 $optagecosts[($chvar - 1)] = $or['cust_cost'] * $optagecosts[($chvar - 1)] / 100;
1884 } else {
1885 $optagecosts[($chvar - 1)] = $tars[$num]['cost'] * $optagecosts[($chvar - 1)] / 100;
1886 }
1887 } elseif (array_key_exists(($chvar - 1), $optagepcent) && $optagepcent[($chvar - 1)] == 2) {
1888 //VBO 1.10 - percentage value of room base cost
1889 if ($is_package === true || (!empty($or['cust_cost']) && $or['cust_cost'] > 0.00)) {
1890 $optagecosts[($chvar - 1)] = $or['cust_cost'] * $optagecosts[($chvar - 1)] / 100;
1891 } else {
1892 $display_rate = isset($tars[$num]['room_base_cost']) ? $tars[$num]['room_base_cost'] : $tars[$num]['cost'];
1893 $optagecosts[($chvar - 1)] = $display_rate * $optagecosts[($chvar - 1)] / 100;
1894 }
1895 }
1896 $actopt[0]['chageintv'] = $chvar;
1897 $actopt[0]['name'] .= ' ('.$optagenames[($chvar - 1)].')';
1898 $actopt[0]['quan'] = $stept[1];
1899 $realcost = (intval($actopt[0]['perday']) == 1 ? (floatval($optagecosts[($chvar - 1)]) * $use_los * $stept[1]) : (floatval($optagecosts[($chvar - 1)]) * $stept[1]));
1900 } else {
1901 $actopt[0]['quan'] = $stept[1];
1902 // VBO 1.11 - options percentage cost of the room total fee
1903 if ($is_package === true || (!empty($or['cust_cost']) && $or['cust_cost'] > 0.00)) {
1904 $deftar_basecosts = $or['cust_cost'];
1905 } else {
1906 $deftar_basecosts = $tars[$num]['cost'];
1907 }
1908 $actopt[0]['cost'] = (int)$actopt[0]['pcentroom'] ? ($deftar_basecosts * $actopt[0]['cost'] / 100) : $actopt[0]['cost'];
1909 //
1910 $realcost = (intval($actopt[0]['perday']) == 1 ? ($actopt[0]['cost'] * $use_los * $stept[1]) : ($actopt[0]['cost'] * $stept[1]));
1911 }
1912 if (!empty($actopt[0]['maxprice']) && $actopt[0]['maxprice'] > 0 && $realcost > $actopt[0]['maxprice']) {
1913 $realcost = $actopt[0]['maxprice'];
1914 if (intval($actopt[0]['hmany']) == 1 && intval($stept[1]) > 1) {
1915 $realcost = $actopt[0]['maxprice'] * $stept[1];
1916 }
1917 }
1918 if ($actopt[0]['perperson'] == 1) {
1919 $realcost = $realcost * $or['adults'];
1920 }
1921 $opt_minus_tax = VikBooking::sayOptionalsMinusIva($realcost, $actopt[0]['idiva']);
1922 $tmpopr = VikBooking::sayOptionalsPlusIva($realcost, $actopt[0]['idiva']);
1923 if ($actopt[0]['is_citytax'] == 1) {
1924 $tot_city_taxes += $opt_minus_tax;
1925 } elseif ($actopt[0]['is_fee'] == 1) {
1926 $tot_fees += $opt_minus_tax;
1927 }
1928 // VBO 1.11 - always calculate the amount of tax no matter if this is already a tax or a fee
1929 if ($tmpopr == $realcost) {
1930 $tot_taxes += ($realcost - $opt_minus_tax);
1931 } else {
1932 $tot_taxes += ($tmpopr - $realcost);
1933 }
1934 //
1935 $isdue += $tmpopr;
1936 $optstr[$num][] = ($stept[1] > 1 ? $stept[1] . " " : "") . $actopt[0]['name'] . ": " . $tmpopr . " " . $currencyname . "\n";
1937 }
1938 }
1939 }
1940
1941 // custom extra costs
1942 if (!empty($or['extracosts'])) {
1943 $cur_extra_costs = json_decode($or['extracosts'], true);
1944 foreach ($cur_extra_costs as $eck => $ecv) {
1945 $ecplustax = !empty($ecv['idtax']) ? VikBooking::sayOptionalsPlusIva($ecv['cost'], $ecv['idtax']) : $ecv['cost'];
1946 $isdue += $ecplustax;
1947 $optstr[$num][] = $ecv['name'] . ": " . $ecplustax . " " . $currencyname."\n";
1948 }
1949 }
1950 }
1951
1952 // coupon
1953 $usedcoupon = false;
1954 $origisdue = $isdue;
1955 if (strlen($row['coupon']) > 0) {
1956 $usedcoupon = true;
1957 $expcoupon = explode(";", $row['coupon']);
1958 $isdue = $isdue - $expcoupon[1];
1959 }
1960
1961 // invoke the payment method class
1962 $exppay = explode('=', $row['idpayment']);
1963 $payment = VikBooking::getPayment($exppay[0], $vbo_tn);
1964
1965 if (empty($row['sid']) && !empty($row['idorderota']) && !empty($row['channel'])) {
1966 $row['sid'] = $row['idorderota'];
1967 }
1968
1969 if (VBOPlatformDetection::isWordPress()) {
1970 /**
1971 * @wponly The payment gateway is now loaded
1972 * using the apposite dispatcher.
1973 *
1974 * @since 1.0.5
1975 */
1976 JLoader::import('adapter.payment.dispatcher');
1977 $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'];
1978 $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'];
1979 $notify_url = JUri::root() . "index.php?option=com_vikbooking&task=notifypayment&sid=" . (!empty($row['idorderota']) && !empty($row['channel']) ? $row['idorderota'] : $row['sid']) . "&ts=" . $row['ts']."&tmpl=component";
1980 $model = JModel::getInstance('vikbooking', 'shortcodes', 'admin');
1981 $itemid = $model->best(array('booking'), (!empty($row['lang']) ? $row['lang'] : null));
1982 $extra_data = [];
1983 if ($itemid) {
1984 $return_url = str_replace(JUri::root(), '', $return_url);
1985 $error_url = str_replace(JUri::root(), '', $error_url);
1986 $notify_url = str_replace(JUri::root(), '', $notify_url);
1987 $return_url = JRoute::rewrite($return_url . "&Itemid={$itemid}", false);
1988 $error_url = JRoute::rewrite($error_url . "&Itemid={$itemid}", false);
1989 $notify_url = JRoute::rewrite($notify_url . "&Itemid={$itemid}", false);
1990 $extra_data = array(
1991 'return_url' => $return_url,
1992 'error_url' => $error_url,
1993 'notify_url' => $notify_url,
1994 );
1995 }
1996 $extra_data['transaction_currency'] = VikBooking::getCurrencyCodePp();
1997
1998 $obj = JPaymentDispatcher::getInstance('vikbooking', $payment['file'], array_merge($row, $extra_data), $payment['params']);
1999 } else {
2000 /**
2001 * @joomlaonly The Payment Factory library will invoke the gateway.
2002 * Make sure to pass the payment gateway some common variables together with the order record.
2003 *
2004 * @since 1.14.3
2005 */
2006 require_once VBO_ADMIN_PATH . DIRECTORY_SEPARATOR . 'payments' . DIRECTORY_SEPARATOR . 'libraries' . DIRECTORY_SEPARATOR . 'factory.php';
2007
2008 $bestitemid = VikBooking::findProperItemIdType(array('booking'));
2009 $extra_data = array(
2010 '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)),
2011 '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)),
2012 'notify_url' => VikBooking::externalroute("index.php?option=com_vikbooking&task=notifypayment&sid=" . (!empty($row['idorderota']) && !empty($row['channel']) ? $row['idorderota'] : $row['sid']) . "&ts=" . $row['ts']."&tmpl=component", false, null),
2013 );
2014 $extra_data['transaction_currency'] = VikBooking::getCurrencyCodePp();
2015
2016 $obj = VBOPaymentFactory::getPaymentInstance($payment['file'], array_merge($row, $extra_data), $payment['params']);
2017 }
2018
2019 $array_result = $obj->validatePayment();
2020 $newpaymentlog = date('c')."\n".$array_result['log']."\n----------\n".$row['paymentlog'];
2021
2022 /**
2023 * OTA reservations containing PCI-DSS card details may receive additional payments through
2024 * the website for upselling or for payments requested. Therefore, the previous card logs
2025 * should be appended, not prepended to the current payment logs for the card details.
2026 *
2027 * @since 1.15.0 (J) - 1.5.0 (WP)
2028 */
2029 if (!empty($row['idorderota']) && !empty($row['channel']) && !empty($row['paymentlog'])) {
2030 if (stripos($row['paymentlog'], 'card number') !== false && strpos($row['paymentlog'], '*') !== false) {
2031 $newpaymentlog = $row['paymentlog'] . "\n----------\n" . date('c') . "\n" . $array_result['log'];
2032 }
2033 }
2034
2035 /**
2036 * Ensure the size of the log does not make the query fail.
2037 *
2038 * @since 1.16.9 (J) - 1.6.9 (WP)
2039 */
2040 if (strlen($newpaymentlog) > 65000) {
2041 $newpaymentlog = substr($newpaymentlog, 0, 65000) . '...';
2042 }
2043
2044 if ($array_result['verified'] == 1) {
2045 // valid payment
2046 $shouldpay = $isdue;
2047 if ($payment['charge'] > 0.00) {
2048 if ($payment['ch_disc'] == 1) {
2049 // charge
2050 if ($payment['val_pcent'] == 1) {
2051 // fixed value
2052 $shouldpay += $payment['charge'];
2053 } else {
2054 // percent value
2055 $percent_to_pay = $shouldpay * $payment['charge'] / 100;
2056 $shouldpay += $percent_to_pay;
2057 }
2058 } else {
2059 // discount
2060 if ($payment['val_pcent'] == 1) {
2061 // fixed value
2062 $shouldpay -= $payment['charge'];
2063 } else {
2064 // percent value
2065 $percent_to_pay = $shouldpay * $payment['charge'] / 100;
2066 $shouldpay -= $percent_to_pay;
2067 }
2068 }
2069 }
2070 //deposit may be skipped by customer choice
2071 $shouldpay_befdep = $shouldpay;
2072 //
2073 if (!VikBooking::payTotal()) {
2074 $percentdeposit = VikBooking::getAccPerCent();
2075 if ($percentdeposit > 0) {
2076 if (VikBooking::getTypeDeposit() == "fixed") {
2077 $shouldpay = $percentdeposit;
2078 } else {
2079 $shouldpay = $shouldpay * $percentdeposit / 100;
2080 }
2081 }
2082 }
2083 //check if the total amount paid is the same as the order total
2084 if (isset($array_result['tot_paid'])) {
2085 $shouldpay = round($shouldpay, 2);
2086 $shouldpay_befdep = round($shouldpay_befdep, 2);
2087 $totreceived = round($array_result['tot_paid'], 2);
2088 if ($shouldpay != $totreceived && $shouldpay_befdep != $totreceived && $row['paymcount'] == 0) {
2089 //the amount paid is different than the order total
2090 //fares might have changed or the deposit might be different
2091 //Sending just an email to the admin that will check
2092 $vbo_app = VikBooking::getVboApplication();
2093 $adsendermail = VikBooking::getSenderMail();
2094 $vbo_app->sendMail($adsendermail, $adsendermail, $recipient_mail, $adsendermail, JText::translate('VBTOTPAYMENTINVALID'), JText::sprintf('VBTOTPAYMENTINVALIDTXT', $row['id'], $totreceived." (".$array_result['tot_paid'].")", $shouldpay), false);
2095 }
2096 /**
2097 * We store the amount paid before applying the charge for the transaction.
2098 *
2099 * @since 1.3.0
2100 */
2101 if ($payment['charge'] > 0.00) {
2102 if ($payment['ch_disc'] == 1) {
2103 // charge
2104 if ($payment['val_pcent'] == 1) {
2105 // fixed value
2106 $array_result['tot_paid'] -= $payment['charge'];
2107 } else {
2108 // percent value
2109 $array_result['tot_paid'] = ($array_result['tot_paid'] / ((100 + $payment['charge']) / 100));
2110 }
2111 } else {
2112 // discount
2113 if ($payment['val_pcent'] == 1) {
2114 // fixed value
2115 $array_result['tot_paid'] += $payment['charge'];
2116 } else {
2117 // percent value
2118 $array_result['tot_paid'] = $array_result['tot_paid'] * (100 + $payment['charge']) / 100;
2119 }
2120 }
2121 $array_result['tot_paid'] = round($array_result['tot_paid'], 2);
2122 }
2123 }
2124
2125 if ($row['paymcount'] == 0 || $row['status'] == 'standby') {
2126 foreach ($orderrooms as $indnum => $r) {
2127 $num = $indnum + 1;
2128
2129 // determine the number of nights of stay and dates to consider
2130 $room_checkin = $row['checkin'];
2131 $room_checkout = $row['checkout'];
2132 $room_realback = $turnover_secs + $row['checkout'];
2133 if (!empty($split_stay) && !empty($split_stay[$indnum]) && $split_stay[$indnum]['idroom'] == $r['idroom']) {
2134 $room_checkin = $split_stay[$indnum]['checkin_ts'];
2135 $room_checkout = $split_stay[$indnum]['checkout_ts'];
2136 $room_realback = $turnover_secs + $split_stay[$indnum]['checkout_ts'];
2137 }
2138
2139 $busy_record = new stdClass;
2140 $busy_record->idroom = $r['idroom'];
2141 $busy_record->checkin = $room_checkin;
2142 $busy_record->checkout = $room_checkout;
2143 $busy_record->realback = $room_realback;
2144
2145 $dbo->insertObject('#__vikbooking_busy', $busy_record, 'id');
2146
2147 if (!isset($busy_record->id)) {
2148 continue;
2149 }
2150
2151 $q = "INSERT INTO `#__vikbooking_ordersbusy` (`idorder`,`idbusy`) VALUES(" . (int)$row['id'] . ", " . (int)$busy_record->id . ");";
2152 $dbo->setQuery($q);
2153 $dbo->execute();
2154 }
2155 }
2156
2157 // ConfirmationNumber
2158 if ($row['paymcount'] == 0 || $row['status'] == 'standby') {
2159 $confirmnumber = VikBooking::generateConfirmNumber($row['id'], true);
2160 }
2161
2162 // update payable amount in case of up-sells or simply in case of another payment received
2163 $new_payable = isset($array_result['tot_paid']) && $array_result['tot_paid'] ? ($row['payable'] - $array_result['tot_paid']) : 0;
2164 $new_payable = $new_payable < 0 ? 0 : $new_payable;
2165
2166 // update booking record
2167 $booking_record = new stdClass;
2168 $booking_record->id = $row['id'];
2169 $booking_record->status = 'confirmed';
2170 if (isset($array_result['tot_paid']) && $array_result['tot_paid']) {
2171 $booking_record->totpaid = ($array_result['tot_paid'] + $row['totpaid']);
2172 }
2173 $booking_record->paymcount = ($row['paymcount'] + 1);
2174 if (!empty($array_result['log'])) {
2175 $booking_record->paymentlog = $newpaymentlog;
2176 }
2177 $booking_record->payable = $new_payable;
2178
2179 $dbo->updateObject('#__vikbooking_orders', $booking_record, 'id');
2180
2181 // assign room specific unit
2182 $set_room_indexes = VikBooking::autoRoomUnit();
2183 $room_indexes_usemap = [];
2184 if ($set_room_indexes === true) {
2185 $q = "SELECT `id`,`idroom`,`roomindex` FROM `#__vikbooking_ordersrooms` WHERE `idorder`=".(int)$row['id'].";";
2186 $dbo->setQuery($q);
2187 $dbo->execute();
2188 $orooms = $dbo->loadAssocList();
2189 foreach ($orooms as $oroom) {
2190 if (!empty($oroom['roomindex'])) {
2191 // room specific unit has already been assigned
2192 continue;
2193 }
2194 $room_indexes = VikBooking::getRoomUnitNumsAvailable($row, $oroom['idroom']);
2195 $use_ind_key = 0;
2196 if ($room_indexes) {
2197 if (!array_key_exists($oroom['idroom'], $room_indexes_usemap)) {
2198 $room_indexes_usemap[$oroom['idroom']] = $use_ind_key;
2199 } else {
2200 $use_ind_key = $room_indexes_usemap[$oroom['idroom']];
2201 }
2202 $q = "UPDATE `#__vikbooking_ordersrooms` SET `roomindex`=".(int)$room_indexes[$use_ind_key]." WHERE `id`=".(int)$oroom['id'].";";
2203 $dbo->setQuery($q);
2204 $dbo->execute();
2205 // update rooms references for the customer email sending function
2206 foreach ($rooms as $rnum => $rr) {
2207 if ($rr['or_id'] == $oroom['id']) {
2208 $rooms[$rnum]['roomindex'] = (int)$room_indexes[$use_ind_key];
2209 break;
2210 }
2211 }
2212 $room_indexes_usemap[$oroom['idroom']]++;
2213 }
2214 }
2215 }
2216
2217 // unlock room(s) for other imminent bookings
2218 $q = "DELETE FROM `#__vikbooking_tmplock` WHERE `idorder`=" . intval($row['id']) . ";";
2219 $dbo->setQuery($q);
2220 $dbo->execute();
2221
2222 // customer booking
2223 $q = "SELECT `idcustomer` FROM `#__vikbooking_customers_orders` WHERE `idorder`=".(int)$row['id'].";";
2224 $dbo->setQuery($q);
2225 $customer_id = $dbo->loadResult();
2226 if ($customer_id) {
2227 $cpin = VikBooking::getCPinIstance();
2228 $cpin->updateBookingCommissions($row['id'], $customer_id);
2229 }
2230
2231 // check if some of the rooms booked have shared calendars
2232 VikBooking::updateSharedCalendars($row['id'], array(), $row['checkin'], $row['checkout']);
2233
2234 /**
2235 * Trigger event to allow third-party plugins to choose whether payment notifications should be sent
2236 *
2237 * @since 1.16.10 (J) - 1.6.10 (WP)
2238 */
2239 $send_notifications = true;
2240 $should_send = VBOFactory::getPlatform()->getDispatcher()->filter('onPaymentReceivedShouldSendNotifications', [$row]);
2241 if (is_array($should_send) && in_array(false, $should_send, true)) {
2242 $send_notifications = false;
2243 }
2244
2245 if ($send_notifications) {
2246 // send email notification to guest and admin
2247 VikBooking::sendBookingEmail($row['id'], array('guest', 'admin'));
2248
2249 // SMS
2250 VikBooking::sendBookingSMS($row['id']);
2251 }
2252
2253 /**
2254 * Payment gateways may set and return the transaction information
2255 * to eventually support a later transaction of type refund.
2256 *
2257 * @since 1.14 (J) - 1.4.0 (WP)
2258 * @since 1.16.2 (J) - 1.6.2 (WP) we attempt to always store the amount paid with this transaction.
2259 * @since 1.16.9 (J) - 1.6.9 (WP) we attempt to store the amount of payment processing fees for the transaction.
2260 */
2261 $tn_data = $array_result['transaction'] ?? null;
2262 if (isset($array_result['tot_paid']) && $array_result['tot_paid']) {
2263 // check event data payload to store
2264 if (is_array($tn_data)) {
2265 // set key
2266 $tn_data['amount_paid'] = (float)$array_result['tot_paid'];
2267 } elseif (is_object($tn_data)) {
2268 // set property
2269 $tn_data->amount_paid = (float)$array_result['tot_paid'];
2270 } elseif (!$tn_data) {
2271 // build an array (we add the payment name because we know there is no other transaction data)
2272 $tn_data = [
2273 'amount_paid' => (float)$array_result['tot_paid'],
2274 'payment_method' => $payment['name'],
2275 ];
2276 }
2277 }
2278 if ($tn_data && isset($array_result['tot_fees']) && $array_result['tot_fees']) {
2279 // check event data payload to store
2280 if (is_array($tn_data)) {
2281 // set key
2282 $tn_data['processing_fees'] = (float)$array_result['tot_fees'];
2283 } elseif (is_object($tn_data)) {
2284 // set property
2285 $tn_data->processing_fees = (float)$array_result['tot_fees'];
2286 }
2287 }
2288
2289 // Booking History
2290 VikBooking::getBookingHistoryInstance()->setBid($row['id'])->setExtraData($tn_data)->store('P' . ($row['paymcount'] > 0 ? 'N' : '0'), $payment['name']);
2291
2292 // invoke VikChannelManager
2293 if (is_file(VCM_SITE_PATH . DIRECTORY_SEPARATOR . "helpers" . DIRECTORY_SEPARATOR . "synch.vikbooking.php")) {
2294 require_once(VCM_SITE_PATH . DIRECTORY_SEPARATOR . "helpers" . DIRECTORY_SEPARATOR . "synch.vikbooking.php");
2295 $vcm = new SynchVikBooking($row['id']);
2296 $vcm->setPushType('new')->sendRequest();
2297 }
2298 $session = JFactory::getSession();
2299 $vcmchanneldata = $session->get('vcmChannelData', '');
2300 if (!empty($vcmchanneldata)) {
2301 $session->set('vcmChannelData', '');
2302 }
2303 //end invoke VikChannelManager
2304 if (method_exists($obj, 'afterValidation')) {
2305 $obj->afterValidation(1);
2306 }
2307 } else {
2308 if (empty($array_result['skip_email'])) {
2309 $vbo_app = VikBooking::getVboApplication();
2310 $adsendermail = VikBooking::getSenderMail();
2311 $vbo_app->sendMail($adsendermail, $adsendermail, $recipient_mail, $adsendermail, JText::translate('VBPAYMENTNOTVER'), JText::translate('VBSERVRESP') . ":\n\n" . $array_result['log'], false);
2312 }
2313 if (!empty($array_result['log'])) {
2314 $q = "UPDATE `#__vikbooking_orders` SET `paymentlog`=".$dbo->quote($newpaymentlog)." WHERE `id`='" . $row['id'] . "';";
2315 $dbo->setQuery($q);
2316 $dbo->execute();
2317 }
2318 if (method_exists($obj, 'afterValidation')) {
2319 $obj->afterValidation(0);
2320 }
2321 }
2322 }
2323
2324 public function currencyconverter()
2325 {
2326 $session = JFactory::getSession();
2327 $pprices = VikRequest::getVar('prices', array(0));
2328 $pfromsymbol = VikRequest::getString('fromsymbol', '', 'request');
2329 $ptocurrency = VikRequest::getString('tocurrency', '', 'request');
2330 $pfromcurrency = VikRequest::getString('fromcurrency', '', 'request');
2331 $default_cur = !empty($pfromcurrency) ? $pfromcurrency : VikBooking::getCurrencyName();
2332 $response = array();
2333 if (!empty($default_cur) && !empty($pprices) && count($pprices) > 0 && !empty($ptocurrency)) {
2334 require_once(VBO_SITE_PATH . DS . "helpers" . DS ."currencyconverter.php");
2335 if ($default_cur != $ptocurrency) {
2336 $format = VikBooking::getNumberFormatData();
2337 $converter = new VboCurrencyConverter($default_cur, $ptocurrency, $pprices, explode(':', $format));
2338 $exchanged = $converter->convert();
2339 if (count($exchanged) > 0) {
2340 $response = $exchanged;
2341 $session->set('vboLastCurrency', $ptocurrency);
2342 } else {
2343 $conv_error = $converter->getError();
2344 $response['error'] = !empty($conv_error) ? $conv_error : JText::translate('VBERRCURCONVINVALIDDATA');
2345 }
2346 } else {
2347 $session->set('vboLastCurrency', $ptocurrency);
2348 foreach ($pprices as $i => $price) {
2349 $response[$i]['symbol'] = $pfromsymbol;
2350 $response[$i]['price'] = $price;
2351 }
2352 }
2353 } else {
2354 $response['error'] = JText::translate('VBERRCURCONVNODATA');
2355 }
2356 if(array_key_exists('error', $response)) {
2357 $session->set('vboLastCurrency', $ptocurrency);
2358 }
2359 echo json_encode($response);
2360 exit;
2361 }
2362
2363 public function signature()
2364 {
2365 VikRequest::setVar('view', 'signature');
2366 parent::display();
2367 }
2368
2369 public function storesignature()
2370 {
2371 $sid = VikRequest::getString('sid', '', 'request');
2372 $ts = VikRequest::getString('ts', '', 'request');
2373 $psignature = VikRequest::getString('signature', '', 'request', VIKREQUEST_ALLOWRAW);
2374 $ppad_width = VikRequest::getInt('pad_width', '', 'request');
2375 $ppad_ratio = VikRequest::getInt('pad_ratio', '', 'request');
2376 $pitemid = VikRequest::getInt('Itemid', '', 'request');
2377 $ptmpl = VikRequest::getString('tmpl', '', 'request');
2378 $dbo = JFactory::getDBO();
2379 $mainframe = JFactory::getApplication();
2380 $q = "SELECT * FROM `#__vikbooking_orders` WHERE `ts`=" . $dbo->quote($ts) . " AND `sid`=" . $dbo->quote($sid) . " AND `status`='confirmed';";
2381 $dbo->setQuery($q);
2382 $dbo->execute();
2383 if ($dbo->getNumRows() < 1) {
2384 VikError::raiseWarning('', 'Booking not found');
2385 $mainframe->redirect('index.php');
2386 exit;
2387 }
2388 $row = $dbo->loadAssoc();
2389 $tonight = mktime(23, 59, 59, date('n'), date('j'), date('Y'));
2390 if ($tonight > $row['checkout']) {
2391 VikError::raiseWarning('', 'Check-out date is in the past');
2392 $mainframe->redirect('index.php');
2393 exit;
2394 }
2395 $customer = array();
2396 $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'].";";
2397 $dbo->setQuery($q);
2398 $dbo->execute();
2399 if ($dbo->getNumRows() > 0) {
2400 $customer = $dbo->loadAssoc();
2401 }
2402 if (!(count($customer) > 0)) {
2403 VikError::raiseWarning('', 'Customer not found');
2404 $mainframe->redirect('index.php');
2405 exit;
2406 }
2407 //check if the signature has been submitted
2408 $signature_data = '';
2409 $cont_type = '';
2410 if (!empty($psignature)) {
2411 /**
2412 * Implemented safe filtering of base64-encoded signature image
2413 * to obtain content and file extension.
2414 *
2415 * @since 1.15.1 (J) - 1.5.4 (WP)
2416 */
2417 if (preg_match("/^data:image\/(png|jpe?g|svg);base64,([A-Za-z0-9\/=+]+)$/", $psignature, $safe_match)) {
2418 $signature_data = base64_decode($safe_match[2]);
2419 $cont_type = $safe_match[1];
2420 }
2421 }
2422 $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);
2423 if (empty($signature_data)) {
2424 VikError::raiseWarning('', JText::translate('VBOSIGNATUREISEMPTY'));
2425 $mainframe->redirect($ret_link);
2426 exit;
2427 }
2428 //write file
2429 $sign_fname = $row['id'].'_'.$row['sid'].'_'.$customer['id'].'.'.$cont_type;
2430 $filepath = VBO_ADMIN_PATH . DIRECTORY_SEPARATOR . 'resources' . DIRECTORY_SEPARATOR . 'idscans' . DIRECTORY_SEPARATOR . $sign_fname;
2431 $fp = fopen($filepath, 'w+');
2432 $bytes = fwrite($fp, $signature_data);
2433 fclose($fp);
2434 if ($bytes !== false && $bytes > 0) {
2435 //update the signature in the DB
2436 $q = "UPDATE `#__vikbooking_customers_orders` SET `signature`=".$dbo->quote($sign_fname)." WHERE `idorder`=".(int)$row['id'].";";
2437 $dbo->setQuery($q);
2438 $dbo->execute();
2439 $mainframe->enqueueMessage(JText::translate('VBOSIGNATURETHANKS'));
2440 //resize image for screens with high resolution
2441 if ($ppad_ratio > 1) {
2442 $new_width = floor(($ppad_width / 2));
2443 $creativik = new vikResizer();
2444 $creativik->proportionalImage($filepath, $filepath, $new_width, $new_width);
2445 }
2446 //
2447 } else {
2448 VikError::raiseWarning('', JText::translate('VBOERRSTORESIGNFILE'));
2449 }
2450 $mainframe->redirect($ret_link);
2451 exit;
2452 }
2453
2454 public function validatepin()
2455 {
2456 $cpin = VikBooking::getCPinIstance();
2457
2458 $ppin = VikRequest::getString('pin', '', 'request');
2459
2460 $response = [];
2461
2462 $customer = $cpin->getCustomerByPin($ppin);
2463 if (count($customer)) {
2464 $response = $customer;
2465 $response['success'] = 1;
2466
2467 if ($cpin->getCustomerCoupon($customer)) {
2468 // set flag indicating that the customer has got dedicated discounts
2469 $response['has_discounts'] = 1;
2470 }
2471 }
2472
2473 echo json_encode($response);
2474 exit;
2475 }
2476
2477 public function docancelbooking()
2478 {
2479 if (!JSession::checkToken()) {
2480 // missing CSRF-proof token
2481 VBOHttpDocument::getInstance()->close(403, JText::translate('JINVALID_TOKEN'));
2482 }
2483
2484 $dbo = JFactory::getDbo();
2485 $mainframe = JFactory::getApplication();
2486
2487 $psid = VikRequest::getString('sid', '', 'request');
2488 $pidorder = VikRequest::getString('idorder', '', 'request');
2489
2490 if (!empty($psid) && !empty($pidorder)) {
2491 $q = "SELECT * FROM `#__vikbooking_orders` WHERE `id`=".intval($pidorder)." AND `sid`=".$dbo->quote($psid)." AND `status`='confirmed';";
2492 $dbo->setQuery($q);
2493 $order = $dbo->loadAssocList();
2494 if ($order) {
2495 $pemail = VikRequest::getString('email', '', 'request');
2496 $preason = VikRequest::getString('reason', '', 'request');
2497 if (!empty($pemail) && !empty($preason)) {
2498 $to = VikBooking::getAdminMail();
2499 if (strpos($to, ',') !== false) {
2500 $all_recipients = explode(',', $to);
2501 foreach ($all_recipients as $k => $v) {
2502 if (empty($v)) {
2503 unset($all_recipients[$k]);
2504 }
2505 }
2506 if (count($all_recipients) > 0) {
2507 $to = $all_recipients;
2508 }
2509 }
2510 //check if the booking can be cancelled
2511 $days_to_arrival = 0;
2512 $is_refundable = 0;
2513 $daysadv_refund_arr = array();
2514 $daysadv_refund = 0;
2515 $now_info = getdate();
2516 $checkin_info = getdate($order[0]['checkin']);
2517 if ($now_info[0] < $checkin_info[0]) {
2518 while ($now_info[0] < $checkin_info[0]) {
2519 if (!($now_info['mday'] != $checkin_info['mday'] || $now_info['mon'] != $checkin_info['mon'] || $now_info['year'] != $checkin_info['year'])) {
2520 break;
2521 }
2522 $days_to_arrival++;
2523 $now_info = getdate(mktime(0, 0, 0, $now_info['mon'], ($now_info['mday'] + 1), $now_info['year']));
2524 }
2525 }
2526 $tars = array();
2527 $is_package = !empty($order[0]['pkg']) ? true : false;
2528 $orderrooms = array();
2529 $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;";
2530 $dbo->setQuery($q);
2531 $orderrooms = $dbo->loadAssocList();
2532 if ($orderrooms) {
2533 foreach($orderrooms as $kor => $or) {
2534 $num = $kor + 1;
2535 if($is_package === true || (!empty($or['cust_cost']) && $or['cust_cost'] > 0.00)) {
2536 //package or custom cost set from the back-end
2537 continue;
2538 }
2539 $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'] . "';";
2540 $dbo->setQuery($q);
2541 $tar = $dbo->loadAssocList();
2542 if ($tar) {
2543 $tars[$num] = $tar[0];
2544 }
2545 }
2546 }
2547 foreach ($tars as $num => $tar) {
2548 if ($tar['free_cancellation'] < 1) {
2549 //if at least one rate plan is non-refundable, the whole reservation cannot be cancelled
2550 $is_refundable = 0;
2551 $daysadv_refund_arr = array();
2552 break;
2553 }
2554 $is_refundable = 1;
2555 $daysadv_refund_arr[] = $tar['canc_deadline'];
2556 }
2557 //get the rate plan with the lowest cancellation deadline
2558 $daysadv_refund = count($daysadv_refund_arr) > 0 ? min($daysadv_refund_arr) : $daysadv_refund;
2559 $resmodcanc = VikBooking::getReservationModCanc();
2560 $resmodcanc = $days_to_arrival < 1 ? 0 : $resmodcanc;
2561 $resmodcancmin = VikBooking::getReservationModCancMin();
2562 $canc_allowed = ($resmodcanc > 1 && $resmodcanc != 2 && $is_refundable > 0 && $daysadv_refund <= $days_to_arrival && $days_to_arrival >= $resmodcancmin);
2563 if (!$canc_allowed) {
2564 VikError::raiseWarning('', JText::translate('VBOERRCANNOTCANCBOOK'));
2565 $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));
2566 exit;
2567 }
2568 //make the cancellation in the db and update the administrator notes with the reason specified by the customer
2569 $new_adminotes = JText::translate('VBOBOOKCANCELLEDEMAILSUBJ').' ('.$pemail.")\n".$preason."\n\n".$order[0]['adminnotes'];
2570 $q = "UPDATE `#__vikbooking_orders` SET `status`='cancelled',`adminnotes`=".$dbo->quote($new_adminotes)." WHERE `id`=".(int)$order[0]['id'].";";
2571 $dbo->setQuery($q);
2572 $dbo->execute();
2573 $q = "SELECT * FROM `#__vikbooking_ordersbusy` WHERE `idorder`=".(int)$order[0]['id'].";";
2574 $dbo->setQuery($q);
2575 $ordbusy = $dbo->loadAssocList();
2576 if ($ordbusy) {
2577 foreach ($ordbusy as $ob) {
2578 $q = "DELETE FROM `#__vikbooking_busy` WHERE `id`=".(int)$ob['idbusy'].";";
2579 $dbo->setQuery($q);
2580 $dbo->execute();
2581 }
2582 }
2583 $q = "DELETE FROM `#__vikbooking_ordersbusy` WHERE `idorder`=".(int)$order[0]['id'].";";
2584 $dbo->setQuery($q);
2585 $dbo->execute();
2586
2587 if ($order[0]['split_stay']) {
2588 // attempt to remove the transient record
2589 VBOFactory::getConfig()->remove('split_stay_' . $order[0]['id']);
2590 }
2591
2592 // Booking History
2593 $history_obj = VikBooking::getBookingHistoryInstance()->setBid($order[0]['id']);
2594 $history_obj->store('CW', $preason);
2595
2596 /**
2597 * Check if the amount paid can be refunded.
2598 *
2599 * @since 1.14 (J) - 1.4.0 (WP)
2600 */
2601 $admin_refund_error = '';
2602 $currencysymb = VikBooking::getCurrencySymb();
2603 $payment = VikBooking::getPayment($order[0]['idpayment']);
2604 $tn_driver = is_array($payment) ? $payment['file'] : null;
2605
2606 // transaction data validation callback
2607 $tn_data_callback = function($data) use ($tn_driver) {
2608 return (is_object($data) && isset($data->driver) && basename($data->driver, '.php') == basename($tn_driver, '.php'));
2609 };
2610 // get previous transactions
2611 $prev_tn_data = $history_obj->getEventsWithData(array('P0', 'PN'), $tn_data_callback);
2612
2613 if (is_array($prev_tn_data) && count($prev_tn_data) && $order[0]['totpaid'] > 0) {
2614 // previous transactions found and total paid > 0
2615 $refund_amount = $order[0]['totpaid'];
2616
2617 // push refund information for the payment gateway
2618 $order[0]['total_to_refund'] = $refund_amount;
2619 $order[0]['transaction'] = $prev_tn_data;
2620 $order[0]['refund_reason'] = $preason;
2621
2622 // push the transaction currency information
2623 $order[0]['transaction_currency'] = VikBooking::getCurrencyCodePp();
2624
2625 /**
2626 * @wponly The payment gateway is loaded
2627 * through the apposite dispatcher.
2628 */
2629 JLoader::import('adapter.payment.dispatcher');
2630 $obj = JPaymentDispatcher::getInstance('vikbooking', $payment['file'], $order[0], $payment['params']);
2631
2632 // check if refund is supported by this gateway
2633 if (method_exists($obj, 'isRefundSupported') && $obj->isRefundSupported()) {
2634 // perform the refund transaction
2635 $array_result = $obj->refund();
2636
2637 if ($array_result['verified'] != 1) {
2638 // refund failed
2639 $admin_refund_error .= "\nRefund transaction failed\n";
2640 // get the refund error message
2641 $admin_refund_error .= !empty($array_result['log']) && is_string($array_result['log']) ? $array_result['log'] : '';
2642 } else {
2643 // refund was successful
2644
2645 /**
2646 * The history event extra data will contain the "amount_paid" (refunded).
2647 *
2648 * @since 1.16.9 (J) - 1.6.9 (WP)
2649 */
2650 if (!empty($array_result['tot_paid'])) {
2651 // overwrite the requested amount with the returned one
2652 $refund_amount = (float)$array_result['tot_paid'];
2653 }
2654 $history_obj->setExtraData([
2655 'amount_paid' => $refund_amount,
2656 ]);
2657
2658 // update total paid, total and refund columns for the booking
2659 $booking = new stdClass;
2660 $booking->id = $order[0]['id'];
2661 if ($order[0]['totpaid'] > 0) {
2662 $booking->totpaid = (float)($order[0]['totpaid'] - $refund_amount);
2663 }
2664 if ($order[0]['total'] > 0) {
2665 $booking->total = (float)($order[0]['total'] - $refund_amount);
2666 }
2667 $booking->refund = (float)$order[0]['refund'] + $refund_amount;
2668 // update record in db
2669 $dbo->updateObject('#__vikbooking_orders', $booking, 'id');
2670
2671 // store the refund event
2672 $event_descr = [
2673 '(' . $payment['name'] . ')',
2674 $currencysymb . ' ' . VikBooking::numberFormat($refund_amount),
2675 ];
2676 $history_obj->store('RF', implode("\n", $event_descr));
2677 }
2678 }
2679 }
2680
2681 // invoke VikChannelManager
2682 if (is_file(VCM_SITE_PATH . DIRECTORY_SEPARATOR . "helpers" . DIRECTORY_SEPARATOR . "synch.vikbooking.php")) {
2683 $vcm_obj = VikBooking::getVcmInvoker();
2684 $vcm_obj->setOids(array($order[0]['id']))->setSyncType('cancel');
2685 $vcm_obj->doSync();
2686 }
2687 // end invoke VikChannelManager
2688
2689 //send email to the administrator
2690 $subject = JText::translate('VBOBOOKCANCELLEDEMAILSUBJ');
2691 // @wponly we do not need to pass the "best item id"
2692 $uri = VikBooking::externalroute("index.php?option=com_vikbooking&view=booking&sid=" . $order[0]['sid'] . "&ts=" . $order[0]['ts'], false);
2693 $msg = JText::sprintf('VBOBOOKCANCELLEDEMAILHEAD', $order[0]['id'], $uri) . "\n\n" . $preason . $admin_refund_error;
2694 $vbo_app = VikBooking::getVboApplication();
2695 $adsendermail = VikBooking::getSenderMail();
2696 $vbo_app->sendMail($adsendermail, $adsendermail, $to, $pemail, $subject, $msg, false);
2697
2698 // SMS
2699 VikBooking::sendBookingSMS($order[0]['id']);
2700
2701 // send cancellation email notification to guest
2702 VikBooking::sendBookingEmail($order[0]['id'], ['guest']);
2703
2704 // go back to the booking details page to show the new status
2705 $mainframe->enqueueMessage(JText::translate('VBOBOOKCANCELLEDRESP'));
2706 $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));
2707 } else {
2708 VikError::raiseWarning('', JText::translate('VBOERRMISSDATA'));
2709 $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));
2710 }
2711 } else {
2712 $mainframe->redirect("index.php");
2713 }
2714 } else {
2715 $mainframe->redirect("index.php");
2716 }
2717 }
2718
2719 public function cancelmodification()
2720 {
2721 $psid = VikRequest::getString('sid', '', 'request');
2722 $pidorder = VikRequest::getString('id', '', 'request');
2723 $dbo = JFactory::getDBO();
2724 $session = JFactory::getSession();
2725 $mainframe = JFactory::getApplication();
2726 if (!empty($psid) && !empty($pidorder)) {
2727 $q = "SELECT * FROM `#__vikbooking_orders` WHERE `id`=".intval($pidorder)." AND `sid`=".$dbo->quote($psid)." AND `status`='confirmed';";
2728 $dbo->setQuery($q);
2729 $dbo->execute();
2730 if ($dbo->getNumRows() == 1) {
2731 $order = $dbo->loadAssocList();
2732 //unset the session value and redirect
2733 $session->set('vboModBooking', '');
2734 $mainframe->redirect(JRoute::rewrite("index.php?option=com_vikbooking&view=booking&sid=".$order[0]['sid']."&ts=".$order[0]['ts'], false));
2735 } else {
2736 $mainframe->redirect("index.php");
2737 }
2738 } else {
2739 $mainframe->redirect("index.php");
2740 }
2741 }
2742
2743 public function tac_av_l()
2744 {
2745 require_once(VBO_SITE_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'tac.vikbooking.php');
2746
2747 //Channel Rates Module
2748 $pvbomodule = VikRequest::getInt('vbomodule', 0, 'request');
2749 $pshow_tax = VikRequest::getInt('show_tax', 0, 'request');
2750 $pdef_rplan = VikRequest::getInt('def_rplan', 0, 'request');
2751 $pchannels_sel = VikRequest::getVar('channels_sel', array());
2752 $pcheckin = VikRequest::getString('checkin', '', 'request');
2753 $pcheckout = VikRequest::getString('checkout', '', 'request');
2754 if ($pvbomodule > 0 && !empty($pcheckin) && !empty($pcheckout)) {
2755 //this is an ajax request, probably made by the module Vik Booking Channel Rates
2756 //we need to prepare some variables before calling the method.
2757 $start_date = date('Y-m-d', VikBooking::getDateTimestamp($pcheckin, 12, 0));
2758 $end_date = date('Y-m-d', VikBooking::getDateTimestamp($pcheckout, 10, 0));
2759 //set (only some) request variables (the rest is sent via Ajax)
2760 VikRequest::setVar('e4jauth', md5('vbo.e4j.vbo'));
2761 VikRequest::setVar('req_type', 'hotel_availability');
2762 VikRequest::setVar('start_date', $start_date);
2763 VikRequest::setVar('end_date', $end_date);
2764 //make call to get the result
2765 TACVBO::$getArray = true;
2766 $website_rates = TACVBO::tac_av_l([
2767 // always force adults and children to be injected as arguments to avoid request conflicts
2768 'adults' => VikRequest::getVar('adults', array()),
2769 'children' => VikRequest::getVar('children', array()) ?: [0],
2770 ]);
2771 //validate response
2772 if (!is_array($website_rates)) {
2773 //error returned
2774 echo json_encode(array('e4j.error' => $website_rates));
2775 exit;
2776 }
2777 if (is_array($website_rates) && isset($website_rates['e4j.error'])) {
2778 //another type of error returned
2779 echo json_encode($website_rates);
2780 exit;
2781 }
2782 if (is_array($website_rates) && !(count($website_rates) > 0)) {
2783 //empty response
2784 echo json_encode(array('e4j.error' => 'empty response'));
2785 exit;
2786 }
2787 //get the list of channels connected, filtered by ID
2788 $channels_map = VikBooking::getChannelsMap($pchannels_sel);
2789 //get the array with the lowest and preferred room rate
2790 $best_room_rate = VikBooking::getBestRoomRate($website_rates, $pdef_rplan);
2791 //get the charge/discount value for the OTAs rates from the Bulk Rates Cache of VCM
2792 $otas_rates_val = VikBooking::getOtasRatesVal($best_room_rate, true);
2793
2794 $otas_rmod = '';
2795 $otas_rmodpcent = 0;
2796 $otas_rmodval = 0;
2797 $otas_rmod_channels = array();
2798 if (!empty($otas_rates_val)) {
2799 if (is_array($otas_rates_val)) {
2800 $otas_rmod_channels = $otas_rates_val;
2801 $use_rates_val = $otas_rates_val[0];
2802 } else {
2803 // string
2804 $use_rates_val = $otas_rates_val;
2805 }
2806 $otas_rmod = substr($use_rates_val, 0, 1); // + or - (charge or discount)
2807 $otas_rmodpcent = substr($use_rates_val, -1) == '%' ? 1 : 0;
2808 $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)));
2809 }
2810 if (!count($best_room_rate)) {
2811 // nothing to parse
2812 echo json_encode(array('e4j.error' => 'no rates'));
2813 exit;
2814 }
2815 // build the response
2816 $final_cost = $pshow_tax > 0 ? ($best_room_rate['cost'] + $best_room_rate['taxes']) : $best_room_rate['cost'];
2817 $rates_resp = array(
2818 'website' => VikBooking::numberFormat($final_cost)
2819 );
2820 if (count($channels_map)) {
2821 $rates_resp['channels'] = array();
2822 }
2823 foreach ($channels_map as $ch) {
2824 $ch_final_cost = $final_cost;
2825
2826 /**
2827 * Check if an alteration for this channel has been specified.
2828 *
2829 * @since 1.15.0 (J) - 1.5.0 (WP)
2830 */
2831 $use_otas_rmod = $otas_rmod;
2832 $use_otas_rmodpcent = $otas_rmodpcent;
2833 $use_otas_rmodval = $otas_rmodval;
2834 if (is_array($otas_rmod_channels) && isset($otas_rmod_channels[$ch['id']])) {
2835 $use_rates_val = $otas_rmod_channels[$ch['id']];
2836 $use_otas_rmod = substr($use_rates_val, 0, 1); //+ or - (charge or discount)
2837 $use_otas_rmodpcent = substr($use_rates_val, -1) == '%' ? 1 : 0;
2838 $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)));
2839 }
2840
2841 if (!empty($use_otas_rmod)) {
2842 if ($use_otas_rmod == '+') {
2843 // charge
2844 if ($use_otas_rmodpcent > 0) {
2845 // percentage
2846 $ch_final_cost = $ch_final_cost * (100 + $use_otas_rmodval) / 100;
2847 } else {
2848 // absolute
2849 $ch_final_cost += $use_otas_rmodval * (!empty($best_room_rate['days']) ? $best_room_rate['days'] : 1);
2850 }
2851 } else {
2852 // discount (must be a fool)
2853 if ($use_otas_rmodpcent > 0) {
2854 // percentage
2855 $ch_final_cost = $ch_final_cost / (($use_otas_rmodval / 100) + 1);
2856 } else {
2857 // absolute
2858 $ch_final_cost -= $use_otas_rmodval * (!empty($best_room_rate['days']) ? $best_room_rate['days'] : 1);
2859 }
2860 }
2861 }
2862
2863 $rates_resp['channels'][$ch['id']] = VikBooking::numberFormat($ch_final_cost);
2864 }
2865 // output the response
2866 echo json_encode($rates_resp);
2867 exit;
2868 }
2869
2870 // proceed with the standard request (that will exit the process)
2871 TACVBO::tac_av_l();
2872 }
2873
2874 /**
2875 * Front-end authentication for the operators
2876 * through their authentication code.
2877 *
2878 * @since 1.11
2879 */
2880 public function operatorlogin()
2881 {
2882 /**
2883 * Extra security fix for the login form token.
2884 *
2885 * @since September 8th 2020
2886 */
2887 if (!JFactory::getSession()->checkToken()) {
2888 throw new Exception("The security token did not match.", 403);
2889 }
2890 //
2891
2892 $app = JFactory::getApplication();
2893 $pauthcode = VikRequest::getString('authcode', '', 'request');
2894 $pitemid = VikRequest::getInt('Itemid', '', 'request');
2895 /**
2896 * We add "&auth=1" to the query string just to avoid caching for a redirect to the same login page URI.
2897 *
2898 * @since September 9th 2020
2899 */
2900 $goto = JRoute::rewrite('index.php?option=com_vikbooking&view=operators&auth=1'.(!empty($pitemid) ? '&Itemid='.$pitemid : ''), false);
2901
2902 if (empty($pauthcode) || !VikBooking::getOperatorInstance()->authOperator($pauthcode)) {
2903 // print warning message
2904 VikError::raiseWarning('', JText::translate('VBOOPERINVAUTHCODE'));
2905 }
2906
2907 $app->redirect($goto);
2908 }
2909
2910 /**
2911 * Front-end logout for the operators.
2912 *
2913 * @since 1.11
2914 */
2915 public function operatorlogout()
2916 {
2917 $app = JFactory::getApplication();
2918 $pitemid = VikRequest::getInt('Itemid', '', 'request');
2919 $goto = JRoute::rewrite('index.php?option=com_vikbooking&view=operators'.(!empty($pitemid) ? '&Itemid='.$pitemid : ''), false);
2920
2921 VikBooking::getOperatorInstance()->logoutOperator();
2922
2923 $app->redirect($goto);
2924 }
2925
2926 /**
2927 * Front-end Pre Check-in submit of the guests details.
2928 *
2929 * @since 1.12
2930 */
2931 public function storeprecheckin()
2932 {
2933 $dbo = JFactory::getDbo();
2934 $app = JFactory::getApplication();
2935 $sid = VikRequest::getString('sid', '', 'request');
2936 $ts = VikRequest::getString('ts', '', 'request');
2937 $pguests = VikRequest::getVar('guests', array());
2938 $pitemid = VikRequest::getInt('Itemid', 0, 'request');
2939
2940 $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';";
2941 $dbo->setQuery($q);
2942 $dbo->execute();
2943 if (!$dbo->getNumRows()) {
2944 throw new Exception('Booking not found', 404);
2945 }
2946 $order = $dbo->loadAssoc();
2947
2948 $orderrooms = array();
2949 $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;";
2950 $dbo->setQuery($q);
2951 $dbo->execute();
2952 if (!$dbo->getNumRows()) {
2953 throw new Exception('No rooms found', 404);
2954 }
2955 $orderrooms = $dbo->loadAssocList();
2956
2957 $q = "SELECT * FROM `#__vikbooking_customers_orders` WHERE `idorder`=".(int)$order['id'].";";
2958 $dbo->setQuery($q);
2959 $dbo->execute();
2960 if (!$dbo->getNumRows()) {
2961 throw new Exception('No customer found', 404);
2962 }
2963 $custorder = $dbo->loadAssoc();
2964
2965 // booking details page
2966 $goto = JRoute::rewrite('index.php?option=com_vikbooking&view=booking&sid=' . $order['sid'] . '&ts=' . $order['ts'] . (!empty($pitemid) ? '&Itemid=' . $pitemid : ''), false);
2967 if (empty($order['sid']) && !empty($order['idorderota'])) {
2968 // booking details page for OTA bookings
2969 $goto = JRoute::rewrite('index.php?option=com_vikbooking&view=booking&sid=' . $order['idorderota'] . '&ts=' . $order['ts'] . (!empty($pitemid) ? '&Itemid=' . $pitemid : ''), false);
2970 }
2971
2972 // make sure pre-checkin is allowed
2973 $precheckin = VikBooking::precheckinEnabled();
2974 if ($precheckin) {
2975 // make sure the limit of days in advance is reflected
2976 $precheckin_mind = VikBooking::precheckinMinOffset();
2977 $precheckin_lim_ts = strtotime("+{$precheckin_mind} days 00:00:00");
2978 $precheckin = ($precheckin_lim_ts <= $order['checkin'] || ($precheckin_mind === 1 && time() <= $order['checkin']));
2979 }
2980 if (!$precheckin) {
2981 // raise error and redirect in case of website or OTA booking
2982 VikError::raiseWarning('', 'Pre-checkin not allowed at this time');
2983 $app->redirect($goto);
2984 exit;
2985 }
2986
2987 // build guest details
2988 $guests_details = array();
2989
2990 // list of keys for the guests details collected via front-end
2991 $front_keys = array();
2992
2993 foreach ($pguests as $ind => $adults) {
2994 foreach ($adults as $aduind => $details) {
2995 foreach ($details as $detkey => $detval) {
2996 if (!in_array($detkey, $front_keys)) {
2997 // push the key of the guest details for later comparison
2998 array_push($front_keys, $detkey);
2999 }
3000 if (strlen($detval)) {
3001 // push value only if not empty
3002 if (!isset($guests_details[$ind])) {
3003 $guests_details[$ind] = array();
3004 }
3005 if (!isset($guests_details[$ind][$aduind])) {
3006 $guests_details[$ind][$aduind] = array();
3007 }
3008 $guests_details[$ind][$aduind][$detkey] = $detval;
3009 }
3010 }
3011 }
3012 }
3013
3014 /**
3015 * Compare the current data collected to the back-end pax_data in case there are some
3016 * fields dedicated to just the back-end for the admins (like extra_notes), and merge.
3017 */
3018 $curpaxdata = json_decode($custorder['pax_data'], true);
3019 if (is_array($curpaxdata) && count($curpaxdata)) {
3020 foreach ($curpaxdata as $ind => $adults) {
3021 if (!isset($guests_details[$ind])) {
3022 // current pax data include a room not present, set it to not lose it
3023 $guests_details[$ind] = $adults;
3024 }
3025 foreach ($adults as $aduind => $details) {
3026 if (!isset($guests_details[$ind][$aduind])) {
3027 // current pax data include a guest not present, set it to not lose it
3028 $guests_details[$ind][$aduind] = $details;
3029 }
3030 foreach ($details as $detkey => $detval) {
3031 if (!in_array($detkey, $front_keys)) {
3032 // merge this key probably reserved to the back-end
3033 $guests_details[$ind][$aduind][$detkey] = $detval;
3034 }
3035 }
3036 }
3037 }
3038 }
3039
3040 // update checkin information
3041 $q = "UPDATE `#__vikbooking_customers_orders` SET `pax_data`=".$dbo->quote(json_encode($guests_details))." WHERE `id`=".(int)$custorder['id'].";";
3042 $dbo->setQuery($q);
3043 $dbo->execute();
3044
3045 // Booking History
3046 VikBooking::getBookingHistoryInstance()->setBid($order['id'])->store('PC');
3047
3048 // print success message and redirect
3049 $app->enqueueMessage(JText::translate('VBOSUBMITPRECHECKINTNKS'));
3050 $app->redirect($goto);
3051 }
3052
3053 /**
3054 * Upsell extra services/options.
3055 *
3056 * @since 1.13 (J) - 1.3.0 (WP)
3057 */
3058 public function upsellextras()
3059 {
3060 $dbo = JFactory::getDbo();
3061 $app = JFactory::getApplication();
3062 $sid = VikRequest::getString('sid', '', 'request');
3063 $ts = VikRequest::getString('ts', '', 'request');
3064 $pitemid = VikRequest::getInt('Itemid', 0, 'request');
3065 $paddopt = VikRequest::getVar('addopt', array());
3066
3067 if (!$paddopt) {
3068 throw new Exception('No extra services selected', 404);
3069 }
3070
3071 $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) . ";";
3072 $dbo->setQuery($q);
3073 $dbo->execute();
3074 if (!$dbo->getNumRows()) {
3075 throw new Exception('Booking not found', 404);
3076 }
3077 $order = $dbo->loadAssoc();
3078
3079 $orderrooms = [];
3080 $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;";
3081 $dbo->setQuery($q);
3082 $dbo->execute();
3083 if (!$dbo->getNumRows()) {
3084 throw new Exception('No rooms found', 404);
3085 }
3086 $orderrooms = $dbo->loadAssocList();
3087
3088 // availability helper
3089 $av_helper = VikBooking::getAvailabilityInstance();
3090
3091 // room stay dates in case of split stay
3092 $room_stay_dates = [];
3093 if ($order['split_stay']) {
3094 if ($order['status'] == 'confirmed') {
3095 $room_stay_dates = $av_helper->loadSplitStayBusyRecords($order['id']);
3096 } else {
3097 $room_stay_dates = VBOFactory::getConfig()->getArray('split_stay_' . $order['id'], []);
3098 }
3099 }
3100
3101 // load all valid and existing options as a security measure
3102 $alloptions = [];
3103 $q = "SELECT * FROM `#__vikbooking_optionals`;";
3104 $dbo->setQuery($q);
3105 $dbo->execute();
3106 if (!$dbo->getNumRows()) {
3107 throw new Exception('No options found', 404);
3108 }
3109 $records = $dbo->loadAssocList();
3110 foreach ($records as $v) {
3111 $alloptions[(int)$v['id']] = $v;
3112 }
3113
3114 $extras_booked = [];
3115 foreach ($orderrooms as $kor => $or) {
3116 if (!isset($paddopt[$kor]) || !count($paddopt[$kor])) {
3117 continue;
3118 }
3119
3120 // determine proper nights of stay
3121 $room_stay_nights = $order['days'];
3122 if ($order['split_stay'] && count($room_stay_dates) && isset($room_stay_dates[$kor]) && $room_stay_dates[$kor]['idroom'] == $or['idroom']) {
3123 $room_stay_checkin = !empty($room_stay_dates[$kor]['checkin_ts']) ? $room_stay_dates[$kor]['checkin_ts'] : $room_stay_dates[$kor]['checkin'];
3124 $room_stay_checkout = !empty($room_stay_dates[$kor]['checkout_ts']) ? $room_stay_dates[$kor]['checkout_ts'] : $room_stay_dates[$kor]['checkout'];
3125 $room_stay_nights = $av_helper->countNightsOfStay($room_stay_checkin, $room_stay_checkout);
3126 }
3127
3128 $extraoptstr = '';
3129 foreach ($paddopt[$kor] as $optid => $quant) {
3130 if (strpos($or['optionals'], $optid . ':') === 0 || strpos($or['optionals'], ';' . $optid . ':') > 0) {
3131 // this option has already been booked, skip it
3132 continue;
3133 }
3134 if (!isset($alloptions[(int)$optid])) {
3135 // this option ID does not exist, skip it
3136 continue;
3137 }
3138 $extraoptstr .= $optid . ':' . (int)$quant . ';';
3139 // push option booked
3140 array_push($extras_booked, array(
3141 'id' => $optid,
3142 'idroom' => $or['idroom'],
3143 'name' => $alloptions[(int)$optid]['name'],
3144 'quant' => $quant,
3145 'room_cost' => (!empty($or['cust_cost']) ? $or['cust_cost'] : $or['room_cost']),
3146 'room_name' => $or['room_name'],
3147 'optcost' => $alloptions[(int)$optid]['cost'],
3148 'adults' => $or['adults'],
3149 'children' => $or['children'],
3150 'nights' => $room_stay_nights,
3151 ));
3152 }
3153 // update options for this room record
3154 $newoptstr = $or['optionals'] . $extraoptstr;
3155 $q = "UPDATE `#__vikbooking_ordersrooms` SET `optionals`=" . $dbo->quote($newoptstr) . " WHERE `id`={$or['id']};";
3156 $dbo->setQuery($q);
3157 $dbo->execute();
3158 }
3159
3160 // increase booking total amount and build event log for the history
3161 $currency = VikBooking::getCurrencySymb();
3162 $totrooms = count($orderrooms);
3163 $increase = 0;
3164 $add_tax = 0;
3165 $extraslog = [];
3166 foreach ($extras_booked as $extra) {
3167 $o = $alloptions[(int)$extra['id']];
3168 if ((int)$o['pcentroom']) {
3169 // make sure we have a cost for the room, or we should skip this type of option for "incomplete" bookings
3170 if (empty($extra['room_cost'])) {
3171 continue;
3172 }
3173 $o['cost'] = ($extra['room_cost'] * $o['cost'] / 100);
3174 }
3175 $optcost = intval($o['perday']) == 1 ? ($o['cost'] * $extra['nights']) : $o['cost'];
3176 if (!empty($o['maxprice']) && $o['maxprice'] > 0 && $optcost > $o['maxprice']) {
3177 $optcost = $o['maxprice'];
3178 }
3179 if ($o['perperson'] == 1) {
3180 $optcost = $optcost * $extra['adults'];
3181 }
3182 $optcost *= $extra['quant'];
3183 $floatoptprice = VikBooking::sayOptionalsPlusIva($optcost, $o['idiva']);
3184 $netoptprice = VikBooking::sayOptionalsMinusIva($optcost, $o['idiva']);
3185 $increase += $floatoptprice;
3186 $add_tax += $floatoptprice - $netoptprice;
3187 array_push($extraslog, ($totrooms > 1 ? $extra['room_name'] . ': ' : '') . $extra['name'] . ($extra['quant'] > 1 ? ' (x' . $extra['quant'] . ')' : '') . ' ' . $currency . ' ' . VikBooking::numberFormat($floatoptprice));
3188 }
3189
3190 $newtotbooking = $order['total'] + $increase;
3191 $new_tot_taxes = $order['tot_taxes'] + $add_tax;
3192 /**
3193 * Important: the 'paymcount' should be increased only if the status is
3194 * "confirmed" or no rooms may be occupied when receiving a payment.
3195 */
3196 $q = "UPDATE `#__vikbooking_orders` SET `total`=" . $dbo->quote($newtotbooking) . ", `paymcount`=" . ($order['status'] == 'confirmed' && (int)$order['paymcount'] < 1 ? '1' : $order['paymcount']) . ", `tot_taxes`=" . $dbo->q($new_tot_taxes) . ", `payable`=" . $dbo->quote(((float)$order['payable'] + $increase)) . " WHERE `id`={$order['id']};";
3197 $dbo->setQuery($q);
3198 $dbo->execute();
3199
3200 // Booking History
3201 VikBooking::getBookingHistoryInstance()->setBid($order['id'])->store('UE', implode("\n", $extraslog));
3202
3203 // send email notification to guest and admin
3204 VikBooking::sendBookingEmail($order['id'], array('guest', 'admin'));
3205
3206 $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);
3207 $app->enqueueMessage(JText::translate('VBOUPSELLRESULTOK'));
3208 $app->redirect($goto);
3209 }
3210
3211 /**
3212 * Submits a new review.
3213 *
3214 * @since 1.3.0
3215 */
3216 public function sendreview()
3217 {
3218 $dbo = JFactory::getDbo();
3219 $app = JFactory::getApplication();
3220 $vbo_tn = VikBooking::getTranslator();
3221 $sid = VikRequest::getString('sid', '', 'request');
3222 $ts = VikRequest::getString('ts', '', 'request');
3223 $ratingmess = VikRequest::getString('ratingmess', '', 'request');
3224 $rating = VikRequest::getVar('rating', array(), 'request', 'array');
3225 $pitemid = VikRequest::getInt('Itemid', 0, 'request');
3226
3227 $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';";
3228 $dbo->setQuery($q);
3229 $dbo->execute();
3230 if (!$dbo->getNumRows()) {
3231 throw new Exception('Booking not found', 404);
3232 }
3233 $order = $dbo->loadAssoc();
3234
3235 // make sure a review can be left for this booking
3236 if (!VikBooking::canBookingBeReviewed($order)) {
3237 throw new Exception('Cannot leave a review at this time', 403);
3238 }
3239
3240 $orderrooms = array();
3241 $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;";
3242 $dbo->setQuery($q);
3243 $dbo->execute();
3244 if (!$dbo->getNumRows()) {
3245 throw new Exception('No rooms found', 404);
3246 }
3247 $orderrooms = $dbo->loadAssocList();
3248
3249 // get customer information
3250 $customer = VikBooking::getCPinIstance()->getCustomerFromBooking($order['id']);
3251
3252 // booking details page
3253 $goto = JRoute::rewrite('index.php?option=com_vikbooking&view=booking&sid=' . $order['sid'] . '&ts=' . $order['ts'] . (!empty($pitemid) ? '&Itemid=' . $pitemid : ''), false);
3254 if (empty($order['sid']) && !empty($order['idorderota'])) {
3255 // booking details page for OTA bookings
3256 $goto = JRoute::rewrite('index.php?option=com_vikbooking&view=booking&sid=' . $order['idorderota'] . '&ts=' . $order['ts'] . (!empty($pitemid) ? '&Itemid=' . $pitemid : ''), false);
3257 }
3258
3259 // reviews settings
3260 $gr_approval = VikBooking::guestReviewsApproval();
3261 $gr_type = VikBooking::guestReviewsType();
3262 $gr_services = VikBooking::guestReviewsServices();
3263 $rawservices = $gr_services;
3264 $vbo_tn->translateContents($gr_services, '#__vikbooking_greview_service');
3265
3266 // make sure all ratings are not empty
3267 if ($gr_type == 'global') {
3268 if (empty($rating[0]) || intval($rating[0]) < 1 || intval($rating[0]) > 5) {
3269 // no or invalid single-rating received
3270 VikError::raiseWarning('', 'Please rate your experience to leave a review.');
3271 $app->redirect($goto);
3272 exit;
3273 }
3274 } else {
3275 if (count($gr_services) != count($rating) || !count($rating)) {
3276 // something is missing
3277 VikError::raiseWarning('', 'Please rate your experience for all services');
3278 $app->redirect($goto);
3279 exit;
3280 }
3281 // make sure all ratings are valid
3282 foreach ($rating as $k => $score) {
3283 if (empty($score) || intval($score) < 1 || intval($score) > 5) {
3284 // no or invalid rating received for this service
3285 VikError::raiseWarning('', 'Please rate your experience to leave a review (missing ' . (isset($gr_services[$k]) ? $gr_services[$k]['service_name'] : '-----') . ').');
3286 $app->redirect($goto);
3287 exit;
3288 }
3289 }
3290 }
3291
3292 // average review score (in base 10) and services map
3293 $avg_score = 0;
3294 $serv_scores = array();
3295
3296 // gather the information
3297 foreach ($rating as $k => $score) {
3298 // rating in base 10
3299 $score = ((int)$score * 2);
3300 //
3301 $avg_score += $score;
3302 if ($gr_type == 'service' && isset($gr_services[$k]) && !empty($gr_services[$k]['service_name'])) {
3303 $skey = $gr_services[$k]['service_name'];
3304 $serv_scores[$skey] = $score;
3305 }
3306 }
3307 // this will be the review_score
3308 $avg_score = round(($avg_score / count($rating)), 2);
3309
3310 // build review content object
3311 $review_content = new stdClass;
3312
3313 // creation date
3314 $review_content->created_timestamp = date('Y-m-d H:i:s');
3315
3316 // scoring per service (if any)
3317 $review_content->scoring = new stdClass;
3318 if (count($serv_scores)) {
3319 $counter = 0;
3320 foreach ($serv_scores as $snametranx => $servscore) {
3321 // we build the object with the original names of the services, as they could have been translated
3322 $origskey = $rawservices[$counter]['service_name'];
3323 $review_content->scoring->{$origskey} = $servscore;
3324 $counter++;
3325 }
3326 }
3327 // scoring total value ("review_score" is a protected key) is added no matter of the review type (service/global)
3328 $review_content->scoring->review_score = $avg_score;
3329
3330 // reviewer information
3331 $review_content->reviewer = new stdClass;
3332 $customer_name = '';
3333 if (count($customer)) {
3334 $review_content->reviewer->name = $customer['first_name'];
3335 $review_content->reviewer->country_code = $customer['country'];
3336 $customer_name = $customer['first_name'] . ' ' . $customer['last_name'];
3337 } else {
3338 $revuname = '';
3339 if (!empty($order['custdata'])) {
3340 $uinfos = explode("\n", $order['custdata']);
3341 $first_info = explode(':', $uinfos[0]);
3342 if (count($first_info) > 1) {
3343 unset($first_info[0]);
3344 $revuname = implode(':', $first_info);
3345 } else {
3346 $revuname = trim($first_info[0]);
3347 }
3348 }
3349 $review_content->reviewer->name = $revuname;
3350 $review_content->reviewer->country_code = $order['country'];
3351 $customer_name = $revuname;
3352 }
3353
3354 // maximum 2000 chars for the message review to avoid spammers
3355 if (!empty($ratingmess) && strlen($ratingmess) > 2000) {
3356 $ratingmess = substr($ratingmess, 0, 2000);
3357 }
3358
3359 // review message
3360 $review_content->content = new stdClass;
3361 $review_content->content->message = !empty($ratingmess) ? $ratingmess : null;
3362
3363 // null reply
3364 $review_content->reply = null;
3365
3366 // check if multiple accounts to find the property name
3367 $property_name = null;
3368 $has_multiaccounts = false;
3369 $multi_map = array();
3370 $q = "SELECT * FROM `#__vikchannelmanager_roomsxref`;";
3371 $dbo->setQuery($q);
3372 $dbo->execute();
3373 if ($dbo->getNumRows()) {
3374 $xref_data = $dbo->loadAssocList();
3375 foreach ($xref_data as $xref) {
3376 if (empty($xref['prop_params'])) {
3377 continue;
3378 }
3379 if (!isset($multi_map[$xref['idchannel']])) {
3380 $multi_map[$xref['idchannel']] = array();
3381 }
3382 if (!isset($multi_map[$xref['idchannel']][$xref['prop_params']])) {
3383 $multi_map[$xref['idchannel']][$xref['prop_params']] = 0;
3384 }
3385 $multi_map[$xref['idchannel']][$xref['prop_params']]++;
3386 }
3387 foreach ($multi_map as $ch_id => $ch_params) {
3388 if (count($ch_params) > 1) {
3389 $has_multiaccounts = true;
3390 break;
3391 }
3392 }
3393 }
3394 if ($has_multiaccounts && (int)$order['roomsnum'] === 1) {
3395 // find the category name of the room booked (if any, and if one room booked)
3396 $q = "SELECT `idcat` FROM `#__vikbooking_rooms` WHERE `id`={$orderrooms[0]['idroom']};";
3397 $dbo->setQuery($q);
3398 $dbo->execute();
3399 if ($dbo->getNumRows()) {
3400 $allcats = $dbo->loadResult();
3401 if (!empty($allcats)) {
3402 $parts = explode(';', $allcats);
3403 if (count($parts) === 2) {
3404 // just one category, get the name of it
3405 $property_name = VikBooking::getCategoryName($parts[0]);
3406 }
3407 }
3408 }
3409 if (empty($property_name)) {
3410 // category not found, get the room name
3411 $property_name = $orderrooms[0]['name'];
3412 }
3413 }
3414
3415 // create record
3416 $review_record = new stdClass;
3417 $review_record->review_id = -1;
3418 $review_record->prop_first_param = null;
3419 $review_record->prop_name = $property_name;
3420 $review_record->channel = null;
3421 $review_record->uniquekey = 0;
3422 $review_record->idorder = $order['id'];
3423 $review_record->dt = JFactory::getDate()->toSql(true);
3424 $review_record->customer_name = $customer_name;
3425 $review_record->lang = JFactory::getLanguage()->getTag();
3426 $review_record->score = $avg_score;
3427 $review_record->country = $order['country'];
3428 $review_record->content = json_encode($review_content);
3429 $review_record->published = ($gr_approval == 'auto' ? 1 : 0);
3430
3431 // insert review
3432 if ($dbo->insertObject('#__vikchannelmanager_otareviews', $review_record, 'id')) {
3433 $app->enqueueMessage(JText::translate('VBOTHANKSREVIEWLEFT'));
3434 // Booking History
3435 VikBooking::getBookingHistoryInstance()->setBid($order['id'])->store('GR');
3436 } else {
3437 VikError::raiseWarning('', JText::translate('VBOREVIEWGENERROR'));
3438 }
3439
3440 // update global score for website and this property (if multiple accounts)
3441 $globscore_id = null;
3442 $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));
3443 $dbo->setQuery($q, 0, 1);
3444 $dbo->execute();
3445 if ($dbo->getNumRows()) {
3446 $globscore_id = $dbo->loadResult();
3447 }
3448 // select score for all reviews for the website and this account
3449 $services_scores = array();
3450 $services_revscount = array();
3451 $revs_count = 0;
3452 $super_tot = 0;
3453 $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)) . ";";
3454 $dbo->setQuery($q);
3455 $dbo->execute();
3456 if ($dbo->getNumRows()) {
3457 $all_scores = $dbo->loadAssocList();
3458 $revs_count += count($all_scores);
3459 foreach ($all_scores as $s) {
3460 $super_tot += $s['score'];
3461 // check if scores were given per service
3462 $s['content'] = json_decode($s['content'], true);
3463 if (isset($s['content']['scoring']) && count($s['content']['scoring']) > 1) {
3464 // review was left for services
3465 foreach ($s['content']['scoring'] as $sname => $sval) {
3466 if (!isset($services_scores[$sname])) {
3467 $services_scores[$sname] = 0;
3468 $services_revscount[$sname] = 0;
3469 }
3470 $services_scores[$sname] += $sval;
3471 $services_revscount[$sname]++;
3472 }
3473 }
3474 }
3475 }
3476 // global average score
3477 $revs_count = $revs_count > 0 ? $revs_count : 1;
3478 $glob_avg_score = ($super_tot / $revs_count);
3479 // services average score
3480 $glob_servs_avg_score = array();
3481 foreach ($services_scores as $sname => $val) {
3482 $services_revscount[$sname] = isset($services_revscount[$sname]) && $services_revscount[$sname] > 0 ? $services_revscount[$sname] : 1;
3483 $glob_servs_avg_score[$sname] = ($val / $services_revscount[$sname]);
3484 }
3485
3486 // build global score content
3487 $glob_score_content = new stdClass;
3488 $glob_score_content->review_score = new stdClass;
3489 $glob_score_content->review_score->score = $glob_avg_score;
3490 $glob_score_content->review_score->review_count = $revs_count;
3491 foreach ($glob_servs_avg_score as $sname => $val) {
3492 $glob_score_content->{$sname} = new stdClass;
3493 $glob_score_content->{$sname}->score = $val;
3494 $glob_score_content->{$sname}->review_count = $services_revscount[$sname];
3495 }
3496
3497 // build global score object
3498 $glob_score_obj = new stdClass;
3499 if (!empty($globscore_id)) {
3500 $glob_score_obj->id = $globscore_id;
3501 }
3502 $glob_score_obj->prop_first_param = null;
3503 $glob_score_obj->prop_name = $property_name;
3504 $glob_score_obj->channel = null;
3505 $glob_score_obj->uniquekey = 0;
3506 $glob_score_obj->last_updated = JFactory::getDate()->toSql(true);
3507 $glob_score_obj->score = round($glob_avg_score, 2);
3508 $glob_score_obj->content = json_encode($glob_score_content);
3509
3510 // update or create global score
3511 if (!empty($globscore_id)) {
3512 $dbo->updateObject('#__vikchannelmanager_otascores', $glob_score_obj, 'id');
3513 } else {
3514 $dbo->insertObject('#__vikchannelmanager_otascores', $glob_score_obj, 'id');
3515 }
3516
3517 // redirect to main view
3518 $app->redirect($goto);
3519 }
3520
3521 /**
3522 * AJAX task to get one monthly availability calendar of the room details page.
3523 *
3524 * @since 1.13.5
3525 */
3526 public function get_avcalendars_data()
3527 {
3528 $dbo = JFactory::getDbo();
3529 $rid = VikRequest::getInt('rid', 0, 'request');
3530 $direction = VikRequest::getString('direction', 'next', 'request');
3531 $fromdt = VikRequest::getString('fromdt', '', 'request');
3532 $nextdt = VikRequest::getString('nextdt', '', 'request');
3533 $prevdt = VikRequest::getString('prevdt', '', 'request');
3534
3535 // make sure vars are not empty
3536 if (empty($rid) || empty($direction) || empty($fromdt) || empty($nextdt) || empty($prevdt)) {
3537 /**
3538 * Search engines may follow this endpoint, so we have to exit with HTTP status code 200
3539 *
3540 * @since 1.16.0 (J) - 1.6.0 (WP)
3541 */
3542 VBOHttpDocument::getInstance()->json(['Invalid request variables']);
3543 }
3544
3545 // date format
3546 $vbo_df = VikBooking::getDateFormat();
3547 if ($vbo_df == "%d/%m/%Y") {
3548 $vbo_df = 'd/m/Y';
3549 } elseif ($vbo_df == "%m/%d/%Y") {
3550 $vbo_df = 'm/d/Y';
3551 } else {
3552 $vbo_df = 'Y/m/d';
3553 }
3554
3555 // configuration settings
3556 $numcalendars = VikBooking::numCalendars();
3557 $showpartlyres = VikBooking::showPartlyReserved();
3558 $showcheckinoutonly = VikBooking::showStatusCheckinoutOnly();
3559 $usepricecal = false;
3560 $inonout_allowed = true;
3561 $timeopst = VikBooking::getTimeOpenStore();
3562 if (is_array($timeopst)) {
3563 if ($timeopst[0] < $timeopst[1]) {
3564 // check-in not allowed on a day where there is already a check out (no arrivals/depatures on the same day)
3565 $inonout_allowed = false;
3566 }
3567 }
3568
3569 // week-days ordering
3570 $firstwday = (int)VikBooking::getFirstWeekDay();
3571 $days_labels = array(
3572 JText::translate('VBSUN'),
3573 JText::translate('VBMON'),
3574 JText::translate('VBTUE'),
3575 JText::translate('VBWED'),
3576 JText::translate('VBTHU'),
3577 JText::translate('VBFRI'),
3578 JText::translate('VBSAT')
3579 );
3580 $days_indexes = array();
3581 for ($i = 0; $i < 7; $i++) {
3582 $days_indexes[$i] = (6 - ($firstwday - $i) + 1) % 7;
3583 }
3584
3585 // first day timestamp of month to read in case of forward navigation
3586 $start_ts = strtotime($fromdt);
3587 $start_info = getdate($start_ts);
3588 if (!$start_ts || !$start_info) {
3589 VBOHttpDocument::getInstance()->close(500, 'Invalid date provided');
3590 }
3591 // backward navigation
3592 if ($direction == 'prev') {
3593 // we need to get the previous month
3594 $start_ts = mktime(0, 0, 0, ($start_info['mon'] - 1), 1, $start_info['year']);
3595 $start_info = getdate($start_ts);
3596 if (!$start_ts || !$start_info) {
3597 VBOHttpDocument::getInstance()->close(500, 'Invalid date calculated');
3598 }
3599 }
3600
3601 // make sure minimum date is respected
3602 $min_lim_ts = mktime(0, 0, 0, date('n'), 1, date('Y'));
3603 if ($start_ts < $min_lim_ts) {
3604 VBOHttpDocument::getInstance()->close(500, 'Dates in the past not allowed');
3605 }
3606
3607 // check the current next and prev dates to help the next AJAX navigations
3608 $nextnav_ts = strtotime($nextdt);
3609 $nextnav_info = getdate($nextnav_ts);
3610 if (!$nextnav_ts || !$nextnav_info) {
3611 VBOHttpDocument::getInstance()->close(500, 'Invalid next navigation date provided');
3612 }
3613 $prevnav_ts = strtotime($prevdt);
3614 $prevnav_info = getdate($prevnav_ts);
3615 if (!$prevnav_ts || !$prevnav_info) {
3616 VBOHttpDocument::getInstance()->close(500, 'Invalid prev navigation date provided');
3617 }
3618
3619 // make sure maximum date is respected
3620 $max_months_future = 12;
3621 $max_date_future = VikBooking::getMaxDateFuture($rid);
3622 if (!empty($max_date_future)) {
3623 $numlim = (int)substr($max_date_future, 1, (strlen($max_date_future) - 2));
3624 $numlim = $numlim < 1 ? 1 : $numlim;
3625 $quantlim = substr($max_date_future, -1, 1);
3626 if ($quantlim == 'm' || $quantlim == 'y') {
3627 $max_months_future = $numlim * ($quantlim == 'm' ? 1 : 12);
3628 $max_ts_future = strtotime("+{$max_months_future} months");
3629 $max_info = getdate($max_ts_future);
3630 $max_endts_future = mktime(23, 59, 59, $max_info['mon'], date('t', $max_info[0]), $max_info['year']);
3631 if ($start_ts > $max_endts_future) {
3632 VBOHttpDocument::getInstance()->close(500, 'Maximum date in the future exceeded');
3633 }
3634 }
3635 }
3636
3637 // get global property closing dates
3638 $cal_closing_dates = VikBooking::parseJsClosingDates();
3639 if (count($cal_closing_dates)) {
3640 foreach ($cal_closing_dates as $ccdk => $ccdv) {
3641 if (!(count($ccdv) == 2)) {
3642 continue;
3643 }
3644 $cal_closing_dates[$ccdk][0] = strtotime($ccdv[0]);
3645 $cal_closing_dates[$ccdk][1] = strtotime($ccdv[1]);
3646 }
3647 }
3648
3649 // load room details
3650 $q = "SELECT * FROM `#__vikbooking_rooms` WHERE `id`={$rid}";
3651 $dbo->setQuery($q, 0, 1);
3652 $dbo->execute();
3653 if (!$dbo->getNumRows()) {
3654 VBOHttpDocument::getInstance()->close(404, 'Room not found');
3655 }
3656 $room_details = $dbo->loadAssoc();
3657
3658 // get the future busy records
3659 $today_ts = mktime(0, 0, 0, date('n'), date('j'), date('Y'));
3660 $previousdayclass = '';
3661
3662 $q = "SELECT * FROM `#__vikbooking_busy` WHERE `idroom`={$room_details['id']} AND `checkout`>={$start_ts};";
3663 $dbo->setQuery($q);
3664 $busy = $dbo->loadAssocList();
3665
3666 // empty day element
3667 $empty_elem = new stdClass;
3668 $empty_elem->type = 'placeholder';
3669 $empty_elem->cont = '&nbsp;';
3670
3671 // build response container
3672 $calendars = array();
3673
3674 // prepare calendar object
3675 $calendar = new stdClass;
3676 $calendar->ts = $start_info[0];
3677 $calendar->mon = $start_info['mon'];
3678 $calendar->mday = $start_info['mday'];
3679 $calendar->year = $start_info['year'];
3680 $calendar->month = VikBooking::sayMonth($start_info['mon']);
3681 $calendar->wdays = array();
3682 for ($i = 0; $i < 7; $i++) {
3683 $d_ind = ($i + $firstwday) < 7 ? ($i + $firstwday) : ($i + $firstwday - 7);
3684 array_push($calendar->wdays, $days_labels[$d_ind]);
3685 }
3686
3687 // build the calendar rows by looping over the days of this month
3688 $calendar->rows = array();
3689
3690 // the row will contain all the placeholders and real days (7 elements at most)
3691 $row = array();
3692 $d_count = 0;
3693
3694 // first, we push empty dates placeholders for printing the first table cells (cells before the 1st of the month)
3695 for ($i = 0, $n = $days_indexes[$start_info['wday']]; $i < $n; $i++, $d_count++) {
3696 // push fake-day element (placeholder)
3697 array_push($row, $empty_elem);
3698 }
3699
3700 // start looping
3701 $loop_end_month = $start_info['mon'];
3702 while ($start_info['mon'] == $loop_end_month) {
3703 if ($d_count > 6) {
3704 // push the current row
3705 array_push($calendar->rows, $row);
3706
3707 // start a new row and reset cells counter
3708 $row = array();
3709 $d_count = 0;
3710 }
3711
3712 // build real-day element
3713 $elem = new stdClass;
3714 $elem->type = 'day';
3715 $elem->cont = $start_info['mday'] < 10 ? "0{$start_info['mday']}" : $start_info['mday'];
3716 $elem->dt = date($vbo_df, $start_info[0]);
3717 $elem->ymd = date('Y-m-d', $start_info[0]);
3718 $elem->ts = $start_info[0];
3719 $elem->class = 'vbtdfree';
3720 $elem->past_class = $start_info[0] < $today_ts ? ' vbtdpast' : '';
3721
3722 // check whether this day has got bookings
3723 $totfound = 0;
3724 $ischeckinday = false;
3725 $ischeckoutday = false;
3726 foreach ($busy as $b) {
3727 $info_in = getdate($b['checkin']);
3728 $checkin_ts = mktime(0, 0, 0, $info_in['mon'], $info_in['mday'], $info_in['year']);
3729 $info_out = getdate($b['checkout']);
3730 $checkout_ts = mktime(0, 0, 0, $info_out['mon'], $info_out['mday'], $info_out['year']);
3731 if ($start_info[0] >= $checkin_ts && $start_info[0] == $checkout_ts) {
3732 $ischeckoutday = true;
3733 }
3734 if ($start_info[0] >= $checkin_ts && $start_info[0] < $checkout_ts) {
3735 $totfound++;
3736 if ($start_info[0] == $checkin_ts) {
3737 $ischeckinday = true;
3738 }
3739 }
3740 }
3741 if ($totfound >= $room_details['units']) {
3742 $elem->class = "vbtdbusy";
3743 if ($ischeckinday && $showcheckinoutonly && !$usepricecal && $inonout_allowed && $previousdayclass != "vbtdbusy" && $previousdayclass != "vbtdbusy vbtdbusyforcheckin") {
3744 $elem->class = "vbtdbusy vbtdbusyforcheckin";
3745 } elseif ($ischeckinday && !$usepricecal && !$inonout_allowed && $previousdayclass != "vbtdbusy" && $previousdayclass != "vbtdbusy vbtdbusyforcheckin") {
3746 // check-out not allowed on a day where someone is already checking-in
3747 $elem->class = "vbtdbusy";
3748 }
3749 } elseif ($totfound > 0) {
3750 if ($showpartlyres) {
3751 $elem->class = "vbtdwarning";
3752 }
3753 } else {
3754 if ($ischeckoutday && !$usepricecal && $showcheckinoutonly && $inonout_allowed && !($room_details['units'] > 1)) {
3755 $elem->class = "vbtdbusy vbtdbusyforcheckout";
3756 } elseif ($ischeckoutday && !$usepricecal && !$inonout_allowed && !($room_details['units'] > 1)) {
3757 $elem->class = "vbtdbusy";
3758 }
3759 }
3760
3761 // check global closing dates
3762 if (count($cal_closing_dates)) {
3763 foreach ($cal_closing_dates as $closed_interval) {
3764 if ($start_info[0] >= $closed_interval[0] && $start_info[0] <= $closed_interval[1]) {
3765 $elem->class = "vbtdbusy";
3766 break;
3767 }
3768 }
3769 }
3770
3771 // push cell element and increase counter
3772 array_push($row, $elem);
3773 $d_count++;
3774
3775 // update previous day class
3776 $previousdayclass = $elem->class;
3777
3778 // go to next day
3779 $start_info = getdate(mktime(0, 0, 0, $start_info['mon'], ($start_info['mday'] + 1), $start_info['year']));
3780 }
3781
3782 if (count($row)) {
3783 // the last row still need to be pushed in the rows
3784 for ($i = $d_count; $i <= 6; $i++) {
3785 // fill last empty days
3786 array_push($row, $empty_elem);
3787 }
3788
3789 // push ending row
3790 array_push($calendar->rows, $row);
3791 }
3792
3793 // push this month's calendar object
3794 array_push($calendars, $calendar);
3795
3796 // check whether next request can navigate forward
3797 $can_nav_next = false;
3798 if ($direction == 'prev') {
3799 // we went prev, so we got to be able to go next
3800 $can_nav_next = true;
3801 } elseif ($direction == 'next' && strtotime("+{$max_months_future} months") > $start_info[0]) {
3802 // went next and max future date is still greater than next hypothetical month
3803 $can_nav_next = true;
3804 }
3805
3806 // check whether next request can navigate backward
3807 $can_nav_prev = false;
3808 if ($direction == 'next') {
3809 // we went next, so we got to be able to go prev
3810 $can_nav_prev = true;
3811 } elseif ($direction == 'prev' && $min_lim_ts < mktime(0, 0, 0, ($start_info['mon'] - 1), 1, $start_info['year'])) {
3812 // went prev and first day of today's month is less than (not equal to) the last month just rendered
3813 $can_nav_prev = true;
3814 }
3815
3816 // calculate next and prev dates for the next navigations to help the AJAX request
3817 $next_ymd = null;
3818 if ($can_nav_next) {
3819 if ($direction == 'next') {
3820 // next forward navigation will start from the ne month
3821 $next_ymd = date('Y-m-d', $start_info[0]);
3822 } else {
3823 // we add the total number of calendars to the month after the one lastly displayed
3824 $next_ymd = date('Y-m-d', strtotime("+" . ($numcalendars - 1) . " months", $start_info[0]));
3825 }
3826 }
3827 $prev_ymd = null;
3828 if ($can_nav_prev) {
3829 if ($direction == 'prev') {
3830 // next backward navigation will start from the last month we just displayed
3831 $prev_ymd = date('Y-m-d', mktime(0, 0, 0, ($start_info['mon'] - 1), 1, $start_info['year']));
3832 } else {
3833 // we add the total number of calendars to the month after the one lastly displayed
3834 $prev_ymd = date('Y-m-d', strtotime("-{$numcalendars} months", $start_info[0]));
3835 }
3836 }
3837
3838 // build response object
3839 $response = new stdClass;
3840 $response->calendars = $calendars;
3841 $response->can_nav_next = $can_nav_next;
3842 $response->can_nav_prev = $can_nav_prev;
3843 $response->next_ymd = $next_ymd;
3844 $response->prev_ymd = $prev_ymd;
3845
3846 echo json_encode($response);
3847 exit;
3848 }
3849
3850 /**
3851 * AJAX request for adding a new room-day note from the front-end tableaux.
3852 *
3853 * @return void
3854 *
3855 * @since 1.13.5
3856 */
3857 public function add_roomdaynote()
3858 {
3859 $dt = VikRequest::getString('dt', '', 'request');
3860 $idroom = VikRequest::getInt('idroom', 0, 'request');
3861 $subunit = VikRequest::getInt('subunit', 0, 'request');
3862 $type = VikRequest::getString('type', '', 'request');
3863 $type = empty($type) ? 'custom' : $type;
3864 $name = VikRequest::getString('name', '', 'request');
3865 $descr = VikRequest::getString('descr', '', 'request');
3866 $cdays = VikRequest::getInt('cdays', 0, 'request');
3867 $cdays = $cdays < 0 ? 0 : $cdays;
3868 $cdays = $cdays > 365 ? 365 : $cdays;
3869 if (empty($idroom) || empty($dt) || !strtotime($dt)) {
3870 echo 'e4j.error.1';
3871 exit;
3872 }
3873
3874 // we put the operator name in the description (if available)
3875 $operator = VikBooking::getOperatorInstance()->getOperatorAccount();
3876 if ($operator !== false) {
3877 $oper_signature = "({$operator['first_name']} {$operator['last_name']})";
3878 $descr = empty($descr) ? $oper_signature : $descr . " \n" . $oper_signature;
3879 }
3880
3881 // reload end date
3882 $end_date = $dt;
3883
3884 // build critical date object
3885 $new_note = array(
3886 'name' => $name,
3887 'type' => $type,
3888 'descr' => $descr,
3889 );
3890
3891 // get object
3892 $notes = VikBooking::getCriticalDatesInstance();
3893
3894 // store the notes for all consecutive dates
3895 for ($i = 0; $i <= $cdays; $i++) {
3896 $store_dt = $dt;
3897 if ($i > 0) {
3898 $dt_info = getdate(strtotime($store_dt));
3899 $store_dt = date('Y-m-d', mktime(0, 0, 0, $dt_info['mon'], ($dt_info['mday'] + $i), $dt_info['year']));
3900 $end_date = $store_dt;
3901 }
3902 $result = $notes->storeDayNote($new_note, $store_dt, $idroom, $subunit);
3903 if (!$result) {
3904 echo 'e4j.error.2';
3905 exit;
3906 }
3907 }
3908
3909 // reload all room day notes for this day for the AJAX response
3910 $all_notes = $notes->loadRoomDayNotes($dt, $end_date, $idroom, $subunit);
3911
3912 if (!$all_notes || !count($all_notes)) {
3913 // no notes found even after storing it
3914 echo 'e4j.error.3';
3915 exit;
3916 }
3917
3918 echo json_encode($all_notes);
3919 exit;
3920 }
3921
3922 /**
3923 * AJAX endpoint to upload customer documents during the pre-checkin.
3924 *
3925 * @throws Exception
3926 *
3927 * @since 1.14 (J) - 1.4.0 (WP)
3928 */
3929 public function precheckin_upload_docs()
3930 {
3931 $dbo = JFactory::getDbo();
3932 $app = JFactory::getApplication();
3933 $input = $app->input;
3934
3935 // get request values
3936 $order_sid = $input->getString('sid', '');
3937 $order_ts = $input->getString('ts', '');
3938 /**
3939 * This is a simple file uploading process, but if
3940 * we wanted to automatically update the pax_data,
3941 * the request vars below would be necessary.
3942 */
3943 $room_index = $input->getInt('room_index', 0);
3944 $guest_index = $input->getInt('guest_index', 0);
3945 $pax_index = $input->getString('pax_index', '');
3946
3947 if (empty($order_sid) || empty($order_ts)) {
3948 throw new Exception('Missing booking details', 404);
3949 }
3950
3951 $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';";
3952 $dbo->setQuery($q);
3953 $dbo->execute();
3954 if (!$dbo->getNumRows()) {
3955 throw new Exception('Booking not found', 404);
3956 }
3957 $order = $dbo->loadAssoc();
3958
3959 $customer = array();
3960 $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'].";";
3961 $dbo->setQuery($q);
3962 $dbo->execute();
3963 if (!$dbo->getNumRows()) {
3964 // one customer must be assigned to this booking for the pre-checkin
3965 throw new Exception('No customers associated to this booking', 404);
3966 }
3967 $customer = $dbo->loadObject();
3968
3969 // make sure pre-checkin is allowed
3970 $precheckin = VikBooking::precheckinEnabled();
3971 if ($precheckin) {
3972 // make sure the limit of days in advance is reflected
3973 $precheckin_mind = VikBooking::precheckinMinOffset();
3974 $precheckin_lim_ts = strtotime("+{$precheckin_mind} days 00:00:00");
3975 $precheckin = ($precheckin_lim_ts <= $order['checkin'] || ($precheckin_mind === 1 && time() <= $order['checkin']));
3976 }
3977 if (!$precheckin) {
3978 throw new Exception('Pre-checkin not allowed at this time', 403);
3979 }
3980
3981 // get uploaded files array (use "raw" to avoid filtering the file to upload)
3982 $files = $input->files->get('docs', array(), 'raw');
3983 if (!count($files)) {
3984 throw new Exception('No files to be uploaded', 500);
3985 }
3986
3987 if (isset($files['name'])) {
3988 // we have a single associative array, we need to push it within a list,
3989 // because the upload iterates the $files array
3990 $files = array($files);
3991 }
3992
3993 // fetch documents folder path
3994 $dirpath = VBO_CUSTOMERS_PATH . DIRECTORY_SEPARATOR;
3995
3996 // check if we have a valid directory
3997 if (empty($customer->docsfolder) || !is_dir($dirpath . $customer->docsfolder)) {
3998 // randomize string
3999 $customer->seed = uniqid();
4000
4001 // create blocks for hashed folder
4002 $parts = array(
4003 $customer->first_name,
4004 $customer->last_name,
4005 md5(serialize($customer)),
4006 );
4007
4008 // join fetched parts
4009 $customer->docsfolder = strtolower(implode('-', array_filter($parts)));
4010
4011 if (strlen($customer->docsfolder) < 16) {
4012 throw new Exception('Possible security breach. Please specify as many details as possible.', 400);
4013 }
4014
4015 jimport('joomla.filesystem.folder');
4016
4017 // create a folder for this customer
4018 $created = JFolder::create($dirpath . $customer->docsfolder);
4019
4020 if (!$created) {
4021 throw new Exception(sprintf('Unable to create the folder [%s]', $dirpath . $customer->docsfolder), 403);
4022 }
4023
4024 unset($customer->seed);
4025
4026 // update customer docs folder
4027 $record = new stdClass;
4028 $record->id = $customer->id;
4029 $record->docsfolder = $customer->docsfolder;
4030 $dbo->updateObject('#__vikbooking_customers', $record, 'id');
4031 }
4032
4033 // prepare the response array of uploaded-file objects
4034 $response = array();
4035 $upload_err = null;
4036
4037 // compose prefix for all files uploaded (must end with an underscrore for View's compatibility)
4038 $file_prefix = str_replace(' ', '-', JText::translate('VBOPRECHECKIN')) . '_';
4039
4040 try {
4041
4042 foreach ($files as $file) {
4043 // sanitize file name
4044 if (!empty($file['name'])) {
4045 // replace quotes and pipe, which is the separator in pax_data
4046 $file['name'] = str_replace(array("'", '"', '|'), '', $file['name']);
4047 }
4048 if (empty($file['name'])) {
4049 continue;
4050 }
4051 // always prepend "pre check-in" to the original file name
4052 $file['name'] = strtolower($file_prefix . $file['name']);
4053 // try to upload the file
4054 $result = VikBooking::uploadFileFromRequest($file, $dirpath . $customer->docsfolder, "/(image\/.+)|(application\/(zip|pdf|msword|vnd.*?))|(text\/(plain|markdown|csv))$/i");
4055 // set a valid URL for the uploaded file
4056 $result->url = str_replace(DIRECTORY_SEPARATOR, '/', str_replace(VBO_CUSTOMERS_PATH . DIRECTORY_SEPARATOR, VBO_CUSTOMERS_URI, $result->path));
4057 // push uploaded file
4058 array_push($response, $result);
4059 }
4060
4061 } catch (Exception $e) {
4062 // do nothing, but catch the error
4063 $upload_err = $e;
4064 }
4065
4066 if (!count($response)) {
4067 throw ($upload_err !== null ? $upload_err : (new Exception('No files could actually be uploaded', 500)));
4068 }
4069
4070 // output the response
4071 echo json_encode($response);
4072 exit;
4073 }
4074
4075 /**
4076 * AJAX endpoint to submit an inquiry/information request.
4077 *
4078 * @since 1.15.0 (J) - 1.5.0 (WP)
4079 */
4080 public function submit_inquiry()
4081 {
4082 if (!JSession::checkToken()) {
4083 // missing CSRF-proof token
4084 VBOHttpDocument::getInstance()->close(403, JText::translate('JINVALID_TOKEN'));
4085 }
4086
4087 $dbo = JFactory::getDbo();
4088 $app = JFactory::getApplication();
4089 $input = $app->input;
4090
4091 // get request values
4092 $checkin_dt = $input->getString('checkindate', '');
4093 $checkin_h = $input->getInt('checkinh', 0);
4094 $checkin_m = $input->getInt('checkinm', 0);
4095 $checkout_dt = $input->getString('checkoutdate', '');
4096 $checkout_h = $input->getInt('checkouth', 0);
4097 $checkout_m = $input->getInt('checkoutm', 0);
4098 $categories = $input->getString('categories', '');
4099 $roomsnum = $input->getInt('roomsnum', 1);
4100 $adults = $input->get('adults', array(), 'int');
4101 $children = $input->get('children', array(), 'int');
4102 $inquiry = $input->get('inquiry', array(), 'raw');
4103 $ulang = $input->getString('ulang', '');
4104
4105 $timeopst = VikBooking::getTimeOpenStore();
4106 if (empty($checkin_h) && empty($checkout_h) && is_array($timeopst)) {
4107 $opent = VikBooking::getHoursMinutes($timeopst[0]);
4108 $closet = VikBooking::getHoursMinutes($timeopst[1]);
4109 $checkin_h = $opent[0];
4110 $checkin_m = $opent[1];
4111 $checkout_h = $closet[0];
4112 $checkout_m = $closet[1];
4113 }
4114
4115 // compose the stay dates
4116 $checkin_ts = VikBooking::getDateTimestamp($checkin_dt, $checkin_h, $checkin_m);
4117 $checkout_ts = VikBooking::getDateTimestamp($checkout_dt, $checkout_h, $checkout_m);
4118
4119 if (empty($checkin_dt) || empty($checkout_dt) || empty($checkin_ts) || empty($checkout_ts)) {
4120 // invalid dates
4121 VBOHttpDocument::getInstance()->close(400, JText::translate('VBINVALIDDATES'));
4122 }
4123
4124 // validate guests
4125 if (!is_array($adults) || !count($adults)) {
4126 // invalid adults
4127 VBOHttpDocument::getInstance()->close(400, JText::translate('VBINCONGRDATA'));
4128 }
4129
4130 // prepare customer information
4131 $res_custdata = array();
4132 $t_first_name = '';
4133 $t_last_name = '';
4134 $guest_email = '';
4135 $guest_phone = '';
4136 $guest_country = '';
4137 $guest_extras = array();
4138 $guest_custom = array();
4139 foreach ($inquiry as $info_type => $info_vals) {
4140 if (empty($info_type) || $info_type == 'checkbox') {
4141 // we ignore any checkbox information
4142 continue;
4143 }
4144 foreach ($info_vals as $info_val) {
4145 if (!is_scalar($info_val) || !strlen($info_val)) {
4146 // empty or invalid field
4147 continue;
4148 }
4149 if ($info_type == 'nominative') {
4150 if (empty($t_first_name)) {
4151 $t_first_name = $info_val;
4152 $res_custdata[JText::translate('VBNAME')] = $info_val;
4153 } else {
4154 $t_last_name = $info_val;
4155 $res_custdata[JText::translate('VBLNAME')] = $info_val;
4156 }
4157 } elseif ($info_type == 'email') {
4158 $guest_email = $info_val;
4159 $res_custdata[JText::translate('ORDER_EMAIL')] = $info_val;
4160 } elseif ($info_type == 'phone') {
4161 $guest_phone = $info_val;
4162 $res_custdata[JText::translate('ORDER_PHONE')] = $info_val;
4163 } elseif ($info_type == 'country') {
4164 $guest_country = $info_val;
4165 $res_custdata[JText::translate('ORDER_STATE')] = $info_val;
4166 } elseif ($info_type == 'city') {
4167 $guest_extras['city'] = $info_val;
4168 $res_custdata[JText::translate('ORDER_CITY')] = $info_val;
4169 } else {
4170 // we treat this as a custom data field
4171 $guest_custom[$info_type] = $info_val;
4172 // inject reservation custdata string for this value
4173 if ($info_type == 'special_requests') {
4174 $res_custdata[JText::translate('ORDER_SPREQUESTS')] = $info_val;
4175 } else {
4176 $readable_ftype = ucwords(str_replace(array('_', '-'), ' ', $info_type));
4177 $res_custdata[$readable_ftype] = $info_val;
4178 }
4179 }
4180 }
4181 }
4182
4183 // validate fields
4184 if (!count($res_custdata)) {
4185 // we received no filled information
4186 VBOHttpDocument::getInstance()->close(400, JText::translate('VBINCONGRDATA'));
4187 }
4188
4189 // build the reservation raw-text for the customer data
4190 $res_custdata_str = VikBooking::buildCustData($res_custdata, "\n");
4191
4192 // store the customer record as first thing
4193 $cpin = VikBooking::getCPinIstance();
4194 $cpin->setCustomerExtraInfo($guest_extras);
4195 $cpin->saveCustomerDetails($t_first_name, $t_last_name, $guest_email, $guest_phone, $guest_country, array());
4196
4197 // build customer object for the inquiry reservation
4198 $customer = new stdClass;
4199 $customer->name = $t_first_name;
4200 $customer->lname = $t_last_name;
4201 $customer->email = $guest_email;
4202 $customer->phone = $guest_phone;
4203 $customer->country = $guest_country;
4204 $customer->lang = $ulang;
4205 $customer->custdata = $res_custdata_str;
4206 $customer->adminnotes = isset($guest_custom['special_requests']) ? $guest_custom['special_requests'] : '';
4207 /**
4208 * Always append the originally selected stay dates and guest party to the
4209 * administrator notes string so that they can be accessed all the times.
4210 */
4211 $customer->adminnotes .= !empty($customer->adminnotes) ? "\n" : '';
4212 $customer->adminnotes .= JText::translate('VBPICKUP') . ': ' . $checkin_dt . "\n";
4213 $customer->adminnotes .= JText::translate('VBRETURN') . ': ' . $checkout_dt . "\n";
4214 $customer->adminnotes .= JText::translate('VBFORMADULTS') . ': ' . array_sum($adults) . "\n";
4215 $customer->adminnotes .= JText::translate('VBFORMCHILDREN') . ': ' . array_sum($children) . "\n";
4216
4217 // prepare response object
4218 $response = new stdClass;
4219 $response->status = 0;
4220 $response->error = '';
4221
4222 // invoke availability helper class
4223 $av_helper = VikBooking::getAvailabilityInstance();
4224
4225 // turn flag on to ignore restrictions, as this is an inquiry and we must allocate the booking
4226 $av_helper->ignoreRestrictions(true);
4227
4228 // increase the default number of back and forth days for alternative date suggestions
4229 $av_helper->setBackForthDays(90);
4230
4231 // set stay dates
4232 $av_helper->setStayDates($checkin_dt, $checkout_dt);
4233
4234 // set room parties, but we expect to always have one room for the inquiry
4235 foreach ($adults as $k => $num_adults) {
4236 $num_children = isset($children[$k]) ? $children[$k] : 0;
4237 $av_helper->setRoomParty($num_adults, $num_children);
4238 }
4239
4240 // load available room rates
4241 $room_rates = $av_helper->getRates();
4242
4243 // check if availability errors occurred
4244 $has_av_error = strlen($av_helper->getError());
4245 $av_error_code = $av_helper->getErrorCode();
4246
4247 // count total fitting records
4248 $tot_records = is_array($room_rates) && !$has_av_error ? count($room_rates) : 0;
4249
4250 // build history extra data base object
4251 $ymd_stay_dates = $av_helper->getStayDates();
4252 $hist_extra_data = new stdClass;
4253 $hist_extra_data->checkin_date = $ymd_stay_dates[0];
4254 $hist_extra_data->checkout_date = $ymd_stay_dates[1];
4255 $hist_extra_data->adults = array_sum($adults);
4256 $hist_extra_data->children = array_sum($children);
4257
4258 if ($tot_records > 0) {
4259 // we can create an inquiry pending reservation for a room rate
4260 $inquiry_res_id = 0;
4261 foreach ($room_rates as $rid => $rates) {
4262 foreach ($rates as $room_rplan) {
4263 if (!is_array($room_rplan) || !isset($room_rplan['idroom'])) {
4264 continue;
4265 }
4266 // we grab the cheapest room and cheapest rate plan (the first room-rate plan)
4267 $inquiry_res_id = $av_helper->createInquiryReservation($room_rplan, $customer);
4268 break;
4269 }
4270 // make sure to create just one inquiry reservation for the cheapest room available
4271 if ($inquiry_res_id) {
4272 break;
4273 }
4274 }
4275
4276 if ($inquiry_res_id) {
4277 // assign booking to customer
4278 $cpin->saveCustomerBooking($inquiry_res_id);
4279
4280 // send email notification to admin
4281 VikBooking::sendBookingEmail($inquiry_res_id, array('admin'));
4282
4283 // trigger SMS sending
4284 VikBooking::sendBookingSMS($inquiry_res_id);
4285
4286 // booking history (set inquiry availability type to 1)
4287 $hist_extra_data->av_type = 1;
4288 VikBooking::getBookingHistoryInstance()->setBid($inquiry_res_id)->setExtraData($hist_extra_data)->store('IR', $customer->adminnotes);
4289 }
4290
4291 // update the response status no matter what
4292 $response->status = 1;
4293
4294 // output response and terminate the request
4295 VBOHttpDocument::getInstance()->json($response);
4296 }
4297
4298 // rely on suggestions in case of no rooms available
4299 if (!is_array($room_rates) || $has_av_error) {
4300 // try to get the suggestions when no availability
4301 list($alternative_dates, $alternative_parties) = $av_helper->findSuggestions();
4302
4303 $inquiry_res_id = 0;
4304 $alt_suggestion = '';
4305
4306 if (count($alternative_dates)) {
4307 $inquiry_res_id = $av_helper->allocateAltDatesInquiry($alternative_dates, $customer);
4308 $alt_suggestion = JText::translate('VBO_ALT_DATES_INQ');
4309 // set inquiry availability type to 2 for alternative dates
4310 $hist_extra_data->av_type = 2;
4311 } elseif (count($alternative_parties)) {
4312 $inquiry_res_id = $av_helper->allocateAltPartyInquiry($alternative_parties, $customer);
4313 $alt_suggestion = JText::translate('VBO_ALT_PARTY_INQ');
4314 // set inquiry availability type to 3 for alternative party
4315 $hist_extra_data->av_type = 3;
4316 }
4317
4318 if ($inquiry_res_id) {
4319 // assign booking to customer
4320 $cpin->saveCustomerBooking($inquiry_res_id);
4321
4322 // send email notification to admin
4323 VikBooking::sendBookingEmail($inquiry_res_id, array('admin'));
4324
4325 // trigger SMS sending
4326 VikBooking::sendBookingSMS($inquiry_res_id);
4327
4328 // booking history
4329 $history_obj = VikBooking::getBookingHistoryInstance()->setBid($inquiry_res_id);
4330 // store first history record
4331 $history_obj->store('IR', $customer->adminnotes);
4332 // store history record mentioning the suggestion used
4333 $history_obj->setExtraData($hist_extra_data)->store('IR', $alt_suggestion);
4334 }
4335
4336 // update the response status no matter what
4337 $response->status = 1;
4338
4339 // output response and terminate the request
4340 VBOHttpDocument::getInstance()->json($response);
4341 }
4342
4343 /**
4344 * If we reach this point it means that no rooms were available and no rooms/dates could be
4345 * suggested as an alternative party. Therefore, we allocate the reservation on a "dummy room".
4346 */
4347 $all_rooms = $av_helper->loadRooms();
4348 if (count($all_rooms)) {
4349 // grab the first "dummy room"
4350 $dummy_room_id = key($all_rooms);
4351 $room_rplan = array(
4352 'idroom' => $dummy_room_id,
4353 );
4354
4355 // create inquiry reservation for a dummy room
4356 $inquiry_res_id = $av_helper->createInquiryReservation($room_rplan, $customer);
4357 $alt_suggestion = JText::translate('VBO_ALT_DUMMY_INQ');
4358 // set inquiry availability type to 4 for "dummy room"
4359 $hist_extra_data->av_type = 4;
4360
4361 if ($inquiry_res_id) {
4362 // assign booking to customer
4363 $cpin->saveCustomerBooking($inquiry_res_id);
4364
4365 // send email notification to admin
4366 VikBooking::sendBookingEmail($inquiry_res_id, array('admin'));
4367
4368 // trigger SMS sending
4369 VikBooking::sendBookingSMS($inquiry_res_id);
4370
4371 // booking history
4372 $history_obj = VikBooking::getBookingHistoryInstance()->setBid($inquiry_res_id);
4373 // store first history record
4374 $history_obj->store('IR', $customer->adminnotes);
4375 // store history record mentioning the suggestion used
4376 $history_obj->setExtraData($hist_extra_data)->store('IR', $alt_suggestion);
4377
4378 // update the response status
4379 $response->status = 1;
4380
4381 // output response and terminate the request
4382 VBOHttpDocument::getInstance()->json($response);
4383 }
4384 }
4385
4386 // if not even a dummy room could be allocated, it means Vik Booking isn't set up
4387 $response->error = $av_helper->explainErrorCode();
4388 if (empty($response->error)) {
4389 $response->error = 'No rooms have been configured on this site yet';
4390 }
4391
4392 // output response and terminate the request
4393 VBOHttpDocument::getInstance()->json($response);
4394 }
4395
4396 /**
4397 * AJAX endpoint to load the states of a given country.
4398 *
4399 * @return void
4400 *
4401 * @since 1.16.0 (J) - 1.6.0 (WP)
4402 */
4403 public function states_load_from_country()
4404 {
4405 $dbo = JFactory::getDbo();
4406
4407 $id_country = VikRequest::getInt('id_country', 0, 'request');
4408 $country_3_code = VikRequest::getString('country_3_code', '', 'request');
4409 $country_2_code = VikRequest::getString('country_2_code', '', 'request');
4410 $country_name = VikRequest::getString('country_name', '', 'request');
4411
4412 if (empty($id_country) && empty($country_3_code) && empty($country_2_code) && empty($country_name)) {
4413 VBOHttpDocument::getInstance()->close(500, 'Missing country identifier');
4414 }
4415
4416 if (!empty($id_country)) {
4417 $q = "SELECT * FROM `#__vikbooking_states` WHERE `id_country`=" . $id_country;
4418 $dbo->setQuery($q);
4419 $dbo->execute();
4420 if (!$dbo->getNumRows()) {
4421 // no records found for this country
4422 VBOHttpDocument::getInstance()->json([]);
4423 }
4424 // output the JSON encoded list of states found
4425 VBOHttpDocument::getInstance()->json($dbo->loadAssocList());
4426 }
4427
4428 // find country ID by name or code
4429 $field_name = $dbo->qn('country_name');
4430 $field_value = $country_name;
4431 if (!empty($country_3_code)) {
4432 $field_name = $dbo->qn('country_3_code');
4433 $field_value = $country_3_code;
4434 }
4435 if (!empty($country_2_code)) {
4436 $field_name = $dbo->qn('country_2_code');
4437 $field_value = $country_2_code;
4438 }
4439
4440 $q = "SELECT `id` FROM `#__vikbooking_countries` WHERE {$field_name}=" . $dbo->quote($field_value);
4441 $dbo->setQuery($q, 0, 1);
4442 $dbo->execute();
4443 if (!$dbo->getNumRows()) {
4444 // country not found
4445 VBOHttpDocument::getInstance()->close(404, sprintf('Country [%s] not found', $field_value));
4446 }
4447
4448 $id_country = $dbo->loadResult();
4449
4450 $q = "SELECT * FROM `#__vikbooking_states` WHERE `id_country`=" . $id_country;
4451 $dbo->setQuery($q);
4452 $dbo->execute();
4453 if (!$dbo->getNumRows()) {
4454 // no records found for this country
4455 VBOHttpDocument::getInstance()->json([]);
4456 }
4457 // output the JSON encoded list of states found
4458 VBOHttpDocument::getInstance()->json($dbo->loadAssocList());
4459 }
4460
4461 /**
4462 * AJAX endpoint to perform the room upgrade operation.
4463 *
4464 * @return void
4465 *
4466 * @since 1.16.0 (J) - 1.6.0 (WP)
4467 */
4468 public function upgrade_room()
4469 {
4470 $dbo = JFactory::getDbo();
4471
4472 $bid = VikRequest::getInt('bid', 0, 'request');
4473 $sid = VikRequest::getString('sid', '', 'request');
4474 $ts = VikRequest::getString('ts', '', 'request');
4475 $room_index = VikRequest::getInt('room_index', 0, 'request');
4476 $room_id = VikRequest::getInt('room_id', 0, 'request');
4477
4478 if (empty($room_id)) {
4479 VBOHttpDocument::getInstance()->close(500, 'Invalid data provided');
4480 }
4481
4482 $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'";
4483 $dbo->setQuery($q, 0, 1);
4484 $dbo->execute();
4485 if (!$dbo->getNumRows()) {
4486 VBOHttpDocument::getInstance()->close(404, JText::translate('VBORDERNOTFOUND'));
4487 }
4488
4489 $booking = $dbo->loadAssoc();
4490
4491 $booking_rooms = VikBooking::loadOrdersRoomsData($booking['id']);
4492 if (!$booking_rooms || !isset($booking_rooms[$room_index])) {
4493 VBOHttpDocument::getInstance()->close(404, JText::translate('VBORDERNOTFOUND'));
4494 }
4495
4496 // access the room helper object
4497 $room_helper = VBORoomHelper::getInstance([
4498 'booking' => $booking,
4499 'rooms' => $booking_rooms,
4500 ]);
4501
4502 // load the upgrade options for this booking
4503 $upgrade_options = $room_helper->getUpgradeOptions();
4504
4505 if (!$upgrade_options || !isset($upgrade_options['upgrade'][$room_index]) || !isset($upgrade_options['upgrade'][$room_index]['r_costs'][$room_id])) {
4506 // the room for the upgrade is not available
4507 VBOHttpDocument::getInstance()->close(500, 'Could not upgrade to the selected room. Please reload the page and try again');
4508 }
4509
4510 $upgrade_room_rate = $upgrade_options['upgrade'][$room_index]['r_costs'][$room_id];
4511
4512 // calculate the cost difference, if any
4513 $current_room_cost = $booking_rooms[$room_index]['cust_cost'] > 0 ? (float)$booking_rooms[$room_index]['cust_cost'] : (float)$booking_rooms[$room_index]['room_cost'];
4514 $upgrade_room_cost = $current_room_cost;
4515 $upgrade_difference = 0;
4516 $upgtax_difference = 0;
4517
4518 if ($current_room_cost < $upgrade_room_rate['upgrade_cost']) {
4519 // we update the total booking amount only if the upgrade room is more expensive
4520 $upgrade_room_cost = $upgrade_room_rate['upgrade_cost'];
4521 $upgrade_difference = $upgrade_room_rate['upgrade_cost'] - $current_room_cost;
4522 // handle taxes
4523 $current_tariff_data = VBORoomHelper::getInstance()->getTariffData($booking_rooms[$room_index]['idtar']);
4524 if ($current_tariff_data) {
4525 // current room tax
4526 $current_cost_plus_tax = VikBooking::sayCostPlusIva($current_room_cost, $current_tariff_data['idprice']);
4527 $current_cost_minus_tax = VikBooking::sayCostMinusIva($current_room_cost, $current_tariff_data['idprice']);
4528 $current_room_tax = $current_cost_plus_tax - $current_cost_minus_tax;
4529 // upgrade room tax
4530 $upgrade_cost_plus_tax = VikBooking::sayCostPlusIva($upgrade_room_rate['upgrade_cost'], $upgrade_room_rate['idprice']);
4531 $upgrade_cost_minus_tax = VikBooking::sayCostMinusIva($upgrade_room_rate['upgrade_cost'], $upgrade_room_rate['idprice']);
4532 $upgrade_room_tax = $upgrade_cost_plus_tax - $upgrade_cost_minus_tax;
4533 // calculate tax difference
4534 $upgtax_difference = $upgrade_room_tax - $current_room_tax;
4535 }
4536
4537 }
4538
4539 // update booking total amounts as first
4540 if ($upgrade_difference > 0) {
4541 $upd_booking = new stdClass;
4542 $upd_booking->id = $booking['id'];
4543 $upd_booking->total = (float)($booking['total'] + $upgrade_difference);
4544 $upd_booking->tot_taxes = (float)($booking['tot_taxes'] + $upgtax_difference);
4545
4546 $dbo->updateObject('#__vikbooking_orders', $upd_booking, 'id');
4547 }
4548
4549 // perform the room upgrade (switch)
4550 $broom_record = new stdClass;
4551 $broom_record->id = $booking_rooms[$room_index]['id'];
4552 $broom_record->idorder = $booking_rooms[$room_index]['idorder'];
4553 $broom_record->idroom = $room_id;
4554 $broom_record->idtar = $upgrade_room_rate['id'];
4555 $broom_record->room_cost = $upgrade_room_cost;
4556
4557 $dbo->updateObject('#__vikbooking_ordersrooms', $broom_record, ['id', 'idorder']);
4558
4559 // Booking History
4560 $hist_descr = !empty($booking_rooms[$room_index]['room_name']) ? $booking_rooms[$room_index]['room_name'] . ' ' : '';
4561 $hist_descr .= '-&gt; ' . $upgrade_options['rooms'][$room_id]['name'];
4562 VikBooking::getBookingHistoryInstance()->setBid($booking['id'])->store('UR', $hist_descr);
4563
4564 // output the JSON encoded successful response
4565 VBOHttpDocument::getInstance()->json(['success' => 1]);
4566 }
4567
4568 /**
4569 * End-point used to ping the execution of the scheduled crontab runners.
4570 *
4571 * @return void
4572 *
4573 * @since 1.17 (J) - 1.7 (WP)
4574 */
4575 public function crontab()
4576 {
4577 VBOFactory::getCrontabSimulator()->run();
4578 exit;
4579 }
4580 }
4581