PluginProbe
VikBooking Hotel Booking Engine & PMS / 1.6.4
VikBooking Hotel Booking Engine & PMS v1.6.4
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.4, at site/helpers/lib.vikbooking.php

13,233 lines 453.5 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 * @wponly JApplication::getMenu() cannot be adapted to WP
63 */
64 }
65
66 public static function isFontAwesomeEnabled($skipsession = false) {
67 if (!$skipsession) {
68 $session = JFactory::getSession();
69 $s = $session->get('vbofaw', '');
70 if (strlen($s)) {
71 return ((int)$s == 1);
72 }
73 }
74 $dbo = JFactory::getDbo();
75 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='usefa';";
76 $dbo->setQuery($q);
77 $dbo->execute();
78 if ($dbo->getNumRows() > 0) {
79 $s = $dbo->loadResult();
80 if (!$skipsession) {
81 $session->set('vbofaw', $s);
82 }
83 return ((int)$s == 1);
84 }
85 $q = "INSERT INTO `#__vikbooking_config` (`param`,`setting`) VALUES ('usefa', '1');";
86 $dbo->setQuery($q);
87 $dbo->execute();
88 if (!$skipsession) {
89 $session->set('vbofaw', '1');
90 }
91 return true;
92 }
93
94 public static function loadFontAwesome($force_load = false) {
95 if (!self::isFontAwesomeEnabled() && !$force_load) {
96 return false;
97 }
98
99 /**
100 * We let the class VikBookingIcons load the proper FontAwesome libraries.
101 *
102 * @since 1.11
103 */
104 VikBookingIcons::loadAssets();
105
106 return true;
107 }
108
109 /**
110 * Checks if modifications or cancellations via front-end are allowed.
111 * 0 = everything is Disabled.
112 * 1 = Disabled, with request message (default).
113 * 2 = Modification Enabled, Cancellation Disabled.
114 * 3 = Cancellation Enabled, Modification Disabled.
115 * 4 = everything is Enabled.
116 *
117 * @return int
118 */
119 public static function getReservationModCanc() {
120 $dbo = JFactory::getDbo();
121 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='resmodcanc';";
122 $dbo->setQuery($q);
123 $dbo->execute();
124 if ($dbo->getNumRows() > 0) {
125 $s = $dbo->loadResult();
126 return (int)$s;
127 }
128 $q = "INSERT INTO `#__vikbooking_config` (`param`,`setting`) VALUES ('resmodcanc', '1');";
129 $dbo->setQuery($q);
130 $dbo->execute();
131 return 1;
132 }
133
134 public static function getReservationModCancMin() {
135 $dbo = JFactory::getDbo();
136 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='resmodcancmin';";
137 $dbo->setQuery($q);
138 $dbo->execute();
139 if ($dbo->getNumRows() > 0) {
140 $s = $dbo->loadResult();
141 return (int)$s;
142 }
143 $q = "INSERT INTO `#__vikbooking_config` (`param`,`setting`) VALUES ('resmodcancmin', '1');";
144 $dbo->setQuery($q);
145 $dbo->execute();
146 return 1;
147 }
148
149 public static function getDefaultDistinctiveFeatures() {
150 $features = array();
151 $features['VBODEFAULTDISTFEATUREONE'] = '';
152 //Below is the default feature for 'Room Code'. One default feature is sufficient
153 //$features['VBODEFAULTDISTFEATURETWO'] = '';
154 return $features;
155 }
156
157 /**
158 * Given the room's parameters and index, tries to take the first distinctive feature.
159 *
160 * @param mixed $rparams string to be decoded, or decoded array/object params
161 * @param int $rindex room index to look for, starting from 1.
162 *
163 * @return mixed false on failure, array otherwise [feature name, feature value]
164 *
165 * @since 1.14 (J) - 1.4.0 (WP)
166 */
167 public static function getRoomUnitDistinctiveFeature($rparams, $rindex) {
168 $rindex = (int)$rindex;
169 if ($rindex < 1 || empty($rparams)) {
170 return false;
171 }
172
173 if (is_string($rparams)) {
174 // decode params
175 $rparams = self::getRoomParam('features', $rparams);
176 }
177
178 if (is_object($rparams)) {
179 // typecast to array
180 $rparams = (array)$rparams;
181 }
182
183 if (!is_array($rparams) || !count($rparams)) {
184 return false;
185 }
186
187 if (isset($rparams['features'])) {
188 $rparams = $rparams['features'];
189 }
190
191 $feature = array();
192 foreach ($rparams as $param_index => $rfeatures) {
193 if ((int)$param_index != $rindex || !is_array($rfeatures) || !count($rfeatures)) {
194 continue;
195 }
196 foreach ($rfeatures as $featname => $featval) {
197 if (empty($featval)) {
198 continue;
199 }
200 // use the first distinctive feature
201 $tn_featname = JText::translate($featname);
202 if ($tn_featname == $featname) {
203 // no translation was applied
204 if (defined('ABSPATH')) {
205 // try to apply a translation through Gettext even if we have to pass a variable
206 $tn_featname = __($featname);
207 } else {
208 // convert the string to a hypothetical INI constant
209 $ini_constant = str_replace(' ', '_', strtoupper($featname));
210 $tn_featname = JText::translate($ini_constant);
211 $tn_featname = $tn_featname == $ini_constant ? $featname : $tn_featname;
212 }
213 }
214 // store values and break loop
215 $feature = array($tn_featname, $featval);
216 break;
217 }
218 }
219
220 return count($feature) ? $feature : false;
221 }
222
223 public static function getRoomUnitNumsUnavailable($order, $idroom) {
224 $dbo = JFactory::getDbo();
225 $unavailable_indexes = array();
226 $first = $order['checkin'];
227 $second = $order['checkout'];
228 $secdiff = $second - $first;
229 $daysdiff = $secdiff / 86400;
230 if (is_int($daysdiff)) {
231 if ($daysdiff < 1) {
232 $daysdiff = 1;
233 }
234 } else {
235 if ($daysdiff < 1) {
236 $daysdiff = 1;
237 } else {
238 $sum = floor($daysdiff) * 86400;
239 $newdiff = $secdiff - $sum;
240 $maxhmore = self::getHoursMoreRb() * 3600;
241 if ($maxhmore >= $newdiff) {
242 $daysdiff = floor($daysdiff);
243 } else {
244 $daysdiff = ceil($daysdiff);
245 }
246 }
247 }
248 $groupdays = self::getGroupDays($first, $second, $daysdiff);
249 $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` ".
250 "FROM `#__vikbooking_busy` AS `b` ".
251 "LEFT JOIN `#__vikbooking_ordersbusy` `ob` ON `ob`.`idbusy`=`b`.`id` ".
252 "LEFT JOIN `#__vikbooking_ordersrooms` `or` ON `or`.`idorder`=`ob`.`idorder` AND `or`.`idorder`!=".(int)$order['id']." ".
253 "LEFT JOIN `#__vikbooking_orders` `o` ON `o`.`id`=`or`.`idorder` AND `o`.`id`=`ob`.`idorder` AND `o`.`id`!=".(int)$order['id']." ".
254 "WHERE `or`.`idroom`=".(int)$idroom." AND `b`.`checkout` > ".time()." AND `o`.`status`='confirmed' AND `ob`.`idorder`!=".(int)$order['id']." AND `ob`.`idorder` > 0;";
255 $dbo->setQuery($q);
256 $busy = $dbo->loadAssocList();
257 if ($busy) {
258 foreach ($groupdays as $gday) {
259 foreach ($busy as $bu) {
260 if (empty($bu['roomindex']) || empty($bu['idorder'])) {
261 continue;
262 }
263 if ($gday >= $bu['checkin'] && $gday <= $bu['realback']) {
264 $unavailable_indexes[$bu['or_id']] = $bu['roomindex'];
265 } elseif (count($groupdays) == 2 && $gday == $groupdays[0]) {
266 if ($groupdays[0] < $bu['checkin'] && $groupdays[0] < $bu['realback'] && $groupdays[1] > $bu['checkin'] && $groupdays[1] > $bu['realback']) {
267 $unavailable_indexes[$bu['or_id']] = $bu['roomindex'];
268 }
269 }
270 }
271 }
272 }
273
274 return $unavailable_indexes;
275 }
276
277 public static function getRoomUnitNumsAvailable($order, $idroom) {
278 $dbo = JFactory::getDbo();
279 $unavailable_indexes = array();
280 $available_indexes = array();
281 $first = $order['checkin'];
282 $second = $order['checkout'];
283 $secdiff = $second - $first;
284 $daysdiff = $secdiff / 86400;
285 if (is_int($daysdiff)) {
286 if ($daysdiff < 1) {
287 $daysdiff = 1;
288 }
289 } else {
290 if ($daysdiff < 1) {
291 $daysdiff = 1;
292 } else {
293 $sum = floor($daysdiff) * 86400;
294 $newdiff = $secdiff - $sum;
295 $maxhmore = self::getHoursMoreRb() * 3600;
296 if ($maxhmore >= $newdiff) {
297 $daysdiff = floor($daysdiff);
298 } else {
299 $daysdiff = ceil($daysdiff);
300 }
301 }
302 }
303 $groupdays = self::getGroupDays($first, $second, $daysdiff);
304 $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` ".
305 "FROM `#__vikbooking_busy` AS `b` ".
306 "LEFT JOIN `#__vikbooking_ordersbusy` `ob` ON `ob`.`idbusy`=`b`.`id` ".
307 "LEFT JOIN `#__vikbooking_ordersrooms` `or` ON `or`.`idorder`=`ob`.`idorder` AND `or`.`idorder`!=".(int)$order['id']." ".
308 "LEFT JOIN `#__vikbooking_orders` `o` ON `o`.`id`=`or`.`idorder` AND `o`.`id`=`ob`.`idorder` AND `o`.`id`!=".(int)$order['id']." ".
309 "WHERE `or`.`idroom`=".(int)$idroom." AND `b`.`checkout` > ".time()." AND `o`.`status`='confirmed' AND `ob`.`idorder`!=".(int)$order['id']." AND `ob`.`idorder` > 0;";
310 $dbo->setQuery($q);
311 $busy = $dbo->loadAssocList();
312 if ($busy) {
313 foreach ($groupdays as $gday) {
314 foreach ($busy as $bu) {
315 if (empty($bu['roomindex']) || empty($bu['idorder'])) {
316 continue;
317 }
318 if ($gday >= $bu['checkin'] && $gday <= $bu['realback']) {
319 $unavailable_indexes[$bu['or_id']] = $bu['roomindex'];
320 } elseif (count($groupdays) == 2 && $gday == $groupdays[0]) {
321 if ($groupdays[0] < $bu['checkin'] && $groupdays[0] < $bu['realback'] && $groupdays[1] > $bu['checkin'] && $groupdays[1] > $bu['realback']) {
322 $unavailable_indexes[$bu['or_id']] = $bu['roomindex'];
323 }
324 }
325 }
326 }
327 }
328 $q = "SELECT `params` FROM `#__vikbooking_rooms` WHERE `id`=".(int)$idroom.";";
329 $dbo->setQuery($q);
330 $dbo->execute();
331 if ($dbo->getNumRows() > 0) {
332 $room_params = $dbo->loadResult();
333 $room_params_arr = json_decode($room_params, true);
334 if (array_key_exists('features', $room_params_arr) && is_array($room_params_arr['features']) && count($room_params_arr['features'])) {
335 foreach ($room_params_arr['features'] as $rind => $rfeatures) {
336 if (in_array($rind, $unavailable_indexes)) {
337 continue;
338 }
339 $available_indexes[] = $rind;
340 }
341 }
342 }
343
344 return $available_indexes;
345 }
346
347 /**
348 * Load the restrictions applying the given filters to the passed rooms.
349 * The ordering of the query SHOULD remain unchanged, because it's required
350 * to have it by ascending order of the ID. So the older records first.
351 * Even when no filters, we always exclude expired restrictions.
352 *
353 * @param boolean $filters whether to apply filters
354 * @param array $rooms the list of rooms to filter
355 *
356 * @return array the list of restrictions found or an empty array.
357 */
358 public static function loadRestrictions($filters = true, $rooms = [])
359 {
360 $dbo = JFactory::getDbo();
361
362 $restrictions = [];
363 $limts = strtotime(date('Y').'-'.date('m').'-'.date('d'));
364
365 if (!$filters) {
366 $q = "SELECT * FROM `#__vikbooking_restrictions` WHERE `dto` = 0 OR `dto` >= ".$limts.";";
367 } else {
368 if (count($rooms) == 0) {
369 $q = "SELECT * FROM `#__vikbooking_restrictions` WHERE `allrooms` = 1 AND (`dto` = 0 OR `dto` >= ".$limts.");";
370 } else {
371 $clause = array();
372 foreach ($rooms as $idr) {
373 if (empty($idr)) continue;
374 $clause[] = "`idrooms` LIKE '%-".intval($idr)."-%'";
375 }
376 if (count($clause) > 0) {
377 $q = "SELECT * FROM `#__vikbooking_restrictions` WHERE (`dto` = 0 OR `dto` >= ".$limts.") AND (`allrooms` = 1 OR (`allrooms` = 0 AND (".implode(" OR ", $clause).")));";
378 } else {
379 $q = "SELECT * FROM `#__vikbooking_restrictions` WHERE `allrooms` = 1 AND (`dto` = 0 OR `dto` >= ".$limts.");";
380 }
381 }
382 }
383 $dbo->setQuery($q);
384 $allrestrictions = $dbo->loadAssocList();
385
386 if ($allrestrictions) {
387 foreach ($allrestrictions as $k => $res) {
388 if (!empty($res['month'])) {
389 $restrictions[$res['month']] = $res;
390 } else {
391 if (!isset($restrictions['range'])) {
392 $restrictions['range'] = array();
393 }
394 $restrictions['range'][$k] = $res;
395 }
396 }
397 }
398
399 return $restrictions;
400 }
401
402 public static function globalRestrictions($restrictions = [])
403 {
404 $ret = [];
405 foreach ($restrictions as $kr => $rr) {
406 if ($kr == 'range') {
407 foreach ($rr as $kd => $dr) {
408 if ($dr['allrooms'] == 1) {
409 $ret['range'][$kd] = $restrictions[$kr][$kd];
410 }
411 }
412 continue;
413 }
414 if ($rr['allrooms'] == 1) {
415 $ret[$kr] = $restrictions[$kr];
416 }
417 }
418 return $ret;
419 }
420
421 /**
422 * From the given restrictions, check-in, check-out and nights, looks for
423 * a restriction to be returned and applied over these stays.
424 * In order to give priority to newer Restriction IDs, the order of the
425 * $restrictions array should be ascending, so that the newer restrictions
426 * will overwrite the array with the last ID (more recent). Only for date ranges.
427 * Returns an array with the record of the restriction found. The loop is not
428 * broken when the first valid restriction is found to give priority to
429 * newer restriction IDs to overwrite older records.
430 *
431 * @param int $first the unix timestamp for the check-in date
432 * @param int $second the unix timestamp for the check-out date
433 * @param int $daysdiff the number of nights of stay
434 * @param array $restrictions the list of restrictions loaded
435 *
436 * @return array the restriction found, or an empty array.
437 */
438 public static function parseSeasonRestrictions($first, $second, $daysdiff, $restrictions) {
439 $season_restrictions = array();
440 $restrcheckin = getdate($first);
441 $restrcheckout = getdate($second);
442 if (array_key_exists($restrcheckin['mon'], $restrictions)) {
443 //restriction found for this month, checking:
444 $season_restrictions['id'] = $restrictions[$restrcheckin['mon']]['id'];
445 $season_restrictions['name'] = $restrictions[$restrcheckin['mon']]['name'];
446 $season_restrictions['allowed'] = true; //set to false when these nights are not allowed
447 if (strlen($restrictions[$restrcheckin['mon']]['wday']) > 0) {
448 //Week Day Arrival Restriction
449 $rvalidwdays = array($restrictions[$restrcheckin['mon']]['wday']);
450 if (strlen($restrictions[$restrcheckin['mon']]['wdaytwo']) > 0) {
451 $rvalidwdays[] = $restrictions[$restrcheckin['mon']]['wdaytwo'];
452 }
453 $season_restrictions['wdays'] = $rvalidwdays;
454 } elseif (!empty($restrictions[$restrcheckin['mon']]['ctad']) || !empty($restrictions[$restrcheckin['mon']]['ctdd'])) {
455 if (!empty($restrictions[$restrcheckin['mon']]['ctad'])) {
456 $season_restrictions['cta'] = explode(',', $restrictions[$restrcheckin['mon']]['ctad']);
457 }
458 if (!empty($restrictions[$restrcheckin['mon']]['ctdd'])) {
459 $season_restrictions['ctd'] = explode(',', $restrictions[$restrcheckin['mon']]['ctdd']);
460 }
461 }
462 if (!empty($restrictions[$restrcheckin['mon']]['maxlos']) && $restrictions[$restrcheckin['mon']]['maxlos'] > 0 && $restrictions[$restrcheckin['mon']]['maxlos'] > $restrictions[$restrcheckin['mon']]['minlos']) {
463 $season_restrictions['maxlos'] = $restrictions[$restrcheckin['mon']]['maxlos'];
464 if ($daysdiff > $restrictions[$restrcheckin['mon']]['maxlos']) {
465 $season_restrictions['allowed'] = false;
466 }
467 }
468 if ($daysdiff < $restrictions[$restrcheckin['mon']]['minlos']) {
469 $season_restrictions['allowed'] = false;
470 }
471 $season_restrictions['minlos'] = $restrictions[$restrcheckin['mon']]['minlos'];
472 } elseif (array_key_exists('range', $restrictions)) {
473 foreach($restrictions['range'] as $restr) {
474 if ($restr['dfrom'] <= $first && $restr['dto'] >= $first) {
475 //restriction found for this date range, checking:
476 $season_restrictions['id'] = $restr['id'];
477 $season_restrictions['name'] = $restr['name'];
478 $season_restrictions['allowed'] = true; //set to false when these nights are not allowed
479 if (strlen((string)$restr['wday']) > 0) {
480 //Week Day Arrival Restriction
481 $rvalidwdays = array($restr['wday']);
482 if (strlen((string)$restr['wdaytwo']) > 0) {
483 $rvalidwdays[] = $restr['wdaytwo'];
484 }
485 $season_restrictions['wdays'] = $rvalidwdays;
486 } elseif (!empty($restr['ctad']) || !empty($restr['ctdd'])) {
487 if (!empty($restr['ctad'])) {
488 $season_restrictions['cta'] = explode(',', $restr['ctad']);
489 }
490 if (!empty($restr['ctdd'])) {
491 $season_restrictions['ctd'] = explode(',', $restr['ctdd']);
492 }
493 }
494 if (!empty($restr['maxlos']) && $restr['maxlos'] > 0 && $restr['maxlos'] > $restr['minlos']) {
495 $season_restrictions['maxlos'] = $restr['maxlos'];
496 if ($daysdiff > $restr['maxlos']) {
497 $season_restrictions['allowed'] = false;
498 }
499 }
500 if ($daysdiff < $restr['minlos']) {
501 $season_restrictions['allowed'] = false;
502 }
503 $season_restrictions['minlos'] = $restr['minlos'];
504 }
505 }
506 }
507
508 return $season_restrictions;
509 }
510
511 public static function compareSeasonRestrictionsNights($restrictions)
512 {
513 $base_compare = array();
514 $base_nights = 0;
515 foreach ($restrictions as $nights => $restr) {
516 $base_compare = $restr;
517 $base_nights = $nights;
518 break;
519 }
520
521 /**
522 * Prepare 1st hypothetical multi dimension array for string-comparison with array_diff().
523 * Keys "cta" and "ctd" may be the 2nd dimension array variables which cannot be casted to string.
524 *
525 * @since 1.15.0 (J) - 1.5.0 (WP)
526 */
527 list($casted_base_compare, $use_base_compare) = self::prepareMultiDimArrayDiff($base_compare);
528
529 foreach ($restrictions as $nights => $restr) {
530 if ($nights == $base_nights) {
531 continue;
532 }
533
534 // prepare 2nd hypothetical multi dimension array for string-comparison with array_diff().
535 list($casted_restr, $use_restr) = self::prepareMultiDimArrayDiff($restr);
536
537 // get associative array of differences
538 $diff = array_diff($use_base_compare, $use_restr);
539
540 if (count($diff) > 0 && array_key_exists('id', $diff)) {
541 // return differences only if the Restriction ID is different: ignore allowed, wdays, minlos, maxlos.
542 // only one Restriction per time should be applied to certain Season Dates but check just in case.
543 return self::restoreMultiDimArrayDiff($casted_base_compare, $casted_restr, $diff);
544 }
545 }
546
547 return array();
548 }
549
550 /**
551 * Methods using array_diff() may need one-dimension arrays, because this
552 * native functions applies a string comparison, and so casting an array
553 * value to a string would generate a Notice message. This method simply
554 * converts any sub-array into a restorable string into a non-scalar var.
555 * This only works when comparing 2 array variables.
556 *
557 * @param array $arr the hypothetical multi dimension array.
558 *
559 * @return array the one dimension array to be passed to array_diff()
560 * and the list of keys converted to strings for comparison.
561 *
562 * @since 1.15.0 (J) - 1.5.0 (WP)
563 */
564 public static function prepareMultiDimArrayDiff($arr)
565 {
566 $casted_keys = array();
567
568 if (!is_array($arr) || !count($arr)) {
569 return array($casted_keys, $arr);
570 }
571
572 foreach ($arr as $key => $val) {
573 if (!is_scalar($val)) {
574 // memorize original value
575 $casted_keys[$key] = $val;
576 // cast to string must be applied
577 $arr[$key] = json_encode($val);
578 }
579 }
580
581 return array($casted_keys, $arr);
582 }
583
584 /**
585 * Methods using array_diff() may need one-dimension arrays, because this
586 * native functions applies a string comparison, and so casting an array
587 * value to a string would generate a Notice message. This method restores
588 * the original values of the "prepared" arrays by getting the list of the
589 * modified keys from non-scalar to scalar values.
590 * This only works when comparing 2 array variables.
591 *
592 * @param array $keys_one associative array of casted key-value pairs from 1st arg.
593 * @param array $keys_two associative array of casted key-value pairs from 2nd arg.
594 * @param array $arr the array result of array_diff().
595 *
596 * @return array the array result of array_diff() with its original values.
597 *
598 * @since 1.15.0 (J) - 1.5.0 (WP)
599 */
600 public static function restoreMultiDimArrayDiff($keys_one, $keys_two, $arr)
601 {
602 if (!is_array($arr) || !count($arr)) {
603 return $arr;
604 }
605
606 foreach ($arr as $key => $val) {
607 // keys returned by array_diff can be present in both $keys_one and $keys_two
608 if (isset($keys_one[$key])) {
609 // always give higher priority to the 1st argument keys for array_diff()
610 $arr[$key] = json_decode($keys_one[$key]);
611 } elseif (isset($keys_two[$key])) {
612 // fallback to 2nd argument keys
613 $arr[$key] = json_decode($keys_two[$key]);
614 }
615 }
616
617 return $arr;
618 }
619
620 public static function roomRestrictions($roomid, $restrictions) {
621 $ret = array();
622 if (!empty($roomid) && count($restrictions) > 0) {
623 foreach($restrictions as $kr => $rr) {
624 if ($kr == 'range') {
625 foreach ($rr as $kd => $dr) {
626 if ($dr['allrooms'] == 0 && !empty($dr['idrooms'])) {
627 $allrooms = explode(';', $dr['idrooms']);
628 if (in_array('-'.$roomid.'-', $allrooms)) {
629 $ret['range'][$kd] = $restrictions[$kr][$kd];
630 }
631 }
632 }
633 } else {
634 if ($rr['allrooms'] == 0 && !empty($rr['idrooms'])) {
635 $allrooms = explode(';', $rr['idrooms']);
636 if (in_array('-'.$roomid.'-', $allrooms)) {
637 $ret[$kr] = $restrictions[$kr];
638 }
639 }
640 }
641 }
642 }
643 return $ret;
644 }
645
646 public static function validateRoomRestriction($roomrestr, $restrcheckin, $restrcheckout, $daysdiff) {
647 $restrictionerrmsg = '';
648 $restrictions_affcount = 0;
649 if (array_key_exists($restrcheckin['mon'], $roomrestr)) {
650 //restriction found for this month, checking:
651 $restrictions_affcount++;
652 if (strlen((string)$roomrestr[$restrcheckin['mon']]['wday'])) {
653 $rvalidwdays = array($roomrestr[$restrcheckin['mon']]['wday']);
654 if (strlen((string)$roomrestr[$restrcheckin['mon']]['wdaytwo'])) {
655 $rvalidwdays[] = $roomrestr[$restrcheckin['mon']]['wdaytwo'];
656 }
657 if (!in_array($restrcheckin['wday'], $rvalidwdays)) {
658 $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']) : ''));
659 } elseif ($roomrestr[$restrcheckin['mon']]['multiplyminlos'] == 1) {
660 if (($daysdiff % $roomrestr[$restrcheckin['mon']]['minlos']) != 0) {
661 $restrictionerrmsg = JText::sprintf('VBRESTRTIPMULTIPLYMINLOS', self::sayMonth($restrcheckin['mon']), $roomrestr[$restrcheckin['mon']]['minlos']);
662 }
663 }
664 $comborestr = self::parseJsDrangeWdayCombo($roomrestr[$restrcheckin['mon']]);
665 if (count($comborestr) > 0) {
666 if (array_key_exists($restrcheckin['wday'], $comborestr)) {
667 if (!in_array($restrcheckout['wday'], $comborestr[$restrcheckin['wday']])) {
668 $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']));
669 }
670 }
671 }
672 } elseif (!empty($roomrestr[$restrcheckin['mon']]['ctad']) || !empty($roomrestr[$restrcheckin['mon']]['ctdd'])) {
673 if (!empty($roomrestr[$restrcheckin['mon']]['ctad'])) {
674 $ctarestrictions = explode(',', $roomrestr[$restrcheckin['mon']]['ctad']);
675 if (in_array('-'.$restrcheckin['wday'].'-', $ctarestrictions)) {
676 $restrictionerrmsg = JText::sprintf('VBRESTRERRWDAYCTAMONTH', self::sayWeekDay($restrcheckin['wday']), self::sayMonth($restrcheckin['mon']));
677 }
678 }
679 if (!empty($roomrestr[$restrcheckin['mon']]['ctdd'])) {
680 $ctdrestrictions = explode(',', $roomrestr[$restrcheckin['mon']]['ctdd']);
681 if (in_array('-'.$restrcheckout['wday'].'-', $ctdrestrictions)) {
682 $restrictionerrmsg = JText::sprintf('VBRESTRERRWDAYCTDMONTH', self::sayWeekDay($restrcheckout['wday']), self::sayMonth($restrcheckin['mon']));
683 }
684 }
685 }
686 if (!empty($roomrestr[$restrcheckin['mon']]['maxlos']) && $roomrestr[$restrcheckin['mon']]['maxlos'] > 0 && $roomrestr[$restrcheckin['mon']]['maxlos'] > $roomrestr[$restrcheckin['mon']]['minlos']) {
687 if ($daysdiff > $roomrestr[$restrcheckin['mon']]['maxlos']) {
688 $restrictionerrmsg = JText::sprintf('VBRESTRTIPMAXLOSEXCEEDED', self::sayMonth($restrcheckin['mon']), $roomrestr[$restrcheckin['mon']]['maxlos']);
689 }
690 }
691 if ($daysdiff < $roomrestr[$restrcheckin['mon']]['minlos']) {
692 $restrictionerrmsg = JText::sprintf('VBRESTRTIPMINLOSEXCEEDED', self::sayMonth($restrcheckin['mon']), $roomrestr[$restrcheckin['mon']]['minlos']);
693 }
694 } elseif (array_key_exists('range', $roomrestr)) {
695 $restrictionsvalid = true;
696 /**
697 * We use this map to know which restriction IDs are okay or not okay with the Min LOS.
698 * The most recent restrictions will have a higher priority over the oldest ones.
699 *
700 * @since 1.12.1
701 */
702 $minlos_priority = array(
703 'ok' => array(),
704 'nok' => array()
705 );
706 foreach ($roomrestr['range'] as $restr) {
707 /**
708 * We should not always add 82799 seconds to the end date of the restriction
709 * because if they only last for one day (like a Saturday), then $restr['dto']
710 * will be already set to the time 23:59:59.
711 *
712 * @since 1.13 (J) - 1.2.18 (WP)
713 */
714 $end_operator = date('Y-m-d', $restr['dfrom']) != date('Y-m-d', $restr['dto']) ? 82799 : 0;
715 //
716 if ($restr['dfrom'] <= $restrcheckin[0] && ($restr['dto'] + $end_operator) >= $restrcheckin[0]) {
717 // restriction found for this date range based on arrival date, check if compliant
718 $restrictions_affcount++;
719 if (strlen((string)$restr['wday']) > 0) {
720 $rvalidwdays = array($restr['wday']);
721 if (strlen((string)$restr['wdaytwo']) > 0) {
722 $rvalidwdays[] = $restr['wdaytwo'];
723 }
724 if (!in_array($restrcheckin['wday'], $rvalidwdays)) {
725 $restrictionsvalid = false;
726 $restrictionerrmsg = JText::sprintf('VBRESTRTIPWDAYARRIVALRANGE', self::sayWeekDay($restr['wday']).(strlen((string)$restr['wdaytwo']) ? '/'.self::sayWeekDay($restr['wdaytwo']) : ''));
727 } elseif ($restr['multiplyminlos'] == 1) {
728 if (($daysdiff % $restr['minlos']) != 0) {
729 $restrictionsvalid = false;
730 $restrictionerrmsg = JText::sprintf('VBRESTRTIPMULTIPLYMINLOSRANGE', $restr['minlos']);
731 }
732 }
733 $comborestr = self::parseJsDrangeWdayCombo($restr);
734 if ($comborestr) {
735 if (array_key_exists($restrcheckin['wday'], $comborestr)) {
736 if (!in_array($restrcheckout['wday'], $comborestr[$restrcheckin['wday']])) {
737 $restrictionsvalid = false;
738 $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']));
739 }
740 }
741 }
742 } elseif (!empty($restr['ctad']) || !empty($restr['ctdd'])) {
743 if (!empty($restr['ctad'])) {
744 $ctarestrictions = explode(',', $restr['ctad']);
745 if (in_array('-'.$restrcheckin['wday'].'-', $ctarestrictions)) {
746 $restrictionerrmsg = JText::sprintf('VBRESTRERRWDAYCTARANGE', self::sayWeekDay($restrcheckin['wday']));
747 }
748 }
749 if (!empty($restr['ctdd'])) {
750 $ctdrestrictions = explode(',', $restr['ctdd']);
751 if (in_array('-'.$restrcheckout['wday'].'-', $ctdrestrictions) && $restrcheckout[0] <= ($restr['dto'] + $end_operator)) {
752 $restrictionerrmsg = JText::sprintf('VBRESTRERRWDAYCTDRANGE', self::sayWeekDay($restrcheckout['wday']));
753 }
754 }
755 }
756 if (!empty($restr['maxlos']) && $restr['maxlos'] > 0 && $restr['maxlos'] > $restr['minlos']) {
757 if ($daysdiff > $restr['maxlos']) {
758 $restrictionsvalid = false;
759 $restrictionerrmsg = JText::sprintf('VBRESTRTIPMAXLOSEXCEEDEDRANGE', $restr['maxlos']);
760 }
761 }
762 if ($daysdiff < $restr['minlos']) {
763 $restrictionsvalid = false;
764 $restrictionerrmsg = JText::sprintf('VBRESTRTIPMINLOSEXCEEDEDRANGE', $restr['minlos']);
765 array_push($minlos_priority['nok'], (int)$restr['id']);
766 } else {
767 array_push($minlos_priority['ok'], (int)$restr['id']);
768 }
769 } elseif ($restr['dfrom'] <= $restrcheckout[0] && ($restr['dto'] + $end_operator) >= $restrcheckout[0] && !empty($restr['ctdd'])) {
770 /**
771 * We validate the CTD restrictions depending on the check-out date.
772 *
773 * @since 1.16.3 (J) - 1.6.3 (WP)
774 */
775 $ctdrestrictions = explode(',', $restr['ctdd']);
776 if (in_array('-'.$restrcheckout['wday'].'-', $ctdrestrictions)) {
777 $restrictions_affcount++;
778 $restrictionsvalid = false;
779 $restrictionerrmsg = JText::sprintf('VBRESTRERRWDAYCTDRANGE', VikBooking::sayWeekDay($restrcheckout['wday']));
780 }
781 }
782 }
783 if (!$restrictionsvalid && count($minlos_priority['ok']) && count($minlos_priority['nok']) && max($minlos_priority['ok']) > max($minlos_priority['nok'])) {
784 // we unset the error message because a more recent restriction is allowing this MinLOS
785 $restrictionerrmsg = '';
786 }
787 }
788 //April 2017 - Check global restriction of Min LOS for TAC functions in VBO and VCM
789 if (empty($restrictionerrmsg) && count($roomrestr) && $restrictions_affcount <= 0) {
790 //Check global MinLOS (only in case there are no restrictions affecting these dates or no restrictions at all)
791 $globminlos = self::getDefaultNightsCalendar();
792 if ($globminlos > 1 && $daysdiff < $globminlos) {
793 $restrictionerrmsg = JText::sprintf('VBRESTRERRMINLOSEXCEEDEDRANGE', $globminlos);
794 }
795 }
796 //
797
798 return $restrictionerrmsg;
799 }
800
801 public static function parseJsDrangeWdayCombo($drestr) {
802 $combo = array();
803 if (strlen((string)$drestr['wday']) && strlen((string)$drestr['wdaytwo']) && !empty($drestr['wdaycombo'])) {
804 $cparts = explode(':', $drestr['wdaycombo']);
805 foreach($cparts as $kc => $cw) {
806 if (!empty($cw)) {
807 $nowcombo = explode('-', $cw);
808 $combo[intval($nowcombo[0])][] = intval($nowcombo[1]);
809 }
810 }
811 }
812 return $combo;
813 }
814
815 public static function validateRoomPackage($pkg_id, $rooms, $numnights, $checkints, $checkoutts) {
816 $dbo = JFactory::getDbo();
817 $pkg = array();
818 $q = "SELECT * FROM `#__vikbooking_packages` WHERE `id`='".intval($pkg_id)."';";
819 $dbo->setQuery($q);
820 $dbo->execute();
821 if ($dbo->getNumRows() == 1) {
822 $pkg = $dbo->loadAssoc();
823 $vbo_tn = self::getTranslator();
824 $vbo_tn->translateContents($pkg, '#__vikbooking_packages');
825 } else {
826 return JText::translate('VBOPKGERRNOTFOUND');
827 }
828 $rooms_req = array();
829 foreach ($rooms as $num => $room) {
830 if (!empty($room['id']) && !in_array($room['id'], $rooms_req)) {
831 $rooms_req[] = $room['id'];
832 }
833 }
834 $q = "SELECT `id` FROM `#__vikbooking_packages_rooms` WHERE `idpackage`=".$pkg['id']." AND `idroom` IN (".implode(', ', $rooms_req).");";
835 $dbo->setQuery($q);
836 $dbo->execute();
837 if ($dbo->getNumRows() != count($rooms_req)) {
838 //error, not all the rooms requested are available for this package
839 return JText::translate('VBOPKGERRNOTROOM');
840 }
841 if ($numnights < $pkg['minlos'] || ($pkg['maxlos'] > 0 && $numnights > $pkg['maxlos'])) {
842 return JText::translate('VBOPKGERRNUMNIGHTS');
843 }
844 if ($checkints < $pkg['dfrom'] || $checkints > $pkg['dto']) {
845 return JText::translate('VBOPKGERRCHECKIND');
846 }
847 if ($checkoutts < $pkg['dfrom'] || ($checkoutts > $pkg['dto'] && date('Y-m-d', $pkg['dfrom']) != date('Y-m-d', $pkg['dto']))) {
848 //VBO 1.10 - we allow a check-out date after the pkg validity-end-date only if the validity dates are equal (dfrom & dto)
849 return JText::translate('VBOPKGERRCHECKOUTD');
850 }
851 if (!empty($pkg['excldates'])) {
852 //this would check if any stay date is excluded
853 //$bookdates_ts = self::getGroupDays($checkints, $checkoutts, $numnights);
854 //check just the arrival and departure dates
855 $bookdates_ts = array($checkints, $checkoutts);
856 $bookdates = array();
857 foreach ($bookdates_ts as $bookdate_ts) {
858 $info_d = getdate($bookdate_ts);
859 $bookdates[] = $info_d['mon'].'-'.$info_d['mday'].'-'.$info_d['year'];
860 }
861 $edates = explode(';', $pkg['excldates']);
862 foreach ($edates as $edate) {
863 if (!empty($edate) && in_array($edate, $bookdates)) {
864 return JText::sprintf('VBOPKGERREXCLUDEDATE', $edate);
865 }
866 }
867 }
868 return $pkg;
869 }
870
871 public static function getPackage($pkg_id) {
872 $dbo = JFactory::getDbo();
873 $pkg = array();
874 $q = "SELECT * FROM `#__vikbooking_packages` WHERE `id`='".intval($pkg_id)."';";
875 $dbo->setQuery($q);
876 $dbo->execute();
877 if ($dbo->getNumRows() == 1) {
878 $pkg = $dbo->loadAssoc();
879 }
880 return $pkg;
881 }
882
883 public static function getRoomParam($paramname, $paramstr)
884 {
885 if (empty($paramstr)) {
886 return '';
887 }
888
889 $paramarr = json_decode($paramstr, true);
890
891 if (is_array($paramarr) && isset($paramarr[$paramname])) {
892 return $paramarr[$paramname];
893 }
894
895 return '';
896 }
897
898 public static function filterNightsSeasonsCal ($arr_nights) {
899 $nights = array();
900 foreach ($arr_nights as $night) {
901 if (intval(trim($night)) > 0) {
902 $nights[] = intval(trim($night));
903 }
904 }
905 sort($nights);
906 return array_unique($nights);
907 }
908
909 public static function getSeasonRangeTs ($from, $to, $year) {
910 $sfrom = 0;
911 $sto = 0;
912 $tsbase = mktime(0, 0, 0, 1, 1, $year);
913 $curyear = $year;
914 $tsbasetwo = $tsbase;
915 $curyeartwo = $year;
916 if ($from > $to) {
917 //between two years
918 $curyeartwo += 1;
919 $tsbasetwo = mktime(0, 0, 0, 1, 1, $curyeartwo);
920 }
921 $sfrom = ($tsbase + $from);
922 $sto = ($tsbasetwo + $to);
923 if ($curyear % 4 == 0 && ($curyear % 100 != 0 || $curyear % 400 == 0)) {
924 //leap years
925 $infoseason = getdate($sfrom);
926 $leapts = mktime(0, 0, 0, 2, 29, $infoseason['year']);
927 if ($infoseason[0] > $leapts) {
928 /**
929 * Timestamp must be greater than the leap-day of Feb 29th.
930 * It used to be checked for >= $leapts.
931 *
932 * @since July 3rd 2019
933 */
934 $sfrom += 86400;
935 if ($curyear == $curyeartwo) {
936 $sto += 86400;
937 }
938 }
939 } elseif ($curyeartwo % 4 == 0 && ($curyeartwo % 100 != 0 || $curyeartwo % 400 == 0)) {
940 //leap years
941 $infoseason = getdate($sto);
942 $leapts = mktime(0, 0, 0, 2, 29, $infoseason['year']);
943 if ($infoseason[0] > $leapts) {
944 /**
945 * Timestamp must be greater than the leap-day of Feb 29th.
946 * It used to be checked for >= $leapts.
947 *
948 * @since July 3rd 2019
949 */
950 $sto += 86400;
951 }
952 }
953 return array($sfrom, $sto);
954 }
955
956 public static function sortSeasonsRangeTs ($all_seasons) {
957 $sorted = array();
958 $map = array();
959 foreach ($all_seasons as $key => $season) {
960 $map[$key] = $season['from_ts'];
961 }
962 asort($map);
963 foreach ($map as $key => $s) {
964 $sorted[] = $all_seasons[$key];
965 }
966 return $sorted;
967 }
968
969 public static function formatSeasonDates ($from_ts, $to_ts) {
970 $one = getdate($from_ts);
971 $two = getdate($to_ts);
972 $months_map = array(
973 1 => JText::translate('VBSHORTMONTHONE'),
974 2 => JText::translate('VBSHORTMONTHTWO'),
975 3 => JText::translate('VBSHORTMONTHTHREE'),
976 4 => JText::translate('VBSHORTMONTHFOUR'),
977 5 => JText::translate('VBSHORTMONTHFIVE'),
978 6 => JText::translate('VBSHORTMONTHSIX'),
979 7 => JText::translate('VBSHORTMONTHSEVEN'),
980 8 => JText::translate('VBSHORTMONTHEIGHT'),
981 9 => JText::translate('VBSHORTMONTHNINE'),
982 10 => JText::translate('VBSHORTMONTHTEN'),
983 11 => JText::translate('VBSHORTMONTHELEVEN'),
984 12 => JText::translate('VBSHORTMONTHTWELVE')
985 );
986 $mday_map = array(
987 1 => JText::translate('VBMDAYFRIST'),
988 2 => JText::translate('VBMDAYSECOND'),
989 3 => JText::translate('VBMDAYTHIRD'),
990 'generic' => JText::translate('VBMDAYNUMGEN')
991 );
992 if ($one['year'] == $two['year']) {
993 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>';
994 }
995 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'];
996 }
997
998 public static function getFirstCustDataField($custdata) {
999 $first_field = '';
1000 if (strpos($custdata, JText::translate('VBDBTEXTROOMCLOSED')) !== false) {
1001 //Room is closed with this booking
1002 return '----';
1003 }
1004 $parts = explode("\n", $custdata);
1005 foreach ($parts as $part) {
1006 if (!empty($part)) {
1007 $field = explode(':', trim($part));
1008 if (!empty($field[1])) {
1009 return trim($field[1]);
1010 }
1011 }
1012 }
1013 return $first_field;
1014 }
1015
1016 /**
1017 * This method composes a string to be logged for the admin
1018 * to keep track of what was inside the booking before the
1019 * modification. Returns a string and it uses language definitions
1020 * that should be available on the front-end and back-end INI files.
1021 *
1022 * @param array $old_booking the array of the booking prior to the modification
1023 * @param array $room_stay_dates optional list of room stay information in case of split stays.
1024 *
1025 * @return string text describing the situation with the booking before the changes.
1026 *
1027 * @since 1.16.0 (J) - 1.6.0 (WP) added second argument.
1028 */
1029 public static function getLogBookingModification($old_booking, $room_stay_dates = [])
1030 {
1031 $vbo_df = self::getDateFormat();
1032 $df = $vbo_df == "%d/%m/%Y" ? 'd/m/Y' : ($vbo_df == "%m/%d/%Y" ? 'm/d/Y' : 'Y-m-d');
1033 $wdays_map = array(
1034 JText::translate('VBWEEKDAYZERO'),
1035 JText::translate('VBWEEKDAYONE'),
1036 JText::translate('VBWEEKDAYTWO'),
1037 JText::translate('VBWEEKDAYTHREE'),
1038 JText::translate('VBWEEKDAYFOUR'),
1039 JText::translate('VBWEEKDAYFIVE'),
1040 JText::translate('VBWEEKDAYSIX'),
1041 );
1042 $now_info = getdate();
1043 $checkin_info = getdate($old_booking['checkin']);
1044 $checkout_info = getdate($old_booking['checkout']);
1045
1046 $datemod = $wdays_map[$now_info['wday']].', '.date($df.' H:i', $now_info[0]);
1047 $prev_nights = $old_booking['days'].' '.($old_booking['days'] > 1 ? JText::translate('VBDAYS') : JText::translate('VBDAY'));
1048 $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]);
1049 $prev_rooms = '';
1050
1051 $orooms_map = [];
1052 $orooms_arr = [];
1053
1054 if (isset($old_booking['rooms_info'])) {
1055 foreach ($old_booking['rooms_info'] as $oroom) {
1056 $orooms_arr[] = $oroom['name'].', '.JText::translate('VBMAILADULTS').': '.$oroom['adults'].', '.JText::translate('VBMAILCHILDREN').': '.$oroom['children'];
1057 if (!empty($oroom['idroom'])) {
1058 $orooms_map[$oroom['idroom']] = $oroom['name'];
1059 }
1060 }
1061 $prev_rooms = implode("\n", $orooms_arr);
1062 }
1063
1064 if (!empty($old_booking['split_stay']) && !empty($room_stay_dates) && count($orooms_map)) {
1065 $split_stay_prev_infos = [];
1066 foreach ($room_stay_dates as $rs_ind => $room_stay) {
1067 if (empty($room_stay['idroom']) || !isset($orooms_map[$room_stay['idroom']])) {
1068 continue;
1069 }
1070 $room_checkin = date('Y-m-d', $room_stay['checkin']);
1071 $room_checkout = date('Y-m-d', $room_stay['checkout']);
1072 $room_nights = !empty($room_stay['nights']) ? $room_stay['nights'] : 0;
1073
1074 $split_stay_descr = $orooms_map[$room_stay['idroom']];
1075 if (!empty($room_nights)) {
1076 $split_stay_descr .= ': ' . $room_nights . ' ' . ($room_nights > 1 ? JText::translate('VBDAYS') : JText::translate('VBDAY'));
1077 }
1078 $split_stay_descr .= ', ';
1079 $split_stay_descr .= $room_checkin . ' - ' . $room_checkout;
1080
1081 // push split stay description string
1082 $split_stay_prev_infos[] = $split_stay_descr;
1083 }
1084 if (count($split_stay_prev_infos)) {
1085 $prev_rooms .= "\n" . JText::translate('VBO_SPLIT_STAY') . ":\n" . implode("\n", $split_stay_prev_infos);
1086 }
1087 }
1088
1089 $currencyname = self::getCurrencyName();
1090 $prev_total = $currencyname.' '.self::numberFormat($old_booking['total']);
1091
1092 return JText::sprintf('VBOBOOKMODLOGSTR', $datemod, $prev_dates, $prev_rooms, $prev_total);
1093 }
1094
1095 /**
1096 * This method invokes the class
1097 * VikChannelManagerLogos (new in VCM 1.6.4) to
1098 * map the name of a channel to its corresponding logo.
1099 * The method can also be used to get an istance of the class.
1100 *
1101 * @param mixed $provenience either a string or an array with main and sub channels.
1102 * @param boolean $get_istance
1103 *
1104 * @return mixed boolean if the Class doesn't exist or if the provenience cannot be matched. Instance otherwise.
1105 */
1106 public static function getVcmChannelsLogo($provenience, $get_istance = false) {
1107 if (!file_exists(VCM_ADMIN_PATH.DIRECTORY_SEPARATOR.'helpers'.DIRECTORY_SEPARATOR.'logos.php')) {
1108 return false;
1109 }
1110 if (!class_exists('VikChannelManagerLogos')) {
1111 require_once(VCM_ADMIN_PATH.DIRECTORY_SEPARATOR.'helpers'.DIRECTORY_SEPARATOR.'logos.php');
1112 }
1113
1114 /**
1115 * Due to the new iCal channel we now support main and sub channels.
1116 *
1117 * @since 1.13 - VCM 1.7.0
1118 */
1119 if (is_string($provenience) && strpos($provenience, '_') !== false) {
1120 $provenience = explode('_', $provenience);
1121 }
1122 $main_channel = '';
1123 $full_channel = '';
1124 if (is_array($provenience)) {
1125 if (count($provenience) > 1) {
1126 $main_channel = $provenience[1];
1127 } else {
1128 $main_channel = $provenience[0];
1129 }
1130 if (stripos($provenience[0], 'ical') !== false) {
1131 $full_channel = implode('_', $provenience);
1132 }
1133 } else {
1134 $main_channel = $provenience;
1135 }
1136
1137 // get object instance by passing the main provenience
1138 $obj = new VikChannelManagerLogos($main_channel);
1139
1140 // update provenience with main and full channel
1141 if (!empty($full_channel)) {
1142 $obj->setProvenience($main_channel, $full_channel);
1143 }
1144
1145 // return either the instance or the logo URL for this channel source
1146 return $get_istance ? $obj : $obj->getLogoURL();
1147 }
1148
1149 public static function vcmAutoUpdate() {
1150 if (!file_exists(VCM_SITE_PATH.DIRECTORY_SEPARATOR.'helpers'.DIRECTORY_SEPARATOR.'lib.vikchannelmanager.php')) {
1151 return -1;
1152 }
1153 $dbo = JFactory::getDbo();
1154 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='vcmautoupd';";
1155 $dbo->setQuery($q);
1156 $dbo->execute();
1157 $s = $dbo->loadAssocList();
1158 return (intval($s[0]['setting']) > 0 ? 1 : 0);
1159 }
1160
1161 public static function getVcmInvoker() {
1162 if (!class_exists('VboVcmInvoker')) {
1163 require_once(VBO_SITE_PATH . DIRECTORY_SEPARATOR . "helpers" . DIRECTORY_SEPARATOR . "vcm.php");
1164 }
1165 return new VboVcmInvoker();
1166 }
1167
1168 public static function getBookingHistoryInstance() {
1169 if (!class_exists('VboBookingHistory')) {
1170 require_once(VBO_SITE_PATH . DIRECTORY_SEPARATOR . "helpers" . DIRECTORY_SEPARATOR . "history.php");
1171 }
1172 return new VboBookingHistory();
1173 }
1174
1175 /**
1176 * Returns an instance of the VCMChatHandler class to handle
1177 * the messaging/chat for the given reservation ID.
1178 *
1179 * @param int $oid the ID of the booking in VBO
1180 * @param string $channel the name of the source channel
1181 *
1182 * @return mixed null if VCM is not available, VCMChatHandler instance otherwise
1183 *
1184 * @since 1.11.2 (J) - 1.1.2 (WP)
1185 * @since 1.16.0 (J) - 1.6.0 (WP) the method can also be used to require the chat handler.
1186 * @since 1.16.4 (J) - 1.6.4 (WP) preloading widgets on WP when VCM is inactive is prevented.
1187 */
1188 public static function getVcmChatInstance($oid, $channel = null)
1189 {
1190 $vcm_messaging_helper = VCM_SITE_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'chat' . DIRECTORY_SEPARATOR . 'handler.php';
1191 if (!is_file($vcm_messaging_helper)) {
1192 // VCM is not available
1193 return null;
1194 }
1195
1196 if (VBOPlatformDetection::isWordPress() && !defined('VIKCHANNELMANAGER_LIBRARIES')) {
1197 // VCM must be inactive, prevent the loading of the instance
1198 return null;
1199 }
1200
1201 // make sure the channel name is correct as it may contain the sub-network
1202 if (!empty($channel)) {
1203 $segments = explode('_', $channel);
1204 if (count($segments) > 1) {
1205 // we take the first segment, as the second could be the source sub-network (expedia_Hotels.com)
1206 $channel = $segments[0];
1207 }
1208 }
1209
1210 // always require main file of the abstract class even if arguments are empty/invalid
1211 require_once $vcm_messaging_helper;
1212
1213 // return the instance of the class for this channel handler
1214 return VCMChatHandler::getInstance($oid, $channel);
1215 }
1216
1217 /**
1218 * Returns an instance of the VCMOpportunityHandler class to handle
1219 * the various opportunities through VCM.
1220 *
1221 * @return mixed null if VCM is not available, VCMOpportunityHandler instance otherwise
1222 *
1223 * @since 1.2.0
1224 */
1225 public static function getVcmOpportunityInstance() {
1226 $vcm_opp_helper = VCM_SITE_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'opportunity.php';
1227 if (!is_file($vcm_opp_helper)) {
1228 // VCM is not available
1229 return null;
1230 }
1231 // require main file of the class
1232 require_once $vcm_opp_helper;
1233 // return the instance of the class
1234 return VCMOpportunityHandler::getInstance();
1235 }
1236
1237 public static function vcmBcomReportingSupported() {
1238 if (!is_file(VCM_SITE_PATH.DIRECTORY_SEPARATOR.'helpers'.DIRECTORY_SEPARATOR.'lib.vikchannelmanager.php')) {
1239 return false;
1240 }
1241 $dbo = JFactory::getDbo();
1242 $q = "SELECT `id` FROM `#__vikchannelmanager_channel` WHERE `uniquekey`='4';";
1243 $dbo->setQuery($q);
1244 $dbo->execute();
1245 return ($dbo->getNumRows() > 0);
1246 }
1247
1248 /**
1249 * Gets a list of all channels supporting the promotions.
1250 *
1251 * @param string $key the key of the handler.
1252 *
1253 * @return mixed false if VCM is not installed, empty array otherwise.
1254 *
1255 * @requires Vik Channel Manager 1.7.1
1256 *
1257 * @since 1.3.0
1258 */
1259 public static function getPromotionHandlers($key = null)
1260 {
1261 if (!is_file(VCM_SITE_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'lib.vikchannelmanager.php')) {
1262 // VCM not installed
1263 return false;
1264 }
1265
1266 if (!class_exists('VikChannelManager') || !method_exists('VikChannelManager', 'getPromotionHandlers')) {
1267 // VCM is outdated yet installed: an empty array is sufficient to not display warning messages
1268 return [];
1269 }
1270
1271 return VikChannelManager::getPromotionHandlers($key);
1272 }
1273
1274 /**
1275 * Gets the factors for suggesting the application of the promotions.
1276 *
1277 * @param mixed $data some optional instructions to be passed as argument.
1278 *
1279 * @return mixed false if VCM is not installed, associative array otherwise.
1280 *
1281 * @requires Vik Channel Manager 1.7.1
1282 *
1283 * @since 1.3.0
1284 */
1285 public static function getPromotionFactors($data = null)
1286 {
1287 if (!is_file(VCM_SITE_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'lib.vikchannelmanager.php')) {
1288 // VCM not installed
1289 return false;
1290 }
1291
1292 if (!class_exists('VikChannelManager') || !method_exists('VikChannelManager', 'getPromotionFactors')) {
1293 // VCM is outdated
1294 return false;
1295 }
1296
1297 return VikChannelManager::getPromotionFactors($data);
1298 }
1299
1300 public static function getTheme() {
1301 $dbo = JFactory::getDbo();
1302 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='theme';";
1303 $dbo->setQuery($q);
1304 $dbo->execute();
1305 $s = $dbo->loadAssocList();
1306 return $s[0]['setting'];
1307 }
1308
1309 public static function getFooterOrdMail($vbo_tn = null) {
1310 $dbo = JFactory::getDbo();
1311 $q = "SELECT `id`,`setting` FROM `#__vikbooking_texts` WHERE `param`='footerordmail';";
1312 $dbo->setQuery($q);
1313 $dbo->execute();
1314 $ft = $dbo->loadAssocList();
1315 if (!is_object($vbo_tn)) {
1316 $vbo_tn = self::getTranslator();
1317 }
1318 $vbo_tn->translateContents($ft, '#__vikbooking_texts');
1319 return $ft[0]['setting'];
1320 }
1321
1322 public static function requireLogin() {
1323 $dbo = JFactory::getDbo();
1324 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='requirelogin';";
1325 $dbo->setQuery($q);
1326 $dbo->execute();
1327 $s = $dbo->loadAssocList();
1328 return intval($s[0]['setting']) == 1 ? true : false;
1329 }
1330
1331 public static function autoRoomUnit() {
1332 $dbo = JFactory::getDbo();
1333 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='autoroomunit';";
1334 $dbo->setQuery($q);
1335 $dbo->execute();
1336 $s = $dbo->loadAssocList();
1337 return intval($s[0]['setting']) == 1 ? true : false;
1338 }
1339
1340 public static function todayBookings() {
1341 $dbo = JFactory::getDbo();
1342 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='todaybookings';";
1343 $dbo->setQuery($q);
1344 $dbo->execute();
1345 $s = $dbo->loadAssocList();
1346 return intval($s[0]['setting']) == 1 ? true : false;
1347 }
1348
1349 public static function couponsEnabled() {
1350 $dbo = JFactory::getDbo();
1351 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='enablecoupons';";
1352 $dbo->setQuery($q);
1353 $dbo->execute();
1354 $s = $dbo->loadAssocList();
1355 return intval($s[0]['setting']) == 1 ? true : false;
1356 }
1357
1358 public static function customersPinEnabled() {
1359 $dbo = JFactory::getDbo();
1360 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='enablepin';";
1361 $dbo->setQuery($q);
1362 $dbo->execute();
1363 $s = $dbo->loadAssocList();
1364 return intval($s[0]['setting']) == 1 ? true : false;
1365 }
1366
1367 /**
1368 * Detects the type of visitor from the user agent.
1369 * Known types are: computer, smartphone, tablet.
1370 *
1371 * @param boolean $returnua whether the type of visitor should be returned. If false
1372 * boolean is returned in case of mobile device detected.
1373 * @param boolean $loadassets whether the system should load an apposite CSS file.
1374 *
1375 * @return mixed string for the type of visitor or boolean if mobile detected.
1376 *
1377 * @since 1.0.13 - Revision September 2018
1378 */
1379 public static function detectUserAgent($returnua = false, $loadassets = true) {
1380 $session = JFactory::getSession();
1381 $sval = $session->get('vbuseragent', '');
1382 $mobiles = array('tablet', 'smartphone');
1383 if (!empty($sval)) {
1384 if ($loadassets) {
1385 self::userAgentStyleSheet($sval);
1386 }
1387 return $returnua ? $sval : in_array($sval, $mobiles);
1388 }
1389
1390 // detect visitor (device) type
1391 $visitoris = 'computer';
1392 try {
1393 if (!class_exists('MobileDetector')) {
1394 require_once VBO_SITE_PATH . DIRECTORY_SEPARATOR . "helpers" . DIRECTORY_SEPARATOR . "mobile_detector.php";
1395 }
1396 $detector = new MobileDetector;
1397 $visitoris = $detector->isMobile() ? ($detector->isTablet() ? 'tablet' : 'smartphone') : 'computer';
1398 } catch (Exception $e) {
1399 // do nothing
1400 }
1401
1402 $session->set('vbuseragent', $visitoris);
1403 if ($loadassets) {
1404 self::userAgentStyleSheet($visitoris);
1405 }
1406
1407 return $returnua ? $visitoris : in_array($visitoris, $mobiles);
1408 }
1409
1410 public static function userAgentStyleSheet($ua) {
1411 /**
1412 * @wponly in order to not interfere with AJAX requests, we do nothing if doing AJAX.
1413 */
1414 if (function_exists('wp_doing_ajax') && wp_doing_ajax()) {
1415 return;
1416 }
1417 //
1418
1419 $document = JFactory::getDocument();
1420 /**
1421 * @wponly the following CSS files are located in /site/resources/ for WP, not just on /site
1422 */
1423 if ($ua == 'smartphone') {
1424 $document->addStyleSheet(VBO_SITE_URI.'resources/vikbooking_smartphones.css');
1425 } elseif ($ua == 'tablet') {
1426 $document->addStyleSheet(VBO_SITE_URI.'resources/vikbooking_tablets.css');
1427 }
1428 return true;
1429 }
1430
1431 public static function loadJquery($skipsession = false) {
1432 if ($skipsession) {
1433 $dbo = JFactory::getDbo();
1434 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='loadjquery';";
1435 $dbo->setQuery($q);
1436 $dbo->execute();
1437 $s = $dbo->loadAssocList();
1438 return intval($s[0]['setting']) == 1 ? true : false;
1439 } else {
1440 $session = JFactory::getSession();
1441 $sval = $session->get('vbloadJquery', '');
1442 if (!empty($sval)) {
1443 return intval($sval) == 1 ? true : false;
1444 } else {
1445 $dbo = JFactory::getDbo();
1446 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='loadjquery';";
1447 $dbo->setQuery($q);
1448 $dbo->execute();
1449 $s = $dbo->loadAssocList();
1450 $session->set('vbloadJquery', $s[0]['setting']);
1451 return intval($s[0]['setting']) == 1 ? true : false;
1452 }
1453 }
1454 }
1455
1456 public static function loadBootstrap($skipsession = false) {
1457 $dbo = JFactory::getDbo();
1458 if ($skipsession) {
1459 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='bootstrap';";
1460 $dbo->setQuery($q);
1461 $dbo->execute();
1462 $s = $dbo->loadAssocList();
1463 return intval($s[0]['setting']) == 1 ? true : false;
1464 } else {
1465 $session = JFactory::getSession();
1466 $sval = $session->get('vbBootstrap', '');
1467 if (!empty($sval)) {
1468 return intval($sval) == 1 ? true : false;
1469 } else {
1470 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='bootstrap';";
1471 $dbo->setQuery($q);
1472 $dbo->execute();
1473 $s = $dbo->loadAssocList();
1474 $session->set('vbBootstrap', $s[0]['setting']);
1475 return intval($s[0]['setting']) == 1 ? true : false;
1476 }
1477 }
1478 }
1479
1480 public static function allowMultiLanguage($skipsession = false) {
1481 if ($skipsession) {
1482 $dbo = JFactory::getDbo();
1483 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='multilang';";
1484 $dbo->setQuery($q);
1485 $dbo->execute();
1486 $s = $dbo->loadAssocList();
1487 return intval($s[0]['setting']) == 1 ? true : false;
1488 } else {
1489 $session = JFactory::getSession();
1490 $sval = $session->get('vbMultiLang', '');
1491 if (!empty($sval)) {
1492 return intval($sval) == 1 ? true : false;
1493 } else {
1494 $dbo = JFactory::getDbo();
1495 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='multilang';";
1496 $dbo->setQuery($q);
1497 $dbo->execute();
1498 $s = $dbo->loadAssocList();
1499 $session->set('vbMultiLang', $s[0]['setting']);
1500 return intval($s[0]['setting']) == 1 ? true : false;
1501 }
1502 }
1503 }
1504
1505 public static function getTranslator()
1506 {
1507 if (!class_exists('VikBookingTranslator')) {
1508 require_once(VBO_SITE_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'translator.php');
1509 }
1510
1511 return new VikBookingTranslator();
1512 }
1513
1514 /**
1515 * New method's name spelled correctly. The object will be cached by default.
1516 *
1517 * @return VikBookingCustomersPin
1518 *
1519 * @since 1.15.5 (J) - 1.5.11 (WP) cache the instance of the object to better support new cookies set.
1520 */
1521 public static function getCPinInstance()
1522 {
1523 static $cpin_instance = null;
1524
1525 if (!$cpin_instance) {
1526 require_once VBO_SITE_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'cpin.php';
1527
1528 $cpin_instance = new VikBookingCustomersPin();
1529 }
1530
1531 return $cpin_instance;
1532 }
1533
1534 /**
1535 * Old method's name spelled wrongly for BC.
1536 *
1537 * @return VikBookingCustomersPin
1538 */
1539 public static function getCPinIstance()
1540 {
1541 return self::getCPinInstance();
1542 }
1543
1544 /**
1545 * Returns an instance of the VikBookingTracker Class.
1546 * It is also possible to call this method to just require the library.
1547 * This is useful for the back-end to access some static methods
1548 * without tracking any data.
1549 *
1550 * @param boolean $require_only whether to return the object.
1551 *
1552 * @return VikBookingTracker
1553 *
1554 * @since 1.11
1555 */
1556 public static function getTracker($require_only = false) {
1557 if (!class_exists('VikBookingTracker')) {
1558 require_once(VBO_SITE_PATH . DIRECTORY_SEPARATOR . "helpers" . DIRECTORY_SEPARATOR . "tracker.php");
1559 }
1560 return $require_only ? true : VikBookingTracker::getInstance();
1561 }
1562
1563 /**
1564 * Returns an instance of the VikBookingOperator Class.
1565 *
1566 * @return VikBookingOperator
1567 *
1568 * @since 1.11
1569 */
1570 public static function getOperatorInstance() {
1571 if (!class_exists('VikBookingOperator')) {
1572 require_once(VBO_SITE_PATH . DIRECTORY_SEPARATOR . "helpers" . DIRECTORY_SEPARATOR . "operator.php");
1573 }
1574 return VikBookingOperator::getInstance();
1575 }
1576
1577 /**
1578 * Returns an instance of the VikBookingFestivities Class.
1579 *
1580 * @return VikBookingFestivities
1581 *
1582 * @since 1.12
1583 */
1584 public static function getFestivitiesInstance() {
1585 if (!class_exists('VikBookingFestivities')) {
1586 require_once(VBO_ADMIN_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'festivities.php');
1587 }
1588 return VikBookingFestivities::getInstance();
1589 }
1590
1591 /**
1592 * Returns an instance of the VikBookingCriticalDates Class.
1593 *
1594 * @return VikBookingCriticalDates
1595 *
1596 * @since 1.13.5
1597 */
1598 public static function getCriticalDatesInstance() {
1599 if (!class_exists('VikBookingCriticalDates')) {
1600 require_once(VBO_ADMIN_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'critical_dates.php');
1601 }
1602 return VikBookingCriticalDates::getInstance();
1603 }
1604
1605 /**
1606 * Checks whether the chat is enabled.
1607 *
1608 * @return int -1 if VCM is not installed, 0 if disabled, 1 otherwise
1609 *
1610 * @since 1.12
1611 */
1612 public static function chatEnabled() {
1613 if (!is_file(VCM_SITE_PATH.DIRECTORY_SEPARATOR.'helpers'.DIRECTORY_SEPARATOR.'lib.vikchannelmanager.php')) {
1614 return -1;
1615 }
1616 $dbo = JFactory::getDbo();
1617 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='chatenabled';";
1618 $dbo->setQuery($q);
1619 $dbo->execute();
1620 if (!$dbo->getNumRows()) {
1621 // enable the chat by default if VCM is installed
1622 $q = "INSERT INTO `#__vikbooking_config` (`param`,`setting`) VALUES('chatenabled', '1');";
1623 $dbo->setQuery($q);
1624 $dbo->execute();
1625 return 1;
1626 }
1627 $s = $dbo->loadResult();
1628 return intval($s);
1629 }
1630
1631 /**
1632 * Loads the chat parameters from the configuration.
1633 *
1634 * @return object stdClass object from decoded JSON string
1635 *
1636 * @since 1.12
1637 */
1638 public static function getChatParams() {
1639 if (!is_file(VCM_SITE_PATH.DIRECTORY_SEPARATOR.'helpers'.DIRECTORY_SEPARATOR.'lib.vikchannelmanager.php')) {
1640 return new stdClass;
1641 }
1642 $dbo = JFactory::getDbo();
1643 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='chatparams';";
1644 $dbo->setQuery($q);
1645 $dbo->execute();
1646 if (!$dbo->getNumRows()) {
1647 // compose default and basic chat params for the first time
1648 $basic_params = new stdClass;
1649 $basic_params->res_status = array('confirmed', 'standby', 'cancelled');
1650 $basic_params->av_type = 'checkin';
1651 $basic_params->av_days = 0;
1652
1653 $q = "INSERT INTO `#__vikbooking_config` (`param`,`setting`) VALUES('chatparams', ".$dbo->quote(json_encode($basic_params)).");";
1654 $dbo->setQuery($q);
1655 $dbo->execute();
1656
1657 return $basic_params;
1658 }
1659 return json_decode($dbo->loadResult());
1660 }
1661
1662 /**
1663 * Checks whether the pre-checkin is enabled.
1664 *
1665 * @return int 0 if disabled, 1 otherwise
1666 *
1667 * @since 1.12
1668 */
1669 public static function precheckinEnabled() {
1670 $dbo = JFactory::getDbo();
1671 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='precheckinenabled';";
1672 $dbo->setQuery($q);
1673 $dbo->execute();
1674 if (!$dbo->getNumRows()) {
1675 // enable the pre-checkin by default
1676 $q = "INSERT INTO `#__vikbooking_config` (`param`,`setting`) VALUES('precheckinenabled', '1');";
1677 $dbo->setQuery($q);
1678 $dbo->execute();
1679 return 1;
1680 }
1681 $s = $dbo->loadResult();
1682 return intval($s);
1683 }
1684
1685 /**
1686 * Returns the minimum number of days in advance to
1687 * enable the pre-checkin via front-end.
1688 *
1689 * @return int the min number of days in advance for pre-checkin.
1690 *
1691 * @since 1.12
1692 */
1693 public static function precheckinMinOffset() {
1694 $dbo = JFactory::getDbo();
1695 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='precheckinminoffset';";
1696 $dbo->setQuery($q);
1697 $dbo->execute();
1698 if (!$dbo->getNumRows()) {
1699 // set the limit to 1 day before arrival by default
1700 $q = "INSERT INTO `#__vikbooking_config` (`param`,`setting`) VALUES('precheckinminoffset', '1');";
1701 $dbo->setQuery($q);
1702 $dbo->execute();
1703 return 1;
1704 }
1705 $s = $dbo->loadResult();
1706 return intval($s);
1707 }
1708
1709 /**
1710 * Whether upselling extra services is enabled.
1711 *
1712 * @return boolean true if enabled, false otherwise.
1713 *
1714 * @since 1.13
1715 */
1716 public static function upsellingEnabled() {
1717 $dbo = JFactory::getDbo();
1718 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='upselling';";
1719 $dbo->setQuery($q);
1720 $dbo->execute();
1721 if (!$dbo->getNumRows()) {
1722 // enable the upselling feature by default
1723 $q = "INSERT INTO `#__vikbooking_config` (`param`,`setting`) VALUES('upselling', '1');";
1724 $dbo->setQuery($q);
1725 $dbo->execute();
1726 return true;
1727 }
1728 return (intval($dbo->loadResult()) > 0);
1729 }
1730
1731 /**
1732 * Collects a list of options/extra that can be upsold for the
1733 * the rooms booked.
1734 *
1735 * @param array $upsell_data list of stdClass objects for each room booked.
1736 * @param array $info some booking details (id, checkin, checkout).
1737 * @param object $vbo_tn the translation object.
1738 *
1739 * @return array list of upsellable options for each room booked.
1740 *
1741 * @since 1.3.0
1742 */
1743 public static function loadUpsellingData($upsell_data, $info, $vbo_tn) {
1744 $dbo = JFactory::getDbo();
1745 // get all options for all rooms booked
1746 $all_room_ids = array();
1747 foreach ($upsell_data as $v) {
1748 if (!in_array($v->id, $all_room_ids)) {
1749 array_push($all_room_ids, $v->id);
1750 }
1751 }
1752 $q = "SELECT `id`, `idopt` FROM `#__vikbooking_rooms` WHERE `id` IN (" . implode(', ', $all_room_ids) . ");";
1753 $dbo->setQuery($q);
1754 $dbo->execute();
1755 if (!$dbo->getNumRows()) {
1756 // no rooms found
1757 return array();
1758 }
1759 $records = $dbo->loadAssocList();
1760 $all_options = array();
1761 $rooms_options = array();
1762 foreach ($records as $v) {
1763 $allopts = explode(';', $v['idopt']);
1764 $room_opt = array();
1765 foreach ($allopts as $o) {
1766 if (empty($o)) {
1767 continue;
1768 }
1769 if (!in_array((int)$o, $all_options)) {
1770 array_push($all_options, (int)$o);
1771 }
1772 array_push($room_opt, (int)$o);
1773 }
1774 $rooms_options[$v['id']] = $room_opt;
1775 }
1776 if (!count($all_options)) {
1777 // no options found
1778 return array();
1779 }
1780 // load all options that could be used by the booked rooms no matter what was already booked
1781 $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;";
1782 $dbo->setQuery($q);
1783 $dbo->execute();
1784 if (!$dbo->getNumRows()) {
1785 // no upsell-able options found
1786 return array();
1787 }
1788 $records = $dbo->loadAssocList();
1789 // filter options by available date and translate records
1790 self::filterOptionalsByDate($records, $info['checkin'], $info['checkout']);
1791 $vbo_tn->translateContents($records, '#__vikbooking_optionals');
1792 $records = !is_array($records) ? array() : $records;
1793 //
1794 $tot_upsellable = 0;
1795 foreach ($upsell_data as $k => $rdata) {
1796 if (!isset($upsell_data[$k]->upsellable)) {
1797 $upsell_data[$k]->upsellable = array();
1798 }
1799 foreach ($records as $opt) {
1800 if (!empty($opt['ageintervals'])) {
1801 // upsellable options should not contain age intervals for children
1802 continue;
1803 }
1804 if (!in_array($opt['id'], $rooms_options[$rdata->id])) {
1805 // this option is not assigned to this room
1806 continue;
1807 }
1808 // check if the option is suited for this room party
1809 $clone_opt = array($opt);
1810 self::filterOptionalsByParty($clone_opt, $rdata->adults, $rdata->children);
1811 if (!is_array($clone_opt) || !count($clone_opt)) {
1812 // this option is not suited for this room party
1813 continue;
1814 }
1815 //
1816 if (in_array($opt['id'], $rdata->options)) {
1817 // this option has already been booked
1818 continue;
1819 }
1820 // push this option and increase counter
1821 array_push($upsell_data[$k]->upsellable, $opt);
1822 $tot_upsellable++;
1823 }
1824 }
1825
1826 // if no upsellable options were found, we return an empty array
1827 return $tot_upsellable > 0 ? $upsell_data : array();
1828 }
1829
1830 /**
1831 * Returns the minimum days in advance for booking, by considering by default
1832 * also the property closing dates. If the property is currently closed, then
1833 * the minimum number of days in advance will be increased.
1834 *
1835 * @param bool $no_closing_dates whether to skip checking closing dates.
1836 *
1837 * @return int the number of days in advance for booking.
1838 *
1839 * @since 1.14 (J) - 1.4.0 (WP) the signature was changed from:
1840 * getMinDaysAdvance($skipsession = false) to:
1841 * getMinDaysAdvance($no_closing_dates = false)
1842 * This was made to return the proper number of days in advance
1843 * in case the property is currently closed.
1844 */
1845 public static function getMinDaysAdvance($no_closing_dates = false) {
1846 // cache value in static var
1847 static $getMinDaysAdvance = null;
1848
1849 if ($getMinDaysAdvance) {
1850 return $getMinDaysAdvance;
1851 }
1852 //
1853
1854 $dbo = JFactory::getDbo();
1855
1856 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='mindaysadvance';";
1857 $dbo->setQuery($q);
1858 $dbo->execute();
1859 $mind = $dbo->getNumRows() ? (int)$dbo->loadResult() : 0;
1860
1861 // update cached var
1862 $getMinDaysAdvance = $mind;
1863
1864 if ($no_closing_dates) {
1865 // do not check the closing dates
1866 return $mind;
1867 }
1868
1869 // check if the property is currently closed
1870 $cur_closed_dates = self::getClosingDates();
1871 if (is_array($cur_closed_dates) && count($cur_closed_dates)) {
1872 $today_midnight = mktime(0, 0, 0);
1873 $closed_until = null;
1874 foreach ($cur_closed_dates as $kcd => $vcd) {
1875 if ($today_midnight >= $vcd['from'] && $today_midnight <= $vcd['to']) {
1876 // closing period found
1877 $closed_until = $vcd['to'];
1878 break;
1879 }
1880 }
1881 if ($closed_until !== null) {
1882 // count the number of days until property is closed
1883 $mind = 0;
1884 $today_info = getdate($today_midnight);
1885 while ($today_info[0] <= $closed_until) {
1886 $mind++;
1887 $today_info = getdate(mktime(0, 0, 0, $today_info['mon'], ($today_info['mday'] + 1), $today_info['year']));
1888 }
1889 }
1890 }
1891
1892 // update cached var
1893 $getMinDaysAdvance = $mind;
1894
1895 return $mind;
1896 }
1897
1898 public static function getDefaultNightsCalendar($skipsession = false) {
1899 if ($skipsession) {
1900 $dbo = JFactory::getDbo();
1901 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='autodefcalnights';";
1902 $dbo->setQuery($q);
1903 $dbo->execute();
1904 $s = $dbo->loadAssocList();
1905 return (int)$s[0]['setting'];
1906 } else {
1907 $session = JFactory::getSession();
1908 $sval = $session->get('vbdefaultNightsCalendar', '');
1909 if (!empty($sval)) {
1910 return (int)$sval;
1911 } else {
1912 $dbo = JFactory::getDbo();
1913 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='autodefcalnights';";
1914 $dbo->setQuery($q);
1915 $dbo->execute();
1916 $s = $dbo->loadAssocList();
1917 $session->set('vbdefaultNightsCalendar', $s[0]['setting']);
1918 return (int)$s[0]['setting'];
1919 }
1920 }
1921 }
1922
1923 public static function getSearchNumRooms($skipsession = true)
1924 {
1925 $dbo = JFactory::getDbo();
1926 if ($skipsession) {
1927 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='numrooms';";
1928 $dbo->setQuery($q);
1929 $dbo->execute();
1930 $s = $dbo->loadAssocList();
1931 return (int)$s[0]['setting'];
1932 }
1933 $session = JFactory::getSession();
1934 $sval = $session->get('vbsearchNumRooms', '');
1935 if (!empty($sval)) {
1936 return (int)$sval;
1937 }
1938 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='numrooms';";
1939 $dbo->setQuery($q);
1940 $dbo->execute();
1941 $s = $dbo->loadAssocList();
1942 $session->set('vbsearchNumRooms', $s[0]['setting']);
1943 return (int)$s[0]['setting'];
1944 }
1945
1946 public static function getSearchNumAdults($skipsession = true)
1947 {
1948 $dbo = JFactory::getDbo();
1949 if ($skipsession) {
1950 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='numadults';";
1951 $dbo->setQuery($q);
1952 $dbo->execute();
1953 $s = $dbo->loadAssocList();
1954 return $s[0]['setting'];
1955 }
1956 $session = JFactory::getSession();
1957 $sval = $session->get('vbsearchNumAdults', '');
1958 if (!empty($sval)) {
1959 return $sval;
1960 }
1961 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='numadults';";
1962 $dbo->setQuery($q);
1963 $dbo->execute();
1964 $s = $dbo->loadAssocList();
1965 $session->set('vbsearchNumAdults', $s[0]['setting']);
1966 return $s[0]['setting'];
1967 }
1968
1969 public static function getSearchNumChildren($skipsession = true)
1970 {
1971 $dbo = JFactory::getDbo();
1972 if ($skipsession) {
1973 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='numchildren';";
1974 $dbo->setQuery($q);
1975 $dbo->execute();
1976 $s = $dbo->loadAssocList();
1977 return $s[0]['setting'];
1978 }
1979 $session = JFactory::getSession();
1980 $sval = $session->get('vbsearchNumChildren', '');
1981 if (!empty($sval)) {
1982 return $sval;
1983 }
1984 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='numchildren';";
1985 $dbo->setQuery($q);
1986 $dbo->execute();
1987 $s = $dbo->loadAssocList();
1988 $session->set('vbsearchNumChildren', $s[0]['setting']);
1989 return $s[0]['setting'];
1990 }
1991
1992 public static function getSmartSearchType($skipsession = true)
1993 {
1994 $dbo = JFactory::getDbo();
1995 if ($skipsession) {
1996 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='smartsearch';";
1997 $dbo->setQuery($q);
1998 $dbo->execute();
1999 $s = $dbo->loadAssocList();
2000 return $s[0]['setting'];
2001 }
2002 $session = JFactory::getSession();
2003 $sval = $session->get('vbsmartSearchType', '');
2004 if (!empty($sval)) {
2005 return $sval;
2006 }
2007 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='smartsearch';";
2008 $dbo->setQuery($q);
2009 $dbo->execute();
2010 $s = $dbo->loadAssocList();
2011 $session->set('vbsmartSearchType', $s[0]['setting']);
2012 return $s[0]['setting'];
2013 }
2014
2015 /**
2016 * Returns the maximum advance booking notice (offset) as a period.
2017 * It either returns the global configuration setting, or the value
2018 * defined at room-level, if any and if requested.
2019 *
2020 * @param int $idroom optional room ID for room-level setting.
2021 *
2022 * @return string the maximum booking notice period (i.e. +1Y).
2023 *
2024 * @since 1.16.3 (J) - 1.6.3 (WP) replaced previous argument $skipsession with
2025 * $idroom to support room-level settings.
2026 */
2027 public static function getMaxDateFuture($idroom = 0)
2028 {
2029 static $max_adv_book_pool = [];
2030
2031 $pool_id = !empty($idroom) && is_numeric($idroom) ? (int)$idroom : 'global';
2032
2033 if (isset($max_adv_book_pool[$pool_id])) {
2034 return $max_adv_book_pool[$pool_id];
2035 }
2036
2037 $config = VBOFactory::getConfig();
2038
2039 if ($pool_id === 'global') {
2040 $max_adv_book_pool[$pool_id] = $config->get('maxdate', '');
2041 } else {
2042 $room_level_max_notice = $config->get("room_{$pool_id}_max_adv_notice");
2043 if (!empty($room_level_max_notice)) {
2044 // setting is defined at room-level
2045 $max_adv_book_pool[$pool_id] = $room_level_max_notice;
2046 } else {
2047 // recurse for global setting
2048 $max_adv_book_pool[$pool_id] = self::getMaxDateFuture();
2049 }
2050 }
2051
2052 return $max_adv_book_pool[$pool_id];
2053 }
2054
2055 /**
2056 * Validates the maximum advance booking notice against the requested check-in
2057 * timestamp. The method supports the validation at both room and property levels.
2058 *
2059 * @param int $checkints the check-in date timestamp.
2060 * @param int $idroom the optional room ID for room-level validation.
2061 *
2062 * @return string error date string in case of validation failure.
2063 *
2064 * @since 1.16.3 (J) - 1.6.3 (WP) introduced $idroom argument for room-level.
2065 */
2066 public static function validateMaxDateBookings($checkints, $idroom = 0)
2067 {
2068 $datelim = self::getMaxDateFuture($idroom);
2069 $datelim = empty($datelim) ? '+2y' : $datelim;
2070 $numlim = (int)substr($datelim, 1, (strlen($datelim) - 2));
2071 $quantlim = substr($datelim, -1, 1);
2072
2073 $now = getdate();
2074 if ($quantlim == 'w') {
2075 $until_ts = strtotime("+$numlim weeks") + 86399;
2076 } else {
2077 $use_mon = $quantlim == 'm' ? ($now['mon'] + $numlim) : $now['mon'];
2078 $use_day = $quantlim == 'd' ? ($now['mday'] + $numlim) : $now['mday'];
2079 $use_year = $quantlim == 'y' ? ($now['year'] + $numlim) : $now['year'];
2080 $until_ts = mktime(23, 59, 59, $use_mon, $use_day, $use_year);
2081 }
2082
2083 if ($until_ts > $now[0] && $checkints > $until_ts) {
2084 $vbo_df = self::getDateFormat();
2085 $df = $vbo_df == "%d/%m/%Y" ? 'd/m/Y' : ($vbo_df == "%m/%d/%Y" ? 'm/d/Y' : 'Y-m-d');
2086
2087 // error, return the maximum date in the future allowed
2088 return date($df, $until_ts);
2089 }
2090
2091 // validation passed
2092 return '';
2093 }
2094
2095 public static function validateMinDaysAdvance($checkints) {
2096 $mindadv = self::getMinDaysAdvance(true);
2097 if ($mindadv > 0) {
2098 $tsinfo = getdate($checkints);
2099 $limit_ts = mktime($tsinfo['hours'], $tsinfo['minutes'], $tsinfo['seconds'], date('n'), ((int)date('j') + $mindadv), date('Y'));
2100 if ($checkints < $limit_ts) {
2101 return $mindadv;
2102 }
2103 }
2104
2105 return '';
2106 }
2107
2108 /**
2109 * The only supported calendar type has been changed to jQuery UI.
2110 *
2111 * @since 1.13
2112 */
2113 public static function calendarType($skipsession = false) {
2114 return 'jqueryui';
2115 }
2116
2117 public static function getSiteLogo() {
2118 $dbo = JFactory::getDbo();
2119 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='sitelogo';";
2120 $dbo->setQuery($q);
2121 $dbo->execute();
2122 $s = $dbo->loadAssocList();
2123 return $s[0]['setting'];
2124 }
2125
2126 public static function getBackendLogo() {
2127 $dbo = JFactory::getDbo();
2128 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='backlogo';";
2129 $dbo->setQuery($q);
2130 $dbo->execute();
2131 if ($dbo->getNumRows() > 0) {
2132 $s = $dbo->loadAssocList();
2133 return $s[0]['setting'];
2134 }
2135 return '';
2136 }
2137
2138 public static function numCalendars() {
2139 $dbo = JFactory::getDbo();
2140 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='numcalendars';";
2141 $dbo->setQuery($q);
2142 $dbo->execute();
2143 $s = $dbo->loadAssocList();
2144 return $s[0]['setting'];
2145 }
2146
2147 public static function getFirstWeekDay($skipsession = false) {
2148 if ($skipsession) {
2149 $dbo = JFactory::getDbo();
2150 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='firstwday';";
2151 $dbo->setQuery($q);
2152 $dbo->execute();
2153 $s = $dbo->loadAssocList();
2154 return $s[0]['setting'];
2155 } else {
2156 $session = JFactory::getSession();
2157 $sval = $session->get('vbfirstWeekDay', '');
2158 if (strlen($sval)) {
2159 return $sval;
2160 } else {
2161 $dbo = JFactory::getDbo();
2162 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='firstwday';";
2163 $dbo->setQuery($q);
2164 $dbo->execute();
2165 $s = $dbo->loadAssocList();
2166 $session->set('vbfirstWeekDay', $s[0]['setting']);
2167 return $s[0]['setting'];
2168 }
2169 }
2170 }
2171
2172 public static function showPartlyReserved() {
2173 $dbo = JFactory::getDbo();
2174 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='showpartlyreserved';";
2175 $dbo->setQuery($q);
2176 $dbo->execute();
2177 $s = $dbo->loadAssocList();
2178 return intval($s[0]['setting']) == 1 ? true : false;
2179 }
2180
2181 public static function showStatusCheckinoutOnly() {
2182 $dbo = JFactory::getDbo();
2183 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='showcheckinoutonly';";
2184 $dbo->setQuery($q);
2185 $dbo->execute();
2186 $s = $dbo->loadAssocList();
2187 return intval($s[0]['setting']) == 1 ? true : false;
2188 }
2189
2190 public static function getDisclaimer($vbo_tn = null) {
2191 $dbo = JFactory::getDbo();
2192 $q = "SELECT `id`,`setting` FROM `#__vikbooking_texts` WHERE `param`='disclaimer';";
2193 $dbo->setQuery($q);
2194 $dbo->execute();
2195 $ft = $dbo->loadAssocList();
2196 if (!is_object($vbo_tn)) {
2197 $vbo_tn = self::getTranslator();
2198 }
2199 $vbo_tn->translateContents($ft, '#__vikbooking_texts');
2200 return $ft[0]['setting'];
2201 }
2202
2203 public static function showFooter() {
2204 $dbo = JFactory::getDbo();
2205 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='showfooter';";
2206 $dbo->setQuery($q);
2207 $dbo->execute();
2208 if ($dbo->getNumRows() == 1) {
2209 $s = $dbo->loadAssocList();
2210 return (intval($s[0]['setting']) == 1 ? true : false);
2211 } else {
2212 return false;
2213 }
2214 }
2215
2216 public static function getPriceName($idp, $vbo_tn = null) {
2217 $dbo = JFactory::getDbo();
2218 $q = "SELECT `id`,`name` FROM `#__vikbooking_prices` WHERE `id`=" . (int)$idp . "";
2219 $dbo->setQuery($q);
2220 $dbo->execute();
2221 if ($dbo->getNumRows() == 1) {
2222 $n = $dbo->loadAssocList();
2223 if (is_object($vbo_tn)) {
2224 $vbo_tn->translateContents($n, '#__vikbooking_prices');
2225 }
2226 return $n[0]['name'];
2227 }
2228 return "";
2229 }
2230
2231 public static function getPriceAttr($idp, $vbo_tn = null) {
2232 $dbo = JFactory::getDbo();
2233 $q = "SELECT `id`,`attr` FROM `#__vikbooking_prices` WHERE `id`=" . (int)$idp . "";
2234 $dbo->setQuery($q);
2235 $dbo->execute();
2236 if ($dbo->getNumRows() == 1) {
2237 $n = $dbo->loadAssocList();
2238 if (is_object($vbo_tn)) {
2239 $vbo_tn->translateContents($n, '#__vikbooking_prices');
2240 }
2241 return $n[0]['attr'];
2242 }
2243 return "";
2244 }
2245
2246 /**
2247 * Fetches the requested rate plan information.
2248 *
2249 * @param int $idp the rate plan ID.
2250 * @param object $vbo_tn the Vik Booking translator object.
2251 *
2252 * @return array empty array or associative record.
2253 */
2254 public static function getPriceInfo($idp, $vbo_tn = null)
2255 {
2256 // cache values
2257 static $priceInfos = [];
2258
2259 $idp = (int)$idp;
2260
2261 if ($priceInfos && isset($priceInfos[$idp])) {
2262 return $priceInfos[$idp];
2263 }
2264
2265 $dbo = JFactory::getDbo();
2266
2267 $q = "SELECT * FROM `#__vikbooking_prices` WHERE `id`={$idp}";
2268 $dbo->setQuery($q, 0, 1);
2269 $rplan = $dbo->loadAssocList();
2270 if (!$rplan) {
2271 return [];
2272 }
2273
2274 if (is_object($vbo_tn)) {
2275 $vbo_tn->translateContents($rplan, '#__vikbooking_prices');
2276 }
2277
2278 // set cached value
2279 $priceInfos[$idp] = $rplan[0];
2280
2281 return $rplan[0];
2282 }
2283
2284 public static function getAliq($idal) {
2285 $dbo = JFactory::getDbo();
2286 $q = "SELECT `aliq` FROM `#__vikbooking_iva` WHERE `id`='" . $idal . "';";
2287 $dbo->setQuery($q);
2288 $dbo->execute();
2289 if ($dbo->getNumRows() > 0) {
2290 $n = $dbo->loadAssocList();
2291 return $n[0]['aliq'];
2292 }
2293 return "";
2294 }
2295
2296 public static function getTimeOpenStore($skipsession = false)
2297 {
2298 // cache value in static var
2299 static $getTimeOpenStore = null;
2300
2301 if ($getTimeOpenStore) {
2302 return $getTimeOpenStore;
2303 }
2304 //
2305
2306 $dbo = JFactory::getDbo();
2307 $session = JFactory::getSession();
2308
2309 if ($skipsession) {
2310 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='timeopenstore';";
2311 $dbo->setQuery($q);
2312 $dbo->execute();
2313 $n = $dbo->loadAssocList();
2314 if (empty($n[0]['setting']) && $n[0]['setting'] != "0") {
2315 return false;
2316 } else {
2317 $x = explode("-", $n[0]['setting']);
2318 if (!empty($x[1]) && $x[1] != "0") {
2319 $getTimeOpenStore = $x;
2320 return $x;
2321 }
2322 }
2323 } else {
2324 $sval = $session->get('vbgetTimeOpenStore', '');
2325 if (!empty($sval)) {
2326 return $sval;
2327 } else {
2328 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='timeopenstore';";
2329 $dbo->setQuery($q);
2330 $dbo->execute();
2331 $n = $dbo->loadAssocList();
2332 if (empty($n[0]['setting']) && $n[0]['setting'] != "0") {
2333 return false;
2334 } else {
2335 $x = explode("-", $n[0]['setting']);
2336 if (!empty($x[1]) && $x[1] != "0") {
2337 $session->set('vbgetTimeOpenStore', $x);
2338 $getTimeOpenStore = $x;
2339 return $x;
2340 }
2341 }
2342 }
2343 }
2344 return false;
2345 }
2346
2347 public static function getHoursMinutes($secs) {
2348 if ($secs >= 3600) {
2349 $op = $secs / 3600;
2350 $hours = floor($op);
2351 $less = $hours * 3600;
2352 $newsec = $secs - $less;
2353 $optwo = $newsec / 60;
2354 $minutes = floor($optwo);
2355 } else {
2356 $hours = "0";
2357 $optwo = $secs / 60;
2358 $minutes = floor($optwo);
2359 }
2360 $x[] = $hours;
2361 $x[] = $minutes;
2362 return $x;
2363 }
2364
2365 public static function getClosingDates()
2366 {
2367 // cache value in static var
2368 static $getClosingDates = null;
2369
2370 if (is_array($getClosingDates)) {
2371 return $getClosingDates;
2372 }
2373
2374 $dbo = JFactory::getDbo();
2375
2376 $allcd = [];
2377
2378 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='closingdates';";
2379 $dbo->setQuery($q);
2380 $s = $dbo->loadAssocList();
2381 if ($s && !empty($s[0]['setting'])) {
2382 $allcd = json_decode($s[0]['setting'], true);
2383 $allcd = is_array($allcd) ? $allcd : array();
2384 $base_ts = mktime(0, 0, 0, date("n"), date("j"), date("Y"));
2385 foreach ($allcd as $k => $v) {
2386 if ($v['to'] < $base_ts) {
2387 unset($allcd[$k]);
2388 }
2389 }
2390 $allcd = array_values($allcd);
2391 }
2392
2393 $getClosingDates = $allcd;
2394
2395 return $allcd;
2396 }
2397
2398 public static function parseJsClosingDates() {
2399 $cd = self::getClosingDates();
2400 if (count($cd) > 0) {
2401 $cdjs = array();
2402 foreach ($cd as $k => $v) {
2403 $cdjs[] = array(date('Y-m-d', $v['from']), date('Y-m-d', $v['to']));
2404 }
2405 return $cdjs;
2406 }
2407 return array();
2408 }
2409
2410 public static function validateClosingDates($checkints, $checkoutts, $df = null)
2411 {
2412 $cd = self::getClosingDates();
2413 if (!count($cd)) {
2414 return '';
2415 }
2416 $df = empty($df) ? 'Y-m-d' : $df;
2417 $margin_seconds = 22 * 60 * 60;
2418 foreach ($cd as $k => $v) {
2419 $inner_closed = ($checkints >= $v['from'] && $checkints <= ($v['to'] + $margin_seconds));
2420 $outer_closed = ($checkoutts >= $v['from'] && $checkoutts <= ($v['to'] + $margin_seconds));
2421 $middle_closed = ($checkints <= $v['from'] && $checkoutts >= ($v['to'] + $margin_seconds));
2422 if ($inner_closed || $outer_closed || $middle_closed) {
2423 return date($df, $v['from']) . ' - ' . date($df, $v['to']);
2424 }
2425 }
2426 return '';
2427 }
2428
2429 /**
2430 * Whether the categories dropdown filter menu should be displayed.
2431 *
2432 * @param boolean $skipsession [optional] re-read the configuration setting.
2433 *
2434 * @return boolean
2435 */
2436 public static function showCategoriesFront($skipsession = true)
2437 {
2438 $dbo = JFactory::getDbo();
2439 if ($skipsession) {
2440 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='showcategories';";
2441 $dbo->setQuery($q);
2442 $dbo->execute();
2443 if ($dbo->getNumRows() > 0) {
2444 $s = $dbo->loadAssocList();
2445 return (intval($s[0]['setting']) == 1);
2446 }
2447 return false;
2448 }
2449 $session = JFactory::getSession();
2450 $sval = $session->get('vbshowCategoriesFront', '');
2451 if (strlen($sval)) {
2452 return (intval($sval) == 1 ? true : false);
2453 }
2454 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='showcategories';";
2455 $dbo->setQuery($q);
2456 $dbo->execute();
2457 if ($dbo->getNumRows() > 0) {
2458 $s = $dbo->loadAssocList();
2459 $session->set('vbshowCategoriesFront', $s[0]['setting']);
2460 return (intval($s[0]['setting']) == 1);
2461 }
2462 return false;
2463 }
2464
2465 /**
2466 * Whether the number of children dropdown menu should be displayed.
2467 * Defaults to skip the session values and to re-read the configuration setting.
2468 *
2469 * @param boolean $skipsession [optional] re-read the configuration setting.
2470 *
2471 * @return boolean
2472 */
2473 public static function showChildrenFront($skipsession = true)
2474 {
2475 $dbo = JFactory::getDbo();
2476 if ($skipsession) {
2477 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='showchildren';";
2478 $dbo->setQuery($q);
2479 $dbo->execute();
2480 if ($dbo->getNumRows() > 0) {
2481 $s = $dbo->loadAssocList();
2482 return (intval($s[0]['setting']) == 1);
2483 }
2484 return false;
2485 }
2486 $session = JFactory::getSession();
2487 $sval = $session->get('vbshowChildrenFront', '');
2488 if (strlen($sval)) {
2489 return (intval($sval) == 1 ? true : false);
2490 }
2491 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='showchildren';";
2492 $dbo->setQuery($q);
2493 $dbo->execute();
2494 if ($dbo->getNumRows() > 0) {
2495 $s = $dbo->loadAssocList();
2496 $session->set('vbshowChildrenFront', $s[0]['setting']);
2497 return (intval($s[0]['setting']) == 1);
2498 }
2499 return false;
2500 }
2501
2502 public static function allowBooking() {
2503 $dbo = JFactory::getDbo();
2504 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='allowbooking';";
2505 $dbo->setQuery($q);
2506 $dbo->execute();
2507 if ($dbo->getNumRows() > 0) {
2508 $s = $dbo->loadAssocList();
2509 return (intval($s[0]['setting']) == 1 ? true : false);
2510 } else {
2511 return false;
2512 }
2513 }
2514
2515 public static function getDisabledBookingMsg($vbo_tn = null) {
2516 $dbo = JFactory::getDbo();
2517 $q = "SELECT `id`,`setting` FROM `#__vikbooking_texts` WHERE `param`='disabledbookingmsg';";
2518 $dbo->setQuery($q);
2519 $dbo->execute();
2520 $s = $dbo->loadAssocList();
2521 if (!is_object($vbo_tn)) {
2522 $vbo_tn = self::getTranslator();
2523 }
2524 $vbo_tn->translateContents($s, '#__vikbooking_texts');
2525 return $s[0]['setting'];
2526 }
2527
2528 public static function getDateFormat($skipsession = true)
2529 {
2530 // cache value in static var
2531 static $getDateFormat = null;
2532
2533 if ($getDateFormat) {
2534 return $getDateFormat;
2535 }
2536
2537 $dbo = JFactory::getDbo();
2538
2539 if ($skipsession) {
2540 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='dateformat'";
2541 $dbo->setQuery($q, 0, 1);
2542 $getDateFormat = $dbo->loadResult();
2543
2544 return $getDateFormat;
2545 }
2546
2547 $session = JFactory::getSession();
2548 $sval = $session->get('vbgetDateFormat', '');
2549 if (!empty($sval)) {
2550 return $sval;
2551 }
2552
2553 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='dateformat'";
2554 $dbo->setQuery($q, 0, 1);
2555 $getDateFormat = $dbo->loadResult();
2556
2557 $session->set('vbgetDateFormat', $getDateFormat);
2558
2559 return $getDateFormat;
2560 }
2561
2562 public static function getDateSeparator($skipsession = true)
2563 {
2564 // cache value in static var
2565 static $getDateSeparator = null;
2566
2567 if ($getDateSeparator) {
2568 return $getDateSeparator;
2569 }
2570
2571 $dbo = JFactory::getDbo();
2572
2573 if ($skipsession) {
2574 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='datesep'";
2575 $dbo->setQuery($q, 0, 1);
2576 $val = $dbo->loadResult();
2577
2578 $getDateSeparator = empty($val) ? "/" : $val;
2579
2580 return $getDateSeparator;
2581 }
2582
2583 $session = JFactory::getSession();
2584 $sval = $session->get('vbgetDateSep', '');
2585 if (!empty($sval)) {
2586 return $sval;
2587 }
2588
2589 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='datesep'";
2590 $dbo->setQuery($q, 0, 1);
2591 $val = $dbo->loadResult();
2592
2593 $getDateSeparator = empty($val) ? "/" : $val;
2594
2595 $session->set('vbgetDateSep', $getDateSeparator);
2596
2597 return $getDateSeparator;
2598 }
2599
2600 public static function getHoursMoreRb($skipsession = false)
2601 {
2602 // cache value in static var
2603 static $getHoursMoreRb = null;
2604
2605 if ($getHoursMoreRb !== null) {
2606 return $getHoursMoreRb;
2607 }
2608
2609 $dbo = JFactory::getDbo();
2610
2611 if ($skipsession) {
2612 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='hoursmorebookingback'";
2613 $dbo->setQuery($q, 0, 1);
2614 $getHoursMoreRb = (int)$dbo->loadResult();
2615
2616 return $getHoursMoreRb;
2617 }
2618
2619 $session = JFactory::getSession();
2620 $sval = $session->get('getHoursMoreRb', '');
2621 if (strlen($sval)) {
2622 return $sval;
2623 }
2624
2625 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='hoursmorebookingback'";
2626 $dbo->setQuery($q, 0, 1);
2627 $getHoursMoreRb = (int)$dbo->loadResult();
2628
2629 $session->set('getHoursMoreRb', $getHoursMoreRb);
2630
2631 return $getHoursMoreRb;
2632 }
2633
2634 public static function getHoursRoomAvail()
2635 {
2636 $dbo = JFactory::getDbo();
2637
2638 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='hoursmoreroomavail'";
2639 $dbo->setQuery($q, 0, 1);
2640
2641 return (int)$dbo->loadResult();
2642 }
2643
2644 public static function getFrontTitle($vbo_tn = null) {
2645 // cache value in static var
2646 static $getFrontTitle = null;
2647
2648 if ($getFrontTitle) {
2649 return $getFrontTitle;
2650 }
2651 //
2652
2653 $dbo = JFactory::getDbo();
2654 $q = "SELECT `id`,`setting` FROM `#__vikbooking_texts` WHERE `param`='fronttitle';";
2655 $dbo->setQuery($q);
2656 $dbo->execute();
2657 $ft = $dbo->loadAssocList();
2658 if (!is_object($vbo_tn)) {
2659 $vbo_tn = self::getTranslator();
2660 }
2661 $vbo_tn->translateContents($ft, '#__vikbooking_texts');
2662
2663 $getFrontTitle = $ft[0]['setting'];
2664
2665 return $ft[0]['setting'];
2666 }
2667
2668 public static function getFrontTitleTag() {
2669 $dbo = JFactory::getDbo();
2670 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='fronttitletag';";
2671 $dbo->setQuery($q);
2672 $dbo->execute();
2673 $ft = $dbo->loadAssocList();
2674 return $ft[0]['setting'];
2675 }
2676
2677 public static function getFrontTitleTagClass() {
2678 $dbo = JFactory::getDbo();
2679 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='fronttitletagclass';";
2680 $dbo->setQuery($q);
2681 $dbo->execute();
2682 $ft = $dbo->loadAssocList();
2683 return $ft[0]['setting'];
2684 }
2685
2686 public static function getCurrencyName() {
2687 $dbo = JFactory::getDbo();
2688 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='currencyname';";
2689 $dbo->setQuery($q);
2690 $dbo->execute();
2691 $ft = $dbo->loadAssocList();
2692 return $ft[0]['setting'];
2693 }
2694
2695 public static function getCurrencySymb($skipsession = false) {
2696 // cache value in static var
2697 static $getCurrencySymb = null;
2698
2699 if ($getCurrencySymb) {
2700 return $getCurrencySymb;
2701 }
2702
2703 $dbo = JFactory::getDbo();
2704
2705 if ($skipsession) {
2706 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='currencysymb'";
2707 $dbo->setQuery($q, 0, 1);
2708 $csymb = $dbo->loadResult();
2709
2710 $getCurrencySymb = $csymb;
2711
2712 return $csymb;
2713 }
2714
2715 $session = JFactory::getSession();
2716 $sval = $session->get('vbgetCurrencySymb', '');
2717 if (!empty($sval)) {
2718 return $sval;
2719 }
2720
2721 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='currencysymb'";
2722 $dbo->setQuery($q, 0, 1);
2723 $csymb = $dbo->loadResult();
2724
2725 // update session value, cache it and return it
2726 $session->set('vbgetCurrencySymb', $csymb);
2727 $getCurrencySymb = $csymb;
2728
2729 return $csymb;
2730 }
2731
2732 public static function getNumberFormatData($skipsession = false) {
2733 // cache value in static var
2734 static $getNumberFormatData = null;
2735
2736 if ($getNumberFormatData) {
2737 return $getNumberFormatData;
2738 }
2739 //
2740
2741 $dbo = JFactory::getDbo();
2742
2743 if ($skipsession) {
2744 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='numberformat'";
2745 $dbo->setQuery($q, 0, 1);
2746 $dbo->execute();
2747 $numfdata = $dbo->loadResult();
2748
2749 // cache value and return it
2750 $getNumberFormatData = $numfdata;
2751
2752 return $numfdata;
2753 }
2754
2755 $session = JFactory::getSession();
2756 $sval = $session->get('getNumberFormatData', '');
2757 if (!empty($sval)) {
2758 return $sval;
2759 }
2760
2761 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='numberformat'";
2762 $dbo->setQuery($q, 0, 1);
2763 $dbo->execute();
2764 $numfdata = $dbo->loadResult();
2765
2766 // update session value, cache it and return it
2767 $session->set('getNumberFormatData', $numfdata);
2768 $getNumberFormatData = $numfdata;
2769
2770 return $numfdata;
2771 }
2772
2773 /**
2774 * It is possible to hide the decimals if they are like "N.00".
2775 *
2776 * @return int 0 if disabled, 1 if enabled (default).
2777 *
2778 * @since 1.14 (J) - 1.4.0 (WP)
2779 */
2780 public static function hideEmptyDecimals()
2781 {
2782 // cache value in static var
2783 static $hideEmptyDecimals = null;
2784
2785 if ($hideEmptyDecimals !== null) {
2786 return $hideEmptyDecimals;
2787 }
2788 //
2789
2790 $dbo = JFactory::getDbo();
2791
2792 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='noemptydecimals';";
2793 $dbo->setQuery($q);
2794 $dbo->execute();
2795 if ($dbo->getNumRows()) {
2796 $hideEmptyDecimals = (int)$dbo->loadResult();
2797 return $hideEmptyDecimals;
2798 }
2799
2800 $q = "INSERT INTO `#__vikbooking_config` (`param`,`setting`) VALUES ('noemptydecimals', '1');";
2801 $dbo->setQuery($q);
2802 $dbo->execute();
2803
2804 $hideEmptyDecimals = 1;
2805
2806 return $hideEmptyDecimals;
2807 }
2808
2809 public static function numberFormat($num, $skipsession = false)
2810 {
2811 $formatvals = self::getNumberFormatData($skipsession);
2812 $formatparts = explode(':', $formatvals);
2813
2814 if ((int)$formatparts[0] > 0 && (floatval($num) - intval($num)) == 0 && self::hideEmptyDecimals()) {
2815 // number has got no decimals
2816 $formatparts[0] = 0;
2817 }
2818
2819 return number_format((float)$num, (int)$formatparts[0], $formatparts[1], $formatparts[2]);
2820 }
2821
2822 /**
2823 * Given a date timestamp, returns the formatted date.
2824 *
2825 * @param int|string $ts the date representation in Unix timestamp.
2826 * @param bool $wtime if true, a formatted date-time will be returned.
2827 *
2828 * @return string the formatted date according to settings.
2829 *
2830 * @since 1.16.0 (J) - 1.6.0 (WP)
2831 */
2832 public static function formatDateTs($ts, $wtime = false)
2833 {
2834 $nowdf = self::getDateFormat();
2835 if ($nowdf == "%d/%m/%Y") {
2836 $df = 'd/m/Y';
2837 } elseif ($nowdf == "%m/%d/%Y") {
2838 $df = 'm/d/Y';
2839 } else {
2840 $df = 'Y/m/d';
2841 }
2842 $datesep = self::getDateSeparator();
2843
2844 $time_f = $wtime ? ' H:i' : '';
2845
2846 return date(str_replace("/", $datesep, $df) . $time_f, $ts);
2847 }
2848
2849 /**
2850 * Given a country name, 3-char or 2-char code, this method attempts
2851 * to guess the best language to assign to the booking according to
2852 * what languages are installed on the website. This way, cron jobs
2853 * and any other email notification will be correctly and automatically
2854 * sent to the guest in the proper language without any manual action.
2855 * To comply with VCM (>= 1.8.3), also VBO supports this feature.
2856 *
2857 * @param string $country the country name, 3-char or 2-char code.
2858 * @param string $locale optional locale lang tag of the guest.
2859 *
2860 * @return mixed the best lang-tag string to use or null.
2861 *
2862 * @since 1.16.0 (J) - 1.6.0 (WP)
2863 */
2864 public static function guessBookingLangFromCountry($country, $locale = null)
2865 {
2866 if (empty($country)) {
2867 return null;
2868 }
2869
2870 // get all the available languages
2871 $known_langs = self::getVboApplication()->getKnownLanguages();
2872 if (!is_array($known_langs) || !count($known_langs)) {
2873 return null;
2874 }
2875
2876 // check if the booking included a supported "locale" for the guest
2877 if (!empty($locale)) {
2878 // make the locale compatible with the language tags format
2879 $locale = str_replace('_', '-', $locale);
2880 foreach ($known_langs as $ltag => $ldet) {
2881 if (stripos($ltag, $locale) !== false || stripos($locale, $ltag) !== false) {
2882 // we support this language tag, so we just use it
2883 return $ltag;
2884 }
2885 }
2886 }
2887
2888 // build similarities with country-languages
2889 $similarities = array(
2890 'AU' => 'en',
2891 'GB' => 'en',
2892 'IE' => 'en',
2893 'NZ' => 'en',
2894 'US' => 'en',
2895 'CA' => array(
2896 'en',
2897 'fr',
2898 ),
2899 'CL' => 'es',
2900 'AR' => 'es',
2901 'PE' => 'es',
2902 'MX' => 'es',
2903 'CR' => 'es',
2904 'CO' => 'es',
2905 'EC' => 'es',
2906 'BO' => 'es',
2907 'CU' => 'es',
2908 'VE' => 'es',
2909 'BE' => 'fr',
2910 'LU' => 'fr',
2911 'CH' => array(
2912 'de',
2913 'it',
2914 'fr',
2915 ),
2916 'AT' => 'de',
2917 'GR' => 'el',
2918 'GL' => 'dk',
2919 );
2920
2921 // fetch values from db
2922 $dbo = JFactory::getDbo();
2923 $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);
2924 $dbo->setQuery($q, 0, 1);
2925 $dbo->execute();
2926 if (!$dbo->getNumRows()) {
2927 return null;
2928 }
2929 $country_record = $dbo->loadAssoc();
2930
2931 // assign country name/code versions
2932 $country_name = $country_record['country_name'];
2933 $country_3char = strtoupper($country_record['country_3_code']);
2934 $country_2char = strtoupper($country_record['country_2_code']);
2935
2936 // build an associative array of language tags and related match-score
2937 $langtags_score = array();
2938 foreach ($known_langs as $ltag => $ldet) {
2939 // default language tag score is 0 for no matches
2940 $langtags_score[$ltag] = 0;
2941 // get language and country codes
2942 $lang_country_codes = explode('-', str_replace('_', '-', strtoupper($ltag)));
2943
2944 // check matches with the installed language details
2945 if ($lang_country_codes[0] == $country_2char || $lang_country_codes[0] == $country_3char) {
2946 // increase language tag score
2947 $langtags_score[$ltag]++;
2948 }
2949 if (!empty($lang_country_codes[1]) && ($lang_country_codes[1] == $country_2char || $lang_country_codes[1] == $country_3char)) {
2950 // increase language tag score
2951 $langtags_score[$ltag]++;
2952 }
2953 if (!empty($ldet['locale'])) {
2954 // sanitize locale for matching the 2-char code safely
2955 $ldet['locale'] = str_replace(array('standard', 'euro', 'iso', 'utf'), '', strtolower($ldet['locale']));
2956 if (stripos($ldet['locale'], $country_2char) !== false || stripos($ldet['locale'], $country_name) !== false) {
2957 // increase language tag score
2958 $langtags_score[$ltag]++;
2959 }
2960 }
2961 if (!empty($ldet['name']) && stripos($ldet['name'], $country_name) !== false) {
2962 // increase language tag score
2963 $langtags_score[$ltag]++;
2964 }
2965 if (!empty($ldet['nativeName']) && stripos($ldet['nativeName'], $country_name) !== false) {
2966 // increase language tag score
2967 $langtags_score[$ltag]++;
2968 }
2969
2970 // check language similarities between countries
2971 if (isset($similarities[$country_2char])) {
2972 $spoken_tags = !is_array($similarities[$country_2char]) ? array($similarities[$country_2char]) : $similarities[$country_2char];
2973 // check if language tag(s) is available for this spoken language
2974 foreach ($spoken_tags as $spoken_tag) {
2975 if ($lang_country_codes[0] == strtoupper($spoken_tag)) {
2976 // increase language tag score
2977 $langtags_score[$ltag]++;
2978 }
2979 }
2980 }
2981 }
2982
2983 // make sure at least one language tag has got some points
2984 if (max($langtags_score) === 0) {
2985 // no languages installed to honor this country
2986 return null;
2987 }
2988
2989 // sort language tag scores
2990 arsort($langtags_score);
2991
2992 // reset array pointer to the first (highest) element
2993 reset($langtags_score);
2994
2995 // return the language tag with the highest score
2996 return key($langtags_score);
2997 }
2998
2999 public static function getCurrencyCodePp() {
3000 $dbo = JFactory::getDbo();
3001 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='currencycodepp';";
3002 $dbo->setQuery($q);
3003 $dbo->execute();
3004 $ft = $dbo->loadAssocList();
3005 return $ft[0]['setting'];
3006 }
3007
3008 public static function getIntroMain($vbo_tn = null) {
3009 $dbo = JFactory::getDbo();
3010 $q = "SELECT `id`,`setting` FROM `#__vikbooking_texts` WHERE `param`='intromain';";
3011 $dbo->setQuery($q);
3012 $dbo->execute();
3013 $ft = $dbo->loadAssocList();
3014 if (!is_object($vbo_tn)) {
3015 $vbo_tn = self::getTranslator();
3016 }
3017 $vbo_tn->translateContents($ft, '#__vikbooking_texts');
3018 return $ft[0]['setting'];
3019 }
3020
3021 public static function getClosingMain($vbo_tn = null) {
3022 $dbo = JFactory::getDbo();
3023 $q = "SELECT `id`,`setting` FROM `#__vikbooking_texts` WHERE `param`='closingmain';";
3024 $dbo->setQuery($q);
3025 $dbo->execute();
3026 $ft = $dbo->loadAssocList();
3027 if (!is_object($vbo_tn)) {
3028 $vbo_tn = self::getTranslator();
3029 }
3030 $vbo_tn->translateContents($ft, '#__vikbooking_texts');
3031 return $ft[0]['setting'];
3032 }
3033
3034 public static function getFullFrontTitle($vbo_tn = null) {
3035 $dbo = JFactory::getDbo();
3036 $q = "SELECT `id`,`setting` FROM `#__vikbooking_texts` WHERE `param`='fronttitle';";
3037 $dbo->setQuery($q);
3038 $dbo->execute();
3039 $ft = $dbo->loadAssocList();
3040 if (!is_object($vbo_tn)) {
3041 $vbo_tn = self::getTranslator();
3042 }
3043 $vbo_tn->translateContents($ft, '#__vikbooking_texts');
3044 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='fronttitletag';";
3045 $dbo->setQuery($q);
3046 $dbo->execute();
3047 $fttag = $dbo->loadAssocList();
3048 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='fronttitletagclass';";
3049 $dbo->setQuery($q);
3050 $dbo->execute();
3051 $fttagclass = $dbo->loadAssocList();
3052 if (empty($ft[0]['setting'])) {
3053 return "";
3054 } else {
3055 if (empty($fttag[0]['setting'])) {
3056 return $ft[0]['setting'] . "<br/>\n";
3057 } else {
3058 $tag = str_replace("<", "", $fttag[0]['setting']);
3059 $tag = str_replace(">", "", $tag);
3060 $tag = str_replace("/", "", $tag);
3061 $tag = trim($tag);
3062 return "<" . $tag . "" . (!empty($fttagclass) ? " class=\"" . $fttagclass[0]['setting'] . "\"" : "") . ">" . $ft[0]['setting'] . "</" . $tag . ">";
3063 }
3064 }
3065 }
3066
3067 public static function dateIsValid($date) {
3068 $df = self::getDateFormat();
3069 $datesep = self::getDateSeparator();
3070 if (strlen($date) != 10) {
3071 return false;
3072 }
3073 $cur_dsep = "/";
3074 if ($datesep != $cur_dsep && strpos($date, $datesep) !== false) {
3075 $cur_dsep = $datesep;
3076 }
3077 $x = explode($cur_dsep, $date);
3078 if ($df == "%d/%m/%Y") {
3079 if (strlen($x[0]) != 2 || $x[0] > 31 || strlen($x[1]) != 2 || $x[1] > 12 || strlen($x[2]) != 4) {
3080 return false;
3081 }
3082 } elseif ($df == "%m/%d/%Y") {
3083 if (strlen($x[1]) != 2 || $x[1] > 31 || strlen($x[0]) != 2 || $x[0] > 12 || strlen($x[2]) != 4) {
3084 return false;
3085 }
3086 } else {
3087 if (strlen($x[2]) != 2 || $x[2] > 31 || strlen($x[1]) != 2 || $x[1] > 12 || strlen($x[0]) != 4) {
3088 return false;
3089 }
3090 }
3091 return true;
3092 }
3093
3094 public static function sayDateFormat() {
3095 $dbo = JFactory::getDbo();
3096 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='dateformat';";
3097 $dbo->setQuery($q);
3098 $dbo->execute();
3099 $s = $dbo->loadAssocList();
3100 if ($s[0]['setting'] == "%d/%m/%Y") {
3101 return JText::translate('VBCONFIGONETWELVE');
3102 } elseif ($s[0]['setting'] == "%m/%d/%Y") {
3103 return JText::translate('VBCONFIGONEMDY');
3104 } else {
3105 return JText::translate('VBCONFIGONETENTHREE');
3106 }
3107 }
3108
3109 /**
3110 * Calculates the Unix timestamp from the given date and
3111 * time. Avoids DST issues thanks to mktime. With prior releases,
3112 * DST issues may occur due to the sum of seconds.
3113 *
3114 * @param string $date the date string formatted with the current settings
3115 * @param int $h hours from 0 to 23 for check-in/check-out
3116 * @param int $m minutes from 0 to 59 for check-in/check-out
3117 * @param int $s seconds from 0 to 59 for check-in/check-out
3118 *
3119 * @return int the Unix timestamp of the date
3120 *
3121 * @since 1.0.14
3122 * @since 1.15.0 (J) - 1.5.0 (WP) added capability to always support Y-m-d format for VCM.
3123 * signature modified for $h and $m that now take 0 as default value.
3124 */
3125 public static function getDateTimestamp($date, $h = 0, $m = 0, $s = 0) {
3126 $df = self::getDateFormat();
3127 $datesep = self::getDateSeparator();
3128 $cur_dsep = "/";
3129 if ($datesep != $cur_dsep && strpos($date, $datesep) !== false) {
3130 $cur_dsep = $datesep;
3131 }
3132 if (preg_match("/^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])$/", $date)) {
3133 // date is in Y-m-d format (with no time)
3134 $cur_dsep = '-';
3135 $df = "%Y/%m/%d";
3136 }
3137 $x = explode($cur_dsep, $date);
3138 if (!(count($x) > 2)) {
3139 return 0;
3140 }
3141 if ($df == "%d/%m/%Y") {
3142 $month = (int)$x[1];
3143 $mday = (int)$x[0];
3144 $year = (int)$x[2];
3145 } elseif ($df == "%m/%d/%Y") {
3146 $month = (int)$x[0];
3147 $mday = (int)$x[1];
3148 $year = (int)$x[2];
3149 } else {
3150 $month = (int)$x[1];
3151 $mday = (int)$x[2];
3152 $year = (int)$x[0];
3153 }
3154 $h = empty($h) ? 0 : (int)$h;
3155 $m = empty($m) ? 0 : (int)$m;
3156 $s = $s > 0 && $s <= 59 ? $s : 0;
3157
3158 return mktime($h, $m, $s, $month, $mday, $year);
3159 }
3160
3161 public static function ivaInclusa($skipsession = false) {
3162 // cache value in static var
3163 static $getTaxIncluded = null;
3164
3165 if ($getTaxIncluded !== null) {
3166 return (bool)$getTaxIncluded;
3167 }
3168
3169 $dbo = JFactory::getDbo();
3170
3171 if ($skipsession) {
3172 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='ivainclusa'";
3173 $dbo->setQuery($q, 0, 1);
3174 $dbo->execute();
3175 $vat_incl_data = $dbo->getNumRows() ? (int)$dbo->loadResult() : 1;
3176
3177 // cache value and return it
3178 $getTaxIncluded = $vat_incl_data;
3179
3180 return (bool)$vat_incl_data;
3181 }
3182
3183 $session = JFactory::getSession();
3184 $sval = $session->get('getTaxIncluded', '');
3185 if (strlen($sval)) {
3186 return (bool)$sval;
3187 }
3188
3189 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='ivainclusa'";
3190 $dbo->setQuery($q, 0, 1);
3191 $dbo->execute();
3192 $vat_incl_data = $dbo->getNumRows() ? (int)$dbo->loadResult() : 1;
3193
3194 // update session value, cache it and return it
3195 $session->set('getTaxIncluded', $vat_incl_data);
3196 $getTaxIncluded = $vat_incl_data;
3197
3198 return (bool)$vat_incl_data;
3199 }
3200
3201 public static function showTaxOnSummaryOnly($skipsession = false) {
3202 if ($skipsession) {
3203 $dbo = JFactory::getDbo();
3204 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='taxsummary';";
3205 $dbo->setQuery($q);
3206 $dbo->execute();
3207 $s = $dbo->loadAssocList();
3208 return (intval($s[0]['setting']) == 1 ? true : false);
3209 } else {
3210 $session = JFactory::getSession();
3211 $sval = $session->get('vbshowTaxOnSummaryOnly', '');
3212 if (strlen($sval) > 0) {
3213 return (intval($sval) == 1 ? true : false);
3214 } else {
3215 $dbo = JFactory::getDbo();
3216 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='taxsummary';";
3217 $dbo->setQuery($q);
3218 $dbo->execute();
3219 $s = $dbo->loadAssocList();
3220 $session->set('vbshowTaxOnSummaryOnly', $s[0]['setting']);
3221 return (intval($s[0]['setting']) == 1 ? true : false);
3222 }
3223 }
3224 }
3225
3226 public static function tokenForm() {
3227 $dbo = JFactory::getDbo();
3228 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='tokenform';";
3229 $dbo->setQuery($q);
3230 $dbo->execute();
3231 $s = $dbo->loadAssocList();
3232 return (intval($s[0]['setting']) == 1 ? true : false);
3233 }
3234
3235 public static function getPaypalAcc() {
3236 $dbo = JFactory::getDbo();
3237 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='ccpaypal';";
3238 $dbo->setQuery($q);
3239 $dbo->execute();
3240 $s = $dbo->loadAssocList();
3241 return $s[0]['setting'];
3242 }
3243
3244 public static function getAccPerCent() {
3245 $dbo = JFactory::getDbo();
3246 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='payaccpercent';";
3247 $dbo->setQuery($q);
3248 $dbo->execute();
3249 $s = $dbo->loadAssocList();
3250 return $s[0]['setting'];
3251 }
3252
3253 public static function getTypeDeposit($skipsession = false) {
3254 if ($skipsession) {
3255 $dbo = JFactory::getDbo();
3256 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='typedeposit';";
3257 $dbo->setQuery($q);
3258 $dbo->execute();
3259 $s = $dbo->loadAssocList();
3260 return $s[0]['setting'];
3261 } else {
3262 $session = JFactory::getSession();
3263 $sval = $session->get('getTypeDeposit', '');
3264 if (strlen($sval) > 0) {
3265 return $sval;
3266 } else {
3267 $dbo = JFactory::getDbo();
3268 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='typedeposit';";
3269 $dbo->setQuery($q);
3270 $dbo->execute();
3271 $s = $dbo->loadAssocList();
3272 $session->set('getTypeDeposit', $s[0]['setting']);
3273 return $s[0]['setting'];
3274 }
3275 }
3276 }
3277
3278 public static function multiplePayments() {
3279 $dbo = JFactory::getDbo();
3280 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='multipay';";
3281 $dbo->setQuery($q);
3282 $dbo->execute();
3283 $s = $dbo->loadAssocList();
3284 return (intval($s[0]['setting']) == 1 ? true : false);
3285 }
3286
3287 public static function getAdminMail() {
3288 $dbo = JFactory::getDbo();
3289 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='adminemail';";
3290 $dbo->setQuery($q);
3291 $dbo->execute();
3292 if ($dbo->getNumRows() > 0) {
3293 $s = $dbo->loadAssocList();
3294 return $s[0]['setting'];
3295 }
3296 return '';
3297 }
3298
3299 public static function getSenderMail() {
3300 $dbo = JFactory::getDbo();
3301 $q="SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='senderemail';";
3302 $dbo->setQuery($q);
3303 $dbo->execute();
3304 $s = $dbo->loadAssocList();
3305 return empty($s[0]['setting']) ? self::getAdminMail() : $s[0]['setting'];
3306 }
3307
3308 public static function getPaymentName($vbo_tn = null) {
3309 $dbo = JFactory::getDbo();
3310 $q = "SELECT `id`,`setting` FROM `#__vikbooking_texts` WHERE `param`='paymentname';";
3311 $dbo->setQuery($q);
3312 $dbo->execute();
3313 $s = $dbo->loadAssocList();
3314 if (!is_object($vbo_tn)) {
3315 $vbo_tn = self::getTranslator();
3316 }
3317 $vbo_tn->translateContents($s, '#__vikbooking_texts');
3318 return $s[0]['setting'];
3319 }
3320
3321 public static function getTermsConditions($vbo_tn = null) {
3322 //VBO 1.10
3323 $dbo = JFactory::getDbo();
3324 $q = "SELECT `id`,`setting` FROM `#__vikbooking_texts` WHERE `param`='termsconds';";
3325 $dbo->setQuery($q);
3326 $dbo->execute();
3327 if ($dbo->getNumRows() > 0) {
3328 $s = $dbo->loadAssocList();
3329 if (!is_object($vbo_tn)) {
3330 $vbo_tn = self::getTranslator();
3331 }
3332 $vbo_tn->translateContents($s, '#__vikbooking_texts');
3333 } else {
3334 //the record has never been saved. Compose it with the default lang definition
3335 $timeopst = self::getTimeOpenStore(true);
3336 if (is_array($timeopst)) {
3337 $openat = self::getHoursMinutes($timeopst[0]);
3338 $closeat = self::getHoursMinutes($timeopst[1]);
3339 } else {
3340 $openat = array(12, 0);
3341 $closeat = array(10, 0);
3342 }
3343 $checkin_str = ($openat[0] < 10 ? '0'.$openat[0] : $openat[0]).':'.($openat[1] < 10 ? '0'.$openat[1] : $openat[1]);
3344 $checkout_str = ($closeat[0] < 10 ? '0'.$closeat[0] : $closeat[0]).':'.($closeat[1] < 10 ? '0'.$closeat[1] : $closeat[1]);
3345 $s = array(0 => array('setting' => nl2br(JText::sprintf('VBOTERMSCONDSDEFTEXT', $checkin_str, $checkout_str))));
3346 }
3347
3348 return $s[0]['setting'];
3349 }
3350
3351 public static function getMinutesLock($conv = false) {
3352 $dbo = JFactory::getDbo();
3353 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='minuteslock';";
3354 $dbo->setQuery($q);
3355 $dbo->execute();
3356 if (!$dbo->getNumRows()) {
3357 return 0;
3358 }
3359 $s = $dbo->loadAssocList();
3360 if ($conv) {
3361 return (time() + ((int)$s[0]['setting'] * 60));
3362 }
3363 return (int)$s[0]['setting'];
3364 }
3365
3366 /**
3367 * Checks if the room units are temporarily locked on the given dates.
3368 *
3369 * @param int $idroom the room ID.
3370 * @param int $units the total number of room units.
3371 * @param int $first the check-in timestamp.
3372 * @param int $second the check-out timestamp.
3373 * @param bool $occupied if true, the system will also consider the booked units.
3374 * @param array $skip_busy_ids optional list of busy record IDs to skip (booking modification).
3375 *
3376 * @return bool true if the room is available, false if fully locked/occupied, hence not bookable.
3377 *
3378 * @since 1.16.0 (J) - 1.6.0 (WP) added $occupied argument.
3379 * @since 1.16.3 (J) - 1.6.3 (WP) added $skip_busy_ids argument to support usage for booking modifications.
3380 */
3381 public static function roomNotLocked($idroom, $units, $first, $second, $occupied = false, $skip_busy_ids = [])
3382 {
3383 $dbo = JFactory::getDbo();
3384
3385 if ($units < 1) {
3386 return false;
3387 }
3388
3389 $actnow = time();
3390
3391 // clean up expired records
3392 $q = "DELETE FROM `#__vikbooking_tmplock` WHERE `until`<" . $dbo->quote($actnow) . ";";
3393 $dbo->setQuery($q);
3394 $dbo->execute();
3395
3396 $secdiff = $second - $first;
3397 $daysdiff = $secdiff / 86400;
3398 if (is_int($daysdiff)) {
3399 if ($daysdiff < 1) {
3400 $daysdiff = 1;
3401 }
3402 } else {
3403 if ($daysdiff < 1) {
3404 $daysdiff = 1;
3405 } else {
3406 $sum = floor($daysdiff) * 86400;
3407 $newdiff = $secdiff - $sum;
3408 $maxhmore = self::getHoursMoreRb() * 3600;
3409 if ($maxhmore >= $newdiff) {
3410 $daysdiff = floor($daysdiff);
3411 } else {
3412 $daysdiff = ceil($daysdiff);
3413 }
3414 }
3415 }
3416 $groupdays = self::getGroupDays($first, $second, $daysdiff);
3417 $tmplock_map = [];
3418
3419 // check temporarily locked records
3420 $check = "SELECT `id`,`checkin`,`realback` FROM `#__vikbooking_tmplock` WHERE `idroom`=" . $dbo->quote($idroom) . " AND `until`>=" . $dbo->quote($actnow) . ";";
3421 $dbo->setQuery($check);
3422 $busy = $dbo->loadAssocList();
3423 if ($busy) {
3424 foreach ($groupdays as $kg => $gday) {
3425 $bfound = 0;
3426 foreach ($busy as $bu) {
3427 if ($gday >= $bu['checkin'] && $gday <= $bu['realback']) {
3428 $bfound++;
3429 }
3430 }
3431 $tmplock_map[$kg] = $bfound;
3432 if ($bfound >= $units) {
3433 return false;
3434 }
3435 }
3436 }
3437
3438 if ($occupied) {
3439 // check also the busy records and some the occupied units to the temporarily locked ones
3440 $check = "SELECT `id`,`checkin`,`realback` FROM `#__vikbooking_busy` WHERE `idroom`=" . $dbo->quote($idroom) . " AND `realback`>=" . $first . ";";
3441 $dbo->setQuery($check);
3442 $busy = $dbo->loadAssocList();
3443 if (!$busy) {
3444 return true;
3445 }
3446
3447 foreach ($groupdays as $kg => $gday) {
3448 $bfound = 0;
3449 foreach ($busy as $bu) {
3450 if (in_array($bu['id'], $skip_busy_ids)) {
3451 // Booking modification
3452 continue;
3453 }
3454 if ($gday >= $bu['checkin'] && $gday <= $bu['realback']) {
3455 $bfound++;
3456 }
3457 }
3458 // sum units occupied to the units temporarily locked
3459 $bfound += isset($tmplock_map[$kg]) ? (int)$tmplock_map[$kg] : 0;
3460 if ($bfound >= $units) {
3461 return false;
3462 }
3463 }
3464 }
3465
3466 return true;
3467 }
3468
3469 public static function getGroupDays($first, $second, $daysdiff) {
3470 $ret = array();
3471 $ret[] = $first;
3472 if ($daysdiff > 1) {
3473 $start = getdate($first);
3474 $end = getdate($second);
3475 $endcheck = mktime(0, 0, 0, $end['mon'], $end['mday'], $end['year']);
3476 for($i = 1; $i < $daysdiff; $i++) {
3477 $checkday = $start['mday'] + $i;
3478 $dayts = mktime(0, 0, 0, $start['mon'], $checkday, $start['year']);
3479 if ($dayts != $endcheck) {
3480 $ret[] = $dayts;
3481 }
3482 }
3483 }
3484 $ret[] = $second;
3485 return $ret;
3486 }
3487
3488 /**
3489 * Counts the hours of difference between the current
3490 * server time and the selected check-in date and time.
3491 *
3492 * @param int $checkin_ts
3493 * @param [int] $now_ts
3494 *
3495 * @return int
3496 */
3497 public static function countHoursToArrival($checkin_ts, $now_ts = '') {
3498 $hoursdiff = 0;
3499
3500 if (empty($now_ts)) {
3501 $now_ts = time();
3502 }
3503
3504 if ($now_ts >= $checkin_ts) {
3505 return $hoursdiff;
3506 }
3507
3508 $hoursdiff = floor(($checkin_ts - $now_ts) / 3600);
3509
3510 return $hoursdiff;
3511 }
3512
3513 /**
3514 * Loads all the occupied records for the given rooms and offset.
3515 *
3516 * @param array $roomids the optional list of room IDs to filter.
3517 * @param int $from_ts the optional base date to use.
3518 * @param int $max_ts the optional max date to use.
3519 * @param bool $closures whether to ignore, include or exclude closures.
3520 *
3521 * @return array
3522 *
3523 * @since 1.15.0 (J) - 1.5.0 (WP) changed arguments signature with 3rd arg.
3524 * @since 1.15.2 (J) - 1.5.5 (WP) changed arguments signature with 4th arg.
3525 */
3526 public static function loadBusyRecords($roomids = array(), $from_ts = 0, $max_ts = 0, $closures = null)
3527 {
3528 $base_ts = empty($from_ts) ? time() : $from_ts;
3529 $busy = [];
3530 $clauses = [];
3531
3532 if (is_array($roomids) && count($roomids)) {
3533 // filter busy records only by the requested room ids
3534 $roomids = array_map(function($rid) {
3535 return (int)$rid;
3536 }, $roomids);
3537 $clauses[] = "`b`.`idroom` IN (" . implode(', ', $roomids) . ")";
3538 }
3539
3540 // exclude past reservations
3541 $clauses[] = "`b`.`checkout` >= {$base_ts}";
3542
3543 if (!empty($max_ts)) {
3544 // exclude future reservations (recommended for large datasets)
3545 $clauses[] = "`b`.`checkin` <= {$max_ts}";
3546 }
3547
3548 if (is_bool($closures)) {
3549 // include or exclude closures
3550 if ($closures) {
3551 $clauses[] = "`o`.`closure` = 1";
3552 } else {
3553 $clauses[] = "`o`.`closure` != 1";
3554 }
3555 }
3556
3557 $dbo = JFactory::getDbo();
3558
3559 $q = "SELECT `b`.*, `ob`.`idorder`, `o`.`closure` FROM `#__vikbooking_busy` AS `b`
3560 LEFT JOIN `#__vikbooking_ordersbusy` AS `ob` ON `ob`.`idbusy`=`b`.`id`
3561 LEFT JOIN `#__vikbooking_orders` AS `o` ON `o`.`id`=`ob`.`idorder`
3562 WHERE " . implode(' AND ', $clauses) . ";";
3563 $dbo->setQuery($q);
3564 $allbusy = $dbo->loadAssocList();
3565 if ($allbusy) {
3566 foreach ($allbusy as $br) {
3567 if (!isset($busy[$br['idroom']])) {
3568 $busy[$br['idroom']] = [];
3569 }
3570 $busy[$br['idroom']][] = $br;
3571 }
3572 }
3573
3574 return $busy;
3575 }
3576
3577 /**
3578 * Loads all the busy records by excluding the closures.
3579 * Built for the page dashboard to calculate the actual
3580 * rooms occupancy by excluding the rooms closed.
3581 *
3582 * @param array $roomids the optional list of room IDs to filter.
3583 * @param int $from_ts the optional base date to use.
3584 * @param int $max_ts the optional max date to use.
3585 *
3586 * @return array the list of busy records.
3587 *
3588 * @since 1.12
3589 * @since 1.15.2 (J) - 1.5.5 (WP) changed arguments signature with 3rd arg.
3590 */
3591 public static function loadBusyRecordsUnclosed($roomids = array(), $from_ts = 0, $max_ts = 0)
3592 {
3593 if (!is_array($roomids) || !count($roomids)) {
3594 return [];
3595 }
3596
3597 return self::loadBusyRecords($roomids, $from_ts, $max_ts, false);
3598 }
3599
3600 public static function loadBookingBusyIds($idorder) {
3601 $busy = array();
3602 if (empty($idorder)) {
3603 return $busy;
3604 }
3605 $dbo = JFactory::getDbo();
3606 $q = "SELECT * FROM `#__vikbooking_ordersbusy` WHERE `idorder`=".(int)$idorder.";";
3607 $dbo->setQuery($q);
3608 $allbusy = $dbo->loadAssocList();
3609 if ($allbusy) {
3610 foreach ($allbusy as $b) {
3611 array_push($busy, $b['idbusy']);
3612 }
3613 }
3614 return $busy;
3615 }
3616
3617 public static function loadLockedRecords($roomids, $ts = 0) {
3618 $dbo = JFactory::getDbo();
3619 $actnow = empty($ts) ? time() : $ts;
3620 $locked = array();
3621 $q = "DELETE FROM `#__vikbooking_tmplock` WHERE `until`<" . $actnow . ";";
3622 $dbo->setQuery($q);
3623 $dbo->execute();
3624 if (!is_array($roomids) || !(count($roomids) > 0)) {
3625 return $locked;
3626 }
3627 $check = "SELECT `id`,`idroom`,`checkin`,`realback` FROM `#__vikbooking_tmplock` WHERE `idroom` IN (".implode(', ', $roomids).") AND `until` > ".$actnow.";";
3628 $dbo->setQuery($check);
3629 $all_locked = $dbo->loadAssocList();
3630 if ($all_locked) {
3631 foreach ($all_locked as $kb => $br) {
3632 $locked[$br['idroom']][$kb] = $br;
3633 }
3634 }
3635 return $locked;
3636 }
3637
3638 public static function getRoomBookingsFromBusyIds($idroom, $arr_bids) {
3639 $bookings = [];
3640 if (empty($idroom) || !is_array($arr_bids) || !(count($arr_bids) > 0)) {
3641 return $bookings;
3642 }
3643 $dbo = JFactory::getDbo();
3644 $q = "SELECT `ob`.`idorder`,`ob`.`idbusy` FROM `#__vikbooking_ordersbusy` AS `ob` WHERE `ob`.`idbusy` IN (".implode(',', $arr_bids).") GROUP BY `ob`.`idorder`,`ob`.`idbusy`;";
3645 $dbo->setQuery($q);
3646 $all_booking_ids = $dbo->loadAssocList();
3647 if ($all_booking_ids) {
3648 $oids = array();
3649 foreach ($all_booking_ids as $bid) {
3650 $oids[] = $bid['idorder'];
3651 }
3652 $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` ".
3653 "FROM `#__vikbooking_ordersrooms` AS `or` ".
3654 "LEFT JOIN `#__vikbooking_orders` `o` ON `o`.`id`=`or`.`idorder` ".
3655 "WHERE `or`.`idorder` IN (".implode(',', $oids).") AND `or`.`idroom`=".(int)$idroom." AND `o`.`status`='confirmed' ".
3656 "ORDER BY `o`.`checkout` ASC;";
3657 $dbo->setQuery($q);
3658 $bookings = $dbo->loadAssocList();
3659 if (!$bookings) {
3660 $bookings = [];
3661 }
3662 }
3663 return $bookings;
3664 }
3665
3666 public static function roomBookable($idroom, $units, $first, $second, $skip_busy_ids = array())
3667 {
3668 $dbo = JFactory::getDbo();
3669
3670 if ($units < 1) {
3671 return false;
3672 }
3673
3674 $secdiff = $second - $first;
3675 $daysdiff = $secdiff / 86400;
3676 if (is_int($daysdiff)) {
3677 if ($daysdiff < 1) {
3678 $daysdiff = 1;
3679 }
3680 } else {
3681 if ($daysdiff < 1) {
3682 $daysdiff = 1;
3683 } else {
3684 $sum = floor($daysdiff) * 86400;
3685 $newdiff = $secdiff - $sum;
3686 $maxhmore = self::getHoursMoreRb() * 3600;
3687 if ($maxhmore >= $newdiff) {
3688 $daysdiff = floor($daysdiff);
3689 } else {
3690 $daysdiff = ceil($daysdiff);
3691 }
3692 }
3693 }
3694
3695 $groupdays = self::getGroupDays($first, $second, $daysdiff);
3696
3697 $check = "SELECT `id`,`checkin`,`realback` FROM `#__vikbooking_busy` WHERE `idroom`=" . $dbo->quote($idroom) . " AND `realback`>=" . $first . ";";
3698 $dbo->setQuery($check);
3699 $busy = $dbo->loadAssocList();
3700 if (!$busy) {
3701 return true;
3702 }
3703
3704 foreach ($groupdays as $gday) {
3705 $bfound = 0;
3706 foreach ($busy as $bu) {
3707 if (in_array($bu['id'], $skip_busy_ids)) {
3708 //VBO 1.10 - Booking modification
3709 continue;
3710 }
3711 if ($gday >= $bu['checkin'] && $gday <= $bu['realback']) {
3712 $bfound++;
3713 }
3714 }
3715 if ($bfound >= $units) {
3716 return false;
3717 }
3718 }
3719
3720 return true;
3721 }
3722
3723 public static function payTotal() {
3724 $dbo = JFactory::getDbo();
3725 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='paytotal';";
3726 $dbo->setQuery($q);
3727 $dbo->execute();
3728 $s = $dbo->loadAssocList();
3729 return (intval($s[0]['setting']) == 1 ? true : false);
3730 }
3731
3732 public static function getDepositIfDays() {
3733 $dbo = JFactory::getDbo();
3734 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='depifdaysadv';";
3735 $dbo->setQuery($q);
3736 $dbo->execute();
3737 $s = $dbo->loadAssocList();
3738 return intval($s[0]['setting']);
3739 }
3740
3741 public static function depositAllowedDaysAdv($checkints) {
3742 $days_adv = self::getDepositIfDays();
3743 if (!($days_adv > 0) || !($checkints > 0)) {
3744 return true;
3745 }
3746 $now_info = getdate();
3747 $maxts = mktime(0, 0, 0, $now_info['mon'], ($now_info['mday'] + $days_adv), $now_info['year']);
3748 return $maxts > $checkints ? false : true;
3749 }
3750
3751 public static function depositCustomerChoice() {
3752 $dbo = JFactory::getDbo();
3753 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='depcustchoice';";
3754 $dbo->setQuery($q);
3755 $dbo->execute();
3756 $s = $dbo->loadAssocList();
3757 return (intval($s[0]['setting']) == 1 ? true : false);
3758 }
3759
3760 public static function getDepositOverrides($getjson = false) {
3761 $dbo = JFactory::getDbo();
3762 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='depoverrides';";
3763 $dbo->setQuery($q);
3764 $dbo->execute();
3765 if ($dbo->getNumRows() > 0) {
3766 $s = $dbo->loadResult();
3767 return $getjson ? $s : json_decode($s, true);
3768 }
3769 //count of this array will be at least 1 to store the "more" property
3770 $def_arr = array('more' => '');
3771 $def_val = json_encode($def_arr);
3772 $q = "INSERT INTO `#__vikbooking_config` (`param`,`setting`) VALUES ('depoverrides', ".$dbo->quote($def_val).");";
3773 $dbo->setQuery($q);
3774 $dbo->execute();
3775 return $getjson ? $def_val : $def_arr;
3776 }
3777
3778 public static function calcDepositOverride($amount_deposit, $nights) {
3779 $overrides = self::getDepositOverrides();
3780 $nights = intval($nights);
3781 $andmore = intval($overrides['more']);
3782 if (!(count($overrides) > 1)) {
3783 //no overrides
3784 return $amount_deposit;
3785 }
3786 foreach ($overrides as $k => $v) {
3787 if ((int)$k == $nights && strlen($v) > 0) {
3788 //exact override found
3789 return (float)$v;
3790 }
3791 }
3792 if ($andmore > 0 && $andmore <= $nights) {
3793 foreach ($overrides as $k => $v) {
3794 if ((int)$k == $andmore && strlen($v) > 0) {
3795 //"and more" nights found
3796 return (float)$v;
3797 }
3798 }
3799 }
3800 //nothing was found
3801 return $amount_deposit;
3802 }
3803
3804 public static function noDepositForNonRefund() {
3805 $dbo = JFactory::getDbo();
3806 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='nodepnonrefund';";
3807 $dbo->setQuery($q);
3808 $dbo->execute();
3809 if ($dbo->getNumRows() > 0) {
3810 $s = $dbo->loadResult();
3811 return ((int)$s == 1);
3812 }
3813 $q = "INSERT INTO `#__vikbooking_config` (`param`,`setting`) VALUES ('nodepnonrefund', '0');";
3814 $dbo->setQuery($q);
3815 $dbo->execute();
3816 //default to false
3817 return false;
3818 }
3819
3820 /**
3821 * This method returns the room-rate array with the lowest price
3822 * that matches the preferred rate plan parameter (if available).
3823 * The array $website_rates could not be an array, or it could be
3824 * an array with the error string (response from the TACVBO Class).
3825 * The method has been introduced in VBO 1.10 and it's mainly used
3826 * by the module mod_vikbooking_channelrates and its ajax requests.
3827 *
3828 * @param array $website_rates the array of the website rates returned by the method fetchWebsiteRates()
3829 * @param int $def_rplan the id of the default type of price to take for display. If empty, take the lowest rate
3830 *
3831 * @return array
3832 */
3833 public static function getBestRoomRate($website_rates, $def_rplan) {
3834 if (!is_array($website_rates) || !(count($website_rates) > 0) || (is_array($website_rates) && isset($website_rates['e4j.error']))) {
3835 return array();
3836 }
3837 $best_room_rate = array();
3838 foreach ($website_rates as $rid => $tars) {
3839 foreach ($tars as $tar) {
3840 if (empty($def_rplan) || (int)$tar['idprice'] == $def_rplan) {
3841 //the array $website_rates is already sorted by price ASC, so we take the first useful array
3842 $best_room_rate = $tar;
3843 break 2;
3844 }
3845 }
3846 }
3847 if (!(count($best_room_rate) > 0)) {
3848 //the default rate plan is not available if we enter this statement, so we take the first and lowest rate
3849 foreach ($website_rates as $rid => $tars) {
3850 foreach ($tars as $tar) {
3851 $best_room_rate = $tar;
3852 break 2;
3853 }
3854 }
3855 }
3856
3857 return $best_room_rate;
3858 }
3859
3860 /**
3861 * This method returns an array with the details
3862 * of all channels in VCM that supports AV requests,
3863 * and that have at least one room type mapped.
3864 * The method invokes the Logos Class to return details
3865 * about the name and logo URL of the channel.
3866 * The method has been introduced in VBO 1.10 and it's mainly used
3867 * by the module mod_vikbooking_channelrates and its ajax requests.
3868 *
3869 * @param array $channels an array of channel IDs to be mapped on the VCM relations
3870 *
3871 * @return array
3872 */
3873 public static function getChannelsMap($channels) {
3874 if (!is_array($channels) || !(count($channels))) {
3875 return array();
3876 }
3877 $vcm_logos = self::getVcmChannelsLogo('', true);
3878 if (!is_object($vcm_logos)) {
3879 return array();
3880 }
3881 $channels_ids = array();
3882 foreach ($channels as $chid) {
3883 $ichid = (int)$chid;
3884 if ($ichid < 1) {
3885 continue;
3886 }
3887 array_push($channels_ids, $ichid);
3888 }
3889 if (!(count($channels_ids))) {
3890 return array();
3891 }
3892 $dbo = JFactory::getDbo();
3893 $q = "SELECT `idchannel`, `channel` FROM `#__vikchannelmanager_roomsxref` WHERE `idchannel` IN (".implode(', ', $channels_ids).") GROUP BY `idchannel`,`channel` ORDER BY `channel` ASC;";
3894 $dbo->setQuery($q);
3895 $dbo->execute();
3896 if ($dbo->getNumRows() < 1) {
3897 return array();
3898 }
3899 $channels_names = $dbo->loadAssocList();
3900 $channels_map = array();
3901 foreach ($channels_names as $ch) {
3902 $ota_logo_url = $vcm_logos->setProvenience($ch['channel'])->getLogoURL();
3903 $ota_logo_url = $ota_logo_url === false ? '' : $ota_logo_url;
3904 $chdata = array(
3905 'id' => $ch['idchannel'],
3906 'name' => ucwords($ch['channel']),
3907 'logo' => $ota_logo_url
3908 );
3909 array_push($channels_map, $chdata);
3910 }
3911 return $channels_map;
3912 }
3913
3914 /**
3915 * This method returns a string to calculate the rates for the OTAs. Data is taken from the Bulk Rates Cache
3916 * of Vik Channel Manager. The string returned contains the charge/discount operator at the position 0 (+ or -),
3917 * and the percentage char (%) at the last position (if percent). Between the first and last position there is
3918 * the float value. The method has been introduced in VBO 1.10 and it's mainly used by the "channel rates"
3919 * module/widget and its ajax requests.
3920 *
3921 * @param array $best_room_rate array containing a specific tariff returned by getBestRoomRate().
3922 * @param bool $per_channel if true, an associative array will be returned for each channel alteration.
3923 *
3924 * @return mixed string (empty or indicating the alteration), or associative array per channel.
3925 *
3926 * @since 1.15.0 (J) - 1.5.0 (WP) added support for VCM different alterations per channel.
3927 */
3928 public static function getOtasRatesVal($best_room_rate, $per_channel = false)
3929 {
3930 $otas_rates_val = '';
3931 if (!is_array($best_room_rate) || !count($best_room_rate) || !is_file(VCM_SITE_PATH.DIRECTORY_SEPARATOR.'helpers'.DIRECTORY_SEPARATOR.'lib.vikchannelmanager.php')) {
3932 return $otas_rates_val;
3933 }
3934 if (!class_exists('VikChannelManager')) {
3935 require_once(VCM_SITE_PATH.DIRECTORY_SEPARATOR.'helpers'.DIRECTORY_SEPARATOR.'lib.vikchannelmanager.php');
3936 }
3937 $bulk_rates_cache = VikChannelManager::getBulkRatesCache();
3938 if (count($bulk_rates_cache) && isset($best_room_rate['idprice'])) {
3939 if (isset($bulk_rates_cache[$best_room_rate['idroom']]) && isset($bulk_rates_cache[$best_room_rate['idroom']][$best_room_rate['idprice']])) {
3940 // the Bulk Rates Cache contains data for this room type and rate plan
3941 $data_cont = $bulk_rates_cache[$best_room_rate['idroom']][$best_room_rate['idprice']];
3942 // build rates modification string
3943 if ((int)$data_cont['rmod'] > 0) {
3944 // rates were modified for the OTAs, check how
3945 $rmodop = (int)$data_cont['rmodop'] > 0 ? '+' : '-';
3946 $rmodpcent = (int)$data_cont['rmodval'] > 0 ? '%' : '';
3947 $otas_rates_val = $rmodop.(float)$data_cont['rmodamount'].$rmodpcent;
3948 // check alterations per channel, if requested
3949 if ($per_channel && !empty($data_cont['rmod_channels']) && is_array($data_cont['rmod_channels'])) {
3950 // we compose an associative array with the alteration for each channel (0 = generic rule)
3951 $channel_alterations = array($otas_rates_val);
3952 foreach ($data_cont['rmod_channels'] as $ch_id => $ch_mods) {
3953 if ((int)$ch_mods['rmod'] > 0) {
3954 // custom rate alteration for this channel
3955 $rmodop = (int)$ch_mods['rmodop'] > 0 ? '+' : '-';
3956 $rmodpcent = (int)$ch_mods['rmodval'] > 0 ? '%' : '';
3957 $ch_alter_string = $rmodop . (float)$ch_mods['rmodamount'] . $rmodpcent;
3958 } else {
3959 // no rates alteration for this channel identifier
3960 $ch_alter_string = '+0%';
3961 }
3962 $channel_alterations[$ch_id] = $ch_alter_string;
3963 }
3964 // overwrite return value with associative array
3965 $otas_rates_val = $channel_alterations;
3966 }
3967 }
3968 }
3969 }
3970
3971 return $otas_rates_val;
3972 }
3973
3974 /**
3975 * This method checks if some non-refundable rates were selected
3976 * (`free_cancellation`=0), the only argument is an array of tariffs.
3977 * The property 'idprice' must be defined on each sub-array.
3978 *
3979 * @param $tars array
3980 *
3981 * @return boolean
3982 **/
3983 public static function findNonRefundableRates($tars = [])
3984 {
3985 $id_prices = [];
3986
3987 foreach ($tars as $tar) {
3988 if (isset($tar['idprice'])) {
3989 if (!in_array($tar['idprice'], $id_prices)) {
3990 array_push($id_prices, (int)$tar['idprice']);
3991 }
3992 continue;
3993 }
3994 foreach ($tar as $t) {
3995 if (isset($t['idprice'])) {
3996 if (!in_array($t['idprice'], $id_prices)) {
3997 array_push($id_prices, (int)$t['idprice']);
3998 }
3999 }
4000 }
4001 }
4002
4003 if (!count($id_prices)) {
4004 // no price IDs found (probably a package)
4005 return false;
4006 }
4007
4008 $dbo = JFactory::getDbo();
4009
4010 $q = "SELECT `id`,`name` FROM `#__vikbooking_prices` WHERE `id` IN (".implode(', ', $id_prices).") AND `free_cancellation`=0;";
4011 $dbo->setQuery($q);
4012 $dbo->execute();
4013
4014 return (bool)($dbo->getNumRows() > 0);
4015 }
4016
4017 /**
4018 * This method checks if the deposit is allowed depending on
4019 * the selected rate plans (idprice) for the rooms reserved.
4020 * If the configuration setting is enabled, and if some
4021 * non-refundable rates were selected (`free_cancellation`=0),
4022 * the method will return false, because the deposit is not allowed.
4023 * The only argument is an array of tariffs. The property 'idprice'
4024 * must be defined on each sub-array (multi-dimension supported)
4025 * throgh the method findNonRefundableRates();
4026 *
4027 * @param $tars array
4028 *
4029 * @return boolean
4030 **/
4031 public static function allowDepositFromRates($tars) {
4032 if (!self::noDepositForNonRefund()) {
4033 //deposit can be paid also if non-refundable rates
4034 return true;
4035 }
4036 return !self::findNonRefundableRates($tars);
4037 }
4038
4039 public static function showSearchSuggestions() {
4040 $dbo = JFactory::getDbo();
4041 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='searchsuggestions';";
4042 $dbo->setQuery($q);
4043 $dbo->execute();
4044 if ($dbo->getNumRows() > 0) {
4045 return (int)$dbo->loadResult();
4046 }
4047 $q = "INSERT INTO `#__vikbooking_config` (`param`,`setting`) VALUES ('searchsuggestions', '1');";
4048 $dbo->setQuery($q);
4049 $dbo->execute();
4050 return 1;
4051 }
4052
4053 /**
4054 * Given a code, returns the coupon information array.
4055 *
4056 * @param string $code the coupon code.
4057 *
4058 * @return array empty array or coupon record.
4059 *
4060 * @since 1.16.0 (J) - 1.6.0 (WP) the customers assigned are also returned.
4061 */
4062 public static function getCouponInfo($code)
4063 {
4064 $dbo = JFactory::getDbo();
4065
4066 $q = "SELECT * FROM `#__vikbooking_coupons` WHERE `code`=".$dbo->quote($code);
4067 $dbo->setQuery($q, 0, 1);
4068 $coupon = $dbo->loadAssoc();
4069 if (!$coupon) {
4070 return [];
4071 }
4072
4073 // check for customers assigned
4074 $q = "SELECT `idcustomer`, `automatic` FROM `#__vikbooking_customers_coupons` WHERE `idcoupon`=" . (int)$coupon['id'];
4075 $dbo->setQuery($q);
4076 $coupon_customers = $dbo->loadAssocList();
4077 if ($coupon_customers) {
4078 // set customers and automatic properties
4079 $coupon['customers'] = [];
4080 $coupon['automatic'] = $coupon_customers[0]['automatic'];
4081 foreach ($coupon_customers as $coupon_customer) {
4082 // push customer ID
4083 $coupon['customers'][] = $coupon_customer['idcustomer'];
4084 }
4085 }
4086
4087 return $coupon;
4088 }
4089
4090 public static function getRoomInfo($idroom)
4091 {
4092 $dbo = JFactory::getDbo();
4093
4094 $q = "SELECT `id`,`name`,`img`,`idcat`,`idcarat`,`info`,`smalldesc` FROM `#__vikbooking_rooms` WHERE `id`=" . (int)$idroom . ";";
4095 $dbo->setQuery($q);
4096 $room = $dbo->loadAssoc();
4097 if ($room) {
4098 return $room;
4099 }
4100
4101 return [];
4102 }
4103
4104 public static function loadOrdersRoomsData($idorder)
4105 {
4106 $dbo = JFactory::getDbo();
4107
4108 $q = "SELECT `or`.*,`r`.`name` AS `room_name`,`r`.`params` FROM `#__vikbooking_ordersrooms` AS `or` LEFT JOIN `#__vikbooking_rooms` `r` ON `r`.`id`=`or`.`idroom` WHERE `or`.`idorder`=" . (int)$idorder . ";";
4109 $dbo->setQuery($q);
4110
4111 return $dbo->loadAssocList();
4112 }
4113
4114 public static function sayCategory($ids, $vbo_tn = null) {
4115 $dbo = JFactory::getDbo();
4116 $split = explode(";", $ids);
4117 $say = "";
4118 foreach ($split as $k => $s) {
4119 if (strlen($s)) {
4120 $q = "SELECT `id`,`name` FROM `#__vikbooking_categories` WHERE `id`='" . $s . "';";
4121 $dbo->setQuery($q);
4122 $dbo->execute();
4123 if ($dbo->getNumRows() < 1) {
4124 continue;
4125 }
4126 $nam = $dbo->loadAssocList();
4127 if (is_object($vbo_tn)) {
4128 $vbo_tn->translateContents($nam, '#__vikbooking_categories');
4129 }
4130 $say .= $nam[0]['name'];
4131 $say .= (strlen($split[($k +1)]) && end($split) != $s ? ", " : "");
4132 }
4133 }
4134 return $say;
4135 }
4136
4137 public static function getRoomCaratOriz($idc, $vbo_tn = null) {
4138 $dbo = JFactory::getDbo();
4139 $split = explode(";", $idc);
4140 $carat = "";
4141 $arr = array();
4142 $where = array();
4143 foreach ($split as $s) {
4144 if (!empty($s)) {
4145 $where[] = $s;
4146 }
4147 }
4148 if (count($where) > 0) {
4149 $q = "SELECT `c`.* FROM `#__vikbooking_characteristics` AS `c` WHERE `c`.`id` IN (".implode(",", $where).") ORDER BY `c`.`ordering` ASC;";
4150 $dbo->setQuery($q);
4151 $dbo->execute();
4152 if ($dbo->getNumRows() > 0) {
4153 $arr = $dbo->loadAssocList();
4154 if (is_object($vbo_tn)) {
4155 $vbo_tn->translateContents($arr, '#__vikbooking_characteristics');
4156 }
4157 }
4158 }
4159 if (count($arr) > 0) {
4160 $carat .= "<div class=\"vbo-room-carats\">\n";
4161 foreach ($arr as $a) {
4162 if (!empty($a['textimg'])) {
4163 //tooltip icon text is not empty
4164 if (!empty($a['icon'])) {
4165 //an icon has been uploaded: display the image
4166 $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";
4167 } else {
4168 if (strpos($a['textimg'], '</i>') !== false) {
4169 //the tooltip icon text is a font-icon, we can use the name as tooltip
4170 $carat .= "<span class=\"vbo-room-carat\"><span class=\"vbo-expl\" data-vbo-expl=\"".$a['name']."\">".$a['textimg']."</span></span>\n";
4171 } else {
4172 //display just the text
4173 $carat .= "<span class=\"vbo-room-carat\">".$a['textimg']."</span>\n";
4174 }
4175 }
4176 } else {
4177 $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");
4178 }
4179 }
4180 $carat .= "</div>\n";
4181 }
4182 return $carat;
4183 }
4184
4185 public static function getRoomOptionals($idopts, $vbo_tn = null) {
4186 $split = explode(";", $idopts);
4187 $dbo = JFactory::getDbo();
4188 $arr = array();
4189 $fetch = array();
4190 foreach ($split as $s) {
4191 if (!empty($s)) {
4192 $fetch[] = $s;
4193 }
4194 }
4195 if (count($fetch) > 0) {
4196 $q = "SELECT * FROM `#__vikbooking_optionals` WHERE `id` IN (".implode(", ", $fetch).") ORDER BY `#__vikbooking_optionals`.`ordering` ASC;";
4197 $dbo->setQuery($q);
4198 $dbo->execute();
4199 if ($dbo->getNumRows() > 0) {
4200 $arr = $dbo->loadAssocList();
4201 if (is_object($vbo_tn)) {
4202 $vbo_tn->translateContents($arr, '#__vikbooking_optionals');
4203 }
4204 return $arr;
4205 }
4206 }
4207 return "";
4208 }
4209
4210 public static function getSingleOption($idopt, $vbo_tn = null) {
4211 $dbo = JFactory::getDbo();
4212 $opt = array();
4213 if (!empty($idopt)) {
4214 $q = "SELECT * FROM `#__vikbooking_optionals` WHERE `id`=" . (int)$idopt . ";";
4215 $dbo->setQuery($q);
4216 $dbo->execute();
4217 if ($dbo->getNumRows() == 1) {
4218 $opt = $dbo->loadAssoc();
4219 if (is_object($vbo_tn)) {
4220 $vbo_tn->translateContents($opt, '#__vikbooking_optionals');
4221 }
4222 }
4223 }
4224 return $opt;
4225 }
4226
4227 /**
4228 * Unsets the options that are not available in the requested dates.
4229 *
4230 * @param array $optionals the array of options passed by reference
4231 * @param int $checkin the timestamp of the check-in date and time
4232 * @param int $checkout the timestamp of the check-out date and time
4233 *
4234 * @return void
4235 *
4236 * @since 1.11
4237 */
4238 public static function filterOptionalsByDate(&$optionals, $checkin, $checkout) {
4239 if (!is_array($optionals) || !count($optionals) || empty($checkin) || empty($checkout)) {
4240 return;
4241 }
4242 foreach ($optionals as $k => $v) {
4243 if (!empty($v['alwaysav'])) {
4244 $dates = explode(';', $v['alwaysav']);
4245 if (empty($dates[0]) || empty($dates[1])) {
4246 continue;
4247 }
4248 // it is sufficient that the check-in is included within the validity dates, we ignore the checkout
4249 if (!($checkin >= $dates[0]) || !($checkin <= $dates[1])) {
4250 unset($optionals[$k]);
4251 }
4252 }
4253 }
4254 if (!count($optionals)) {
4255 // the system requires it to be an empty string
4256 $optionals = '';
4257 }
4258 }
4259
4260 /**
4261 * Unsets the options that are not suited for the requested room party.
4262 *
4263 * @param array $optionals the array of options passed by reference.
4264 * @param int $adults the number of adults in the room party.
4265 * @param int $children the number of children in the room party.
4266 *
4267 * @return void
4268 *
4269 * @since 1.13.5
4270 */
4271 public static function filterOptionalsByParty(&$optionals, $adults, $children) {
4272 if (!is_array($optionals) || !count($optionals) || (empty($adults) && empty($children))) {
4273 return;
4274 }
4275 foreach ($optionals as $k => $v) {
4276 if (empty($v['oparams'])) {
4277 continue;
4278 }
4279 $v['oparams'] = json_decode($v['oparams'], true);
4280 if (!is_array($v['oparams']) || !count($v['oparams']) || (empty($v['oparams']['minguestsnum']) && empty($v['oparams']['maxguestsnum']))) {
4281 continue;
4282 }
4283 if (!empty($v['oparams']['minguestsnum'])) {
4284 // filter by minimum adults/guests
4285 if ($v['oparams']['mingueststype'] == 'adults' && $adults <= $v['oparams']['minguestsnum']) {
4286 // minimum number of adults not sufficient, unset option
4287 unset($optionals[$k]);
4288 } elseif ($v['oparams']['mingueststype'] == 'guests' && ($adults + $children) <= $v['oparams']['minguestsnum']) {
4289 // minimum number of total guests not sufficient, unset option
4290 unset($optionals[$k]);
4291 }
4292 }
4293 if (!empty($v['oparams']['maxguestsnum'])) {
4294 // filter by maximum adults/guests
4295 if ($v['oparams']['maxgueststype'] == 'adults' && $adults >= $v['oparams']['maxguestsnum']) {
4296 // maximum number of adults exceeded, unset option
4297 unset($optionals[$k]);
4298 } elseif ($v['oparams']['maxgueststype'] == 'guests' && ($adults + $children) >= $v['oparams']['maxguestsnum']) {
4299 // maximum number of total guests exceeded, unset option
4300 unset($optionals[$k]);
4301 }
4302 }
4303 }
4304 if (!count($optionals)) {
4305 // the system requires it to be an empty string
4306 $optionals = '';
4307 }
4308 }
4309
4310 public static function getMandatoryTaxesFees($id_rooms, $num_adults, $num_nights) {
4311 $dbo = JFactory::getDbo();
4312 $taxes = 0;
4313 $fees = 0;
4314 $options_data = array();
4315 $id_options = array();
4316 $q = "SELECT `id`,`idopt` FROM `#__vikbooking_rooms` WHERE `id` IN (".implode(", ", $id_rooms).");";
4317 $dbo->setQuery($q);
4318 $dbo->execute();
4319 if ($dbo->getNumRows() > 0) {
4320 $assocs = $dbo->loadAssocList();
4321 foreach ($assocs as $opts) {
4322 if (!empty($opts['idopt'])) {
4323 $r_ido = explode(';', rtrim($opts['idopt']));
4324 foreach ($r_ido as $ido) {
4325 if (!empty($ido) && !in_array($ido, $id_options)) {
4326 $id_options[] = $ido;
4327 }
4328 }
4329 }
4330 }
4331 }
4332 if (count($id_options) > 0) {
4333 $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);";
4334 $dbo->setQuery($q);
4335 $dbo->execute();
4336 if ($dbo->getNumRows() > 0) {
4337 $alltaxesfees = $dbo->loadAssocList();
4338 foreach ($alltaxesfees as $tf) {
4339 $realcost = (intval($tf['perday']) == 1 ? ($tf['cost'] * $num_nights) : $tf['cost']);
4340 if (!empty($tf['maxprice']) && $tf['maxprice'] > 0 && $realcost > $tf['maxprice']) {
4341 $realcost = $tf['maxprice'];
4342 }
4343 $realcost = $tf['perperson'] == 1 ? ($realcost * $num_adults) : $realcost;
4344 $realcost = self::sayOptionalsPlusIva($realcost, $tf['idiva']);
4345 if ($tf['is_citytax'] == 1) {
4346 $taxes += $realcost;
4347 } elseif ($tf['is_fee'] == 1) {
4348 $fees += $realcost;
4349 }
4350 $optsett = explode('-', $tf['forceval']);
4351 $options_data[] = $tf['id'].':'.$optsett[0];
4352 }
4353 }
4354 }
4355 return array('city_taxes' => $taxes, 'fees' => $fees, 'options' => $options_data);
4356 }
4357
4358 /**
4359 * From a list of option/extras records, it returns an array with the options/extras
4360 * that do not support age intervals, and options for the children age intervals.
4361 * To be used as list($optionals, $ageintervals) = VikBooking::loadOptionAgeIntervals($optionals).
4362 *
4363 * @param array $optionals the full list of options/extras records.
4364 * @param int $adults the number of adults in the room requested.
4365 * @param int $children the number of children in the room requested.
4366 *
4367 * @return array the filtered regular and children age intervals options.
4368 */
4369 public static function loadOptionAgeIntervals($optionals, $adults = null, $children = null) {
4370 // container for age intervals is an empty string by default
4371 $ageintervals = '';
4372
4373 // regular options should start as an array
4374 if (!is_array($optionals)) {
4375 $optionals = array();
4376 }
4377
4378 // check for the first valid age intervals option
4379 foreach ($optionals as $kopt => $opt) {
4380 if (!empty($opt['ageintervals'])) {
4381 $intervals = explode(';;', $opt['ageintervals']);
4382 foreach($intervals as $intv) {
4383 if (empty($intv)) {
4384 continue;
4385 }
4386 $parts = explode('_', $intv);
4387 if (count($parts) >= 3) {
4388 // age intervals option found, get the first and only one
4389 $ageintervals = $optionals[$kopt];
4390 break 2;
4391 }
4392 }
4393 }
4394 }
4395
4396 if (is_array($ageintervals)) {
4397 /**
4398 * We allow price adjustments for a minimum total guests by overriding the costs
4399 * for each age interval for specific child numbers when the party covers some children.
4400 * For example, when children should pay only starting from the 5th guest (minguestsnum=4),
4401 * in case of a room party for 2 adults and 3 children, only the 3rd child should pay the
4402 * regular fees, while the 1st and second children should have all age intervals to 0.00.
4403 *
4404 * @since 1.13.5
4405 */
4406 $oparams = !empty($ageintervals['oparams']) ? json_decode($ageintervals['oparams'], true) : array();
4407 $oparams = !is_array($oparams) ? array() : $oparams;
4408 $adults = (int)$adults;
4409 $children = (int)$children;
4410 $valid_guesttype = (!empty($oparams['mingueststype']) && $oparams['mingueststype'] == 'guests');
4411 if ($children > 0 && !empty($oparams['minguestsnum']) && $valid_guesttype && ($adults + $children) > $oparams['minguestsnum']) {
4412 $children_to_pay = ($adults + $children) - $oparams['minguestsnum'];
4413 if ($children_to_pay < $children) {
4414 // compose string for free age intervals
4415 $intervals = explode(';;', $ageintervals['ageintervals']);
4416 foreach ($intervals as $k => $v) {
4417 $parts = explode('_', $v);
4418 // set cost to 0 (key = 2)
4419 $parts[2] = '0';
4420 $intervals[$k] = implode('_', $parts);
4421 }
4422 $free_ageintervals = implode(';;', $intervals);
4423 for ($i = 1; $i <= $children; $i++) {
4424 if (($children - $i + 1) <= $children_to_pay) {
4425 // costs for this Nth last child should be regular as this child should pay
4426 $ageintervals['ageintervals_child' . $i] = $ageintervals['ageintervals'];
4427 } else {
4428 // override costs to 0.00 for this Nth first child since it's covered by the min guests number
4429 $ageintervals['ageintervals_child' . $i] = $free_ageintervals;
4430 }
4431 }
4432 }
4433 }
4434 //
4435
4436 // remove age intervals from regular options
4437 foreach ($optionals as $kopt => $opt) {
4438 if ($opt['id'] == $ageintervals['id'] || !empty($opt['ageintervals'])) {
4439 // unset the option of type age intervals from the regular options
4440 unset($optionals[$kopt]);
4441 }
4442 }
4443 }
4444
4445 // when no more regular options, we return an empty string rather than an array
4446 if (!count($optionals)) {
4447 $optionals = '';
4448 }
4449
4450 // return the filtered list of regular options and age intervals options
4451 return array($optionals, $ageintervals);
4452 }
4453
4454 /**
4455 * Returns an array of overrides (if any) for the specific children Nth number in the party.
4456 * This is because some children in the party may not need to pay anything due to the min guests.
4457 *
4458 * @param array $optional the option full record to parse.
4459 * @param int $adults the number of adults in the room requested.
4460 * @param int $children the number of children in the room requested.
4461 *
4462 * @return array associative array with default age intervals and override strings.
4463 *
4464 * @since 1.13.5
4465 */
4466 public static function getOptionIntervalChildOverrides($optional, $adults, $children)
4467 {
4468 if (!is_array($optional)) {
4469 $optional = array();
4470 }
4471
4472 $overrides = array(
4473 'ageintervals' => (!empty($optional['ageintervals']) ? $optional['ageintervals'] : '')
4474 );
4475
4476 $oparams = !empty($optional['oparams']) ? json_decode($optional['oparams'], true) : array();
4477 $oparams = !is_array($oparams) ? array() : $oparams;
4478 $adults = (int)$adults;
4479 $children = (int)$children;
4480
4481 if (!count($oparams)) {
4482 return $overrides;
4483 }
4484
4485 $valid_guesttype = (!empty($oparams['mingueststype']) && $oparams['mingueststype'] == 'guests');
4486 if ($children > 0 && !empty($oparams['minguestsnum']) && $valid_guesttype && ($adults + $children) > $oparams['minguestsnum']) {
4487 $children_to_pay = ($adults + $children) - $oparams['minguestsnum'];
4488 if ($children_to_pay < $children) {
4489 // compose string for free age intervals
4490 $intervals = explode(';;', $optional['ageintervals']);
4491 foreach ($intervals as $k => $v) {
4492 $parts = explode('_', $v);
4493 // set cost to 0 (key = 2)
4494 $parts[2] = '0';
4495 $intervals[$k] = implode('_', $parts);
4496 }
4497 $free_ageintervals = implode(';;', $intervals);
4498 for ($i = 1; $i <= $children; $i++) {
4499 if (($children - $i + 1) <= $children_to_pay) {
4500 // costs for this Nth last child should be regular as this child should pay
4501 $overrides['ageintervals_child' . $i] = $optional['ageintervals'];
4502 } else {
4503 // override costs to 0.00 for this Nth first child since it's covered by the min guests number
4504 $overrides['ageintervals_child' . $i] = $free_ageintervals;
4505 }
4506 }
4507 }
4508 }
4509
4510 return $overrides;
4511 }
4512
4513 /**
4514 * Returns the number/index of the children being parsed given the full list of room options string,
4515 * the current key in the array of the split room options string, the ID of the ageintervals option,
4516 * and the total number of children. This is useful for later applying the cost overrides for the
4517 * children ages through the method getOptionIntervalChildOverrides().
4518 *
4519 * @param string $roptstr plain room option string of "#__vikbooking_ordersrooms".
4520 * @param int $optid the ID of the option of type age intervals to check.
4521 * @param int $roptkey the current position in the loop of the room options string.
4522 * @param int $children the number of children in the room requested.
4523 *
4524 * @return int the position/number of the children being parsed, -1 if not found.
4525 *
4526 * @since 1.13.5
4527 */
4528 public static function getRoomOptionChildNumber($roptstr, $optid, $roptkey, $children)
4529 {
4530 // default to -1 for not found
4531 $child_num = -1;
4532
4533 $valid_opt_counter = 0;
4534
4535 $roptions = explode(";", $roptstr);
4536 foreach ($roptions as $k => $opt) {
4537 if (empty($opt)) {
4538 continue;
4539 }
4540 $optvals = explode(":", $opt);
4541 /**
4542 * In some cases, like the "saveorder" task, the room option string may contain
4543 * the room number beside the option ID, separated with an underscore.
4544 * If an underscore is present, we need to split it to find the option ID.
4545 */
4546 if (strpos($optvals[0], '_') !== false) {
4547 // underscore found in room number portion, extract the option ID
4548 $rn_parts = explode('_', $optvals[0]);
4549 // 0th element is the room number in the party, 1st elem is the option ID
4550 $optvals[0] = $rn_parts[1];
4551 }
4552 //
4553 if ((int)$optvals[0] != (int)$optid) {
4554 // we are not interested into this option ID
4555 continue;
4556 }
4557 // increase counter for this option ID
4558 $valid_opt_counter++;
4559
4560 if ((int)$k == (int)$roptkey && $valid_opt_counter <= $children) {
4561 // children position found, we need to return it as a 0th base
4562 $child_num = ($valid_opt_counter - 1);
4563 break;
4564 }
4565 }
4566
4567 return $child_num;
4568 }
4569
4570 public static function getOptionIntervalsCosts($intvstr) {
4571 $optcosts = array();
4572 $intervals = explode(';;', $intvstr);
4573 foreach ($intervals as $kintv => $intv) {
4574 if (empty($intv)) continue;
4575 $parts = explode('_', $intv);
4576 if (count($parts) >= 3) {
4577 $optcosts[$kintv] = (float)$parts[2];
4578 }
4579 }
4580 return $optcosts;
4581 }
4582
4583 public static function getOptionIntervalsAges($intvstr) {
4584 $optages = array();
4585 $intervals = explode(';;', $intvstr);
4586 foreach ($intervals as $kintv => $intv) {
4587 if (empty($intv)) continue;
4588 $parts = explode('_', $intv);
4589 if (count($parts) >= 3) {
4590 $optages[$kintv] = $parts[0].' - '.$parts[1];
4591 }
4592 }
4593 return $optages;
4594 }
4595
4596 public static function getOptionIntervalsPercentage($intvstr) {
4597 /* returns an associative array to tell whether an interval has a percentage cost (VBO 1.8) */
4598 $optcostspcent = array();
4599 $intervals = explode(';;', $intvstr);
4600 foreach ($intervals as $kintv => $intv) {
4601 if (empty($intv)) continue;
4602 $parts = explode('_', $intv);
4603 if (count($parts) >= 3) {
4604 //fixed amount
4605 $setval = 0;
4606 if (array_key_exists(3, $parts) && strpos($parts[3], '%b') !== false) {
4607 //percentage value of the room base cost (VBO 1.10)
4608 $setval = 2;
4609 } elseif (array_key_exists(3, $parts) && strpos($parts[3], '%') !== false) {
4610 //percentage value of the adults tariff
4611 $setval = 1;
4612 }
4613 $optcostspcent[$kintv] = $setval;
4614 }
4615 }
4616 return $optcostspcent;
4617 }
4618
4619 public static function dayValidTs($days, $first, $second)
4620 {
4621 $secdiff = $second - $first;
4622 $daysdiff = $secdiff / 86400;
4623 if (is_int($daysdiff)) {
4624 if ($daysdiff < 1) {
4625 $daysdiff = 1;
4626 }
4627 } else {
4628 if ($daysdiff < 1) {
4629 $daysdiff = 1;
4630 } else {
4631 $sum = floor($daysdiff) * 86400;
4632 $newdiff = $secdiff - $sum;
4633 $maxhmore = self::getHoursMoreRb() * 3600;
4634 if ($maxhmore >= $newdiff) {
4635 $daysdiff = floor($daysdiff);
4636 } else {
4637 $daysdiff = ceil($daysdiff);
4638 }
4639 }
4640 }
4641
4642 return ($daysdiff == $days);
4643 }
4644
4645 /**
4646 * Calculates the stay fee inclusive of taxes.
4647 *
4648 * @param float $cost the cost to evaluate.
4649 * @param int $idprice the rate plan ID associated.
4650 *
4651 * @return float the given cost after tax, if taxes had to be applied.
4652 */
4653 public static function sayCostPlusIva($cost, $idprice)
4654 {
4655 $ivainclusa = self::ivaInclusa();
4656 if ($ivainclusa) {
4657 return $cost;
4658 }
4659
4660 static $tax_rplans_map = [];
4661
4662 if (array_key_exists($idprice, $tax_rplans_map)) {
4663 $tax_rate = $tax_rplans_map[$idprice];
4664 } else {
4665 $dbo = JFactory::getDbo();
4666 $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 . ";";
4667 $dbo->setQuery($q);
4668 $tax_rate = $dbo->loadAssoc();
4669
4670 // cache value
4671 $tax_rplans_map[$idprice] = $tax_rate;
4672 }
4673
4674 if (!$tax_rate || empty($tax_rate['aliq'])) {
4675 return $cost;
4676 }
4677
4678 $subt = 100 + $tax_rate['aliq'];
4679 $op = ($cost * $subt / 100);
4680
4681 /**
4682 * Tax Cap implementation for prices tax excluded (most common).
4683 *
4684 * @since 1.12
4685 */
4686 if ($tax_rate['taxcap'] > 0 && ($op - $cost) > $tax_rate['taxcap']) {
4687 $op = ($cost + $tax_rate['taxcap']);
4688 }
4689
4690 // apply rounding to avoid issues with multiple tax rates when tax excluded
4691 $formatvals = self::getNumberFormatData();
4692 $formatparts = explode(':', $formatvals);
4693 $rounded_op = round($op, (int)$formatparts[0]);
4694 if ($rounded_op > $op) {
4695 return $rounded_op;
4696 }
4697
4698 /**
4699 * When using base costs with decimals, and no tax rates assigned, maybe for a foreigners rate plan,
4700 * no rounding is ever made, and so we should always apply such rounding to avoid getting decimals if they should be 0.
4701 *
4702 * @since 1.11.1
4703 */
4704 return round($op, (int)$formatparts[0]);
4705 }
4706
4707 /**
4708 * Calculates the stay fee exclusive of taxes.
4709 *
4710 * @param float $cost the cost to evaluate.
4711 * @param int $idprice the rate plan ID associated.
4712 *
4713 * @return float the given cost before tax.
4714 */
4715 public static function sayCostMinusIva($cost, $idprice)
4716 {
4717 $ivainclusa = self::ivaInclusa();
4718 if (!$ivainclusa) {
4719 return $cost;
4720 }
4721
4722 static $tax_rplans_map = [];
4723
4724 if (array_key_exists($idprice, $tax_rplans_map)) {
4725 $tax_rate = $tax_rplans_map[$idprice];
4726 } else {
4727 $dbo = JFactory::getDbo();
4728 $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 . ";";
4729 $dbo->setQuery($q);
4730 $tax_rate = $dbo->loadAssoc();
4731
4732 // cache value
4733 $tax_rplans_map[$idprice] = $tax_rate;
4734 }
4735
4736 if (!$tax_rate || empty($tax_rate['aliq'])) {
4737 return $cost;
4738 }
4739
4740 $subt = 100 + $tax_rate['aliq'];
4741 $op = ($cost * 100 / $subt);
4742
4743 /**
4744 * Tax Cap implementation also when prices tax included.
4745 *
4746 * @since 1.12
4747 */
4748 if ($tax_rate['taxcap'] > 0 && ($cost - $op) > $tax_rate['taxcap']) {
4749 $op = ($cost - $tax_rate['taxcap']);
4750 }
4751
4752 // apply rounding to avoid issues with multiple tax rates when tax included
4753 $formatvals = self::getNumberFormatData();
4754 $formatparts = explode(':', $formatvals);
4755 $rounded_op = round($op, (int)$formatparts[0]);
4756 if ($rounded_op < $op) {
4757 return $rounded_op;
4758 }
4759
4760 /**
4761 * When using base costs with decimals, and no tax rates assigned, maybe for a foreigners rate plan,
4762 * no rounding is ever made, and so we should always apply such rounding to avoid getting decimals if they should be 0.
4763 *
4764 * @since 1.11.1
4765 */
4766 return round($op, (int)$formatparts[0]);
4767 }
4768
4769 /**
4770 * Given an option/extra cost, determines whether taxes should be applied over it.
4771 * Can also be used to calculate taxes on the extra costs per room in the bookings
4772 *
4773 * @param float $cost the cost to evaluate, option or extra service fee.
4774 * @param int $idiva the ID of the tax rate to use to apply taxes.
4775 * @param bool $force if true, taxes will always be applied.
4776 *
4777 * @return float the given option/extra cost plus tax, if taxes had to be applied.
4778 *
4779 * @since 1.15.0 (J) - 1.5.0 (WP) added third argument $force to comply with VCM.
4780 */
4781 public static function sayOptionalsPlusIva($cost, $idiva, $force = false)
4782 {
4783 $ivainclusa = self::ivaInclusa();
4784 if ($ivainclusa && $force !== true) {
4785 return $cost;
4786 }
4787
4788 $tax_rate = VBOTaxonomySummary::getTaxRateRecord($idiva);
4789 if (!$tax_rate) {
4790 return $cost;
4791 }
4792
4793 $subt = 100 + $tax_rate['aliq'];
4794 $op = ($cost * $subt / 100);
4795
4796 /**
4797 * Tax Cap implementation for prices tax excluded (most common).
4798 *
4799 * @since 1.12
4800 */
4801 if ($tax_rate['taxcap'] > 0 && ($op - $cost) > $tax_rate['taxcap']) {
4802 $op = ($cost + $tax_rate['taxcap']);
4803 }
4804
4805 // apply rounding to avoid issues with multiple tax rates when tax excluded
4806 $formatvals = self::getNumberFormatData();
4807 $formatparts = explode(':', $formatvals);
4808 $rounded_op = round($op, (int)$formatparts[0]);
4809 if ($rounded_op > $op) {
4810 return $rounded_op;
4811 }
4812
4813 return $op;
4814 }
4815
4816 /**
4817 * Calculates the net amount for an option/extra service.
4818 *
4819 * @param float $cost the cost to evaluate, option or extra service fee.
4820 * @param int $idiva the ID of the tax rate to use to apply taxes.
4821 *
4822 * @return float the given option/extra cost before tax, if taxes were inclusive.
4823 */
4824 public static function sayOptionalsMinusIva($cost, $idiva)
4825 {
4826 if (empty($idiva)) {
4827 return $cost;
4828 }
4829
4830 $ivainclusa = self::ivaInclusa();
4831 if (!$ivainclusa) {
4832 return $cost;
4833 }
4834
4835 $tax_rate = VBOTaxonomySummary::getTaxRateRecord($idiva);
4836 if (!$tax_rate) {
4837 return $cost;
4838 }
4839
4840 $subt = 100 + $tax_rate['aliq'];
4841 $op = ($cost * 100 / $subt);
4842
4843 /**
4844 * Tax Cap implementation also when prices tax included.
4845 *
4846 * @since 1.12
4847 */
4848 if ($tax_rate['taxcap'] > 0 && ($cost - $op) > $tax_rate['taxcap']) {
4849 $op = ($cost - $tax_rate['taxcap']);
4850 }
4851
4852 // apply rounding to avoid issues with multiple tax rates when tax included
4853 $formatvals = self::getNumberFormatData();
4854 $formatparts = explode(':', $formatvals);
4855 $rounded_op = round($op, (int)$formatparts[0]);
4856 if ($rounded_op < $op) {
4857 return $rounded_op;
4858 }
4859
4860 return $op;
4861 }
4862
4863 /**
4864 * Given a cost, determines whether taxes should be applied over it.
4865 *
4866 * @param float $cost the cost to evaluate, could be a room custom price.
4867 * @param int $idiva the ID of the tax rate to use to apply taxes.
4868 * @param bool $force if true, taxes will always be applied.
4869 *
4870 * @return float the given cost plus tax, if taxes had to be applied.
4871 *
4872 * @since 1.15.0 (J) - 1.5.0 (WP) added third argument $force to comply with VCM.
4873 */
4874 public static function sayPackagePlusIva($cost, $idiva, $force = false)
4875 {
4876 $ivainclusa = self::ivaInclusa();
4877 if ($ivainclusa && $force !== true) {
4878 return $cost;
4879 }
4880
4881 $tax_rate = VBOTaxonomySummary::getTaxRateRecord($idiva);
4882 if (!$tax_rate) {
4883 return $cost;
4884 }
4885
4886 $subt = 100 + $tax_rate['aliq'];
4887 $op = ($cost * $subt / 100);
4888
4889 /**
4890 * Tax Cap implementation for prices tax excluded (most common).
4891 *
4892 * @since 1.12
4893 */
4894 if ($tax_rate['taxcap'] > 0 && ($op - $cost) > $tax_rate['taxcap']) {
4895 $op = ($cost + $tax_rate['taxcap']);
4896 }
4897
4898 // apply rounding to avoid issues with multiple tax rates when tax excluded
4899 $formatvals = self::getNumberFormatData();
4900 $formatparts = explode(':', $formatvals);
4901 $rounded_op = round($op, (int)$formatparts[0]);
4902 if ($rounded_op > $op) {
4903 return $rounded_op;
4904 }
4905
4906 return $op;
4907 }
4908
4909 /**
4910 * Calculates the net amount for a package.
4911 *
4912 * @param float $cost the cost to evaluate, option or extra service fee.
4913 * @param int $idiva the ID of the tax rate to use to apply taxes.
4914 * @param bool $force_invoice_excltax whether to force the deduction of taxes.
4915 *
4916 * @return float the given cost before tax.
4917 */
4918 public static function sayPackageMinusIva($cost, $idiva, $force_invoice_excltax = false)
4919 {
4920 $ivainclusa = self::ivaInclusa();
4921 if (!$ivainclusa && $force_invoice_excltax !== true) {
4922 return $cost;
4923 }
4924
4925 $tax_rate = VBOTaxonomySummary::getTaxRateRecord($idiva);
4926 if (!$tax_rate) {
4927 return $cost;
4928 }
4929
4930 $subt = 100 + $tax_rate['aliq'];
4931 $op = ($cost * 100 / $subt);
4932
4933 /**
4934 * Tax Cap implementation also when prices tax included.
4935 *
4936 * @since 1.12
4937 */
4938 if ($tax_rate['taxcap'] > 0 && ($cost - $op) > $tax_rate['taxcap']) {
4939 $op = ($cost - $tax_rate['taxcap']);
4940 }
4941
4942 // apply rounding to avoid issues with multiple tax rates when tax included
4943 $formatvals = self::getNumberFormatData();
4944 $formatparts = explode(':', $formatvals);
4945 $rounded_op = round($op, (int)$formatparts[0]);
4946 if ($rounded_op < $op) {
4947 return $rounded_op;
4948 }
4949
4950 return $op;
4951 }
4952
4953 public static function getSecretLink()
4954 {
4955 $dbo = JFactory::getDbo();
4956
4957 $sid = mt_rand();
4958
4959 $q = "SELECT `sid` FROM `#__vikbooking_orders`;";
4960 $dbo->setQuery($q);
4961 $all = $dbo->loadAssocList();
4962 if ($all) {
4963 $list = [];
4964 foreach ($all as $s) {
4965 $list[] = $s['sid'];
4966 }
4967 if (in_array($sid, $list)) {
4968 while(in_array($sid, $list)) {
4969 $sid++;
4970 }
4971 }
4972 }
4973
4974 return $sid;
4975 }
4976
4977 /**
4978 * Generates a confirmation number for the given booking.
4979 * Modified the way random string-numbers are obtained to
4980 * avoid any possible brute-force attempt.
4981 *
4982 * @since 1.15.1 (J) - 1.5.4 (WP)
4983 */
4984 public static function generateConfirmNumber($oid, $update = true)
4985 {
4986 $dbo = JFactory::getDbo();
4987
4988 $confirmnumb = date('y') . $oid;
4989 for ($i = 0; $i < 8; $i++) {
4990 $confirmnumb .= rand(0, 9);
4991 }
4992
4993 if ($update) {
4994 $q = "UPDATE `#__vikbooking_orders` SET `confirmnumber`=" . $dbo->quote($confirmnumb) . " WHERE `id`=" . (int)$oid . ";";
4995 $dbo->setQuery($q);
4996 $dbo->execute();
4997 }
4998
4999 return $confirmnumb;
5000 }
5001
5002 public static function buildCustData($arr, $sep) {
5003 $cdata = "";
5004 foreach ($arr as $k => $e) {
5005 if (strlen($e)) {
5006 $cdata .= (strlen($k) > 0 ? $k . ": " : "") . $e . $sep;
5007 }
5008 }
5009 return $cdata;
5010 }
5011
5012 /**
5013 * This method parses the Joomla menu object to see if a menu item of a
5014 * specific type is available, to get its ID. Useful when links should be
5015 * displayed in pages where there is no Itemid set (booking details pages).
5016 *
5017 * @param array $viewtypes list of accepted menu items
5018 * @param string $lang the optional language to use.
5019 *
5020 * @return int
5021 *
5022 * @since 1.15.6 (J) - 1.5.12 (WP) added second argument $lang.
5023 */
5024 public static function findProperItemIdType($viewtypes, $lang = null)
5025 {
5026 if (VBOPlatformDetection::isWordPress()) {
5027 $model = JModel::getInstance('vikbooking', 'shortcodes', 'admin');
5028
5029 $itemid = $model->best($viewtypes, $lang);
5030
5031 if ($itemid) {
5032 return $itemid;
5033 }
5034
5035 return 0;
5036 }
5037
5038 $bestitemid = 0;
5039
5040 $current_lang = !empty($lang) ? $lang : JFactory::getLanguage()->getTag();
5041
5042 $app = JFactory::getApplication();
5043
5044 $menu = $app->getMenu('site');
5045
5046 if (!$menu) {
5047 return 0;
5048 }
5049
5050 $menu_items = $menu->getMenu();
5051
5052 if (!$menu_items) {
5053 return 0;
5054 }
5055
5056 foreach ($menu_items as $itemid => $item) {
5057 if (isset($item->query['option']) && $item->query['option'] == 'com_vikbooking' && in_array($item->query['view'], $viewtypes)) {
5058 // proper menu item type found
5059 $bestitemid = empty($bestitemid) ? $itemid : $bestitemid;
5060
5061 if (isset($item->language) && $item->language == $current_lang) {
5062 // we found the exact menu item type for the given language
5063 return $itemid;
5064 }
5065 }
5066 }
5067
5068 return $bestitemid;
5069 }
5070
5071 /**
5072 * Rewrites an internal URI that needs to be used outside of the website.
5073 * This means that the routed URI MUST start with the base path of the site.
5074 *
5075 * @param mixed $query The query string or an associative array of data.
5076 * @param boolean $xhtml Replace & by &amp; for XML compliance.
5077 * @param mixed $itemid The itemid to use. If null, the current one will be used.
5078 *
5079 * @return string The complete routed URI.
5080 *
5081 * @since 1.15.0 (J) - 1.5.0 (WP) adopted use of VBOPlatformUriAware, which also supports
5082 * routing from back-end (if available on the CMS version).
5083 */
5084 public static function externalroute($query = '', $xhtml = true, $itemid = null)
5085 {
5086 return VBOFactory::getPlatform()->getUri()->route($query, $xhtml, $itemid);
5087 }
5088
5089 /**
5090 * Generates an iCal file to be attached to the email message for the
5091 * customer or the administrator with some basic booking details.
5092 *
5093 * @param string $recip either admin or customer.
5094 * @param array $booking the booking array or some keys.
5095 *
5096 * @return mixed string in case of success, false otherwise.
5097 *
5098 * @since 1.12.0
5099 */
5100 public static function getEmailIcal($recip, $booking) {
5101 // load configuration setting
5102 $attachical = self::attachIcal();
5103
5104 if ($attachical === 0) {
5105 // do not attach any iCal file
5106 return false;
5107 }
5108
5109 if ($attachical === 2 && strpos($recip, 'admin') === false) {
5110 // skip the iCal for the admin
5111 return false;
5112 }
5113
5114 if ($attachical === 3 && strpos($recip, 'admin') !== false) {
5115 // skip the iCal for the customer
5116 return false;
5117 }
5118
5119 if (strpos($recip, 'admin') !== false) {
5120 // prepare event description and summary for the admin
5121 $description = $booking['custdata'];
5122 $summary = !empty($booking['subject']) ? $booking['subject'] : '';
5123 $fname = $booking['ts'] . '.ics';
5124 } else {
5125 // event description and summary for the customer
5126 $description = '';
5127 $summary = self::getFrontTitle();
5128 $fname = 'reservation_reminder.ics';
5129 }
5130
5131 // prepare iCal head
5132 $company_name = self::getFrontTitle();
5133 $ics_str = "BEGIN:VCALENDAR\r\n" .
5134 "PRODID:-//".$company_name."//".JUri::root()." 1.0//EN\r\n" .
5135 "CALSCALE:GREGORIAN\r\n" .
5136 "VERSION:2.0\r\n";
5137 // compose iCal body
5138 $ics_str .= 'BEGIN:VEVENT'."\r\n";
5139 $ics_str .= 'DTEND;VALUE=DATE:'.date('Ymd', $booking['checkout'])."\r\n";
5140 $ics_str .= 'DTSTART;VALUE=DATE:'.date('Ymd', $booking['checkin'])."\r\n";
5141 $ics_str .= 'UID:'.sha1($booking['ts'])."\r\n";
5142 $ics_str .= 'DESCRIPTION:'.preg_replace('/([\,;])/','\\\$1', $description)."\r\n";
5143 $ics_str .= 'SUMMARY:'.preg_replace('/([\,;])/','\\\$1', $summary)."\r\n";
5144 $ics_str .= 'LOCATION:'.preg_replace('/([\,;])/','\\\$1', $company_name)."\r\n";
5145 $ics_str .= 'END:VEVENT'."\r\n";
5146 // close iCal file content
5147 $ics_str .= "END:VCALENDAR";
5148
5149 /**
5150 * Trigger event to allow third party plugins to overwrite the iCal file.
5151 *
5152 * @since 1.16.0 (J) - 1.6.0 (WP)
5153 */
5154 VBOFactory::getPlatform()->getDispatcher()->trigger('onBeforeCreateMailIcalVikBooking', [$recip, $booking, &$ics_str]);
5155
5156 if (empty($ics_str)) {
5157 return false;
5158 }
5159
5160 // store the event onto a .ics file. We use the resources folder in back-end.
5161 $fpath = VBO_ADMIN_PATH . DIRECTORY_SEPARATOR . 'resources' . DIRECTORY_SEPARATOR . $fname;
5162 $fp = fopen($fpath, 'w+');
5163 $bytes = fwrite($fp, $ics_str);
5164 fclose($fp);
5165
5166 return $bytes ? $fpath : false;
5167 }
5168
5169 public static function loadEmailTemplate($booking_info = array()) {
5170 define('_VIKBOOKINGEXEC', '1');
5171 ob_start();
5172 include VBO_SITE_PATH . DIRECTORY_SEPARATOR . "helpers" . DIRECTORY_SEPARATOR . "email_tmpl.php";
5173 $content = ob_get_contents();
5174 ob_end_clean();
5175 return $content;
5176 }
5177
5178 /**
5179 * Parses the raw HTML content of the booking email template.
5180 *
5181 * @param string $tmpl the raw content of the template.
5182 * @param mixed $bid int for the booking ID or booking array.
5183 * @param array $rooms list of rooms booked and translated.
5184 * @param array $rates list of translated rates for the booked rooms.
5185 * @param array $options list of translated options booked.
5186 * @param float [$total] the booking total amount (in case it has changed).
5187 * @param string [$link] the booking link can be passed for the no-deposit.
5188 *
5189 * @return string the HTML content of the parsed email template.
5190 *
5191 * @since 1.13 with different arguments.
5192 */
5193 public static function parseEmailTemplate($tmpl, $bid, $rooms, $rates, $options, $total = 0, $link = null)
5194 {
5195 $app = JFactory::getApplication();
5196 $dbo = JFactory::getDbo();
5197 $vbo_tn = self::getTranslator();
5198
5199 // availability helper
5200 $av_helper = self::getAvailabilityInstance();
5201
5202 // get necessary values
5203 if (is_array($bid)) {
5204 // we got the full booking record
5205 $order_info = $bid;
5206 $bid = $order_info['id'];
5207 } else {
5208 $order_info = array();
5209 $q = "SELECT * FROM `#__vikbooking_orders` WHERE `id`=" . (int)$bid . ";";
5210 $dbo->setQuery($q);
5211 $order_info = $dbo->loadAssoc();
5212 if (!$order_info) {
5213 throw new Exception('Booking not found', 404);
5214 }
5215 }
5216
5217 $tars_info = array();
5218 $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'] . ";";
5219 $dbo->setQuery($q);
5220 $tars_info = $dbo->loadAssocList();
5221 if (!$tars_info) {
5222 throw new Exception('No rooms found', 404);
5223 }
5224
5225 /**
5226 * Split stay reservation.
5227 *
5228 * @since 1.16.0 (J) - 1.6.0 (WP)
5229 */
5230 $room_stay_dates = [];
5231 if ($order_info['split_stay']) {
5232 if ($order_info['status'] == 'confirmed') {
5233 $room_stay_dates = $av_helper->loadSplitStayBusyRecords($order_info['id']);
5234 } else {
5235 $room_stay_dates = VBOFactory::getConfig()->getArray('split_stay_' . $order_info['id'], []);
5236 }
5237 // immediately count the number of nights of stay for each split room
5238 foreach ($room_stay_dates as $sps_r_k => $sps_r_v) {
5239 if (!empty($sps_r_v['checkin_ts']) && !empty($sps_r_v['checkout_ts'])) {
5240 // overwrite values for compatibility with non-confirmed bookings
5241 $sps_r_v['checkin'] = $sps_r_v['checkin_ts'];
5242 $sps_r_v['checkout'] = $sps_r_v['checkout_ts'];
5243 }
5244 $sps_r_v['nights'] = $av_helper->countNightsOfStay($sps_r_v['checkin'], $sps_r_v['checkout']);
5245 // overwrite the whole array
5246 $room_stay_dates[$sps_r_k] = $sps_r_v;
5247 }
5248 }
5249
5250 // check if the language in use is the same as the one used during the checkout
5251 $lang = JFactory::getLanguage();
5252 if (!empty($order_info['lang'])) {
5253 if ($lang->getTag() != $order_info['lang']) {
5254 $lang->load('com_vikbooking', (defined('VIKBOOKING_LANG') ? VIKBOOKING_LANG : JPATH_SITE), $order_info['lang'], true);
5255 if (defined('_JEXEC') && !defined('ABSPATH')) {
5256 $lang->load('joomla', JPATH_SITE, $order_info['lang'], true);
5257 }
5258 }
5259 if ($vbo_tn->getDefaultLang() != $order_info['lang']) {
5260 // force the translation to start because contents should be translated
5261 $vbo_tn::$force_tolang = $order_info['lang'];
5262 }
5263 }
5264
5265 // values for replacements
5266 $company_name = self::getFrontTitle();
5267 $currencyname = self::getCurrencyName();
5268 $sitelogo = self::getSiteLogo();
5269 $footermess = self::getFooterOrdMail($vbo_tn);
5270 $dateformat = self::getDateFormat();
5271 $datesep = self::getDateSeparator();
5272 if ($dateformat == "%d/%m/%Y") {
5273 $df = 'd/m/Y';
5274 } elseif ($dateformat == "%m/%d/%Y") {
5275 $df = 'm/d/Y';
5276 } else {
5277 $df = 'Y/m/d';
5278 }
5279 $create_date = date(str_replace("/", $datesep, $df) . ' H:i', $order_info['ts']);
5280 $checkin_date = date(str_replace("/", $datesep, $df) . ' H:i', $order_info['checkin']);
5281 $checkout_date = date(str_replace("/", $datesep, $df) . ' H:i', $order_info['checkout']);
5282 $customer_info = nl2br($order_info['custdata']);
5283 $company_logo = '';
5284 if (!empty($sitelogo) && is_file(VBO_ADMIN_PATH . DIRECTORY_SEPARATOR . 'resources'. DIRECTORY_SEPARATOR . $sitelogo)) {
5285 $company_logo = '<img src="' . VBO_ADMIN_URI . 'resources/' . $sitelogo . '" alt="' . $company_name . '" />';
5286 }
5287 if ($order_info['status'] == 'cancelled') {
5288 $confirmnumber = '';
5289 $status_str = JText::translate('VBCANCELLED');
5290 } elseif ($order_info['status'] == 'standby') {
5291 $confirmnumber = '';
5292 $status_str = JText::translate('VBWAITINGFORPAYMENT');
5293 if (!empty($order_info['type']) && !strcasecmp($order_info['type'], 'Inquiry')) {
5294 // this is an inquiry reservation
5295 $status_str = JText::translate('VBO_INQUIRY_PENDING');
5296 }
5297 } else {
5298 $confirmnumber = $order_info['confirmnumber'];
5299 $status_str = JText::translate('VBCOMPLETED');
5300 }
5301 // booking total amount
5302 $total = $total === 0 ? (float)$order_info['total'] : (float)$total;
5303 // booking link
5304 $use_sid = !empty($order_info['idorderota']) && !empty($order_info['channel']) ? $order_info['idorderota'] : $order_info['sid'];
5305 if (is_null($link)) {
5306 $lang_link = !empty($order_info['lang']) ? "&lang={$order_info['lang']}" : '';
5307 $link = self::externalroute("index.php?option=com_vikbooking&view=booking&sid={$use_sid}&ts={$order_info['ts']}{$lang_link}", false);
5308 }
5309
5310 // raw HTML content
5311 $parsed = $tmpl;
5312
5313 /**
5314 * Parse all conditional text rules.
5315 *
5316 * @since 1.14 (J) - 1.4.0 (WP)
5317 */
5318 self::getConditionalRulesInstance()
5319 ->set(array('booking', 'rooms'), array($order_info, $rooms))
5320 ->parseTokens($parsed);
5321 //
5322
5323 // special tokens (tags) replacement
5324 $parsed = str_replace("{logo}", $company_logo, $parsed);
5325 $parsed = str_replace("{company_name}", $company_name, $parsed);
5326 $parsed = str_replace("{order_id}", $order_info['id'], $parsed);
5327 $statusclass = $order_info['status'] == 'confirmed' ? "confirmed" : "standby";
5328 $statusclass = $order_info['status'] == 'cancelled' ? "cancelled" : $statusclass;
5329 $parsed = str_replace("{order_status_class}", $statusclass, $parsed);
5330 $parsed = str_replace("{order_status}", $status_str, $parsed);
5331 $parsed = str_replace("{order_date}", $create_date, $parsed);
5332
5333 // PIN Code
5334 if ($order_info['status'] == 'confirmed' && self::customersPinEnabled()) {
5335 $cpin = self::getCPinIstance();
5336 $customer_pin = $cpin->getPinCodeByOrderId($order_info['id']);
5337 if (!empty($customer_pin)) {
5338 $customer_info .= '<h3>'.JText::translate('VBYOURPIN').': '.$customer_pin.'</h3>';
5339 }
5340 }
5341
5342 $parsed = str_replace("{customer_info}", $customer_info, $parsed);
5343 // Confirmation Number
5344 if (strlen($confirmnumber) > 0) {
5345 $parsed = str_replace("{confirmnumb}", $confirmnumber, $parsed);
5346 } else {
5347 $parsed = preg_replace('#('.preg_quote('{confirmnumb_delimiter}').')(.*)('.preg_quote('{/confirmnumb_delimiter}').')#si', '$1'.' '.'$3', $parsed);
5348 }
5349 $parsed = str_replace("{confirmnumb_delimiter}", "", $parsed);
5350 $parsed = str_replace("{/confirmnumb_delimiter}", "", $parsed);
5351
5352 $roomsnum = count($rooms);
5353 $parsed = str_replace("{rooms_count}", $roomsnum, $parsed);
5354 $roomstr = "";
5355 // Rooms Distinctive Features
5356 preg_match_all('/\{roomfeature ([a-zA-Z0-9 ]+)\}/U', $parsed, $matches);
5357 //
5358 foreach ($rooms as $num => $r) {
5359 $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/>";
5360 // Rooms Distinctive Features
5361 if (is_array($matches[1]) && count($matches[1])) {
5362 $distinctive_features = array();
5363 $rparams = (array)json_decode($r['params'], true);
5364 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'])) {
5365 $distinctive_features = $rparams['features'][$r['roomindex']];
5366 }
5367 $docheck = (count($distinctive_features) > 0);
5368 foreach($matches[1] as $reqf) {
5369 $feature_found = false;
5370 if ($docheck) {
5371 foreach ($distinctive_features as $dfk => $dfv) {
5372 if (stripos($dfk, $reqf) !== false) {
5373 $feature_found = $dfk;
5374 if (strlen(trim($dfk)) == strlen(trim($reqf))) {
5375 break;
5376 }
5377 }
5378 }
5379 }
5380 if ($feature_found !== false && strlen($distinctive_features[$feature_found]) > 0) {
5381 $roomstr .= JText::translate($feature_found).': '.$distinctive_features[$feature_found].'<br/>';
5382 }
5383 $parsed = str_replace("{roomfeature ".$reqf."}", "", $parsed);
5384 }
5385 }
5386 }
5387
5388 // custom fields replace
5389 preg_match_all('/\{customfield ([0-9]+)\}/U', $parsed, $cmatches);
5390 if (is_array($cmatches[1]) && count($cmatches[1])) {
5391 $cfids = array();
5392 foreach ($cmatches[1] as $cfid ) {
5393 $cfids[] = $cfid;
5394 }
5395 $q = "SELECT * FROM `#__vikbooking_custfields` WHERE `id` IN (".implode(", ", $cfids).");";
5396 $dbo->setQuery($q);
5397 $dbo->execute();
5398 $cfields = $dbo->getNumRows() ? $dbo->loadAssocList() : array();
5399 $vbo_tn->translateContents($cfields, '#__vikbooking_custfields');
5400 $cfmap = array();
5401 foreach ($cfields as $cf) {
5402 $cfmap[trim(JText::translate($cf['name']))] = $cf['id'];
5403 }
5404 $cfmapreplace = array();
5405 $partsreceived = explode("\n", $order_info['custdata']);
5406 if (count($partsreceived) > 0) {
5407 foreach($partsreceived as $pst) {
5408 if (!empty($pst)) {
5409 $tmpdata = explode(":", $pst);
5410 if (array_key_exists(trim($tmpdata[0]), $cfmap)) {
5411 $cfmapreplace[$cfmap[trim($tmpdata[0])]] = trim($tmpdata[1]);
5412 }
5413 }
5414 }
5415 }
5416 foreach ($cmatches[1] as $cfid ) {
5417 if (array_key_exists($cfid, $cfmapreplace)) {
5418 $parsed = str_replace("{customfield ".$cfid."}", $cfmapreplace[$cfid], $parsed);
5419 } else {
5420 $parsed = str_replace("{customfield ".$cfid."}", "", $parsed);
5421 }
5422 }
5423 }
5424
5425 $parsed = str_replace("{rooms_info}", $roomstr, $parsed);
5426 $parsed = str_replace("{checkin_date}", $checkin_date, $parsed);
5427 $parsed = str_replace("{checkout_date}", $checkout_date, $parsed);
5428
5429 // order details
5430 $orderdetails = "";
5431 $room_loop_ind = isset($rooms[0]) ? 0 : 1;
5432 foreach ($rooms as $num => $r) {
5433 $room_id = !empty($r['idroom']) ? $r['idroom'] : $r['id'];
5434 $use_room_ind = $num - $room_loop_ind;
5435 $split_stay_str = '';
5436 $split_stay_info = [];
5437 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) {
5438 $split_stay_info[] = $room_stay_dates[$use_room_ind]['nights'] . ' ' . ($room_stay_dates[$use_room_ind]['nights'] > 1 ? JText::translate('VBDAYS') : JText::translate('VBDAY'));
5439 $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']);
5440 $split_stay_str .= '<br/>' . implode(', ', $split_stay_info);
5441 }
5442
5443 $expdet = explode("\n", isset($rates[$num]) ? $rates[$num] : '-----');
5444 $faredets = explode(":", $expdet[0]);
5445 $orderdetails .= '<div class="roombooked"><strong>' . $r['name'] . '</strong>' . $split_stay_str . '<br/>' . $faredets[0];
5446 if (!empty($expdet[1])) {
5447 $attrfaredets = explode(":", $expdet[1]);
5448 if (strlen($attrfaredets[1]) > 0) {
5449 $orderdetails .= ' - '.$attrfaredets[0].':'.$attrfaredets[1];
5450 }
5451 }
5452 $fareprice = isset($faredets[1]) ? trim(str_replace($currencyname, "", $faredets[1])) : 0;
5453 $orderdetails .= '<div class="service-amount" style="float: right;"><span>'.$currencyname.' '.self::numberFormat($fareprice).'</span></div></div>';
5454 // options
5455 if (isset($options[$num]) && is_array($options[$num]) && count($options[$num]) > 0) {
5456 foreach ($options[$num] as $oo) {
5457 $expopts = explode("\n", $oo);
5458 foreach($expopts as $optinfo) {
5459 if (!empty($optinfo)) {
5460 $splitopt = explode(":", $optinfo);
5461 $optprice = trim(str_replace($currencyname, "", $splitopt[1]));
5462 $orderdetails .= '<div class="roomoption"><span>'.$splitopt[0].'</span><div class="service-amount" style="float: right;"><span>'.$currencyname.' '.self::numberFormat($optprice).'</span></div></div>';
5463 }
5464 }
5465 }
5466 }
5467 //
5468 if ($roomsnum > 1 && $num < $roomsnum) {
5469 $orderdetails .= '<br/>';
5470 }
5471 }
5472 //
5473 // coupon
5474 if (!empty($order_info['coupon'])) {
5475 $expcoupon = explode(";", $order_info['coupon']);
5476 $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>';
5477 }
5478 //
5479 // discount payment method
5480 if ($order_info['status'] != 'cancelled') {
5481 $idpayment = $order_info['idpayment'];
5482 if (!empty($idpayment)) {
5483 $exppay = explode('=', $idpayment);
5484 $payment = self::getPayment($exppay[0], $vbo_tn);
5485 if (is_array($payment)) {
5486 if ($payment['charge'] > 0.00 && $payment['ch_disc'] != 1) {
5487 // Discount (not charge)
5488 if ($payment['val_pcent'] == 1) {
5489 // fixed value
5490 $total -= $payment['charge'];
5491 $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>';
5492 } else {
5493 // percent value
5494 $percent_disc = $total * $payment['charge'] / 100;
5495 $total -= $percent_disc;
5496 $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>';
5497 }
5498 }
5499 }
5500 }
5501 }
5502 //
5503 $parsed = str_replace("{order_details}", $orderdetails, $parsed);
5504 //
5505 $parsed = str_replace("{order_total}", $currencyname.' '.self::numberFormat($total), $parsed);
5506 $parsed = str_replace("{footer_emailtext}", $footermess, $parsed);
5507 $parsed = str_replace("{order_link}", '<a href="'.$link.'">'.$link.'</a>', $parsed);
5508 $parsed = str_replace("{booking_link}", $link, $parsed);
5509
5510 // deposit
5511 $deposit_str = '';
5512 if (!in_array($order_info['status'], array('confirmed', 'cancelled')) && !self::payTotal() && self::allowDepositFromRates($tars_info)) {
5513 $percentdeposit = self::getAccPerCent();
5514 $percentdeposit = self::calcDepositOverride($percentdeposit, $order_info['days']);
5515 if ($percentdeposit > 0 && self::depositAllowedDaysAdv($order_info['checkin'])) {
5516 if (self::getTypeDeposit() == "fixed") {
5517 $deposit_amount = $percentdeposit;
5518 } else {
5519 $deposit_amount = $total * $percentdeposit / 100;
5520 }
5521 if ($deposit_amount > 0) {
5522 $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>';
5523 }
5524 }
5525 }
5526 $parsed = str_replace("{order_deposit}", $deposit_str, $parsed);
5527 //
5528 // Amount Paid - Remaining Balance - Refunded Amount - Cancellation Fee
5529 $totpaid_str = '';
5530 if ($order_info['refund'] > 0) {
5531 $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>';
5532 }
5533 if ($order_info['status'] != 'cancelled') {
5534 $tot_paid = $order_info['totpaid'];
5535 $diff_topay = (float)$total - (float)$tot_paid;
5536 if ((float)$tot_paid > 0) {
5537 $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>';
5538 // only in case the remaining balance is greater than 1 to avoid commissions issues
5539 if ($diff_topay > 1) {
5540 $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>';
5541 }
5542 }
5543 }
5544 if ($order_info['status'] == 'cancelled' && isset($order_info['canc_fee']) && $order_info['canc_fee'] > 0) {
5545 $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>';
5546 }
5547 $parsed = str_replace("{order_total_paid}", $totpaid_str, $parsed);
5548
5549 /**
5550 * Language direction (LTR or RTL).
5551 *
5552 * @since 1.16.4 (J) - 1.6.4 (WP)
5553 */
5554 $lang_direction = 'ltr';
5555 if (!empty($order_info['lang'])) {
5556 // attempt to define the language direction and replace the special tag
5557 try {
5558 $lang_direction = JLanguage::getInstance($order_info['lang'])->isRtl() ? 'rtl' : 'ltr';
5559 } catch (Throwable $e) {
5560 // do nothing, default to ltr
5561 $lang_direction = 'ltr';
5562 }
5563 // static map for Arabic and Hebrew
5564 $lang_direction = !strcasecmp($order_info['lang'], 'ar') || !strcasecmp($order_info['lang'], 'he') ? 'rtl' : $lang_direction;
5565 }
5566 $parsed = str_replace("{lang_direction}", $lang_direction, $parsed);
5567 $parsed = str_replace("{text_natural_direction}", ($lang_direction == 'ltr' ? 'left' : 'right'), $parsed);
5568 $parsed = str_replace("{text_opposite_direction}", ($lang_direction == 'ltr' ? 'right' : 'left'), $parsed);
5569
5570 /**
5571 * Replace static floating styles depending on language direction (LTR or RTL).
5572 * For BC with older email template sources, we need to keep the inline style
5573 * for the floating of certain elements, which looks good only on LTR.
5574 *
5575 * @since 1.16.4 (J) - 1.6.4 (WP)
5576 */
5577 if ($lang_direction === 'rtl') {
5578 // get rid of inline static style declaration because the template uses CSS classes
5579 $parsed = str_replace("float: right;", '', $parsed);
5580 }
5581
5582 return $parsed;
5583 }
5584
5585 /**
5586 * New method for sending booking email messages
5587 * to the guest or to the administrator(s).
5588 *
5589 * @param int $bid the booking ID.
5590 * @param array $for guest, admin or a custom email address.
5591 * @param bool $send whether to send or return the HTML message.
5592 * @param bool $no_config if true, no configuration settings will be used for the status.
5593 *
5594 * @return mixed True or False depending on the result or HTML string for the preview.
5595 *
5596 * @since 1.13 (J) - 1.3 (WP)
5597 * @since 1.16.3 (J) - 1.6.3 (WP) added fourth argument $no_config to send the email even if
5598 * the reservation status is pending and against the configuration.
5599 */
5600 public static function sendBookingEmail($bid, $for = array(), $send = true, $no_config = false)
5601 {
5602 $dbo = JFactory::getDbo();
5603 $app = JFactory::getApplication();
5604
5605 $vbo_tn = self::getTranslator();
5606 $av_helper = self::getAvailabilityInstance();
5607
5608 $q = "SELECT * FROM `#__vikbooking_orders` WHERE `id`=" . (int)$bid . ";";
5609 $dbo->setQuery($q);
5610 $booking = $dbo->loadAssoc();
5611 if (!$booking) {
5612 return false;
5613 }
5614
5615 $result = false;
5616
5617 // check if the language in use is the same as the one used during the checkout
5618 $lang = JFactory::getLanguage();
5619 if (!empty($booking['lang'])) {
5620 if ($lang->getTag() != $booking['lang']) {
5621 $lang->load('com_vikbooking', (defined('VIKBOOKING_LANG') ? VIKBOOKING_LANG : JPATH_SITE), $booking['lang'], true);
5622 if (defined('_JEXEC') && !defined('ABSPATH')) {
5623 $lang->load('joomla', JPATH_SITE, $booking['lang'], true);
5624 }
5625 }
5626 if ($vbo_tn->getDefaultLang() != $booking['lang']) {
5627 // force the translation to start because contents should be translated
5628 $vbo_tn::$force_tolang = $booking['lang'];
5629 }
5630 }
5631
5632 // load rooms booked
5633 $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;";
5634 $dbo->setQuery($q);
5635 $ordersrooms = $dbo->loadAssocList();
5636 if (!$ordersrooms) {
5637 return false;
5638 }
5639 $vbo_tn->translateContents($ordersrooms, '#__vikbooking_rooms', array('id' => 'r_reference_id'));
5640
5641 /**
5642 * Split stay reservation.
5643 *
5644 * @since 1.16.0 (J) - 1.6.0 (WP)
5645 */
5646 $room_stay_dates = [];
5647 if ($booking['split_stay']) {
5648 if ($booking['status'] == 'confirmed') {
5649 $room_stay_dates = $av_helper->loadSplitStayBusyRecords($booking['id']);
5650 } else {
5651 $room_stay_dates = VBOFactory::getConfig()->getArray('split_stay_' . $booking['id'], []);
5652 }
5653 // immediately count the number of nights of stay for each split room
5654 foreach ($room_stay_dates as $sps_r_k => $sps_r_v) {
5655 if (!empty($sps_r_v['checkin_ts']) && !empty($sps_r_v['checkout_ts'])) {
5656 // overwrite values for compatibility with non-confirmed bookings
5657 $sps_r_v['checkin'] = $sps_r_v['checkin_ts'];
5658 $sps_r_v['checkout'] = $sps_r_v['checkout_ts'];
5659 }
5660 $sps_r_v['nights'] = $av_helper->countNightsOfStay($sps_r_v['checkin'], $sps_r_v['checkout']);
5661 // overwrite the whole array
5662 $room_stay_dates[$sps_r_k] = $sps_r_v;
5663 }
5664 }
5665
5666 $rooms = array();
5667 $tars = array();
5668 $is_package = !empty($booking['pkg']) ? true : false;
5669 $ftitle = self::getFrontTitle();
5670 $currencyname = self::getCurrencyName();
5671 foreach ($ordersrooms as $kor => $or) {
5672 $num = $kor + 1;
5673 $rooms[$num] = $or;
5674 if ($is_package === true || (!empty($or['cust_cost']) && $or['cust_cost'] > 0.00)) {
5675 // package or custom cost set from the back-end
5676 continue;
5677 }
5678
5679 // determine the days to consider for the count of the availability
5680 $room_nights = $booking['days'];
5681 $room_checkin = $booking['checkin'];
5682 $room_checkout = $booking['checkout'];
5683 if ($booking['split_stay'] && count($room_stay_dates) && isset($room_stay_dates[$kor]) && $room_stay_dates[$kor]['idroom'] == $or['idroom']) {
5684 $room_nights = $room_stay_dates[$kor]['nights'];
5685 $room_checkin = $room_stay_dates[$kor]['checkin'];
5686 $room_checkout = $room_stay_dates[$kor]['checkout'];
5687 }
5688
5689 $q = "SELECT * FROM `#__vikbooking_dispcost` WHERE `id`=" . (int)$or['idtar'] . ";";
5690 $dbo->setQuery($q);
5691 $tar = $dbo->loadAssocList();
5692 if (!$tar) {
5693 // tariff not found
5694 if (self::isAdmin()) {
5695 VikError::raiseWarning('', JText::translate('VBERRNOFAREFOUND'));
5696 }
5697 continue;
5698 }
5699
5700 // apply seasonal rates
5701 $tar = self::applySeasonsRoom($tar, $room_checkin, $room_checkout);
5702
5703 // different usage
5704 if ($or['fromadult'] <= $or['adults'] && $or['toadult'] >= $or['adults']) {
5705 $diffusageprice = self::loadAdultsDiff($or['idroom'], $or['adults']);
5706 // Occupancy Override
5707 $occ_ovr = self::occupancyOverrideExists($tar, $or['adults']);
5708 $diffusageprice = $occ_ovr !== false ? $occ_ovr : $diffusageprice;
5709 //
5710 if (is_array($diffusageprice)) {
5711 // set a charge or discount to the price(s) for the different usage of the room
5712 foreach ($tar as $kpr => $vpr) {
5713 $tar[$kpr]['diffusage'] = $or['adults'];
5714 if ($diffusageprice['chdisc'] == 1) {
5715 // charge
5716 if ($diffusageprice['valpcent'] == 1) {
5717 // fixed value
5718 $tar[$kpr]['diffusagecostpernight'] = $diffusageprice['pernight'] == 1 ? 1 : 0;
5719 $aduseval = $diffusageprice['pernight'] == 1 ? $diffusageprice['value'] * $tar[$kpr]['days'] : $diffusageprice['value'];
5720 $tar[$kpr]['diffusagecost'] = "+".$aduseval;
5721 $tar[$kpr]['room_base_cost'] = $vpr['cost'];
5722 $tar[$kpr]['cost'] = $vpr['cost'] + $aduseval;
5723 } else {
5724 // percentage value
5725 $tar[$kpr]['diffusagecostpernight'] = $diffusageprice['pernight'] == 1 ? $vpr['cost'] : 0;
5726 $aduseval = $diffusageprice['pernight'] == 1 ? round(($vpr['cost'] * $diffusageprice['value'] / 100) * $tar[$kpr]['days'] + $vpr['cost'], 2) : round(($vpr['cost'] * (100 + $diffusageprice['value']) / 100), 2);
5727 $tar[$kpr]['diffusagecost'] = "+".$diffusageprice['value']."%";
5728 $tar[$kpr]['room_base_cost'] = $vpr['cost'];
5729 $tar[$kpr]['cost'] = $aduseval;
5730 }
5731 } else {
5732 // discount
5733 if ($diffusageprice['valpcent'] == 1) {
5734 // fixed value
5735 $tar[$kpr]['diffusagecostpernight'] = $diffusageprice['pernight'] == 1 ? 1 : 0;
5736 $aduseval = $diffusageprice['pernight'] == 1 ? $diffusageprice['value'] * $tar[$kpr]['days'] : $diffusageprice['value'];
5737 $tar[$kpr]['diffusagecost'] = "-".$aduseval;
5738 $tar[$kpr]['room_base_cost'] = $vpr['cost'];
5739 $tar[$kpr]['cost'] = $vpr['cost'] - $aduseval;
5740 } else {
5741 // percentage value
5742 $tar[$kpr]['diffusagecostpernight'] = $diffusageprice['pernight'] == 1 ? $vpr['cost'] : 0;
5743 $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);
5744 $tar[$kpr]['diffusagecost'] = "-".$diffusageprice['value']."%";
5745 $tar[$kpr]['room_base_cost'] = $vpr['cost'];
5746 $tar[$kpr]['cost'] = $aduseval;
5747 }
5748 }
5749 }
5750 }
5751 }
5752 //
5753 $tars[$num] = $tar[0];
5754 }
5755
5756 $secdiff = $booking['checkout'] - $booking['checkin'];
5757 $daysdiff = $secdiff / 86400;
5758 if (is_int($daysdiff)) {
5759 if ($daysdiff < 1) {
5760 $daysdiff = 1;
5761 }
5762 } else {
5763 if ($daysdiff < 1) {
5764 $daysdiff = 1;
5765 } else {
5766 $sum = floor($daysdiff) * 86400;
5767 $newdiff = $secdiff - $sum;
5768 $maxhmore = self::getHoursMoreRb() * 3600;
5769 if ($maxhmore >= $newdiff) {
5770 $daysdiff = floor($daysdiff);
5771 } else {
5772 $daysdiff = ceil($daysdiff);
5773 }
5774 }
5775 }
5776
5777 $isdue = 0;
5778 $pricestr = array();
5779 $optstr = array();
5780 foreach ($ordersrooms as $kor => $or) {
5781 $num = $kor + 1;
5782
5783 // determine the days to consider for the count of the availability
5784 $room_nights = $booking['days'];
5785 $room_checkin = $booking['checkin'];
5786 $room_checkout = $booking['checkout'];
5787 if ($booking['split_stay'] && count($room_stay_dates) && isset($room_stay_dates[$kor]) && $room_stay_dates[$kor]['idroom'] == $or['idroom']) {
5788 $room_nights = $room_stay_dates[$kor]['nights'];
5789 $room_checkin = $room_stay_dates[$kor]['checkin'];
5790 $room_checkout = $room_stay_dates[$kor]['checkout'];
5791 }
5792
5793 if ($is_package === true || (!empty($or['cust_cost']) && $or['cust_cost'] > 0.00)) {
5794 // package cost or cust_cost may not be inclusive of taxes if prices tax included is off
5795 $calctar = self::sayPackagePlusIva($or['cust_cost'], $or['cust_idiva']);
5796 $isdue += $calctar;
5797 $pricestr[$num] = (!empty($or['pkg_name']) ? $or['pkg_name'] : (!empty($or['otarplan']) ? ucwords($or['otarplan']) : JText::translate('VBOROOMCUSTRATEPLAN'))).": ".$calctar." ".$currencyname;
5798 } elseif (array_key_exists($num, $tars) && is_array($tars[$num])) {
5799 $display_rate = !empty($or['room_cost']) ? $or['room_cost'] : $tars[$num]['cost'];
5800 $calctar = self::sayCostPlusIva($display_rate, $tars[$num]['idprice']);
5801 $tars[$num]['calctar'] = $calctar;
5802 $isdue += $calctar;
5803 $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'] : "");
5804 }
5805
5806 if (!empty($or['optionals'])) {
5807 $stepo = explode(";", $or['optionals']);
5808 foreach ($stepo as $roptkey => $oo) {
5809 if (empty($oo)) {
5810 continue;
5811 }
5812 $stept = explode(":", $oo);
5813 $q = "SELECT * FROM `#__vikbooking_optionals` WHERE `id`=" . $dbo->quote($stept[0]) . ";";
5814 $dbo->setQuery($q);
5815 $dbo->execute();
5816 if ($dbo->getNumRows() == 1) {
5817 $actopt = $dbo->loadAssocList();
5818 $vbo_tn->translateContents($actopt, '#__vikbooking_optionals', array(), array(), (!empty($booking['lang']) ? $booking['lang'] : null));
5819 $chvar = '';
5820 if (!empty($actopt[0]['ageintervals']) && $or['children'] > 0 && strstr($stept[1], '-') != false) {
5821 $optagenames = self::getOptionIntervalsAges($actopt[0]['ageintervals']);
5822 $optagepcent = self::getOptionIntervalsPercentage($actopt[0]['ageintervals']);
5823 $optageovrct = self::getOptionIntervalChildOverrides($actopt[0], $or['adults'], $or['children']);
5824 $child_num = self::getRoomOptionChildNumber($or['optionals'], $actopt[0]['id'], $roptkey, $or['children']);
5825 $optagecosts = self::getOptionIntervalsCosts(isset($optageovrct['ageintervals_child' . ($child_num + 1)]) ? $optageovrct['ageintervals_child' . ($child_num + 1)] : $actopt[0]['ageintervals']);
5826 $agestept = explode('-', $stept[1]);
5827 $stept[1] = $agestept[0];
5828 $chvar = $agestept[1];
5829 if (array_key_exists(($chvar - 1), $optagepcent) && $optagepcent[($chvar - 1)] == 1) {
5830 // percentage value of the adults tariff
5831 if ($is_package === true || (!empty($or['cust_cost']) && $or['cust_cost'] > 0.00)) {
5832 $optagecosts[($chvar - 1)] = $or['cust_cost'] * $optagecosts[($chvar - 1)] / 100;
5833 } else {
5834 $display_rate = !empty($or['room_cost']) ? $or['room_cost'] : $tars[$num]['cost'];
5835 $optagecosts[($chvar - 1)] = $display_rate * $optagecosts[($chvar - 1)] / 100;
5836 }
5837 } elseif (array_key_exists(($chvar - 1), $optagepcent) && $optagepcent[($chvar - 1)] == 2) {
5838 // VBO 1.10 - percentage value of room base cost
5839 if ($is_package === true || (!empty($or['cust_cost']) && $or['cust_cost'] > 0.00)) {
5840 $optagecosts[($chvar - 1)] = $or['cust_cost'] * $optagecosts[($chvar - 1)] / 100;
5841 } else {
5842 $display_rate = isset($tars[$num]['room_base_cost']) ? $tars[$num]['room_base_cost'] : (!empty($or['room_cost']) ? $or['room_cost'] : $tars[$num]['cost']);
5843 $optagecosts[($chvar - 1)] = $display_rate * $optagecosts[($chvar - 1)] / 100;
5844 }
5845 }
5846 $actopt[0]['chageintv'] = $chvar;
5847 $actopt[0]['name'] .= ' ('.$optagenames[($chvar - 1)].')';
5848 $actopt[0]['quan'] = $stept[1];
5849 $realcost = (intval($actopt[0]['perday']) == 1 ? (floatval($optagecosts[($chvar - 1)]) * $room_nights * $stept[1]) : (floatval($optagecosts[($chvar - 1)]) * $stept[1]));
5850 } else {
5851 $actopt[0]['quan'] = $stept[1];
5852 // VBO 1.11 - options percentage cost of the room total fee
5853 if ($is_package === true || (!empty($or['cust_cost']) && $or['cust_cost'] > 0.00)) {
5854 $deftar_basecosts = $or['cust_cost'];
5855 } else {
5856 $deftar_basecosts = !empty($or['room_cost']) ? $or['room_cost'] : $tars[$num]['cost'];
5857 }
5858 $actopt[0]['cost'] = (int)$actopt[0]['pcentroom'] ? ($deftar_basecosts * $actopt[0]['cost'] / 100) : $actopt[0]['cost'];
5859 //
5860 $realcost = (intval($actopt[0]['perday']) == 1 ? ($actopt[0]['cost'] * $room_nights * $stept[1]) : ($actopt[0]['cost'] * $stept[1]));
5861 }
5862 if (!empty($actopt[0]['maxprice']) && $actopt[0]['maxprice'] > 0 && $realcost > $actopt[0]['maxprice']) {
5863 $realcost = $actopt[0]['maxprice'];
5864 if (intval($actopt[0]['hmany']) == 1 && intval($stept[1]) > 1) {
5865 $realcost = $actopt[0]['maxprice'] * $stept[1];
5866 }
5867 }
5868 if ($actopt[0]['perperson'] == 1) {
5869 $realcost = $realcost * $or['adults'];
5870 }
5871 $tmpopr = self::sayOptionalsPlusIva($realcost, $actopt[0]['idiva']);
5872 $isdue += $tmpopr;
5873 $optstr[$num][] = ($stept[1] > 1 ? $stept[1] . " " : "") . $actopt[0]['name'] . ": " . $tmpopr . " " . $currencyname . "\n";
5874 }
5875 }
5876 }
5877
5878 // custom extra costs
5879 if (!empty($or['extracosts'])) {
5880 $cur_extra_costs = json_decode($or['extracosts'], true);
5881 foreach ($cur_extra_costs as $eck => $ecv) {
5882 $ecplustax = !empty($ecv['idtax']) ? self::sayOptionalsPlusIva($ecv['cost'], $ecv['idtax']) : $ecv['cost'];
5883 $isdue += $ecplustax;
5884 $optstr[$num][] = $ecv['name'] . ": " . $ecplustax . " " . $currencyname."\n";
5885 }
5886 }
5887 }
5888
5889 // force the original total amount if rates have changed
5890 if (number_format($isdue, 2) != number_format($booking['total'], 2)) {
5891 $isdue = $booking['total'];
5892 }
5893
5894 // mail subject
5895 $subject = JText::sprintf('VBOMAILSUBJECT', strip_tags($ftitle));
5896 // $subject = '=?UTF-8?B?' . base64_encode($subject) . '?=';
5897
5898 // inject the recipient of the message for the template
5899 $booking['for'] = $for;
5900
5901 // load template file that will get $booking as variable
5902 $tmpl = self::loadEmailTemplate($booking);
5903
5904 // parse email template
5905 $hmess = self::parseEmailTemplate($tmpl, $booking, $rooms, $pricestr, $optstr, $isdue);
5906 $hmess = '<html>'."\n".'<head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"></head>'."\n".'<body>'.$hmess.'</body>'."\n".'</html>';
5907
5908 if ($send !== true) {
5909 // return the content of the email message parsed
5910 return $hmess;
5911 }
5912
5913 // when the message can be sent
5914 $sendwhen = self::getSendEmailWhen();
5915
5916 if ($no_config) {
5917 // force the configuration setting to send the message no matter what's the booking status
5918 $sendwhen = 1;
5919 }
5920
5921 // send the message
5922 foreach ($for as $who) {
5923 $use_subject = $subject;
5924 $recipients = array();
5925 $attachments = self::addEmailAttachment(null);
5926 $attach_ical = false;
5927 $force_replyto = null;
5928 if (strpos($who, '@') !== false) {
5929 // send email to custom email address
5930 array_push($recipients, trim($who));
5931 } elseif (stripos($who, 'guest') !== false || stripos($who, 'customer') !== false) {
5932 // send email to the customer
5933 if ($sendwhen > 1 && $booking['status'] == 'standby') {
5934 continue;
5935 }
5936 array_push($recipients, $booking['custmail']);
5937 /**
5938 * Check whether an iCal should be attached for the customer.
5939 *
5940 * @since 1.12.0
5941 */
5942 $attach_ical = self::getEmailIcal('customer', $booking);
5943 } elseif (stripos($who, 'admin') !== false) {
5944 // send email to the administrator(s)
5945 if ($sendwhen > 1 && $booking['status'] == 'standby') {
5946 continue;
5947 }
5948 $use_subject = $subject . ' #' . $booking['id'];
5949 $adminemail = self::getAdminMail();
5950 $extra_admin_recipients = self::addAdminEmailRecipient(null);
5951 if (empty($adminemail) && empty($extra_admin_recipients)) {
5952 // Prevent Joomla Exceptions that would stop the script execution
5953 VikError::raiseWarning('', 'The administrator email address is empty. Email message could not be sent.');
5954 continue;
5955 }
5956 if (strpos($adminemail, ',') !== false) {
5957 // multiple addresses
5958 $adminemails = explode(',', $adminemail);
5959 foreach ($adminemails as $am) {
5960 if (strpos($am, '@') !== false) {
5961 array_push($recipients, trim($am));
5962 }
5963 }
5964 } else {
5965 // single address
5966 array_push($recipients, trim($adminemail));
5967 }
5968
5969 // merge extra recipients
5970 $recipients = array_merge($recipients, $extra_admin_recipients);
5971
5972 // admin should reply to the customer
5973 $force_replyto = !empty($booking['custmail']) ? $booking['custmail'] : $force_replyto;
5974
5975 /**
5976 * Check whether an iCal should be attached for the admin.
5977 *
5978 * @since 1.2.0
5979 */
5980 $attach_ical = self::getEmailIcal('admin', array(
5981 'ts' => $booking['ts'],
5982 'custdata' => $booking['custdata'],
5983 'checkin' => $booking['checkin'],
5984 'checkout' => $booking['checkout'],
5985 'subject' => JText::sprintf('VBNEWORDER', $booking['id']),
5986 ));
5987 }
5988
5989 // send the message, recipients should always be an array to support multiple admin addresses
5990
5991 // get sender e-mail
5992 $adsendermail = VBOFactory::getConfig()->get('senderemail');
5993
5994 // init mail data
5995 $mail = new VBOMailWrapper([
5996 'sender' => [$adsendermail, $ftitle],
5997 'recipient' => $recipients,
5998 'bcc' => self::addAdminEmailRecipient(null, true),
5999 'reply' => !empty($force_replyto) ? $force_replyto : $adsendermail,
6000 'subject' => $use_subject,
6001 'content' => $hmess,
6002 'attachments' => $attachments,
6003 ]);
6004
6005 if ($attach_ical !== false && $booking['status'] != 'standby') {
6006 // attach iCal file
6007 $mail->addAttachment($attach_ical);
6008 }
6009
6010 /**
6011 * Trigger event to allow third party plugins to overwrite any aspect of the mail message.
6012 *
6013 * @see VBOMailWrapper is the $mail object and its setter methods can modify the mail data.
6014 *
6015 * @since 1.15.4 (J) - 1.5.10 (WP)
6016 */
6017 VBOFactory::getPlatform()->getDispatcher()->trigger('onBeforeSendBookingMailVikBooking', [$who, $booking, $mail]);
6018
6019 // send e-mail
6020 $result = VBOFactory::getPlatform()->getMailer()->send($mail) || $result;
6021
6022 // unlink iCal file
6023 if ($attach_ical !== false) {
6024 @unlink($attach_ical);
6025 }
6026 }
6027
6028 return $result;
6029 }
6030
6031 /**
6032 * This method allows to add one or more recipient email
6033 * addresses for the next queue of email sending for the admin.
6034 * This method can be used in the template file for the customer
6035 * email to register an additional email address, maybe when a
6036 * specific room-type is booked.
6037 * The methods sending the email messages are supposed to call this
6038 * method by passing no arguments to obtain the extra addresses set.
6039 *
6040 * @param mixed $email null, string or array of email address(es).
6041 * @param bool $bcc if true, addresses will be used as bcc.
6042 * @param bool $reset if true, the queues will be emptied.
6043 *
6044 * @return array the current extra recipients or bcc addresses set.
6045 *
6046 * @since 1.13 (J) - 1.3.0 (WP)
6047 * @since 1.14 (J) - 1.4.0 (WP) added argument $bcc.
6048 * @since 1.16 (J) - 1.6.0 (WP) added argument $reset.
6049 */
6050 public static function addAdminEmailRecipient($email, $bcc = false, $reset = false)
6051 {
6052 static $extra_recipients = [];
6053 static $extra_bcc = [];
6054
6055 if ($reset) {
6056 $extra_recipients = [];
6057 $extra_bcc = [];
6058 }
6059
6060 if (!empty($email)) {
6061 if (is_scalar($email)) {
6062 if ($bcc) {
6063 array_push($extra_bcc, $email);
6064 } else {
6065 array_push($extra_recipients, $email);
6066 }
6067 } else {
6068 if ($bcc) {
6069 $extra_bcc = array_merge($extra_bcc, $email);
6070 } else {
6071 $extra_recipients = array_merge($extra_recipients, $email);
6072 }
6073 }
6074 }
6075
6076 return $bcc ? array_unique($extra_bcc) : array_unique($extra_recipients);
6077 }
6078
6079 /**
6080 * This method serves to add one or more attachments to the
6081 * next queue of email sending for the admin.
6082 * The methods sending the email messages are supposed to call this
6083 * method by passing a null argument to obtain the attachments set.
6084 *
6085 * @param mixed $file null or string with path to file to attach.
6086 * @param bool $reset if true, the queue will be emptied.
6087 *
6088 * @return array the current attachments set.
6089 *
6090 * @since 1.14
6091 * @since 1.16 (J) - 1.6.0 (WP) added argument $reset.
6092 */
6093 public static function addEmailAttachment($file, $reset = false)
6094 {
6095 static $extra_attachments = [];
6096
6097 if ($reset) {
6098 $extra_attachments = [];
6099 }
6100
6101 if (!empty($file)) {
6102 if (is_scalar($file)) {
6103 array_push($extra_attachments, $file);
6104 } else {
6105 $extra_attachments = array_merge($extra_attachments, $file);
6106 }
6107 }
6108
6109 return array_unique($extra_attachments);
6110 }
6111
6112 /**
6113 * This method is called whenever some rooms get booked.
6114 * It checks whether the rooms involved have shared calendars.
6115 * If some are found, then also such rooms will be occupied.
6116 *
6117 * @param int $bid the booking ID.
6118 * @param array $roomids the list of rooms booked.
6119 * @param int $checkin checkin timestamp.
6120 * @param int $checkout checkout timestamp.
6121 *
6122 * @return boolean true if some other cals were occupied, false otherwise.
6123 *
6124 * @since 1.13 (J) - 1.3.0 (WP)
6125 *
6126 * @see SynchVikBooking::getRoomsSharedCalsInvolved() in VCM that checks if this method exists.
6127 */
6128 public static function updateSharedCalendars($bid, $roomids = array(), $checkin = 0, $checkout = 0)
6129 {
6130 $dbo = JFactory::getDbo();
6131 $bid = (int)$bid;
6132
6133 // availability helper
6134 $av_helper = self::getAvailabilityInstance();
6135
6136 // check for split stay rooms reservation
6137 $known_split_stay = false;
6138 $is_split_stay = 0;
6139
6140 if (empty($checkin) || empty($checkout)) {
6141 // get checkin and checkout timestamps from booking
6142 $q = "SELECT `checkin`, `checkout`, `split_stay` FROM `#__vikbooking_orders` WHERE `id`={$bid};";
6143 $dbo->setQuery($q);
6144 $dbo->execute();
6145 if (!$dbo->getNumRows()) {
6146 // booking not found
6147 return false;
6148 }
6149 $bdata = $dbo->loadAssoc();
6150 $checkin = $bdata['checkin'];
6151 $checkout = $bdata['checkout'];
6152 $is_split_stay = $bdata['split_stay'];
6153 // turn flag on
6154 $known_split_stay = true;
6155 }
6156
6157 if (!$known_split_stay) {
6158 // we need to query the db to access this information
6159 $q = "SELECT `split_stay` FROM `#__vikbooking_orders` WHERE `id`={$bid}";
6160 $dbo->setQuery($q, 0, 1);
6161 $dbo->execute();
6162 if ($dbo->getNumRows()) {
6163 $is_split_stay = (int)$dbo->loadResult();
6164 }
6165 // turn flag on
6166 $known_split_stay = true;
6167 }
6168
6169 // check split stay room records
6170 $room_stay_dates = [];
6171 if ($is_split_stay) {
6172 $room_stay_dates = $av_helper->loadSplitStayBusyRecords($bid);
6173 // immediately count the number of nights of stay for each split room
6174 foreach ($room_stay_dates as $sps_r_k => $sps_r_v) {
6175 if (!empty($sps_r_v['checkin_ts']) && !empty($sps_r_v['checkout_ts'])) {
6176 // overwrite values for compatibility with non-confirmed bookings
6177 $sps_r_v['checkin'] = $sps_r_v['checkin_ts'];
6178 $sps_r_v['checkout'] = $sps_r_v['checkout_ts'];
6179 }
6180 $sps_r_v['nights'] = $av_helper->countNightsOfStay($sps_r_v['checkin'], $sps_r_v['checkout']);
6181 // overwrite the whole array
6182 $room_stay_dates[$sps_r_k] = $sps_r_v;
6183 }
6184 }
6185
6186 // get the rooms booked
6187 if (!count($roomids)) {
6188 // get the IDs of all rooms booked
6189 $q = "SELECT `idroom` FROM `#__vikbooking_ordersrooms` WHERE `idorder`={$bid};";
6190 $dbo->setQuery($q);
6191 $orr = $dbo->loadAssocList();
6192 if ($orr) {
6193 foreach ($orr as $or) {
6194 array_push($roomids, $or['idroom']);
6195 }
6196 }
6197 }
6198 if (!count($roomids) || empty($bid)) {
6199 // unable to proceed
6200 return false;
6201 }
6202 $roomids = array_unique($roomids);
6203
6204 // build room stay dates map in case of split stay
6205 $room_split_stay_map = [];
6206 if ($is_split_stay && !empty($room_stay_dates) && count($room_stay_dates) == count($roomids) && count($roomids) > 1) {
6207 // we have unique room IDs in a split stay reservation for more than one room
6208 foreach ($room_stay_dates as $room_stay_date) {
6209 $room_split_stay_map[$room_stay_date['idroom']] = $room_stay_date;
6210 }
6211 }
6212
6213 // get rooms involved
6214 $involved = [];
6215 $involved_stay_map = [];
6216 $q = "SELECT * FROM `#__vikbooking_calendars_xref` WHERE `mainroom` IN (" . implode(', ', $roomids) . ") OR `childroom` IN (" . implode(', ', $roomids) . ");";
6217 $dbo->setQuery($q);
6218 $rooms_found = $dbo->loadAssocList();
6219 if (!$rooms_found) {
6220 // no rooms involved that need their calendars updated
6221 return false;
6222 }
6223 foreach ($rooms_found as $rf) {
6224 if (!in_array($rf['mainroom'], $roomids)) {
6225 // push room ID
6226 array_push($involved, $rf['mainroom']);
6227 // check for specific stay dates in connected room
6228 if (!empty($rf['childroom']) && isset($room_split_stay_map[$rf['childroom']])) {
6229 // split stay information available
6230 $involved_stay_map[$rf['mainroom']] = $room_split_stay_map[$rf['childroom']];
6231 }
6232 }
6233 if (!in_array($rf['childroom'], $roomids)) {
6234 // push room ID
6235 array_push($involved, $rf['childroom']);
6236 // check for specific stay dates in connected room
6237 if (!empty($rf['mainroom']) && isset($room_split_stay_map[$rf['mainroom']])) {
6238 // split stay information available
6239 $involved_stay_map[$rf['childroom']] = $room_split_stay_map[$rf['mainroom']];
6240 }
6241 }
6242 }
6243 $involved = array_unique($involved);
6244 if (!count($involved)) {
6245 // no rooms involved
6246 return false;
6247 }
6248
6249 // turnover seconds
6250 $turnover_secs = VikBooking::getHoursRoomAvail() * 3600;
6251
6252 // occupy the calendars for the involved rooms found
6253 $bids_generated = [];
6254 foreach ($involved as $rid) {
6255 // determine the values to use
6256 $room_checkin = (int)$checkin;
6257 $room_checkout = (int)$checkout;
6258 $room_realback = ($checkout + $turnover_secs);
6259 if (isset($involved_stay_map[$rid])) {
6260 // split stay dates available
6261 $room_checkin = (int)$involved_stay_map[$rid]['checkin'];
6262 $room_checkout = (int)$involved_stay_map[$rid]['checkout'];
6263 $room_realback = ($room_checkout + $turnover_secs);
6264 }
6265
6266 // occupy record in room with shared calendar
6267 $q = "INSERT INTO `#__vikbooking_busy` (`idroom`,`checkin`,`checkout`,`realback`,`sharedcal`) VALUES(" . (int)$rid . ", " . $room_checkin . ", " . $room_checkout . ", " . $room_realback . ", 1);";
6268 $dbo->setQuery($q);
6269 $dbo->execute();
6270 array_push($bids_generated, $dbo->insertid());
6271 }
6272
6273 // store busy relations created
6274 foreach ($bids_generated as $busyid) {
6275 $q = "INSERT INTO `#__vikbooking_ordersbusy` (`idorder`,`idbusy`) VALUES(" . $bid . ", " . (int)$busyid . ");";
6276 $dbo->setQuery($q);
6277 $dbo->execute();
6278 }
6279
6280 return true;
6281 }
6282
6283 /**
6284 * This method is needed whenever a booking gets modified by
6285 * adding or removing rooms. This way we reset (remove) all
6286 * busy records that were previously stored due to a shared
6287 * calendar. The correct relations should then be re-created
6288 * by calling updateSharedCalendars().
6289 *
6290 * @param int $bid the booking ID.
6291 *
6292 * @return boolean true if some records were cleaned.
6293 *
6294 * @see updateSharedCalendars() should be called after.
6295 *
6296 * @since 1.3.0
6297 */
6298 public static function cleanSharedCalendarsBusy($bid) {
6299 $dbo = JFactory::getDbo();
6300 $bid = (int)$bid;
6301 // get all the occupied records due to shared calendars for this booking
6302 $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;";
6303 $dbo->setQuery($q);
6304 $allbusy = $dbo->loadAssocList();
6305 if (!$allbusy) {
6306 return false;
6307 }
6308
6309 $busy_ids = array();
6310 foreach ($allbusy as $b) {
6311 // push busy ID to be removed later
6312 array_push($busy_ids, $b['id']);
6313 // delete the current busy ID-booking ID relation for this shared calendar
6314 $q = "DELETE FROM `#__vikbooking_ordersbusy` WHERE `idorder`={$bid} AND `idbusy`={$b['id']};";
6315 $dbo->setQuery($q);
6316 $dbo->execute();
6317 }
6318 if (count($busy_ids)) {
6319 // delete all busy records due to shared calendars
6320 $q = "DELETE FROM `#__vikbooking_busy` WHERE `id` IN (" . implode(', ', $busy_ids) . ");";
6321 $dbo->setQuery($q);
6322 $dbo->execute();
6323 }
6324
6325 return true;
6326 }
6327
6328 public static function sendJutility() {
6329 //deprecated in VBO 1.10
6330 $dbo = JFactory::getDbo();
6331 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='sendjutility';";
6332 $dbo->setQuery($q);
6333 $dbo->execute();
6334 $s = $dbo->loadAssocList();
6335 return (intval($s[0]['setting']) == 1 ? true : false);
6336 }
6337
6338 public static function getCategoryName($idcat, $vbo_tn = null) {
6339 $dbo = JFactory::getDbo();
6340 $q = "SELECT `id`,`name` FROM `#__vikbooking_categories` WHERE `id`=" . $dbo->quote($idcat) . ";";
6341 $dbo->setQuery($q);
6342 $dbo->execute();
6343 $p = $dbo->getNumRows() > 0 ? $dbo->loadAssocList() : array();
6344 if (is_object($vbo_tn) && count($p) > 0) {
6345 $vbo_tn->translateContents($p, '#__vikbooking_categories');
6346 }
6347 return count($p) > 0 ? $p[0]['name'] : '';
6348 }
6349
6350 public static function loadAdultsDiff($idroom, $adults)
6351 {
6352 static $obp_map = [];
6353
6354 $map_sign = "{$idroom}:{$adults}";
6355
6356 if (array_key_exists($map_sign, $obp_map)) {
6357 return $obp_map[$map_sign];
6358 }
6359
6360 $dbo = JFactory::getDbo();
6361
6362 $q = "SELECT * FROM `#__vikbooking_adultsdiff` WHERE `idroom`=" . (int)$idroom . " AND `adults`=" . (int)$adults;
6363 $dbo->setQuery($q, 0, 1);
6364 $obp_diff = $dbo->loadAssoc();
6365 if ($obp_diff) {
6366 // cache value and return it
6367 $obp_map[$map_sign] = $obp_diff;
6368
6369 return $obp_diff;
6370 }
6371
6372 // cache value and return it
6373 $obp_map[$map_sign] = null;
6374
6375 return null;
6376 }
6377
6378 public static function loadRoomAdultsDiff($idroom)
6379 {
6380 $dbo = JFactory::getDbo();
6381
6382 $q = "SELECT * FROM `#__vikbooking_adultsdiff` WHERE `idroom`=" . (int)$idroom . " ORDER BY `adults` ASC;";
6383 $dbo->setQuery($q);
6384 $diff = $dbo->loadAssocList();
6385 if ($diff) {
6386 $roomdiff = [];
6387 foreach ($diff as $v) {
6388 $roomdiff[$v['adults']] = $v;
6389 }
6390
6391 return $roomdiff;
6392 }
6393
6394 return [];
6395 }
6396
6397 public static function occupancyOverrideExists($tar, $adults) {
6398 foreach ($tar as $k => $v) {
6399 if (is_array($v) && array_key_exists('occupancy_ovr', $v)) {
6400 if (array_key_exists($adults, $v['occupancy_ovr'])) {
6401 return $v['occupancy_ovr'][$adults];
6402 }
6403 }
6404 }
6405 return false;
6406 }
6407
6408 public static function getChildrenCharges($idroom, $children, $ages, $num_nights) {
6409 /* charges as percentage amounts of the adults tariff not supported for third parties (only VBO 1.8) */
6410 $charges = array();
6411 if (!($children > 0) || !(count($ages) > 0)) {
6412 return $charges;
6413 }
6414 $dbo = JFactory::getDbo();
6415 $id_options = array();
6416 $q = "SELECT `id`,`idopt` FROM `#__vikbooking_rooms` WHERE `id`=".(int)$idroom.";";
6417 $dbo->setQuery($q);
6418 $dbo->execute();
6419 if ($dbo->getNumRows() > 0) {
6420 $assocs = $dbo->loadAssocList();
6421 foreach ($assocs as $opts) {
6422 if (!empty($opts['idopt'])) {
6423 $r_ido = explode(';', rtrim($opts['idopt']));
6424 foreach ($r_ido as $ido) {
6425 if (!empty($ido) && !in_array($ido, $id_options)) {
6426 $id_options[] = $ido;
6427 }
6428 }
6429 }
6430 }
6431 }
6432 if (count($id_options) > 0) {
6433 $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;";
6434 $dbo->setQuery($q);
6435 $dbo->execute();
6436 if ($dbo->getNumRows() > 0) {
6437 $ageintervals = $dbo->loadAssocList();
6438 $split_ages = explode(';;', $ageintervals[0]['ageintervals']);
6439 $age_range = array();
6440 foreach ($split_ages as $kg => $spage) {
6441 if (empty($spage)) {
6442 continue;
6443 }
6444 $parts = explode('_', $spage);
6445 if (strlen($parts[0]) > 0 && intval($parts[1]) > 0 && floatval($parts[2]) > 0) {
6446 $ind = count($age_range);
6447 $age_range[$ind]['from'] = intval($parts[0]);
6448 $age_range[$ind]['to'] = intval($parts[1]);
6449 //taxes are calculated later in VCM
6450 //$age_range[$ind]['cost'] = self::sayOptionalsPlusIva((floatval($parts[2]) * $num_nights), $ageintervals[0]['idiva']);
6451 $age_range[$ind]['cost'] = floatval($parts[2]) * $num_nights;
6452 $age_range[$ind]['option_str'] = $ageintervals[0]['id'].':1-'.($kg + 1);
6453 }
6454 }
6455 if (count($age_range) > 0) {
6456 $tot_charge = 0;
6457 $affected = array();
6458 $option_str = '';
6459 foreach ($ages as $age) {
6460 if (strlen($age) == 0) {
6461 continue;
6462 }
6463 foreach ($age_range as $range) {
6464 if (intval($age) >= $range['from'] && intval($age) <= $range['to']) {
6465 $tot_charge += $range['cost'];
6466 $affected[] = $age;
6467 $option_str .= $range['option_str'].';';
6468 break;
6469 }
6470 }
6471 }
6472 if ($tot_charge > 0) {
6473 $charges['total'] = $tot_charge;
6474 $charges['affected'] = $affected;
6475 $charges['options'] = $option_str;
6476 }
6477 }
6478 }
6479 }
6480
6481 return $charges;
6482 }
6483
6484 public static function sortRoomPrices($arr) {
6485 $newarr = array();
6486 foreach ($arr as $k => $v) {
6487 $newarr[$k] = $v['cost'];
6488 }
6489 asort($newarr);
6490 $sorted = array();
6491 foreach ($newarr as $k => $v) {
6492 $sorted[$k] = $arr[$k];
6493 }
6494 return $sorted;
6495 }
6496
6497 public static function sortResults($arr) {
6498 $newarr = array();
6499 foreach ($arr as $k => $v) {
6500 $newarr[$k] = $v[0]['cost'];
6501 }
6502 asort($newarr);
6503 $sorted = array();
6504 foreach ($newarr as $k => $v) {
6505 $sorted[$k] = $arr[$k];
6506 }
6507 return $sorted;
6508 }
6509
6510 public static function sortMultipleResults($arr) {
6511 foreach ($arr as $k => $v) {
6512 $newarr = array();
6513 foreach ($v as $subk => $subv) {
6514 $newarr[$subk] = $subv[0]['cost'];
6515 }
6516 asort($newarr);
6517 $sorted = array();
6518 foreach ($newarr as $nk => $v) {
6519 $sorted[$nk] = $arr[$k][$nk];
6520 }
6521 $arr[$k] = $sorted;
6522 }
6523 return $arr;
6524 }
6525
6526 /**
6527 * Caches a list of promotion IDs that will be skipped when applying the special rates. This is
6528 * useful for the VCM's Bulk Actions to register a skip of the promotions for the various OTAs
6529 * to avoid duplicate discounts to be applied (room-rate-day level and promotion).
6530 *
6531 * @param array $promos optional list of promotion IDs to register in the execution flow.
6532 *
6533 * @return array list of cached promotion IDs (if any).
6534 *
6535 * @since 1.16.4 (J) - 1.6.4 (WP)
6536 */
6537 public static function registerPromotionIds(array $promos = null)
6538 {
6539 static $flagged_promos = [];
6540
6541 if ($promos) {
6542 // set cached value
6543 $flagged_promos = $promos;
6544 }
6545
6546 return $flagged_promos;
6547 }
6548
6549 /**
6550 * Applies the seasonal rates over a list of room rate records.
6551 *
6552 * @param array $arr list of room rate records.
6553 * @param int $from unix timestamp for start date.
6554 * @param int $to unix timestamp for end date.
6555 *
6556 * @return array list of manipulated room rate records.
6557 */
6558 public static function applySeasonalPrices(array $arr, $from, $to)
6559 {
6560 $dbo = JFactory::getDbo();
6561 $vbo_tn = self::getTranslator();
6562
6563 $roomschange = [];
6564 $one = getdate($from);
6565
6566 // leap years
6567 if (($one['year'] % 4) == 0 && ($one['year'] % 100 != 0 || $one['year'] % 400 == 0)) {
6568 $isleap = true;
6569 } else {
6570 $isleap = false;
6571 }
6572
6573 $baseone = mktime(0, 0, 0, 1, 1, $one['year']);
6574 $tomidnightone = intval($one['hours']) * 3600;
6575 $tomidnightone += intval($one['minutes']) * 60;
6576 $sfrom = $from - $baseone - $tomidnightone;
6577 $fromdayts = mktime(0, 0, 0, $one['mon'], $one['mday'], $one['year']);
6578 $two = getdate($to);
6579 $basetwo = mktime(0, 0, 0, 1, 1, $two['year']);
6580 $tomidnighttwo = intval($two['hours']) * 3600;
6581 $tomidnighttwo += intval($two['minutes']) * 60;
6582 $sto = $to - $basetwo - $tomidnighttwo;
6583
6584 // leap years, last day of the month of the season
6585 if ($isleap) {
6586 $leapts = mktime(0, 0, 0, 2, 29, $two['year']);
6587 if ($two[0] > $leapts) {
6588 /**
6589 * Timestamp must be greater than the leap-day of Feb 29th.
6590 * It used to be checked for >= $leapts.
6591 *
6592 * @since July 3rd 2019
6593 */
6594 $sfrom -= 86400;
6595 $sto -= 86400;
6596 } elseif ($sto < $sfrom && $one['year'] < $two['year']) {
6597 //lower checkin date when in leap year but not for checkout
6598 $sfrom -= 86400;
6599 }
6600 }
6601
6602 // check for DST changes to adjust the query values to fetch
6603 if (date('I', $from) != date('I', mktime($one['hours'], $one['minutes'], $one['seconds'], $one['mon'], ($one['mday'] - 1), $one['year']))) {
6604 if (date('Y-m-d', $to) == date('Y-m-d', mktime($one['hours'], $one['minutes'], $one['seconds'], $one['mon'], ($one['mday'] + 1), $one['year']))) {
6605 // we are parsing the day when the DST changed (probably a Sunday)
6606 if (!date('I', $from)) {
6607 // DST was just turned off
6608 $sfrom -= 3600;
6609 }
6610 }
6611 }
6612
6613 // count nights requested
6614 $booking_nights = 1;
6615 foreach ($arr as $k => $a) {
6616 if (!isset($a[0])) {
6617 continue;
6618 }
6619 if (isset($a[0]['booking_nights'])) {
6620 // this value may be set when displaying pricing calendars
6621 $booking_nights = $a[0]['booking_nights'];
6622 break;
6623 }
6624 if (isset($a[0]['days'])) {
6625 $booking_nights = $a[0]['days'];
6626 break;
6627 }
6628 }
6629
6630 /**
6631 * Get a list of promotion IDs that may have been set to avoid duplicate discounts on OTAs.
6632 *
6633 * @since 1.16.4 (J) - 1.6.4 (WP)
6634 */
6635 $skip_promo_ids = self::registerPromotionIds();
6636
6637 $q = "SELECT * FROM `#__vikbooking_seasons` WHERE (" .
6638 ($sto > $sfrom ? "(`from`<=" . $sfrom . " AND `to`>=" . $sto . ") " : "") .
6639 ($sto > $sfrom ? "OR (`from`<=" . $sfrom . " AND `to`>=" . $sfrom . ") " : "(`from`<=" . $sfrom . " AND `to`<=" . $sfrom . " AND `from`>`to`) ") .
6640 ($sto > $sfrom ? "OR (`from`<=" . $sto . " AND `to`>=" . $sto . ") " : "OR (`from`>=" . $sto . " AND `to`>=" . $sto . " AND `from`>`to`) ") .
6641 ($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`)") .
6642 ($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`)") .
6643 ($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
6644 ($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
6645 ") ORDER BY `#__vikbooking_seasons`.`promo` ASC;";
6646 $dbo->setQuery($q);
6647 $seasons = $dbo->loadAssocList();
6648
6649 if ($seasons) {
6650 $vbo_tn->translateContents($seasons, '#__vikbooking_seasons');
6651 $applyseasons = false;
6652 $mem = [];
6653 foreach ($arr as $k => $a) {
6654 $mem[$k]['daysused'] = 0;
6655 $mem[$k]['sum'] = [];
6656 /**
6657 * The keys below are all needed to apply the promotions on the room's final cost.
6658 *
6659 * @since 1.13.5
6660 */
6661 $mem[$k]['diffs'] = [];
6662 $mem[$k]['trans_keys'] = [];
6663 $mem[$k]['trans_factors'] = [];
6664 $mem[$k]['trans_affdays'] = [];
6665 }
6666 foreach ($seasons as $s) {
6667 // check if this is a promotion registered for skipping
6668 if (isset($s['promo']) && $s['promo'] && in_array($s['id'], $skip_promo_ids)) {
6669 continue;
6670 }
6671
6672 // Special Price tied to the year
6673 if (!empty($s['year']) && $s['year'] > 0) {
6674 //VBO 1.7 - do not skip seasons tied to the year for bookings between two years
6675 if ($one['year'] != $s['year'] && $two['year'] != $s['year']) {
6676 //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
6677 if (($one['year'] - $s['year']) != 1 || $s['from'] < $s['to']) {
6678 continue;
6679 }
6680 //VBO 1.9 - tied to 2016 going through Jan 2017: dates of December 2017 should skip this speacial price
6681 if (($one['year'] - $s['year']) == 1 && $s['from'] > $s['to']) {
6682 $calc_ends = mktime(0, 0, 0, 1, 1, ($s['year'] + 1)) + $s['to'];
6683 if ($calc_ends < ($from - $tomidnightone)) {
6684 continue;
6685 }
6686 }
6687 } elseif ($one['year'] < $s['year'] && $two['year'] == $s['year']) {
6688 //VBO 1.9 - season tied to the year 2017 accross 2018 and we are parsing dates accross prev year 2016-2017
6689 if ($s['from'] > $s['to']) {
6690 continue;
6691 }
6692 if (($basetwo + $s['from'] + 86399) > $to) {
6693 /**
6694 * Assuming that we are on 2021, and we are booking a 2-night stay from 30/12 to 01/01. This statement involves
6695 * a special price tied to the year 2022 for the night of 31/12 (or near dates), but we are booking the night of
6696 * New Year's Eve of 2021, and so the special price pre-prepared for the year after (2022) should be ignored.
6697 *
6698 * @since 1.14.3 (J) - 1.4.3 (WP)
6699 */
6700 continue;
6701 }
6702 } elseif ($one['year'] == $s['year'] && $two['year'] > $s['year']) {
6703 if (($baseone + $s['to'] + 86399) < $from && $s['from'] < $s['to']) {
6704 /**
6705 * Assuming that we are on 2021, and we are booking a 4-night stay from 29/12 to 02/01. This statement involves
6706 * a special price tied to the year 2021 for the night of 01/01 (or near dates), but we are booking the night of
6707 * First of the Year of 2022, and so the old special price for the year before (2021) should be ignored.
6708 *
6709 * @since 1.14.3 (J) - 1.4.3 (WP)
6710 */
6711 continue;
6712 }
6713 } elseif ($one['year'] == $s['year'] && $two['year'] == $s['year'] && $s['from'] > $s['to']) {
6714 //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)
6715 if (($baseone + $s['from']) > $to) {
6716 continue;
6717 }
6718 }
6719 }
6720 //
6721 $allrooms = explode(",", $s['idrooms']);
6722 $allprices = !empty($s['idprices']) ? explode(",", $s['idprices']) : [];
6723 $inits = $baseone + $s['from'];
6724 if ($s['from'] < $s['to']) {
6725 $ends = $basetwo + $s['to'];
6726 //VikBooking 1.6 check if the inits must be set to the year after
6727 //ex. Season Jan 6 to Feb 12 - Booking Dec 31 to Jan 8 to charge Jan 6,7
6728 if ($sfrom > $s['from'] && $sto >= $s['from'] && $sfrom > $s['to'] && $sto <= $s['to'] && $s['from'] < $s['to'] && $sfrom > $sto) {
6729 $tmpbase = mktime(0, 0, 0, 1, 1, ($one['year'] + 1));
6730 $inits = $tmpbase + $s['from'];
6731 } elseif ($sfrom >= $s['from'] && $sfrom <= $s['to'] && $sto < $s['from'] && $sto < $s['to'] && $sfrom > $sto) {
6732 //VBO 1.7 - Season Dec 23 to Dec 29 - Booking Dec 29 to Jan 5
6733 $ends = $baseone + $s['to'];
6734 } elseif ($sfrom <= $s['from'] && $sfrom <= $s['to'] && $sto < $s['from'] && $sto < $s['to'] && $sfrom > $sto) {
6735 //VBO 1.7 - Season Dec 30 to Dec 31 - Booking Dec 29 to Jan 5
6736 $ends = $baseone + $s['to'];
6737 } elseif ($sfrom > $s['from'] && $sfrom > $s['to'] && $sto >= $s['from'] && ($sto >= $s['to'] || $sto <= $s['to']) && $sfrom > $sto) {
6738 //VBO 1.7 - Season Jan 1 to Jan 2 - Booking Dec 29 to Jan 5
6739 $inits = $basetwo + $s['from'];
6740 }
6741 } else {
6742 //between 2 years
6743 if ($baseone < $basetwo) {
6744 //ex. 29/12/2012 - 14/01/2013
6745 $ends = $basetwo + $s['to'];
6746 } else {
6747 if (($sfrom >= $s['from'] && $sto >= $s['from']) OR ($sfrom < $s['from'] && $sto >= $s['from'] && $sfrom > $s['to'] && $sto > $s['to'])) {
6748 //ex. 25/12 - 30/12 with init season on 20/12 OR 27/12 for counting 28,29,30/12
6749 $tmpbase = mktime(0, 0, 0, 1, 1, ($one['year'] + 1));
6750 $ends = $tmpbase + $s['to'];
6751 } else {
6752 //ex. 03/01 - 09/01
6753 $ends = $basetwo + $s['to'];
6754 $tmpbase = mktime(0, 0, 0, 1, 1, ($one['year'] - 1));
6755 $inits = $tmpbase + $s['from'];
6756 }
6757 }
6758 }
6759
6760 // leap years
6761 if ($isleap == true) {
6762 $infoseason = getdate($inits);
6763 $leapts = mktime(0, 0, 0, 2, 29, $infoseason['year']);
6764 // VikBooking 1.6 added below && $infoseason['year'] == $one['year']
6765 // 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
6766 if ($infoseason[0] > $leapts && $infoseason['year'] == $one['year']) {
6767 /**
6768 * Timestamp must be greater than the leap-day of Feb 29th.
6769 * It used to be checked for >= $leapts.
6770 *
6771 * @since July 3rd 2019
6772 */
6773 $inits += 86400;
6774 $ends += 86400;
6775 }
6776 }
6777
6778 // promotions
6779 $promotion = [];
6780 if ($s['promo'] == 1) {
6781 $daysadv = (($inits - time()) / 86400);
6782 $daysadv = $daysadv > 0 ? (int)ceil($daysadv) : 0;
6783 if (!empty($s['promodaysadv']) && $s['promodaysadv'] > $daysadv) {
6784 continue;
6785 } elseif (!empty($s['promolastmin']) && $s['promolastmin'] > 0) {
6786 $secstocheckin = ($from - time());
6787 if ($s['promolastmin'] < $secstocheckin) {
6788 // VBO 1.11 - too many seconds to the check-in date, skip this last minute promotion
6789 continue;
6790 }
6791 }
6792 if ($s['promominlos'] > 1 && $booking_nights < $s['promominlos']) {
6793 /**
6794 * The minimum length of stay parameter is also taken to exclude the promotion from the calculation.
6795 *
6796 * @since 1.13.5
6797 */
6798 continue;
6799 }
6800 $promotion['todaydaysadv'] = $daysadv;
6801 $promotion['promodaysadv'] = $s['promodaysadv'];
6802 $promotion['promotxt'] = $s['promotxt'];
6803 }
6804
6805 // occupancy override
6806 $occupancy_ovr = !empty($s['occupancy_ovr']) ? json_decode($s['occupancy_ovr'], true) : [];
6807
6808 // week days
6809 $filterwdays = !empty($s['wdays']) ? true : false;
6810 $wdays = $filterwdays ? explode(';', $s['wdays']) : '';
6811 if (is_array($wdays) && $wdays) {
6812 foreach ($wdays as $kw => $wd) {
6813 if (strlen($wd) == 0) {
6814 unset($wdays[$kw]);
6815 }
6816 }
6817 }
6818
6819 // checkin must be after the beginning of the season
6820 $checkininclok = true;
6821 if ($s['checkinincl'] == 1) {
6822 $checkininclok = false;
6823 if ($s['from'] < $s['to']) {
6824 if ($sfrom >= $s['from'] && $sfrom <= $s['to']) {
6825 $checkininclok = true;
6826 }
6827 } else {
6828 if (($sfrom >= $s['from'] && $sfrom > $s['to']) || ($sfrom < $s['from'] && $sfrom <= $s['to'])) {
6829 $checkininclok = true;
6830 }
6831 }
6832 }
6833 if ($checkininclok !== true) {
6834 continue;
6835 }
6836
6837 foreach ($arr as $k => $a) {
6838 // applied only to some types of price
6839 if ($allprices && !empty($allprices[0])) {
6840 if (!in_array("-" . $a[0]['idprice'] . "-", $allprices)) {
6841 continue;
6842 }
6843 }
6844 // applied only to some room types
6845 if (!in_array("-" . $a[0]['idroom'] . "-", $allrooms)) {
6846 continue;
6847 }
6848
6849 // count affected nights of stay
6850 $affdays = 0;
6851 $season_fromdayts = $fromdayts;
6852 $is_dst = date('I', $season_fromdayts);
6853 for ($i = 0; $i < $a[0]['days']; $i++) {
6854 $todayts = $season_fromdayts + ($i * 86400);
6855 $is_now_dst = date('I', $todayts);
6856 if ($is_dst != $is_now_dst) {
6857 // Daylight Saving Time has changed, check how
6858 if ((bool)$is_dst === true) {
6859 $todayts += 3600;
6860 $season_fromdayts += 3600;
6861 } else {
6862 $todayts -= 3600;
6863 $season_fromdayts -= 3600;
6864 }
6865 $is_dst = $is_now_dst;
6866 }
6867 if ($todayts >= $inits && $todayts <= $ends) {
6868 // week days
6869 if ($filterwdays == true) {
6870 $checkwday = getdate($todayts);
6871 if (in_array($checkwday['wday'], $wdays)) {
6872 $affdays++;
6873 }
6874 } else {
6875 $affdays++;
6876 }
6877 //
6878 }
6879 }
6880 if (!($affdays > 0)) {
6881 // no nights affected
6882 continue;
6883 }
6884
6885 // apply the rule
6886 $applyseasons = true;
6887 $dailyprice = $a[0]['cost'] / $a[0]['days'];
6888
6889 // modification factor object
6890 $factor = new stdClass;
6891
6892 // calculate new price progressively
6893 if (intval($s['val_pcent']) == 2) {
6894 // percentage value
6895 $factor->pcent = 1;
6896 $pctval = $s['diffcost'];
6897 if (strlen($s['losoverride']) > 0) {
6898 // values overrides
6899 $arrvaloverrides = [];
6900 $valovrparts = explode('_', $s['losoverride']);
6901 foreach ($valovrparts as $valovr) {
6902 if (!empty($valovr)) {
6903 $ovrinfo = explode(':', $valovr);
6904 if (strstr($ovrinfo[0], '-i') != false) {
6905 $ovrinfo[0] = str_replace('-i', '', $ovrinfo[0]);
6906 if ((int)$ovrinfo[0] < $a[0]['days']) {
6907 $arrvaloverrides[$a[0]['days']] = $ovrinfo[1];
6908 }
6909 }
6910 $arrvaloverrides[$ovrinfo[0]] = $ovrinfo[1];
6911 }
6912 }
6913 if (array_key_exists($a[0]['days'], $arrvaloverrides)) {
6914 $pctval = $arrvaloverrides[$a[0]['days']];
6915 }
6916 }
6917 if (intval($s['type']) == 1) {
6918 // charge
6919 $factor->type = '+';
6920 $cpercent = 100 + $pctval;
6921 } else {
6922 // discount
6923 $factor->type = '-';
6924 $cpercent = 100 - $pctval;
6925 }
6926 $factor->amount = $pctval;
6927 $newprice = ($dailyprice * $cpercent / 100) * $affdays;
6928 } else {
6929 // absolute value
6930 $factor->pcent = 0;
6931 $absval = $s['diffcost'];
6932 if (strlen($s['losoverride']) > 0) {
6933 // values overrides
6934 $arrvaloverrides = [];
6935 $valovrparts = explode('_', $s['losoverride']);
6936 foreach ($valovrparts as $valovr) {
6937 if (!empty($valovr)) {
6938 $ovrinfo = explode(':', $valovr);
6939 if (strstr($ovrinfo[0], '-i') != false) {
6940 $ovrinfo[0] = str_replace('-i', '', $ovrinfo[0]);
6941 if ((int)$ovrinfo[0] < $a[0]['days']) {
6942 $arrvaloverrides[$a[0]['days']] = $ovrinfo[1];
6943 }
6944 }
6945 $arrvaloverrides[$ovrinfo[0]] = $ovrinfo[1];
6946 }
6947 }
6948 if (array_key_exists($a[0]['days'], $arrvaloverrides)) {
6949 $absval = $arrvaloverrides[$a[0]['days']];
6950 }
6951 }
6952 if (intval($s['type']) == 1) {
6953 // charge
6954 $factor->type = '+';
6955 $newprice = ($dailyprice + $absval) * $affdays;
6956 } else {
6957 // discount
6958 $factor->type = '-';
6959 $newprice = ($dailyprice - $absval) * $affdays;
6960 }
6961 $factor->amount = $absval;
6962 }
6963
6964 // apply rounding
6965 $factor->roundmode = $s['roundmode'];
6966 if (!empty($s['roundmode'])) {
6967 $newprice = round($newprice, 0, constant($s['roundmode']));
6968 } else {
6969 $newprice = round($newprice, 2);
6970 }
6971
6972 // define the promotion (only if no value overrides set the amount to 0)
6973 if ($promotion && ((isset($absval) && $absval > 0) || $pctval > 0)) {
6974 /**
6975 * Include the discount information (if any). The cost re-calculated may not be
6976 * precise if multiple special prices were applied over the same dates.
6977 *
6978 * @since 1.13
6979 */
6980 if ($s['type'] == 2 && $s['diffcost'] > 0) {
6981 $promotion['discount'] = [
6982 'amount' => $s['diffcost'],
6983 'pcent' => (int)($s['val_pcent'] == 2),
6984 ];
6985 }
6986 //
6987 $mem[$k]['promotion'] = $promotion;
6988 }
6989
6990 // define the occupancy override
6991 if (array_key_exists($a[0]['idroom'], $occupancy_ovr) && $occupancy_ovr[$a[0]['idroom']]) {
6992 $mem[$k]['occupancy_ovr'] = $occupancy_ovr[$a[0]['idroom']];
6993 }
6994
6995 // push difference generated only if to be applied progressively
6996 if (!$s['promo'] || ($s['promo'] && !$s['promofinalprice'])) {
6997 /**
6998 * Push the difference generated by this special price for later transliteration of final price,
6999 * only if the special price is calculated progressively and not on the final price.
7000 *
7001 * @since 1.13.5
7002 */
7003 array_push($mem[$k]['diffs'], ($newprice - ($dailyprice * $affdays)));
7004 } elseif ($s['promo'] && $s['promofinalprice'] && $factor->pcent) {
7005 /**
7006 * This is a % promotion to be applied on the final price, so we need to save that this memory key
7007 * will need the transliteration, aka adjusting this new price by applying the charge/discount on
7008 * all differences applied by the previous special pricing rules.
7009 *
7010 * @since 1.13.5
7011 */
7012 array_push($mem[$k]['trans_keys'], count($mem[$k]['sum']));
7013 array_push($mem[$k]['trans_factors'], $factor);
7014 array_push($mem[$k]['trans_affdays'], $affdays);
7015 }
7016
7017 // push values in memory array
7018 array_push($mem[$k]['sum'], $newprice);
7019 $mem[$k]['daysused'] += $affdays;
7020 array_push($roomschange, $a[0]['idroom']);
7021 }
7022 }
7023 if ($applyseasons) {
7024 foreach ($mem as $k => $v) {
7025 if ($v['daysused'] > 0 && $v['sum']) {
7026 $newprice = 0;
7027 $dailyprice = $arr[$k][0]['cost'] / $arr[$k][0]['days'];
7028 $restdays = $arr[$k][0]['days'] - $v['daysused'];
7029 $addrest = $restdays * $dailyprice;
7030 $newprice += $addrest;
7031
7032 // calculate new final cost
7033 $redo_rounding = null;
7034 foreach ($v['sum'] as $sum_index => $add) {
7035 /**
7036 * The application of the various special pricing rules is made in a progressive and cumulative way
7037 * by always starting from the room base cost or its average daily cost. However, promotions may need
7038 * to be applied on the room final cost, and not in a progresive way. In order to keep the progressive
7039 * algorithm, for applying the special prices on the room final cost we need to apply the same promotion
7040 * onto the differences generated by all the regular and progressively applied special pricing rules.
7041 *
7042 * @since 1.13.5
7043 */
7044 if (in_array($sum_index, $v['trans_keys']) && $v['diffs']) {
7045 /**
7046 * This progressive price difference must be applied on the room final cost, so we need to
7047 * apply the transliteration over the other differences applied by other special prices.
7048 */
7049 $transliterate_key = array_search($sum_index, $v['trans_keys']);
7050 if ($transliterate_key !== false && isset($v['trans_factors'][$transliterate_key])) {
7051 // this is the % promotion we are looking for applying it on the final cost
7052 $factor = $v['trans_factors'][$transliterate_key];
7053 if (is_object($factor) && $factor->pcent) {
7054 $final_factor = 0;
7055 foreach ($v['diffs'] as $diff_index => $prog_diff) {
7056 $final_factor += $prog_diff * $factor->amount / 100;
7057 }
7058 // update rounding
7059 $redo_rounding = !empty($factor->roundmode) ? $factor->roundmode : $redo_rounding;
7060
7061 /**
7062 * Leverage the final factor depending on how many nights this affected.
7063 *
7064 * @since 1.15.0 (J) - 1.5.0 (WP)
7065 */
7066 if (isset($v['trans_affdays'][$transliterate_key]) && $v['trans_affdays'][$transliterate_key] < $arr[$k][0]['days']) {
7067 $avg_final_factor = $final_factor / $arr[$k][0]['days'];
7068 $final_factor = $avg_final_factor * $v['trans_affdays'][$transliterate_key];
7069 }
7070
7071 // apply the final transliteration to obtain a value like if it was applied on the room's final cost
7072 $add = $factor->type == '+' ? ($add + $final_factor) : ($add - $final_factor);
7073 }
7074 }
7075 }
7076
7077 // apply new price progressively
7078 $newprice += $add;
7079 }
7080
7081 // apply rounding from factor
7082 if (!empty($redo_rounding)) {
7083 $newprice = round($newprice, 0, constant($redo_rounding));
7084 }
7085
7086 // set promotion (if any)
7087 if (isset($v['promotion'])) {
7088 $arr[$k][0]['promotion'] = $v['promotion'];
7089 }
7090
7091 // set occupancy overrides (if any)
7092 if (isset($v['occupancy_ovr'])) {
7093 $arr[$k][0]['occupancy_ovr'] = $v['occupancy_ovr'];
7094 }
7095
7096 // set new final cost and update nights affected
7097 $arr[$k][0]['cost'] = $newprice;
7098 $arr[$k][0]['affdays'] = $v['daysused'];
7099 }
7100 }
7101 }
7102 }
7103
7104 // week days with no season
7105 $roomschange = array_unique($roomschange);
7106 $q = "SELECT * FROM `#__vikbooking_seasons` WHERE ((`from` = 0 AND `to` = 0) OR (`from` IS NULL AND `to` IS NULL));";
7107 $dbo->setQuery($q);
7108 $specials = $dbo->loadAssocList();
7109 if ($specials) {
7110 $vbo_tn->translateContents($specials, '#__vikbooking_seasons');
7111 $applyseasons = false;
7112 unset($mem);
7113 $mem = [];
7114 foreach ($arr as $k => $a) {
7115 $mem[$k]['daysused'] = 0;
7116 $mem[$k]['sum'] = [];
7117 }
7118 foreach ($specials as $s) {
7119 // check if this is a promotion registered for skipping
7120 if (isset($s['promo']) && $s['promo'] && in_array($s['id'], $skip_promo_ids)) {
7121 continue;
7122 }
7123
7124 // Special Price tied to the year
7125 if (!empty($s['year']) && $s['year'] > 0) {
7126 if ($one['year'] != $s['year']) {
7127 continue;
7128 }
7129 }
7130
7131 $allrooms = explode(",", $s['idrooms']);
7132 $allprices = !empty($s['idprices']) ? explode(",", $s['idprices']) : [];
7133 // week days
7134 $filterwdays = !empty($s['wdays']) ? true : false;
7135 $wdays = $filterwdays == true ? explode(';', $s['wdays']) : '';
7136 if (is_array($wdays) && $wdays) {
7137 foreach ($wdays as $kw => $wd) {
7138 if (strlen($wd) == 0) {
7139 unset($wdays[$kw]);
7140 }
7141 }
7142 }
7143
7144 foreach ($arr as $k => $a) {
7145 // only rooms with no price modifications from seasons
7146
7147 // applied only to some types of price
7148 if ($allprices && !empty($allprices[0])) {
7149 if (!in_array("-" . $a[0]['idprice'] . "-", $allprices)) {
7150 continue;
7151 }
7152 }
7153
7154 /**
7155 * We should not exclude the rooms that already had a modification of the price through a season
7156 * with a dates filter or we risk to get invalid prices by skipping a rule for just some weekdays.
7157 * The control " || in_array($a[0]['idroom'], $roomschange)" was removed from the IF below.
7158 *
7159 * @since 1.11
7160 */
7161 if (!in_array("-" . $a[0]['idroom'] . "-", $allrooms)) {
7162 continue;
7163 }
7164
7165 $affdays = 0;
7166 $season_fromdayts = $fromdayts;
7167 $is_dst = date('I', $season_fromdayts);
7168 for ($i = 0; $i < $a[0]['days']; $i++) {
7169 $todayts = $season_fromdayts + ($i * 86400);
7170 $is_now_dst = date('I', $todayts);
7171 if ($is_dst != $is_now_dst) {
7172 // Daylight Saving Time has changed, check how
7173 if ((bool)$is_dst === true) {
7174 $todayts += 3600;
7175 $season_fromdayts += 3600;
7176 } else {
7177 $todayts -= 3600;
7178 $season_fromdayts -= 3600;
7179 }
7180 $is_dst = $is_now_dst;
7181 }
7182 // week days
7183 if ($filterwdays == true) {
7184 $checkwday = getdate($todayts);
7185 if (in_array($checkwday['wday'], $wdays)) {
7186 $affdays++;
7187 }
7188 }
7189 }
7190 if (!($affdays > 0)) {
7191 // no nights affected
7192 continue;
7193 }
7194
7195 // apply the rule
7196 $applyseasons = true;
7197 $dailyprice = $a[0]['cost'] / $a[0]['days'];
7198
7199 if (intval($s['val_pcent']) == 2) {
7200 // percentage value
7201 $pctval = $s['diffcost'];
7202 if (strlen($s['losoverride']) > 0) {
7203 // values overrides
7204 $arrvaloverrides = [];
7205 $valovrparts = explode('_', $s['losoverride']);
7206 foreach ($valovrparts as $valovr) {
7207 if (!empty($valovr)) {
7208 $ovrinfo = explode(':', $valovr);
7209 if (strstr($ovrinfo[0], '-i') != false) {
7210 $ovrinfo[0] = str_replace('-i', '', $ovrinfo[0]);
7211 if ((int)$ovrinfo[0] < $a[0]['days']) {
7212 $arrvaloverrides[$a[0]['days']] = $ovrinfo[1];
7213 }
7214 }
7215 $arrvaloverrides[$ovrinfo[0]] = $ovrinfo[1];
7216 }
7217 }
7218 if (array_key_exists($a[0]['days'], $arrvaloverrides)) {
7219 $pctval = $arrvaloverrides[$a[0]['days']];
7220 }
7221 }
7222 if (intval($s['type']) == 1) {
7223 // charge
7224 $cpercent = 100 + $pctval;
7225 } else {
7226 // discount
7227 $cpercent = 100 - $pctval;
7228 }
7229 $newprice = ($dailyprice * $cpercent / 100) * $affdays;
7230 } else {
7231 // absolute value
7232 $absval = $s['diffcost'];
7233 if (strlen($s['losoverride']) > 0) {
7234 // values overrides
7235 $arrvaloverrides = [];
7236 $valovrparts = explode('_', $s['losoverride']);
7237 foreach ($valovrparts as $valovr) {
7238 if (!empty($valovr)) {
7239 $ovrinfo = explode(':', $valovr);
7240 if (strstr($ovrinfo[0], '-i') != false) {
7241 $ovrinfo[0] = str_replace('-i', '', $ovrinfo[0]);
7242 if ((int)$ovrinfo[0] < $a[0]['days']) {
7243 $arrvaloverrides[$a[0]['days']] = $ovrinfo[1];
7244 }
7245 }
7246 $arrvaloverrides[$ovrinfo[0]] = $ovrinfo[1];
7247 }
7248 }
7249 if (array_key_exists($a[0]['days'], $arrvaloverrides)) {
7250 $absval = $arrvaloverrides[$a[0]['days']];
7251 }
7252 }
7253 if (intval($s['type']) == 1) {
7254 // charge
7255 $newprice = ($dailyprice + $absval) * $affdays;
7256 } else {
7257 // discount
7258 $newprice = ($dailyprice - $absval) * $affdays;
7259 }
7260 }
7261
7262 // apply rounding
7263 if (!empty($s['roundmode'])) {
7264 $newprice = round($newprice, 0, constant($s['roundmode']));
7265 } else {
7266 $newprice = round($newprice, 2);
7267 }
7268
7269 // push values in memory array
7270 array_push($mem[$k]['sum'], $newprice);
7271 $mem[$k]['daysused'] += $affdays;
7272 }
7273 }
7274 if ($applyseasons) {
7275 foreach ($mem as $k => $v) {
7276 if ($v['daysused'] > 0 && $v['sum']) {
7277 $newprice = 0;
7278 $dailyprice = $arr[$k][0]['cost'] / $arr[$k][0]['days'];
7279 $restdays = $arr[$k][0]['days'] - $v['daysused'];
7280 $addrest = $restdays * $dailyprice;
7281 $newprice += $addrest;
7282 foreach ($v['sum'] as $add) {
7283 $newprice += $add;
7284 }
7285 $arr[$k][0]['cost'] = $newprice;
7286 $arr[$k][0]['affdays'] = $v['daysused'];
7287 }
7288 }
7289 }
7290 }
7291 // end week days with no season
7292
7293 return $arr;
7294 }
7295
7296 /**
7297 * Applies the special prices over an array of tariffs.
7298 * The function is also used by VCM (>= 1.6.5) with specific arguments.
7299 *
7300 * @param array $arr array of tariffs taken from the DB
7301 * @param int $from start timestamp
7302 * @param int $to end timestamp
7303 * @param array $parsed_season array of a season to parse (used to render the seasons calendars in back-end and front-end)
7304 * @param array $seasons_dates (VBO 1.10) array of seasons with dates filter taken from the DB to avoid multiple queries (VCM)
7305 * @param array $seasons_wdays (VBO 1.10) array of seasons with weekdays filter (only) taken from the DB to avoid multiple queries (VCM)
7306 *
7307 * @return array
7308 */
7309 public static function applySeasonsRoom(array $arr, $from, $to, array $parsed_season = [], array $seasons_dates = [], array $seasons_wdays = [])
7310 {
7311 $dbo = JFactory::getDbo();
7312 $vbo_tn = self::getTranslator();
7313
7314 $roomschange = [];
7315 $one = getdate($from);
7316
7317 // leap years
7318 if ($one['year'] % 4 == 0 && ($one['year'] % 100 != 0 || $one['year'] % 400 == 0)) {
7319 $isleap = true;
7320 } else {
7321 $isleap = false;
7322 }
7323
7324 $baseone = mktime(0, 0, 0, 1, 1, $one['year']);
7325 $tomidnightone = intval($one['hours']) * 3600;
7326 $tomidnightone += intval($one['minutes']) * 60;
7327 $sfrom = $from - $baseone - $tomidnightone;
7328 $fromdayts = mktime(0, 0, 0, $one['mon'], $one['mday'], $one['year']);
7329 $two = getdate($to);
7330 $basetwo = mktime(0, 0, 0, 1, 1, $two['year']);
7331 $tomidnighttwo = intval($two['hours']) * 3600;
7332 $tomidnighttwo += intval($two['minutes']) * 60;
7333 $sto = $to - $basetwo - $tomidnighttwo;
7334
7335 // leap years, last day of the month of the season
7336 if ($isleap) {
7337 $leapts = mktime(0, 0, 0, 2, 29, $two['year']);
7338 if ($two[0] > $leapts) {
7339 /**
7340 * Timestamp must be greater than the leap-day of Feb 29th.
7341 * It used to be checked for >= $leapts.
7342 *
7343 * @since July 3rd 2019
7344 */
7345 $sfrom -= 86400;
7346 $sto -= 86400;
7347 } elseif ($sto < $sfrom && $one['year'] < $two['year']) {
7348 // lower checkin date when in leap year but not for checkout
7349 $sfrom -= 86400;
7350 }
7351 }
7352
7353 // check for DST changes to adjust the query values to fetch
7354 if (date('I', $from) != date('I', mktime($one['hours'], $one['minutes'], $one['seconds'], $one['mon'], ($one['mday'] - 1), $one['year']))) {
7355 if (date('Y-m-d', $to) == date('Y-m-d', mktime($one['hours'], $one['minutes'], $one['seconds'], $one['mon'], ($one['mday'] + 1), $one['year']))) {
7356 // we are parsing the day when the DST changed (probably a Sunday)
7357 if (!date('I', $from)) {
7358 // DST was just turned off
7359 $sfrom -= 3600;
7360 }
7361 }
7362 }
7363
7364 // count nights requested
7365 $booking_nights = 1;
7366 foreach ($arr as $k => $a) {
7367 if (isset($a['booking_nights'])) {
7368 // this value may be set when displaying pricing calendars
7369 $booking_nights = $a['booking_nights'];
7370 break;
7371 }
7372 if (isset($a['days'])) {
7373 $booking_nights = $a['days'];
7374 break;
7375 }
7376 }
7377
7378 /**
7379 * Get a list of promotion IDs that may have been set to avoid duplicate discounts on OTAs.
7380 *
7381 * @since 1.16.4 (J) - 1.6.4 (WP)
7382 */
7383 $skip_promo_ids = self::registerPromotionIds();
7384
7385 $totseasons = 0;
7386 if (!$parsed_season && !$seasons_dates) {
7387 $q = "SELECT * FROM `#__vikbooking_seasons` WHERE (" .
7388 ($sto > $sfrom ? "(`from`<=" . $sfrom . " AND `to`>=" . $sto . ") " : "") .
7389 ($sto > $sfrom ? "OR (`from`<=" . $sfrom . " AND `to`>=" . $sfrom . ") " : "(`from`<=" . $sfrom . " AND `to`<=" . $sfrom . " AND `from`>`to`) ") .
7390 ($sto > $sfrom ? "OR (`from`<=" . $sto . " AND `to`>=" . $sto . ") " : "OR (`from`>=" . $sto . " AND `to`>=" . $sto . " AND `from`>`to`) ") .
7391 ($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`)") .
7392 ($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`)") .
7393 ($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
7394 ($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
7395 ") ORDER BY `#__vikbooking_seasons`.`promo` ASC;";
7396 $dbo->setQuery($q);
7397 $seasons = $dbo->loadAssocList();
7398 $totseasons = $seasons ? count($seasons) : 0;
7399 }
7400
7401 if ($totseasons > 0 || $parsed_season || $seasons_dates) {
7402 if ($totseasons > 0) {
7403 $seasons = $seasons;
7404 } elseif ($parsed_season) {
7405 $seasons = [$parsed_season];
7406 } else {
7407 $seasons = $seasons_dates;
7408 }
7409 $vbo_tn->translateContents($seasons, '#__vikbooking_seasons');
7410 $applyseasons = false;
7411 $mem = [];
7412 foreach ($arr as $k => $a) {
7413 $mem[$k]['daysused'] = 0;
7414 $mem[$k]['sum'] = [];
7415 $mem[$k]['spids'] = [];
7416 /**
7417 * The keys below are all needed to apply the promotions on the room's final cost.
7418 *
7419 * @since 1.13.5
7420 */
7421 $mem[$k]['diffs'] = [];
7422 $mem[$k]['trans_keys'] = [];
7423 $mem[$k]['trans_factors'] = [];
7424 $mem[$k]['trans_affdays'] = [];
7425 }
7426 $affdayslistless = [];
7427 foreach ($seasons as $s) {
7428 // 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
7429 if (empty($s['from']) && empty($s['to']) && !empty($s['wdays'])) {
7430 // a season for Jan 1st to Jan 1st (1 day), with NO week-days filter is still accepted
7431 continue;
7432 }
7433
7434 /**
7435 * VCM may build a fake season as a "restriction placeholder" if no special prices found.
7436 * We need to skip such fake seasons as they do not need any parsing.
7437 *
7438 * @since 1.13
7439 */
7440 if (empty($s['from']) && empty($s['to']) && empty($s['diffcost']) && !isset($s['from'])) {
7441 continue;
7442 }
7443
7444 // check if this is a promotion registered for skipping
7445 if (isset($s['promo']) && $s['promo'] && in_array($s['id'], $skip_promo_ids)) {
7446 continue;
7447 }
7448
7449 // Special Price tied to the year
7450 if (!empty($s['year']) && $s['year'] > 0) {
7451 //VBO 1.7 - do not skip seasons tied to the year for bookings between two years
7452 if ($one['year'] != $s['year'] && $two['year'] != $s['year']) {
7453 //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
7454 if (($one['year'] - $s['year']) != 1 || $s['from'] < $s['to']) {
7455 continue;
7456 }
7457 //VBO 1.9 - tied to 2016 going through Jan 2017: dates of December 2017 should skip this speacial price
7458 if (($one['year'] - $s['year']) == 1 && $s['from'] > $s['to']) {
7459 $calc_ends = mktime(0, 0, 0, 1, 1, ($s['year'] + 1)) + $s['to'];
7460 if ($calc_ends < ($from - $tomidnightone)) {
7461 continue;
7462 }
7463 }
7464 } elseif ($one['year'] < $s['year'] && $two['year'] == $s['year']) {
7465 //VBO 1.9 - season tied to the year 2017 accross 2018 and we are parsing dates accross prev year 2016-2017
7466 if ($s['from'] > $s['to']) {
7467 continue;
7468 }
7469 if (($basetwo + $s['from'] + 86399) > $to) {
7470 /**
7471 * Assuming that we are on 2021, and we are booking a 2-night stay from 30/12 to 01/01. This statement involves
7472 * a special price tied to the year 2022 for the night of 31/12 (or near dates), but we are booking the night of
7473 * New Year's Eve of 2021, and so the special price pre-prepared for the year after (2022) should be ignored.
7474 *
7475 * @since 1.14.3 (J) - 1.4.3 (WP)
7476 */
7477 continue;
7478 }
7479 } elseif ($one['year'] == $s['year'] && $two['year'] > $s['year']) {
7480 if (($baseone + $s['to'] + 86399) < $from && $s['from'] < $s['to']) {
7481 /**
7482 * Assuming that we are on 2021, and we are booking a 4-night stay from 29/12 to 02/01. This statement involves
7483 * a special price tied to the year 2021 for the night of 01/01 (or near dates), but we are booking the night of
7484 * First of the Year of 2022, and so the old special price for the year before (2021) should be ignored.
7485 *
7486 * @since 1.14.3 (J) - 1.4.3 (WP)
7487 */
7488 continue;
7489 }
7490 } elseif ($one['year'] == $s['year'] && $two['year'] == $s['year'] && $s['from'] > $s['to']) {
7491 //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)
7492 if (($baseone + $s['from']) > $to) {
7493 continue;
7494 }
7495 }
7496 }
7497 //
7498 $allrooms = !empty($s['idrooms']) ? explode(",", $s['idrooms']) : [];
7499 $allprices = !empty($s['idprices']) ? explode(",", $s['idprices']) : [];
7500 $inits = $baseone + $s['from'];
7501 if ($s['from'] < $s['to']) {
7502 $ends = $basetwo + $s['to'];
7503 //VikBooking 1.6 check if the inits must be set to the year after
7504 //ex. Season Jan 6 to Feb 12 - Booking Dec 31 to Jan 8 to charge Jan 6,7
7505 if ($sfrom > $s['from'] && $sto >= $s['from'] && $sfrom > $s['to'] && $sto <= $s['to'] && $s['from'] < $s['to'] && $sfrom > $sto) {
7506 $tmpbase = mktime(0, 0, 0, 1, 1, ($one['year'] + 1));
7507 $inits = $tmpbase + $s['from'];
7508 } elseif ($sfrom >= $s['from'] && $sfrom <= $s['to'] && $sto < $s['from'] && $sto < $s['to'] && $sfrom > $sto) {
7509 //VBO 1.7 - Season Dec 23 to Dec 29 - Booking Dec 29 to Jan 5
7510 $ends = $baseone + $s['to'];
7511 } elseif ($sfrom <= $s['from'] && $sfrom <= $s['to'] && $sto < $s['from'] && $sto < $s['to'] && $sfrom > $sto) {
7512 //VBO 1.7 - Season Dec 30 to Dec 31 - Booking Dec 29 to Jan 5
7513 $ends = $baseone + $s['to'];
7514 } elseif ($sfrom > $s['from'] && $sfrom > $s['to'] && $sto >= $s['from'] && ($sto >= $s['to'] || $sto <= $s['to']) && $sfrom > $sto) {
7515 //VBO 1.7 - Season Jan 1 to Jan 2 - Booking Dec 29 to Jan 5
7516 $inits = $basetwo + $s['from'];
7517 }
7518 } else {
7519 //between 2 years
7520 if ($baseone < $basetwo) {
7521 //ex. 29/12/2012 - 14/01/2013
7522 $ends = $basetwo + $s['to'];
7523 } else {
7524 if (($sfrom >= $s['from'] && $sto >= $s['from']) || ($sfrom < $s['from'] && $sto >= $s['from'] && $sfrom > $s['to'] && $sto > $s['to'])) {
7525 //ex. 25/12 - 30/12 with init season on 20/12 OR 27/12 for counting 28,29,30/12
7526 $tmpbase = mktime(0, 0, 0, 1, 1, ($one['year'] + 1));
7527 $ends = $tmpbase + $s['to'];
7528 } else {
7529 //ex. 03/01 - 09/01
7530 $ends = $basetwo + $s['to'];
7531 $tmpbase = mktime(0, 0, 0, 1, 1, ($one['year'] - 1));
7532 $inits = $tmpbase + $s['from'];
7533 }
7534 }
7535 }
7536 // leap years
7537 if ($isleap == true) {
7538 $infoseason = getdate($inits);
7539 $leapts = mktime(0, 0, 0, 2, 29, $infoseason['year']);
7540 // VikBooking 1.6 added below && $infoseason['year'] == $one['year']
7541 // 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
7542 if ($infoseason[0] > $leapts && $infoseason['year'] == $one['year']) {
7543 /**
7544 * Timestamp must be greater than the leap-day of Feb 29th.
7545 * It used to be checked for >= $leapts.
7546 *
7547 * @since July 3rd 2019
7548 */
7549 $inits += 86400;
7550 $ends += 86400;
7551 }
7552 }
7553
7554 // promotions
7555 $promotion = [];
7556 if ($s['promo'] == 1) {
7557 $daysadv = (($inits - time()) / 86400);
7558 $daysadv = $daysadv > 0 ? (int)ceil($daysadv) : 0;
7559 if (!empty($s['promodaysadv']) && $s['promodaysadv'] > $daysadv) {
7560 continue;
7561 } elseif (!empty($s['promolastmin']) && $s['promolastmin'] > 0) {
7562 $secstocheckin = ($from - time());
7563 if ($s['promolastmin'] < $secstocheckin) {
7564 // VBO 1.11 - too many seconds to the check-in date, skip this last minute promotion
7565 continue;
7566 }
7567 }
7568 if ($s['promominlos'] > 1 && $booking_nights < $s['promominlos']) {
7569 /**
7570 * The minimum length of stay parameter is also taken to exclude the promotion from the calculation.
7571 *
7572 * @since 1.13.5
7573 */
7574 continue;
7575 }
7576 $promotion['todaydaysadv'] = $daysadv;
7577 $promotion['promodaysadv'] = $s['promodaysadv'];
7578 $promotion['promotxt'] = $s['promotxt'];
7579 }
7580
7581 // occupancy override
7582 $occupancy_ovr = !empty($s['occupancy_ovr']) ? json_decode($s['occupancy_ovr'], true) : [];
7583
7584 //week days
7585 $filterwdays = !empty($s['wdays']) ? true : false;
7586 $wdays = $filterwdays == true ? explode(';', $s['wdays']) : '';
7587 if (is_array($wdays) && $wdays) {
7588 foreach ($wdays as $kw => $wd) {
7589 if (strlen($wd) == 0) {
7590 unset($wdays[$kw]);
7591 }
7592 }
7593 }
7594
7595 // checkin must be after the begin of the season
7596 $checkininclok = true;
7597 if ($s['checkinincl'] == 1) {
7598 $checkininclok = false;
7599 if ($s['from'] < $s['to']) {
7600 if ($sfrom >= $s['from'] && $sfrom <= $s['to']) {
7601 $checkininclok = true;
7602 }
7603 } else {
7604 if (($sfrom >= $s['from'] && $sfrom > $s['to']) || ($sfrom < $s['from'] && $sfrom <= $s['to'])) {
7605 $checkininclok = true;
7606 }
7607 }
7608 }
7609 if ($checkininclok !== true) {
7610 continue;
7611 }
7612
7613 foreach ($arr as $k => $a) {
7614 // applied only to some types of price
7615 if ($allprices && !empty($allprices[0])) {
7616 // VikBooking 1.6: Price Calendar sets the idprice to -1
7617 if (!in_array("-" . $a['idprice'] . "-", $allprices) && $a['idprice'] > 0) {
7618 continue;
7619 }
7620 }
7621 // applied only to some room types
7622 if (!in_array("-" . $a['idroom'] . "-", $allrooms)) {
7623 continue;
7624 }
7625
7626 if (isset($a['days']) && is_float($a['days'])) {
7627 // prevent any possible warning with array_key_exists()
7628 $a['days'] = (int)$a['days'];
7629 }
7630
7631 $affdays = 0;
7632 $season_fromdayts = $fromdayts;
7633 $is_dst = date('I', $season_fromdayts);
7634 for ($i = 0; $i < $a['days']; $i++) {
7635 $todayts = $season_fromdayts + ($i * 86400);
7636 $is_now_dst = date('I', $todayts);
7637 if ($is_dst != $is_now_dst) {
7638 // Daylight Saving Time has changed, check how
7639 if ((bool)$is_dst === true) {
7640 $todayts += 3600;
7641 $season_fromdayts += 3600;
7642 } else {
7643 $todayts -= 3600;
7644 $season_fromdayts -= 3600;
7645 }
7646 $is_dst = $is_now_dst;
7647 }
7648 if ($todayts >= $inits && $todayts <= $ends) {
7649 $checkwday = getdate($todayts);
7650 // week days
7651 if ($filterwdays == true) {
7652 if (in_array($checkwday['wday'], $wdays)) {
7653 if (!isset($arr[$k]['affdayslist'])) {
7654 $arr[$k]['affdayslist'] = [];
7655 }
7656 $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;
7657 $arr[$k]['origdailycost'] = $a['cost'] / $a['days'];
7658 $affdayslistless[$s['id']][] = $checkwday['wday'].'-'.$checkwday['mday'].'-'.$checkwday['mon'];
7659 $affdays++;
7660 }
7661 } else {
7662 if (!isset($arr[$k]['affdayslist'])) {
7663 $arr[$k]['affdayslist'] = [];
7664 }
7665 $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;
7666 $arr[$k]['origdailycost'] = $a['cost'] / $a['days'];
7667 $affdayslistless[$s['id']][] = $checkwday['wday'].'-'.$checkwday['mday'].'-'.$checkwday['mon'];
7668 $affdays++;
7669 }
7670 //
7671 }
7672 }
7673
7674 if (!($affdays > 0)) {
7675 // no nights affected
7676 continue;
7677 }
7678
7679 // apply the rule
7680 $applyseasons = true;
7681 $dailyprice = $a['cost'] / $a['days'];
7682
7683 // modification factor object
7684 $factor = new stdClass;
7685
7686 // calculate new price progressively
7687 if (intval($s['val_pcent']) == 2) {
7688 // percentage value
7689 $factor->pcent = 1;
7690 $pctval = $s['diffcost'];
7691 if (strlen($s['losoverride']) > 0) {
7692 // values overrides
7693 $arrvaloverrides = [];
7694 $valovrparts = explode('_', $s['losoverride']);
7695 foreach ($valovrparts as $valovr) {
7696 if (!empty($valovr)) {
7697 $ovrinfo = explode(':', $valovr);
7698 if (strstr($ovrinfo[0], '-i') != false) {
7699 $ovrinfo[0] = str_replace('-i', '', $ovrinfo[0]);
7700 if ((int)$ovrinfo[0] < $a['days']) {
7701 $arrvaloverrides[$a['days']] = $ovrinfo[1];
7702 }
7703 }
7704 $arrvaloverrides[$ovrinfo[0]] = $ovrinfo[1];
7705 }
7706 }
7707 if (isset($a['days']) && array_key_exists($a['days'], $arrvaloverrides)) {
7708 $pctval = $arrvaloverrides[$a['days']];
7709 }
7710 }
7711 if (intval($s['type']) == 1) {
7712 // charge
7713 $factor->type = '+';
7714 $cpercent = 100 + $pctval;
7715 } else {
7716 // discount
7717 $factor->type = '-';
7718 $cpercent = 100 - $pctval;
7719 }
7720 $factor->amount = $pctval;
7721 $dailysum = ($dailyprice * $cpercent / 100);
7722 $newprice = $dailysum * $affdays;
7723 } else {
7724 // absolute value
7725 $factor->pcent = 0;
7726 $absval = $s['diffcost'];
7727 if (strlen($s['losoverride']) > 0) {
7728 // values overrides
7729 $arrvaloverrides = [];
7730 $valovrparts = explode('_', $s['losoverride']);
7731 foreach ($valovrparts as $valovr) {
7732 if (!empty($valovr)) {
7733 $ovrinfo = explode(':', $valovr);
7734 if (strstr($ovrinfo[0], '-i') != false) {
7735 $ovrinfo[0] = str_replace('-i', '', $ovrinfo[0]);
7736 if ((int)$ovrinfo[0] < $a['days']) {
7737 $arrvaloverrides[$a['days']] = $ovrinfo[1];
7738 }
7739 }
7740 $arrvaloverrides[$ovrinfo[0]] = $ovrinfo[1];
7741 }
7742 }
7743 if (isset($a['days']) && array_key_exists($a['days'], $arrvaloverrides)) {
7744 $absval = $arrvaloverrides[$a['days']];
7745 }
7746 }
7747 if (intval($s['type']) == 1) {
7748 // charge
7749 $factor->type = '+';
7750 $dailysum = ($dailyprice + $absval);
7751 $newprice = $dailysum * $affdays;
7752 } else {
7753 // discount
7754 $factor->type = '-';
7755 $dailysum = ($dailyprice - $absval);
7756 $newprice = $dailysum * $affdays;
7757 }
7758 $factor->amount = $absval;
7759 }
7760
7761 // apply rounding
7762 $factor->roundmode = $s['roundmode'];
7763 if (!empty($s['roundmode'])) {
7764 $newprice = round($newprice, 0, constant($s['roundmode']));
7765 } else {
7766 $newprice = round($newprice, 2);
7767 }
7768
7769 // define the promotion (only if no value overrides set the amount to 0)
7770 if ($promotion && ((isset($absval) && $absval > 0) || $pctval > 0)) {
7771 /**
7772 * Include the discount information (if any). The cost re-calculated may not be
7773 * precise if multiple special prices were applied over the same dates.
7774 *
7775 * @since 1.13
7776 */
7777 if ($s['type'] == 2 && $s['diffcost'] > 0) {
7778 $promotion['discount'] = [
7779 'amount' => $s['diffcost'],
7780 'pcent' => (int)($s['val_pcent'] == 2),
7781 ];
7782 }
7783 //
7784 $mem[$k]['promotion'] = $promotion;
7785 }
7786
7787 // define the occupancy override
7788 if (array_key_exists($a['idroom'], $occupancy_ovr) && $occupancy_ovr[$a['idroom']]) {
7789 $mem[$k]['occupancy_ovr'] = $occupancy_ovr[$a['idroom']];
7790 }
7791
7792 // affected days list
7793 if (isset($arr[$k]['affdayslist']) && is_array($arr[$k]['affdayslist'])) {
7794 foreach ($arr[$k]['affdayslist'] as $affk => $affv) {
7795 if (isset($affdayslistless[$s['id']]) && in_array($affk, $affdayslistless[$s['id']])) {
7796 $arr[$k]['affdayslist'][$affk] = !empty($arr[$k]['affdayslist'][$affk]) && $arr[$k]['affdayslist'][$affk] > 0 ? ($arr[$k]['affdayslist'][$affk] - $arr[$k]['origdailycost'] + $dailysum) : ($affv + $dailysum);
7797 }
7798 }
7799 }
7800
7801 // push special price ID
7802 if (!in_array($s['id'], $mem[$k]['spids'])) {
7803 array_push($mem[$k]['spids'], $s['id']);
7804 }
7805
7806 // push difference generated only if to be applied progressively
7807 if (!$s['promo'] || ($s['promo'] && !$s['promofinalprice'])) {
7808 /**
7809 * Push the difference generated by this special price for later transliteration of final price,
7810 * only if the special price is calculated progressively and not on the final price.
7811 *
7812 * @since 1.13.5
7813 */
7814 array_push($mem[$k]['diffs'], ($newprice - ($dailyprice * $affdays)));
7815 } elseif ($s['promo'] && $s['promofinalprice'] && $factor->pcent) {
7816 /**
7817 * This is a % promotion to be applied on the final price, so we need to save that this memory key
7818 * will need the transliteration, aka adjusting this new price by applying the charge/discount on
7819 * all differences applied by the previous special pricing rules.
7820 *
7821 * @since 1.13.5
7822 */
7823 array_push($mem[$k]['trans_keys'], count($mem[$k]['sum']));
7824 array_push($mem[$k]['trans_factors'], $factor);
7825 array_push($mem[$k]['trans_affdays'], $affdays);
7826 }
7827
7828 // push values in memory array
7829 array_push($mem[$k]['sum'], $newprice);
7830 $mem[$k]['daysused'] += $affdays;
7831 array_push($roomschange, $a['idroom']);
7832 }
7833 }
7834 if ($applyseasons) {
7835 foreach ($mem as $k => $v) {
7836 if ($v['daysused'] > 0 && $v['sum']) {
7837 $newprice = 0;
7838 $dailyprice = $arr[$k]['cost'] / $arr[$k]['days'];
7839 $restdays = $arr[$k]['days'] - $v['daysused'];
7840 $addrest = $restdays * $dailyprice;
7841 $newprice += $addrest;
7842
7843 // calculate new final cost
7844 $redo_rounding = null;
7845 foreach ($v['sum'] as $sum_index => $add) {
7846 /**
7847 * The application of the various special pricing rules is made in a progressive and cumulative way
7848 * by always starting from the room base cost or its average daily cost. However, promotions may need
7849 * to be applied on the room final cost, and not in a progresive way. In order to keep the progressive
7850 * algorithm, for applying the special prices on the room final cost we need to apply the same promotion
7851 * onto the differences generated by all the regular and progressively applied special pricing rules.
7852 *
7853 * @since 1.13.5
7854 */
7855 if (in_array($sum_index, $v['trans_keys']) && $v['diffs']) {
7856 /**
7857 * This progressive price difference must be applied on the room final cost, so we need to
7858 * apply the transliteration over the other differences applied by other special prices.
7859 */
7860 $transliterate_key = array_search($sum_index, $v['trans_keys']);
7861 if ($transliterate_key !== false && isset($v['trans_factors'][$transliterate_key])) {
7862 // this is the % promotion we are looking for applying it on the final cost
7863 $factor = $v['trans_factors'][$transliterate_key];
7864 if (is_object($factor) && $factor->pcent) {
7865 $final_factor = 0;
7866 foreach ($v['diffs'] as $diff_index => $prog_diff) {
7867 $final_factor += $prog_diff * $factor->amount / 100;
7868 }
7869 // update rounding
7870 $redo_rounding = !empty($factor->roundmode) ? $factor->roundmode : $redo_rounding;
7871
7872 /**
7873 * Leverage the final factor depending on how many nights this affected.
7874 *
7875 * @since 1.15.0 (J) - 1.5.0 (WP)
7876 */
7877 if (isset($v['trans_affdays'][$transliterate_key]) && $v['trans_affdays'][$transliterate_key] < $arr[$k]['days']) {
7878 $avg_final_factor = $final_factor / $arr[$k]['days'];
7879 $final_factor = $avg_final_factor * $v['trans_affdays'][$transliterate_key];
7880 }
7881
7882 // apply the final transliteration to obtain a value like if it was applied on the room's final cost
7883 $add = $factor->type == '+' ? ($add + $final_factor) : ($add - $final_factor);
7884 }
7885 }
7886 }
7887
7888 // apply new price progressively
7889 $newprice += $add;
7890 }
7891
7892 // apply rounding from factor
7893 if (!empty($redo_rounding)) {
7894 $newprice = round($newprice, 0, constant($redo_rounding));
7895 }
7896
7897 // set promotion (if any)
7898 if (isset($v['promotion'])) {
7899 $arr[$k]['promotion'] = $v['promotion'];
7900 }
7901
7902 // set occupancy overrides (if any)
7903 if (isset($v['occupancy_ovr'])) {
7904 $arr[$k]['occupancy_ovr'] = $v['occupancy_ovr'];
7905 }
7906
7907 // set new final cost and update nights affected
7908 $arr[$k]['cost'] = $newprice;
7909 $arr[$k]['affdays'] = $v['daysused'];
7910 if (array_key_exists('spids', $v) && $v['spids']) {
7911 $arr[$k]['spids'] = $v['spids'];
7912 }
7913 }
7914 }
7915 }
7916 }
7917
7918 // week days with no season
7919 $roomschange = array_unique($roomschange);
7920 $totspecials = 0;
7921 if (!$seasons_wdays) {
7922 $q = "SELECT * FROM `#__vikbooking_seasons` WHERE ((`from` = 0 AND `to` = 0) OR (`from` IS NULL AND `to` IS NULL));";
7923 $dbo->setQuery($q);
7924 $specials = $dbo->loadAssocList();
7925 $totspecials = $specials ? count($specials) : 0;
7926 }
7927 if ($totspecials > 0 || $seasons_wdays) {
7928 $specials = $totspecials > 0 ? $specials : $seasons_wdays;
7929 $vbo_tn->translateContents($specials, '#__vikbooking_seasons');
7930 $applyseasons = false;
7931 /**
7932 * We no longer unset the previous memory of the seasons with dates filters
7933 * because we need the responses to be merged. We do it only if not set.
7934 * We only keep the property 'spids' but the others should be unset.
7935 *
7936 * @since 1.11
7937 */
7938 if (!isset($mem)) {
7939 $mem = [];
7940 foreach ($arr as $k => $a) {
7941 $mem[$k]['daysused'] = 0;
7942 $mem[$k]['sum'] = [];
7943 $mem[$k]['spids'] = [];
7944 }
7945 } else {
7946 foreach ($arr as $k => $a) {
7947 $mem[$k]['daysused'] = 0;
7948 $mem[$k]['sum'] = [];
7949 }
7950 }
7951 //
7952 foreach ($specials as $s) {
7953 // 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
7954 if (!empty($s['from']) || !empty($s['to'])) {
7955 continue;
7956 }
7957
7958 // check if this is a promotion registered for skipping
7959 if (isset($s['promo']) && $s['promo'] && in_array($s['id'], $skip_promo_ids)) {
7960 continue;
7961 }
7962
7963 // Special Price tied to the year
7964 if (!empty($s['year']) && $s['year'] > 0) {
7965 if ($one['year'] != $s['year']) {
7966 continue;
7967 }
7968 }
7969
7970 $allrooms = !empty($s['idrooms']) ? explode(",", $s['idrooms']) : [];
7971 $allprices = !empty($s['idprices']) ? explode(",", $s['idprices']) : [];
7972 // week days
7973 $filterwdays = !empty($s['wdays']) ? true : false;
7974 $wdays = $filterwdays == true ? explode(';', $s['wdays']) : '';
7975 if (is_array($wdays) && $wdays) {
7976 foreach ($wdays as $kw => $wd) {
7977 if (strlen($wd) == 0) {
7978 unset($wdays[$kw]);
7979 }
7980 }
7981 }
7982
7983 foreach ($arr as $k => $a) {
7984 // only rooms with no price modifications from seasons
7985
7986 // applied only to some types of price
7987 if ($allprices && !empty($allprices[0])) {
7988 // VikBooking 1.6: Price Calendar sets the idprice to -1
7989 if (!in_array("-" . $a['idprice'] . "-", $allprices) && $a['idprice'] > 0) {
7990 continue;
7991 }
7992 }
7993
7994 /**
7995 * We should not exclude the rooms that already had a modification of the price through a season
7996 * with a dates filter or we risk to get invalid prices by skipping a rule for just some weekdays.
7997 * The control " || in_array($a['idroom'], $roomschange)" was removed from the IF below.
7998 *
7999 * @since 1.11
8000 */
8001 if (!in_array("-" . $a['idroom'] . "-", $allrooms)) {
8002 continue;
8003 }
8004
8005 if (isset($a['days']) && is_float($a['days'])) {
8006 // prevent any possible warning with array_key_exists()
8007 $a['days'] = (int)$a['days'];
8008 }
8009
8010 $affdays = 0;
8011 $season_fromdayts = $fromdayts;
8012 $is_dst = date('I', $season_fromdayts);
8013 for ($i = 0; $i < $a['days']; $i++) {
8014 $todayts = $season_fromdayts + ($i * 86400);
8015 $is_now_dst = date('I', $todayts);
8016 if ($is_dst != $is_now_dst) {
8017 // Daylight Saving Time has changed, check how
8018 if ((bool)$is_dst === true) {
8019 $todayts += 3600;
8020 $season_fromdayts += 3600;
8021 } else {
8022 $todayts -= 3600;
8023 $season_fromdayts -= 3600;
8024 }
8025 $is_dst = $is_now_dst;
8026 }
8027 // week days
8028 if ($filterwdays == true) {
8029 $checkwday = getdate($todayts);
8030 if (in_array($checkwday['wday'], $wdays)) {
8031 $arr[$k]['affdayslist'][$checkwday['wday'].'-'.$checkwday['mday'].'-'.$checkwday['mon']] = 0;
8032 $arr[$k]['origdailycost'] = $a['cost'] / $a['days'];
8033 $affdays++;
8034 }
8035 }
8036 //
8037 }
8038
8039 if (!($affdays > 0)) {
8040 // no nights affected
8041 continue;
8042 }
8043
8044 // apply the rule
8045 $applyseasons = true;
8046 $dailyprice = $a['cost'] / $a['days'];
8047
8048 if (intval($s['val_pcent']) == 2) {
8049 // percentage value
8050 $pctval = $s['diffcost'];
8051 if (strlen($s['losoverride']) > 0) {
8052 // values overrides
8053 $arrvaloverrides = [];
8054 $valovrparts = explode('_', $s['losoverride']);
8055 foreach ($valovrparts as $valovr) {
8056 if (!empty($valovr)) {
8057 $ovrinfo = explode(':', $valovr);
8058 if (strstr($ovrinfo[0], '-i') != false) {
8059 $ovrinfo[0] = str_replace('-i', '', $ovrinfo[0]);
8060 if ((int)$ovrinfo[0] < $a['days']) {
8061 $arrvaloverrides[$a['days']] = $ovrinfo[1];
8062 }
8063 }
8064 $arrvaloverrides[$ovrinfo[0]] = $ovrinfo[1];
8065 }
8066 }
8067 if (isset($a['days']) && array_key_exists($a['days'], $arrvaloverrides)) {
8068 $pctval = $arrvaloverrides[$a['days']];
8069 }
8070 }
8071 if (intval($s['type']) == 1) {
8072 // charge
8073 $cpercent = 100 + $pctval;
8074 } else {
8075 // discount
8076 $cpercent = 100 - $pctval;
8077 }
8078 $dailysum = ($dailyprice * $cpercent / 100);
8079 $newprice = $dailysum * $affdays;
8080 } else {
8081 // absolute value
8082 $absval = $s['diffcost'];
8083 if (strlen($s['losoverride']) > 0) {
8084 // values overrides
8085 $arrvaloverrides = [];
8086 $valovrparts = explode('_', $s['losoverride']);
8087 foreach ($valovrparts as $valovr) {
8088 if (!empty($valovr)) {
8089 $ovrinfo = explode(':', $valovr);
8090 if (strstr($ovrinfo[0], '-i') != false) {
8091 $ovrinfo[0] = str_replace('-i', '', $ovrinfo[0]);
8092 if ((int)$ovrinfo[0] < $a['days']) {
8093 $arrvaloverrides[$a['days']] = $ovrinfo[1];
8094 }
8095 }
8096 $arrvaloverrides[$ovrinfo[0]] = $ovrinfo[1];
8097 }
8098 }
8099 if (isset($a['days']) && array_key_exists($a['days'], $arrvaloverrides)) {
8100 $absval = $arrvaloverrides[$a['days']];
8101 }
8102 }
8103 if (intval($s['type']) == 1) {
8104 // charge
8105 $dailysum = ($dailyprice + $absval);
8106 $newprice = $dailysum * $affdays;
8107 } else {
8108 // discount
8109 $dailysum = ($dailyprice - $absval);
8110 $newprice = $dailysum * $affdays;
8111 }
8112 }
8113
8114 // apply rounding
8115 if (!empty($s['roundmode'])) {
8116 $newprice = round($newprice, 0, constant($s['roundmode']));
8117 } else {
8118 $newprice = round($newprice, 2);
8119 }
8120
8121 foreach ($arr[$k]['affdayslist'] as $affk => $affv) {
8122 $arr[$k]['affdayslist'][$affk] = $affv + $dailysum;
8123 }
8124 if (!in_array($s['id'], $mem[$k]['spids'])) {
8125 $mem[$k]['spids'][] = $s['id'];
8126 }
8127 $mem[$k]['sum'][] = $newprice;
8128 $mem[$k]['daysused'] += $affdays;
8129 }
8130 }
8131 if ($applyseasons) {
8132 foreach ($mem as $k => $v) {
8133 if ($v['daysused'] > 0 && $v['sum']) {
8134 $newprice = 0;
8135 $dailyprice = $arr[$k]['cost'] / $arr[$k]['days'];
8136 $restdays = $arr[$k]['days'] - $v['daysused'];
8137 $addrest = $restdays * $dailyprice;
8138 $newprice += $addrest;
8139 foreach ($v['sum'] as $add) {
8140 $newprice += $add;
8141 }
8142 $arr[$k]['cost'] = $newprice;
8143 $arr[$k]['affdays'] = $v['daysused'];
8144 if (array_key_exists('spids', $v) && $v['spids']) {
8145 $arr[$k]['spids'] = $v['spids'];
8146 }
8147 }
8148 }
8149 }
8150 }
8151 // end week days with no season
8152
8153 return $arr;
8154 }
8155
8156 public static function getRoomRplansClosingDates($idroom) {
8157 $dbo = JFactory::getDbo();
8158 $closingd = array();
8159 $q = "SELECT * FROM `#__vikbooking_prices` WHERE `closingd` IS NOT NULL;";
8160 $dbo->setQuery($q);
8161 $dbo->execute();
8162 if ($dbo->getNumRows() > 0) {
8163 $price_records = $dbo->loadAssocList();
8164 foreach ($price_records as $prec) {
8165 if (empty($prec['closingd'])) {
8166 continue;
8167 }
8168 $price_closing = json_decode($prec['closingd'], true);
8169 if (!is_array($price_closing) || !count($price_closing) || !array_key_exists($idroom, $price_closing) || !count($price_closing[$idroom])) {
8170 continue;
8171 }
8172 //check expired dates and clean up
8173 $today_midnight = mktime(0, 0, 0);
8174 $cleaned = false;
8175 foreach ($price_closing[$idroom] as $k => $v) {
8176 if (strtotime($v) < $today_midnight) {
8177 $cleaned = true;
8178 unset($price_closing[$idroom][$k]);
8179 }
8180 }
8181 //
8182 if (!count($price_closing[$idroom])) {
8183 unset($price_closing[$idroom]);
8184 } elseif ($cleaned === true) {
8185 //reset array keys for smaller JSON size
8186 $price_closing[$idroom] = array_values($price_closing[$idroom]);
8187 }
8188 if ($cleaned === true) {
8189 $q = "UPDATE `#__vikbooking_prices` SET `closingd`=".(count($price_closing) > 0 ? $dbo->quote(json_encode($price_closing)) : "NULL")." WHERE `id`=".$prec['id'].";";
8190 $dbo->setQuery($q);
8191 $dbo->execute();
8192 }
8193 if (!isset($price_closing[$idroom]) || !count($price_closing[$idroom])) {
8194 continue;
8195 }
8196 $closingd[$prec['id']] = $price_closing[$idroom];
8197 }
8198 }
8199 return $closingd;
8200 }
8201
8202 public static function getRoomRplansClosedInDates($roomids, $checkints, $numnights) {
8203 $dbo = JFactory::getDbo();
8204 $closingd = array();
8205 $q = "SELECT * FROM `#__vikbooking_prices` WHERE `closingd` IS NOT NULL;";
8206 $dbo->setQuery($q);
8207 $dbo->execute();
8208 if ($dbo->getNumRows() > 0 && count($roomids) > 0) {
8209 $price_records = $dbo->loadAssocList();
8210 $info_start = getdate($checkints);
8211 $checkin_midnight = mktime(0, 0, 0, $info_start['mon'], $info_start['mday'], $info_start['year']);
8212 $all_nights = array();
8213 for ($i=0; $i < (int)$numnights; $i++) {
8214 $next_midnight = mktime(0, 0, 0, $info_start['mon'], ($info_start['mday'] + $i), $info_start['year']);
8215 $all_nights[] = date('Y-m-d', $next_midnight);
8216 }
8217 foreach ($price_records as $prec) {
8218 if (empty($prec['closingd'])) {
8219 continue;
8220 }
8221 $price_closing = json_decode($prec['closingd'], true);
8222 if (!is_array($price_closing) || !count($price_closing)) {
8223 continue;
8224 }
8225 foreach ($price_closing as $idroom => $rclosedd) {
8226 if (!in_array($idroom, $roomids) || !is_array($rclosedd)) {
8227 continue;
8228 }
8229 if (!array_key_exists($idroom, $closingd)) {
8230 $closingd[$idroom] = array();
8231 }
8232 foreach ($all_nights as $night) {
8233 if (in_array($night, $rclosedd)) {
8234 if (array_key_exists($prec['id'], $closingd[$idroom])) {
8235 $closingd[$idroom][$prec['id']][] = $night;
8236 } else {
8237 $closingd[$idroom][$prec['id']] = array($night);
8238 }
8239 }
8240 }
8241 }
8242 }
8243 }
8244
8245 return $closingd;
8246 }
8247
8248 /**
8249 * Tells whether at least one payment method must be selected to check-out.
8250 *
8251 * @param array $rooms_involved optional list of room IDs being booked.
8252 *
8253 * @return bool true if at least one payment method is applicable.
8254 *
8255 * @since 1.16.0 (J) - 1.6.0 (WP) added 1st argument to check the room associations.
8256 */
8257 public static function areTherePayments($rooms_involved = [])
8258 {
8259 $dbo = JFactory::getDbo();
8260
8261 $q = "SELECT `id`, `idrooms` FROM `#__vikbooking_gpayments` WHERE `published`=1;";
8262 $dbo->setQuery($q);
8263 $dbo->execute();
8264
8265 if (!$dbo->getNumRows()) {
8266 // no published payment options found
8267 return false;
8268 }
8269
8270 $pay_options = $dbo->loadAssocList();
8271
8272 if (!count($rooms_involved)) {
8273 // if no filters given, return true
8274 return true;
8275 }
8276
8277 // validate room associations
8278 foreach ($pay_options as $pay_k => $pay_v) {
8279 if (empty($pay_v['idrooms'])) {
8280 continue;
8281 }
8282 $rooms_supported = json_decode($pay_v['idrooms']);
8283 if (!is_array($rooms_supported) || !count($rooms_supported)) {
8284 continue;
8285 }
8286 // make sure all rooms being booked are supported
8287 foreach ($rooms_involved as $room_id_booked) {
8288 if (!in_array($room_id_booked, $rooms_supported)) {
8289 // we need to unset this payment option as soon as we find one non-matching room ID
8290 unset($pay_options[$pay_k]);
8291 break;
8292 }
8293 }
8294 }
8295
8296 return (bool)count($pay_options);
8297 }
8298
8299 public static function getPayment($idp, $vbo_tn = null)
8300 {
8301 if (empty($idp)) {
8302 return false;
8303 }
8304
8305 if (strstr((string)$idp, '=') !== false) {
8306 $parts = explode('=', $idp);
8307 $idp = $parts[0];
8308 }
8309
8310 $dbo = JFactory::getDbo();
8311
8312 $q = "SELECT * FROM `#__vikbooking_gpayments` WHERE `id`=" . $dbo->quote($idp) . ";";
8313 $dbo->setQuery($q);
8314 $payment = $dbo->loadAssocList();
8315 if (!$payment) {
8316 return false;
8317 }
8318
8319 if (is_object($vbo_tn)) {
8320 $vbo_tn->translateContents($payment, '#__vikbooking_gpayments');
8321 }
8322
8323 return $payment[0];
8324 }
8325
8326 public static function getCronKey() {
8327 $dbo = JFactory::getDbo();
8328 $ckey = '';
8329 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='cronkey';";
8330 $dbo->setQuery($q);
8331 $dbo->execute();
8332 if ($dbo->getNumRows() > 0) {
8333 $cval = $dbo->loadAssocList();
8334 $ckey = $cval[0]['setting'];
8335 }
8336 return $ckey;
8337 }
8338
8339 public static function getNextInvoiceNumber () {
8340 $dbo = JFactory::getDbo();
8341 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='invoiceinum';";
8342 $dbo->setQuery($q);
8343 $dbo->execute();
8344 $s = $dbo->loadAssocList();
8345 return (intval($s[0]['setting']) + 1);
8346 }
8347
8348 public static function getInvoiceNumberSuffix () {
8349 $dbo = JFactory::getDbo();
8350 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='invoicesuffix';";
8351 $dbo->setQuery($q);
8352 $dbo->execute();
8353 $s = $dbo->loadAssocList();
8354 return $s[0]['setting'];
8355 }
8356
8357 public static function getInvoiceCompanyInfo () {
8358 $dbo = JFactory::getDbo();
8359 $q="SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='invcompanyinfo';";
8360 $dbo->setQuery($q);
8361 $dbo->execute();
8362 $s=$dbo->loadAssocList();
8363 return $s[0]['setting'];
8364 }
8365
8366 /**
8367 * Gets the number for the next booking receipt generation,
8368 * updates the last receipt number used for the later iterations,
8369 * stores a new receipt record to keep track of the receipts issued.
8370 *
8371 * @param int $bid the Booking ID for which we are/want to generating the receipt.
8372 * @param [int] $newnum the last number used for generating the receipt.
8373 *
8374 * @return int
8375 */
8376 public static function getNextReceiptNumber ($bid, $newnum = false) {
8377 $dbo = JFactory::getDbo();
8378 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='receiptinum';";
8379 $dbo->setQuery($q);
8380 $dbo->execute();
8381 if ($dbo->getNumRows() > 0) {
8382 $s = $dbo->loadResult();
8383 //check if this booking has already a receipt, and return that number
8384 $prev_receipt = array();
8385 $q = "SELECT * FROM `#__vikbooking_receipts` WHERE `idorder`=".(int)$bid.";";
8386 $dbo->setQuery($q);
8387 $dbo->execute();
8388 if ($dbo->getNumRows() > 0) {
8389 $prev_receipt = $dbo->loadAssoc();
8390 }
8391 //update value (receipt generated)
8392 if ($newnum !== false && $newnum > 0) {
8393 $s = (int)$newnum;
8394 if (!(count($prev_receipt) > 0)) {
8395 $q = "UPDATE `#__vikbooking_config` SET `setting`=".$s." WHERE `param`='receiptinum';";
8396 $dbo->setQuery($q);
8397 $dbo->execute();
8398 //insert the new receipt record
8399 $q = "INSERT INTO `#__vikbooking_receipts` (`number`,`idorder`,`created_on`) VALUES (".(int)$newnum.", ".(int)$bid.", ".time().");";
8400 $dbo->setQuery($q);
8401 $dbo->execute();
8402 } else {
8403 //update receipt record
8404 $q = "UPDATE `#__vikbooking_receipts` SET `number`=".(int)$newnum.",`created_on`=".time()." WHERE `idorder`=".(int)$bid.";";
8405 $dbo->setQuery($q);
8406 $dbo->execute();
8407 }
8408 }
8409 //
8410 return count($prev_receipt) > 0 ? (int)$prev_receipt['number'] : ((int)$s + 1);
8411 }
8412 //first execution of the method should create the configuration record
8413 $q = "INSERT INTO `#__vikbooking_config` (`param`,`setting`) VALUES ('receiptinum', '0');";
8414 $dbo->setQuery($q);
8415 $dbo->execute();
8416 return 1;
8417 }
8418
8419 public static function getReceiptNotes ($newnotes = false) {
8420 $dbo = JFactory::getDbo();
8421 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='receiptnotes';";
8422 $dbo->setQuery($q);
8423 $dbo->execute();
8424 if ($dbo->getNumRows() > 0) {
8425 $s = $dbo->loadResult();
8426 //update value
8427 if ($newnotes !== false) {
8428 $s = $newnotes;
8429 $q = "UPDATE `#__vikbooking_config` SET `setting`=".$dbo->quote($s)." WHERE `param`='receiptnotes';";
8430 $dbo->setQuery($q);
8431 $dbo->execute();
8432 }
8433 //
8434 return $s;
8435 }
8436 $q = "INSERT INTO `#__vikbooking_config` (`param`,`setting`) VALUES ('receiptnotes', '');";
8437 $dbo->setQuery($q);
8438 $dbo->execute();
8439 return "";
8440 }
8441
8442 public static function loadColorTagsRules()
8443 {
8444 $color_tag_rules = [
8445 0 => 'VBOCOLORTAGRULECUSTOMCOLOR',
8446 'pend1' => 'VBWAITINGFORPAYMENT',
8447 'conf1' => 'VBDBTEXTROOMCLOSED',
8448 'conf2' => 'VBOCOLORTAGRULECONFTWO',
8449 'conf3' => 'VBOCOLORTAGRULECONFTHREE',
8450 'inv1' => 'VBOCOLORTAGRULEINVONE',
8451 'rcp1' => 'VBOCOLORTAGRULERCPONE',
8452 'conf4' => 'VBOCOLORTAGRULECONFFOUR',
8453 'conf5' => 'VBOCOLORTAGRULECONFFIVE',
8454 'inv2' => 'VBOCOLORTAGRULEINVTWO',
8455 ];
8456
8457 /**
8458 * Trigger event to let third-party plugins override the default booking color tag rules.
8459 *
8460 * @since 1.16.4 (J) - 1.6.4 (WP)
8461 */
8462 VBOFactory::getPlatform()->getDispatcher()->trigger('onBeforeLoadColorTagRules', [&$color_tag_rules]);
8463
8464 return $color_tag_rules;
8465 }
8466
8467 public static function loadDefaultColorTags()
8468 {
8469 $default_color_tags = [
8470 [
8471 'color' => '#9b9b9b',
8472 'name' => 'VBWAITINGFORPAYMENT',
8473 'rule' => 'pend1'
8474 ],
8475 [
8476 'color' => '#333333',
8477 'name' => 'VBDBTEXTROOMCLOSED',
8478 'rule' => 'conf1'
8479 ],
8480 [
8481 'color' => '#ff8606',
8482 'name' => 'VBOCOLORTAGRULECONFTWO',
8483 'rule' => 'conf2'
8484 ],
8485 [
8486 'color' => '#0418c9',
8487 'name' => 'VBOCOLORTAGRULECONFTHREE',
8488 'rule' => 'conf3'
8489 ],
8490 [
8491 'color' => '#bed953',
8492 'name' => 'VBOCOLORTAGRULEINVONE',
8493 'rule' => 'inv1'
8494 ],
8495 [
8496 'color' => '#67f5b5',
8497 'name' => 'VBOCOLORTAGRULERCPONE',
8498 'rule' => 'rcp1'
8499 ],
8500 [
8501 'color' => '#04d2c2',
8502 'name' => 'VBOCOLORTAGRULECONFFOUR',
8503 'rule' => 'conf4'
8504 ],
8505 [
8506 'color' => '#00b316',
8507 'name' => 'VBOCOLORTAGRULECONFFIVE',
8508 'rule' => 'conf5'
8509 ],
8510 [
8511 'color' => '#00f323',
8512 'name' => 'VBOCOLORTAGRULEINVTWO',
8513 'rule' => 'inv2'
8514 ],
8515 ];
8516
8517 /**
8518 * Trigger event to let third-party plugins override the default color tags.
8519 *
8520 * @since 1.16.4 (J) - 1.6.4 (WP)
8521 */
8522 VBOFactory::getPlatform()->getDispatcher()->trigger('onBeforeLoadDefaultColorTags', [&$default_color_tags]);
8523
8524 return $default_color_tags;
8525 }
8526
8527 public static function loadBookingsColorTags()
8528 {
8529 $dbo = JFactory::getDbo();
8530
8531 $bookings_ctags = VBOFactory::getConfig()->getArray('bookingsctags', []);
8532
8533 return is_array($bookings_ctags) && $bookings_ctags ? $bookings_ctags : self::loadDefaultColorTags();
8534 }
8535
8536 public static function getBestColorContrast($hexcolor)
8537 {
8538 $hexcolor = str_replace('#', '', $hexcolor);
8539
8540 if (empty($hexcolor) || strlen($hexcolor) != 6) {
8541 return '#000000';
8542 }
8543
8544 $r = hexdec(substr($hexcolor, 0, 2));
8545 $g = hexdec(substr($hexcolor, 2, 2));
8546 $b = hexdec(substr($hexcolor, 4, 2));
8547
8548 // Counting the perceptive luminance - human eye favors green color
8549 // < 0.5 bright colors
8550 // > 0.5 dark colors
8551
8552 return (1 - ( 0.299 * $r + 0.587 * $g + 0.114 * $b ) / 255) < 0.5 ? '#000000' : '#ffffff';
8553 }
8554
8555 public static function applyBookingColorTag($booking, $tags = [])
8556 {
8557 if (!is_array($tags) || !$tags) {
8558 $tags = self::loadBookingsColorTags();
8559 }
8560
8561 if (!empty($booking['colortag'])) {
8562 $color_tag_arr = json_decode($booking['colortag'], true);
8563 if (is_array($color_tag_arr) && array_key_exists('color', $color_tag_arr)) {
8564 $color_tag_arr['fontcolor'] = self::getBestColorContrast($color_tag_arr['color']);
8565 return $color_tag_arr;
8566 }
8567 }
8568
8569 $dbo = JFactory::getDbo();
8570
8571 $bid = array_key_exists('idorder', $booking) ? $booking['idorder'] : $booking['id'];
8572 $docs_data = [];
8573 $invoice_numb = false;
8574 $receipt_numb = false;
8575
8576 if ($booking['status'] == 'confirmed') {
8577 $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`
8578 FROM `#__vikbooking_orders` AS `b`
8579 LEFT JOIN `#__vikbooking_invoices` AS `i` ON `b`.`id`=`i`.`idorder`
8580 LEFT JOIN `#__vikbooking_receipts` AS `r` ON `b`.`id`=`r`.`idorder`
8581 WHERE `b`.`id`=" . (int)$bid . ";";
8582 $dbo->setQuery($q);
8583 $docs_data = $dbo->loadAssoc();
8584 if ($docs_data) {
8585 $invoice_numb = (!empty($docs_data['inv_id']));
8586 $receipt_numb = (!empty($docs_data['rcp_id']));
8587
8588 /**
8589 * We inject the invoice number or receipt number into the color tag rules.
8590 * This is useful for the Tableaux page to show the invoice/receipt number.
8591 *
8592 * @since 1.12 (J) - 1.1.7 (WP)
8593 */
8594 if ($invoice_numb || $receipt_numb) {
8595 foreach ($tags as &$tval) {
8596 if ($invoice_numb) {
8597 $tval['invoice_number'] = $docs_data['inv_number'];
8598 $tval['invoice_file_name'] = $docs_data['inv_file_name'];
8599 }
8600 if ($receipt_numb) {
8601 $tval['receipt_number'] = $docs_data['rcp_number'];
8602 }
8603 }
8604 // unset last reference
8605 unset($tval);
8606 }
8607 }
8608 }
8609
8610 /**
8611 * Some OTA bookings using an OTA-Collect business model may look as not entirely
8612 * paid when they actually are, due to commissions and pass through taxes. We also
8613 * take care of those OTA reservations that include a Virtual Credit Card (Hotel Collect).
8614 *
8615 * @since 1.16.3 (J) - 1.6.3 (WP)
8616 */
8617 $ota_collected_booking = false;
8618 $hotel_collect_vcc_unpaid = false;
8619 if ($booking['status'] == 'confirmed' && !empty($booking['idorderota']) && !empty($booking['channel']) && $booking['total'] > 0 && $booking['totpaid'] > 0) {
8620 if (stripos($booking['channel'], 'airbnb') !== false && ($booking['checkout'] < time() || date('Y-m-d', $booking['checkout']) == date('Y-m-d'))) {
8621 $ota_collected_booking = true;
8622 }
8623 if (!empty($booking['paymentlog']) && stripos($booking['paymentlog'], 'virtual credit card') !== false) {
8624 // VCC detected, check the history for a payment event
8625 $history_obj = self::getBookingHistoryInstance()->setBid($bid);
8626 // list of history types to fetch
8627 $h_payment_types = [];
8628 $h_payment_group = $history_obj->getTypeGroups($group = 'GPM');
8629 if ($h_payment_group && $h_payment_group['types']) {
8630 // merge default types with 'PU' for the manually set "new amount paid" and with 'RU' (refunded amount updated)
8631 $h_payment_types = array_unique(array_merge($h_payment_group['types'], ['PU', 'RU']));
8632 }
8633 $payment_ev_datas = $history_obj->getEventsWithData($h_payment_types, $callvalid = null, $onlydata = false);
8634 if (!$payment_ev_datas) {
8635 // this booking should be flagged as "unpaid" because a payment (manual/online) has never been recorded
8636 $hotel_collect_vcc_unpaid = true;
8637 }
8638 }
8639 }
8640
8641 foreach ($tags as $tkey => $tval) {
8642 if (empty($tval['rule'])) {
8643 continue;
8644 }
8645
8646 /**
8647 * Trigger event to let third-party plugins validate the custom rule.
8648 *
8649 * @since 1.16.4 (J) - 1.6.4 (WP)
8650 */
8651 $custom_rule = VBOFactory::getPlatform()->getDispatcher()->filter('onApplyBookingColorTag', [$ota_collected_booking, $hotel_collect_vcc_unpaid, $booking, $docs_data, $tval]);
8652 if (!empty($custom_rule)) {
8653 return (array) $custom_rule[0];
8654 }
8655
8656 switch ($tval['rule']) {
8657 case 'pend1':
8658 // Room is waiting for the payment (locked record)
8659 if ($booking['status'] == 'standby') {
8660 $q = "SELECT `id` FROM `#__vikbooking_tmplock` WHERE `idorder`=" . (int)$bid . " AND `until`>=" . time() . ";";
8661 $dbo->setQuery($q);
8662 $tmp_lock = $dbo->loadAssoc();
8663 if ($tmp_lock) {
8664 $tval['fontcolor'] = self::getBestColorContrast($tval['color']);
8665 return $tval;
8666 }
8667 }
8668 break;
8669 case 'conf1':
8670 // Confirmed (Room Closed)
8671 if ($booking['status'] == 'confirmed' && $booking['custdata'] == JText::translate('VBDBTEXTROOMCLOSED')) {
8672 $tval['fontcolor'] = self::getBestColorContrast($tval['color']);
8673 return $tval;
8674 }
8675 break;
8676 case 'conf2':
8677 // Confirmed (No Rate 0.00/NULL Total)
8678 if ($booking['status'] == 'confirmed' && (empty($booking['total']) || $booking['total'] <= 0.00 || $booking['total'] === null)) {
8679 $tval['fontcolor'] = self::getBestColorContrast($tval['color']);
8680 return $tval;
8681 }
8682 break;
8683 case 'conf3':
8684 // Confirmed (Total > 0 && Total Paid = 0 && No Invoice && No Receipt)
8685 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) {
8686 $tval['fontcolor'] = self::getBestColorContrast($tval['color']);
8687 return $tval;
8688 }
8689 break;
8690 case 'inv1':
8691 // Confirmed + Invoice (Total > 0 && Total Paid = 0 && Invoice Exists)
8692 if ($booking['status'] == 'confirmed' && $booking['total'] > 0 && (empty($booking['totpaid']) || $booking['totpaid'] <= 0.00 || $booking['totpaid'] === null) && $invoice_numb !== false) {
8693 $tval['fontcolor'] = self::getBestColorContrast($tval['color']);
8694 return $tval;
8695 }
8696 break;
8697 case 'rcp1':
8698 // Confirmed + Receipt Issued (Total > 0 && Total Paid = 0 && Receipt Issued)
8699 if ($booking['status'] == 'confirmed' && $booking['total'] > 0 && (empty($booking['totpaid']) || $booking['totpaid'] <= 0.00 || $booking['totpaid'] === null) && $receipt_numb !== false) {
8700 $tval['fontcolor'] = self::getBestColorContrast($tval['color']);
8701 return $tval;
8702 }
8703 break;
8704 case 'conf4':
8705 // Confirmed (Total > 0 && Total Paid > 0 && Total Paid < Total)
8706 if ($booking['status'] == 'confirmed' && $booking['total'] > 0 && $booking['totpaid'] > 0 && $booking['totpaid'] < $booking['total'] && !$ota_collected_booking) {
8707 $tval['fontcolor'] = self::getBestColorContrast($tval['color']);
8708 return $tval;
8709 }
8710 break;
8711 case 'conf5':
8712 // Confirmed (Total > 0 && Total Paid >= Total)
8713 if ($booking['status'] == 'confirmed' && $booking['total'] > 0 && $booking['totpaid'] > 0 && ($booking['totpaid'] >= $booking['total'] || $ota_collected_booking) && !$hotel_collect_vcc_unpaid) {
8714 $tval['fontcolor'] = self::getBestColorContrast($tval['color']);
8715 return $tval;
8716 }
8717 break;
8718 case 'inv2':
8719 // Confirmed + Invoice + Paid (Total > 0 && Total Paid >= Total && Invoice Exists)
8720 if ($booking['status'] == 'confirmed' && $booking['total'] > 0 && $booking['totpaid'] > 0 && ($booking['totpaid'] >= $booking['total'] || $ota_collected_booking) && $invoice_numb !== false) {
8721 $tval['fontcolor'] = self::getBestColorContrast($tval['color']);
8722 return $tval;
8723 }
8724 break;
8725 default:
8726 // unsupported rule
8727 break;
8728 }
8729 }
8730
8731 return [];
8732 }
8733
8734 public static function getBookingInfoFromID($bid)
8735 {
8736 $dbo = JFactory::getDbo();
8737
8738 $q = "SELECT * FROM `#__vikbooking_orders` WHERE `id`=" . (int)$bid . ";";
8739 $dbo->setQuery($q);
8740 $booking_info = $dbo->loadAssoc();
8741 if ($booking_info) {
8742 return $booking_info;
8743 }
8744
8745 return [];
8746 }
8747
8748 /**
8749 * Load booking details for a given list of IDs.
8750 *
8751 * @param int $roomid the room integer number.
8752 * @param array $room_bids_pool list of booking IDs involved.
8753 *
8754 * @return array
8755 */
8756 public static function loadRoomIndexesBookings($roomid, array $room_bids_pool)
8757 {
8758 $dbo = JFactory::getDbo();
8759
8760 if (empty($roomid) || !$room_bids_pool) {
8761 return [];
8762 }
8763
8764 $room_features_bookings = [];
8765 $roomid = (int)$roomid;
8766
8767 $all_bids = [];
8768 foreach ($room_bids_pool as $day => $bids) {
8769 $all_bids = array_merge($all_bids, $bids);
8770 }
8771 $all_bids = array_map('intval', array_unique($all_bids));
8772 $all_bids_str = implode(', ', $all_bids);
8773
8774 $q = "SELECT `or`.`id`,`or`.`idorder`,`or`.`roomindex` FROM `#__vikbooking_ordersrooms` AS `or` WHERE `or`.`idroom`={$roomid} AND `or`.`idorder` IN ({$all_bids_str})";
8775 $dbo->setQuery($q);
8776 $rbookings = $dbo->loadAssocList();
8777 if (!$rbookings) {
8778 return [];
8779 }
8780
8781 foreach ($rbookings as $k => $v) {
8782 if (empty($v['roomindex'])) {
8783 continue;
8784 }
8785 if (!isset($room_features_bookings[$v['roomindex']])) {
8786 $room_features_bookings[$v['roomindex']] = [];
8787 }
8788 $room_features_bookings[$v['roomindex']][] = $v['idorder'];
8789 }
8790
8791 return $room_features_bookings;
8792 }
8793
8794 public static function getSendEmailWhen() {
8795 $dbo = JFactory::getDbo();
8796 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='emailsendwhen';";
8797 $dbo->setQuery($q);
8798 $dbo->execute();
8799 if ($dbo->getNumRows() > 0) {
8800 $cval = $dbo->loadAssocList();
8801 return intval($cval[0]['setting']) > 1 ? 2 : 1;
8802 } else {
8803 $q = "INSERT INTO `#__vikbooking_config` (`param`,`setting`) VALUES ('emailsendwhen','1');";
8804 $dbo->setQuery($q);
8805 $dbo->execute();
8806 }
8807 return 1;
8808 }
8809
8810 public static function getMinutesAutoRemove() {
8811 $dbo = JFactory::getDbo();
8812 $minar = 0;
8813 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='minautoremove';";
8814 $dbo->setQuery($q);
8815 $dbo->execute();
8816 if ($dbo->getNumRows() > 0) {
8817 $minar = (int)$dbo->loadResult();
8818 }
8819 return $minar;
8820 }
8821
8822 public static function getSMSAPIClass() {
8823 $dbo = JFactory::getDbo();
8824 $cfile = '';
8825 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='smsapi';";
8826 $dbo->setQuery($q);
8827 $dbo->execute();
8828 if ($dbo->getNumRows() > 0) {
8829 $cval = $dbo->loadAssocList();
8830 $cfile = $cval[0]['setting'];
8831 }
8832 return $cfile;
8833 }
8834
8835 public static function autoSendSMSEnabled() {
8836 $dbo = JFactory::getDbo();
8837 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='smsautosend';";
8838 $dbo->setQuery($q);
8839 $dbo->execute();
8840 if ($dbo->getNumRows() > 0) {
8841 $cval = $dbo->loadAssocList();
8842 return intval($cval[0]['setting']) > 0 ? true : false;
8843 }
8844 return false;
8845 }
8846
8847 public static function getSendSMSTo() {
8848 $dbo = JFactory::getDbo();
8849 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='smssendto';";
8850 $dbo->setQuery($q);
8851 $dbo->execute();
8852 if ($dbo->getNumRows() > 0) {
8853 $cval = $dbo->loadAssocList();
8854 if (!empty($cval[0]['setting'])) {
8855 $sto = json_decode($cval[0]['setting'], true);
8856 if (is_array($sto)) {
8857 return $sto;
8858 }
8859 }
8860 }
8861 return array();
8862 }
8863
8864 public static function getSendSMSWhen() {
8865 $dbo = JFactory::getDbo();
8866 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='smssendwhen';";
8867 $dbo->setQuery($q);
8868 $dbo->execute();
8869 if ($dbo->getNumRows() > 0) {
8870 $cval = $dbo->loadAssocList();
8871 return intval($cval[0]['setting']) > 1 ? 2 : 1;
8872 }
8873 return 1;
8874 }
8875
8876 public static function getSMSAdminPhone() {
8877 $dbo = JFactory::getDbo();
8878 $pnum = '';
8879 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='smsadminphone';";
8880 $dbo->setQuery($q);
8881 $dbo->execute();
8882 if ($dbo->getNumRows() > 0) {
8883 $cval = $dbo->loadAssocList();
8884 $pnum = $cval[0]['setting'];
8885 }
8886 return $pnum;
8887 }
8888
8889 public static function getSMSParams($as_array = true) {
8890 $dbo = JFactory::getDbo();
8891 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='smsparams';";
8892 $dbo->setQuery($q);
8893 $dbo->execute();
8894 if ($dbo->getNumRows() > 0) {
8895 $cval = $dbo->loadAssocList();
8896 if (!empty($cval[0]['setting'])) {
8897 if (!$as_array) {
8898 return $cval[0]['setting'];
8899 }
8900 $sparams = json_decode($cval[0]['setting'], true);
8901 if (is_array($sparams)) {
8902 return $sparams;
8903 }
8904 }
8905 }
8906 return array();
8907 }
8908
8909 public static function getSMSTemplate($vbo_tn = null, $booking_status = 'confirmed', $type = 'admin') {
8910 $dbo = JFactory::getDbo();
8911 switch (strtolower($booking_status)) {
8912 case 'standby':
8913 $status = 'pend';
8914 break;
8915 case 'cancelled':
8916 $status = 'canc';
8917 break;
8918 default:
8919 $status = '';
8920 break;
8921 }
8922 $paramtype = 'sms'.$type.'tpl'.$status;
8923 $q = "SELECT `id`,`setting` FROM `#__vikbooking_texts` WHERE `param`='".$paramtype."';";
8924 $dbo->setQuery($q);
8925 $dbo->execute();
8926 if ($dbo->getNumRows() < 1) {
8927 if ($status == 'canc') {
8928 //Type cancelled is used by VCM since v1.6.6
8929 $q = "INSERT INTO `#__vikbooking_texts` (`param`,`exp`,`setting`) VALUES ('".$paramtype."','".($type == 'admin' ? 'Administrator' : 'Customer')." SMS Template (Cancelled)','');";
8930 $dbo->setQuery($q);
8931 $dbo->execute();
8932 }
8933 return '';
8934 }
8935 $ft = $dbo->loadAssocList();
8936 if (is_object($vbo_tn)) {
8937 $vbo_tn->translateContents($ft, '#__vikbooking_texts');
8938 }
8939 return $ft[0]['setting'];
8940 }
8941
8942 public static function getSMSAdminTemplate($vbo_tn = null, $booking_status = 'confirmed') {
8943 return self::getSMSTemplate($vbo_tn, $booking_status, 'admin');
8944 }
8945
8946 public static function getSMSCustomerTemplate($vbo_tn = null, $booking_status = 'confirmed') {
8947 return self::getSMSTemplate($vbo_tn, $booking_status, 'customer');
8948 }
8949
8950 public static function checkPhonePrefixCountry($phone, $country_threecode) {
8951 $dbo = JFactory::getDbo();
8952 $phone = str_replace(" ", '', trim($phone));
8953 $cprefix = '';
8954 if (!empty($country_threecode)) {
8955 $q = "SELECT `phone_prefix` FROM `#__vikbooking_countries` WHERE `country_3_code`=".$dbo->quote($country_threecode).";";
8956 $dbo->setQuery($q);
8957 $dbo->execute();
8958 if ($dbo->getNumRows() > 0) {
8959 $cprefix = $dbo->loadResult();
8960 $cprefix = str_replace(" ", '', trim($cprefix));
8961 }
8962 }
8963 if (!empty($cprefix)) {
8964 if (substr($phone, 0, 1) != '+') {
8965 if (substr($phone, 0, 2) == '00') {
8966 $phone = '+'.substr($phone, 2);
8967 } else {
8968 $phone = $cprefix.$phone;
8969 }
8970 }
8971 }
8972 return $phone;
8973 }
8974
8975 public static function parseAdminSMSTemplate($booking, $booking_rooms, $vbo_tn = null) {
8976 $tpl = self::getSMSAdminTemplate($vbo_tn, $booking['status']);
8977
8978 /**
8979 * Parse all conditional text rules.
8980 *
8981 * @since 1.14 (J) - 1.4.0 (WP)
8982 */
8983 self::getConditionalRulesInstance()
8984 ->set(array('booking', 'rooms'), array($booking, $booking_rooms))
8985 ->parseTokens($tpl);
8986 //
8987
8988 $vbo_df = self::getDateFormat();
8989 $df = $vbo_df == "%d/%m/%Y" ? 'd/m/Y' : ($vbo_df == "%m/%d/%Y" ? 'm/d/Y' : 'Y-m-d');
8990 $datesep = self::getDateSeparator();
8991
8992 $tpl = str_replace('{customer_name}', $booking['customer_name'], $tpl);
8993 $tpl = str_replace('{booking_id}', $booking['id'], $tpl);
8994 $tpl = str_replace('{checkin_date}', date(str_replace("/", $datesep, $df), $booking['checkin']), $tpl);
8995 $tpl = str_replace('{checkout_date}', date(str_replace("/", $datesep, $df), $booking['checkout']), $tpl);
8996 $tpl = str_replace('{num_nights}', $booking['days'], $tpl);
8997 if (!empty($booking['confirmnumber'])) {
8998 $tpl = str_replace("{confirmnumb}", $booking['confirmnumber'], $tpl);
8999 } else {
9000 $tpl = str_replace("{confirmnumb}", '', $tpl);
9001 }
9002 if (!empty($booking['idorderota'])) {
9003 $tpl = str_replace("{ota_booking_id}", $booking['idorderota'], $tpl);
9004 } else {
9005 $tpl = str_replace("{ota_booking_id}", '', $tpl);
9006 }
9007
9008 $rooms_booked = array();
9009 $rooms_names = array();
9010 $tot_adults = 0;
9011 $tot_children = 0;
9012 $tot_guests = 0;
9013 foreach ($booking_rooms as $broom) {
9014 $rooms_names[] = $broom['room_name'];
9015 if (array_key_exists($broom['room_name'], $rooms_booked)) {
9016 $rooms_booked[$broom['room_name']] += 1;
9017 } else {
9018 $rooms_booked[$broom['room_name']] = 1;
9019 }
9020 $tot_adults += (int)$broom['adults'];
9021 $tot_children += (int)$broom['children'];
9022 $tot_guests += ((int)$broom['adults'] + (int)$broom['children']);
9023 }
9024 $tpl = str_replace('{tot_adults}', $tot_adults, $tpl);
9025 $tpl = str_replace('{tot_children}', $tot_children, $tpl);
9026 $tpl = str_replace('{tot_guests}', $tot_guests, $tpl);
9027 $rooms_booked_quant = array();
9028 foreach ($rooms_booked as $rname => $quant) {
9029 $rooms_booked_quant[] = ($quant > 1 ? $quant.' ' : '').$rname;
9030 }
9031 $tpl = str_replace('{rooms_booked}', implode(', ', $rooms_booked_quant), $tpl);
9032 $tpl = str_replace('{rooms_names}', implode(', ', $rooms_names), $tpl);
9033 $tpl = str_replace('{customer_country}', $booking['country_name'], $tpl);
9034 $tpl = str_replace('{customer_email}', $booking['custmail'], $tpl);
9035 $tpl = str_replace('{customer_phone}', $booking['phone'], $tpl);
9036 $tpl = str_replace('{total}', self::numberFormat($booking['total']), $tpl);
9037 $tpl = str_replace('{total_paid}', self::numberFormat($booking['totpaid']), $tpl);
9038 $remaining_bal = $booking['total'] - $booking['totpaid'];
9039 $tpl = str_replace('{remaining_balance}', self::numberFormat($remaining_bal), $tpl);
9040
9041 return $tpl;
9042 }
9043
9044 public static function parseCustomerSMSTemplate($booking, $booking_rooms, $vbo_tn = null, $force_text = null) {
9045 $tpl = !empty($force_text) ? $force_text : self::getSMSCustomerTemplate($vbo_tn, $booking['status']);
9046
9047 /**
9048 * Parse all conditional text rules.
9049 *
9050 * @since 1.14 (J) - 1.4.0 (WP)
9051 */
9052 self::getConditionalRulesInstance()
9053 ->set(array('booking', 'rooms'), array($booking, $booking_rooms))
9054 ->parseTokens($tpl);
9055 //
9056
9057 $vbo_df = self::getDateFormat();
9058 $df = $vbo_df == "%d/%m/%Y" ? 'd/m/Y' : ($vbo_df == "%m/%d/%Y" ? 'm/d/Y' : 'Y-m-d');
9059 $datesep = self::getDateSeparator();
9060
9061 $tpl = str_replace('{customer_name}', $booking['customer_name'], $tpl);
9062 $tpl = str_replace('{booking_id}', $booking['id'], $tpl);
9063 $tpl = str_replace('{checkin_date}', date(str_replace("/", $datesep, $df), $booking['checkin']), $tpl);
9064 $tpl = str_replace('{checkout_date}', date(str_replace("/", $datesep, $df), $booking['checkout']), $tpl);
9065 $tpl = str_replace('{num_nights}', $booking['days'], $tpl);
9066 if (!empty($booking['confirmnumber'])) {
9067 $tpl = str_replace("{confirmnumb}", $booking['confirmnumber'], $tpl);
9068 } else {
9069 $tpl = str_replace("{confirmnumb}", '', $tpl);
9070 }
9071 if (!empty($booking['idorderota'])) {
9072 $tpl = str_replace("{ota_booking_id}", $booking['idorderota'], $tpl);
9073 } else {
9074 $tpl = str_replace("{ota_booking_id}", '', $tpl);
9075 }
9076
9077 $rooms_booked = array();
9078 $rooms_names = array();
9079 $tot_adults = 0;
9080 $tot_children = 0;
9081 $tot_guests = 0;
9082 foreach ($booking_rooms as $broom) {
9083 $rooms_names[] = $broom['room_name'];
9084 if (array_key_exists($broom['room_name'], $rooms_booked)) {
9085 $rooms_booked[$broom['room_name']] += 1;
9086 } else {
9087 $rooms_booked[$broom['room_name']] = 1;
9088 }
9089 $tot_adults += (int)$broom['adults'];
9090 $tot_children += (int)$broom['children'];
9091 $tot_guests += ((int)$broom['adults'] + (int)$broom['children']);
9092 }
9093 $tpl = str_replace('{tot_adults}', $tot_adults, $tpl);
9094 $tpl = str_replace('{tot_children}', $tot_children, $tpl);
9095 $tpl = str_replace('{tot_guests}', $tot_guests, $tpl);
9096 $rooms_booked_quant = array();
9097 foreach ($rooms_booked as $rname => $quant) {
9098 $rooms_booked_quant[] = ($quant > 1 ? $quant.' ' : '').$rname;
9099 }
9100 $tpl = str_replace('{rooms_booked}', implode(', ', $rooms_booked_quant), $tpl);
9101 $tpl = str_replace('{rooms_names}', implode(', ', $rooms_names), $tpl);
9102 $tpl = str_replace('{total}', self::numberFormat($booking['total']), $tpl);
9103 $tpl = str_replace('{total_paid}', self::numberFormat($booking['totpaid']), $tpl);
9104 $remaining_bal = $booking['total'] - $booking['totpaid'];
9105 $tpl = str_replace('{remaining_balance}', self::numberFormat($remaining_bal), $tpl);
9106 $tpl = str_replace('{customer_pin}', $booking['customer_pin'], $tpl);
9107
9108 $use_sid = empty($booking['sid']) && !empty($booking['idorderota']) ? $booking['idorderota'] : $booking['sid'];
9109 $book_link = JUri::root() . 'index.php?option=com_vikbooking&view=booking&sid=' . $use_sid . '&ts=' . $booking['ts'];
9110
9111 if (VBOPlatformDetection::isWordPress()) {
9112 /**
9113 * @wponly Rewrite URI for front-end
9114 */
9115 $book_link = str_replace(JUri::root(), '', $book_link);
9116 $model = JModel::getInstance('vikbooking', 'shortcodes', 'admin');
9117 $itemid = $model->best(['booking'], (!empty($booking['lang']) ? $booking['lang'] : null));
9118 if ($itemid) {
9119 $book_link = JRoute::rewrite($book_link . "&Itemid={$itemid}", false);
9120 }
9121 } else {
9122 /**
9123 * @joomlaonly Rewrite URI for front-end
9124 */
9125 $bestitemid = self::findProperItemIdType(['booking'], (!empty($booking['lang']) ? $booking['lang'] : null));
9126 $uri_parts = [
9127 'option' => 'com_vikbooking',
9128 'view' => 'booking',
9129 'sid' => $use_sid,
9130 'ts' => $booking['ts'],
9131 ];
9132 if (!empty($bestitemid) && !empty($booking['lang']) && JFactory::getApplication()->isClient('administrator')) {
9133 // inject lang in query string for proper link routing
9134 $uri_parts['lang'] = $booking['lang'];
9135 }
9136 $book_link = self::externalroute('index.php?' . http_build_query($uri_parts), false, (!empty($bestitemid) ? $bestitemid : null));
9137 }
9138
9139 $tpl = str_replace('{booking_link}', $book_link, $tpl);
9140 // we use the alias tag {booking_url} to support just the URI unlike {order_link} that includes HTML
9141 $tpl = str_replace('{booking_url}', $book_link, $tpl);
9142
9143 return $tpl;
9144 }
9145
9146 public static function sendBookingSMS($oid, $skip_send_to = array(), $force_send_to = array(), $force_text = null) {
9147 $dbo = JFactory::getDbo();
9148 if (!class_exists('VboApplication')) {
9149 require_once(VBO_ADMIN_PATH.DIRECTORY_SEPARATOR.'helpers'.DIRECTORY_SEPARATOR.'jv_helper.php');
9150 }
9151 $vbo_app = new VboApplication;
9152 if (empty($oid)) {
9153 return false;
9154 }
9155 $sms_api = self::getSMSAPIClass();
9156 if (empty($sms_api)) {
9157 return false;
9158 }
9159 if (!is_file(VBO_ADMIN_PATH.DIRECTORY_SEPARATOR.'smsapi'.DIRECTORY_SEPARATOR.$sms_api)) {
9160 return false;
9161 }
9162 $sms_api_params = self::getSMSParams();
9163 if (!is_array($sms_api_params) || !(count($sms_api_params) > 0)) {
9164 return false;
9165 }
9166 if (!self::autoSendSMSEnabled() && !(count($force_send_to) > 0)) {
9167 return false;
9168 }
9169 $send_sms_to = self::getSendSMSTo();
9170 if (!(count($send_sms_to) > 0) && !(count($force_send_to) > 0)) {
9171 return false;
9172 }
9173 $booking = array();
9174 $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.";";
9175 $dbo->setQuery($q);
9176 $dbo->execute();
9177 if ($dbo->getNumRows() > 0) {
9178 $booking = $dbo->loadAssoc();
9179 }
9180 if (!(count($booking) > 0)) {
9181 return false;
9182 }
9183 if (strtolower($booking['status']) == 'standby' && self::getSendSMSWhen() < 2) {
9184 return false;
9185 }
9186 $booking_rooms = array();
9187 $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'].";";
9188 $dbo->setQuery($q);
9189 $dbo->execute();
9190 if ($dbo->getNumRows() > 0) {
9191 $booking_rooms = $dbo->loadAssocList();
9192 }
9193 $admin_phone = self::getSMSAdminPhone();
9194 $admin_sendermail = self::getSenderMail();
9195 $admin_email = self::getAdminMail();
9196 $f_result = false;
9197 require_once(VBO_ADMIN_PATH.DIRECTORY_SEPARATOR.'smsapi'.DIRECTORY_SEPARATOR.$sms_api);
9198 if ((in_array('admin', $send_sms_to) && !empty($admin_phone) && !in_array('admin', $skip_send_to)) || in_array('admin', $force_send_to)) {
9199 //SMS for the administrator
9200 $sms_text = self::parseAdminSMSTemplate($booking, $booking_rooms);
9201 if (!empty($sms_text)) {
9202 $sms_obj = new VikSmsApi($booking, $sms_api_params);
9203 //administrator phone can contain multiple numbers separated by comma or semicolon
9204 $admin_phones = array();
9205 if (strpos($admin_phone, ',') !== false) {
9206 $all_phones = explode(',', $admin_phone);
9207 foreach ($all_phones as $aph) {
9208 if (!empty($aph)) {
9209 $admin_phones[] = trim($aph);
9210 }
9211 }
9212 } elseif (strpos($admin_phone, ';') !== false) {
9213 $all_phones = explode(';', $admin_phone);
9214 foreach ($all_phones as $aph) {
9215 if (!empty($aph)) {
9216 $admin_phones[] = trim($aph);
9217 }
9218 }
9219 } else {
9220 $admin_phones[] = $admin_phone;
9221 }
9222 foreach ($admin_phones as $admphone) {
9223 $response_obj = $sms_obj->sendMessage($admphone, $sms_text);
9224 if ( !$sms_obj->validateResponse($response_obj) ) {
9225 //notify the administrator via email with the error of the SMS sending
9226 $vbo_app->sendMail($admin_sendermail, $admin_sendermail, $admin_email, $admin_sendermail, JText::translate('VBOSENDSMSERRMAILSUBJ'), JText::translate('VBOSENDADMINSMSERRMAILTXT')."<br />".$sms_obj->getLog(), true);
9227 } else {
9228 $f_result = true;
9229 }
9230 }
9231 }
9232 }
9233 if ((in_array('customer', $send_sms_to) && !empty($booking['phone']) && !in_array('customer', $skip_send_to)) || in_array('customer', $force_send_to)) {
9234 //SMS for the Customer
9235 $vbo_tn = self::getTranslator();
9236 $vbo_tn->translateContents($booking_rooms, '#__vikbooking_rooms', array('id' => 'idroom', 'room_name' => 'name'));
9237 $sms_text = self::parseCustomerSMSTemplate($booking, $booking_rooms, $vbo_tn, $force_text);
9238 if (!empty($sms_text)) {
9239 $sms_obj = new VikSmsApi($booking, $sms_api_params);
9240 $response_obj = $sms_obj->sendMessage($booking['phone'], $sms_text);
9241 if ( !$sms_obj->validateResponse($response_obj) ) {
9242 //notify the administrator via email with the error of the SMS sending
9243 $vbo_app->sendMail($admin_sendermail, $admin_sendermail, $admin_email, $admin_sendermail, JText::translate('VBOSENDSMSERRMAILSUBJ'), JText::translate('VBOSENDCUSTOMERSMSERRMAILTXT')."<br />".$sms_obj->getLog(), true);
9244 } else {
9245 $f_result = true;
9246 }
9247 }
9248 }
9249 return $f_result;
9250 }
9251
9252 public static function loadInvoiceTmpl($booking_info = array(), $booking_rooms = array())
9253 {
9254 if (!defined('_VIKBOOKINGEXEC')) {
9255 define('_VIKBOOKINGEXEC', '1');
9256 }
9257
9258 ob_start();
9259 include VBO_SITE_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'invoices' . DIRECTORY_SEPARATOR . 'invoice_tmpl.php';
9260 $content = ob_get_contents();
9261 ob_end_clean();
9262
9263 $default_params = array(
9264 'show_header' => 0,
9265 'header_data' => array(),
9266 'show_footer' => 0,
9267 'pdf_page_orientation' => 'PDF_PAGE_ORIENTATION',
9268 'pdf_unit' => 'PDF_UNIT',
9269 'pdf_page_format' => 'PDF_PAGE_FORMAT',
9270 'pdf_margin_left' => 'PDF_MARGIN_LEFT',
9271 'pdf_margin_top' => 'PDF_MARGIN_TOP',
9272 'pdf_margin_right' => 'PDF_MARGIN_RIGHT',
9273 'pdf_margin_header' => 'PDF_MARGIN_HEADER',
9274 'pdf_margin_footer' => 'PDF_MARGIN_FOOTER',
9275 'pdf_margin_bottom' => 'PDF_MARGIN_BOTTOM',
9276 'pdf_image_scale_ratio' => 'PDF_IMAGE_SCALE_RATIO',
9277 'header_font_size' => '10',
9278 'body_font_size' => '10',
9279 'footer_font_size' => '8',
9280 'show_lines_taxrate_col' => 0,
9281 );
9282
9283 if (defined('_VIKBOOKING_INVOICE_PARAMS') && isset($invoice_params) && is_array($invoice_params) && count($invoice_params)) {
9284 $default_params = array_merge($default_params, $invoice_params);
9285 }
9286
9287 return array($content, $default_params);
9288 }
9289
9290 /**
9291 * Includes within a buffer the template file for the custom (manual) invoice.
9292 *
9293 * @param array $invoice the record of the custom invoice
9294 * @param array $customer the customer record
9295 *
9296 * @return string the HTML content of the template file
9297 *
9298 * @since 1.11.1
9299 */
9300 public static function loadCustomInvoiceTmpl($invoice, $customer) {
9301 if (!defined('_VIKBOOKINGEXEC')) {
9302 define('_VIKBOOKINGEXEC', '1');
9303 }
9304 ob_start();
9305 include VBO_SITE_PATH . DIRECTORY_SEPARATOR . "helpers" . DIRECTORY_SEPARATOR . "invoices" . DIRECTORY_SEPARATOR . "custom_invoice_tmpl.php";
9306 $content = ob_get_contents();
9307 ob_end_clean();
9308 $default_params = array(
9309 'show_header' => 0,
9310 'header_data' => array(),
9311 'show_footer' => 0,
9312 'pdf_page_orientation' => 'PDF_PAGE_ORIENTATION',
9313 'pdf_unit' => 'PDF_UNIT',
9314 'pdf_page_format' => 'PDF_PAGE_FORMAT',
9315 'pdf_margin_left' => 'PDF_MARGIN_LEFT',
9316 'pdf_margin_top' => 'PDF_MARGIN_TOP',
9317 'pdf_margin_right' => 'PDF_MARGIN_RIGHT',
9318 'pdf_margin_header' => 'PDF_MARGIN_HEADER',
9319 'pdf_margin_footer' => 'PDF_MARGIN_FOOTER',
9320 'pdf_margin_bottom' => 'PDF_MARGIN_BOTTOM',
9321 'pdf_image_scale_ratio' => 'PDF_IMAGE_SCALE_RATIO',
9322 'header_font_size' => '10',
9323 'body_font_size' => '10',
9324 'footer_font_size' => '8'
9325 );
9326 if (defined('_VIKBOOKING_INVOICE_PARAMS') && isset($invoice_params) && @count($invoice_params) > 0) {
9327 $default_params = array_merge($default_params, $invoice_params);
9328 }
9329 return array($content, $default_params);
9330 }
9331
9332 public static function parseInvoiceTemplate($invoicetpl, $booking, $booking_rooms, $invoice_num, $invoice_suff, $invoice_date, $company_info, $vbo_tn = null, $pdfparams = [])
9333 {
9334 // clone the original template file source
9335 $parsed = $invoicetpl;
9336
9337 /**
9338 * Parse all conditional text rules.
9339 *
9340 * @since 1.14 (J) - 1.4.0 (WP)
9341 */
9342 self::getConditionalRulesInstance()
9343 ->set(array('booking', 'rooms'), array($booking, $booking_rooms))
9344 ->parseTokens($parsed);
9345 //
9346
9347 $dbo = JFactory::getDbo();
9348 if (is_null($vbo_tn)) {
9349 $vbo_tn = self::getTranslator();
9350 }
9351 // availability helper
9352 $av_helper = self::getAvailabilityInstance();
9353
9354 $nowdf = self::getDateFormat();
9355 if ($nowdf == "%d/%m/%Y") {
9356 $df = 'd/m/Y';
9357 } elseif ($nowdf == "%m/%d/%Y") {
9358 $df = 'm/d/Y';
9359 } else {
9360 $df = 'Y/m/d';
9361 }
9362 $datesep = self::getDateSeparator();
9363
9364 /**
9365 * Split stay reservation.
9366 *
9367 * @since 1.16.0 (J) - 1.6.0 (WP)
9368 */
9369 $room_stay_dates = [];
9370 if ($order_info['split_stay']) {
9371 if ($order_info['status'] == 'confirmed') {
9372 $room_stay_dates = $av_helper->loadSplitStayBusyRecords($order_info['id']);
9373 } else {
9374 $room_stay_dates = VBOFactory::getConfig()->getArray('split_stay_' . $order_info['id'], []);
9375 }
9376 // immediately count the number of nights of stay for each split room
9377 foreach ($room_stay_dates as $sps_r_k => $sps_r_v) {
9378 if (!empty($sps_r_v['checkin_ts']) && !empty($sps_r_v['checkout_ts'])) {
9379 // overwrite values for compatibility with non-confirmed bookings
9380 $sps_r_v['checkin'] = $sps_r_v['checkin_ts'];
9381 $sps_r_v['checkout'] = $sps_r_v['checkout_ts'];
9382 }
9383 $sps_r_v['nights'] = $av_helper->countNightsOfStay($sps_r_v['checkin'], $sps_r_v['checkout']);
9384 // overwrite the whole array
9385 $room_stay_dates[$sps_r_k] = $sps_r_v;
9386 }
9387 }
9388
9389 $companylogo = self::getSiteLogo();
9390 $uselogo = '';
9391 if (!empty($companylogo)) {
9392 /**
9393 * Let's try to prevent TCPDF errors.
9394 *
9395 * @since August 2nd 2019
9396 */
9397 if (is_file(VBO_ADMIN_PATH . DIRECTORY_SEPARATOR . 'resources' . DIRECTORY_SEPARATOR . $companylogo)) {
9398 $uselogo = '<img src="' . VBO_ADMIN_URI_REL . 'resources/' . $companylogo . '"/>';
9399 } elseif (is_file(VBO_SITE_PATH . DIRECTORY_SEPARATOR . 'resources' . DIRECTORY_SEPARATOR . $companylogo)) {
9400 $uselogo = '<img src="' . VBO_SITE_URI_REL . 'resources/' . $companylogo . '"/>';
9401 }
9402 }
9403 $parsed = str_replace("{company_logo}", $uselogo, $parsed);
9404 $parsed = str_replace("{company_info}", $company_info, $parsed);
9405 $parsed = str_replace("{invoice_number}", $invoice_num, $parsed);
9406 $parsed = str_replace("{invoice_suffix}", $invoice_suff, $parsed);
9407 $parsed = str_replace("{invoice_date}", $invoice_date, $parsed);
9408 $parsed = str_replace("{customer_info}", nl2br(rtrim($booking['custdata'], "\n")), $parsed);
9409
9410 // custom fields replacement
9411 preg_match_all('/\{customfield ([0-9]+)\}/U', $parsed, $cmatches);
9412 if (is_array($cmatches[1]) && @count($cmatches[1]) > 0) {
9413 $cfids = array();
9414 foreach ($cmatches[1] as $cfid ){
9415 $cfids[] = $cfid;
9416 }
9417 $q = "SELECT * FROM `#__vikbooking_custfields` WHERE `id` IN (".implode(", ", $cfids).");";
9418 $dbo->setQuery($q);
9419 $dbo->execute();
9420 $cfields = $dbo->getNumRows() > 0 ? $dbo->loadAssocList() : "";
9421 $vbo_tn->translateContents($cfields, '#__vikbooking_custfields');
9422 $cfmap = array();
9423 if (is_array($cfields)) {
9424 foreach($cfields as $cf) {
9425 $cfmap[trim(JText::translate($cf['name']))] = $cf['id'];
9426 }
9427 }
9428 $cfmapreplace = array();
9429 $partsreceived = explode("\n", $booking['custdata']);
9430 if (count($partsreceived) > 0) {
9431 foreach ($partsreceived as $pst) {
9432 if (!empty($pst)) {
9433 $tmpdata = explode(":", $pst);
9434 if (array_key_exists(trim($tmpdata[0]), $cfmap)) {
9435 $cfmapreplace[$cfmap[trim($tmpdata[0])]] = trim($tmpdata[1]);
9436 }
9437 }
9438 }
9439 }
9440 foreach ($cmatches[1] as $cfid ){
9441 if (array_key_exists($cfid, $cfmapreplace)) {
9442 $parsed = str_replace("{customfield ".$cfid."}", $cfmapreplace[$cfid], $parsed);
9443 } else {
9444 $parsed = str_replace("{customfield ".$cfid."}", "", $parsed);
9445 }
9446 }
9447 }
9448
9449 // invoice price description - Start
9450 $rooms = array();
9451 $tars = array();
9452 $arrpeople = array();
9453 $is_package = !empty($booking['pkg']) ? true : false;
9454 $tot_adults = 0;
9455 $tot_children = 0;
9456 $tot_guests = 0;
9457 foreach ($booking_rooms as $kor => $or) {
9458 $num = $kor + 1;
9459 $rooms[$num] = $or;
9460
9461 // determine the days to consider for the count of the availability
9462 $room_nights = $booking['days'];
9463 $room_checkin = $booking['checkin'];
9464 $room_checkout = $booking['checkout'];
9465 if ($booking['split_stay'] && count($room_stay_dates) && isset($room_stay_dates[$kor]) && $room_stay_dates[$kor]['idroom'] == $or['idroom']) {
9466 $room_nights = $room_stay_dates[$kor]['nights'];
9467 $room_checkin = $room_stay_dates[$kor]['checkin'];
9468 $room_checkout = $room_stay_dates[$kor]['checkout'];
9469 }
9470
9471 $arrpeople[$num]['adults'] = $or['adults'];
9472 $arrpeople[$num]['children'] = $or['children'];
9473 $tot_adults += $or['adults'];
9474 $tot_children += $or['children'];
9475 $tot_guests += ($or['adults'] + $or['children']);
9476 if ($is_package === true || (!empty($or['cust_cost']) && $or['cust_cost'] > 0.00)) {
9477 //package or custom cost set from the back-end
9478 continue;
9479 }
9480 $q = "SELECT * FROM `#__vikbooking_dispcost` WHERE `id`=" . (int)$or['idtar'] . ";";
9481 $dbo->setQuery($q);
9482 $dbo->execute();
9483 if ($dbo->getNumRows() > 0) {
9484 $tar = $dbo->loadAssocList();
9485 $tar = self::applySeasonsRoom($tar, $room_checkin, $room_checkout);
9486 //different usage
9487 if ($or['fromadult'] <= $or['adults'] && $or['toadult'] >= $or['adults']) {
9488 $diffusageprice = self::loadAdultsDiff($or['idroom'], $or['adults']);
9489 //Occupancy Override
9490 $occ_ovr = self::occupancyOverrideExists($tar, $or['adults']);
9491 $diffusageprice = $occ_ovr !== false ? $occ_ovr : $diffusageprice;
9492 //
9493 if (is_array($diffusageprice)) {
9494 //set a charge or discount to the price(s) for the different usage of the room
9495 foreach($tar as $kpr => $vpr) {
9496 $tar[$kpr]['diffusage'] = $or['adults'];
9497 if ($diffusageprice['chdisc'] == 1) {
9498 //charge
9499 if ($diffusageprice['valpcent'] == 1) {
9500 //fixed value
9501 $tar[$kpr]['diffusagecostpernight'] = $diffusageprice['pernight'] == 1 ? 1 : 0;
9502 $aduseval = $diffusageprice['pernight'] == 1 ? $diffusageprice['value'] * $tar[$kpr]['days'] : $diffusageprice['value'];
9503 $tar[$kpr]['diffusagecost'] = "+".$aduseval;
9504 $tar[$kpr]['room_base_cost'] = $vpr['cost'];
9505 $tar[$kpr]['cost'] = $vpr['cost'] + $aduseval;
9506 } else {
9507 //percentage value
9508 $tar[$kpr]['diffusagecostpernight'] = $diffusageprice['pernight'] == 1 ? $vpr['cost'] : 0;
9509 $aduseval = $diffusageprice['pernight'] == 1 ? round(($vpr['cost'] * $diffusageprice['value'] / 100) * $tar[$kpr]['days'] + $vpr['cost'], 2) : round(($vpr['cost'] * (100 + $diffusageprice['value']) / 100), 2);
9510 $tar[$kpr]['diffusagecost'] = "+".$diffusageprice['value']."%";
9511 $tar[$kpr]['room_base_cost'] = $vpr['cost'];
9512 $tar[$kpr]['cost'] = $aduseval;
9513 }
9514 } else {
9515 //discount
9516 if ($diffusageprice['valpcent'] == 1) {
9517 //fixed value
9518 $tar[$kpr]['diffusagecostpernight'] = $diffusageprice['pernight'] == 1 ? 1 : 0;
9519 $aduseval = $diffusageprice['pernight'] == 1 ? $diffusageprice['value'] * $tar[$kpr]['days'] : $diffusageprice['value'];
9520 $tar[$kpr]['diffusagecost'] = "-".$aduseval;
9521 $tar[$kpr]['room_base_cost'] = $vpr['cost'];
9522 $tar[$kpr]['cost'] = $vpr['cost'] - $aduseval;
9523 } else {
9524 //percentage value
9525 $tar[$kpr]['diffusagecostpernight'] = $diffusageprice['pernight'] == 1 ? $vpr['cost'] : 0;
9526 $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);
9527 $tar[$kpr]['diffusagecost'] = "-".$diffusageprice['value']."%";
9528 $tar[$kpr]['room_base_cost'] = $vpr['cost'];
9529 $tar[$kpr]['cost'] = $aduseval;
9530 }
9531 }
9532 }
9533 }
9534 }
9535 //
9536 $tars[$num] = $tar[0];
9537 }
9538 }
9539 $parsed = str_replace("{checkin_date}", date(str_replace("/", $datesep, $df), $booking['checkin']), $parsed);
9540 $parsed = str_replace("{checkout_date}", date(str_replace("/", $datesep, $df), $booking['checkout']), $parsed);
9541 $parsed = str_replace("{num_nights}", $booking['days'], $parsed);
9542 $parsed = str_replace("{tot_guests}", $tot_guests, $parsed);
9543 $parsed = str_replace("{tot_adults}", $tot_adults, $parsed);
9544 $parsed = str_replace("{tot_children}", $tot_children, $parsed);
9545
9546 $isdue = 0;
9547 $tot_taxes = 0;
9548 $tot_city_taxes = 0;
9549 $tot_fees = 0;
9550 $pricestr = array();
9551 $optstr = array();
9552 // start building the tax summary
9553 VBOTaxonomySummary::start();
9554 foreach ($booking_rooms as $kor => $or) {
9555 $num = $kor + 1;
9556
9557 // determine the days to consider for the count of the availability
9558 $room_nights = $booking['days'];
9559 $room_checkin = $booking['checkin'];
9560 $room_checkout = $booking['checkout'];
9561 if ($booking['split_stay'] && count($room_stay_dates) && isset($room_stay_dates[$kor]) && $room_stay_dates[$kor]['idroom'] == $or['idroom']) {
9562 $room_nights = $room_stay_dates[$kor]['nights'];
9563 $room_checkin = $room_stay_dates[$kor]['checkin'];
9564 $room_checkout = $room_stay_dates[$kor]['checkout'];
9565 }
9566
9567 $pricestr[$num] = array();
9568 if ($is_package === true || (!empty($or['cust_cost']) && $or['cust_cost'] > 0.00)) {
9569 // package cost or cust_cost may not be inclusive of taxes if prices tax included is off
9570 $calctar = self::sayPackagePlusIva($or['cust_cost'], $or['cust_idiva']);
9571 $cost_minus_tax = self::sayPackageMinusIva($or['cust_cost'], $or['cust_idiva']);
9572 $pricestr[$num]['name'] = (!empty($or['pkg_name']) ? $or['pkg_name'] : (!empty($or['otarplan']) ? ucwords($or['otarplan']) : JText::translate('VBOROOMCUSTRATEPLAN')));
9573 $pricestr[$num]['tot'] = $calctar;
9574 $pricestr[$num]['tax'] = ($calctar - $cost_minus_tax);
9575 $tot_taxes += ($calctar - $cost_minus_tax);
9576 $isdue += $calctar;
9577 $tax_rate = VBOTaxonomySummary::addOptionTax($or['cust_idiva'], ($calctar - $cost_minus_tax));
9578 $pricestr[$num]['tax_rate'] = $tax_rate;
9579 } elseif (array_key_exists($num, $tars) && is_array($tars[$num])) {
9580 $display_rate = !empty($or['room_cost']) ? $or['room_cost'] : $tars[$num]['cost'];
9581 $calctar = self::sayCostPlusIva($display_rate, $tars[$num]['idprice']);
9582 $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'] : "");
9583 $pricestr[$num]['tot'] = $calctar;
9584 $tars[$num]['calctar'] = $calctar;
9585 $isdue += $calctar;
9586 if ($calctar == $display_rate) {
9587 $cost_minus_tax = self::sayCostMinusIva($display_rate, $tars[$num]['idprice']);
9588 $tot_taxes += ($display_rate - $cost_minus_tax);
9589 $pricestr[$num]['tax'] = ($display_rate - $cost_minus_tax);
9590 $tax_rate = VBOTaxonomySummary::addRatePlanTax($tars[$num]['idprice'], ($display_rate - $cost_minus_tax));
9591 $pricestr[$num]['tax_rate'] = $tax_rate;
9592 } else {
9593 $tot_taxes += ($calctar - $display_rate);
9594 $pricestr[$num]['tax'] = ($calctar - $display_rate);
9595 $tax_rate = VBOTaxonomySummary::addRatePlanTax($tars[$num]['idprice'], ($calctar - $display_rate));
9596 $pricestr[$num]['tax_rate'] = $tax_rate;
9597 }
9598 }
9599 $optstr[$num] = array();
9600 $opt_ind = 0;
9601 if (!empty($or['optionals'])) {
9602 $stepo = explode(";", $or['optionals']);
9603 foreach ($stepo as $roptkey => $oo) {
9604 if (!empty($oo)) {
9605 $stept = explode(":", $oo);
9606 $q = "SELECT * FROM `#__vikbooking_optionals` WHERE `id`=" . $dbo->quote($stept[0]) . ";";
9607 $dbo->setQuery($q);
9608 $dbo->execute();
9609 if ($dbo->getNumRows() == 1) {
9610 $actopt = $dbo->loadAssocList();
9611 if (is_object($vbo_tn)) {
9612 $vbo_tn->translateContents($actopt, '#__vikbooking_optionals');
9613 }
9614 $optstr[$num][$opt_ind] = array();
9615 $chvar = '';
9616 if (!empty($actopt[0]['ageintervals']) && $or['children'] > 0 && strstr($stept[1], '-') != false) {
9617 $optagenames = self::getOptionIntervalsAges($actopt[0]['ageintervals']);
9618 $optagepcent = self::getOptionIntervalsPercentage($actopt[0]['ageintervals']);
9619 $optageovrct = self::getOptionIntervalChildOverrides($actopt[0], $or['adults'], $or['children']);
9620 $child_num = self::getRoomOptionChildNumber($or['optionals'], $actopt[0]['id'], $roptkey, $or['children']);
9621 $optagecosts = self::getOptionIntervalsCosts(isset($optageovrct['ageintervals_child' . ($child_num + 1)]) ? $optageovrct['ageintervals_child' . ($child_num + 1)] : $actopt[0]['ageintervals']);
9622 $agestept = explode('-', $stept[1]);
9623 $stept[1] = $agestept[0];
9624 $chvar = $agestept[1];
9625 if (array_key_exists(($chvar - 1), $optagepcent) && $optagepcent[($chvar - 1)] == 1) {
9626 //percentage value of the adults tariff
9627 if ($is_package === true || (!empty($or['cust_cost']) && $or['cust_cost'] > 0.00)) {
9628 $optagecosts[($chvar - 1)] = $or['cust_cost'] * $optagecosts[($chvar - 1)] / 100;
9629 } else {
9630 $display_rate = !empty($or['room_cost']) ? $or['room_cost'] : $tars[$num]['cost'];
9631 $optagecosts[($chvar - 1)] = $display_rate * $optagecosts[($chvar - 1)] / 100;
9632 }
9633 } elseif (array_key_exists(($chvar - 1), $optagepcent) && $optagepcent[($chvar - 1)] == 2) {
9634 //VBO 1.10 - percentage value of room base cost
9635 if ($is_package === true || (!empty($or['cust_cost']) && $or['cust_cost'] > 0.00)) {
9636 $optagecosts[($chvar - 1)] = $or['cust_cost'] * $optagecosts[($chvar - 1)] / 100;
9637 } else {
9638 $display_rate = isset($tars[$num]['room_base_cost']) ? $tars[$num]['room_base_cost'] : (!empty($or['room_cost']) ? $or['room_cost'] : $tars[$num]['cost']);
9639 $optagecosts[($chvar - 1)] = $display_rate * $optagecosts[($chvar - 1)] / 100;
9640 }
9641 }
9642 $actopt[0]['chageintv'] = $chvar;
9643 $actopt[0]['name'] .= ' ('.$optagenames[($chvar - 1)].')';
9644 $actopt[0]['quan'] = $stept[1];
9645 $realcost = (intval($actopt[0]['perday']) == 1 ? (floatval($optagecosts[($chvar - 1)]) * $room_nights * $stept[1]) : (floatval($optagecosts[($chvar - 1)]) * $stept[1]));
9646 } else {
9647 $actopt[0]['quan'] = $stept[1];
9648 // VBO 1.11 - options percentage cost of the room total fee
9649 if ($is_package === true || (!empty($or['cust_cost']) && $or['cust_cost'] > 0.00)) {
9650 $deftar_basecosts = $or['cust_cost'];
9651 } else {
9652 $deftar_basecosts = !empty($or['room_cost']) ? $or['room_cost'] : $tars[$num]['cost'];
9653 }
9654 $actopt[0]['cost'] = (int)$actopt[0]['pcentroom'] ? ($deftar_basecosts * $actopt[0]['cost'] / 100) : $actopt[0]['cost'];
9655 //
9656 $realcost = (intval($actopt[0]['perday']) == 1 ? ($actopt[0]['cost'] * $room_nights * $stept[1]) : ($actopt[0]['cost'] * $stept[1]));
9657 }
9658 if (!empty($actopt[0]['maxprice']) && $actopt[0]['maxprice'] > 0 && $realcost > $actopt[0]['maxprice']) {
9659 $realcost = $actopt[0]['maxprice'];
9660 if (intval($actopt[0]['hmany']) == 1 && intval($stept[1]) > 1) {
9661 $realcost = $actopt[0]['maxprice'] * $stept[1];
9662 }
9663 }
9664 if ($actopt[0]['perperson'] == 1) {
9665 $realcost = $realcost * $or['adults'];
9666 }
9667 $tmpopr = self::sayOptionalsPlusIva($realcost, $actopt[0]['idiva']);
9668 $optstr[$num][$opt_ind]['name'] = ($stept[1] > 1 ? $stept[1] . " " : "") . $actopt[0]['name'];
9669 $optstr[$num][$opt_ind]['tot'] = $tmpopr;
9670 $optstr[$num][$opt_ind]['tax'] = 0;
9671 if ($actopt[0]['is_citytax'] == 1) {
9672 $tot_city_taxes += $tmpopr;
9673 } elseif ($actopt[0]['is_fee'] == 1) {
9674 $tot_fees += $tmpopr;
9675 }
9676 // VBO 1.11 - always calculate the amount of tax no matter if this is already a tax or a fee
9677 if ($tmpopr == $realcost) {
9678 $opt_minus_tax = self::sayOptionalsMinusIva($realcost, $actopt[0]['idiva']);
9679 $tot_taxes += ($realcost - $opt_minus_tax);
9680 $optstr[$num][$opt_ind]['tax'] = ($realcost - $opt_minus_tax);
9681 $tax_rate = VBOTaxonomySummary::addOptionTax($actopt[0]['idiva'], ($realcost - $opt_minus_tax));
9682 $optstr[$num][$opt_ind]['tax_rate'] = $tax_rate;
9683 } else {
9684 $tot_taxes += ($tmpopr - $realcost);
9685 $optstr[$num][$opt_ind]['tax'] = ($tmpopr - $realcost);
9686 $tax_rate = VBOTaxonomySummary::addOptionTax($actopt[0]['idiva'], ($tmpopr - $realcost));
9687 $optstr[$num][$opt_ind]['tax_rate'] = $tax_rate;
9688 }
9689 //
9690 $opt_ind++;
9691 $isdue += $tmpopr;
9692 }
9693 }
9694 }
9695 }
9696
9697 // custom extra costs
9698 if (!empty($or['extracosts'])) {
9699 $cur_extra_costs = json_decode($or['extracosts'], true);
9700 foreach ($cur_extra_costs as $eck => $ecv) {
9701 $ecplustax = !empty($ecv['idtax']) ? self::sayOptionalsPlusIva($ecv['cost'], $ecv['idtax']) : $ecv['cost'];
9702 $isdue += $ecplustax;
9703 $optstr[$num][$opt_ind]['name'] = $ecv['name'];
9704 $optstr[$num][$opt_ind]['tot'] = $ecplustax;
9705 $optstr[$num][$opt_ind]['tax'] = 0;
9706 if ($ecplustax == $ecv['cost']) {
9707 $ec_minus_tax = !empty($ecv['idtax']) ? self::sayOptionalsMinusIva($ecv['cost'], $ecv['idtax']) : $ecv['cost'];
9708 $tot_taxes += ($ecv['cost'] - $ec_minus_tax);
9709 $optstr[$num][$opt_ind]['tax'] = ($ecv['cost'] - $ec_minus_tax);
9710 $tax_rate = VBOTaxonomySummary::addOptionTax($ecv['idtax'], ($ecv['cost'] - $ec_minus_tax));
9711 $optstr[$num][$opt_ind]['tax_rate'] = $tax_rate;
9712 } else {
9713 $tot_taxes += ($ecplustax - $ecv['cost']);
9714 $optstr[$num][$opt_ind]['tax'] = ($ecplustax - $ecv['cost']);
9715 $tax_rate = VBOTaxonomySummary::addOptionTax($ecv['idtax'], ($ecplustax - $ecv['cost']));
9716 $optstr[$num][$opt_ind]['tax_rate'] = $tax_rate;
9717 }
9718 $opt_ind++;
9719 }
9720 }
9721 }
9722
9723 $usedcoupon = false;
9724 if (strlen($booking['coupon']) > 0) {
9725 $orig_isdue = $isdue;
9726 $expcoupon = explode(";", $booking['coupon']);
9727 $usedcoupon = $expcoupon;
9728 $isdue = $isdue - (float)$expcoupon[1];
9729 if ($isdue != $orig_isdue) {
9730 //lower taxes proportionally
9731 $tot_taxes = $isdue * $tot_taxes / $orig_isdue;
9732 }
9733 }
9734 if ($booking['refund'] > 0) {
9735 $orig_isdue = $isdue;
9736 $isdue -= $booking['refund'];
9737 if ($isdue != $orig_isdue) {
9738 //lower taxes proportionally
9739 $tot_taxes = $isdue * $tot_taxes / $orig_isdue;
9740 }
9741 }
9742 $rows_written = 0;
9743 $inv_rows = '';
9744 foreach ($pricestr as $num => $price_descr) {
9745 // add room split stay information, if any
9746 $split_stay_str = '';
9747 $kor = $num - 1;
9748 if ($booking['split_stay'] && count($room_stay_dates) && isset($room_stay_dates[$kor])) {
9749 $split_stay_str .= $room_stay_dates[$kor]['nights'] . ' ' . ($room_stay_dates[$kor]['nights'] > 1 ? JText::translate('VBDAYS') : JText::translate('VBDAY')) . ', ';
9750 $split_stay_str .= date(str_replace("/", $datesep, $df), $room_stay_dates[$kor]['checkin']) . ' - ' . date(str_replace("/", $datesep, $df), $room_stay_dates[$kor]['checkout']);
9751 }
9752
9753 $inv_rows .= '<tr>'."\n";
9754 $inv_rows .= '<td>' . $rooms[$num]['room_name'] . (!empty($split_stay_str) ? '<br/>' . $split_stay_str : '') . '<br/>' . nl2br(rtrim($price_descr['name'], "\n")) . '</td>' . "\n";
9755 $inv_rows .= '<td>'.$booking['currencyname'].' '.self::numberFormat(($price_descr['tot'] - $price_descr['tax'])).'</td>'."\n";
9756 $inv_rows .= '<td>'.$booking['currencyname'].' '.self::numberFormat($price_descr['tax']).'</td>'."\n";
9757 $inv_rows .= '<td>'.$booking['currencyname'].' '.self::numberFormat($price_descr['tot']).'</td>'."\n";
9758 if (!empty($pdfparams['show_lines_taxrate_col'])) {
9759 $inv_rows .= '<td>' . (isset($price_descr['tax_rate']) ? $price_descr['tax_rate'] : '0') . '%</td>'."\n";
9760 }
9761 $inv_rows .= '</tr>'."\n";
9762 $rows_written++;
9763 if (array_key_exists($num, $optstr) && count($optstr[$num]) > 0) {
9764 foreach ($optstr[$num] as $optk => $optv) {
9765 $inv_rows .= '<tr>'."\n";
9766 $inv_rows .= '<td>'.$optv['name'].'</td>'."\n";
9767 $inv_rows .= '<td>'.$booking['currencyname'].' '.self::numberFormat(($optv['tot'] - $optv['tax'])).'</td>'."\n";
9768 $inv_rows .= '<td>'.$booking['currencyname'].' '.self::numberFormat($optv['tax']).'</td>'."\n";
9769 $inv_rows .= '<td>'.$booking['currencyname'].' '.self::numberFormat($optv['tot']).'</td>'."\n";
9770 if (!empty($pdfparams['show_lines_taxrate_col'])) {
9771 $inv_rows .= '<td>' . (isset($optv['tax_rate']) ? $optv['tax_rate'] : '0') . '%</td>'."\n";
9772 }
9773 $inv_rows .= '</tr>'."\n";
9774 $rows_written++;
9775 }
9776 }
9777 }
9778
9779 // if discount print row
9780 if ($usedcoupon !== false) {
9781 $inv_rows .= '<tr>'."\n";
9782 $inv_rows .= '<td></td><td></td><td></td><td></td>'."\n";
9783 $inv_rows .= '</tr>'."\n";
9784 $inv_rows .= '<tr>'."\n";
9785 $inv_rows .= '<td>'.$usedcoupon[2].'</td>'."\n";
9786 $inv_rows .= '<td></td>'."\n";
9787 $inv_rows .= '<td></td>'."\n";
9788 $inv_rows .= '<td>- '.$booking['currencyname'].' '.self::numberFormat($usedcoupon[1]).'</td>'."\n";
9789 $inv_rows .= '</tr>'."\n";
9790 $rows_written += 2;
9791 }
9792 // if refunded amount, print row
9793 if ($booking['refund'] > 0) {
9794 $inv_rows .= '<tr>'."\n";
9795 $inv_rows .= '<td></td><td></td><td></td><td></td>'."\n";
9796 $inv_rows .= '</tr>'."\n";
9797 $inv_rows .= '<tr>'."\n";
9798 $inv_rows .= '<td>' . JText::translate('VBO_AMOUNT_REFUNDED') . '</td>'."\n";
9799 $inv_rows .= '<td></td>'."\n";
9800 $inv_rows .= '<td></td>'."\n";
9801 $inv_rows .= '<td>- '.$booking['currencyname'].' '.self::numberFormat($booking['refund']).'</td>'."\n";
9802 $inv_rows .= '</tr>'."\n";
9803 $rows_written += 2;
9804 }
9805 //
9806 $min_records = 8;
9807 if ($rows_written < $min_records) {
9808 for ($i = 1; $i <= ($min_records - $rows_written); $i++) {
9809 $inv_rows .= '<tr>'."\n";
9810 $inv_rows .= '<td></td>'."\n";
9811 $inv_rows .= '<td></td>'."\n";
9812 $inv_rows .= '<td></td>'."\n";
9813 $inv_rows .= '</tr>'."\n";
9814 }
9815 }
9816 //invoice price description - End
9817 $parsed = str_replace("{invoice_products_descriptions}", $inv_rows, $parsed);
9818 $parsed = str_replace("{invoice_totalnet}", $booking['currencyname'].' '.self::numberFormat(($isdue - $tot_taxes)), $parsed);
9819 $parsed = str_replace("{invoice_totaltax}", $booking['currencyname'].' '.self::numberFormat($tot_taxes), $parsed);
9820 $parsed = str_replace("{invoice_grandtotal}", $booking['currencyname'].' '.self::numberFormat($isdue), $parsed);
9821 $parsed = str_replace("{inv_notes}", $booking['inv_notes'], $parsed);
9822
9823 // invoice tax summary
9824 $tax_summary = VBOTaxonomySummary::get();
9825 $tax_names = VBOTaxonomySummary::getNames();
9826 $taxsum_rows = "";
9827 foreach ($tax_summary as $tax_rate => $tax_amount) {
9828 if (!$tax_rate && !$tax_amount) {
9829 continue;
9830 }
9831 $tax_name = isset($tax_names[$tax_rate]) ? $tax_names[$tax_rate] : JText::translate('VBO_INV_VATGST');
9832 $taxsum_rows .= '<tr>' . "\n";
9833 $taxsum_rows .= '<td>' . $tax_name . '</td>' . "\n";
9834 $taxsum_rows .= '<td>' . $tax_rate . '%</td>' . "\n";
9835 $taxsum_rows .= '<td>' . $booking['currencyname'] . ' ' . self::numberFormat($tax_amount) . '</td>' . "\n";
9836 $taxsum_rows .= '</tr>' . "\n";
9837 }
9838 $parsed = str_replace("{invoice_tax_summary}", $taxsum_rows, $parsed);
9839
9840 return $parsed;
9841 }
9842
9843 /**
9844 * Specific method for parsing the template file for the custom (manual) invoices.
9845 *
9846 * @param string $invoicetpl the plain custom invoice template file before parsing
9847 * @param array $invoice the invoice record
9848 * @param array $customer the customer record
9849 *
9850 * @return string the HTML content of the parsed custom invoice template
9851 *
9852 * @since 1.11.1
9853 */
9854 public static function parseCustomInvoiceTemplate($invoicetpl, $invoice, $customer) {
9855 $nowdf = self::getDateFormat(true);
9856 if ($nowdf == "%d/%m/%Y") {
9857 $df = 'd/m/Y';
9858 } elseif ($nowdf == "%m/%d/%Y") {
9859 $df = 'm/d/Y';
9860 } else {
9861 $df = 'Y/m/d';
9862 }
9863 $datesep = self::getDateSeparator(true);
9864 $companylogo = self::getSiteLogo();
9865 $uselogo = '';
9866 if (!empty($companylogo)) {
9867 /**
9868 * Let's try to prevent TCPDF errors by checking if the file exists.
9869 *
9870 * @since August 2nd 2019
9871 */
9872 if (is_file(VBO_ADMIN_PATH . DIRECTORY_SEPARATOR . 'resources' . DIRECTORY_SEPARATOR . $companylogo)) {
9873 $uselogo = '<img src="' . VBO_ADMIN_URI_REL . 'resources/' . $companylogo . '"/>';
9874 } elseif (is_file(VBO_SITE_PATH . DIRECTORY_SEPARATOR . 'resources' . DIRECTORY_SEPARATOR . $companylogo)) {
9875 $uselogo = '<img src="' . VBO_SITE_URI_REL . 'resources/' . $companylogo . '"/>';
9876 }
9877 }
9878 $invoicetpl = str_replace("{company_logo}", $uselogo, $invoicetpl);
9879 $invoicetpl = str_replace("{company_info}", self::getInvoiceCompanyInfo(), $invoicetpl);
9880 $invoicetpl = str_replace("{invoice_number}", $invoice['number'], $invoicetpl);
9881 $invoicetpl = str_replace("{invoice_date}", date(str_replace("/", $datesep, $df), $invoice['for_date']), $invoicetpl);
9882 // customer information
9883 $custinfo = '';
9884 $custinfo .= $customer['first_name'] . ' ' . $customer['last_name'] . "\n";
9885 $custinfo .= $customer['email'] . "\n";
9886 $custinfo .= !empty($customer['company']) ? $customer['company'] . "\n" : '';
9887 $custinfo .= !empty($customer['vat']) ? $customer['vat'] . "\n" : '';
9888 $custinfo .= !empty($customer['address']) ? $customer['address'] . "\n" : '';
9889 $custinfo .= (!empty($customer['zip']) ? $customer['zip'] . " " : '') . (!empty($customer['city']) ? $customer['city'] . "\n" : '');
9890 $custinfo .= (!empty($customer['country_name']) ? $customer['country_name'] . "\n" : (!empty($customer['country']) ? $customer['country'] . "\n" : ''));
9891 $custinfo .= !empty($customer['fisccode']) ? $customer['fisccode'] . "\n" : '';
9892 $invoicetpl = str_replace("{customer_info}", nl2br($custinfo), $invoicetpl);
9893 // invoice notes
9894 $invoicetpl = str_replace("{invoice_notes}", (isset($invoice['rawcont']) && isset($invoice['rawcont']['notes']) ? $invoice['rawcont']['notes'] : ''), $invoicetpl);
9895
9896 return $invoicetpl;
9897 }
9898
9899 public static function generateBookingInvoice($booking, $invoice_num = 0, $invoice_suff = '', $invoice_date = '', $company_info = '', $translate = false)
9900 {
9901 $invoice_num = empty($invoice_num) ? self::getNextInvoiceNumber() : $invoice_num;
9902 $invoice_suff = empty($invoice_suff) ? self::getInvoiceNumberSuffix() : $invoice_suff;
9903 $company_info = empty($company_info) ? self::getInvoiceCompanyInfo() : $company_info;
9904
9905 if (!is_array($booking) || !$booking || !($booking['total'] > 0)) {
9906 return false;
9907 }
9908
9909 /**
9910 * Trigger event to allow third party plugins to override some values for the invoice generation.
9911 *
9912 * @since 1.16.3 (J) - 1.6.3 (WP)
9913 */
9914 $event_args = [
9915 'invoice_num' => $invoice_num,
9916 'invoice_suff' => $invoice_suff,
9917 'invoice_date' => $invoice_date,
9918 'company_info' => $company_info,
9919 'translate' => $translate,
9920 ];
9921 VBOFactory::getPlatform()->getDispatcher()->trigger('onBeforeGenerateInvoiceVikBooking', [$booking, &$event_args]);
9922 if (is_array($event_args) && $event_args) {
9923 // extract the new values to be used
9924 extract($event_args);
9925 }
9926
9927 $dbo = JFactory::getDbo();
9928 $vbo_tn = self::getTranslator();
9929 $currencyname = self::getCurrencyName();
9930 $booking['currencyname'] = $currencyname;
9931 $nowdf = self::getDateFormat(true);
9932 if ($nowdf == "%d/%m/%Y") {
9933 $df = 'd/m/Y';
9934 } elseif ($nowdf == "%m/%d/%Y") {
9935 $df = 'm/d/Y';
9936 } else {
9937 $df = 'Y/m/d';
9938 }
9939 $datesep = self::getDateSeparator(true);
9940 if (empty($invoice_date)) {
9941 $invoice_date = date(str_replace("/", $datesep, $df), $booking['ts']);
9942 $used_date = $booking['ts'];
9943 } else {
9944 /**
9945 * We could be re-generating an invoice for a booking that already had a invoice.
9946 * In order to modify some entries in the invoice, the whole PDF is re-generated.
9947 * It is now possible to keep the same invoice date as the previous one, so check
9948 * what value contains $invoice_date to see if it's different from today's date.
9949 * The cron jobs may be calling this method with a $invoice_date = 1, so we need
9950 * to also check the length of the string $invoice_date before using that date.
9951 *
9952 * @since 1.10 - August 2018
9953 */
9954 $base_ts = time();
9955 if (date($df, $base_ts) != $invoice_date && strlen($invoice_date) >= 6) {
9956 $base_ts = self::getDateTimestamp($invoice_date, 0, 0);
9957 }
9958 $invoice_date = date(str_replace("/", $datesep, $df), $base_ts);
9959 $used_date = $base_ts;
9960 //
9961 }
9962 $booking_rooms = array();
9963 $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'].";";
9964 $dbo->setQuery($q);
9965 $dbo->execute();
9966 if ($dbo->getNumRows() > 0) {
9967 $booking_rooms = $dbo->loadAssocList();
9968 }
9969 if (!(count($booking_rooms) > 0)) {
9970 return false;
9971 }
9972 //Translations for the invoices are disabled by default as well as the language definitions for the customer language
9973 if ($translate === true) {
9974 if (!empty($booking['lang'])) {
9975 $lang = JFactory::getLanguage();
9976 if ($lang->getTag() != $booking['lang']) {
9977 if (VBOPlatformDetection::isWordPress()) {
9978 $lang->load('com_vikbooking', VIKBOOKING_LANG, $booking['lang'], true);
9979 } else {
9980 $lang->load('com_vikbooking', JPATH_SITE, $booking['lang'], true);
9981 $lang->load('com_vikbooking', JPATH_ADMINISTRATOR, $booking['lang'], true);
9982 $lang->load('joomla', JPATH_SITE, $booking['lang'], true);
9983 $lang->load('joomla', JPATH_ADMINISTRATOR, $booking['lang'], true);
9984 }
9985 }
9986 if ($vbo_tn->getDefaultLang() != $booking['lang']) {
9987 // force the translation to start because contents should be translated
9988 $vbo_tn::$force_tolang = $booking['lang'];
9989 }
9990 $vbo_tn->translateContents($booking_rooms, '#__vikbooking_rooms', array('id' => 'idroom', 'room_name' => 'name'), array(), $booking['lang']);
9991 }
9992 }
9993 //
9994 if (!class_exists('TCPDF')) {
9995 require_once(VBO_SITE_PATH . DIRECTORY_SEPARATOR . "helpers" . DIRECTORY_SEPARATOR . "tcpdf" . DIRECTORY_SEPARATOR . 'tcpdf.php');
9996 }
9997 $usepdffont = is_file(VBO_SITE_PATH . DIRECTORY_SEPARATOR . "helpers" . DIRECTORY_SEPARATOR . "tcpdf" . DIRECTORY_SEPARATOR . "fonts" . DIRECTORY_SEPARATOR . "dejavusans.php") ? 'dejavusans' : 'helvetica';
9998
9999 /**
10000 * Trigger event to allow third party plugins to return a specific font name.
10001 *
10002 * @since 1.16.0 (J) - 1.6.0 (WP)
10003 */
10004 $custom_pdf_font = VBOFactory::getPlatform()->getDispatcher()->filter('onGetPdfFontNameVikBooking', [$usepdffont]);
10005 if (is_array($custom_pdf_font) && !empty($custom_pdf_font[0])) {
10006 $usepdffont = $custom_pdf_font[0];
10007 }
10008
10009 //vikbooking 1.8 - set array variable to the template file
10010 $q = "SELECT * FROM `#__vikbooking_orders` WHERE `id`='".(int)$booking['id']."';";
10011 $dbo->setQuery($q);
10012 $dbo->execute();
10013 $booking_info = $dbo->loadAssoc();
10014 list($invoicetpl, $pdfparams) = self::loadInvoiceTmpl($booking_info, $booking_rooms);
10015 //
10016 $invoice_body = self::parseInvoiceTemplate($invoicetpl, $booking, $booking_rooms, $invoice_num, $invoice_suff, $invoice_date, $company_info, ($translate === true ? $vbo_tn : null), $pdfparams);
10017 $pdffname = $booking['id'] . '_' . $booking['sid'] . '.pdf';
10018 $pathpdf = VBO_SITE_PATH . DIRECTORY_SEPARATOR . "helpers" . DIRECTORY_SEPARATOR . "invoices" . DIRECTORY_SEPARATOR . "generated" . DIRECTORY_SEPARATOR . $pdffname;
10019 if (file_exists($pathpdf)) @unlink($pathpdf);
10020 $pdf_page_format = is_array($pdfparams['pdf_page_format']) ? $pdfparams['pdf_page_format'] : constant($pdfparams['pdf_page_format']);
10021 $pdf = new TCPDF(constant($pdfparams['pdf_page_orientation']), constant($pdfparams['pdf_unit']), $pdf_page_format, true, 'UTF-8', false);
10022 $pdf->SetTitle(JText::translate('VBOINVNUM').' '.$invoice_num);
10023 //Header for each page of the pdf
10024 if ($pdfparams['show_header'] == 1 && count($pdfparams['header_data']) > 0) {
10025 $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]);
10026 }
10027 //Change some currencies to their unicode (decimal) value
10028 $unichr_map = array('EUR' => 8364, 'USD' => 36, 'AUD' => 36, 'CAD' => 36, 'GBP' => 163);
10029 if (array_key_exists($booking['currencyname'], $unichr_map)) {
10030 $invoice_body = str_replace($booking['currencyname'], TCPDF_FONTS::unichr($unichr_map[$booking['currencyname']]), $invoice_body);
10031 }
10032 //header and footer fonts
10033 $pdf->setHeaderFont(array($usepdffont, '', $pdfparams['header_font_size']));
10034 $pdf->setFooterFont(array($usepdffont, '', $pdfparams['footer_font_size']));
10035 //margins
10036 $pdf->SetMargins(constant($pdfparams['pdf_margin_left']), constant($pdfparams['pdf_margin_top']), constant($pdfparams['pdf_margin_right']));
10037 $pdf->SetHeaderMargin(constant($pdfparams['pdf_margin_header']));
10038 $pdf->SetFooterMargin(constant($pdfparams['pdf_margin_footer']));
10039 //
10040 $pdf->SetAutoPageBreak(true, constant($pdfparams['pdf_margin_bottom']));
10041 $pdf->setImageScale(constant($pdfparams['pdf_image_scale_ratio']));
10042 $pdf->SetFont($usepdffont, '', (int)$pdfparams['body_font_size']);
10043 if ($pdfparams['show_header'] == 0 || !(count($pdfparams['header_data']) > 0)) {
10044 $pdf->SetPrintHeader(false);
10045 }
10046 if ($pdfparams['show_footer'] == 0) {
10047 $pdf->SetPrintFooter(false);
10048 }
10049 $pdf->AddPage();
10050 $pdf->writeHTML($invoice_body, true, false, true, false, '');
10051 $pdf->lastPage();
10052 $pdf->Output($pathpdf, 'F');
10053 if (!file_exists($pathpdf)) {
10054 return false;
10055 } else {
10056 /**
10057 * @wponly - trigger files mirroring
10058 */
10059 VikBookingLoader::import('update.manager');
10060 VikBookingUpdateManager::triggerUploadBackup($pathpdf);
10061 //
10062 }
10063 //insert or update record for this invoice
10064 $invoice_id = 0;
10065 $q = "SELECT `id` FROM `#__vikbooking_invoices` WHERE `idorder`=".(int)$booking['id'].";";
10066 $dbo->setQuery($q);
10067 $dbo->execute();
10068 if ($dbo->getNumRows() > 0) {
10069 $invoice_data = $dbo->loadAssocList();
10070 $invoice_id = $invoice_data[0]['id'];
10071 }
10072 //Booking History
10073 self::getBookingHistoryInstance()->setBid($booking['id'])->store('BI', '#'.$invoice_num.$invoice_suff);
10074 //
10075 if ($invoice_id > 0) {
10076 //update
10077 $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.";";
10078 $dbo->setQuery($q);
10079 $dbo->execute();
10080 $retval = $invoice_id;
10081 } else {
10082 //insert
10083 $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.");";
10084 $dbo->setQuery($q);
10085 $dbo->execute();
10086 $lid = $dbo->insertid();
10087 $retval = $lid > 0 ? $lid : false;
10088 }
10089
10090 /**
10091 * The generation of the analogic invoices can trigger the drivers for the
10092 * generation of the e-Invoices if they are set to automatically run.
10093 * However, the e-Invoicing classes may be calling this method, so we need
10094 * to make sure the eInvoicing class is not running before proceeding.
10095 * This method could be called within a loop, and so the second iterations
10096 * may already have loaded the eInvocing class. For this we use a static variable.
10097 * This piece of code should run after updating the information of the PDF invoice.
10098 *
10099 * @since 1.11
10100 */
10101 static $einvocing_can_run = false;
10102 if (!defined('VBO_EINVOICING_RUN') && !$einvocing_can_run) {
10103 // allow second iterations calling this method to run
10104 $einvocing_can_run = true;
10105 }
10106 if ($einvocing_can_run === true) {
10107 $q = "SELECT * FROM `#__vikbooking_einvoicing_config` WHERE `automatic`=1;";
10108 $dbo->setQuery($q);
10109 $dbo->execute();
10110 if ($dbo->getNumRows()) {
10111 $drivers = $dbo->loadAssocList();
10112 // require the parent abstract class
10113 $driver_base = VBO_ADMIN_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'einvoicing' . DIRECTORY_SEPARATOR;
10114 require_once $driver_base . 'einvoicing.php';
10115
10116 /**
10117 * Trigger event to let other plugins register additional drivers.
10118 *
10119 * @return array A list of supported drivers.
10120 */
10121 $list = JFactory::getApplication()->triggerEvent('onLoadEinvoicingDrivers');
10122
10123 // invoke all drivers that should run automatically
10124 $driver_base .= 'drivers' . DIRECTORY_SEPARATOR;
10125 foreach ($drivers as $driver) {
10126 $driver_file = $driver['driver'].'.php';
10127 $driver_path = $driver_base . $driver_file;
10128 if (is_file($driver_path)) {
10129 // require the driver sub-class
10130 require_once $driver_path;
10131 }
10132 // invoke the class
10133 $classname = 'VikBookingEInvoicing'.str_replace(' ', '', ucwords(str_replace('.php', '', str_replace('_', ' ', $driver_file))));
10134 if (!class_exists($classname)) {
10135 continue;
10136 }
10137 $driver_obj = new $classname;
10138 // set the flag of the external call
10139 $driver_obj->externalCall = __METHOD__;
10140 // inject invoice number to avoid discrepanices between analogic and electronic, maybe due to missing information for the e-Invoices
10141 $driver_obj->externalData['einvnum'] = $invoice_num;
10142 // generate the e-Invoice
10143 $driver_err = '';
10144 if (!$driver_obj->generateEInvoice((int)$booking['id'])) {
10145 $driver_err = $driver_obj->getError();
10146 VikError::raiseWarning('', $driver_err);
10147 }
10148 // Booking History - store log for the e-Invoice result
10149 self::getBookingHistoryInstance()->setBid($booking['id'])->store('BI', $driver_obj->getName().(!empty($driver_err) ? ': '.$driver_err : ''));
10150 }
10151 }
10152 }
10153 //
10154
10155 // return the result of the generation of the PDF invoice
10156 return $retval;
10157 }
10158
10159 /**
10160 * Generates an analogic invoice in PDF format for a custom list of services.
10161 * No bookings are assigned to this custom invoice. The method parses the same
10162 * invoice template as for the regular process with real booking IDs.
10163 * The invoice number must be stored before calling this method.
10164 *
10165 * @param int $invoice_id the ID of the custom invoice record
10166 *
10167 * @return boolean
10168 *
10169 * @since 1.11.1
10170 */
10171 public static function generateCustomInvoice($invoice_id) {
10172 $dbo = JFactory::getDbo();
10173 $mainframe = JFactory::getApplication();
10174 $q = "SELECT * FROM `#__vikbooking_invoices` WHERE `id`=".(int)$invoice_id." AND `idorder` < 0;";
10175 $dbo->setQuery($q);
10176 $dbo->execute();
10177 if (!$dbo->getNumRows()) {
10178 return false;
10179 }
10180 $invoice = $dbo->loadAssoc();
10181 $rawcont = !empty($invoice['rawcont']) ? json_decode($invoice['rawcont'], true) : array();
10182 $rawcont = is_array($rawcont) ? $rawcont : array();
10183 $rows = isset($rawcont['rows']) ? $rawcont['rows'] : array();
10184 $invoice['rawcont'] = $rawcont;
10185 $customer = self::getCPinIstance()->getCustomerByID($invoice['idcustomer']);
10186 if (!count($rawcont) || !count($rows) || !count($customer)) {
10187 // at least one invoice raw is mandatory as well as the customer
10188 return false;
10189 }
10190 $company_info = self::getInvoiceCompanyInfo();
10191 // load invoice template file
10192 list($invoicetpl, $pdfparams) = self::loadCustomInvoiceTmpl($invoice, $customer);
10193 // parse invoice template file
10194 $invoice_body = self::parseCustomInvoiceTemplate($invoicetpl, $invoice, $customer);
10195 //
10196 if (!class_exists('TCPDF')) {
10197 require_once(VBO_SITE_PATH . DIRECTORY_SEPARATOR . "helpers" . DIRECTORY_SEPARATOR . "tcpdf" . DIRECTORY_SEPARATOR . 'tcpdf.php');
10198 }
10199 $usepdffont = is_file(VBO_SITE_PATH . DIRECTORY_SEPARATOR . "helpers" . DIRECTORY_SEPARATOR . "tcpdf" . DIRECTORY_SEPARATOR . "fonts" . DIRECTORY_SEPARATOR . "dejavusans.php") ? 'dejavusans' : 'helvetica';
10200
10201 /**
10202 * Trigger event to allow third party plugins to return a specific font name.
10203 *
10204 * @since 1.16.0 (J) - 1.6.0 (WP)
10205 */
10206 $custom_pdf_font = VBOFactory::getPlatform()->getDispatcher()->filter('onGetPdfFontNameVikBooking', [$usepdffont]);
10207 if (is_array($custom_pdf_font) && !empty($custom_pdf_font[0])) {
10208 $usepdffont = $custom_pdf_font[0];
10209 }
10210
10211 $pdffname = $invoice['file_name'];
10212 $pathpdf = VBO_SITE_PATH . DIRECTORY_SEPARATOR . "helpers" . DIRECTORY_SEPARATOR . "invoices" . DIRECTORY_SEPARATOR . "generated" . DIRECTORY_SEPARATOR . $pdffname;
10213 if (file_exists($pathpdf)) @unlink($pathpdf);
10214 $pdf_page_format = is_array($pdfparams['pdf_page_format']) ? $pdfparams['pdf_page_format'] : constant($pdfparams['pdf_page_format']);
10215 $pdf = new TCPDF(constant($pdfparams['pdf_page_orientation']), constant($pdfparams['pdf_unit']), $pdf_page_format, true, 'UTF-8', false);
10216 $pdf->SetTitle(JText::translate('VBOINVNUM').' '.$invoice['number']);
10217 //Header for each page of the pdf
10218 if ($pdfparams['show_header'] == 1 && count($pdfparams['header_data']) > 0) {
10219 $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]);
10220 }
10221 //Change some currencies to their unicode (decimal) value
10222 $currencyname = self::getCurrencyName();
10223 $unichr_map = array('EUR' => 8364, 'USD' => 36, 'AUD' => 36, 'CAD' => 36, 'GBP' => 163);
10224 if (array_key_exists($currencyname, $unichr_map)) {
10225 $invoice_body = str_replace($currencyname, TCPDF_FONTS::unichr($unichr_map[$currencyname]), $invoice_body);
10226 }
10227 //header and footer fonts
10228 $pdf->setHeaderFont(array($usepdffont, '', $pdfparams['header_font_size']));
10229 $pdf->setFooterFont(array($usepdffont, '', $pdfparams['footer_font_size']));
10230 //margins
10231 $pdf->SetMargins(constant($pdfparams['pdf_margin_left']), constant($pdfparams['pdf_margin_top']), constant($pdfparams['pdf_margin_right']));
10232 $pdf->SetHeaderMargin(constant($pdfparams['pdf_margin_header']));
10233 $pdf->SetFooterMargin(constant($pdfparams['pdf_margin_footer']));
10234 //
10235 $pdf->SetAutoPageBreak(true, constant($pdfparams['pdf_margin_bottom']));
10236 $pdf->setImageScale(constant($pdfparams['pdf_image_scale_ratio']));
10237 $pdf->SetFont($usepdffont, '', (int)$pdfparams['body_font_size']);
10238 if ($pdfparams['show_header'] == 0 || !(count($pdfparams['header_data']) > 0)) {
10239 $pdf->SetPrintHeader(false);
10240 }
10241 if ($pdfparams['show_footer'] == 0) {
10242 $pdf->SetPrintFooter(false);
10243 }
10244 $pdf->AddPage();
10245 $pdf->writeHTML($invoice_body, true, false, true, false, '');
10246 $pdf->lastPage();
10247 $pdf->Output($pathpdf, 'F');
10248 if (is_file($pathpdf)) {
10249 /**
10250 * @wponly - trigger files mirroring
10251 */
10252 VikBookingLoader::import('update.manager');
10253 VikBookingUpdateManager::triggerUploadBackup($pathpdf);
10254 //
10255 }
10256
10257 /**
10258 * The generation of the analogic invoices can trigger the drivers for the
10259 * generation of the e-Invoices if they are set to automatically run.
10260 * However, the e-Invoicing classes may be calling this method, so we need
10261 * to make sure the eInvoicing class is not running before proceeding.
10262 * This method could be called within a loop, and so the second iterations
10263 * may already have loaded the eInvocing class. For this we use a static variable.
10264 * This piece of code should run after updating the information of the PDF invoice.
10265 *
10266 * @since 1.11.1
10267 */
10268 $q = "SELECT * FROM `#__vikbooking_einvoicing_config` WHERE `automatic`=1;";
10269 $dbo->setQuery($q);
10270 $dbo->execute();
10271 if ($dbo->getNumRows()) {
10272 $drivers = $dbo->loadAssocList();
10273 // make sure the invoice number is just a number
10274 $invoice['number'] = str_replace(self::getInvoiceNumberSuffix(), '', $invoice['number']);
10275 // require the parent abstract class
10276 $driver_base = VBO_ADMIN_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'einvoicing' . DIRECTORY_SEPARATOR;
10277 require_once $driver_base . 'einvoicing.php';
10278
10279 /**
10280 * Trigger event to let other plugins register additional drivers.
10281 *
10282 * @return array A list of supported drivers.
10283 */
10284 $list = JFactory::getApplication()->triggerEvent('onLoadEinvoicingDrivers');
10285
10286 // invoke all drivers that should run automatically
10287 $driver_base .= 'drivers' . DIRECTORY_SEPARATOR;
10288 foreach ($drivers as $driver) {
10289 $driver_file = $driver['driver'].'.php';
10290 $driver_path = $driver_base . $driver_file;
10291 if (is_file($driver_path)) {
10292 // require the driver sub-class
10293 require_once $driver_path;
10294 }
10295 // invoke the class
10296 $classname = 'VikBookingEInvoicing'.str_replace(' ', '', ucwords(str_replace('.php', '', str_replace('_', ' ', $driver_file))));
10297 if (!class_exists($classname)) {
10298 continue;
10299 }
10300 $driver_obj = new $classname;
10301 // set the flag of the external call
10302 $driver_obj->externalCall = __METHOD__;
10303 // inject custom invoice number, date and details to avoid discrepanices between analogic and electronic
10304 $driver_obj->externalData['einvnum'] = $invoice['number'];
10305 $driver_obj->externalData['einvdate'] = (int)$invoice['for_date'];
10306 $driver_obj->externalData['einvcustom'] = $invoice;
10307 // prepare data array for the generation of the e-Invoice
10308 $einvdata = $driver_obj->prepareCustomInvoiceData($invoice, $customer);
10309 // before generating the e-invoice, make sure to obliterate it if exists already (case of update custom invoice)
10310 $preveinv = $driver_obj->eInvoiceExists(array('idorder' => $einvdata[0]['id']));
10311 if ($preveinv !== false) {
10312 $driver_obj->obliterateEInvoice(array('id' => $preveinv));
10313 }
10314 // generate the e-Invoice
10315 $driver_err = '';
10316 if (!$driver_obj->generateEInvoice($einvdata)) {
10317 $driver_err = $driver_obj->getError();
10318 VikError::raiseWarning('', $driver_err);
10319 }
10320 }
10321 }
10322 //
10323
10324 // return the result of the generation of the PDF invoice
10325 return is_file($pathpdf);
10326 }
10327
10328 public static function sendBookingInvoice($invoice_id, $booking, $text = '', $subject = '') {
10329 if (!(count($booking) > 0) || empty($invoice_id) || empty($booking['custmail'])) {
10330 return false;
10331 }
10332 $dbo = JFactory::getDbo();
10333 $invoice_data = array();
10334 $q = "SELECT * FROM `#__vikbooking_invoices` WHERE `id`=".(int)$invoice_id.";";
10335 $dbo->setQuery($q);
10336 $dbo->execute();
10337 if ($dbo->getNumRows() == 1) {
10338 $invoice_data = $dbo->loadAssoc();
10339 }
10340 if (!(count($invoice_data) > 0)) {
10341 return false;
10342 }
10343 $mail_text = empty($text) ? JText::translate('VBOEMAILINVOICEATTACHTXT') : $text;
10344 $mail_subject = empty($subject) ? JText::translate('VBOEMAILINVOICEATTACHSUBJ') : $subject;
10345 $invoice_file_path = VBO_SITE_PATH . DIRECTORY_SEPARATOR . "helpers" . DIRECTORY_SEPARATOR . "invoices" . DIRECTORY_SEPARATOR . "generated" . DIRECTORY_SEPARATOR . $invoice_data['file_name'];
10346 if (!file_exists($invoice_file_path)) {
10347 return false;
10348 }
10349 if (!class_exists('VboApplication')) {
10350 require_once(VBO_ADMIN_PATH.DIRECTORY_SEPARATOR.'helpers'.DIRECTORY_SEPARATOR.'jv_helper.php');
10351 }
10352 $vbo_app = new VboApplication;
10353 $admin_sendermail = self::getSenderMail();
10354 $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);
10355 //update record
10356 $q = "UPDATE `#__vikbooking_invoices` SET `emailed`=1, `emailed_to`=".$dbo->quote($booking['custmail'])." WHERE `id`=".(int)$invoice_id.";";
10357 $dbo->setQuery($q);
10358 $dbo->execute();
10359 //
10360 return true;
10361 }
10362
10363 public static function loadCheckinDocTmpl($booking_info = array(), $booking_rooms = array(), $customer = array()) {
10364 if (!defined('_VIKBOOKINGEXEC')) {
10365 define('_VIKBOOKINGEXEC', '1');
10366 }
10367 ob_start();
10368 include VBO_SITE_PATH . DIRECTORY_SEPARATOR . "helpers" . DIRECTORY_SEPARATOR . "checkins" . DIRECTORY_SEPARATOR . "checkin_tmpl.php";
10369 $content = ob_get_contents();
10370 ob_end_clean();
10371 $default_params = array(
10372 'show_header' => 0,
10373 'header_data' => array(),
10374 'show_footer' => 0,
10375 'pdf_page_orientation' => 'PDF_PAGE_ORIENTATION',
10376 'pdf_unit' => 'PDF_UNIT',
10377 'pdf_page_format' => 'PDF_PAGE_FORMAT',
10378 'pdf_margin_left' => 'PDF_MARGIN_LEFT',
10379 'pdf_margin_top' => 'PDF_MARGIN_TOP',
10380 'pdf_margin_right' => 'PDF_MARGIN_RIGHT',
10381 'pdf_margin_header' => 'PDF_MARGIN_HEADER',
10382 'pdf_margin_footer' => 'PDF_MARGIN_FOOTER',
10383 'pdf_margin_bottom' => 'PDF_MARGIN_BOTTOM',
10384 'pdf_image_scale_ratio' => 'PDF_IMAGE_SCALE_RATIO',
10385 'header_font_size' => '10',
10386 'body_font_size' => '10',
10387 'footer_font_size' => '8'
10388 );
10389 if (defined('_VIKBOOKING_CHECKIN_PARAMS') && isset($checkin_params) && @count($checkin_params) > 0) {
10390 $default_params = array_merge($default_params, $checkin_params);
10391 }
10392 return array($content, $default_params);
10393 }
10394
10395 public static function parseCheckinDocTemplate($checkintpl, $booking, $booking_rooms, $customer) {
10396 $parsed = $checkintpl;
10397 $dbo = JFactory::getDbo();
10398 $app = JFactory::getApplication();
10399 $nowdf = self::getDateFormat();
10400 if ($nowdf == "%d/%m/%Y") {
10401 $df='d/m/Y';
10402 } elseif ($nowdf == "%m/%d/%Y") {
10403 $df='m/d/Y';
10404 } else {
10405 $df='Y/m/d';
10406 }
10407 $datesep = self::getDateSeparator();
10408 $companylogo = self::getSiteLogo();
10409 $uselogo = '';
10410 if (!empty($companylogo)) {
10411 /**
10412 * Let's try to prevent TCPDF errors, as custom logos are always uploaded in /admin
10413 *
10414 * @since March 16th 2021
10415 */
10416 if (is_file(VBO_ADMIN_PATH . DIRECTORY_SEPARATOR . 'resources' . DIRECTORY_SEPARATOR . $companylogo)) {
10417 // $uselogo = '<img src="' . VBO_ADMIN_URI_REL . 'resources/' . $companylogo . '"/>';
10418 $uselogo = '<img src="' . VBO_ADMIN_URI . 'resources/' . $companylogo . '"/>';
10419 } elseif (is_file(VBO_SITE_PATH . DIRECTORY_SEPARATOR . 'resources' . DIRECTORY_SEPARATOR . $companylogo)) {
10420 // $uselogo = '<img src="' . VBO_SITE_URI_REL . 'resources/' . $companylogo . '"/>';
10421 $uselogo = '<img src="' . VBO_SITE_URI . 'resources/' . $companylogo . '"/>';
10422 }
10423 }
10424 $company_name = self::getFrontTitle();
10425 $company_info = self::getInvoiceCompanyInfo();
10426 $parsed = str_replace("{company_name}", $company_name, $parsed);
10427 $parsed = str_replace("{company_logo}", $uselogo, $parsed);
10428 $parsed = str_replace("{company_info}", $company_info, $parsed);
10429 $parsed = str_replace("{customer_info}", nl2br(rtrim($booking['custdata'], "\n")), $parsed);
10430 $parsed = str_replace("{checkin_date}", date(str_replace("/", $datesep, $df), $booking['checkin']), $parsed);
10431 $parsed = str_replace("{checkout_date}", date(str_replace("/", $datesep, $df), $booking['checkout']), $parsed);
10432 $parsed = str_replace("{num_nights}", $booking['days'], $parsed);
10433 $tot_guests = 0;
10434 $tot_adults = 0;
10435 $tot_children = 0;
10436 foreach ($booking_rooms as $kor => $or) {
10437 $tot_guests += ($or['adults'] + $or['children']);
10438 $tot_adults += $or['adults'];
10439 $tot_children += $or['children'];
10440 }
10441 $parsed = str_replace("{tot_guests}", $tot_guests, $parsed);
10442 $parsed = str_replace("{tot_adults}", $tot_adults, $parsed);
10443 $parsed = str_replace("{tot_children}", $tot_children, $parsed);
10444 if (count($customer) && isset($customer['comments'])) {
10445 $parsed = str_replace("{checkin_comments}", $customer['comments'], $parsed);
10446 }
10447 $termsconds = self::getTermsConditions();
10448 $parsed = str_replace("{terms_and_conditions}", $termsconds, $parsed);
10449 //custom fields replace
10450 preg_match_all('/\{customfield ([0-9]+)\}/U', $parsed, $cmatches);
10451 if (is_array($cmatches[1]) && @count($cmatches[1]) > 0) {
10452 $cfids = array();
10453 foreach ($cmatches[1] as $cfid ){
10454 $cfids[] = $cfid;
10455 }
10456 $q = "SELECT * FROM `#__vikbooking_custfields` WHERE `id` IN (".implode(", ", $cfids).");";
10457 $dbo->setQuery($q);
10458 $dbo->execute();
10459 $cfields = $dbo->getNumRows() > 0 ? $dbo->loadAssocList() : "";
10460 $vbo_tn->translateContents($cfields, '#__vikbooking_custfields');
10461 $cfmap = array();
10462 if (is_array($cfields)) {
10463 foreach ($cfields as $cf) {
10464 $cfmap[trim(JText::translate($cf['name']))] = $cf['id'];
10465 }
10466 }
10467 $cfmapreplace = array();
10468 $partsreceived = explode("\n", $booking['custdata']);
10469 if (count($partsreceived) > 0) {
10470 foreach ($partsreceived as $pst) {
10471 if (!empty($pst)) {
10472 $tmpdata = explode(":", $pst);
10473 if (array_key_exists(trim($tmpdata[0]), $cfmap)) {
10474 $cfmapreplace[$cfmap[trim($tmpdata[0])]] = trim($tmpdata[1]);
10475 }
10476 }
10477 }
10478 }
10479 foreach($cmatches[1] as $cfid ){
10480 if (array_key_exists($cfid, $cfmapreplace)) {
10481 $parsed = str_replace("{customfield ".$cfid."}", $cfmapreplace[$cfid], $parsed);
10482 } else {
10483 $parsed = str_replace("{customfield ".$cfid."}", "", $parsed);
10484 }
10485 }
10486 }
10487 //end custom fields replace
10488
10489 return $parsed;
10490 }
10491
10492 /**
10493 * Returns an array of key-value pairs to be used for building the Guest Details
10494 * in the Check-in process. The keys will be compared to the fields of the table
10495 * #__customers to see if some values already exist. The values can use lang defs
10496 * of both front-end or back-end. To be called as list(fields, attributes).
10497 *
10498 * @param boolean $precheckin true if requested for front-end pre check-in.
10499 * @param string $type optional type of custom pax fields to force.
10500 *
10501 * @return array key-value pairs for showing and collecting guest details.
10502 *
10503 * @since 1.15.0 (J) - 1.5.0 (WP) introduced custom pax data collection type.
10504 * @since 1.16.3 (J) - 1.6.3 (WP) implemented hook to override the pre-checkin custom fields.
10505 */
10506 public static function getPaxFields($precheckin = false, $type = null)
10507 {
10508 if (!$precheckin) {
10509 // back-end check-in ("registration") key-value pairs
10510
10511 // check the type of pax fields collection data
10512 $collection_type = !empty($type) ? $type : VBOFactory::getConfig()->getString('checkindata', 'basic');
10513
10514 // return the requested pax fields collection list
10515 $custom_pax_fields = VBOCheckinPax::getFields($collection_type);
10516
10517 if (is_array($custom_pax_fields) && count($custom_pax_fields)) {
10518 // requested driver returned a list of fields
10519 return $custom_pax_fields;
10520 }
10521
10522 // in case the driver is unsupported, fallback to "basic" (default) driver
10523 return VBOCheckinPax::getFields('basic');
10524 }
10525
10526 // front-end key-value pairs for pre check-in
10527 $precheckin_pax_fields = [
10528 [
10529 'first_name' => JText::translate('VBCCFIRSTNAME'),
10530 'last_name' => JText::translate('VBCCLASTNAME'),
10531 'date_birth' => JText::translate('ORDER_DBIRTH'),
10532 'place_birth' => JText::translate('VBOCUSTPLACEBIRTH'),
10533 'country' => JText::translate('ORDER_STATE'),
10534 'city' => JText::translate('ORDER_CITY'),
10535 'zip' => JText::translate('ORDER_ZIP'),
10536 'nationality' => JText::translate('VBOCUSTNATIONALITY'),
10537 'gender' => JText::translate('VBOCUSTGENDER'),
10538 'doctype' => JText::translate('VBOCUSTDOCTYPE'),
10539 'docnum' => JText::translate('VBOCUSTDOCNUM'),
10540 'documents' => JText::translate('VBO_CUSTOMER_UPLOAD_DOCS'),
10541 ],
10542 [
10543 'first_name' => 'text',
10544 'last_name' => 'text',
10545 'date_birth' => 'calendar',
10546 'place_birth' => 'text',
10547 'country' => 'country',
10548 'city' => 'text',
10549 'zip' => 'text',
10550 'nationality' => 'country',
10551 'gender' => [
10552 'M' => JText::translate('VBOCUSTGENDERM'),
10553 'F' => JText::translate('VBOCUSTGENDERF'),
10554 ],
10555 'doctype' => 'text',
10556 'docnum' => 'text',
10557 'documents' => 'file',
10558 ],
10559 ];
10560
10561 // make a safe copy of the default precheckin fields
10562 $def_precheckin_pax_fields = $precheckin_pax_fields;
10563
10564 /**
10565 * Trigger event to allow third party plugins to override the default pre-checkin pax fields.
10566 *
10567 * @since 1.16.3 (J) - 1.6.3 (WP)
10568 */
10569 VBOFactory::getPlatform()->getDispatcher()->trigger('onDisplayPrecheckinPaxFieldsVikBooking', [&$precheckin_pax_fields]);
10570
10571 if (is_array($precheckin_pax_fields) && count($precheckin_pax_fields) > 1 && !empty($precheckin_pax_fields[0]) && !empty($precheckin_pax_fields[1])) {
10572 // use the filtered pax fields
10573 $def_precheckin_pax_fields = $precheckin_pax_fields;
10574 }
10575
10576 // return the front-end key-value pairs for pre check-in
10577 return $def_precheckin_pax_fields;
10578 }
10579
10580 /**
10581 * Returns the associative list of countries from the DB.
10582 *
10583 * @param bool $tn whether to translate the country name.
10584 * @param bool $no_id whether to unset the ID of the country.
10585 *
10586 * @return array associative or empty array.
10587 *
10588 * @since 1.15.0 (J) - 1.5.0 (WP) translations supported and applied by default.
10589 * @since 1.16.0 (J) - 1.6.0 (WP) added second argument.
10590 */
10591 public static function getCountriesArray($tn = true, $no_id = true)
10592 {
10593 $dbo = JFactory::getDbo();
10594 $all_countries = [];
10595
10596 $q = "SELECT " . ($no_id ? '`id`, `country_name`, `country_3_code`' : '*') . " FROM `#__vikbooking_countries` ORDER BY `country_name` ASC;";
10597 $dbo->setQuery($q);
10598 $dbo->execute();
10599 if (!$dbo->getNumRows()) {
10600 return [];
10601 }
10602 $countries = $dbo->loadAssocList();
10603
10604 if ($tn === true) {
10605 $vbo_tn = self::getTranslator();
10606 $vbo_tn->translateContents($countries, '#__vikbooking_countries');
10607 // re-apply sorting by country name
10608 $sorting = [];
10609 foreach ($countries as $country) {
10610 $sorting[$country['country_name']] = $country;
10611 }
10612 ksort($sorting);
10613 $sorted = [];
10614 foreach ($sorting as $country) {
10615 $sorted[] = $country;
10616 }
10617 $countries = $sorted;
10618 unset($sorting, $sorted);
10619 }
10620
10621 foreach ($countries as $v) {
10622 if ($no_id) {
10623 // keep the original structure by unsetting the ID only needed for translation
10624 unset($v['id']);
10625 }
10626 $all_countries[$v['country_3_code']] = $v;
10627 }
10628
10629 return $all_countries;
10630 }
10631
10632 public static function getCountriesSelect($name, $all_countries = array(), $current_value = '', $empty_value = ' ')
10633 {
10634 if (!count($all_countries)) {
10635 $all_countries = self::getCountriesArray();
10636 }
10637
10638 $countries = '<select name="'.$name.'">'."\n";
10639 if (strlen($empty_value)) {
10640 $countries .= '<option value="">'.$empty_value.'</option>'."\n";
10641 }
10642 foreach ($all_countries as $v) {
10643 $countries .= '<option value="'.$v['country_3_code'].'"'.($v['country_3_code'] == $current_value ? ' selected="selected"' : '').'>'.$v['country_name'].'</option>'."\n";
10644 }
10645 $countries .= '</select>';
10646
10647 return $countries;
10648 }
10649
10650 public static function getThumbSize($skipsession = false)
10651 {
10652 static $thumb_size = null;
10653
10654 if ($thumb_size) {
10655 return $thumb_size;
10656 }
10657
10658 $thumb_size = VBOFactory::getConfig()->get('thumbsize', 500);
10659
10660 return $thumb_size;
10661 }
10662
10663 /**
10664 * Checks whether an iCal file for the reservation should be
10665 * attached to the confirmation email for customer and/or admin.
10666 *
10667 * @return int 1=admin+customer, 2=admin, 3=customer, 0=no
10668 *
10669 * @since 1.2.0
10670 */
10671 public static function attachIcal() {
10672 $dbo = JFactory::getDbo();
10673 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='attachical';";
10674 $dbo->setQuery($q);
10675 $dbo->execute();
10676 if ($dbo->getNumRows() > 0) {
10677 $s = $dbo->loadResult();
10678 return (int)$s;
10679 }
10680 $q = "INSERT INTO `#__vikbooking_config` (`param`,`setting`) VALUES ('attachical', '1');";
10681 $dbo->setQuery($q);
10682 $dbo->execute();
10683 return 1;
10684 }
10685
10686 /**
10687 * How the calculation of the orphan dates should take place.
10688 *
10689 * @return string "next" for only checking the bookings ahead, "prevnext" if
10690 * also the previous bookings should be checked.
10691 *
10692 * @since 1.3.0
10693 */
10694 public static function orphansCalculation() {
10695 $dbo = JFactory::getDbo();
10696 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='orphanscalculation'";
10697 $dbo->setQuery($q, 0, 1);
10698 $dbo->execute();
10699 if ($dbo->getNumRows()) {
10700 return $dbo->loadResult();
10701 }
10702 $q = "INSERT INTO `#__vikbooking_config` (`param`,`setting`) VALUES ('orphanscalculation', 'next');";
10703 $dbo->setQuery($q);
10704 $dbo->execute();
10705 return 'next';
10706 }
10707
10708 /**
10709 * Returns the name of the template file to use for the front-end View "search".
10710 *
10711 * @return string the name of the template file. New and upgraded user will both use the classic file.
10712 *
10713 * @since 1.3.0
10714 */
10715 public static function searchResultsTmpl() {
10716 $dbo = JFactory::getDbo();
10717 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='searchrestmpl'";
10718 $dbo->setQuery($q, 0, 1);
10719 $dbo->execute();
10720 if ($dbo->getNumRows()) {
10721 return $dbo->loadResult();
10722 }
10723 // only those who have updated will be missing this configuration setting
10724 $q = "INSERT INTO `#__vikbooking_config` (`param`,`setting`) VALUES ('searchrestmpl', 'classic');";
10725 $dbo->setQuery($q);
10726 $dbo->execute();
10727 return 'classic';
10728 }
10729
10730 /**
10731 * Returns a string without any new-line characters
10732 * to be used for JavaScript values without facing
10733 * errors like 'unterminated string literal'.
10734 * By passing nl2br($str) as argument, we can keep
10735 * the wanted new-line HTML tags for PRE tags.
10736 * We use implode() with just one argument to
10737 * not use an empty string as glue for the string.
10738 *
10739 * @param $str string
10740 *
10741 * @return string
10742 */
10743 public static function strTrimLiteral($str) {
10744 $str = str_replace(array("\r\n", "\r"), "\n", $str);
10745 $lines = explode("\n", $str);
10746 $new_lines = array();
10747 foreach ($lines as $i => $line) {
10748 if (strlen($line)) {
10749 $new_lines[] = trim($line);
10750 }
10751 }
10752 return implode($new_lines);
10753 }
10754
10755 /**
10756 * Returns an instance of the VboApplication class.
10757 *
10758 * @return VboApplication
10759 */
10760 public static function getVboApplication()
10761 {
10762 // require library
10763 require_once VBO_ADMIN_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'jv_helper.php';
10764
10765 // return a new instance
10766 return new VboApplication();
10767 }
10768
10769 /**
10770 * Returns the translation of the given 0-indexed week day.
10771 *
10772 * @param int $wd the 0-indexed day of the week.
10773 * @param bool $short whether to get the short version of the weekday.
10774 *
10775 * @return string the name of the week day.
10776 *
10777 * @since 1.15.0 (J) - 1.5.0 (WP) added support for short translation.
10778 */
10779 public static function sayWeekDay($wd, $short = false)
10780 {
10781 switch ($wd) {
10782 case '6' :
10783 $ret = $short ? JText::translate('VBSAT') : JText::translate('VBWEEKDAYSIX');
10784 break;
10785 case '5' :
10786 $ret = $short ? JText::translate('VBFRI') : JText::translate('VBWEEKDAYFIVE');
10787 break;
10788 case '4' :
10789 $ret = $short ? JText::translate('VBTHU') : JText::translate('VBWEEKDAYFOUR');
10790 break;
10791 case '3' :
10792 $ret = $short ? JText::translate('VBWED') : JText::translate('VBWEEKDAYTHREE');
10793 break;
10794 case '2' :
10795 $ret = $short ? JText::translate('VBTUE') : JText::translate('VBWEEKDAYTWO');
10796 break;
10797 case '1' :
10798 $ret = $short ? JText::translate('VBMON') : JText::translate('VBWEEKDAYONE');
10799 break;
10800 default :
10801 $ret = $short ? JText::translate('VBSUN') : JText::translate('VBWEEKDAYZERO');
10802 break;
10803 }
10804 return $ret;
10805 }
10806
10807 /**
10808 * Returns the translation of the given 1-indexed month of the year.
10809 *
10810 * @param int $idm the 1-indexed month of the year.
10811 * @param bool $short whether to get the short version of the month.
10812 *
10813 * @return string the name of the month.
10814 *
10815 * @since 1.15.0 (J) - 1.5.0 (WP) added support for short translation.
10816 */
10817 public static function sayMonth($idm, $short = false)
10818 {
10819 switch ($idm) {
10820 case '12' :
10821 $ret = $short ? JText::translate('VBSHORTMONTHTWELVE') : JText::translate('VBMONTHTWELVE');
10822 break;
10823 case '11' :
10824 $ret = $short ? JText::translate('VBSHORTMONTHELEVEN') : JText::translate('VBMONTHELEVEN');
10825 break;
10826 case '10' :
10827 $ret = $short ? JText::translate('VBSHORTMONTHTEN') : JText::translate('VBMONTHTEN');
10828 break;
10829 case '9' :
10830 $ret = $short ? JText::translate('VBSHORTMONTHNINE') : JText::translate('VBMONTHNINE');
10831 break;
10832 case '8' :
10833 $ret = $short ? JText::translate('VBSHORTMONTHEIGHT') : JText::translate('VBMONTHEIGHT');
10834 break;
10835 case '7' :
10836 $ret = $short ? JText::translate('VBSHORTMONTHSEVEN') : JText::translate('VBMONTHSEVEN');
10837 break;
10838 case '6' :
10839 $ret = $short ? JText::translate('VBSHORTMONTHSIX') : JText::translate('VBMONTHSIX');
10840 break;
10841 case '5' :
10842 $ret = $short ? JText::translate('VBSHORTMONTHFIVE') : JText::translate('VBMONTHFIVE');
10843 break;
10844 case '4' :
10845 $ret = $short ? JText::translate('VBSHORTMONTHFOUR') : JText::translate('VBMONTHFOUR');
10846 break;
10847 case '3' :
10848 $ret = $short ? JText::translate('VBSHORTMONTHTHREE') : JText::translate('VBMONTHTHREE');
10849 break;
10850 case '2' :
10851 $ret = $short ? JText::translate('VBSHORTMONTHTWO') : JText::translate('VBMONTHTWO');
10852 break;
10853 default :
10854 $ret = $short ? JText::translate('VBSHORTMONTHONE') : JText::translate('VBMONTHONE');
10855 break;
10856 }
10857 return $ret;
10858 }
10859
10860 public static function sayDayMonth($d) {
10861 switch ($d) {
10862 case '31' :
10863 $ret = JText::translate('VBDAYMONTHTHIRTYONE');
10864 break;
10865 case '30' :
10866 $ret = JText::translate('VBDAYMONTHTHIRTY');
10867 break;
10868 case '29' :
10869 $ret = JText::translate('VBDAYMONTHTWENTYNINE');
10870 break;
10871 case '28' :
10872 $ret = JText::translate('VBDAYMONTHTWENTYEIGHT');
10873 break;
10874 case '27' :
10875 $ret = JText::translate('VBDAYMONTHTWENTYSEVEN');
10876 break;
10877 case '26' :
10878 $ret = JText::translate('VBDAYMONTHTWENTYSIX');
10879 break;
10880 case '25' :
10881 $ret = JText::translate('VBDAYMONTHTWENTYFIVE');
10882 break;
10883 case '24' :
10884 $ret = JText::translate('VBDAYMONTHTWENTYFOUR');
10885 break;
10886 case '23' :
10887 $ret = JText::translate('VBDAYMONTHTWENTYTHREE');
10888 break;
10889 case '22' :
10890 $ret = JText::translate('VBDAYMONTHTWENTYTWO');
10891 break;
10892 case '21' :
10893 $ret = JText::translate('VBDAYMONTHTWENTYONE');
10894 break;
10895 case '20' :
10896 $ret = JText::translate('VBDAYMONTHTWENTY');
10897 break;
10898 case '19' :
10899 $ret = JText::translate('VBDAYMONTHNINETEEN');
10900 break;
10901 case '18' :
10902 $ret = JText::translate('VBDAYMONTHEIGHTEEN');
10903 break;
10904 case '17' :
10905 $ret = JText::translate('VBDAYMONTHSEVENTEEN');
10906 break;
10907 case '16' :
10908 $ret = JText::translate('VBDAYMONTHSIXTEEN');
10909 break;
10910 case '15' :
10911 $ret = JText::translate('VBDAYMONTHFIFTEEN');
10912 break;
10913 case '14' :
10914 $ret = JText::translate('VBDAYMONTHFOURTEEN');
10915 break;
10916 case '13' :
10917 $ret = JText::translate('VBDAYMONTHTHIRTEEN');
10918 break;
10919 case '12' :
10920 $ret = JText::translate('VBDAYMONTHTWELVE');
10921 break;
10922 case '11' :
10923 $ret = JText::translate('VBDAYMONTHELEVEN');
10924 break;
10925 case '10' :
10926 $ret = JText::translate('VBDAYMONTHTEN');
10927 break;
10928 case '9' :
10929 $ret = JText::translate('VBDAYMONTHNINE');
10930 break;
10931 case '8' :
10932 $ret = JText::translate('VBDAYMONTHEIGHT');
10933 break;
10934 case '7' :
10935 $ret = JText::translate('VBDAYMONTHSEVEN');
10936 break;
10937 case '6' :
10938 $ret = JText::translate('VBDAYMONTHSIX');
10939 break;
10940 case '5' :
10941 $ret = JText::translate('VBDAYMONTHFIVE');
10942 break;
10943 case '4' :
10944 $ret = JText::translate('VBDAYMONTHFOUR');
10945 break;
10946 case '3' :
10947 $ret = JText::translate('VBDAYMONTHTHREE');
10948 break;
10949 case '2' :
10950 $ret = JText::translate('VBDAYMONTHTWO');
10951 break;
10952 default :
10953 $ret = JText::translate('VBDAYMONTHONE');
10954 break;
10955 }
10956 return $ret;
10957 }
10958
10959 public static function totElements($arr) {
10960 $n = 0;
10961 if (is_array($arr)) {
10962 foreach ($arr as $a) {
10963 if (!empty($a)) {
10964 $n++;
10965 }
10966 }
10967 return $n;
10968 }
10969 return false;
10970 }
10971
10972 /**
10973 * Returns a list of documents that were uploaded
10974 * for the specified customer.
10975 *
10976 * @param integer $id The customer ID.
10977 *
10978 * @return array A list of documents.
10979 *
10980 * @since 1.3.0
10981 */
10982 public static function getCustomerDocuments($id)
10983 {
10984 $dbo = JFactory::getDbo();
10985
10986 $q = $dbo->getQuery(true)
10987 ->select($dbo->qn('docsfolder'))
10988 ->from($dbo->qn('#__vikbooking_customers'))
10989 ->where($dbo->qn('id') . ' = ' . (int) $id);
10990
10991 $dbo->setQuery($q, 0, 1);
10992 $dbo->execute();
10993
10994 if (!$dbo->getNumRows())
10995 {
10996 // customer not found
10997 return array();
10998 }
10999
11000 // retrieve customer documents directory name
11001 $dirname = $dbo->loadResult();
11002
11003 if (empty($dirname))
11004 {
11005 // no available directory
11006 return array();
11007 }
11008
11009 // build documents folder path
11010 $dirname = VBO_CUSTOMERS_PATH . DIRECTORY_SEPARATOR . $dirname;
11011
11012 if (!is_dir($dirname))
11013 {
11014 // the customer directory doesn't exist
11015 return array();
11016 }
11017
11018 // read all files from customer directory
11019 $glob = glob($dirname . DIRECTORY_SEPARATOR . '*');
11020
11021 $files = array();
11022
11023 foreach ($glob as $path)
11024 {
11025 // skip "index.html"
11026 if (!preg_match("/[\/\\\\]index\.html$/i", $path))
11027 {
11028 // extract name and extension from file path
11029 if (preg_match("/(.*)\.([a-z0-9]{2,})$/i", basename($path), $match))
11030 {
11031 $name = $match[1];
11032 $ext = $match[2];
11033 }
11034 else
11035 {
11036 $name = basename($path);
11037 $ext = '';
11038 }
11039
11040 $file = new stdClass;
11041 $file->path = $path;
11042 $file->name = $name;
11043 $file->ext = $ext;
11044 $file->basename = $file->name . '.' . $file->ext;
11045 $file->size = filesize($path);
11046 $file->date = filemtime($path);
11047 $file->url = str_replace(DIRECTORY_SEPARATOR, '/', str_replace(VBO_CUSTOMERS_PATH . DIRECTORY_SEPARATOR, VBO_CUSTOMERS_URI, $file->path));
11048
11049 $files[] = $file;
11050 }
11051 }
11052
11053 // sort files by creation date
11054 usort($files, function($a, $b)
11055 {
11056 return $b->date - $a->date;
11057 });
11058
11059 return $files;
11060 }
11061
11062 public static function displayPaymentParameters($pfile, $pparams = '') {
11063 $html = '<p>---------</p>';
11064
11065 /**
11066 * @wponly The payment gateway is now loaded
11067 * using the apposite dispatcher.
11068 *
11069 * @since 1.0.5
11070 */
11071 JLoader::import('adapter.payment.dispatcher');
11072
11073 try
11074 {
11075 $payment = JPaymentDispatcher::getInstance('vikbooking', $pfile);
11076 }
11077 catch (Exception $e)
11078 {
11079 // payment not found
11080 $html = $e->getMessage();
11081
11082 if ($code = $e->getCode())
11083 {
11084 $html = '<b>' . $code . '</b> : ' . $html;
11085 }
11086
11087 return $html;
11088 }
11089 //
11090
11091 $arrparams = !empty($pparams) ? json_decode($pparams, true) : array();
11092 $arrparams = !is_array($arrparams) ? array() : $arrparams;
11093
11094 // get admin parameters
11095 $pconfig = $payment->getAdminParameters();
11096
11097 if (count($pconfig) > 0) {
11098 $html = '';
11099 foreach ($pconfig as $value => $cont) {
11100 if (empty($value)) {
11101 continue;
11102 }
11103 $labelparts = explode('//', (isset($cont['label']) ? $cont['label'] : ''));
11104 $label = $labelparts[0];
11105 $labelhelp = isset($labelparts[1]) ? $labelparts[1] : '';
11106 if (!empty($cont['help'])) {
11107 $labelhelp = $cont['help'];
11108 }
11109 $default_paramv = isset($cont['default']) ? $cont['default'] : null;
11110 $html .= '<div class="vbo-param-container">';
11111 if (strlen($label) > 0 && (!isset($cont['hidden']) || $cont['hidden'] != true)) {
11112 $html .= '<div class="vbo-param-label">'.$label.'</div>';
11113 }
11114 $html .= '<div class="vbo-param-setting">';
11115 switch ($cont['type']) {
11116 case 'custom':
11117 $html .= $cont['html'];
11118 break;
11119 case 'select':
11120 $options = isset($cont['options']) && is_array($cont['options']) ? $cont['options'] : array();
11121 $is_assoc = (array_keys($options) !== range(0, count($options) - 1));
11122 if (isset($cont['multiple']) && $cont['multiple']) {
11123 $html .= '<select name="vikpaymentparams['.$value.'][]" multiple="multiple">';
11124 } else {
11125 $html .= '<select name="vikpaymentparams['.$value.']">';
11126 }
11127 foreach ($options as $optkey => $poption) {
11128 $checkval = $is_assoc ? $optkey : $poption;
11129 $selected = false;
11130 if (isset($arrparams[$value])) {
11131 if (is_array($arrparams[$value])) {
11132 $selected = in_array($checkval, $arrparams[$value]);
11133 } else {
11134 $selected = ($checkval == $arrparams[$value]);
11135 }
11136 } elseif (isset($default_paramv)) {
11137 if (is_array($default_paramv)) {
11138 $selected = in_array($checkval, $default_paramv);
11139 } else {
11140 $selected = ($checkval == $default_paramv);
11141 }
11142 }
11143 $html .= '<option value="' . ($is_assoc ? $optkey : $poption) . '"'.($selected ? ' selected="selected"' : '').'>'.$poption.'</option>';
11144 }
11145 $html .= '</select>';
11146 break;
11147 case 'password':
11148 $html .= '<div class="btn-wrapper input-append">';
11149 $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"/>';
11150 $html .= '<button type="button" class="btn btn-primary" onclick="vikPaymentParamTogglePwd(this);"><i class="' . VikBookingIcons::i('eye') . '"></i></button>';
11151 $html .= '</div>';
11152 break;
11153 case 'number':
11154 $number_attr = array();
11155 if (isset($cont['min'])) {
11156 $number_attr[] = 'min="' . JHtml::fetch('esc_attr', $cont['min']) . '"';
11157 }
11158 if (isset($cont['max'])) {
11159 $number_attr[] = 'max="' . JHtml::fetch('esc_attr', $cont['max']) . '"';
11160 }
11161 if (isset($cont['step'])) {
11162 $number_attr[] = 'step="' . JHtml::fetch('esc_attr', $cont['step']) . '"';
11163 }
11164 $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) . '/>';
11165 break;
11166 case 'textarea':
11167 $html .= '<textarea name="vikpaymentparams['.$value.']">'.(isset($arrparams[$value]) ? JHtml::fetch('esc_textarea', $arrparams[$value]) : JHtml::fetch('esc_textarea', $default_paramv)).'</textarea>';
11168 break;
11169 case 'hidden':
11170 $html .= '<input type="hidden" name="vikpaymentparams['.$value.']" value="'.(isset($arrparams[$value]) ? JHtml::fetch('esc_attr', $arrparams[$value]) : JHtml::fetch('esc_attr', $default_paramv)).'"/>';
11171 break;
11172 case 'checkbox':
11173 // always display a hidden input value turned off before the actual checkbox to support the "off" (0) status
11174 $html .= '<input type="hidden" name="vikpaymentparams['.$value.']" value="0" />';
11175 $html .= self::getVboApplication()->printYesNoButtons('vikpaymentparams['.$value.']', JText::translate('VBYES'), JText::translate('VBNO'), (isset($arrparams[$value]) ? (int)$arrparams[$value] : (int)$default_paramv), 1, 0);
11176 break;
11177 default:
11178 $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"/>';
11179 break;
11180 }
11181 if (strlen($labelhelp) > 0) {
11182 $html .= '<span class="vbo-param-setting-comment">'.$labelhelp.'</span>';
11183 }
11184 $html .= '</div>';
11185 $html .= '</div>';
11186 }
11187 // JS helper function to toggle the password fields
11188 $html .= "\n" . '<script>' . "\n";
11189 $html .= 'function vikPaymentParamTogglePwd(elem) {' . "\n";
11190 $html .= ' var btn = jQuery(elem), inp = btn.parent().find("input").first();' . "\n";
11191 $html .= ' if (!inp || !inp.length) {return false;}' . "\n";
11192 $html .= ' var inp_type = inp.attr("type");' . "\n";
11193 $html .= ' inp.attr("type", (inp_type == "password" ? "text" : "password"));' . "\n";
11194 $html .= '}' . "\n";
11195 $html .= "\n" . '</script>' . "\n";
11196 }
11197
11198 return $html;
11199 }
11200
11201 public static function displaySMSParameters($pfile, $params = null)
11202 {
11203 $html = '---------';
11204
11205 $params = is_string($params) ? json_decode($params, true) : $params;
11206 $params = is_array($params) ? $params : [];
11207
11208 if (empty($pfile) || !is_file(VBO_ADMIN_PATH . DIRECTORY_SEPARATOR . 'smsapi' . DIRECTORY_SEPARATOR . $pfile)) {
11209 return $html;
11210 }
11211
11212 // attempt to load the class file
11213 require_once(VBO_ADMIN_PATH . DIRECTORY_SEPARATOR . 'smsapi' . DIRECTORY_SEPARATOR . $pfile);
11214
11215 if (!class_exists('VikSmsApi') || !method_exists('VikSmsApi', 'getAdminParameters')) {
11216 return $html;
11217 }
11218
11219 // load the gateway parameters
11220 $config = VikSmsApi::getAdminParameters();
11221
11222 if (!is_array($config) || !count($config)) {
11223 return $html;
11224 }
11225
11226 // flags for JS helpers
11227 $js_helpers = array();
11228
11229 $html = '';
11230 foreach ($config as $value => $cont) {
11231 if (empty($value)) {
11232 continue;
11233 }
11234 $inp_attr = '';
11235 if (isset($cont['attributes'])) {
11236 foreach ($cont['attributes'] as $inpk => $inpv) {
11237 $inp_attr .= $inpk.'="'.$inpv.'" ';
11238 }
11239 $inp_attr = ' ' . rtrim($inp_attr);
11240 }
11241 $labelparts = explode('//', (isset($cont['label']) ? $cont['label'] : ''));
11242 $label = $labelparts[0];
11243 $labelhelp = isset($labelparts[1]) ? $labelparts[1] : '';
11244 if (!empty($cont['help'])) {
11245 $labelhelp = $cont['help'];
11246 }
11247 $default_paramv = isset($cont['default']) ? $cont['default'] : null;
11248 $html .= '<div class="vbo-param-container' . (in_array($cont['type'], array('textarea', 'visual_html')) ? ' vbo-param-container-full' : '') . '">';
11249 if (strlen($label) > 0 && (!isset($cont['hidden']) || $cont['hidden'] != true)) {
11250 $html .= '<div class="vbo-param-label">'.$label.'</div>';
11251 }
11252 $html .= '<div class="vbo-param-setting">';
11253 switch ($cont['type']) {
11254 case 'custom':
11255 $html .= $cont['html'];
11256 break;
11257 case 'select':
11258 $options = isset($cont['options']) && is_array($cont['options']) ? $cont['options'] : array();
11259 $is_assoc = (array_keys($options) !== range(0, count($options) - 1));
11260 if (isset($cont['multiple']) && $cont['multiple']) {
11261 $html .= '<select name="viksmsparams['.$value.'][]" multiple="multiple"' . $inp_attr . '>';
11262 } else {
11263 $html .= '<select name="viksmsparams['.$value.']"' . $inp_attr . '>';
11264 }
11265 foreach ($options as $optkey => $poption) {
11266 $checkval = $is_assoc ? $optkey : $poption;
11267 $selected = false;
11268 if (isset($params[$value])) {
11269 if (is_array($params[$value])) {
11270 $selected = in_array($checkval, $params[$value]);
11271 } else {
11272 $selected = ($checkval == $params[$value]);
11273 }
11274 } elseif (isset($default_paramv)) {
11275 if (is_array($default_paramv)) {
11276 $selected = in_array($checkval, $default_paramv);
11277 } else {
11278 $selected = ($checkval == $default_paramv);
11279 }
11280 }
11281 $html .= '<option value="' . ($is_assoc ? $optkey : $poption) . '"'.($selected ? ' selected="selected"' : '').'>'.$poption.'</option>';
11282 }
11283 $html .= '</select>';
11284 break;
11285 case 'password':
11286 $html .= '<div class="btn-wrapper input-append">';
11287 $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 . '/>';
11288 $html .= '<button type="button" class="btn btn-primary" onclick="vikSMSParamTogglePwd(this);"><i class="' . VikBookingIcons::i('eye') . '"></i></button>';
11289 $html .= '</div>';
11290 // set flag for JS helper
11291 $js_helpers[] = $cont['type'];
11292 break;
11293 case 'number':
11294 $number_attr = array();
11295 if (isset($cont['min'])) {
11296 $number_attr[] = 'min="' . JHtml::fetch('esc_attr', $cont['min']) . '"';
11297 }
11298 if (isset($cont['max'])) {
11299 $number_attr[] = 'max="' . JHtml::fetch('esc_attr', $cont['max']) . '"';
11300 }
11301 if (isset($cont['step'])) {
11302 $number_attr[] = 'step="' . JHtml::fetch('esc_attr', $cont['step']) . '"';
11303 }
11304 $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 . '/>';
11305 break;
11306 case 'textarea':
11307 $html .= '<textarea name="viksmsparams['.$value.']"' . $inp_attr . '>'.(isset($params[$value]) ? JHtml::fetch('esc_textarea', $params[$value]) : JHtml::fetch('esc_textarea', $default_paramv)).'</textarea>';
11308 break;
11309 case 'visual_html':
11310 $tarea_cont = isset($params[$value]) ? JHtml::fetch('esc_textarea', $params[$value]) : JHtml::fetch('esc_textarea', $default_paramv);
11311 $tarea_attr = isset($cont['attributes']) && is_array($cont['attributes']) ? $cont['attributes'] : array();
11312 $editor_opts = isset($cont['editor_opts']) && is_array($cont['editor_opts']) ? $cont['editor_opts'] : array();
11313 $editor_btns = isset($cont['editor_btns']) && is_array($cont['editor_btns']) ? $cont['editor_btns'] : array();
11314 $html .= self::getVboApplication()->renderVisualEditor('viksmsparams[' . $value . ']', $tarea_cont, $tarea_attr, $editor_opts, $editor_btns);
11315 break;
11316 case 'hidden':
11317 $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 . '/>';
11318 break;
11319 case 'checkbox':
11320 // always display a hidden input value turned off before the actual checkbox to support the "off" (0) status
11321 $html .= '<input type="hidden" name="viksmsparams['.$value.']" value="0" />';
11322 $html .= self::getVboApplication()->printYesNoButtons('viksmsparams['.$value.']', JText::translate('VBYES'), JText::translate('VBNO'), (isset($params[$value]) ? (int)$params[$value] : (int)$default_paramv), 1, 0);
11323 break;
11324 default:
11325 $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 . '/>';
11326 break;
11327 }
11328 if (strlen($labelhelp) > 0) {
11329 $html .= '<span class="vbo-param-setting-comment">'.$labelhelp.'</span>';
11330 }
11331 $html .= '</div>';
11332 $html .= '</div>';
11333 }
11334
11335 // JS helper functions
11336 if (in_array('password', $js_helpers)) {
11337 // toggle the password fields
11338 $html .= "\n" . '<script>' . "\n";
11339 $html .= 'function vikSMSParamTogglePwd(elem) {' . "\n";
11340 $html .= ' var btn = jQuery(elem), inp = btn.parent().find("input").first();' . "\n";
11341 $html .= ' if (!inp || !inp.length) {return false;}' . "\n";
11342 $html .= ' var inp_type = inp.attr("type");' . "\n";
11343 $html .= ' inp.attr("type", (inp_type == "password" ? "text" : "password"));' . "\n";
11344 $html .= '}' . "\n";
11345 $html .= "\n" . '</script>' . "\n";
11346 }
11347
11348 return $html;
11349 }
11350
11351 /**
11352 * Renders the params of the requested cron job file-class.
11353 *
11354 * @param string $file the name of the cron job driver file.
11355 * @param array $params the parameters array.
11356 *
11357 * @return string the necessary HTML content to render.
11358 *
11359 * @since 1.15.0 (J) - 1.5.0 (WP) - support for new fields added.
11360 */
11361 public static function displayCronParameters($file, $params = [])
11362 {
11363 try
11364 {
11365 // attempt to create a new instance
11366 $job = VBOFactory::getCronFactory()->createInstance($file);
11367 }
11368 catch (Exception $e)
11369 {
11370 // something went wrong, display error message
11371 return '<p>' . $e->getMessage() . '<p>';
11372 }
11373
11374 // get admin parameters
11375 $config = $job->getForm();
11376
11377 if (!is_array($config) || !count($config))
11378 {
11379 return '<p>---------</p>';
11380 }
11381
11382 // flags for JS helpers
11383 $js_helpers = array();
11384
11385 $html = '';
11386 foreach ($config as $value => $cont) {
11387 if (empty($value)) {
11388 continue;
11389 }
11390
11391 $inp_attr = '';
11392 if (isset($cont['attributes']) && is_array($cont['attributes'])) {
11393 foreach ($cont['attributes'] as $inpk => $inpv) {
11394 $inp_attr .= $inpk . '="' . $inpv . '" ';
11395 }
11396 $inp_attr = ' ' . rtrim($inp_attr);
11397 }
11398
11399 $labelparts = explode('//', (isset($cont['label']) ? $cont['label'] : ''));
11400 $label = $labelparts[0];
11401 $labelhelp = isset($labelparts[1]) ? $labelparts[1] : '';
11402 if (!empty($cont['help'])) {
11403 $labelhelp = $cont['help'];
11404 }
11405
11406 $default_paramv = isset($cont['default']) ? $cont['default'] : null;
11407 $nested_style = (isset($cont['nested']) && $cont['nested']);
11408 $hidden_wrapper = false;
11409 if (isset($cont['conditional']) && isset($config[$cont['conditional']])) {
11410 $check_cond = isset($config[$cont['conditional']]['default']) ? $config[$cont['conditional']]['default'] : null;
11411 $check_cond = isset($params[$cont['conditional']]) ? $params[$cont['conditional']] : $check_cond;
11412 if (!is_null($check_cond) && (!$check_cond || !strcasecmp($check_cond, 'off'))) {
11413 // hide current field because the field to who this is dependant is "off" or disabled (i.e. 0)
11414 $hidden_wrapper = true;
11415 }
11416 }
11417
11418 $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;"' : '') . '>';
11419 if (strlen($label) > 0 && (!isset($cont['hidden']) || $cont['hidden'] != true)) {
11420 $html .= '<div class="vbo-param-label">' . $label . '</div>';
11421 }
11422 $html .= '<div class="vbo-param-setting">';
11423
11424 switch ($cont['type']) {
11425 case 'custom':
11426 $html .= $cont['html'];
11427 break;
11428 case 'select':
11429 $options = isset($cont['options']) && is_array($cont['options']) ? $cont['options'] : [];
11430 $is_assoc = (array_keys($options) !== range(0, count($options) - 1));
11431 if (isset($cont['multiple']) && $cont['multiple']) {
11432 $html .= '<select name="vikcronparams['.$value.'][]" multiple="multiple"' . $inp_attr . '>';
11433 } else {
11434 $html .= '<select name="vikcronparams['.$value.']"' . $inp_attr . '>';
11435 }
11436 foreach ($options as $optkey => $poption) {
11437 $checkval = $is_assoc ? $optkey : $poption;
11438 $selected = false;
11439 if (isset($params[$value])) {
11440 if (is_array($params[$value])) {
11441 $selected = in_array($checkval, $params[$value]);
11442 } else {
11443 $selected = ($checkval == $params[$value]);
11444 }
11445 } elseif (isset($default_paramv)) {
11446 if (is_array($default_paramv)) {
11447 $selected = in_array($checkval, $default_paramv);
11448 } else {
11449 $selected = ($checkval == $default_paramv);
11450 }
11451 }
11452 $html .= '<option value="' . ($is_assoc ? $optkey : $poption) . '"'.($selected ? ' selected="selected"' : '').'>'.$poption.'</option>';
11453 }
11454 $html .= '</select>';
11455 break;
11456 case 'password':
11457 $html .= '<div class="btn-wrapper input-append">';
11458 $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 . '/>';
11459 $html .= '<button type="button" class="btn btn-primary" onclick="vikCronParamTogglePwd(this);"><i class="' . VikBookingIcons::i('eye') . '"></i></button>';
11460 $html .= '</div>';
11461 // set flag for JS helper
11462 $js_helpers[] = $cont['type'];
11463 break;
11464 case 'number':
11465 $number_attr = [];
11466 if (isset($cont['min'])) {
11467 $number_attr[] = 'min="' . JHtml::fetch('esc_attr', $cont['min']) . '"';
11468 }
11469 if (isset($cont['max'])) {
11470 $number_attr[] = 'max="' . JHtml::fetch('esc_attr', $cont['max']) . '"';
11471 }
11472 if (isset($cont['step'])) {
11473 $number_attr[] = 'step="' . JHtml::fetch('esc_attr', $cont['step']) . '"';
11474 }
11475 $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 . '/>';
11476 break;
11477 case 'textarea':
11478 $html .= '<textarea name="vikcronparams['.$value.']"' . $inp_attr . '>'.(isset($params[$value]) ? JHtml::fetch('esc_textarea', $params[$value]) : JHtml::fetch('esc_textarea', $default_paramv)).'</textarea>';
11479 break;
11480 case 'visual_html':
11481 $tarea_cont = isset($params[$value]) ? JHtml::fetch('esc_textarea', $params[$value]) : JHtml::fetch('esc_textarea', $default_paramv);
11482 $tarea_attr = isset($cont['attributes']) && is_array($cont['attributes']) ? $cont['attributes'] : [];
11483 $editor_opts = isset($cont['editor_opts']) && is_array($cont['editor_opts']) ? $cont['editor_opts'] : [];
11484 $editor_btns = isset($cont['editor_btns']) && is_array($cont['editor_btns']) ? $cont['editor_btns'] : [];
11485 $html .= self::getVboApplication()->renderVisualEditor('vikcronparams[' . $value . ']', $tarea_cont, $tarea_attr, $editor_opts, $editor_btns);
11486 break;
11487 case 'codemirror':
11488 $editor = JEditor::getInstance('codemirror');
11489 $e_options = isset($cont['options']) && is_array($cont['options']) ? $cont['options'] : [];
11490 $e_name = 'vikcronparams[' . $value . ']';
11491 $e_value = isset($params[$value]) ? $params[$value] : $default_paramv;
11492 $e_width = isset($e_options['width']) ? $e_options['width'] : '100%';
11493 $e_height = isset($e_options['height']) ? $e_options['height'] : 300;
11494 $e_col = isset($e_options['col']) ? $e_options['col'] : 70;
11495 $e_row = isset($e_options['row']) ? $e_options['row'] : 20;
11496 $e_buttons = isset($e_options['buttons']) ? (bool)$e_options['buttons'] : true;
11497 $e_id = isset($e_options['id']) ? $e_options['id'] : 'vikcronparams_' . $value;
11498 $e_params = isset($e_options['params']) && is_array($e_options['params']) ? $e_options['params'] : [];
11499 if (interface_exists('Throwable')) {
11500 /**
11501 * With PHP >= 7 supporting throwable exceptions for Fatal Errors
11502 * we try to avoid issues with third party plugins that make use
11503 * of the WP native function get_current_screen().
11504 *
11505 * @wponly
11506 */
11507 try {
11508 $html .= $editor->display($e_name, $e_value, $e_width, $e_height, $e_col, $e_row, $e_buttons, $e_id, $e_params);
11509 } catch (Throwable $t) {
11510 $html .= $t->getMessage() . ' in ' . $t->getFile() . ':' . $t->getLine() . '<br/>';
11511 $html .= '<textarea name="vikcronparams[' . $value . ']"' . $inp_attr . '>' . (isset($params[$value]) ? JHtml::fetch('esc_textarea', $params[$value]) : JHtml::fetch('esc_textarea', $default_paramv)) . '</textarea>';
11512 }
11513 } else {
11514 $html .= $editor->display($e_name, $e_value, $e_width, $e_height, $e_col, $e_row, $e_buttons, $e_id, $e_params);
11515 }
11516 break;
11517 case 'hidden':
11518 $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 . '/>';
11519 break;
11520 case 'checkbox':
11521 // always display a hidden input value turned off before the actual checkbox to support the "off" (0) status
11522 $html .= '<input type="hidden" name="vikcronparams['.$value.']" value="0" />';
11523 $html .= self::getVboApplication()->printYesNoButtons('vikcronparams['.$value.']', JText::translate('VBYES'), JText::translate('VBNO'), (isset($params[$value]) ? (int)$params[$value] : (int)$default_paramv), 1, 0);
11524 break;
11525 default:
11526 $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 . '/>';
11527 break;
11528 }
11529
11530 if (strlen($labelhelp)) {
11531 $html .= '<span class="vbo-param-setting-comment">'.$labelhelp.'</span>';
11532 }
11533
11534 $html .= '</div>';
11535 $html .= '</div>';
11536 }
11537
11538 // JS helper functions
11539 if (in_array('password', $js_helpers)) {
11540 // toggle the password fields
11541 $html .= "\n" . '<script>' . "\n";
11542 $html .= 'function vikCronParamTogglePwd(elem) {' . "\n";
11543 $html .= ' var btn = jQuery(elem), inp = btn.parent().find("input").first();' . "\n";
11544 $html .= ' if (!inp || !inp.length) {return false;}' . "\n";
11545 $html .= ' var inp_type = inp.attr("type");' . "\n";
11546 $html .= ' inp.attr("type", (inp_type == "password" ? "text" : "password"));' . "\n";
11547 $html .= '}' . "\n";
11548 $html .= "\n" . '</script>' . "\n";
11549 }
11550
11551 return $html;
11552 }
11553
11554 public static function invokeChannelManager($skiporder = true, $order = array()) {
11555 $task = VikRequest::getString('task', '', 'request');
11556 $view = VikRequest::getString('view', '', 'request');
11557 $tmpl = VikRequest::getString('tmpl', '', 'request');
11558 $noimpression = array('vieworder', 'booking');
11559 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')) {
11560 //VCM Channel Impression
11561 if (!class_exists('VikChannelManagerConfig')) {
11562 require_once(VCM_SITE_PATH.DIRECTORY_SEPARATOR.'helpers'.DIRECTORY_SEPARATOR.'vcm_config.php');
11563 }
11564 if (!class_exists('VikChannelManager')) {
11565 require_once(VCM_SITE_PATH.DIRECTORY_SEPARATOR.'helpers'.DIRECTORY_SEPARATOR.'lib.vikchannelmanager.php');
11566 }
11567 VikChannelManager::invokeChannelImpression();
11568 } elseif ($tmpl != 'component' && count($order) > 0 && file_exists(VCM_SITE_PATH.DIRECTORY_SEPARATOR.'helpers'.DIRECTORY_SEPARATOR.'lib.vikchannelmanager.php')) {
11569 //VCM Channel Conversion-Impression
11570 if (!class_exists('VikChannelManagerConfig')) {
11571 require_once(VCM_SITE_PATH.DIRECTORY_SEPARATOR.'helpers'.DIRECTORY_SEPARATOR.'vcm_config.php');
11572 }
11573 if (!class_exists('VikChannelManager')) {
11574 require_once(VCM_SITE_PATH.DIRECTORY_SEPARATOR.'helpers'.DIRECTORY_SEPARATOR.'lib.vikchannelmanager.php');
11575 }
11576 VikChannelManager::invokeChannelConversionImpression($order);
11577 }
11578 }
11579
11580 public static function validEmail($email) {
11581 $isValid = true;
11582 $atIndex = strrpos($email, "@");
11583 if (is_bool($atIndex) && !$atIndex) {
11584 $isValid = false;
11585 } else {
11586 $domain = substr($email, $atIndex +1);
11587 $local = substr($email, 0, $atIndex);
11588 $localLen = strlen($local);
11589 $domainLen = strlen($domain);
11590 if ($localLen < 1 || $localLen > 64) {
11591 // local part length exceeded
11592 $isValid = false;
11593 } else
11594 if ($domainLen < 1 || $domainLen > 255) {
11595 // domain part length exceeded
11596 $isValid = false;
11597 } else
11598 if ($local[0] == '.' || $local[$localLen -1] == '.') {
11599 // local part starts or ends with '.'
11600 $isValid = false;
11601 } else
11602 if (preg_match('/\\.\\./', $local)) {
11603 // local part has two consecutive dots
11604 $isValid = false;
11605 } else
11606 if (!preg_match('/^[A-Za-z0-9\\-\\.]+$/', $domain)) {
11607 // character not valid in domain part
11608 $isValid = false;
11609 } else
11610 if (preg_match('/\\.\\./', $domain)) {
11611 // domain part has two consecutive dots
11612 $isValid = false;
11613 } else
11614 if (!preg_match('/^(\\\\.|[A-Za-z0-9!#%&`_=\\/$\'*+?^{}|~.-])+$/', str_replace("\\\\", "", $local))) {
11615 // character not valid in local part unless
11616 // local part is quoted
11617 if (!preg_match('/^"(\\\\"|[^"])+"$/', str_replace("\\\\", "", $local))) {
11618 $isValid = false;
11619 }
11620 }
11621 if ($isValid && !(checkdnsrr($domain, "MX") || checkdnsrr($domain, "A"))) {
11622 // domain not found in DNS
11623 $isValid = false;
11624 }
11625 }
11626 return $isValid;
11627 }
11628
11629 public static function caniWrite($path) {
11630 if ($path[strlen($path) - 1] == '/') {
11631 // ricorsivo return a temporary file path
11632 return self::caniWrite($path . uniqid(mt_rand()) . '.tmp');
11633 }
11634 if (is_dir($path)) {
11635 return self::caniWrite($path . '/' . uniqid(mt_rand()) . '.tmp');
11636 }
11637 // check tmp file for read/write capabilities
11638 $rm = file_exists($path);
11639 $f = @fopen($path, 'a');
11640 if ($f === false) {
11641 return false;
11642 }
11643 fclose($f);
11644 if (!$rm) {
11645 unlink($path);
11646 }
11647 return true;
11648 }
11649
11650 /**
11651 * Alias method of JFile::upload to unify any
11652 * upload function into one.
11653 *
11654 * @param string $src The name of the php (temporary) uploaded file.
11655 * @param string $dest The path (including filename) to move the uploaded file to.
11656 * @param boolean [$copy_only] Whether to skip the file upload and just copy the file.
11657 *
11658 * @return boolean True on success.
11659 *
11660 * @since 1.10 - Revision April 24th 2018 for compatibility with the VikWP Framework.
11661 * @wponly 1.0.7 added the third $copy_only argument to remove the use of copy()
11662 */
11663 public static function uploadFile($src, $dest, $copy_only = false) {
11664 // always attempt to include the File class
11665 jimport('joomla.filesystem.file');
11666
11667 // upload the file
11668 if (!$copy_only) {
11669 $result = JFile::upload($src, $dest);
11670 } else {
11671 // this is to avoid the use of the PHP function copy() and allow files mirroring in WP (triggerUploadBackup)
11672 $result = JFile::copy($src, $dest);
11673 }
11674
11675 /**
11676 * @wponly in order to not lose uploaded files after installing an update,
11677 * we need to move any uploaded file onto a recovery folder.
11678 */
11679 if ($result) {
11680 VikBookingLoader::import('update.manager');
11681 VikBookingUpdateManager::triggerUploadBackup($dest);
11682 }
11683 //
11684
11685 // return upload result
11686 return $result;
11687 }
11688
11689 /**
11690 * Helper method used to upload the given file (retrieved from $_FILES)
11691 * into the specified destination.
11692 *
11693 * @param array $file An associative array with the file details.
11694 * @param string $dest The destination path.
11695 * @param string $filters A string (or a regex) containing the allowed extensions.
11696 *
11697 * @return array The uploading result.
11698 *
11699 * @throws RuntimeException
11700 *
11701 * @since 1.3.0
11702 */
11703 public static function uploadFileFromRequest($file, $dest, $filters = '*')
11704 {
11705 jimport('joomla.filesystem.file');
11706
11707 $dest = rtrim($dest, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
11708
11709 if (empty($file['name']))
11710 {
11711 throw new RuntimeException('Missing file', 400);
11712 }
11713
11714 /**
11715 * Make sure the upload of the file didn't raise any errors.
11716 *
11717 * @link https://www.php.net/manual/en/features.file-upload.errors.php
11718 */
11719 if ((int) $file['error'])
11720 {
11721 if ($file['error'] == UPLOAD_ERR_INI_SIZE)
11722 {
11723 $error = 'The uploaded file exceeds the upload_max_filesize directive in php.ini.';
11724 }
11725 else if ($file['error'] == UPLOAD_ERR_FORM_SIZE)
11726 {
11727 $error = 'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form.';
11728 }
11729 else if ($file['error'] == UPLOAD_ERR_PARTIAL)
11730 {
11731 $error = 'The uploaded file was only partially uploaded.';
11732 }
11733 else if ($file['error'] == UPLOAD_ERR_NO_FILE)
11734 {
11735 $error = 'No file was uploaded.';
11736 }
11737 else if ($file['error'] == UPLOAD_ERR_NO_TMP_DIR)
11738 {
11739 $error = 'Missing a temporary folder.';
11740 }
11741 else if ($file['error'] == UPLOAD_ERR_CANT_WRITE)
11742 {
11743 $error = 'Failed to write file to disk.';
11744 }
11745 else if ($file['error'] == UPLOAD_ERR_EXTENSION)
11746 {
11747 $error = 'A PHP extension stopped the file upload.';
11748 }
11749 else
11750 {
11751 $error = 'Unknown error.';
11752 }
11753
11754 throw new RuntimeException($error, 500);
11755 }
11756
11757 $src = $file['tmp_name'];
11758
11759 // extract file name and extension
11760 if (preg_match("/(.*?)(\.[0-9a-z]{2,})$/i", basename($file['name']), $match))
11761 {
11762 $filename = $match[1];
11763 $fileext = $match[2];
11764 }
11765 else
11766 {
11767 // probably no extension provided
11768 $filename = basename($file['name']);
11769 $fileext = '';
11770 }
11771
11772 $j = '';
11773
11774 if (file_exists($dest . $filename . $fileext))
11775 {
11776 $j = 2;
11777
11778 while (file_exists($dest . $filename . '-' . $j . $fileext))
11779 {
11780 $j++;
11781 }
11782
11783 $j = '-' . $j;
11784 }
11785
11786 $finaldest = $dest . $filename . $j . $fileext;
11787
11788 if ($filters !== '*')
11789 {
11790 $ext = $file['type'];
11791
11792 // check if we have a regex
11793 if (preg_match("/^[#\/]/", $filters) && preg_match("/[#\/][a-z]*$/", $filters))
11794 {
11795 if (!preg_match($filters, $ext))
11796 {
11797 // extension not supported
11798 throw new RuntimeException(sprintf('Extension [%s] is not supported', $ext), 400);
11799 }
11800 }
11801 else
11802 {
11803 // get all supported types
11804 $types = array_map('strtolower', array_filter(explode(',', $filters)));
11805
11806 if (!in_array($ext, $types))
11807 {
11808 // extension not supported
11809 throw new RuntimeException(sprintf('Extension [%s] is not supported', $ext), 400);
11810 }
11811 }
11812 }
11813
11814 // try to upload the file
11815 if (!JFile::upload($src, $finaldest, $use_streams = false, $allow_unsafe = true))
11816 {
11817 throw new RuntimeException(sprintf('Unable to upload the file [%s] to [%s]', $src, $finaldest), 500);
11818 }
11819
11820 $file = new stdClass;
11821 $file->name = $filename . $j;
11822 $file->ext = ltrim($fileext, '.');
11823 $file->filename = basename($finaldest);
11824 $file->path = $finaldest;
11825
11826 return $file;
11827 }
11828
11829 /**
11830 * Gets the instance of a specific report class.
11831 *
11832 * @param string $report the name of the report to load.
11833 *
11834 * @return mixed false or report object instance.
11835 *
11836 * @since 1.3.0
11837 */
11838 public static function getReportInstance($report)
11839 {
11840 require_once VBO_ADMIN_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'report' . DIRECTORY_SEPARATOR . 'report.php';
11841
11842 return VikBookingReport::getInstanceOf($report);
11843 }
11844
11845 /**
11846 * Checks whether the guest reviews are enabled and VCM is installed.
11847 *
11848 * @return boolean true if enabled, false otherwise.
11849 *
11850 * @since 1.3.0
11851 */
11852 public static function allowGuestReviews()
11853 {
11854 $dbo = JFactory::getDbo();
11855 $vcm_installed = is_file(VCM_SITE_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'lib.vikchannelmanager.php');
11856 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='grenabled';";
11857 $dbo->setQuery($q);
11858 $dbo->execute();
11859 if ($dbo->getNumRows()) {
11860 $s = $dbo->loadResult();
11861 return ((int)$s === 1 && $vcm_installed);
11862 }
11863 $q = "INSERT INTO `#__vikbooking_config` (`param`,`setting`) VALUES ('grenabled', '1');";
11864 $dbo->setQuery($q);
11865 $dbo->execute();
11866 return $vcm_installed;
11867 }
11868
11869 /**
11870 * Gets the minimum chars for the guest review message.
11871 *
11872 * @return int minimum number of chars for the comment (0 = no limits, -1 = disabled).
11873 *
11874 * @since 1.3.0
11875 */
11876 public static function guestReviewMinChars()
11877 {
11878 $dbo = JFactory::getDbo();
11879 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='grminchars';";
11880 $dbo->setQuery($q);
11881 $dbo->execute();
11882 if ($dbo->getNumRows() > 0) {
11883 return (int)$dbo->loadResult();
11884 }
11885 $q = "INSERT INTO `#__vikbooking_config` (`param`,`setting`) VALUES ('grminchars', '15');";
11886 $dbo->setQuery($q);
11887 $dbo->execute();
11888 return 15;
11889 }
11890
11891 /**
11892 * The approval type for new guest reviews.
11893 *
11894 * @return string auto or manual.
11895 *
11896 * @since 1.3.0
11897 */
11898 public static function guestReviewsApproval()
11899 {
11900 $dbo = JFactory::getDbo();
11901 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='grappr';";
11902 $dbo->setQuery($q);
11903 $dbo->execute();
11904 if ($dbo->getNumRows() > 0) {
11905 return $dbo->loadResult() == 'manual' ? 'manual' : 'auto';
11906 }
11907 $q = "INSERT INTO `#__vikbooking_config` (`param`,`setting`) VALUES ('grappr', 'auto');";
11908 $dbo->setQuery($q);
11909 $dbo->execute();
11910 return 'auto';
11911 }
11912
11913 /**
11914 * The type of reviews guests should leave.
11915 *
11916 * @return string global or service.
11917 *
11918 * @since 1.3.0
11919 */
11920 public static function guestReviewsType()
11921 {
11922 $dbo = JFactory::getDbo();
11923 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='grtype';";
11924 $dbo->setQuery($q);
11925 $dbo->execute();
11926 if ($dbo->getNumRows() > 0) {
11927 return $dbo->loadResult() == 'global' ? 'global' : 'service';
11928 }
11929 $q = "INSERT INTO `#__vikbooking_config` (`param`,`setting`) VALUES ('grtype', 'service');";
11930 $dbo->setQuery($q);
11931 $dbo->execute();
11932 return 'service';
11933 }
11934
11935 /**
11936 * The services to be reviewed by the guests.
11937 *
11938 * @return array list, empty or not, of the services to be reviewed.
11939 *
11940 * @since 1.3.0
11941 */
11942 public static function guestReviewsServices()
11943 {
11944 $dbo = JFactory::getDbo();
11945 $q = "SELECT `id`,`service_name` FROM `#__vikbooking_greview_service` GROUP BY `service_name` ORDER BY `id` ASC;";
11946 $dbo->setQuery($q);
11947 $dbo->execute();
11948 if ($dbo->getNumRows()) {
11949 return $dbo->loadAssocList();
11950 }
11951 // insert the default services
11952 $default_services = array(
11953 JText::translate('VBOGREVVALUE'),
11954 JText::translate('VBOGREVLOCATION'),
11955 JText::translate('VBOGREVSTAFF'),
11956 JText::translate('VBOGREVCLEAN'),
11957 JText::translate('VBOGREVCOMFORT'),
11958 JText::translate('VBOGREVFACILITIES'),
11959 );
11960 foreach ($default_services as $def_service) {
11961 $q = "INSERT INTO `#__vikbooking_greview_service` (`service_name`) VALUES (" . $dbo->quote($def_service) . ");";
11962 $dbo->setQuery($q);
11963 $dbo->execute();
11964 }
11965 return array();
11966 }
11967
11968 /**
11969 * Checks whether the guest reviews should be downloaded (max 1 per day).
11970 * VCM must be installed in order to download the new reviews.
11971 *
11972 * @return int -1 if VCM is not installed, 0 if already downloaded, 1 for download.
11973 *
11974 * @since 1.3.0
11975 */
11976 public static function shouldDownloadReviews()
11977 {
11978 if (!is_file(VCM_SITE_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'lib.vikchannelmanager.php')) {
11979 return -1;
11980 }
11981
11982 $today = date('Y-m-d');
11983
11984 $dbo = JFactory::getDbo();
11985 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='gr_last_download' LIMIT 1;";
11986 $dbo->setQuery($q);
11987 $dbo->execute();
11988 if ($dbo->getNumRows()) {
11989 $last_download = $dbo->loadResult();
11990 // update last download day
11991 $q = "UPDATE `#__vikbooking_config` SET `setting`=" . $dbo->quote($today) . " WHERE `param`='gr_last_download';";
11992 $dbo->setQuery($q);
11993 $dbo->execute();
11994 //
11995 return ($last_download != $today ? 1 : 0);
11996 }
11997 $q = "INSERT INTO `#__vikbooking_config` (`param`,`setting`) VALUES ('gr_last_download', " . $dbo->quote($today) . ");";
11998 $dbo->setQuery($q);
11999 $dbo->execute();
12000 return 1;
12001 }
12002
12003 /**
12004 * Tells whether a booking can be reviewed.
12005 *
12006 * @param array $booking booking array details.
12007 *
12008 * @return boolean true if a review can be left, false otherwise.
12009 *
12010 * @since 1.3.0
12011 */
12012 public static function canBookingBeReviewed($booking)
12013 {
12014 // reviews must be enabled and supported
12015 if (!self::allowGuestReviews()) {
12016 return false;
12017 }
12018
12019 // booking status must be confirmed
12020 if ($booking['status'] != 'confirmed') {
12021 return false;
12022 }
12023
12024 // review can be left starting from the check-out day
12025 $checkout_info = getdate($booking['checkout']);
12026 $checkout_midn = mktime(0, 0, 0, $checkout_info['mon'], $checkout_info['mday'], $checkout_info['year']);
12027 if (time() < $checkout_midn) {
12028 return false;
12029 }
12030
12031 // make sure a review for this booking does not exist
12032 $noreview = true;
12033 $dbo = JFactory::getDbo();
12034 try {
12035 $q = "SELECT `id` FROM `#__vikchannelmanager_otareviews` WHERE `idorder`=" . $dbo->quote($booking['id']) . ";";
12036 $dbo->setQuery($q);
12037 $dbo->execute();
12038 $noreview = ($dbo->getNumRows() < 1);
12039 } catch (Exception $e) {
12040 // if the query fails, we do not allow the review to be left
12041 $noreview = false;
12042 }
12043
12044 return $noreview;
12045 }
12046
12047 /**
12048 * Gets the review for a booking, no matter if it's published or not.
12049 * Review will be returned only if reviews are enabled and supported.
12050 * Only reviews left through the website, no OTA reviews as this method
12051 * should be used in the front-end to display the review to the guest.
12052 *
12053 * @param array $booking booking array details.
12054 *
12055 * @return mixed associative array or false.
12056 *
12057 * @since 1.3.0
12058 */
12059 public static function getBookingReview($booking)
12060 {
12061 // reviews must be enabled and supported
12062 if (!self::allowGuestReviews()) {
12063 return false;
12064 }
12065
12066 if (empty($booking['id'])) {
12067 return false;
12068 }
12069
12070 // make sure a review for this booking exists
12071 $dbo = JFactory::getDbo();
12072 $review = array();
12073 try {
12074 $q = "SELECT * FROM `#__vikchannelmanager_otareviews` WHERE `idorder`=" . $dbo->quote($booking['id']) . " LIMIT 1;";
12075 $dbo->setQuery($q);
12076 $dbo->execute();
12077 $review = $dbo->getNumRows() ? $dbo->loadAssoc() : $review;
12078 } catch (Exception $e) {
12079 // query has failed
12080 $review = array();
12081 }
12082
12083 if (count($review)) {
12084 // make sure the review was left from the website
12085 $review['uniquekey'] = (int)$review['uniquekey'];
12086 }
12087
12088 if (count($review) && !empty($review['uniquekey'])) {
12089 // this is an OTA review
12090 return false;
12091 }
12092
12093 if (count($review) && !empty($review['content'])) {
12094 // decode content
12095 $review['content'] = json_decode($review['content'], true);
12096 }
12097
12098 return count($review) ? $review : false;
12099 }
12100
12101 /**
12102 * Attempts to find a translation for a raw customer data label.
12103 * Bookings downloaded from the OTAs will save a raw string of information
12104 * composed of pairs of label-value separated by new line feeds.
12105 * We try to translate the given label into the current language.
12106 *
12107 * @param string $label the raw string label to translate.
12108 *
12109 * @return string either the original or the translated label.
12110 *
12111 * @since 1.3.5
12112 * @since 1.4.0 back-end support added.
12113 */
12114 public static function tnCustomerRawDataLabel($label)
12115 {
12116 // this is a map of the known labels
12117 $known_lbls = array(
12118 'NAME' => 'VBOCUSTOMERNOMINATIVE',
12119 'LASTNAME' => 'ORDER_LNAME',
12120 'COUNTRY' => 'ORDER_STATE',
12121 'EMAIL' => 'VBMAIL',
12122 'TELEPHONE' => 'VBPHONE',
12123 'PHONE' => 'VBPHONE',
12124 'SPECIALREQUEST' => 'ORDER_SPREQUESTS',
12125 'MEAL_PLAN' => 'VBOMEALPLAN',
12126 'CITY' => 'VBCITY',
12127 'BEDPREFERENCE' => 'VBOBEDPREFERENCE',
12128 'BOOKER_IS_GENIUS' => 'VBOBOOKERISGENIUS',
12129 );
12130
12131 if (self::isAdmin()) {
12132 // override the map of known translation strings
12133 $known_lbls = array(
12134 'NAME' => 'ORDER_NAME',
12135 'LASTNAME' => 'ORDER_LNAME',
12136 'COUNTRY' => 'ORDER_STATE',
12137 'ADDRESS' => 'ORDER_ADDRESS',
12138 'CITY' => 'ORDER_CITY',
12139 'LOCATION' => 'ORDER_CITY',
12140 'EMAIL' => 'ORDER_EMAIL',
12141 'TELEPHONE' => 'ORDER_PHONE',
12142 'PHONE' => 'ORDER_PHONE',
12143 'SPECIALREQUEST' => 'ORDER_SPREQUESTS',
12144 );
12145 }
12146
12147 // we get rid of any empty space by keeping the underscores
12148 $converted = str_replace(' ', '', strtoupper($label));
12149
12150 if (isset($known_lbls[$converted])) {
12151 // this language definition has been mapped
12152 return JText::translate($known_lbls[$converted]);
12153 }
12154
12155 // we try to guess the translation string by prepending VBO
12156 $guessed = JText::translate('VBO' . $converted);
12157 if ($guessed != 'VBO' . $converted) {
12158 // the label was translated correctly
12159 return $guessed;
12160 }
12161
12162 // this label could not be translated, so we return the plain string
12163 return $label;
12164 }
12165
12166 /**
12167 * We check whether some bookings are available for import from third party plugins.
12168 *
12169 * @return mixed array with list of plugins supported, false otherwise.
12170 *
12171 * @wponly this method is only useful for WordPress.
12172 *
12173 * @since 1.3.5
12174 */
12175 public static function canImportBookingsFromThirdPartyPlugins()
12176 {
12177 $dbo = JFactory::getDbo();
12178
12179 $plugins = array();
12180
12181 /**
12182 * As requested from hundreds of our clients, for the moment we check only the custom
12183 * post types of type "mphb_booking" to see if some bookings are available for import.
12184 */
12185 $q = "SELECT `post_type` FROM `#__posts` WHERE `post_type`=" . $dbo->quote('mphb_booking') . ";";
12186 $dbo->setQuery($q);
12187 $dbo->execute();
12188
12189 if (!$dbo->getNumRows()) {
12190 return false;
12191 }
12192
12193 // push this third party plugin
12194 $plugins['mphb'] = 'MotoPress Hotel Booking';
12195
12196 return count($plugins) ? $plugins : false;
12197 }
12198
12199 /**
12200 * This method returns a list of the known languages sorted by the
12201 * administrator custom preferences. Useful for the phone input fields.
12202 *
12203 * @param boolean $code_assoc whether to get an associative array with the lang name.
12204 *
12205 * @return array the sorted list of preferred countries.
12206 *
12207 * @since 1.3.11
12208 */
12209 public static function preferredCountriesOrdering($code_assoc = false)
12210 {
12211 $preferred_countries = array();
12212
12213 // try to get the preferred countries from db
12214 $dbo = JFactory::getDbo();
12215 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='preferred_countries';";
12216 $dbo->setQuery($q);
12217 $dbo->execute();
12218 if (!$dbo->getNumRows()) {
12219 // create empty configuration record
12220 $q = "INSERT INTO `#__vikbooking_config` (`param`,`setting`) VALUES ('preferred_countries', '[]');";
12221 $dbo->setQuery($q);
12222 $dbo->execute();
12223 } else {
12224 $preferred_countries = json_decode($dbo->loadResult());
12225 }
12226
12227 // get the default known languages
12228 $sorted_known_langs = self::getVboApplication()->getKnownLanguages();
12229
12230 if (!is_array($preferred_countries) || !count($preferred_countries)) {
12231 // sort the default known languages by country code alphabetically
12232 ksort($sorted_known_langs);
12233 foreach ($sorted_known_langs as $k => $v) {
12234 $langsep = strpos($k, '_') !== false ? '_' : '-';
12235 $langparts = explode($langsep, $k);
12236 array_push($preferred_countries, isset($langparts[1]) ? strtolower($langparts[1]) : strtolower($langparts[0]));
12237 }
12238 // update the database record
12239 $q = "UPDATE `#__vikbooking_config` SET `setting`=" . $dbo->quote(json_encode($preferred_countries)) . " WHERE `param`='preferred_countries';";
12240 $dbo->setQuery($q);
12241 $dbo->execute();
12242 }
12243
12244 if ($code_assoc) {
12245 // this is useful for displaying the preferred countries codes together with the language name
12246 $map = array();
12247 foreach ($preferred_countries as $ccode) {
12248 // look for the current country code in the keys of the known language tags
12249 $match_found = false;
12250 foreach ($sorted_known_langs as $langtag => $langinfo) {
12251 $langsep = strpos($langtag, '_') !== false ? '_' : '-';
12252 $langparts = explode($langsep, $langtag);
12253 if (isset($langparts[1]) && strtoupper($ccode) == strtoupper($langparts[1])) {
12254 // match found
12255 $match_found = true;
12256 $map[$ccode] = !empty($langinfo['nativeName']) ? $langinfo['nativeName'] : $langinfo['name'];
12257 } elseif (strtoupper($ccode) == strtoupper($langparts[0])) {
12258 // match found
12259 $match_found = true;
12260 $map[$ccode] = !empty($langinfo['nativeName']) ? $langinfo['nativeName'] : $langinfo['name'];
12261 }
12262 }
12263 if (!$match_found) {
12264 // in case someone would like to add a custom country code via DB, we allow to do so by returning the raw value
12265 $map[$ccode] = strtoupper($ccode);
12266 }
12267 }
12268 if (count($map)) {
12269 // set the associatve array to be returned
12270 $preferred_countries = $map;
12271 }
12272 }
12273
12274 return $preferred_countries;
12275 }
12276
12277 /**
12278 * Gets the instance of the admin widgets helper class.
12279 *
12280 * @return VikBookingHelperAdminWidgets
12281 *
12282 * @since 1.4.0
12283 */
12284 public static function getAdminWidgetsInstance()
12285 {
12286 require_once VBO_ADMIN_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'admin_widgets.php';
12287
12288 return VikBookingHelperAdminWidgets::getInstance();
12289 }
12290
12291 /**
12292 * Gets the instance of the conditional rules helper class.
12293 *
12294 * @param bool $require_only whether to return the object.
12295 *
12296 * @return mixed VikBookingHelperConditionalRules or true.
12297 *
12298 * @since 1.4.0
12299 */
12300 public static function getConditionalRulesInstance($require_only = false)
12301 {
12302 require_once VBO_ADMIN_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'conditional_rules.php';
12303
12304 return $require_only ? true : VikBookingHelperConditionalRules::getInstance();
12305 }
12306
12307 /**
12308 * Gets the instance of the geocoding helper class.
12309 *
12310 * @param bool $require_only whether to return the object.
12311 *
12312 * @return mixed VikBookingHelperGeocoding or true.
12313 *
12314 * @since 1.4.0
12315 */
12316 public static function getGeocodingInstance($require_only = false)
12317 {
12318 require_once VBO_ADMIN_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'geocoding.php';
12319
12320 return $require_only ? true : VikBookingHelperGeocoding::getInstance();
12321 }
12322
12323 /**
12324 * Helper method to cope with the removal of the same method
12325 * in the JApplication class introduced with Joomla 4. Using
12326 * isClient() would break the compatibility with J < 3.7 so
12327 * we can rely on this helper method to avoid Fatal Errors.
12328 *
12329 * @return boolean
12330 *
12331 * @since October 2020
12332 */
12333 public static function isAdmin()
12334 {
12335 $app = JFactory::getApplication();
12336 if (method_exists($app, 'isClient')) {
12337 return $app->isClient('administrator');
12338 }
12339
12340 return $app->isAdmin();
12341 }
12342
12343 /**
12344 * Helper method to cope with the removal of the same method
12345 * in the JApplication class introduced with Joomla 4. Using
12346 * isClient() would break the compatibility with J < 3.7 so
12347 * we can rely on this helper method to avoid Fatal Errors.
12348 *
12349 * @return boolean
12350 *
12351 * @since October 2020
12352 */
12353 public static function isSite()
12354 {
12355 $app = JFactory::getApplication();
12356 if (method_exists($app, 'isClient')) {
12357 return $app->isClient('site');
12358 }
12359
12360 return $app->isSite();
12361 }
12362
12363 /**
12364 * Gets the Google Maps API Key.
12365 *
12366 * @return string
12367 *
12368 * @since 1.14 (J) - 1.4.0 (WP)
12369 */
12370 public static function getGoogleMapsKey()
12371 {
12372 $dbo = JFactory::getDbo();
12373 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='gmapskey';";
12374 $dbo->setQuery($q);
12375 $dbo->execute();
12376 if ($dbo->getNumRows()) {
12377 return $dbo->loadResult();
12378 }
12379 $q = "INSERT INTO `#__vikbooking_config` (`param`,`setting`) VALUES ('gmapskey', '');";
12380 $dbo->setQuery($q);
12381 $dbo->execute();
12382 return '';
12383 }
12384
12385 /**
12386 * Checks whether the interactive map booking is enabled.
12387 *
12388 * @return bool
12389 *
12390 * @since 1.14 (J) - 1.4.0 (WP)
12391 */
12392 public static function interactiveMapEnabled()
12393 {
12394 $dbo = JFactory::getDbo();
12395 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='interactive_map';";
12396 $dbo->setQuery($q);
12397 $dbo->execute();
12398 if ($dbo->getNumRows()) {
12399 return ((int)$dbo->loadResult() > 0);
12400 }
12401
12402 $q = "INSERT INTO `#__vikbooking_config` (`param`,`setting`) VALUES ('interactive_map', '0');";
12403 $dbo->setQuery($q);
12404 $dbo->execute();
12405
12406 return false;
12407 }
12408
12409 /**
12410 * Gets the preferred colors saved in the configuration, if any.
12411 *
12412 * @return array
12413 *
12414 * @since 1.14 (J) - 1.4.0 (WP)
12415 */
12416 public static function getPreferredColors()
12417 {
12418 $dbo = JFactory::getDbo();
12419 $pref_colors = array(
12420 'textcolor' => '',
12421 'bgcolor' => '',
12422 'fontcolor' => '',
12423 'bgcolorhov' => '',
12424 'fontcolorhov' => '',
12425 );
12426
12427 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='pref_colors';";
12428 $dbo->setQuery($q);
12429 $dbo->execute();
12430 if ($dbo->getNumRows()) {
12431 $colors = json_decode($dbo->loadResult(), true);
12432 if (!is_array($colors) || !isset($colors['textcolor'])) {
12433 return $pref_colors;
12434 }
12435 return $colors;
12436 }
12437 $q = "INSERT INTO `#__vikbooking_config` (`param`,`setting`) VALUES ('pref_colors', '{}');";
12438 $dbo->setQuery($q);
12439 $dbo->execute();
12440 return $pref_colors;
12441 }
12442
12443 /**
12444 * Adds to the document inline styles for the preferred colors, if any.
12445 *
12446 * @return void
12447 *
12448 * @since 1.14 (J) - 1.4.0 (WP)
12449 */
12450 public static function loadPreferredColorStyles()
12451 {
12452 $view = VikRequest::getString('view', '', 'request');
12453 $pref_colors = self::getPreferredColors();
12454
12455 $css_classes = array();
12456
12457 if (!empty($pref_colors['textcolor'])) {
12458 // titles and headings
12459 array_push($css_classes, '.vbo-pref-color-text { color: ' . $pref_colors['textcolor'] . ' !important; }');
12460 // stepbar, oconfirm
12461 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; }');
12462 // buttons secondary color
12463 array_push($css_classes, '.vbo-pref-color-btn-secondary { border: 2px solid ' . $pref_colors['textcolor'] . ' !important; color: ' . $pref_colors['textcolor'] . ' !important; background: transparent !important; }');
12464 if (!empty($pref_colors['fontcolor'])) {
12465 array_push($css_classes, '.vbo-pref-color-btn-secondary:hover { color: ' . $pref_colors['fontcolor'] . ' !important; background: ' . $pref_colors['textcolor'] . ' !important; }');
12466 }
12467 // datepicker
12468 array_push($css_classes, '.ui-datepicker .ui-datepicker-today {
12469 color: ' . $pref_colors['textcolor'] . ' !important;
12470 border-color: ' . $pref_colors['textcolor'] . ' !important;
12471 }');
12472 array_push($css_classes, '.ui-datepicker .ui-datepicker-today a {
12473 color: ' . $pref_colors['textcolor'] . ' !important;
12474 }');
12475 // operators tableaux
12476 if ($view == 'tableaux') {
12477 array_push($css_classes, '.vbo-roomdaynote-empty .vbo-roomdaynote-trigger i { color: ' . $pref_colors['textcolor'] . ' !important; }');
12478 }
12479 }
12480
12481 if (!empty($pref_colors['bgcolor']) && !empty($pref_colors['fontcolor'])) {
12482 // elements with backgrounds
12483 array_push($css_classes, '.vbo-pref-color-element { background-color: ' . $pref_colors['bgcolor'] . ' !important; color: ' . $pref_colors['fontcolor'] . ' !important; }');
12484 array_push($css_classes, '.vbo-pref-bordercolor { border-color: ' . $pref_colors['bgcolor'] . ' !important; }');
12485 array_push($css_classes, '.vbo-pref-bordertext { color: ' . $pref_colors['bgcolor'] . ' !important; border-color: ' . $pref_colors['bgcolor'] . ' !important; }');
12486 // buttons with backgrounds
12487 array_push($css_classes, '.vbo-pref-color-btn { background-color: ' . $pref_colors['bgcolor'] . ' !important; color: ' . $pref_colors['fontcolor'] . ' !important; }');
12488 // stepbar
12489 array_push($css_classes, 'ol.vbo-stepbar li.vbo-step-complete:before { background-color: ' . $pref_colors['bgcolor'] . ' !important; }');
12490 // datepicker
12491 array_push($css_classes, '.ui-datepicker table td:hover {
12492 border-color: ' . $pref_colors['bgcolor'] . ' !important;
12493 }');
12494 array_push($css_classes, '.ui-datepicker .ui-datepicker-current-day {
12495 background: ' . $pref_colors['bgcolor'] . ' !important;
12496 color: ' . $pref_colors['fontcolor'] . ' !important;
12497 }');
12498 array_push($css_classes, '.ui-datepicker .ui-datepicker-current-day a {
12499 color: ' . $pref_colors['fontcolor'] . ' !important;
12500 }');
12501 // operators tableaux
12502 if ($view == 'tableaux') {
12503 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; }');
12504 }
12505 }
12506
12507 if (!empty($pref_colors['bgcolorhov']) && !empty($pref_colors['fontcolorhov'])) {
12508 // buttons with backgrounds during hover state
12509 array_push($css_classes, '.vbo-pref-color-btn:hover { background-color: ' . $pref_colors['bgcolorhov'] . ' !important; color: ' . $pref_colors['fontcolorhov'] . ' !important; }');
12510 // operators tableaux
12511 if ($view == 'tableaux') {
12512 array_push($css_classes, '.vbo-tableaux-togglefullscreen:hover { background-color: ' . $pref_colors['bgcolorhov'] . ' !important; color: ' . $pref_colors['fontcolorhov'] . ' !important; }');
12513 }
12514 }
12515
12516 if (!count($css_classes)) {
12517 return;
12518 }
12519
12520 // add in-line style declaration
12521 JFactory::getDocument()->addStyleDeclaration(implode("\n", $css_classes));
12522 }
12523
12524 /**
12525 * Given the full endpoint URL for the AJAX request, it
12526 * returns an appropriate URI for the current platform.
12527 *
12528 * @param mixed $query The query string or a routed URL.
12529 * @param boolean $xhtml Replace & by &amp; for XML compliance.
12530 *
12531 * @return string The AJAX end-point URI.
12532 *
12533 * @since 1.14 (J) - 1.4.0 (WP)
12534 * @since 1.15.0 (J) - 1.5.0 (WP) we rely on the new platform libraries.
12535 */
12536 public static function ajaxUrl($query = '', $xhtml = false)
12537 {
12538 return VBOFactory::getPlatform()->getUri()->ajax($query, $xhtml);
12539 }
12540
12541 /**
12542 * Tells whether no tax rates have been defined so far.
12543 *
12544 * @return bool True if no tax rates defined, false otherwise.
12545 *
12546 * @since 1.14 (J) - 1.4.0 (WP)
12547 */
12548 public static function noTaxRates()
12549 {
12550 $dbo = JFactory::getDbo();
12551
12552 $q = "SELECT `id`, `aliq` FROM `#__vikbooking_iva` WHERE `aliq` > 0;";
12553 $dbo->setQuery($q);
12554 $dbo->execute();
12555
12556 return ($dbo->getNumRows() < 1);
12557 }
12558
12559 /**
12560 * Booking Type feature is strictly connected to VCM and OTAs. Updated
12561 * versions of VBO will always support it as long as VCM is installed.
12562 * Needed by VCM to understand whether certain SQL queries can be performed.
12563 *
12564 * @return bool true if VCM exists, false otherwise.
12565 *
12566 * @since 1.14 (J) - 1.4.0 (WP)
12567 */
12568 public static function isBookingTypeSupported()
12569 {
12570 return is_file(VCM_SITE_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'lib.vikchannelmanager.php');
12571 }
12572
12573 /**
12574 * Returns an instance of the Rates Flow helper class of VCM, if available.
12575 *
12576 * @return mixed VCMRatesFlow if VCM available and updated, or null.
12577 *
12578 * @since 1.15.0 (J) - 1.5.0 (WP)
12579 */
12580 public static function getRatesFlowInstance()
12581 {
12582 $vcm_fpath = VCM_ADMIN_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'rates_flow.php';
12583 if (!is_file($vcm_fpath)) {
12584 return null;
12585 }
12586
12587 // load dependencies
12588 require_once $vcm_fpath;
12589
12590 // return the instance of the class
12591 return VCMRatesFlow::getInstance();
12592 }
12593
12594 /**
12595 * Returns the current appearance preference.
12596 *
12597 * @return string light, auto or dark.
12598 *
12599 * @since 1.15.0 (J) - 1.5.0 (WP)
12600 */
12601 public static function getAppearancePref()
12602 {
12603 $dbo = JFactory::getDbo();
12604
12605 // auto is the default appearance preference
12606 $default_pref = 'auto';
12607
12608 // accepted preferences
12609 $valid_pref = array(
12610 'auto',
12611 'light',
12612 'dark',
12613 );
12614
12615 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='appearance_pref';";
12616 $dbo->setQuery($q);
12617 $dbo->execute();
12618 if (!$dbo->getNumRows()) {
12619 // missing record
12620 $q = "INSERT INTO `#__vikbooking_config` (`param`,`setting`) VALUES('appearance_pref', " . $dbo->quote($default_pref) . ");";
12621 $dbo->setQuery($q);
12622 $dbo->execute();
12623
12624 return $default_pref;
12625 }
12626
12627 $current_pref = $dbo->loadResult();
12628
12629 return in_array($current_pref, $valid_pref) ? $current_pref : $default_pref;
12630 }
12631
12632 /**
12633 * According to the appearance preferences, the apposite
12634 * CSS assets are loaded within the document.
12635 *
12636 * @param bool $get_info true to not load any assets.
12637 *
12638 * @return mixed string light, auto, dark, info array or false.
12639 *
12640 * @since 1.15.0 (J) - 1.5.0 (WP)
12641 * @since 1.16.0 (J) - 1.6.0 (WP) added argument $get_info.
12642 */
12643 public static function loadAppearancePreferenceAssets($get_info = false)
12644 {
12645 // get document object
12646 $document = JFactory::getDocument();
12647
12648 // load current color scheme preference
12649 $current_pref = self::getAppearancePref();
12650
12651 // define caching values
12652 $file_opt = array('version' => VIKBOOKING_SOFTWARE_VERSION);
12653
12654 // define file attributes
12655 $file_attr = array('id' => 'vbo-css-appearance-' . $current_pref);
12656
12657 // apposite file path and URI for back-end
12658 $css_path = VBO_ADMIN_PATH . DIRECTORY_SEPARATOR . 'resources' . DIRECTORY_SEPARATOR . 'vbo-appearance-' . $current_pref . '.css';
12659 $css_uri = VBO_ADMIN_URI . 'resources/vbo-appearance-' . $current_pref . '.css';
12660
12661 // check if this is for the front-end ("auto" file only)
12662 if (self::isSite() && in_array($current_pref, array('auto', 'dark'))) {
12663 $css_path = VBO_SITE_PATH . DIRECTORY_SEPARATOR . 'resources' . DIRECTORY_SEPARATOR . 'vbo-appearance-auto.css';
12664 $css_uri = VBO_SITE_URI . 'resources/vbo-appearance-auto.css';
12665 // check if front-end appearance is disabled
12666 if (!VBOFactory::getConfig()->getInt('appearance_front', 0)) {
12667 // do nothing for the front-end by default
12668 return false;
12669 }
12670 }
12671
12672 if (!defined('ABSPATH')) {
12673 $css_path = str_replace('resources' . DIRECTORY_SEPARATOR, '', $css_path);
12674 $css_uri = str_replace('resources/', '', $css_uri);
12675 }
12676
12677 if (!is_file($css_path)) {
12678 // this preference does not require a specific stylesheet
12679 return false;
12680 }
12681
12682 if ($get_info) {
12683 // do not actually load any assets
12684 return [
12685 'id' => $file_attr['id'],
12686 'href' => $css_uri,
12687 ];
12688 }
12689
12690 // load the apposite CSS file
12691 $document->addStyleSheet($css_uri, $file_opt, $file_attr);
12692
12693 return $current_pref;
12694 }
12695
12696 /**
12697 * VBO trigger for VCM subscription status and updates. If called within the admin section
12698 * returns the expiration reminder array to display a modal window, and sets a warning
12699 * message. If called within the front-end, may trigger an email alert.
12700 *
12701 * @return mixed null, array or boolean depending on client position.
12702 *
12703 * @since 1.15.0 (J) - 1.5.0 (WP)
12704 */
12705 public static function getVCMSubscriptionStatus()
12706 {
12707 $vcm_lib_path = VCM_SITE_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'lib.vikchannelmanager.php';
12708 if (!is_file($vcm_lib_path)) {
12709 // do nothing
12710 return null;
12711 }
12712
12713 // determine client position
12714 $is_admin = self::isAdmin();
12715
12716 try {
12717 if (!class_exists('VikChannelManager') || !method_exists('VikChannelManager', 'checkSubscriptionReminder')) {
12718 // VCM is outdated
12719 return null;
12720 }
12721
12722 // always attempt to load the admin lang handler of VCM
12723 $lang = JFactory::getLanguage();
12724
12725 // load the VCM admin language file
12726 $vcm_admin_lang_path = '';
12727 if (!defined('ABSPATH') && defined('JPATH_ADMINISTRATOR')) {
12728 $vcm_admin_lang_path = JPATH_ADMINISTRATOR;
12729 } elseif (defined('VIKCHANNELMANAGER_ADMIN_LANG')) {
12730 $vcm_admin_lang_path = VIKCHANNELMANAGER_ADMIN_LANG;
12731 } elseif (defined('ABSPATH') && defined('VIKBOOKING_ADMIN_LANG')) {
12732 /**
12733 * If running within Vik Booking, the constant VIKCHANNELMANAGER_ADMIN_LANG may not be available
12734 */
12735 $vcm_admin_lang_path = str_replace('vikbooking', 'vikchannelmanager', VIKBOOKING_ADMIN_LANG);
12736 }
12737 // load translation file
12738 $lang->load('com_vikchannelmanager', $vcm_admin_lang_path, $lang->getTag(), true);
12739 if (defined('VIKCHANNELMANAGER_LIBRARIES') && method_exists($lang, 'attachHandler')) {
12740 /**
12741 * @wponly load language admin handler as well for WP.
12742 * We do this only because of WordPress, but in a way also compatible with Joomla as
12743 * the constant VIKCHANNELMANAGER_LIBRARIES and method attachHandler are not in Joomla.
12744 */
12745 $lang->attachHandler(VIKCHANNELMANAGER_LIBRARIES . DIRECTORY_SEPARATOR . 'language' . DIRECTORY_SEPARATOR . 'admin.php', 'vikchannelmanager');
12746 }
12747
12748 if ($is_admin) {
12749 // back-end section of the site: check status
12750 if (VikChannelManager::loadExpirationDetails() === false) {
12751 // download and update the expiration details
12752 VikChannelManager::downloadExpirationDetails();
12753 }
12754 $expiration_reminder = null;
12755 if (VikChannelManager::isSubscriptionExpired()) {
12756 VikError::raiseWarning('', JText::translate('VCM_WARN_SUBSCR_EXPIRED'));
12757 } else {
12758 $expiration_reminder = VikChannelManager::shouldRemindExpiration();
12759 // we also check if an update is available
12760 if (VikChannelManager::checkUpdates()) {
12761 // an update is more important
12762 JFactory::getApplication()->redirect('index.php?option=com_vikchannelmanager&task=update_program&forcecheck=1&force_check=1');
12763 exit;
12764 }
12765 }
12766 // return reminder status
12767 return $expiration_reminder;
12768 }
12769
12770 // front-end section of the site: trigger alert, if any (bool)
12771 return VikChannelManager::checkSubscriptionReminder();
12772
12773 } catch (Exception $e) {
12774 // do nothing
12775 }
12776
12777 return null;
12778 }
12779
12780 /**
12781 * Given a list of website rate plans, returns the sorted list.
12782 * Naming score is the highest sorting method, pricing scoring is also supported.
12783 *
12784 * @param array $rate_plans the list of rate plan arrays to sort.
12785 * @param bool $assoc whether to keep the original array keys.
12786 *
12787 * @return array the sorted list of rate plan arrays.
12788 *
12789 * @since 1.15.0 (J) - 1.5.0 (WP)
12790 */
12791 public static function sortRatePlans($rate_plans, $assoc = false)
12792 {
12793 if (!is_array($rate_plans) || count($rate_plans) < 2) {
12794 return $rate_plans;
12795 }
12796
12797 foreach ($rate_plans as $rp_ind => $rate_plan) {
12798 if (!is_array($rate_plan) || empty($rate_plan['name'])) {
12799 // do not proceed in case of a weird array structure
12800 return $rate_plans;
12801 }
12802 // count the sorting-score of this rate plan
12803 $sort_score = 0;
12804 if (stripos($rate_plan['name'], 'Standard') !== false || stripos($rate_plan['name'], 'Base') !== false) {
12805 // "standard rate" rate plans should go first
12806 $sort_score += 3;
12807 } else {
12808 $sort_score--;
12809 }
12810 if (stripos($rate_plan['name'], 'Non') === false && stripos($rate_plan['name'], 'Not') === false) {
12811 // this doesn't seem to be a "non refundable rate" so it should go first
12812 $sort_score += 2;
12813 }
12814
12815 // set sorting score for this rate plan ID
12816 $rate_plans[$rp_ind]['sort_score'] = $sort_score;
12817 }
12818
12819 // apply custom sorting and keep index association
12820 uasort($rate_plans, function($a, $b) {
12821 // sort by name-scoring DESC
12822 $sort_diff = $b['sort_score'] - $a['sort_score'];
12823 if (isset($b['cost'])) {
12824 // apply also sorting by price ASC
12825 $sort_diff += ($a['cost'] < $b['cost']) ? -1 : 1;
12826 }
12827 return $sort_diff;
12828 });
12829
12830 return !$assoc ? array_values($rate_plans) : $rate_plans;
12831 }
12832
12833 /**
12834 * Returns an instance of the Availability helper class.
12835 *
12836 * @return VikBookingAvailability
12837 *
12838 * @since 1.15.0 (J) - 1.5.0 (WP)
12839 */
12840 public static function getAvailabilityInstance()
12841 {
12842 // require library
12843 require_once VBO_ADMIN_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'availability.php';
12844
12845 // return the instance of the class
12846 return VikBookingAvailability::getInstance();
12847 }
12848
12849 /**
12850 * Helper method to quickly guess the internal dependency to import.
12851 *
12852 * @param string $path_id either a full path to a file or its identifier.
12853 *
12854 * @return bool true if the requested library was imported or false.
12855 *
12856 * @since 1.15.0 (J) - 1.5.0 (WP)
12857 */
12858 public static function import($path_id)
12859 {
12860 if (empty($path_id)) {
12861 return false;
12862 }
12863
12864 // always append .php extension
12865 $path_id = basename($path_id, '.php') . '.php';
12866
12867 if (is_file($path_id)) {
12868 // full path given
12869 require_once $path_id;
12870
12871 return true;
12872 }
12873
12874 // try guessing where the file id is located
12875 if (is_file(VBO_SITE_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . $path_id)) {
12876 // library was found
12877 require_once VBO_SITE_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . $path_id;
12878
12879 return true;
12880 }
12881
12882 if (is_file(VBO_ADMIN_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . $path_id)) {
12883 // library was found
12884 require_once VBO_ADMIN_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . $path_id;
12885
12886 return true;
12887 }
12888
12889 return false;
12890 }
12891 }
12892
12893 if (!class_exists('VikResizer'))
12894 {
12895 class VikResizer
12896 {
12897 public function __construct()
12898 {
12899 // objects of this class can also be instantiated without calling the methods statically.
12900 }
12901
12902 /**
12903 * Resizes an image proportionally. For PNG files it can optionally
12904 * trim the image to exclude the transparency, and add some padding to it.
12905 * All PNG files keep the alpha background in the resized version.
12906 *
12907 * @param string $fileimg path to original image file
12908 * @param string $dest path to destination image file
12909 * @param int $towidth
12910 * @param int $toheight
12911 * @param bool $trim_png remove empty background from image
12912 * @param string $trim_pad CSS-style version of padding (top right bottom left) ex: '1 2 3 4'
12913 *
12914 * @return boolean
12915 */
12916 public static function proportionalImage($fileimg, $dest, $towidth, $toheight, $trim_png = false, $trim_pad = null)
12917 {
12918 if (!is_file($fileimg)) {
12919 return false;
12920 }
12921 if (empty($towidth) && empty($toheight)) {
12922 return copy($fileimg, $dest);
12923 }
12924
12925 $result = true;
12926
12927 list ($owid, $ohei, $type) = getimagesize($fileimg);
12928
12929 if ($owid > $towidth || $ohei > $toheight) {
12930 $xscale = $owid / $towidth;
12931 $yscale = $ohei / $toheight;
12932 if ($yscale > $xscale) {
12933 $new_width = round($owid * (1 / $yscale));
12934 $new_height = round($ohei * (1 / $yscale));
12935 } else {
12936 $new_width = round($owid * (1 / $xscale));
12937 $new_height = round($ohei * (1 / $xscale));
12938 }
12939
12940 $imageresized = imagecreatetruecolor($new_width, $new_height);
12941
12942 switch ($type) {
12943 case '1' :
12944 $imagetmp = imagecreatefromgif ($fileimg);
12945 break;
12946 case '2' :
12947 $imagetmp = imagecreatefromjpeg($fileimg);
12948 break;
12949 case '18' :
12950 $imagetmp = imagecreatefromwebp($fileimg);
12951 break;
12952 default :
12953 //keep alpha for PNG files
12954 $background = imagecolorallocate($imageresized, 0, 0, 0);
12955 imagecolortransparent($imageresized, $background);
12956 imagealphablending($imageresized, false);
12957 imagesavealpha($imageresized, true);
12958 //
12959 $imagetmp = imagecreatefrompng($fileimg);
12960 break;
12961 }
12962
12963 if (!$imagetmp) {
12964 return false;
12965 }
12966
12967 imagecopyresampled($imageresized, $imagetmp, 0, 0, 0, 0, $new_width, $new_height, $owid, $ohei);
12968
12969 switch ($type) {
12970 case '1' :
12971 imagegif($imageresized, $dest);
12972 break;
12973 case '2' :
12974 imagejpeg($imageresized, $dest);
12975 break;
12976 case '18' :
12977 imagewebp($imageresized, $dest);
12978 break;
12979 default :
12980 if ($trim_png) {
12981 self::imageTrim($imageresized, $background, $trim_pad);
12982 }
12983 imagepng($imageresized, $dest);
12984 break;
12985 }
12986
12987 imagedestroy($imageresized);
12988 } else {
12989 $result = copy($fileimg, $dest);
12990 }
12991
12992 if (defined('ABSPATH')) {
12993 /**
12994 * @wponly in order to not lose resized files after installing an update,
12995 * we need to move any uploaded file onto a recovery folder.
12996 */
12997 VikBookingLoader::import('update.manager');
12998 VikBookingUpdateManager::triggerUploadBackup($dest);
12999 }
13000
13001 return $result;
13002 }
13003
13004 /**
13005 * (BETA) Resizes an image proportionally. For PNG files it can optionally
13006 * trim the image to exclude the transparency, and add some padding to it.
13007 * All PNG files keep the alpha background in the resized version.
13008 *
13009 * @param resource $im Image link resource (reference)
13010 * @param int $bg imagecolorallocate color identifier
13011 * @param string $pad CSS-style version of padding (top right bottom left) ex: '1 2 3 4'
13012 *
13013 * @return void
13014 */
13015 public static function imagetrim(&$im, $bg, $pad = null)
13016 {
13017 // Calculate padding for each side.
13018 if (isset($pad)) {
13019 $pp = explode(' ', $pad);
13020 if (isset($pp[3])) {
13021 $p = array((int) $pp[0], (int) $pp[1], (int) $pp[2], (int) $pp[3]);
13022 } elseif (isset($pp[2])) {
13023 $p = array((int) $pp[0], (int) $pp[1], (int) $pp[2], (int) $pp[1]);
13024 } elseif (isset($pp[1])) {
13025 $p = array((int) $pp[0], (int) $pp[1], (int) $pp[0], (int) $pp[1]);
13026 } else {
13027 $p = array_fill(0, 4, (int) $pp[0]);
13028 }
13029 } else {
13030 $p = array_fill(0, 4, 0);
13031 }
13032
13033 // Get the image width and height.
13034 $imw = imagesx($im);
13035 $imh = imagesy($im);
13036
13037 // Set the X variables.
13038 $xmin = $imw;
13039 $xmax = 0;
13040
13041 // Start scanning for the edges.
13042 for ($iy=0; $iy<$imh; $iy++) {
13043 $first = true;
13044 for ($ix=0; $ix<$imw; $ix++) {
13045 $ndx = imagecolorat($im, $ix, $iy);
13046 if ($ndx != $bg) {
13047 if ($xmin > $ix) {
13048 $xmin = $ix;
13049 }
13050 if ($xmax < $ix) {
13051 $xmax = $ix;
13052 }
13053 if (!isset($ymin)) {
13054 $ymin = $iy;
13055 }
13056 $ymax = $iy;
13057 if ($first) {
13058 $ix = $xmax;
13059 $first = false;
13060 }
13061 }
13062 }
13063 }
13064
13065 // The new width and height of the image. (not including padding)
13066 $imw = 1+$xmax-$xmin; // Image width in pixels
13067 $imh = 1+$ymax-$ymin; // Image height in pixels
13068
13069 // Make another image to place the trimmed version in.
13070 $im2 = imagecreatetruecolor($imw+$p[1]+$p[3], $imh+$p[0]+$p[2]);
13071
13072 // Make the background of the new image the same as the background of the old one.
13073 $bg2 = imagecolorallocate($im2, ($bg >> 16) & 0xFF, ($bg >> 8) & 0xFF, $bg & 0xFF);
13074 imagefill($im2, 0, 0, $bg2);
13075
13076 // Copy it over to the new image.
13077 imagecopy($im2, $im, $p[3], $p[0], $xmin, $ymin, $imw, $imh);
13078
13079 // To finish up, we replace the old image which is referenced.
13080 $im = $im2;
13081 }
13082
13083 public static function bandedImage($fileimg, $dest, $towidth, $toheight, $rgb)
13084 {
13085 if (!file_exists($fileimg)) {
13086 return false;
13087 }
13088 if (empty($towidth) && empty($toheight)) {
13089 copy($fileimg, $dest);
13090 return true;
13091 }
13092
13093 $exp = explode(",", $rgb);
13094 if (count($exp) == 3) {
13095 $r = trim($exp[0]);
13096 $g = trim($exp[1]);
13097 $b = trim($exp[2]);
13098 } else {
13099 $r = 0;
13100 $g = 0;
13101 $b = 0;
13102 }
13103
13104 list ($owid, $ohei, $type) = getimagesize($fileimg);
13105
13106 if ($owid > $towidth || $ohei > $toheight) {
13107 $xscale = $owid / $towidth;
13108 $yscale = $ohei / $toheight;
13109 if ($yscale > $xscale) {
13110 $new_width = round($owid * (1 / $yscale));
13111 $new_height = round($ohei * (1 / $yscale));
13112 $ydest = 0;
13113 $diff = $towidth - $new_width;
13114 $xdest = ($diff > 0 ? round($diff / 2) : 0);
13115 } else {
13116 $new_width = round($owid * (1 / $xscale));
13117 $new_height = round($ohei * (1 / $xscale));
13118 $xdest = 0;
13119 $diff = $toheight - $new_height;
13120 $ydest = ($diff > 0 ? round($diff / 2) : 0);
13121 }
13122
13123 $imageresized = imagecreatetruecolor($towidth, $toheight);
13124
13125 $bgColor = imagecolorallocate($imageresized, (int) $r, (int) $g, (int) $b);
13126 imagefill($imageresized, 0, 0, $bgColor);
13127
13128 switch ($type) {
13129 case '1' :
13130 $imagetmp = imagecreatefromgif ($fileimg);
13131 break;
13132 case '2' :
13133 $imagetmp = imagecreatefromjpeg($fileimg);
13134 break;
13135 default :
13136 $imagetmp = imagecreatefrompng($fileimg);
13137 break;
13138 }
13139
13140 imagecopyresampled($imageresized, $imagetmp, $xdest, $ydest, 0, 0, $new_width, $new_height, $owid, $ohei);
13141
13142 switch ($type) {
13143 case '1' :
13144 imagegif ($imageresized, $dest);
13145 break;
13146 case '2' :
13147 imagejpeg($imageresized, $dest);
13148 break;
13149 default :
13150 imagepng($imageresized, $dest);
13151 break;
13152 }
13153
13154 imagedestroy($imageresized);
13155
13156 return true;
13157 } else {
13158 copy($fileimg, $dest);
13159 }
13160 return true;
13161 }
13162
13163 public static function croppedImage($fileimg, $dest, $towidth, $toheight)
13164 {
13165 if (!file_exists($fileimg)) {
13166 return false;
13167 }
13168 if (empty($towidth) && empty($toheight)) {
13169 copy($fileimg, $dest);
13170 return true;
13171 }
13172
13173 list ($owid, $ohei, $type) = getimagesize($fileimg);
13174
13175 if ($owid <= $ohei) {
13176 $new_width = $towidth;
13177 $new_height = ($towidth / $owid) * $ohei;
13178 } else {
13179 $new_height = $toheight;
13180 $new_width = ($new_height / $ohei) * $owid;
13181 }
13182
13183 switch ($type) {
13184 case '1' :
13185 $img_src = imagecreatefromgif ($fileimg);
13186 $img_dest = imagecreate($new_width, $new_height);
13187 break;
13188 case '2' :
13189 $img_src = imagecreatefromjpeg($fileimg);
13190 $img_dest = imagecreatetruecolor($new_width, $new_height);
13191 break;
13192 default :
13193 $img_src = imagecreatefrompng($fileimg);
13194 $img_dest = imagecreatetruecolor($new_width, $new_height);
13195 break;
13196 }
13197
13198 imagecopyresampled($img_dest, $img_src, 0, 0, 0, 0, $new_width, $new_height, $owid, $ohei);
13199
13200 switch ($type) {
13201 case '1' :
13202 $cropped = imagecreate($towidth, $toheight);
13203 break;
13204 case '2' :
13205 $cropped = imagecreatetruecolor($towidth, $toheight);
13206 break;
13207 default :
13208 $cropped = imagecreatetruecolor($towidth, $toheight);
13209 break;
13210 }
13211
13212 imagecopy($cropped, $img_dest, 0, 0, 0, 0, $owid, $ohei);
13213
13214 switch ($type) {
13215 case '1' :
13216 imagegif ($cropped, $dest);
13217 break;
13218 case '2' :
13219 imagejpeg($cropped, $dest);
13220 break;
13221 default :
13222 imagepng($cropped, $dest);
13223 break;
13224 }
13225
13226 imagedestroy($img_dest);
13227 imagedestroy($cropped);
13228
13229 return true;
13230 }
13231 }
13232 }
13233