ILIAS  trunk Revision v11.0_alpha-3011-gc6b235a2e85
class.ilCalendarSchedule.php
Go to the documentation of this file.
1<?php
2
19declare(strict_types=1);
20
29{
30 public const TYPE_DAY = 1;
31 public const TYPE_WEEK = 2;
32 public const TYPE_MONTH = 3;
33 public const TYPE_INBOX = 4;
34 public const TYPE_HALF_YEAR = 6;
35
36 // @deprecated
37 public const TYPE_PD_UPCOMING = 5;
38
39 protected int $limit_events = -1;
40 protected array $schedule = array();
41 protected string $timezone = ilTimeZone::UTC;
42 protected int $weekstart;
43 protected int $type = 0;
44
45 protected bool $subitems_enabled = false;
46
47 protected ?ilDate $start = null;
48 protected ?ilDate $end = null;
49 protected ilObjUser $user;
51 protected ?ilDBInterface $db;
52 protected array $filters = array();
53
57 protected bool $strict_period = true;
58 protected ilLogger $logger;
59
60 public function __construct(ilDate $seed, int $a_type, ?int $a_user_id = null, bool $a_strict_period = false)
61 {
62 global $DIC;
63
64 $this->logger = $DIC->logger()->cal();
65 $this->db = $DIC->database();
66 $this->type = $a_type;
67
68 //this strict period is just to avoid possible side effects.
69 //I there are none, we can get rid of this strict period control and remove it from the constructor
70 //and from the calls in ilCalendarView getEvents.
71 $this->strict_period = $a_strict_period;
72
73 $this->user = $DIC->user();
74 if ($a_user_id !== null && $a_user_id !== $DIC->user()->getId()) {
75 $this->user = new ilObjUser($a_user_id);
76 }
77 $this->user_settings = ilCalendarUserSettings::_getInstanceByUserId($this->user->getId());
78 $this->weekstart = $this->user_settings->getWeekStart();
79 if ($this->user->getTimeZone()) {
80 $this->timezone = $this->user->getTimeZone();
81 }
82 $this->initPeriod($seed);
83
84 // portfolio does custom filter handling (booking group ids)
86 // consultation hour calendar views do not mind calendar category visibility
88 // this is the "default" filter which handles currently hidden categories for the user
89 $this->addFilter(new ilCalendarScheduleFilterHidden($this->user->getId()));
90 } else {
91 // handle booking visibility (target object, booked out)
92 //this filter deals with consultation hours
93 $this->addFilter(new ilCalendarScheduleFilterBookings($this->user->getId()));
94 }
95
97 //this filter deals with booking pool reservations
98 $this->addFilter(new ilCalendarScheduleFilterBookingPool($this->user->getId()));
99 }
100
103 }
104
105 $this->addFilter(new ilCalendarScheduleFilterExercise($this->user->getId()));
106 $this->addFilter(new ilCalendarScheduleFilterTimings($this->user->getId()));
107 }
108 }
109
113 protected function areEventsLimited(): bool
114 {
115 return $this->limit_events != -1;
116 }
117
118 public function getEventsLimit(): int
119 {
120 return $this->limit_events;
121 }
122
123 public function setEventsLimit(int $a_limit): void
124 {
125 $this->limit_events = $a_limit;
126 }
127
128 public function addSubitemCalendars(bool $a_status): void
129 {
130 $this->subitems_enabled = $a_status;
131 }
132
133 public function enabledSubitemCalendars(): bool
134 {
136 }
137
138 public function addFilter(ilCalendarScheduleFilter $a_filter): void
139 {
140 $this->filters[] = $a_filter;
141 }
142
143 public function getByDay(ilDate $a_start, string $a_timezone): array
144 {
145 $start = new ilDateTime($a_start->get(IL_CAL_DATETIME), IL_CAL_DATETIME, $this->timezone);
146 $fstart = new ilDate($a_start->get(IL_CAL_UNIX), IL_CAL_UNIX);
147 $fend = clone $fstart;
148
149 $f_unix_start = $fstart->get(IL_CAL_UNIX);
150 $fend->increment(ilDateTime::DAY, 1);
151 $f_unix_end = $fend->get(IL_CAL_UNIX);
152
153 $unix_start = $start->get(IL_CAL_UNIX);
155 $unix_end = $start->get(IL_CAL_UNIX);
156
157 $counter = 0;
158
159 $tmp_date = new ilDateTime($unix_start, IL_CAL_UNIX, $this->timezone);
160 $tmp_schedule = array();
161 $tmp_schedule_fullday = array();
162 foreach ($this->schedule as $schedule) {
163 if ($schedule['fullday']) {
164 if (($f_unix_start == $schedule['dstart']) or
165 $f_unix_start == $schedule['dend'] or
166 ($f_unix_start > $schedule['dstart'] and $f_unix_end <= $schedule['dend'])) {
167 $tmp_schedule_fullday[] = $schedule;
168 }
169 } elseif (($schedule['dstart'] == $unix_start) or
170 (($schedule['dstart'] <= $unix_start) and ($schedule['dend'] > $unix_start)) or
171 (($schedule['dstart'] >= $unix_start) and ($schedule['dstart'] < $unix_end))) {
172 $tmp_schedule[] = $schedule;
173 }
174 }
175
176 //order non full day events by starting date;
177 usort($tmp_schedule, function ($a, $b) {
178 return $a['dstart'] <=> $b['dstart'];
179 });
180
181 //merge both arrays keeping the full day events first and then rest ordered by starting date.
182 return array_merge($tmp_schedule_fullday, $tmp_schedule);
183 }
184
185 public function calculate(): void
186 {
187 $events = $this->getEvents();
188
189 // we need category type for booking handling
190 $ids = array();
191 foreach ($events as $event) {
192 $ids[] = $event->getEntryId();
193 }
194
196 $cat_types = array();
197 foreach (array_unique($cat_map) as $cat_id) {
198 $cat = new ilCalendarCategory($cat_id);
199 $cat_types[$cat_id] = $cat->getType();
200 }
201
202 $counter = 0;
203 foreach ($events as $event) {
204 // Calculdate recurring events
205 if ($recs = ilCalendarRecurrences::_getRecurrences($event->getEntryId())) {
206 $duration = $event->getEnd()->get(IL_CAL_UNIX) - $event->getStart()->get(IL_CAL_UNIX);
207 foreach ($recs as $rec) {
208 $calc = new ilCalendarRecurrenceCalculator($event, $rec);
209 foreach ($calc->calculateDateList($this->start, $this->end)->get() as $rec_date) {
210 if ($this->type == self::TYPE_PD_UPCOMING &&
211 $rec_date->get(IL_CAL_UNIX) < time()) {
212 continue;
213 }
214
215 $this->schedule[$counter]['event'] = $event;
216 $this->schedule[$counter]['dstart'] = $rec_date->get(IL_CAL_UNIX);
217 $this->schedule[$counter]['dend'] = $this->schedule[$counter]['dstart'] + $duration;
218 $this->schedule[$counter]['fullday'] = $event->isFullday();
219 $this->schedule[$counter]['category_id'] = $cat_map[$event->getEntryId()];
220 $this->schedule[$counter]['category_type'] = $cat_types[$cat_map[$event->getEntryId()]];
221
222 switch ($this->type) {
223 case self::TYPE_DAY:
224 case self::TYPE_WEEK:
225 // store date info (used for calculation of overlapping events)
226 $tmp_date = new ilDateTime(
227 $this->schedule[$counter]['dstart'],
229 $this->timezone
230 );
231 $this->schedule[$counter]['start_info'] = $tmp_date->get(
233 '',
234 $this->timezone
235 );
236
237 $tmp_date = new ilDateTime(
238 $this->schedule[$counter]['dend'],
240 $this->timezone
241 );
242 $this->schedule[$counter]['end_info'] = $tmp_date->get(
244 '',
245 $this->timezone
246 );
247 break;
248
249 default:
250 break;
251 }
252 $counter++;
253 if ($this->type != self::TYPE_PD_UPCOMING &&
254 $this->areEventsLimited() && $counter >= $this->getEventsLimit()) {
255 break;
256 }
257 }
258 }
259 } else {
260 $this->schedule[$counter]['event'] = $event;
261 $this->schedule[$counter]['dstart'] = $event->getStart()->get(IL_CAL_UNIX);
262 $this->schedule[$counter]['dend'] = $event->getEnd()->get(IL_CAL_UNIX);
263 $this->schedule[$counter]['fullday'] = $event->isFullday();
264 $this->schedule[$counter]['category_id'] = $cat_map[$event->getEntryId()];
265 $this->schedule[$counter]['category_type'] = $cat_types[$cat_map[$event->getEntryId()]];
266
267 if (!$event->isFullday()) {
268 switch ($this->type) {
269 case self::TYPE_DAY:
270 case self::TYPE_WEEK:
271 // store date info (used for calculation of overlapping events)
272 $tmp_date = new ilDateTime(
273 $this->schedule[$counter]['dstart'],
275 $this->timezone
276 );
277 $this->schedule[$counter]['start_info'] = $tmp_date->get(
279 '',
280 $this->timezone
281 );
282
283 $tmp_date = new ilDateTime($this->schedule[$counter]['dend'], IL_CAL_UNIX, $this->timezone);
284 $this->schedule[$counter]['end_info'] = $tmp_date->get(
286 '',
287 $this->timezone
288 );
289 break;
290
291 default:
292 break;
293 }
294 }
295 $counter++;
296 if ($this->type != self::TYPE_PD_UPCOMING &&
297 $this->areEventsLimited() && $counter >= $this->getEventsLimit()) {
298 break;
299 }
300 }
301 }
302
303 if ($this->type == self::TYPE_PD_UPCOMING) {
304 $this->schedule = ilArrayUtil::sortArray($this->schedule, "dstart", "asc", true);
305 if ($this->areEventsLimited() && sizeof($this->schedule) >= $this->getEventsLimit()) {
306 $this->schedule = array_slice($this->schedule, 0, $this->getEventsLimit());
307 }
308 }
309 }
310
311 public function getScheduledEvents(): array
312 {
313 return $this->schedule;
314 }
315
316 protected function filterCategories(array $a_cats): array
317 {
318 if (!count($a_cats)) {
319 return $a_cats;
320 }
321 foreach ($this->filters as $filter) {
322 $a_cats = $filter->filterCategories($a_cats);
323 }
324 return $a_cats;
325 }
326
328 {
329 foreach ($this->filters as $filter) {
330 $res = $filter->modifyEvent($event);
331 if ($res === false) {
332 $this->logger->notice('filtering failed for ' . get_class($filter));
333 return null;
334 }
335 if (is_null($res)) { // see #35241
336 return null;
337 }
338 $event = $res;
339 }
340 return $event;
341 }
342
343 protected function addCustomEvents(ilDate $start, ilDate $end, array $categories): array
344 {
345 $new_events = array();
346 foreach ($this->filters as $filter) {
347 $events_by_filter = $filter->addCustomEvents($start, $end, $categories);
348 if ($events_by_filter) {
349 $new_events = array_merge($new_events, $events_by_filter);
350 }
351 }
352 return $new_events;
353 }
354
362 public function getChangedEvents(bool $a_include_subitem_calendars = false): array
363 {
364 $cats = ilCalendarCategories::_getInstance($this->user->getId())->getCategories($a_include_subitem_calendars);
365 $cats = $this->filterCategories($cats);
366
367 if (!count($cats)) {
368 return array();
369 }
370
371 $start = new ilDate(date('Y-m-d', time()), IL_CAL_DATE);
373
374 $query = "SELECT ce.cal_id cal_id FROM cal_entries ce " .
375 "JOIN cal_cat_assignments ca ON ca.cal_id = ce.cal_id " .
376 "WHERE last_update > " . $this->db->quote($start->get(IL_CAL_DATETIME), 'timestamp') . " " .
377 "AND " . $this->db->in('ca.cat_id', $cats, false, 'integer') . ' ' .
378 "ORDER BY last_update";
379 $res = $this->db->query($query);
380
381 $events = [];
382 while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) {
383 $event = new ilCalendarEntry($row->cal_id);
384 $valid_event = $this->modifyEventByFilters($event);
385 if ($valid_event) {
386 $events[] = $valid_event;
387 }
388 }
389
390 foreach ($this->addCustomEvents($this->start, $this->end, $cats) as $event) {
391 $events[] = $event;
392 }
393
394 return $events;
395 }
396
400 public function getEvents(): array
401 {
402 $cats = ilCalendarCategories::_getInstance($this->user->getId())->getCategories($this->enabledSubitemCalendars());
403 $cats = $this->filterCategories($cats);
404
405 if (!count($cats)) {
406 return array();
407 }
408
409 // TODO: optimize
410 $query = "SELECT ce.cal_id cal_id" .
411 " FROM cal_entries ce" .
412 " LEFT JOIN cal_recurrence_rules crr ON (ce.cal_id = crr.cal_id)" .
413 " JOIN cal_cat_assignments ca ON (ca.cal_id = ce.cal_id)";
414
415 if ($this->type != self::TYPE_INBOX) {
416 $query .= " WHERE ((starta <= " . $this->db->quote(
417 $this->end->get(IL_CAL_DATETIME, '', 'UTC'),
418 'timestamp'
419 ) .
420 " AND enda >= " . $this->db->quote($this->start->get(IL_CAL_DATETIME, '', 'UTC'), 'timestamp') . ")" .
421 " OR (starta <= " . $this->db->quote($this->end->get(IL_CAL_DATETIME, '', 'UTC'), 'timestamp') .
422 " AND NOT rule_id IS NULL))";
423 } else {
424 $date = new ilDateTime(mktime(0, 0, 0), IL_CAL_UNIX);
425 $query .= " WHERE starta >= " . $this->db->quote($date->get(IL_CAL_DATETIME, '', 'UTC'), 'timestamp');
426 }
427
428 $query .= " AND " . $this->db->in('ca.cat_id', $cats, false, 'integer') .
429 " ORDER BY starta";
430
431 $res = $this->db->query($query);
432
433 $events = [];
434 while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) {
435 $event = new ilCalendarEntry((int) $row->cal_id);
436 $valid_event = $this->modifyEventByFilters($event);
437 if ($valid_event) {
438 $events[] = $valid_event;
439 }
440 }
441 foreach ($this->addCustomEvents($this->start, $this->end, $cats) as $event) {
442 $events[] = $event;
443 }
444 return $events;
445 }
446
447 protected function initPeriod(ilDate $seed): void
448 {
449 switch ($this->type) {
450 case self::TYPE_DAY:
451 $this->start = clone $seed;
452 $this->end = clone $seed;
453 //this strict period is just to avoid possible side effects.
454 if (!$this->strict_period) {
455 $this->start->increment(IL_CAL_DAY, -2);
456 $this->end->increment(IL_CAL_DAY, 2);
457 } else {
458 $this->end->increment(IL_CAL_DAY, 1);
459 $this->end->increment(IL_CAL_SECOND, -1);
460 }
461 break;
462
463 case self::TYPE_WEEK:
464 $this->start = clone $seed;
465 $start_info = $this->start->get(IL_CAL_FKT_GETDATE, '', 'UTC');
466 $day_diff = $this->weekstart - $start_info['isoday'];
467
468 if (abs($day_diff) === 7) {
469 $day_diff = 0;
470 }
471
472 //this strict period is just to avoid possible side effects.
473 if ($this->strict_period) {
474 $this->start->increment(IL_CAL_DAY, $day_diff);
475 $this->end = clone $this->start;
476 $this->end->increment(IL_CAL_WEEK); #22173
477 } else {
478 $this->start->increment(IL_CAL_DAY, $day_diff);
479 $this->start->increment(IL_CAL_DAY, -1);
480 $this->end = clone $this->start;
481 $this->end->increment(IL_CAL_DAY, 9);
482 }
483 break;
484
485 case self::TYPE_MONTH:
486 if ($this->strict_period) {
487 $this->start = clone $seed;
488 $this->end = clone $seed;
489 $this->end->increment(IL_CAL_MONTH, 1);
490 } else {
491 $year_month = $seed->get(IL_CAL_FKT_DATE, 'Y-m', 'UTC');
492 list($year, $month) = explode('-', $year_month);
493 $year = (int) $year;
494 $month = (int) $month;
495
496 #21716
497 $this->start = new ilDate($year_month . '-01', IL_CAL_DATE);
498
499 $start_unix_time = $this->start->getUnixTime();
500
501 $start_day_of_week = (int) date('w', $start_unix_time);
502
503 $number_days_previous_month = 0;
504
505 if ($start_day_of_week === 0 && $this->weekstart === ilCalendarSettings::WEEK_START_MONDAY) {
506 $number_days_previous_month = 6;
507 } elseif ($start_day_of_week > 0) {
508 $number_days_previous_month = $start_day_of_week;
509
510 if ($this->weekstart === ilCalendarSettings::WEEK_START_MONDAY) {
511 $number_days_previous_month = $start_day_of_week - 1;
512 }
513 }
514
515 $this->start->increment(IL_CAL_DAY, -$number_days_previous_month);
516
517 #21716
518 $this->end = new ilDate(
519 $year_month . '-' . ilCalendarUtil::_getMaxDayOfMonth($year, $month),
521 );
522
523 $end_unix_time = $this->end->getUnixTime();
524
525 $end_day_of_week = (int) date('w', $end_unix_time);
526
527 if ($end_day_of_week > 0) {
528 $number_days_next_month = 7 - $end_day_of_week;
529
530 if ($this->weekstart == ilCalendarSettings::WEEK_START_SUNDAY) {
531 $number_days_next_month = $number_days_next_month - 1;
532 }
533
534 $this->end->increment(IL_CAL_DAY, $number_days_next_month);
535 }
536 }
537
538 break;
539
541 $this->start = clone $seed;
542 $this->end = clone $this->start;
543 $this->end->increment(IL_CAL_MONTH, 6);
544 break;
545
547 case self::TYPE_INBOX:
548 $this->start = $seed;
549 $this->end = clone $this->start;
550 $this->end->increment(IL_CAL_MONTH, 12);
551 break;
552 }
553 }
554
555 public function setPeriod(ilDate $a_start, ilDate $a_end): void
556 {
557 $this->start = $a_start;
558 $this->end = $a_end;
559 }
560}
$duration
const IL_CAL_FKT_GETDATE
const IL_CAL_DATE
const IL_CAL_WEEK
const IL_CAL_UNIX
const IL_CAL_DATETIME
const IL_CAL_MONTH
const IL_CAL_FKT_DATE
const IL_CAL_DAY
const IL_CAL_SECOND
static sortArray(array $array, string $a_array_sortby_key, string $a_array_sortorder="asc", bool $a_numeric=false, bool $a_keep_keys=false)
static _getInstance($a_usr_id=0)
get singleton instance
Stores calendar categories.
Model for a calendar entry.
Calculates an ilDateList for a given calendar entry and recurrence rule.
static _getRecurrences(int $a_cal_id)
get all recurrences of an appointment
Calendar schedule filter for booking pool reservations.
Calendar schedule filter for consultation hour bookings.
Calendar schedule filter for hidden categories.
Calendar schedule filter for individual timings.
Represents a list of calendar appointments (including recurring events) for a specific user in a give...
areEventsLimited()
Check if events are limited.
getByDay(ilDate $a_start, string $a_timezone)
modifyEventByFilters(ilCalendarEntry $event)
ilCalendarUserSettings $user_settings
getEvents()
Read events (will be moved to another class, since only active and/or visible calendars are shown)
addCustomEvents(ilDate $start, ilDate $end, array $categories)
addFilter(ilCalendarScheduleFilter $a_filter)
setPeriod(ilDate $a_start, ilDate $a_end)
__construct(ilDate $seed, int $a_type, ?int $a_user_id=null, bool $a_strict_period=false)
getChangedEvents(bool $a_include_subitem_calendars=false)
get new/changed events
addSubitemCalendars(bool $a_status)
static _getInstanceByUserId(int $a_user_id)
static _getMaxDayOfMonth(int $a_year, int $a_month)
get max day of month 2008,2 => 29
@classDescription Date and time handling
increment(string $a_type, int $a_count=1)
Class for single dates.
get(int $a_format, string $a_format_str='', string $a_tz='')
get formatted date
Component logger with individual log levels by component id.
User class.
Calendar schedule filter interface.
Interface ilDBInterface.
$res
Definition: ltiservices.php:69
$a
thx to https://mlocati.github.io/php-cs-fixer-configurator for the examples
global $DIC
Definition: shib_login.php:26
$counter