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

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

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