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

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

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