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

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

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