PluginProbe
VikBooking Hotel Booking Engine & PMS / 1.6.7
VikBooking Hotel Booking Engine & PMS v1.6.7
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.6.7, at site/controller.php

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