PluginProbe
VikBooking Hotel Booking Engine & PMS / 1.7.2
VikBooking Hotel Booking Engine & PMS v1.7.2
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.7.2, at admin/helpers/src/room/helper.php

540 lines 16.8 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 * Gets the available room upgrade options, if any.
118 *
119 * @param VikBookingTranslator $vbo_tn the translator object.
120 *
121 * @return array list of available upgrade options,
122 * or empty array if nothing availabe.
123 *
124 * @since 1.16.0 (J) - 1.6.0 (WP)
125 */
126 public function getUpgradeOptions($vbo_tn = null)
127 {
128 $booking = $this->get('booking', []);
129 $rooms = $this->get('rooms', []);
130
131 if (!$booking || !$rooms || $booking['status'] != 'confirmed') {
132 return [];
133 }
134
135 $dbo = JFactory::getDbo();
136 $config = VBOFactory::getConfig();
137
138 $upgrade_options = [];
139 $room_ids = [];
140
141 foreach ($rooms as $num => $broom) {
142 if (empty($broom['idroom']) || empty($broom['idtar'])) {
143 // room must have a valid tariff assigned
144 continue;
145 }
146 $room_upgrade_options = $config->getArray('room_upgrade_options_' . $broom['idroom'], []);
147 if (empty($room_upgrade_options) || empty($room_upgrade_options['rooms'])) {
148 // no relations for this room
149 continue;
150 }
151 // fetch the original tariff for this room
152 $orig_tariff = $this->getTariffData($broom['idtar']);
153 if (!$orig_tariff) {
154 // unable to get the original tariff information for this room booked
155 continue;
156 }
157 // push suitable rooms
158 $upgrade_options[$num] = [
159 'rooms' => array_map('intval', array_filter(array_unique($room_upgrade_options['rooms']))),
160 'discount' => (!empty($room_upgrade_options['discount']) ? (float)$room_upgrade_options['discount'] : 0),
161 'tariff' => $orig_tariff,
162 'r_costs' => [],
163 ];
164 $room_ids = array_merge($room_ids, $room_upgrade_options['rooms']);
165 }
166
167 if (!$upgrade_options) {
168 return [];
169 }
170
171 // get all room IDs involved
172 $room_ids = array_map('intval', array_filter(array_unique($room_ids)));
173
174 $q = "SELECT * FROM `#__vikbooking_rooms` WHERE `id` IN (" . implode(', ', $room_ids) . ") AND `avail`=1;";
175 $dbo->setQuery($q);
176 $room_records = $dbo->loadAssocList();
177 if (!$room_records) {
178 return [];
179 }
180 if ($vbo_tn) {
181 // translate rooms
182 $vbo_tn->translateContents($room_records, '#__vikbooking_rooms');
183 }
184
185 // build up an associative array of room infos
186 $room_infos = [];
187 foreach ($room_records as $room_record) {
188 $room_infos[$room_record['id']] = $this->prepareCMSContents($room_record, ['info', 'smalldesc']);
189 }
190 unset($room_records);
191
192 // keep the count of the room units suggested
193 $room_units_counter = [];
194
195 // filter the suitable rooms by rate plan, and calculate the costs
196 foreach ($upgrade_options as $num => $upgrade_option) {
197 // build the costs for each upgrade room option
198 $upgrade_room_costs = [];
199 // parse all rooms compatible
200 foreach ($upgrade_option['rooms'] as $rkey => $rid) {
201 // find the same tariff for this room and nights
202 $room_same_tariff = $this->findTariff($rid, $upgrade_option['tariff']['days'], $upgrade_option['tariff']['idprice']);
203 if (!$room_same_tariff || !isset($room_infos[$rid])) {
204 // this room is not suited
205 unset($upgrade_options[$num]['rooms'][$rkey]);
206 continue;
207 }
208
209 // count the actual number of room remaining units
210 $use_room_units = $room_infos[$rid]['units'];
211 if (isset($room_units_counter[$rid])) {
212 $use_room_units -= $room_units_counter[$rid];
213 }
214
215 // make sure the room is bookable on these dates (restrictions are ignored)
216 if (!VikBooking::roomBookable($rid, $use_room_units, $booking['checkin'], $booking['checkout'])) {
217 // room is not available for upgrade
218 unset($upgrade_options[$num]['rooms'][$rkey]);
219 continue;
220 }
221
222 // update room units counter
223 if (!isset($room_units_counter[$rid])) {
224 $room_units_counter[$rid] = 0;
225 }
226 $room_units_counter[$rid]++;
227
228 // apply seasonal rates
229 $tar = VikBooking::applySeasonsRoom([$room_same_tariff], $booking['checkin'], $booking['checkout']);
230
231 // apply OBP rules
232 $tar = $this->applyOBPRules($tar, $room_infos[$rid], $rooms[$num]['adults']);
233
234 // apply upgrade discount (if any) and calculate upgrade cost
235 foreach ($tar as $tk => $tv) {
236 $tar[$tk]['upgrade_cost'] = $upgrade_option['discount'] > 0 ? round(($tv['cost'] * (100 - $upgrade_option['discount']) / 100), 2) : $tv['cost'];
237 }
238
239 // push room tariff (just one rate plan, the originally booked one)
240 $upgrade_room_costs[$rid] = $tar[0];
241 }
242
243 if (!count($upgrade_options[$num]['rooms'])) {
244 // no more suitable rooms
245 unset($upgrade_options[$num]);
246 continue;
247 }
248
249 // sort by price descending (most expensive on top)
250 $sort_map = [];
251 foreach ($upgrade_room_costs as $rid => $tar) {
252 $sort_map[$rid] = $tar['upgrade_cost'];
253 }
254 arsort($sort_map);
255
256 // replace values with sorted ordering
257 $cp_upgrade_room_costs = [];
258 foreach ($sort_map as $rid => $sorted) {
259 $cp_upgrade_room_costs[$rid] = $upgrade_room_costs[$rid];
260 }
261 $upgrade_room_costs = $cp_upgrade_room_costs;
262
263 // set upgrade room costs
264 $upgrade_options[$num]['r_costs'] = $upgrade_room_costs;
265 }
266
267 if (!count($upgrade_options)) {
268 return [];
269 }
270
271 // return the associative array information
272 return [
273 'upgrade' => $upgrade_options,
274 'rooms' => $room_infos,
275 ];
276 }
277
278 /**
279 * Gets the record details about a specific tariff ID.
280 *
281 * @param int $idtar the ID of the room-tariff.
282 *
283 * @return array record found, or empty array.
284 *
285 * @since 1.16.0 (J) - 1.6.0 (WP)
286 */
287 public function getTariffData($idtar)
288 {
289 $dbo = JFactory::getDbo();
290
291 $dbo->setQuery("SELECT * FROM `#__vikbooking_dispcost` WHERE `id` = " . (int)$idtar, 0, 1);
292 $tariff = $dbo->loadAssoc();
293 if (!$tariff) {
294 return [];
295 }
296
297 return $tariff;
298 }
299
300 /**
301 * Finds a tariff for the given rate plan ID, room and nights.
302 *
303 * @param int $rid the room ID.
304 * @param int $nights the number of nights of stay.
305 * @param int $idprice the rate plan ID.
306 *
307 * @return array record found or empty array.
308 *
309 * @since 1.16.0 (J) - 1.6.0 (WP)
310 */
311 public function findTariff($rid, $nights, $idprice)
312 {
313 $dbo = JFactory::getDbo();
314
315 $q = "SELECT `t`.*, `p`.`name` AS `rate_plan_name` FROM `#__vikbooking_dispcost` AS `t`
316 LEFT JOIN `#__vikbooking_prices` AS `p` ON `t`.`idprice`=`p`.`id`
317 WHERE `t`.`idroom` = " . (int)$rid . " AND `t`.`days`=" . (int)$nights . " AND `t`.`idprice`=" . (int)$idprice;
318 $dbo->setQuery($q, 0, 1);
319 $tariff = $dbo->loadAssoc();
320 if (!$tariff) {
321 return [];
322 }
323
324 return $tariff;
325 }
326
327 /**
328 * Applies the OBP rules over an array of tariffs.
329 *
330 * @param array $tar list of tariff records, one per rate plan, after seasonal rates.
331 * @param array $room the room (or order-room) record for which tariffs where loaded.
332 * @param int $adults the number of adults to consider.
333 *
334 * @return array original tariffs array with OBP costs applied.
335 *
336 * @since 1.16.0 (J) - 1.6.0 (WP)
337 */
338 public function applyOBPRules(array $tar, array $room, $adults = 2)
339 {
340 // check for different usage
341 if (!isset($room['fromadult']) || $room['fromadult'] > $adults || $room['toadult'] < $adults) {
342 return $tar;
343 }
344
345 // check for room ID
346 $use_room_id = isset($room['idroom']) ? $room['idroom'] : $room['id'];
347
348 // different usage
349 $diffusageprice = VikBooking::loadAdultsDiff($use_room_id, $adults);
350
351 /**
352 * Memorize immediately the OBP rules defined at room-level in order to avoid conflicts
353 * with rate plans with and without OBP overrides defined at rate plan level through SP.
354 */
355 $orig_diffusage = $diffusageprice;
356
357 // occupancy overrides
358 $occ_ovr = VikBooking::occupancyOverrideExists($tar, $adults);
359 $diffusageprice = $occ_ovr !== false ? $occ_ovr : $diffusageprice;
360
361 if (!$diffusageprice) {
362 return $tar;
363 }
364
365 // set a charge or discount to the price(s) for the different usage of the room
366 foreach ($tar as $kpr => $vpr) {
367 // occupancy override
368 $diffusageprice = isset($vpr['occupancy_ovr']) && isset($vpr['occupancy_ovr'][$adults]) ? $vpr['occupancy_ovr'][$adults] : $orig_diffusage;
369
370 // set usage of the room
371 $tar[$kpr]['diffusage'] = $adults;
372
373 if ($diffusageprice['chdisc'] == 1) {
374 // charge
375 if ($diffusageprice['valpcent'] == 1) {
376 // fixed value
377 $tar[$kpr]['diffusagecostpernight'] = $diffusageprice['pernight'] == 1 ? 1 : 0;
378 $aduseval = $diffusageprice['pernight'] == 1 ? $diffusageprice['value'] * $tar[$kpr]['days'] : $diffusageprice['value'];
379 $tar[$kpr]['diffusagecost'] = "+" . $aduseval;
380 $tar[$kpr]['room_base_cost'] = $vpr['cost'];
381 $tar[$kpr]['cost'] = $vpr['cost'] + $aduseval;
382 } else {
383 // percentage value
384 $tar[$kpr]['diffusagecostpernight'] = $diffusageprice['pernight'] == 1 ? $vpr['cost'] : 0;
385 $aduseval = $diffusageprice['pernight'] == 1 ? round(($vpr['cost'] * $diffusageprice['value'] / 100) * $tar[$kpr]['days'] + $vpr['cost'], 2) : round(($vpr['cost'] * (100 + $diffusageprice['value']) / 100), 2);
386 $tar[$kpr]['diffusagecost'] = "+" . $diffusageprice['value'] . "%";
387 $tar[$kpr]['room_base_cost'] = $vpr['cost'];
388 $tar[$kpr]['cost'] = $aduseval;
389 }
390 } else {
391 // discount
392 if ($diffusageprice['valpcent'] == 1) {
393 // fixed value
394 $tar[$kpr]['diffusagecostpernight'] = $diffusageprice['pernight'] == 1 ? 1 : 0;
395 $aduseval = $diffusageprice['pernight'] == 1 ? $diffusageprice['value'] * $tar[$kpr]['days'] : $diffusageprice['value'];
396 $tar[$kpr]['diffusagecost'] = "-" . $aduseval;
397 $tar[$kpr]['room_base_cost'] = $vpr['cost'];
398 $tar[$kpr]['cost'] = $vpr['cost'] - $aduseval;
399 } else {
400 // percentage value
401 $tar[$kpr]['diffusagecostpernight'] = $diffusageprice['pernight'] == 1 ? $vpr['cost'] : 0;
402 $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);
403 $tar[$kpr]['diffusagecost'] = "-" . $diffusageprice['value'] . "%";
404 $tar[$kpr]['room_base_cost'] = $vpr['cost'];
405 $tar[$kpr]['cost'] = $aduseval;
406 }
407 }
408 }
409
410 // return the array of tariffs with OBP included
411 return $tar;
412 }
413
414 /**
415 * Prepares some description strings for the current CMS, by triggering
416 * the necessary platform-related functions for third party plugins.
417 *
418 * @param array $room_record the room record to prepare.
419 * @param array $keys list of record keys to prepare.
420 *
421 * @return array the original array given with keys prepared.
422 *
423 * @since 1.16.0 (J) - 1.6.0 (WP)
424 */
425 public function prepareCMSContents(array $room_record, array $keys)
426 {
427 foreach ($keys as $key) {
428 if (!isset($room_record[$key])) {
429 continue;
430 }
431
432 if (VBOPlatformDetection::isWordPress()) {
433 /**
434 * @wponly we try to parse any shortcode inside the HTML description of the room
435 */
436 $room_record[$key] = do_shortcode(wpautop($room_record[$key]));
437 } else {
438 // BEGIN: Joomla Content Plugins Rendering
439 JPluginHelper::importPlugin('content');
440
441 $myItem = JTable::getInstance('content');
442
443 $myItem->text = $room_record[$key];
444 $objparams = array();
445 if (class_exists('JEventDispatcher')) {
446 $dispatcher = JEventDispatcher::getInstance();
447 $dispatcher->trigger('onContentPrepare', array('com_vikbooking.roomdetails', &$myItem, &$objparams, 0));
448 } else {
449 /**
450 * @joomla4only
451 */
452 $dispatcher = JFactory::getApplication();
453 if (method_exists($dispatcher, 'triggerEvent')) {
454 $dispatcher->triggerEvent('onContentPrepare', array('com_vikbooking.roomdetails', &$myItem, &$objparams, 0));
455 }
456 }
457 $room_record[$key] = $myItem->text;
458 // END: Joomla Content Plugins Rendering
459 }
460 }
461
462 return $room_record;
463 }
464
465 /**
466 * Gets an associative list of rate plans with a few pricing information for the given room.
467 *
468 * @param int $rid the VBO room id.
469 * @param int $rplan_id optional rate plan ID to get.
470 *
471 * @return array associative list of rate plans for the given room or specific rate plan.
472 *
473 * @since 1.16.3 (J) - 1.6.3 (WP)
474 */
475 public function getRatePlans($rid = 0, $rplan_id = 0)
476 {
477 if (empty($rid)) {
478 $rid = $this->get('id', 0);
479 }
480
481 $dbo = JFactory::getDbo();
482
483 $q = $dbo->getQuery(true)
484 ->select([
485 $dbo->qn('r.id'),
486 $dbo->qn('r.idroom'),
487 $dbo->qn('r.days'),
488 $dbo->qn('r.idprice'),
489 $dbo->qn('r.cost'),
490 $dbo->qn('p.name'),
491 $dbo->qn('p.minlos'),
492 ])
493 ->from($dbo->qn('#__vikbooking_dispcost', 'r'))
494 ->leftJoin($dbo->qn('#__vikbooking_prices', 'p') . ' ON ' . $dbo->qn('r.idprice') . ' = ' . $dbo->qn('p.id'))
495 ->where($dbo->qn('r.idroom') . ' = ' . (int)$rid)
496 ->order($dbo->qn('r.days') . ' ASC')
497 ->order($dbo->qn('r.cost') . ' ASC');
498
499 $dbo->setQuery($q, 0, 50);
500
501 $tariffs = $dbo->loadObjectList();
502
503 if (!$tariffs) {
504 return [];
505 }
506
507 $parsed_room_prices = [];
508 foreach ($tariffs as $rrk => $rrv) {
509 if (isset($parsed_room_prices[$rrv->idprice])) {
510 unset($tariffs[$rrk]);
511 continue;
512 }
513 $tariffs[$rrk]->cost = round(($rrv->cost / $rrv->days), 2);
514 $tariffs[$rrk]->days = 1;
515 $parsed_room_prices[$rrv->idprice] = 1;
516 }
517
518 $tariffs = array_values($tariffs);
519
520 $room_rate_plans = [];
521 foreach ($tariffs as $rplan) {
522 if ($rplan_id && $rplan_id == $rplan->idprice) {
523 return (array)$rplan;
524 }
525
526 $room_rate_plans[] = [
527 'id' => $rplan->idprice,
528 'name' => $rplan->name,
529 'cost' => $rplan->cost,
530 ];
531 }
532
533 if ($rplan_id) {
534 return [];
535 }
536
537 return $room_rate_plans;
538 }
539 }
540