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

tac.vikbooking.php in VikBooking Hotel Booking Engine & PMS 1.8.16, at site/helpers/tac.vikbooking.php

713 lines 25.2 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 /**
14 * Helper class originally introcued to get the hotel availability and rates.
15 */
16 class TACVBO
17 {
18 /**
19 * @var bool whether to return an array.
20 */
21 public static $getArray = false;
22
23 /**
24 * @var bool flag to ignore booking restrictions.
25 *
26 * @since 1.15.0 (J) - 1.5.0 (WP)
27 */
28 public static $ignoreRestrictions = false;
29
30 /**
31 * @var bool flag to ignore rooms availability.
32 *
33 * @since 1.16.10 (J) - 1.6.10 (WP)
34 */
35 public static $ignoreAvailability = false;
36
37 /**
38 * @var int optional max rooms limit.
39 *
40 * @since 1.16.10 (J) - 1.6.10 (WP)
41 */
42 public static $maxRoomsLimit = 0;
43
44 /**
45 * @var array optional list of room ids involved.
46 *
47 * @since 1.16.10 (J) - 1.6.10 (WP)
48 */
49 public static $forcedRoomIds = [];
50
51 /**
52 * Error code identifier:
53 *
54 * 1 = missing/invalid request options.
55 * 2 = invalid authentication.
56 * 3 = no rooms found for the given party.
57 * 4 = not compliant with booking restrictions.
58 * 5 = not compliant with global closing dates.
59 * 6 = no rates defined for the given length of stay.
60 * 7 = no availability for the dates requested (error code 8 might have occurred as well).
61 * 8 = no rooms available due to restrictions at room or rate plan level.
62 *
63 * @var int the error code identifier.
64 *
65 * @since 1.15.0 (J) - 1.5.0 (WP)
66 */
67 protected static $errorCode = 0;
68
69 /**
70 * Given the request or injected arguments, calulcates the availability and rates.
71 *
72 * @param array $options forced options injected rather than request variables.
73 *
74 * @return mixed array with values or error description if getArray=true, or void.
75 *
76 * @since 1.15.0 (J) - 1.5.0 (WP) added $options argument.
77 */
78 public static function tac_av_l(array $options = [])
79 {
80 $dbo = JFactory::getDbo();
81 $vbo_tn = VikBooking::getTranslator();
82
83 $response = 'e4j.error';
84 $args = $options;
85
86 /**
87 * All the calls to VikRequest's methods should not use 'request' as 3rd argument,
88 * but rather 'default' to get the right $input object in the VikRequest Class.
89 * This will resolve issues after the setVar() method was called by other functions.
90 * POST and GET requests will still provide non-empty values by using 'default'.
91 *
92 * @since 1.10
93 */
94 $args['hash'] = empty($args['hash']) ? VikRequest::getString('e4jauth', '', 'default') : $args['hash'];
95 $args['start_date'] = empty($args['start_date']) ? VikRequest::getString('start_date', '', 'default') : $args['start_date'];
96 $args['end_date'] = empty($args['end_date']) ? VikRequest::getString('end_date', '', 'default') : $args['end_date'];
97 $args['nights'] = empty($args['nights']) ? VikRequest::getInt('nights', 1, 'default') : $args['nights'];
98 $args['num_rooms'] = empty($args['num_rooms']) ? VikRequest::getInt('num_rooms', 1, 'default') : $args['num_rooms'];
99
100 $args['start_ts'] = strtotime($args['start_date']);
101 $args['end_ts'] = strtotime($args['end_date']);
102
103 $valid = true;
104 $mandatory_keys = array(
105 'start_date',
106 'end_date',
107 'nights',
108 'num_rooms',
109 );
110 foreach ($mandatory_keys as $man_key) {
111 $valid = $valid && isset($args[$man_key]) && !empty($args[$man_key]);
112 }
113
114 // request type
115 $req_type = VikRequest::getString('req_type', '', 'default');
116 $req_type = empty($req_type) && !empty($args['req_type']) ? $args['req_type'] : $req_type;
117
118 // API version
119 $tac_apiv = 4;
120 // API v4
121 $args['num_adults'] = empty($args['num_adults']) ? VikRequest::getInt('num_adults', 1, 'request') : $args['num_adults'];
122 // API v5
123 $args['adults'] = empty($args['adults']) ? VikRequest::getVar('adults', []) : $args['adults'];
124 $args['children'] = empty($args['children']) ? VikRequest::getVar('children', []) : $args['children'];
125 $args['children_age'] = empty($args['children_age']) ? VikRequest::getVar('children_age', []) : $args['children_age'];
126 if (!empty($args['adults']) && !empty($args['children'])) {
127 $tac_apiv = 5;
128 }
129
130 if ($tac_apiv == 4) {
131 $valid = !empty($args['num_adults']) ? $valid : false;
132 } elseif ($tac_apiv == 5) {
133 $valid = !empty($args['adults']) ? $valid : false;
134 }
135
136 /**
137 * The back-end page Calendar can allow the admin to force bookings in case of
138 * no-availability. Its AJAX requests will contain "only_rates=1" and we compose
139 * an extra array-key with all the fully-booked rooms for the requested dates.
140 *
141 * @since 1.14 (J) - 1.4.0 (WP)
142 */
143 $only_rates = VikRequest::getInt('only_rates', 0, 'request');
144 $only_rates = empty($only_rates) && !empty($args['only_rates']) ? $args['only_rates'] : $only_rates;
145 $fullybooked = [];
146
147 if (!$valid) {
148 // set error code
149 self::$errorCode = 1;
150
151 if (self::$getArray) {
152 return $response;
153 }
154 echo $response;
155 exit;
156 }
157
158 $checkauth = md5('vbo.e4j.vbo');
159 if ($checkauth != $args['hash'] && !$options) {
160 // set error code
161 self::$errorCode = 2;
162
163 $response = 'e4j.error.auth';
164 if (self::$getArray) {
165 return $response;
166 }
167 echo $response;
168 exit;
169 }
170
171 if ($tac_apiv == 5) {
172 // compose adults-children array and sql clause
173 $arradultsrooms = [];
174 $arradultsclause = [];
175 $arrpeople = [];
176 if ($args['adults']) {
177 foreach ($args['adults'] as $kad => $adu) {
178 $roomnumb = $kad + 1;
179 if (strlen($adu)) {
180 $numadults = intval($adu);
181 if ($numadults >= 0) {
182 $arradultsrooms[$roomnumb] = $numadults;
183 $arrpeople[$roomnumb]['adults'] = $numadults;
184 $strclause = "(`fromadult`<=".$numadults." AND `toadult`>=".$numadults."";
185 if (!empty($args['children'][$kad]) && intval($args['children'][$kad]) > 0) {
186 $numchildren = intval($args['children'][$kad]);
187 $arrpeople[$roomnumb]['children'] = $numchildren;
188 $arrpeople[$roomnumb]['children_age'] = isset($args['children_age'][$roomnumb]) && $args['children_age'][$roomnumb] ? $args['children_age'][$roomnumb] : [];
189 $strclause .= " AND `fromchild`<=".$numchildren." AND `tochild`>=".$numchildren."";
190 } else {
191 $arrpeople[$roomnumb]['children'] = 0;
192 $arrpeople[$roomnumb]['children_age'] = [];
193 if (intval($args['children'][$kad]) == 0) {
194 $strclause .= " AND `fromchild` = 0";
195 }
196 }
197 $strclause .= " AND `totpeople` >= ".($arrpeople[$roomnumb]['adults'] + $arrpeople[$roomnumb]['children']);
198 $strclause .= " AND `mintotpeople` <= ".($arrpeople[$roomnumb]['adults'] + $arrpeople[$roomnumb]['children']);
199 $strclause .= ")";
200 $arradultsclause[] = $strclause;
201 }
202 }
203 }
204 }
205 // set $args['adults'] to the number of adults occupying the first room but it could be a party of multiple rooms
206 $args['num_adults'] = $arrpeople[1]['adults'];
207 // this clause would return one room type for each party type: implode(" OR ", $arradultsclause) - the AND clause must be used rather than OR.
208 $q = "SELECT `id`, `units` FROM `#__vikbooking_rooms` WHERE `avail`=1 AND (" . implode(" AND ", $arradultsclause) . ")" . (self::$forcedRoomIds ? ' AND `id` IN (' . implode(', ', array_map('intval', self::$forcedRoomIds)) . ')' : '');
209 } else {
210 // API v4
211 $arrpeople = [];
212 $arrpeople[1]['adults'] = $args['num_adults'];
213 $arrpeople[1]['children'] = 0;
214 $q = "SELECT `id`, `units` FROM `#__vikbooking_rooms` WHERE `avail`=1 AND `toadult` >= " . $args['num_adults'] . (self::$forcedRoomIds ? ' AND `id` IN (' . implode(', ', array_map('intval', self::$forcedRoomIds)) . ')' : '');
215 }
216
217 $dbo->setQuery($q, 0, self::$maxRoomsLimit);
218 $avail_rooms = $dbo->loadAssocList();
219
220 if (!$avail_rooms) {
221 // set error code
222 self::$errorCode = 3;
223
224 // build error data
225 $errorData = ['e4j.error' => 'No valid rooms were found for the requested number of guests'];
226
227 if (self::$getArray) {
228 return $errorData;
229 }
230
231 // send JSON (erroneous) response to output
232 VBOHttpDocument::getInstance()->json($errorData);
233 }
234
235 // arr[0] = (sec) checkin, arr[1] = (sec) checkout
236 $check_in_out = VikBooking::getTimeOpenStore();
237 if (is_array($check_in_out)) {
238 $args['start_ts'] += $check_in_out[0];
239 $args['end_ts'] += $check_in_out[1];
240 }
241
242 $room_ids = [];
243 for ($i = 0; $i < count($avail_rooms); $i++) {
244 $room_ids[$i] = $avail_rooms[$i]['id'];
245 }
246
247 $all_restrictions = VikBooking::loadRestrictions(true, $room_ids);
248 $glob_restrictions = VikBooking::globalRestrictions($all_restrictions);
249
250 // validate restrictions error message
251 $x_restr = $glob_restrictions ? VikBooking::validateRoomRestriction($glob_restrictions, getdate($args['start_ts']), getdate($args['end_ts']), $args['nights']) : '';
252
253 if (!self::$ignoreRestrictions && strlen($x_restr)) {
254 // set error code
255 self::$errorCode = 4;
256
257 // build error data
258 $errorData = ['e4j.error' => 'Unable to proceed because of booking Restrictions in these dates (' . $x_restr . ')'];
259
260 if (self::$getArray) {
261 return $errorData;
262 }
263
264 // send JSON (erroneous) response to output
265 VBOHttpDocument::getInstance()->json($errorData);
266 }
267
268 $nowdf = VikBooking::getDateFormat();
269 if ($nowdf == "%d/%m/%Y") {
270 $df = 'd/m/Y';
271 } elseif ($nowdf == "%m/%d/%Y") {
272 $df = 'm/d/Y';
273 } else {
274 $df = 'Y/m/d';
275 }
276 // closing dates
277 $err_closingdates = VikBooking::validateClosingDates($args['start_ts'], $args['end_ts'], $df);
278 if (!self::$ignoreRestrictions && !empty($err_closingdates)) {
279 // set error code
280 self::$errorCode = 5;
281
282 // build error data
283 $errorData = ['e4j.error' => JText::sprintf('VBERRDATESCLOSED', $err_closingdates)];
284
285 if (self::$getArray) {
286 return $errorData;
287 }
288
289 // send JSON (erroneous) response to output
290 VBOHttpDocument::getInstance()->json($errorData);
291 }
292
293 // hours to arrival may affect rate plan restrictions
294 $hoursdiff = VikBooking::countHoursToArrival($args['start_ts']);
295
296 // get rates
297 $room_ids = [];
298 foreach ($avail_rooms as $k => $room) {
299 $room_ids[$room['id']] = $room;
300 }
301 $rates = [];
302 $q = "SELECT `p`.*, `r`.`id` AS `r_reference_id`, `r`.`name` AS `r_short_desc`, `r`.`img`, `r`.`units`, `r`.`moreimgs`, `r`.`imgcaptions`, `prices`.`id` AS `price_reference_id`, `prices`.`name` AS `pricename`, `prices`.`breakfast_included`, `prices`.`free_cancellation`, `prices`.`canc_deadline`, `prices`.`canc_policy`, `prices`.`minlos`, `prices`.`minhadv`, `prices`.`meal_plans` FROM `#__vikbooking_dispcost` AS `p`, `#__vikbooking_rooms` AS `r`, `#__vikbooking_prices` AS `prices` WHERE `r`.`id`=`p`.`idroom` AND `p`.`idprice`=`prices`.`id` AND `p`.`days`=".$args['nights']." AND `r`.`id` IN (".implode(',', array_keys($room_ids)).") ORDER BY `p`.`cost` ASC;";
303 $dbo->setQuery($q);
304 $rates = $dbo->loadAssocList();
305 if (!$rates) {
306 // set error code
307 self::$errorCode = 6;
308
309 // build error data
310 $errorData = ['e4j.error' => 'The Query for fetching the rates returned an empty result'];
311
312 if (self::$getArray) {
313 return $errorData;
314 }
315
316 // send JSON (erroneous) response to output
317 VBOHttpDocument::getInstance()->json($errorData);
318 }
319 $vbo_tn->translateContents($rates, '#__vikbooking_rooms', array('id' => 'r_reference_id', 'r_short_desc' => 'name'));
320 $vbo_tn->translateContents($rates, '#__vikbooking_prices', array('id' => 'price_reference_id', 'pricename' => 'name'));
321
322 // build list of eligible room rates
323 $arr_rates = [];
324
325 /**
326 * If all results are excluded because of restrictions at rate plan level, we turn on this flag
327 * to know that the rate plans have a Min LOS or a Min Hours in Advance (Advance Booking Offset).
328 */
329 $err_rplan_restr = false;
330
331 // extract all the involved room IDs with base rates defined before restrictions validation
332 $involved_listing_ids = array_values(array_unique(array_column($rates, 'idroom')));
333
334 // scan and assign the eligible room rates
335 foreach ($rates as $rate) {
336 // rate plans with a minlos, or with a min hours in advance
337 if ((!empty($rate['minlos']) && $rate['minlos'] > $args['nights']) || $hoursdiff < $rate['minhadv']) {
338 // this flag will tell us that some results were excluded due to restrictions at rate plan level
339 $err_rplan_restr = true;
340 continue;
341 } else {
342 // we don't want the properties 'minlos' and 'minhadv' in the response.
343 unset($rate['minlos']);
344 unset($rate['minhadv']);
345 }
346 // push eligible room rate
347 if (!isset($arr_rates[$rate['idroom']])) {
348 $arr_rates[$rate['idroom']] = [];
349 }
350 $arr_rates[$rate['idroom']][] = $rate;
351 }
352
353 /**
354 * If restrictions are set to be ignored, maybe because we need to focus on availability,
355 * we perform a check to see if some listings suffered availability due to rate-plan
356 * level restrictions. In case we find some, we re-do the process by including at least
357 * one rate plan to those listings that would be otherwise missing rates and availability.
358 *
359 * @since 1.18.6 (J) - 1.8.6 (WP)
360 */
361 $listings_norates = array_diff($involved_listing_ids, array_keys($arr_rates));
362 if (self::$ignoreRestrictions && !self::$ignoreAvailability && $listings_norates) {
363 // push back room rates to listings with no rates as we are focusing on availability
364 foreach ($listings_norates as $recover_listing_id) {
365 foreach ($rates as $rate) {
366 if ($rate['idroom'] != $recover_listing_id) {
367 // not the listing we look for
368 continue;
369 }
370 if ($arr_rates[$rate['idroom']] ?? []) {
371 // one room rate is sufficient
372 continue;
373 }
374 // restore availability for this listing by pushing the first rate plan
375 $arr_rates[$rate['idroom']] = [$rate];
376 }
377 }
378 }
379
380 // flags to understand exactly what's making some rooms unavailable
381 $err_rtype_restr = false;
382 $err_rtype_booked = false;
383
384 // check availability for the rooms with a rate for this number of nights
385 $minus_units = 0;
386 if (count($arr_rates) < $args['num_rooms']) {
387 $minus_units = $args['num_rooms'] - count($arr_rates);
388 }
389 foreach ($arr_rates as $k => $datarate) {
390 $room = $room_ids[$k];
391 $consider_units = $room['units'] - $minus_units;
392 if (!self::$ignoreAvailability && ($consider_units <= 0 || !VikBooking::roomBookable($room['id'], $consider_units, $args['start_ts'], $args['end_ts']))) {
393 // unset room from results
394 unset($arr_rates[$k]);
395 // push room as fully booked
396 array_push($fullybooked, (int)$room['id']);
397 // turn flag on
398 $err_rtype_booked = true;
399 } else {
400 if (!self::$ignoreRestrictions && $all_restrictions) {
401 $room_restr = VikBooking::roomRestrictions($room['id'], $all_restrictions);
402 if ($room_restr) {
403 if (strlen(VikBooking::validateRoomRestriction($room_restr, getdate($args['start_ts']), getdate($args['end_ts']), $args['nights'])) > 0) {
404 // unset room from results
405 unset($arr_rates[$k]);
406 // turn flag on
407 $err_rtype_restr = true;
408 }
409 }
410 }
411 }
412 }
413
414 if (!$arr_rates) {
415 // set error code
416 self::$errorCode = $err_rtype_booked ? 7 : 8;
417
418 // build error response
419 $err_mess = JText::translate('VBNOROOMSINDATE');
420 $err_mess = $err_mess == 'VBNOROOMSINDATE' ? JText::translate('VBO_AV_ECODE_7') : $err_mess;
421
422 $res = ['e4j.error' => $err_mess];
423 if ($only_rates && $fullybooked) {
424 $res['fullybooked'] = $fullybooked;
425 }
426
427 // concatenate rate plan restriction errors, if any
428 if ($err_rplan_restr === true) {
429 $res['e4j.error'] .= ' (Rate Plan Restrictions)';
430 }
431
432 if (self::$getArray) {
433 return $res;
434 }
435
436 // send JSON (erroneous) response to output
437 VBOHttpDocument::getInstance()->json($res);
438 }
439
440 // apply special prices
441 $arr_rates = VikBooking::applySeasonalPrices($arr_rates, $args['start_ts'], $args['end_ts']);
442 $multi_rates = 1;
443 foreach ($arr_rates as $idr => $tars) {
444 $multi_rates = count($tars) > $multi_rates ? count($tars) : $multi_rates;
445 }
446 if ($multi_rates > 1) {
447 for ($r = 1; $r < $multi_rates; $r++) {
448 $deeper_rates = [];
449 foreach ($arr_rates as $idr => $tars) {
450 foreach ($tars as $tk => $tar) {
451 if ($tk == $r) {
452 $deeper_rates[$idr][0] = $tar;
453 break;
454 }
455 }
456 }
457 if (!$deeper_rates) {
458 continue;
459 }
460 $deeper_rates = VikBooking::applySeasonalPrices($deeper_rates, $args['start_ts'], $args['end_ts']);
461 foreach ($deeper_rates as $idr => $dtars) {
462 foreach ($dtars as $dtk => $dtar) {
463 $arr_rates[$idr][$r] = $dtar;
464 }
465 }
466 }
467 }
468
469 // children ages charge
470 $children_sums = [];
471
472 // sum charges/discounts per occupancy for each room party
473 foreach ($arrpeople as $roomnumb => $party) {
474 // charges/discounts per adults occupancy
475 foreach ($arr_rates as $r => $rates) {
476 $children_charges = VikBooking::getChildrenCharges($r, $party['children'], $party['children_age'], $args['nights']);
477 if ($children_charges) {
478 $children_sums[$r] += $children_charges['total'];
479 }
480
481 $diffusageprice = VikBooking::loadAdultsDiff($r, $party['adults']);
482 $rplan_obp_overrides = [];
483
484 // Occupancy Override - Special Price may be setting a charge/discount for this occupancy while default price had no occupancy pricing
485 if (!$diffusageprice) {
486 foreach ($rates as $kpr => $vpr) {
487 if (isset($vpr['occupancy_ovr'][$party['adults']]) && strlen((string) $vpr['occupancy_ovr'][$party['adults']]['value'])) {
488 if (!$diffusageprice) {
489 // set the first OBP override rule found
490 $diffusageprice = $vpr['occupancy_ovr'][$party['adults']];
491 }
492 // set the OBP override rule at rate-plan level
493 $rplan_obp_overrides[$vpr['idprice']] = $diffusageprice;
494 }
495 }
496 reset($rates);
497 }
498
499 if ($diffusageprice) {
500 /**
501 * OBP offsets can be overridden at rate-plan-level, while OBP offsets are regularly defined at room-level.
502 * In case we detect OBP overrides only for some rate plans, we try to apply them on the proper rate plans.
503 *
504 * @since 1.17.6 (J) - 1.7.6 (WP)
505 */
506 $rplan_level_obp_overrides = ($rplan_obp_overrides && count($rates) != count($rplan_obp_overrides));
507 $null_obp_override = [
508 $party['adults'] => [
509 'chdisc' => 1,
510 'valpcent' => 1,
511 'pernight' => 0,
512 'value' => 0,
513 ],
514 ];
515
516 // apply OBP rules
517 foreach ($rates as $kpr => $vpr) {
518 if ($roomnumb == 1) {
519 $arr_rates[$r][$kpr]['costbeforeoccupancy'] = (float) $arr_rates[$r][$kpr]['cost'];
520 }
521 // Occupancy Override
522 if (isset($vpr['occupancy_ovr'][$party['adults']]) && strlen((string) $vpr['occupancy_ovr'][$party['adults']]['value'])) {
523 if ($rplan_level_obp_overrides && !($rplan_obp_overrides[$vpr['idprice']] ?? null)) {
524 // apply a null OBP override for this rate plan ID
525 $diffusageprice = $null_obp_override;
526 } else {
527 // apply the OBP override rules
528 $diffusageprice = $vpr['occupancy_ovr'][$party['adults']];
529 }
530 } elseif ($rplan_level_obp_overrides && !($rplan_obp_overrides[$vpr['idprice']] ?? null)) {
531 // apply a null OBP override for this rate plan ID
532 $diffusageprice = $null_obp_override;
533 }
534 $arr_rates[$r][$kpr]['diffusage'] = $party['adults'];
535 if ($diffusageprice['chdisc'] == 1) {
536 // charge
537 if ($diffusageprice['valpcent'] == 1) {
538 // fixed value
539 $aduseval = $diffusageprice['pernight'] == 1 ? $diffusageprice['value'] * $arr_rates[$r][$kpr]['days'] : $diffusageprice['value'];
540 $arr_rates[$r][$kpr]['diffusagecost'][$roomnumb] = $aduseval;
541 $arr_rates[$r][$kpr]['cost'] += $aduseval;
542 } else {
543 // percentage value
544 $aduseval = $diffusageprice['pernight'] == 1 ? round(($arr_rates[$r][$kpr]['costbeforeoccupancy'] * $diffusageprice['value'] / 100) * $arr_rates[$r][$kpr]['days'], 2) : round(($arr_rates[$r][$kpr]['costbeforeoccupancy'] * $diffusageprice['value'] / 100), 2);
545 $arr_rates[$r][$kpr]['diffusagecost'][$roomnumb] = $aduseval;
546 $arr_rates[$r][$kpr]['cost'] += $aduseval;
547 }
548 } else {
549 // discount
550 if ($diffusageprice['valpcent'] == 1) {
551 // fixed value
552 $aduseval = $diffusageprice['pernight'] == 1 ? $diffusageprice['value'] * $arr_rates[$r][$kpr]['days'] : $diffusageprice['value'];
553 $arr_rates[$r][$kpr]['diffusagediscount'][$roomnumb] = $aduseval;
554 $arr_rates[$r][$kpr]['cost'] -= $aduseval;
555 } else {
556 // percentage value
557 $aduseval = $diffusageprice['pernight'] == 1 ? round(((($arr_rates[$r][$kpr]['costbeforeoccupancy'] / $arr_rates[$r][$kpr]['days']) * $diffusageprice['value'] / 100) * $arr_rates[$r][$kpr]['days']), 2) : round(($arr_rates[$r][$kpr]['costbeforeoccupancy'] * $diffusageprice['value'] / 100), 2);
558 $arr_rates[$r][$kpr]['diffusagediscount'][$roomnumb] = $aduseval;
559 $arr_rates[$r][$kpr]['cost'] -= $aduseval;
560 }
561 }
562 }
563 } elseif ($roomnumb == 1) {
564 foreach ($rates as $kpr => $vpr) {
565 $arr_rates[$r][$kpr]['costbeforeoccupancy'] = (float) $arr_rates[$r][$kpr]['cost'];
566 }
567 }
568 }
569 }
570
571 // if the rooms are given to a party of multiple rooms, multiply the basic rates per room per number of rooms
572 for ($i = 2; $i <= $args['num_rooms']; $i++) {
573 foreach ($arr_rates as $r => $rates) {
574 foreach ($rates as $kpr => $vpr) {
575 $arr_rates[$r][$kpr]['cost'] += $arr_rates[$r][$kpr]['costbeforeoccupancy'];
576 }
577 }
578 }
579
580 // children ages charge
581 if ($children_sums) {
582 foreach ($arr_rates as $r => $rates) {
583 if (array_key_exists($r, $children_sums)) {
584 foreach ($rates as $kpr => $vpr) {
585 $arr_rates[$r][$kpr]['cost'] += $children_sums[$r];
586 }
587 }
588 }
589 }
590
591 //sort results by price ASC
592 $arr_rates = VikBooking::sortResults($arr_rates);
593
594 // compose taxes information
595 $ivainclusa = VikBooking::ivaInclusa();
596 $rates_ids = [];
597 foreach ($arr_rates as $r => $rate) {
598 foreach ($rate as $ids) {
599 if (!in_array($ids['idprice'], $rates_ids)) {
600 $rates_ids[] = $ids['idprice'];
601 }
602 }
603 }
604 $tax_rates = [];
605 $q = "SELECT `p`.`id`,`t`.`aliq`,`t`.`taxcap` FROM `#__vikbooking_prices` AS `p` LEFT JOIN `#__vikbooking_iva` `t` ON `p`.`idiva`=`t`.`id` WHERE `p`.`id` IN (".implode(',', $rates_ids).");";
606 $dbo->setQuery($q);
607 $alltaxrates = $dbo->loadAssocList();
608 if ($alltaxrates) {
609 foreach ($alltaxrates as $tx) {
610 if (!empty($tx['aliq']) && $tx['aliq'] > 0) {
611 /**
612 * Tax Cap implementation.
613 *
614 * @since 1.12
615 */
616 $tax_rates[$tx['id']] = array($tx['aliq'], (float)$tx['taxcap']);
617 }
618 }
619 }
620 if ($tax_rates) {
621 foreach ($arr_rates as $r => $rates) {
622 foreach ($rates as $k => $rate) {
623 if (array_key_exists($rate['idprice'], $tax_rates)) {
624 if (intval($ivainclusa) == 1) {
625 // prices tax included
626 $realcost = $rate['cost'];
627 $tax_oper = ($tax_rates[$rate['idprice']][0] + 100) / 100;
628 $taxes = $rate['cost'] - ($rate['cost'] / $tax_oper);
629 /**
630 * Tax Cap implementation.
631 *
632 * @since 1.12
633 */
634 if ($tax_rates[$rate['idprice']][1] > 0 && $taxes > $tax_rates[$rate['idprice']][1]) {
635 $taxes = $tax_rates[$rate['idprice']][1];
636 }
637 } else {
638 // prices tax excluded
639 $realcost = $rate['cost'] * (100 + $tax_rates[$rate['idprice']][0]) / 100;
640 $taxes = $realcost - $rate['cost'];
641 /**
642 * Tax Cap implementation.
643 *
644 * @since 1.12
645 */
646 if ($tax_rates[$rate['idprice']][1] > 0 && $taxes > $tax_rates[$rate['idprice']][1]) {
647 $realcost = $rate['cost'] + $tax_rates[$rate['idprice']][1];
648 $taxes = $tax_rates[$rate['idprice']][1];
649 }
650 }
651 if (!($options['wtax'] ?? null) && ($req_type == 'hotel_availability' || $req_type == 'booking_availability')) {
652 // always set 'cost' to the base rate tax excluded
653 $realcost = $realcost - $taxes;
654 }
655 $arr_rates[$r][$k]['cost'] = round($realcost, 2);
656 $arr_rates[$r][$k]['taxes'] = round($taxes, 2);
657 } else {
658 // make sure to populate the "taxes" property as an empty value
659 $arr_rates[$r][$k]['taxes'] = round(0, 2);
660 }
661 }
662 }
663 // sum taxes/fees for each room party
664 foreach ($arrpeople as $roomnumb => $party) {
665 foreach ($arr_rates as $r => $rates) {
666 $city_tax_fees = VikBooking::getMandatoryTaxesFees([$r], $party['adults'], $args['nights'], $args['start_ts'], $args['end_ts']);
667 foreach ($rates as $k => $rate) {
668 if (!isset($arr_rates[$r][$k]['city_taxes'])) {
669 $arr_rates[$r][$k]['city_taxes'] = 0;
670 }
671 if (!isset($arr_rates[$r][$k]['fees'])) {
672 $arr_rates[$r][$k]['fees'] = 0;
673 }
674 $arr_rates[$r][$k]['city_taxes'] += round($city_tax_fees['city_taxes'], 2);
675 $arr_rates[$r][$k]['fees'] += round($city_tax_fees['fees'], 2);
676 }
677 }
678 }
679 } else {
680 foreach ($arr_rates as $r => $rates) {
681 foreach ($rates as $k => $rate) {
682 $arr_rates[$r][$k]['taxes'] = round(0, 2);
683 $arr_rates[$r][$k]['city_taxes'] = round(0, 2);
684 $arr_rates[$r][$k]['fees'] = round(0, 2);
685 }
686 }
687 }
688
689 if (self::$getArray) {
690 return $arr_rates;
691 }
692
693 if ($only_rates && $fullybooked) {
694 $arr_rates['fullybooked'] = $fullybooked;
695 }
696
697 // send JSON response to output
698 VBOHttpDocument::getInstance()->json($arr_rates);
699 }
700
701 /**
702 * Returns the error code to understand what produced an error.
703 *
704 * @return int the error code identifier.
705 *
706 * @since 1.15.0 (J) - 1.5.0 (WP)
707 */
708 public static function getErrorCode()
709 {
710 return self::$errorCode;
711 }
712 }
713