PluginProbe
VikBooking Hotel Booking Engine & PMS / 1.8.16
VikBooking Hotel Booking Engine & PMS v1.8.16
1.8.16 1.8.15 1.8.14 1.8.13 1.8.12 1.8.11 1.8.10 1.8.9 1.8.6 1.8.7 1.8.8 trunk 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.6.5 1.6.6 1.6.7 1.6.8 1.6.9 1.7.0 1.7.1 1.7.2 All 37 releases
← All changes | admin/helpers/festivities.php +1092 -0 1.6.0 → 1.8.16 View file →
@@ -1,0 +1,1092 @@
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 +}