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

helper.php in VikBooking Hotel Booking Engine & PMS 1.8.7, at admin/helpers/src/room/helper.php

896 lines 27.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 core
5 * @author Alessio Gaggii - E4J s.r.l.
6 * @copyright Copyright (C) 2022 E4J s.r.l. All Rights Reserved.
7 * @license http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
8 * @link https://vikwp.com
9 */
10
11 // No direct access
12 defined('ABSPATH') or die('No script kiddies please!');
13
14 /**
15 * Helper class to handle rooms data.
16 *
17 * @since 1.15.1 (J) - 1.5.2 (WP)
18 */
19 final class VBORoomHelper extends JObject
20 {
21 /**
22 * The singleton instance of the class.
23 *
24 * @var VBORoomHelper
25 */
26 private static $instance = null;
27
28 /**
29 * Proxy to construct the object.
30 *
31 * @param array|object $data optional data to bind.
32 * @param boolean $anew true for forcing a new instance.
33 *
34 * @return self
35 */
36 public static function getInstance($data = [], $anew = false)
37 {
38 if (is_null(static::$instance) || $anew) {
39 static::$instance = new static($data);
40 }
41
42 return static::$instance;
43 }
44
45 /**
46 * Checks whether a room has been configured with LOS pricing rules.
47 * VCM comes with a similar built-in method, but we need this feature
48 * to be available also for those who only use VBO. Moreover, this method
49 * can identify the first night with a non-proportional rate.
50 *
51 * @param int $idroom the ID of the room in VBO.
52 * @param int $idprice the optional rate plan ID in VBO.
53 * @param bool $get_nights whether to return the number of nights when LOS starts.
54 *
55 * @return bool|int false on failure or if no LOS prices found, true or int otherwise.
56 */
57 public static function hasLosRecords($idroom, $idprice = 0, $get_nights = false)
58 {
59 if (empty($idroom)) {
60 return false;
61 }
62
63 $dbo = JFactory::getDbo();
64 $q = "SELECT * FROM `#__vikbooking_dispcost` WHERE `idroom`=" . (int)$idroom . (!empty($idprice) ? " AND `idprice`=" . (int)$idprice : '') . " ORDER BY `days` ASC;";
65 $dbo->setQuery($q);
66 $los_data = $dbo->loadAssocList();
67 if (!$los_data) {
68 return false;
69 }
70
71 $los_pricing = array();
72 foreach ($los_data as $cost) {
73 if (!isset($los_pricing[$cost['days']])) {
74 $los_pricing[$cost['days']] = array();
75 }
76 array_push($los_pricing[$cost['days']], $cost);
77 }
78 // sort by number of nights
79 ksort($los_pricing);
80
81 // compose lowest costs per rate plan
82 $base_costs = array();
83 foreach ($los_pricing as $nights => $costs) {
84 foreach ($costs as $rplan_cost) {
85 $base_costs[$rplan_cost['idprice']] = ($rplan_cost['cost'] / $rplan_cost['days']);
86 }
87 // we take the costs for the lowest number of nights
88 break;
89 }
90
91 // check if rates change depending on the number of nights of stay
92 foreach ($los_pricing as $nights => $costs) {
93 foreach ($costs as $rplan_cost) {
94 $base_cost = ($rplan_cost['cost'] / $rplan_cost['days']);
95 if (isset($base_costs[$rplan_cost['idprice']]) && round($base_costs[$rplan_cost['idprice']], 2) != round($base_cost, 2)) {
96 /**
97 * Average rates should be compared after applying rounding or we may face issues.
98 * For example, 383.97 / 3 = 127.99, but it's actually = 127.99000000000001 with
99 * an absolute number for the difference with 127.99 of 1.4210854715202004E-14
100 * which results to be greater than 0 but less than 1. Therefore, we also allow
101 * an absolute number for the difference of 0.05 cents for a proper check.
102 */
103 $price_diff = abs($base_costs[$rplan_cost['idprice']] - $base_cost);
104 if ($price_diff > 0.05) {
105 // this is a non-proportional cost per night, so LOS records have been defined
106 return $get_nights ? $nights : true;
107 }
108 }
109 }
110 }
111
112 // all costs per night were proportional
113 return false;
114 }
115
116 /**
117 * Calculates the effective Min LOS from the Rates Table
118 * for the given room and rate plan ID.
119 *
120 * @param int $idroom the ID of the room-type on the website.
121 * @param int $idprice the ID of the rate plan on the website.
122 *
123 * @return int the effective Min LOS or 0.
124 *
125 * @since 1.18.0 (J) - 1.8.0 (WP)
126 */
127 public static function calcEffectiveMinLOS($idroom, $idprice)
128 {
129 if (empty($idroom) || empty($idprice)) {
130 return 0;
131 }
132
133 $dbo = JFactory::getDbo();
134
135 $dbo->setQuery(
136 $dbo->getQuery(true)
137 ->select('MIN(' . $dbo->qn('days') . ')')
138 ->from($dbo->qn('#__vikbooking_dispcost'))
139 ->where($dbo->qn('idroom') . ' = ' . (int) $idroom)
140 ->where($dbo->qn('idprice') . ' = ' . (int) $idprice)
141 , 0, 1);
142
143 return (int) $dbo->loadResult();
144 }
145
146 /**
147 * Calculates the effective Max LOS from the Rates Table
148 * for the given room and rate plan ID.
149 *
150 * @param int $idroom the ID of the room-type on the website.
151 * @param int $idprice the ID of the rate plan on the website.
152 *
153 * @return int the effective Max LOS or 0.
154 *
155 * @since 1.18.0 (J) - 1.8.0 (WP)
156 */
157 public static function calcEffectiveMaxLOS($idroom, $idprice)
158 {
159 if (empty($idroom) || empty($idprice)) {
160 return 0;
161 }
162
163 $dbo = JFactory::getDbo();
164
165 $dbo->setQuery(
166 $dbo->getQuery(true)
167 ->select('MAX(' . $dbo->qn('days') . ')')
168 ->from($dbo->qn('#__vikbooking_dispcost'))
169 ->where($dbo->qn('idroom') . ' = ' . (int) $idroom)
170 ->where($dbo->qn('idprice') . ' = ' . (int) $idprice)
171 , 0, 1);
172
173 return (int) $dbo->loadResult();
174 }
175
176 /**
177 * Calculates the highest Max LOS from the Rates Table for the given room.
178 * Useful to tell the highest max LOS allowed in a multi-rate plan environment.
179 *
180 * @param int $idroom the ID of the room-type on the website.
181 * @param bool $needs_multi_rate whether the rates table requires multiple rate plans.
182 *
183 * @return int the effective Max LOS or 0.
184 *
185 * @since 1.18.6 (J) - 1.8.6 (WP)
186 */
187 public static function calcHighestMaxLOS(int $idroom, bool $needs_multi_rate = false)
188 {
189 if (empty($idroom)) {
190 return 0;
191 }
192
193 $dbo = JFactory::getDbo();
194
195 $dbo->setQuery(
196 $dbo->getQuery(true)
197 ->select('MAX(' . $dbo->qn('days') . ')')
198 ->from($dbo->qn('#__vikbooking_dispcost'))
199 ->where($dbo->qn('idroom') . ' = ' . (int) $idroom)
200 , 0, 1);
201
202 $highest_max_los = (int) $dbo->loadResult();
203
204 if ($needs_multi_rate) {
205 $dbo->setQuery(
206 $dbo->getQuery(true)
207 ->select('COUNT(DISTINCT ' . $dbo->qn('idprice') . ')')
208 ->from($dbo->qn('#__vikbooking_dispcost'))
209 ->where($dbo->qn('idroom') . ' = ' . (int) $idroom)
210 , 0, 1);
211
212 $total_room_rplans = (int) $dbo->loadResult();
213
214 if ($total_room_rplans < 2) {
215 // this room has got rates defined for a single rate plan
216 return 0;
217 }
218 }
219
220 return $highest_max_los;
221 }
222
223 /**
224 * Gets the available room upgrade options, if any.
225 *
226 * @param VikBookingTranslator $vbo_tn the translator object.
227 *
228 * @return array list of available upgrade options,
229 * or empty array if nothing availabe.
230 *
231 * @since 1.16.0 (J) - 1.6.0 (WP)
232 */
233 public function getUpgradeOptions($vbo_tn = null)
234 {
235 $booking = $this->get('booking', []);
236 $rooms = $this->get('rooms', []);
237
238 if (!$booking || !$rooms || $booking['status'] != 'confirmed') {
239 return [];
240 }
241
242 $dbo = JFactory::getDbo();
243 $config = VBOFactory::getConfig();
244
245 $upgrade_options = [];
246 $room_ids = [];
247
248 foreach ($rooms as $num => $broom) {
249 if (empty($broom['idroom']) || empty($broom['idtar'])) {
250 // room must have a valid tariff assigned
251 continue;
252 }
253 $room_upgrade_options = $config->getArray('room_upgrade_options_' . $broom['idroom'], []);
254 if (empty($room_upgrade_options) || empty($room_upgrade_options['rooms'])) {
255 // no relations for this room
256 continue;
257 }
258 // fetch the original tariff for this room
259 $orig_tariff = $this->getTariffData($broom['idtar']);
260 if (!$orig_tariff) {
261 // unable to get the original tariff information for this room booked
262 continue;
263 }
264 // push suitable rooms
265 $upgrade_options[$num] = [
266 'rooms' => array_map('intval', array_filter(array_unique($room_upgrade_options['rooms']))),
267 'discount' => (!empty($room_upgrade_options['discount']) ? (float)$room_upgrade_options['discount'] : 0),
268 'tariff' => $orig_tariff,
269 'r_costs' => [],
270 ];
271 $room_ids = array_merge($room_ids, $room_upgrade_options['rooms']);
272 }
273
274 if (!$upgrade_options) {
275 return [];
276 }
277
278 // get all room IDs involved
279 $room_ids = array_map('intval', array_filter(array_unique($room_ids)));
280
281 $q = "SELECT * FROM `#__vikbooking_rooms` WHERE `id` IN (" . implode(', ', $room_ids) . ") AND `avail`=1;";
282 $dbo->setQuery($q);
283 $room_records = $dbo->loadAssocList();
284 if (!$room_records) {
285 return [];
286 }
287 if ($vbo_tn) {
288 // translate rooms
289 $vbo_tn->translateContents($room_records, '#__vikbooking_rooms');
290 }
291
292 // build up an associative array of room infos
293 $room_infos = [];
294 foreach ($room_records as $room_record) {
295 $room_infos[$room_record['id']] = $this->prepareCMSContents($room_record, ['info', 'smalldesc']);
296 }
297 unset($room_records);
298
299 // keep the count of the room units suggested
300 $room_units_counter = [];
301
302 // filter the suitable rooms by rate plan, and calculate the costs
303 foreach ($upgrade_options as $num => $upgrade_option) {
304 // build the costs for each upgrade room option
305 $upgrade_room_costs = [];
306 // parse all rooms compatible
307 foreach ($upgrade_option['rooms'] as $rkey => $rid) {
308 // find the same tariff for this room and nights
309 $room_same_tariff = $this->findTariff($rid, $upgrade_option['tariff']['days'], $upgrade_option['tariff']['idprice']);
310 if (!$room_same_tariff || !isset($room_infos[$rid])) {
311 // this room is not suited
312 unset($upgrade_options[$num]['rooms'][$rkey]);
313 continue;
314 }
315
316 // count the actual number of room remaining units
317 $use_room_units = $room_infos[$rid]['units'];
318 if (isset($room_units_counter[$rid])) {
319 $use_room_units -= $room_units_counter[$rid];
320 }
321
322 // make sure the room is bookable on these dates (restrictions are ignored)
323 if (!VikBooking::roomBookable($rid, $use_room_units, $booking['checkin'], $booking['checkout'])) {
324 // room is not available for upgrade
325 unset($upgrade_options[$num]['rooms'][$rkey]);
326 continue;
327 }
328
329 // update room units counter
330 if (!isset($room_units_counter[$rid])) {
331 $room_units_counter[$rid] = 0;
332 }
333 $room_units_counter[$rid]++;
334
335 // apply seasonal rates
336 $tar = VikBooking::applySeasonsRoom([$room_same_tariff], $booking['checkin'], $booking['checkout']);
337
338 // apply OBP rules
339 $tar = $this->applyOBPRules($tar, $room_infos[$rid], $rooms[$num]['adults']);
340
341 // apply upgrade discount (if any) and calculate upgrade cost
342 foreach ($tar as $tk => $tv) {
343 $tar[$tk]['upgrade_cost'] = $upgrade_option['discount'] > 0 ? round(($tv['cost'] * (100 - $upgrade_option['discount']) / 100), 2) : $tv['cost'];
344 }
345
346 // push room tariff (just one rate plan, the originally booked one)
347 $upgrade_room_costs[$rid] = $tar[0];
348 }
349
350 if (!count($upgrade_options[$num]['rooms'])) {
351 // no more suitable rooms
352 unset($upgrade_options[$num]);
353 continue;
354 }
355
356 // sort by price descending (most expensive on top)
357 $sort_map = [];
358 foreach ($upgrade_room_costs as $rid => $tar) {
359 $sort_map[$rid] = $tar['upgrade_cost'];
360 }
361 arsort($sort_map);
362
363 // replace values with sorted ordering
364 $cp_upgrade_room_costs = [];
365 foreach ($sort_map as $rid => $sorted) {
366 $cp_upgrade_room_costs[$rid] = $upgrade_room_costs[$rid];
367 }
368 $upgrade_room_costs = $cp_upgrade_room_costs;
369
370 // set upgrade room costs
371 $upgrade_options[$num]['r_costs'] = $upgrade_room_costs;
372 }
373
374 if (!count($upgrade_options)) {
375 return [];
376 }
377
378 // return the associative array information
379 return [
380 'upgrade' => $upgrade_options,
381 'rooms' => $room_infos,
382 ];
383 }
384
385 /**
386 * Gets the record details about a specific tariff ID.
387 *
388 * @param int $idtar the ID of the room-tariff.
389 *
390 * @return array record found, or empty array.
391 *
392 * @since 1.16.0 (J) - 1.6.0 (WP)
393 */
394 public function getTariffData($idtar)
395 {
396 $dbo = JFactory::getDbo();
397
398 $dbo->setQuery("SELECT * FROM `#__vikbooking_dispcost` WHERE `id` = " . (int)$idtar, 0, 1);
399 $tariff = $dbo->loadAssoc();
400 if (!$tariff) {
401 return [];
402 }
403
404 return $tariff;
405 }
406
407 /**
408 * Finds a tariff for the given rate plan ID, room and nights.
409 *
410 * @param int $rid the room ID.
411 * @param int $nights the number of nights of stay.
412 * @param int $idprice the rate plan ID.
413 *
414 * @return array record found or empty array.
415 *
416 * @since 1.16.0 (J) - 1.6.0 (WP)
417 */
418 public function findTariff($rid, $nights, $idprice)
419 {
420 $dbo = JFactory::getDbo();
421
422 $q = "SELECT `t`.*, `p`.`name` AS `rate_plan_name` FROM `#__vikbooking_dispcost` AS `t`
423 LEFT JOIN `#__vikbooking_prices` AS `p` ON `t`.`idprice`=`p`.`id`
424 WHERE `t`.`idroom` = " . (int)$rid . " AND `t`.`days`=" . (int)$nights . " AND `t`.`idprice`=" . (int)$idprice;
425 $dbo->setQuery($q, 0, 1);
426 $tariff = $dbo->loadAssoc();
427 if (!$tariff) {
428 return [];
429 }
430
431 return $tariff;
432 }
433
434 /**
435 * Applies the OBP rules over an array of tariffs.
436 *
437 * @param array $tar list of tariff records, one per rate plan, after seasonal rates.
438 * @param array $room the room (or order-room) record for which tariffs where loaded.
439 * @param int $adults the number of adults to consider.
440 *
441 * @return array original tariffs array with OBP costs applied.
442 *
443 * @since 1.16.0 (J) - 1.6.0 (WP)
444 */
445 public function applyOBPRules(array $tar, array $room, $adults = 2)
446 {
447 // check for different usage
448 if (!isset($room['fromadult']) || $room['fromadult'] > $adults || $room['toadult'] < $adults) {
449 return $tar;
450 }
451
452 // check for room ID
453 $use_room_id = isset($room['idroom']) ? $room['idroom'] : $room['id'];
454
455 // different usage
456 $diffusageprice = VikBooking::loadAdultsDiff($use_room_id, $adults);
457
458 /**
459 * Memorize immediately the OBP rules defined at room-level in order to avoid conflicts
460 * with rate plans with and without OBP overrides defined at rate plan level through SP.
461 */
462 $orig_diffusage = $diffusageprice;
463
464 // occupancy overrides
465 $occ_ovr = VikBooking::occupancyOverrideExists($tar, $adults);
466 $diffusageprice = $occ_ovr !== false ? $occ_ovr : $diffusageprice;
467
468 if (!$diffusageprice) {
469 return $tar;
470 }
471
472 // set a charge or discount to the price(s) for the different usage of the room
473 foreach ($tar as $kpr => $vpr) {
474 // occupancy override
475 $diffusageprice = isset($vpr['occupancy_ovr']) && isset($vpr['occupancy_ovr'][$adults]) ? $vpr['occupancy_ovr'][$adults] : $orig_diffusage;
476
477 // set usage of the room
478 $tar[$kpr]['diffusage'] = $adults;
479
480 if ($diffusageprice['chdisc'] == 1) {
481 // charge
482 if ($diffusageprice['valpcent'] == 1) {
483 // fixed value
484 $tar[$kpr]['diffusagecostpernight'] = $diffusageprice['pernight'] == 1 ? 1 : 0;
485 $aduseval = $diffusageprice['pernight'] == 1 ? $diffusageprice['value'] * $tar[$kpr]['days'] : $diffusageprice['value'];
486 $tar[$kpr]['diffusagecost'] = "+" . $aduseval;
487 $tar[$kpr]['room_base_cost'] = $vpr['cost'];
488 $tar[$kpr]['cost'] = $vpr['cost'] + $aduseval;
489 } else {
490 // percentage value
491 $tar[$kpr]['diffusagecostpernight'] = $diffusageprice['pernight'] == 1 ? $vpr['cost'] : 0;
492 $aduseval = $diffusageprice['pernight'] == 1 ? round(($vpr['cost'] * $diffusageprice['value'] / 100) * $tar[$kpr]['days'] + $vpr['cost'], 2) : round(($vpr['cost'] * (100 + $diffusageprice['value']) / 100), 2);
493 $tar[$kpr]['diffusagecost'] = "+" . $diffusageprice['value'] . "%";
494 $tar[$kpr]['room_base_cost'] = $vpr['cost'];
495 $tar[$kpr]['cost'] = $aduseval;
496 }
497 } else {
498 // discount
499 if ($diffusageprice['valpcent'] == 1) {
500 // fixed value
501 $tar[$kpr]['diffusagecostpernight'] = $diffusageprice['pernight'] == 1 ? 1 : 0;
502 $aduseval = $diffusageprice['pernight'] == 1 ? $diffusageprice['value'] * $tar[$kpr]['days'] : $diffusageprice['value'];
503 $tar[$kpr]['diffusagecost'] = "-" . $aduseval;
504 $tar[$kpr]['room_base_cost'] = $vpr['cost'];
505 $tar[$kpr]['cost'] = $vpr['cost'] - $aduseval;
506 } else {
507 // percentage value
508 $tar[$kpr]['diffusagecostpernight'] = $diffusageprice['pernight'] == 1 ? $vpr['cost'] : 0;
509 $aduseval = $diffusageprice['pernight'] == 1 ? round($vpr['cost'] - ((($vpr['cost'] / $tar[$kpr]['days']) * $diffusageprice['value'] / 100) * $tar[$kpr]['days']), 2) : round(($vpr['cost'] * (100 - $diffusageprice['value']) / 100), 2);
510 $tar[$kpr]['diffusagecost'] = "-" . $diffusageprice['value'] . "%";
511 $tar[$kpr]['room_base_cost'] = $vpr['cost'];
512 $tar[$kpr]['cost'] = $aduseval;
513 }
514 }
515 }
516
517 // return the array of tariffs with OBP included
518 return $tar;
519 }
520
521 /**
522 * Prepares some description strings for the current CMS, by triggering
523 * the necessary platform-related functions for third party plugins.
524 *
525 * @param array $room_record the room record to prepare.
526 * @param array $keys list of record keys to prepare.
527 *
528 * @return array the original array given with keys prepared.
529 *
530 * @since 1.16.0 (J) - 1.6.0 (WP)
531 */
532 public function prepareCMSContents(array $room_record, array $keys)
533 {
534 foreach ($keys as $key) {
535 if (!isset($room_record[$key])) {
536 continue;
537 }
538
539 if (VBOPlatformDetection::isWordPress()) {
540 /**
541 * @wponly we try to parse any shortcode inside the HTML description of the room
542 */
543 $room_record[$key] = do_shortcode(wpautop($room_record[$key]));
544 } else {
545 // BEGIN: Joomla Content Plugins Rendering
546 JPluginHelper::importPlugin('content');
547
548 $myItem = JTable::getInstance('content');
549
550 $myItem->text = $room_record[$key];
551 $objparams = array();
552 if (class_exists('JEventDispatcher')) {
553 $dispatcher = JEventDispatcher::getInstance();
554 $dispatcher->trigger('onContentPrepare', array('com_vikbooking.roomdetails', &$myItem, &$objparams, 0));
555 } else {
556 /**
557 * @joomla4only
558 */
559 $dispatcher = JFactory::getApplication();
560 if (method_exists($dispatcher, 'triggerEvent')) {
561 $dispatcher->triggerEvent('onContentPrepare', array('com_vikbooking.roomdetails', &$myItem, &$objparams, 0));
562 }
563 }
564 $room_record[$key] = $myItem->text;
565 // END: Joomla Content Plugins Rendering
566 }
567 }
568
569 return $room_record;
570 }
571
572 /**
573 * Gets an associative list of rate plans with a few pricing information for the given room.
574 *
575 * @param int $rid the VBO room id.
576 * @param int $rplan_id optional rate plan ID to get.
577 *
578 * @return array associative list of rate plans for the given room or specific rate plan.
579 *
580 * @since 1.16.3 (J) - 1.6.3 (WP)
581 */
582 public function getRatePlans($rid = 0, $rplan_id = 0)
583 {
584 if (empty($rid)) {
585 $rid = $this->get('id', 0);
586 }
587
588 $dbo = JFactory::getDbo();
589
590 $q = $dbo->getQuery(true)
591 ->select([
592 $dbo->qn('r.id'),
593 $dbo->qn('r.idroom'),
594 $dbo->qn('r.days'),
595 $dbo->qn('r.idprice'),
596 $dbo->qn('r.cost'),
597 $dbo->qn('p.name'),
598 $dbo->qn('p.minlos'),
599 $dbo->qn('p.derived_id'),
600 ])
601 ->from($dbo->qn('#__vikbooking_dispcost', 'r'))
602 ->leftJoin($dbo->qn('#__vikbooking_prices', 'p') . ' ON ' . $dbo->qn('r.idprice') . ' = ' . $dbo->qn('p.id'))
603 ->where($dbo->qn('r.idroom') . ' = ' . (int)$rid)
604 ->order($dbo->qn('r.days') . ' ASC')
605 ->order($dbo->qn('r.cost') . ' ASC');
606
607 $dbo->setQuery($q, 0, 50);
608
609 $tariffs = $dbo->loadObjectList();
610
611 if (!$tariffs) {
612 return [];
613 }
614
615 $parsed_room_prices = [];
616 foreach ($tariffs as $rrk => $rrv) {
617 if (isset($parsed_room_prices[$rrv->idprice])) {
618 unset($tariffs[$rrk]);
619 continue;
620 }
621 $tariffs[$rrk]->cost = round(($rrv->cost / $rrv->days), 2);
622 $tariffs[$rrk]->days = 1;
623 $parsed_room_prices[$rrv->idprice] = 1;
624 }
625
626 $tariffs = array_values($tariffs);
627
628 $room_rate_plans = [];
629 foreach ($tariffs as $rplan) {
630 if ($rplan_id && $rplan_id == $rplan->idprice) {
631 return (array)$rplan;
632 }
633
634 $room_rate_plans[] = [
635 'id' => $rplan->idprice,
636 'name' => $rplan->name,
637 'cost' => $rplan->cost,
638 'minlos' => $rplan->minlos,
639 'derived_id' => $rplan->derived_id,
640 ];
641 }
642
643 if ($rplan_id) {
644 return [];
645 }
646
647 return $room_rate_plans;
648 }
649
650 /**
651 * Calculates if the provided booking information require a split payment for the damage deposit.
652 *
653 * @param array $booking The booking record data.
654 * @param array $booking_rooms The rooms booking list.
655 *
656 * @return array Associative list of damage deposit details.
657 *
658 * @since 1.17.6 (J) - 1.7.6 (WP)
659 */
660 public function getDamageDepositSplitPayment(array $booking, array $booking_rooms)
661 {
662 // load all option records of type damage deposit
663 $dbo = JFactory::getDbo();
664 $dbo->setQuery(
665 $dbo->getQuery(true)
666 ->select('*')
667 ->from($dbo->qn('#__vikbooking_optionals'))
668 ->where($dbo->qn('forcesel') . ' = 1')
669 ->where($dbo->qn('oparams') . ' LIKE ' . $dbo->q('%' . str_replace(['{', '}'], '', json_encode(['damagedep' => 1])) . '%'))
670 );
671 $dd_records = $dbo->loadAssocList();
672
673 // scan all records for validation, if any
674 foreach ($dd_records as &$dd_record) {
675 // make sure to decode the option params
676 $dd_record['oparams'] = (array) json_decode($dd_record['oparams'], true);
677
678 if (empty($dd_record['oparams']['damagedep_settings']['paywhen'])) {
679 // no separate payment defined
680 unset($dd_record);
681 continue;
682 }
683
684 // validate maximum nights of stay
685 if (!empty($dd_record['oparams']['damagedep_settings']['bmaxlos']) && ($booking['days'] ?? 1) > $dd_record['oparams']['damagedep_settings']['bmaxlos']) {
686 // limit exceeded
687 unset($dd_record);
688 continue;
689 }
690
691 // validate payment method ID
692 if (empty($dd_record['oparams']['damagedep_settings']['payid']) && empty($booking['idpayment'])) {
693 // no payment method defined anywhere
694 unset($dd_record);
695 continue;
696 }
697
698 // calculate and set the payment window values
699 $dd_record['payment_window'] = [];
700 if (!strlen((string) $dd_record['oparams']['damagedep_settings']['paywind'])) {
701 // payable from today (always)
702 $dd_record['payment_window']['payment_from_dt'] = date('Y-m-d');
703 $dd_record['payment_window']['payable'] = true;
704 } elseif (empty($dd_record['oparams']['damagedep_settings']['paywind'])) {
705 // payable from the check-in day
706 $dd_record['payment_window']['payment_from_dt'] = date('Y-m-d', $booking['checkin']);
707 $dd_record['payment_window']['payable'] = strtotime($dd_record['payment_window']['payment_from_dt']) <= strtotime(date('Y-m-d'));
708 } else {
709 // calculate the payable date
710 $window_days = (int) $dd_record['oparams']['damagedep_settings']['paywind'];
711 $dd_record['payment_window']['payment_from_dt'] = date('Y-m-d', strtotime(sprintf('-%d days', $window_days), $booking['checkin']));
712 $dd_record['payment_window']['payable'] = strtotime($dd_record['payment_window']['payment_from_dt']) <= strtotime(date('Y-m-d'));
713 }
714
715 // check if a custom payment method should be used
716 if (!empty($dd_record['oparams']['damagedep_settings']['payid'])) {
717 $dd_record['payment_window']['pay_id'] = $dd_record['oparams']['damagedep_settings']['payid'];
718 }
719
720 // ensure damage deposit amount was not paid already
721 if (empty($booking['idorderota']) && !empty($booking['totpaid']) && $booking['totpaid'] >= ($booking['total'] ?? 0)) {
722 // payment window not available because damage deposit already paid
723 $dd_record['payment_window'] = [];
724 }
725 }
726
727 // unset last reference
728 unset($dd_record);
729
730 if (!$dd_records) {
731 // unable to proceed
732 return [];
733 }
734
735 // always reset array keys
736 $dd_records = array_values($dd_records);
737
738 // list of damage deposit option IDs
739 $dd_record_ids = array_column($dd_records, 'id');
740
741 // room reservation IDs affected
742 $room_reservation_dd = [];
743
744 // collect all damage deposit options from the booked rooms
745 $rooms_dd_data = [];
746 foreach ($booking_rooms as $or) {
747 if (empty($or['optionals'])) {
748 continue;
749 }
750
751 $stepo = array_filter(explode(";", $or['optionals']));
752 foreach ($stepo as $roptkey => $one) {
753 $stept = explode(":", $one);
754 if (in_array($stept[0], $dd_record_ids)) {
755 // push damage deposit ID and room record
756 $rooms_dd_data[] = [
757 'dd_id' => $stept[0],
758 'rr' => $or,
759 ];
760
761 // push room reservation ID
762 $room_reservation_dd[] = $or['idroom'] ?? 0;
763 }
764 }
765 }
766
767 if (!$rooms_dd_data) {
768 // no damage deposit options were booked
769 return [];
770 }
771
772 // get the unique array
773 $rooms_dd_unique = array_values(array_unique(array_column($rooms_dd_data, 'dd_id')));
774
775 // turn the records into an associative list
776 $dd_records_assoc = [];
777 foreach ($dd_records as $dd_record) {
778 $dd_records_assoc[$dd_record['id']] = $dd_record;
779 }
780
781 // calculate amounts and damage deposit payment window
782 $tot_dd_amount_gross = 0;
783 $tot_dd_amount_net = 0;
784 $tot_dd_amount_tax = 0;
785 $payment_window = [];
786
787 foreach ($rooms_dd_data as $room_dd_data) {
788 $opt_id = $room_dd_data['dd_id'];
789 if (!($dd_records_assoc[$opt_id] ?? [])) {
790 continue;
791 }
792
793 // calculate damage deposit price
794 $dd_price = (float) $dd_records_assoc[$opt_id]['cost'];
795 if (!empty($dd_records_assoc[$opt_id]['pcentroom'])) {
796 // percent cost of the room reservation
797 $room_cost = ($room_dd_data['rr']['room_cost'] ?? 0) ?: ($room_dd_data['rr']['cust_cost'] ?? 0) ?: 0;
798 $dd_price = $room_cost * $dd_price / 100;
799 }
800
801 if ($dd_price <= 0) {
802 // invalid damage deposit cost
803 continue;
804 }
805
806 if ($dd_records_assoc[$opt_id]['perday'] == 1) {
807 // cost per night
808 $dd_price = $dd_price * ($booking['days'] ?? 1);
809 }
810
811 if (($dd_records_assoc[$opt_id]['maxprice'] ?? 0) > 0 && $dd_price > $dd_records_assoc[$opt_id]['maxprice']) {
812 // maximum cost
813 $dd_price = (float) $dd_records_assoc[$opt_id]['maxprice'];
814 }
815
816 if ($dd_records_assoc[$opt_id]['perperson'] == 1) {
817 // cost per person
818 $dd_price = $dd_price * ((int) $room_dd_data['rr']['adults']);
819 }
820
821 /**
822 * Trigger event to allow third party plugins to apply a custom calculation for the option/extra fee or tax.
823 *
824 * @since 1.17.7 (J) - 1.7.7 (WP)
825 */
826 $custom_calculation = VBOFactory::getPlatform()->getDispatcher()->filter('onCalculateBookingOptionFeeCost', [$dd_price, &$dd_records_assoc[$opt_id], $booking, $booking_rooms]);
827 if ($custom_calculation) {
828 $dd_price = (float) $custom_calculation[0];
829 }
830
831 if ($dd_price <= 0) {
832 // invalid damage deposit cost
833 continue;
834 }
835
836 // calculate taxes, if any
837 $dd_amount_gross = VikBooking::sayOptionalsPlusIva($dd_price, $dd_records_assoc[$opt_id]['idiva']);
838 $dd_amount_net = VikBooking::sayOptionalsMinusIva($dd_price, $dd_records_assoc[$opt_id]['idiva']);
839 $dd_amount_tax = $dd_amount_gross - $dd_amount_net;
840
841 // increase global values
842 $tot_dd_amount_gross += $dd_amount_gross;
843 $tot_dd_amount_net += $dd_amount_net;
844 $tot_dd_amount_tax += $dd_amount_tax;
845
846 // update payment window (one for all option records)
847 $payment_window = (array) $dd_records_assoc[$opt_id]['payment_window'];
848 }
849
850 if (!$tot_dd_amount_gross) {
851 // no compliant damage deposit option found for separate payment
852 return [];
853 }
854
855 return [
856 'damagedep_gross' => $tot_dd_amount_gross,
857 'damagedep_net' => $tot_dd_amount_net,
858 'damagedep_tax' => $tot_dd_amount_tax,
859 'payment_window' => $payment_window,
860 'damagedep_rids' => $room_reservation_dd,
861 ];
862 }
863
864 /**
865 * Given a list of room records, returns an associative list
866 * of room IDs and corresponding mini thumbnail URLs, if any.
867 *
868 * @param array $rooms List of room records.
869 *
870 * @return array
871 *
872 * @since 1.17.6 (J) - 1.7.6 (WP)
873 */
874 public function loadMiniThumbnails(array $rooms, string $def_uri = '')
875 {
876 $mini_thumbnails = [];
877
878 $base_img_path = implode(DIRECTORY_SEPARATOR, [VBO_SITE_PATH, 'resources', 'uploads']) . DIRECTORY_SEPARATOR;
879 $base_img_uri = VBO_SITE_URI . 'resources/uploads/';
880
881 foreach ($rooms as $room) {
882 if (empty($room['id'])) {
883 continue;
884 }
885
886 if (!empty($room['img']) && is_file($base_img_path . 'mini_' . $room['img'])) {
887 $mini_thumbnails[$room['id']] = $base_img_uri . 'mini_' . $room['img'];
888 } elseif ($def_uri) {
889 $mini_thumbnails[$room['id']] = $def_uri;
890 }
891 }
892
893 return $mini_thumbnails;
894 }
895 }
896