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
← All changes | admin/helpers/src/mealplan/manager.php +488 -0 1.6.2 → 1.8.16 View file →
@@ -1,0 +1,488 @@
1 +<?php
2 +/**
3 + * @package VikBooking
4 + * @subpackage core
5 + * @author Alessio Gaggii - E4J s.r.l.
6 + * @copyright Copyright (C) 2023 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 + * Meal plan manager.
16 + *
17 + * @since 1.16.1 (J) - 1.6.1 (WP)
18 + */
19 +final class VBOMealplanManager extends JObject
20 +{
21 + /**
22 + * The singleton instance of the class.
23 + *
24 + * @var VBOMealplanManager
25 + */
26 + private static $instance = null;
27 +
28 + /**
29 + * @var array
30 + */
31 + private $meal_plans = [];
32 +
33 + /**
34 + * Proxy to construct the object.
35 + *
36 + * @param array|object $data optional data to bind.
37 + * @param boolean $anew true for forcing a new instance.
38 + *
39 + * @return self
40 + */
41 + public static function getInstance($data = [], $anew = false)
42 + {
43 + if (is_null(static::$instance) || $anew) {
44 + static::$instance = new static($data);
45 + }
46 +
47 + return static::$instance;
48 + }
49 +
50 + /**
51 + * Class constructor.
52 + *
53 + * @param array|object $data optional data to bind.
54 + */
55 + public function __construct($data)
56 + {
57 + // make sure to load the back-end language definitions
58 + if (VikBooking::isSite()) {
59 + $lang = JFactory::getLanguage();
60 + $lang_tag = $lang->getTag();
61 + if (VBOPlatformDetection::isWordPress()) {
62 + $lang->load('com_vikbooking', VIKBOOKING_LANG, $lang_tag, true);
63 + $lang->attachHandler(VIKBOOKING_LIBRARIES . DIRECTORY_SEPARATOR . 'language' . DIRECTORY_SEPARATOR . 'admin.php', 'vikbooking');
64 + } else {
65 + $lang->load('com_vikbooking', JPATH_ADMINISTRATOR, $lang_tag, true);
66 + $lang->load('joomla', JPATH_ADMINISTRATOR, $lang_tag, true);
67 + }
68 + }
69 +
70 + // set up the various meal plans
71 + $this->setup();
72 +
73 + parent::__construct($data);
74 + }
75 +
76 + /**
77 + * Tells whether the specified rate plan includes the given meal plan enum code.
78 + *
79 + * @param array|int $rplan either the rate plan record or rate plan ID.
80 + * @param string $meal_enum the meal plan enum identifier.
81 + *
82 + * @return bool
83 + */
84 + public function ratePlanMealIncluded($rplan, $meal_enum)
85 + {
86 + if (is_scalar($rplan)) {
87 + $rplan = VikBooking::getPriceInfo($rplan);
88 + }
89 +
90 + if (!is_array($rplan) || !$rplan) {
91 + return false;
92 + }
93 +
94 + // for BC we check the dedicated flag to breakfast included
95 + if (!strcasecmp($meal_enum, 'breakfast') && !empty($rplan['breakfast_included'])) {
96 + return true;
97 + }
98 +
99 + if (empty($rplan['meal_plans'])) {
100 + return false;
101 + }
102 +
103 + if (is_string($rplan['meal_plans'])) {
104 + return (stripos($rplan['meal_plans'], $meal_enum) !== false);
105 + }
106 +
107 + if (is_array($rplan['meal_plans'])) {
108 + return in_array($meal_enum, $rplan['meal_plans']);
109 + }
110 +
111 + return false;
112 + }
113 +
114 + /**
115 + * Returns an associative list of meal plans included in the room rate plan.
116 + *
117 + * @param array|int $rplan either the rate plan record or rate plan ID.
118 + *
119 + * @return array
120 + */
121 + public function ratePlanIncludedMeals($rplan)
122 + {
123 + if (is_scalar($rplan)) {
124 + $rplan = VikBooking::getPriceInfo($rplan);
125 + }
126 +
127 + if (!is_array($rplan) || !$rplan) {
128 + return [];
129 + }
130 +
131 + $included_meals = [];
132 +
133 + // for BC we check the dedicated flag to breakfast included
134 + if (!empty($rplan['breakfast_included'])) {
135 + $included_meals['breakfast'] = $this->meal_plans['breakfast'];
136 + }
137 +
138 + if (empty($rplan['meal_plans'])) {
139 + return $included_meals;
140 + }
141 +
142 + if (is_string($rplan['meal_plans'])) {
143 + // we support JSON encoded strings as well as comma-separated strings
144 + $included = json_decode($rplan['meal_plans'], true);
145 + if (is_array($included)) {
146 + $rplan['meal_plans'] = $included;
147 + } else {
148 + $rplan['meal_plans'] = explode(',', $rplan['meal_plans']);
149 + $rplan['meal_plans'] = array_map(function($mn) {
150 + return trim($mn);
151 + }, $rplan['meal_plans']);
152 + }
153 + }
154 +
155 + if (!is_array($rplan['meal_plans']) || !$rplan['meal_plans']) {
156 + return $included_meals;
157 + }
158 +
159 + foreach ($rplan['meal_plans'] as $meal_enum) {
160 + if (isset($this->meal_plans[$meal_enum])) {
161 + $included_meals[$meal_enum] = $this->meal_plans[$meal_enum];
162 + }
163 + }
164 +
165 + return $included_meals;
166 + }
167 +
168 + /**
169 + * Returns an associative list of meal plans included in OTA reservation record.
170 + * For those OTA bookings downloaded by older versions of VCM, this method attempts
171 + * to find the meals included in the reservation by parsing the "Meal_plan" string.
172 + * Alternatively, it relies on VCM to check the Content/Product/Listing API data.
173 + *
174 + * @param array $res the OTA reservation record.
175 + * @param array $room_res the optional room reservation record.
176 + *
177 + * @return array list of included meal enum values calculated on the fly.
178 + */
179 + public function otaDataIncludedMeals(array $res, array $room_res = [])
180 + {
181 + $included_meals = [];
182 +
183 + // make sure this is actually an OTA reservation downloaded by VCM
184 + if (empty($res['idorderota']) || empty($res['channel']) || empty($res['custdata'])) {
185 + return $included_meals;
186 + }
187 +
188 + // this usually works for Booking.com reservations
189 + foreach ($this->meal_plans as $meal_enum => $meal_name) {
190 + $meal_regexp = preg_quote($meal_name);
191 + if (preg_match("/meal_plan:([a-z0-9 ,\.])*{$meal_regexp}/i", $res['custdata'], $matches)) {
192 + if ($matches && $matches[0]) {
193 + /**
194 + * Make sure the statement is not "Enjoy a convenient {meal} at the property for NN per person, per night."
195 + * or "Breakfast costs US$12 per person per night." (same for "Lunch costs" and "Dinner costs").
196 + *
197 + * @since 1.16.2 (J) - 1.6.2 (WP)
198 + */
199 + if (preg_match("/enjoy|convenient/i", $matches[0])) {
200 + // the meal plan is not included, it is just available at the property
201 + continue;
202 + } elseif (stripos($res['custdata'], "{$matches[0]} cost") !== false) {
203 + // the meal plan is not included, it has a cost
204 + continue;
205 + }
206 + }
207 + $included_meals[$meal_enum] = $meal_name;
208 + }
209 + }
210 +
211 + // let VCM detect any included meal plan for this OTA reservation
212 + if (!$included_meals && class_exists('VCMOtaRateplan')) {
213 + // this method will rely on the information stored through Content/Product/Listing APIs of the involved OTA
214 + $included_meals = VCMOtaRateplan::getInstance(['meal_plans' => $this->meal_plans])->findIncludedMeals($res, $room_res);
215 + }
216 +
217 + return $included_meals;
218 + }
219 +
220 + /**
221 + * Tells if a meal plan is included in the OTA reservation or room data.
222 + *
223 + * @param array $res the OTA reservation record.
224 + * @param array $room_res the optional room reservation record.
225 + * @param string $meal_enum the meal plan enum identifier.
226 + *
227 + * @return bool
228 + */
229 + public function otaDataMealIncluded(array $res, array $room_res, $meal_enum)
230 + {
231 + $included_meals = $this->otaDataIncludedMeals($res, $room_res);
232 +
233 + return isset($included_meals[$meal_enum]);
234 + }
235 +
236 + /**
237 + * Tells whether the given room reservation record includes the given meal plan enum code.
238 + *
239 + * @param array $room_res the room reservation record.
240 + * @param string $meal_enum the meal plan enum identifier.
241 + *
242 + * @return bool
243 + */
244 + public function roomRateMealIncluded($room_res, $meal_enum)
245 + {
246 + if (!is_array($room_res) || empty($room_res['meals'])) {
247 + return false;
248 + }
249 +
250 + if (is_string($room_res['meals'])) {
251 + return (stripos($room_res['meals'], $meal_enum) !== false);
252 + }
253 +
254 + if (is_array($room_res['meals'])) {
255 + return in_array($meal_enum, $room_res['meals']);
256 + }
257 +
258 + return false;
259 + }
260 +
261 + /**
262 + * Returns an associative list of meal plans included in the room reservation record.
263 + *
264 + * @param array $room_res the room reservation record.
265 + *
266 + * @return array
267 + */
268 + public function roomRateIncludedMeals($room_res)
269 + {
270 + if (!is_array($room_res) || empty($room_res['meals'])) {
271 + return [];
272 + }
273 +
274 + $meals_included = $room_res['meals'];
275 +
276 + if (is_string($meals_included)) {
277 + // we support JSON encoded strings as well as comma-separated strings
278 + $included = json_decode($meals_included, true);
279 + if (is_array($included)) {
280 + $meals_included = $included;
281 + } else {
282 + $meals_included = explode(',', $meals_included);
283 + $meals_included = array_map(function($mn) {
284 + return trim($mn);
285 + }, $meals_included);
286 + }
287 + }
288 +
289 + if (!is_array($meals_included) || !$meals_included) {
290 + return [];
291 + }
292 +
293 + $valid_enums = [];
294 + foreach ($this->meal_plans as $meal_enum => $meal_name) {
295 + if (isset($meals_included[$meal_enum]) || in_array($meal_enum, $meals_included)) {
296 + $valid_enums[$meal_enum] = $meal_name;
297 + }
298 + }
299 +
300 + return $valid_enums;
301 + }
302 +
303 + /**
304 + * Given one or more meal plan enum code, tries to convert them to translated strings.
305 + *
306 + * @param array|string $meal_enum the meal plan enum identifier(s).
307 + *
308 + * @return string
309 + */
310 + public function sayMealPlanEnum($meal_enum)
311 + {
312 + if (!$meal_enum) {
313 + return '';
314 + }
315 +
316 + $plans_included = [];
317 + if (!is_array($meal_enum)) {
318 + $meal_enum = [$meal_enum];
319 + }
320 +
321 + foreach ($meal_enum as $enum) {
322 + if (is_string($enum) && isset($this->meal_plans[$enum])) {
323 + $plans_included[] = $this->meal_plans[$enum];
324 + }
325 + }
326 +
327 + if (!$plans_included) {
328 + return '';
329 + }
330 +
331 + return implode(', ', array_unique($plans_included));
332 + }
333 +
334 + /**
335 + * This method is mainly used by VCM when storing new reservations onto VBO. Given
336 + * a raw list of meals included in the OTA room tariff, builds the string to be stored.
337 + * The information about the OTA meals is usually fetched from the rooms mapping information
338 + * when receiving the whole reservation payload including the OTA room and rate plan IDs.
339 + * In case the OTA rate plan was mapped with a "meal_plans" property, that value will be
340 + * passed along to this method for parsing the OTA enum values transmitted by the E4jConnect servers.
341 + *
342 + * @param string|array $ota_meals the raw meals included fetched from the OTA rate plan.
343 + *
344 + * @return string the meals value to be stored onto the room reservation record.
345 + */
346 + public function buildOTAMealPlans($ota_meals)
347 + {
348 + $store_meals = '';
349 +
350 + if (empty($ota_meals)) {
351 + return $store_meals;
352 + }
353 +
354 + // convert the OTA meals into an array of enums or raw names
355 + if (is_string($ota_meals)) {
356 + // we usually have comma-separated strings
357 + $included_meals = explode(',', $ota_meals);
358 + if ($included_meals && !empty($included_meals[0])) {
359 + // we've got something to parse
360 + $ota_meals = array_map(function($mn) {
361 + return trim($mn);
362 + }, $included_meals);
363 + } else {
364 + // attempt to decode a JSON string
365 + $ota_meals = json_decode($ota_meals, true);
366 + }
367 + }
368 +
369 + if (!is_array($ota_meals) || !$ota_meals) {
370 + return $store_meals;
371 + }
372 +
373 + $valid_enums = [];
374 + $known_enums = array_keys($this->meal_plans);
375 +
376 + foreach ($ota_meals as $ota_meal) {
377 + foreach ($known_enums as $known_enum) {
378 + if ($ota_meal && !strcasecmp($ota_meal, $known_enum)) {
379 + // valid meal found
380 + $valid_enums[] = $known_enum;
381 + // parse the next one, if any
382 + break;
383 + }
384 + }
385 + }
386 +
387 + if ($valid_enums) {
388 + // some valid OTA meals were found
389 + $store_meals = json_encode(array_unique($valid_enums));
390 + }
391 +
392 + return $store_meals;
393 + }
394 +
395 + /**
396 + * Returns the supported meal plans.
397 + *
398 + * @return array
399 + */
400 + public function getPlans()
401 + {
402 + return $this->meal_plans;
403 + }
404 +
405 + /**
406 + * Returns an associative list of meal plans and short name identifier (B, L, D).
407 + *
408 + * @return array
409 + */
410 + public function getShortMealPlans()
411 + {
412 + return [
413 + 'breakfast' => JText::translate('VBO_SHORTM_BREAKFAST'),
414 + 'lunch' => JText::translate('VBO_SHORTM_LUNCH'),
415 + 'dinner' => JText::translate('VBO_SHORTM_DINNER'),
416 + ];
417 + }
418 +
419 + /**
420 + * Finds the ID and name of the rate plan from the given tariff ID.
421 + *
422 + * @param int $idtar the ID of the tariff.
423 + *
424 + * @return array list of rate plan name (or an empty string) and ID.
425 + */
426 + public function getPriceData($idtar)
427 + {
428 + if (empty($idtar)) {
429 + return [JText::translate('VBOROOMCUSTRATEPLAN'), 0];
430 + }
431 +
432 + $dbo = JFactory::getDbo();
433 +
434 + $q = "SELECT `p`.`id`, `p`.`name` FROM `#__vikbooking_prices` AS `p`
435 + LEFT JOIN `#__vikbooking_dispcost` AS `t` ON `p`.`id`=`t`.`idprice` WHERE `t`.`id`=" . (int)$idtar;
436 + $dbo->setQuery($q, 0, 1);
437 + $price_record = $dbo->loadAssoc();
438 +
439 + if ($price_record) {
440 + return [$price_record['name'], $price_record['id']];
441 + }
442 +
443 + return ['', 0];
444 + }
445 +
446 + /**
447 + * Prepares the various meal plans supported.
448 + *
449 + * @return void
450 + */
451 + private function setup()
452 + {
453 + $config = VBOFactory::getConfig();
454 +
455 + $def_meals = $this->getDefaultMealPlans();
456 +
457 + $meals = $config->getArray('meal_plans', []);
458 + if (!$meals) {
459 + $meals = $def_meals;
460 + $config->set('meal_plans', $meals);
461 + }
462 +
463 + // map translations at runtime
464 + foreach ($meals as $meal_enum => &$meal_name) {
465 + $meal_name = $def_meals[$meal_enum];
466 + }
467 +
468 + // unset last reference
469 + unset($meal_name);
470 +
471 + // set updated map
472 + $this->meal_plans = $meals;
473 + }
474 +
475 + /**
476 + * Returns an associative list of default meal plans.
477 + *
478 + * @return array
479 + */
480 + private function getDefaultMealPlans()
481 + {
482 + return [
483 + 'breakfast' => JText::translate('VBO_MEAL_BREAKFAST'),
484 + 'lunch' => JText::translate('VBO_MEAL_LUNCH'),
485 + 'dinner' => JText::translate('VBO_MEAL_DINNER'),
486 + ];
487 + }
488 +}