PluginProbe
VikBooking Hotel Booking Engine & PMS / 1.6.5
VikBooking Hotel Booking Engine & PMS v1.6.5
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 / lib.vikbooking.php

lib.vikbooking.php in VikBooking Hotel Booking Engine & PMS 1.6.5, at site/helpers/lib.vikbooking.php

13,172 lines 448.1 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 if (!class_exists('VikBookingIcons')) {
14 // require the Icons class
15 require_once(VBO_SITE_PATH . DIRECTORY_SEPARATOR . "helpers" . DIRECTORY_SEPARATOR . "icons.php");
16 }
17
18 if (!function_exists('showSelectVb')) {
19 function showSelectVb($err, $err_code_info = array()) {
20 include(VBO_SITE_PATH.DIRECTORY_SEPARATOR.'helpers'.DIRECTORY_SEPARATOR.'error_form.php');
21 }
22 }
23
24 class VikBooking
25 {
26 public static function addJoomlaUser($name, $username, $email, $password) {
27 //new method
28 jimport('joomla.application.component.helper');
29 $params = JComponentHelper::getParams('com_users');
30 $user = new JUser;
31 $data = array();
32 //Get the default new user group, Registered if not specified.
33 $system = $params->get('new_usertype', 2);
34 $data['groups'] = array();
35 $data['groups'][] = $system;
36 $data['name'] = $name;
37 $data['username'] = $username;
38 $data['email'] = self::getVboApplication()->emailToPunycode($email);
39 $data['password'] = $password;
40 $data['password2'] = $password;
41 $data['sendEmail'] = 0; //should the user receive system mails?
42 //$data['block'] = 0;
43 if (!$user->bind($data)) {
44 VikError::raiseWarning('', JText::translate($user->getError()));
45 return false;
46 }
47 if (!$user->save()) {
48 VikError::raiseWarning('', JText::translate($user->getError()));
49 return false;
50 }
51 return $user->id;
52 }
53
54 public static function userIsLogged() {
55 $user = JFactory::getUser();
56
57 return !$user->guest;
58 }
59
60 public static function prepareViewContent()
61 {
62 /**
63 * @wponly JApplication::getMenu() cannot be adapted to WP
64 */
65 }
66
67 public static function isFontAwesomeEnabled()
68 {
69 return VBOFactory::getConfig()->getBool('usefa', true);
70 }
71
72 public static function loadFontAwesome($force_load = false)
73 {
74 if (!self::isFontAwesomeEnabled() && !$force_load) {
75 return false;
76 }
77
78 /**
79 * We let the class VikBookingIcons load the proper FontAwesome libraries.
80 *
81 * @since 1.11
82 */
83 VikBookingIcons::loadAssets();
84
85 return true;
86 }
87
88 /**
89 * Checks if modifications or cancellations via front-end are allowed.
90 * 0 = everything is Disabled.
91 * 1 = Disabled, with request message (default).
92 * 2 = Modification Enabled, Cancellation Disabled.
93 * 3 = Cancellation Enabled, Modification Disabled.
94 * 4 = everything is Enabled.
95 *
96 * @return int
97 */
98 public static function getReservationModCanc()
99 {
100 return VBOFactory::getConfig()->getInt('resmodcanc', 1);
101 }
102
103 public static function getReservationModCancMin()
104 {
105 return VBOFactory::getConfig()->getInt('resmodcancmin', 1);
106 }
107
108 public static function getDefaultDistinctiveFeatures()
109 {
110 return [
111 'VBODEFAULTDISTFEATUREONE' => '',
112 // Below is the default feature for 'Room Code'. One default feature is sufficient
113 // 'VBODEFAULTDISTFEATURETWO' => '',
114 ];
115 }
116
117 /**
118 * Given the room's parameters and index, tries to take the first distinctive feature.
119 *
120 * @param mixed $rparams string to be decoded, or decoded array/object params
121 * @param int $rindex room index to look for, starting from 1.
122 *
123 * @return mixed false on failure, array otherwise [feature name, feature value]
124 *
125 * @since 1.14 (J) - 1.4.0 (WP)
126 */
127 public static function getRoomUnitDistinctiveFeature($rparams, $rindex) {
128 $rindex = (int)$rindex;
129 if ($rindex < 1 || empty($rparams)) {
130 return false;
131 }
132
133 if (is_string($rparams)) {
134 // decode params
135 $rparams = self::getRoomParam('features', $rparams);
136 }
137
138 if (is_object($rparams)) {
139 // typecast to array
140 $rparams = (array)$rparams;
141 }
142
143 if (!is_array($rparams) || !count($rparams)) {
144 return false;
145 }
146
147 if (isset($rparams['features'])) {
148 $rparams = $rparams['features'];
149 }
150
151 $feature = array();
152 foreach ($rparams as $param_index => $rfeatures) {
153 if ((int)$param_index != $rindex || !is_array($rfeatures) || !count($rfeatures)) {
154 continue;
155 }
156 foreach ($rfeatures as $featname => $featval) {
157 if (empty($featval)) {
158 continue;
159 }
160 // use the first distinctive feature
161 $tn_featname = JText::translate($featname);
162 if ($tn_featname == $featname) {
163 // no translation was applied
164 if (VBOPlatformDetection::isWordPress()) {
165 // try to apply a translation through Gettext even if we have to pass a variable
166 $tn_featname = __($featname);
167 } else {
168 // convert the string to a hypothetical INI constant
169 $ini_constant = str_replace(' ', '_', strtoupper($featname));
170 $tn_featname = JText::translate($ini_constant);
171 $tn_featname = $tn_featname == $ini_constant ? $featname : $tn_featname;
172 }
173 }
174 // store values and break loop
175 $feature = array($tn_featname, $featval);
176 break;
177 }
178 }
179
180 return count($feature) ? $feature : false;
181 }
182
183 public static function getRoomUnitNumsUnavailable($order, $idroom) {
184 $dbo = JFactory::getDbo();
185 $unavailable_indexes = array();
186 $first = $order['checkin'];
187 $second = $order['checkout'];
188 $secdiff = $second - $first;
189 $daysdiff = $secdiff / 86400;
190 if (is_int($daysdiff)) {
191 if ($daysdiff < 1) {
192 $daysdiff = 1;
193 }
194 } else {
195 if ($daysdiff < 1) {
196 $daysdiff = 1;
197 } else {
198 $sum = floor($daysdiff) * 86400;
199 $newdiff = $secdiff - $sum;
200 $maxhmore = self::getHoursMoreRb() * 3600;
201 if ($maxhmore >= $newdiff) {
202 $daysdiff = floor($daysdiff);
203 } else {
204 $daysdiff = ceil($daysdiff);
205 }
206 }
207 }
208 $groupdays = self::getGroupDays($first, $second, $daysdiff);
209 $q = "SELECT `b`.`id`,`b`.`checkin`,`b`.`checkout`,`b`.`realback`,`ob`.`idorder`,`ob`.`idbusy`,`or`.`id` AS `or_id`,`or`.`idroom`,`or`.`roomindex`,`o`.`status` ".
210 "FROM `#__vikbooking_busy` AS `b` ".
211 "LEFT JOIN `#__vikbooking_ordersbusy` `ob` ON `ob`.`idbusy`=`b`.`id` ".
212 "LEFT JOIN `#__vikbooking_ordersrooms` `or` ON `or`.`idorder`=`ob`.`idorder` AND `or`.`idorder`!=".(int)$order['id']." ".
213 "LEFT JOIN `#__vikbooking_orders` `o` ON `o`.`id`=`or`.`idorder` AND `o`.`id`=`ob`.`idorder` AND `o`.`id`!=".(int)$order['id']." ".
214 "WHERE `or`.`idroom`=".(int)$idroom." AND `b`.`checkout` > ".time()." AND `o`.`status`='confirmed' AND `ob`.`idorder`!=".(int)$order['id']." AND `ob`.`idorder` > 0;";
215 $dbo->setQuery($q);
216 $busy = $dbo->loadAssocList();
217 if ($busy) {
218 foreach ($groupdays as $gday) {
219 foreach ($busy as $bu) {
220 if (empty($bu['roomindex']) || empty($bu['idorder'])) {
221 continue;
222 }
223 if ($gday >= $bu['checkin'] && $gday <= $bu['realback']) {
224 $unavailable_indexes[$bu['or_id']] = $bu['roomindex'];
225 } elseif (count($groupdays) == 2 && $gday == $groupdays[0]) {
226 if ($groupdays[0] < $bu['checkin'] && $groupdays[0] < $bu['realback'] && $groupdays[1] > $bu['checkin'] && $groupdays[1] > $bu['realback']) {
227 $unavailable_indexes[$bu['or_id']] = $bu['roomindex'];
228 }
229 }
230 }
231 }
232 }
233
234 return $unavailable_indexes;
235 }
236
237 public static function getRoomUnitNumsAvailable($order, $idroom) {
238 $dbo = JFactory::getDbo();
239 $unavailable_indexes = array();
240 $available_indexes = array();
241 $first = $order['checkin'];
242 $second = $order['checkout'];
243 $secdiff = $second - $first;
244 $daysdiff = $secdiff / 86400;
245 if (is_int($daysdiff)) {
246 if ($daysdiff < 1) {
247 $daysdiff = 1;
248 }
249 } else {
250 if ($daysdiff < 1) {
251 $daysdiff = 1;
252 } else {
253 $sum = floor($daysdiff) * 86400;
254 $newdiff = $secdiff - $sum;
255 $maxhmore = self::getHoursMoreRb() * 3600;
256 if ($maxhmore >= $newdiff) {
257 $daysdiff = floor($daysdiff);
258 } else {
259 $daysdiff = ceil($daysdiff);
260 }
261 }
262 }
263 $groupdays = self::getGroupDays($first, $second, $daysdiff);
264 $q = "SELECT `b`.`id`,`b`.`checkin`,`b`.`checkout`,`b`.`realback`,`ob`.`idorder`,`ob`.`idbusy`,`or`.`id` AS `or_id`,`or`.`idroom`,`or`.`roomindex`,`o`.`status` ".
265 "FROM `#__vikbooking_busy` AS `b` ".
266 "LEFT JOIN `#__vikbooking_ordersbusy` `ob` ON `ob`.`idbusy`=`b`.`id` ".
267 "LEFT JOIN `#__vikbooking_ordersrooms` `or` ON `or`.`idorder`=`ob`.`idorder` AND `or`.`idorder`!=".(int)$order['id']." ".
268 "LEFT JOIN `#__vikbooking_orders` `o` ON `o`.`id`=`or`.`idorder` AND `o`.`id`=`ob`.`idorder` AND `o`.`id`!=".(int)$order['id']." ".
269 "WHERE `or`.`idroom`=".(int)$idroom." AND `b`.`checkout` > ".time()." AND `o`.`status`='confirmed' AND `ob`.`idorder`!=".(int)$order['id']." AND `ob`.`idorder` > 0;";
270 $dbo->setQuery($q);
271 $busy = $dbo->loadAssocList();
272 if ($busy) {
273 foreach ($groupdays as $gday) {
274 foreach ($busy as $bu) {
275 if (empty($bu['roomindex']) || empty($bu['idorder'])) {
276 continue;
277 }
278 if ($gday >= $bu['checkin'] && $gday <= $bu['realback']) {
279 $unavailable_indexes[$bu['or_id']] = $bu['roomindex'];
280 } elseif (count($groupdays) == 2 && $gday == $groupdays[0]) {
281 if ($groupdays[0] < $bu['checkin'] && $groupdays[0] < $bu['realback'] && $groupdays[1] > $bu['checkin'] && $groupdays[1] > $bu['realback']) {
282 $unavailable_indexes[$bu['or_id']] = $bu['roomindex'];
283 }
284 }
285 }
286 }
287 }
288 $q = "SELECT `params` FROM `#__vikbooking_rooms` WHERE `id`=".(int)$idroom.";";
289 $dbo->setQuery($q);
290 $room_params = $dbo->loadResult();
291 if ($room_params) {
292 $room_params_arr = json_decode($room_params, true);
293 if (array_key_exists('features', $room_params_arr) && is_array($room_params_arr['features']) && count($room_params_arr['features'])) {
294 foreach ($room_params_arr['features'] as $rind => $rfeatures) {
295 if (in_array($rind, $unavailable_indexes)) {
296 continue;
297 }
298 $available_indexes[] = $rind;
299 }
300 }
301 }
302
303 return $available_indexes;
304 }
305
306 /**
307 * Load the restrictions applying the given filters to the passed rooms.
308 * The ordering of the query SHOULD remain unchanged, because it's required
309 * to have it by ascending order of the ID. So the older records first.
310 * Even when no filters, we always exclude expired restrictions.
311 *
312 * @param boolean $filters whether to apply filters
313 * @param array $rooms the list of rooms to filter
314 *
315 * @return array the list of restrictions found or an empty array.
316 */
317 public static function loadRestrictions($filters = true, $rooms = [])
318 {
319 $dbo = JFactory::getDbo();
320
321 $restrictions = [];
322 $limts = strtotime(date('Y').'-'.date('m').'-'.date('d'));
323
324 if (!$filters) {
325 $q = "SELECT * FROM `#__vikbooking_restrictions` WHERE `dto` = 0 OR `dto` >= ".$limts.";";
326 } else {
327 if (count($rooms) == 0) {
328 $q = "SELECT * FROM `#__vikbooking_restrictions` WHERE `allrooms` = 1 AND (`dto` = 0 OR `dto` >= ".$limts.");";
329 } else {
330 $clause = array();
331 foreach ($rooms as $idr) {
332 if (empty($idr)) continue;
333 $clause[] = "`idrooms` LIKE '%-".intval($idr)."-%'";
334 }
335 if (count($clause) > 0) {
336 $q = "SELECT * FROM `#__vikbooking_restrictions` WHERE (`dto` = 0 OR `dto` >= ".$limts.") AND (`allrooms` = 1 OR (`allrooms` = 0 AND (".implode(" OR ", $clause).")));";
337 } else {
338 $q = "SELECT * FROM `#__vikbooking_restrictions` WHERE `allrooms` = 1 AND (`dto` = 0 OR `dto` >= ".$limts.");";
339 }
340 }
341 }
342 $dbo->setQuery($q);
343 $allrestrictions = $dbo->loadAssocList();
344
345 if ($allrestrictions) {
346 foreach ($allrestrictions as $k => $res) {
347 if (!empty($res['month'])) {
348 $restrictions[$res['month']] = $res;
349 } else {
350 if (!isset($restrictions['range'])) {
351 $restrictions['range'] = array();
352 }
353 $restrictions['range'][$k] = $res;
354 }
355 }
356 }
357
358 return $restrictions;
359 }
360
361 public static function globalRestrictions($restrictions = [])
362 {
363 $ret = [];
364 foreach ($restrictions as $kr => $rr) {
365 if ($kr == 'range') {
366 foreach ($rr as $kd => $dr) {
367 if ($dr['allrooms'] == 1) {
368 $ret['range'][$kd] = $restrictions[$kr][$kd];
369 }
370 }
371 continue;
372 }
373 if ($rr['allrooms'] == 1) {
374 $ret[$kr] = $restrictions[$kr];
375 }
376 }
377 return $ret;
378 }
379
380 /**
381 * From the given restrictions, check-in, check-out and nights, looks for
382 * a restriction to be returned and applied over these stays.
383 * In order to give priority to newer Restriction IDs, the order of the
384 * $restrictions array should be ascending, so that the newer restrictions
385 * will overwrite the array with the last ID (more recent). Only for date ranges.
386 * Returns an array with the record of the restriction found. The loop is not
387 * broken when the first valid restriction is found to give priority to
388 * newer restriction IDs to overwrite older records.
389 *
390 * @param int $first the unix timestamp for the check-in date
391 * @param int $second the unix timestamp for the check-out date
392 * @param int $daysdiff the number of nights of stay
393 * @param array $restrictions the list of restrictions loaded
394 *
395 * @return array the restriction found, or an empty array.
396 */
397 public static function parseSeasonRestrictions($first, $second, $daysdiff, $restrictions) {
398 $season_restrictions = array();
399 $restrcheckin = getdate($first);
400 $restrcheckout = getdate($second);
401 if (array_key_exists($restrcheckin['mon'], $restrictions)) {
402 //restriction found for this month, checking:
403 $season_restrictions['id'] = $restrictions[$restrcheckin['mon']]['id'];
404 $season_restrictions['name'] = $restrictions[$restrcheckin['mon']]['name'];
405 $season_restrictions['allowed'] = true; //set to false when these nights are not allowed
406 if (strlen($restrictions[$restrcheckin['mon']]['wday']) > 0) {
407 //Week Day Arrival Restriction
408 $rvalidwdays = array($restrictions[$restrcheckin['mon']]['wday']);
409 if (strlen($restrictions[$restrcheckin['mon']]['wdaytwo']) > 0) {
410 $rvalidwdays[] = $restrictions[$restrcheckin['mon']]['wdaytwo'];
411 }
412 $season_restrictions['wdays'] = $rvalidwdays;
413 } elseif (!empty($restrictions[$restrcheckin['mon']]['ctad']) || !empty($restrictions[$restrcheckin['mon']]['ctdd'])) {
414 if (!empty($restrictions[$restrcheckin['mon']]['ctad'])) {
415 $season_restrictions['cta'] = explode(',', $restrictions[$restrcheckin['mon']]['ctad']);
416 }
417 if (!empty($restrictions[$restrcheckin['mon']]['ctdd'])) {
418 $season_restrictions['ctd'] = explode(',', $restrictions[$restrcheckin['mon']]['ctdd']);
419 }
420 }
421 if (!empty($restrictions[$restrcheckin['mon']]['maxlos']) && $restrictions[$restrcheckin['mon']]['maxlos'] > 0 && $restrictions[$restrcheckin['mon']]['maxlos'] > $restrictions[$restrcheckin['mon']]['minlos']) {
422 $season_restrictions['maxlos'] = $restrictions[$restrcheckin['mon']]['maxlos'];
423 if ($daysdiff > $restrictions[$restrcheckin['mon']]['maxlos']) {
424 $season_restrictions['allowed'] = false;
425 }
426 }
427 if ($daysdiff < $restrictions[$restrcheckin['mon']]['minlos']) {
428 $season_restrictions['allowed'] = false;
429 }
430 $season_restrictions['minlos'] = $restrictions[$restrcheckin['mon']]['minlos'];
431 } elseif (array_key_exists('range', $restrictions)) {
432 foreach ($restrictions['range'] as $restr) {
433 if ($restr['dfrom'] <= $first && $restr['dto'] >= $first) {
434 //restriction found for this date range, checking:
435 $season_restrictions['id'] = $restr['id'];
436 $season_restrictions['name'] = $restr['name'];
437 $season_restrictions['allowed'] = true; //set to false when these nights are not allowed
438 if (strlen((string)$restr['wday']) > 0) {
439 //Week Day Arrival Restriction
440 $rvalidwdays = array($restr['wday']);
441 if (strlen((string)$restr['wdaytwo']) > 0) {
442 $rvalidwdays[] = $restr['wdaytwo'];
443 }
444 $season_restrictions['wdays'] = $rvalidwdays;
445 } elseif (!empty($restr['ctad']) || !empty($restr['ctdd'])) {
446 if (!empty($restr['ctad'])) {
447 $season_restrictions['cta'] = explode(',', $restr['ctad']);
448 }
449 if (!empty($restr['ctdd'])) {
450 $season_restrictions['ctd'] = explode(',', $restr['ctdd']);
451 }
452 }
453 if (!empty($restr['maxlos']) && $restr['maxlos'] > 0 && $restr['maxlos'] > $restr['minlos']) {
454 $season_restrictions['maxlos'] = $restr['maxlos'];
455 if ($daysdiff > $restr['maxlos']) {
456 $season_restrictions['allowed'] = false;
457 }
458 }
459 if ($daysdiff < $restr['minlos']) {
460 $season_restrictions['allowed'] = false;
461 }
462 $season_restrictions['minlos'] = $restr['minlos'];
463 }
464 }
465 }
466
467 return $season_restrictions;
468 }
469
470 public static function compareSeasonRestrictionsNights($restrictions)
471 {
472 $base_compare = array();
473 $base_nights = 0;
474 foreach ($restrictions as $nights => $restr) {
475 $base_compare = $restr;
476 $base_nights = $nights;
477 break;
478 }
479
480 /**
481 * Prepare 1st hypothetical multi dimension array for string-comparison with array_diff().
482 * Keys "cta" and "ctd" may be the 2nd dimension array variables which cannot be casted to string.
483 *
484 * @since 1.15.0 (J) - 1.5.0 (WP)
485 */
486 list($casted_base_compare, $use_base_compare) = self::prepareMultiDimArrayDiff($base_compare);
487
488 foreach ($restrictions as $nights => $restr) {
489 if ($nights == $base_nights) {
490 continue;
491 }
492
493 // prepare 2nd hypothetical multi dimension array for string-comparison with array_diff().
494 list($casted_restr, $use_restr) = self::prepareMultiDimArrayDiff($restr);
495
496 // get associative array of differences
497 $diff = array_diff($use_base_compare, $use_restr);
498
499 if (count($diff) > 0 && array_key_exists('id', $diff)) {
500 // return differences only if the Restriction ID is different: ignore allowed, wdays, minlos, maxlos.
501 // only one Restriction per time should be applied to certain Season Dates but check just in case.
502 return self::restoreMultiDimArrayDiff($casted_base_compare, $casted_restr, $diff);
503 }
504 }
505
506 return array();
507 }
508
509 /**
510 * Methods using array_diff() may need one-dimension arrays, because this
511 * native functions applies a string comparison, and so casting an array
512 * value to a string would generate a Notice message. This method simply
513 * converts any sub-array into a restorable string into a non-scalar var.
514 * This only works when comparing 2 array variables.
515 *
516 * @param array $arr the hypothetical multi dimension array.
517 *
518 * @return array the one dimension array to be passed to array_diff()
519 * and the list of keys converted to strings for comparison.
520 *
521 * @since 1.15.0 (J) - 1.5.0 (WP)
522 */
523 public static function prepareMultiDimArrayDiff($arr)
524 {
525 $casted_keys = array();
526
527 if (!is_array($arr) || !count($arr)) {
528 return array($casted_keys, $arr);
529 }
530
531 foreach ($arr as $key => $val) {
532 if (!is_scalar($val)) {
533 // memorize original value
534 $casted_keys[$key] = $val;
535 // cast to string must be applied
536 $arr[$key] = json_encode($val);
537 }
538 }
539
540 return array($casted_keys, $arr);
541 }
542
543 /**
544 * Methods using array_diff() may need one-dimension arrays, because this
545 * native functions applies a string comparison, and so casting an array
546 * value to a string would generate a Notice message. This method restores
547 * the original values of the "prepared" arrays by getting the list of the
548 * modified keys from non-scalar to scalar values.
549 * This only works when comparing 2 array variables.
550 *
551 * @param array $keys_one associative array of casted key-value pairs from 1st arg.
552 * @param array $keys_two associative array of casted key-value pairs from 2nd arg.
553 * @param array $arr the array result of array_diff().
554 *
555 * @return array the array result of array_diff() with its original values.
556 *
557 * @since 1.15.0 (J) - 1.5.0 (WP)
558 */
559 public static function restoreMultiDimArrayDiff($keys_one, $keys_two, $arr)
560 {
561 if (!is_array($arr) || !count($arr)) {
562 return $arr;
563 }
564
565 foreach ($arr as $key => $val) {
566 // keys returned by array_diff can be present in both $keys_one and $keys_two
567 if (isset($keys_one[$key])) {
568 // always give higher priority to the 1st argument keys for array_diff()
569 $arr[$key] = json_decode($keys_one[$key]);
570 } elseif (isset($keys_two[$key])) {
571 // fallback to 2nd argument keys
572 $arr[$key] = json_decode($keys_two[$key]);
573 }
574 }
575
576 return $arr;
577 }
578
579 public static function roomRestrictions($roomid, $restrictions) {
580 $ret = array();
581 if (!empty($roomid) && count($restrictions) > 0) {
582 foreach ($restrictions as $kr => $rr) {
583 if ($kr == 'range') {
584 foreach ($rr as $kd => $dr) {
585 if ($dr['allrooms'] == 0 && !empty($dr['idrooms'])) {
586 $allrooms = explode(';', $dr['idrooms']);
587 if (in_array('-'.$roomid.'-', $allrooms)) {
588 $ret['range'][$kd] = $restrictions[$kr][$kd];
589 }
590 }
591 }
592 } else {
593 if ($rr['allrooms'] == 0 && !empty($rr['idrooms'])) {
594 $allrooms = explode(';', $rr['idrooms']);
595 if (in_array('-'.$roomid.'-', $allrooms)) {
596 $ret[$kr] = $restrictions[$kr];
597 }
598 }
599 }
600 }
601 }
602 return $ret;
603 }
604
605 public static function validateRoomRestriction($roomrestr, $restrcheckin, $restrcheckout, $daysdiff) {
606 $restrictionerrmsg = '';
607 $restrictions_affcount = 0;
608 if (array_key_exists($restrcheckin['mon'], $roomrestr)) {
609 //restriction found for this month, checking:
610 $restrictions_affcount++;
611 if (strlen((string)$roomrestr[$restrcheckin['mon']]['wday'])) {
612 $rvalidwdays = array($roomrestr[$restrcheckin['mon']]['wday']);
613 if (strlen((string)$roomrestr[$restrcheckin['mon']]['wdaytwo'])) {
614 $rvalidwdays[] = $roomrestr[$restrcheckin['mon']]['wdaytwo'];
615 }
616 if (!in_array($restrcheckin['wday'], $rvalidwdays)) {
617 $restrictionerrmsg = JText::sprintf('VBRESTRTIPWDAYARRIVAL', self::sayMonth($restrcheckin['mon']), self::sayWeekDay($roomrestr[$restrcheckin['mon']]['wday']).(strlen($roomrestr[$restrcheckin['mon']]['wdaytwo']) > 0 ? '/'.self::sayWeekDay($roomrestr[$restrcheckin['mon']]['wdaytwo']) : ''));
618 } elseif ($roomrestr[$restrcheckin['mon']]['multiplyminlos'] == 1) {
619 if (($daysdiff % $roomrestr[$restrcheckin['mon']]['minlos']) != 0) {
620 $restrictionerrmsg = JText::sprintf('VBRESTRTIPMULTIPLYMINLOS', self::sayMonth($restrcheckin['mon']), $roomrestr[$restrcheckin['mon']]['minlos']);
621 }
622 }
623 $comborestr = self::parseJsDrangeWdayCombo($roomrestr[$restrcheckin['mon']]);
624 if (count($comborestr) > 0) {
625 if (array_key_exists($restrcheckin['wday'], $comborestr)) {
626 if (!in_array($restrcheckout['wday'], $comborestr[$restrcheckin['wday']])) {
627 $restrictionerrmsg = JText::sprintf('VBRESTRTIPWDAYCOMBO', self::sayMonth($restrcheckin['mon']), self::sayWeekDay($comborestr[$restrcheckin['wday']][0]).(count($comborestr[$restrcheckin['wday']]) == 2 ? '/'.self::sayWeekDay($comborestr[$restrcheckin['wday']][1]) : ''), self::sayWeekDay($restrcheckin['wday']));
628 }
629 }
630 }
631 } elseif (!empty($roomrestr[$restrcheckin['mon']]['ctad']) || !empty($roomrestr[$restrcheckin['mon']]['ctdd'])) {
632 if (!empty($roomrestr[$restrcheckin['mon']]['ctad'])) {
633 $ctarestrictions = explode(',', $roomrestr[$restrcheckin['mon']]['ctad']);
634 if (in_array('-'.$restrcheckin['wday'].'-', $ctarestrictions)) {
635 $restrictionerrmsg = JText::sprintf('VBRESTRERRWDAYCTAMONTH', self::sayWeekDay($restrcheckin['wday']), self::sayMonth($restrcheckin['mon']));
636 }
637 }
638 if (!empty($roomrestr[$restrcheckin['mon']]['ctdd'])) {
639 $ctdrestrictions = explode(',', $roomrestr[$restrcheckin['mon']]['ctdd']);
640 if (in_array('-'.$restrcheckout['wday'].'-', $ctdrestrictions)) {
641 $restrictionerrmsg = JText::sprintf('VBRESTRERRWDAYCTDMONTH', self::sayWeekDay($restrcheckout['wday']), self::sayMonth($restrcheckin['mon']));
642 }
643 }
644 }
645 if (!empty($roomrestr[$restrcheckin['mon']]['maxlos']) && $roomrestr[$restrcheckin['mon']]['maxlos'] > 0 && $roomrestr[$restrcheckin['mon']]['maxlos'] > $roomrestr[$restrcheckin['mon']]['minlos']) {
646 if ($daysdiff > $roomrestr[$restrcheckin['mon']]['maxlos']) {
647 $restrictionerrmsg = JText::sprintf('VBRESTRTIPMAXLOSEXCEEDED', self::sayMonth($restrcheckin['mon']), $roomrestr[$restrcheckin['mon']]['maxlos']);
648 }
649 }
650 if ($daysdiff < $roomrestr[$restrcheckin['mon']]['minlos']) {
651 $restrictionerrmsg = JText::sprintf('VBRESTRTIPMINLOSEXCEEDED', self::sayMonth($restrcheckin['mon']), $roomrestr[$restrcheckin['mon']]['minlos']);
652 }
653 } elseif (array_key_exists('range', $roomrestr)) {
654 $restrictionsvalid = true;
655 /**
656 * We use this map to know which restriction IDs are okay or not okay with the Min LOS.
657 * The most recent restrictions will have a higher priority over the oldest ones.
658 *
659 * @since 1.12.1
660 */
661 $minlos_priority = array(
662 'ok' => array(),
663 'nok' => array()
664 );
665 foreach ($roomrestr['range'] as $restr) {
666 /**
667 * We should not always add 82799 seconds to the end date of the restriction
668 * because if they only last for one day (like a Saturday), then $restr['dto']
669 * will be already set to the time 23:59:59.
670 *
671 * @since 1.13 (J) - 1.2.18 (WP)
672 */
673 $end_operator = date('Y-m-d', $restr['dfrom']) != date('Y-m-d', $restr['dto']) ? 82799 : 0;
674 //
675 if ($restr['dfrom'] <= $restrcheckin[0] && ($restr['dto'] + $end_operator) >= $restrcheckin[0]) {
676 // restriction found for this date range based on arrival date, check if compliant
677 $restrictions_affcount++;
678 if (strlen((string)$restr['wday']) > 0) {
679 $rvalidwdays = array($restr['wday']);
680 if (strlen((string)$restr['wdaytwo']) > 0) {
681 $rvalidwdays[] = $restr['wdaytwo'];
682 }
683 if (!in_array($restrcheckin['wday'], $rvalidwdays)) {
684 $restrictionsvalid = false;
685 $restrictionerrmsg = JText::sprintf('VBRESTRTIPWDAYARRIVALRANGE', self::sayWeekDay($restr['wday']).(strlen((string)$restr['wdaytwo']) ? '/'.self::sayWeekDay($restr['wdaytwo']) : ''));
686 } elseif ($restr['multiplyminlos'] == 1) {
687 if (($daysdiff % $restr['minlos']) != 0) {
688 $restrictionsvalid = false;
689 $restrictionerrmsg = JText::sprintf('VBRESTRTIPMULTIPLYMINLOSRANGE', $restr['minlos']);
690 }
691 }
692 $comborestr = self::parseJsDrangeWdayCombo($restr);
693 if ($comborestr) {
694 if (array_key_exists($restrcheckin['wday'], $comborestr)) {
695 if (!in_array($restrcheckout['wday'], $comborestr[$restrcheckin['wday']])) {
696 $restrictionsvalid = false;
697 $restrictionerrmsg = JText::sprintf('VBRESTRTIPWDAYCOMBORANGE', self::sayWeekDay($comborestr[$restrcheckin['wday']][0]).(count($comborestr[$restrcheckin['wday']]) == 2 ? '/'.self::sayWeekDay($comborestr[$restrcheckin['wday']][1]) : ''), self::sayWeekDay($restrcheckin['wday']));
698 }
699 }
700 }
701 } elseif (!empty($restr['ctad']) || !empty($restr['ctdd'])) {
702 if (!empty($restr['ctad'])) {
703 $ctarestrictions = explode(',', $restr['ctad']);
704 if (in_array('-'.$restrcheckin['wday'].'-', $ctarestrictions)) {
705 $restrictionerrmsg = JText::sprintf('VBRESTRERRWDAYCTARANGE', self::sayWeekDay($restrcheckin['wday']));
706 }
707 }
708 if (!empty($restr['ctdd'])) {
709 $ctdrestrictions = explode(',', $restr['ctdd']);
710 if (in_array('-'.$restrcheckout['wday'].'-', $ctdrestrictions) && $restrcheckout[0] <= ($restr['dto'] + $end_operator)) {
711 $restrictionerrmsg = JText::sprintf('VBRESTRERRWDAYCTDRANGE', self::sayWeekDay($restrcheckout['wday']));
712 }
713 }
714 }
715 if (!empty($restr['maxlos']) && $restr['maxlos'] > 0 && $restr['maxlos'] > $restr['minlos']) {
716 if ($daysdiff > $restr['maxlos']) {
717 $restrictionsvalid = false;
718 $restrictionerrmsg = JText::sprintf('VBRESTRTIPMAXLOSEXCEEDEDRANGE', $restr['maxlos']);
719 }
720 }
721 if ($daysdiff < $restr['minlos']) {
722 $restrictionsvalid = false;
723 $restrictionerrmsg = JText::sprintf('VBRESTRTIPMINLOSEXCEEDEDRANGE', $restr['minlos']);
724 array_push($minlos_priority['nok'], (int)$restr['id']);
725 } else {
726 array_push($minlos_priority['ok'], (int)$restr['id']);
727 }
728 } elseif ($restr['dfrom'] <= $restrcheckout[0] && ($restr['dto'] + $end_operator) >= $restrcheckout[0] && !empty($restr['ctdd'])) {
729 /**
730 * We validate the CTD restrictions depending on the check-out date.
731 *
732 * @since 1.16.3 (J) - 1.6.3 (WP)
733 */
734 $ctdrestrictions = explode(',', $restr['ctdd']);
735 if (in_array('-'.$restrcheckout['wday'].'-', $ctdrestrictions)) {
736 $restrictions_affcount++;
737 $restrictionsvalid = false;
738 $restrictionerrmsg = JText::sprintf('VBRESTRERRWDAYCTDRANGE', VikBooking::sayWeekDay($restrcheckout['wday']));
739 }
740 }
741 }
742 if (!$restrictionsvalid && count($minlos_priority['ok']) && count($minlos_priority['nok']) && max($minlos_priority['ok']) > max($minlos_priority['nok'])) {
743 // we unset the error message because a more recent restriction is allowing this MinLOS
744 $restrictionerrmsg = '';
745 }
746 }
747 //April 2017 - Check global restriction of Min LOS for TAC functions in VBO and VCM
748 if (empty($restrictionerrmsg) && count($roomrestr) && $restrictions_affcount <= 0) {
749 //Check global MinLOS (only in case there are no restrictions affecting these dates or no restrictions at all)
750 $globminlos = self::getDefaultNightsCalendar();
751 if ($globminlos > 1 && $daysdiff < $globminlos) {
752 $restrictionerrmsg = JText::sprintf('VBRESTRERRMINLOSEXCEEDEDRANGE', $globminlos);
753 }
754 }
755 //
756
757 return $restrictionerrmsg;
758 }
759
760 public static function parseJsDrangeWdayCombo($drestr) {
761 $combo = array();
762 if (strlen((string)$drestr['wday']) && strlen((string)$drestr['wdaytwo']) && !empty($drestr['wdaycombo'])) {
763 $cparts = explode(':', $drestr['wdaycombo']);
764 foreach ($cparts as $kc => $cw) {
765 if (!empty($cw)) {
766 $nowcombo = explode('-', $cw);
767 $combo[intval($nowcombo[0])][] = intval($nowcombo[1]);
768 }
769 }
770 }
771 return $combo;
772 }
773
774 public static function validateRoomPackage($pkg_id, $rooms, $numnights, $checkints, $checkoutts) {
775 $dbo = JFactory::getDbo();
776 $pkg = array();
777 $q = "SELECT * FROM `#__vikbooking_packages` WHERE `id`='".intval($pkg_id)."';";
778 $dbo->setQuery($q);
779 $dbo->execute();
780 if ($dbo->getNumRows() == 1) {
781 $pkg = $dbo->loadAssoc();
782 $vbo_tn = self::getTranslator();
783 $vbo_tn->translateContents($pkg, '#__vikbooking_packages');
784 } else {
785 return JText::translate('VBOPKGERRNOTFOUND');
786 }
787 $rooms_req = array();
788 foreach ($rooms as $num => $room) {
789 if (!empty($room['id']) && !in_array($room['id'], $rooms_req)) {
790 $rooms_req[] = $room['id'];
791 }
792 }
793 $q = "SELECT `id` FROM `#__vikbooking_packages_rooms` WHERE `idpackage`=".$pkg['id']." AND `idroom` IN (".implode(', ', $rooms_req).");";
794 $dbo->setQuery($q);
795 $dbo->execute();
796 if ($dbo->getNumRows() != count($rooms_req)) {
797 //error, not all the rooms requested are available for this package
798 return JText::translate('VBOPKGERRNOTROOM');
799 }
800 if ($numnights < $pkg['minlos'] || ($pkg['maxlos'] > 0 && $numnights > $pkg['maxlos'])) {
801 return JText::translate('VBOPKGERRNUMNIGHTS');
802 }
803 if ($checkints < $pkg['dfrom'] || $checkints > $pkg['dto']) {
804 return JText::translate('VBOPKGERRCHECKIND');
805 }
806 if ($checkoutts < $pkg['dfrom'] || ($checkoutts > $pkg['dto'] && date('Y-m-d', $pkg['dfrom']) != date('Y-m-d', $pkg['dto']))) {
807 //VBO 1.10 - we allow a check-out date after the pkg validity-end-date only if the validity dates are equal (dfrom & dto)
808 return JText::translate('VBOPKGERRCHECKOUTD');
809 }
810 if (!empty($pkg['excldates'])) {
811 //this would check if any stay date is excluded
812 //$bookdates_ts = self::getGroupDays($checkints, $checkoutts, $numnights);
813 //check just the arrival and departure dates
814 $bookdates_ts = array($checkints, $checkoutts);
815 $bookdates = array();
816 foreach ($bookdates_ts as $bookdate_ts) {
817 $info_d = getdate($bookdate_ts);
818 $bookdates[] = $info_d['mon'].'-'.$info_d['mday'].'-'.$info_d['year'];
819 }
820 $edates = explode(';', $pkg['excldates']);
821 foreach ($edates as $edate) {
822 if (!empty($edate) && in_array($edate, $bookdates)) {
823 return JText::sprintf('VBOPKGERREXCLUDEDATE', $edate);
824 }
825 }
826 }
827 return $pkg;
828 }
829
830 public static function getPackage($pkg_id) {
831 $dbo = JFactory::getDbo();
832 $pkg = array();
833 $q = "SELECT * FROM `#__vikbooking_packages` WHERE `id`='".intval($pkg_id)."';";
834 $dbo->setQuery($q);
835 $dbo->execute();
836 if ($dbo->getNumRows() == 1) {
837 $pkg = $dbo->loadAssoc();
838 }
839 return $pkg;
840 }
841
842 public static function getRoomParam($paramname, $paramstr)
843 {
844 if (empty($paramstr)) {
845 return '';
846 }
847
848 $paramarr = json_decode($paramstr, true);
849
850 if (is_array($paramarr) && isset($paramarr[$paramname])) {
851 return $paramarr[$paramname];
852 }
853
854 return '';
855 }
856
857 public static function filterNightsSeasonsCal ($arr_nights) {
858 $nights = array();
859 foreach ($arr_nights as $night) {
860 if (intval(trim($night)) > 0) {
861 $nights[] = intval(trim($night));
862 }
863 }
864 sort($nights);
865 return array_unique($nights);
866 }
867
868 public static function getSeasonRangeTs ($from, $to, $year) {
869 $sfrom = 0;
870 $sto = 0;
871 $tsbase = mktime(0, 0, 0, 1, 1, $year);
872 $curyear = $year;
873 $tsbasetwo = $tsbase;
874 $curyeartwo = $year;
875 if ($from > $to) {
876 //between two years
877 $curyeartwo += 1;
878 $tsbasetwo = mktime(0, 0, 0, 1, 1, $curyeartwo);
879 }
880 $sfrom = ($tsbase + $from);
881 $sto = ($tsbasetwo + $to);
882 if ($curyear % 4 == 0 && ($curyear % 100 != 0 || $curyear % 400 == 0)) {
883 //leap years
884 $infoseason = getdate($sfrom);
885 $leapts = mktime(0, 0, 0, 2, 29, $infoseason['year']);
886 if ($infoseason[0] > $leapts) {
887 /**
888 * Timestamp must be greater than the leap-day of Feb 29th.
889 * It used to be checked for >= $leapts.
890 *
891 * @since July 3rd 2019
892 */
893 $sfrom += 86400;
894 if ($curyear == $curyeartwo) {
895 $sto += 86400;
896 }
897 }
898 } elseif ($curyeartwo % 4 == 0 && ($curyeartwo % 100 != 0 || $curyeartwo % 400 == 0)) {
899 //leap years
900 $infoseason = getdate($sto);
901 $leapts = mktime(0, 0, 0, 2, 29, $infoseason['year']);
902 if ($infoseason[0] > $leapts) {
903 /**
904 * Timestamp must be greater than the leap-day of Feb 29th.
905 * It used to be checked for >= $leapts.
906 *
907 * @since July 3rd 2019
908 */
909 $sto += 86400;
910 }
911 }
912 return array($sfrom, $sto);
913 }
914
915 public static function sortSeasonsRangeTs ($all_seasons) {
916 $sorted = array();
917 $map = array();
918 foreach ($all_seasons as $key => $season) {
919 $map[$key] = $season['from_ts'];
920 }
921 asort($map);
922 foreach ($map as $key => $s) {
923 $sorted[] = $all_seasons[$key];
924 }
925 return $sorted;
926 }
927
928 public static function formatSeasonDates ($from_ts, $to_ts) {
929 $one = getdate($from_ts);
930 $two = getdate($to_ts);
931 $months_map = array(
932 1 => JText::translate('VBSHORTMONTHONE'),
933 2 => JText::translate('VBSHORTMONTHTWO'),
934 3 => JText::translate('VBSHORTMONTHTHREE'),
935 4 => JText::translate('VBSHORTMONTHFOUR'),
936 5 => JText::translate('VBSHORTMONTHFIVE'),
937 6 => JText::translate('VBSHORTMONTHSIX'),
938 7 => JText::translate('VBSHORTMONTHSEVEN'),
939 8 => JText::translate('VBSHORTMONTHEIGHT'),
940 9 => JText::translate('VBSHORTMONTHNINE'),
941 10 => JText::translate('VBSHORTMONTHTEN'),
942 11 => JText::translate('VBSHORTMONTHELEVEN'),
943 12 => JText::translate('VBSHORTMONTHTWELVE')
944 );
945 $mday_map = array(
946 1 => JText::translate('VBMDAYFRIST'),
947 2 => JText::translate('VBMDAYSECOND'),
948 3 => JText::translate('VBMDAYTHIRD'),
949 'generic' => JText::translate('VBMDAYNUMGEN')
950 );
951 if ($one['year'] == $two['year']) {
952 return $one['year'].' '.$months_map[(int)$one['mon']].' '.$one['mday'].'<sup>'.(array_key_exists((int)substr($one['mday'], -1), $mday_map) && ($one['mday'] < 10 || $one['mday'] > 20) ? $mday_map[(int)substr($one['mday'], -1)] : $mday_map['generic']).'</sup> - '.$months_map[(int)$two['mon']].' '.$two['mday'].'<sup>'.(array_key_exists((int)substr($two['mday'], -1), $mday_map) && ($two['mday'] < 10 || $two['mday'] > 20) ? $mday_map[(int)substr($two['mday'], -1)] : $mday_map['generic']).'</sup>';
953 }
954 return $months_map[(int)$one['mon']].' '.$one['mday'].'<sup>'.(array_key_exists((int)substr($one['mday'], -1), $mday_map) && ($one['mday'] < 10 || $one['mday'] > 20) ? $mday_map[(int)substr($one['mday'], -1)] : $mday_map['generic']).'</sup> '.$one['year'].' - '.$months_map[(int)$two['mon']].' '.$two['mday'].'<sup>'.(array_key_exists((int)substr($two['mday'], -1), $mday_map) && ($two['mday'] < 10 || $two['mday'] > 20) ? $mday_map[(int)substr($two['mday'], -1)] : $mday_map['generic']).'</sup> '.$two['year'];
955 }
956
957 public static function getFirstCustDataField($custdata) {
958 $first_field = '';
959 if (strpos($custdata, JText::translate('VBDBTEXTROOMCLOSED')) !== false) {
960 //Room is closed with this booking
961 return '----';
962 }
963 $parts = explode("\n", $custdata);
964 foreach ($parts as $part) {
965 if (!empty($part)) {
966 $field = explode(':', trim($part));
967 if (!empty($field[1])) {
968 return trim($field[1]);
969 }
970 }
971 }
972 return $first_field;
973 }
974
975 /**
976 * This method composes a string to be logged for the admin
977 * to keep track of what was inside the booking before the
978 * modification. Returns a string and it uses language definitions
979 * that should be available on the front-end and back-end INI files.
980 *
981 * @param array $old_booking the array of the booking prior to the modification
982 * @param array $room_stay_dates optional list of room stay information in case of split stays.
983 *
984 * @return string text describing the situation with the booking before the changes.
985 *
986 * @since 1.16.0 (J) - 1.6.0 (WP) added second argument.
987 */
988 public static function getLogBookingModification($old_booking, $room_stay_dates = [])
989 {
990 $vbo_df = self::getDateFormat();
991 $df = $vbo_df == "%d/%m/%Y" ? 'd/m/Y' : ($vbo_df == "%m/%d/%Y" ? 'm/d/Y' : 'Y-m-d');
992 $wdays_map = array(
993 JText::translate('VBWEEKDAYZERO'),
994 JText::translate('VBWEEKDAYONE'),
995 JText::translate('VBWEEKDAYTWO'),
996 JText::translate('VBWEEKDAYTHREE'),
997 JText::translate('VBWEEKDAYFOUR'),
998 JText::translate('VBWEEKDAYFIVE'),
999 JText::translate('VBWEEKDAYSIX'),
1000 );
1001 $now_info = getdate();
1002 $checkin_info = getdate($old_booking['checkin']);
1003 $checkout_info = getdate($old_booking['checkout']);
1004
1005 $datemod = $wdays_map[$now_info['wday']].', '.date($df.' H:i', $now_info[0]);
1006 $prev_nights = $old_booking['days'].' '.($old_booking['days'] > 1 ? JText::translate('VBDAYS') : JText::translate('VBDAY'));
1007 $prev_dates = $prev_nights.' - '.$wdays_map[$checkin_info['wday']].', '.date($df.' H:i', $checkin_info[0]).' - '.$wdays_map[$checkout_info['wday']].', '.date($df.' H:i', $checkout_info[0]);
1008 $prev_rooms = '';
1009
1010 $orooms_map = [];
1011 $orooms_arr = [];
1012
1013 if (isset($old_booking['rooms_info'])) {
1014 foreach ($old_booking['rooms_info'] as $oroom) {
1015 $orooms_arr[] = $oroom['name'].', '.JText::translate('VBMAILADULTS').': '.$oroom['adults'].', '.JText::translate('VBMAILCHILDREN').': '.$oroom['children'];
1016 if (!empty($oroom['idroom'])) {
1017 $orooms_map[$oroom['idroom']] = $oroom['name'];
1018 }
1019 }
1020 $prev_rooms = implode("\n", $orooms_arr);
1021 }
1022
1023 if (!empty($old_booking['split_stay']) && !empty($room_stay_dates) && count($orooms_map)) {
1024 $split_stay_prev_infos = [];
1025 foreach ($room_stay_dates as $rs_ind => $room_stay) {
1026 if (empty($room_stay['idroom']) || !isset($orooms_map[$room_stay['idroom']])) {
1027 continue;
1028 }
1029 $room_checkin = date('Y-m-d', $room_stay['checkin']);
1030 $room_checkout = date('Y-m-d', $room_stay['checkout']);
1031 $room_nights = !empty($room_stay['nights']) ? $room_stay['nights'] : 0;
1032
1033 $split_stay_descr = $orooms_map[$room_stay['idroom']];
1034 if (!empty($room_nights)) {
1035 $split_stay_descr .= ': ' . $room_nights . ' ' . ($room_nights > 1 ? JText::translate('VBDAYS') : JText::translate('VBDAY'));
1036 }
1037 $split_stay_descr .= ', ';
1038 $split_stay_descr .= $room_checkin . ' - ' . $room_checkout;
1039
1040 // push split stay description string
1041 $split_stay_prev_infos[] = $split_stay_descr;
1042 }
1043 if (count($split_stay_prev_infos)) {
1044 $prev_rooms .= "\n" . JText::translate('VBO_SPLIT_STAY') . ":\n" . implode("\n", $split_stay_prev_infos);
1045 }
1046 }
1047
1048 $currencyname = self::getCurrencyName();
1049 $prev_total = $currencyname.' '.self::numberFormat($old_booking['total']);
1050
1051 return JText::sprintf('VBOBOOKMODLOGSTR', $datemod, $prev_dates, $prev_rooms, $prev_total);
1052 }
1053
1054 /**
1055 * This method invokes the class
1056 * VikChannelManagerLogos (new in VCM 1.6.4) to
1057 * map the name of a channel to its corresponding logo.
1058 * The method can also be used to get an istance of the class.
1059 *
1060 * @param mixed $provenience either a string or an array with main and sub channels.
1061 * @param boolean $get_istance
1062 *
1063 * @return mixed boolean if the Class doesn't exist or if the provenience cannot be matched. Instance otherwise.
1064 */
1065 public static function getVcmChannelsLogo($provenience, $get_istance = false) {
1066 if (!file_exists(VCM_ADMIN_PATH.DIRECTORY_SEPARATOR.'helpers'.DIRECTORY_SEPARATOR.'logos.php')) {
1067 return false;
1068 }
1069 if (!class_exists('VikChannelManagerLogos')) {
1070 require_once(VCM_ADMIN_PATH.DIRECTORY_SEPARATOR.'helpers'.DIRECTORY_SEPARATOR.'logos.php');
1071 }
1072
1073 /**
1074 * Due to the new iCal channel we now support main and sub channels.
1075 *
1076 * @since 1.13 - VCM 1.7.0
1077 */
1078 if (is_string($provenience) && strpos($provenience, '_') !== false) {
1079 $provenience = explode('_', $provenience);
1080 }
1081 $main_channel = '';
1082 $full_channel = '';
1083 if (is_array($provenience)) {
1084 if (count($provenience) > 1) {
1085 $main_channel = $provenience[1];
1086 } else {
1087 $main_channel = $provenience[0];
1088 }
1089 if (stripos($provenience[0], 'ical') !== false) {
1090 $full_channel = implode('_', $provenience);
1091 }
1092 } else {
1093 $main_channel = $provenience;
1094 }
1095
1096 // get object instance by passing the main provenience
1097 $obj = new VikChannelManagerLogos($main_channel);
1098
1099 // update provenience with main and full channel
1100 if (!empty($full_channel)) {
1101 $obj->setProvenience($main_channel, $full_channel);
1102 }
1103
1104 // return either the instance or the logo URL for this channel source
1105 return $get_istance ? $obj : $obj->getLogoURL();
1106 }
1107
1108 public static function vcmAutoUpdate()
1109 {
1110 if (!is_file(VCM_SITE_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'lib.vikchannelmanager.php')) {
1111 return -1;
1112 }
1113
1114 $vcm_auto_upd = (int)VBOFactory::getConfig()->get('vcmautoupd', 0);
1115
1116 return $vcm_auto_upd ? 1 : 0;
1117 }
1118
1119 public static function getVcmInvoker()
1120 {
1121 if (!class_exists('VboVcmInvoker')) {
1122 require_once(VBO_SITE_PATH . DIRECTORY_SEPARATOR . "helpers" . DIRECTORY_SEPARATOR . "vcm.php");
1123 }
1124
1125 return new VboVcmInvoker();
1126 }
1127
1128 /**
1129 * Returns an instance of VikBooking History object.
1130 *
1131 * @param int $bid optional booking ID to bind.
1132 *
1133 * @return VboBookingHistory
1134 *
1135 * @since 1.16.5 (J) - 1.6.5 (WP) added argument $bid.
1136 */
1137 public static function getBookingHistoryInstance($bid = 0)
1138 {
1139 if (!class_exists('VboBookingHistory')) {
1140 require_once(VBO_SITE_PATH . DIRECTORY_SEPARATOR . "helpers" . DIRECTORY_SEPARATOR . "history.php");
1141 }
1142
1143 return new VboBookingHistory($bid);
1144 }
1145
1146 /**
1147 * Returns an instance of the VCMChatHandler class to handle
1148 * the messaging/chat for the given reservation ID.
1149 *
1150 * @param int $oid the ID of the booking in VBO
1151 * @param string $channel the name of the source channel
1152 *
1153 * @return mixed null if VCM is not available, VCMChatHandler instance otherwise
1154 *
1155 * @since 1.11.2 (J) - 1.1.2 (WP)
1156 * @since 1.16.0 (J) - 1.6.0 (WP) the method can also be used to require the chat handler.
1157 * @since 1.16.4 (J) - 1.6.4 (WP) preloading widgets on WP when VCM is inactive is prevented.
1158 */
1159 public static function getVcmChatInstance($oid, $channel = null)
1160 {
1161 $vcm_messaging_helper = VCM_SITE_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'chat' . DIRECTORY_SEPARATOR . 'handler.php';
1162 if (!is_file($vcm_messaging_helper)) {
1163 // VCM is not available
1164 return null;
1165 }
1166
1167 if (VBOPlatformDetection::isWordPress() && !defined('VIKCHANNELMANAGER_LIBRARIES')) {
1168 // VCM must be inactive, prevent the loading of the instance
1169 return null;
1170 }
1171
1172 // make sure the channel name is correct as it may contain the sub-network
1173 if (!empty($channel)) {
1174 $segments = explode('_', $channel);
1175 if (count($segments) > 1) {
1176 // we take the first segment, as the second could be the source sub-network (expedia_Hotels.com)
1177 $channel = $segments[0];
1178 }
1179 }
1180
1181 // always require main file of the abstract class even if arguments are empty/invalid
1182 require_once $vcm_messaging_helper;
1183
1184 // return the instance of the class for this channel handler
1185 return VCMChatHandler::getInstance($oid, $channel);
1186 }
1187
1188 /**
1189 * Returns an instance of the VCMOpportunityHandler class to handle
1190 * the various opportunities through VCM.
1191 *
1192 * @return mixed null if VCM is not available, VCMOpportunityHandler instance otherwise
1193 *
1194 * @since 1.2.0
1195 */
1196 public static function getVcmOpportunityInstance() {
1197 $vcm_opp_helper = VCM_SITE_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'opportunity.php';
1198 if (!is_file($vcm_opp_helper)) {
1199 // VCM is not available
1200 return null;
1201 }
1202 // require main file of the class
1203 require_once $vcm_opp_helper;
1204 // return the instance of the class
1205 return VCMOpportunityHandler::getInstance();
1206 }
1207
1208 public static function vcmBcomReportingSupported() {
1209 if (!is_file(VCM_SITE_PATH.DIRECTORY_SEPARATOR.'helpers'.DIRECTORY_SEPARATOR.'lib.vikchannelmanager.php')) {
1210 return false;
1211 }
1212 $dbo = JFactory::getDbo();
1213 $q = "SELECT `id` FROM `#__vikchannelmanager_channel` WHERE `uniquekey`='4';";
1214 $dbo->setQuery($q);
1215 $dbo->execute();
1216 return ($dbo->getNumRows() > 0);
1217 }
1218
1219 /**
1220 * Gets a list of all channels supporting the promotions.
1221 *
1222 * @param string $key the key of the handler.
1223 *
1224 * @return mixed false if VCM is not installed, empty array otherwise.
1225 *
1226 * @requires Vik Channel Manager 1.7.1
1227 *
1228 * @since 1.3.0
1229 */
1230 public static function getPromotionHandlers($key = null)
1231 {
1232 if (!is_file(VCM_SITE_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'lib.vikchannelmanager.php')) {
1233 // VCM not installed
1234 return false;
1235 }
1236
1237 if (!class_exists('VikChannelManager') || !method_exists('VikChannelManager', 'getPromotionHandlers')) {
1238 // VCM is outdated yet installed: an empty array is sufficient to not display warning messages
1239 return [];
1240 }
1241
1242 return VikChannelManager::getPromotionHandlers($key);
1243 }
1244
1245 /**
1246 * Gets the factors for suggesting the application of the promotions.
1247 *
1248 * @param mixed $data some optional instructions to be passed as argument.
1249 *
1250 * @return mixed false if VCM is not installed, associative array otherwise.
1251 *
1252 * @requires Vik Channel Manager 1.7.1
1253 *
1254 * @since 1.3.0
1255 */
1256 public static function getPromotionFactors($data = null)
1257 {
1258 if (!is_file(VCM_SITE_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'lib.vikchannelmanager.php')) {
1259 // VCM not installed
1260 return false;
1261 }
1262
1263 if (!class_exists('VikChannelManager') || !method_exists('VikChannelManager', 'getPromotionFactors')) {
1264 // VCM is outdated
1265 return false;
1266 }
1267
1268 return VikChannelManager::getPromotionFactors($data);
1269 }
1270
1271 public static function getTheme()
1272 {
1273 return VBOFactory::getConfig()->get('theme', '');
1274 }
1275
1276 public static function getFooterOrdMail($vbo_tn = null)
1277 {
1278 $dbo = JFactory::getDbo();
1279
1280 $q = "SELECT `id`,`setting` FROM `#__vikbooking_texts` WHERE `param`='footerordmail';";
1281 $dbo->setQuery($q);
1282 $ft = $dbo->loadAssocList();
1283
1284 if (!is_object($vbo_tn)) {
1285 $vbo_tn = self::getTranslator();
1286 }
1287
1288 $vbo_tn->translateContents($ft, '#__vikbooking_texts');
1289
1290 return $ft ? $ft[0]['setting'] : '';
1291 }
1292
1293 public static function requireLogin()
1294 {
1295 return VBOFactory::getConfig()->getBool('requirelogin', false);
1296 }
1297
1298 public static function autoRoomUnit()
1299 {
1300 return VBOFactory::getConfig()->getBool('autoroomunit', false);
1301 }
1302
1303 public static function todayBookings()
1304 {
1305 static $todayBookings = null;
1306
1307 if ($todayBookings === null) {
1308 $todayBookings = VBOFactory::getConfig()->getBool('todaybookings', false);
1309 }
1310
1311 return $todayBookings;
1312 }
1313
1314 public static function couponsEnabled()
1315 {
1316 return VBOFactory::getConfig()->getBool('enablecoupons', false);
1317 }
1318
1319 public static function customersPinEnabled()
1320 {
1321 return VBOFactory::getConfig()->getBool('enablepin', false);
1322 }
1323
1324 /**
1325 * Detects the type of visitor from the user agent.
1326 * Known types are: computer, smartphone, tablet.
1327 *
1328 * @param boolean $returnua whether the type of visitor should be returned. If false
1329 * boolean is returned in case of mobile device detected.
1330 * @param boolean $loadassets whether the system should load an apposite CSS file.
1331 *
1332 * @return mixed string for the type of visitor or boolean if mobile detected.
1333 *
1334 * @since 1.0.13 - Revision September 2018
1335 */
1336 public static function detectUserAgent($returnua = false, $loadassets = true) {
1337 $session = JFactory::getSession();
1338 $sval = $session->get('vbuseragent', '');
1339 $mobiles = array('tablet', 'smartphone');
1340 if (!empty($sval)) {
1341 if ($loadassets) {
1342 self::userAgentStyleSheet($sval);
1343 }
1344 return $returnua ? $sval : in_array($sval, $mobiles);
1345 }
1346
1347 // detect visitor (device) type
1348 $visitoris = 'computer';
1349 try {
1350 if (!class_exists('MobileDetector')) {
1351 require_once VBO_SITE_PATH . DIRECTORY_SEPARATOR . "helpers" . DIRECTORY_SEPARATOR . "mobile_detector.php";
1352 }
1353 $detector = new MobileDetector;
1354 $visitoris = $detector->isMobile() ? ($detector->isTablet() ? 'tablet' : 'smartphone') : 'computer';
1355 } catch (Exception $e) {
1356 // do nothing
1357 }
1358
1359 $session->set('vbuseragent', $visitoris);
1360 if ($loadassets) {
1361 self::userAgentStyleSheet($visitoris);
1362 }
1363
1364 return $returnua ? $visitoris : in_array($visitoris, $mobiles);
1365 }
1366
1367 public static function userAgentStyleSheet($ua)
1368 {
1369 /**
1370 * @wponly in order to not interfere with AJAX requests, we do nothing if doing AJAX.
1371 */
1372 if (function_exists('wp_doing_ajax') && wp_doing_ajax()) {
1373 return;
1374 }
1375 //
1376
1377 $document = JFactory::getDocument();
1378 /**
1379 * @wponly the following CSS files are located in /site/resources/ for WP, not just on /site
1380 */
1381 if ($ua == 'smartphone') {
1382 $document->addStyleSheet(VBO_SITE_URI.'resources/vikbooking_smartphones.css');
1383 } elseif ($ua == 'tablet') {
1384 $document->addStyleSheet(VBO_SITE_URI.'resources/vikbooking_tablets.css');
1385 }
1386 return true;
1387 }
1388
1389 public static function loadJquery()
1390 {
1391 return VBOFactory::getConfig()->getBool('loadjquery', true);
1392 }
1393
1394 public static function loadBootstrap()
1395 {
1396 return VBOFactory::getConfig()->getBool('bootstrap', true);
1397 }
1398
1399 public static function allowMultiLanguage()
1400 {
1401 return VBOFactory::getConfig()->getBool('multilang', true);
1402 }
1403
1404 public static function getTranslator()
1405 {
1406 if (!class_exists('VikBookingTranslator')) {
1407 require_once(VBO_SITE_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'translator.php');
1408 }
1409
1410 return new VikBookingTranslator();
1411 }
1412
1413 /**
1414 * New method's name spelled correctly. The object will be cached by default.
1415 *
1416 * @return VikBookingCustomersPin
1417 *
1418 * @since 1.15.5 (J) - 1.5.11 (WP) cache the instance of the object to better support new cookies set.
1419 */
1420 public static function getCPinInstance()
1421 {
1422 static $cpin_instance = null;
1423
1424 if (!$cpin_instance) {
1425 require_once VBO_SITE_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'cpin.php';
1426
1427 $cpin_instance = new VikBookingCustomersPin();
1428 }
1429
1430 return $cpin_instance;
1431 }
1432
1433 /**
1434 * Old method's name spelled wrongly for BC.
1435 *
1436 * @return VikBookingCustomersPin
1437 */
1438 public static function getCPinIstance()
1439 {
1440 return self::getCPinInstance();
1441 }
1442
1443 /**
1444 * Returns an instance of the VikBookingTracker Class.
1445 * It is also possible to call this method to just require the library.
1446 * This is useful for the back-end to access some static methods
1447 * without tracking any data.
1448 *
1449 * @param boolean $require_only whether to return the object.
1450 *
1451 * @return VikBookingTracker
1452 *
1453 * @since 1.11
1454 */
1455 public static function getTracker($require_only = false) {
1456 if (!class_exists('VikBookingTracker')) {
1457 require_once(VBO_SITE_PATH . DIRECTORY_SEPARATOR . "helpers" . DIRECTORY_SEPARATOR . "tracker.php");
1458 }
1459 return $require_only ? true : VikBookingTracker::getInstance();
1460 }
1461
1462 /**
1463 * Returns an instance of the VikBookingOperator Class.
1464 *
1465 * @return VikBookingOperator
1466 *
1467 * @since 1.11
1468 */
1469 public static function getOperatorInstance() {
1470 if (!class_exists('VikBookingOperator')) {
1471 require_once(VBO_SITE_PATH . DIRECTORY_SEPARATOR . "helpers" . DIRECTORY_SEPARATOR . "operator.php");
1472 }
1473 return VikBookingOperator::getInstance();
1474 }
1475
1476 /**
1477 * Returns an instance of the VikBookingFestivities Class.
1478 *
1479 * @return VikBookingFestivities
1480 *
1481 * @since 1.12
1482 */
1483 public static function getFestivitiesInstance() {
1484 if (!class_exists('VikBookingFestivities')) {
1485 require_once(VBO_ADMIN_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'festivities.php');
1486 }
1487 return VikBookingFestivities::getInstance();
1488 }
1489
1490 /**
1491 * Returns an instance of the VikBookingCriticalDates Class.
1492 *
1493 * @return VikBookingCriticalDates
1494 *
1495 * @since 1.13.5
1496 */
1497 public static function getCriticalDatesInstance() {
1498 if (!class_exists('VikBookingCriticalDates')) {
1499 require_once(VBO_ADMIN_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'critical_dates.php');
1500 }
1501 return VikBookingCriticalDates::getInstance();
1502 }
1503
1504 /**
1505 * Checks whether the chat is enabled.
1506 *
1507 * @return int -1 if VCM is not installed, 0 if disabled, 1 otherwise
1508 *
1509 * @since 1.12
1510 */
1511 public static function chatEnabled() {
1512 if (!is_file(VCM_SITE_PATH.DIRECTORY_SEPARATOR.'helpers'.DIRECTORY_SEPARATOR.'lib.vikchannelmanager.php')) {
1513 return -1;
1514 }
1515 $dbo = JFactory::getDbo();
1516 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='chatenabled';";
1517 $dbo->setQuery($q);
1518 $dbo->execute();
1519 if (!$dbo->getNumRows()) {
1520 // enable the chat by default if VCM is installed
1521 $q = "INSERT INTO `#__vikbooking_config` (`param`,`setting`) VALUES('chatenabled', '1');";
1522 $dbo->setQuery($q);
1523 $dbo->execute();
1524 return 1;
1525 }
1526 $s = $dbo->loadResult();
1527 return intval($s);
1528 }
1529
1530 /**
1531 * Loads the chat parameters from the configuration.
1532 *
1533 * @return object stdClass object from decoded JSON string
1534 *
1535 * @since 1.12
1536 */
1537 public static function getChatParams() {
1538 if (!is_file(VCM_SITE_PATH.DIRECTORY_SEPARATOR.'helpers'.DIRECTORY_SEPARATOR.'lib.vikchannelmanager.php')) {
1539 return new stdClass;
1540 }
1541 $dbo = JFactory::getDbo();
1542 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='chatparams';";
1543 $dbo->setQuery($q);
1544 $dbo->execute();
1545 if (!$dbo->getNumRows()) {
1546 // compose default and basic chat params for the first time
1547 $basic_params = new stdClass;
1548 $basic_params->res_status = array('confirmed', 'standby', 'cancelled');
1549 $basic_params->av_type = 'checkin';
1550 $basic_params->av_days = 0;
1551
1552 $q = "INSERT INTO `#__vikbooking_config` (`param`,`setting`) VALUES('chatparams', ".$dbo->quote(json_encode($basic_params)).");";
1553 $dbo->setQuery($q);
1554 $dbo->execute();
1555
1556 return $basic_params;
1557 }
1558 return json_decode($dbo->loadResult());
1559 }
1560
1561 /**
1562 * Checks whether the pre-checkin is enabled.
1563 *
1564 * @return int 0 if disabled, 1 otherwise
1565 *
1566 * @since 1.12
1567 */
1568 public static function precheckinEnabled() {
1569 $dbo = JFactory::getDbo();
1570 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='precheckinenabled';";
1571 $dbo->setQuery($q);
1572 $dbo->execute();
1573 if (!$dbo->getNumRows()) {
1574 // enable the pre-checkin by default
1575 $q = "INSERT INTO `#__vikbooking_config` (`param`,`setting`) VALUES('precheckinenabled', '1');";
1576 $dbo->setQuery($q);
1577 $dbo->execute();
1578 return 1;
1579 }
1580 $s = $dbo->loadResult();
1581 return intval($s);
1582 }
1583
1584 /**
1585 * Returns the minimum number of days in advance to
1586 * enable the pre-checkin via front-end.
1587 *
1588 * @return int the min number of days in advance for pre-checkin.
1589 *
1590 * @since 1.12
1591 */
1592 public static function precheckinMinOffset() {
1593 $dbo = JFactory::getDbo();
1594 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='precheckinminoffset';";
1595 $dbo->setQuery($q);
1596 $dbo->execute();
1597 if (!$dbo->getNumRows()) {
1598 // set the limit to 1 day before arrival by default
1599 $q = "INSERT INTO `#__vikbooking_config` (`param`,`setting`) VALUES('precheckinminoffset', '1');";
1600 $dbo->setQuery($q);
1601 $dbo->execute();
1602 return 1;
1603 }
1604 $s = $dbo->loadResult();
1605 return intval($s);
1606 }
1607
1608 /**
1609 * Whether upselling extra services is enabled.
1610 *
1611 * @return boolean true if enabled, false otherwise.
1612 *
1613 * @since 1.13
1614 */
1615 public static function upsellingEnabled() {
1616 $dbo = JFactory::getDbo();
1617 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='upselling';";
1618 $dbo->setQuery($q);
1619 $dbo->execute();
1620 if (!$dbo->getNumRows()) {
1621 // enable the upselling feature by default
1622 $q = "INSERT INTO `#__vikbooking_config` (`param`,`setting`) VALUES('upselling', '1');";
1623 $dbo->setQuery($q);
1624 $dbo->execute();
1625 return true;
1626 }
1627 return (intval($dbo->loadResult()) > 0);
1628 }
1629
1630 /**
1631 * Collects a list of options/extra that can be upsold for the
1632 * the rooms booked.
1633 *
1634 * @param array $upsell_data list of stdClass objects for each room booked.
1635 * @param array $info some booking details (id, checkin, checkout).
1636 * @param object $vbo_tn the translation object.
1637 *
1638 * @return array list of upsellable options for each room booked.
1639 *
1640 * @since 1.3.0
1641 */
1642 public static function loadUpsellingData($upsell_data, $info, $vbo_tn) {
1643 $dbo = JFactory::getDbo();
1644 // get all options for all rooms booked
1645 $all_room_ids = array();
1646 foreach ($upsell_data as $v) {
1647 if (!in_array($v->id, $all_room_ids)) {
1648 array_push($all_room_ids, $v->id);
1649 }
1650 }
1651 $q = "SELECT `id`, `idopt` FROM `#__vikbooking_rooms` WHERE `id` IN (" . implode(', ', $all_room_ids) . ");";
1652 $dbo->setQuery($q);
1653 $dbo->execute();
1654 if (!$dbo->getNumRows()) {
1655 // no rooms found
1656 return array();
1657 }
1658 $records = $dbo->loadAssocList();
1659 $all_options = array();
1660 $rooms_options = array();
1661 foreach ($records as $v) {
1662 $allopts = explode(';', $v['idopt']);
1663 $room_opt = array();
1664 foreach ($allopts as $o) {
1665 if (empty($o)) {
1666 continue;
1667 }
1668 if (!in_array((int)$o, $all_options)) {
1669 array_push($all_options, (int)$o);
1670 }
1671 array_push($room_opt, (int)$o);
1672 }
1673 $rooms_options[$v['id']] = $room_opt;
1674 }
1675 if (!count($all_options)) {
1676 // no options found
1677 return array();
1678 }
1679 // load all options that could be used by the booked rooms no matter what was already booked
1680 $q = "SELECT * FROM `#__vikbooking_optionals` WHERE `id` IN (" . implode(', ', $all_options) . ") AND `forcesel`=0 AND `ifchildren`=0 AND `is_citytax`=0 AND `is_fee`=0;";
1681 $dbo->setQuery($q);
1682 $dbo->execute();
1683 if (!$dbo->getNumRows()) {
1684 // no upsell-able options found
1685 return array();
1686 }
1687 $records = $dbo->loadAssocList();
1688 // filter options by available date and translate records
1689 self::filterOptionalsByDate($records, $info['checkin'], $info['checkout']);
1690 $vbo_tn->translateContents($records, '#__vikbooking_optionals');
1691 $records = !is_array($records) ? array() : $records;
1692 //
1693 $tot_upsellable = 0;
1694 foreach ($upsell_data as $k => $rdata) {
1695 if (!isset($upsell_data[$k]->upsellable)) {
1696 $upsell_data[$k]->upsellable = array();
1697 }
1698 foreach ($records as $opt) {
1699 if (!empty($opt['ageintervals'])) {
1700 // upsellable options should not contain age intervals for children
1701 continue;
1702 }
1703 if (!in_array($opt['id'], $rooms_options[$rdata->id])) {
1704 // this option is not assigned to this room
1705 continue;
1706 }
1707 // check if the option is suited for this room party
1708 $clone_opt = array($opt);
1709 self::filterOptionalsByParty($clone_opt, $rdata->adults, $rdata->children);
1710 if (!is_array($clone_opt) || !count($clone_opt)) {
1711 // this option is not suited for this room party
1712 continue;
1713 }
1714 //
1715 if (in_array($opt['id'], $rdata->options)) {
1716 // this option has already been booked
1717 continue;
1718 }
1719 // push this option and increase counter
1720 array_push($upsell_data[$k]->upsellable, $opt);
1721 $tot_upsellable++;
1722 }
1723 }
1724
1725 // if no upsellable options were found, we return an empty array
1726 return $tot_upsellable > 0 ? $upsell_data : array();
1727 }
1728
1729 /**
1730 * Returns the minimum days in advance for booking, by considering by default
1731 * also the property closing dates. If the property is currently closed, then
1732 * the minimum number of days in advance will be increased.
1733 *
1734 * @param bool $no_closing_dates whether to skip checking closing dates.
1735 *
1736 * @return int the number of days in advance for booking.
1737 *
1738 * @since 1.14 (J) - 1.4.0 (WP) the signature was changed from:
1739 * getMinDaysAdvance($skipsession = false) to:
1740 * getMinDaysAdvance($no_closing_dates = false)
1741 * This was made to return the proper number of days in advance
1742 * in case the property is currently closed.
1743 */
1744 public static function getMinDaysAdvance($no_closing_dates = false) {
1745 // cache value in static var
1746 static $getMinDaysAdvance = null;
1747
1748 if ($getMinDaysAdvance) {
1749 return $getMinDaysAdvance;
1750 }
1751 //
1752
1753 $dbo = JFactory::getDbo();
1754
1755 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='mindaysadvance';";
1756 $dbo->setQuery($q);
1757 $dbo->execute();
1758 $mind = $dbo->getNumRows() ? (int)$dbo->loadResult() : 0;
1759
1760 // update cached var
1761 $getMinDaysAdvance = $mind;
1762
1763 if ($no_closing_dates) {
1764 // do not check the closing dates
1765 return $mind;
1766 }
1767
1768 // check if the property is currently closed
1769 $cur_closed_dates = self::getClosingDates();
1770 if (is_array($cur_closed_dates) && count($cur_closed_dates)) {
1771 $today_midnight = mktime(0, 0, 0);
1772 $closed_until = null;
1773 foreach ($cur_closed_dates as $kcd => $vcd) {
1774 if ($today_midnight >= $vcd['from'] && $today_midnight <= $vcd['to']) {
1775 // closing period found
1776 $closed_until = $vcd['to'];
1777 break;
1778 }
1779 }
1780 if ($closed_until !== null) {
1781 // count the number of days until property is closed
1782 $mind = 0;
1783 $today_info = getdate($today_midnight);
1784 while ($today_info[0] <= $closed_until) {
1785 $mind++;
1786 $today_info = getdate(mktime(0, 0, 0, $today_info['mon'], ($today_info['mday'] + 1), $today_info['year']));
1787 }
1788 }
1789 }
1790
1791 // update cached var
1792 $getMinDaysAdvance = $mind;
1793
1794 return $mind;
1795 }
1796
1797 public static function getDefaultNightsCalendar($skipsession = false) {
1798 if ($skipsession) {
1799 $dbo = JFactory::getDbo();
1800 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='autodefcalnights';";
1801 $dbo->setQuery($q);
1802 $dbo->execute();
1803 $s = $dbo->loadAssocList();
1804 return (int)$s[0]['setting'];
1805 } else {
1806 $session = JFactory::getSession();
1807 $sval = $session->get('vbdefaultNightsCalendar', '');
1808 if (!empty($sval)) {
1809 return (int)$sval;
1810 } else {
1811 $dbo = JFactory::getDbo();
1812 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='autodefcalnights';";
1813 $dbo->setQuery($q);
1814 $dbo->execute();
1815 $s = $dbo->loadAssocList();
1816 $session->set('vbdefaultNightsCalendar', $s[0]['setting']);
1817 return (int)$s[0]['setting'];
1818 }
1819 }
1820 }
1821
1822 public static function getSearchNumRooms($skipsession = true)
1823 {
1824 $dbo = JFactory::getDbo();
1825 if ($skipsession) {
1826 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='numrooms';";
1827 $dbo->setQuery($q);
1828 $dbo->execute();
1829 $s = $dbo->loadAssocList();
1830 return (int)$s[0]['setting'];
1831 }
1832 $session = JFactory::getSession();
1833 $sval = $session->get('vbsearchNumRooms', '');
1834 if (!empty($sval)) {
1835 return (int)$sval;
1836 }
1837 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='numrooms';";
1838 $dbo->setQuery($q);
1839 $dbo->execute();
1840 $s = $dbo->loadAssocList();
1841 $session->set('vbsearchNumRooms', $s[0]['setting']);
1842 return (int)$s[0]['setting'];
1843 }
1844
1845 public static function getSearchNumAdults($skipsession = true)
1846 {
1847 $dbo = JFactory::getDbo();
1848 if ($skipsession) {
1849 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='numadults';";
1850 $dbo->setQuery($q);
1851 $dbo->execute();
1852 $s = $dbo->loadAssocList();
1853 return $s[0]['setting'];
1854 }
1855 $session = JFactory::getSession();
1856 $sval = $session->get('vbsearchNumAdults', '');
1857 if (!empty($sval)) {
1858 return $sval;
1859 }
1860 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='numadults';";
1861 $dbo->setQuery($q);
1862 $dbo->execute();
1863 $s = $dbo->loadAssocList();
1864 $session->set('vbsearchNumAdults', $s[0]['setting']);
1865 return $s[0]['setting'];
1866 }
1867
1868 public static function getSearchNumChildren($skipsession = true)
1869 {
1870 $dbo = JFactory::getDbo();
1871 if ($skipsession) {
1872 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='numchildren';";
1873 $dbo->setQuery($q);
1874 $dbo->execute();
1875 $s = $dbo->loadAssocList();
1876 return $s[0]['setting'];
1877 }
1878 $session = JFactory::getSession();
1879 $sval = $session->get('vbsearchNumChildren', '');
1880 if (!empty($sval)) {
1881 return $sval;
1882 }
1883 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='numchildren';";
1884 $dbo->setQuery($q);
1885 $dbo->execute();
1886 $s = $dbo->loadAssocList();
1887 $session->set('vbsearchNumChildren', $s[0]['setting']);
1888 return $s[0]['setting'];
1889 }
1890
1891 public static function getSmartSearchType($skipsession = true)
1892 {
1893 $dbo = JFactory::getDbo();
1894 if ($skipsession) {
1895 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='smartsearch';";
1896 $dbo->setQuery($q);
1897 $dbo->execute();
1898 $s = $dbo->loadAssocList();
1899 return $s[0]['setting'];
1900 }
1901 $session = JFactory::getSession();
1902 $sval = $session->get('vbsmartSearchType', '');
1903 if (!empty($sval)) {
1904 return $sval;
1905 }
1906 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='smartsearch';";
1907 $dbo->setQuery($q);
1908 $dbo->execute();
1909 $s = $dbo->loadAssocList();
1910 $session->set('vbsmartSearchType', $s[0]['setting']);
1911 return $s[0]['setting'];
1912 }
1913
1914 /**
1915 * Returns the maximum advance booking notice (offset) as a period.
1916 * It either returns the global configuration setting, or the value
1917 * defined at room-level, if any and if requested.
1918 *
1919 * @param int $idroom optional room ID for room-level setting.
1920 *
1921 * @return string the maximum booking notice period (i.e. +1Y).
1922 *
1923 * @since 1.16.3 (J) - 1.6.3 (WP) replaced previous argument $skipsession with
1924 * $idroom to support room-level settings.
1925 */
1926 public static function getMaxDateFuture($idroom = 0)
1927 {
1928 static $max_adv_book_pool = [];
1929
1930 $pool_id = !empty($idroom) && is_numeric($idroom) ? (int)$idroom : 'global';
1931
1932 if (isset($max_adv_book_pool[$pool_id])) {
1933 return $max_adv_book_pool[$pool_id];
1934 }
1935
1936 $config = VBOFactory::getConfig();
1937
1938 if ($pool_id === 'global') {
1939 $max_adv_book_pool[$pool_id] = $config->get('maxdate', '');
1940 } else {
1941 $room_level_max_notice = $config->get("room_{$pool_id}_max_adv_notice");
1942 if (!empty($room_level_max_notice)) {
1943 // setting is defined at room-level
1944 $max_adv_book_pool[$pool_id] = $room_level_max_notice;
1945 } else {
1946 // recurse for global setting
1947 $max_adv_book_pool[$pool_id] = self::getMaxDateFuture();
1948 }
1949 }
1950
1951 return $max_adv_book_pool[$pool_id];
1952 }
1953
1954 /**
1955 * Validates the maximum advance booking notice against the requested check-in
1956 * timestamp. The method supports the validation at both room and property levels.
1957 *
1958 * @param int $checkints the check-in date timestamp.
1959 * @param int $idroom the optional room ID for room-level validation.
1960 *
1961 * @return string error date string in case of validation failure.
1962 *
1963 * @since 1.16.3 (J) - 1.6.3 (WP) introduced $idroom argument for room-level.
1964 */
1965 public static function validateMaxDateBookings($checkints, $idroom = 0)
1966 {
1967 $datelim = self::getMaxDateFuture($idroom);
1968 $datelim = empty($datelim) ? '+2y' : $datelim;
1969 $numlim = (int)substr($datelim, 1, (strlen($datelim) - 2));
1970 $quantlim = substr($datelim, -1, 1);
1971
1972 $now = getdate();
1973 if ($quantlim == 'w') {
1974 $until_ts = strtotime("+$numlim weeks") + 86399;
1975 } else {
1976 $use_mon = $quantlim == 'm' ? ($now['mon'] + $numlim) : $now['mon'];
1977 $use_day = $quantlim == 'd' ? ($now['mday'] + $numlim) : $now['mday'];
1978 $use_year = $quantlim == 'y' ? ($now['year'] + $numlim) : $now['year'];
1979 $until_ts = mktime(23, 59, 59, $use_mon, $use_day, $use_year);
1980 }
1981
1982 if ($until_ts > $now[0] && $checkints > $until_ts) {
1983 $vbo_df = self::getDateFormat();
1984 $df = $vbo_df == "%d/%m/%Y" ? 'd/m/Y' : ($vbo_df == "%m/%d/%Y" ? 'm/d/Y' : 'Y-m-d');
1985
1986 // error, return the maximum date in the future allowed
1987 return date($df, $until_ts);
1988 }
1989
1990 // validation passed
1991 return '';
1992 }
1993
1994 public static function validateMinDaysAdvance($checkints) {
1995 $mindadv = self::getMinDaysAdvance(true);
1996 if ($mindadv > 0) {
1997 $tsinfo = getdate($checkints);
1998 $limit_ts = mktime($tsinfo['hours'], $tsinfo['minutes'], $tsinfo['seconds'], date('n'), ((int)date('j') + $mindadv), date('Y'));
1999 if ($checkints < $limit_ts) {
2000 return $mindadv;
2001 }
2002 }
2003
2004 return '';
2005 }
2006
2007 /**
2008 * The only supported calendar type has been changed to jQuery UI.
2009 *
2010 * @since 1.13
2011 */
2012 public static function calendarType($skipsession = false)
2013 {
2014 return 'jqueryui';
2015 }
2016
2017 public static function getSiteLogo()
2018 {
2019 static $siteLogo = null;
2020
2021 if ($siteLogo !== null) {
2022 return $siteLogo;
2023 }
2024
2025 $siteLogo = VBOFactory::getConfig()->get('sitelogo', '');
2026
2027 return $siteLogo;
2028 }
2029
2030 public static function getBackendLogo()
2031 {
2032 static $backLogo = null;
2033
2034 if ($backLogo !== null) {
2035 return $backLogo;
2036 }
2037
2038 $backLogo = VBOFactory::getConfig()->get('backlogo', '');
2039
2040 return $backLogo;
2041 }
2042
2043 public static function numCalendars()
2044 {
2045 static $numCalendars = null;
2046
2047 if ($numCalendars !== null) {
2048 return $numCalendars;
2049 }
2050
2051 $numCalendars = VBOFactory::getConfig()->getUInt('numcalendars', 0);
2052
2053 return $numCalendars;
2054 }
2055
2056 public static function getFirstWeekDay()
2057 {
2058 static $firstWeekDay = null;
2059
2060 if ($firstWeekDay !== null) {
2061 return $firstWeekDay;
2062 }
2063
2064 $firstWeekDay = VBOFactory::getConfig()->getUInt('firstwday', 0);
2065
2066 return $firstWeekDay;
2067 }
2068
2069 public static function showPartlyReserved()
2070 {
2071 return VBOFactory::getConfig()->getBool('showpartlyreserved', false);
2072 }
2073
2074 public static function showStatusCheckinoutOnly()
2075 {
2076 return VBOFactory::getConfig()->getBool('showcheckinoutonly', false);
2077 }
2078
2079 public static function getDisclaimer($vbo_tn = null)
2080 {
2081 $dbo = JFactory::getDbo();
2082
2083 $q = "SELECT `id`,`setting` FROM `#__vikbooking_texts` WHERE `param`='disclaimer';";
2084 $dbo->setQuery($q);
2085 $ft = $dbo->loadAssocList();
2086 if (!$ft) {
2087 return '';
2088 }
2089
2090 if (!is_object($vbo_tn)) {
2091 $vbo_tn = self::getTranslator();
2092 }
2093 $vbo_tn->translateContents($ft, '#__vikbooking_texts');
2094
2095 return $ft[0]['setting'];
2096 }
2097
2098 public static function showFooter()
2099 {
2100 return VBOFactory::getConfig()->getBool('showfooter', false);
2101 }
2102
2103 public static function getPriceName($idp, $vbo_tn = null)
2104 {
2105 $dbo = JFactory::getDbo();
2106
2107 $q = "SELECT `id`,`name` FROM `#__vikbooking_prices` WHERE `id`=" . (int)$idp;
2108 $dbo->setQuery($q, 0, 1);
2109 $n = $dbo->loadAssocList();
2110 if ($n) {
2111 if (is_object($vbo_tn)) {
2112 $vbo_tn->translateContents($n, '#__vikbooking_prices');
2113 }
2114 return $n[0]['name'];
2115 }
2116
2117 return "";
2118 }
2119
2120 public static function getPriceAttr($idp, $vbo_tn = null)
2121 {
2122 $dbo = JFactory::getDbo();
2123
2124 $q = "SELECT `id`,`attr` FROM `#__vikbooking_prices` WHERE `id`=" . (int)$idp;
2125 $dbo->setQuery($q, 0, 1);
2126 $n = $dbo->loadAssocList();
2127 if ($n) {
2128 if (is_object($vbo_tn)) {
2129 $vbo_tn->translateContents($n, '#__vikbooking_prices');
2130 }
2131 return $n[0]['attr'];
2132 }
2133
2134 return "";
2135 }
2136
2137 /**
2138 * Fetches the requested rate plan information.
2139 *
2140 * @param int $idp the rate plan ID.
2141 * @param object $vbo_tn the Vik Booking translator object.
2142 *
2143 * @return array empty array or associative record.
2144 */
2145 public static function getPriceInfo($idp, $vbo_tn = null)
2146 {
2147 // cache values
2148 static $priceInfos = [];
2149
2150 $idp = (int)$idp;
2151
2152 if ($priceInfos && isset($priceInfos[$idp])) {
2153 return $priceInfos[$idp];
2154 }
2155
2156 $dbo = JFactory::getDbo();
2157
2158 $q = "SELECT * FROM `#__vikbooking_prices` WHERE `id`={$idp}";
2159 $dbo->setQuery($q, 0, 1);
2160 $rplan = $dbo->loadAssocList();
2161 if (!$rplan) {
2162 return [];
2163 }
2164
2165 if (is_object($vbo_tn)) {
2166 $vbo_tn->translateContents($rplan, '#__vikbooking_prices');
2167 }
2168
2169 // set cached value
2170 $priceInfos[$idp] = $rplan[0];
2171
2172 return $rplan[0];
2173 }
2174
2175 public static function getAliq($idal)
2176 {
2177 $dbo = JFactory::getDbo();
2178
2179 $q = "SELECT `aliq` FROM `#__vikbooking_iva` WHERE `id`=" . (int)$idal . ";";
2180 $dbo->setQuery($q);
2181 $aliq = $dbo->loadResult();
2182 if ($aliq) {
2183 return $aliq;
2184 }
2185
2186 return 0;
2187 }
2188
2189 public static function getTimeOpenStore($skipsession = false)
2190 {
2191 // cache value in static var
2192 static $getTimeOpenStore = null;
2193
2194 if ($getTimeOpenStore) {
2195 return $getTimeOpenStore;
2196 }
2197 //
2198
2199 $dbo = JFactory::getDbo();
2200 $session = JFactory::getSession();
2201
2202 if ($skipsession) {
2203 $setting = VBOFactory::getConfig()->get('timeopenstore', '');
2204 if (empty($setting) && $setting != "0") {
2205 return false;
2206 } else {
2207 $x = explode("-", $setting);
2208 if (!empty($x[1]) && $x[1] != "0") {
2209 $getTimeOpenStore = $x;
2210 return $x;
2211 }
2212 }
2213 } else {
2214 $sval = $session->get('vbgetTimeOpenStore', '');
2215 if (!empty($sval)) {
2216 return $sval;
2217 } else {
2218 $setting = VBOFactory::getConfig()->get('timeopenstore', '');
2219 if (empty($setting) && $setting != "0") {
2220 return false;
2221 } else {
2222 $x = explode("-", $setting);
2223 if (!empty($x[1]) && $x[1] != "0") {
2224 $session->set('vbgetTimeOpenStore', $x);
2225 $getTimeOpenStore = $x;
2226 return $x;
2227 }
2228 }
2229 }
2230 }
2231 return false;
2232 }
2233
2234 public static function getHoursMinutes($secs) {
2235 if ($secs >= 3600) {
2236 $op = $secs / 3600;
2237 $hours = floor($op);
2238 $less = $hours * 3600;
2239 $newsec = $secs - $less;
2240 $optwo = $newsec / 60;
2241 $minutes = floor($optwo);
2242 } else {
2243 $hours = "0";
2244 $optwo = $secs / 60;
2245 $minutes = floor($optwo);
2246 }
2247 $x[] = $hours;
2248 $x[] = $minutes;
2249 return $x;
2250 }
2251
2252 public static function getClosingDates()
2253 {
2254 // cache value in static var
2255 static $getClosingDates = null;
2256
2257 if (is_array($getClosingDates)) {
2258 return $getClosingDates;
2259 }
2260
2261 $dbo = JFactory::getDbo();
2262
2263 $allcd = [];
2264
2265 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='closingdates';";
2266 $dbo->setQuery($q);
2267 $s = $dbo->loadAssocList();
2268 if ($s && !empty($s[0]['setting'])) {
2269 $allcd = json_decode($s[0]['setting'], true);
2270 $allcd = is_array($allcd) ? $allcd : array();
2271 $base_ts = mktime(0, 0, 0, date("n"), date("j"), date("Y"));
2272 foreach ($allcd as $k => $v) {
2273 if ($v['to'] < $base_ts) {
2274 unset($allcd[$k]);
2275 }
2276 }
2277 $allcd = array_values($allcd);
2278 }
2279
2280 $getClosingDates = $allcd;
2281
2282 return $allcd;
2283 }
2284
2285 public static function parseJsClosingDates() {
2286 $cd = self::getClosingDates();
2287 if (count($cd) > 0) {
2288 $cdjs = array();
2289 foreach ($cd as $k => $v) {
2290 $cdjs[] = array(date('Y-m-d', $v['from']), date('Y-m-d', $v['to']));
2291 }
2292 return $cdjs;
2293 }
2294 return array();
2295 }
2296
2297 public static function validateClosingDates($checkints, $checkoutts, $df = null)
2298 {
2299 $cd = self::getClosingDates();
2300 if (!count($cd)) {
2301 return '';
2302 }
2303 $df = empty($df) ? 'Y-m-d' : $df;
2304 $margin_seconds = 22 * 60 * 60;
2305 foreach ($cd as $k => $v) {
2306 $inner_closed = ($checkints >= $v['from'] && $checkints <= ($v['to'] + $margin_seconds));
2307 $outer_closed = ($checkoutts >= $v['from'] && $checkoutts <= ($v['to'] + $margin_seconds));
2308 $middle_closed = ($checkints <= $v['from'] && $checkoutts >= ($v['to'] + $margin_seconds));
2309 if ($inner_closed || $outer_closed || $middle_closed) {
2310 return date($df, $v['from']) . ' - ' . date($df, $v['to']);
2311 }
2312 }
2313 return '';
2314 }
2315
2316 /**
2317 * Whether the categories dropdown filter menu should be displayed.
2318 *
2319 * @param boolean $skipsession [optional] re-read the configuration setting.
2320 *
2321 * @return boolean
2322 */
2323 public static function showCategoriesFront($skipsession = true)
2324 {
2325 $dbo = JFactory::getDbo();
2326 if ($skipsession) {
2327 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='showcategories';";
2328 $dbo->setQuery($q);
2329 $dbo->execute();
2330 if ($dbo->getNumRows() > 0) {
2331 $s = $dbo->loadAssocList();
2332 return (intval($s[0]['setting']) == 1);
2333 }
2334 return false;
2335 }
2336 $session = JFactory::getSession();
2337 $sval = $session->get('vbshowCategoriesFront', '');
2338 if (strlen($sval)) {
2339 return (intval($sval) == 1 ? true : false);
2340 }
2341 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='showcategories';";
2342 $dbo->setQuery($q);
2343 $dbo->execute();
2344 if ($dbo->getNumRows() > 0) {
2345 $s = $dbo->loadAssocList();
2346 $session->set('vbshowCategoriesFront', $s[0]['setting']);
2347 return (intval($s[0]['setting']) == 1);
2348 }
2349 return false;
2350 }
2351
2352 /**
2353 * Whether the number of children dropdown menu should be displayed.
2354 * Defaults to skip the session values and to re-read the configuration setting.
2355 *
2356 * @param boolean $skipsession [optional] re-read the configuration setting.
2357 *
2358 * @return boolean
2359 */
2360 public static function showChildrenFront($skipsession = true)
2361 {
2362 $dbo = JFactory::getDbo();
2363 if ($skipsession) {
2364 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='showchildren';";
2365 $dbo->setQuery($q);
2366 $dbo->execute();
2367 if ($dbo->getNumRows() > 0) {
2368 $s = $dbo->loadAssocList();
2369 return (intval($s[0]['setting']) == 1);
2370 }
2371 return false;
2372 }
2373 $session = JFactory::getSession();
2374 $sval = $session->get('vbshowChildrenFront', '');
2375 if (strlen($sval)) {
2376 return (intval($sval) == 1 ? true : false);
2377 }
2378 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='showchildren';";
2379 $dbo->setQuery($q);
2380 $dbo->execute();
2381 if ($dbo->getNumRows() > 0) {
2382 $s = $dbo->loadAssocList();
2383 $session->set('vbshowChildrenFront', $s[0]['setting']);
2384 return (intval($s[0]['setting']) == 1);
2385 }
2386 return false;
2387 }
2388
2389 public static function allowBooking() {
2390 $dbo = JFactory::getDbo();
2391 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='allowbooking';";
2392 $dbo->setQuery($q);
2393 $dbo->execute();
2394 if ($dbo->getNumRows() > 0) {
2395 $s = $dbo->loadAssocList();
2396 return (intval($s[0]['setting']) == 1 ? true : false);
2397 } else {
2398 return false;
2399 }
2400 }
2401
2402 public static function getDisabledBookingMsg($vbo_tn = null) {
2403 $dbo = JFactory::getDbo();
2404 $q = "SELECT `id`,`setting` FROM `#__vikbooking_texts` WHERE `param`='disabledbookingmsg';";
2405 $dbo->setQuery($q);
2406 $dbo->execute();
2407 $s = $dbo->loadAssocList();
2408 if (!is_object($vbo_tn)) {
2409 $vbo_tn = self::getTranslator();
2410 }
2411 $vbo_tn->translateContents($s, '#__vikbooking_texts');
2412 return $s[0]['setting'];
2413 }
2414
2415 /**
2416 * Returns the text for the guests allowed policy.
2417 *
2418 * @param mixed $vbo_tn null or VikBookingTranslator instance to translate the text.
2419 *
2420 * @return string either the raw or the translated policy text.
2421 *
2422 * @since 1.16.5 (WP) - 1.6.5 (WP)
2423 */
2424 public static function getGuestsAllowedPolicy($vbo_tn = null)
2425 {
2426 $dbo = JFactory::getDbo();
2427
2428 $q = $dbo->getQuery(true)
2429 ->select([$dbo->qn('id'), $dbo->qn('setting')])
2430 ->from($dbo->qn('#__vikbooking_texts'))
2431 ->where($dbo->qn('param') . ' = ' . $dbo->q('guests_allowed_policy'));
2432
2433 $dbo->setQuery($q);
2434 $text_record = $dbo->loadAssocList();
2435
2436 if (!$text_record) {
2437 // missing record, insert it with empty values
2438 if (self::isAdmin()) {
2439 $record = new stdClass;
2440 $record->param = 'guests_allowed_policy';
2441 $record->exp = JText::translate('VBO_GUESTS_POLICY');
2442 $record->setting = '';
2443 $dbo->insertObject('#__vikbooking_texts', $record, 'id');
2444 }
2445
2446 return '';
2447 }
2448
2449 if (is_object($vbo_tn)) {
2450 // translate the record only if requested
2451 $vbo_tn->translateContents($text_record, '#__vikbooking_texts');
2452 }
2453
2454 return $text_record[0]['setting'];
2455 }
2456
2457 public static function getDateFormat($skipsession = true)
2458 {
2459 // cache value in static var
2460 static $getDateFormat = null;
2461
2462 if ($getDateFormat) {
2463 return $getDateFormat;
2464 }
2465
2466 $dbo = JFactory::getDbo();
2467
2468 if ($skipsession) {
2469 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='dateformat'";
2470 $dbo->setQuery($q, 0, 1);
2471 $getDateFormat = $dbo->loadResult();
2472
2473 return $getDateFormat;
2474 }
2475
2476 $session = JFactory::getSession();
2477 $sval = $session->get('vbgetDateFormat', '');
2478 if (!empty($sval)) {
2479 return $sval;
2480 }
2481
2482 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='dateformat'";
2483 $dbo->setQuery($q, 0, 1);
2484 $getDateFormat = $dbo->loadResult();
2485
2486 $session->set('vbgetDateFormat', $getDateFormat);
2487
2488 return $getDateFormat;
2489 }
2490
2491 public static function getDateSeparator($skipsession = true)
2492 {
2493 // cache value in static var
2494 static $getDateSeparator = null;
2495
2496 if ($getDateSeparator) {
2497 return $getDateSeparator;
2498 }
2499
2500 $dbo = JFactory::getDbo();
2501
2502 if ($skipsession) {
2503 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='datesep'";
2504 $dbo->setQuery($q, 0, 1);
2505 $val = $dbo->loadResult();
2506
2507 $getDateSeparator = empty($val) ? "/" : $val;
2508
2509 return $getDateSeparator;
2510 }
2511
2512 $session = JFactory::getSession();
2513 $sval = $session->get('vbgetDateSep', '');
2514 if (!empty($sval)) {
2515 return $sval;
2516 }
2517
2518 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='datesep'";
2519 $dbo->setQuery($q, 0, 1);
2520 $val = $dbo->loadResult();
2521
2522 $getDateSeparator = empty($val) ? "/" : $val;
2523
2524 $session->set('vbgetDateSep', $getDateSeparator);
2525
2526 return $getDateSeparator;
2527 }
2528
2529 public static function getHoursMoreRb($skipsession = false)
2530 {
2531 // cache value in static var
2532 static $getHoursMoreRb = null;
2533
2534 if ($getHoursMoreRb !== null) {
2535 return $getHoursMoreRb;
2536 }
2537
2538 $dbo = JFactory::getDbo();
2539
2540 if ($skipsession) {
2541 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='hoursmorebookingback'";
2542 $dbo->setQuery($q, 0, 1);
2543 $getHoursMoreRb = (int)$dbo->loadResult();
2544
2545 return $getHoursMoreRb;
2546 }
2547
2548 $session = JFactory::getSession();
2549 $sval = $session->get('getHoursMoreRb', '');
2550 if (strlen($sval)) {
2551 return $sval;
2552 }
2553
2554 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='hoursmorebookingback'";
2555 $dbo->setQuery($q, 0, 1);
2556 $getHoursMoreRb = (int)$dbo->loadResult();
2557
2558 $session->set('getHoursMoreRb', $getHoursMoreRb);
2559
2560 return $getHoursMoreRb;
2561 }
2562
2563 public static function getHoursRoomAvail()
2564 {
2565 $dbo = JFactory::getDbo();
2566
2567 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='hoursmoreroomavail'";
2568 $dbo->setQuery($q, 0, 1);
2569
2570 return (int)$dbo->loadResult();
2571 }
2572
2573 public static function getFrontTitle($vbo_tn = null) {
2574 // cache value in static var
2575 static $getFrontTitle = null;
2576
2577 if ($getFrontTitle) {
2578 return $getFrontTitle;
2579 }
2580 //
2581
2582 $dbo = JFactory::getDbo();
2583 $q = "SELECT `id`,`setting` FROM `#__vikbooking_texts` WHERE `param`='fronttitle';";
2584 $dbo->setQuery($q);
2585 $dbo->execute();
2586 $ft = $dbo->loadAssocList();
2587 if (!is_object($vbo_tn)) {
2588 $vbo_tn = self::getTranslator();
2589 }
2590 $vbo_tn->translateContents($ft, '#__vikbooking_texts');
2591
2592 $getFrontTitle = $ft[0]['setting'];
2593
2594 return $ft[0]['setting'];
2595 }
2596
2597 public static function getFrontTitleTag()
2598 {
2599 return VBOFactory::getConfig()->get('fronttitletag', '');
2600 }
2601
2602 public static function getFrontTitleTagClass()
2603 {
2604 return VBOFactory::getConfig()->get('fronttitletagclass', '');
2605 }
2606
2607 public static function getCurrencyName()
2608 {
2609 static $currencyName = null;
2610
2611 if ($currencyName !== null) {
2612 return $currencyName;
2613 }
2614
2615 $currencyName = VBOFactory::getConfig()->get('currencyname', '');
2616
2617 return $currencyName;
2618 }
2619
2620 public static function getCurrencySymb($skipsession = false) {
2621 // cache value in static var
2622 static $getCurrencySymb = null;
2623
2624 if ($getCurrencySymb) {
2625 return $getCurrencySymb;
2626 }
2627
2628 $dbo = JFactory::getDbo();
2629
2630 if ($skipsession) {
2631 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='currencysymb'";
2632 $dbo->setQuery($q, 0, 1);
2633 $csymb = $dbo->loadResult();
2634
2635 $getCurrencySymb = $csymb;
2636
2637 return $csymb;
2638 }
2639
2640 $session = JFactory::getSession();
2641 $sval = $session->get('vbgetCurrencySymb', '');
2642 if (!empty($sval)) {
2643 return $sval;
2644 }
2645
2646 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='currencysymb'";
2647 $dbo->setQuery($q, 0, 1);
2648 $csymb = $dbo->loadResult();
2649
2650 // update session value, cache it and return it
2651 $session->set('vbgetCurrencySymb', $csymb);
2652 $getCurrencySymb = $csymb;
2653
2654 return $csymb;
2655 }
2656
2657 public static function getNumberFormatData($skipsession = false) {
2658 // cache value in static var
2659 static $getNumberFormatData = null;
2660
2661 if ($getNumberFormatData) {
2662 return $getNumberFormatData;
2663 }
2664 //
2665
2666 $dbo = JFactory::getDbo();
2667
2668 if ($skipsession) {
2669 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='numberformat'";
2670 $dbo->setQuery($q, 0, 1);
2671 $dbo->execute();
2672 $numfdata = $dbo->loadResult();
2673
2674 // cache value and return it
2675 $getNumberFormatData = $numfdata;
2676
2677 return $numfdata;
2678 }
2679
2680 $session = JFactory::getSession();
2681 $sval = $session->get('getNumberFormatData', '');
2682 if (!empty($sval)) {
2683 return $sval;
2684 }
2685
2686 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='numberformat'";
2687 $dbo->setQuery($q, 0, 1);
2688 $dbo->execute();
2689 $numfdata = $dbo->loadResult();
2690
2691 // update session value, cache it and return it
2692 $session->set('getNumberFormatData', $numfdata);
2693 $getNumberFormatData = $numfdata;
2694
2695 return $numfdata;
2696 }
2697
2698 /**
2699 * It is possible to hide the decimals if they are like "N.00".
2700 *
2701 * @return int 0 if disabled, 1 if enabled (default).
2702 *
2703 * @since 1.14 (J) - 1.4.0 (WP)
2704 */
2705 public static function hideEmptyDecimals()
2706 {
2707 // cache value in static var
2708 static $hideEmptyDecimals = null;
2709
2710 if ($hideEmptyDecimals !== null) {
2711 return $hideEmptyDecimals;
2712 }
2713 //
2714
2715 $dbo = JFactory::getDbo();
2716
2717 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='noemptydecimals';";
2718 $dbo->setQuery($q);
2719 $hideEmptyDecimals = $dbo->loadResult();
2720 if ($hideEmptyDecimals !== null) {
2721 $hideEmptyDecimals = (int)$hideEmptyDecimals;
2722 return $hideEmptyDecimals;
2723 }
2724
2725 $q = "INSERT INTO `#__vikbooking_config` (`param`,`setting`) VALUES ('noemptydecimals', '1');";
2726 $dbo->setQuery($q);
2727 $dbo->execute();
2728
2729 $hideEmptyDecimals = 1;
2730
2731 return $hideEmptyDecimals;
2732 }
2733
2734 public static function numberFormat($num, $skipsession = false)
2735 {
2736 $formatvals = self::getNumberFormatData($skipsession);
2737 $formatparts = explode(':', $formatvals);
2738
2739 if ((int)$formatparts[0] > 0 && (floatval($num) - intval($num)) == 0 && self::hideEmptyDecimals()) {
2740 // number has got no decimals
2741 $formatparts[0] = 0;
2742 }
2743
2744 return number_format((float)$num, (int)$formatparts[0], $formatparts[1], $formatparts[2]);
2745 }
2746
2747 /**
2748 * Given a date timestamp, returns the formatted date.
2749 *
2750 * @param int|string $ts the date representation in Unix timestamp.
2751 * @param bool $wtime if true, a formatted date-time will be returned.
2752 *
2753 * @return string the formatted date according to settings.
2754 *
2755 * @since 1.16.0 (J) - 1.6.0 (WP)
2756 */
2757 public static function formatDateTs($ts, $wtime = false)
2758 {
2759 $nowdf = self::getDateFormat();
2760 if ($nowdf == "%d/%m/%Y") {
2761 $df = 'd/m/Y';
2762 } elseif ($nowdf == "%m/%d/%Y") {
2763 $df = 'm/d/Y';
2764 } else {
2765 $df = 'Y/m/d';
2766 }
2767 $datesep = self::getDateSeparator();
2768
2769 $time_f = $wtime ? ' H:i' : '';
2770
2771 return date(str_replace("/", $datesep, $df) . $time_f, $ts);
2772 }
2773
2774 /**
2775 * Given a country name, 3-char or 2-char code, this method attempts
2776 * to guess the best language to assign to the booking according to
2777 * what languages are installed on the website. This way, cron jobs
2778 * and any other email notification will be correctly and automatically
2779 * sent to the guest in the proper language without any manual action.
2780 * To comply with VCM (>= 1.8.3), also VBO supports this feature.
2781 *
2782 * @param string $country the country name, 3-char or 2-char code.
2783 * @param string $locale optional locale lang tag of the guest.
2784 *
2785 * @return mixed the best lang-tag string to use or null.
2786 *
2787 * @since 1.16.0 (J) - 1.6.0 (WP)
2788 */
2789 public static function guessBookingLangFromCountry($country, $locale = null)
2790 {
2791 if (empty($country)) {
2792 return null;
2793 }
2794
2795 // get all the available languages
2796 $known_langs = self::getVboApplication()->getKnownLanguages();
2797 if (!is_array($known_langs) || !count($known_langs)) {
2798 return null;
2799 }
2800
2801 // check if the booking included a supported "locale" for the guest
2802 if (!empty($locale)) {
2803 // make the locale compatible with the language tags format
2804 $locale = str_replace('_', '-', $locale);
2805 foreach ($known_langs as $ltag => $ldet) {
2806 if (stripos($ltag, $locale) !== false || stripos($locale, $ltag) !== false) {
2807 // we support this language tag, so we just use it
2808 return $ltag;
2809 }
2810 }
2811 }
2812
2813 // build similarities with country-languages
2814 $similarities = array(
2815 'AU' => 'en',
2816 'GB' => 'en',
2817 'IE' => 'en',
2818 'NZ' => 'en',
2819 'US' => 'en',
2820 'CA' => array(
2821 'en',
2822 'fr',
2823 ),
2824 'CL' => 'es',
2825 'AR' => 'es',
2826 'PE' => 'es',
2827 'MX' => 'es',
2828 'CR' => 'es',
2829 'CO' => 'es',
2830 'EC' => 'es',
2831 'BO' => 'es',
2832 'CU' => 'es',
2833 'VE' => 'es',
2834 'BE' => 'fr',
2835 'LU' => 'fr',
2836 'CH' => array(
2837 'de',
2838 'it',
2839 'fr',
2840 ),
2841 'AT' => 'de',
2842 'GR' => 'el',
2843 'GL' => 'dk',
2844 );
2845
2846 // fetch values from db
2847 $dbo = JFactory::getDbo();
2848 $q = "SELECT `country_name`, `country_3_code`, `country_2_code` FROM `#__vikbooking_countries` WHERE `country_name`=" . $dbo->quote($country) . " OR `country_3_code`=" . $dbo->quote($country) . " OR `country_2_code`=" . $dbo->quote($country);
2849 $dbo->setQuery($q, 0, 1);
2850 $dbo->execute();
2851 if (!$dbo->getNumRows()) {
2852 return null;
2853 }
2854 $country_record = $dbo->loadAssoc();
2855
2856 // assign country name/code versions
2857 $country_name = $country_record['country_name'];
2858 $country_3char = strtoupper($country_record['country_3_code']);
2859 $country_2char = strtoupper($country_record['country_2_code']);
2860
2861 // build an associative array of language tags and related match-score
2862 $langtags_score = array();
2863 foreach ($known_langs as $ltag => $ldet) {
2864 // default language tag score is 0 for no matches
2865 $langtags_score[$ltag] = 0;
2866 // get language and country codes
2867 $lang_country_codes = explode('-', str_replace('_', '-', strtoupper($ltag)));
2868
2869 // check matches with the installed language details
2870 if ($lang_country_codes[0] == $country_2char || $lang_country_codes[0] == $country_3char) {
2871 // increase language tag score
2872 $langtags_score[$ltag]++;
2873 }
2874 if (!empty($lang_country_codes[1]) && ($lang_country_codes[1] == $country_2char || $lang_country_codes[1] == $country_3char)) {
2875 // increase language tag score
2876 $langtags_score[$ltag]++;
2877 }
2878 if (!empty($ldet['locale'])) {
2879 // sanitize locale for matching the 2-char code safely
2880 $ldet['locale'] = str_replace(array('standard', 'euro', 'iso', 'utf'), '', strtolower($ldet['locale']));
2881 if (stripos($ldet['locale'], $country_2char) !== false || stripos($ldet['locale'], $country_name) !== false) {
2882 // increase language tag score
2883 $langtags_score[$ltag]++;
2884 }
2885 }
2886 if (!empty($ldet['name']) && stripos($ldet['name'], $country_name) !== false) {
2887 // increase language tag score
2888 $langtags_score[$ltag]++;
2889 }
2890 if (!empty($ldet['nativeName']) && stripos($ldet['nativeName'], $country_name) !== false) {
2891 // increase language tag score
2892 $langtags_score[$ltag]++;
2893 }
2894
2895 // check language similarities between countries
2896 if (isset($similarities[$country_2char])) {
2897 $spoken_tags = !is_array($similarities[$country_2char]) ? array($similarities[$country_2char]) : $similarities[$country_2char];
2898 // check if language tag(s) is available for this spoken language
2899 foreach ($spoken_tags as $spoken_tag) {
2900 if ($lang_country_codes[0] == strtoupper($spoken_tag)) {
2901 // increase language tag score
2902 $langtags_score[$ltag]++;
2903 }
2904 }
2905 }
2906 }
2907
2908 // make sure at least one language tag has got some points
2909 if (max($langtags_score) === 0) {
2910 // no languages installed to honor this country
2911 return null;
2912 }
2913
2914 // sort language tag scores
2915 arsort($langtags_score);
2916
2917 // reset array pointer to the first (highest) element
2918 reset($langtags_score);
2919
2920 // return the language tag with the highest score
2921 return key($langtags_score);
2922 }
2923
2924 public static function getCurrencyCodePp() {
2925 $dbo = JFactory::getDbo();
2926 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='currencycodepp';";
2927 $dbo->setQuery($q);
2928 $dbo->execute();
2929 $ft = $dbo->loadAssocList();
2930 return $ft[0]['setting'];
2931 }
2932
2933 public static function getIntroMain($vbo_tn = null) {
2934 $dbo = JFactory::getDbo();
2935 $q = "SELECT `id`,`setting` FROM `#__vikbooking_texts` WHERE `param`='intromain';";
2936 $dbo->setQuery($q);
2937 $dbo->execute();
2938 $ft = $dbo->loadAssocList();
2939 if (!is_object($vbo_tn)) {
2940 $vbo_tn = self::getTranslator();
2941 }
2942 $vbo_tn->translateContents($ft, '#__vikbooking_texts');
2943 return $ft[0]['setting'];
2944 }
2945
2946 public static function getClosingMain($vbo_tn = null) {
2947 $dbo = JFactory::getDbo();
2948 $q = "SELECT `id`,`setting` FROM `#__vikbooking_texts` WHERE `param`='closingmain';";
2949 $dbo->setQuery($q);
2950 $dbo->execute();
2951 $ft = $dbo->loadAssocList();
2952 if (!is_object($vbo_tn)) {
2953 $vbo_tn = self::getTranslator();
2954 }
2955 $vbo_tn->translateContents($ft, '#__vikbooking_texts');
2956 return $ft[0]['setting'];
2957 }
2958
2959 public static function getFullFrontTitle($vbo_tn = null) {
2960 $dbo = JFactory::getDbo();
2961 $q = "SELECT `id`,`setting` FROM `#__vikbooking_texts` WHERE `param`='fronttitle';";
2962 $dbo->setQuery($q);
2963 $dbo->execute();
2964 $ft = $dbo->loadAssocList();
2965 if (!is_object($vbo_tn)) {
2966 $vbo_tn = self::getTranslator();
2967 }
2968 $vbo_tn->translateContents($ft, '#__vikbooking_texts');
2969 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='fronttitletag';";
2970 $dbo->setQuery($q);
2971 $dbo->execute();
2972 $fttag = $dbo->loadAssocList();
2973 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='fronttitletagclass';";
2974 $dbo->setQuery($q);
2975 $dbo->execute();
2976 $fttagclass = $dbo->loadAssocList();
2977 if (empty($ft[0]['setting'])) {
2978 return "";
2979 } else {
2980 if (empty($fttag[0]['setting'])) {
2981 return $ft[0]['setting'] . "<br/>\n";
2982 } else {
2983 $tag = str_replace("<", "", $fttag[0]['setting']);
2984 $tag = str_replace(">", "", $tag);
2985 $tag = str_replace("/", "", $tag);
2986 $tag = trim($tag);
2987 return "<" . $tag . "" . (!empty($fttagclass) ? " class=\"" . $fttagclass[0]['setting'] . "\"" : "") . ">" . $ft[0]['setting'] . "</" . $tag . ">";
2988 }
2989 }
2990 }
2991
2992 public static function dateIsValid($date) {
2993 $df = self::getDateFormat();
2994 $datesep = self::getDateSeparator();
2995 if (strlen($date) != 10) {
2996 return false;
2997 }
2998 $cur_dsep = "/";
2999 if ($datesep != $cur_dsep && strpos($date, $datesep) !== false) {
3000 $cur_dsep = $datesep;
3001 }
3002 $x = explode($cur_dsep, $date);
3003 if ($df == "%d/%m/%Y") {
3004 if (strlen($x[0]) != 2 || $x[0] > 31 || strlen($x[1]) != 2 || $x[1] > 12 || strlen($x[2]) != 4) {
3005 return false;
3006 }
3007 } elseif ($df == "%m/%d/%Y") {
3008 if (strlen($x[1]) != 2 || $x[1] > 31 || strlen($x[0]) != 2 || $x[0] > 12 || strlen($x[2]) != 4) {
3009 return false;
3010 }
3011 } else {
3012 if (strlen($x[2]) != 2 || $x[2] > 31 || strlen($x[1]) != 2 || $x[1] > 12 || strlen($x[0]) != 4) {
3013 return false;
3014 }
3015 }
3016 return true;
3017 }
3018
3019 public static function sayDateFormat() {
3020 $dbo = JFactory::getDbo();
3021 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='dateformat';";
3022 $dbo->setQuery($q);
3023 $dbo->execute();
3024 $s = $dbo->loadAssocList();
3025 if ($s[0]['setting'] == "%d/%m/%Y") {
3026 return JText::translate('VBCONFIGONETWELVE');
3027 } elseif ($s[0]['setting'] == "%m/%d/%Y") {
3028 return JText::translate('VBCONFIGONEMDY');
3029 } else {
3030 return JText::translate('VBCONFIGONETENTHREE');
3031 }
3032 }
3033
3034 /**
3035 * Calculates the Unix timestamp from the given date and
3036 * time. Avoids DST issues thanks to mktime. With prior releases,
3037 * DST issues may occur due to the sum of seconds.
3038 *
3039 * @param string $date the date string formatted with the current settings
3040 * @param int $h hours from 0 to 23 for check-in/check-out
3041 * @param int $m minutes from 0 to 59 for check-in/check-out
3042 * @param int $s seconds from 0 to 59 for check-in/check-out
3043 *
3044 * @return int the Unix timestamp of the date
3045 *
3046 * @since 1.0.14
3047 * @since 1.15.0 (J) - 1.5.0 (WP) added capability to always support Y-m-d format for VCM.
3048 * signature modified for $h and $m that now take 0 as default value.
3049 */
3050 public static function getDateTimestamp($date, $h = 0, $m = 0, $s = 0) {
3051 $df = self::getDateFormat();
3052 $datesep = self::getDateSeparator();
3053 $cur_dsep = "/";
3054 if ($datesep != $cur_dsep && strpos($date, $datesep) !== false) {
3055 $cur_dsep = $datesep;
3056 }
3057 if (preg_match("/^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])$/", $date)) {
3058 // date is in Y-m-d format (with no time)
3059 $cur_dsep = '-';
3060 $df = "%Y/%m/%d";
3061 }
3062 $x = explode($cur_dsep, $date);
3063 if (!(count($x) > 2)) {
3064 return 0;
3065 }
3066 if ($df == "%d/%m/%Y") {
3067 $month = (int)$x[1];
3068 $mday = (int)$x[0];
3069 $year = (int)$x[2];
3070 } elseif ($df == "%m/%d/%Y") {
3071 $month = (int)$x[0];
3072 $mday = (int)$x[1];
3073 $year = (int)$x[2];
3074 } else {
3075 $month = (int)$x[1];
3076 $mday = (int)$x[2];
3077 $year = (int)$x[0];
3078 }
3079 $h = empty($h) ? 0 : (int)$h;
3080 $m = empty($m) ? 0 : (int)$m;
3081 $s = $s > 0 && $s <= 59 ? $s : 0;
3082
3083 return mktime($h, $m, $s, $month, $mday, $year);
3084 }
3085
3086 public static function ivaInclusa($skipsession = false) {
3087 // cache value in static var
3088 static $getTaxIncluded = null;
3089
3090 if ($getTaxIncluded !== null) {
3091 return (bool)$getTaxIncluded;
3092 }
3093
3094 $dbo = JFactory::getDbo();
3095
3096 if ($skipsession) {
3097 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='ivainclusa'";
3098 $dbo->setQuery($q, 0, 1);
3099 $dbo->execute();
3100 $vat_incl_data = $dbo->getNumRows() ? (int)$dbo->loadResult() : 1;
3101
3102 // cache value and return it
3103 $getTaxIncluded = $vat_incl_data;
3104
3105 return (bool)$vat_incl_data;
3106 }
3107
3108 $session = JFactory::getSession();
3109 $sval = $session->get('getTaxIncluded', '');
3110 if (strlen($sval)) {
3111 return (bool)$sval;
3112 }
3113
3114 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='ivainclusa'";
3115 $dbo->setQuery($q, 0, 1);
3116 $dbo->execute();
3117 $vat_incl_data = $dbo->getNumRows() ? (int)$dbo->loadResult() : 1;
3118
3119 // update session value, cache it and return it
3120 $session->set('getTaxIncluded', $vat_incl_data);
3121 $getTaxIncluded = $vat_incl_data;
3122
3123 return (bool)$vat_incl_data;
3124 }
3125
3126 public static function showTaxOnSummaryOnly($skipsession = false) {
3127 if ($skipsession) {
3128 $dbo = JFactory::getDbo();
3129 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='taxsummary';";
3130 $dbo->setQuery($q);
3131 $dbo->execute();
3132 $s = $dbo->loadAssocList();
3133 return (intval($s[0]['setting']) == 1 ? true : false);
3134 } else {
3135 $session = JFactory::getSession();
3136 $sval = $session->get('vbshowTaxOnSummaryOnly', '');
3137 if (strlen($sval) > 0) {
3138 return (intval($sval) == 1 ? true : false);
3139 } else {
3140 $dbo = JFactory::getDbo();
3141 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='taxsummary';";
3142 $dbo->setQuery($q);
3143 $dbo->execute();
3144 $s = $dbo->loadAssocList();
3145 $session->set('vbshowTaxOnSummaryOnly', $s[0]['setting']);
3146 return (intval($s[0]['setting']) == 1 ? true : false);
3147 }
3148 }
3149 }
3150
3151 public static function tokenForm() {
3152 $dbo = JFactory::getDbo();
3153 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='tokenform';";
3154 $dbo->setQuery($q);
3155 $dbo->execute();
3156 $s = $dbo->loadAssocList();
3157 return (intval($s[0]['setting']) == 1 ? true : false);
3158 }
3159
3160 public static function getPaypalAcc() {
3161 $dbo = JFactory::getDbo();
3162 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='ccpaypal';";
3163 $dbo->setQuery($q);
3164 $dbo->execute();
3165 $s = $dbo->loadAssocList();
3166 return $s[0]['setting'];
3167 }
3168
3169 public static function getAccPerCent() {
3170 $dbo = JFactory::getDbo();
3171 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='payaccpercent';";
3172 $dbo->setQuery($q);
3173 $dbo->execute();
3174 $s = $dbo->loadAssocList();
3175 return $s[0]['setting'];
3176 }
3177
3178 public static function getTypeDeposit($skipsession = false) {
3179 if ($skipsession) {
3180 $dbo = JFactory::getDbo();
3181 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='typedeposit';";
3182 $dbo->setQuery($q);
3183 $dbo->execute();
3184 $s = $dbo->loadAssocList();
3185 return $s[0]['setting'];
3186 } else {
3187 $session = JFactory::getSession();
3188 $sval = $session->get('getTypeDeposit', '');
3189 if (strlen($sval) > 0) {
3190 return $sval;
3191 } else {
3192 $dbo = JFactory::getDbo();
3193 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='typedeposit';";
3194 $dbo->setQuery($q);
3195 $dbo->execute();
3196 $s = $dbo->loadAssocList();
3197 $session->set('getTypeDeposit', $s[0]['setting']);
3198 return $s[0]['setting'];
3199 }
3200 }
3201 }
3202
3203 public static function multiplePayments()
3204 {
3205 return VBOFactory::getConfig()->getBool('multipay', false);
3206 }
3207
3208 public static function getAdminMail()
3209 {
3210 return VBOFactory::getConfig()->getString('adminemail', '');
3211 }
3212
3213 public static function getSenderMail()
3214 {
3215 $sender = VBOFactory::getConfig()->getString('senderemail', '');
3216
3217 return empty($sender) ? self::getAdminMail() : $sender;
3218 }
3219
3220 public static function getPaymentName($vbo_tn = null) {
3221 $dbo = JFactory::getDbo();
3222 $q = "SELECT `id`,`setting` FROM `#__vikbooking_texts` WHERE `param`='paymentname';";
3223 $dbo->setQuery($q);
3224 $dbo->execute();
3225 $s = $dbo->loadAssocList();
3226 if (!is_object($vbo_tn)) {
3227 $vbo_tn = self::getTranslator();
3228 }
3229 $vbo_tn->translateContents($s, '#__vikbooking_texts');
3230 return $s[0]['setting'];
3231 }
3232
3233 public static function getTermsConditions($vbo_tn = null) {
3234 //VBO 1.10
3235 $dbo = JFactory::getDbo();
3236 $q = "SELECT `id`,`setting` FROM `#__vikbooking_texts` WHERE `param`='termsconds';";
3237 $dbo->setQuery($q);
3238 $dbo->execute();
3239 if ($dbo->getNumRows() > 0) {
3240 $s = $dbo->loadAssocList();
3241 if (!is_object($vbo_tn)) {
3242 $vbo_tn = self::getTranslator();
3243 }
3244 $vbo_tn->translateContents($s, '#__vikbooking_texts');
3245 } else {
3246 //the record has never been saved. Compose it with the default lang definition
3247 $timeopst = self::getTimeOpenStore(true);
3248 if (is_array($timeopst)) {
3249 $openat = self::getHoursMinutes($timeopst[0]);
3250 $closeat = self::getHoursMinutes($timeopst[1]);
3251 } else {
3252 $openat = array(12, 0);
3253 $closeat = array(10, 0);
3254 }
3255 $checkin_str = ($openat[0] < 10 ? '0'.$openat[0] : $openat[0]).':'.($openat[1] < 10 ? '0'.$openat[1] : $openat[1]);
3256 $checkout_str = ($closeat[0] < 10 ? '0'.$closeat[0] : $closeat[0]).':'.($closeat[1] < 10 ? '0'.$closeat[1] : $closeat[1]);
3257 $s = array(0 => array('setting' => nl2br(JText::sprintf('VBOTERMSCONDSDEFTEXT', $checkin_str, $checkout_str))));
3258 }
3259
3260 return $s[0]['setting'];
3261 }
3262
3263 public static function getMinutesLock($conv = false) {
3264 $dbo = JFactory::getDbo();
3265 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='minuteslock';";
3266 $dbo->setQuery($q);
3267 $dbo->execute();
3268 if (!$dbo->getNumRows()) {
3269 return 0;
3270 }
3271 $s = $dbo->loadAssocList();
3272 if ($conv) {
3273 return (time() + ((int)$s[0]['setting'] * 60));
3274 }
3275 return (int)$s[0]['setting'];
3276 }
3277
3278 /**
3279 * Checks if the room units are temporarily locked on the given dates.
3280 *
3281 * @param int $idroom the room ID.
3282 * @param int $units the total number of room units.
3283 * @param int $first the check-in timestamp.
3284 * @param int $second the check-out timestamp.
3285 * @param bool $occupied if true, the system will also consider the booked units.
3286 * @param array $skip_busy_ids optional list of busy record IDs to skip (booking modification).
3287 *
3288 * @return bool true if the room is available, false if fully locked/occupied, hence not bookable.
3289 *
3290 * @since 1.16.0 (J) - 1.6.0 (WP) added $occupied argument.
3291 * @since 1.16.3 (J) - 1.6.3 (WP) added $skip_busy_ids argument to support usage for booking modifications.
3292 */
3293 public static function roomNotLocked($idroom, $units, $first, $second, $occupied = false, $skip_busy_ids = [])
3294 {
3295 $dbo = JFactory::getDbo();
3296
3297 if ($units < 1) {
3298 return false;
3299 }
3300
3301 $actnow = time();
3302
3303 // clean up expired records
3304 $q = "DELETE FROM `#__vikbooking_tmplock` WHERE `until`<" . $dbo->quote($actnow) . ";";
3305 $dbo->setQuery($q);
3306 $dbo->execute();
3307
3308 $secdiff = $second - $first;
3309 $daysdiff = $secdiff / 86400;
3310 if (is_int($daysdiff)) {
3311 if ($daysdiff < 1) {
3312 $daysdiff = 1;
3313 }
3314 } else {
3315 if ($daysdiff < 1) {
3316 $daysdiff = 1;
3317 } else {
3318 $sum = floor($daysdiff) * 86400;
3319 $newdiff = $secdiff - $sum;
3320 $maxhmore = self::getHoursMoreRb() * 3600;
3321 if ($maxhmore >= $newdiff) {
3322 $daysdiff = floor($daysdiff);
3323 } else {
3324 $daysdiff = ceil($daysdiff);
3325 }
3326 }
3327 }
3328 $groupdays = self::getGroupDays($first, $second, $daysdiff);
3329 $tmplock_map = [];
3330
3331 // check temporarily locked records
3332 $check = "SELECT `id`,`checkin`,`realback` FROM `#__vikbooking_tmplock` WHERE `idroom`=" . $dbo->quote($idroom) . " AND `until`>=" . $dbo->quote($actnow) . ";";
3333 $dbo->setQuery($check);
3334 $busy = $dbo->loadAssocList();
3335 if ($busy) {
3336 foreach ($groupdays as $kg => $gday) {
3337 $bfound = 0;
3338 foreach ($busy as $bu) {
3339 if ($gday >= $bu['checkin'] && $gday <= $bu['realback']) {
3340 $bfound++;
3341 }
3342 }
3343 $tmplock_map[$kg] = $bfound;
3344 if ($bfound >= $units) {
3345 return false;
3346 }
3347 }
3348 }
3349
3350 if ($occupied) {
3351 // check also the busy records and some the occupied units to the temporarily locked ones
3352 $check = "SELECT `id`,`checkin`,`realback` FROM `#__vikbooking_busy` WHERE `idroom`=" . $dbo->quote($idroom) . " AND `realback`>=" . $first . ";";
3353 $dbo->setQuery($check);
3354 $busy = $dbo->loadAssocList();
3355 if (!$busy) {
3356 return true;
3357 }
3358
3359 foreach ($groupdays as $kg => $gday) {
3360 $bfound = 0;
3361 foreach ($busy as $bu) {
3362 if (in_array($bu['id'], $skip_busy_ids)) {
3363 // Booking modification
3364 continue;
3365 }
3366 if ($gday >= $bu['checkin'] && $gday <= $bu['realback']) {
3367 $bfound++;
3368 }
3369 }
3370 // sum units occupied to the units temporarily locked
3371 $bfound += isset($tmplock_map[$kg]) ? (int)$tmplock_map[$kg] : 0;
3372 if ($bfound >= $units) {
3373 return false;
3374 }
3375 }
3376 }
3377
3378 return true;
3379 }
3380
3381 public static function getGroupDays($first, $second, $daysdiff) {
3382 $ret = array();
3383 $ret[] = $first;
3384 if ($daysdiff > 1) {
3385 $start = getdate($first);
3386 $end = getdate($second);
3387 $endcheck = mktime(0, 0, 0, $end['mon'], $end['mday'], $end['year']);
3388 for($i = 1; $i < $daysdiff; $i++) {
3389 $checkday = $start['mday'] + $i;
3390 $dayts = mktime(0, 0, 0, $start['mon'], $checkday, $start['year']);
3391 if ($dayts != $endcheck) {
3392 $ret[] = $dayts;
3393 }
3394 }
3395 }
3396 $ret[] = $second;
3397 return $ret;
3398 }
3399
3400 /**
3401 * Counts the hours of difference between the current
3402 * server time and the selected check-in date and time.
3403 *
3404 * @param int $checkin_ts
3405 * @param [int] $now_ts
3406 *
3407 * @return int
3408 */
3409 public static function countHoursToArrival($checkin_ts, $now_ts = '') {
3410 $hoursdiff = 0;
3411
3412 if (empty($now_ts)) {
3413 $now_ts = time();
3414 }
3415
3416 if ($now_ts >= $checkin_ts) {
3417 return $hoursdiff;
3418 }
3419
3420 $hoursdiff = floor(($checkin_ts - $now_ts) / 3600);
3421
3422 return $hoursdiff;
3423 }
3424
3425 /**
3426 * Loads all the occupied records for the given rooms and offset.
3427 *
3428 * @param array $roomids the optional list of room IDs to filter.
3429 * @param int $from_ts the optional base date to use.
3430 * @param int $max_ts the optional max date to use.
3431 * @param bool $closures whether to ignore, include or exclude closures.
3432 *
3433 * @return array
3434 *
3435 * @since 1.15.0 (J) - 1.5.0 (WP) changed arguments signature with 3rd arg.
3436 * @since 1.15.2 (J) - 1.5.5 (WP) changed arguments signature with 4th arg.
3437 */
3438 public static function loadBusyRecords($roomids = array(), $from_ts = 0, $max_ts = 0, $closures = null)
3439 {
3440 $base_ts = empty($from_ts) ? time() : $from_ts;
3441 $busy = [];
3442 $clauses = [];
3443
3444 if (is_array($roomids) && count($roomids)) {
3445 // filter busy records only by the requested room ids
3446 $roomids = array_map(function($rid) {
3447 return (int)$rid;
3448 }, $roomids);
3449 $clauses[] = "`b`.`idroom` IN (" . implode(', ', $roomids) . ")";
3450 }
3451
3452 // exclude past reservations
3453 $clauses[] = "`b`.`checkout` >= {$base_ts}";
3454
3455 if (!empty($max_ts)) {
3456 // exclude future reservations (recommended for large datasets)
3457 $clauses[] = "`b`.`checkin` <= {$max_ts}";
3458 }
3459
3460 if (is_bool($closures)) {
3461 // include or exclude closures
3462 if ($closures) {
3463 $clauses[] = "`o`.`closure` = 1";
3464 } else {
3465 $clauses[] = "`o`.`closure` != 1";
3466 }
3467 }
3468
3469 $dbo = JFactory::getDbo();
3470
3471 $q = "SELECT `b`.*, `ob`.`idorder`, `o`.`closure` FROM `#__vikbooking_busy` AS `b`
3472 LEFT JOIN `#__vikbooking_ordersbusy` AS `ob` ON `ob`.`idbusy`=`b`.`id`
3473 LEFT JOIN `#__vikbooking_orders` AS `o` ON `o`.`id`=`ob`.`idorder`
3474 WHERE " . implode(' AND ', $clauses) . ";";
3475 $dbo->setQuery($q);
3476 $allbusy = $dbo->loadAssocList();
3477 if ($allbusy) {
3478 foreach ($allbusy as $br) {
3479 if (!isset($busy[$br['idroom']])) {
3480 $busy[$br['idroom']] = [];
3481 }
3482 $busy[$br['idroom']][] = $br;
3483 }
3484 }
3485
3486 return $busy;
3487 }
3488
3489 /**
3490 * Loads all the busy records by excluding the closures.
3491 * Built for the page dashboard to calculate the actual
3492 * rooms occupancy by excluding the rooms closed.
3493 *
3494 * @param array $roomids the optional list of room IDs to filter.
3495 * @param int $from_ts the optional base date to use.
3496 * @param int $max_ts the optional max date to use.
3497 *
3498 * @return array the list of busy records.
3499 *
3500 * @since 1.12
3501 * @since 1.15.2 (J) - 1.5.5 (WP) changed arguments signature with 3rd arg.
3502 */
3503 public static function loadBusyRecordsUnclosed($roomids = array(), $from_ts = 0, $max_ts = 0)
3504 {
3505 if (!is_array($roomids) || !count($roomids)) {
3506 return [];
3507 }
3508
3509 return self::loadBusyRecords($roomids, $from_ts, $max_ts, false);
3510 }
3511
3512 public static function loadBookingBusyIds($idorder) {
3513 $busy = array();
3514 if (empty($idorder)) {
3515 return $busy;
3516 }
3517 $dbo = JFactory::getDbo();
3518 $q = "SELECT * FROM `#__vikbooking_ordersbusy` WHERE `idorder`=".(int)$idorder.";";
3519 $dbo->setQuery($q);
3520 $allbusy = $dbo->loadAssocList();
3521 if ($allbusy) {
3522 foreach ($allbusy as $b) {
3523 array_push($busy, $b['idbusy']);
3524 }
3525 }
3526 return $busy;
3527 }
3528
3529 public static function loadLockedRecords($roomids, $ts = 0) {
3530 $dbo = JFactory::getDbo();
3531 $actnow = empty($ts) ? time() : $ts;
3532 $locked = array();
3533 $q = "DELETE FROM `#__vikbooking_tmplock` WHERE `until`<" . $actnow . ";";
3534 $dbo->setQuery($q);
3535 $dbo->execute();
3536 if (!is_array($roomids) || !(count($roomids) > 0)) {
3537 return $locked;
3538 }
3539 $check = "SELECT `id`,`idroom`,`checkin`,`realback` FROM `#__vikbooking_tmplock` WHERE `idroom` IN (".implode(', ', $roomids).") AND `until` > ".$actnow.";";
3540 $dbo->setQuery($check);
3541 $all_locked = $dbo->loadAssocList();
3542 if ($all_locked) {
3543 foreach ($all_locked as $kb => $br) {
3544 $locked[$br['idroom']][$kb] = $br;
3545 }
3546 }
3547 return $locked;
3548 }
3549
3550 public static function getRoomBookingsFromBusyIds($idroom, $arr_bids) {
3551 $bookings = [];
3552 if (empty($idroom) || !is_array($arr_bids) || !(count($arr_bids) > 0)) {
3553 return $bookings;
3554 }
3555 $dbo = JFactory::getDbo();
3556 $q = "SELECT `ob`.`idorder`,`ob`.`idbusy` FROM `#__vikbooking_ordersbusy` AS `ob` WHERE `ob`.`idbusy` IN (".implode(',', $arr_bids).") GROUP BY `ob`.`idorder`,`ob`.`idbusy`;";
3557 $dbo->setQuery($q);
3558 $all_booking_ids = $dbo->loadAssocList();
3559 if ($all_booking_ids) {
3560 $oids = array();
3561 foreach ($all_booking_ids as $bid) {
3562 $oids[] = $bid['idorder'];
3563 }
3564 $q = "SELECT `or`.`idorder`,CONCAT_WS(' ',`or`.`t_first_name`,`or`.`t_last_name`) AS `nominative`,`or`.`roomindex`,`o`.`status`,`o`.`days`,`o`.`checkout`,`o`.`custdata`,`o`.`country`,`o`.`closure`,`o`.`checked` ".
3565 "FROM `#__vikbooking_ordersrooms` AS `or` ".
3566 "LEFT JOIN `#__vikbooking_orders` `o` ON `o`.`id`=`or`.`idorder` ".
3567 "WHERE `or`.`idorder` IN (".implode(',', $oids).") AND `or`.`idroom`=".(int)$idroom." AND `o`.`status`='confirmed' ".
3568 "ORDER BY `o`.`checkout` ASC;";
3569 $dbo->setQuery($q);
3570 $bookings = $dbo->loadAssocList();
3571 if (!$bookings) {
3572 $bookings = [];
3573 }
3574 }
3575 return $bookings;
3576 }
3577
3578 public static function roomBookable($idroom, $units, $first, $second, $skip_busy_ids = array())
3579 {
3580 $dbo = JFactory::getDbo();
3581
3582 if ($units < 1) {
3583 return false;
3584 }
3585
3586 $secdiff = $second - $first;
3587 $daysdiff = $secdiff / 86400;
3588 if (is_int($daysdiff)) {
3589 if ($daysdiff < 1) {
3590 $daysdiff = 1;
3591 }
3592 } else {
3593 if ($daysdiff < 1) {
3594 $daysdiff = 1;
3595 } else {
3596 $sum = floor($daysdiff) * 86400;
3597 $newdiff = $secdiff - $sum;
3598 $maxhmore = self::getHoursMoreRb() * 3600;
3599 if ($maxhmore >= $newdiff) {
3600 $daysdiff = floor($daysdiff);
3601 } else {
3602 $daysdiff = ceil($daysdiff);
3603 }
3604 }
3605 }
3606
3607 $groupdays = self::getGroupDays($first, $second, $daysdiff);
3608
3609 $check = "SELECT `id`,`checkin`,`realback` FROM `#__vikbooking_busy` WHERE `idroom`=" . $dbo->quote($idroom) . " AND `realback`>=" . $first . ";";
3610 $dbo->setQuery($check);
3611 $busy = $dbo->loadAssocList();
3612 if (!$busy) {
3613 return true;
3614 }
3615
3616 foreach ($groupdays as $gday) {
3617 $bfound = 0;
3618 foreach ($busy as $bu) {
3619 if (in_array($bu['id'], $skip_busy_ids)) {
3620 //VBO 1.10 - Booking modification
3621 continue;
3622 }
3623 if ($gday >= $bu['checkin'] && $gday <= $bu['realback']) {
3624 $bfound++;
3625 }
3626 }
3627 if ($bfound >= $units) {
3628 return false;
3629 }
3630 }
3631
3632 return true;
3633 }
3634
3635 public static function payTotal() {
3636 $dbo = JFactory::getDbo();
3637 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='paytotal';";
3638 $dbo->setQuery($q);
3639 $dbo->execute();
3640 $s = $dbo->loadAssocList();
3641 return (intval($s[0]['setting']) == 1 ? true : false);
3642 }
3643
3644 public static function getDepositIfDays() {
3645 $dbo = JFactory::getDbo();
3646 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='depifdaysadv';";
3647 $dbo->setQuery($q);
3648 $dbo->execute();
3649 $s = $dbo->loadAssocList();
3650 return intval($s[0]['setting']);
3651 }
3652
3653 public static function depositAllowedDaysAdv($checkints) {
3654 $days_adv = self::getDepositIfDays();
3655 if (!($days_adv > 0) || !($checkints > 0)) {
3656 return true;
3657 }
3658 $now_info = getdate();
3659 $maxts = mktime(0, 0, 0, $now_info['mon'], ($now_info['mday'] + $days_adv), $now_info['year']);
3660 return $maxts > $checkints ? false : true;
3661 }
3662
3663 public static function depositCustomerChoice() {
3664 $dbo = JFactory::getDbo();
3665 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='depcustchoice';";
3666 $dbo->setQuery($q);
3667 $dbo->execute();
3668 $s = $dbo->loadAssocList();
3669 return (intval($s[0]['setting']) == 1 ? true : false);
3670 }
3671
3672 public static function getDepositOverrides($getjson = false) {
3673 $dbo = JFactory::getDbo();
3674 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='depoverrides';";
3675 $dbo->setQuery($q);
3676 $dbo->execute();
3677 if ($dbo->getNumRows() > 0) {
3678 $s = $dbo->loadResult();
3679 return $getjson ? $s : json_decode($s, true);
3680 }
3681 //count of this array will be at least 1 to store the "more" property
3682 $def_arr = array('more' => '');
3683 $def_val = json_encode($def_arr);
3684 $q = "INSERT INTO `#__vikbooking_config` (`param`,`setting`) VALUES ('depoverrides', ".$dbo->quote($def_val).");";
3685 $dbo->setQuery($q);
3686 $dbo->execute();
3687 return $getjson ? $def_val : $def_arr;
3688 }
3689
3690 public static function calcDepositOverride($amount_deposit, $nights) {
3691 $overrides = self::getDepositOverrides();
3692 $nights = intval($nights);
3693 $andmore = intval($overrides['more']);
3694 if (!(count($overrides) > 1)) {
3695 //no overrides
3696 return $amount_deposit;
3697 }
3698 foreach ($overrides as $k => $v) {
3699 if ((int)$k == $nights && strlen($v) > 0) {
3700 //exact override found
3701 return (float)$v;
3702 }
3703 }
3704 if ($andmore > 0 && $andmore <= $nights) {
3705 foreach ($overrides as $k => $v) {
3706 if ((int)$k == $andmore && strlen($v) > 0) {
3707 //"and more" nights found
3708 return (float)$v;
3709 }
3710 }
3711 }
3712 //nothing was found
3713 return $amount_deposit;
3714 }
3715
3716 public static function noDepositForNonRefund() {
3717 $dbo = JFactory::getDbo();
3718 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='nodepnonrefund';";
3719 $dbo->setQuery($q);
3720 $dbo->execute();
3721 if ($dbo->getNumRows() > 0) {
3722 $s = $dbo->loadResult();
3723 return ((int)$s == 1);
3724 }
3725 $q = "INSERT INTO `#__vikbooking_config` (`param`,`setting`) VALUES ('nodepnonrefund', '0');";
3726 $dbo->setQuery($q);
3727 $dbo->execute();
3728 //default to false
3729 return false;
3730 }
3731
3732 /**
3733 * This method returns the room-rate array with the lowest price
3734 * that matches the preferred rate plan parameter (if available).
3735 * The array $website_rates could not be an array, or it could be
3736 * an array with the error string (response from the TACVBO Class).
3737 * The method has been introduced in VBO 1.10 and it's mainly used
3738 * by the module mod_vikbooking_channelrates and its ajax requests.
3739 *
3740 * @param array $website_rates the array of the website rates returned by the method fetchWebsiteRates()
3741 * @param int $def_rplan the id of the default type of price to take for display. If empty, take the lowest rate
3742 *
3743 * @return array
3744 */
3745 public static function getBestRoomRate($website_rates, $def_rplan) {
3746 if (!is_array($website_rates) || !(count($website_rates) > 0) || (is_array($website_rates) && isset($website_rates['e4j.error']))) {
3747 return array();
3748 }
3749 $best_room_rate = array();
3750 foreach ($website_rates as $rid => $tars) {
3751 foreach ($tars as $tar) {
3752 if (empty($def_rplan) || (int)$tar['idprice'] == $def_rplan) {
3753 //the array $website_rates is already sorted by price ASC, so we take the first useful array
3754 $best_room_rate = $tar;
3755 break 2;
3756 }
3757 }
3758 }
3759 if (!(count($best_room_rate) > 0)) {
3760 //the default rate plan is not available if we enter this statement, so we take the first and lowest rate
3761 foreach ($website_rates as $rid => $tars) {
3762 foreach ($tars as $tar) {
3763 $best_room_rate = $tar;
3764 break 2;
3765 }
3766 }
3767 }
3768
3769 return $best_room_rate;
3770 }
3771
3772 /**
3773 * This method returns an array with the details
3774 * of all channels in VCM that supports AV requests,
3775 * and that have at least one room type mapped.
3776 * The method invokes the Logos Class to return details
3777 * about the name and logo URL of the channel.
3778 * The method has been introduced in VBO 1.10 and it's mainly used
3779 * by the module mod_vikbooking_channelrates and its ajax requests.
3780 *
3781 * @param array $channels an array of channel IDs to be mapped on the VCM relations
3782 *
3783 * @return array
3784 */
3785 public static function getChannelsMap($channels) {
3786 if (!is_array($channels) || !(count($channels))) {
3787 return array();
3788 }
3789 $vcm_logos = self::getVcmChannelsLogo('', true);
3790 if (!is_object($vcm_logos)) {
3791 return array();
3792 }
3793 $channels_ids = array();
3794 foreach ($channels as $chid) {
3795 $ichid = (int)$chid;
3796 if ($ichid < 1) {
3797 continue;
3798 }
3799 array_push($channels_ids, $ichid);
3800 }
3801 if (!(count($channels_ids))) {
3802 return array();
3803 }
3804 $dbo = JFactory::getDbo();
3805 $q = "SELECT `idchannel`, `channel` FROM `#__vikchannelmanager_roomsxref` WHERE `idchannel` IN (".implode(', ', $channels_ids).") GROUP BY `idchannel`,`channel` ORDER BY `channel` ASC;";
3806 $dbo->setQuery($q);
3807 $dbo->execute();
3808 if ($dbo->getNumRows() < 1) {
3809 return array();
3810 }
3811 $channels_names = $dbo->loadAssocList();
3812 $channels_map = array();
3813 foreach ($channels_names as $ch) {
3814 $ota_logo_url = $vcm_logos->setProvenience($ch['channel'])->getLogoURL();
3815 $ota_logo_url = $ota_logo_url === false ? '' : $ota_logo_url;
3816 $chdata = array(
3817 'id' => $ch['idchannel'],
3818 'name' => ucwords($ch['channel']),
3819 'logo' => $ota_logo_url
3820 );
3821 array_push($channels_map, $chdata);
3822 }
3823 return $channels_map;
3824 }
3825
3826 /**
3827 * This method returns a string to calculate the rates for the OTAs. Data is taken from the Bulk Rates Cache
3828 * of Vik Channel Manager. The string returned contains the charge/discount operator at the position 0 (+ or -),
3829 * and the percentage char (%) at the last position (if percent). Between the first and last position there is
3830 * the float value. The method has been introduced in VBO 1.10 and it's mainly used by the "channel rates"
3831 * module/widget and its ajax requests.
3832 *
3833 * @param array $best_room_rate array containing a specific tariff returned by getBestRoomRate().
3834 * @param bool $per_channel if true, an associative array will be returned for each channel alteration.
3835 *
3836 * @return mixed string (empty or indicating the alteration), or associative array per channel.
3837 *
3838 * @since 1.15.0 (J) - 1.5.0 (WP) added support for VCM different alterations per channel.
3839 */
3840 public static function getOtasRatesVal($best_room_rate, $per_channel = false)
3841 {
3842 $otas_rates_val = '';
3843 if (!is_array($best_room_rate) || !count($best_room_rate) || !is_file(VCM_SITE_PATH.DIRECTORY_SEPARATOR.'helpers'.DIRECTORY_SEPARATOR.'lib.vikchannelmanager.php')) {
3844 return $otas_rates_val;
3845 }
3846 if (!class_exists('VikChannelManager')) {
3847 require_once(VCM_SITE_PATH.DIRECTORY_SEPARATOR.'helpers'.DIRECTORY_SEPARATOR.'lib.vikchannelmanager.php');
3848 }
3849 $bulk_rates_cache = VikChannelManager::getBulkRatesCache();
3850 if (count($bulk_rates_cache) && isset($best_room_rate['idprice'])) {
3851 if (isset($bulk_rates_cache[$best_room_rate['idroom']]) && isset($bulk_rates_cache[$best_room_rate['idroom']][$best_room_rate['idprice']])) {
3852 // the Bulk Rates Cache contains data for this room type and rate plan
3853 $data_cont = $bulk_rates_cache[$best_room_rate['idroom']][$best_room_rate['idprice']];
3854 // build rates modification string
3855 if ((int)$data_cont['rmod'] > 0) {
3856 // rates were modified for the OTAs, check how
3857 $rmodop = (int)$data_cont['rmodop'] > 0 ? '+' : '-';
3858 $rmodpcent = (int)$data_cont['rmodval'] > 0 ? '%' : '';
3859 $otas_rates_val = $rmodop.(float)$data_cont['rmodamount'].$rmodpcent;
3860 // check alterations per channel, if requested
3861 if ($per_channel && !empty($data_cont['rmod_channels']) && is_array($data_cont['rmod_channels'])) {
3862 // we compose an associative array with the alteration for each channel (0 = generic rule)
3863 $channel_alterations = array($otas_rates_val);
3864 foreach ($data_cont['rmod_channels'] as $ch_id => $ch_mods) {
3865 if ((int)$ch_mods['rmod'] > 0) {
3866 // custom rate alteration for this channel
3867 $rmodop = (int)$ch_mods['rmodop'] > 0 ? '+' : '-';
3868 $rmodpcent = (int)$ch_mods['rmodval'] > 0 ? '%' : '';
3869 $ch_alter_string = $rmodop . (float)$ch_mods['rmodamount'] . $rmodpcent;
3870 } else {
3871 // no rates alteration for this channel identifier
3872 $ch_alter_string = '+0%';
3873 }
3874 $channel_alterations[$ch_id] = $ch_alter_string;
3875 }
3876 // overwrite return value with associative array
3877 $otas_rates_val = $channel_alterations;
3878 }
3879 }
3880 }
3881 }
3882
3883 return $otas_rates_val;
3884 }
3885
3886 /**
3887 * This method checks if some non-refundable rates were selected
3888 * (`free_cancellation`=0), the only argument is an array of tariffs.
3889 * The property 'idprice' must be defined on each sub-array.
3890 *
3891 * @param $tars array
3892 *
3893 * @return boolean
3894 **/
3895 public static function findNonRefundableRates($tars = [])
3896 {
3897 $id_prices = [];
3898
3899 foreach ($tars as $tar) {
3900 if (isset($tar['idprice'])) {
3901 if (!in_array($tar['idprice'], $id_prices)) {
3902 array_push($id_prices, (int)$tar['idprice']);
3903 }
3904 continue;
3905 }
3906 foreach ($tar as $t) {
3907 if (isset($t['idprice'])) {
3908 if (!in_array($t['idprice'], $id_prices)) {
3909 array_push($id_prices, (int)$t['idprice']);
3910 }
3911 }
3912 }
3913 }
3914
3915 if (!count($id_prices)) {
3916 // no price IDs found (probably a package)
3917 return false;
3918 }
3919
3920 $dbo = JFactory::getDbo();
3921
3922 $q = "SELECT `id`,`name` FROM `#__vikbooking_prices` WHERE `id` IN (".implode(', ', $id_prices).") AND `free_cancellation`=0;";
3923 $dbo->setQuery($q);
3924 $dbo->execute();
3925
3926 return (bool)($dbo->getNumRows() > 0);
3927 }
3928
3929 /**
3930 * This method checks if the deposit is allowed depending on
3931 * the selected rate plans (idprice) for the rooms reserved.
3932 * If the configuration setting is enabled, and if some
3933 * non-refundable rates were selected (`free_cancellation`=0),
3934 * the method will return false, because the deposit is not allowed.
3935 * The only argument is an array of tariffs. The property 'idprice'
3936 * must be defined on each sub-array (multi-dimension supported)
3937 * throgh the method findNonRefundableRates();
3938 *
3939 * @param $tars array
3940 *
3941 * @return boolean
3942 **/
3943 public static function allowDepositFromRates($tars) {
3944 if (!self::noDepositForNonRefund()) {
3945 //deposit can be paid also if non-refundable rates
3946 return true;
3947 }
3948 return !self::findNonRefundableRates($tars);
3949 }
3950
3951 public static function showSearchSuggestions() {
3952 $dbo = JFactory::getDbo();
3953 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='searchsuggestions';";
3954 $dbo->setQuery($q);
3955 $dbo->execute();
3956 if ($dbo->getNumRows() > 0) {
3957 return (int)$dbo->loadResult();
3958 }
3959 $q = "INSERT INTO `#__vikbooking_config` (`param`,`setting`) VALUES ('searchsuggestions', '1');";
3960 $dbo->setQuery($q);
3961 $dbo->execute();
3962 return 1;
3963 }
3964
3965 /**
3966 * Given a code, returns the coupon information array.
3967 *
3968 * @param string $code the coupon code.
3969 *
3970 * @return array empty array or coupon record.
3971 *
3972 * @since 1.16.0 (J) - 1.6.0 (WP) the customers assigned are also returned.
3973 */
3974 public static function getCouponInfo($code)
3975 {
3976 $dbo = JFactory::getDbo();
3977
3978 $q = "SELECT * FROM `#__vikbooking_coupons` WHERE `code`=".$dbo->quote($code);
3979 $dbo->setQuery($q, 0, 1);
3980 $coupon = $dbo->loadAssoc();
3981 if (!$coupon) {
3982 return [];
3983 }
3984
3985 // check for customers assigned
3986 $q = "SELECT `idcustomer`, `automatic` FROM `#__vikbooking_customers_coupons` WHERE `idcoupon`=" . (int)$coupon['id'];
3987 $dbo->setQuery($q);
3988 $coupon_customers = $dbo->loadAssocList();
3989 if ($coupon_customers) {
3990 // set customers and automatic properties
3991 $coupon['customers'] = [];
3992 $coupon['automatic'] = $coupon_customers[0]['automatic'];
3993 foreach ($coupon_customers as $coupon_customer) {
3994 // push customer ID
3995 $coupon['customers'][] = $coupon_customer['idcustomer'];
3996 }
3997 }
3998
3999 return $coupon;
4000 }
4001
4002 public static function getRoomInfo($idroom)
4003 {
4004 $dbo = JFactory::getDbo();
4005
4006 $q = "SELECT `id`,`name`,`img`,`idcat`,`idcarat`,`info`,`smalldesc` FROM `#__vikbooking_rooms` WHERE `id`=" . (int)$idroom . ";";
4007 $dbo->setQuery($q);
4008 $room = $dbo->loadAssoc();
4009 if ($room) {
4010 return $room;
4011 }
4012
4013 return [];
4014 }
4015
4016 public static function loadOrdersRoomsData($idorder)
4017 {
4018 $dbo = JFactory::getDbo();
4019
4020 $q = $dbo->getQuery(true)
4021 ->select($dbo->qn('or') . '.*')
4022 ->select($dbo->qn('r.name', 'room_name'))
4023 ->select($dbo->qn('r.params'))
4024 ->from($dbo->qn('#__vikbooking_ordersrooms', 'or'))
4025 ->leftJoin($dbo->qn('#__vikbooking_rooms', 'r') . ' ON ' . $dbo->qn('or.idroom') . ' = ' . $dbo->qn('r.id'))
4026 ->where($dbo->qn('or.idorder') . ' = ' . (int)$idorder)
4027 ->order($dbo->qn('or.id') . ' ASC');
4028
4029 $dbo->setQuery($q);
4030
4031 return $dbo->loadAssocList();
4032 }
4033
4034 public static function sayCategory($ids, $vbo_tn = null)
4035 {
4036 $dbo = JFactory::getDbo();
4037 $split = explode(";", $ids);
4038 $say = "";
4039 foreach ($split as $k => $s) {
4040 if (!strlen($s)) {
4041 continue;
4042 }
4043 $q = "SELECT `id`,`name` FROM `#__vikbooking_categories` WHERE `id`=" . (int)$s . ";";
4044 $dbo->setQuery($q);
4045 $nam = $dbo->loadAssocList();
4046 if (!$nam) {
4047 continue;
4048 }
4049 if (is_object($vbo_tn)) {
4050 $vbo_tn->translateContents($nam, '#__vikbooking_categories');
4051 }
4052 $say .= $nam[0]['name'];
4053 $say .= (strlen($split[($k +1)]) && end($split) != $s ? ", " : "");
4054 }
4055 return $say;
4056 }
4057
4058 public static function getRoomCaratOriz($idc, $vbo_tn = null)
4059 {
4060 $dbo = JFactory::getDbo();
4061 $split = explode(";", $idc);
4062 $carat = "";
4063 $arr = array();
4064 $where = array();
4065 foreach ($split as $s) {
4066 if (!empty($s)) {
4067 $where[] = $s;
4068 }
4069 }
4070 if ($where) {
4071 $q = "SELECT `c`.* FROM `#__vikbooking_characteristics` AS `c` WHERE `c`.`id` IN (" . implode(",", $where) . ") ORDER BY `c`.`ordering` ASC;";
4072 $dbo->setQuery($q);
4073 $arr = $dbo->loadAssocList();
4074 if ($arr && is_object($vbo_tn)) {
4075 $vbo_tn->translateContents($arr, '#__vikbooking_characteristics');
4076 }
4077 }
4078 if ($arr) {
4079 $carat .= "<div class=\"vbo-room-carats\">\n";
4080 foreach ($arr as $a) {
4081 if (!empty($a['textimg'])) {
4082 //tooltip icon text is not empty
4083 if (!empty($a['icon'])) {
4084 //an icon has been uploaded: display the image
4085 $carat .= "<span class=\"vbo-room-carat\"><span class=\"vbo-expl\" data-vbo-expl=\"".$a['textimg']."\"><img src=\"".VBO_SITE_URI."resources/uploads/".$a['icon']."\" alt=\"" . $a['name'] . "\" /></span></span>\n";
4086 } else {
4087 if (strpos($a['textimg'], '</i>') !== false) {
4088 //the tooltip icon text is a font-icon, we can use the name as tooltip
4089 $carat .= "<span class=\"vbo-room-carat\"><span class=\"vbo-expl\" data-vbo-expl=\"".$a['name']."\">".$a['textimg']."</span></span>\n";
4090 } else {
4091 //display just the text
4092 $carat .= "<span class=\"vbo-room-carat\">".$a['textimg']."</span>\n";
4093 }
4094 }
4095 } else {
4096 $carat .= (!empty($a['icon']) ? "<span class=\"vbo-room-carat\"><img src=\"".VBO_SITE_URI."resources/uploads/" . $a['icon'] . "\" alt=\"" . $a['name'] . "\" title=\"" . $a['name'] . "\"/></span>\n" : "<span class=\"vbo-room-carat\">".$a['name']."</span>\n");
4097 }
4098 }
4099 $carat .= "</div>\n";
4100 }
4101 return $carat;
4102 }
4103
4104 public static function getRoomOptionals($idopts, $vbo_tn = null)
4105 {
4106 $dbo = JFactory::getDbo();
4107 $split = explode(";", $idopts);
4108 $fetch = array();
4109 foreach ($split as $s) {
4110 if (!empty($s)) {
4111 $fetch[] = $s;
4112 }
4113 }
4114 if ($fetch) {
4115 $q = "SELECT * FROM `#__vikbooking_optionals` WHERE `id` IN (".implode(", ", $fetch).") ORDER BY `#__vikbooking_optionals`.`ordering` ASC;";
4116 $dbo->setQuery($q);
4117 $arr = $dbo->loadAssocList();
4118 if ($arr) {
4119 if (is_object($vbo_tn)) {
4120 $vbo_tn->translateContents($arr, '#__vikbooking_optionals');
4121 }
4122 return $arr;
4123 }
4124 }
4125 return [];
4126 }
4127
4128 public static function getSingleOption($idopt, $vbo_tn = null)
4129 {
4130 $dbo = JFactory::getDbo();
4131 $opt = array();
4132 if (!empty($idopt)) {
4133 $q = "SELECT * FROM `#__vikbooking_optionals` WHERE `id`=" . (int)$idopt . ";";
4134 $dbo->setQuery($q);
4135 $opt = $dbo->loadAssoc();
4136 if ($opt && is_object($vbo_tn)) {
4137 $vbo_tn->translateContents($opt, '#__vikbooking_optionals');
4138 }
4139 }
4140 return $opt;
4141 }
4142
4143 /**
4144 * Unsets the options that are not available in the requested dates.
4145 *
4146 * @param array $optionals the array of options passed by reference
4147 * @param int $checkin the timestamp of the check-in date and time
4148 * @param int $checkout the timestamp of the check-out date and time
4149 *
4150 * @return void
4151 *
4152 * @since 1.11
4153 */
4154 public static function filterOptionalsByDate(&$optionals, $checkin, $checkout) {
4155 if (!is_array($optionals) || !count($optionals) || empty($checkin) || empty($checkout)) {
4156 return;
4157 }
4158 foreach ($optionals as $k => $v) {
4159 if (!empty($v['alwaysav'])) {
4160 $dates = explode(';', $v['alwaysav']);
4161 if (empty($dates[0]) || empty($dates[1])) {
4162 continue;
4163 }
4164 // it is sufficient that the check-in is included within the validity dates, we ignore the checkout
4165 if (!($checkin >= $dates[0]) || !($checkin <= $dates[1])) {
4166 unset($optionals[$k]);
4167 }
4168 }
4169 }
4170 if (!count($optionals)) {
4171 // the system requires it to be an empty string
4172 $optionals = '';
4173 }
4174 }
4175
4176 /**
4177 * Unsets the options that are not suited for the requested room party.
4178 *
4179 * @param array $optionals the array of options passed by reference.
4180 * @param int $adults the number of adults in the room party.
4181 * @param int $children the number of children in the room party.
4182 *
4183 * @return void
4184 *
4185 * @since 1.13.5
4186 */
4187 public static function filterOptionalsByParty(&$optionals, $adults, $children) {
4188 if (!is_array($optionals) || !count($optionals) || (empty($adults) && empty($children))) {
4189 return;
4190 }
4191 foreach ($optionals as $k => $v) {
4192 if (empty($v['oparams'])) {
4193 continue;
4194 }
4195 $v['oparams'] = json_decode($v['oparams'], true);
4196 if (!is_array($v['oparams']) || !count($v['oparams']) || (empty($v['oparams']['minguestsnum']) && empty($v['oparams']['maxguestsnum']))) {
4197 continue;
4198 }
4199 if (!empty($v['oparams']['minguestsnum'])) {
4200 // filter by minimum adults/guests
4201 if ($v['oparams']['mingueststype'] == 'adults' && $adults <= $v['oparams']['minguestsnum']) {
4202 // minimum number of adults not sufficient, unset option
4203 unset($optionals[$k]);
4204 } elseif ($v['oparams']['mingueststype'] == 'guests' && ($adults + $children) <= $v['oparams']['minguestsnum']) {
4205 // minimum number of total guests not sufficient, unset option
4206 unset($optionals[$k]);
4207 }
4208 }
4209 if (!empty($v['oparams']['maxguestsnum'])) {
4210 // filter by maximum adults/guests
4211 if ($v['oparams']['maxgueststype'] == 'adults' && $adults >= $v['oparams']['maxguestsnum']) {
4212 // maximum number of adults exceeded, unset option
4213 unset($optionals[$k]);
4214 } elseif ($v['oparams']['maxgueststype'] == 'guests' && ($adults + $children) >= $v['oparams']['maxguestsnum']) {
4215 // maximum number of total guests exceeded, unset option
4216 unset($optionals[$k]);
4217 }
4218 }
4219 }
4220 if (!count($optionals)) {
4221 // the system requires it to be an empty string
4222 $optionals = '';
4223 }
4224 }
4225
4226 public static function getMandatoryTaxesFees($id_rooms, $num_adults, $num_nights) {
4227 $dbo = JFactory::getDbo();
4228 $taxes = 0;
4229 $fees = 0;
4230 $options_data = array();
4231 $id_options = array();
4232 $q = "SELECT `id`,`idopt` FROM `#__vikbooking_rooms` WHERE `id` IN (".implode(", ", $id_rooms).");";
4233 $dbo->setQuery($q);
4234 $dbo->execute();
4235 if ($dbo->getNumRows() > 0) {
4236 $assocs = $dbo->loadAssocList();
4237 foreach ($assocs as $opts) {
4238 if (!empty($opts['idopt'])) {
4239 $r_ido = explode(';', rtrim($opts['idopt']));
4240 foreach ($r_ido as $ido) {
4241 if (!empty($ido) && !in_array($ido, $id_options)) {
4242 $id_options[] = $ido;
4243 }
4244 }
4245 }
4246 }
4247 }
4248 if (count($id_options) > 0) {
4249 $q = "SELECT * FROM `#__vikbooking_optionals` WHERE `id` IN (".implode(", ", $id_options).") AND `forcesel`=1 AND `ifchildren`=0 AND (`is_citytax`=1 OR `is_fee`=1);";
4250 $dbo->setQuery($q);
4251 $dbo->execute();
4252 if ($dbo->getNumRows() > 0) {
4253 $alltaxesfees = $dbo->loadAssocList();
4254 foreach ($alltaxesfees as $tf) {
4255 $realcost = (intval($tf['perday']) == 1 ? ($tf['cost'] * $num_nights) : $tf['cost']);
4256 if (!empty($tf['maxprice']) && $tf['maxprice'] > 0 && $realcost > $tf['maxprice']) {
4257 $realcost = $tf['maxprice'];
4258 }
4259 $realcost = $tf['perperson'] == 1 ? ($realcost * $num_adults) : $realcost;
4260 $realcost = self::sayOptionalsPlusIva($realcost, $tf['idiva']);
4261 if ($tf['is_citytax'] == 1) {
4262 $taxes += $realcost;
4263 } elseif ($tf['is_fee'] == 1) {
4264 $fees += $realcost;
4265 }
4266 $optsett = explode('-', $tf['forceval']);
4267 $options_data[] = $tf['id'].':'.$optsett[0];
4268 }
4269 }
4270 }
4271 return array('city_taxes' => $taxes, 'fees' => $fees, 'options' => $options_data);
4272 }
4273
4274 /**
4275 * From a list of option/extras records, it returns an array with the options/extras
4276 * that do not support age intervals, and options for the children age intervals.
4277 * To be used as list($optionals, $ageintervals) = VikBooking::loadOptionAgeIntervals($optionals).
4278 *
4279 * @param array $optionals the full list of options/extras records.
4280 * @param int $adults the number of adults in the room requested.
4281 * @param int $children the number of children in the room requested.
4282 *
4283 * @return array the filtered regular and children age intervals options.
4284 */
4285 public static function loadOptionAgeIntervals($optionals, $adults = null, $children = null)
4286 {
4287 // container for age intervals
4288 $ageintervals = [];
4289
4290 // regular options should start as an array
4291 if (!is_array($optionals)) {
4292 $optionals = [];
4293 }
4294
4295 // check for the first valid age intervals option
4296 foreach ($optionals as $kopt => $opt) {
4297 if (!empty($opt['ageintervals'])) {
4298 $intervals = explode(';;', $opt['ageintervals']);
4299 foreach ($intervals as $intv) {
4300 if (empty($intv)) {
4301 continue;
4302 }
4303 $parts = explode('_', $intv);
4304 if (count($parts) >= 3) {
4305 // age intervals option found, get the first and only one
4306 $ageintervals = $optionals[$kopt];
4307 break 2;
4308 }
4309 }
4310 }
4311 }
4312
4313 if ($ageintervals) {
4314 /**
4315 * We allow price adjustments for a minimum total guests by overriding the costs
4316 * for each age interval for specific child numbers when the party covers some children.
4317 * For example, when children should pay only starting from the 5th guest (minguestsnum=4),
4318 * in case of a room party for 2 adults and 3 children, only the 3rd child should pay the
4319 * regular fees, while the 1st and second children should have all age intervals to 0.00.
4320 *
4321 * @since 1.13.5
4322 */
4323 $oparams = !empty($ageintervals['oparams']) ? json_decode($ageintervals['oparams'], true) : array();
4324 $oparams = !is_array($oparams) ? array() : $oparams;
4325 $adults = (int)$adults;
4326 $children = (int)$children;
4327 $valid_guesttype = (!empty($oparams['mingueststype']) && $oparams['mingueststype'] == 'guests');
4328 if ($children > 0 && !empty($oparams['minguestsnum']) && $valid_guesttype && ($adults + $children) > $oparams['minguestsnum']) {
4329 $children_to_pay = ($adults + $children) - $oparams['minguestsnum'];
4330 if ($children_to_pay < $children) {
4331 // compose string for free age intervals
4332 $intervals = explode(';;', $ageintervals['ageintervals']);
4333 foreach ($intervals as $k => $v) {
4334 $parts = explode('_', $v);
4335 // set cost to 0 (key = 2)
4336 $parts[2] = '0';
4337 $intervals[$k] = implode('_', $parts);
4338 }
4339 $free_ageintervals = implode(';;', $intervals);
4340 for ($i = 1; $i <= $children; $i++) {
4341 if (($children - $i + 1) <= $children_to_pay) {
4342 // costs for this Nth last child should be regular as this child should pay
4343 $ageintervals['ageintervals_child' . $i] = $ageintervals['ageintervals'];
4344 } else {
4345 // override costs to 0.00 for this Nth first child since it's covered by the min guests number
4346 $ageintervals['ageintervals_child' . $i] = $free_ageintervals;
4347 }
4348 }
4349 }
4350 }
4351
4352 // remove age intervals from regular options
4353 foreach ($optionals as $kopt => $opt) {
4354 if ($opt['id'] == $ageintervals['id'] || !empty($opt['ageintervals'])) {
4355 // unset the option of type age intervals from the regular options
4356 unset($optionals[$kopt]);
4357 }
4358 }
4359 }
4360
4361 // return the filtered list of regular options and age intervals options
4362 return [$optionals, $ageintervals];
4363 }
4364
4365 /**
4366 * Returns an array of overrides (if any) for the specific children Nth number in the party.
4367 * This is because some children in the party may not need to pay anything due to the min guests.
4368 *
4369 * @param array $optional the option full record to parse.
4370 * @param int $adults the number of adults in the room requested.
4371 * @param int $children the number of children in the room requested.
4372 *
4373 * @return array associative array with default age intervals and override strings.
4374 *
4375 * @since 1.13.5
4376 */
4377 public static function getOptionIntervalChildOverrides($optional, $adults, $children)
4378 {
4379 if (!is_array($optional)) {
4380 $optional = array();
4381 }
4382
4383 $overrides = array(
4384 'ageintervals' => (!empty($optional['ageintervals']) ? $optional['ageintervals'] : '')
4385 );
4386
4387 $oparams = !empty($optional['oparams']) ? json_decode($optional['oparams'], true) : array();
4388 $oparams = !is_array($oparams) ? array() : $oparams;
4389 $adults = (int)$adults;
4390 $children = (int)$children;
4391
4392 if (!count($oparams)) {
4393 return $overrides;
4394 }
4395
4396 $valid_guesttype = (!empty($oparams['mingueststype']) && $oparams['mingueststype'] == 'guests');
4397 if ($children > 0 && !empty($oparams['minguestsnum']) && $valid_guesttype && ($adults + $children) > $oparams['minguestsnum']) {
4398 $children_to_pay = ($adults + $children) - $oparams['minguestsnum'];
4399 if ($children_to_pay < $children) {
4400 // compose string for free age intervals
4401 $intervals = explode(';;', $optional['ageintervals']);
4402 foreach ($intervals as $k => $v) {
4403 $parts = explode('_', $v);
4404 // set cost to 0 (key = 2)
4405 $parts[2] = '0';
4406 $intervals[$k] = implode('_', $parts);
4407 }
4408 $free_ageintervals = implode(';;', $intervals);
4409 for ($i = 1; $i <= $children; $i++) {
4410 if (($children - $i + 1) <= $children_to_pay) {
4411 // costs for this Nth last child should be regular as this child should pay
4412 $overrides['ageintervals_child' . $i] = $optional['ageintervals'];
4413 } else {
4414 // override costs to 0.00 for this Nth first child since it's covered by the min guests number
4415 $overrides['ageintervals_child' . $i] = $free_ageintervals;
4416 }
4417 }
4418 }
4419 }
4420
4421 return $overrides;
4422 }
4423
4424 /**
4425 * Returns the number/index of the children being parsed given the full list of room options string,
4426 * the current key in the array of the split room options string, the ID of the ageintervals option,
4427 * and the total number of children. This is useful for later applying the cost overrides for the
4428 * children ages through the method getOptionIntervalChildOverrides().
4429 *
4430 * @param string $roptstr plain room option string of "#__vikbooking_ordersrooms".
4431 * @param int $optid the ID of the option of type age intervals to check.
4432 * @param int $roptkey the current position in the loop of the room options string.
4433 * @param int $children the number of children in the room requested.
4434 *
4435 * @return int the position/number of the children being parsed, -1 if not found.
4436 *
4437 * @since 1.13.5
4438 */
4439 public static function getRoomOptionChildNumber($roptstr, $optid, $roptkey, $children)
4440 {
4441 // default to -1 for not found
4442 $child_num = -1;
4443
4444 $valid_opt_counter = 0;
4445
4446 $roptions = explode(";", $roptstr);
4447 foreach ($roptions as $k => $opt) {
4448 if (empty($opt)) {
4449 continue;
4450 }
4451 $optvals = explode(":", $opt);
4452 /**
4453 * In some cases, like the "saveorder" task, the room option string may contain
4454 * the room number beside the option ID, separated with an underscore.
4455 * If an underscore is present, we need to split it to find the option ID.
4456 */
4457 if (strpos($optvals[0], '_') !== false) {
4458 // underscore found in room number portion, extract the option ID
4459 $rn_parts = explode('_', $optvals[0]);
4460 // 0th element is the room number in the party, 1st elem is the option ID
4461 $optvals[0] = $rn_parts[1];
4462 }
4463 //
4464 if ((int)$optvals[0] != (int)$optid) {
4465 // we are not interested into this option ID
4466 continue;
4467 }
4468 // increase counter for this option ID
4469 $valid_opt_counter++;
4470
4471 if ((int)$k == (int)$roptkey && $valid_opt_counter <= $children) {
4472 // children position found, we need to return it as a 0th base
4473 $child_num = ($valid_opt_counter - 1);
4474 break;
4475 }
4476 }
4477
4478 return $child_num;
4479 }
4480
4481 public static function getOptionIntervalsCosts($intvstr) {
4482 $optcosts = array();
4483 $intervals = explode(';;', $intvstr);
4484 foreach ($intervals as $kintv => $intv) {
4485 if (empty($intv)) continue;
4486 $parts = explode('_', $intv);
4487 if (count($parts) >= 3) {
4488 $optcosts[$kintv] = (float)$parts[2];
4489 }
4490 }
4491 return $optcosts;
4492 }
4493
4494 public static function getOptionIntervalsAges($intvstr) {
4495 $optages = array();
4496 $intervals = explode(';;', $intvstr);
4497 foreach ($intervals as $kintv => $intv) {
4498 if (empty($intv)) continue;
4499 $parts = explode('_', $intv);
4500 if (count($parts) >= 3) {
4501 $optages[$kintv] = $parts[0].' - '.$parts[1];
4502 }
4503 }
4504 return $optages;
4505 }
4506
4507 public static function getOptionIntervalsPercentage($intvstr) {
4508 /* returns an associative array to tell whether an interval has a percentage cost (VBO 1.8) */
4509 $optcostspcent = array();
4510 $intervals = explode(';;', $intvstr);
4511 foreach ($intervals as $kintv => $intv) {
4512 if (empty($intv)) continue;
4513 $parts = explode('_', $intv);
4514 if (count($parts) >= 3) {
4515 //fixed amount
4516 $setval = 0;
4517 if (array_key_exists(3, $parts) && strpos($parts[3], '%b') !== false) {
4518 //percentage value of the room base cost (VBO 1.10)
4519 $setval = 2;
4520 } elseif (array_key_exists(3, $parts) && strpos($parts[3], '%') !== false) {
4521 //percentage value of the adults tariff
4522 $setval = 1;
4523 }
4524 $optcostspcent[$kintv] = $setval;
4525 }
4526 }
4527 return $optcostspcent;
4528 }
4529
4530 public static function dayValidTs($days, $first, $second)
4531 {
4532 $secdiff = $second - $first;
4533 $daysdiff = $secdiff / 86400;
4534 if (is_int($daysdiff)) {
4535 if ($daysdiff < 1) {
4536 $daysdiff = 1;
4537 }
4538 } else {
4539 if ($daysdiff < 1) {
4540 $daysdiff = 1;
4541 } else {
4542 $sum = floor($daysdiff) * 86400;
4543 $newdiff = $secdiff - $sum;
4544 $maxhmore = self::getHoursMoreRb() * 3600;
4545 if ($maxhmore >= $newdiff) {
4546 $daysdiff = floor($daysdiff);
4547 } else {
4548 $daysdiff = ceil($daysdiff);
4549 }
4550 }
4551 }
4552
4553 return ($daysdiff == $days);
4554 }
4555
4556 /**
4557 * Calculates the stay fee inclusive of taxes.
4558 *
4559 * @param float $cost the cost to evaluate.
4560 * @param int $idprice the rate plan ID associated.
4561 *
4562 * @return float the given cost after tax, if taxes had to be applied.
4563 */
4564 public static function sayCostPlusIva($cost, $idprice)
4565 {
4566 $ivainclusa = self::ivaInclusa();
4567 if ($ivainclusa) {
4568 return $cost;
4569 }
4570
4571 static $tax_rplans_map = [];
4572
4573 if (array_key_exists($idprice, $tax_rplans_map)) {
4574 $tax_rate = $tax_rplans_map[$idprice];
4575 } else {
4576 $dbo = JFactory::getDbo();
4577 $q = "SELECT `p`.`idiva`,`i`.`aliq`,`i`.`taxcap` FROM `#__vikbooking_prices` AS `p` LEFT JOIN `#__vikbooking_iva` `i` ON `i`.`id`=`p`.`idiva` WHERE `p`.`id`=" . (int)$idprice . ";";
4578 $dbo->setQuery($q);
4579 $tax_rate = $dbo->loadAssoc();
4580
4581 // cache value
4582 $tax_rplans_map[$idprice] = $tax_rate;
4583 }
4584
4585 if (!$tax_rate || empty($tax_rate['aliq'])) {
4586 return $cost;
4587 }
4588
4589 $subt = 100 + $tax_rate['aliq'];
4590 $op = ($cost * $subt / 100);
4591
4592 /**
4593 * Tax Cap implementation for prices tax excluded (most common).
4594 *
4595 * @since 1.12
4596 */
4597 if ($tax_rate['taxcap'] > 0 && ($op - $cost) > $tax_rate['taxcap']) {
4598 $op = ($cost + $tax_rate['taxcap']);
4599 }
4600
4601 // apply rounding to avoid issues with multiple tax rates when tax excluded
4602 $formatvals = self::getNumberFormatData();
4603 $formatparts = explode(':', $formatvals);
4604 $rounded_op = round($op, (int)$formatparts[0]);
4605 if ($rounded_op > $op) {
4606 return $rounded_op;
4607 }
4608
4609 /**
4610 * When using base costs with decimals, and no tax rates assigned, maybe for a foreigners rate plan,
4611 * no rounding is ever made, and so we should always apply such rounding to avoid getting decimals if they should be 0.
4612 *
4613 * @since 1.11.1
4614 */
4615 return round($op, (int)$formatparts[0]);
4616 }
4617
4618 /**
4619 * Calculates the stay fee exclusive of taxes.
4620 *
4621 * @param float $cost the cost to evaluate.
4622 * @param int $idprice the rate plan ID associated.
4623 *
4624 * @return float the given cost before tax.
4625 */
4626 public static function sayCostMinusIva($cost, $idprice)
4627 {
4628 $ivainclusa = self::ivaInclusa();
4629 if (!$ivainclusa) {
4630 return $cost;
4631 }
4632
4633 static $tax_rplans_map = [];
4634
4635 if (array_key_exists($idprice, $tax_rplans_map)) {
4636 $tax_rate = $tax_rplans_map[$idprice];
4637 } else {
4638 $dbo = JFactory::getDbo();
4639 $q = "SELECT `p`.`idiva`,`i`.`aliq`,`i`.`taxcap` FROM `#__vikbooking_prices` AS `p` LEFT JOIN `#__vikbooking_iva` `i` ON `i`.`id`=`p`.`idiva` WHERE `p`.`id`=" . (int)$idprice . ";";
4640 $dbo->setQuery($q);
4641 $tax_rate = $dbo->loadAssoc();
4642
4643 // cache value
4644 $tax_rplans_map[$idprice] = $tax_rate;
4645 }
4646
4647 if (!$tax_rate || empty($tax_rate['aliq'])) {
4648 return $cost;
4649 }
4650
4651 $subt = 100 + $tax_rate['aliq'];
4652 $op = ($cost * 100 / $subt);
4653
4654 /**
4655 * Tax Cap implementation also when prices tax included.
4656 *
4657 * @since 1.12
4658 */
4659 if ($tax_rate['taxcap'] > 0 && ($cost - $op) > $tax_rate['taxcap']) {
4660 $op = ($cost - $tax_rate['taxcap']);
4661 }
4662
4663 // apply rounding to avoid issues with multiple tax rates when tax included
4664 $formatvals = self::getNumberFormatData();
4665 $formatparts = explode(':', $formatvals);
4666 $rounded_op = round($op, (int)$formatparts[0]);
4667 if ($rounded_op < $op) {
4668 return $rounded_op;
4669 }
4670
4671 /**
4672 * When using base costs with decimals, and no tax rates assigned, maybe for a foreigners rate plan,
4673 * no rounding is ever made, and so we should always apply such rounding to avoid getting decimals if they should be 0.
4674 *
4675 * @since 1.11.1
4676 */
4677 return round($op, (int)$formatparts[0]);
4678 }
4679
4680 /**
4681 * Given an option/extra cost, determines whether taxes should be applied over it.
4682 * Can also be used to calculate taxes on the extra costs per room in the bookings
4683 *
4684 * @param float $cost the cost to evaluate, option or extra service fee.
4685 * @param int $idiva the ID of the tax rate to use to apply taxes.
4686 * @param bool $force if true, taxes will always be applied.
4687 *
4688 * @return float the given option/extra cost plus tax, if taxes had to be applied.
4689 *
4690 * @since 1.15.0 (J) - 1.5.0 (WP) added third argument $force to comply with VCM.
4691 */
4692 public static function sayOptionalsPlusIva($cost, $idiva, $force = false)
4693 {
4694 $ivainclusa = self::ivaInclusa();
4695 if ($ivainclusa && $force !== true) {
4696 return $cost;
4697 }
4698
4699 $tax_rate = VBOTaxonomySummary::getTaxRateRecord($idiva);
4700 if (!$tax_rate) {
4701 return $cost;
4702 }
4703
4704 $subt = 100 + $tax_rate['aliq'];
4705 $op = ($cost * $subt / 100);
4706
4707 /**
4708 * Tax Cap implementation for prices tax excluded (most common).
4709 *
4710 * @since 1.12
4711 */
4712 if ($tax_rate['taxcap'] > 0 && ($op - $cost) > $tax_rate['taxcap']) {
4713 $op = ($cost + $tax_rate['taxcap']);
4714 }
4715
4716 // apply rounding to avoid issues with multiple tax rates when tax excluded
4717 $formatvals = self::getNumberFormatData();
4718 $formatparts = explode(':', $formatvals);
4719 $rounded_op = round($op, (int)$formatparts[0]);
4720 if ($rounded_op > $op) {
4721 return $rounded_op;
4722 }
4723
4724 return $op;
4725 }
4726
4727 /**
4728 * Calculates the net amount for an option/extra service.
4729 *
4730 * @param float $cost the cost to evaluate, option or extra service fee.
4731 * @param int $idiva the ID of the tax rate to use to apply taxes.
4732 *
4733 * @return float the given option/extra cost before tax, if taxes were inclusive.
4734 */
4735 public static function sayOptionalsMinusIva($cost, $idiva)
4736 {
4737 if (empty($idiva)) {
4738 return $cost;
4739 }
4740
4741 $ivainclusa = self::ivaInclusa();
4742 if (!$ivainclusa) {
4743 return $cost;
4744 }
4745
4746 $tax_rate = VBOTaxonomySummary::getTaxRateRecord($idiva);
4747 if (!$tax_rate) {
4748 return $cost;
4749 }
4750
4751 $subt = 100 + $tax_rate['aliq'];
4752 $op = ($cost * 100 / $subt);
4753
4754 /**
4755 * Tax Cap implementation also when prices tax included.
4756 *
4757 * @since 1.12
4758 */
4759 if ($tax_rate['taxcap'] > 0 && ($cost - $op) > $tax_rate['taxcap']) {
4760 $op = ($cost - $tax_rate['taxcap']);
4761 }
4762
4763 // apply rounding to avoid issues with multiple tax rates when tax included
4764 $formatvals = self::getNumberFormatData();
4765 $formatparts = explode(':', $formatvals);
4766 $rounded_op = round($op, (int)$formatparts[0]);
4767 if ($rounded_op < $op) {
4768 return $rounded_op;
4769 }
4770
4771 return $op;
4772 }
4773
4774 /**
4775 * Given a cost, determines whether taxes should be applied over it.
4776 *
4777 * @param float $cost the cost to evaluate, could be a room custom price.
4778 * @param int $idiva the ID of the tax rate to use to apply taxes.
4779 * @param bool $force if true, taxes will always be applied.
4780 *
4781 * @return float the given cost plus tax, if taxes had to be applied.
4782 *
4783 * @since 1.15.0 (J) - 1.5.0 (WP) added third argument $force to comply with VCM.
4784 */
4785 public static function sayPackagePlusIva($cost, $idiva, $force = false)
4786 {
4787 $ivainclusa = self::ivaInclusa();
4788 if ($ivainclusa && $force !== true) {
4789 return $cost;
4790 }
4791
4792 $tax_rate = VBOTaxonomySummary::getTaxRateRecord($idiva);
4793 if (!$tax_rate) {
4794 return $cost;
4795 }
4796
4797 $subt = 100 + $tax_rate['aliq'];
4798 $op = ($cost * $subt / 100);
4799
4800 /**
4801 * Tax Cap implementation for prices tax excluded (most common).
4802 *
4803 * @since 1.12
4804 */
4805 if ($tax_rate['taxcap'] > 0 && ($op - $cost) > $tax_rate['taxcap']) {
4806 $op = ($cost + $tax_rate['taxcap']);
4807 }
4808
4809 // apply rounding to avoid issues with multiple tax rates when tax excluded
4810 $formatvals = self::getNumberFormatData();
4811 $formatparts = explode(':', $formatvals);
4812 $rounded_op = round($op, (int)$formatparts[0]);
4813 if ($rounded_op > $op) {
4814 return $rounded_op;
4815 }
4816
4817 return $op;
4818 }
4819
4820 /**
4821 * Calculates the net amount for a package.
4822 *
4823 * @param float $cost the cost to evaluate, option or extra service fee.
4824 * @param int $idiva the ID of the tax rate to use to apply taxes.
4825 * @param bool $force_invoice_excltax whether to force the deduction of taxes.
4826 *
4827 * @return float the given cost before tax.
4828 */
4829 public static function sayPackageMinusIva($cost, $idiva, $force_invoice_excltax = false)
4830 {
4831 $ivainclusa = self::ivaInclusa();
4832 if (!$ivainclusa && $force_invoice_excltax !== true) {
4833 return $cost;
4834 }
4835
4836 $tax_rate = VBOTaxonomySummary::getTaxRateRecord($idiva);
4837 if (!$tax_rate) {
4838 return $cost;
4839 }
4840
4841 $subt = 100 + $tax_rate['aliq'];
4842 $op = ($cost * 100 / $subt);
4843
4844 /**
4845 * Tax Cap implementation also when prices tax included.
4846 *
4847 * @since 1.12
4848 */
4849 if ($tax_rate['taxcap'] > 0 && ($cost - $op) > $tax_rate['taxcap']) {
4850 $op = ($cost - $tax_rate['taxcap']);
4851 }
4852
4853 // apply rounding to avoid issues with multiple tax rates when tax included
4854 $formatvals = self::getNumberFormatData();
4855 $formatparts = explode(':', $formatvals);
4856 $rounded_op = round($op, (int)$formatparts[0]);
4857 if ($rounded_op < $op) {
4858 return $rounded_op;
4859 }
4860
4861 return $op;
4862 }
4863
4864 public static function getSecretLink()
4865 {
4866 $dbo = JFactory::getDbo();
4867
4868 $sid = mt_rand();
4869
4870 $q = "SELECT `sid` FROM `#__vikbooking_orders`;";
4871 $dbo->setQuery($q);
4872 $all = $dbo->loadAssocList();
4873 if ($all) {
4874 $list = [];
4875 foreach ($all as $s) {
4876 $list[] = $s['sid'];
4877 }
4878 if (in_array($sid, $list)) {
4879 while(in_array($sid, $list)) {
4880 $sid++;
4881 }
4882 }
4883 }
4884
4885 return $sid;
4886 }
4887
4888 /**
4889 * Generates a confirmation number for the given booking.
4890 * Modified the way random string-numbers are obtained to
4891 * avoid any possible brute-force attempt.
4892 *
4893 * @since 1.15.1 (J) - 1.5.4 (WP)
4894 */
4895 public static function generateConfirmNumber($oid, $update = true)
4896 {
4897 $dbo = JFactory::getDbo();
4898
4899 $confirmnumb = date('y') . $oid;
4900 for ($i = 0; $i < 8; $i++) {
4901 $confirmnumb .= rand(0, 9);
4902 }
4903
4904 if ($update) {
4905 $q = "UPDATE `#__vikbooking_orders` SET `confirmnumber`=" . $dbo->quote($confirmnumb) . " WHERE `id`=" . (int)$oid . ";";
4906 $dbo->setQuery($q);
4907 $dbo->execute();
4908 }
4909
4910 return $confirmnumb;
4911 }
4912
4913 public static function buildCustData($arr, $sep) {
4914 $cdata = "";
4915 foreach ($arr as $k => $e) {
4916 if (strlen($e)) {
4917 $cdata .= (strlen($k) > 0 ? $k . ": " : "") . $e . $sep;
4918 }
4919 }
4920 return $cdata;
4921 }
4922
4923 /**
4924 * This method parses the Joomla menu object to see if a menu item of a
4925 * specific type is available, to get its ID. Useful when links should be
4926 * displayed in pages where there is no Itemid set (booking details pages).
4927 *
4928 * @param array $viewtypes list of accepted menu items
4929 * @param string $lang the optional language to use.
4930 *
4931 * @return int
4932 *
4933 * @since 1.15.6 (J) - 1.5.12 (WP) added second argument $lang.
4934 */
4935 public static function findProperItemIdType($viewtypes, $lang = null)
4936 {
4937 if (VBOPlatformDetection::isWordPress()) {
4938 $model = JModel::getInstance('vikbooking', 'shortcodes', 'admin');
4939
4940 $itemid = $model->best($viewtypes, $lang);
4941
4942 if ($itemid) {
4943 return $itemid;
4944 }
4945
4946 return 0;
4947 }
4948
4949 $bestitemid = 0;
4950
4951 $current_lang = !empty($lang) ? $lang : JFactory::getLanguage()->getTag();
4952
4953 $app = JFactory::getApplication();
4954
4955 $menu = $app->getMenu('site');
4956
4957 if (!$menu) {
4958 return 0;
4959 }
4960
4961 $menu_items = $menu->getMenu();
4962
4963 if (!$menu_items) {
4964 return 0;
4965 }
4966
4967 foreach ($menu_items as $itemid => $item) {
4968 if (isset($item->query['option']) && $item->query['option'] == 'com_vikbooking' && in_array($item->query['view'], $viewtypes)) {
4969 // proper menu item type found
4970 $bestitemid = empty($bestitemid) ? $itemid : $bestitemid;
4971
4972 if (isset($item->language) && $item->language == $current_lang) {
4973 // we found the exact menu item type for the given language
4974 return $itemid;
4975 }
4976 }
4977 }
4978
4979 return $bestitemid;
4980 }
4981
4982 /**
4983 * Rewrites an internal URI that needs to be used outside of the website.
4984 * This means that the routed URI MUST start with the base path of the site.
4985 *
4986 * @param mixed $query The query string or an associative array of data.
4987 * @param boolean $xhtml Replace & by &amp; for XML compliance.
4988 * @param mixed $itemid The itemid to use. If null, the current one will be used.
4989 *
4990 * @return string The complete routed URI.
4991 *
4992 * @since 1.15.0 (J) - 1.5.0 (WP) adopted use of VBOPlatformUriAware, which also supports
4993 * routing from back-end (if available on the CMS version).
4994 */
4995 public static function externalroute($query = '', $xhtml = true, $itemid = null)
4996 {
4997 return VBOFactory::getPlatform()->getUri()->route($query, $xhtml, $itemid);
4998 }
4999
5000 /**
5001 * Generates an iCal file to be attached to the email message for the
5002 * customer or the administrator with some basic booking details.
5003 *
5004 * @param string $recip either admin or customer.
5005 * @param array $booking the booking array or some keys.
5006 *
5007 * @return mixed string in case of success, false otherwise.
5008 *
5009 * @since 1.12.0
5010 */
5011 public static function getEmailIcal($recip, $booking) {
5012 // load configuration setting
5013 $attachical = self::attachIcal();
5014
5015 if ($attachical === 0) {
5016 // do not attach any iCal file
5017 return false;
5018 }
5019
5020 if ($attachical === 2 && strpos($recip, 'admin') === false) {
5021 // skip the iCal for the admin
5022 return false;
5023 }
5024
5025 if ($attachical === 3 && strpos($recip, 'admin') !== false) {
5026 // skip the iCal for the customer
5027 return false;
5028 }
5029
5030 if (strpos($recip, 'admin') !== false) {
5031 // prepare event description and summary for the admin
5032 $description = $booking['custdata'];
5033 $summary = !empty($booking['subject']) ? $booking['subject'] : '';
5034 $fname = $booking['ts'] . '.ics';
5035 } else {
5036 // event description and summary for the customer
5037 $description = '';
5038 $summary = self::getFrontTitle();
5039 $fname = 'reservation_reminder.ics';
5040 }
5041
5042 // prepare iCal head
5043 $company_name = self::getFrontTitle();
5044 $ics_str = "BEGIN:VCALENDAR\r\n" .
5045 "PRODID:-//".$company_name."//".JUri::root()." 1.0//EN\r\n" .
5046 "CALSCALE:GREGORIAN\r\n" .
5047 "VERSION:2.0\r\n";
5048 // compose iCal body
5049 $ics_str .= 'BEGIN:VEVENT'."\r\n";
5050 $ics_str .= 'DTEND;VALUE=DATE:'.date('Ymd', $booking['checkout'])."\r\n";
5051 $ics_str .= 'DTSTART;VALUE=DATE:'.date('Ymd', $booking['checkin'])."\r\n";
5052 $ics_str .= 'UID:'.sha1($booking['ts'])."\r\n";
5053 $ics_str .= 'DESCRIPTION:'.preg_replace('/([\,;])/','\\\$1', $description)."\r\n";
5054 $ics_str .= 'SUMMARY:'.preg_replace('/([\,;])/','\\\$1', $summary)."\r\n";
5055 $ics_str .= 'LOCATION:'.preg_replace('/([\,;])/','\\\$1', $company_name)."\r\n";
5056 $ics_str .= 'END:VEVENT'."\r\n";
5057 // close iCal file content
5058 $ics_str .= "END:VCALENDAR";
5059
5060 /**
5061 * Trigger event to allow third party plugins to overwrite the iCal file.
5062 *
5063 * @since 1.16.0 (J) - 1.6.0 (WP)
5064 */
5065 VBOFactory::getPlatform()->getDispatcher()->trigger('onBeforeCreateMailIcalVikBooking', [$recip, $booking, &$ics_str]);
5066
5067 if (empty($ics_str)) {
5068 return false;
5069 }
5070
5071 // store the event onto a .ics file. We use the resources folder in back-end.
5072 $fpath = VBO_ADMIN_PATH . DIRECTORY_SEPARATOR . 'resources' . DIRECTORY_SEPARATOR . $fname;
5073 $fp = fopen($fpath, 'w+');
5074 $bytes = fwrite($fp, $ics_str);
5075 fclose($fp);
5076
5077 return $bytes ? $fpath : false;
5078 }
5079
5080 public static function loadEmailTemplate($booking_info = array()) {
5081 define('_VIKBOOKINGEXEC', '1');
5082 ob_start();
5083 include VBO_SITE_PATH . DIRECTORY_SEPARATOR . "helpers" . DIRECTORY_SEPARATOR . "email_tmpl.php";
5084 $content = ob_get_contents();
5085 ob_end_clean();
5086 return $content;
5087 }
5088
5089 /**
5090 * Parses the raw HTML content of the booking email template.
5091 *
5092 * @param string $tmpl the raw content of the template.
5093 * @param mixed $bid int for the booking ID or booking array.
5094 * @param array $rooms list of rooms booked and translated.
5095 * @param array $rates list of translated rates for the booked rooms.
5096 * @param array $options list of translated options booked.
5097 * @param float [$total] the booking total amount (in case it has changed).
5098 * @param string [$link] the booking link can be passed for the no-deposit.
5099 *
5100 * @return string the HTML content of the parsed email template.
5101 *
5102 * @since 1.13 with different arguments.
5103 */
5104 public static function parseEmailTemplate($tmpl, $bid, $rooms, $rates, $options, $total = 0, $link = null)
5105 {
5106 $app = JFactory::getApplication();
5107 $dbo = JFactory::getDbo();
5108 $vbo_tn = self::getTranslator();
5109
5110 // availability helper
5111 $av_helper = self::getAvailabilityInstance();
5112
5113 // get necessary values
5114 if (is_array($bid)) {
5115 // we got the full booking record
5116 $order_info = $bid;
5117 $bid = $order_info['id'];
5118 } else {
5119 $order_info = array();
5120 $q = "SELECT * FROM `#__vikbooking_orders` WHERE `id`=" . (int)$bid . ";";
5121 $dbo->setQuery($q);
5122 $order_info = $dbo->loadAssoc();
5123 if (!$order_info) {
5124 throw new Exception('Booking not found', 404);
5125 }
5126 }
5127
5128 $tars_info = array();
5129 $q = "SELECT `or`.`id`,`or`.`idroom`,`or`.`idtar`,`d`.`idprice` FROM `#__vikbooking_ordersrooms` AS `or` LEFT JOIN `#__vikbooking_dispcost` AS `d` ON `or`.`idtar`=`d`.`id` WHERE `or`.`idorder`=" . (int)$order_info['id'] . ";";
5130 $dbo->setQuery($q);
5131 $tars_info = $dbo->loadAssocList();
5132 if (!$tars_info) {
5133 throw new Exception('No rooms found', 404);
5134 }
5135
5136 /**
5137 * Split stay reservation.
5138 *
5139 * @since 1.16.0 (J) - 1.6.0 (WP)
5140 */
5141 $room_stay_dates = [];
5142 if ($order_info['split_stay']) {
5143 if ($order_info['status'] == 'confirmed') {
5144 $room_stay_dates = $av_helper->loadSplitStayBusyRecords($order_info['id']);
5145 } else {
5146 $room_stay_dates = VBOFactory::getConfig()->getArray('split_stay_' . $order_info['id'], []);
5147 }
5148 // immediately count the number of nights of stay for each split room
5149 foreach ($room_stay_dates as $sps_r_k => $sps_r_v) {
5150 if (!empty($sps_r_v['checkin_ts']) && !empty($sps_r_v['checkout_ts'])) {
5151 // overwrite values for compatibility with non-confirmed bookings
5152 $sps_r_v['checkin'] = $sps_r_v['checkin_ts'];
5153 $sps_r_v['checkout'] = $sps_r_v['checkout_ts'];
5154 }
5155 $sps_r_v['nights'] = $av_helper->countNightsOfStay($sps_r_v['checkin'], $sps_r_v['checkout']);
5156 // overwrite the whole array
5157 $room_stay_dates[$sps_r_k] = $sps_r_v;
5158 }
5159 }
5160
5161 // check if the language in use is the same as the one used during the checkout
5162 $lang = JFactory::getLanguage();
5163 if (!empty($order_info['lang'])) {
5164 if ($lang->getTag() != $order_info['lang']) {
5165 $lang->load('com_vikbooking', (defined('VIKBOOKING_LANG') ? VIKBOOKING_LANG : JPATH_SITE), $order_info['lang'], true);
5166 if (VBOPlatformDetection::isJoomla()) {
5167 $lang->load('joomla', JPATH_SITE, $order_info['lang'], true);
5168 }
5169 }
5170 if ($vbo_tn->getDefaultLang() != $order_info['lang']) {
5171 // force the translation to start because contents should be translated
5172 $vbo_tn::$force_tolang = $order_info['lang'];
5173 }
5174 }
5175
5176 // values for replacements
5177 $company_name = self::getFrontTitle();
5178 $currencyname = self::getCurrencyName();
5179 $sitelogo = self::getSiteLogo();
5180 $footermess = self::getFooterOrdMail($vbo_tn);
5181 $dateformat = self::getDateFormat();
5182 $datesep = self::getDateSeparator();
5183 if ($dateformat == "%d/%m/%Y") {
5184 $df = 'd/m/Y';
5185 } elseif ($dateformat == "%m/%d/%Y") {
5186 $df = 'm/d/Y';
5187 } else {
5188 $df = 'Y/m/d';
5189 }
5190 $create_date = date(str_replace("/", $datesep, $df) . ' H:i', $order_info['ts']);
5191 $checkin_date = date(str_replace("/", $datesep, $df) . ' H:i', $order_info['checkin']);
5192 $checkout_date = date(str_replace("/", $datesep, $df) . ' H:i', $order_info['checkout']);
5193 $customer_info = nl2br($order_info['custdata']);
5194 $company_logo = '';
5195 if (!empty($sitelogo) && is_file(VBO_ADMIN_PATH . DIRECTORY_SEPARATOR . 'resources'. DIRECTORY_SEPARATOR . $sitelogo)) {
5196 $company_logo = '<img src="' . VBO_ADMIN_URI . 'resources/' . $sitelogo . '" alt="' . $company_name . '" />';
5197 }
5198 if ($order_info['status'] == 'cancelled') {
5199 $confirmnumber = '';
5200 $status_str = JText::translate('VBCANCELLED');
5201 } elseif ($order_info['status'] == 'standby') {
5202 $confirmnumber = '';
5203 $status_str = JText::translate('VBWAITINGFORPAYMENT');
5204 if (!empty($order_info['type']) && !strcasecmp($order_info['type'], 'Inquiry')) {
5205 // this is an inquiry reservation
5206 $status_str = JText::translate('VBO_INQUIRY_PENDING');
5207 }
5208 } else {
5209 $confirmnumber = $order_info['confirmnumber'];
5210 $status_str = JText::translate('VBCOMPLETED');
5211 }
5212 // booking total amount
5213 $total = $total === 0 ? (float)$order_info['total'] : (float)$total;
5214 // booking link
5215 $use_sid = !empty($order_info['idorderota']) && !empty($order_info['channel']) ? $order_info['idorderota'] : $order_info['sid'];
5216 if (is_null($link)) {
5217 $lang_link = !empty($order_info['lang']) ? "&lang={$order_info['lang']}" : '';
5218 $link = self::externalroute("index.php?option=com_vikbooking&view=booking&sid={$use_sid}&ts={$order_info['ts']}{$lang_link}", false);
5219 }
5220
5221 // raw HTML content
5222 $parsed = $tmpl;
5223
5224 /**
5225 * Parse all conditional text rules.
5226 *
5227 * @since 1.14 (J) - 1.4.0 (WP)
5228 */
5229 self::getConditionalRulesInstance()
5230 ->set(array('booking', 'rooms'), array($order_info, $rooms))
5231 ->parseTokens($parsed);
5232 //
5233
5234 // special tokens (tags) replacement
5235 $parsed = str_replace("{logo}", $company_logo, $parsed);
5236 $parsed = str_replace("{company_name}", $company_name, $parsed);
5237 $parsed = str_replace("{order_id}", $order_info['id'], $parsed);
5238 $statusclass = $order_info['status'] == 'confirmed' ? "confirmed" : "standby";
5239 $statusclass = $order_info['status'] == 'cancelled' ? "cancelled" : $statusclass;
5240 $parsed = str_replace("{order_status_class}", $statusclass, $parsed);
5241 $parsed = str_replace("{order_status}", $status_str, $parsed);
5242 $parsed = str_replace("{order_date}", $create_date, $parsed);
5243
5244 // PIN Code
5245 if ($order_info['status'] == 'confirmed' && self::customersPinEnabled()) {
5246 $cpin = self::getCPinIstance();
5247 $customer_pin = $cpin->getPinCodeByOrderId($order_info['id']);
5248 if (!empty($customer_pin)) {
5249 $customer_info .= '<h3>'.JText::translate('VBYOURPIN').': '.$customer_pin.'</h3>';
5250 }
5251 }
5252
5253 $parsed = str_replace("{customer_info}", $customer_info, $parsed);
5254 // Confirmation Number
5255 if (strlen($confirmnumber) > 0) {
5256 $parsed = str_replace("{confirmnumb}", $confirmnumber, $parsed);
5257 } else {
5258 $parsed = preg_replace('#('.preg_quote('{confirmnumb_delimiter}').')(.*)('.preg_quote('{/confirmnumb_delimiter}').')#si', '$1'.' '.'$3', $parsed);
5259 }
5260 $parsed = str_replace("{confirmnumb_delimiter}", "", $parsed);
5261 $parsed = str_replace("{/confirmnumb_delimiter}", "", $parsed);
5262
5263 $roomsnum = count($rooms);
5264 $parsed = str_replace("{rooms_count}", $roomsnum, $parsed);
5265 $roomstr = "";
5266 // Rooms Distinctive Features
5267 preg_match_all('/\{roomfeature ([a-zA-Z0-9 ]+)\}/U', $parsed, $matches);
5268 //
5269 foreach ($rooms as $num => $r) {
5270 $roomstr .= "<strong>".$r['name']."</strong> ".$r['adults']." ".($r['adults'] > 1 ? JText::translate('VBMAILADULTS') : JText::translate('VBMAILADULT')).($r['children'] > 0 ? ", ".$r['children']." ".($r['children'] > 1 ? JText::translate('VBMAILCHILDREN') : JText::translate('VBMAILCHILD')) : "")."<br/>";
5271 // Rooms Distinctive Features
5272 if (is_array($matches[1]) && count($matches[1])) {
5273 $distinctive_features = array();
5274 $rparams = (array)json_decode($r['params'], true);
5275 if (array_key_exists('features', $rparams) && count($rparams['features']) > 0 && array_key_exists('roomindex', $r) && !empty($r['roomindex']) && array_key_exists($r['roomindex'], $rparams['features'])) {
5276 $distinctive_features = $rparams['features'][$r['roomindex']];
5277 }
5278 $docheck = (count($distinctive_features) > 0);
5279 foreach ($matches[1] as $reqf) {
5280 $feature_found = false;
5281 if ($docheck) {
5282 foreach ($distinctive_features as $dfk => $dfv) {
5283 if (stripos($dfk, $reqf) !== false) {
5284 $feature_found = $dfk;
5285 if (strlen(trim($dfk)) == strlen(trim($reqf))) {
5286 break;
5287 }
5288 }
5289 }
5290 }
5291 if ($feature_found !== false && strlen($distinctive_features[$feature_found]) > 0) {
5292 $roomstr .= JText::translate($feature_found).': '.$distinctive_features[$feature_found].'<br/>';
5293 }
5294 $parsed = str_replace("{roomfeature ".$reqf."}", "", $parsed);
5295 }
5296 }
5297 }
5298
5299 // custom fields replace
5300 preg_match_all('/\{customfield ([0-9]+)\}/U', $parsed, $cmatches);
5301 if (is_array($cmatches[1]) && count($cmatches[1])) {
5302 $cfids = array();
5303 foreach ($cmatches[1] as $cfid ) {
5304 $cfids[] = $cfid;
5305 }
5306 $q = "SELECT * FROM `#__vikbooking_custfields` WHERE `id` IN (".implode(", ", $cfids).");";
5307 $dbo->setQuery($q);
5308 $dbo->execute();
5309 $cfields = $dbo->getNumRows() ? $dbo->loadAssocList() : array();
5310 $vbo_tn->translateContents($cfields, '#__vikbooking_custfields');
5311 $cfmap = array();
5312 foreach ($cfields as $cf) {
5313 $cfmap[trim(JText::translate($cf['name']))] = $cf['id'];
5314 }
5315 $cfmapreplace = array();
5316 $partsreceived = explode("\n", $order_info['custdata']);
5317 if (count($partsreceived) > 0) {
5318 foreach ($partsreceived as $pst) {
5319 if (!empty($pst)) {
5320 $tmpdata = explode(":", $pst);
5321 if (array_key_exists(trim($tmpdata[0]), $cfmap)) {
5322 $cfmapreplace[$cfmap[trim($tmpdata[0])]] = trim($tmpdata[1]);
5323 }
5324 }
5325 }
5326 }
5327 foreach ($cmatches[1] as $cfid ) {
5328 if (array_key_exists($cfid, $cfmapreplace)) {
5329 $parsed = str_replace("{customfield ".$cfid."}", $cfmapreplace[$cfid], $parsed);
5330 } else {
5331 $parsed = str_replace("{customfield ".$cfid."}", "", $parsed);
5332 }
5333 }
5334 }
5335
5336 $parsed = str_replace("{rooms_info}", $roomstr, $parsed);
5337 $parsed = str_replace("{checkin_date}", $checkin_date, $parsed);
5338 $parsed = str_replace("{checkout_date}", $checkout_date, $parsed);
5339
5340 // order details
5341 $orderdetails = "";
5342 $room_loop_ind = isset($rooms[0]) ? 0 : 1;
5343 foreach ($rooms as $num => $r) {
5344 $room_id = !empty($r['idroom']) ? $r['idroom'] : $r['id'];
5345 $use_room_ind = $num - $room_loop_ind;
5346 $split_stay_str = '';
5347 $split_stay_info = [];
5348 if ($order_info['split_stay'] && !empty($room_stay_dates) && isset($room_stay_dates[$use_room_ind]) && $room_stay_dates[$use_room_ind]['idroom'] == $room_id) {
5349 $split_stay_info[] = $room_stay_dates[$use_room_ind]['nights'] . ' ' . ($room_stay_dates[$use_room_ind]['nights'] > 1 ? JText::translate('VBDAYS') : JText::translate('VBDAY'));
5350 $split_stay_info[] = date(str_replace("/", $datesep, $df), $room_stay_dates[$use_room_ind]['checkin']) . ' - ' . date(str_replace("/", $datesep, $df), $room_stay_dates[$use_room_ind]['checkout']);
5351 $split_stay_str .= '<br/>' . implode(', ', $split_stay_info);
5352 }
5353
5354 $expdet = explode("\n", isset($rates[$num]) ? $rates[$num] : '-----');
5355 $faredets = explode(":", $expdet[0]);
5356 $orderdetails .= '<div class="roombooked"><strong>' . $r['name'] . '</strong>' . $split_stay_str . '<br/>' . $faredets[0];
5357 if (!empty($expdet[1])) {
5358 $attrfaredets = explode(":", $expdet[1]);
5359 if (strlen($attrfaredets[1]) > 0) {
5360 $orderdetails .= ' - '.$attrfaredets[0].':'.$attrfaredets[1];
5361 }
5362 }
5363 $fareprice = isset($faredets[1]) ? trim(str_replace($currencyname, "", $faredets[1])) : 0;
5364 $orderdetails .= '<div class="service-amount" style="float: right;"><span>'.$currencyname.' '.self::numberFormat($fareprice).'</span></div></div>';
5365 // options
5366 if (isset($options[$num]) && is_array($options[$num]) && count($options[$num]) > 0) {
5367 foreach ($options[$num] as $oo) {
5368 $expopts = explode("\n", $oo);
5369 foreach ($expopts as $optinfo) {
5370 if (!empty($optinfo)) {
5371 $splitopt = explode(":", $optinfo);
5372 $optprice = trim(str_replace($currencyname, "", $splitopt[1]));
5373 $orderdetails .= '<div class="roomoption"><span>'.$splitopt[0].'</span><div class="service-amount" style="float: right;"><span>'.$currencyname.' '.self::numberFormat($optprice).'</span></div></div>';
5374 }
5375 }
5376 }
5377 }
5378 //
5379 if ($roomsnum > 1 && $num < $roomsnum) {
5380 $orderdetails .= '<br/>';
5381 }
5382 }
5383 //
5384 // coupon
5385 if (!empty($order_info['coupon'])) {
5386 $expcoupon = explode(";", $order_info['coupon']);
5387 $orderdetails .= '<br/><div class="discount"><span>'.JText::translate('VBCOUPON').' '.$expcoupon[2].'</span><div class="service-amount" style="float: right;"><span>- '.$currencyname.' '.self::numberFormat($expcoupon[1]).'</span></div></div>';
5388 }
5389 //
5390 // discount payment method
5391 if ($order_info['status'] != 'cancelled') {
5392 $idpayment = $order_info['idpayment'];
5393 if (!empty($idpayment)) {
5394 $exppay = explode('=', $idpayment);
5395 $payment = self::getPayment($exppay[0], $vbo_tn);
5396 if (is_array($payment)) {
5397 if ($payment['charge'] > 0.00 && $payment['ch_disc'] != 1) {
5398 // Discount (not charge)
5399 if ($payment['val_pcent'] == 1) {
5400 // fixed value
5401 $total -= $payment['charge'];
5402 $orderdetails .= '<br/><div class="discount"><span>'.$payment['name'].'</span><div class="service-amount" style="float: right;"><span>- '.$currencyname.' '.self::numberFormat($payment['charge']).'</span></div></div>';
5403 } else {
5404 // percent value
5405 $percent_disc = $total * $payment['charge'] / 100;
5406 $total -= $percent_disc;
5407 $orderdetails .= '<br/><div class="discount"><span>'.$payment['name'].'</span><div class="service-amount" style="float: right;"><span>- '.$currencyname.' '.self::numberFormat($percent_disc).'</span></div></div>';
5408 }
5409 }
5410 }
5411 }
5412 }
5413 //
5414 $parsed = str_replace("{order_details}", $orderdetails, $parsed);
5415 //
5416 $parsed = str_replace("{order_total}", $currencyname.' '.self::numberFormat($total), $parsed);
5417 $parsed = str_replace("{footer_emailtext}", $footermess, $parsed);
5418 $parsed = str_replace("{order_link}", '<a href="'.$link.'">'.$link.'</a>', $parsed);
5419 $parsed = str_replace("{booking_link}", $link, $parsed);
5420
5421 // deposit
5422 $deposit_str = '';
5423 if (!in_array($order_info['status'], array('confirmed', 'cancelled')) && !self::payTotal() && self::allowDepositFromRates($tars_info)) {
5424 $percentdeposit = self::getAccPerCent();
5425 $percentdeposit = self::calcDepositOverride($percentdeposit, $order_info['days']);
5426 if ($percentdeposit > 0 && self::depositAllowedDaysAdv($order_info['checkin'])) {
5427 if (self::getTypeDeposit() == "fixed") {
5428 $deposit_amount = $percentdeposit;
5429 } else {
5430 $deposit_amount = $total * $percentdeposit / 100;
5431 }
5432 if ($deposit_amount > 0) {
5433 $deposit_str = '<div class="deposit"><span>'.JText::translate('VBLEAVEDEPOSIT').'</span><div class="service-amount" style="float: right;"><strong>'.$currencyname.' '.self::numberFormat($deposit_amount).'</strong></div></div>';
5434 }
5435 }
5436 }
5437 $parsed = str_replace("{order_deposit}", $deposit_str, $parsed);
5438 //
5439 // Amount Paid - Remaining Balance - Refunded Amount - Cancellation Fee
5440 $totpaid_str = '';
5441 if ($order_info['refund'] > 0) {
5442 $totpaid_str .= '<div class="amountpaid amountrefunded"><span>' . JText::translate('VBO_AMOUNT_REFUNDED') . '</span><div class="service-amount" style="float: right;"><strong>' . $currencyname . ' ' . self::numberFormat($order_info['refund']) . '</strong></div></div>';
5443 }
5444 if ($order_info['status'] != 'cancelled') {
5445 $tot_paid = $order_info['totpaid'];
5446 $diff_topay = (float)$total - (float)$tot_paid;
5447 if ((float)$tot_paid > 0) {
5448 $totpaid_str .= '<div class="amountpaid"><span>'.JText::translate('VBAMOUNTPAID').'</span><div class="service-amount" style="float: right;"><strong>'.$currencyname.' '.self::numberFormat($tot_paid).'</strong></div></div>';
5449 // only in case the remaining balance is greater than 1 to avoid commissions issues
5450 if ($diff_topay > 1) {
5451 $totpaid_str .= '<div class="amountpaid"><span>'.JText::translate('VBTOTALREMAINING').'</span><div class="service-amount" style="float: right;"><strong>'.$currencyname.' '.self::numberFormat($diff_topay).'</strong></div></div>';
5452 }
5453 }
5454 }
5455 if ($order_info['status'] == 'cancelled' && isset($order_info['canc_fee']) && $order_info['canc_fee'] > 0) {
5456 $totpaid_str .= '<div class="amountpaid amountcancfee"><span>' . JText::translate('VBO_CANC_FEE') . '</span><div class="service-amount" style="float: right;"><strong>' . $currencyname . ' ' . self::numberFormat($order_info['canc_fee']) . '</strong></div></div>';
5457 }
5458 $parsed = str_replace("{order_total_paid}", $totpaid_str, $parsed);
5459
5460 /**
5461 * Language direction (LTR or RTL).
5462 *
5463 * @since 1.16.4 (J) - 1.6.4 (WP)
5464 */
5465 $lang_direction = 'ltr';
5466 if (!empty($order_info['lang'])) {
5467 // attempt to define the language direction and replace the special tag
5468 try {
5469 $lang_direction = JLanguage::getInstance($order_info['lang'])->isRtl() ? 'rtl' : 'ltr';
5470 } catch (Throwable $e) {
5471 // do nothing, default to ltr
5472 $lang_direction = 'ltr';
5473 }
5474 // static map for Arabic and Hebrew
5475 $lang_direction = !strcasecmp($order_info['lang'], 'ar') || !strcasecmp($order_info['lang'], 'he') ? 'rtl' : $lang_direction;
5476 }
5477 $parsed = str_replace("{lang_direction}", $lang_direction, $parsed);
5478 $parsed = str_replace("{text_natural_direction}", ($lang_direction == 'ltr' ? 'left' : 'right'), $parsed);
5479 $parsed = str_replace("{text_opposite_direction}", ($lang_direction == 'ltr' ? 'right' : 'left'), $parsed);
5480
5481 /**
5482 * Replace static floating styles depending on language direction (LTR or RTL).
5483 * For BC with older email template sources, we need to keep the inline style
5484 * for the floating of certain elements, which looks good only on LTR.
5485 *
5486 * @since 1.16.4 (J) - 1.6.4 (WP)
5487 */
5488 if ($lang_direction === 'rtl') {
5489 // get rid of inline static style declaration because the template uses CSS classes
5490 $parsed = str_replace("float: right;", '', $parsed);
5491 }
5492
5493 return $parsed;
5494 }
5495
5496 /**
5497 * New method for sending booking email messages
5498 * to the guest or to the administrator(s).
5499 *
5500 * @param int $bid the booking ID.
5501 * @param array $for guest, admin or a custom email address.
5502 * @param bool $send whether to send or return the HTML message.
5503 * @param bool $no_config if true, no configuration settings will be used for the status.
5504 *
5505 * @return mixed True or False depending on the result or HTML string for the preview.
5506 *
5507 * @since 1.13 (J) - 1.3 (WP)
5508 * @since 1.16.3 (J) - 1.6.3 (WP) added fourth argument $no_config to send the email even if
5509 * the reservation status is pending and against the configuration.
5510 */
5511 public static function sendBookingEmail($bid, $for = array(), $send = true, $no_config = false)
5512 {
5513 $dbo = JFactory::getDbo();
5514 $app = JFactory::getApplication();
5515
5516 $vbo_tn = self::getTranslator();
5517 $av_helper = self::getAvailabilityInstance();
5518
5519 $q = "SELECT * FROM `#__vikbooking_orders` WHERE `id`=" . (int)$bid . ";";
5520 $dbo->setQuery($q);
5521 $booking = $dbo->loadAssoc();
5522 if (!$booking) {
5523 return false;
5524 }
5525
5526 $result = false;
5527
5528 // check if the language in use is the same as the one used during the checkout
5529 $lang = JFactory::getLanguage();
5530 if (!empty($booking['lang'])) {
5531 if ($lang->getTag() != $booking['lang']) {
5532 $lang->load('com_vikbooking', (defined('VIKBOOKING_LANG') ? VIKBOOKING_LANG : JPATH_SITE), $booking['lang'], true);
5533 if (VBOPlatformDetection::isJoomla()) {
5534 $lang->load('joomla', JPATH_SITE, $booking['lang'], true);
5535 }
5536 }
5537 if ($vbo_tn->getDefaultLang() != $booking['lang']) {
5538 // force the translation to start because contents should be translated
5539 $vbo_tn::$force_tolang = $booking['lang'];
5540 }
5541 }
5542
5543 // load rooms booked
5544 $q = "SELECT `or`.*,`r`.`id` AS `r_reference_id`,`r`.`name`,`r`.`units`,`r`.`fromadult`,`r`.`toadult`,`r`.`params` FROM `#__vikbooking_ordersrooms` AS `or`,`#__vikbooking_rooms` AS `r` WHERE `or`.`idorder`=" . $booking['id'] . " AND `or`.`idroom`=`r`.`id` ORDER BY `or`.`id` ASC;";
5545 $dbo->setQuery($q);
5546 $ordersrooms = $dbo->loadAssocList();
5547 if (!$ordersrooms) {
5548 return false;
5549 }
5550 $vbo_tn->translateContents($ordersrooms, '#__vikbooking_rooms', array('id' => 'r_reference_id'));
5551
5552 /**
5553 * Split stay reservation.
5554 *
5555 * @since 1.16.0 (J) - 1.6.0 (WP)
5556 */
5557 $room_stay_dates = [];
5558 if ($booking['split_stay']) {
5559 if ($booking['status'] == 'confirmed') {
5560 $room_stay_dates = $av_helper->loadSplitStayBusyRecords($booking['id']);
5561 } else {
5562 $room_stay_dates = VBOFactory::getConfig()->getArray('split_stay_' . $booking['id'], []);
5563 }
5564 // immediately count the number of nights of stay for each split room
5565 foreach ($room_stay_dates as $sps_r_k => $sps_r_v) {
5566 if (!empty($sps_r_v['checkin_ts']) && !empty($sps_r_v['checkout_ts'])) {
5567 // overwrite values for compatibility with non-confirmed bookings
5568 $sps_r_v['checkin'] = $sps_r_v['checkin_ts'];
5569 $sps_r_v['checkout'] = $sps_r_v['checkout_ts'];
5570 }
5571 $sps_r_v['nights'] = $av_helper->countNightsOfStay($sps_r_v['checkin'], $sps_r_v['checkout']);
5572 // overwrite the whole array
5573 $room_stay_dates[$sps_r_k] = $sps_r_v;
5574 }
5575 }
5576
5577 $rooms = array();
5578 $tars = array();
5579 $is_package = !empty($booking['pkg']) ? true : false;
5580 $ftitle = self::getFrontTitle();
5581 $currencyname = self::getCurrencyName();
5582 foreach ($ordersrooms as $kor => $or) {
5583 $num = $kor + 1;
5584 $rooms[$num] = $or;
5585 if ($is_package === true || (!empty($or['cust_cost']) && $or['cust_cost'] > 0.00)) {
5586 // package or custom cost set from the back-end
5587 continue;
5588 }
5589
5590 // determine the days to consider for the count of the availability
5591 $room_nights = $booking['days'];
5592 $room_checkin = $booking['checkin'];
5593 $room_checkout = $booking['checkout'];
5594 if ($booking['split_stay'] && count($room_stay_dates) && isset($room_stay_dates[$kor]) && $room_stay_dates[$kor]['idroom'] == $or['idroom']) {
5595 $room_nights = $room_stay_dates[$kor]['nights'];
5596 $room_checkin = $room_stay_dates[$kor]['checkin'];
5597 $room_checkout = $room_stay_dates[$kor]['checkout'];
5598 }
5599
5600 $q = "SELECT * FROM `#__vikbooking_dispcost` WHERE `id`=" . (int)$or['idtar'] . ";";
5601 $dbo->setQuery($q);
5602 $tar = $dbo->loadAssocList();
5603 if (!$tar) {
5604 // tariff not found
5605 if (self::isAdmin()) {
5606 VikError::raiseWarning('', JText::translate('VBERRNOFAREFOUND'));
5607 }
5608 continue;
5609 }
5610
5611 // apply seasonal rates
5612 $tar = self::applySeasonsRoom($tar, $room_checkin, $room_checkout);
5613
5614 // different usage
5615 if ($or['fromadult'] <= $or['adults'] && $or['toadult'] >= $or['adults']) {
5616 $diffusageprice = self::loadAdultsDiff($or['idroom'], $or['adults']);
5617 // Occupancy Override
5618 $occ_ovr = self::occupancyOverrideExists($tar, $or['adults']);
5619 $diffusageprice = $occ_ovr !== false ? $occ_ovr : $diffusageprice;
5620 //
5621 if (is_array($diffusageprice)) {
5622 // set a charge or discount to the price(s) for the different usage of the room
5623 foreach ($tar as $kpr => $vpr) {
5624 $tar[$kpr]['diffusage'] = $or['adults'];
5625 if ($diffusageprice['chdisc'] == 1) {
5626 // charge
5627 if ($diffusageprice['valpcent'] == 1) {
5628 // fixed value
5629 $tar[$kpr]['diffusagecostpernight'] = $diffusageprice['pernight'] == 1 ? 1 : 0;
5630 $aduseval = $diffusageprice['pernight'] == 1 ? $diffusageprice['value'] * $tar[$kpr]['days'] : $diffusageprice['value'];
5631 $tar[$kpr]['diffusagecost'] = "+".$aduseval;
5632 $tar[$kpr]['room_base_cost'] = $vpr['cost'];
5633 $tar[$kpr]['cost'] = $vpr['cost'] + $aduseval;
5634 } else {
5635 // percentage value
5636 $tar[$kpr]['diffusagecostpernight'] = $diffusageprice['pernight'] == 1 ? $vpr['cost'] : 0;
5637 $aduseval = $diffusageprice['pernight'] == 1 ? round(($vpr['cost'] * $diffusageprice['value'] / 100) * $tar[$kpr]['days'] + $vpr['cost'], 2) : round(($vpr['cost'] * (100 + $diffusageprice['value']) / 100), 2);
5638 $tar[$kpr]['diffusagecost'] = "+".$diffusageprice['value']."%";
5639 $tar[$kpr]['room_base_cost'] = $vpr['cost'];
5640 $tar[$kpr]['cost'] = $aduseval;
5641 }
5642 } else {
5643 // discount
5644 if ($diffusageprice['valpcent'] == 1) {
5645 // fixed value
5646 $tar[$kpr]['diffusagecostpernight'] = $diffusageprice['pernight'] == 1 ? 1 : 0;
5647 $aduseval = $diffusageprice['pernight'] == 1 ? $diffusageprice['value'] * $tar[$kpr]['days'] : $diffusageprice['value'];
5648 $tar[$kpr]['diffusagecost'] = "-".$aduseval;
5649 $tar[$kpr]['room_base_cost'] = $vpr['cost'];
5650 $tar[$kpr]['cost'] = $vpr['cost'] - $aduseval;
5651 } else {
5652 // percentage value
5653 $tar[$kpr]['diffusagecostpernight'] = $diffusageprice['pernight'] == 1 ? $vpr['cost'] : 0;
5654 $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);
5655 $tar[$kpr]['diffusagecost'] = "-".$diffusageprice['value']."%";
5656 $tar[$kpr]['room_base_cost'] = $vpr['cost'];
5657 $tar[$kpr]['cost'] = $aduseval;
5658 }
5659 }
5660 }
5661 }
5662 }
5663 //
5664 $tars[$num] = $tar[0];
5665 }
5666
5667 $secdiff = $booking['checkout'] - $booking['checkin'];
5668 $daysdiff = $secdiff / 86400;
5669 if (is_int($daysdiff)) {
5670 if ($daysdiff < 1) {
5671 $daysdiff = 1;
5672 }
5673 } else {
5674 if ($daysdiff < 1) {
5675 $daysdiff = 1;
5676 } else {
5677 $sum = floor($daysdiff) * 86400;
5678 $newdiff = $secdiff - $sum;
5679 $maxhmore = self::getHoursMoreRb() * 3600;
5680 if ($maxhmore >= $newdiff) {
5681 $daysdiff = floor($daysdiff);
5682 } else {
5683 $daysdiff = ceil($daysdiff);
5684 }
5685 }
5686 }
5687
5688 $isdue = 0;
5689 $pricestr = array();
5690 $optstr = array();
5691 foreach ($ordersrooms as $kor => $or) {
5692 $num = $kor + 1;
5693
5694 // determine the days to consider for the count of the availability
5695 $room_nights = $booking['days'];
5696 $room_checkin = $booking['checkin'];
5697 $room_checkout = $booking['checkout'];
5698 if ($booking['split_stay'] && count($room_stay_dates) && isset($room_stay_dates[$kor]) && $room_stay_dates[$kor]['idroom'] == $or['idroom']) {
5699 $room_nights = $room_stay_dates[$kor]['nights'];
5700 $room_checkin = $room_stay_dates[$kor]['checkin'];
5701 $room_checkout = $room_stay_dates[$kor]['checkout'];
5702 }
5703
5704 if ($is_package === true || (!empty($or['cust_cost']) && $or['cust_cost'] > 0.00)) {
5705 // package cost or cust_cost may not be inclusive of taxes if prices tax included is off
5706 $calctar = self::sayPackagePlusIva($or['cust_cost'], $or['cust_idiva']);
5707 $isdue += $calctar;
5708 $pricestr[$num] = (!empty($or['pkg_name']) ? $or['pkg_name'] : (!empty($or['otarplan']) ? ucwords($or['otarplan']) : JText::translate('VBOROOMCUSTRATEPLAN'))).": ".$calctar." ".$currencyname;
5709 } elseif (array_key_exists($num, $tars) && is_array($tars[$num])) {
5710 $display_rate = !empty($or['room_cost']) ? $or['room_cost'] : $tars[$num]['cost'];
5711 $calctar = self::sayCostPlusIva($display_rate, $tars[$num]['idprice']);
5712 $tars[$num]['calctar'] = $calctar;
5713 $isdue += $calctar;
5714 $pricestr[$num] = self::getPriceName($tars[$num]['idprice'], $vbo_tn) . ": " . $calctar . " " . $currencyname . (!empty($tars[$num]['attrdata']) ? "\n" . self::getPriceAttr($tars[$num]['idprice'], $vbo_tn) . ": " . $tars[$num]['attrdata'] : "");
5715 }
5716
5717 if (!empty($or['optionals'])) {
5718 $stepo = explode(";", $or['optionals']);
5719 foreach ($stepo as $roptkey => $oo) {
5720 if (empty($oo)) {
5721 continue;
5722 }
5723 $stept = explode(":", $oo);
5724 $q = "SELECT * FROM `#__vikbooking_optionals` WHERE `id`=" . $dbo->quote($stept[0]) . ";";
5725 $dbo->setQuery($q);
5726 $dbo->execute();
5727 if ($dbo->getNumRows() == 1) {
5728 $actopt = $dbo->loadAssocList();
5729 $vbo_tn->translateContents($actopt, '#__vikbooking_optionals', array(), array(), (!empty($booking['lang']) ? $booking['lang'] : null));
5730 $chvar = '';
5731 if (!empty($actopt[0]['ageintervals']) && $or['children'] > 0 && strstr($stept[1], '-') != false) {
5732 $optagenames = self::getOptionIntervalsAges($actopt[0]['ageintervals']);
5733 $optagepcent = self::getOptionIntervalsPercentage($actopt[0]['ageintervals']);
5734 $optageovrct = self::getOptionIntervalChildOverrides($actopt[0], $or['adults'], $or['children']);
5735 $child_num = self::getRoomOptionChildNumber($or['optionals'], $actopt[0]['id'], $roptkey, $or['children']);
5736 $optagecosts = self::getOptionIntervalsCosts(isset($optageovrct['ageintervals_child' . ($child_num + 1)]) ? $optageovrct['ageintervals_child' . ($child_num + 1)] : $actopt[0]['ageintervals']);
5737 $agestept = explode('-', $stept[1]);
5738 $stept[1] = $agestept[0];
5739 $chvar = $agestept[1];
5740 if (array_key_exists(($chvar - 1), $optagepcent) && $optagepcent[($chvar - 1)] == 1) {
5741 // percentage value of the adults tariff
5742 if ($is_package === true || (!empty($or['cust_cost']) && $or['cust_cost'] > 0.00)) {
5743 $optagecosts[($chvar - 1)] = $or['cust_cost'] * $optagecosts[($chvar - 1)] / 100;
5744 } else {
5745 $display_rate = !empty($or['room_cost']) ? $or['room_cost'] : $tars[$num]['cost'];
5746 $optagecosts[($chvar - 1)] = $display_rate * $optagecosts[($chvar - 1)] / 100;
5747 }
5748 } elseif (array_key_exists(($chvar - 1), $optagepcent) && $optagepcent[($chvar - 1)] == 2) {
5749 // VBO 1.10 - percentage value of room base cost
5750 if ($is_package === true || (!empty($or['cust_cost']) && $or['cust_cost'] > 0.00)) {
5751 $optagecosts[($chvar - 1)] = $or['cust_cost'] * $optagecosts[($chvar - 1)] / 100;
5752 } else {
5753 $display_rate = isset($tars[$num]['room_base_cost']) ? $tars[$num]['room_base_cost'] : (!empty($or['room_cost']) ? $or['room_cost'] : $tars[$num]['cost']);
5754 $optagecosts[($chvar - 1)] = $display_rate * $optagecosts[($chvar - 1)] / 100;
5755 }
5756 }
5757 $actopt[0]['chageintv'] = $chvar;
5758 $actopt[0]['name'] .= ' ('.$optagenames[($chvar - 1)].')';
5759 $actopt[0]['quan'] = $stept[1];
5760 $realcost = (intval($actopt[0]['perday']) == 1 ? (floatval($optagecosts[($chvar - 1)]) * $room_nights * $stept[1]) : (floatval($optagecosts[($chvar - 1)]) * $stept[1]));
5761 } else {
5762 $actopt[0]['quan'] = $stept[1];
5763 // VBO 1.11 - options percentage cost of the room total fee
5764 if ($is_package === true || (!empty($or['cust_cost']) && $or['cust_cost'] > 0.00)) {
5765 $deftar_basecosts = $or['cust_cost'];
5766 } else {
5767 $deftar_basecosts = !empty($or['room_cost']) ? $or['room_cost'] : $tars[$num]['cost'];
5768 }
5769 $actopt[0]['cost'] = (int)$actopt[0]['pcentroom'] ? ($deftar_basecosts * $actopt[0]['cost'] / 100) : $actopt[0]['cost'];
5770 //
5771 $realcost = (intval($actopt[0]['perday']) == 1 ? ($actopt[0]['cost'] * $room_nights * $stept[1]) : ($actopt[0]['cost'] * $stept[1]));
5772 }
5773 if (!empty($actopt[0]['maxprice']) && $actopt[0]['maxprice'] > 0 && $realcost > $actopt[0]['maxprice']) {
5774 $realcost = $actopt[0]['maxprice'];
5775 if (intval($actopt[0]['hmany']) == 1 && intval($stept[1]) > 1) {
5776 $realcost = $actopt[0]['maxprice'] * $stept[1];
5777 }
5778 }
5779 if ($actopt[0]['perperson'] == 1) {
5780 $realcost = $realcost * $or['adults'];
5781 }
5782 $tmpopr = self::sayOptionalsPlusIva($realcost, $actopt[0]['idiva']);
5783 $isdue += $tmpopr;
5784 $optstr[$num][] = ($stept[1] > 1 ? $stept[1] . " " : "") . $actopt[0]['name'] . ": " . $tmpopr . " " . $currencyname . "\n";
5785 }
5786 }
5787 }
5788
5789 // custom extra costs
5790 if (!empty($or['extracosts'])) {
5791 $cur_extra_costs = json_decode($or['extracosts'], true);
5792 foreach ($cur_extra_costs as $eck => $ecv) {
5793 $ecplustax = !empty($ecv['idtax']) ? self::sayOptionalsPlusIva($ecv['cost'], $ecv['idtax']) : $ecv['cost'];
5794 $isdue += $ecplustax;
5795 $optstr[$num][] = $ecv['name'] . ": " . $ecplustax . " " . $currencyname."\n";
5796 }
5797 }
5798 }
5799
5800 // force the original total amount if rates have changed
5801 if (number_format($isdue, 2) != number_format($booking['total'], 2)) {
5802 $isdue = $booking['total'];
5803 }
5804
5805 // mail subject
5806 $subject = JText::sprintf('VBOMAILSUBJECT', strip_tags($ftitle));
5807 // $subject = '=?UTF-8?B?' . base64_encode($subject) . '?=';
5808
5809 // inject the recipient of the message for the template
5810 $booking['for'] = $for;
5811
5812 // load template file that will get $booking as variable
5813 $tmpl = self::loadEmailTemplate($booking);
5814
5815 // parse email template
5816 $hmess = self::parseEmailTemplate($tmpl, $booking, $rooms, $pricestr, $optstr, $isdue);
5817 $hmess = '<html>'."\n".'<head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"></head>'."\n".'<body>'.$hmess.'</body>'."\n".'</html>';
5818
5819 if ($send !== true) {
5820 // return the content of the email message parsed
5821 return $hmess;
5822 }
5823
5824 // when the message can be sent
5825 $sendwhen = self::getSendEmailWhen();
5826
5827 if ($no_config) {
5828 // force the configuration setting to send the message no matter what's the booking status
5829 $sendwhen = 1;
5830 }
5831
5832 // send the message
5833 foreach ($for as $who) {
5834 $use_subject = $subject;
5835 $recipients = array();
5836 $attachments = self::addEmailAttachment(null);
5837 $attach_ical = false;
5838 $force_replyto = null;
5839 if (strpos($who, '@') !== false) {
5840 // send email to custom email address
5841 array_push($recipients, trim($who));
5842 } elseif (stripos($who, 'guest') !== false || stripos($who, 'customer') !== false) {
5843 // send email to the customer
5844 if ($sendwhen > 1 && $booking['status'] == 'standby') {
5845 continue;
5846 }
5847 array_push($recipients, $booking['custmail']);
5848 /**
5849 * Check whether an iCal should be attached for the customer.
5850 *
5851 * @since 1.12.0
5852 */
5853 $attach_ical = self::getEmailIcal('customer', $booking);
5854 } elseif (stripos($who, 'admin') !== false) {
5855 // send email to the administrator(s)
5856 if ($sendwhen > 1 && $booking['status'] == 'standby') {
5857 continue;
5858 }
5859 $use_subject = $subject . ' #' . $booking['id'];
5860 $adminemail = self::getAdminMail();
5861 $extra_admin_recipients = self::addAdminEmailRecipient(null);
5862 if (empty($adminemail) && empty($extra_admin_recipients)) {
5863 // Prevent Joomla Exceptions that would stop the script execution
5864 VikError::raiseWarning('', 'The administrator email address is empty. Email message could not be sent.');
5865 continue;
5866 }
5867 if (strpos($adminemail, ',') !== false) {
5868 // multiple addresses
5869 $adminemails = explode(',', $adminemail);
5870 foreach ($adminemails as $am) {
5871 if (strpos($am, '@') !== false) {
5872 array_push($recipients, trim($am));
5873 }
5874 }
5875 } else {
5876 // single address
5877 array_push($recipients, trim($adminemail));
5878 }
5879
5880 // merge extra recipients
5881 $recipients = array_merge($recipients, $extra_admin_recipients);
5882
5883 // admin should reply to the customer
5884 $force_replyto = !empty($booking['custmail']) ? $booking['custmail'] : $force_replyto;
5885
5886 /**
5887 * Check whether an iCal should be attached for the admin.
5888 *
5889 * @since 1.2.0
5890 */
5891 $attach_ical = self::getEmailIcal('admin', array(
5892 'ts' => $booking['ts'],
5893 'custdata' => $booking['custdata'],
5894 'checkin' => $booking['checkin'],
5895 'checkout' => $booking['checkout'],
5896 'subject' => JText::sprintf('VBNEWORDER', $booking['id']),
5897 ));
5898 }
5899
5900 // send the message, recipients should always be an array to support multiple admin addresses
5901
5902 // get sender e-mail
5903 $adsendermail = VBOFactory::getConfig()->get('senderemail');
5904
5905 // init mail data
5906 $mail = new VBOMailWrapper([
5907 'sender' => [$adsendermail, $ftitle],
5908 'recipient' => $recipients,
5909 'bcc' => self::addAdminEmailRecipient(null, true),
5910 'reply' => !empty($force_replyto) ? $force_replyto : $adsendermail,
5911 'subject' => $use_subject,
5912 'content' => $hmess,
5913 'attachments' => $attachments,
5914 ]);
5915
5916 if ($attach_ical !== false && $booking['status'] != 'standby') {
5917 // attach iCal file
5918 $mail->addAttachment($attach_ical);
5919 }
5920
5921 /**
5922 * Trigger event to allow third party plugins to overwrite any aspect of the mail message.
5923 *
5924 * @see VBOMailWrapper is the $mail object and its setter methods can modify the mail data.
5925 *
5926 * @since 1.15.4 (J) - 1.5.10 (WP)
5927 */
5928 VBOFactory::getPlatform()->getDispatcher()->trigger('onBeforeSendBookingMailVikBooking', [$who, $booking, $mail]);
5929
5930 // send e-mail
5931 $result = VBOFactory::getPlatform()->getMailer()->send($mail) || $result;
5932
5933 // unlink iCal file
5934 if ($attach_ical !== false) {
5935 @unlink($attach_ical);
5936 }
5937 }
5938
5939 return $result;
5940 }
5941
5942 /**
5943 * This method allows to add one or more recipient email
5944 * addresses for the next queue of email sending for the admin.
5945 * This method can be used in the template file for the customer
5946 * email to register an additional email address, maybe when a
5947 * specific room-type is booked.
5948 * The methods sending the email messages are supposed to call this
5949 * method by passing no arguments to obtain the extra addresses set.
5950 *
5951 * @param mixed $email null, string or array of email address(es).
5952 * @param bool $bcc if true, addresses will be used as bcc.
5953 * @param bool $reset if true, the queues will be emptied.
5954 *
5955 * @return array the current extra recipients or bcc addresses set.
5956 *
5957 * @since 1.13 (J) - 1.3.0 (WP)
5958 * @since 1.14 (J) - 1.4.0 (WP) added argument $bcc.
5959 * @since 1.16 (J) - 1.6.0 (WP) added argument $reset.
5960 */
5961 public static function addAdminEmailRecipient($email, $bcc = false, $reset = false)
5962 {
5963 static $extra_recipients = [];
5964 static $extra_bcc = [];
5965
5966 if ($reset) {
5967 $extra_recipients = [];
5968 $extra_bcc = [];
5969 }
5970
5971 if (!empty($email)) {
5972 if (is_scalar($email)) {
5973 if ($bcc) {
5974 array_push($extra_bcc, $email);
5975 } else {
5976 array_push($extra_recipients, $email);
5977 }
5978 } else {
5979 if ($bcc) {
5980 $extra_bcc = array_merge($extra_bcc, $email);
5981 } else {
5982 $extra_recipients = array_merge($extra_recipients, $email);
5983 }
5984 }
5985 }
5986
5987 return $bcc ? array_unique($extra_bcc) : array_unique($extra_recipients);
5988 }
5989
5990 /**
5991 * This method serves to add one or more attachments to the
5992 * next queue of email sending for the admin.
5993 * The methods sending the email messages are supposed to call this
5994 * method by passing a null argument to obtain the attachments set.
5995 *
5996 * @param mixed $file null or string with path to file to attach.
5997 * @param bool $reset if true, the queue will be emptied.
5998 *
5999 * @return array the current attachments set.
6000 *
6001 * @since 1.14
6002 * @since 1.16 (J) - 1.6.0 (WP) added argument $reset.
6003 */
6004 public static function addEmailAttachment($file, $reset = false)
6005 {
6006 static $extra_attachments = [];
6007
6008 if ($reset) {
6009 $extra_attachments = [];
6010 }
6011
6012 if (!empty($file)) {
6013 if (is_scalar($file)) {
6014 array_push($extra_attachments, $file);
6015 } else {
6016 $extra_attachments = array_merge($extra_attachments, $file);
6017 }
6018 }
6019
6020 return array_unique($extra_attachments);
6021 }
6022
6023 /**
6024 * This method is called whenever some rooms get booked.
6025 * It checks whether the rooms involved have shared calendars.
6026 * If some are found, then also such rooms will be occupied.
6027 *
6028 * @param int $bid the booking ID.
6029 * @param array $roomids the list of rooms booked.
6030 * @param int $checkin checkin timestamp.
6031 * @param int $checkout checkout timestamp.
6032 *
6033 * @return boolean true if some other cals were occupied, false otherwise.
6034 *
6035 * @since 1.13 (J) - 1.3.0 (WP)
6036 *
6037 * @see SynchVikBooking::getRoomsSharedCalsInvolved() in VCM that checks if this method exists.
6038 */
6039 public static function updateSharedCalendars($bid, $roomids = array(), $checkin = 0, $checkout = 0)
6040 {
6041 $dbo = JFactory::getDbo();
6042 $bid = (int)$bid;
6043
6044 // availability helper
6045 $av_helper = self::getAvailabilityInstance();
6046
6047 // check for split stay rooms reservation
6048 $known_split_stay = false;
6049 $is_split_stay = 0;
6050
6051 if (empty($checkin) || empty($checkout)) {
6052 // get checkin and checkout timestamps from booking
6053 $q = "SELECT `checkin`, `checkout`, `split_stay` FROM `#__vikbooking_orders` WHERE `id`={$bid};";
6054 $dbo->setQuery($q);
6055 $dbo->execute();
6056 if (!$dbo->getNumRows()) {
6057 // booking not found
6058 return false;
6059 }
6060 $bdata = $dbo->loadAssoc();
6061 $checkin = $bdata['checkin'];
6062 $checkout = $bdata['checkout'];
6063 $is_split_stay = $bdata['split_stay'];
6064 // turn flag on
6065 $known_split_stay = true;
6066 }
6067
6068 if (!$known_split_stay) {
6069 // we need to query the db to access this information
6070 $q = "SELECT `split_stay` FROM `#__vikbooking_orders` WHERE `id`={$bid}";
6071 $dbo->setQuery($q, 0, 1);
6072 $dbo->execute();
6073 if ($dbo->getNumRows()) {
6074 $is_split_stay = (int)$dbo->loadResult();
6075 }
6076 // turn flag on
6077 $known_split_stay = true;
6078 }
6079
6080 // check split stay room records
6081 $room_stay_dates = [];
6082 if ($is_split_stay) {
6083 $room_stay_dates = $av_helper->loadSplitStayBusyRecords($bid);
6084 // immediately count the number of nights of stay for each split room
6085 foreach ($room_stay_dates as $sps_r_k => $sps_r_v) {
6086 if (!empty($sps_r_v['checkin_ts']) && !empty($sps_r_v['checkout_ts'])) {
6087 // overwrite values for compatibility with non-confirmed bookings
6088 $sps_r_v['checkin'] = $sps_r_v['checkin_ts'];
6089 $sps_r_v['checkout'] = $sps_r_v['checkout_ts'];
6090 }
6091 $sps_r_v['nights'] = $av_helper->countNightsOfStay($sps_r_v['checkin'], $sps_r_v['checkout']);
6092 // overwrite the whole array
6093 $room_stay_dates[$sps_r_k] = $sps_r_v;
6094 }
6095 }
6096
6097 // get the rooms booked
6098 if (!count($roomids)) {
6099 // get the IDs of all rooms booked
6100 $q = "SELECT `idroom` FROM `#__vikbooking_ordersrooms` WHERE `idorder`={$bid};";
6101 $dbo->setQuery($q);
6102 $orr = $dbo->loadAssocList();
6103 if ($orr) {
6104 foreach ($orr as $or) {
6105 array_push($roomids, $or['idroom']);
6106 }
6107 }
6108 }
6109 if (!count($roomids) || empty($bid)) {
6110 // unable to proceed
6111 return false;
6112 }
6113 $roomids = array_unique($roomids);
6114
6115 // build room stay dates map in case of split stay
6116 $room_split_stay_map = [];
6117 if ($is_split_stay && !empty($room_stay_dates) && count($room_stay_dates) == count($roomids) && count($roomids) > 1) {
6118 // we have unique room IDs in a split stay reservation for more than one room
6119 foreach ($room_stay_dates as $room_stay_date) {
6120 $room_split_stay_map[$room_stay_date['idroom']] = $room_stay_date;
6121 }
6122 }
6123
6124 // get rooms involved
6125 $involved = [];
6126 $involved_stay_map = [];
6127 $q = "SELECT * FROM `#__vikbooking_calendars_xref` WHERE `mainroom` IN (" . implode(', ', $roomids) . ") OR `childroom` IN (" . implode(', ', $roomids) . ");";
6128 $dbo->setQuery($q);
6129 $rooms_found = $dbo->loadAssocList();
6130 if (!$rooms_found) {
6131 // no rooms involved that need their calendars updated
6132 return false;
6133 }
6134 foreach ($rooms_found as $rf) {
6135 if (!in_array($rf['mainroom'], $roomids)) {
6136 // push room ID
6137 array_push($involved, $rf['mainroom']);
6138 // check for specific stay dates in connected room
6139 if (!empty($rf['childroom']) && isset($room_split_stay_map[$rf['childroom']])) {
6140 // split stay information available
6141 $involved_stay_map[$rf['mainroom']] = $room_split_stay_map[$rf['childroom']];
6142 }
6143 }
6144 if (!in_array($rf['childroom'], $roomids)) {
6145 // push room ID
6146 array_push($involved, $rf['childroom']);
6147 // check for specific stay dates in connected room
6148 if (!empty($rf['mainroom']) && isset($room_split_stay_map[$rf['mainroom']])) {
6149 // split stay information available
6150 $involved_stay_map[$rf['childroom']] = $room_split_stay_map[$rf['mainroom']];
6151 }
6152 }
6153 }
6154 $involved = array_unique($involved);
6155 if (!count($involved)) {
6156 // no rooms involved
6157 return false;
6158 }
6159
6160 // turnover seconds
6161 $turnover_secs = VikBooking::getHoursRoomAvail() * 3600;
6162
6163 // occupy the calendars for the involved rooms found
6164 $bids_generated = [];
6165 foreach ($involved as $rid) {
6166 // determine the values to use
6167 $room_checkin = (int)$checkin;
6168 $room_checkout = (int)$checkout;
6169 $room_realback = ($checkout + $turnover_secs);
6170 if (isset($involved_stay_map[$rid])) {
6171 // split stay dates available
6172 $room_checkin = (int)$involved_stay_map[$rid]['checkin'];
6173 $room_checkout = (int)$involved_stay_map[$rid]['checkout'];
6174 $room_realback = ($room_checkout + $turnover_secs);
6175 }
6176
6177 // occupy record in room with shared calendar
6178 $q = "INSERT INTO `#__vikbooking_busy` (`idroom`,`checkin`,`checkout`,`realback`,`sharedcal`) VALUES(" . (int)$rid . ", " . $room_checkin . ", " . $room_checkout . ", " . $room_realback . ", 1);";
6179 $dbo->setQuery($q);
6180 $dbo->execute();
6181 array_push($bids_generated, $dbo->insertid());
6182 }
6183
6184 // store busy relations created
6185 foreach ($bids_generated as $busyid) {
6186 $q = "INSERT INTO `#__vikbooking_ordersbusy` (`idorder`,`idbusy`) VALUES(" . $bid . ", " . (int)$busyid . ");";
6187 $dbo->setQuery($q);
6188 $dbo->execute();
6189 }
6190
6191 return true;
6192 }
6193
6194 /**
6195 * This method is needed whenever a booking gets modified by
6196 * adding or removing rooms. This way we reset (remove) all
6197 * busy records that were previously stored due to a shared
6198 * calendar. The correct relations should then be re-created
6199 * by calling updateSharedCalendars().
6200 *
6201 * @param int $bid the booking ID.
6202 *
6203 * @return boolean true if some records were cleaned.
6204 *
6205 * @see updateSharedCalendars() should be called after.
6206 *
6207 * @since 1.3.0
6208 */
6209 public static function cleanSharedCalendarsBusy($bid) {
6210 $dbo = JFactory::getDbo();
6211 $bid = (int)$bid;
6212 // get all the occupied records due to shared calendars for this booking
6213 $q = "SELECT `b`.`id`, `b`.`idroom`, `b`.`sharedcal`, `ob`.`idorder`, `ob`.`idbusy` FROM `#__vikbooking_busy` AS `b` LEFT JOIN `#__vikbooking_ordersbusy` AS `ob` ON `ob`.`idbusy`=`b`.`id` WHERE `ob`.`idorder`={$bid} AND `b`.`sharedcal`=1;";
6214 $dbo->setQuery($q);
6215 $allbusy = $dbo->loadAssocList();
6216 if (!$allbusy) {
6217 return false;
6218 }
6219
6220 $busy_ids = array();
6221 foreach ($allbusy as $b) {
6222 // push busy ID to be removed later
6223 array_push($busy_ids, $b['id']);
6224 // delete the current busy ID-booking ID relation for this shared calendar
6225 $q = "DELETE FROM `#__vikbooking_ordersbusy` WHERE `idorder`={$bid} AND `idbusy`={$b['id']};";
6226 $dbo->setQuery($q);
6227 $dbo->execute();
6228 }
6229 if (count($busy_ids)) {
6230 // delete all busy records due to shared calendars
6231 $q = "DELETE FROM `#__vikbooking_busy` WHERE `id` IN (" . implode(', ', $busy_ids) . ");";
6232 $dbo->setQuery($q);
6233 $dbo->execute();
6234 }
6235
6236 return true;
6237 }
6238
6239 public static function sendJutility() {
6240 //deprecated in VBO 1.10
6241 $dbo = JFactory::getDbo();
6242 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='sendjutility';";
6243 $dbo->setQuery($q);
6244 $dbo->execute();
6245 $s = $dbo->loadAssocList();
6246 return (intval($s[0]['setting']) == 1 ? true : false);
6247 }
6248
6249 public static function getCategoryName($idcat, $vbo_tn = null) {
6250 $dbo = JFactory::getDbo();
6251 $q = "SELECT `id`,`name` FROM `#__vikbooking_categories` WHERE `id`=" . $dbo->quote($idcat) . ";";
6252 $dbo->setQuery($q);
6253 $dbo->execute();
6254 $p = $dbo->getNumRows() > 0 ? $dbo->loadAssocList() : array();
6255 if (is_object($vbo_tn) && count($p) > 0) {
6256 $vbo_tn->translateContents($p, '#__vikbooking_categories');
6257 }
6258 return count($p) > 0 ? $p[0]['name'] : '';
6259 }
6260
6261 public static function loadAdultsDiff($idroom, $adults)
6262 {
6263 static $obp_map = [];
6264
6265 $map_sign = "{$idroom}:{$adults}";
6266
6267 if (array_key_exists($map_sign, $obp_map)) {
6268 return $obp_map[$map_sign];
6269 }
6270
6271 $dbo = JFactory::getDbo();
6272
6273 $q = "SELECT * FROM `#__vikbooking_adultsdiff` WHERE `idroom`=" . (int)$idroom . " AND `adults`=" . (int)$adults;
6274 $dbo->setQuery($q, 0, 1);
6275 $obp_diff = $dbo->loadAssoc();
6276 if ($obp_diff) {
6277 // cache value and return it
6278 $obp_map[$map_sign] = $obp_diff;
6279
6280 return $obp_diff;
6281 }
6282
6283 // cache value and return it
6284 $obp_map[$map_sign] = null;
6285
6286 return null;
6287 }
6288
6289 public static function loadRoomAdultsDiff($idroom)
6290 {
6291 $dbo = JFactory::getDbo();
6292
6293 $q = "SELECT * FROM `#__vikbooking_adultsdiff` WHERE `idroom`=" . (int)$idroom . " ORDER BY `adults` ASC;";
6294 $dbo->setQuery($q);
6295 $diff = $dbo->loadAssocList();
6296 if ($diff) {
6297 $roomdiff = [];
6298 foreach ($diff as $v) {
6299 $roomdiff[$v['adults']] = $v;
6300 }
6301
6302 return $roomdiff;
6303 }
6304
6305 return [];
6306 }
6307
6308 public static function occupancyOverrideExists($tar, $adults) {
6309 foreach ($tar as $k => $v) {
6310 if (is_array($v) && array_key_exists('occupancy_ovr', $v)) {
6311 if (array_key_exists($adults, $v['occupancy_ovr'])) {
6312 return $v['occupancy_ovr'][$adults];
6313 }
6314 }
6315 }
6316 return false;
6317 }
6318
6319 public static function getChildrenCharges($idroom, $children, $ages, $num_nights) {
6320 /* charges as percentage amounts of the adults tariff not supported for third parties (only VBO 1.8) */
6321 $charges = array();
6322 if (!($children > 0) || !(count($ages) > 0)) {
6323 return $charges;
6324 }
6325 $dbo = JFactory::getDbo();
6326 $id_options = array();
6327 $q = "SELECT `id`,`idopt` FROM `#__vikbooking_rooms` WHERE `id`=".(int)$idroom.";";
6328 $dbo->setQuery($q);
6329 $dbo->execute();
6330 if ($dbo->getNumRows() > 0) {
6331 $assocs = $dbo->loadAssocList();
6332 foreach ($assocs as $opts) {
6333 if (!empty($opts['idopt'])) {
6334 $r_ido = explode(';', rtrim($opts['idopt']));
6335 foreach ($r_ido as $ido) {
6336 if (!empty($ido) && !in_array($ido, $id_options)) {
6337 $id_options[] = $ido;
6338 }
6339 }
6340 }
6341 }
6342 }
6343 if (count($id_options) > 0) {
6344 $q = "SELECT * FROM `#__vikbooking_optionals` WHERE `id` IN (".implode(", ", $id_options).") AND `ifchildren`=1 AND (LENGTH(`ageintervals`) > 0 OR `ageintervals` IS NOT NULL) LIMIT 1;";
6345 $dbo->setQuery($q);
6346 $dbo->execute();
6347 if ($dbo->getNumRows() > 0) {
6348 $ageintervals = $dbo->loadAssocList();
6349 $split_ages = explode(';;', $ageintervals[0]['ageintervals']);
6350 $age_range = array();
6351 foreach ($split_ages as $kg => $spage) {
6352 if (empty($spage)) {
6353 continue;
6354 }
6355 $parts = explode('_', $spage);
6356 if (strlen($parts[0]) > 0 && intval($parts[1]) > 0 && floatval($parts[2]) > 0) {
6357 $ind = count($age_range);
6358 $age_range[$ind]['from'] = intval($parts[0]);
6359 $age_range[$ind]['to'] = intval($parts[1]);
6360 //taxes are calculated later in VCM
6361 //$age_range[$ind]['cost'] = self::sayOptionalsPlusIva((floatval($parts[2]) * $num_nights), $ageintervals[0]['idiva']);
6362 $age_range[$ind]['cost'] = floatval($parts[2]) * $num_nights;
6363 $age_range[$ind]['option_str'] = $ageintervals[0]['id'].':1-'.($kg + 1);
6364 }
6365 }
6366 if (count($age_range) > 0) {
6367 $tot_charge = 0;
6368 $affected = array();
6369 $option_str = '';
6370 foreach ($ages as $age) {
6371 if (strlen($age) == 0) {
6372 continue;
6373 }
6374 foreach ($age_range as $range) {
6375 if (intval($age) >= $range['from'] && intval($age) <= $range['to']) {
6376 $tot_charge += $range['cost'];
6377 $affected[] = $age;
6378 $option_str .= $range['option_str'].';';
6379 break;
6380 }
6381 }
6382 }
6383 if ($tot_charge > 0) {
6384 $charges['total'] = $tot_charge;
6385 $charges['affected'] = $affected;
6386 $charges['options'] = $option_str;
6387 }
6388 }
6389 }
6390 }
6391
6392 return $charges;
6393 }
6394
6395 public static function sortRoomPrices($arr) {
6396 $newarr = array();
6397 foreach ($arr as $k => $v) {
6398 $newarr[$k] = $v['cost'];
6399 }
6400 asort($newarr);
6401 $sorted = array();
6402 foreach ($newarr as $k => $v) {
6403 $sorted[$k] = $arr[$k];
6404 }
6405 return $sorted;
6406 }
6407
6408 public static function sortResults($arr) {
6409 $newarr = array();
6410 foreach ($arr as $k => $v) {
6411 $newarr[$k] = $v[0]['cost'];
6412 }
6413 asort($newarr);
6414 $sorted = array();
6415 foreach ($newarr as $k => $v) {
6416 $sorted[$k] = $arr[$k];
6417 }
6418 return $sorted;
6419 }
6420
6421 public static function sortMultipleResults($arr) {
6422 foreach ($arr as $k => $v) {
6423 $newarr = array();
6424 foreach ($v as $subk => $subv) {
6425 $newarr[$subk] = $subv[0]['cost'];
6426 }
6427 asort($newarr);
6428 $sorted = array();
6429 foreach ($newarr as $nk => $v) {
6430 $sorted[$nk] = $arr[$k][$nk];
6431 }
6432 $arr[$k] = $sorted;
6433 }
6434 return $arr;
6435 }
6436
6437 /**
6438 * Caches a list of promotion IDs that will be skipped when applying the special rates. This is
6439 * useful for the VCM's Bulk Actions to register a skip of the promotions for the various OTAs
6440 * to avoid duplicate discounts to be applied (room-rate-day level and promotion).
6441 *
6442 * @param array $promos optional list of promotion IDs to register in the execution flow.
6443 *
6444 * @return array list of cached promotion IDs (if any).
6445 *
6446 * @since 1.16.4 (J) - 1.6.4 (WP)
6447 */
6448 public static function registerPromotionIds(array $promos = null)
6449 {
6450 static $flagged_promos = [];
6451
6452 if ($promos) {
6453 // set cached value
6454 $flagged_promos = $promos;
6455 }
6456
6457 return $flagged_promos;
6458 }
6459
6460 /**
6461 * Getter and setter for seasons cache allowed.
6462 *
6463 * @param bool $enabled true or false for setter, null for getter.
6464 *
6465 * @return bool|null
6466 *
6467 * @since 1.16.5 (J) - 1.6.5 (WP)
6468 */
6469 public static function setSeasonsCache($enabled = null)
6470 {
6471 static $seasons_cache = null;
6472
6473 if ($enabled !== null) {
6474 // set value
6475 $seasons_cache = (bool)$enabled;
6476 }
6477
6478 return $seasons_cache;
6479 }
6480
6481 /**
6482 * Applies the seasonal rates over a list of room rate records.
6483 *
6484 * @param array $arr list of room rate records.
6485 * @param int $from unix timestamp for start date.
6486 * @param int $to unix timestamp for end date.
6487 *
6488 * @return array list of manipulated room rate records.
6489 */
6490 public static function applySeasonalPrices(array $arr, $from, $to)
6491 {
6492 $dbo = JFactory::getDbo();
6493 $vbo_tn = self::getTranslator();
6494
6495 $roomschange = [];
6496 $one = getdate($from);
6497
6498 // leap years
6499 if (($one['year'] % 4) == 0 && ($one['year'] % 100 != 0 || $one['year'] % 400 == 0)) {
6500 $isleap = true;
6501 } else {
6502 $isleap = false;
6503 }
6504
6505 $baseone = mktime(0, 0, 0, 1, 1, $one['year']);
6506 $tomidnightone = intval($one['hours']) * 3600;
6507 $tomidnightone += intval($one['minutes']) * 60;
6508 $sfrom = $from - $baseone - $tomidnightone;
6509 $fromdayts = mktime(0, 0, 0, $one['mon'], $one['mday'], $one['year']);
6510 $two = getdate($to);
6511 $basetwo = mktime(0, 0, 0, 1, 1, $two['year']);
6512 $tomidnighttwo = intval($two['hours']) * 3600;
6513 $tomidnighttwo += intval($two['minutes']) * 60;
6514 $sto = $to - $basetwo - $tomidnighttwo;
6515
6516 // leap years, last day of the month of the season
6517 if ($isleap) {
6518 $leapts = mktime(0, 0, 0, 2, 29, $two['year']);
6519 if ($two[0] > $leapts) {
6520 /**
6521 * Timestamp must be greater than the leap-day of Feb 29th.
6522 * It used to be checked for >= $leapts.
6523 *
6524 * @since July 3rd 2019
6525 */
6526 $sfrom -= 86400;
6527 $sto -= 86400;
6528 } elseif ($sto < $sfrom && $one['year'] < $two['year']) {
6529 //lower checkin date when in leap year but not for checkout
6530 $sfrom -= 86400;
6531 }
6532 }
6533
6534 // check for DST changes to adjust the query values to fetch
6535 if (date('I', $from) != date('I', mktime($one['hours'], $one['minutes'], $one['seconds'], $one['mon'], ($one['mday'] - 1), $one['year']))) {
6536 if (date('Y-m-d', $to) == date('Y-m-d', mktime($one['hours'], $one['minutes'], $one['seconds'], $one['mon'], ($one['mday'] + 1), $one['year']))) {
6537 // we are parsing the day when the DST changed (probably a Sunday)
6538 if (!date('I', $from)) {
6539 // DST was just turned off
6540 $sfrom -= 3600;
6541 }
6542 }
6543 }
6544
6545 // count nights requested
6546 $booking_nights = 1;
6547 foreach ($arr as $k => $a) {
6548 if (!isset($a[0])) {
6549 continue;
6550 }
6551 if (isset($a[0]['booking_nights'])) {
6552 // this value may be set when displaying pricing calendars
6553 $booking_nights = $a[0]['booking_nights'];
6554 break;
6555 }
6556 if (isset($a[0]['days'])) {
6557 $booking_nights = $a[0]['days'];
6558 break;
6559 }
6560 }
6561
6562 /**
6563 * Get a list of promotion IDs that may have been set to avoid duplicate discounts on OTAs.
6564 *
6565 * @since 1.16.4 (J) - 1.6.4 (WP)
6566 */
6567 $skip_promo_ids = self::registerPromotionIds();
6568
6569 $q = "SELECT * FROM `#__vikbooking_seasons` WHERE (" .
6570 ($sto > $sfrom ? "(`from`<=" . $sfrom . " AND `to`>=" . $sto . ") " : "") .
6571 ($sto > $sfrom ? "OR (`from`<=" . $sfrom . " AND `to`>=" . $sfrom . ") " : "(`from`<=" . $sfrom . " AND `to`<=" . $sfrom . " AND `from`>`to`) ") .
6572 ($sto > $sfrom ? "OR (`from`<=" . $sto . " AND `to`>=" . $sto . ") " : "OR (`from`>=" . $sto . " AND `to`>=" . $sto . " AND `from`>`to`) ") .
6573 ($sto > $sfrom ? "OR (`from`>=" . $sfrom . " AND `from`<=" . $sto . " AND `to`>=" . $sfrom . " AND `to`<=" . $sto . ")" : "OR (`from`>=" . $sfrom . " AND `from`>" . $sto . " AND `to`<" . $sfrom . " AND `to`<=" . $sto . " AND `from`>`to`)") .
6574 ($sto > $sfrom ? " OR (`from`<=" . $sfrom . " AND `from`<=" . $sto . " AND `to`<" . $sfrom . " AND `to`<" . $sto . " AND `from`>`to`) OR (`from`>" . $sfrom . " AND `from`>" . $sto . " AND `to`>=" . $sfrom . " AND `to`>=" . $sto . " AND `from`>`to`)" : " OR (`from` <=" . $sfrom . " AND `to` >=" . $sfrom . " AND `from` >=" . $sto . " AND `to` >" . $sto . " AND `from` < `to`)") .
6575 ($sto > $sfrom ? " OR (`from` >=" . $sfrom . " AND `from` <" . $sto . " AND `to` <" . $sfrom . " AND `to` <" . $sto . " AND `from` > `to`)" : " OR (`from` <" . $sfrom . " AND `to` >=" . $sto . " AND `from` <=" . $sto . " AND `to` <" . $sfrom . " AND `from` < `to`)"). //VBO 1.6 Else part is for Season Jan 6 to Feb 12 - Booking Dec 31 to Jan 8
6576 ($sto > $sfrom ? " OR (`from` >" . $sfrom . " AND `from` >" . $sto . " AND `to` >=" . $sfrom . " AND `to` <" . $sto . " AND `from` > `to`)" : " OR (`from` >=" . $sfrom . " AND `from` >" . $sto . " AND `to` >" . $sfrom . " AND `to` >" . $sto . " AND `from` < `to`) OR (`from` <" . $sfrom . " AND `from` <" . $sto . " AND `to` <" . $sfrom . " AND `to` <=" . $sto . " AND `from` < `to`)"). //VBO 1.7 Else part for seasons Dec 25 to Dec 31, Jan 2 to Jan 5 - Booking Dec 20 to Jan 7
6577 ") ORDER BY `#__vikbooking_seasons`.`promo` ASC;";
6578 $dbo->setQuery($q);
6579 $seasons = $dbo->loadAssocList();
6580
6581 if ($seasons) {
6582 $vbo_tn->translateContents($seasons, '#__vikbooking_seasons');
6583 $applyseasons = false;
6584 $mem = [];
6585 foreach ($arr as $k => $a) {
6586 $mem[$k]['daysused'] = 0;
6587 $mem[$k]['sum'] = [];
6588 /**
6589 * The keys below are all needed to apply the promotions on the room's final cost.
6590 *
6591 * @since 1.13.5
6592 */
6593 $mem[$k]['diffs'] = [];
6594 $mem[$k]['trans_keys'] = [];
6595 $mem[$k]['trans_factors'] = [];
6596 $mem[$k]['trans_affdays'] = [];
6597 }
6598 foreach ($seasons as $s) {
6599 // check if this is a promotion registered for skipping
6600 if (isset($s['promo']) && $s['promo'] && in_array($s['id'], $skip_promo_ids)) {
6601 continue;
6602 }
6603
6604 // Special Price tied to the year
6605 if (!empty($s['year']) && $s['year'] > 0) {
6606 //VBO 1.7 - do not skip seasons tied to the year for bookings between two years
6607 if ($one['year'] != $s['year'] && $two['year'] != $s['year']) {
6608 //VBO 1.9 - tied to the year can be set for prev year (Dec 27 to Jan 3) and booking can be Jan 1 to Jan 3 - do not skip in this case
6609 if (($one['year'] - $s['year']) != 1 || $s['from'] < $s['to']) {
6610 continue;
6611 }
6612 //VBO 1.9 - tied to 2016 going through Jan 2017: dates of December 2017 should skip this speacial price
6613 if (($one['year'] - $s['year']) == 1 && $s['from'] > $s['to']) {
6614 $calc_ends = mktime(0, 0, 0, 1, 1, ($s['year'] + 1)) + $s['to'];
6615 if ($calc_ends < ($from - $tomidnightone)) {
6616 continue;
6617 }
6618 }
6619 } elseif ($one['year'] < $s['year'] && $two['year'] == $s['year']) {
6620 //VBO 1.9 - season tied to the year 2017 accross 2018 and we are parsing dates accross prev year 2016-2017
6621 if ($s['from'] > $s['to']) {
6622 continue;
6623 }
6624 if (($basetwo + $s['from'] + 86399) > $to) {
6625 /**
6626 * Assuming that we are on 2021, and we are booking a 2-night stay from 30/12 to 01/01. This statement involves
6627 * a special price tied to the year 2022 for the night of 31/12 (or near dates), but we are booking the night of
6628 * New Year's Eve of 2021, and so the special price pre-prepared for the year after (2022) should be ignored.
6629 *
6630 * @since 1.14.3 (J) - 1.4.3 (WP)
6631 */
6632 continue;
6633 }
6634 } elseif ($one['year'] == $s['year'] && $two['year'] > $s['year']) {
6635 if (($baseone + $s['to'] + 86399) < $from && $s['from'] < $s['to']) {
6636 /**
6637 * Assuming that we are on 2021, and we are booking a 4-night stay from 29/12 to 02/01. This statement involves
6638 * a special price tied to the year 2021 for the night of 01/01 (or near dates), but we are booking the night of
6639 * First of the Year of 2022, and so the old special price for the year before (2021) should be ignored.
6640 *
6641 * @since 1.14.3 (J) - 1.4.3 (WP)
6642 */
6643 continue;
6644 }
6645 } elseif ($one['year'] == $s['year'] && $two['year'] == $s['year'] && $s['from'] > $s['to']) {
6646 //VBO 1.9 - season tied to the year 2017 accross 2018 and we are parsing dates at the beginning of 2017 due to beginning loop in 2016 (Rates Overview)
6647 if (($baseone + $s['from']) > $to) {
6648 continue;
6649 }
6650 }
6651 }
6652 //
6653 $allrooms = explode(",", $s['idrooms']);
6654 $allprices = !empty($s['idprices']) ? explode(",", $s['idprices']) : [];
6655 $inits = $baseone + $s['from'];
6656 if ($s['from'] < $s['to']) {
6657 $ends = $basetwo + $s['to'];
6658 //VikBooking 1.6 check if the inits must be set to the year after
6659 //ex. Season Jan 6 to Feb 12 - Booking Dec 31 to Jan 8 to charge Jan 6,7
6660 if ($sfrom > $s['from'] && $sto >= $s['from'] && $sfrom > $s['to'] && $sto <= $s['to'] && $s['from'] < $s['to'] && $sfrom > $sto) {
6661 $tmpbase = mktime(0, 0, 0, 1, 1, ($one['year'] + 1));
6662 $inits = $tmpbase + $s['from'];
6663 } elseif ($sfrom >= $s['from'] && $sfrom <= $s['to'] && $sto < $s['from'] && $sto < $s['to'] && $sfrom > $sto) {
6664 //VBO 1.7 - Season Dec 23 to Dec 29 - Booking Dec 29 to Jan 5
6665 $ends = $baseone + $s['to'];
6666 } elseif ($sfrom <= $s['from'] && $sfrom <= $s['to'] && $sto < $s['from'] && $sto < $s['to'] && $sfrom > $sto) {
6667 //VBO 1.7 - Season Dec 30 to Dec 31 - Booking Dec 29 to Jan 5
6668 $ends = $baseone + $s['to'];
6669 } elseif ($sfrom > $s['from'] && $sfrom > $s['to'] && $sto >= $s['from'] && ($sto >= $s['to'] || $sto <= $s['to']) && $sfrom > $sto) {
6670 //VBO 1.7 - Season Jan 1 to Jan 2 - Booking Dec 29 to Jan 5
6671 $inits = $basetwo + $s['from'];
6672 }
6673 } else {
6674 //between 2 years
6675 if ($baseone < $basetwo) {
6676 //ex. 29/12/2012 - 14/01/2013
6677 $ends = $basetwo + $s['to'];
6678 } else {
6679 if (($sfrom >= $s['from'] && $sto >= $s['from']) OR ($sfrom < $s['from'] && $sto >= $s['from'] && $sfrom > $s['to'] && $sto > $s['to'])) {
6680 //ex. 25/12 - 30/12 with init season on 20/12 OR 27/12 for counting 28,29,30/12
6681 $tmpbase = mktime(0, 0, 0, 1, 1, ($one['year'] + 1));
6682 $ends = $tmpbase + $s['to'];
6683 } else {
6684 //ex. 03/01 - 09/01
6685 $ends = $basetwo + $s['to'];
6686 $tmpbase = mktime(0, 0, 0, 1, 1, ($one['year'] - 1));
6687 $inits = $tmpbase + $s['from'];
6688 }
6689 }
6690 }
6691
6692 // leap years
6693 if ($isleap == true) {
6694 $infoseason = getdate($inits);
6695 $leapts = mktime(0, 0, 0, 2, 29, $infoseason['year']);
6696 // VikBooking 1.6 added below && $infoseason['year'] == $one['year']
6697 // for those seasons like 2015 Dec 14 to 2016 Jan 5 and booking dates like 2016 Jan 1 to Jan 6 where 2015 is not leap
6698 if ($infoseason[0] > $leapts && $infoseason['year'] == $one['year']) {
6699 /**
6700 * Timestamp must be greater than the leap-day of Feb 29th.
6701 * It used to be checked for >= $leapts.
6702 *
6703 * @since July 3rd 2019
6704 */
6705 $inits += 86400;
6706 $ends += 86400;
6707 }
6708 }
6709
6710 // promotions
6711 $promotion = [];
6712 if ($s['promo'] == 1) {
6713 $daysadv = (($inits - time()) / 86400);
6714 $daysadv = $daysadv > 0 ? (int)ceil($daysadv) : 0;
6715 if (!empty($s['promodaysadv']) && $s['promodaysadv'] > $daysadv) {
6716 continue;
6717 } elseif (!empty($s['promolastmin']) && $s['promolastmin'] > 0) {
6718 $secstocheckin = ($from - time());
6719 if ($s['promolastmin'] < $secstocheckin) {
6720 // VBO 1.11 - too many seconds to the check-in date, skip this last minute promotion
6721 continue;
6722 }
6723 }
6724 if ($s['promominlos'] > 1 && $booking_nights < $s['promominlos']) {
6725 /**
6726 * The minimum length of stay parameter is also taken to exclude the promotion from the calculation.
6727 *
6728 * @since 1.13.5
6729 */
6730 continue;
6731 }
6732 $promotion['todaydaysadv'] = $daysadv;
6733 $promotion['promodaysadv'] = $s['promodaysadv'];
6734 $promotion['promotxt'] = $s['promotxt'];
6735 }
6736
6737 // occupancy override
6738 $occupancy_ovr = !empty($s['occupancy_ovr']) ? json_decode($s['occupancy_ovr'], true) : [];
6739
6740 // week days
6741 $filterwdays = !empty($s['wdays']) ? true : false;
6742 $wdays = $filterwdays ? explode(';', $s['wdays']) : '';
6743 if (is_array($wdays) && $wdays) {
6744 foreach ($wdays as $kw => $wd) {
6745 if (strlen($wd) == 0) {
6746 unset($wdays[$kw]);
6747 }
6748 }
6749 }
6750
6751 // checkin must be after the beginning of the season
6752 $checkininclok = true;
6753 if ($s['checkinincl'] == 1) {
6754 $checkininclok = false;
6755 if ($s['from'] < $s['to']) {
6756 if ($sfrom >= $s['from'] && $sfrom <= $s['to']) {
6757 $checkininclok = true;
6758 }
6759 } else {
6760 if (($sfrom >= $s['from'] && $sfrom > $s['to']) || ($sfrom < $s['from'] && $sfrom <= $s['to'])) {
6761 $checkininclok = true;
6762 }
6763 }
6764 }
6765 if ($checkininclok !== true) {
6766 continue;
6767 }
6768
6769 foreach ($arr as $k => $a) {
6770 // applied only to some types of price
6771 if ($allprices && !empty($allprices[0])) {
6772 if (!in_array("-" . $a[0]['idprice'] . "-", $allprices)) {
6773 continue;
6774 }
6775 }
6776 // applied only to some room types
6777 if (!in_array("-" . $a[0]['idroom'] . "-", $allrooms)) {
6778 continue;
6779 }
6780
6781 // count affected nights of stay
6782 $affdays = 0;
6783 $season_fromdayts = $fromdayts;
6784 $is_dst = date('I', $season_fromdayts);
6785 for ($i = 0; $i < $a[0]['days']; $i++) {
6786 $todayts = $season_fromdayts + ($i * 86400);
6787 $is_now_dst = date('I', $todayts);
6788 if ($is_dst != $is_now_dst) {
6789 // Daylight Saving Time has changed, check how
6790 if ((bool)$is_dst === true) {
6791 $todayts += 3600;
6792 $season_fromdayts += 3600;
6793 } else {
6794 $todayts -= 3600;
6795 $season_fromdayts -= 3600;
6796 }
6797 $is_dst = $is_now_dst;
6798 }
6799 if ($todayts >= $inits && $todayts <= $ends) {
6800 // week days
6801 if ($filterwdays == true) {
6802 $checkwday = getdate($todayts);
6803 if (in_array($checkwday['wday'], $wdays)) {
6804 $affdays++;
6805 }
6806 } else {
6807 $affdays++;
6808 }
6809 //
6810 }
6811 }
6812 if (!($affdays > 0)) {
6813 // no nights affected
6814 continue;
6815 }
6816
6817 // apply the rule
6818 $applyseasons = true;
6819 $dailyprice = $a[0]['cost'] / $a[0]['days'];
6820
6821 // modification factor object
6822 $factor = new stdClass;
6823
6824 // calculate new price progressively
6825 if (intval($s['val_pcent']) == 2) {
6826 // percentage value
6827 $factor->pcent = 1;
6828 $pctval = $s['diffcost'];
6829 if (strlen((string)$s['losoverride'])) {
6830 // values overrides
6831 $arrvaloverrides = [];
6832 $valovrparts = explode('_', $s['losoverride']);
6833 foreach ($valovrparts as $valovr) {
6834 if (!empty($valovr)) {
6835 $ovrinfo = explode(':', $valovr);
6836 if (strstr($ovrinfo[0], '-i') != false) {
6837 $ovrinfo[0] = str_replace('-i', '', $ovrinfo[0]);
6838 if ((int)$ovrinfo[0] < $a[0]['days']) {
6839 $arrvaloverrides[$a[0]['days']] = $ovrinfo[1];
6840 }
6841 }
6842 $arrvaloverrides[$ovrinfo[0]] = $ovrinfo[1];
6843 }
6844 }
6845 if (array_key_exists($a[0]['days'], $arrvaloverrides)) {
6846 $pctval = $arrvaloverrides[$a[0]['days']];
6847 }
6848 }
6849 if (intval($s['type']) == 1) {
6850 // charge
6851 $factor->type = '+';
6852 $cpercent = 100 + $pctval;
6853 } else {
6854 // discount
6855 $factor->type = '-';
6856 $cpercent = 100 - $pctval;
6857 }
6858 $factor->amount = $pctval;
6859 $newprice = ($dailyprice * $cpercent / 100) * $affdays;
6860 } else {
6861 // absolute value
6862 $factor->pcent = 0;
6863 $absval = $s['diffcost'];
6864 if (strlen((string)$s['losoverride'])) {
6865 // values overrides
6866 $arrvaloverrides = [];
6867 $valovrparts = explode('_', $s['losoverride']);
6868 foreach ($valovrparts as $valovr) {
6869 if (!empty($valovr)) {
6870 $ovrinfo = explode(':', $valovr);
6871 if (strstr($ovrinfo[0], '-i') != false) {
6872 $ovrinfo[0] = str_replace('-i', '', $ovrinfo[0]);
6873 if ((int)$ovrinfo[0] < $a[0]['days']) {
6874 $arrvaloverrides[$a[0]['days']] = $ovrinfo[1];
6875 }
6876 }
6877 $arrvaloverrides[$ovrinfo[0]] = $ovrinfo[1];
6878 }
6879 }
6880 if (array_key_exists($a[0]['days'], $arrvaloverrides)) {
6881 $absval = $arrvaloverrides[$a[0]['days']];
6882 }
6883 }
6884 if (intval($s['type']) == 1) {
6885 // charge
6886 $factor->type = '+';
6887 $newprice = ($dailyprice + $absval) * $affdays;
6888 } else {
6889 // discount
6890 $factor->type = '-';
6891 $newprice = ($dailyprice - $absval) * $affdays;
6892 }
6893 $factor->amount = $absval;
6894 }
6895
6896 // apply rounding
6897 $factor->roundmode = $s['roundmode'];
6898 if (!empty($s['roundmode'])) {
6899 $newprice = round($newprice, 0, constant($s['roundmode']));
6900 } else {
6901 $newprice = round($newprice, 2);
6902 }
6903
6904 // define the promotion (only if no value overrides set the amount to 0)
6905 if ($promotion && ((isset($absval) && $absval > 0) || $pctval > 0)) {
6906 /**
6907 * Include the discount information (if any). The cost re-calculated may not be
6908 * precise if multiple special prices were applied over the same dates.
6909 *
6910 * @since 1.13
6911 */
6912 if ($s['type'] == 2 && $s['diffcost'] > 0) {
6913 $promotion['discount'] = [
6914 'amount' => $s['diffcost'],
6915 'pcent' => (int)($s['val_pcent'] == 2),
6916 ];
6917 }
6918 //
6919 $mem[$k]['promotion'] = $promotion;
6920 }
6921
6922 // define the occupancy override
6923 if (array_key_exists($a[0]['idroom'], $occupancy_ovr) && $occupancy_ovr[$a[0]['idroom']]) {
6924 $mem[$k]['occupancy_ovr'] = $occupancy_ovr[$a[0]['idroom']];
6925 }
6926
6927 // push difference generated only if to be applied progressively
6928 if (!$s['promo'] || ($s['promo'] && !$s['promofinalprice'])) {
6929 /**
6930 * Push the difference generated by this special price for later transliteration of final price,
6931 * only if the special price is calculated progressively and not on the final price.
6932 *
6933 * @since 1.13.5
6934 */
6935 array_push($mem[$k]['diffs'], ($newprice - ($dailyprice * $affdays)));
6936 } elseif ($s['promo'] && $s['promofinalprice'] && $factor->pcent) {
6937 /**
6938 * This is a % promotion to be applied on the final price, so we need to save that this memory key
6939 * will need the transliteration, aka adjusting this new price by applying the charge/discount on
6940 * all differences applied by the previous special pricing rules.
6941 *
6942 * @since 1.13.5
6943 */
6944 array_push($mem[$k]['trans_keys'], count($mem[$k]['sum']));
6945 array_push($mem[$k]['trans_factors'], $factor);
6946 array_push($mem[$k]['trans_affdays'], $affdays);
6947 }
6948
6949 // push values in memory array
6950 array_push($mem[$k]['sum'], $newprice);
6951 $mem[$k]['daysused'] += $affdays;
6952 array_push($roomschange, $a[0]['idroom']);
6953 }
6954 }
6955 if ($applyseasons) {
6956 foreach ($mem as $k => $v) {
6957 if ($v['daysused'] > 0 && $v['sum']) {
6958 $newprice = 0;
6959 $dailyprice = $arr[$k][0]['cost'] / $arr[$k][0]['days'];
6960 $restdays = $arr[$k][0]['days'] - $v['daysused'];
6961 $addrest = $restdays * $dailyprice;
6962 $newprice += $addrest;
6963
6964 // calculate new final cost
6965 $redo_rounding = null;
6966 foreach ($v['sum'] as $sum_index => $add) {
6967 /**
6968 * The application of the various special pricing rules is made in a progressive and cumulative way
6969 * by always starting from the room base cost or its average daily cost. However, promotions may need
6970 * to be applied on the room final cost, and not in a progresive way. In order to keep the progressive
6971 * algorithm, for applying the special prices on the room final cost we need to apply the same promotion
6972 * onto the differences generated by all the regular and progressively applied special pricing rules.
6973 *
6974 * @since 1.13.5
6975 */
6976 if (in_array($sum_index, $v['trans_keys']) && $v['diffs']) {
6977 /**
6978 * This progressive price difference must be applied on the room final cost, so we need to
6979 * apply the transliteration over the other differences applied by other special prices.
6980 */
6981 $transliterate_key = array_search($sum_index, $v['trans_keys']);
6982 if ($transliterate_key !== false && isset($v['trans_factors'][$transliterate_key])) {
6983 // this is the % promotion we are looking for applying it on the final cost
6984 $factor = $v['trans_factors'][$transliterate_key];
6985 if (is_object($factor) && $factor->pcent) {
6986 $final_factor = 0;
6987 foreach ($v['diffs'] as $diff_index => $prog_diff) {
6988 $final_factor += $prog_diff * $factor->amount / 100;
6989 }
6990 // update rounding
6991 $redo_rounding = !empty($factor->roundmode) ? $factor->roundmode : $redo_rounding;
6992
6993 /**
6994 * Leverage the final factor depending on how many nights this affected.
6995 *
6996 * @since 1.15.0 (J) - 1.5.0 (WP)
6997 */
6998 if (isset($v['trans_affdays'][$transliterate_key]) && $v['trans_affdays'][$transliterate_key] < $arr[$k][0]['days']) {
6999 $avg_final_factor = $final_factor / $arr[$k][0]['days'];
7000 $final_factor = $avg_final_factor * $v['trans_affdays'][$transliterate_key];
7001 }
7002
7003 // apply the final transliteration to obtain a value like if it was applied on the room's final cost
7004 $add = $factor->type == '+' ? ($add + $final_factor) : ($add - $final_factor);
7005 }
7006 }
7007 }
7008
7009 // apply new price progressively
7010 $newprice += $add;
7011 }
7012
7013 // apply rounding from factor
7014 if (!empty($redo_rounding)) {
7015 $newprice = round($newprice, 0, constant($redo_rounding));
7016 }
7017
7018 // set promotion (if any)
7019 if (isset($v['promotion'])) {
7020 $arr[$k][0]['promotion'] = $v['promotion'];
7021 }
7022
7023 // set occupancy overrides (if any)
7024 if (isset($v['occupancy_ovr'])) {
7025 $arr[$k][0]['occupancy_ovr'] = $v['occupancy_ovr'];
7026 }
7027
7028 // set new final cost and update nights affected
7029 $arr[$k][0]['cost'] = $newprice;
7030 $arr[$k][0]['affdays'] = $v['daysused'];
7031 }
7032 }
7033 }
7034 }
7035
7036 // week days with no season
7037 $roomschange = array_unique($roomschange);
7038 $q = "SELECT * FROM `#__vikbooking_seasons` WHERE ((`from` = 0 AND `to` = 0) OR (`from` IS NULL AND `to` IS NULL));";
7039 $dbo->setQuery($q);
7040 $specials = $dbo->loadAssocList();
7041 if ($specials) {
7042 $vbo_tn->translateContents($specials, '#__vikbooking_seasons');
7043 $applyseasons = false;
7044 unset($mem);
7045 $mem = [];
7046 foreach ($arr as $k => $a) {
7047 $mem[$k]['daysused'] = 0;
7048 $mem[$k]['sum'] = [];
7049 }
7050 foreach ($specials as $s) {
7051 // check if this is a promotion registered for skipping
7052 if (isset($s['promo']) && $s['promo'] && in_array($s['id'], $skip_promo_ids)) {
7053 continue;
7054 }
7055
7056 // Special Price tied to the year
7057 if (!empty($s['year']) && $s['year'] > 0) {
7058 if ($one['year'] != $s['year']) {
7059 continue;
7060 }
7061 }
7062
7063 $allrooms = explode(",", $s['idrooms']);
7064 $allprices = !empty($s['idprices']) ? explode(",", $s['idprices']) : [];
7065 // week days
7066 $filterwdays = !empty($s['wdays']) ? true : false;
7067 $wdays = $filterwdays == true ? explode(';', $s['wdays']) : '';
7068 if (is_array($wdays) && $wdays) {
7069 foreach ($wdays as $kw => $wd) {
7070 if (strlen($wd) == 0) {
7071 unset($wdays[$kw]);
7072 }
7073 }
7074 }
7075
7076 foreach ($arr as $k => $a) {
7077 // only rooms with no price modifications from seasons
7078
7079 // applied only to some types of price
7080 if ($allprices && !empty($allprices[0])) {
7081 if (!in_array("-" . $a[0]['idprice'] . "-", $allprices)) {
7082 continue;
7083 }
7084 }
7085
7086 /**
7087 * We should not exclude the rooms that already had a modification of the price through a season
7088 * with a dates filter or we risk to get invalid prices by skipping a rule for just some weekdays.
7089 * The control " || in_array($a[0]['idroom'], $roomschange)" was removed from the IF below.
7090 *
7091 * @since 1.11
7092 */
7093 if (!in_array("-" . $a[0]['idroom'] . "-", $allrooms)) {
7094 continue;
7095 }
7096
7097 $affdays = 0;
7098 $season_fromdayts = $fromdayts;
7099 $is_dst = date('I', $season_fromdayts);
7100 for ($i = 0; $i < $a[0]['days']; $i++) {
7101 $todayts = $season_fromdayts + ($i * 86400);
7102 $is_now_dst = date('I', $todayts);
7103 if ($is_dst != $is_now_dst) {
7104 // Daylight Saving Time has changed, check how
7105 if ((bool)$is_dst === true) {
7106 $todayts += 3600;
7107 $season_fromdayts += 3600;
7108 } else {
7109 $todayts -= 3600;
7110 $season_fromdayts -= 3600;
7111 }
7112 $is_dst = $is_now_dst;
7113 }
7114 // week days
7115 if ($filterwdays == true) {
7116 $checkwday = getdate($todayts);
7117 if (in_array($checkwday['wday'], $wdays)) {
7118 $affdays++;
7119 }
7120 }
7121 }
7122 if (!($affdays > 0)) {
7123 // no nights affected
7124 continue;
7125 }
7126
7127 // apply the rule
7128 $applyseasons = true;
7129 $dailyprice = $a[0]['cost'] / $a[0]['days'];
7130
7131 if (intval($s['val_pcent']) == 2) {
7132 // percentage value
7133 $pctval = $s['diffcost'];
7134 if (strlen((string)$s['losoverride'])) {
7135 // values overrides
7136 $arrvaloverrides = [];
7137 $valovrparts = explode('_', $s['losoverride']);
7138 foreach ($valovrparts as $valovr) {
7139 if (!empty($valovr)) {
7140 $ovrinfo = explode(':', $valovr);
7141 if (strstr($ovrinfo[0], '-i') != false) {
7142 $ovrinfo[0] = str_replace('-i', '', $ovrinfo[0]);
7143 if ((int)$ovrinfo[0] < $a[0]['days']) {
7144 $arrvaloverrides[$a[0]['days']] = $ovrinfo[1];
7145 }
7146 }
7147 $arrvaloverrides[$ovrinfo[0]] = $ovrinfo[1];
7148 }
7149 }
7150 if (array_key_exists($a[0]['days'], $arrvaloverrides)) {
7151 $pctval = $arrvaloverrides[$a[0]['days']];
7152 }
7153 }
7154 if (intval($s['type']) == 1) {
7155 // charge
7156 $cpercent = 100 + $pctval;
7157 } else {
7158 // discount
7159 $cpercent = 100 - $pctval;
7160 }
7161 $newprice = ($dailyprice * $cpercent / 100) * $affdays;
7162 } else {
7163 // absolute value
7164 $absval = $s['diffcost'];
7165 if (strlen((string)$s['losoverride'])) {
7166 // values overrides
7167 $arrvaloverrides = [];
7168 $valovrparts = explode('_', $s['losoverride']);
7169 foreach ($valovrparts as $valovr) {
7170 if (!empty($valovr)) {
7171 $ovrinfo = explode(':', $valovr);
7172 if (strstr($ovrinfo[0], '-i') != false) {
7173 $ovrinfo[0] = str_replace('-i', '', $ovrinfo[0]);
7174 if ((int)$ovrinfo[0] < $a[0]['days']) {
7175 $arrvaloverrides[$a[0]['days']] = $ovrinfo[1];
7176 }
7177 }
7178 $arrvaloverrides[$ovrinfo[0]] = $ovrinfo[1];
7179 }
7180 }
7181 if (array_key_exists($a[0]['days'], $arrvaloverrides)) {
7182 $absval = $arrvaloverrides[$a[0]['days']];
7183 }
7184 }
7185 if (intval($s['type']) == 1) {
7186 // charge
7187 $newprice = ($dailyprice + $absval) * $affdays;
7188 } else {
7189 // discount
7190 $newprice = ($dailyprice - $absval) * $affdays;
7191 }
7192 }
7193
7194 // apply rounding
7195 if (!empty($s['roundmode'])) {
7196 $newprice = round($newprice, 0, constant($s['roundmode']));
7197 } else {
7198 $newprice = round($newprice, 2);
7199 }
7200
7201 // push values in memory array
7202 array_push($mem[$k]['sum'], $newprice);
7203 $mem[$k]['daysused'] += $affdays;
7204 }
7205 }
7206 if ($applyseasons) {
7207 foreach ($mem as $k => $v) {
7208 if ($v['daysused'] > 0 && $v['sum']) {
7209 $newprice = 0;
7210 $dailyprice = $arr[$k][0]['cost'] / $arr[$k][0]['days'];
7211 $restdays = $arr[$k][0]['days'] - $v['daysused'];
7212 $addrest = $restdays * $dailyprice;
7213 $newprice += $addrest;
7214 foreach ($v['sum'] as $add) {
7215 $newprice += $add;
7216 }
7217 $arr[$k][0]['cost'] = $newprice;
7218 $arr[$k][0]['affdays'] = $v['daysused'];
7219 }
7220 }
7221 }
7222 }
7223 // end week days with no season
7224
7225 return $arr;
7226 }
7227
7228 /**
7229 * Applies the special prices over an array of tariffs.
7230 * The function is also used by VCM (>= 1.6.5) with specific arguments.
7231 *
7232 * @param array $arr array of tariffs taken from the DB
7233 * @param int $from start timestamp
7234 * @param int $to end timestamp
7235 * @param array $parsed_season array of a season to parse (used to render the seasons calendars in back-end and front-end)
7236 * @param array $seasons_dates (VBO 1.10) array of seasons with dates filter taken from the DB to avoid multiple queries (VCM)
7237 * @param array $seasons_wdays (VBO 1.10) array of seasons with weekdays filter (only) taken from the DB to avoid multiple queries (VCM)
7238 *
7239 * @return array
7240 */
7241 public static function applySeasonsRoom(array $arr, $from, $to, array $parsed_season = [], array $seasons_dates = [], array $seasons_wdays = [])
7242 {
7243 static $cached_seasons = [];
7244
7245 $cache_signature = '';
7246 $cache_signature_wdays = '';
7247 if (self::setSeasonsCache() !== false && !$parsed_season && !$seasons_dates && !$seasons_wdays) {
7248 // enable season records caching
7249 $cache_signature = "{$from}_{$to}";
7250 $cache_signature_wdays = "wdays";
7251 }
7252
7253 $dbo = JFactory::getDbo();
7254 $vbo_tn = self::getTranslator();
7255
7256 $roomschange = [];
7257 $one = getdate($from);
7258
7259 // leap years
7260 if ($one['year'] % 4 == 0 && ($one['year'] % 100 != 0 || $one['year'] % 400 == 0)) {
7261 $isleap = true;
7262 } else {
7263 $isleap = false;
7264 }
7265
7266 $baseone = mktime(0, 0, 0, 1, 1, $one['year']);
7267 $tomidnightone = intval($one['hours']) * 3600;
7268 $tomidnightone += intval($one['minutes']) * 60;
7269 $sfrom = $from - $baseone - $tomidnightone;
7270 $fromdayts = mktime(0, 0, 0, $one['mon'], $one['mday'], $one['year']);
7271 $two = getdate($to);
7272 $basetwo = mktime(0, 0, 0, 1, 1, $two['year']);
7273 $tomidnighttwo = intval($two['hours']) * 3600;
7274 $tomidnighttwo += intval($two['minutes']) * 60;
7275 $sto = $to - $basetwo - $tomidnighttwo;
7276
7277 // leap years, last day of the month of the season
7278 if ($isleap) {
7279 $leapts = mktime(0, 0, 0, 2, 29, $two['year']);
7280 if ($two[0] > $leapts) {
7281 /**
7282 * Timestamp must be greater than the leap-day of Feb 29th.
7283 * It used to be checked for >= $leapts.
7284 *
7285 * @since July 3rd 2019
7286 */
7287 $sfrom -= 86400;
7288 $sto -= 86400;
7289 } elseif ($sto < $sfrom && $one['year'] < $two['year']) {
7290 // lower checkin date when in leap year but not for checkout
7291 $sfrom -= 86400;
7292 }
7293 }
7294
7295 // check for DST changes to adjust the query values to fetch
7296 if (date('I', $from) != date('I', mktime($one['hours'], $one['minutes'], $one['seconds'], $one['mon'], ($one['mday'] - 1), $one['year']))) {
7297 if (date('Y-m-d', $to) == date('Y-m-d', mktime($one['hours'], $one['minutes'], $one['seconds'], $one['mon'], ($one['mday'] + 1), $one['year']))) {
7298 // we are parsing the day when the DST changed (probably a Sunday)
7299 if (!date('I', $from)) {
7300 // DST was just turned off
7301 $sfrom -= 3600;
7302 }
7303 }
7304 }
7305
7306 // count nights requested
7307 $booking_nights = 1;
7308 foreach ($arr as $k => $a) {
7309 if (isset($a['booking_nights'])) {
7310 // this value may be set when displaying pricing calendars
7311 $booking_nights = $a['booking_nights'];
7312 break;
7313 }
7314 if (isset($a['days'])) {
7315 $booking_nights = $a['days'];
7316 break;
7317 }
7318 }
7319
7320 /**
7321 * Get a list of promotion IDs that may have been set to avoid duplicate discounts on OTAs.
7322 *
7323 * @since 1.16.4 (J) - 1.6.4 (WP)
7324 */
7325 $skip_promo_ids = self::registerPromotionIds();
7326
7327 $totseasons = 0;
7328 if (!$parsed_season && !$seasons_dates) {
7329 $q = "SELECT * FROM `#__vikbooking_seasons` WHERE (" .
7330 ($sto > $sfrom ? "(`from`<=" . $sfrom . " AND `to`>=" . $sto . ") " : "") .
7331 ($sto > $sfrom ? "OR (`from`<=" . $sfrom . " AND `to`>=" . $sfrom . ") " : "(`from`<=" . $sfrom . " AND `to`<=" . $sfrom . " AND `from`>`to`) ") .
7332 ($sto > $sfrom ? "OR (`from`<=" . $sto . " AND `to`>=" . $sto . ") " : "OR (`from`>=" . $sto . " AND `to`>=" . $sto . " AND `from`>`to`) ") .
7333 ($sto > $sfrom ? "OR (`from`>=" . $sfrom . " AND `from`<=" . $sto . " AND `to`>=" . $sfrom . " AND `to`<=" . $sto . ")" : "OR (`from`>=" . $sfrom . " AND `from`>" . $sto . " AND `to`<" . $sfrom . " AND `to`<=" . $sto . " AND `from`>`to`)") .
7334 ($sto > $sfrom ? " OR (`from`<=" . $sfrom . " AND `from`<=" . $sto . " AND `to`<" . $sfrom . " AND `to`<" . $sto . " AND `from`>`to`) OR (`from`>" . $sfrom . " AND `from`>" . $sto . " AND `to`>=" . $sfrom . " AND `to`>=" . $sto . " AND `from`>`to`)" : " OR (`from` <=" . $sfrom . " AND `to` >=" . $sfrom . " AND `from` >=" . $sto . " AND `to` >" . $sto . " AND `from` < `to`)") .
7335 ($sto > $sfrom ? " OR (`from` >=" . $sfrom . " AND `from` <" . $sto . " AND `to` <" . $sfrom . " AND `to` <" . $sto . " AND `from` > `to`)" : " OR (`from` <" . $sfrom . " AND `to` >=" . $sto . " AND `from` <=" . $sto . " AND `to` <" . $sfrom . " AND `from` < `to`)"). //VBO 1.6 Else part is for Season Jan 6 to Feb 12 - Booking Dec 31 to Jan 8
7336 ($sto > $sfrom ? " OR (`from` >" . $sfrom . " AND `from` >" . $sto . " AND `to` >=" . $sfrom . " AND `to` <" . $sto . " AND `from` > `to`)" : " OR (`from` >=" . $sfrom . " AND `from` >" . $sto . " AND `to` >" . $sfrom . " AND `to` >" . $sto . " AND `from` < `to`) OR (`from` <" . $sfrom . " AND `from` <" . $sto . " AND `to` <" . $sfrom . " AND `to` <=" . $sto . " AND `from` < `to`)"). //VBO 1.7 Else part for seasons Dec 25 to Dec 31, Jan 2 to Jan 5 - Booking Dec 20 to Jan 7
7337 ") ORDER BY `#__vikbooking_seasons`.`promo` ASC;";
7338
7339 if ($cache_signature && isset($cached_seasons[$cache_signature])) {
7340 // avoid making a query
7341 $seasons = $cached_seasons[$cache_signature];
7342 } else {
7343 // get the season records by running the query
7344 $dbo->setQuery($q);
7345 $seasons = $dbo->loadAssocList();
7346 }
7347
7348 // count total seasons
7349 $totseasons = $seasons ? count($seasons) : 0;
7350
7351 if ($cache_signature) {
7352 // cache season records
7353 $cached_seasons[$cache_signature] = $seasons;
7354 }
7355 }
7356
7357 if ($totseasons > 0 || $parsed_season || $seasons_dates) {
7358 if ($totseasons > 0) {
7359 $seasons = $seasons;
7360 } elseif ($parsed_season) {
7361 $seasons = [$parsed_season];
7362 } else {
7363 $seasons = $seasons_dates;
7364 }
7365 $vbo_tn->translateContents($seasons, '#__vikbooking_seasons');
7366 $applyseasons = false;
7367 $mem = [];
7368 foreach ($arr as $k => $a) {
7369 $mem[$k]['daysused'] = 0;
7370 $mem[$k]['sum'] = [];
7371 $mem[$k]['spids'] = [];
7372 /**
7373 * The keys below are all needed to apply the promotions on the room's final cost.
7374 *
7375 * @since 1.13.5
7376 */
7377 $mem[$k]['diffs'] = [];
7378 $mem[$k]['trans_keys'] = [];
7379 $mem[$k]['trans_factors'] = [];
7380 $mem[$k]['trans_affdays'] = [];
7381 }
7382 $affdayslistless = [];
7383 foreach ($seasons as $s) {
7384 // VBO 1.10 - double check that the 'from' and 'to' properties are not empty (dates filter), in case VCM passes an array of seasons already taken from the DB
7385 if (empty($s['from']) && empty($s['to']) && !empty($s['wdays'])) {
7386 // a season for Jan 1st to Jan 1st (1 day), with NO week-days filter is still accepted
7387 continue;
7388 }
7389
7390 /**
7391 * VCM may build a fake season as a "restriction placeholder" if no special prices found.
7392 * We need to skip such fake seasons as they do not need any parsing.
7393 *
7394 * @since 1.13
7395 */
7396 if (empty($s['from']) && empty($s['to']) && empty($s['diffcost']) && !isset($s['from'])) {
7397 continue;
7398 }
7399
7400 // check if this is a promotion registered for skipping
7401 if (isset($s['promo']) && $s['promo'] && in_array($s['id'], $skip_promo_ids)) {
7402 continue;
7403 }
7404
7405 // Special Price tied to the year
7406 if (!empty($s['year']) && $s['year'] > 0) {
7407 //VBO 1.7 - do not skip seasons tied to the year for bookings between two years
7408 if ($one['year'] != $s['year'] && $two['year'] != $s['year']) {
7409 //VBO 1.9 - tied to the year can be set for prev year (Dec 27 to Jan 3) and booking can be Jan 1 to Jan 3 - do not skip in this case
7410 if (($one['year'] - $s['year']) != 1 || $s['from'] < $s['to']) {
7411 continue;
7412 }
7413 //VBO 1.9 - tied to 2016 going through Jan 2017: dates of December 2017 should skip this speacial price
7414 if (($one['year'] - $s['year']) == 1 && $s['from'] > $s['to']) {
7415 $calc_ends = mktime(0, 0, 0, 1, 1, ($s['year'] + 1)) + $s['to'];
7416 if ($calc_ends < ($from - $tomidnightone)) {
7417 continue;
7418 }
7419 }
7420 } elseif ($one['year'] < $s['year'] && $two['year'] == $s['year']) {
7421 //VBO 1.9 - season tied to the year 2017 accross 2018 and we are parsing dates accross prev year 2016-2017
7422 if ($s['from'] > $s['to']) {
7423 continue;
7424 }
7425 if (($basetwo + $s['from'] + 86399) > $to) {
7426 /**
7427 * Assuming that we are on 2021, and we are booking a 2-night stay from 30/12 to 01/01. This statement involves
7428 * a special price tied to the year 2022 for the night of 31/12 (or near dates), but we are booking the night of
7429 * New Year's Eve of 2021, and so the special price pre-prepared for the year after (2022) should be ignored.
7430 *
7431 * @since 1.14.3 (J) - 1.4.3 (WP)
7432 */
7433 continue;
7434 }
7435 } elseif ($one['year'] == $s['year'] && $two['year'] > $s['year']) {
7436 if (($baseone + $s['to'] + 86399) < $from && $s['from'] < $s['to']) {
7437 /**
7438 * Assuming that we are on 2021, and we are booking a 4-night stay from 29/12 to 02/01. This statement involves
7439 * a special price tied to the year 2021 for the night of 01/01 (or near dates), but we are booking the night of
7440 * First of the Year of 2022, and so the old special price for the year before (2021) should be ignored.
7441 *
7442 * @since 1.14.3 (J) - 1.4.3 (WP)
7443 */
7444 continue;
7445 }
7446 } elseif ($one['year'] == $s['year'] && $two['year'] == $s['year'] && $s['from'] > $s['to']) {
7447 //VBO 1.9 - season tied to the year 2017 accross 2018 and we are parsing dates at the beginning of 2017 due to beginning loop in 2016 (Rates Overview)
7448 if (($baseone + $s['from']) > $to) {
7449 continue;
7450 }
7451 }
7452 }
7453 //
7454 $allrooms = !empty($s['idrooms']) ? explode(",", $s['idrooms']) : [];
7455 $allprices = !empty($s['idprices']) ? explode(",", $s['idprices']) : [];
7456 $inits = $baseone + $s['from'];
7457 if ($s['from'] < $s['to']) {
7458 $ends = $basetwo + $s['to'];
7459 //VikBooking 1.6 check if the inits must be set to the year after
7460 //ex. Season Jan 6 to Feb 12 - Booking Dec 31 to Jan 8 to charge Jan 6,7
7461 if ($sfrom > $s['from'] && $sto >= $s['from'] && $sfrom > $s['to'] && $sto <= $s['to'] && $s['from'] < $s['to'] && $sfrom > $sto) {
7462 $tmpbase = mktime(0, 0, 0, 1, 1, ($one['year'] + 1));
7463 $inits = $tmpbase + $s['from'];
7464 } elseif ($sfrom >= $s['from'] && $sfrom <= $s['to'] && $sto < $s['from'] && $sto < $s['to'] && $sfrom > $sto) {
7465 //VBO 1.7 - Season Dec 23 to Dec 29 - Booking Dec 29 to Jan 5
7466 $ends = $baseone + $s['to'];
7467 } elseif ($sfrom <= $s['from'] && $sfrom <= $s['to'] && $sto < $s['from'] && $sto < $s['to'] && $sfrom > $sto) {
7468 //VBO 1.7 - Season Dec 30 to Dec 31 - Booking Dec 29 to Jan 5
7469 $ends = $baseone + $s['to'];
7470 } elseif ($sfrom > $s['from'] && $sfrom > $s['to'] && $sto >= $s['from'] && ($sto >= $s['to'] || $sto <= $s['to']) && $sfrom > $sto) {
7471 //VBO 1.7 - Season Jan 1 to Jan 2 - Booking Dec 29 to Jan 5
7472 $inits = $basetwo + $s['from'];
7473 }
7474 } else {
7475 //between 2 years
7476 if ($baseone < $basetwo) {
7477 //ex. 29/12/2012 - 14/01/2013
7478 $ends = $basetwo + $s['to'];
7479 } else {
7480 if (($sfrom >= $s['from'] && $sto >= $s['from']) || ($sfrom < $s['from'] && $sto >= $s['from'] && $sfrom > $s['to'] && $sto > $s['to'])) {
7481 //ex. 25/12 - 30/12 with init season on 20/12 OR 27/12 for counting 28,29,30/12
7482 $tmpbase = mktime(0, 0, 0, 1, 1, ($one['year'] + 1));
7483 $ends = $tmpbase + $s['to'];
7484 } else {
7485 //ex. 03/01 - 09/01
7486 $ends = $basetwo + $s['to'];
7487 $tmpbase = mktime(0, 0, 0, 1, 1, ($one['year'] - 1));
7488 $inits = $tmpbase + $s['from'];
7489 }
7490 }
7491 }
7492 // leap years
7493 if ($isleap == true) {
7494 $infoseason = getdate($inits);
7495 $leapts = mktime(0, 0, 0, 2, 29, $infoseason['year']);
7496 // VikBooking 1.6 added below && $infoseason['year'] == $one['year']
7497 // for those seasons like 2015 Dec 14 to 2016 Jan 5 and booking dates like 2016 Jan 1 to Jan 6 where 2015 is not leap
7498 if ($infoseason[0] > $leapts && $infoseason['year'] == $one['year']) {
7499 /**
7500 * Timestamp must be greater than the leap-day of Feb 29th.
7501 * It used to be checked for >= $leapts.
7502 *
7503 * @since July 3rd 2019
7504 */
7505 $inits += 86400;
7506 $ends += 86400;
7507 }
7508 }
7509
7510 // promotions
7511 $promotion = [];
7512 if ($s['promo'] == 1) {
7513 $daysadv = (($inits - time()) / 86400);
7514 $daysadv = $daysadv > 0 ? (int)ceil($daysadv) : 0;
7515 if (!empty($s['promodaysadv']) && $s['promodaysadv'] > $daysadv) {
7516 continue;
7517 } elseif (!empty($s['promolastmin']) && $s['promolastmin'] > 0) {
7518 $secstocheckin = ($from - time());
7519 if ($s['promolastmin'] < $secstocheckin) {
7520 // VBO 1.11 - too many seconds to the check-in date, skip this last minute promotion
7521 continue;
7522 }
7523 }
7524 if ($s['promominlos'] > 1 && $booking_nights < $s['promominlos']) {
7525 /**
7526 * The minimum length of stay parameter is also taken to exclude the promotion from the calculation.
7527 *
7528 * @since 1.13.5
7529 */
7530 continue;
7531 }
7532 $promotion['todaydaysadv'] = $daysadv;
7533 $promotion['promodaysadv'] = $s['promodaysadv'];
7534 $promotion['promotxt'] = $s['promotxt'];
7535 }
7536
7537 // occupancy override
7538 $occupancy_ovr = !empty($s['occupancy_ovr']) ? json_decode($s['occupancy_ovr'], true) : [];
7539
7540 //week days
7541 $filterwdays = !empty($s['wdays']) ? true : false;
7542 $wdays = $filterwdays == true ? explode(';', $s['wdays']) : '';
7543 if (is_array($wdays) && $wdays) {
7544 foreach ($wdays as $kw => $wd) {
7545 if (strlen($wd) == 0) {
7546 unset($wdays[$kw]);
7547 }
7548 }
7549 }
7550
7551 // checkin must be after the begin of the season
7552 $checkininclok = true;
7553 if ($s['checkinincl'] == 1) {
7554 $checkininclok = false;
7555 if ($s['from'] < $s['to']) {
7556 if ($sfrom >= $s['from'] && $sfrom <= $s['to']) {
7557 $checkininclok = true;
7558 }
7559 } else {
7560 if (($sfrom >= $s['from'] && $sfrom > $s['to']) || ($sfrom < $s['from'] && $sfrom <= $s['to'])) {
7561 $checkininclok = true;
7562 }
7563 }
7564 }
7565 if ($checkininclok !== true) {
7566 continue;
7567 }
7568
7569 foreach ($arr as $k => $a) {
7570 // applied only to some types of price
7571 if ($allprices && !empty($allprices[0])) {
7572 // VikBooking 1.6: Price Calendar sets the idprice to -1
7573 if (!in_array("-" . $a['idprice'] . "-", $allprices) && $a['idprice'] > 0) {
7574 continue;
7575 }
7576 }
7577 // applied only to some room types
7578 if (!in_array("-" . $a['idroom'] . "-", $allrooms)) {
7579 continue;
7580 }
7581
7582 if (isset($a['days']) && is_float($a['days'])) {
7583 // prevent any possible warning with array_key_exists()
7584 $a['days'] = (int)$a['days'];
7585 }
7586
7587 $affdays = 0;
7588 $season_fromdayts = $fromdayts;
7589 $is_dst = date('I', $season_fromdayts);
7590 for ($i = 0; $i < $a['days']; $i++) {
7591 $todayts = $season_fromdayts + ($i * 86400);
7592 $is_now_dst = date('I', $todayts);
7593 if ($is_dst != $is_now_dst) {
7594 // Daylight Saving Time has changed, check how
7595 if ((bool)$is_dst === true) {
7596 $todayts += 3600;
7597 $season_fromdayts += 3600;
7598 } else {
7599 $todayts -= 3600;
7600 $season_fromdayts -= 3600;
7601 }
7602 $is_dst = $is_now_dst;
7603 }
7604 if ($todayts >= $inits && $todayts <= $ends) {
7605 $checkwday = getdate($todayts);
7606 // week days
7607 if ($filterwdays == true) {
7608 if (in_array($checkwday['wday'], $wdays)) {
7609 if (!isset($arr[$k]['affdayslist'])) {
7610 $arr[$k]['affdayslist'] = [];
7611 }
7612 $arr[$k]['affdayslist'][$checkwday['wday'].'-'.$checkwday['mday'].'-'.$checkwday['mon']] = isset($arr[$k]['affdayslist'][$checkwday['wday'].'-'.$checkwday['mday'].'-'.$checkwday['mon']]) && $arr[$k]['affdayslist'][$checkwday['wday'].'-'.$checkwday['mday'].'-'.$checkwday['mon']] > 0 ? $arr[$k]['affdayslist'][$checkwday['wday'].'-'.$checkwday['mday'].'-'.$checkwday['mon']] : 0;
7613 $arr[$k]['origdailycost'] = $a['cost'] / $a['days'];
7614 $affdayslistless[$s['id']][] = $checkwday['wday'].'-'.$checkwday['mday'].'-'.$checkwday['mon'];
7615 $affdays++;
7616 }
7617 } else {
7618 if (!isset($arr[$k]['affdayslist'])) {
7619 $arr[$k]['affdayslist'] = [];
7620 }
7621 $arr[$k]['affdayslist'][$checkwday['wday'].'-'.$checkwday['mday'].'-'.$checkwday['mon']] = isset($arr[$k]['affdayslist'][$checkwday['wday'].'-'.$checkwday['mday'].'-'.$checkwday['mon']]) && $arr[$k]['affdayslist'][$checkwday['wday'].'-'.$checkwday['mday'].'-'.$checkwday['mon']] > 0 ? $arr[$k]['affdayslist'][$checkwday['wday'].'-'.$checkwday['mday'].'-'.$checkwday['mon']] : 0;
7622 $arr[$k]['origdailycost'] = $a['cost'] / $a['days'];
7623 $affdayslistless[$s['id']][] = $checkwday['wday'].'-'.$checkwday['mday'].'-'.$checkwday['mon'];
7624 $affdays++;
7625 }
7626 //
7627 }
7628 }
7629
7630 if (!($affdays > 0)) {
7631 // no nights affected
7632 continue;
7633 }
7634
7635 // apply the rule
7636 $applyseasons = true;
7637 $dailyprice = $a['cost'] / $a['days'];
7638
7639 // modification factor object
7640 $factor = new stdClass;
7641
7642 // calculate new price progressively
7643 if (intval($s['val_pcent']) == 2) {
7644 // percentage value
7645 $factor->pcent = 1;
7646 $pctval = $s['diffcost'];
7647 if (strlen((string)$s['losoverride'])) {
7648 // values overrides
7649 $arrvaloverrides = [];
7650 $valovrparts = explode('_', $s['losoverride']);
7651 foreach ($valovrparts as $valovr) {
7652 if (!empty($valovr)) {
7653 $ovrinfo = explode(':', $valovr);
7654 if (strstr($ovrinfo[0], '-i') != false) {
7655 $ovrinfo[0] = str_replace('-i', '', $ovrinfo[0]);
7656 if ((int)$ovrinfo[0] < $a['days']) {
7657 $arrvaloverrides[$a['days']] = $ovrinfo[1];
7658 }
7659 }
7660 $arrvaloverrides[$ovrinfo[0]] = $ovrinfo[1];
7661 }
7662 }
7663 if (isset($a['days']) && array_key_exists($a['days'], $arrvaloverrides)) {
7664 $pctval = $arrvaloverrides[$a['days']];
7665 }
7666 }
7667 if (intval($s['type']) == 1) {
7668 // charge
7669 $factor->type = '+';
7670 $cpercent = 100 + $pctval;
7671 } else {
7672 // discount
7673 $factor->type = '-';
7674 $cpercent = 100 - $pctval;
7675 }
7676 $factor->amount = $pctval;
7677 $dailysum = ($dailyprice * $cpercent / 100);
7678 $newprice = $dailysum * $affdays;
7679 } else {
7680 // absolute value
7681 $factor->pcent = 0;
7682 $absval = $s['diffcost'];
7683 if (strlen((string)$s['losoverride'])) {
7684 // values overrides
7685 $arrvaloverrides = [];
7686 $valovrparts = explode('_', $s['losoverride']);
7687 foreach ($valovrparts as $valovr) {
7688 if (!empty($valovr)) {
7689 $ovrinfo = explode(':', $valovr);
7690 if (strstr($ovrinfo[0], '-i') != false) {
7691 $ovrinfo[0] = str_replace('-i', '', $ovrinfo[0]);
7692 if ((int)$ovrinfo[0] < $a['days']) {
7693 $arrvaloverrides[$a['days']] = $ovrinfo[1];
7694 }
7695 }
7696 $arrvaloverrides[$ovrinfo[0]] = $ovrinfo[1];
7697 }
7698 }
7699 if (isset($a['days']) && array_key_exists($a['days'], $arrvaloverrides)) {
7700 $absval = $arrvaloverrides[$a['days']];
7701 }
7702 }
7703 if (intval($s['type']) == 1) {
7704 // charge
7705 $factor->type = '+';
7706 $dailysum = ($dailyprice + $absval);
7707 $newprice = $dailysum * $affdays;
7708 } else {
7709 // discount
7710 $factor->type = '-';
7711 $dailysum = ($dailyprice - $absval);
7712 $newprice = $dailysum * $affdays;
7713 }
7714 $factor->amount = $absval;
7715 }
7716
7717 // apply rounding
7718 $factor->roundmode = $s['roundmode'];
7719 if (!empty($s['roundmode'])) {
7720 $newprice = round($newprice, 0, constant($s['roundmode']));
7721 } else {
7722 $newprice = round($newprice, 2);
7723 }
7724
7725 // define the promotion (only if no value overrides set the amount to 0)
7726 if ($promotion && ((isset($absval) && $absval > 0) || $pctval > 0)) {
7727 /**
7728 * Include the discount information (if any). The cost re-calculated may not be
7729 * precise if multiple special prices were applied over the same dates.
7730 *
7731 * @since 1.13
7732 */
7733 if ($s['type'] == 2 && $s['diffcost'] > 0) {
7734 $promotion['discount'] = [
7735 'amount' => $s['diffcost'],
7736 'pcent' => (int)($s['val_pcent'] == 2),
7737 ];
7738 }
7739 //
7740 $mem[$k]['promotion'] = $promotion;
7741 }
7742
7743 // define the occupancy override
7744 if (array_key_exists($a['idroom'], $occupancy_ovr) && $occupancy_ovr[$a['idroom']]) {
7745 $mem[$k]['occupancy_ovr'] = $occupancy_ovr[$a['idroom']];
7746 }
7747
7748 // affected days list
7749 if (isset($arr[$k]['affdayslist']) && is_array($arr[$k]['affdayslist'])) {
7750 foreach ($arr[$k]['affdayslist'] as $affk => $affv) {
7751 if (isset($affdayslistless[$s['id']]) && in_array($affk, $affdayslistless[$s['id']])) {
7752 $arr[$k]['affdayslist'][$affk] = !empty($arr[$k]['affdayslist'][$affk]) && $arr[$k]['affdayslist'][$affk] > 0 ? ($arr[$k]['affdayslist'][$affk] - $arr[$k]['origdailycost'] + $dailysum) : ($affv + $dailysum);
7753 }
7754 }
7755 }
7756
7757 // push special price ID
7758 if (!in_array($s['id'], $mem[$k]['spids'])) {
7759 array_push($mem[$k]['spids'], $s['id']);
7760 }
7761
7762 // push difference generated only if to be applied progressively
7763 if (!$s['promo'] || ($s['promo'] && !$s['promofinalprice'])) {
7764 /**
7765 * Push the difference generated by this special price for later transliteration of final price,
7766 * only if the special price is calculated progressively and not on the final price.
7767 *
7768 * @since 1.13.5
7769 */
7770 array_push($mem[$k]['diffs'], ($newprice - ($dailyprice * $affdays)));
7771 } elseif ($s['promo'] && $s['promofinalprice'] && $factor->pcent) {
7772 /**
7773 * This is a % promotion to be applied on the final price, so we need to save that this memory key
7774 * will need the transliteration, aka adjusting this new price by applying the charge/discount on
7775 * all differences applied by the previous special pricing rules.
7776 *
7777 * @since 1.13.5
7778 */
7779 array_push($mem[$k]['trans_keys'], count($mem[$k]['sum']));
7780 array_push($mem[$k]['trans_factors'], $factor);
7781 array_push($mem[$k]['trans_affdays'], $affdays);
7782 }
7783
7784 // push values in memory array
7785 array_push($mem[$k]['sum'], $newprice);
7786 $mem[$k]['daysused'] += $affdays;
7787 array_push($roomschange, $a['idroom']);
7788 }
7789 }
7790 if ($applyseasons) {
7791 foreach ($mem as $k => $v) {
7792 if ($v['daysused'] > 0 && $v['sum']) {
7793 $newprice = 0;
7794 $dailyprice = $arr[$k]['cost'] / $arr[$k]['days'];
7795 $restdays = $arr[$k]['days'] - $v['daysused'];
7796 $addrest = $restdays * $dailyprice;
7797 $newprice += $addrest;
7798
7799 // calculate new final cost
7800 $redo_rounding = null;
7801 foreach ($v['sum'] as $sum_index => $add) {
7802 /**
7803 * The application of the various special pricing rules is made in a progressive and cumulative way
7804 * by always starting from the room base cost or its average daily cost. However, promotions may need
7805 * to be applied on the room final cost, and not in a progresive way. In order to keep the progressive
7806 * algorithm, for applying the special prices on the room final cost we need to apply the same promotion
7807 * onto the differences generated by all the regular and progressively applied special pricing rules.
7808 *
7809 * @since 1.13.5
7810 */
7811 if (in_array($sum_index, $v['trans_keys']) && $v['diffs']) {
7812 /**
7813 * This progressive price difference must be applied on the room final cost, so we need to
7814 * apply the transliteration over the other differences applied by other special prices.
7815 */
7816 $transliterate_key = array_search($sum_index, $v['trans_keys']);
7817 if ($transliterate_key !== false && isset($v['trans_factors'][$transliterate_key])) {
7818 // this is the % promotion we are looking for applying it on the final cost
7819 $factor = $v['trans_factors'][$transliterate_key];
7820 if (is_object($factor) && $factor->pcent) {
7821 $final_factor = 0;
7822 foreach ($v['diffs'] as $diff_index => $prog_diff) {
7823 $final_factor += $prog_diff * $factor->amount / 100;
7824 }
7825 // update rounding
7826 $redo_rounding = !empty($factor->roundmode) ? $factor->roundmode : $redo_rounding;
7827
7828 /**
7829 * Leverage the final factor depending on how many nights this affected.
7830 *
7831 * @since 1.15.0 (J) - 1.5.0 (WP)
7832 */
7833 if (isset($v['trans_affdays'][$transliterate_key]) && $v['trans_affdays'][$transliterate_key] < $arr[$k]['days']) {
7834 $avg_final_factor = $final_factor / $arr[$k]['days'];
7835 $final_factor = $avg_final_factor * $v['trans_affdays'][$transliterate_key];
7836 }
7837
7838 // apply the final transliteration to obtain a value like if it was applied on the room's final cost
7839 $add = $factor->type == '+' ? ($add + $final_factor) : ($add - $final_factor);
7840 }
7841 }
7842 }
7843
7844 // apply new price progressively
7845 $newprice += $add;
7846 }
7847
7848 // apply rounding from factor
7849 if (!empty($redo_rounding)) {
7850 $newprice = round($newprice, 0, constant($redo_rounding));
7851 }
7852
7853 // set promotion (if any)
7854 if (isset($v['promotion'])) {
7855 $arr[$k]['promotion'] = $v['promotion'];
7856 }
7857
7858 // set occupancy overrides (if any)
7859 if (isset($v['occupancy_ovr'])) {
7860 $arr[$k]['occupancy_ovr'] = $v['occupancy_ovr'];
7861 }
7862
7863 // set new final cost and update nights affected
7864 $arr[$k]['cost'] = $newprice;
7865 $arr[$k]['affdays'] = $v['daysused'];
7866 if (array_key_exists('spids', $v) && $v['spids']) {
7867 $arr[$k]['spids'] = $v['spids'];
7868 }
7869 }
7870 }
7871 }
7872 }
7873
7874 // week days with no season
7875 $roomschange = array_unique($roomschange);
7876 $totspecials = 0;
7877 if (!$seasons_wdays) {
7878 $q = "SELECT * FROM `#__vikbooking_seasons` WHERE ((`from` = 0 AND `to` = 0) OR (`from` IS NULL AND `to` IS NULL));";
7879
7880 if ($cache_signature_wdays && isset($cached_seasons[$cache_signature_wdays])) {
7881 // avoid making a query
7882 $specials = $cached_seasons[$cache_signature_wdays];
7883 } else {
7884 // get the week-days-with-no-season records by running the query
7885 $dbo->setQuery($q);
7886 $specials = $dbo->loadAssocList();
7887 }
7888
7889 // count records
7890 $totspecials = $specials ? count($specials) : 0;
7891
7892 if ($cache_signature_wdays) {
7893 // cache week-days-with-no-season records
7894 $cached_seasons[$cache_signature_wdays] = $seasons;
7895 }
7896 }
7897 if ($totspecials > 0 || $seasons_wdays) {
7898 $specials = $totspecials > 0 ? $specials : $seasons_wdays;
7899 $vbo_tn->translateContents($specials, '#__vikbooking_seasons');
7900 $applyseasons = false;
7901 /**
7902 * We no longer unset the previous memory of the seasons with dates filters
7903 * because we need the responses to be merged. We do it only if not set.
7904 * We only keep the property 'spids' but the others should be unset.
7905 *
7906 * @since 1.11
7907 */
7908 if (!isset($mem)) {
7909 $mem = [];
7910 foreach ($arr as $k => $a) {
7911 $mem[$k]['daysused'] = 0;
7912 $mem[$k]['sum'] = [];
7913 $mem[$k]['spids'] = [];
7914 }
7915 } else {
7916 foreach ($arr as $k => $a) {
7917 $mem[$k]['daysused'] = 0;
7918 $mem[$k]['sum'] = [];
7919 }
7920 }
7921 //
7922 foreach ($specials as $s) {
7923 // VBO 1.10 - double check that the 'from' and 'to' properties are empty (only weekdays), in case VCM passes an array of seasons already taken from the DB
7924 if (!empty($s['from']) || !empty($s['to'])) {
7925 continue;
7926 }
7927
7928 // check if this is a promotion registered for skipping
7929 if (isset($s['promo']) && $s['promo'] && in_array($s['id'], $skip_promo_ids)) {
7930 continue;
7931 }
7932
7933 // Special Price tied to the year
7934 if (!empty($s['year']) && $s['year'] > 0) {
7935 if ($one['year'] != $s['year']) {
7936 continue;
7937 }
7938 }
7939
7940 $allrooms = !empty($s['idrooms']) ? explode(",", $s['idrooms']) : [];
7941 $allprices = !empty($s['idprices']) ? explode(",", $s['idprices']) : [];
7942 // week days
7943 $filterwdays = !empty($s['wdays']) ? true : false;
7944 $wdays = $filterwdays == true ? explode(';', $s['wdays']) : '';
7945 if (is_array($wdays) && $wdays) {
7946 foreach ($wdays as $kw => $wd) {
7947 if (strlen($wd) == 0) {
7948 unset($wdays[$kw]);
7949 }
7950 }
7951 }
7952
7953 foreach ($arr as $k => $a) {
7954 // only rooms with no price modifications from seasons
7955
7956 // applied only to some types of price
7957 if ($allprices && !empty($allprices[0])) {
7958 // VikBooking 1.6: Price Calendar sets the idprice to -1
7959 if (!in_array("-" . $a['idprice'] . "-", $allprices) && $a['idprice'] > 0) {
7960 continue;
7961 }
7962 }
7963
7964 /**
7965 * We should not exclude the rooms that already had a modification of the price through a season
7966 * with a dates filter or we risk to get invalid prices by skipping a rule for just some weekdays.
7967 * The control " || in_array($a['idroom'], $roomschange)" was removed from the IF below.
7968 *
7969 * @since 1.11
7970 */
7971 if (!in_array("-" . $a['idroom'] . "-", $allrooms)) {
7972 continue;
7973 }
7974
7975 if (isset($a['days']) && is_float($a['days'])) {
7976 // prevent any possible warning with array_key_exists()
7977 $a['days'] = (int)$a['days'];
7978 }
7979
7980 $affdays = 0;
7981 $season_fromdayts = $fromdayts;
7982 $is_dst = date('I', $season_fromdayts);
7983 for ($i = 0; $i < $a['days']; $i++) {
7984 $todayts = $season_fromdayts + ($i * 86400);
7985 $is_now_dst = date('I', $todayts);
7986 if ($is_dst != $is_now_dst) {
7987 // Daylight Saving Time has changed, check how
7988 if ((bool)$is_dst === true) {
7989 $todayts += 3600;
7990 $season_fromdayts += 3600;
7991 } else {
7992 $todayts -= 3600;
7993 $season_fromdayts -= 3600;
7994 }
7995 $is_dst = $is_now_dst;
7996 }
7997 // week days
7998 if ($filterwdays == true) {
7999 $checkwday = getdate($todayts);
8000 if (in_array($checkwday['wday'], $wdays)) {
8001 $arr[$k]['affdayslist'][$checkwday['wday'].'-'.$checkwday['mday'].'-'.$checkwday['mon']] = 0;
8002 $arr[$k]['origdailycost'] = $a['cost'] / $a['days'];
8003 $affdays++;
8004 }
8005 }
8006 //
8007 }
8008
8009 if (!($affdays > 0)) {
8010 // no nights affected
8011 continue;
8012 }
8013
8014 // apply the rule
8015 $applyseasons = true;
8016 $dailyprice = $a['cost'] / $a['days'];
8017
8018 if (intval($s['val_pcent']) == 2) {
8019 // percentage value
8020 $pctval = $s['diffcost'];
8021 if (strlen((string)$s['losoverride'])) {
8022 // values overrides
8023 $arrvaloverrides = [];
8024 $valovrparts = explode('_', $s['losoverride']);
8025 foreach ($valovrparts as $valovr) {
8026 if (!empty($valovr)) {
8027 $ovrinfo = explode(':', $valovr);
8028 if (strstr($ovrinfo[0], '-i') != false) {
8029 $ovrinfo[0] = str_replace('-i', '', $ovrinfo[0]);
8030 if ((int)$ovrinfo[0] < $a['days']) {
8031 $arrvaloverrides[$a['days']] = $ovrinfo[1];
8032 }
8033 }
8034 $arrvaloverrides[$ovrinfo[0]] = $ovrinfo[1];
8035 }
8036 }
8037 if (isset($a['days']) && array_key_exists($a['days'], $arrvaloverrides)) {
8038 $pctval = $arrvaloverrides[$a['days']];
8039 }
8040 }
8041 if (intval($s['type']) == 1) {
8042 // charge
8043 $cpercent = 100 + $pctval;
8044 } else {
8045 // discount
8046 $cpercent = 100 - $pctval;
8047 }
8048 $dailysum = ($dailyprice * $cpercent / 100);
8049 $newprice = $dailysum * $affdays;
8050 } else {
8051 // absolute value
8052 $absval = $s['diffcost'];
8053 if (strlen((string)$s['losoverride'])) {
8054 // values overrides
8055 $arrvaloverrides = [];
8056 $valovrparts = explode('_', $s['losoverride']);
8057 foreach ($valovrparts as $valovr) {
8058 if (!empty($valovr)) {
8059 $ovrinfo = explode(':', $valovr);
8060 if (strstr($ovrinfo[0], '-i') != false) {
8061 $ovrinfo[0] = str_replace('-i', '', $ovrinfo[0]);
8062 if ((int)$ovrinfo[0] < $a['days']) {
8063 $arrvaloverrides[$a['days']] = $ovrinfo[1];
8064 }
8065 }
8066 $arrvaloverrides[$ovrinfo[0]] = $ovrinfo[1];
8067 }
8068 }
8069 if (isset($a['days']) && array_key_exists($a['days'], $arrvaloverrides)) {
8070 $absval = $arrvaloverrides[$a['days']];
8071 }
8072 }
8073 if (intval($s['type']) == 1) {
8074 // charge
8075 $dailysum = ($dailyprice + $absval);
8076 $newprice = $dailysum * $affdays;
8077 } else {
8078 // discount
8079 $dailysum = ($dailyprice - $absval);
8080 $newprice = $dailysum * $affdays;
8081 }
8082 }
8083
8084 // apply rounding
8085 if (!empty($s['roundmode'])) {
8086 $newprice = round($newprice, 0, constant($s['roundmode']));
8087 } else {
8088 $newprice = round($newprice, 2);
8089 }
8090
8091 foreach ($arr[$k]['affdayslist'] as $affk => $affv) {
8092 $arr[$k]['affdayslist'][$affk] = $affv + $dailysum;
8093 }
8094 if (!in_array($s['id'], $mem[$k]['spids'])) {
8095 $mem[$k]['spids'][] = $s['id'];
8096 }
8097 $mem[$k]['sum'][] = $newprice;
8098 $mem[$k]['daysused'] += $affdays;
8099 }
8100 }
8101 if ($applyseasons) {
8102 foreach ($mem as $k => $v) {
8103 if ($v['daysused'] > 0 && $v['sum']) {
8104 $newprice = 0;
8105 $dailyprice = $arr[$k]['cost'] / $arr[$k]['days'];
8106 $restdays = $arr[$k]['days'] - $v['daysused'];
8107 $addrest = $restdays * $dailyprice;
8108 $newprice += $addrest;
8109 foreach ($v['sum'] as $add) {
8110 $newprice += $add;
8111 }
8112 $arr[$k]['cost'] = $newprice;
8113 $arr[$k]['affdays'] = $v['daysused'];
8114 if (array_key_exists('spids', $v) && $v['spids']) {
8115 $arr[$k]['spids'] = $v['spids'];
8116 }
8117 }
8118 }
8119 }
8120 }
8121 // end week days with no season
8122
8123 return $arr;
8124 }
8125
8126 public static function getRoomRplansClosingDates($idroom)
8127 {
8128 $dbo = JFactory::getDbo();
8129
8130 $q = "SELECT * FROM `#__vikbooking_prices` WHERE `closingd` IS NOT NULL;";
8131 $dbo->setQuery($q);
8132 $price_records = $dbo->loadAssocList();
8133 if (!$price_records) {
8134 return [];
8135 }
8136
8137 $closingd = [];
8138 foreach ($price_records as $prec) {
8139 if (empty($prec['closingd'])) {
8140 continue;
8141 }
8142 $price_closing = json_decode($prec['closingd'], true);
8143 if (!$price_closing || !isset($price_closing[$idroom]) || !$price_closing[$idroom]) {
8144 continue;
8145 }
8146 // check expired dates and clean up
8147 $today_midnight = mktime(0, 0, 0);
8148 $cleaned = false;
8149 foreach ($price_closing[$idroom] as $k => $v) {
8150 if (strtotime($v) < $today_midnight) {
8151 $cleaned = true;
8152 unset($price_closing[$idroom][$k]);
8153 }
8154 }
8155 if (!$price_closing[$idroom]) {
8156 unset($price_closing[$idroom]);
8157 } elseif ($cleaned === true) {
8158 // reset array keys for smaller JSON size
8159 $price_closing[$idroom] = array_values($price_closing[$idroom]);
8160 }
8161 if ($cleaned === true) {
8162 $q = "UPDATE `#__vikbooking_prices` SET `closingd`=".(count($price_closing) > 0 ? $dbo->quote(json_encode($price_closing)) : "NULL")." WHERE `id`=".$prec['id'].";";
8163 $dbo->setQuery($q);
8164 $dbo->execute();
8165 }
8166 if (!isset($price_closing[$idroom]) || !$price_closing[$idroom]) {
8167 continue;
8168 }
8169 $closingd[$prec['id']] = $price_closing[$idroom];
8170 }
8171
8172 return $closingd;
8173 }
8174
8175 public static function getRoomRplansClosedInDates($roomids, $checkints, $numnights)
8176 {
8177 $dbo = JFactory::getDbo();
8178
8179 $closingd = [];
8180
8181 $q = "SELECT * FROM `#__vikbooking_prices` WHERE `closingd` IS NOT NULL;";
8182 $dbo->setQuery($q);
8183 $price_records = $dbo->loadAssocList();
8184 if ($price_records && $roomids) {
8185 $info_start = getdate($checkints);
8186 $checkin_midnight = mktime(0, 0, 0, $info_start['mon'], $info_start['mday'], $info_start['year']);
8187 $all_nights = [];
8188 for ($i = 0; $i < (int)$numnights; $i++) {
8189 $next_midnight = mktime(0, 0, 0, $info_start['mon'], ($info_start['mday'] + $i), $info_start['year']);
8190 $all_nights[] = date('Y-m-d', $next_midnight);
8191 }
8192 foreach ($price_records as $prec) {
8193 if (empty($prec['closingd'])) {
8194 continue;
8195 }
8196 $price_closing = json_decode($prec['closingd'], true);
8197 if (!$price_closing) {
8198 continue;
8199 }
8200 foreach ($price_closing as $idroom => $rclosedd) {
8201 if (!in_array($idroom, $roomids) || !is_array($rclosedd)) {
8202 continue;
8203 }
8204 if (!array_key_exists($idroom, $closingd)) {
8205 $closingd[$idroom] = [];
8206 }
8207 foreach ($all_nights as $night) {
8208 if (in_array($night, $rclosedd)) {
8209 if (array_key_exists($prec['id'], $closingd[$idroom])) {
8210 $closingd[$idroom][$prec['id']][] = $night;
8211 } else {
8212 $closingd[$idroom][$prec['id']] = [$night];
8213 }
8214 }
8215 }
8216 }
8217 }
8218 }
8219
8220 return $closingd;
8221 }
8222
8223 /**
8224 * Tells whether at least one payment method must be selected to check-out.
8225 *
8226 * @param array $rooms_involved optional list of room IDs being booked.
8227 *
8228 * @return bool true if at least one payment method is applicable.
8229 *
8230 * @since 1.16.0 (J) - 1.6.0 (WP) added 1st argument to check the room associations.
8231 */
8232 public static function areTherePayments($rooms_involved = [])
8233 {
8234 $dbo = JFactory::getDbo();
8235
8236 $q = "SELECT `id`, `idrooms` FROM `#__vikbooking_gpayments` WHERE `published`=1;";
8237 $dbo->setQuery($q);
8238 $dbo->execute();
8239
8240 if (!$dbo->getNumRows()) {
8241 // no published payment options found
8242 return false;
8243 }
8244
8245 $pay_options = $dbo->loadAssocList();
8246
8247 if (!count($rooms_involved)) {
8248 // if no filters given, return true
8249 return true;
8250 }
8251
8252 // validate room associations
8253 foreach ($pay_options as $pay_k => $pay_v) {
8254 if (empty($pay_v['idrooms'])) {
8255 continue;
8256 }
8257 $rooms_supported = json_decode($pay_v['idrooms']);
8258 if (!is_array($rooms_supported) || !count($rooms_supported)) {
8259 continue;
8260 }
8261 // make sure all rooms being booked are supported
8262 foreach ($rooms_involved as $room_id_booked) {
8263 if (!in_array($room_id_booked, $rooms_supported)) {
8264 // we need to unset this payment option as soon as we find one non-matching room ID
8265 unset($pay_options[$pay_k]);
8266 break;
8267 }
8268 }
8269 }
8270
8271 return (bool)count($pay_options);
8272 }
8273
8274 public static function getPayment($idp, $vbo_tn = null)
8275 {
8276 if (empty($idp)) {
8277 return false;
8278 }
8279
8280 if (strstr((string)$idp, '=') !== false) {
8281 $parts = explode('=', $idp);
8282 $idp = $parts[0];
8283 }
8284
8285 $dbo = JFactory::getDbo();
8286
8287 $q = "SELECT * FROM `#__vikbooking_gpayments` WHERE `id`=" . $dbo->quote($idp) . ";";
8288 $dbo->setQuery($q);
8289 $payment = $dbo->loadAssocList();
8290 if (!$payment) {
8291 return false;
8292 }
8293
8294 if (is_object($vbo_tn)) {
8295 $vbo_tn->translateContents($payment, '#__vikbooking_gpayments');
8296 }
8297
8298 return $payment[0];
8299 }
8300
8301 public static function getCronKey() {
8302 $dbo = JFactory::getDbo();
8303 $ckey = '';
8304 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='cronkey';";
8305 $dbo->setQuery($q);
8306 $dbo->execute();
8307 if ($dbo->getNumRows() > 0) {
8308 $cval = $dbo->loadAssocList();
8309 $ckey = $cval[0]['setting'];
8310 }
8311 return $ckey;
8312 }
8313
8314 public static function getNextInvoiceNumber () {
8315 $dbo = JFactory::getDbo();
8316 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='invoiceinum';";
8317 $dbo->setQuery($q);
8318 $dbo->execute();
8319 $s = $dbo->loadAssocList();
8320 return (intval($s[0]['setting']) + 1);
8321 }
8322
8323 public static function getInvoiceNumberSuffix () {
8324 $dbo = JFactory::getDbo();
8325 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='invoicesuffix';";
8326 $dbo->setQuery($q);
8327 $dbo->execute();
8328 $s = $dbo->loadAssocList();
8329 return $s[0]['setting'];
8330 }
8331
8332 public static function getInvoiceCompanyInfo () {
8333 $dbo = JFactory::getDbo();
8334 $q="SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='invcompanyinfo';";
8335 $dbo->setQuery($q);
8336 $dbo->execute();
8337 $s=$dbo->loadAssocList();
8338 return $s[0]['setting'];
8339 }
8340
8341 /**
8342 * Gets the number for the next booking receipt generation,
8343 * updates the last receipt number used for the later iterations,
8344 * stores a new receipt record to keep track of the receipts issued.
8345 *
8346 * @param int $bid the Booking ID for which we are/want to generating the receipt.
8347 * @param [int] $newnum the last number used for generating the receipt.
8348 *
8349 * @return int
8350 */
8351 public static function getNextReceiptNumber ($bid, $newnum = false) {
8352 $dbo = JFactory::getDbo();
8353 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='receiptinum';";
8354 $dbo->setQuery($q);
8355 $dbo->execute();
8356 if ($dbo->getNumRows() > 0) {
8357 $s = $dbo->loadResult();
8358 //check if this booking has already a receipt, and return that number
8359 $prev_receipt = array();
8360 $q = "SELECT * FROM `#__vikbooking_receipts` WHERE `idorder`=".(int)$bid.";";
8361 $dbo->setQuery($q);
8362 $dbo->execute();
8363 if ($dbo->getNumRows() > 0) {
8364 $prev_receipt = $dbo->loadAssoc();
8365 }
8366 //update value (receipt generated)
8367 if ($newnum !== false && $newnum > 0) {
8368 $s = (int)$newnum;
8369 if (!(count($prev_receipt) > 0)) {
8370 $q = "UPDATE `#__vikbooking_config` SET `setting`=".$s." WHERE `param`='receiptinum';";
8371 $dbo->setQuery($q);
8372 $dbo->execute();
8373 //insert the new receipt record
8374 $q = "INSERT INTO `#__vikbooking_receipts` (`number`,`idorder`,`created_on`) VALUES (".(int)$newnum.", ".(int)$bid.", ".time().");";
8375 $dbo->setQuery($q);
8376 $dbo->execute();
8377 } else {
8378 //update receipt record
8379 $q = "UPDATE `#__vikbooking_receipts` SET `number`=".(int)$newnum.",`created_on`=".time()." WHERE `idorder`=".(int)$bid.";";
8380 $dbo->setQuery($q);
8381 $dbo->execute();
8382 }
8383 }
8384 //
8385 return count($prev_receipt) > 0 ? (int)$prev_receipt['number'] : ((int)$s + 1);
8386 }
8387 //first execution of the method should create the configuration record
8388 $q = "INSERT INTO `#__vikbooking_config` (`param`,`setting`) VALUES ('receiptinum', '0');";
8389 $dbo->setQuery($q);
8390 $dbo->execute();
8391 return 1;
8392 }
8393
8394 public static function getReceiptNotes ($newnotes = false) {
8395 $dbo = JFactory::getDbo();
8396 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='receiptnotes';";
8397 $dbo->setQuery($q);
8398 $dbo->execute();
8399 if ($dbo->getNumRows() > 0) {
8400 $s = $dbo->loadResult();
8401 //update value
8402 if ($newnotes !== false) {
8403 $s = $newnotes;
8404 $q = "UPDATE `#__vikbooking_config` SET `setting`=".$dbo->quote($s)." WHERE `param`='receiptnotes';";
8405 $dbo->setQuery($q);
8406 $dbo->execute();
8407 }
8408 //
8409 return $s;
8410 }
8411 $q = "INSERT INTO `#__vikbooking_config` (`param`,`setting`) VALUES ('receiptnotes', '');";
8412 $dbo->setQuery($q);
8413 $dbo->execute();
8414 return "";
8415 }
8416
8417 public static function loadColorTagsRules()
8418 {
8419 $color_tag_rules = [
8420 0 => 'VBOCOLORTAGRULECUSTOMCOLOR',
8421 'pend1' => 'VBWAITINGFORPAYMENT',
8422 'conf1' => 'VBDBTEXTROOMCLOSED',
8423 'conf2' => 'VBOCOLORTAGRULECONFTWO',
8424 'conf3' => 'VBOCOLORTAGRULECONFTHREE',
8425 'inv1' => 'VBOCOLORTAGRULEINVONE',
8426 'rcp1' => 'VBOCOLORTAGRULERCPONE',
8427 'conf4' => 'VBOCOLORTAGRULECONFFOUR',
8428 'conf5' => 'VBOCOLORTAGRULECONFFIVE',
8429 'inv2' => 'VBOCOLORTAGRULEINVTWO',
8430 ];
8431
8432 /**
8433 * Trigger event to let third-party plugins override the default booking color tag rules.
8434 *
8435 * @since 1.16.4 (J) - 1.6.4 (WP)
8436 */
8437 VBOFactory::getPlatform()->getDispatcher()->trigger('onBeforeLoadColorTagRules', [&$color_tag_rules]);
8438
8439 return $color_tag_rules;
8440 }
8441
8442 public static function loadDefaultColorTags()
8443 {
8444 $default_color_tags = [
8445 [
8446 'color' => '#9b9b9b',
8447 'name' => 'VBWAITINGFORPAYMENT',
8448 'rule' => 'pend1'
8449 ],
8450 [
8451 'color' => '#333333',
8452 'name' => 'VBDBTEXTROOMCLOSED',
8453 'rule' => 'conf1'
8454 ],
8455 [
8456 'color' => '#ff8606',
8457 'name' => 'VBOCOLORTAGRULECONFTWO',
8458 'rule' => 'conf2'
8459 ],
8460 [
8461 'color' => '#0418c9',
8462 'name' => 'VBOCOLORTAGRULECONFTHREE',
8463 'rule' => 'conf3'
8464 ],
8465 [
8466 'color' => '#bed953',
8467 'name' => 'VBOCOLORTAGRULEINVONE',
8468 'rule' => 'inv1'
8469 ],
8470 [
8471 'color' => '#67f5b5',
8472 'name' => 'VBOCOLORTAGRULERCPONE',
8473 'rule' => 'rcp1'
8474 ],
8475 [
8476 'color' => '#04d2c2',
8477 'name' => 'VBOCOLORTAGRULECONFFOUR',
8478 'rule' => 'conf4'
8479 ],
8480 [
8481 'color' => '#00b316',
8482 'name' => 'VBOCOLORTAGRULECONFFIVE',
8483 'rule' => 'conf5'
8484 ],
8485 [
8486 'color' => '#00f323',
8487 'name' => 'VBOCOLORTAGRULEINVTWO',
8488 'rule' => 'inv2'
8489 ],
8490 ];
8491
8492 /**
8493 * Trigger event to let third-party plugins override the default color tags.
8494 *
8495 * @since 1.16.4 (J) - 1.6.4 (WP)
8496 */
8497 VBOFactory::getPlatform()->getDispatcher()->trigger('onBeforeLoadDefaultColorTags', [&$default_color_tags]);
8498
8499 return $default_color_tags;
8500 }
8501
8502 public static function loadBookingsColorTags()
8503 {
8504 $dbo = JFactory::getDbo();
8505
8506 $bookings_ctags = VBOFactory::getConfig()->getArray('bookingsctags', []);
8507
8508 return is_array($bookings_ctags) && $bookings_ctags ? $bookings_ctags : self::loadDefaultColorTags();
8509 }
8510
8511 public static function getBestColorContrast($hexcolor)
8512 {
8513 $hexcolor = str_replace('#', '', $hexcolor);
8514
8515 if (empty($hexcolor) || strlen($hexcolor) != 6) {
8516 return '#000000';
8517 }
8518
8519 $r = hexdec(substr($hexcolor, 0, 2));
8520 $g = hexdec(substr($hexcolor, 2, 2));
8521 $b = hexdec(substr($hexcolor, 4, 2));
8522
8523 // Counting the perceptive luminance - human eye favors green color
8524 // < 0.5 bright colors
8525 // > 0.5 dark colors
8526
8527 return (1 - ( 0.299 * $r + 0.587 * $g + 0.114 * $b ) / 255) < 0.5 ? '#000000' : '#ffffff';
8528 }
8529
8530 public static function applyBookingColorTag($booking, $tags = [])
8531 {
8532 if (!is_array($tags) || !$tags) {
8533 $tags = self::loadBookingsColorTags();
8534 }
8535
8536 if (!empty($booking['colortag'])) {
8537 $color_tag_arr = json_decode($booking['colortag'], true);
8538 if (is_array($color_tag_arr) && array_key_exists('color', $color_tag_arr)) {
8539 $color_tag_arr['fontcolor'] = self::getBestColorContrast($color_tag_arr['color']);
8540 return $color_tag_arr;
8541 }
8542 }
8543
8544 $dbo = JFactory::getDbo();
8545
8546 $bid = array_key_exists('idorder', $booking) ? $booking['idorder'] : $booking['id'];
8547 $docs_data = [];
8548 $invoice_numb = false;
8549 $receipt_numb = false;
8550
8551 if ($booking['status'] == 'confirmed') {
8552 $q = "SELECT `b`.`id` AS `o_id`, `i`.`id` AS `inv_id`, `i`.`number` AS `inv_number`, `i`.`file_name` AS `inv_file_name`, `r`.`id` AS `rcp_id`, `r`.`number` AS `rcp_number`
8553 FROM `#__vikbooking_orders` AS `b`
8554 LEFT JOIN `#__vikbooking_invoices` AS `i` ON `b`.`id`=`i`.`idorder`
8555 LEFT JOIN `#__vikbooking_receipts` AS `r` ON `b`.`id`=`r`.`idorder`
8556 WHERE `b`.`id`=" . (int)$bid . ";";
8557 $dbo->setQuery($q);
8558 $docs_data = $dbo->loadAssoc();
8559 if ($docs_data) {
8560 $invoice_numb = (!empty($docs_data['inv_id']));
8561 $receipt_numb = (!empty($docs_data['rcp_id']));
8562
8563 /**
8564 * We inject the invoice number or receipt number into the color tag rules.
8565 * This is useful for the Tableaux page to show the invoice/receipt number.
8566 *
8567 * @since 1.12 (J) - 1.1.7 (WP)
8568 */
8569 if ($invoice_numb || $receipt_numb) {
8570 foreach ($tags as &$tval) {
8571 if ($invoice_numb) {
8572 $tval['invoice_number'] = $docs_data['inv_number'];
8573 $tval['invoice_file_name'] = $docs_data['inv_file_name'];
8574 }
8575 if ($receipt_numb) {
8576 $tval['receipt_number'] = $docs_data['rcp_number'];
8577 }
8578 }
8579 // unset last reference
8580 unset($tval);
8581 }
8582 }
8583 }
8584
8585 /**
8586 * Some OTA bookings using an OTA-Collect business model may look as not entirely
8587 * paid when they actually are, due to commissions and pass through taxes. We also
8588 * take care of those OTA reservations that include a Virtual Credit Card (Hotel Collect).
8589 *
8590 * @since 1.16.3 (J) - 1.6.3 (WP)
8591 */
8592 $ota_collected_booking = false;
8593 $hotel_collect_vcc_unpaid = false;
8594 if ($booking['status'] == 'confirmed' && !empty($booking['idorderota']) && !empty($booking['channel']) && $booking['total'] > 0 && $booking['totpaid'] > 0) {
8595 if (stripos($booking['channel'], 'airbnb') !== false && ($booking['checkout'] < time() || date('Y-m-d', $booking['checkout']) == date('Y-m-d'))) {
8596 $ota_collected_booking = true;
8597 }
8598 if (!empty($booking['paymentlog']) && stripos($booking['paymentlog'], 'virtual credit card') !== false) {
8599 // VCC detected, check the history for a payment event
8600 $history_obj = self::getBookingHistoryInstance()->setBid($bid);
8601 // list of history types to fetch
8602 $h_payment_types = [];
8603 $h_payment_group = $history_obj->getTypeGroups($group = 'GPM');
8604 if ($h_payment_group && $h_payment_group['types']) {
8605 // merge default types with 'PU' for the manually set "new amount paid" and with 'RU' (refunded amount updated)
8606 $h_payment_types = array_unique(array_merge($h_payment_group['types'], ['PU', 'RU']));
8607 }
8608 $payment_ev_datas = $history_obj->getEventsWithData($h_payment_types, $callvalid = null, $onlydata = false);
8609 if (!$payment_ev_datas) {
8610 // this booking should be flagged as "unpaid" because a payment (manual/online) has never been recorded
8611 $hotel_collect_vcc_unpaid = true;
8612 }
8613 }
8614 }
8615
8616 foreach ($tags as $tkey => $tval) {
8617 if (empty($tval['rule'])) {
8618 continue;
8619 }
8620
8621 /**
8622 * Trigger event to let third-party plugins validate the custom rule.
8623 *
8624 * @since 1.16.4 (J) - 1.6.4 (WP)
8625 */
8626 $custom_rule = VBOFactory::getPlatform()->getDispatcher()->filter('onApplyBookingColorTag', [$ota_collected_booking, $hotel_collect_vcc_unpaid, $booking, $docs_data, $tval]);
8627 if (!empty($custom_rule)) {
8628 return (array) $custom_rule[0];
8629 }
8630
8631 switch ($tval['rule']) {
8632 case 'pend1':
8633 // Room is waiting for the payment (locked record)
8634 if ($booking['status'] == 'standby') {
8635 $q = "SELECT `id` FROM `#__vikbooking_tmplock` WHERE `idorder`=" . (int)$bid . " AND `until`>=" . time() . ";";
8636 $dbo->setQuery($q);
8637 $tmp_lock = $dbo->loadAssoc();
8638 if ($tmp_lock) {
8639 $tval['fontcolor'] = self::getBestColorContrast($tval['color']);
8640 return $tval;
8641 }
8642 }
8643 break;
8644 case 'conf1':
8645 // Confirmed (Room Closed)
8646 if ($booking['status'] == 'confirmed' && $booking['custdata'] == JText::translate('VBDBTEXTROOMCLOSED')) {
8647 $tval['fontcolor'] = self::getBestColorContrast($tval['color']);
8648 return $tval;
8649 }
8650 break;
8651 case 'conf2':
8652 // Confirmed (No Rate 0.00/NULL Total)
8653 if ($booking['status'] == 'confirmed' && (empty($booking['total']) || $booking['total'] <= 0.00 || $booking['total'] === null)) {
8654 $tval['fontcolor'] = self::getBestColorContrast($tval['color']);
8655 return $tval;
8656 }
8657 break;
8658 case 'conf3':
8659 // Confirmed (Total > 0 && Total Paid = 0 && No Invoice && No Receipt)
8660 if ($booking['status'] == 'confirmed' && $booking['total'] > 0 && (empty($booking['totpaid']) || $booking['totpaid'] <= 0.00 || $booking['totpaid'] === null || $hotel_collect_vcc_unpaid) && $invoice_numb === false && $receipt_numb === false) {
8661 $tval['fontcolor'] = self::getBestColorContrast($tval['color']);
8662 return $tval;
8663 }
8664 break;
8665 case 'inv1':
8666 // Confirmed + Invoice (Total > 0 && Total Paid = 0 && Invoice Exists)
8667 if ($booking['status'] == 'confirmed' && $booking['total'] > 0 && (empty($booking['totpaid']) || $booking['totpaid'] <= 0.00 || $booking['totpaid'] === null) && $invoice_numb !== false) {
8668 $tval['fontcolor'] = self::getBestColorContrast($tval['color']);
8669 return $tval;
8670 }
8671 break;
8672 case 'rcp1':
8673 // Confirmed + Receipt Issued (Total > 0 && Total Paid = 0 && Receipt Issued)
8674 if ($booking['status'] == 'confirmed' && $booking['total'] > 0 && (empty($booking['totpaid']) || $booking['totpaid'] <= 0.00 || $booking['totpaid'] === null) && $receipt_numb !== false) {
8675 $tval['fontcolor'] = self::getBestColorContrast($tval['color']);
8676 return $tval;
8677 }
8678 break;
8679 case 'conf4':
8680 // Confirmed (Total > 0 && Total Paid > 0 && Total Paid < Total)
8681 if ($booking['status'] == 'confirmed' && $booking['total'] > 0 && $booking['totpaid'] > 0 && $booking['totpaid'] < $booking['total'] && !$ota_collected_booking) {
8682 $tval['fontcolor'] = self::getBestColorContrast($tval['color']);
8683 return $tval;
8684 }
8685 break;
8686 case 'conf5':
8687 // Confirmed (Total > 0 && Total Paid >= Total)
8688 if ($booking['status'] == 'confirmed' && $booking['total'] > 0 && $booking['totpaid'] > 0 && ($booking['totpaid'] >= $booking['total'] || $ota_collected_booking) && !$hotel_collect_vcc_unpaid) {
8689 $tval['fontcolor'] = self::getBestColorContrast($tval['color']);
8690 return $tval;
8691 }
8692 break;
8693 case 'inv2':
8694 // Confirmed + Invoice + Paid (Total > 0 && Total Paid >= Total && Invoice Exists)
8695 if ($booking['status'] == 'confirmed' && $booking['total'] > 0 && $booking['totpaid'] > 0 && ($booking['totpaid'] >= $booking['total'] || $ota_collected_booking) && $invoice_numb !== false) {
8696 $tval['fontcolor'] = self::getBestColorContrast($tval['color']);
8697 return $tval;
8698 }
8699 break;
8700 default:
8701 // unsupported rule
8702 break;
8703 }
8704 }
8705
8706 return [];
8707 }
8708
8709 public static function getBookingInfoFromID($bid)
8710 {
8711 $dbo = JFactory::getDbo();
8712
8713 $q = "SELECT * FROM `#__vikbooking_orders` WHERE `id`=" . (int)$bid . ";";
8714 $dbo->setQuery($q);
8715 $booking_info = $dbo->loadAssoc();
8716 if ($booking_info) {
8717 return $booking_info;
8718 }
8719
8720 return [];
8721 }
8722
8723 /**
8724 * Load booking details for a given list of IDs.
8725 *
8726 * @param int $roomid the room integer number.
8727 * @param array $room_bids_pool list of booking IDs involved.
8728 *
8729 * @return array
8730 */
8731 public static function loadRoomIndexesBookings($roomid, array $room_bids_pool)
8732 {
8733 $dbo = JFactory::getDbo();
8734
8735 if (empty($roomid) || !$room_bids_pool) {
8736 return [];
8737 }
8738
8739 $room_features_bookings = [];
8740 $roomid = (int)$roomid;
8741
8742 $all_bids = [];
8743 foreach ($room_bids_pool as $day => $bids) {
8744 $all_bids = array_merge($all_bids, $bids);
8745 }
8746 $all_bids = array_map('intval', array_unique($all_bids));
8747 $all_bids_str = implode(', ', $all_bids);
8748
8749 $q = "SELECT `or`.`id`,`or`.`idorder`,`or`.`roomindex` FROM `#__vikbooking_ordersrooms` AS `or` WHERE `or`.`idroom`={$roomid} AND `or`.`idorder` IN ({$all_bids_str})";
8750 $dbo->setQuery($q);
8751 $rbookings = $dbo->loadAssocList();
8752 if (!$rbookings) {
8753 return [];
8754 }
8755
8756 foreach ($rbookings as $k => $v) {
8757 if (empty($v['roomindex'])) {
8758 continue;
8759 }
8760 if (!isset($room_features_bookings[$v['roomindex']])) {
8761 $room_features_bookings[$v['roomindex']] = [];
8762 }
8763 $room_features_bookings[$v['roomindex']][] = $v['idorder'];
8764 }
8765
8766 return $room_features_bookings;
8767 }
8768
8769 public static function getSendEmailWhen() {
8770 $dbo = JFactory::getDbo();
8771 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='emailsendwhen';";
8772 $dbo->setQuery($q);
8773 $dbo->execute();
8774 if ($dbo->getNumRows() > 0) {
8775 $cval = $dbo->loadAssocList();
8776 return intval($cval[0]['setting']) > 1 ? 2 : 1;
8777 } else {
8778 $q = "INSERT INTO `#__vikbooking_config` (`param`,`setting`) VALUES ('emailsendwhen','1');";
8779 $dbo->setQuery($q);
8780 $dbo->execute();
8781 }
8782 return 1;
8783 }
8784
8785 public static function getMinutesAutoRemove() {
8786 $dbo = JFactory::getDbo();
8787 $minar = 0;
8788 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='minautoremove';";
8789 $dbo->setQuery($q);
8790 $dbo->execute();
8791 if ($dbo->getNumRows() > 0) {
8792 $minar = (int)$dbo->loadResult();
8793 }
8794 return $minar;
8795 }
8796
8797 public static function getSMSAPIClass() {
8798 $dbo = JFactory::getDbo();
8799 $cfile = '';
8800 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='smsapi';";
8801 $dbo->setQuery($q);
8802 $dbo->execute();
8803 if ($dbo->getNumRows() > 0) {
8804 $cval = $dbo->loadAssocList();
8805 $cfile = $cval[0]['setting'];
8806 }
8807 return $cfile;
8808 }
8809
8810 public static function autoSendSMSEnabled() {
8811 $dbo = JFactory::getDbo();
8812 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='smsautosend';";
8813 $dbo->setQuery($q);
8814 $dbo->execute();
8815 if ($dbo->getNumRows() > 0) {
8816 $cval = $dbo->loadAssocList();
8817 return intval($cval[0]['setting']) > 0 ? true : false;
8818 }
8819 return false;
8820 }
8821
8822 public static function getSendSMSTo() {
8823 $dbo = JFactory::getDbo();
8824 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='smssendto';";
8825 $dbo->setQuery($q);
8826 $dbo->execute();
8827 if ($dbo->getNumRows() > 0) {
8828 $cval = $dbo->loadAssocList();
8829 if (!empty($cval[0]['setting'])) {
8830 $sto = json_decode($cval[0]['setting'], true);
8831 if (is_array($sto)) {
8832 return $sto;
8833 }
8834 }
8835 }
8836 return array();
8837 }
8838
8839 public static function getSendSMSWhen() {
8840 $dbo = JFactory::getDbo();
8841 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='smssendwhen';";
8842 $dbo->setQuery($q);
8843 $dbo->execute();
8844 if ($dbo->getNumRows() > 0) {
8845 $cval = $dbo->loadAssocList();
8846 return intval($cval[0]['setting']) > 1 ? 2 : 1;
8847 }
8848 return 1;
8849 }
8850
8851 public static function getSMSAdminPhone() {
8852 $dbo = JFactory::getDbo();
8853 $pnum = '';
8854 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='smsadminphone';";
8855 $dbo->setQuery($q);
8856 $dbo->execute();
8857 if ($dbo->getNumRows() > 0) {
8858 $cval = $dbo->loadAssocList();
8859 $pnum = $cval[0]['setting'];
8860 }
8861 return $pnum;
8862 }
8863
8864 public static function getSMSParams($as_array = true) {
8865 $dbo = JFactory::getDbo();
8866 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='smsparams';";
8867 $dbo->setQuery($q);
8868 $dbo->execute();
8869 if ($dbo->getNumRows() > 0) {
8870 $cval = $dbo->loadAssocList();
8871 if (!empty($cval[0]['setting'])) {
8872 if (!$as_array) {
8873 return $cval[0]['setting'];
8874 }
8875 $sparams = json_decode($cval[0]['setting'], true);
8876 if (is_array($sparams)) {
8877 return $sparams;
8878 }
8879 }
8880 }
8881 return array();
8882 }
8883
8884 public static function getSMSTemplate($vbo_tn = null, $booking_status = 'confirmed', $type = 'admin') {
8885 $dbo = JFactory::getDbo();
8886 switch (strtolower($booking_status)) {
8887 case 'standby':
8888 $status = 'pend';
8889 break;
8890 case 'cancelled':
8891 $status = 'canc';
8892 break;
8893 default:
8894 $status = '';
8895 break;
8896 }
8897 $paramtype = 'sms'.$type.'tpl'.$status;
8898 $q = "SELECT `id`,`setting` FROM `#__vikbooking_texts` WHERE `param`='".$paramtype."';";
8899 $dbo->setQuery($q);
8900 $dbo->execute();
8901 if ($dbo->getNumRows() < 1) {
8902 if ($status == 'canc') {
8903 //Type cancelled is used by VCM since v1.6.6
8904 $q = "INSERT INTO `#__vikbooking_texts` (`param`,`exp`,`setting`) VALUES ('".$paramtype."','".($type == 'admin' ? 'Administrator' : 'Customer')." SMS Template (Cancelled)','');";
8905 $dbo->setQuery($q);
8906 $dbo->execute();
8907 }
8908 return '';
8909 }
8910 $ft = $dbo->loadAssocList();
8911 if (is_object($vbo_tn)) {
8912 $vbo_tn->translateContents($ft, '#__vikbooking_texts');
8913 }
8914 return $ft[0]['setting'];
8915 }
8916
8917 public static function getSMSAdminTemplate($vbo_tn = null, $booking_status = 'confirmed') {
8918 return self::getSMSTemplate($vbo_tn, $booking_status, 'admin');
8919 }
8920
8921 public static function getSMSCustomerTemplate($vbo_tn = null, $booking_status = 'confirmed') {
8922 return self::getSMSTemplate($vbo_tn, $booking_status, 'customer');
8923 }
8924
8925 public static function checkPhonePrefixCountry($phone, $country_threecode) {
8926 $dbo = JFactory::getDbo();
8927 $phone = str_replace(" ", '', trim($phone));
8928 $cprefix = '';
8929 if (!empty($country_threecode)) {
8930 $q = "SELECT `phone_prefix` FROM `#__vikbooking_countries` WHERE `country_3_code`=".$dbo->quote($country_threecode).";";
8931 $dbo->setQuery($q);
8932 $dbo->execute();
8933 if ($dbo->getNumRows() > 0) {
8934 $cprefix = $dbo->loadResult();
8935 $cprefix = str_replace(" ", '', trim($cprefix));
8936 }
8937 }
8938 if (!empty($cprefix)) {
8939 if (substr($phone, 0, 1) != '+') {
8940 if (substr($phone, 0, 2) == '00') {
8941 $phone = '+'.substr($phone, 2);
8942 } else {
8943 $phone = $cprefix.$phone;
8944 }
8945 }
8946 }
8947 return $phone;
8948 }
8949
8950 public static function parseAdminSMSTemplate($booking, $booking_rooms, $vbo_tn = null) {
8951 $tpl = self::getSMSAdminTemplate($vbo_tn, $booking['status']);
8952
8953 /**
8954 * Parse all conditional text rules.
8955 *
8956 * @since 1.14 (J) - 1.4.0 (WP)
8957 */
8958 self::getConditionalRulesInstance()
8959 ->set(array('booking', 'rooms'), array($booking, $booking_rooms))
8960 ->parseTokens($tpl);
8961 //
8962
8963 $vbo_df = self::getDateFormat();
8964 $df = $vbo_df == "%d/%m/%Y" ? 'd/m/Y' : ($vbo_df == "%m/%d/%Y" ? 'm/d/Y' : 'Y-m-d');
8965 $datesep = self::getDateSeparator();
8966
8967 $tpl = str_replace('{customer_name}', $booking['customer_name'], $tpl);
8968 $tpl = str_replace('{booking_id}', $booking['id'], $tpl);
8969 $tpl = str_replace('{checkin_date}', date(str_replace("/", $datesep, $df), $booking['checkin']), $tpl);
8970 $tpl = str_replace('{checkout_date}', date(str_replace("/", $datesep, $df), $booking['checkout']), $tpl);
8971 $tpl = str_replace('{num_nights}', $booking['days'], $tpl);
8972 if (!empty($booking['confirmnumber'])) {
8973 $tpl = str_replace("{confirmnumb}", $booking['confirmnumber'], $tpl);
8974 } else {
8975 $tpl = str_replace("{confirmnumb}", '', $tpl);
8976 }
8977 if (!empty($booking['idorderota'])) {
8978 $tpl = str_replace("{ota_booking_id}", $booking['idorderota'], $tpl);
8979 } else {
8980 $tpl = str_replace("{ota_booking_id}", '', $tpl);
8981 }
8982
8983 $rooms_booked = array();
8984 $rooms_names = array();
8985 $tot_adults = 0;
8986 $tot_children = 0;
8987 $tot_guests = 0;
8988 foreach ($booking_rooms as $broom) {
8989 $rooms_names[] = $broom['room_name'];
8990 if (array_key_exists($broom['room_name'], $rooms_booked)) {
8991 $rooms_booked[$broom['room_name']] += 1;
8992 } else {
8993 $rooms_booked[$broom['room_name']] = 1;
8994 }
8995 $tot_adults += (int)$broom['adults'];
8996 $tot_children += (int)$broom['children'];
8997 $tot_guests += ((int)$broom['adults'] + (int)$broom['children']);
8998 }
8999 $tpl = str_replace('{tot_adults}', $tot_adults, $tpl);
9000 $tpl = str_replace('{tot_children}', $tot_children, $tpl);
9001 $tpl = str_replace('{tot_guests}', $tot_guests, $tpl);
9002 $rooms_booked_quant = array();
9003 foreach ($rooms_booked as $rname => $quant) {
9004 $rooms_booked_quant[] = ($quant > 1 ? $quant.' ' : '').$rname;
9005 }
9006 $tpl = str_replace('{rooms_booked}', implode(', ', $rooms_booked_quant), $tpl);
9007 $tpl = str_replace('{rooms_names}', implode(', ', $rooms_names), $tpl);
9008 $tpl = str_replace('{customer_country}', $booking['country_name'], $tpl);
9009 $tpl = str_replace('{customer_email}', $booking['custmail'], $tpl);
9010 $tpl = str_replace('{customer_phone}', $booking['phone'], $tpl);
9011 $tpl = str_replace('{total}', self::numberFormat($booking['total']), $tpl);
9012 $tpl = str_replace('{total_paid}', self::numberFormat($booking['totpaid']), $tpl);
9013 $remaining_bal = $booking['total'] - $booking['totpaid'];
9014 $tpl = str_replace('{remaining_balance}', self::numberFormat($remaining_bal), $tpl);
9015
9016 return $tpl;
9017 }
9018
9019 public static function parseCustomerSMSTemplate($booking, $booking_rooms, $vbo_tn = null, $force_text = null) {
9020 $tpl = !empty($force_text) ? $force_text : self::getSMSCustomerTemplate($vbo_tn, $booking['status']);
9021
9022 /**
9023 * Parse all conditional text rules.
9024 *
9025 * @since 1.14 (J) - 1.4.0 (WP)
9026 */
9027 self::getConditionalRulesInstance()
9028 ->set(array('booking', 'rooms'), array($booking, $booking_rooms))
9029 ->parseTokens($tpl);
9030 //
9031
9032 $vbo_df = self::getDateFormat();
9033 $df = $vbo_df == "%d/%m/%Y" ? 'd/m/Y' : ($vbo_df == "%m/%d/%Y" ? 'm/d/Y' : 'Y-m-d');
9034 $datesep = self::getDateSeparator();
9035
9036 $tpl = str_replace('{customer_name}', $booking['customer_name'], $tpl);
9037 $tpl = str_replace('{booking_id}', $booking['id'], $tpl);
9038 $tpl = str_replace('{checkin_date}', date(str_replace("/", $datesep, $df), $booking['checkin']), $tpl);
9039 $tpl = str_replace('{checkout_date}', date(str_replace("/", $datesep, $df), $booking['checkout']), $tpl);
9040 $tpl = str_replace('{num_nights}', $booking['days'], $tpl);
9041 if (!empty($booking['confirmnumber'])) {
9042 $tpl = str_replace("{confirmnumb}", $booking['confirmnumber'], $tpl);
9043 } else {
9044 $tpl = str_replace("{confirmnumb}", '', $tpl);
9045 }
9046 if (!empty($booking['idorderota'])) {
9047 $tpl = str_replace("{ota_booking_id}", $booking['idorderota'], $tpl);
9048 } else {
9049 $tpl = str_replace("{ota_booking_id}", '', $tpl);
9050 }
9051
9052 $rooms_booked = array();
9053 $rooms_names = array();
9054 $tot_adults = 0;
9055 $tot_children = 0;
9056 $tot_guests = 0;
9057 foreach ($booking_rooms as $broom) {
9058 $rooms_names[] = $broom['room_name'];
9059 if (array_key_exists($broom['room_name'], $rooms_booked)) {
9060 $rooms_booked[$broom['room_name']] += 1;
9061 } else {
9062 $rooms_booked[$broom['room_name']] = 1;
9063 }
9064 $tot_adults += (int)$broom['adults'];
9065 $tot_children += (int)$broom['children'];
9066 $tot_guests += ((int)$broom['adults'] + (int)$broom['children']);
9067 }
9068 $tpl = str_replace('{tot_adults}', $tot_adults, $tpl);
9069 $tpl = str_replace('{tot_children}', $tot_children, $tpl);
9070 $tpl = str_replace('{tot_guests}', $tot_guests, $tpl);
9071 $rooms_booked_quant = array();
9072 foreach ($rooms_booked as $rname => $quant) {
9073 $rooms_booked_quant[] = ($quant > 1 ? $quant.' ' : '').$rname;
9074 }
9075 $tpl = str_replace('{rooms_booked}', implode(', ', $rooms_booked_quant), $tpl);
9076 $tpl = str_replace('{rooms_names}', implode(', ', $rooms_names), $tpl);
9077 $tpl = str_replace('{total}', self::numberFormat($booking['total']), $tpl);
9078 $tpl = str_replace('{total_paid}', self::numberFormat($booking['totpaid']), $tpl);
9079 $remaining_bal = $booking['total'] - $booking['totpaid'];
9080 $tpl = str_replace('{remaining_balance}', self::numberFormat($remaining_bal), $tpl);
9081 $tpl = str_replace('{customer_pin}', $booking['customer_pin'], $tpl);
9082
9083 $use_sid = empty($booking['sid']) && !empty($booking['idorderota']) ? $booking['idorderota'] : $booking['sid'];
9084 $book_link = JUri::root() . 'index.php?option=com_vikbooking&view=booking&sid=' . $use_sid . '&ts=' . $booking['ts'];
9085
9086 if (VBOPlatformDetection::isWordPress()) {
9087 /**
9088 * @wponly Rewrite URI for front-end
9089 */
9090 $book_link = str_replace(JUri::root(), '', $book_link);
9091 $model = JModel::getInstance('vikbooking', 'shortcodes', 'admin');
9092 $itemid = $model->best(['booking'], (!empty($booking['lang']) ? $booking['lang'] : null));
9093 if ($itemid) {
9094 $book_link = JRoute::rewrite($book_link . "&Itemid={$itemid}", false);
9095 }
9096 } else {
9097 /**
9098 * @joomlaonly Rewrite URI for front-end
9099 */
9100 $bestitemid = self::findProperItemIdType(['booking'], (!empty($booking['lang']) ? $booking['lang'] : null));
9101 $uri_parts = [
9102 'option' => 'com_vikbooking',
9103 'view' => 'booking',
9104 'sid' => $use_sid,
9105 'ts' => $booking['ts'],
9106 ];
9107 if (!empty($bestitemid) && !empty($booking['lang']) && JFactory::getApplication()->isClient('administrator')) {
9108 // inject lang in query string for proper link routing
9109 $uri_parts['lang'] = $booking['lang'];
9110 }
9111 $book_link = self::externalroute('index.php?' . http_build_query($uri_parts), false, (!empty($bestitemid) ? $bestitemid : null));
9112 }
9113
9114 $tpl = str_replace('{booking_link}', $book_link, $tpl);
9115 // we use the alias tag {booking_url} to support just the URI unlike {order_link} that includes HTML
9116 $tpl = str_replace('{booking_url}', $book_link, $tpl);
9117
9118 return $tpl;
9119 }
9120
9121 public static function sendBookingSMS($oid, $skip_send_to = array(), $force_send_to = array(), $force_text = null) {
9122 $dbo = JFactory::getDbo();
9123 if (!class_exists('VboApplication')) {
9124 require_once(VBO_ADMIN_PATH.DIRECTORY_SEPARATOR.'helpers'.DIRECTORY_SEPARATOR.'jv_helper.php');
9125 }
9126 $vbo_app = new VboApplication;
9127 if (empty($oid)) {
9128 return false;
9129 }
9130 $sms_api = self::getSMSAPIClass();
9131 if (empty($sms_api)) {
9132 return false;
9133 }
9134 if (!is_file(VBO_ADMIN_PATH.DIRECTORY_SEPARATOR.'smsapi'.DIRECTORY_SEPARATOR.$sms_api)) {
9135 return false;
9136 }
9137 $sms_api_params = self::getSMSParams();
9138 if (!is_array($sms_api_params) || !(count($sms_api_params) > 0)) {
9139 return false;
9140 }
9141 if (!self::autoSendSMSEnabled() && !(count($force_send_to) > 0)) {
9142 return false;
9143 }
9144 $send_sms_to = self::getSendSMSTo();
9145 if (!(count($send_sms_to) > 0) && !(count($force_send_to) > 0)) {
9146 return false;
9147 }
9148 $booking = array();
9149 $q = "SELECT `o`.*,`co`.`idcustomer`,CONCAT_WS(' ',`c`.`first_name`,`c`.`last_name`) AS `customer_name`,`c`.`pin` AS `customer_pin`,`nat`.`country_name` FROM `#__vikbooking_orders` AS `o` LEFT JOIN `#__vikbooking_customers_orders` `co` ON `co`.`idorder`=`o`.`id` AND `co`.`idorder`=".(int)$oid." LEFT JOIN `#__vikbooking_customers` `c` ON `c`.`id`=`co`.`idcustomer` LEFT JOIN `#__vikbooking_countries` `nat` ON `nat`.`country_3_code`=`o`.`country` WHERE `o`.`id`=".(int)$oid.";";
9150 $dbo->setQuery($q);
9151 $dbo->execute();
9152 if ($dbo->getNumRows() > 0) {
9153 $booking = $dbo->loadAssoc();
9154 }
9155 if (!(count($booking) > 0)) {
9156 return false;
9157 }
9158 if (strtolower($booking['status']) == 'standby' && self::getSendSMSWhen() < 2) {
9159 return false;
9160 }
9161 $booking_rooms = array();
9162 $q = "SELECT `or`.*,`r`.`name` AS `room_name` FROM `#__vikbooking_ordersrooms` AS `or` LEFT JOIN `#__vikbooking_rooms` `r` ON `r`.`id`=`or`.`idroom` WHERE `or`.`idorder`=".(int)$booking['id'].";";
9163 $dbo->setQuery($q);
9164 $dbo->execute();
9165 if ($dbo->getNumRows() > 0) {
9166 $booking_rooms = $dbo->loadAssocList();
9167 }
9168 $admin_phone = self::getSMSAdminPhone();
9169 $admin_sendermail = self::getSenderMail();
9170 $admin_email = self::getAdminMail();
9171 $f_result = false;
9172 require_once(VBO_ADMIN_PATH.DIRECTORY_SEPARATOR.'smsapi'.DIRECTORY_SEPARATOR.$sms_api);
9173 if ((in_array('admin', $send_sms_to) && !empty($admin_phone) && !in_array('admin', $skip_send_to)) || in_array('admin', $force_send_to)) {
9174 //SMS for the administrator
9175 $sms_text = self::parseAdminSMSTemplate($booking, $booking_rooms);
9176 if (!empty($sms_text)) {
9177 $sms_obj = new VikSmsApi($booking, $sms_api_params);
9178 //administrator phone can contain multiple numbers separated by comma or semicolon
9179 $admin_phones = array();
9180 if (strpos($admin_phone, ',') !== false) {
9181 $all_phones = explode(',', $admin_phone);
9182 foreach ($all_phones as $aph) {
9183 if (!empty($aph)) {
9184 $admin_phones[] = trim($aph);
9185 }
9186 }
9187 } elseif (strpos($admin_phone, ';') !== false) {
9188 $all_phones = explode(';', $admin_phone);
9189 foreach ($all_phones as $aph) {
9190 if (!empty($aph)) {
9191 $admin_phones[] = trim($aph);
9192 }
9193 }
9194 } else {
9195 $admin_phones[] = $admin_phone;
9196 }
9197 foreach ($admin_phones as $admphone) {
9198 $response_obj = $sms_obj->sendMessage($admphone, $sms_text);
9199 if ( !$sms_obj->validateResponse($response_obj) ) {
9200 //notify the administrator via email with the error of the SMS sending
9201 $vbo_app->sendMail($admin_sendermail, $admin_sendermail, $admin_email, $admin_sendermail, JText::translate('VBOSENDSMSERRMAILSUBJ'), JText::translate('VBOSENDADMINSMSERRMAILTXT')."<br />".$sms_obj->getLog(), true);
9202 } else {
9203 $f_result = true;
9204 }
9205 }
9206 }
9207 }
9208 if ((in_array('customer', $send_sms_to) && !empty($booking['phone']) && !in_array('customer', $skip_send_to)) || in_array('customer', $force_send_to)) {
9209 //SMS for the Customer
9210 $vbo_tn = self::getTranslator();
9211 $vbo_tn->translateContents($booking_rooms, '#__vikbooking_rooms', array('id' => 'idroom', 'room_name' => 'name'));
9212 $sms_text = self::parseCustomerSMSTemplate($booking, $booking_rooms, $vbo_tn, $force_text);
9213 if (!empty($sms_text)) {
9214 $sms_obj = new VikSmsApi($booking, $sms_api_params);
9215 $response_obj = $sms_obj->sendMessage($booking['phone'], $sms_text);
9216 if ( !$sms_obj->validateResponse($response_obj) ) {
9217 //notify the administrator via email with the error of the SMS sending
9218 $vbo_app->sendMail($admin_sendermail, $admin_sendermail, $admin_email, $admin_sendermail, JText::translate('VBOSENDSMSERRMAILSUBJ'), JText::translate('VBOSENDCUSTOMERSMSERRMAILTXT')."<br />".$sms_obj->getLog(), true);
9219 } else {
9220 $f_result = true;
9221 }
9222 }
9223 }
9224 return $f_result;
9225 }
9226
9227 public static function loadInvoiceTmpl($booking_info = array(), $booking_rooms = array())
9228 {
9229 if (!defined('_VIKBOOKINGEXEC')) {
9230 define('_VIKBOOKINGEXEC', '1');
9231 }
9232
9233 ob_start();
9234 include VBO_SITE_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'invoices' . DIRECTORY_SEPARATOR . 'invoice_tmpl.php';
9235 $content = ob_get_contents();
9236 ob_end_clean();
9237
9238 $default_params = array(
9239 'show_header' => 0,
9240 'header_data' => array(),
9241 'show_footer' => 0,
9242 'pdf_page_orientation' => 'PDF_PAGE_ORIENTATION',
9243 'pdf_unit' => 'PDF_UNIT',
9244 'pdf_page_format' => 'PDF_PAGE_FORMAT',
9245 'pdf_margin_left' => 'PDF_MARGIN_LEFT',
9246 'pdf_margin_top' => 'PDF_MARGIN_TOP',
9247 'pdf_margin_right' => 'PDF_MARGIN_RIGHT',
9248 'pdf_margin_header' => 'PDF_MARGIN_HEADER',
9249 'pdf_margin_footer' => 'PDF_MARGIN_FOOTER',
9250 'pdf_margin_bottom' => 'PDF_MARGIN_BOTTOM',
9251 'pdf_image_scale_ratio' => 'PDF_IMAGE_SCALE_RATIO',
9252 'header_font_size' => '10',
9253 'body_font_size' => '10',
9254 'footer_font_size' => '8',
9255 'show_lines_taxrate_col' => 0,
9256 );
9257
9258 if (defined('_VIKBOOKING_INVOICE_PARAMS') && isset($invoice_params) && is_array($invoice_params) && count($invoice_params)) {
9259 $default_params = array_merge($default_params, $invoice_params);
9260 }
9261
9262 return array($content, $default_params);
9263 }
9264
9265 /**
9266 * Includes within a buffer the template file for the custom (manual) invoice.
9267 *
9268 * @param array $invoice the record of the custom invoice
9269 * @param array $customer the customer record
9270 *
9271 * @return string the HTML content of the template file
9272 *
9273 * @since 1.11.1
9274 */
9275 public static function loadCustomInvoiceTmpl($invoice, $customer) {
9276 if (!defined('_VIKBOOKINGEXEC')) {
9277 define('_VIKBOOKINGEXEC', '1');
9278 }
9279 ob_start();
9280 include VBO_SITE_PATH . DIRECTORY_SEPARATOR . "helpers" . DIRECTORY_SEPARATOR . "invoices" . DIRECTORY_SEPARATOR . "custom_invoice_tmpl.php";
9281 $content = ob_get_contents();
9282 ob_end_clean();
9283 $default_params = array(
9284 'show_header' => 0,
9285 'header_data' => array(),
9286 'show_footer' => 0,
9287 'pdf_page_orientation' => 'PDF_PAGE_ORIENTATION',
9288 'pdf_unit' => 'PDF_UNIT',
9289 'pdf_page_format' => 'PDF_PAGE_FORMAT',
9290 'pdf_margin_left' => 'PDF_MARGIN_LEFT',
9291 'pdf_margin_top' => 'PDF_MARGIN_TOP',
9292 'pdf_margin_right' => 'PDF_MARGIN_RIGHT',
9293 'pdf_margin_header' => 'PDF_MARGIN_HEADER',
9294 'pdf_margin_footer' => 'PDF_MARGIN_FOOTER',
9295 'pdf_margin_bottom' => 'PDF_MARGIN_BOTTOM',
9296 'pdf_image_scale_ratio' => 'PDF_IMAGE_SCALE_RATIO',
9297 'header_font_size' => '10',
9298 'body_font_size' => '10',
9299 'footer_font_size' => '8'
9300 );
9301 if (defined('_VIKBOOKING_INVOICE_PARAMS') && isset($invoice_params) && @count($invoice_params) > 0) {
9302 $default_params = array_merge($default_params, $invoice_params);
9303 }
9304 return array($content, $default_params);
9305 }
9306
9307 public static function parseInvoiceTemplate($invoicetpl, $booking, $booking_rooms, $invoice_num, $invoice_suff, $invoice_date, $company_info, $vbo_tn = null, $pdfparams = [])
9308 {
9309 // clone the original template file source
9310 $parsed = $invoicetpl;
9311
9312 /**
9313 * Parse all conditional text rules.
9314 *
9315 * @since 1.14 (J) - 1.4.0 (WP)
9316 */
9317 self::getConditionalRulesInstance()
9318 ->set(array('booking', 'rooms'), array($booking, $booking_rooms))
9319 ->parseTokens($parsed);
9320 //
9321
9322 $dbo = JFactory::getDbo();
9323 if (is_null($vbo_tn)) {
9324 $vbo_tn = self::getTranslator();
9325 }
9326 // availability helper
9327 $av_helper = self::getAvailabilityInstance();
9328
9329 $nowdf = self::getDateFormat();
9330 if ($nowdf == "%d/%m/%Y") {
9331 $df = 'd/m/Y';
9332 } elseif ($nowdf == "%m/%d/%Y") {
9333 $df = 'm/d/Y';
9334 } else {
9335 $df = 'Y/m/d';
9336 }
9337 $datesep = self::getDateSeparator();
9338
9339 /**
9340 * Split stay reservation.
9341 *
9342 * @since 1.16.0 (J) - 1.6.0 (WP)
9343 */
9344 $room_stay_dates = [];
9345 if ($order_info['split_stay']) {
9346 if ($order_info['status'] == 'confirmed') {
9347 $room_stay_dates = $av_helper->loadSplitStayBusyRecords($order_info['id']);
9348 } else {
9349 $room_stay_dates = VBOFactory::getConfig()->getArray('split_stay_' . $order_info['id'], []);
9350 }
9351 // immediately count the number of nights of stay for each split room
9352 foreach ($room_stay_dates as $sps_r_k => $sps_r_v) {
9353 if (!empty($sps_r_v['checkin_ts']) && !empty($sps_r_v['checkout_ts'])) {
9354 // overwrite values for compatibility with non-confirmed bookings
9355 $sps_r_v['checkin'] = $sps_r_v['checkin_ts'];
9356 $sps_r_v['checkout'] = $sps_r_v['checkout_ts'];
9357 }
9358 $sps_r_v['nights'] = $av_helper->countNightsOfStay($sps_r_v['checkin'], $sps_r_v['checkout']);
9359 // overwrite the whole array
9360 $room_stay_dates[$sps_r_k] = $sps_r_v;
9361 }
9362 }
9363
9364 $companylogo = self::getSiteLogo();
9365 $uselogo = '';
9366 if (!empty($companylogo)) {
9367 /**
9368 * Let's try to prevent TCPDF errors.
9369 *
9370 * @since August 2nd 2019
9371 */
9372 if (is_file(VBO_ADMIN_PATH . DIRECTORY_SEPARATOR . 'resources' . DIRECTORY_SEPARATOR . $companylogo)) {
9373 $uselogo = '<img src="' . VBO_ADMIN_URI_REL . 'resources/' . $companylogo . '"/>';
9374 } elseif (is_file(VBO_SITE_PATH . DIRECTORY_SEPARATOR . 'resources' . DIRECTORY_SEPARATOR . $companylogo)) {
9375 $uselogo = '<img src="' . VBO_SITE_URI_REL . 'resources/' . $companylogo . '"/>';
9376 }
9377 }
9378 $parsed = str_replace("{company_logo}", $uselogo, $parsed);
9379 $parsed = str_replace("{company_info}", $company_info, $parsed);
9380 $parsed = str_replace("{invoice_number}", $invoice_num, $parsed);
9381 $parsed = str_replace("{invoice_suffix}", $invoice_suff, $parsed);
9382 $parsed = str_replace("{invoice_date}", $invoice_date, $parsed);
9383 $parsed = str_replace("{customer_info}", nl2br(rtrim($booking['custdata'], "\n")), $parsed);
9384
9385 // custom fields replacement
9386 preg_match_all('/\{customfield ([0-9]+)\}/U', $parsed, $cmatches);
9387 if (is_array($cmatches[1]) && @count($cmatches[1]) > 0) {
9388 $cfids = array();
9389 foreach ($cmatches[1] as $cfid ){
9390 $cfids[] = $cfid;
9391 }
9392 $q = "SELECT * FROM `#__vikbooking_custfields` WHERE `id` IN (".implode(", ", $cfids).");";
9393 $dbo->setQuery($q);
9394 $dbo->execute();
9395 $cfields = $dbo->getNumRows() > 0 ? $dbo->loadAssocList() : "";
9396 $vbo_tn->translateContents($cfields, '#__vikbooking_custfields');
9397 $cfmap = array();
9398 if (is_array($cfields)) {
9399 foreach ($cfields as $cf) {
9400 $cfmap[trim(JText::translate($cf['name']))] = $cf['id'];
9401 }
9402 }
9403 $cfmapreplace = array();
9404 $partsreceived = explode("\n", $booking['custdata']);
9405 if (count($partsreceived) > 0) {
9406 foreach ($partsreceived as $pst) {
9407 if (!empty($pst)) {
9408 $tmpdata = explode(":", $pst);
9409 if (array_key_exists(trim($tmpdata[0]), $cfmap)) {
9410 $cfmapreplace[$cfmap[trim($tmpdata[0])]] = trim($tmpdata[1]);
9411 }
9412 }
9413 }
9414 }
9415 foreach ($cmatches[1] as $cfid ){
9416 if (array_key_exists($cfid, $cfmapreplace)) {
9417 $parsed = str_replace("{customfield ".$cfid."}", $cfmapreplace[$cfid], $parsed);
9418 } else {
9419 $parsed = str_replace("{customfield ".$cfid."}", "", $parsed);
9420 }
9421 }
9422 }
9423
9424 // invoice price description - Start
9425 $rooms = array();
9426 $tars = array();
9427 $arrpeople = array();
9428 $is_package = !empty($booking['pkg']) ? true : false;
9429 $tot_adults = 0;
9430 $tot_children = 0;
9431 $tot_guests = 0;
9432 foreach ($booking_rooms as $kor => $or) {
9433 $num = $kor + 1;
9434 $rooms[$num] = $or;
9435
9436 // determine the days to consider for the count of the availability
9437 $room_nights = $booking['days'];
9438 $room_checkin = $booking['checkin'];
9439 $room_checkout = $booking['checkout'];
9440 if ($booking['split_stay'] && count($room_stay_dates) && isset($room_stay_dates[$kor]) && $room_stay_dates[$kor]['idroom'] == $or['idroom']) {
9441 $room_nights = $room_stay_dates[$kor]['nights'];
9442 $room_checkin = $room_stay_dates[$kor]['checkin'];
9443 $room_checkout = $room_stay_dates[$kor]['checkout'];
9444 }
9445
9446 $arrpeople[$num]['adults'] = $or['adults'];
9447 $arrpeople[$num]['children'] = $or['children'];
9448 $tot_adults += $or['adults'];
9449 $tot_children += $or['children'];
9450 $tot_guests += ($or['adults'] + $or['children']);
9451 if ($is_package === true || (!empty($or['cust_cost']) && $or['cust_cost'] > 0.00)) {
9452 //package or custom cost set from the back-end
9453 continue;
9454 }
9455 $q = "SELECT * FROM `#__vikbooking_dispcost` WHERE `id`=" . (int)$or['idtar'] . ";";
9456 $dbo->setQuery($q);
9457 $dbo->execute();
9458 if ($dbo->getNumRows() > 0) {
9459 $tar = $dbo->loadAssocList();
9460 $tar = self::applySeasonsRoom($tar, $room_checkin, $room_checkout);
9461 //different usage
9462 if ($or['fromadult'] <= $or['adults'] && $or['toadult'] >= $or['adults']) {
9463 $diffusageprice = self::loadAdultsDiff($or['idroom'], $or['adults']);
9464 //Occupancy Override
9465 $occ_ovr = self::occupancyOverrideExists($tar, $or['adults']);
9466 $diffusageprice = $occ_ovr !== false ? $occ_ovr : $diffusageprice;
9467 //
9468 if (is_array($diffusageprice)) {
9469 //set a charge or discount to the price(s) for the different usage of the room
9470 foreach ($tar as $kpr => $vpr) {
9471 $tar[$kpr]['diffusage'] = $or['adults'];
9472 if ($diffusageprice['chdisc'] == 1) {
9473 //charge
9474 if ($diffusageprice['valpcent'] == 1) {
9475 //fixed value
9476 $tar[$kpr]['diffusagecostpernight'] = $diffusageprice['pernight'] == 1 ? 1 : 0;
9477 $aduseval = $diffusageprice['pernight'] == 1 ? $diffusageprice['value'] * $tar[$kpr]['days'] : $diffusageprice['value'];
9478 $tar[$kpr]['diffusagecost'] = "+".$aduseval;
9479 $tar[$kpr]['room_base_cost'] = $vpr['cost'];
9480 $tar[$kpr]['cost'] = $vpr['cost'] + $aduseval;
9481 } else {
9482 //percentage value
9483 $tar[$kpr]['diffusagecostpernight'] = $diffusageprice['pernight'] == 1 ? $vpr['cost'] : 0;
9484 $aduseval = $diffusageprice['pernight'] == 1 ? round(($vpr['cost'] * $diffusageprice['value'] / 100) * $tar[$kpr]['days'] + $vpr['cost'], 2) : round(($vpr['cost'] * (100 + $diffusageprice['value']) / 100), 2);
9485 $tar[$kpr]['diffusagecost'] = "+".$diffusageprice['value']."%";
9486 $tar[$kpr]['room_base_cost'] = $vpr['cost'];
9487 $tar[$kpr]['cost'] = $aduseval;
9488 }
9489 } else {
9490 //discount
9491 if ($diffusageprice['valpcent'] == 1) {
9492 //fixed value
9493 $tar[$kpr]['diffusagecostpernight'] = $diffusageprice['pernight'] == 1 ? 1 : 0;
9494 $aduseval = $diffusageprice['pernight'] == 1 ? $diffusageprice['value'] * $tar[$kpr]['days'] : $diffusageprice['value'];
9495 $tar[$kpr]['diffusagecost'] = "-".$aduseval;
9496 $tar[$kpr]['room_base_cost'] = $vpr['cost'];
9497 $tar[$kpr]['cost'] = $vpr['cost'] - $aduseval;
9498 } else {
9499 //percentage value
9500 $tar[$kpr]['diffusagecostpernight'] = $diffusageprice['pernight'] == 1 ? $vpr['cost'] : 0;
9501 $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);
9502 $tar[$kpr]['diffusagecost'] = "-".$diffusageprice['value']."%";
9503 $tar[$kpr]['room_base_cost'] = $vpr['cost'];
9504 $tar[$kpr]['cost'] = $aduseval;
9505 }
9506 }
9507 }
9508 }
9509 }
9510 //
9511 $tars[$num] = $tar[0];
9512 }
9513 }
9514 $parsed = str_replace("{checkin_date}", date(str_replace("/", $datesep, $df), $booking['checkin']), $parsed);
9515 $parsed = str_replace("{checkout_date}", date(str_replace("/", $datesep, $df), $booking['checkout']), $parsed);
9516 $parsed = str_replace("{num_nights}", $booking['days'], $parsed);
9517 $parsed = str_replace("{tot_guests}", $tot_guests, $parsed);
9518 $parsed = str_replace("{tot_adults}", $tot_adults, $parsed);
9519 $parsed = str_replace("{tot_children}", $tot_children, $parsed);
9520
9521 $isdue = 0;
9522 $tot_taxes = 0;
9523 $tot_city_taxes = 0;
9524 $tot_fees = 0;
9525 $pricestr = array();
9526 $optstr = array();
9527 // start building the tax summary
9528 VBOTaxonomySummary::start();
9529 foreach ($booking_rooms as $kor => $or) {
9530 $num = $kor + 1;
9531
9532 // determine the days to consider for the count of the availability
9533 $room_nights = $booking['days'];
9534 $room_checkin = $booking['checkin'];
9535 $room_checkout = $booking['checkout'];
9536 if ($booking['split_stay'] && count($room_stay_dates) && isset($room_stay_dates[$kor]) && $room_stay_dates[$kor]['idroom'] == $or['idroom']) {
9537 $room_nights = $room_stay_dates[$kor]['nights'];
9538 $room_checkin = $room_stay_dates[$kor]['checkin'];
9539 $room_checkout = $room_stay_dates[$kor]['checkout'];
9540 }
9541
9542 $pricestr[$num] = array();
9543 if ($is_package === true || (!empty($or['cust_cost']) && $or['cust_cost'] > 0.00)) {
9544 // package cost or cust_cost may not be inclusive of taxes if prices tax included is off
9545 $calctar = self::sayPackagePlusIva($or['cust_cost'], $or['cust_idiva']);
9546 $cost_minus_tax = self::sayPackageMinusIva($or['cust_cost'], $or['cust_idiva']);
9547 $pricestr[$num]['name'] = (!empty($or['pkg_name']) ? $or['pkg_name'] : (!empty($or['otarplan']) ? ucwords($or['otarplan']) : JText::translate('VBOROOMCUSTRATEPLAN')));
9548 $pricestr[$num]['tot'] = $calctar;
9549 $pricestr[$num]['tax'] = ($calctar - $cost_minus_tax);
9550 $tot_taxes += ($calctar - $cost_minus_tax);
9551 $isdue += $calctar;
9552 $tax_rate = VBOTaxonomySummary::addOptionTax($or['cust_idiva'], ($calctar - $cost_minus_tax));
9553 $pricestr[$num]['tax_rate'] = $tax_rate;
9554 } elseif (array_key_exists($num, $tars) && is_array($tars[$num])) {
9555 $display_rate = !empty($or['room_cost']) ? $or['room_cost'] : $tars[$num]['cost'];
9556 $calctar = self::sayCostPlusIva($display_rate, $tars[$num]['idprice']);
9557 $pricestr[$num]['name'] = self::getPriceName($tars[$num]['idprice'], $vbo_tn) . (!empty($tars[$num]['attrdata']) ? "\n" . self::getPriceAttr($tars[$num]['idprice'], $vbo_tn) . ": " . $tars[$num]['attrdata'] : "");
9558 $pricestr[$num]['tot'] = $calctar;
9559 $tars[$num]['calctar'] = $calctar;
9560 $isdue += $calctar;
9561 if ($calctar == $display_rate) {
9562 $cost_minus_tax = self::sayCostMinusIva($display_rate, $tars[$num]['idprice']);
9563 $tot_taxes += ($display_rate - $cost_minus_tax);
9564 $pricestr[$num]['tax'] = ($display_rate - $cost_minus_tax);
9565 $tax_rate = VBOTaxonomySummary::addRatePlanTax($tars[$num]['idprice'], ($display_rate - $cost_minus_tax));
9566 $pricestr[$num]['tax_rate'] = $tax_rate;
9567 } else {
9568 $tot_taxes += ($calctar - $display_rate);
9569 $pricestr[$num]['tax'] = ($calctar - $display_rate);
9570 $tax_rate = VBOTaxonomySummary::addRatePlanTax($tars[$num]['idprice'], ($calctar - $display_rate));
9571 $pricestr[$num]['tax_rate'] = $tax_rate;
9572 }
9573 }
9574 $optstr[$num] = array();
9575 $opt_ind = 0;
9576 if (!empty($or['optionals'])) {
9577 $stepo = explode(";", $or['optionals']);
9578 foreach ($stepo as $roptkey => $oo) {
9579 if (!empty($oo)) {
9580 $stept = explode(":", $oo);
9581 $q = "SELECT * FROM `#__vikbooking_optionals` WHERE `id`=" . $dbo->quote($stept[0]) . ";";
9582 $dbo->setQuery($q);
9583 $dbo->execute();
9584 if ($dbo->getNumRows() == 1) {
9585 $actopt = $dbo->loadAssocList();
9586 if (is_object($vbo_tn)) {
9587 $vbo_tn->translateContents($actopt, '#__vikbooking_optionals');
9588 }
9589 $optstr[$num][$opt_ind] = array();
9590 $chvar = '';
9591 if (!empty($actopt[0]['ageintervals']) && $or['children'] > 0 && strstr($stept[1], '-') != false) {
9592 $optagenames = self::getOptionIntervalsAges($actopt[0]['ageintervals']);
9593 $optagepcent = self::getOptionIntervalsPercentage($actopt[0]['ageintervals']);
9594 $optageovrct = self::getOptionIntervalChildOverrides($actopt[0], $or['adults'], $or['children']);
9595 $child_num = self::getRoomOptionChildNumber($or['optionals'], $actopt[0]['id'], $roptkey, $or['children']);
9596 $optagecosts = self::getOptionIntervalsCosts(isset($optageovrct['ageintervals_child' . ($child_num + 1)]) ? $optageovrct['ageintervals_child' . ($child_num + 1)] : $actopt[0]['ageintervals']);
9597 $agestept = explode('-', $stept[1]);
9598 $stept[1] = $agestept[0];
9599 $chvar = $agestept[1];
9600 if (array_key_exists(($chvar - 1), $optagepcent) && $optagepcent[($chvar - 1)] == 1) {
9601 //percentage value of the adults tariff
9602 if ($is_package === true || (!empty($or['cust_cost']) && $or['cust_cost'] > 0.00)) {
9603 $optagecosts[($chvar - 1)] = $or['cust_cost'] * $optagecosts[($chvar - 1)] / 100;
9604 } else {
9605 $display_rate = !empty($or['room_cost']) ? $or['room_cost'] : $tars[$num]['cost'];
9606 $optagecosts[($chvar - 1)] = $display_rate * $optagecosts[($chvar - 1)] / 100;
9607 }
9608 } elseif (array_key_exists(($chvar - 1), $optagepcent) && $optagepcent[($chvar - 1)] == 2) {
9609 //VBO 1.10 - percentage value of room base cost
9610 if ($is_package === true || (!empty($or['cust_cost']) && $or['cust_cost'] > 0.00)) {
9611 $optagecosts[($chvar - 1)] = $or['cust_cost'] * $optagecosts[($chvar - 1)] / 100;
9612 } else {
9613 $display_rate = isset($tars[$num]['room_base_cost']) ? $tars[$num]['room_base_cost'] : (!empty($or['room_cost']) ? $or['room_cost'] : $tars[$num]['cost']);
9614 $optagecosts[($chvar - 1)] = $display_rate * $optagecosts[($chvar - 1)] / 100;
9615 }
9616 }
9617 $actopt[0]['chageintv'] = $chvar;
9618 $actopt[0]['name'] .= ' ('.$optagenames[($chvar - 1)].')';
9619 $actopt[0]['quan'] = $stept[1];
9620 $realcost = (intval($actopt[0]['perday']) == 1 ? (floatval($optagecosts[($chvar - 1)]) * $room_nights * $stept[1]) : (floatval($optagecosts[($chvar - 1)]) * $stept[1]));
9621 } else {
9622 $actopt[0]['quan'] = $stept[1];
9623 // VBO 1.11 - options percentage cost of the room total fee
9624 if ($is_package === true || (!empty($or['cust_cost']) && $or['cust_cost'] > 0.00)) {
9625 $deftar_basecosts = $or['cust_cost'];
9626 } else {
9627 $deftar_basecosts = !empty($or['room_cost']) ? $or['room_cost'] : $tars[$num]['cost'];
9628 }
9629 $actopt[0]['cost'] = (int)$actopt[0]['pcentroom'] ? ($deftar_basecosts * $actopt[0]['cost'] / 100) : $actopt[0]['cost'];
9630 //
9631 $realcost = (intval($actopt[0]['perday']) == 1 ? ($actopt[0]['cost'] * $room_nights * $stept[1]) : ($actopt[0]['cost'] * $stept[1]));
9632 }
9633 if (!empty($actopt[0]['maxprice']) && $actopt[0]['maxprice'] > 0 && $realcost > $actopt[0]['maxprice']) {
9634 $realcost = $actopt[0]['maxprice'];
9635 if (intval($actopt[0]['hmany']) == 1 && intval($stept[1]) > 1) {
9636 $realcost = $actopt[0]['maxprice'] * $stept[1];
9637 }
9638 }
9639 if ($actopt[0]['perperson'] == 1) {
9640 $realcost = $realcost * $or['adults'];
9641 }
9642 $tmpopr = self::sayOptionalsPlusIva($realcost, $actopt[0]['idiva']);
9643 $optstr[$num][$opt_ind]['name'] = ($stept[1] > 1 ? $stept[1] . " " : "") . $actopt[0]['name'];
9644 $optstr[$num][$opt_ind]['tot'] = $tmpopr;
9645 $optstr[$num][$opt_ind]['tax'] = 0;
9646 if ($actopt[0]['is_citytax'] == 1) {
9647 $tot_city_taxes += $tmpopr;
9648 } elseif ($actopt[0]['is_fee'] == 1) {
9649 $tot_fees += $tmpopr;
9650 }
9651 // VBO 1.11 - always calculate the amount of tax no matter if this is already a tax or a fee
9652 if ($tmpopr == $realcost) {
9653 $opt_minus_tax = self::sayOptionalsMinusIva($realcost, $actopt[0]['idiva']);
9654 $tot_taxes += ($realcost - $opt_minus_tax);
9655 $optstr[$num][$opt_ind]['tax'] = ($realcost - $opt_minus_tax);
9656 $tax_rate = VBOTaxonomySummary::addOptionTax($actopt[0]['idiva'], ($realcost - $opt_minus_tax));
9657 $optstr[$num][$opt_ind]['tax_rate'] = $tax_rate;
9658 } else {
9659 $tot_taxes += ($tmpopr - $realcost);
9660 $optstr[$num][$opt_ind]['tax'] = ($tmpopr - $realcost);
9661 $tax_rate = VBOTaxonomySummary::addOptionTax($actopt[0]['idiva'], ($tmpopr - $realcost));
9662 $optstr[$num][$opt_ind]['tax_rate'] = $tax_rate;
9663 }
9664 //
9665 $opt_ind++;
9666 $isdue += $tmpopr;
9667 }
9668 }
9669 }
9670 }
9671
9672 // custom extra costs
9673 if (!empty($or['extracosts'])) {
9674 $cur_extra_costs = json_decode($or['extracosts'], true);
9675 foreach ($cur_extra_costs as $eck => $ecv) {
9676 $ecplustax = !empty($ecv['idtax']) ? self::sayOptionalsPlusIva($ecv['cost'], $ecv['idtax']) : $ecv['cost'];
9677 $isdue += $ecplustax;
9678 $optstr[$num][$opt_ind]['name'] = $ecv['name'];
9679 $optstr[$num][$opt_ind]['tot'] = $ecplustax;
9680 $optstr[$num][$opt_ind]['tax'] = 0;
9681 if ($ecplustax == $ecv['cost']) {
9682 $ec_minus_tax = !empty($ecv['idtax']) ? self::sayOptionalsMinusIva($ecv['cost'], $ecv['idtax']) : $ecv['cost'];
9683 $tot_taxes += ($ecv['cost'] - $ec_minus_tax);
9684 $optstr[$num][$opt_ind]['tax'] = ($ecv['cost'] - $ec_minus_tax);
9685 $tax_rate = VBOTaxonomySummary::addOptionTax($ecv['idtax'], ($ecv['cost'] - $ec_minus_tax));
9686 $optstr[$num][$opt_ind]['tax_rate'] = $tax_rate;
9687 } else {
9688 $tot_taxes += ($ecplustax - $ecv['cost']);
9689 $optstr[$num][$opt_ind]['tax'] = ($ecplustax - $ecv['cost']);
9690 $tax_rate = VBOTaxonomySummary::addOptionTax($ecv['idtax'], ($ecplustax - $ecv['cost']));
9691 $optstr[$num][$opt_ind]['tax_rate'] = $tax_rate;
9692 }
9693 $opt_ind++;
9694 }
9695 }
9696 }
9697
9698 $usedcoupon = false;
9699 if (strlen($booking['coupon']) > 0) {
9700 $orig_isdue = $isdue;
9701 $expcoupon = explode(";", $booking['coupon']);
9702 $usedcoupon = $expcoupon;
9703 $isdue = $isdue - (float)$expcoupon[1];
9704 if ($isdue != $orig_isdue) {
9705 //lower taxes proportionally
9706 $tot_taxes = $isdue * $tot_taxes / $orig_isdue;
9707 }
9708 }
9709 if ($booking['refund'] > 0) {
9710 $orig_isdue = $isdue;
9711 $isdue -= $booking['refund'];
9712 if ($isdue != $orig_isdue) {
9713 //lower taxes proportionally
9714 $tot_taxes = $isdue * $tot_taxes / $orig_isdue;
9715 }
9716 }
9717 $rows_written = 0;
9718 $inv_rows = '';
9719 foreach ($pricestr as $num => $price_descr) {
9720 // add room split stay information, if any
9721 $split_stay_str = '';
9722 $kor = $num - 1;
9723 if ($booking['split_stay'] && count($room_stay_dates) && isset($room_stay_dates[$kor])) {
9724 $split_stay_str .= $room_stay_dates[$kor]['nights'] . ' ' . ($room_stay_dates[$kor]['nights'] > 1 ? JText::translate('VBDAYS') : JText::translate('VBDAY')) . ', ';
9725 $split_stay_str .= date(str_replace("/", $datesep, $df), $room_stay_dates[$kor]['checkin']) . ' - ' . date(str_replace("/", $datesep, $df), $room_stay_dates[$kor]['checkout']);
9726 }
9727
9728 $inv_rows .= '<tr>'."\n";
9729 $inv_rows .= '<td>' . $rooms[$num]['room_name'] . (!empty($split_stay_str) ? '<br/>' . $split_stay_str : '') . '<br/>' . nl2br(rtrim($price_descr['name'], "\n")) . '</td>' . "\n";
9730 $inv_rows .= '<td>'.$booking['currencyname'].' '.self::numberFormat(($price_descr['tot'] - $price_descr['tax'])).'</td>'."\n";
9731 $inv_rows .= '<td>'.$booking['currencyname'].' '.self::numberFormat($price_descr['tax']).'</td>'."\n";
9732 $inv_rows .= '<td>'.$booking['currencyname'].' '.self::numberFormat($price_descr['tot']).'</td>'."\n";
9733 if (!empty($pdfparams['show_lines_taxrate_col'])) {
9734 $inv_rows .= '<td>' . (isset($price_descr['tax_rate']) ? $price_descr['tax_rate'] : '0') . '%</td>'."\n";
9735 }
9736 $inv_rows .= '</tr>'."\n";
9737 $rows_written++;
9738 if (array_key_exists($num, $optstr) && count($optstr[$num]) > 0) {
9739 foreach ($optstr[$num] as $optk => $optv) {
9740 $inv_rows .= '<tr>'."\n";
9741 $inv_rows .= '<td>'.$optv['name'].'</td>'."\n";
9742 $inv_rows .= '<td>'.$booking['currencyname'].' '.self::numberFormat(($optv['tot'] - $optv['tax'])).'</td>'."\n";
9743 $inv_rows .= '<td>'.$booking['currencyname'].' '.self::numberFormat($optv['tax']).'</td>'."\n";
9744 $inv_rows .= '<td>'.$booking['currencyname'].' '.self::numberFormat($optv['tot']).'</td>'."\n";
9745 if (!empty($pdfparams['show_lines_taxrate_col'])) {
9746 $inv_rows .= '<td>' . (isset($optv['tax_rate']) ? $optv['tax_rate'] : '0') . '%</td>'."\n";
9747 }
9748 $inv_rows .= '</tr>'."\n";
9749 $rows_written++;
9750 }
9751 }
9752 }
9753
9754 // if discount print row
9755 if ($usedcoupon !== false) {
9756 $inv_rows .= '<tr>'."\n";
9757 $inv_rows .= '<td></td><td></td><td></td><td></td>'."\n";
9758 $inv_rows .= '</tr>'."\n";
9759 $inv_rows .= '<tr>'."\n";
9760 $inv_rows .= '<td>'.$usedcoupon[2].'</td>'."\n";
9761 $inv_rows .= '<td></td>'."\n";
9762 $inv_rows .= '<td></td>'."\n";
9763 $inv_rows .= '<td>- '.$booking['currencyname'].' '.self::numberFormat($usedcoupon[1]).'</td>'."\n";
9764 $inv_rows .= '</tr>'."\n";
9765 $rows_written += 2;
9766 }
9767 // if refunded amount, print row
9768 if ($booking['refund'] > 0) {
9769 $inv_rows .= '<tr>'."\n";
9770 $inv_rows .= '<td></td><td></td><td></td><td></td>'."\n";
9771 $inv_rows .= '</tr>'."\n";
9772 $inv_rows .= '<tr>'."\n";
9773 $inv_rows .= '<td>' . JText::translate('VBO_AMOUNT_REFUNDED') . '</td>'."\n";
9774 $inv_rows .= '<td></td>'."\n";
9775 $inv_rows .= '<td></td>'."\n";
9776 $inv_rows .= '<td>- '.$booking['currencyname'].' '.self::numberFormat($booking['refund']).'</td>'."\n";
9777 $inv_rows .= '</tr>'."\n";
9778 $rows_written += 2;
9779 }
9780 //
9781 $min_records = 8;
9782 if ($rows_written < $min_records) {
9783 for ($i = 1; $i <= ($min_records - $rows_written); $i++) {
9784 $inv_rows .= '<tr>'."\n";
9785 $inv_rows .= '<td></td>'."\n";
9786 $inv_rows .= '<td></td>'."\n";
9787 $inv_rows .= '<td></td>'."\n";
9788 $inv_rows .= '</tr>'."\n";
9789 }
9790 }
9791 //invoice price description - End
9792 $parsed = str_replace("{invoice_products_descriptions}", $inv_rows, $parsed);
9793 $parsed = str_replace("{invoice_totalnet}", $booking['currencyname'].' '.self::numberFormat(($isdue - $tot_taxes)), $parsed);
9794 $parsed = str_replace("{invoice_totaltax}", $booking['currencyname'].' '.self::numberFormat($tot_taxes), $parsed);
9795 $parsed = str_replace("{invoice_grandtotal}", $booking['currencyname'].' '.self::numberFormat($isdue), $parsed);
9796 $parsed = str_replace("{inv_notes}", $booking['inv_notes'], $parsed);
9797
9798 // invoice tax summary
9799 $tax_summary = VBOTaxonomySummary::get();
9800 $tax_names = VBOTaxonomySummary::getNames();
9801 $taxsum_rows = "";
9802 foreach ($tax_summary as $tax_rate => $tax_amount) {
9803 if (!$tax_rate && !$tax_amount) {
9804 continue;
9805 }
9806 $tax_name = isset($tax_names[$tax_rate]) ? $tax_names[$tax_rate] : JText::translate('VBO_INV_VATGST');
9807 $taxsum_rows .= '<tr>' . "\n";
9808 $taxsum_rows .= '<td>' . $tax_name . '</td>' . "\n";
9809 $taxsum_rows .= '<td>' . $tax_rate . '%</td>' . "\n";
9810 $taxsum_rows .= '<td>' . $booking['currencyname'] . ' ' . self::numberFormat($tax_amount) . '</td>' . "\n";
9811 $taxsum_rows .= '</tr>' . "\n";
9812 }
9813 $parsed = str_replace("{invoice_tax_summary}", $taxsum_rows, $parsed);
9814
9815 return $parsed;
9816 }
9817
9818 /**
9819 * Specific method for parsing the template file for the custom (manual) invoices.
9820 *
9821 * @param string $invoicetpl the plain custom invoice template file before parsing
9822 * @param array $invoice the invoice record
9823 * @param array $customer the customer record
9824 *
9825 * @return string the HTML content of the parsed custom invoice template
9826 *
9827 * @since 1.11.1
9828 */
9829 public static function parseCustomInvoiceTemplate($invoicetpl, $invoice, $customer) {
9830 $nowdf = self::getDateFormat(true);
9831 if ($nowdf == "%d/%m/%Y") {
9832 $df = 'd/m/Y';
9833 } elseif ($nowdf == "%m/%d/%Y") {
9834 $df = 'm/d/Y';
9835 } else {
9836 $df = 'Y/m/d';
9837 }
9838 $datesep = self::getDateSeparator(true);
9839 $companylogo = self::getSiteLogo();
9840 $uselogo = '';
9841 if (!empty($companylogo)) {
9842 /**
9843 * Let's try to prevent TCPDF errors by checking if the file exists.
9844 *
9845 * @since August 2nd 2019
9846 */
9847 if (is_file(VBO_ADMIN_PATH . DIRECTORY_SEPARATOR . 'resources' . DIRECTORY_SEPARATOR . $companylogo)) {
9848 $uselogo = '<img src="' . VBO_ADMIN_URI_REL . 'resources/' . $companylogo . '"/>';
9849 } elseif (is_file(VBO_SITE_PATH . DIRECTORY_SEPARATOR . 'resources' . DIRECTORY_SEPARATOR . $companylogo)) {
9850 $uselogo = '<img src="' . VBO_SITE_URI_REL . 'resources/' . $companylogo . '"/>';
9851 }
9852 }
9853 $invoicetpl = str_replace("{company_logo}", $uselogo, $invoicetpl);
9854 $invoicetpl = str_replace("{company_info}", self::getInvoiceCompanyInfo(), $invoicetpl);
9855 $invoicetpl = str_replace("{invoice_number}", $invoice['number'], $invoicetpl);
9856 $invoicetpl = str_replace("{invoice_date}", date(str_replace("/", $datesep, $df), $invoice['for_date']), $invoicetpl);
9857 // customer information
9858 $custinfo = '';
9859 $custinfo .= $customer['first_name'] . ' ' . $customer['last_name'] . "\n";
9860 $custinfo .= $customer['email'] . "\n";
9861 $custinfo .= !empty($customer['company']) ? $customer['company'] . "\n" : '';
9862 $custinfo .= !empty($customer['vat']) ? $customer['vat'] . "\n" : '';
9863 $custinfo .= !empty($customer['address']) ? $customer['address'] . "\n" : '';
9864 $custinfo .= (!empty($customer['zip']) ? $customer['zip'] . " " : '') . (!empty($customer['city']) ? $customer['city'] . "\n" : '');
9865 $custinfo .= (!empty($customer['country_name']) ? $customer['country_name'] . "\n" : (!empty($customer['country']) ? $customer['country'] . "\n" : ''));
9866 $custinfo .= !empty($customer['fisccode']) ? $customer['fisccode'] . "\n" : '';
9867 $invoicetpl = str_replace("{customer_info}", nl2br($custinfo), $invoicetpl);
9868 // invoice notes
9869 $invoicetpl = str_replace("{invoice_notes}", (isset($invoice['rawcont']) && isset($invoice['rawcont']['notes']) ? $invoice['rawcont']['notes'] : ''), $invoicetpl);
9870
9871 return $invoicetpl;
9872 }
9873
9874 public static function generateBookingInvoice($booking, $invoice_num = 0, $invoice_suff = '', $invoice_date = '', $company_info = '', $translate = false)
9875 {
9876 $invoice_num = empty($invoice_num) ? self::getNextInvoiceNumber() : $invoice_num;
9877 $invoice_suff = empty($invoice_suff) ? self::getInvoiceNumberSuffix() : $invoice_suff;
9878 $company_info = empty($company_info) ? self::getInvoiceCompanyInfo() : $company_info;
9879
9880 if (!is_array($booking) || !$booking || !($booking['total'] > 0)) {
9881 return false;
9882 }
9883
9884 /**
9885 * Trigger event to allow third party plugins to override some values for the invoice generation.
9886 *
9887 * @since 1.16.3 (J) - 1.6.3 (WP)
9888 */
9889 $event_args = [
9890 'invoice_num' => $invoice_num,
9891 'invoice_suff' => $invoice_suff,
9892 'invoice_date' => $invoice_date,
9893 'company_info' => $company_info,
9894 'translate' => $translate,
9895 ];
9896 VBOFactory::getPlatform()->getDispatcher()->trigger('onBeforeGenerateInvoiceVikBooking', [$booking, &$event_args]);
9897 if (is_array($event_args) && $event_args) {
9898 // extract the new values to be used
9899 extract($event_args);
9900 }
9901
9902 $dbo = JFactory::getDbo();
9903 $vbo_tn = self::getTranslator();
9904 $currencyname = self::getCurrencyName();
9905 $booking['currencyname'] = $currencyname;
9906 $nowdf = self::getDateFormat(true);
9907 if ($nowdf == "%d/%m/%Y") {
9908 $df = 'd/m/Y';
9909 } elseif ($nowdf == "%m/%d/%Y") {
9910 $df = 'm/d/Y';
9911 } else {
9912 $df = 'Y/m/d';
9913 }
9914 $datesep = self::getDateSeparator(true);
9915 if (empty($invoice_date)) {
9916 $invoice_date = date(str_replace("/", $datesep, $df), $booking['ts']);
9917 $used_date = $booking['ts'];
9918 } else {
9919 /**
9920 * We could be re-generating an invoice for a booking that already had a invoice.
9921 * In order to modify some entries in the invoice, the whole PDF is re-generated.
9922 * It is now possible to keep the same invoice date as the previous one, so check
9923 * what value contains $invoice_date to see if it's different from today's date.
9924 * The cron jobs may be calling this method with a $invoice_date = 1, so we need
9925 * to also check the length of the string $invoice_date before using that date.
9926 *
9927 * @since 1.10 - August 2018
9928 */
9929 $base_ts = time();
9930 if (date($df, $base_ts) != $invoice_date && strlen($invoice_date) >= 6) {
9931 $base_ts = self::getDateTimestamp($invoice_date, 0, 0);
9932 }
9933 $invoice_date = date(str_replace("/", $datesep, $df), $base_ts);
9934 $used_date = $base_ts;
9935 //
9936 }
9937 $booking_rooms = array();
9938 $q = "SELECT `or`.*,`r`.`name` AS `room_name`,`r`.`fromadult`,`r`.`toadult` FROM `#__vikbooking_ordersrooms` AS `or` LEFT JOIN `#__vikbooking_rooms` `r` ON `r`.`id`=`or`.`idroom` WHERE `or`.`idorder`=".(int)$booking['id'].";";
9939 $dbo->setQuery($q);
9940 $dbo->execute();
9941 if ($dbo->getNumRows() > 0) {
9942 $booking_rooms = $dbo->loadAssocList();
9943 }
9944 if (!(count($booking_rooms) > 0)) {
9945 return false;
9946 }
9947 //Translations for the invoices are disabled by default as well as the language definitions for the customer language
9948 if ($translate === true) {
9949 if (!empty($booking['lang'])) {
9950 $lang = JFactory::getLanguage();
9951 if ($lang->getTag() != $booking['lang']) {
9952 if (VBOPlatformDetection::isWordPress()) {
9953 $lang->load('com_vikbooking', VIKBOOKING_LANG, $booking['lang'], true);
9954 } else {
9955 $lang->load('com_vikbooking', JPATH_SITE, $booking['lang'], true);
9956 $lang->load('com_vikbooking', JPATH_ADMINISTRATOR, $booking['lang'], true);
9957 $lang->load('joomla', JPATH_SITE, $booking['lang'], true);
9958 $lang->load('joomla', JPATH_ADMINISTRATOR, $booking['lang'], true);
9959 }
9960 }
9961 if ($vbo_tn->getDefaultLang() != $booking['lang']) {
9962 // force the translation to start because contents should be translated
9963 $vbo_tn::$force_tolang = $booking['lang'];
9964 }
9965 $vbo_tn->translateContents($booking_rooms, '#__vikbooking_rooms', array('id' => 'idroom', 'room_name' => 'name'), array(), $booking['lang']);
9966 }
9967 }
9968 //
9969 if (!class_exists('TCPDF')) {
9970 require_once(VBO_SITE_PATH . DIRECTORY_SEPARATOR . "helpers" . DIRECTORY_SEPARATOR . "tcpdf" . DIRECTORY_SEPARATOR . 'tcpdf.php');
9971 }
9972 $usepdffont = is_file(VBO_SITE_PATH . DIRECTORY_SEPARATOR . "helpers" . DIRECTORY_SEPARATOR . "tcpdf" . DIRECTORY_SEPARATOR . "fonts" . DIRECTORY_SEPARATOR . "dejavusans.php") ? 'dejavusans' : 'helvetica';
9973
9974 /**
9975 * Trigger event to allow third party plugins to return a specific font name.
9976 *
9977 * @since 1.16.0 (J) - 1.6.0 (WP)
9978 */
9979 $custom_pdf_font = VBOFactory::getPlatform()->getDispatcher()->filter('onGetPdfFontNameVikBooking', [$usepdffont]);
9980 if (is_array($custom_pdf_font) && !empty($custom_pdf_font[0])) {
9981 $usepdffont = $custom_pdf_font[0];
9982 }
9983
9984 //vikbooking 1.8 - set array variable to the template file
9985 $q = "SELECT * FROM `#__vikbooking_orders` WHERE `id`='".(int)$booking['id']."';";
9986 $dbo->setQuery($q);
9987 $dbo->execute();
9988 $booking_info = $dbo->loadAssoc();
9989 list($invoicetpl, $pdfparams) = self::loadInvoiceTmpl($booking_info, $booking_rooms);
9990 //
9991 $invoice_body = self::parseInvoiceTemplate($invoicetpl, $booking, $booking_rooms, $invoice_num, $invoice_suff, $invoice_date, $company_info, ($translate === true ? $vbo_tn : null), $pdfparams);
9992 $pdffname = $booking['id'] . '_' . $booking['sid'] . '.pdf';
9993 $pathpdf = VBO_SITE_PATH . DIRECTORY_SEPARATOR . "helpers" . DIRECTORY_SEPARATOR . "invoices" . DIRECTORY_SEPARATOR . "generated" . DIRECTORY_SEPARATOR . $pdffname;
9994 if (file_exists($pathpdf)) @unlink($pathpdf);
9995 $pdf_page_format = is_array($pdfparams['pdf_page_format']) ? $pdfparams['pdf_page_format'] : constant($pdfparams['pdf_page_format']);
9996 $pdf = new TCPDF(constant($pdfparams['pdf_page_orientation']), constant($pdfparams['pdf_unit']), $pdf_page_format, true, 'UTF-8', false);
9997 $pdf->SetTitle(JText::translate('VBOINVNUM').' '.$invoice_num);
9998 //Header for each page of the pdf
9999 if ($pdfparams['show_header'] == 1 && count($pdfparams['header_data']) > 0) {
10000 $pdf->SetHeaderData($pdfparams['header_data'][0], $pdfparams['header_data'][1], $pdfparams['header_data'][2], $pdfparams['header_data'][3], $pdfparams['header_data'][4], $pdfparams['header_data'][5]);
10001 }
10002 //Change some currencies to their unicode (decimal) value
10003 $unichr_map = array('EUR' => 8364, 'USD' => 36, 'AUD' => 36, 'CAD' => 36, 'GBP' => 163);
10004 if (array_key_exists($booking['currencyname'], $unichr_map)) {
10005 $invoice_body = str_replace($booking['currencyname'], TCPDF_FONTS::unichr($unichr_map[$booking['currencyname']]), $invoice_body);
10006 }
10007 //header and footer fonts
10008 $pdf->setHeaderFont(array($usepdffont, '', $pdfparams['header_font_size']));
10009 $pdf->setFooterFont(array($usepdffont, '', $pdfparams['footer_font_size']));
10010 //margins
10011 $pdf->SetMargins(constant($pdfparams['pdf_margin_left']), constant($pdfparams['pdf_margin_top']), constant($pdfparams['pdf_margin_right']));
10012 $pdf->SetHeaderMargin(constant($pdfparams['pdf_margin_header']));
10013 $pdf->SetFooterMargin(constant($pdfparams['pdf_margin_footer']));
10014 //
10015 $pdf->SetAutoPageBreak(true, constant($pdfparams['pdf_margin_bottom']));
10016 $pdf->setImageScale(constant($pdfparams['pdf_image_scale_ratio']));
10017 $pdf->SetFont($usepdffont, '', (int)$pdfparams['body_font_size']);
10018 if ($pdfparams['show_header'] == 0 || !(count($pdfparams['header_data']) > 0)) {
10019 $pdf->SetPrintHeader(false);
10020 }
10021 if ($pdfparams['show_footer'] == 0) {
10022 $pdf->SetPrintFooter(false);
10023 }
10024 $pdf->AddPage();
10025 $pdf->writeHTML($invoice_body, true, false, true, false, '');
10026 $pdf->lastPage();
10027 $pdf->Output($pathpdf, 'F');
10028 if (!file_exists($pathpdf)) {
10029 return false;
10030 } else {
10031 /**
10032 * @wponly - trigger files mirroring
10033 */
10034 VikBookingLoader::import('update.manager');
10035 VikBookingUpdateManager::triggerUploadBackup($pathpdf);
10036 //
10037 }
10038 //insert or update record for this invoice
10039 $invoice_id = 0;
10040 $q = "SELECT `id` FROM `#__vikbooking_invoices` WHERE `idorder`=".(int)$booking['id'].";";
10041 $dbo->setQuery($q);
10042 $dbo->execute();
10043 if ($dbo->getNumRows() > 0) {
10044 $invoice_data = $dbo->loadAssocList();
10045 $invoice_id = $invoice_data[0]['id'];
10046 }
10047 //Booking History
10048 self::getBookingHistoryInstance()->setBid($booking['id'])->store('BI', '#'.$invoice_num.$invoice_suff);
10049 //
10050 if ($invoice_id > 0) {
10051 //update
10052 $q = "UPDATE `#__vikbooking_invoices` SET `number`=".$dbo->quote($invoice_num.$invoice_suff).", `file_name`=".$dbo->quote($pdffname).", `created_on`=".time().", `for_date`=".(int)$used_date." WHERE `id`=".(int)$invoice_id.";";
10053 $dbo->setQuery($q);
10054 $dbo->execute();
10055 $retval = $invoice_id;
10056 } else {
10057 //insert
10058 $q = "INSERT INTO `#__vikbooking_invoices` (`number`,`file_name`,`idorder`,`idcustomer`,`created_on`,`for_date`) VALUES(".$dbo->quote($invoice_num.$invoice_suff).", ".$dbo->quote($pdffname).", ".(int)$booking['id'].", ".(int)$booking['idcustomer'].", ".time().", ".(int)$used_date.");";
10059 $dbo->setQuery($q);
10060 $dbo->execute();
10061 $lid = $dbo->insertid();
10062 $retval = $lid > 0 ? $lid : false;
10063 }
10064
10065 /**
10066 * The generation of the analogic invoices can trigger the drivers for the
10067 * generation of the e-Invoices if they are set to automatically run.
10068 * However, the e-Invoicing classes may be calling this method, so we need
10069 * to make sure the eInvoicing class is not running before proceeding.
10070 * This method could be called within a loop, and so the second iterations
10071 * may already have loaded the eInvocing class. For this we use a static variable.
10072 * This piece of code should run after updating the information of the PDF invoice.
10073 *
10074 * @since 1.11
10075 */
10076 static $einvocing_can_run = false;
10077 if (!defined('VBO_EINVOICING_RUN') && !$einvocing_can_run) {
10078 // allow second iterations calling this method to run
10079 $einvocing_can_run = true;
10080 }
10081 if ($einvocing_can_run === true) {
10082 $q = "SELECT * FROM `#__vikbooking_einvoicing_config` WHERE `automatic`=1;";
10083 $dbo->setQuery($q);
10084 $dbo->execute();
10085 if ($dbo->getNumRows()) {
10086 $drivers = $dbo->loadAssocList();
10087 // require the parent abstract class
10088 $driver_base = VBO_ADMIN_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'einvoicing' . DIRECTORY_SEPARATOR;
10089 require_once $driver_base . 'einvoicing.php';
10090
10091 /**
10092 * Trigger event to let other plugins register additional drivers.
10093 *
10094 * @return array A list of supported drivers.
10095 */
10096 $list = VBOFactory::getPlatform()->getDispatcher()->filter('onLoadEinvoicingDrivers');
10097
10098 // invoke all drivers that should run automatically
10099 $driver_base .= 'drivers' . DIRECTORY_SEPARATOR;
10100 foreach ($drivers as $driver) {
10101 $driver_file = $driver['driver'].'.php';
10102 $driver_path = $driver_base . $driver_file;
10103 if (is_file($driver_path)) {
10104 // require the driver sub-class
10105 require_once $driver_path;
10106 }
10107 // invoke the class
10108 $classname = 'VikBookingEInvoicing'.str_replace(' ', '', ucwords(str_replace('.php', '', str_replace('_', ' ', $driver_file))));
10109 if (!class_exists($classname)) {
10110 continue;
10111 }
10112 $driver_obj = new $classname;
10113 // set the flag of the external call
10114 $driver_obj->externalCall = __METHOD__;
10115 // inject invoice number to avoid discrepanices between analogic and electronic, maybe due to missing information for the e-Invoices
10116 $driver_obj->externalData['einvnum'] = $invoice_num;
10117 // generate the e-Invoice
10118 $driver_err = '';
10119 if (!$driver_obj->generateEInvoice((int)$booking['id'])) {
10120 $driver_err = $driver_obj->getError();
10121 VikError::raiseWarning('', $driver_err);
10122 }
10123 // Booking History - store log for the e-Invoice result
10124 self::getBookingHistoryInstance()->setBid($booking['id'])->store('BI', $driver_obj->getName().(!empty($driver_err) ? ': '.$driver_err : ''));
10125 }
10126 }
10127 }
10128 //
10129
10130 // return the result of the generation of the PDF invoice
10131 return $retval;
10132 }
10133
10134 /**
10135 * Generates an analogic invoice in PDF format for a custom list of services.
10136 * No bookings are assigned to this custom invoice. The method parses the same
10137 * invoice template as for the regular process with real booking IDs.
10138 * The invoice number must be stored before calling this method.
10139 *
10140 * @param int $invoice_id the ID of the custom invoice record
10141 *
10142 * @return boolean
10143 *
10144 * @since 1.11.1
10145 */
10146 public static function generateCustomInvoice($invoice_id) {
10147 $dbo = JFactory::getDbo();
10148 $mainframe = JFactory::getApplication();
10149 $q = "SELECT * FROM `#__vikbooking_invoices` WHERE `id`=".(int)$invoice_id." AND `idorder` < 0;";
10150 $dbo->setQuery($q);
10151 $dbo->execute();
10152 if (!$dbo->getNumRows()) {
10153 return false;
10154 }
10155 $invoice = $dbo->loadAssoc();
10156 $rawcont = !empty($invoice['rawcont']) ? json_decode($invoice['rawcont'], true) : array();
10157 $rawcont = is_array($rawcont) ? $rawcont : array();
10158 $rows = isset($rawcont['rows']) ? $rawcont['rows'] : array();
10159 $invoice['rawcont'] = $rawcont;
10160 $customer = self::getCPinIstance()->getCustomerByID($invoice['idcustomer']);
10161 if (!count($rawcont) || !count($rows) || !count($customer)) {
10162 // at least one invoice raw is mandatory as well as the customer
10163 return false;
10164 }
10165 $company_info = self::getInvoiceCompanyInfo();
10166 // load invoice template file
10167 list($invoicetpl, $pdfparams) = self::loadCustomInvoiceTmpl($invoice, $customer);
10168 // parse invoice template file
10169 $invoice_body = self::parseCustomInvoiceTemplate($invoicetpl, $invoice, $customer);
10170 //
10171 if (!class_exists('TCPDF')) {
10172 require_once(VBO_SITE_PATH . DIRECTORY_SEPARATOR . "helpers" . DIRECTORY_SEPARATOR . "tcpdf" . DIRECTORY_SEPARATOR . 'tcpdf.php');
10173 }
10174 $usepdffont = is_file(VBO_SITE_PATH . DIRECTORY_SEPARATOR . "helpers" . DIRECTORY_SEPARATOR . "tcpdf" . DIRECTORY_SEPARATOR . "fonts" . DIRECTORY_SEPARATOR . "dejavusans.php") ? 'dejavusans' : 'helvetica';
10175
10176 /**
10177 * Trigger event to allow third party plugins to return a specific font name.
10178 *
10179 * @since 1.16.0 (J) - 1.6.0 (WP)
10180 */
10181 $custom_pdf_font = VBOFactory::getPlatform()->getDispatcher()->filter('onGetPdfFontNameVikBooking', [$usepdffont]);
10182 if (is_array($custom_pdf_font) && !empty($custom_pdf_font[0])) {
10183 $usepdffont = $custom_pdf_font[0];
10184 }
10185
10186 $pdffname = $invoice['file_name'];
10187 $pathpdf = VBO_SITE_PATH . DIRECTORY_SEPARATOR . "helpers" . DIRECTORY_SEPARATOR . "invoices" . DIRECTORY_SEPARATOR . "generated" . DIRECTORY_SEPARATOR . $pdffname;
10188 if (file_exists($pathpdf)) @unlink($pathpdf);
10189 $pdf_page_format = is_array($pdfparams['pdf_page_format']) ? $pdfparams['pdf_page_format'] : constant($pdfparams['pdf_page_format']);
10190 $pdf = new TCPDF(constant($pdfparams['pdf_page_orientation']), constant($pdfparams['pdf_unit']), $pdf_page_format, true, 'UTF-8', false);
10191 $pdf->SetTitle(JText::translate('VBOINVNUM').' '.$invoice['number']);
10192 //Header for each page of the pdf
10193 if ($pdfparams['show_header'] == 1 && count($pdfparams['header_data']) > 0) {
10194 $pdf->SetHeaderData($pdfparams['header_data'][0], $pdfparams['header_data'][1], $pdfparams['header_data'][2], $pdfparams['header_data'][3], $pdfparams['header_data'][4], $pdfparams['header_data'][5]);
10195 }
10196 //Change some currencies to their unicode (decimal) value
10197 $currencyname = self::getCurrencyName();
10198 $unichr_map = array('EUR' => 8364, 'USD' => 36, 'AUD' => 36, 'CAD' => 36, 'GBP' => 163);
10199 if (array_key_exists($currencyname, $unichr_map)) {
10200 $invoice_body = str_replace($currencyname, TCPDF_FONTS::unichr($unichr_map[$currencyname]), $invoice_body);
10201 }
10202 //header and footer fonts
10203 $pdf->setHeaderFont(array($usepdffont, '', $pdfparams['header_font_size']));
10204 $pdf->setFooterFont(array($usepdffont, '', $pdfparams['footer_font_size']));
10205 //margins
10206 $pdf->SetMargins(constant($pdfparams['pdf_margin_left']), constant($pdfparams['pdf_margin_top']), constant($pdfparams['pdf_margin_right']));
10207 $pdf->SetHeaderMargin(constant($pdfparams['pdf_margin_header']));
10208 $pdf->SetFooterMargin(constant($pdfparams['pdf_margin_footer']));
10209 //
10210 $pdf->SetAutoPageBreak(true, constant($pdfparams['pdf_margin_bottom']));
10211 $pdf->setImageScale(constant($pdfparams['pdf_image_scale_ratio']));
10212 $pdf->SetFont($usepdffont, '', (int)$pdfparams['body_font_size']);
10213 if ($pdfparams['show_header'] == 0 || !(count($pdfparams['header_data']) > 0)) {
10214 $pdf->SetPrintHeader(false);
10215 }
10216 if ($pdfparams['show_footer'] == 0) {
10217 $pdf->SetPrintFooter(false);
10218 }
10219 $pdf->AddPage();
10220 $pdf->writeHTML($invoice_body, true, false, true, false, '');
10221 $pdf->lastPage();
10222 $pdf->Output($pathpdf, 'F');
10223 if (is_file($pathpdf)) {
10224 /**
10225 * @wponly - trigger files mirroring
10226 */
10227 VikBookingLoader::import('update.manager');
10228 VikBookingUpdateManager::triggerUploadBackup($pathpdf);
10229 //
10230 }
10231
10232 /**
10233 * The generation of the analogic invoices can trigger the drivers for the
10234 * generation of the e-Invoices if they are set to automatically run.
10235 * However, the e-Invoicing classes may be calling this method, so we need
10236 * to make sure the eInvoicing class is not running before proceeding.
10237 * This method could be called within a loop, and so the second iterations
10238 * may already have loaded the eInvocing class. For this we use a static variable.
10239 * This piece of code should run after updating the information of the PDF invoice.
10240 *
10241 * @since 1.11.1
10242 */
10243 $q = "SELECT * FROM `#__vikbooking_einvoicing_config` WHERE `automatic`=1;";
10244 $dbo->setQuery($q);
10245 $dbo->execute();
10246 if ($dbo->getNumRows()) {
10247 $drivers = $dbo->loadAssocList();
10248 // make sure the invoice number is just a number
10249 $invoice['number'] = str_replace(self::getInvoiceNumberSuffix(), '', $invoice['number']);
10250 // require the parent abstract class
10251 $driver_base = VBO_ADMIN_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'einvoicing' . DIRECTORY_SEPARATOR;
10252 require_once $driver_base . 'einvoicing.php';
10253
10254 /**
10255 * Trigger event to let other plugins register additional drivers.
10256 *
10257 * @return array A list of supported drivers.
10258 */
10259 $list = VBOFactory::getPlatform()->getDispatcher()->filter('onLoadEinvoicingDrivers');
10260
10261 // invoke all drivers that should run automatically
10262 $driver_base .= 'drivers' . DIRECTORY_SEPARATOR;
10263 foreach ($drivers as $driver) {
10264 $driver_file = $driver['driver'].'.php';
10265 $driver_path = $driver_base . $driver_file;
10266 if (is_file($driver_path)) {
10267 // require the driver sub-class
10268 require_once $driver_path;
10269 }
10270 // invoke the class
10271 $classname = 'VikBookingEInvoicing'.str_replace(' ', '', ucwords(str_replace('.php', '', str_replace('_', ' ', $driver_file))));
10272 if (!class_exists($classname)) {
10273 continue;
10274 }
10275 $driver_obj = new $classname;
10276 // set the flag of the external call
10277 $driver_obj->externalCall = __METHOD__;
10278 // inject custom invoice number, date and details to avoid discrepanices between analogic and electronic
10279 $driver_obj->externalData['einvnum'] = $invoice['number'];
10280 $driver_obj->externalData['einvdate'] = (int)$invoice['for_date'];
10281 $driver_obj->externalData['einvcustom'] = $invoice;
10282 // prepare data array for the generation of the e-Invoice
10283 $einvdata = $driver_obj->prepareCustomInvoiceData($invoice, $customer);
10284 // before generating the e-invoice, make sure to obliterate it if exists already (case of update custom invoice)
10285 $preveinv = $driver_obj->eInvoiceExists(array('idorder' => $einvdata[0]['id']));
10286 if ($preveinv !== false) {
10287 $driver_obj->obliterateEInvoice(array('id' => $preveinv));
10288 }
10289 // generate the e-Invoice
10290 $driver_err = '';
10291 if (!$driver_obj->generateEInvoice($einvdata)) {
10292 $driver_err = $driver_obj->getError();
10293 VikError::raiseWarning('', $driver_err);
10294 }
10295 }
10296 }
10297 //
10298
10299 // return the result of the generation of the PDF invoice
10300 return is_file($pathpdf);
10301 }
10302
10303 public static function sendBookingInvoice($invoice_id, $booking, $text = '', $subject = '') {
10304 if (!(count($booking) > 0) || empty($invoice_id) || empty($booking['custmail'])) {
10305 return false;
10306 }
10307 $dbo = JFactory::getDbo();
10308 $invoice_data = array();
10309 $q = "SELECT * FROM `#__vikbooking_invoices` WHERE `id`=".(int)$invoice_id.";";
10310 $dbo->setQuery($q);
10311 $dbo->execute();
10312 if ($dbo->getNumRows() == 1) {
10313 $invoice_data = $dbo->loadAssoc();
10314 }
10315 if (!(count($invoice_data) > 0)) {
10316 return false;
10317 }
10318 $mail_text = empty($text) ? JText::translate('VBOEMAILINVOICEATTACHTXT') : $text;
10319 $mail_subject = empty($subject) ? JText::translate('VBOEMAILINVOICEATTACHSUBJ') : $subject;
10320 $invoice_file_path = VBO_SITE_PATH . DIRECTORY_SEPARATOR . "helpers" . DIRECTORY_SEPARATOR . "invoices" . DIRECTORY_SEPARATOR . "generated" . DIRECTORY_SEPARATOR . $invoice_data['file_name'];
10321 if (!file_exists($invoice_file_path)) {
10322 return false;
10323 }
10324 if (!class_exists('VboApplication')) {
10325 require_once(VBO_ADMIN_PATH.DIRECTORY_SEPARATOR.'helpers'.DIRECTORY_SEPARATOR.'jv_helper.php');
10326 }
10327 $vbo_app = new VboApplication;
10328 $admin_sendermail = self::getSenderMail();
10329 $vbo_app->sendMail($admin_sendermail, $admin_sendermail, $booking['custmail'], $admin_sendermail, $mail_subject, $mail_text, (strpos($mail_text, '<') !== false && strpos($mail_text, '>') !== false ? true : false), 'base64', $invoice_file_path);
10330 //update record
10331 $q = "UPDATE `#__vikbooking_invoices` SET `emailed`=1, `emailed_to`=".$dbo->quote($booking['custmail'])." WHERE `id`=".(int)$invoice_id.";";
10332 $dbo->setQuery($q);
10333 $dbo->execute();
10334 //
10335 return true;
10336 }
10337
10338 public static function loadCheckinDocTmpl($booking_info = array(), $booking_rooms = array(), $customer = array()) {
10339 if (!defined('_VIKBOOKINGEXEC')) {
10340 define('_VIKBOOKINGEXEC', '1');
10341 }
10342 ob_start();
10343 include VBO_SITE_PATH . DIRECTORY_SEPARATOR . "helpers" . DIRECTORY_SEPARATOR . "checkins" . DIRECTORY_SEPARATOR . "checkin_tmpl.php";
10344 $content = ob_get_contents();
10345 ob_end_clean();
10346 $default_params = array(
10347 'show_header' => 0,
10348 'header_data' => array(),
10349 'show_footer' => 0,
10350 'pdf_page_orientation' => 'PDF_PAGE_ORIENTATION',
10351 'pdf_unit' => 'PDF_UNIT',
10352 'pdf_page_format' => 'PDF_PAGE_FORMAT',
10353 'pdf_margin_left' => 'PDF_MARGIN_LEFT',
10354 'pdf_margin_top' => 'PDF_MARGIN_TOP',
10355 'pdf_margin_right' => 'PDF_MARGIN_RIGHT',
10356 'pdf_margin_header' => 'PDF_MARGIN_HEADER',
10357 'pdf_margin_footer' => 'PDF_MARGIN_FOOTER',
10358 'pdf_margin_bottom' => 'PDF_MARGIN_BOTTOM',
10359 'pdf_image_scale_ratio' => 'PDF_IMAGE_SCALE_RATIO',
10360 'header_font_size' => '10',
10361 'body_font_size' => '10',
10362 'footer_font_size' => '8'
10363 );
10364 if (defined('_VIKBOOKING_CHECKIN_PARAMS') && isset($checkin_params) && @count($checkin_params) > 0) {
10365 $default_params = array_merge($default_params, $checkin_params);
10366 }
10367 return array($content, $default_params);
10368 }
10369
10370 public static function parseCheckinDocTemplate($checkintpl, $booking, $booking_rooms, $customer) {
10371 $parsed = $checkintpl;
10372 $dbo = JFactory::getDbo();
10373 $app = JFactory::getApplication();
10374 $nowdf = self::getDateFormat();
10375 if ($nowdf == "%d/%m/%Y") {
10376 $df='d/m/Y';
10377 } elseif ($nowdf == "%m/%d/%Y") {
10378 $df='m/d/Y';
10379 } else {
10380 $df='Y/m/d';
10381 }
10382 $datesep = self::getDateSeparator();
10383 $companylogo = self::getSiteLogo();
10384 $uselogo = '';
10385 if (!empty($companylogo)) {
10386 /**
10387 * Let's try to prevent TCPDF errors, as custom logos are always uploaded in /admin
10388 *
10389 * @since March 16th 2021
10390 */
10391 if (is_file(VBO_ADMIN_PATH . DIRECTORY_SEPARATOR . 'resources' . DIRECTORY_SEPARATOR . $companylogo)) {
10392 // $uselogo = '<img src="' . VBO_ADMIN_URI_REL . 'resources/' . $companylogo . '"/>';
10393 $uselogo = '<img src="' . VBO_ADMIN_URI . 'resources/' . $companylogo . '"/>';
10394 } elseif (is_file(VBO_SITE_PATH . DIRECTORY_SEPARATOR . 'resources' . DIRECTORY_SEPARATOR . $companylogo)) {
10395 // $uselogo = '<img src="' . VBO_SITE_URI_REL . 'resources/' . $companylogo . '"/>';
10396 $uselogo = '<img src="' . VBO_SITE_URI . 'resources/' . $companylogo . '"/>';
10397 }
10398 }
10399 $company_name = self::getFrontTitle();
10400 $company_info = self::getInvoiceCompanyInfo();
10401 $parsed = str_replace("{company_name}", $company_name, $parsed);
10402 $parsed = str_replace("{company_logo}", $uselogo, $parsed);
10403 $parsed = str_replace("{company_info}", $company_info, $parsed);
10404 $parsed = str_replace("{customer_info}", nl2br(rtrim($booking['custdata'], "\n")), $parsed);
10405 $parsed = str_replace("{checkin_date}", date(str_replace("/", $datesep, $df), $booking['checkin']), $parsed);
10406 $parsed = str_replace("{checkout_date}", date(str_replace("/", $datesep, $df), $booking['checkout']), $parsed);
10407 $parsed = str_replace("{num_nights}", $booking['days'], $parsed);
10408 $tot_guests = 0;
10409 $tot_adults = 0;
10410 $tot_children = 0;
10411 foreach ($booking_rooms as $kor => $or) {
10412 $tot_guests += ($or['adults'] + $or['children']);
10413 $tot_adults += $or['adults'];
10414 $tot_children += $or['children'];
10415 }
10416 $parsed = str_replace("{tot_guests}", $tot_guests, $parsed);
10417 $parsed = str_replace("{tot_adults}", $tot_adults, $parsed);
10418 $parsed = str_replace("{tot_children}", $tot_children, $parsed);
10419 if (count($customer) && isset($customer['comments'])) {
10420 $parsed = str_replace("{checkin_comments}", $customer['comments'], $parsed);
10421 }
10422 $termsconds = self::getTermsConditions();
10423 $parsed = str_replace("{terms_and_conditions}", $termsconds, $parsed);
10424 //custom fields replace
10425 preg_match_all('/\{customfield ([0-9]+)\}/U', $parsed, $cmatches);
10426 if (is_array($cmatches[1]) && @count($cmatches[1]) > 0) {
10427 $cfids = array();
10428 foreach ($cmatches[1] as $cfid ){
10429 $cfids[] = $cfid;
10430 }
10431 $q = "SELECT * FROM `#__vikbooking_custfields` WHERE `id` IN (".implode(", ", $cfids).");";
10432 $dbo->setQuery($q);
10433 $dbo->execute();
10434 $cfields = $dbo->getNumRows() > 0 ? $dbo->loadAssocList() : "";
10435 $vbo_tn->translateContents($cfields, '#__vikbooking_custfields');
10436 $cfmap = array();
10437 if (is_array($cfields)) {
10438 foreach ($cfields as $cf) {
10439 $cfmap[trim(JText::translate($cf['name']))] = $cf['id'];
10440 }
10441 }
10442 $cfmapreplace = array();
10443 $partsreceived = explode("\n", $booking['custdata']);
10444 if (count($partsreceived) > 0) {
10445 foreach ($partsreceived as $pst) {
10446 if (!empty($pst)) {
10447 $tmpdata = explode(":", $pst);
10448 if (array_key_exists(trim($tmpdata[0]), $cfmap)) {
10449 $cfmapreplace[$cfmap[trim($tmpdata[0])]] = trim($tmpdata[1]);
10450 }
10451 }
10452 }
10453 }
10454 foreach ($cmatches[1] as $cfid ){
10455 if (array_key_exists($cfid, $cfmapreplace)) {
10456 $parsed = str_replace("{customfield ".$cfid."}", $cfmapreplace[$cfid], $parsed);
10457 } else {
10458 $parsed = str_replace("{customfield ".$cfid."}", "", $parsed);
10459 }
10460 }
10461 }
10462 //end custom fields replace
10463
10464 return $parsed;
10465 }
10466
10467 /**
10468 * Returns an array of key-value pairs to be used for building the Guest Details
10469 * in the Check-in process. The keys will be compared to the fields of the table
10470 * #__customers to see if some values already exist. The values can use lang defs
10471 * of both front-end or back-end. To be called as list(fields, attributes).
10472 *
10473 * @param boolean $precheckin true if requested for front-end pre check-in.
10474 * @param string $type optional type of custom pax fields to force.
10475 *
10476 * @return array key-value pairs for showing and collecting guest details.
10477 *
10478 * @since 1.15.0 (J) - 1.5.0 (WP) introduced custom pax data collection type.
10479 * @since 1.16.3 (J) - 1.6.3 (WP) implemented hook to override the pre-checkin custom fields.
10480 */
10481 public static function getPaxFields($precheckin = false, $type = null)
10482 {
10483 if (!$precheckin) {
10484 // back-end check-in ("registration") key-value pairs
10485
10486 // check the type of pax fields collection data
10487 $collection_type = !empty($type) ? $type : VBOFactory::getConfig()->getString('checkindata', 'basic');
10488
10489 // return the requested pax fields collection list
10490 $custom_pax_fields = VBOCheckinPax::getFields($collection_type);
10491
10492 if (is_array($custom_pax_fields) && count($custom_pax_fields)) {
10493 // requested driver returned a list of fields
10494 return $custom_pax_fields;
10495 }
10496
10497 // in case the driver is unsupported, fallback to "basic" (default) driver
10498 return VBOCheckinPax::getFields('basic');
10499 }
10500
10501 // front-end key-value pairs for pre check-in
10502 $precheckin_pax_fields = [
10503 [
10504 'first_name' => JText::translate('VBCCFIRSTNAME'),
10505 'last_name' => JText::translate('VBCCLASTNAME'),
10506 'date_birth' => JText::translate('ORDER_DBIRTH'),
10507 'place_birth' => JText::translate('VBOCUSTPLACEBIRTH'),
10508 'country' => JText::translate('ORDER_STATE'),
10509 'city' => JText::translate('ORDER_CITY'),
10510 'zip' => JText::translate('ORDER_ZIP'),
10511 'nationality' => JText::translate('VBOCUSTNATIONALITY'),
10512 'gender' => JText::translate('VBOCUSTGENDER'),
10513 'doctype' => JText::translate('VBOCUSTDOCTYPE'),
10514 'docnum' => JText::translate('VBOCUSTDOCNUM'),
10515 'documents' => JText::translate('VBO_CUSTOMER_UPLOAD_DOCS'),
10516 ],
10517 [
10518 'first_name' => 'text',
10519 'last_name' => 'text',
10520 'date_birth' => 'calendar',
10521 'place_birth' => 'text',
10522 'country' => 'country',
10523 'city' => 'text',
10524 'zip' => 'text',
10525 'nationality' => 'country',
10526 'gender' => [
10527 'M' => JText::translate('VBOCUSTGENDERM'),
10528 'F' => JText::translate('VBOCUSTGENDERF'),
10529 ],
10530 'doctype' => 'text',
10531 'docnum' => 'text',
10532 'documents' => 'file',
10533 ],
10534 ];
10535
10536 // make a safe copy of the default precheckin fields
10537 $def_precheckin_pax_fields = $precheckin_pax_fields;
10538
10539 /**
10540 * Trigger event to allow third party plugins to override the default pre-checkin pax fields.
10541 *
10542 * @since 1.16.3 (J) - 1.6.3 (WP)
10543 */
10544 VBOFactory::getPlatform()->getDispatcher()->trigger('onDisplayPrecheckinPaxFieldsVikBooking', [&$precheckin_pax_fields]);
10545
10546 if (is_array($precheckin_pax_fields) && count($precheckin_pax_fields) > 1 && !empty($precheckin_pax_fields[0]) && !empty($precheckin_pax_fields[1])) {
10547 // use the filtered pax fields
10548 $def_precheckin_pax_fields = $precheckin_pax_fields;
10549 }
10550
10551 // return the front-end key-value pairs for pre check-in
10552 return $def_precheckin_pax_fields;
10553 }
10554
10555 /**
10556 * Returns the associative list of countries from the DB.
10557 *
10558 * @param bool $tn whether to translate the country name.
10559 * @param bool $no_id whether to unset the ID of the country.
10560 *
10561 * @return array associative or empty array.
10562 *
10563 * @since 1.15.0 (J) - 1.5.0 (WP) translations supported and applied by default.
10564 * @since 1.16.0 (J) - 1.6.0 (WP) added second argument.
10565 */
10566 public static function getCountriesArray($tn = true, $no_id = true)
10567 {
10568 $dbo = JFactory::getDbo();
10569 $all_countries = [];
10570
10571 $q = "SELECT " . ($no_id ? '`id`, `country_name`, `country_3_code`' : '*') . " FROM `#__vikbooking_countries` ORDER BY `country_name` ASC;";
10572 $dbo->setQuery($q);
10573 $dbo->execute();
10574 if (!$dbo->getNumRows()) {
10575 return [];
10576 }
10577 $countries = $dbo->loadAssocList();
10578
10579 if ($tn === true) {
10580 $vbo_tn = self::getTranslator();
10581 $vbo_tn->translateContents($countries, '#__vikbooking_countries');
10582 // re-apply sorting by country name
10583 $sorting = [];
10584 foreach ($countries as $country) {
10585 $sorting[$country['country_name']] = $country;
10586 }
10587 ksort($sorting);
10588 $sorted = [];
10589 foreach ($sorting as $country) {
10590 $sorted[] = $country;
10591 }
10592 $countries = $sorted;
10593 unset($sorting, $sorted);
10594 }
10595
10596 foreach ($countries as $v) {
10597 if ($no_id) {
10598 // keep the original structure by unsetting the ID only needed for translation
10599 unset($v['id']);
10600 }
10601 $all_countries[$v['country_3_code']] = $v;
10602 }
10603
10604 return $all_countries;
10605 }
10606
10607 public static function getCountriesSelect($name, $all_countries = array(), $current_value = '', $empty_value = ' ')
10608 {
10609 if (!count($all_countries)) {
10610 $all_countries = self::getCountriesArray();
10611 }
10612
10613 $countries = '<select name="'.$name.'">'."\n";
10614 if (strlen($empty_value)) {
10615 $countries .= '<option value="">'.$empty_value.'</option>'."\n";
10616 }
10617 foreach ($all_countries as $v) {
10618 $countries .= '<option value="'.$v['country_3_code'].'"'.($v['country_3_code'] == $current_value ? ' selected="selected"' : '').'>'.$v['country_name'].'</option>'."\n";
10619 }
10620 $countries .= '</select>';
10621
10622 return $countries;
10623 }
10624
10625 public static function getThumbSize($skipsession = false)
10626 {
10627 static $thumb_size = null;
10628
10629 if ($thumb_size) {
10630 return $thumb_size;
10631 }
10632
10633 $thumb_size = VBOFactory::getConfig()->get('thumbsize', 500);
10634
10635 return $thumb_size;
10636 }
10637
10638 /**
10639 * Checks whether an iCal file for the reservation should be
10640 * attached to the confirmation email for customer and/or admin.
10641 *
10642 * @return int 1=admin+customer, 2=admin, 3=customer, 0=no
10643 *
10644 * @since 1.12.0 (J) - 1.2.0 (WP)
10645 */
10646 public static function attachIcal()
10647 {
10648 return VBOFactory::getConfig()->getInt('attachical', 1);
10649 }
10650
10651 /**
10652 * How the calculation of the orphan dates should take place.
10653 *
10654 * @return string "next" for only checking the bookings ahead, "prevnext" if
10655 * also the previous bookings should be checked.
10656 *
10657 * @since 1.13 (J) - 1.3.0 (WP)
10658 */
10659 public static function orphansCalculation()
10660 {
10661 return VBOFactory::getConfig()->get('orphanscalculation', 'next');
10662 }
10663
10664 /**
10665 * Returns the name of the template file to use for the front-end View "search".
10666 *
10667 * @return string the name of the template file. New and upgraded user will both use the classic file.
10668 *
10669 * @since 1.13 (J) - 1.3.0 (WP)
10670 */
10671 public static function searchResultsTmpl()
10672 {
10673 return VBOFactory::getConfig()->get('searchrestmpl', 'classic');
10674 }
10675
10676 /**
10677 * Returns a string without any new-line characters
10678 * to be used for JavaScript values without facing
10679 * errors like 'unterminated string literal'.
10680 * By passing nl2br($str) as argument, we can keep
10681 * the wanted new-line HTML tags for PRE tags.
10682 * We use implode() with just one argument to
10683 * not use an empty string as glue for the string.
10684 *
10685 * @param $str string
10686 *
10687 * @return string
10688 */
10689 public static function strTrimLiteral($str) {
10690 $str = str_replace(array("\r\n", "\r"), "\n", $str);
10691 $lines = explode("\n", $str);
10692 $new_lines = array();
10693 foreach ($lines as $i => $line) {
10694 if (strlen($line)) {
10695 $new_lines[] = trim($line);
10696 }
10697 }
10698 return implode($new_lines);
10699 }
10700
10701 /**
10702 * Returns an instance of the VboApplication class.
10703 *
10704 * @return VboApplication
10705 */
10706 public static function getVboApplication()
10707 {
10708 // require library
10709 require_once VBO_ADMIN_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'jv_helper.php';
10710
10711 // return a new instance
10712 return new VboApplication();
10713 }
10714
10715 /**
10716 * Returns the translation of the given 0-indexed week day.
10717 *
10718 * @param int $wd the 0-indexed day of the week.
10719 * @param bool $short whether to get the short version of the weekday.
10720 *
10721 * @return string the name of the week day.
10722 *
10723 * @since 1.15.0 (J) - 1.5.0 (WP) added support for short translation.
10724 */
10725 public static function sayWeekDay($wd, $short = false)
10726 {
10727 switch ($wd) {
10728 case '6' :
10729 $ret = $short ? JText::translate('VBSAT') : JText::translate('VBWEEKDAYSIX');
10730 break;
10731 case '5' :
10732 $ret = $short ? JText::translate('VBFRI') : JText::translate('VBWEEKDAYFIVE');
10733 break;
10734 case '4' :
10735 $ret = $short ? JText::translate('VBTHU') : JText::translate('VBWEEKDAYFOUR');
10736 break;
10737 case '3' :
10738 $ret = $short ? JText::translate('VBWED') : JText::translate('VBWEEKDAYTHREE');
10739 break;
10740 case '2' :
10741 $ret = $short ? JText::translate('VBTUE') : JText::translate('VBWEEKDAYTWO');
10742 break;
10743 case '1' :
10744 $ret = $short ? JText::translate('VBMON') : JText::translate('VBWEEKDAYONE');
10745 break;
10746 default :
10747 $ret = $short ? JText::translate('VBSUN') : JText::translate('VBWEEKDAYZERO');
10748 break;
10749 }
10750 return $ret;
10751 }
10752
10753 /**
10754 * Returns the translation of the given 1-indexed month of the year.
10755 *
10756 * @param int $idm the 1-indexed month of the year.
10757 * @param bool $short whether to get the short version of the month.
10758 *
10759 * @return string the name of the month.
10760 *
10761 * @since 1.15.0 (J) - 1.5.0 (WP) added support for short translation.
10762 */
10763 public static function sayMonth($idm, $short = false)
10764 {
10765 switch ($idm) {
10766 case '12' :
10767 $ret = $short ? JText::translate('VBSHORTMONTHTWELVE') : JText::translate('VBMONTHTWELVE');
10768 break;
10769 case '11' :
10770 $ret = $short ? JText::translate('VBSHORTMONTHELEVEN') : JText::translate('VBMONTHELEVEN');
10771 break;
10772 case '10' :
10773 $ret = $short ? JText::translate('VBSHORTMONTHTEN') : JText::translate('VBMONTHTEN');
10774 break;
10775 case '9' :
10776 $ret = $short ? JText::translate('VBSHORTMONTHNINE') : JText::translate('VBMONTHNINE');
10777 break;
10778 case '8' :
10779 $ret = $short ? JText::translate('VBSHORTMONTHEIGHT') : JText::translate('VBMONTHEIGHT');
10780 break;
10781 case '7' :
10782 $ret = $short ? JText::translate('VBSHORTMONTHSEVEN') : JText::translate('VBMONTHSEVEN');
10783 break;
10784 case '6' :
10785 $ret = $short ? JText::translate('VBSHORTMONTHSIX') : JText::translate('VBMONTHSIX');
10786 break;
10787 case '5' :
10788 $ret = $short ? JText::translate('VBSHORTMONTHFIVE') : JText::translate('VBMONTHFIVE');
10789 break;
10790 case '4' :
10791 $ret = $short ? JText::translate('VBSHORTMONTHFOUR') : JText::translate('VBMONTHFOUR');
10792 break;
10793 case '3' :
10794 $ret = $short ? JText::translate('VBSHORTMONTHTHREE') : JText::translate('VBMONTHTHREE');
10795 break;
10796 case '2' :
10797 $ret = $short ? JText::translate('VBSHORTMONTHTWO') : JText::translate('VBMONTHTWO');
10798 break;
10799 default :
10800 $ret = $short ? JText::translate('VBSHORTMONTHONE') : JText::translate('VBMONTHONE');
10801 break;
10802 }
10803 return $ret;
10804 }
10805
10806 public static function sayDayMonth($d) {
10807 switch ($d) {
10808 case '31' :
10809 $ret = JText::translate('VBDAYMONTHTHIRTYONE');
10810 break;
10811 case '30' :
10812 $ret = JText::translate('VBDAYMONTHTHIRTY');
10813 break;
10814 case '29' :
10815 $ret = JText::translate('VBDAYMONTHTWENTYNINE');
10816 break;
10817 case '28' :
10818 $ret = JText::translate('VBDAYMONTHTWENTYEIGHT');
10819 break;
10820 case '27' :
10821 $ret = JText::translate('VBDAYMONTHTWENTYSEVEN');
10822 break;
10823 case '26' :
10824 $ret = JText::translate('VBDAYMONTHTWENTYSIX');
10825 break;
10826 case '25' :
10827 $ret = JText::translate('VBDAYMONTHTWENTYFIVE');
10828 break;
10829 case '24' :
10830 $ret = JText::translate('VBDAYMONTHTWENTYFOUR');
10831 break;
10832 case '23' :
10833 $ret = JText::translate('VBDAYMONTHTWENTYTHREE');
10834 break;
10835 case '22' :
10836 $ret = JText::translate('VBDAYMONTHTWENTYTWO');
10837 break;
10838 case '21' :
10839 $ret = JText::translate('VBDAYMONTHTWENTYONE');
10840 break;
10841 case '20' :
10842 $ret = JText::translate('VBDAYMONTHTWENTY');
10843 break;
10844 case '19' :
10845 $ret = JText::translate('VBDAYMONTHNINETEEN');
10846 break;
10847 case '18' :
10848 $ret = JText::translate('VBDAYMONTHEIGHTEEN');
10849 break;
10850 case '17' :
10851 $ret = JText::translate('VBDAYMONTHSEVENTEEN');
10852 break;
10853 case '16' :
10854 $ret = JText::translate('VBDAYMONTHSIXTEEN');
10855 break;
10856 case '15' :
10857 $ret = JText::translate('VBDAYMONTHFIFTEEN');
10858 break;
10859 case '14' :
10860 $ret = JText::translate('VBDAYMONTHFOURTEEN');
10861 break;
10862 case '13' :
10863 $ret = JText::translate('VBDAYMONTHTHIRTEEN');
10864 break;
10865 case '12' :
10866 $ret = JText::translate('VBDAYMONTHTWELVE');
10867 break;
10868 case '11' :
10869 $ret = JText::translate('VBDAYMONTHELEVEN');
10870 break;
10871 case '10' :
10872 $ret = JText::translate('VBDAYMONTHTEN');
10873 break;
10874 case '9' :
10875 $ret = JText::translate('VBDAYMONTHNINE');
10876 break;
10877 case '8' :
10878 $ret = JText::translate('VBDAYMONTHEIGHT');
10879 break;
10880 case '7' :
10881 $ret = JText::translate('VBDAYMONTHSEVEN');
10882 break;
10883 case '6' :
10884 $ret = JText::translate('VBDAYMONTHSIX');
10885 break;
10886 case '5' :
10887 $ret = JText::translate('VBDAYMONTHFIVE');
10888 break;
10889 case '4' :
10890 $ret = JText::translate('VBDAYMONTHFOUR');
10891 break;
10892 case '3' :
10893 $ret = JText::translate('VBDAYMONTHTHREE');
10894 break;
10895 case '2' :
10896 $ret = JText::translate('VBDAYMONTHTWO');
10897 break;
10898 default :
10899 $ret = JText::translate('VBDAYMONTHONE');
10900 break;
10901 }
10902 return $ret;
10903 }
10904
10905 public static function totElements($arr) {
10906 $n = 0;
10907 if (is_array($arr)) {
10908 foreach ($arr as $a) {
10909 if (!empty($a)) {
10910 $n++;
10911 }
10912 }
10913 return $n;
10914 }
10915 return false;
10916 }
10917
10918 /**
10919 * Returns a list of documents that were uploaded
10920 * for the specified customer.
10921 *
10922 * @param integer $id The customer ID.
10923 *
10924 * @return array A list of documents.
10925 *
10926 * @since 1.3.0
10927 */
10928 public static function getCustomerDocuments($id)
10929 {
10930 $dbo = JFactory::getDbo();
10931
10932 $q = $dbo->getQuery(true)
10933 ->select($dbo->qn('docsfolder'))
10934 ->from($dbo->qn('#__vikbooking_customers'))
10935 ->where($dbo->qn('id') . ' = ' . (int) $id);
10936
10937 $dbo->setQuery($q, 0, 1);
10938 $dbo->execute();
10939
10940 if (!$dbo->getNumRows())
10941 {
10942 // customer not found
10943 return array();
10944 }
10945
10946 // retrieve customer documents directory name
10947 $dirname = $dbo->loadResult();
10948
10949 if (empty($dirname))
10950 {
10951 // no available directory
10952 return array();
10953 }
10954
10955 // build documents folder path
10956 $dirname = VBO_CUSTOMERS_PATH . DIRECTORY_SEPARATOR . $dirname;
10957
10958 if (!is_dir($dirname))
10959 {
10960 // the customer directory doesn't exist
10961 return array();
10962 }
10963
10964 // read all files from customer directory
10965 $glob = glob($dirname . DIRECTORY_SEPARATOR . '*');
10966
10967 $files = array();
10968
10969 foreach ($glob as $path)
10970 {
10971 // skip "index.html"
10972 if (!preg_match("/[\/\\\\]index\.html$/i", $path))
10973 {
10974 // extract name and extension from file path
10975 if (preg_match("/(.*)\.([a-z0-9]{2,})$/i", basename($path), $match))
10976 {
10977 $name = $match[1];
10978 $ext = $match[2];
10979 }
10980 else
10981 {
10982 $name = basename($path);
10983 $ext = '';
10984 }
10985
10986 $file = new stdClass;
10987 $file->path = $path;
10988 $file->name = $name;
10989 $file->ext = $ext;
10990 $file->basename = $file->name . '.' . $file->ext;
10991 $file->size = filesize($path);
10992 $file->date = filemtime($path);
10993 $file->url = str_replace(DIRECTORY_SEPARATOR, '/', str_replace(VBO_CUSTOMERS_PATH . DIRECTORY_SEPARATOR, VBO_CUSTOMERS_URI, $file->path));
10994
10995 $files[] = $file;
10996 }
10997 }
10998
10999 // sort files by creation date
11000 usort($files, function($a, $b)
11001 {
11002 return $b->date - $a->date;
11003 });
11004
11005 return $files;
11006 }
11007
11008 public static function displayPaymentParameters($pfile, $pparams = '') {
11009 $html = '<p>---------</p>';
11010
11011 /**
11012 * @wponly The payment gateway is now loaded
11013 * using the apposite dispatcher.
11014 *
11015 * @since 1.0.5
11016 */
11017 JLoader::import('adapter.payment.dispatcher');
11018
11019 try
11020 {
11021 $payment = JPaymentDispatcher::getInstance('vikbooking', $pfile);
11022 }
11023 catch (Exception $e)
11024 {
11025 // payment not found
11026 $html = $e->getMessage();
11027
11028 if ($code = $e->getCode())
11029 {
11030 $html = '<b>' . $code . '</b> : ' . $html;
11031 }
11032
11033 return $html;
11034 }
11035 //
11036
11037 $arrparams = !empty($pparams) ? json_decode($pparams, true) : array();
11038 $arrparams = !is_array($arrparams) ? array() : $arrparams;
11039
11040 // get admin parameters
11041 $pconfig = $payment->getAdminParameters();
11042
11043 if (count($pconfig) > 0) {
11044 $html = '';
11045 foreach ($pconfig as $value => $cont) {
11046 if (empty($value)) {
11047 continue;
11048 }
11049 $labelparts = explode('//', (isset($cont['label']) ? $cont['label'] : ''));
11050 $label = $labelparts[0];
11051 $labelhelp = isset($labelparts[1]) ? $labelparts[1] : '';
11052 if (!empty($cont['help'])) {
11053 $labelhelp = $cont['help'];
11054 }
11055 $default_paramv = isset($cont['default']) ? $cont['default'] : null;
11056 $html .= '<div class="vbo-param-container">';
11057 if (strlen($label) > 0 && (!isset($cont['hidden']) || $cont['hidden'] != true)) {
11058 $html .= '<div class="vbo-param-label">'.$label.'</div>';
11059 }
11060 $html .= '<div class="vbo-param-setting">';
11061 switch ($cont['type']) {
11062 case 'custom':
11063 $html .= $cont['html'];
11064 break;
11065 case 'select':
11066 $options = isset($cont['options']) && is_array($cont['options']) ? $cont['options'] : array();
11067 $is_assoc = (array_keys($options) !== range(0, count($options) - 1));
11068 if (isset($cont['multiple']) && $cont['multiple']) {
11069 $html .= '<select name="vikpaymentparams['.$value.'][]" multiple="multiple">';
11070 } else {
11071 $html .= '<select name="vikpaymentparams['.$value.']">';
11072 }
11073 foreach ($options as $optkey => $poption) {
11074 $checkval = $is_assoc ? $optkey : $poption;
11075 $selected = false;
11076 if (isset($arrparams[$value])) {
11077 if (is_array($arrparams[$value])) {
11078 $selected = in_array($checkval, $arrparams[$value]);
11079 } else {
11080 $selected = ($checkval == $arrparams[$value]);
11081 }
11082 } elseif (isset($default_paramv)) {
11083 if (is_array($default_paramv)) {
11084 $selected = in_array($checkval, $default_paramv);
11085 } else {
11086 $selected = ($checkval == $default_paramv);
11087 }
11088 }
11089 $html .= '<option value="' . ($is_assoc ? $optkey : $poption) . '"'.($selected ? ' selected="selected"' : '').'>'.$poption.'</option>';
11090 }
11091 $html .= '</select>';
11092 break;
11093 case 'password':
11094 $html .= '<div class="btn-wrapper input-append">';
11095 $html .= '<input type="password" name="vikpaymentparams['.$value.']" value="'.(isset($arrparams[$value]) ? JHtml::fetch('esc_attr', $arrparams[$value]) : JHtml::fetch('esc_attr', $default_paramv)).'" size="20"/>';
11096 $html .= '<button type="button" class="btn btn-primary" onclick="vikPaymentParamTogglePwd(this);"><i class="' . VikBookingIcons::i('eye') . '"></i></button>';
11097 $html .= '</div>';
11098 break;
11099 case 'number':
11100 $number_attr = array();
11101 if (isset($cont['min'])) {
11102 $number_attr[] = 'min="' . JHtml::fetch('esc_attr', $cont['min']) . '"';
11103 }
11104 if (isset($cont['max'])) {
11105 $number_attr[] = 'max="' . JHtml::fetch('esc_attr', $cont['max']) . '"';
11106 }
11107 if (isset($cont['step'])) {
11108 $number_attr[] = 'step="' . JHtml::fetch('esc_attr', $cont['step']) . '"';
11109 }
11110 $html .= '<input type="number" name="vikpaymentparams['.$value.']" value="'.(isset($arrparams[$value]) ? JHtml::fetch('esc_attr', $arrparams[$value]) : JHtml::fetch('esc_attr', $default_paramv)).'" ' . implode(' ', $number_attr) . '/>';
11111 break;
11112 case 'textarea':
11113 $html .= '<textarea name="vikpaymentparams['.$value.']">'.(isset($arrparams[$value]) ? JHtml::fetch('esc_textarea', $arrparams[$value]) : JHtml::fetch('esc_textarea', $default_paramv)).'</textarea>';
11114 break;
11115 case 'hidden':
11116 $html .= '<input type="hidden" name="vikpaymentparams['.$value.']" value="'.(isset($arrparams[$value]) ? JHtml::fetch('esc_attr', $arrparams[$value]) : JHtml::fetch('esc_attr', $default_paramv)).'"/>';
11117 break;
11118 case 'checkbox':
11119 // always display a hidden input value turned off before the actual checkbox to support the "off" (0) status
11120 $html .= '<input type="hidden" name="vikpaymentparams['.$value.']" value="0" />';
11121 $html .= self::getVboApplication()->printYesNoButtons('vikpaymentparams['.$value.']', JText::translate('VBYES'), JText::translate('VBNO'), (isset($arrparams[$value]) ? (int)$arrparams[$value] : (int)$default_paramv), 1, 0);
11122 break;
11123 default:
11124 $html .= '<input type="text" name="vikpaymentparams['.$value.']" value="'.(isset($arrparams[$value]) ? JHtml::fetch('esc_attr', $arrparams[$value]) : JHtml::fetch('esc_attr', $default_paramv)).'" size="20"/>';
11125 break;
11126 }
11127 if (strlen($labelhelp) > 0) {
11128 $html .= '<span class="vbo-param-setting-comment">'.$labelhelp.'</span>';
11129 }
11130 $html .= '</div>';
11131 $html .= '</div>';
11132 }
11133 // JS helper function to toggle the password fields
11134 $html .= "\n" . '<script>' . "\n";
11135 $html .= 'function vikPaymentParamTogglePwd(elem) {' . "\n";
11136 $html .= ' var btn = jQuery(elem), inp = btn.parent().find("input").first();' . "\n";
11137 $html .= ' if (!inp || !inp.length) {return false;}' . "\n";
11138 $html .= ' var inp_type = inp.attr("type");' . "\n";
11139 $html .= ' inp.attr("type", (inp_type == "password" ? "text" : "password"));' . "\n";
11140 $html .= '}' . "\n";
11141 $html .= "\n" . '</script>' . "\n";
11142 }
11143
11144 return $html;
11145 }
11146
11147 public static function displaySMSParameters($pfile, $params = null)
11148 {
11149 $html = '---------';
11150
11151 $params = is_string($params) ? json_decode($params, true) : $params;
11152 $params = is_array($params) ? $params : [];
11153
11154 if (empty($pfile) || !is_file(VBO_ADMIN_PATH . DIRECTORY_SEPARATOR . 'smsapi' . DIRECTORY_SEPARATOR . $pfile)) {
11155 return $html;
11156 }
11157
11158 // attempt to load the class file
11159 require_once(VBO_ADMIN_PATH . DIRECTORY_SEPARATOR . 'smsapi' . DIRECTORY_SEPARATOR . $pfile);
11160
11161 if (!class_exists('VikSmsApi') || !method_exists('VikSmsApi', 'getAdminParameters')) {
11162 return $html;
11163 }
11164
11165 // load the gateway parameters
11166 $config = VikSmsApi::getAdminParameters();
11167
11168 if (!is_array($config) || !count($config)) {
11169 return $html;
11170 }
11171
11172 // flags for JS helpers
11173 $js_helpers = array();
11174
11175 $html = '';
11176 foreach ($config as $value => $cont) {
11177 if (empty($value)) {
11178 continue;
11179 }
11180 $inp_attr = '';
11181 if (isset($cont['attributes'])) {
11182 foreach ($cont['attributes'] as $inpk => $inpv) {
11183 $inp_attr .= $inpk.'="'.$inpv.'" ';
11184 }
11185 $inp_attr = ' ' . rtrim($inp_attr);
11186 }
11187 $labelparts = explode('//', (isset($cont['label']) ? $cont['label'] : ''));
11188 $label = $labelparts[0];
11189 $labelhelp = isset($labelparts[1]) ? $labelparts[1] : '';
11190 if (!empty($cont['help'])) {
11191 $labelhelp = $cont['help'];
11192 }
11193 $default_paramv = isset($cont['default']) ? $cont['default'] : null;
11194 $html .= '<div class="vbo-param-container' . (in_array($cont['type'], array('textarea', 'visual_html')) ? ' vbo-param-container-full' : '') . '">';
11195 if (strlen($label) > 0 && (!isset($cont['hidden']) || $cont['hidden'] != true)) {
11196 $html .= '<div class="vbo-param-label">'.$label.'</div>';
11197 }
11198 $html .= '<div class="vbo-param-setting">';
11199 switch ($cont['type']) {
11200 case 'custom':
11201 $html .= $cont['html'];
11202 break;
11203 case 'select':
11204 $options = isset($cont['options']) && is_array($cont['options']) ? $cont['options'] : array();
11205 $is_assoc = (array_keys($options) !== range(0, count($options) - 1));
11206 if (isset($cont['multiple']) && $cont['multiple']) {
11207 $html .= '<select name="viksmsparams['.$value.'][]" multiple="multiple"' . $inp_attr . '>';
11208 } else {
11209 $html .= '<select name="viksmsparams['.$value.']"' . $inp_attr . '>';
11210 }
11211 foreach ($options as $optkey => $poption) {
11212 $checkval = $is_assoc ? $optkey : $poption;
11213 $selected = false;
11214 if (isset($params[$value])) {
11215 if (is_array($params[$value])) {
11216 $selected = in_array($checkval, $params[$value]);
11217 } else {
11218 $selected = ($checkval == $params[$value]);
11219 }
11220 } elseif (isset($default_paramv)) {
11221 if (is_array($default_paramv)) {
11222 $selected = in_array($checkval, $default_paramv);
11223 } else {
11224 $selected = ($checkval == $default_paramv);
11225 }
11226 }
11227 $html .= '<option value="' . ($is_assoc ? $optkey : $poption) . '"'.($selected ? ' selected="selected"' : '').'>'.$poption.'</option>';
11228 }
11229 $html .= '</select>';
11230 break;
11231 case 'password':
11232 $html .= '<div class="btn-wrapper input-append">';
11233 $html .= '<input type="password" name="viksmsparams['.$value.']" value="'.(isset($params[$value]) ? JHtml::fetch('esc_attr', $params[$value]) : JHtml::fetch('esc_attr', $default_paramv)).'" size="20"' . $inp_attr . '/>';
11234 $html .= '<button type="button" class="btn btn-primary" onclick="vikSMSParamTogglePwd(this);"><i class="' . VikBookingIcons::i('eye') . '"></i></button>';
11235 $html .= '</div>';
11236 // set flag for JS helper
11237 $js_helpers[] = $cont['type'];
11238 break;
11239 case 'number':
11240 $number_attr = array();
11241 if (isset($cont['min'])) {
11242 $number_attr[] = 'min="' . JHtml::fetch('esc_attr', $cont['min']) . '"';
11243 }
11244 if (isset($cont['max'])) {
11245 $number_attr[] = 'max="' . JHtml::fetch('esc_attr', $cont['max']) . '"';
11246 }
11247 if (isset($cont['step'])) {
11248 $number_attr[] = 'step="' . JHtml::fetch('esc_attr', $cont['step']) . '"';
11249 }
11250 $html .= '<input type="number" name="viksmsparams['.$value.']" value="'.(isset($params[$value]) ? JHtml::fetch('esc_attr', $params[$value]) : JHtml::fetch('esc_attr', $default_paramv)).'" ' . implode(' ', $number_attr) . $inp_attr . '/>';
11251 break;
11252 case 'textarea':
11253 $html .= '<textarea name="viksmsparams['.$value.']"' . $inp_attr . '>'.(isset($params[$value]) ? JHtml::fetch('esc_textarea', $params[$value]) : JHtml::fetch('esc_textarea', $default_paramv)).'</textarea>';
11254 break;
11255 case 'visual_html':
11256 $tarea_cont = isset($params[$value]) ? JHtml::fetch('esc_textarea', $params[$value]) : JHtml::fetch('esc_textarea', $default_paramv);
11257 $tarea_attr = isset($cont['attributes']) && is_array($cont['attributes']) ? $cont['attributes'] : array();
11258 $editor_opts = isset($cont['editor_opts']) && is_array($cont['editor_opts']) ? $cont['editor_opts'] : array();
11259 $editor_btns = isset($cont['editor_btns']) && is_array($cont['editor_btns']) ? $cont['editor_btns'] : array();
11260 $html .= self::getVboApplication()->renderVisualEditor('viksmsparams[' . $value . ']', $tarea_cont, $tarea_attr, $editor_opts, $editor_btns);
11261 break;
11262 case 'hidden':
11263 $html .= '<input type="hidden" name="viksmsparams['.$value.']" value="'.(isset($params[$value]) ? JHtml::fetch('esc_attr', $params[$value]) : JHtml::fetch('esc_attr', $default_paramv)).'"' . $inp_attr . '/>';
11264 break;
11265 case 'checkbox':
11266 // always display a hidden input value turned off before the actual checkbox to support the "off" (0) status
11267 $html .= '<input type="hidden" name="viksmsparams['.$value.']" value="0" />';
11268 $html .= self::getVboApplication()->printYesNoButtons('viksmsparams['.$value.']', JText::translate('VBYES'), JText::translate('VBNO'), (isset($params[$value]) ? (int)$params[$value] : (int)$default_paramv), 1, 0);
11269 break;
11270 default:
11271 $html .= '<input type="text" name="viksmsparams['.$value.']" value="'.(isset($params[$value]) ? JHtml::fetch('esc_attr', $params[$value]) : JHtml::fetch('esc_attr', $default_paramv)).'" size="20"' . $inp_attr . '/>';
11272 break;
11273 }
11274 if (strlen($labelhelp) > 0) {
11275 $html .= '<span class="vbo-param-setting-comment">'.$labelhelp.'</span>';
11276 }
11277 $html .= '</div>';
11278 $html .= '</div>';
11279 }
11280
11281 // JS helper functions
11282 if (in_array('password', $js_helpers)) {
11283 // toggle the password fields
11284 $html .= "\n" . '<script>' . "\n";
11285 $html .= 'function vikSMSParamTogglePwd(elem) {' . "\n";
11286 $html .= ' var btn = jQuery(elem), inp = btn.parent().find("input").first();' . "\n";
11287 $html .= ' if (!inp || !inp.length) {return false;}' . "\n";
11288 $html .= ' var inp_type = inp.attr("type");' . "\n";
11289 $html .= ' inp.attr("type", (inp_type == "password" ? "text" : "password"));' . "\n";
11290 $html .= '}' . "\n";
11291 $html .= "\n" . '</script>' . "\n";
11292 }
11293
11294 return $html;
11295 }
11296
11297 /**
11298 * Renders the params of the requested cron job file-class.
11299 *
11300 * @param string $file the name of the cron job driver file.
11301 * @param array $params the parameters array.
11302 *
11303 * @return string the necessary HTML content to render.
11304 *
11305 * @since 1.15.0 (J) - 1.5.0 (WP) - support for new fields added.
11306 */
11307 public static function displayCronParameters($file, $params = [])
11308 {
11309 try
11310 {
11311 // attempt to create a new instance
11312 $job = VBOFactory::getCronFactory()->createInstance($file);
11313 }
11314 catch (Exception $e)
11315 {
11316 // something went wrong, display error message
11317 return '<p>' . $e->getMessage() . '<p>';
11318 }
11319
11320 // get admin parameters
11321 $config = $job->getForm();
11322
11323 if (!is_array($config) || !count($config))
11324 {
11325 return '<p>---------</p>';
11326 }
11327
11328 // flags for JS helpers
11329 $js_helpers = array();
11330
11331 $html = '';
11332 foreach ($config as $value => $cont) {
11333 if (empty($value)) {
11334 continue;
11335 }
11336
11337 $inp_attr = '';
11338 if (isset($cont['attributes']) && is_array($cont['attributes'])) {
11339 foreach ($cont['attributes'] as $inpk => $inpv) {
11340 $inp_attr .= $inpk . '="' . $inpv . '" ';
11341 }
11342 $inp_attr = ' ' . rtrim($inp_attr);
11343 }
11344
11345 $labelparts = explode('//', (isset($cont['label']) ? $cont['label'] : ''));
11346 $label = $labelparts[0];
11347 $labelhelp = isset($labelparts[1]) ? $labelparts[1] : '';
11348 if (!empty($cont['help'])) {
11349 $labelhelp = $cont['help'];
11350 }
11351
11352 $default_paramv = isset($cont['default']) ? $cont['default'] : null;
11353 $nested_style = (isset($cont['nested']) && $cont['nested']);
11354 $hidden_wrapper = false;
11355 if (isset($cont['conditional']) && isset($config[$cont['conditional']])) {
11356 $check_cond = isset($config[$cont['conditional']]['default']) ? $config[$cont['conditional']]['default'] : null;
11357 $check_cond = isset($params[$cont['conditional']]) ? $params[$cont['conditional']] : $check_cond;
11358 if (!is_null($check_cond) && (!$check_cond || !strcasecmp($check_cond, 'off'))) {
11359 // hide current field because the field to who this is dependant is "off" or disabled (i.e. 0)
11360 $hidden_wrapper = true;
11361 }
11362 }
11363
11364 $html .= '<div class="vbo-param-container' . (in_array($cont['type'], ['textarea', 'visual_html']) ? ' vbo-param-container-full' : '') . ($nested_style ? ' vbo-param-nested' : '') . '"' . ($hidden_wrapper ? ' style="display: none;"' : '') . '>';
11365 if (strlen($label) > 0 && (!isset($cont['hidden']) || $cont['hidden'] != true)) {
11366 $html .= '<div class="vbo-param-label">' . $label . '</div>';
11367 }
11368 $html .= '<div class="vbo-param-setting">';
11369
11370 switch ($cont['type']) {
11371 case 'custom':
11372 $html .= $cont['html'];
11373 break;
11374 case 'select':
11375 $options = isset($cont['options']) && is_array($cont['options']) ? $cont['options'] : [];
11376 $is_assoc = (array_keys($options) !== range(0, count($options) - 1));
11377 if (isset($cont['multiple']) && $cont['multiple']) {
11378 $html .= '<select name="vikcronparams['.$value.'][]" multiple="multiple"' . $inp_attr . '>';
11379 } else {
11380 $html .= '<select name="vikcronparams['.$value.']"' . $inp_attr . '>';
11381 }
11382 foreach ($options as $optkey => $poption) {
11383 $checkval = $is_assoc ? $optkey : $poption;
11384 $selected = false;
11385 if (isset($params[$value])) {
11386 if (is_array($params[$value])) {
11387 $selected = in_array($checkval, $params[$value]);
11388 } else {
11389 $selected = ($checkval == $params[$value]);
11390 }
11391 } elseif (isset($default_paramv)) {
11392 if (is_array($default_paramv)) {
11393 $selected = in_array($checkval, $default_paramv);
11394 } else {
11395 $selected = ($checkval == $default_paramv);
11396 }
11397 }
11398 $html .= '<option value="' . ($is_assoc ? $optkey : $poption) . '"'.($selected ? ' selected="selected"' : '').'>'.$poption.'</option>';
11399 }
11400 $html .= '</select>';
11401 break;
11402 case 'password':
11403 $html .= '<div class="btn-wrapper input-append">';
11404 $html .= '<input type="password" name="vikcronparams['.$value.']" value="'.(isset($params[$value]) ? JHtml::fetch('esc_attr', $params[$value]) : JHtml::fetch('esc_attr', $default_paramv)).'" size="20"' . $inp_attr . '/>';
11405 $html .= '<button type="button" class="btn btn-primary" onclick="vikCronParamTogglePwd(this);"><i class="' . VikBookingIcons::i('eye') . '"></i></button>';
11406 $html .= '</div>';
11407 // set flag for JS helper
11408 $js_helpers[] = $cont['type'];
11409 break;
11410 case 'number':
11411 $number_attr = [];
11412 if (isset($cont['min'])) {
11413 $number_attr[] = 'min="' . JHtml::fetch('esc_attr', $cont['min']) . '"';
11414 }
11415 if (isset($cont['max'])) {
11416 $number_attr[] = 'max="' . JHtml::fetch('esc_attr', $cont['max']) . '"';
11417 }
11418 if (isset($cont['step'])) {
11419 $number_attr[] = 'step="' . JHtml::fetch('esc_attr', $cont['step']) . '"';
11420 }
11421 $html .= '<input type="number" name="vikcronparams['.$value.']" value="'.(isset($params[$value]) ? JHtml::fetch('esc_attr', $params[$value]) : JHtml::fetch('esc_attr', $default_paramv)).'" ' . implode(' ', $number_attr) . $inp_attr . '/>';
11422 break;
11423 case 'textarea':
11424 $html .= '<textarea name="vikcronparams['.$value.']"' . $inp_attr . '>'.(isset($params[$value]) ? JHtml::fetch('esc_textarea', $params[$value]) : JHtml::fetch('esc_textarea', $default_paramv)).'</textarea>';
11425 break;
11426 case 'visual_html':
11427 $tarea_cont = isset($params[$value]) ? JHtml::fetch('esc_textarea', $params[$value]) : JHtml::fetch('esc_textarea', $default_paramv);
11428 $tarea_attr = isset($cont['attributes']) && is_array($cont['attributes']) ? $cont['attributes'] : [];
11429 $editor_opts = isset($cont['editor_opts']) && is_array($cont['editor_opts']) ? $cont['editor_opts'] : [];
11430 $editor_btns = isset($cont['editor_btns']) && is_array($cont['editor_btns']) ? $cont['editor_btns'] : [];
11431 $html .= self::getVboApplication()->renderVisualEditor('vikcronparams[' . $value . ']', $tarea_cont, $tarea_attr, $editor_opts, $editor_btns);
11432 break;
11433 case 'codemirror':
11434 $editor = JEditor::getInstance('codemirror');
11435 $e_options = isset($cont['options']) && is_array($cont['options']) ? $cont['options'] : [];
11436 $e_name = 'vikcronparams[' . $value . ']';
11437 $e_value = isset($params[$value]) ? $params[$value] : $default_paramv;
11438 $e_width = isset($e_options['width']) ? $e_options['width'] : '100%';
11439 $e_height = isset($e_options['height']) ? $e_options['height'] : 300;
11440 $e_col = isset($e_options['col']) ? $e_options['col'] : 70;
11441 $e_row = isset($e_options['row']) ? $e_options['row'] : 20;
11442 $e_buttons = isset($e_options['buttons']) ? (bool)$e_options['buttons'] : true;
11443 $e_id = isset($e_options['id']) ? $e_options['id'] : 'vikcronparams_' . $value;
11444 $e_params = isset($e_options['params']) && is_array($e_options['params']) ? $e_options['params'] : [];
11445 if (interface_exists('Throwable')) {
11446 /**
11447 * With PHP >= 7 supporting throwable exceptions for Fatal Errors
11448 * we try to avoid issues with third party plugins that make use
11449 * of the WP native function get_current_screen().
11450 *
11451 * @wponly
11452 */
11453 try {
11454 $html .= $editor->display($e_name, $e_value, $e_width, $e_height, $e_col, $e_row, $e_buttons, $e_id, $e_asset = null, $e_autor = null, $e_params);
11455 } catch (Throwable $t) {
11456 $html .= $t->getMessage() . ' in ' . $t->getFile() . ':' . $t->getLine() . '<br/>';
11457 $html .= '<textarea name="vikcronparams[' . $value . ']"' . $inp_attr . '>' . (isset($params[$value]) ? JHtml::fetch('esc_textarea', $params[$value]) : JHtml::fetch('esc_textarea', $default_paramv)) . '</textarea>';
11458 }
11459 } else {
11460 $html .= $editor->display($e_name, $e_value, $e_width, $e_height, $e_col, $e_row, $e_buttons, $e_id, $e_asset = null, $e_autor = null, $e_params);
11461 }
11462 break;
11463 case 'hidden':
11464 $html .= '<input type="hidden" name="vikcronparams['.$value.']" value="'.(isset($params[$value]) ? JHtml::fetch('esc_attr', $params[$value]) : JHtml::fetch('esc_attr', $default_paramv)).'"' . $inp_attr . '/>';
11465 break;
11466 case 'checkbox':
11467 // always display a hidden input value turned off before the actual checkbox to support the "off" (0) status
11468 $html .= '<input type="hidden" name="vikcronparams['.$value.']" value="0" />';
11469 $html .= self::getVboApplication()->printYesNoButtons('vikcronparams['.$value.']', JText::translate('VBYES'), JText::translate('VBNO'), (isset($params[$value]) ? (int)$params[$value] : (int)$default_paramv), 1, 0);
11470 break;
11471 default:
11472 $html .= '<input type="text" name="vikcronparams['.$value.']" value="'.(isset($params[$value]) ? JHtml::fetch('esc_attr', $params[$value]) : JHtml::fetch('esc_attr', $default_paramv)).'" size="20"' . $inp_attr . '/>';
11473 break;
11474 }
11475
11476 if (strlen($labelhelp)) {
11477 $html .= '<span class="vbo-param-setting-comment">'.$labelhelp.'</span>';
11478 }
11479
11480 $html .= '</div>';
11481 $html .= '</div>';
11482 }
11483
11484 // JS helper functions
11485 if (in_array('password', $js_helpers)) {
11486 // toggle the password fields
11487 $html .= "\n" . '<script>' . "\n";
11488 $html .= 'function vikCronParamTogglePwd(elem) {' . "\n";
11489 $html .= ' var btn = jQuery(elem), inp = btn.parent().find("input").first();' . "\n";
11490 $html .= ' if (!inp || !inp.length) {return false;}' . "\n";
11491 $html .= ' var inp_type = inp.attr("type");' . "\n";
11492 $html .= ' inp.attr("type", (inp_type == "password" ? "text" : "password"));' . "\n";
11493 $html .= '}' . "\n";
11494 $html .= "\n" . '</script>' . "\n";
11495 }
11496
11497 return $html;
11498 }
11499
11500 public static function invokeChannelManager($skiporder = true, $order = array()) {
11501 $task = VikRequest::getString('task', '', 'request');
11502 $view = VikRequest::getString('view', '', 'request');
11503 $tmpl = VikRequest::getString('tmpl', '', 'request');
11504 $noimpression = array('vieworder', 'booking');
11505 if ($tmpl != 'component' && (!$skiporder || (!in_array($task, $noimpression) && !in_array($view, $noimpression))) && file_exists(VCM_SITE_PATH.DIRECTORY_SEPARATOR.'helpers'.DIRECTORY_SEPARATOR.'lib.vikchannelmanager.php')) {
11506 //VCM Channel Impression
11507 if (!class_exists('VikChannelManagerConfig')) {
11508 require_once(VCM_SITE_PATH.DIRECTORY_SEPARATOR.'helpers'.DIRECTORY_SEPARATOR.'vcm_config.php');
11509 }
11510 if (!class_exists('VikChannelManager')) {
11511 require_once(VCM_SITE_PATH.DIRECTORY_SEPARATOR.'helpers'.DIRECTORY_SEPARATOR.'lib.vikchannelmanager.php');
11512 }
11513 VikChannelManager::invokeChannelImpression();
11514 } elseif ($tmpl != 'component' && count($order) > 0 && file_exists(VCM_SITE_PATH.DIRECTORY_SEPARATOR.'helpers'.DIRECTORY_SEPARATOR.'lib.vikchannelmanager.php')) {
11515 //VCM Channel Conversion-Impression
11516 if (!class_exists('VikChannelManagerConfig')) {
11517 require_once(VCM_SITE_PATH.DIRECTORY_SEPARATOR.'helpers'.DIRECTORY_SEPARATOR.'vcm_config.php');
11518 }
11519 if (!class_exists('VikChannelManager')) {
11520 require_once(VCM_SITE_PATH.DIRECTORY_SEPARATOR.'helpers'.DIRECTORY_SEPARATOR.'lib.vikchannelmanager.php');
11521 }
11522 VikChannelManager::invokeChannelConversionImpression($order);
11523 }
11524 }
11525
11526 public static function validEmail($email) {
11527 $isValid = true;
11528 $atIndex = strrpos($email, "@");
11529 if (is_bool($atIndex) && !$atIndex) {
11530 $isValid = false;
11531 } else {
11532 $domain = substr($email, $atIndex +1);
11533 $local = substr($email, 0, $atIndex);
11534 $localLen = strlen($local);
11535 $domainLen = strlen($domain);
11536 if ($localLen < 1 || $localLen > 64) {
11537 // local part length exceeded
11538 $isValid = false;
11539 } else
11540 if ($domainLen < 1 || $domainLen > 255) {
11541 // domain part length exceeded
11542 $isValid = false;
11543 } else
11544 if ($local[0] == '.' || $local[$localLen -1] == '.') {
11545 // local part starts or ends with '.'
11546 $isValid = false;
11547 } else
11548 if (preg_match('/\\.\\./', $local)) {
11549 // local part has two consecutive dots
11550 $isValid = false;
11551 } else
11552 if (!preg_match('/^[A-Za-z0-9\\-\\.]+$/', $domain)) {
11553 // character not valid in domain part
11554 $isValid = false;
11555 } else
11556 if (preg_match('/\\.\\./', $domain)) {
11557 // domain part has two consecutive dots
11558 $isValid = false;
11559 } else
11560 if (!preg_match('/^(\\\\.|[A-Za-z0-9!#%&`_=\\/$\'*+?^{}|~.-])+$/', str_replace("\\\\", "", $local))) {
11561 // character not valid in local part unless
11562 // local part is quoted
11563 if (!preg_match('/^"(\\\\"|[^"])+"$/', str_replace("\\\\", "", $local))) {
11564 $isValid = false;
11565 }
11566 }
11567 if ($isValid && !(checkdnsrr($domain, "MX") || checkdnsrr($domain, "A"))) {
11568 // domain not found in DNS
11569 $isValid = false;
11570 }
11571 }
11572 return $isValid;
11573 }
11574
11575 public static function caniWrite($path) {
11576 if ($path[strlen($path) - 1] == '/') {
11577 // ricorsivo return a temporary file path
11578 return self::caniWrite($path . uniqid(mt_rand()) . '.tmp');
11579 }
11580 if (is_dir($path)) {
11581 return self::caniWrite($path . '/' . uniqid(mt_rand()) . '.tmp');
11582 }
11583 // check tmp file for read/write capabilities
11584 $rm = file_exists($path);
11585 $f = @fopen($path, 'a');
11586 if ($f === false) {
11587 return false;
11588 }
11589 fclose($f);
11590 if (!$rm) {
11591 unlink($path);
11592 }
11593 return true;
11594 }
11595
11596 /**
11597 * Alias method of JFile::upload to unify any
11598 * upload function into one.
11599 *
11600 * @param string $src The name of the php (temporary) uploaded file.
11601 * @param string $dest The path (including filename) to move the uploaded file to.
11602 * @param boolean [$copy_only] Whether to skip the file upload and just copy the file.
11603 *
11604 * @return boolean True on success.
11605 *
11606 * @since 1.10 - Revision April 24th 2018 for compatibility with the VikWP Framework.
11607 * @wponly 1.0.7 added the third $copy_only argument to remove the use of copy()
11608 */
11609 public static function uploadFile($src, $dest, $copy_only = false) {
11610 // always attempt to include the File class
11611 jimport('joomla.filesystem.file');
11612
11613 // upload the file
11614 if (!$copy_only) {
11615 $result = JFile::upload($src, $dest);
11616 } else {
11617 // this is to avoid the use of the PHP function copy() and allow files mirroring in WP (triggerUploadBackup)
11618 $result = JFile::copy($src, $dest);
11619 }
11620
11621 /**
11622 * @wponly in order to not lose uploaded files after installing an update,
11623 * we need to move any uploaded file onto a recovery folder.
11624 */
11625 if ($result) {
11626 VikBookingLoader::import('update.manager');
11627 VikBookingUpdateManager::triggerUploadBackup($dest);
11628 }
11629 //
11630
11631 // return upload result
11632 return $result;
11633 }
11634
11635 /**
11636 * Helper method used to upload the given file (retrieved from $_FILES)
11637 * into the specified destination.
11638 *
11639 * @param array $file An associative array with the file details.
11640 * @param string $dest The destination path.
11641 * @param string $filters A string (or a regex) containing the allowed extensions.
11642 *
11643 * @return array The uploading result.
11644 *
11645 * @throws RuntimeException
11646 *
11647 * @since 1.3.0
11648 */
11649 public static function uploadFileFromRequest($file, $dest, $filters = '*')
11650 {
11651 jimport('joomla.filesystem.file');
11652
11653 $dest = rtrim($dest, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
11654
11655 if (empty($file['name']))
11656 {
11657 throw new RuntimeException('Missing file', 400);
11658 }
11659
11660 /**
11661 * Make sure the upload of the file didn't raise any errors.
11662 *
11663 * @link https://www.php.net/manual/en/features.file-upload.errors.php
11664 */
11665 if ((int) $file['error'])
11666 {
11667 if ($file['error'] == UPLOAD_ERR_INI_SIZE)
11668 {
11669 $error = 'The uploaded file exceeds the upload_max_filesize directive in php.ini.';
11670 }
11671 else if ($file['error'] == UPLOAD_ERR_FORM_SIZE)
11672 {
11673 $error = 'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form.';
11674 }
11675 else if ($file['error'] == UPLOAD_ERR_PARTIAL)
11676 {
11677 $error = 'The uploaded file was only partially uploaded.';
11678 }
11679 else if ($file['error'] == UPLOAD_ERR_NO_FILE)
11680 {
11681 $error = 'No file was uploaded.';
11682 }
11683 else if ($file['error'] == UPLOAD_ERR_NO_TMP_DIR)
11684 {
11685 $error = 'Missing a temporary folder.';
11686 }
11687 else if ($file['error'] == UPLOAD_ERR_CANT_WRITE)
11688 {
11689 $error = 'Failed to write file to disk.';
11690 }
11691 else if ($file['error'] == UPLOAD_ERR_EXTENSION)
11692 {
11693 $error = 'A PHP extension stopped the file upload.';
11694 }
11695 else
11696 {
11697 $error = 'Unknown error.';
11698 }
11699
11700 throw new RuntimeException($error, 500);
11701 }
11702
11703 $src = $file['tmp_name'];
11704
11705 // extract file name and extension
11706 if (preg_match("/(.*?)(\.[0-9a-z]{2,})$/i", basename($file['name']), $match))
11707 {
11708 $filename = $match[1];
11709 $fileext = $match[2];
11710 }
11711 else
11712 {
11713 // probably no extension provided
11714 $filename = basename($file['name']);
11715 $fileext = '';
11716 }
11717
11718 $j = '';
11719
11720 if (file_exists($dest . $filename . $fileext))
11721 {
11722 $j = 2;
11723
11724 while (file_exists($dest . $filename . '-' . $j . $fileext))
11725 {
11726 $j++;
11727 }
11728
11729 $j = '-' . $j;
11730 }
11731
11732 $finaldest = $dest . $filename . $j . $fileext;
11733
11734 if ($filters !== '*')
11735 {
11736 $ext = $file['type'];
11737
11738 // check if we have a regex
11739 if (preg_match("/^[#\/]/", $filters) && preg_match("/[#\/][a-z]*$/", $filters))
11740 {
11741 if (!preg_match($filters, $ext))
11742 {
11743 // extension not supported
11744 throw new RuntimeException(sprintf('Extension [%s] is not supported', $ext), 400);
11745 }
11746 }
11747 else
11748 {
11749 // get all supported types
11750 $types = array_map('strtolower', array_filter(explode(',', $filters)));
11751
11752 if (!in_array($ext, $types))
11753 {
11754 // extension not supported
11755 throw new RuntimeException(sprintf('Extension [%s] is not supported', $ext), 400);
11756 }
11757 }
11758 }
11759
11760 // try to upload the file
11761 if (!JFile::upload($src, $finaldest, $use_streams = false, $allow_unsafe = true))
11762 {
11763 throw new RuntimeException(sprintf('Unable to upload the file [%s] to [%s]', $src, $finaldest), 500);
11764 }
11765
11766 $file = new stdClass;
11767 $file->name = $filename . $j;
11768 $file->ext = ltrim($fileext, '.');
11769 $file->filename = basename($finaldest);
11770 $file->path = $finaldest;
11771
11772 return $file;
11773 }
11774
11775 /**
11776 * Gets the instance of a specific report class.
11777 *
11778 * @param string $report the name of the report to load.
11779 *
11780 * @return mixed false or report object instance.
11781 *
11782 * @since 1.3.0
11783 */
11784 public static function getReportInstance($report)
11785 {
11786 require_once VBO_ADMIN_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'report' . DIRECTORY_SEPARATOR . 'report.php';
11787
11788 return VikBookingReport::getInstanceOf($report);
11789 }
11790
11791 /**
11792 * Checks whether the guest reviews are enabled and VCM is installed.
11793 *
11794 * @return boolean true if enabled, false otherwise.
11795 *
11796 * @since 1.3.0
11797 */
11798 public static function allowGuestReviews()
11799 {
11800 $dbo = JFactory::getDbo();
11801 $vcm_installed = is_file(VCM_SITE_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'lib.vikchannelmanager.php');
11802 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='grenabled';";
11803 $dbo->setQuery($q);
11804 $dbo->execute();
11805 if ($dbo->getNumRows()) {
11806 $s = $dbo->loadResult();
11807 return ((int)$s === 1 && $vcm_installed);
11808 }
11809 $q = "INSERT INTO `#__vikbooking_config` (`param`,`setting`) VALUES ('grenabled', '1');";
11810 $dbo->setQuery($q);
11811 $dbo->execute();
11812 return $vcm_installed;
11813 }
11814
11815 /**
11816 * Gets the minimum chars for the guest review message.
11817 *
11818 * @return int minimum number of chars for the comment (0 = no limits, -1 = disabled).
11819 *
11820 * @since 1.3.0
11821 */
11822 public static function guestReviewMinChars()
11823 {
11824 $dbo = JFactory::getDbo();
11825 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='grminchars';";
11826 $dbo->setQuery($q);
11827 $dbo->execute();
11828 if ($dbo->getNumRows() > 0) {
11829 return (int)$dbo->loadResult();
11830 }
11831 $q = "INSERT INTO `#__vikbooking_config` (`param`,`setting`) VALUES ('grminchars', '15');";
11832 $dbo->setQuery($q);
11833 $dbo->execute();
11834 return 15;
11835 }
11836
11837 /**
11838 * The approval type for new guest reviews.
11839 *
11840 * @return string auto or manual.
11841 *
11842 * @since 1.3.0
11843 */
11844 public static function guestReviewsApproval()
11845 {
11846 $dbo = JFactory::getDbo();
11847 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='grappr';";
11848 $dbo->setQuery($q);
11849 $dbo->execute();
11850 if ($dbo->getNumRows() > 0) {
11851 return $dbo->loadResult() == 'manual' ? 'manual' : 'auto';
11852 }
11853 $q = "INSERT INTO `#__vikbooking_config` (`param`,`setting`) VALUES ('grappr', 'auto');";
11854 $dbo->setQuery($q);
11855 $dbo->execute();
11856 return 'auto';
11857 }
11858
11859 /**
11860 * The type of reviews guests should leave.
11861 *
11862 * @return string global or service.
11863 *
11864 * @since 1.3.0
11865 */
11866 public static function guestReviewsType()
11867 {
11868 $dbo = JFactory::getDbo();
11869 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='grtype';";
11870 $dbo->setQuery($q);
11871 $dbo->execute();
11872 if ($dbo->getNumRows() > 0) {
11873 return $dbo->loadResult() == 'global' ? 'global' : 'service';
11874 }
11875 $q = "INSERT INTO `#__vikbooking_config` (`param`,`setting`) VALUES ('grtype', 'service');";
11876 $dbo->setQuery($q);
11877 $dbo->execute();
11878 return 'service';
11879 }
11880
11881 /**
11882 * The services to be reviewed by the guests.
11883 *
11884 * @return array list, empty or not, of the services to be reviewed.
11885 *
11886 * @since 1.3.0
11887 */
11888 public static function guestReviewsServices()
11889 {
11890 $dbo = JFactory::getDbo();
11891 $q = "SELECT `id`,`service_name` FROM `#__vikbooking_greview_service` GROUP BY `service_name` ORDER BY `id` ASC;";
11892 $dbo->setQuery($q);
11893 $dbo->execute();
11894 if ($dbo->getNumRows()) {
11895 return $dbo->loadAssocList();
11896 }
11897 // insert the default services
11898 $default_services = array(
11899 JText::translate('VBOGREVVALUE'),
11900 JText::translate('VBOGREVLOCATION'),
11901 JText::translate('VBOGREVSTAFF'),
11902 JText::translate('VBOGREVCLEAN'),
11903 JText::translate('VBOGREVCOMFORT'),
11904 JText::translate('VBOGREVFACILITIES'),
11905 );
11906 foreach ($default_services as $def_service) {
11907 $q = "INSERT INTO `#__vikbooking_greview_service` (`service_name`) VALUES (" . $dbo->quote($def_service) . ");";
11908 $dbo->setQuery($q);
11909 $dbo->execute();
11910 }
11911 return array();
11912 }
11913
11914 /**
11915 * Checks whether the guest reviews should be downloaded (max 1 per day).
11916 * VCM must be installed in order to download the new reviews.
11917 *
11918 * @return int -1 if VCM is not installed, 0 if already downloaded, 1 for download.
11919 *
11920 * @since 1.3.0
11921 */
11922 public static function shouldDownloadReviews()
11923 {
11924 if (!is_file(VCM_SITE_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'lib.vikchannelmanager.php')) {
11925 return -1;
11926 }
11927
11928 $today = date('Y-m-d');
11929
11930 $dbo = JFactory::getDbo();
11931 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='gr_last_download' LIMIT 1;";
11932 $dbo->setQuery($q);
11933 $dbo->execute();
11934 if ($dbo->getNumRows()) {
11935 $last_download = $dbo->loadResult();
11936 // update last download day
11937 $q = "UPDATE `#__vikbooking_config` SET `setting`=" . $dbo->quote($today) . " WHERE `param`='gr_last_download';";
11938 $dbo->setQuery($q);
11939 $dbo->execute();
11940 //
11941 return ($last_download != $today ? 1 : 0);
11942 }
11943 $q = "INSERT INTO `#__vikbooking_config` (`param`,`setting`) VALUES ('gr_last_download', " . $dbo->quote($today) . ");";
11944 $dbo->setQuery($q);
11945 $dbo->execute();
11946 return 1;
11947 }
11948
11949 /**
11950 * Tells whether a booking can be reviewed.
11951 *
11952 * @param array $booking booking array details.
11953 *
11954 * @return boolean true if a review can be left, false otherwise.
11955 *
11956 * @since 1.3.0
11957 */
11958 public static function canBookingBeReviewed($booking)
11959 {
11960 // reviews must be enabled and supported
11961 if (!self::allowGuestReviews()) {
11962 return false;
11963 }
11964
11965 // booking status must be confirmed
11966 if ($booking['status'] != 'confirmed') {
11967 return false;
11968 }
11969
11970 // review can be left starting from the check-out day
11971 $checkout_info = getdate($booking['checkout']);
11972 $checkout_midn = mktime(0, 0, 0, $checkout_info['mon'], $checkout_info['mday'], $checkout_info['year']);
11973 if (time() < $checkout_midn) {
11974 return false;
11975 }
11976
11977 // make sure a review for this booking does not exist
11978 $noreview = true;
11979 $dbo = JFactory::getDbo();
11980 try {
11981 $q = "SELECT `id` FROM `#__vikchannelmanager_otareviews` WHERE `idorder`=" . $dbo->quote($booking['id']) . ";";
11982 $dbo->setQuery($q);
11983 $dbo->execute();
11984 $noreview = ($dbo->getNumRows() < 1);
11985 } catch (Exception $e) {
11986 // if the query fails, we do not allow the review to be left
11987 $noreview = false;
11988 }
11989
11990 return $noreview;
11991 }
11992
11993 /**
11994 * Gets the review for a booking, no matter if it's published or not.
11995 * Review will be returned only if reviews are enabled and supported.
11996 * Only reviews left through the website, no OTA reviews as this method
11997 * should be used in the front-end to display the review to the guest.
11998 *
11999 * @param array $booking booking array details.
12000 *
12001 * @return mixed associative array or false.
12002 *
12003 * @since 1.3.0
12004 */
12005 public static function getBookingReview($booking)
12006 {
12007 // reviews must be enabled and supported
12008 if (!self::allowGuestReviews()) {
12009 return false;
12010 }
12011
12012 if (empty($booking['id'])) {
12013 return false;
12014 }
12015
12016 // make sure a review for this booking exists
12017 $dbo = JFactory::getDbo();
12018 $review = array();
12019 try {
12020 $q = "SELECT * FROM `#__vikchannelmanager_otareviews` WHERE `idorder`=" . $dbo->quote($booking['id']) . " LIMIT 1;";
12021 $dbo->setQuery($q);
12022 $dbo->execute();
12023 $review = $dbo->getNumRows() ? $dbo->loadAssoc() : $review;
12024 } catch (Exception $e) {
12025 // query has failed
12026 $review = array();
12027 }
12028
12029 if (count($review)) {
12030 // make sure the review was left from the website
12031 $review['uniquekey'] = (int)$review['uniquekey'];
12032 }
12033
12034 if (count($review) && !empty($review['uniquekey'])) {
12035 // this is an OTA review
12036 return false;
12037 }
12038
12039 if (count($review) && !empty($review['content'])) {
12040 // decode content
12041 $review['content'] = json_decode($review['content'], true);
12042 }
12043
12044 return count($review) ? $review : false;
12045 }
12046
12047 /**
12048 * Attempts to find a translation for a raw customer data label.
12049 * Bookings downloaded from the OTAs will save a raw string of information
12050 * composed of pairs of label-value separated by new line feeds.
12051 * We try to translate the given label into the current language.
12052 *
12053 * @param string $label the raw string label to translate.
12054 *
12055 * @return string either the original or the translated label.
12056 *
12057 * @since 1.3.5
12058 * @since 1.4.0 back-end support added.
12059 */
12060 public static function tnCustomerRawDataLabel($label)
12061 {
12062 // this is a map of the known labels
12063 $known_lbls = array(
12064 'NAME' => 'VBOCUSTOMERNOMINATIVE',
12065 'LASTNAME' => 'ORDER_LNAME',
12066 'COUNTRY' => 'ORDER_STATE',
12067 'EMAIL' => 'VBMAIL',
12068 'TELEPHONE' => 'VBPHONE',
12069 'PHONE' => 'VBPHONE',
12070 'SPECIALREQUEST' => 'ORDER_SPREQUESTS',
12071 'MEAL_PLAN' => 'VBOMEALPLAN',
12072 'CITY' => 'VBCITY',
12073 'BEDPREFERENCE' => 'VBOBEDPREFERENCE',
12074 'BOOKER_IS_GENIUS' => 'VBOBOOKERISGENIUS',
12075 );
12076
12077 if (self::isAdmin()) {
12078 // override the map of known translation strings
12079 $known_lbls = array(
12080 'NAME' => 'ORDER_NAME',
12081 'LASTNAME' => 'ORDER_LNAME',
12082 'COUNTRY' => 'ORDER_STATE',
12083 'ADDRESS' => 'ORDER_ADDRESS',
12084 'CITY' => 'ORDER_CITY',
12085 'LOCATION' => 'ORDER_CITY',
12086 'EMAIL' => 'ORDER_EMAIL',
12087 'TELEPHONE' => 'ORDER_PHONE',
12088 'PHONE' => 'ORDER_PHONE',
12089 'SPECIALREQUEST' => 'ORDER_SPREQUESTS',
12090 );
12091 }
12092
12093 // we get rid of any empty space by keeping the underscores
12094 $converted = str_replace(' ', '', strtoupper($label));
12095
12096 if (isset($known_lbls[$converted])) {
12097 // this language definition has been mapped
12098 return JText::translate($known_lbls[$converted]);
12099 }
12100
12101 // we try to guess the translation string by prepending VBO
12102 $guessed = JText::translate('VBO' . $converted);
12103 if ($guessed != 'VBO' . $converted) {
12104 // the label was translated correctly
12105 return $guessed;
12106 }
12107
12108 // this label could not be translated, so we return the plain string
12109 return $label;
12110 }
12111
12112 /**
12113 * We check whether some bookings are available for import from third party plugins.
12114 *
12115 * @return mixed array with list of plugins supported, false otherwise.
12116 *
12117 * @wponly this method is only useful for WordPress.
12118 *
12119 * @since 1.3.5
12120 */
12121 public static function canImportBookingsFromThirdPartyPlugins()
12122 {
12123 $dbo = JFactory::getDbo();
12124
12125 $plugins = array();
12126
12127 /**
12128 * As requested from hundreds of our clients, for the moment we check only the custom
12129 * post types of type "mphb_booking" to see if some bookings are available for import.
12130 */
12131 $q = "SELECT `post_type` FROM `#__posts` WHERE `post_type`=" . $dbo->quote('mphb_booking') . ";";
12132 $dbo->setQuery($q);
12133 $dbo->execute();
12134
12135 if (!$dbo->getNumRows()) {
12136 return false;
12137 }
12138
12139 // push this third party plugin
12140 $plugins['mphb'] = 'MotoPress Hotel Booking';
12141
12142 return count($plugins) ? $plugins : false;
12143 }
12144
12145 /**
12146 * This method returns a list of the known languages sorted by the
12147 * administrator custom preferences. Useful for the phone input fields.
12148 *
12149 * @param boolean $code_assoc whether to get an associative array with the lang name.
12150 *
12151 * @return array the sorted list of preferred countries.
12152 *
12153 * @since 1.3.11
12154 */
12155 public static function preferredCountriesOrdering($code_assoc = false)
12156 {
12157 $preferred_countries = array();
12158
12159 // try to get the preferred countries from db
12160 $dbo = JFactory::getDbo();
12161 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='preferred_countries';";
12162 $dbo->setQuery($q);
12163 $setting = $dbo->loadResult();
12164 if (!$setting) {
12165 // create empty configuration record
12166 $q = "INSERT INTO `#__vikbooking_config` (`param`,`setting`) VALUES ('preferred_countries', '[]');";
12167 $dbo->setQuery($q);
12168 $dbo->execute();
12169 } else {
12170 $preferred_countries = json_decode($setting);
12171 }
12172
12173 // get the default known languages
12174 $sorted_known_langs = self::getVboApplication()->getKnownLanguages();
12175
12176 if (!is_array($preferred_countries) || !count($preferred_countries)) {
12177 // sort the default known languages by country code alphabetically
12178 ksort($sorted_known_langs);
12179 foreach ($sorted_known_langs as $k => $v) {
12180 $langsep = strpos($k, '_') !== false ? '_' : '-';
12181 $langparts = explode($langsep, $k);
12182 array_push($preferred_countries, isset($langparts[1]) ? strtolower($langparts[1]) : strtolower($langparts[0]));
12183 }
12184 // update the database record
12185 $q = "UPDATE `#__vikbooking_config` SET `setting`=" . $dbo->quote(json_encode($preferred_countries)) . " WHERE `param`='preferred_countries';";
12186 $dbo->setQuery($q);
12187 $dbo->execute();
12188 }
12189
12190 if ($code_assoc) {
12191 // this is useful for displaying the preferred countries codes together with the language name
12192 $map = array();
12193 foreach ($preferred_countries as $ccode) {
12194 // look for the current country code in the keys of the known language tags
12195 $match_found = false;
12196 foreach ($sorted_known_langs as $langtag => $langinfo) {
12197 $langsep = strpos($langtag, '_') !== false ? '_' : '-';
12198 $langparts = explode($langsep, $langtag);
12199 if (isset($langparts[1]) && strtoupper($ccode) == strtoupper($langparts[1])) {
12200 // match found
12201 $match_found = true;
12202 $map[$ccode] = !empty($langinfo['nativeName']) ? $langinfo['nativeName'] : $langinfo['name'];
12203 } elseif (strtoupper($ccode) == strtoupper($langparts[0])) {
12204 // match found
12205 $match_found = true;
12206 $map[$ccode] = !empty($langinfo['nativeName']) ? $langinfo['nativeName'] : $langinfo['name'];
12207 }
12208 }
12209 if (!$match_found) {
12210 // in case someone would like to add a custom country code via DB, we allow to do so by returning the raw value
12211 $map[$ccode] = strtoupper($ccode);
12212 }
12213 }
12214 if (count($map)) {
12215 // set the associatve array to be returned
12216 $preferred_countries = $map;
12217 }
12218 }
12219
12220 return $preferred_countries;
12221 }
12222
12223 /**
12224 * Gets the instance of the admin widgets helper class.
12225 *
12226 * @return VikBookingHelperAdminWidgets
12227 *
12228 * @since 1.4.0
12229 */
12230 public static function getAdminWidgetsInstance()
12231 {
12232 require_once VBO_ADMIN_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'admin_widgets.php';
12233
12234 return VikBookingHelperAdminWidgets::getInstance();
12235 }
12236
12237 /**
12238 * Gets the instance of the conditional rules helper class.
12239 *
12240 * @param bool $require_only whether to return the object.
12241 *
12242 * @return mixed VikBookingHelperConditionalRules or true.
12243 *
12244 * @since 1.4.0
12245 */
12246 public static function getConditionalRulesInstance($require_only = false)
12247 {
12248 require_once VBO_ADMIN_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'conditional_rules.php';
12249
12250 return $require_only ? true : VikBookingHelperConditionalRules::getInstance();
12251 }
12252
12253 /**
12254 * Gets the instance of the geocoding helper class.
12255 *
12256 * @param bool $require_only whether to return the object.
12257 *
12258 * @return mixed VikBookingHelperGeocoding or true.
12259 *
12260 * @since 1.4.0
12261 */
12262 public static function getGeocodingInstance($require_only = false)
12263 {
12264 require_once VBO_ADMIN_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'geocoding.php';
12265
12266 return $require_only ? true : VikBookingHelperGeocoding::getInstance();
12267 }
12268
12269 /**
12270 * Helper method to cope with the removal of the same method
12271 * in the JApplication class introduced with Joomla 4. Using
12272 * isClient() would break the compatibility with J < 3.7 so
12273 * we can rely on this helper method to avoid Fatal Errors.
12274 *
12275 * @return boolean
12276 *
12277 * @since October 2020
12278 */
12279 public static function isAdmin()
12280 {
12281 $app = JFactory::getApplication();
12282 if (method_exists($app, 'isClient')) {
12283 return $app->isClient('administrator');
12284 }
12285
12286 return $app->isAdmin();
12287 }
12288
12289 /**
12290 * Helper method to cope with the removal of the same method
12291 * in the JApplication class introduced with Joomla 4. Using
12292 * isClient() would break the compatibility with J < 3.7 so
12293 * we can rely on this helper method to avoid Fatal Errors.
12294 *
12295 * @return boolean
12296 *
12297 * @since October 2020
12298 */
12299 public static function isSite()
12300 {
12301 $app = JFactory::getApplication();
12302 if (method_exists($app, 'isClient')) {
12303 return $app->isClient('site');
12304 }
12305
12306 return $app->isSite();
12307 }
12308
12309 /**
12310 * Gets the Google Maps API Key.
12311 *
12312 * @return string
12313 *
12314 * @since 1.14 (J) - 1.4.0 (WP)
12315 */
12316 public static function getGoogleMapsKey()
12317 {
12318 $dbo = JFactory::getDbo();
12319 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='gmapskey';";
12320 $dbo->setQuery($q);
12321 $dbo->execute();
12322 if ($dbo->getNumRows()) {
12323 return $dbo->loadResult();
12324 }
12325 $q = "INSERT INTO `#__vikbooking_config` (`param`,`setting`) VALUES ('gmapskey', '');";
12326 $dbo->setQuery($q);
12327 $dbo->execute();
12328 return '';
12329 }
12330
12331 /**
12332 * Checks whether the interactive map booking is enabled.
12333 *
12334 * @return bool
12335 *
12336 * @since 1.14 (J) - 1.4.0 (WP)
12337 */
12338 public static function interactiveMapEnabled()
12339 {
12340 $dbo = JFactory::getDbo();
12341 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='interactive_map';";
12342 $dbo->setQuery($q);
12343 $dbo->execute();
12344 if ($dbo->getNumRows()) {
12345 return ((int)$dbo->loadResult() > 0);
12346 }
12347
12348 $q = "INSERT INTO `#__vikbooking_config` (`param`,`setting`) VALUES ('interactive_map', '0');";
12349 $dbo->setQuery($q);
12350 $dbo->execute();
12351
12352 return false;
12353 }
12354
12355 /**
12356 * Gets the preferred colors saved in the configuration, if any.
12357 *
12358 * @return array
12359 *
12360 * @since 1.14 (J) - 1.4.0 (WP)
12361 */
12362 public static function getPreferredColors()
12363 {
12364 $dbo = JFactory::getDbo();
12365 $pref_colors = array(
12366 'textcolor' => '',
12367 'bgcolor' => '',
12368 'fontcolor' => '',
12369 'bgcolorhov' => '',
12370 'fontcolorhov' => '',
12371 );
12372
12373 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='pref_colors';";
12374 $dbo->setQuery($q);
12375 $dbo->execute();
12376 if ($dbo->getNumRows()) {
12377 $colors = json_decode($dbo->loadResult(), true);
12378 if (!is_array($colors) || !isset($colors['textcolor'])) {
12379 return $pref_colors;
12380 }
12381 return $colors;
12382 }
12383 $q = "INSERT INTO `#__vikbooking_config` (`param`,`setting`) VALUES ('pref_colors', '{}');";
12384 $dbo->setQuery($q);
12385 $dbo->execute();
12386 return $pref_colors;
12387 }
12388
12389 /**
12390 * Adds to the document inline styles for the preferred colors, if any.
12391 *
12392 * @return void
12393 *
12394 * @since 1.14 (J) - 1.4.0 (WP)
12395 */
12396 public static function loadPreferredColorStyles()
12397 {
12398 $view = VikRequest::getString('view', '', 'request');
12399 $pref_colors = self::getPreferredColors();
12400
12401 $css_classes = array();
12402
12403 if (!empty($pref_colors['textcolor'])) {
12404 // titles and headings
12405 array_push($css_classes, '.vbo-pref-color-text { color: ' . $pref_colors['textcolor'] . ' !important; }');
12406 // stepbar, oconfirm
12407 array_push($css_classes, 'ol.vbo-stepbar li.vbo-step-complete, ol.vbo-stepbar li.vbo-step-current, ol.vbo-stepbar li.vbo-step-current:before, .vbo-coupon-outer, .vbo-enterpin-block { border-color: ' . $pref_colors['textcolor'] . ' !important; }');
12408 // buttons secondary color
12409 array_push($css_classes, '.vbo-pref-color-btn-secondary { border: 2px solid ' . $pref_colors['textcolor'] . ' !important; color: ' . $pref_colors['textcolor'] . ' !important; background: transparent !important; }');
12410 if (!empty($pref_colors['fontcolor'])) {
12411 array_push($css_classes, '.vbo-pref-color-btn-secondary:hover { color: ' . $pref_colors['fontcolor'] . ' !important; background: ' . $pref_colors['textcolor'] . ' !important; }');
12412 }
12413 // datepicker
12414 array_push($css_classes, '.ui-datepicker .ui-datepicker-today {
12415 color: ' . $pref_colors['textcolor'] . ' !important;
12416 border-color: ' . $pref_colors['textcolor'] . ' !important;
12417 }');
12418 array_push($css_classes, '.ui-datepicker .ui-datepicker-today a {
12419 color: ' . $pref_colors['textcolor'] . ' !important;
12420 }');
12421 // operators tableaux
12422 if ($view == 'tableaux') {
12423 array_push($css_classes, '.vbo-roomdaynote-empty .vbo-roomdaynote-trigger i { color: ' . $pref_colors['textcolor'] . ' !important; }');
12424 }
12425 }
12426
12427 if (!empty($pref_colors['bgcolor']) && !empty($pref_colors['fontcolor'])) {
12428 // elements with backgrounds
12429 array_push($css_classes, '.vbo-pref-color-element { background-color: ' . $pref_colors['bgcolor'] . ' !important; color: ' . $pref_colors['fontcolor'] . ' !important; }');
12430 array_push($css_classes, '.vbo-pref-bordercolor { border-color: ' . $pref_colors['bgcolor'] . ' !important; }');
12431 array_push($css_classes, '.vbo-pref-bordertext { color: ' . $pref_colors['bgcolor'] . ' !important; border-color: ' . $pref_colors['bgcolor'] . ' !important; }');
12432 // buttons with backgrounds
12433 array_push($css_classes, '.vbo-pref-color-btn { background-color: ' . $pref_colors['bgcolor'] . ' !important; color: ' . $pref_colors['fontcolor'] . ' !important; }');
12434 // stepbar
12435 array_push($css_classes, 'ol.vbo-stepbar li.vbo-step-complete:before { background-color: ' . $pref_colors['bgcolor'] . ' !important; }');
12436 // datepicker
12437 array_push($css_classes, '.ui-datepicker table td:hover {
12438 border-color: ' . $pref_colors['bgcolor'] . ' !important;
12439 }');
12440 array_push($css_classes, '.ui-datepicker .ui-datepicker-current-day {
12441 background: ' . $pref_colors['bgcolor'] . ' !important;
12442 color: ' . $pref_colors['fontcolor'] . ' !important;
12443 }');
12444 array_push($css_classes, '.ui-datepicker .ui-datepicker-current-day a {
12445 color: ' . $pref_colors['fontcolor'] . ' !important;
12446 }');
12447 // operators tableaux
12448 if ($view == 'tableaux') {
12449 array_push($css_classes, '.vbo-tableaux-roombooks > div:not(.vbo-tableaux-booking-empty), .vbo-tableaux-togglefullscreen { background-color: ' . $pref_colors['bgcolor'] . ' !important; color: ' . $pref_colors['fontcolor'] . ' !important; }');
12450 }
12451 }
12452
12453 if (!empty($pref_colors['bgcolorhov']) && !empty($pref_colors['fontcolorhov'])) {
12454 // buttons with backgrounds during hover state
12455 array_push($css_classes, '.vbo-pref-color-btn:hover { background-color: ' . $pref_colors['bgcolorhov'] . ' !important; color: ' . $pref_colors['fontcolorhov'] . ' !important; }');
12456 // operators tableaux
12457 if ($view == 'tableaux') {
12458 array_push($css_classes, '.vbo-tableaux-togglefullscreen:hover { background-color: ' . $pref_colors['bgcolorhov'] . ' !important; color: ' . $pref_colors['fontcolorhov'] . ' !important; }');
12459 }
12460 }
12461
12462 if (!count($css_classes)) {
12463 return;
12464 }
12465
12466 // add in-line style declaration
12467 JFactory::getDocument()->addStyleDeclaration(implode("\n", $css_classes));
12468 }
12469
12470 /**
12471 * Given the full endpoint URL for the AJAX request, it
12472 * returns an appropriate URI for the current platform.
12473 *
12474 * @param mixed $query The query string or a routed URL.
12475 * @param boolean $xhtml Replace & by &amp; for XML compliance.
12476 *
12477 * @return string The AJAX end-point URI.
12478 *
12479 * @since 1.14 (J) - 1.4.0 (WP)
12480 * @since 1.15.0 (J) - 1.5.0 (WP) we rely on the new platform libraries.
12481 */
12482 public static function ajaxUrl($query = '', $xhtml = false)
12483 {
12484 return VBOFactory::getPlatform()->getUri()->ajax($query, $xhtml);
12485 }
12486
12487 /**
12488 * Tells whether no tax rates have been defined so far.
12489 *
12490 * @return bool True if no tax rates defined, false otherwise.
12491 *
12492 * @since 1.14 (J) - 1.4.0 (WP)
12493 */
12494 public static function noTaxRates()
12495 {
12496 $dbo = JFactory::getDbo();
12497
12498 $q = "SELECT `id`, `aliq` FROM `#__vikbooking_iva` WHERE `aliq` > 0;";
12499 $dbo->setQuery($q);
12500 $dbo->execute();
12501
12502 return ($dbo->getNumRows() < 1);
12503 }
12504
12505 /**
12506 * Booking Type feature is strictly connected to VCM and OTAs. Updated
12507 * versions of VBO will always support it as long as VCM is installed.
12508 * Needed by VCM to understand whether certain SQL queries can be performed.
12509 *
12510 * @return bool true if VCM exists, false otherwise.
12511 *
12512 * @since 1.14 (J) - 1.4.0 (WP)
12513 */
12514 public static function isBookingTypeSupported()
12515 {
12516 return is_file(VCM_SITE_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'lib.vikchannelmanager.php');
12517 }
12518
12519 /**
12520 * Returns an instance of the Rates Flow helper class of VCM, if available.
12521 *
12522 * @return mixed VCMRatesFlow if VCM available and updated, or null.
12523 *
12524 * @since 1.15.0 (J) - 1.5.0 (WP)
12525 */
12526 public static function getRatesFlowInstance()
12527 {
12528 $vcm_fpath = VCM_ADMIN_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'rates_flow.php';
12529 if (!is_file($vcm_fpath)) {
12530 return null;
12531 }
12532
12533 // load dependencies
12534 require_once $vcm_fpath;
12535
12536 // return the instance of the class
12537 return VCMRatesFlow::getInstance();
12538 }
12539
12540 /**
12541 * Returns the current appearance preference.
12542 *
12543 * @return string light, auto or dark.
12544 *
12545 * @since 1.15.0 (J) - 1.5.0 (WP)
12546 */
12547 public static function getAppearancePref()
12548 {
12549 static $preference = null;
12550
12551 if ($preference !== null) {
12552 return $preference;
12553 }
12554
12555 // auto is the default appearance preference
12556 $default_pref = 'auto';
12557
12558 // accepted preferences
12559 $valid_pref = [
12560 'auto',
12561 'light',
12562 'dark',
12563 ];
12564
12565 $preference = VBOFactory::getConfig()->get('appearance_pref', $default_pref);
12566 $preference = in_array($preference, $valid_pref) ? $preference : $default_pref;
12567
12568 return $preference;
12569 }
12570
12571 /**
12572 * According to the appearance preferences, the apposite
12573 * CSS assets are loaded within the document.
12574 *
12575 * @param bool $get_info true to not load any assets.
12576 *
12577 * @return mixed string light, auto, dark, info array or false.
12578 *
12579 * @since 1.15.0 (J) - 1.5.0 (WP)
12580 * @since 1.16.0 (J) - 1.6.0 (WP) added argument $get_info.
12581 */
12582 public static function loadAppearancePreferenceAssets($get_info = false)
12583 {
12584 // get document object
12585 $document = JFactory::getDocument();
12586
12587 // load current color scheme preference
12588 $current_pref = self::getAppearancePref();
12589
12590 // define caching values
12591 $file_opt = array('version' => VIKBOOKING_SOFTWARE_VERSION);
12592
12593 // define file attributes
12594 $file_attr = array('id' => 'vbo-css-appearance-' . $current_pref);
12595
12596 // apposite file path and URI for back-end
12597 $css_path = VBO_ADMIN_PATH . DIRECTORY_SEPARATOR . 'resources' . DIRECTORY_SEPARATOR . 'vbo-appearance-' . $current_pref . '.css';
12598 $css_uri = VBO_ADMIN_URI . 'resources/vbo-appearance-' . $current_pref . '.css';
12599
12600 // check if this is for the front-end ("auto" file only)
12601 if (self::isSite() && in_array($current_pref, array('auto', 'dark'))) {
12602 $css_path = VBO_SITE_PATH . DIRECTORY_SEPARATOR . 'resources' . DIRECTORY_SEPARATOR . 'vbo-appearance-auto.css';
12603 $css_uri = VBO_SITE_URI . 'resources/vbo-appearance-auto.css';
12604 // check if front-end appearance is disabled
12605 if (!VBOFactory::getConfig()->getInt('appearance_front', 0)) {
12606 // do nothing for the front-end by default
12607 return false;
12608 }
12609 }
12610
12611 if (VBOPlatformDetection::isJoomla()) {
12612 $css_path = str_replace('resources' . DIRECTORY_SEPARATOR, '', $css_path);
12613 $css_uri = str_replace('resources/', '', $css_uri);
12614 }
12615
12616 if (!is_file($css_path)) {
12617 // this preference does not require a specific stylesheet
12618 return false;
12619 }
12620
12621 if ($get_info) {
12622 // do not actually load any assets
12623 return [
12624 'id' => $file_attr['id'],
12625 'href' => $css_uri,
12626 ];
12627 }
12628
12629 // load the apposite CSS file
12630 $document->addStyleSheet($css_uri, $file_opt, $file_attr);
12631
12632 return $current_pref;
12633 }
12634
12635 /**
12636 * VBO trigger for VCM subscription status and updates. If called within the admin section
12637 * returns the expiration reminder array to display a modal window, and sets a warning
12638 * message. If called within the front-end, may trigger an email alert.
12639 *
12640 * @return mixed null, array or boolean depending on client position.
12641 *
12642 * @since 1.15.0 (J) - 1.5.0 (WP)
12643 */
12644 public static function getVCMSubscriptionStatus()
12645 {
12646 $vcm_lib_path = VCM_SITE_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'lib.vikchannelmanager.php';
12647 if (!is_file($vcm_lib_path)) {
12648 // do nothing
12649 return null;
12650 }
12651
12652 // determine client position
12653 $is_admin = self::isAdmin();
12654
12655 try {
12656 if (!class_exists('VikChannelManager') || !method_exists('VikChannelManager', 'checkSubscriptionReminder')) {
12657 // VCM is outdated
12658 return null;
12659 }
12660
12661 // always attempt to load the admin lang handler of VCM
12662 $lang = JFactory::getLanguage();
12663
12664 // load the VCM admin language file
12665 $vcm_admin_lang_path = '';
12666 if (VBOPlatformDetection::isJoomla()) {
12667 $vcm_admin_lang_path = JPATH_ADMINISTRATOR;
12668 } elseif (defined('VIKCHANNELMANAGER_ADMIN_LANG')) {
12669 $vcm_admin_lang_path = VIKCHANNELMANAGER_ADMIN_LANG;
12670 } elseif (VBOPlatformDetection::isWordPress() && defined('VIKBOOKING_ADMIN_LANG')) {
12671 /**
12672 * If running within Vik Booking, the constant VIKCHANNELMANAGER_ADMIN_LANG may not be available
12673 */
12674 $vcm_admin_lang_path = str_replace('vikbooking', 'vikchannelmanager', VIKBOOKING_ADMIN_LANG);
12675 }
12676 // load translation file
12677 $lang->load('com_vikchannelmanager', $vcm_admin_lang_path, $lang->getTag(), true);
12678 if (VBOPlatformDetection::isWordPress() && defined('VIKCHANNELMANAGER_LIBRARIES')) {
12679 /**
12680 * @wponly load language admin handler as well for WP.
12681 * We do this only because of WordPress, but in a way also compatible with Joomla as
12682 * the constant VIKCHANNELMANAGER_LIBRARIES and method attachHandler are not in Joomla.
12683 */
12684 $lang->attachHandler(VIKCHANNELMANAGER_LIBRARIES . DIRECTORY_SEPARATOR . 'language' . DIRECTORY_SEPARATOR . 'admin.php', 'vikchannelmanager');
12685 }
12686
12687 if ($is_admin) {
12688 // back-end section of the site: check status
12689 if (VikChannelManager::loadExpirationDetails() === false) {
12690 // download and update the expiration details
12691 VikChannelManager::downloadExpirationDetails();
12692 }
12693 $expiration_reminder = null;
12694 if (VikChannelManager::isSubscriptionExpired()) {
12695 VikError::raiseWarning('', JText::translate('VCM_WARN_SUBSCR_EXPIRED'));
12696 } else {
12697 $expiration_reminder = VikChannelManager::shouldRemindExpiration();
12698 // we also check if an update is available
12699 if (VikChannelManager::checkUpdates()) {
12700 // an update is more important
12701 JFactory::getApplication()->redirect('index.php?option=com_vikchannelmanager&task=update_program&forcecheck=1&force_check=1');
12702 exit;
12703 }
12704 }
12705 // return reminder status
12706 return $expiration_reminder;
12707 }
12708
12709 // front-end section of the site: trigger alert, if any (bool)
12710 return VikChannelManager::checkSubscriptionReminder();
12711
12712 } catch (Exception $e) {
12713 // do nothing
12714 }
12715
12716 return null;
12717 }
12718
12719 /**
12720 * Given a list of website rate plans, returns the sorted list.
12721 * Naming score is the highest sorting method, pricing scoring is also supported.
12722 *
12723 * @param array $rate_plans the list of rate plan arrays to sort.
12724 * @param bool $assoc whether to keep the original array keys.
12725 *
12726 * @return array the sorted list of rate plan arrays.
12727 *
12728 * @since 1.15.0 (J) - 1.5.0 (WP)
12729 */
12730 public static function sortRatePlans($rate_plans, $assoc = false)
12731 {
12732 if (!is_array($rate_plans) || count($rate_plans) < 2) {
12733 return $rate_plans;
12734 }
12735
12736 foreach ($rate_plans as $rp_ind => $rate_plan) {
12737 if (!is_array($rate_plan) || empty($rate_plan['name'])) {
12738 // do not proceed in case of a weird array structure
12739 return $rate_plans;
12740 }
12741 // count the sorting-score of this rate plan
12742 $sort_score = 0;
12743 if (stripos($rate_plan['name'], 'Standard') !== false || stripos($rate_plan['name'], 'Base') !== false) {
12744 // "standard rate" rate plans should go first
12745 $sort_score += 3;
12746 } else {
12747 $sort_score--;
12748 }
12749 if (stripos($rate_plan['name'], 'Non') === false && stripos($rate_plan['name'], 'Not') === false) {
12750 // this doesn't seem to be a "non refundable rate" so it should go first
12751 $sort_score += 2;
12752 }
12753
12754 // set sorting score for this rate plan ID
12755 $rate_plans[$rp_ind]['sort_score'] = $sort_score;
12756 }
12757
12758 // apply custom sorting and keep index association
12759 uasort($rate_plans, function($a, $b) {
12760 // sort by name-scoring DESC
12761 $sort_diff = $b['sort_score'] - $a['sort_score'];
12762 if (isset($b['cost'])) {
12763 // apply also sorting by price ASC
12764 $sort_diff += ($a['cost'] < $b['cost']) ? -1 : 1;
12765 }
12766 return $sort_diff;
12767 });
12768
12769 return !$assoc ? array_values($rate_plans) : $rate_plans;
12770 }
12771
12772 /**
12773 * Returns an instance of the Availability helper class.
12774 *
12775 * @return VikBookingAvailability
12776 *
12777 * @since 1.15.0 (J) - 1.5.0 (WP)
12778 */
12779 public static function getAvailabilityInstance()
12780 {
12781 // require library
12782 require_once VBO_ADMIN_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'availability.php';
12783
12784 // return the instance of the class
12785 return VikBookingAvailability::getInstance();
12786 }
12787
12788 /**
12789 * Helper method to quickly guess the internal dependency to import.
12790 *
12791 * @param string $path_id either a full path to a file or its identifier.
12792 *
12793 * @return bool true if the requested library was imported or false.
12794 *
12795 * @since 1.15.0 (J) - 1.5.0 (WP)
12796 */
12797 public static function import($path_id)
12798 {
12799 if (empty($path_id)) {
12800 return false;
12801 }
12802
12803 // always append .php extension
12804 $path_id = basename($path_id, '.php') . '.php';
12805
12806 if (is_file($path_id)) {
12807 // full path given
12808 require_once $path_id;
12809
12810 return true;
12811 }
12812
12813 // try guessing where the file id is located
12814 if (is_file(VBO_SITE_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . $path_id)) {
12815 // library was found
12816 require_once VBO_SITE_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . $path_id;
12817
12818 return true;
12819 }
12820
12821 if (is_file(VBO_ADMIN_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . $path_id)) {
12822 // library was found
12823 require_once VBO_ADMIN_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . $path_id;
12824
12825 return true;
12826 }
12827
12828 return false;
12829 }
12830 }
12831
12832 if (!class_exists('VikResizer'))
12833 {
12834 class VikResizer
12835 {
12836 public function __construct()
12837 {
12838 // objects of this class can also be instantiated without calling the methods statically.
12839 }
12840
12841 /**
12842 * Resizes an image proportionally. For PNG files it can optionally
12843 * trim the image to exclude the transparency, and add some padding to it.
12844 * All PNG files keep the alpha background in the resized version.
12845 *
12846 * @param string $fileimg path to original image file
12847 * @param string $dest path to destination image file
12848 * @param int $towidth
12849 * @param int $toheight
12850 * @param bool $trim_png remove empty background from image
12851 * @param string $trim_pad CSS-style version of padding (top right bottom left) ex: '1 2 3 4'
12852 *
12853 * @return boolean
12854 */
12855 public static function proportionalImage($fileimg, $dest, $towidth, $toheight, $trim_png = false, $trim_pad = null)
12856 {
12857 if (!is_file($fileimg)) {
12858 return false;
12859 }
12860 if (empty($towidth) && empty($toheight)) {
12861 return copy($fileimg, $dest);
12862 }
12863
12864 $result = true;
12865
12866 list ($owid, $ohei, $type) = getimagesize($fileimg);
12867
12868 if ($owid > $towidth || $ohei > $toheight) {
12869 $xscale = $owid / $towidth;
12870 $yscale = $ohei / $toheight;
12871 if ($yscale > $xscale) {
12872 $new_width = round($owid * (1 / $yscale));
12873 $new_height = round($ohei * (1 / $yscale));
12874 } else {
12875 $new_width = round($owid * (1 / $xscale));
12876 $new_height = round($ohei * (1 / $xscale));
12877 }
12878
12879 $imageresized = imagecreatetruecolor($new_width, $new_height);
12880
12881 switch ($type) {
12882 case '1' :
12883 $imagetmp = imagecreatefromgif ($fileimg);
12884 break;
12885 case '2' :
12886 $imagetmp = imagecreatefromjpeg($fileimg);
12887 break;
12888 case '18' :
12889 $imagetmp = imagecreatefromwebp($fileimg);
12890 break;
12891 default :
12892 //keep alpha for PNG files
12893 $background = imagecolorallocate($imageresized, 0, 0, 0);
12894 imagecolortransparent($imageresized, $background);
12895 imagealphablending($imageresized, false);
12896 imagesavealpha($imageresized, true);
12897 //
12898 $imagetmp = imagecreatefrompng($fileimg);
12899 break;
12900 }
12901
12902 if (!$imagetmp) {
12903 return false;
12904 }
12905
12906 imagecopyresampled($imageresized, $imagetmp, 0, 0, 0, 0, $new_width, $new_height, $owid, $ohei);
12907
12908 switch ($type) {
12909 case '1' :
12910 imagegif($imageresized, $dest);
12911 break;
12912 case '2' :
12913 imagejpeg($imageresized, $dest);
12914 break;
12915 case '18' :
12916 imagewebp($imageresized, $dest);
12917 break;
12918 default :
12919 if ($trim_png) {
12920 self::imageTrim($imageresized, $background, $trim_pad);
12921 }
12922 imagepng($imageresized, $dest);
12923 break;
12924 }
12925
12926 imagedestroy($imageresized);
12927 } else {
12928 $result = copy($fileimg, $dest);
12929 }
12930
12931 if (VBOPlatformDetection::isWordPress()) {
12932 /**
12933 * @wponly in order to not lose resized files after installing an update,
12934 * we need to move any uploaded file onto a recovery folder.
12935 */
12936 VikBookingLoader::import('update.manager');
12937 VikBookingUpdateManager::triggerUploadBackup($dest);
12938 }
12939
12940 return $result;
12941 }
12942
12943 /**
12944 * (BETA) Resizes an image proportionally. For PNG files it can optionally
12945 * trim the image to exclude the transparency, and add some padding to it.
12946 * All PNG files keep the alpha background in the resized version.
12947 *
12948 * @param resource $im Image link resource (reference)
12949 * @param int $bg imagecolorallocate color identifier
12950 * @param string $pad CSS-style version of padding (top right bottom left) ex: '1 2 3 4'
12951 *
12952 * @return void
12953 */
12954 public static function imagetrim(&$im, $bg, $pad = null)
12955 {
12956 // Calculate padding for each side.
12957 if (isset($pad)) {
12958 $pp = explode(' ', $pad);
12959 if (isset($pp[3])) {
12960 $p = array((int) $pp[0], (int) $pp[1], (int) $pp[2], (int) $pp[3]);
12961 } elseif (isset($pp[2])) {
12962 $p = array((int) $pp[0], (int) $pp[1], (int) $pp[2], (int) $pp[1]);
12963 } elseif (isset($pp[1])) {
12964 $p = array((int) $pp[0], (int) $pp[1], (int) $pp[0], (int) $pp[1]);
12965 } else {
12966 $p = array_fill(0, 4, (int) $pp[0]);
12967 }
12968 } else {
12969 $p = array_fill(0, 4, 0);
12970 }
12971
12972 // Get the image width and height.
12973 $imw = imagesx($im);
12974 $imh = imagesy($im);
12975
12976 // Set the X variables.
12977 $xmin = $imw;
12978 $xmax = 0;
12979
12980 // Start scanning for the edges.
12981 for ($iy=0; $iy<$imh; $iy++) {
12982 $first = true;
12983 for ($ix=0; $ix<$imw; $ix++) {
12984 $ndx = imagecolorat($im, $ix, $iy);
12985 if ($ndx != $bg) {
12986 if ($xmin > $ix) {
12987 $xmin = $ix;
12988 }
12989 if ($xmax < $ix) {
12990 $xmax = $ix;
12991 }
12992 if (!isset($ymin)) {
12993 $ymin = $iy;
12994 }
12995 $ymax = $iy;
12996 if ($first) {
12997 $ix = $xmax;
12998 $first = false;
12999 }
13000 }
13001 }
13002 }
13003
13004 // The new width and height of the image. (not including padding)
13005 $imw = 1+$xmax-$xmin; // Image width in pixels
13006 $imh = 1+$ymax-$ymin; // Image height in pixels
13007
13008 // Make another image to place the trimmed version in.
13009 $im2 = imagecreatetruecolor($imw+$p[1]+$p[3], $imh+$p[0]+$p[2]);
13010
13011 // Make the background of the new image the same as the background of the old one.
13012 $bg2 = imagecolorallocate($im2, ($bg >> 16) & 0xFF, ($bg >> 8) & 0xFF, $bg & 0xFF);
13013 imagefill($im2, 0, 0, $bg2);
13014
13015 // Copy it over to the new image.
13016 imagecopy($im2, $im, $p[3], $p[0], $xmin, $ymin, $imw, $imh);
13017
13018 // To finish up, we replace the old image which is referenced.
13019 $im = $im2;
13020 }
13021
13022 public static function bandedImage($fileimg, $dest, $towidth, $toheight, $rgb)
13023 {
13024 if (!file_exists($fileimg)) {
13025 return false;
13026 }
13027 if (empty($towidth) && empty($toheight)) {
13028 copy($fileimg, $dest);
13029 return true;
13030 }
13031
13032 $exp = explode(",", $rgb);
13033 if (count($exp) == 3) {
13034 $r = trim($exp[0]);
13035 $g = trim($exp[1]);
13036 $b = trim($exp[2]);
13037 } else {
13038 $r = 0;
13039 $g = 0;
13040 $b = 0;
13041 }
13042
13043 list ($owid, $ohei, $type) = getimagesize($fileimg);
13044
13045 if ($owid > $towidth || $ohei > $toheight) {
13046 $xscale = $owid / $towidth;
13047 $yscale = $ohei / $toheight;
13048 if ($yscale > $xscale) {
13049 $new_width = round($owid * (1 / $yscale));
13050 $new_height = round($ohei * (1 / $yscale));
13051 $ydest = 0;
13052 $diff = $towidth - $new_width;
13053 $xdest = ($diff > 0 ? round($diff / 2) : 0);
13054 } else {
13055 $new_width = round($owid * (1 / $xscale));
13056 $new_height = round($ohei * (1 / $xscale));
13057 $xdest = 0;
13058 $diff = $toheight - $new_height;
13059 $ydest = ($diff > 0 ? round($diff / 2) : 0);
13060 }
13061
13062 $imageresized = imagecreatetruecolor($towidth, $toheight);
13063
13064 $bgColor = imagecolorallocate($imageresized, (int) $r, (int) $g, (int) $b);
13065 imagefill($imageresized, 0, 0, $bgColor);
13066
13067 switch ($type) {
13068 case '1' :
13069 $imagetmp = imagecreatefromgif ($fileimg);
13070 break;
13071 case '2' :
13072 $imagetmp = imagecreatefromjpeg($fileimg);
13073 break;
13074 default :
13075 $imagetmp = imagecreatefrompng($fileimg);
13076 break;
13077 }
13078
13079 imagecopyresampled($imageresized, $imagetmp, $xdest, $ydest, 0, 0, $new_width, $new_height, $owid, $ohei);
13080
13081 switch ($type) {
13082 case '1' :
13083 imagegif ($imageresized, $dest);
13084 break;
13085 case '2' :
13086 imagejpeg($imageresized, $dest);
13087 break;
13088 default :
13089 imagepng($imageresized, $dest);
13090 break;
13091 }
13092
13093 imagedestroy($imageresized);
13094
13095 return true;
13096 } else {
13097 copy($fileimg, $dest);
13098 }
13099 return true;
13100 }
13101
13102 public static function croppedImage($fileimg, $dest, $towidth, $toheight)
13103 {
13104 if (!file_exists($fileimg)) {
13105 return false;
13106 }
13107 if (empty($towidth) && empty($toheight)) {
13108 copy($fileimg, $dest);
13109 return true;
13110 }
13111
13112 list ($owid, $ohei, $type) = getimagesize($fileimg);
13113
13114 if ($owid <= $ohei) {
13115 $new_width = $towidth;
13116 $new_height = ($towidth / $owid) * $ohei;
13117 } else {
13118 $new_height = $toheight;
13119 $new_width = ($new_height / $ohei) * $owid;
13120 }
13121
13122 switch ($type) {
13123 case '1' :
13124 $img_src = imagecreatefromgif ($fileimg);
13125 $img_dest = imagecreate($new_width, $new_height);
13126 break;
13127 case '2' :
13128 $img_src = imagecreatefromjpeg($fileimg);
13129 $img_dest = imagecreatetruecolor($new_width, $new_height);
13130 break;
13131 default :
13132 $img_src = imagecreatefrompng($fileimg);
13133 $img_dest = imagecreatetruecolor($new_width, $new_height);
13134 break;
13135 }
13136
13137 imagecopyresampled($img_dest, $img_src, 0, 0, 0, 0, $new_width, $new_height, $owid, $ohei);
13138
13139 switch ($type) {
13140 case '1' :
13141 $cropped = imagecreate($towidth, $toheight);
13142 break;
13143 case '2' :
13144 $cropped = imagecreatetruecolor($towidth, $toheight);
13145 break;
13146 default :
13147 $cropped = imagecreatetruecolor($towidth, $toheight);
13148 break;
13149 }
13150
13151 imagecopy($cropped, $img_dest, 0, 0, 0, 0, $owid, $ohei);
13152
13153 switch ($type) {
13154 case '1' :
13155 imagegif ($cropped, $dest);
13156 break;
13157 case '2' :
13158 imagejpeg($cropped, $dest);
13159 break;
13160 default :
13161 imagepng($cropped, $dest);
13162 break;
13163 }
13164
13165 imagedestroy($img_dest);
13166 imagedestroy($cropped);
13167
13168 return true;
13169 }
13170 }
13171 }
13172