PluginProbe
VikBooking Hotel Booking Engine & PMS / trunk
VikBooking Hotel Booking Engine & PMS vtrunk
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 1.7.3 1.7.4 All 35 releases
vikbooking / site / controller.php

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

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