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