aboutsummaryrefslogtreecommitdiff
path: root/lib/classes/calendar/ICalendarImport.class.php
diff options
context:
space:
mode:
authorPhilipp Schüttlöffel <schuettloeffel@zqs.uni-hannover.de>2024-09-24 10:53:31 +0200
committerPhilipp Schüttlöffel <schuettloeffel@zqs.uni-hannover.de>2024-09-24 10:53:31 +0200
commit4459dd7917f4d1c34f40bb68f0e991e9c3d53e4c (patch)
tree5c07151ae61276d334e88f6309c30d439a85c12e /lib/classes/calendar/ICalendarImport.class.php
parentda0022e5c1abbf9825ae76debaabdff7e8623bb4 (diff)
parent97a188592c679890a25c37ab78463add76a52ff7 (diff)
Merge branch 'main' into issue-3911issue-3911
Diffstat (limited to 'lib/classes/calendar/ICalendarImport.class.php')
-rw-r--r--lib/classes/calendar/ICalendarImport.class.php678
1 files changed, 0 insertions, 678 deletions
diff --git a/lib/classes/calendar/ICalendarImport.class.php b/lib/classes/calendar/ICalendarImport.class.php
deleted file mode 100644
index e78696d..0000000
--- a/lib/classes/calendar/ICalendarImport.class.php
+++ /dev/null
@@ -1,678 +0,0 @@
-<?php
-class ICalendarImport
-{
- private $range_id;
-
- private $count = 0;
-
- private $dates = [];
-
- private $import_time;
-
- private $convert_to_private = false;
-
- public function __construct($range_id)
- {
- $this->range_id = $range_id;
- $this->import_time = time();
- }
-
- public function import($ical_data)
- {
- $this->parse($ical_data);
- }
-
- public function countEvents($ical_data)
- {
- $matches = [];
- if (is_null($this->count)) {
- // Unfold any folded lines
- $data = preg_replace('/\x0D?\x0A[\x20\x09]/', '', $ical_data);
- preg_match_all('/(BEGIN:VEVENT(\r\n|\r|\n)[\W\w]*?END:VEVENT\r?\n?)/', $ical_data, $matches);
- $this->count = sizeof($matches[1]);
- }
-
- return $this->count;
- }
-
- public function getCountEvents() : int
- {
- return (int) $this->count;
- }
-
- public function convertPublicToPrivate(bool $to_private = true) : void
- {
- $this->convert_to_private = $to_private;
- }
-
- /**
- * Parse a string containing vCalendar data.
- *
- * @access private
- * @param string $data The data to parse
- */
- public function parse(string $data)
- {
- // match categories
- $studip_categories = [];
- $i = 1;
- foreach ($GLOBALS['PERS_TERMIN_KAT'] as $cat) {
- $studip_categories[mb_strtolower($cat['name'])] = $i++;
- }
-
- // Unfold any folded lines
- // the CR is optional for files imported from Korganizer (non-standard)
- $data = $this->unfoldLine($data);
-
- if (!preg_match('/BEGIN:VCALENDAR(\r\n|\r|\n)([\W\w]*)END:VCALENDAR\r?\n?/', $data, $matches)) {
- throw new UnexpectedValueException();
- }
-
- // client identifier
- if (!$this->parseClientIdentifier($matches[2])) {
- throw new UnexpectedValueException();
- }
-
- // All sub components
- if (!preg_match_all('/BEGIN:VEVENT(\r\n|\r|\n)([\w\W]*?)END:VEVENT(\r\n|\r|\n)/', $matches[2], $v_events)) {
- // _("Die importierte Datei enthält keine Termine.")
- throw new UnexpectedValueException();
- }
-
- if ($this->count) {
- $this->count = 0;
- }
- foreach ($v_events[2] as $v_event) {
-
- if (preg_match_all('/(.*):(.*)(\r|\n)+/', $v_event, $matches)) {
- $properties = [];
- $check = [];
- foreach ($matches[0] as $property) {
- preg_match('/([^;^:]*)((;[^:]*)?):(.*)/', $property, $parts);
- $tag = $parts[1];
- $value = $parts[4];
- $params = [];
-
- // skip seminar events
- if ((!$this->import_sem) && $tag == 'UID') {
- if (mb_strpos($value, 'Stud.IP-SEM') === 0) {
- continue 2;
- }
- }
-
- if (!empty($parts[2])) {
- preg_match_all('/;(([^;=]*)(=([^;]*))?)/', $parts[2], $param_parts);
- foreach ($param_parts[2] as $key => $param_name)
- $params[mb_strtoupper($param_name)] = mb_strtoupper($param_parts[4][$key]);
-
- if ($params['ENCODING']) {
- switch ($params['ENCODING']) {
- case 'QUOTED-PRINTABLE':
- $value = $this->qp_decode($value);
- break;
-
- case 'BASE64':
- $value = base64_decode($value);
- break;
- }
- }
- }
-
- switch ($tag) {
- // text fields
- case 'DESCRIPTION':
- case 'SUMMARY':
- case 'LOCATION':
- $value = preg_replace('/\\\\,/', ',', $value);
- $value = preg_replace('/\\\\n/', "\n", $value);
- $properties[$tag] = trim($value);
- break;
-
- case 'CATEGORIES':
- $categories = [];
- $properties['STUDIP_CATEGORY'] = null;
- foreach (explode(',', $value) as $category) {
- if (!$studip_categories[mb_strtolower($category)]) {
- $categories[] = $category;
- } else if (!$properties['STUDIP_CATEGORY']) {
- $properties['STUDIP_CATEGORY']
- = $studip_categories[mb_strtolower($category)];
- }
- }
- $properties[$tag] = implode(',', $categories);
- break;
-
- // Date fields
- case 'DCREATED': // vCalendar property name for "CREATED"
- case 'DTSTAMP':
- case 'COMPLETED':
- case 'CREATED':
- case 'LAST-MODIFIED':
- $properties[$tag] = $this->parseDateTime($value);
- break;
-
- case 'DTSTART':
- case 'DTEND':
- // checking for day events
- if ($params['VALUE'] == 'DATE')
- $check['DAY_EVENT'] = true;
- case 'DUE':
- case 'RECURRENCE-ID':
- $properties[$tag] = $this->parseDateTime($value);
- break;
-
- case 'RDATE':
- if (array_key_exists('VALUE', $params)) {
- if ($params['VALUE'] == 'PERIOD') {
- $properties[$tag] = $this->parsePeriod($value);
- } else {
- $properties[$tag] = $this->parseDateTime($value);
- }
- } else {
- $properties[$tag] = $this->parseDateTime($value);
- }
- break;
-
- case 'TRIGGER':
- if (array_key_exists('VALUE', $params)) {
- if ($params['VALUE'] == 'DATE-TIME') {
- $properties[$tag] = $this->parseDateTime($value);
- } else {
- $properties[$tag] = $this->parseDuration($value);
- }
- } else {
- $properties[$tag] = $this->parseDuration($value);
- }
- break;
-
- case 'EXDATE':
- $properties[$tag] = [];
- // comma seperated dates
- $values = [];
- $dates = [];
- preg_match_all('/,([^,]*)/', ',' . $value, $values);
- foreach ($values[1] as $value) {
- if (array_key_exists('VALUE', $params)) {
- if ($params['VALUE'] == 'DATE-TIME') {
- $dates[] = $this->parseDateTime($value);
- } else if ($params['VALUE'] == 'DATE') {
- $dates[] = $this->parseDate($value);
- }
- } else {
- $dates[] = $this->parseDateTime($value);
- }
- }
- // some iCalendar exports (e.g. KOrganizer) use an EXDATE-entry for every
- // exception, so we have to merge them
- array_merge($properties[$tag], $dates);
- break;
-
- // Duration fields
- case 'DURATION':
- $attibutes[$tag] = $this->parseDuration($value);
- break;
-
- // Period of time fields
- case 'FREEBUSY':
- $values = [];
- $periods = [];
- preg_match_all('/,([^,]*)/', ',' . $value, $values);
- foreach ($values[1] as $value) {
- $periods[] = $this->parsePeriod($value);
- }
-
- $properties[$tag] = $periods;
- break;
-
- // UTC offset fields
- case 'TZOFFSETFROM':
- case 'TZOFFSETTO':
- $properties[$tag] = $this->parseUtcOffset($value);
- break;
-
- case 'PRIORITY':
- $properties[$tag] = $this->parsePriority($value);
- break;
-
- case 'CLASS':
- switch (trim($value)) {
- case 'PUBLIC':
- $properties[$tag] = 'PUBLIC';
- break;
- case 'CONFIDENTIAL':
- $properties[$tag] = 'CONFIDENTIAL';
- break;
- default:
- $properties[$tag] = 'PRIVATE';
- }
- break;
-
- // Integer fields
- case 'PERCENT-COMPLETE':
- case 'REPEAT':
- case 'SEQUENCE':
- $properties[$tag] = intval($value);
- break;
-
- // Geo fields
- case 'GEO':
- $floats = explode(';', $value);
- $value['latitude'] = floatval($floats[0]);
- $value['longitude'] = floatval($floats[1]);
- $properties[$tag] = $value;
- break;
-
- // Recursion fields
- case 'EXRULE':
- case 'RRULE':
- $properties[$tag] = $this->parseRecurrence($value);
- break;
-
- default:
- // string fields
- $properties[$tag] = trim($value);
- break;
- }
- }
-
- if (!$properties['RRULE']['rtype']) {
- $properties['RRULE'] = ['rtype' => 'SINGLE'];
- }
-
- if (!$properties['LAST-MODIFIED']) {
- $properties['LAST-MODIFIED'] = $properties['DTSTAMP'] ?: $properties['CREATED'] ?? time();
- }
-
- if (!$properties['DTSTART'] || ($properties['EXDATE'] && !$properties['RRULE'])) {
- // _("Die Datei ist keine gültige iCalendar-Datei!")
- throw new UnexpectedValueException();
- }
-
- if (!$properties['DTEND']) {
- $properties['DTEND'] = $properties['DTSTART'];
- }
-
- // day events starts at 00:00:00 and ends at 23:59:59
- if ($check['DAY_EVENT'])
- $properties['DTEND']--;
-
- // default: all imported events are set to private
- if (!$properties['CLASS']
- || ($this->convert_to_private && $properties['CLASS'] == 'PUBLIC')) {
- $properties['CLASS'] = 'PRIVATE';
- }
-
- /*
- if (isset($studip_categories[$properties['CATEGORIES']])) {
- $properties['STUDIP_CATEGORY'] = $studip_categories[$properties['CATEGORIES']];
- $properties['CATEGORIES'] = '';
- }
- *
- */
-
- $this->createDateFromProperties($properties);
- } else {
- // _("Die Datei ist keine gültige iCalendar-Datei!")
- throw new InvalidValuesException();
- }
- $this->count++;
- }
-
- return true;
- }
-
- private function createDateFromProperties($properties)
- {
- $date = CalendarDate::findOneBySQL(
- 'LEFT JOIN `calendar_date_assignments`
- ON `calendar_dates`.`id` = `calendar_date_assignments`.`calendar_date_id`
- WHERE `calendar_dates`.`unique_id` = :uid
- AND `calendar_date_assignments`.`range_id` = :range_id',
- [
- ':uid' => $properties['UID'],
- ':range_id' => $this->range_id
- ]
- );
-
- if (!$date) {
- $date = new CalendarDate();
- $date->id = $date->getNewId();
- $date->author_id = $this->range_id;
- $date->editor_id = $this->range_id;
- $range_date = new CalendarDateAssignment();
- $range_date->range_id = $this->range_id;
- $range_date->participation = '';
- $date->calendars[] = $range_date;
- }
-
- $date->begin = $properties['DTSTART']->getTimestamp();
- $date->end = $properties['DTEND']->getTimestamp();
- $date->title = $properties['SUMMARY'];
- $date->description = $properties['DESCRIPTION'];
- $date->access = $properties['CLASS'] ?? 'PRIVATE';
- $date->user_category = $properties['CATEGORIES'];
- $date->category = $properties['STUDIP_CATEGORY'] ?: 1;
- $date->priority = $properties['PRIORITY'] ?? '';
- $date->location = $properties['LOCATION'];
- if (is_array($properties['EXDATE'])) {
- foreach ($properties['EXDATE'] as $exdate) {
- $exception = new CalendarDateException();
- $exception->date = $exdate->format('Y-m-d');
- $date->exceptions[] = $exception;
- }
- }
- $date->mkdate = $properties['CREATED'] ? $properties['CREATED']->getTimestamp() : time();
- if (isset($properties['LAST-MODIFIED'])) {
- $date->chdate = $properties['LAST-MODIFIED']->getTimestamp();
- } else {
- $date->chdate = $date->mkdate;
- }
- $date->import_date = $this->import_time;
- $date->unique_id = $properties['UID'];
-
- $this->setRecurrenceRule($date, $properties['RRULE']);
- $date->store();
- }
-
- private function setRecurrenceRule(CalendarDate $date, $rrule)
- {
- $date->interval = $rrule['linterval'] ?? 1;
- if (strlen($rrule['wdays'] ?? '')) {
- $date->offset = $rrule['sinterval'] ?? 0;
- $date->days = $rrule['wdays'] ?? null;
- } else {
- $date->offset = $rrule['day'] ?? 0;
- $date->days = $rrule['sinterval'] ?? null;
- }
- $date->month = $rrule['month'] ?? null;
- $date->repetition_type = $rrule['rtype'] ?? 'SINGLE';
- $date->number_of_dates = $rrule['count'] ?? 1;
- $date->repetition_end = $rrule['expire'] ?? 0;
- }
-
- private function unfoldLine($data)
- {
- return preg_replace('/\x0D?\x0A[\x20\x09]/', '', $data);
- }
-
- /**
- * Parse a UTC Offset field
- */
- private function parseUtcOffset($offset_text)
- {
- $offset = 0;
- if (preg_match('/(\+|-)([0-9]{2})([0-9]{2})([0-9]{2})?/', $offset_text, $matches)) {
- $offset += 3600 * intval($matches[2]);
- $offset += 60 * intval($matches[3]);
- $offset *= ( $matches[1] == '+' ? 1 : -1);
- if (array_key_exists(4, $matches)) {
- $offset += intval($matches[4]);
- }
- }
- return $offset;
- }
-
- /**
- * Parse a Time Period field
- */
- private function parsePeriod($period_text): array
- {
- $matches = explode('/', $period_text);
-
- $start = $this->parseDateTime($matches[0]);
-
- if ($duration = $this->parseDuration($matches[1])) {
- return ['start' => $start, 'duration' => $duration];
- } else if ($end = $this->parseDateTime($matches[1])) {
- return ['start' => $start, 'end' => $end];
- }
- return [];
- }
-
- /**
- * Parse a DateTime field
- */
- private function parseDateTime(String $date_time)
- {
- $parts = explode('T', $date_time);
- if (count($parts) != 2) {
- // not a date time string but may be just a date string
- $date = $this->parseDate($date_time);
- return DateTimeImmutable::createFromFormat('YmdHis', implode('', $date) . '000000');
- }
-
- $date = $this->parseDate($parts[0]);
- $time = $this->parseTime($parts[1]);
-
- if ($time['zone'] == 'UTC') {
- $time_zone = new DateTimeZone('UTC');
- } else {
- $time_zone = new DateTimeZone('Europe/Berlin');
- }
- return DateTimeImmutable::createFromFormat(
- 'YmdHis',
- implode('', $date) . $time['hour'] . $time['minute'] . $time['second'],
- $time_zone
- );
- }
-
- /**
- * Parse a Time field
- */
- private function parseTime($time_text): array
- {
- $matches = [];
- if (preg_match('/([0-9]{2})([0-9]{2})([0-9]{2})(Z)?/', $time_text, $matches)) {
- $time['hour'] = $matches[1];
- $time['minute'] = $matches[2];
- $time['second'] = $matches[3];
- if (array_key_exists(4, $matches)) {
- $time['zone'] = 'UTC';
- } else {
- $time['zone'] = 'LOCAL';
- }
- return $time;
- }
- throw new InvalidValuesException();
- }
-
- /**
- * Parse a Date field
- */
- private function parseDate($date_text): array
- {
- $matches = [];
- if (preg_match('/([0-9]{4})([0-9]{2})([0-9]{2})/', $date_text, $matches)) {
- $date['year'] = $matches[1];
- $date['month'] = $matches[2];
- $date['mday'] = $matches[3];
- return $date;
- }
- throw new InvalidValuesException();
- }
-
- /**
- * Parse a Duration Value field
- */
- private function parseDuration($interval_text): DateInterval
- {
- return new DateInterval($interval_text);
- }
-
- private function parsePriority($value)
- {
- $value = intval($value);
- if ($value > 0 && $value < 5) {
- return 'HIGH';
- }
-
- if ($value == 5) {
- return 'MEDIUM';
- }
-
- if ($value > 5 && $value < 10) {
- return 'LOW';
- }
-
- return '';
- }
-
- /**
- * Parse a recurrence rule.
- *
- * @param $text string The text of the recurrence rule.
- * @return array The translated recurrence rule as array.
- * @throws InvalidValuesException
- */
- private function parseRecurrence($text): array
- {
- global $_calendar_error;
-
- if (preg_match_all('/([A-Za-z]*?)=([^;]*);?/', $text, $matches, PREG_SET_ORDER)) {
- $r_rule = [];
-
- foreach ($matches as $match) {
- switch ($match[1]) {
- case 'FREQ' :
- switch (trim($match[2])) {
- case 'DAILY' :
- case 'WEEKLY' :
- case 'MONTHLY' :
- case 'YEARLY' :
- $r_rule['rtype'] = trim($match[2]);
- break;
- default:
- throw new InvalidValuesException(
- _("Der Import enthält Kalenderdaten, die Stud.IP nicht korrekt darstellen kann.")
- );
- }
- break;
-
- case 'UNTIL' :
- $r_rule['expire'] = $this->parseDateTime($match[2]);
- break;
-
- case 'COUNT' :
- $r_rule['count'] = intval($match[2]);
- break;
-
- case 'INTERVAL' :
- $r_rule['linterval'] = intval($match[2]);
- break;
-
- case 'BYSECOND' :
- case 'BYMINUTE' :
- case 'BYHOUR' :
- case 'BYWEEKNO' :
- case 'BYYEARDAY' :
- throw new InvalidValuesException(
- _("Der Import enthält Kalenderdaten, die Stud.IP nicht korrekt darstellen kann.")
- );
- case 'BYDAY' :
- $byday = $this->parseByDay($match[2]);
- $r_rule['wdays'] = $byday['wdays'];
- if ($byday['sinterval'])
- $r_rule['sinterval'] = $byday['sinterval'];
- break;
-
- case 'BYMONTH' :
- $r_rule['month'] = $this->parseByMonth($match[2]);
- break;
-
- case 'BYMONTHDAY' :
- $r_rule['day'] = $this->parseByMonthDay($match[2]);
- break;
-
- case 'BYSETPOS':
- $r_rule['sinterval'] = intval($match[2]);
- break;
-
- case 'WKST' :
- break;
- }
- }
- }
-
- return $r_rule;
- }
-
- private function parseByDay($text)
- {
- global $_calendar_error;
-
- preg_match_all('/(-?\d{1,2})?(MO|TU|WE|TH|FR|SA|SU),?/', $text, $matches, PREG_SET_ORDER);
- $wdays_map = ['MO' => '1', 'TU' => '2', 'WE' => '3', 'TH' => '4', 'FR' => '5',
- 'SA' => '6', 'SU' => '7'];
- $wdays = "";
- $sinterval = null;
- foreach ($matches as $match) {
- $wdays .= $wdays_map[$match[2]];
- if ($match[1]) {
- if (!$sinterval && ((int) $match[1]) > 0 || $match[1] == '-1') {
- if ($match[1] == '-1') {
- $sinterval = '5';
- } else {
- $sinterval = $match[1];
- }
- } else {
- throw new InvalidValuesException(
- _("Der Import enthält Kalenderdaten, die Stud.IP nicht korrekt darstellen kann.")
- );
- }
- }
- }
-
- return $wdays ? ['wdays' => $wdays, 'sinterval' => $sinterval] : false;
- }
-
- private function parseByMonthDay($text)
- {
- $days = explode(',', $text);
- if (count($days) > 1 || ((int) $days[0]) < 0) {
- return false;
- }
-
- return $days[0];
- }
-
- private function parseByMonth($text)
- {
- $months = explode(',', $text);
- if (count($months) > 1) {
- return false;
- }
-
- return $months[0];
- }
-
- private function qp_decode($value)
- {
- return preg_replace_callback("/=([0-9A-F]{2})/", function ($m) {return chr(hexdec($m[1]));}, $value);
- }
-
- private function parseClientIdentifier(&$data)
- {
- global $_calendar_error;
-
- if ($this->client_identifier == '') {
- if (!preg_match('/PRODID((;[\W\w]*)*):([\W\w]+?)(\r\n|\r|\n)/', $data, $matches)
- || !trim($matches[3])) {
- // _("Die Datei ist keine gültige iCalendar-Datei!")
- throw new InvalidValuesException();
- } else {
- $this->client_identifier = trim($matches[3]);
- }
- }
- return true;
- }
-
- public function getClientIdentifier($data = null)
- {
- if (!is_null($data)) {
- $this->parseClientIdentifier($data);
- }
-
- return $this->client_identifier;
- }
-
-}