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

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