1: <?php
2:
3: /**
4: * Calendar modules
5: * @package modules
6: * @subpackage calendar
7: */
8:
9: if (!defined('DEBUG_MODE')) { die(); }
10:
11: /**
12: * @subpackage calendar/lib
13: */
14: class Hm_Cal_Output {
15:
16: private $output_mod;
17: private $events;
18: private $today;
19: private $month;
20: private $year;
21: private $format;
22: private $date;
23:
24: public function __construct($output_mod, $events) {
25: $this->events = $events;
26: $this->output_mod = $output_mod;
27: $this->today = date('Y-m-d');
28: }
29:
30: public function output($data, $date, $format) {
31: $this->date = $date;
32: $this->year = date('Y', strtotime($date));
33: $this->format = $format;
34: if (method_exists($this, 'output_'.$format)) {
35: if ($format != 'year') {
36: $this->month = (int) date('n', strtotime($date));
37: }
38: return $this->{'output_'.$format}($data);
39: }
40: else {
41: die('Invalid output format');
42: }
43: }
44:
45: private function output_day($day) {
46: $res = '<td class="';
47: if ($day == $this->today) {
48: $res .= 'today bg-light';
49: }
50: if ($this->month != (int) date('n', strtotime($day))) {
51: $res .= 'offmonth ';
52: }
53: $res .= '">'.$this->output_mod->translate_number(date('d', (strtotime($day))));
54: $res .= $this->output_event($day);
55: $res .= '</td>';
56: return $res;
57: }
58:
59: private function output_event($day) {
60: if (array_key_exists($day, $this->events)) {
61: usort($this->events[$day], function($a, $b) {
62: if ($a['ts'] == $b['ts']) {
63: return 0;
64: }
65: return ($a['ts'] < $b['ts']) ? -1 : 1;
66: });
67: $res = '';
68: foreach ($this->events[$day] as $event) {
69: $res .= '<div class="cal_event">'.
70: $this->output_event_details($event).
71: $this->output_mod->html_safe(date('H:i', $event['ts'])).
72: ' <a class="cal_title cursor-pointer" href="#">'.$this->output_mod->html_safe($event['title']).
73: '</a></div>';
74: }
75: return $res;
76: }
77: return '';
78: }
79:
80: private function output_event_details($event) {
81: $res = '<div class="event_details">'.
82: '<div class="event_title d-flex flex-column border-bottom mb-2"><span class="fs-5">'.$this->output_mod->html_safe($event['title']).'</span>'.
83: '<small class="event_date fw-lighter">'.$this->output_mod->html_safe(date('H:i A', $event['ts'])).'</small></div>';
84: if (mb_strlen(trim($event['description']))) {
85: $res .= '<div class="event_detail mb-2">'.$this->output_mod->html_safe($event['description']).'</div>';
86: }
87: if (mb_strlen(trim($event['repeat_interval']))) {
88: $res .= '<div class="event_repeat"><small class="fw-lighter fst-italic">'.$this->output_mod->trans(sprintf('Repeats every %s', $event['repeat_interval'])).'</small></div>';
89: }
90: $res .= '<form method="post"><input type="hidden" name="delete_ts" value="'.$this->output_mod->html_safe($event['ts']).'" />'.
91: '<input type="hidden" name="delete_id" value="'.$this->output_mod->html_safe($event['id']).'" />'.
92: '<input type="hidden" name="hm_page_key" value="'.$this->output_mod->html_safe(Hm_Request_Key::generate()).'" />'.
93: '<div class="event_delete"><a class="btn btn-danger btn-sm">Delete</a></div></form>';
94: $res .= '</div>';
95: return $res;
96: }
97:
98: private function output_week($week) {
99: $res = '';
100: if ($this->format == 'week') {
101: $res .= $this->title();
102: $res .= '<table class="calendar_week">';
103: $res .= $this->output_heading();
104: }
105: $res .= '<tr>';
106: foreach ($week as $day) {
107: $res .= $this->output_day($day);
108: }
109: $res .= '</tr>';
110: if ($this->format == 'week') {
111: $res .= '</table>';
112: }
113: return $res;
114: }
115:
116: private function output_month($month) {
117: $res = $this->title();
118: $res .= '<div class="m-4 border"><table class="calendar_month">';
119: $res .= $this->output_heading();
120: foreach ($month as $week) {
121: $res .= $this->output_week($week);
122: }
123: $res .= '</table></div>';
124: return $res;
125: }
126:
127: private function output_year($year) {
128: $res = '<div class="calendar_year">';
129: foreach ($year as $id => $month) {
130: $this->month = $id;
131: $res .= $this->output_month($month);
132: }
133: $res .= '</div>';
134: return $res;
135: }
136:
137: private function prev_next_week() {
138: return array(sprintf('<a href="?page=calendar&date=%s">&lt;</a>', date('Y-m-d', strtotime("-1 week", strtotime($this->date)))),
139: sprintf('<a href="?page=calendar&date=%s">&gt;</a>', date('Y-m-d', strtotime("+1 week", strtotime($this->date)))));
140: }
141:
142: private function prev_next_month() {
143: return array(sprintf('<a href="?page=calendar&date=%s">&lt;</a>', date('Y-m', strtotime("-1 month", strtotime($this->year.'-'.$this->month)))),
144: sprintf('<a href="?page=calendar&date=%s">&gt;</a>', date('Y-m', strtotime("+1 month", strtotime($this->year.'-'.$this->month)))));
145: }
146:
147: private function prev_next_year() {
148: return array(sprintf('<a href="?page=calendar&date=%s">&lt;</a>', date('Y', strtotime("-1 year", strtotime($this->date)))),
149: sprintf('<a href="?page=calendar&date=%s">&gt;</a>', date('Y', strtotime("+1 year", strtotime($this->date)))));
150: }
151:
152: private function prev_next() {
153: if (method_exists($this, 'prev_next_'.$this->format)) {
154: return $this->{'prev_next_'.$this->format}();
155: }
156: die('invalid format');
157: }
158:
159: private function title() {
160: list($prev, $next) = $this->prev_next();
161: $year = date('Y', strtotime($this->year.'-'.$this->month));
162: $month = date('F', strtotime($this->year.'-'.$this->month));
163: $title = sprintf('%s, %%s', $month);
164: $title = sprintf($this->output_mod->trans($title), $this->output_mod->translate_number($year));
165: return '<div class="month_label p-4 h5">'.$prev.' '.$title.' '.$next.'</div>';
166: }
167:
168: private function output_heading() {
169: $days = array('Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday');
170: return '<tr>'.implode('', array_map(function($v) {
171: return sprintf('<th class="p-2 fw-bold border-bottom border-end">%s</th>', $this->output_mod->trans($v)); }, $days)).'</tr>';
172: }
173: }
174:
175: /**
176: * @subpackage calendar/lib
177: */
178: class Hm_Cal_Event_Store {
179: private $data = array();
180:
181: public function __construct() {
182: }
183:
184: public function load($data) {
185: foreach ($data as $event) {
186: $event = new Hm_Cal_Event($event);
187: if ($event->is_valid()) {
188: $this->data[] = $event;
189: }
190: }
191: }
192:
193: public function add($data) {
194: $event = new Hm_Cal_Event($data);
195: if ($event->is_valid()) {
196: $this->data[] = $event;
197: return true;
198: }
199: return false;
200: }
201:
202: public function dump() {
203: $res = array();
204: foreach ($this->data as $event) {
205: $res[] = $event->dump();
206: }
207: return $res;
208: }
209:
210: public function in_date_range($start, $end) {
211: $events = array();
212: foreach ($this->data as $event) {
213: $event_data = $event->in_date_range($start, $end);
214: if (count($event_data)) {
215: foreach ($event_data as $event) {
216: if (array_key_exists($event['date'], $events)) {
217: $events[$event['date']][] = $event;
218: }
219: else {
220: $events[$event['date']] = array($event);
221: }
222: }
223: }
224: }
225: return $events;
226: }
227:
228: public function delete($id) {
229: $events = array();
230: $deleted = false;
231: foreach ($this->data as $event) {
232: if ($event->get('id') == $id) {
233: $deleted = true;
234: continue;
235: }
236: $events[] = $event;
237: }
238: $this->data = $events;
239: return $deleted;
240: }
241: }
242:
243: /**
244: * @subpackage calendar/lib
245: */
246: class Hm_Cal_Event {
247:
248: private $data = array(
249: 'title' => NULL,
250: 'description' => NULL,
251: 'date' => NULL,
252: 'repeat_interval' => NULL,
253: );
254:
255: public function __construct($data) {
256: if (is_array($data)) {
257: foreach ($this->data as $name => $value) {
258: if (array_key_exists($name, $data)) {
259: $this->data[$name] = $data[$name];
260: }
261: }
262: }
263: $this->data['id'] = md5(implode('', array_values($this->data)));
264: }
265:
266: public function is_valid() {
267: return $this->get('date');
268: }
269:
270: public function get($name, $default=false) {
271: if (array_key_exists($name, $this->data)) {
272: return $this->data[$name];
273: }
274: return $default;
275: }
276: public function dump() {
277: return $this->data;
278: }
279:
280: private function check_start_date($start, $end) {
281: $ts = false;
282: if ($this->get('date')) {
283: $ts = $this->get('date');
284: if ($ts) {
285: return $ts;
286: }
287: }
288: return $ts;
289: }
290:
291: private function fast_forward_repeat_event($ts, $interval, $limit) {
292: while ($ts < $limit) {
293: $ts = strtotime('+1 '.$interval, $ts);
294: }
295: return $ts;
296: }
297:
298: private function collect_repeat_events($ts, $interval, $limit) {
299: $times = array();
300: while ($ts < $limit) {
301: $times[] = $ts;
302: $ts = strtotime('+1 '.$interval, $ts);
303: }
304: return $times;
305: }
306:
307: private function check_repeat($start, $end, $ts) {
308: $times = array();
309: if ($this->get('repeat_interval')) {
310: if (strtotime('+1 '.$this->get('repeat_interval'), $ts)) {
311: $ts = $this->fast_forward_repeat_event($ts, $this->get('repeat_interval'), $start);
312: if ($ts < $end) {
313: $times = array_merge($times, $this->collect_repeat_events($ts, $this->get('repeat_interval'), $end));
314: }
315: }
316: }
317: return $times;
318: }
319:
320: public function in_date_range($start_date, $end_date) {
321: $start = strtotime($start_date);
322: $end = strtotime($end_date);
323: $times = array();
324: $results = array();
325: if ($start && $end) {
326: $ts = $this->check_start_date($start, $end);
327: if ($ts) {
328: if ($ts > $start && $ts < $end) {
329: $times[] = $ts;
330: }
331: $times = array_merge($times, $this->check_repeat($start, $end, $ts));
332: }
333: }
334: $times = array_unique($times);
335: return $this->fill_event_details($times);
336: }
337:
338: private function fill_event_details($times) {
339: $results = array();
340: foreach ($times as $time) {
341: $result_item = array();
342: $day = date('Y-m-d', $time);
343: foreach ($this->data as $name => $val) {
344: $result_item[$name] = $val;
345: }
346: $result_item['ts'] = $time;
347: $result_item['date'] = date('Y-m-d', $time);
348: $results[] = $result_item;
349: }
350: return $results;
351: }
352: }
353:
354: /**
355: * @subpackage calendar/lib
356: */
357: class Hm_Cal_Data {
358:
359: private $ts = false;
360: private $today = false;
361: private $day_pos = false;
362: private $month_pos = false;
363:
364: public function __construct() {
365: $this->today = time();
366: }
367:
368: private function check_input_params($str_time, $format) {
369: $this->ts = strtotime($str_time);
370: if ($this->ts === false || $this->ts === -1) {
371: die('Invalid date input');
372: }
373: $this->set_start_ts($format);
374: }
375:
376: public function output($str_time, $format='month') {
377: $this->check_input_params($str_time, $format);
378: if (method_exists($this, $format)) {
379: return $this->$format();
380: }
381: }
382:
383: private function set_start_ts($format) {
384: if (method_exists($this, 'start_'.$format)) {
385: $this->{'start_'.$format}($this->ts);
386: }
387: else {
388: die('Invalid output format');
389: }
390: }
391:
392: private function start_week($ts) {
393: $offset = date('w', $ts);
394: $this->day_pos = strtotime(sprintf('-%d day', $offset), $ts);
395: }
396:
397: private function start_month($ts) {
398: $first_day = strtotime(date('Y-m-01', $ts));
399: $this->month_pos = (int) date('n', $ts);
400: $offset = date('w', $first_day);
401: $this->day_pos = strtotime(sprintf('-%d day', $offset), $first_day);
402: }
403:
404: private function start_year($ts) {
405: $first_day = strtotime(date('Y-01-01', $ts));
406: $offset = date('w', $first_day);
407: $this->day_pos = strtotime(sprintf('-%d day', $offset), $first_day);
408: $this->month_pos = 1;
409: }
410:
411: private function increment_day() {
412: $this->day_pos = strtotime('+1 day', $this->day_pos);
413: }
414:
415: private function week() {
416: $res = array();
417: for ($i = 0; $i < 7; $i++) {
418: $res[] = $this->day();
419: }
420: return $res;
421: }
422:
423: private function day() {
424: $day = date('Y-m-d', $this->day_pos);
425: $this->increment_day();
426: return $day;
427: }
428:
429: private function month() {
430: $res = array();
431: for ($i = 0; $i < 6; $i++) {
432: $res[] = $this->week();
433: if ((int) date('n', $this->day_pos) !== $this->month_pos) {
434: break;
435: }
436: }
437: return $res;
438: }
439:
440: private function year() {
441: $res = array();
442: for ($i = 0; $i < 12; $i++) {
443: $res[$this->month_pos] = $this->month();
444: $this->start_month($this->day_pos);
445: }
446: return $res;
447: }
448: }
449: