PluginProbe
VikBooking Hotel Booking Engine & PMS / 1.8.6
VikBooking Hotel Booking Engine & PMS v1.8.6
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 1.7.3 All 36 releases
vikbooking / admin / helpers / festivities.php

festivities.php in VikBooking Hotel Booking Engine & PMS 1.8.6, at admin/helpers/festivities.php

1,093 lines 31.6 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 /**
14 * Check whether we can extend the VCM's main Festivities class
15 * or if we need to define a new middle-man class just to make VBO work.
16 * The main class VikBookingFestivities is declared at the bottom of the file.
17 */
18 $vcm_festivites = false;
19 if (is_file(VCM_ADMIN_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'festivities.php')) {
20 if (!class_exists('VCMFestivities')) {
21 require_once(VCM_ADMIN_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'festivities.php');
22 }
23 if (method_exists('VCMFestivities', 'getInstance')) {
24 // VCM is up to date and so we can extend its main class for the festivites
25 $vcm_festivites = true;
26 }
27 }
28
29 if ($vcm_festivites === true) {
30 // we can use the updated, native and main class of VCM
31 class VikBookingFestivitiesBC extends VCMFestivities { }
32 } else {
33 /**
34 * VCM missing/outdated. We need to define the old class for backward compatibility.
35 * The class of VCM has to be the MAIN one, so all festivities should be
36 * declared there and the new class VikBookingFestivities will extend it.
37 */
38 class VikBookingFestivitiesBC
39 {
40 /**
41 * The singleton instance of the class.
42 *
43 * @var VCMFestivities
44 */
45 protected static $instance = null;
46
47 /**
48 * The (current) timestamp from which the class
49 * should calculate the closest festivities.
50 *
51 * @var int
52 */
53 protected $now;
54
55 /**
56 * Whether the class should translate the names
57 * of the festivities. False by default in this BC class.
58 *
59 * @var boolean
60 */
61 public $translate = false;
62
63 /**
64 * The list of all the supported regions for the festivities.
65 *
66 * @var array
67 */
68 protected $regions = array(
69 'global' => 'GLOBAL',
70 'ita' => 'ITA',
71 'fra' => 'FRA',
72 'ger' => 'GER',
73 'spa' => 'SPA',
74 'usa' => 'USA'
75 );
76
77 /**
78 * The list of all the supported festivities.
79 *
80 * @var array
81 */
82 protected $festivities = array(
83 'newYearsDay' => array(
84 'regions' => array('global'),
85 'mon' => 1,
86 'mday' => 1
87 ),
88 'epiphany' => array(
89 'regions' => array('global'),
90 'mon' => 1,
91 'mday' => 6
92 ),
93 'mlkDay' => array(
94 'regions' => array('usa'),
95 'func' => 'getMLKDate' //3rd monday of January
96 ),
97 'valentinesDay' => array(
98 'regions' => array('global'),
99 'mon' => 2,
100 'mday' => 14
101 ),
102 'presidentsDay' => array(
103 'regions' => array('usa'),
104 'func' => 'getPresidentsDate' //3rd monday of February
105 ),
106 'rosenmontag' => array(
107 'regions' => array('ger'),
108 'func' => 'getRosenmontagDate' //monday before Ash Wednesday (7th week before Easter)
109 ),
110 'mardiGras' => array(
111 'regions' => array('global'),
112 'func' => 'getMardigrasDate' //tuesday before Ash Wednesday (7th week before Easter)
113 ),
114 'easter' => array(
115 'regions' => array('global'),
116 'func' => 'getEasterDate'
117 ),
118 'endofwarita' => array(
119 'regions' => array('ita'),
120 'mon' => 4,
121 'mday' => 25
122 ),
123 'walpurgisNight'=> array(
124 'regions' => array('ger'),
125 'mon' => 4,
126 'mday' => 30
127 ),
128 'dayOfWork' => array(
129 'regions' => array('ita', 'fra', 'ger'),
130 'mon' => 5,
131 'mday' => 1
132 ),
133 'cincomayo' => array(
134 'regions' => array('usa'),
135 'mon' => 5,
136 'mday' => 5
137 ),
138 'vedayfra' => array(
139 'regions' => array('fra'),
140 'mon' => 5,
141 'mday' => 8
142 ),
143 'memorialDay' => array(
144 'regions' => array('usa'),
145 'func' => 'getMemorialDate' //last monday of May
146 ),
147 'republicDay' => array(
148 'regions' => array('ita'),
149 'mon' => 6,
150 'mday' => 2
151 ),
152 '4thJuly' => array(
153 'regions' => array('usa'),
154 'mon' => 7,
155 'mday' => 4
156 ),
157 'bastilleDay' => array(
158 'regions' => array('fra'),
159 'mon' => 7,
160 'mday' => 14
161 ),
162 'ferragosto' => array(
163 'regions' => array('ita'),
164 'mon' => 8,
165 'mday' => 15
166 ),
167 'laborDay' => array(
168 'regions' => array('usa'),
169 'func' => 'getLaborDate' //1st Monday of September
170 ),
171 'columbusDay' => array(
172 'regions' => array('usa'),
173 'func' => 'getColumbusDate' //2nd Monday of October
174 ),
175 'hispanityDay' => array(
176 'regions' => array('spa'),
177 'mon' => 10,
178 'mday' => 12,
179 ),
180 'halloween' => array(
181 'regions' => array('global'),
182 'mon' => 10,
183 'mday' => 31,
184 'bridge' => array('saintsDay', 'soulsDay')
185 ),
186 'saintsDay' => array(
187 'regions' => array('ita'),
188 'mon' => 11,
189 'mday' => 1,
190 'bridge' => array('soulsDay')
191 ),
192 'soulsDay' => array(
193 'regions' => array('ita'),
194 'mon' => 11,
195 'mday' => 2
196 ),
197 'wallOfBerlin' => array(
198 'regions' => array('ger'),
199 'mon' => 11,
200 'mday' => 9
201 ),
202 'armisticeDay' => array(
203 'regions' => array('fra'),
204 'mon' => 11,
205 'mday' => 11
206 ),
207 'veteransDay' => array(
208 'regions' => array('usa'),
209 'mon' => 11,
210 'mday' => 11
211 ),
212 'thanksgiving' => array(
213 'regions' => array('usa'),
214 'func' => 'getThanksgivingDate' //4th Thursday of November
215 ),
216 'immacolata' => array(
217 'regions' => array('ita', 'spa'),
218 'mon' => 12,
219 'mday' => 8
220 ),
221 'christmasEve' => array(
222 'regions' => array('global'),
223 'mon' => 12,
224 'mday' => 24,
225 'bridge' => array('christmasDay', 'stStephensDay', 'newYearsEve', 'newYearsDay')
226 ),
227 'christmasDay' => array(
228 'regions' => array('global'),
229 'mon' => 12,
230 'mday' => 25,
231 'bridge' => array('stStephensDay', 'newYearsEve', 'newYearsDay')
232 ),
233 'stStephensDay' => array(
234 'regions' => array('global'),
235 'mon' => 12,
236 'mday' => 26,
237 'bridge' => array('newYearsEve', 'newYearsDay')
238 ),
239 'newYearsEve' => array(
240 'regions' => array('global'),
241 'mon' => 12,
242 'mday' => 31,
243 'bridge' => array('newYearsDay')
244 ),
245 );
246
247 /**
248 * Class constructor is still public, even though
249 * it is possible to access the Singletone instance
250 * of the class through the method getInstance().
251 *
252 * @see getInstance()
253 * @since VCM 1.6.13
254 */
255 public function __construct()
256 {
257 $this->now = time();
258 }
259
260 /**
261 * Returns the global class object, either
262 * a new instance or the existing instance
263 * if the class was already instantiated.
264 * This method was introduced in the v 1.6.13 for
265 * VBO to check whether the class can be extended.
266 * It used to have private vars and methods that
267 * have now been changed to protected.
268 *
269 * @return self A new instance of the class.
270 *
271 * @since VCM 1.6.13
272 */
273 public static function getInstance()
274 {
275 if (is_null(static::$instance)) {
276 static::$instance = new static();
277 }
278
279 return static::$instance;
280 }
281
282 /**
283 * Method to set the current timestamp from which
284 * the class should find the closest festivities.
285 *
286 * @param int $from_ts
287 *
288 * @return self
289 */
290 public function setFromTimestamp($from_ts)
291 {
292 if (!empty($from_ts)) {
293 $this->now = $from_ts;
294 }
295
296 return $this;
297 }
298
299 /**
300 * Returns all the supported regions with
301 * the corresponding translated names.
302 *
303 * @return array
304 */
305 public function getTranslatedRegions()
306 {
307 $trans_regions = $this->regions;
308 foreach ($trans_regions as $k => $v) {
309 if ($this->translate) {
310 $say_name = JText::translate('VCMFEST'.strtoupper($v));
311 // make sure the lang definition is available
312 $say_name = substr($say_name, 0, 7) == 'VCMFEST' ? $k : $say_name;
313 } else {
314 $say_name = $k;
315 }
316 $trans_regions[$k] = $say_name;
317 }
318
319 return $trans_regions;
320 }
321
322 /**
323 * Main method to get a list of the closest festivities
324 * for the given region, starting from the current timestamp.
325 *
326 * @param string [$region]
327 *
328 * @return array
329 */
330 public function loadFestivities($region = 'global')
331 {
332 return $this->calculateFestivititesDates(
333 $this->getFestivitiesByRegion(strtolower($region))
334 );
335 }
336
337 /**
338 * Method that filters the festivities by the
339 * requested region or the global ones.
340 *
341 * @param string $region
342 *
343 * @return array
344 */
345 protected function getFestivitiesByRegion($region)
346 {
347 $fests = array();
348 foreach ($this->festivities as $k => $v) {
349 if ($v['regions'][0] == 'global' || in_array($region, $v['regions'])) {
350 $fests[$k] = $v;
351 }
352 }
353
354 return $fests;
355 }
356
357 /**
358 * Method that parses all the region-filtered festivities to
359 * calculate the next timestamp for each fest and the end date,
360 * by setting the properties 'from_ts' and 'to_ts' for each fest.
361 * Ex. If a holiday is on Monday, the method sets the dates to Sat-Mon.
362 * Ex. If a holiday is on Thursday, the method sets the dates to Thu-Sat.
363 * Ex. If a holiday is on Friday, the method sets the dates to Fri-Sat.
364 * The method returns a sorted and filtered array of key-value pairs with
365 * some new properties: 'next_ts', 'from_ts', 'to_ts', 'wday', 'trans_name'.
366 *
367 * @param array $regions_festivities (global festivities + region's festivities)
368 *
369 * @return array
370 */
371 protected function calculateFestivititesDates($regions_festivities)
372 {
373 $fests = array();
374 $info_from = getdate($this->now);
375
376 //calculate the next timestamp from today of each festivity ('from_ts')
377 foreach ($regions_festivities as $k => $v) {
378 if (isset($v['func'])) {
379 //custom function
380 if (!method_exists($this, $v['func']) || !is_callable(array($this, $v['func']))) {
381 continue;
382 }
383 $next_ts = $this->{$v['func']}();
384 } else {
385 //fixed month and month-day
386 $next_ts = mktime(0, 0, 0, $v['mon'], $v['mday'], $info_from['year']);
387 if ($next_ts < $this->now) {
388 $next_ts = mktime(0, 0, 0, $v['mon'], $v['mday'], ($info_from['year'] + 1));
389 }
390 }
391 if (empty($next_ts)) {
392 continue;
393 }
394 $info_next = getdate($next_ts);
395 $v['wday'] = $info_next['wday'];
396 $v['from_ts'] = $v['next_ts'] = $next_ts;
397 $regions_festivities[$k] = $v;
398 $fests[$k] = $v;
399 }
400 //
401
402 //calculate the end date for each festivity by considering weekends and bridges ('to_ts', and 'from_ts', if necessary)
403 foreach ($fests as $k => $v) {
404 $info_date = getdate($v['from_ts']);
405 if ((int)$info_date['wday'] < 2) {
406 //Festivity is on a Sunday or a Monday, switch 'from_ts' to the Saturday before
407 $fests[$k]['from_ts'] = mktime(0, 0, 0, $info_date['mon'], ($info_date['mday'] - ((int)$info_date['wday'] + 1)), $info_date['year']);
408 }
409 if (isset($v['bridge']) && count($v['bridge'])) {
410 //Check next bridges of this festivity
411 $bridges_ts = array();
412 foreach ($v['bridge'] as $fest_key) {
413 if (isset($fests[$fest_key]) && isset($fests[$fest_key]['from_ts'])) {
414 $bridges_ts[] = $regions_festivities[$fest_key]['from_ts'];
415 }
416 }
417 if (count($bridges_ts)) {
418 //bridges found: set the 'to_ts' to the last festivity of the bridge and continue
419 $info_max = getdate(max($bridges_ts));
420 $fests[$k]['to_ts'] = mktime(23, 59, 59, $info_max['mon'], $info_max['mday'], $info_max['year']);
421 continue;
422 }
423 }
424 if ((int)$info_date['wday'] == 4) {
425 //Festivity is on a Thursday, set 'to_ts' to the Saturday after
426 $fests[$k]['to_ts'] = mktime(0, 0, 0, $info_date['mon'], ($info_date['mday'] + 2), $info_date['year']);
427 continue;
428 }
429 if ((int)$info_date['wday'] == 5) {
430 //Festivity is on a Friday, set 'to_ts' to the Saturday after
431 $fests[$k]['to_ts'] = mktime(0, 0, 0, $info_date['mon'], ($info_date['mday'] + 1), $info_date['year']);
432 continue;
433 }
434 //no bridges, set 'to_ts' to the end of the same day at 23:59:59
435 $fests[$k]['to_ts'] = mktime(23, 59, 59, $info_date['mon'], $info_date['mday'], $info_date['year']);
436 }
437 //
438
439 // sorting and translation
440 $sort_map = array();
441 foreach ($fests as $k => $v) {
442 $sort_map[$k] = $v['next_ts'];
443 if ($this->translate) {
444 $say_name = JText::translate('VCMFEST'.strtoupper($k));
445 // make sure the lang definition is available
446 $say_name = substr($say_name, 0, 7) == 'VCMFEST' ? $k : $say_name;
447 } else {
448 $say_name = $k;
449 }
450 $fests[$k]['trans_name'] = $say_name;
451 }
452 asort($sort_map);
453 $sorted_fests = array();
454 foreach ($sort_map as $k => $v) {
455 $sorted_fests[$k] = $fests[$k];
456 }
457 $fests = $sorted_fests;
458
459 return $fests;
460 }
461
462 /**
463 * Custom method that returns the next Easter ts.
464 * We use easter_days() to get the number of days where Easter
465 * falls after March 21st of the given year.
466 *
467 * @return int
468 */
469 public function getEasterDate()
470 {
471 $next_ts = mktime(0, 0, 0, 3, (21 + easter_days(date('Y', $this->now))), date('Y', $this->now));
472 if ($next_ts < $this->now) {
473 $next_ts = mktime(0, 0, 0, 3, (21 + easter_days(((int)date('Y', $this->now) + 1))), ((int)date('Y', $this->now) + 1));
474 }
475
476 return $next_ts;
477 }
478
479 /**
480 * Custom method that returns the Rosenmontag ts.
481 * (Monday before the Ash Wednesday, 7th week before Easter)
482 *
483 * @return int
484 */
485 public function getRosenmontagDate()
486 {
487 $eightw_easter = getdate(strtotime('-7 weeks', mktime(0, 0, 0, 3, (21 + easter_days(date('Y', $this->now))), date('Y', $this->now))));
488 $next_ts = mktime(0, 0, 0, $eightw_easter['mon'], ($eightw_easter['mday'] + 1), $eightw_easter['year']);
489 if ($next_ts < $this->now) {
490 $eightw_easter = getdate(strtotime('-7 weeks', mktime(0, 0, 0, 3, (21 + easter_days(((int)date('Y', $this->now) + 1))), ((int)date('Y', $this->now) + 1))));
491 $next_ts = mktime(0, 0, 0, $eightw_easter['mon'], ($eightw_easter['mday'] + 1), $eightw_easter['year']);
492 }
493
494 return $next_ts;
495 }
496
497 /**
498 * Custom method that returns the Mardi Gras ts.
499 * (Tuesday before the Ash Wednesday, 7th week before Easter)
500 *
501 * @return int
502 */
503 public function getMardigrasDate()
504 {
505 $eightw_easter = getdate(strtotime('-7 weeks', mktime(0, 0, 0, 3, (21 + easter_days(date('Y', $this->now))), date('Y', $this->now))));
506 $next_ts = mktime(0, 0, 0, $eightw_easter['mon'], ($eightw_easter['mday'] + 2), $eightw_easter['year']);
507 if ($next_ts < $this->now) {
508 $eightw_easter = getdate(strtotime('-7 weeks', mktime(0, 0, 0, 3, (21 + easter_days(((int)date('Y', $this->now) + 1))), ((int)date('Y', $this->now) + 1))));
509 $next_ts = mktime(0, 0, 0, $eightw_easter['mon'], ($eightw_easter['mday'] + 2), $eightw_easter['year']);
510 }
511
512 return $next_ts;
513 }
514
515 /**
516 * Custom method that returns the next ts for the
517 * Martin Luther King's Day (3rd Monday of January).
518 *
519 * @return int
520 */
521 public function getMLKDate()
522 {
523 $next_ts = strtotime('third Monday of January '.date('Y', $this->now));
524 if (!empty($next_ts) && $next_ts < $this->now) {
525 $next_ts = strtotime('third Monday of January '.((int)date('Y', $this->now) + 1));
526 }
527
528 return $next_ts;
529 }
530
531 /**
532 * Custom method that returns the next ts for the
533 * President's Day (3rd Monday of February).
534 *
535 * @return int
536 */
537 public function getPresidentsDate()
538 {
539 $next_ts = strtotime('third Monday of February '.date('Y', $this->now));
540 if (!empty($next_ts) && $next_ts < $this->now) {
541 $next_ts = strtotime('third Monday of February '.((int)date('Y', $this->now) + 1));
542 }
543
544 return $next_ts;
545 }
546
547 /**
548 * Custom method that returns the next ts for the
549 * Memorial's Day (last Monday of May).
550 *
551 * @return int
552 */
553 public function getMemorialDate()
554 {
555 $next_ts = strtotime('last Monday of May '.date('Y', $this->now));
556 if (!empty($next_ts) && $next_ts < $this->now) {
557 $next_ts = strtotime('last Monday of May '.((int)date('Y', $this->now) + 1));
558 }
559
560 return $next_ts;
561 }
562
563 /**
564 * Custom method that returns the next ts for the
565 * Labor's Day (first Monday of September).
566 *
567 * @return int
568 */
569 public function getLaborDate()
570 {
571 $next_ts = strtotime('first Monday of September '.date('Y', $this->now));
572 if (!empty($next_ts) && $next_ts < $this->now) {
573 $next_ts = strtotime('first Monday of September '.((int)date('Y', $this->now) + 1));
574 }
575
576 return $next_ts;
577 }
578
579 /**
580 * Custom method that returns the next ts for the
581 * Columbus's Day (second Monday of October).
582 *
583 * @return int
584 */
585 public function getColumbusDate()
586 {
587 $next_ts = strtotime('second Monday of October '.date('Y', $this->now));
588 if (!empty($next_ts) && $next_ts < $this->now) {
589 $next_ts = strtotime('second Monday of October '.((int)date('Y', $this->now) + 1));
590 }
591
592 return $next_ts;
593 }
594
595 /**
596 * Custom method that returns the next ts for the
597 * Thanksgiving Day (4th Thursday of November).
598 *
599 * @return int
600 */
601 public function getThanksgivingDate()
602 {
603 $next_ts = strtotime('fourth Thursday of November '.date('Y', $this->now));
604 if (!empty($next_ts) && $next_ts < $this->now) {
605 $next_ts = strtotime('fourth Thursday of November '.((int)date('Y', $this->now) + 1));
606 }
607
608 return $next_ts;
609 }
610 }
611 }
612
613 /**
614 * Festivities handler class for Vik Booking.
615 *
616 * @since 1.12.0
617 */
618 class VikBookingFestivities extends VikBookingFestivitiesBC
619 {
620 /**
621 * The singleton instance of the class.
622 *
623 * @var VikBookingTracker
624 */
625 protected static $instance = null;
626
627 /**
628 * The guessed region for the festivities.
629 *
630 * @var string
631 */
632 protected $guessed_region = 'global';
633
634 /**
635 * Regions adapter values to find the proper region key in festivities.
636 *
637 * @var array
638 */
639 protected $region_adapters = array(
640 'de' => 'ger',
641 'es' => 'spa',
642 'en' => 'usa',
643 );
644
645 /**
646 * Class constructor is public for bc.
647 *
648 * @see getInstance()
649 */
650 public function __construct()
651 {
652 // call parent constructor
653 parent::__construct();
654
655 // load the language file of VCM
656 $this->loadLanguage();
657
658 // guess the region to use for the festivities
659 $this->guessRegion();
660 }
661
662 /**
663 * Returns the global Tracker object, either
664 * a new instance or the existing instance
665 * if the class was already instantiated.
666 *
667 * @return self A new instance of the class.
668 */
669 public static function getInstance()
670 {
671 if (is_null(static::$instance)) {
672 static::$instance = new static();
673 }
674
675 return static::$instance;
676 }
677
678 /**
679 * Checks whether the next festivities should be verified
680 * depending on the last time they were checked.
681 * By default we check new festivities every month.
682 *
683 * @param string $current_check the current checking identifier.
684 *
685 * @return boolean True if it's time to check, false otherwise.
686 */
687 public function shouldCheckFestivities($current_check = '')
688 {
689 if (empty($current_check)) {
690 $current_check = date('Y-m');
691 }
692 $dbo = JFactory::getDbo();
693 $q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`='fests_last_check';";
694 $dbo->setQuery($q);
695 $last_check = $dbo->loadResult();
696 if ($last_check) {
697 $should_check = ($last_check != $current_check);
698 if ($should_check) {
699 // update last time we checked
700 $q = "UPDATE `#__vikbooking_config` SET `setting`=".$dbo->quote($current_check)." WHERE `param`='fests_last_check';";
701 $dbo->setQuery($q);
702 $dbo->execute();
703 }
704 return $should_check;
705 }
706
707 // first time we access this method
708 $q = "INSERT INTO `#__vikbooking_config` (`param`,`setting`) VALUES ('fests_last_check', ".$dbo->quote($current_check).");";
709 $dbo->setQuery($q);
710 $dbo->execute();
711 return true;
712 }
713
714 /**
715 * Main method to find the next festivities for the calculated/given region
716 * and to store them onto the database, only if they do not exist already.
717 *
718 * @return int the total number of festivities stored
719 *
720 * @see shouldCheckFestivities() should be called before this method to not waste resources.
721 */
722 public function storeNextFestivities()
723 {
724 $tot_added = 0;
725 $all_fests = $this->getNextFestivities();
726
727 // stores the festivities found if they do not exist already
728 foreach ($all_fests as $k => $v) {
729 $fest_ymd = date('Y-m-d', $v['next_ts']);
730 if (!$this->festivityExists($fest_ymd, $k) && $this->storeFestivity($fest_ymd, $v, $k)) {
731 $tot_added++;
732 }
733 }
734
735 return $tot_added;
736 }
737
738 /**
739 * Gets all the next festivities for the current region.
740 *
741 * @return array
742 */
743 public function getNextFestivities()
744 {
745 $all_fests = $this->loadFestivities($this->guessed_region);
746
747 // add up some useful properties for other methods, not really for storing
748 foreach ($all_fests as $k => $v) {
749 $all_fests[$k]['next_day'] = date('Y-m-d', $v['next_ts']);
750 $all_fests[$k]['from_day'] = date('Y-m-d', $v['from_ts']);
751 $all_fests[$k]['to_day'] = date('Y-m-d', $v['to_ts']);
752 $all_fests[$k]['all_timestamps'] = $this->getFestsDatesArray($v['from_ts'], $v['to_ts']);
753 }
754
755 return $all_fests;
756 }
757
758 /**
759 * Checks whether a precise festivity exists on the given date.
760 *
761 * @param string $ymd the date string in Y-m-d format.
762 * @param string $key the key name of the fest (custom/VCM).
763 * @param boolean $get whether to get the found record.
764 *
765 * @return mixed True/array if festivity exists, false otherwise.
766 */
767 public function festivityExists($ymd, $key = '', $get = false)
768 {
769 $dbo = JFactory::getDbo();
770 $q = "SELECT * FROM `#__vikbooking_fests_dates` WHERE `dt`=".$dbo->quote($ymd);
771 $dbo->setQuery($q);
772 $festivity = $dbo->loadAssoc();
773 if (!$festivity) {
774 return false;
775 }
776
777 $fests_info = json_decode($festivity['festinfo']);
778 if (!$fests_info) {
779 return false;
780 }
781
782 // update this information in the array in case it needs to be returned
783 $festivity['festinfo'] = $fests_info;
784
785 // seek for the requested fest
786 foreach ($fests_info as $fest) {
787 if (!empty($fest->type) && $fest->type == $key) {
788 return $get ? $festivity : true;
789 }
790 }
791 return false;
792 }
793
794 /**
795 * Stores/updates a festivity for the given date. If a record for the given date exists, then the
796 * festivity is added to or updated in the current record. Otherwise a new record is created.
797 *
798 * @param string $ymd the date string in Y-m-d format.
799 * @param array $fest the array information of the festivity to be JSON-encoded.
800 * @param string $type the key name of the festivity ('custom' for manual entries).
801 * @param string $descr the description of the festivity (some notes).
802 *
803 * @return boolean True if the new record is stored or updated, false otherwise.
804 */
805 public function storeFestivity($ymd, $fest, $type = 'custom', $descr = '')
806 {
807 // build "hot dates" next to the main festivity if set, and if description is empty
808 $descr = empty($descr) && isset($fest['all_timestamps']) && count($fest['all_timestamps']) > 1 ? $this->parseBridgeTimestamps($fest['all_timestamps']) : $descr;
809
810 // build festivity mandatory properties ('trans_name' is another mandatory prop which must be already set)
811 $fest['type'] = $type;
812 $fest['descr'] = $descr;
813
814 $dbo = JFactory::getDbo();
815 $q = "SELECT * FROM `#__vikbooking_fests_dates` WHERE `dt`=".$dbo->quote($ymd);
816 $dbo->setQuery($q);
817 $dbo->execute();
818 if (!$dbo->getNumRows()) {
819 // create a new record
820 $q = "INSERT INTO `#__vikbooking_fests_dates` (`dt`, `festinfo`) VALUES (".$dbo->quote($ymd).", ".$dbo->quote(json_encode(array($fest))).");";
821 $dbo->setQuery($q);
822 $dbo->execute();
823
824 return ((int)$dbo->insertid() > 0);
825 }
826 // update current record by pushing the festivity into the existing festinfo array of objects
827 $festivity = $dbo->loadAssoc();
828 $fests_arr = json_decode($festivity['festinfo']);
829 array_push($fests_arr, (object)$fest);
830 $q = "UPDATE `#__vikbooking_fests_dates` SET `festinfo`=".$dbo->quote(json_encode($fests_arr))." WHERE `id`=".$festivity['id'];
831 $dbo->setQuery($q);
832 $dbo->execute();
833
834 return ((int)$dbo->getAffectedRows() > 0);
835 }
836
837 /**
838 * Deletes a specific festivity for the given date.
839 * If the day will not contain anymore fests, then
840 * the whole record will be deleted.
841 *
842 * @param string $ymd the date string in Y-m-d format.
843 * @param int $index the array index of the fest.
844 * @param string $type the key name of the festivity ('custom' for manual entries).
845 *
846 * @return boolean True if the fest is removed, false otherwise.
847 */
848 public function deleteFestivity($ymd, $index, $type = 'custom')
849 {
850 $record = $this->festivityExists($ymd, $type, true);
851 if (!$record) {
852 return false;
853 }
854
855 $drop_record = false;
856 if (isset($record['festinfo'][$index])) {
857 // fest was found by array-index
858 array_splice($record['festinfo'], $index, 1);
859 if (!count($record['festinfo'])) {
860 // this day has got no more fests
861 $drop_record = true;
862 }
863 } else {
864 // seek for the requested fest by type
865 foreach ($record['festinfo'] as $k => $fest) {
866 if (!empty($fest->type) && $fest->type == $type) {
867 // fest found
868 array_splice($record['festinfo'], $k, 1);
869 if (!count($record['festinfo'])) {
870 // this day has got no more fests
871 $drop_record = true;
872 }
873 break;
874 }
875 }
876 }
877
878 $dbo = JFactory::getDbo();
879 if ($drop_record) {
880 $q = "DELETE FROM `#__vikbooking_fests_dates` WHERE `dt`=".$dbo->quote($ymd);
881 } else {
882 $q = "UPDATE `#__vikbooking_fests_dates` SET `festinfo`=".$dbo->quote(json_encode($record['festinfo']))." WHERE `dt`=".$dbo->quote($ymd);
883 }
884 $dbo->setQuery($q);
885 $dbo->execute();
886
887 return true;
888 }
889
890 /**
891 * Loads all the fests from Vik Booking, either the custom and the global ones.
892 * Returns an associative array indexed by date with decoded fests information.
893 *
894 * @param string $from_ymd the optional minimum date in Y-m-d (defaults to today).
895 * @param string $to_ymd the optional maximum date in Y-m-d (defaults to none).
896 *
897 * @return array the list of festivities found in Vik Booking.
898 */
899 public function loadFestDates($from_ymd = null, $to_ymd = null)
900 {
901 $fests = array();
902 if (empty($from_ymd)) {
903 $from_ymd = date('Y-m-d');
904 }
905
906 $dbo = JFactory::getDbo();
907 $q = "SELECT * FROM `#__vikbooking_fests_dates` WHERE `dt`>=".$dbo->quote($from_ymd).(!empty($to_ymd) ? " AND `dt`<=".$dbo->quote($to_ymd) : '')." ORDER BY `dt` ASC;";
908 $dbo->setQuery($q);
909 $all_fests = $dbo->loadAssocList();
910 if ($all_fests) {
911 // make sure to decode all festivity infos
912 foreach ($all_fests as $k => $v) {
913 $v['festinfo'] = json_decode($v['festinfo']);
914 // make sure the TS properties are set for custom fests
915 foreach ($v['festinfo'] as $fk => $fv) {
916 if (!isset($fv->next_ts)) {
917 $start_ts = strtotime($v['dt']);
918 $start_info = getdate($start_ts);
919 $v['festinfo'][$fk]->next_ts = $start_ts;
920 $v['festinfo'][$fk]->from_ts = $start_ts;
921 $v['festinfo'][$fk]->to_ts = mktime(23, 59, 59, $start_info['mon'], $start_info['mday'], $start_info['year']);
922 }
923 }
924 //
925 $fests[$v['dt']] = $v;
926 }
927 }
928
929 return $fests;
930 }
931
932 /**
933 * Sets the proper region for the festivities to look for.
934 * If the given region identifier does not exist, the 'global' region is used.
935 * By instantiating the class object, the constructor will guess automatically
936 * the region and will call this method.
937 *
938 * @param string $identifier the region identifier (array key).
939 *
940 * @return boolean True if region exists, false otherwise by using the global region.
941 */
942 public function setRegion($identifier)
943 {
944 if (isset($this->regions[$identifier])) {
945 // region requested exists
946 $this->guessed_region = $identifier;
947 return true;
948 }
949
950 // use default region
951 $this->guessed_region = 'global';
952 return false;
953 }
954
955 /**
956 * Attempts to find the proper region for the festivities
957 * from a given identifier, or from the language tag if empty.
958 * If the guessed region does not exist, then we revert to 'global'.
959 *
960 * @param string $identifier the region identifier (array key).
961 *
962 * @return boolean True if region exists, false otherwise.
963 *
964 * @uses setRegion()
965 */
966 protected function guessRegion($identifier = '')
967 {
968 if (empty($identifier)) {
969 $identifier = substr(JFactory::getLanguage()->getTag(), 0, 2);
970 }
971
972 // make sure the identifier is in lower case
973 $identifier = strtolower($identifier);
974
975 // check if identifier is an adapter value for a region
976 if (isset($this->region_adapters[$identifier])) {
977 // adapter value was found, so this is more likely a valid identifier
978 $identifier = $this->region_adapters[$identifier];
979 }
980
981 // parse all festivities to find the proper matching region identifier if not yet found
982 if (!isset($this->regions[$identifier])) {
983 $regions = array_keys($this->regions);
984 foreach ($regions as $region) {
985 $haystack = strlen($identifier) > strlen($region) ? $identifier : $region;
986 $needle = strlen($identifier) > strlen($region) ? $region : $identifier;
987 if (strpos($haystack, $needle) !== false) {
988 // we were lucky enough to find a valid region, so just update the identifier
989 $identifier = $region;
990 break;
991 }
992 }
993 }
994
995 return $this->setRegion($identifier);
996 }
997
998 /**
999 * Calculates the range of dates affecting the festivity start and end times.
1000 *
1001 * @param int $from_ts festivity start timestamp
1002 * @param int $to_ts festivity end timestamp
1003 *
1004 * @return array the range of dates (timestamps) touching the festivity
1005 */
1006 protected function getFestsDatesArray($from_ts, $to_ts)
1007 {
1008 $dates_ts = array();
1009 if (empty($from_ts) || empty($to_ts) || $to_ts < $from_ts) {
1010 return $dates_ts;
1011 }
1012
1013 $from_info = getdate($from_ts);
1014 while ($from_info[0] <= $to_ts) {
1015 array_push($dates_ts, $from_info[0]);
1016 // next day
1017 $from_info = getdate(mktime(0, 0, 0, $from_info['mon'], ($from_info['mday'] + 1), $from_info['year']));
1018 }
1019
1020 return $dates_ts;
1021 }
1022
1023 /**
1024 * Converts a list of timestamps into a list of readable dates.
1025 *
1026 * @param array $all_ts list of timestamps affecting the fest.
1027 *
1028 * @return string the list (c.s.v.) of dates touching this festivity or an empty string.
1029 */
1030 protected function parseBridgeTimestamps($all_ts)
1031 {
1032 $hot_dates = array();
1033 if (empty($all_ts) || !is_array($all_ts)) {
1034 return '';
1035 }
1036
1037 $nowdf = VikBooking::getDateFormat();
1038 if ($nowdf == "%d/%m/%Y") {
1039 $df = 'd/m/Y';
1040 } elseif ($nowdf == "%m/%d/%Y") {
1041 $df = 'm/d/Y';
1042 } else {
1043 $df = 'Y/m/d';
1044 }
1045 $datesep = VikBooking::getDateSeparator();
1046
1047 foreach ($all_ts as $ts) {
1048 array_push($hot_dates, date(str_replace("/", $datesep, $df), $ts));
1049 }
1050
1051 return implode(', ', $hot_dates);
1052 }
1053
1054 /**
1055 * Loads the language file from VCM, necessary to translate most festivities.
1056 * This method does not extend anything from the VCM's parent class. It's a
1057 * method only of VikBookingFestivities. Compatible with both Joomla and WordPress.
1058 *
1059 * @return void
1060 */
1061 protected function loadLanguage()
1062 {
1063 // load the VCM admin language file
1064 $vcm_admin_lang_path = '';
1065 if (VBOPlatformDetection::isJoomla()) {
1066 $vcm_admin_lang_path = JPATH_ADMINISTRATOR;
1067 } elseif (defined('VIKCHANNELMANAGER_ADMIN_LANG')) {
1068 $vcm_admin_lang_path = VIKCHANNELMANAGER_ADMIN_LANG;
1069 } elseif (VBOPlatformDetection::isWordPress()) {
1070 /**
1071 * If running within Vik Booking, the constant VIKCHANNELMANAGER_ADMIN_LANG may not be available
1072 *
1073 * @since 1.4.3 (WP) - 1.14.2 (J)
1074 */
1075 $vcm_admin_lang_path = str_replace('vikbooking', 'vikchannelmanager', VIKBOOKING_ADMIN_LANG);
1076 }
1077 if (empty($vcm_admin_lang_path)) {
1078 return;
1079 }
1080
1081 $lang = JFactory::getLanguage();
1082 $lang->load('com_vikchannelmanager', $vcm_admin_lang_path);
1083 if (VBOPlatformDetection::isWordPress() && defined('VIKCHANNELMANAGER_LIBRARIES')) {
1084 /**
1085 * @wponly load language admin handler as well for WP.
1086 * We do this only because of WordPress, but in a way also compatible with Joomla as
1087 * the constant VIKCHANNELMANAGER_LIBRARIES and method attachHandler are not in Joomla.
1088 */
1089 $lang->attachHandler(VIKCHANNELMANAGER_LIBRARIES . DIRECTORY_SEPARATOR . 'language' . DIRECTORY_SEPARATOR . 'admin.php', 'vikchannelmanager');
1090 }
1091 }
1092 }
1093