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 / admin / helpers / festivities.php

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

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