ILIAS  trunk Revision v11.0_alpha-3011-gc6b235a2e85
class.ilICalParser.php
Go to the documentation of this file.
1<?php
2
19declare(strict_types=1);
20
26{
27 public const INPUT_STRING = 1;
28 public const INPUT_FILE = 2;
29
30 protected ilLogger $log;
31
32 protected ?ilCalendarCategory $category = null;
33
34 protected string $ical = '';
35 protected string $file = '';
36 protected ?ilTimeZone $default_timezone = null;
37 protected array $container = array();
38
39 public function __construct(string $a_ical, int $a_type)
40 {
41 global $DIC;
42 if ($a_type == self::INPUT_STRING) {
43 $this->ical = $a_ical;
44 } elseif ($a_type == self::INPUT_FILE) {
45 $this->file = $a_ical;
46 $this->ical = file_get_contents($a_ical);
47
48 if (!strlen($this->ical)) {
49 throw new ilICalParserException('Cannot parse empty ical file: ' . $a_ical);
50 }
51 }
52 $this->log = $DIC->logger()->cal();
53 $this->default_timezone = ilTimeZone::_getInstance();
54 }
55
56 public function setCategoryId(int $a_id): void
57 {
58 $this->category = new ilCalendarCategory($a_id);
59 }
60
61 public function parse(): void
62 {
63 $lines = $this->tokenize($this->ical, ilICalUtils::ICAL_EOL);
64 if (count($lines) == 1) {
65 $lines = $this->tokenize($this->ical, ilICalUtils::ICAL_EOL_FB);
66 }
67 for ($i = 0; $i < count($lines); $i++) {
68 $line = $lines[$i];
69
70 // Check for next multilines (they start with a space)
71 $offset = 1;
72 while (
73 isset($lines[$i + $offset]) &&
74 (
75 (strpos($lines[$i + $offset], ilICalUtils::ICAL_SPACE) === 0) ||
76 (strpos($lines[$i + $offset], ilICalUtils::ICAL_TAB) === 0)
77 )
78 ) {
79 $lines[$i + $offset] = str_replace(ilICalUtils::ICAL_EOL, '', $lines[$i + $offset]);
80 $line = $line . substr($lines[$i + $offset], 1);
81 $offset++;
82 }
83 $i += ($offset - 1);
84
85 // Parse this line
86 $this->parseLine($line);
87 }
88 }
89
90 protected function getContainer(): ?ilICalItem
91 {
92 if (count($this->container)) {
93 return $this->container[count($this->container) - 1];
94 }
95 return null;
96 }
97
101 protected function setContainer(ilICalItem $a_container): void
102 {
103 $this->container = array($a_container);
104 }
105
106 protected function dropContainer(): ?ilICalItem
107 {
108 if (is_array($this->container)) {
109 return array_pop($this->container);
110 }
111 return null;
112 }
113
114 protected function pushContainer(ilICalItem $a_container): void
115 {
116 $this->container[] = $a_container;
117 }
118
119 protected function parseLine(string $line): void
120 {
121 switch (trim($line)) {
122 case 'BEGIN:VCALENDAR':
123 $this->log->debug('BEGIN VCALENDAR');
124 $this->setContainer(new ilICalComponent('VCALENDAR'));
125 break;
126
127 case 'END:VCALENDAR':
128 $this->log->debug('END VCALENDAR');
129 break;
130
131 case 'BEGIN:VEVENT':
132 $this->log->debug('BEGIN VEVENT');
133 $this->pushContainer(new ilICalComponent('VEVENT'));
134 break;
135
136 case 'END:VEVENT':
137 $this->log->debug('END VEVENT');
138 $this->writeEvent();
139 $this->dropContainer();
140 break;
141
142 case 'BEGIN:VTIMEZONE':
143 $this->log->debug('BEGIN VTIMEZONE');
144 $container = new ilICalComponent('VTIMEZONE');
145 $this->pushContainer($container);
146 break;
147
148 case 'END:VTIMEZONE':
149 $this->log->debug('END VTIMEZONE');
150 if ($tzid = $this->getContainer()->getItemsByName('TZID')) {
151 $this->default_timezone = $this->getTZ($tzid[0]->getValue());
152 }
153 $this->dropContainer();
154 break;
155
156 default:
157 if (strpos(trim($line), 'BEGIN') === 0) {
158 $this->log->info('Do not handling line:' . $line);
159 break;
160 }
161 if (strpos(trim($line), 'X-WR-TIMEZONE') === 0) {
162 list($param, $value) = $this->splitLine($line);
163 $this->default_timezone = $this->getTZ($value);
164 } else {
165 list($params, $values) = $this->splitLine($line);
166 $this->storeItems($params, $values);
167 }
168 break;
169 }
170 }
171
172 protected function storeItems(string $a_param_part, string $a_value_part): void
173 {
174 // Check for a semicolon in param part and split it.
175 $items = array();
176 if ($splitted_param = explode(';', $a_param_part)) {
177 $counter = 0;
178 foreach ($splitted_param as $param) {
179 if (!$counter) {
180 $items[$counter]['param'] = $param;
181 $items[$counter]['value'] = $a_value_part;
182 } elseif ($splitted_param_values = explode('=', $param)) {
183 $items[$counter]['param'] = $splitted_param_values[0];
184 $items[$counter]['value'] = $splitted_param_values[1];
185 }
186 ++$counter;
187 }
188 }
189 // Split value part
190 $substituted_values = str_replace('\;', '', $a_value_part);
191
192 $values = array();
193 if ($splitted_values = explode(';', $substituted_values)) {
194 $counter = 0;
195 foreach ($splitted_values as $value) {
196 // Split by '='
197 $splitted_value_values = explode('=', $value);
198 if (is_array($splitted_value_values) && count($splitted_value_values) >= 2) {
199 $values[$counter]['param'] = $splitted_value_values[0];
200 $values[$counter]['value'] = $splitted_value_values[1];
201 }
202 ++$counter;
203 }
204 }
205
206 // Return if there are no values
207 if (!count($items)) {
208 $this->log->write(__METHOD__ . ': Cannot parse parameter: ' . $a_param_part . ', value: ' . $a_value_part);
209 return;
210 }
211
212 $counter = 0;
213 foreach ($items as $item) {
214 if (!$counter) {
215 // First is ical-Parameter
216 $parameter = new ilICalProperty($item['param'], $item['value']);
217
218 if (!$this->getContainer() instanceof ilICalItem) {
219 continue;
220 }
221
222 $this->getContainer()->addItem($parameter);
223 $this->pushContainer($parameter);
224
225 if (count($values) > 1) {
226 foreach ($values as $value) {
227 $value = new ilICalValue($value['param'], $value['value']);
228 $this->getContainer()->addItem($value);
229 }
230 }
231 } else {
232 $value = new ilICalParameter($item['param'], $item['value']);
233 $this->getContainer()->addItem($value);
234 }
235 ++$counter;
236 }
237 $this->dropContainer();
238 }
239
240 protected function splitLine(string $a_line): array
241 {
242 $matches = array();
243
244 if (preg_match('/([^:]+):(.*)/', $a_line, $matches)) {
245 return array($matches[1], $matches[2]);
246 } else {
247 $this->log->notice(' Found invalid parameter: ' . $a_line);
248 }
249 return array('', '');
250 }
251
252 protected function tokenize(string $a_string, string $a_tokenizer): array
253 {
254 return explode($a_tokenizer, $a_string);
255 }
256
257 protected function getTZ(string $a_timezone): ilTimeZone
258 {
259 $parts = explode('/', $a_timezone);
260 $tz = array_pop($parts);
261 $continent = array_pop($parts);
262 if (isset($continent) and $continent) {
263 $timezone = $continent . '/' . $tz;
264 } else {
265 $timezone = $a_timezone;
266 }
267 try {
268 if ($this->default_timezone->getIdentifier() == $timezone) {
270 } else {
271 $this->log->info(': Found new timezone: ' . $timezone);
272 return ilTimeZone::_getInstance(trim($timezone));
273 }
274 } catch (ilTimeZoneException $e) {
275 $this->log->notice(': Found invalid timezone: ' . $timezone);
277 }
278 }
279
280 protected function switchTZ(ilTimeZone $timezone): void
281 {
282 try {
283 $timezone->switchTZ();
284 } catch (ilTimeZoneException $e) {
285 $this->log->notice(': Found invalid timezone: ' . $timezone->getIdentifier());
286 }
287 }
288
289 protected function restoreTZ(): void
290 {
291 $this->default_timezone->restoreTZ();
292 }
293
294 protected function writeEvent(): void
295 {
296 $entry = new ilCalendarEntry();
297
298 // Search for summary
299 foreach ($this->getContainer()->getItemsByName('SUMMARY', false) as $item) {
300 if (is_a($item, 'ilICalProperty')) {
301 $entry->setTitle($this->purgeString($item->getValue()));
302 break;
303 }
304 }
305 // Search description
306 foreach ($this->getContainer()->getItemsByName('DESCRIPTION', false) as $item) {
307 if (is_a($item, 'ilICalProperty')) {
308 $entry->setDescription($this->purgeString($item->getValue()));
309 break;
310 }
311 }
312
313 // Search location
314 foreach ($this->getContainer()->getItemsByName('LOCATION', false) as $item) {
315 if (is_a($item, 'ilICalProperty')) {
316 $entry->setLocation($this->purgeString($item->getValue()));
317 break;
318 }
319 }
320
321 foreach ($this->getContainer()->getItemsByName('DTSTART') as $start) {
322 $fullday = false;
323 foreach ($start->getItemsByName('VALUE') as $type) {
324 if ($type->getValue() == 'DATE') {
325 $fullday = true;
326 }
327 }
328 $start_tz = $this->default_timezone;
329 foreach ($start->getItemsByName('TZID') as $param) {
330 $start_tz = $this->getTZ($param->getValue());
331 }
332 if ($fullday) {
333 $start = new ilDate(
334 $start->getValue(),
336 );
337 } else {
338 $start = new ilDateTime(
339 $start->getValue(),
341 $start_tz->getIdentifier()
342 );
343 }
344 $entry->setStart($start);
345 $entry->setFullday($fullday);
346 }
347
348 foreach ($this->getContainer()->getItemsByName('DTEND') as $end) {
349 $fullday = false;
350 foreach ($end->getItemsByName('VALUE') as $type) {
351 if ($type->getValue() == 'DATE') {
352 $fullday = true;
353 }
354 }
355 $end_tz = $this->default_timezone;
356 foreach ($end->getItemsByName('TZID') as $param) {
357 $end_tz = $this->getTZ($param->getValue());
358 }
359 if ($fullday) {
360 $end = new ilDate(
361 $end->getValue(),
363 );
364 $end->increment(IL_CAL_DAY, -1);
365 } else {
366 $end = new ilDateTime(
367 $end->getValue(),
369 $end_tz->getIdentifier()
370 );
371 }
372 $entry->setEnd($end);
373 $entry->setFullday($fullday);
374 }
375
376 if (!$entry->getStart() instanceof ilDateTime) {
377 $this->log->warning('Cannot find start date. Event ignored.');
378 return;
379 }
380
381 // check if end date is given otherwise replace with start
382 if (
383 !$entry->getEnd() instanceof ilDateTime &&
384 $entry->getStart() instanceof ilDateTime
385 ) {
386 $entry->setEnd($entry->getStart());
387 }
388
389 // save calendar event
390 if ($this->category->getLocationType() == ilCalendarCategory::LTYPE_REMOTE) {
391 $entry->setAutoGenerated(true);
392 }
393 $entry->save();
394
395 // Search exclusions
396 // Only possible after entry is saved, otherwise the id is not available
397 foreach ($this->getContainer()->getItemsByName('EXDATE', false) as $item) {
398 if (is_a($item, 'ilICalProperty')) {
399 $rec_exclusion = new ilCalendarRecurrenceExclusion();
400 $rec_exclusion->setEntryId($entry->getEntryId());
401 $rec_exclusion->setDate(new ilDate($item->getValue(), IL_CAL_DATE));
402 $rec_exclusion->save();
403 }
404 }
405
406 $ass = new ilCalendarCategoryAssignments($entry->getEntryId());
407 $ass->addAssignment($this->category->getCategoryID());
408
409 // Recurrences
410 foreach ($this->getContainer()->getItemsByName('RRULE') as $recurrence) {
411 $rec = new ilCalendarRecurrence();
412 $rec->setEntryId($entry->getEntryId());
413
414 foreach ($recurrence->getItemsByName('FREQ') as $freq) {
415 switch ($freq->getValue()) {
416 case 'DAILY':
417 case 'WEEKLY':
418 case 'MONTHLY':
419 case 'YEARLY':
420 $rec->setFrequenceType((string) $freq->getValue());
421 break;
422
423 default:
424 $this->log->notice(': Cannot handle recurring event of type: ' . $freq->getValue());
425 break 3;
426 }
427 }
428
429 foreach ($recurrence->getItemsByName('COUNT') as $value) {
430 $rec->setFrequenceUntilCount((int) $value->getValue());
431 break;
432 }
433 foreach ($recurrence->getItemsByName('UNTIL') as $until) {
434 $rec->setFrequenceUntilDate(new ilDate($until->getValue(), IL_CAL_DATE));
435 break;
436 }
437 foreach ($recurrence->getItemsByName('INTERVAL') as $value) {
438 $rec->setInterval((int) $value->getValue());
439 break;
440 }
441 foreach ($recurrence->getItemsByName('BYDAY') as $value) {
442 $rec->setBYDAY((string) $value->getValue());
443 break;
444 }
445 foreach ($recurrence->getItemsByName('BYWEEKNO') as $value) {
446 $rec->setBYWEEKNO((string) $value->getValue());
447 break;
448 }
449 foreach ($recurrence->getItemsByName('BYMONTH') as $value) {
450 $rec->setBYMONTH((string) $value->getValue());
451 break;
452 }
453 foreach ($recurrence->getItemsByName('BYMONTHDAY') as $value) {
454 $rec->setBYMONTHDAY((string) $value->getValue());
455 break;
456 }
457 foreach ($recurrence->getItemsByName('BYYEARDAY') as $value) {
458 $rec->setBYYEARDAY((string) $value->getValue());
459 break;
460 }
461 foreach ($recurrence->getItemsByName('BYSETPOS') as $value) {
462 $rec->setBYSETPOS((string) $value->getValue());
463 break;
464 }
465 foreach ($recurrence->getItemsByName('WKST') as $value) {
466 $rec->setWeekstart((string) $value->getValue());
467 break;
468 }
469 $rec->save();
470 }
471 }
472
473 protected function purgeString(string $a_string): string
474 {
475 $a_string = str_replace("\;", ";", $a_string);
476 $a_string = str_replace("\,", ",", $a_string);
477 $a_string = str_replace("\:", ":", $a_string);
478 return ilUtil::stripSlashes($a_string);
479 }
480}
const IL_CAL_DATE
const IL_CAL_DATETIME
const IL_CAL_DAY
Stores calendar categories.
Model for a calendar entry.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
Model of calendar entry recurrcences based on iCalendar-RFC-5545.
@classDescription Date and time handling
Class for single dates.
Represents a ical component.
Abstract base class for all ical items (Component, Parameter and Value)
This class represents a ical parameter E.g VALUE=DATETIME.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
splitLine(string $a_line)
storeItems(string $a_param_part, string $a_value_part)
tokenize(string $a_string, string $a_tokenizer)
switchTZ(ilTimeZone $timezone)
setCategoryId(int $a_id)
ilCalendarCategory $category
parseLine(string $line)
ilTimeZone $default_timezone
setContainer(ilICalItem $a_container)
purgeString(string $a_string)
__construct(string $a_ical, int $a_type)
pushContainer(ilICalItem $a_container)
getTZ(string $a_timezone)
Represents a ical property.
Used for storage og multiple values E.g RRULE:FREQ=WEEKLY;COUNT=20;INTERVAL=2;BYDAY=TU.
Component logger with individual log levels by component id.
Class for TimeZone exceptions.
This class offers methods for timezone handling.
switchTZ()
Switch timezone to given timezone.
static _getInstance(string $a_tz='')
get instance by timezone
static stripSlashes(string $a_str, bool $a_strip_html=true, string $a_allow="")
if(! $DIC->user() ->getId()||!ilLTIConsumerAccess::hasCustomProviderCreationAccess()) $params
Definition: ltiregstart.php:31
if($clientAssertionType !='urn:ietf:params:oauth:client-assertion-type:jwt-bearer'|| $grantType !='client_credentials') $parts
Definition: ltitoken.php:61
getValue()
Get the value that is displayed in the input client side.
Definition: Group.php:49
global $DIC
Definition: shib_login.php:26
$counter
$param
Definition: xapitoken.php:46