diff options
Diffstat (limited to 'lib/classes/calendar/ICalendarExport.class.php')
| -rw-r--r-- | lib/classes/calendar/ICalendarExport.class.php | 644 |
1 files changed, 0 insertions, 644 deletions
diff --git a/lib/classes/calendar/ICalendarExport.class.php b/lib/classes/calendar/ICalendarExport.class.php deleted file mode 100644 index ce50f87..0000000 --- a/lib/classes/calendar/ICalendarExport.class.php +++ /dev/null @@ -1,644 +0,0 @@ -<?php -/** - * ICalendarExport.class.php - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation; either version 2 of - * the License, or (at your option) any later version. - * - * @author Peter Thienel <thienel@data-quest.de> - * @author Moritz Strohm <strohm@data-quest.de> - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - * @since 5.5 - */ - - -class ICalendarExport -{ - /** - * Line break used in iCalendar - */ - const NEWLINE = "\r\n"; - - /** - * Default start of the week - */ - const WEEKSTART = 'MO'; - - /** - * Holds the time (as unix timestamp) used for - * the timestamp in every exported iCalendar object. - * - * @var int $time - */ - private $time = 0; - - public function __construct() - { - $this->default_filename_suffix = "ics"; - $this->format = "iCalendar"; - } - - public function exportCalendarDates(string $range_id, DateTimeInterface $start, DateTimeInterface $end): string - { - if ($this->time === 0) { - $this->time = time(); - } - $dates = CalendarDate::findBySQL( - "LEFT JOIN `calendar_date_assignments` - ON `calendar_dates`.`id` = `calendar_date_assignments`.`calendar_date_id` - WHERE - `calendar_date_assignments`.`range_id` = :range_id - AND ( - (`calendar_dates`.`begin` <= :end - AND `calendar_dates`.`end` >= :begin) - OR (`calendar_dates`.`repetition_type` != 'SINGLE' - AND (`calendar_dates`.`repetition_end` >= :end - OR `calendar_dates`.`repetition_end` = 0) - AND `calendar_dates`.`begin` < :end))", - [ - ':range_id' => $range_id, - ':begin' => $start->getTimestamp(), - ':end' => $end->getTimestamp(), - ] - ); - $ical = ''; - foreach ($dates as $date) { - $ical .= $this->writeICalEvent($this->prepareCalendarDate($date)); - } - return $ical; - } - - public function exportCourseDates(string $user_id, DateTimeInterface $start, DateTimeInterface $end) - { - if ($this->time === 0) { - $this->time = time(); - } - $dates = CourseDate::findBySql( - "LEFT JOIN `seminar_user` - ON `termine`.`range_id` = `seminar_user`.`Seminar_id` - WHERE - `seminar_user`.`user_id` = :user_id - AND `seminar_user`.`bind_calendar` = 1 - AND (`termine`.`date` <= :end - AND `termine`.`end_time` >= :begin)", - [ - ':user_id' => $user_id, - ':begin' => $start->getTimestamp(), - ':end' => $end->getTimestamp(), - ] - ); - $ical = ''; - foreach ($dates as $date) { - $ical .= $this->writeICalEvent($this->prepareCourseDate($date)); - } - return $ical; - } - - public function exportCourseExDates(string $user_id, DateTimeInterface $start, DateTimeInterface $end) - { - if ($this->time === 0) { - $this->time = time(); - } - $dates = CourseExDate::findBySql( - "LEFT JOIN `seminar_user` - ON `ex_termine`.`range_id` = `seminar_user`.`Seminar_id` - WHERE - `seminar_user`.`user_id` = :user_id - AND `seminar_user`.`bind_calendar` = 1 - AND (`ex_termine`.`date` <= :end - AND `ex_termine`.`end_time` >= :begin)", - [ - ':user_id' => $user_id, - ':begin' => $start->getTimestamp(), - ':end' => $end->getTimestamp(), - ] - ); - $ical = ''; - foreach ($dates as $date) { - $ical .= $this->writeICalEvent($this->prepareCourseDate($date)); - } - return $ical; - } - - /** - * @param CalendarDate | CourseExDate $date - * @return array - */ - public function prepareCalendarDate($date): array - { - return [ - 'SUMMARY' => $date->title, - 'DESCRIPTION' => $date->description, - 'LOCATION' => $date->location, - 'CATEGORIES' => $date->getCategoryAsString(), - 'LAST-MODIFIED' => $date->chdate, - 'CREATED' => $date->mkdate, - 'DTSTAMP' => $this->time, - 'DTSTART' => $date->begin, - 'DTEND' => $date->end, - 'EXDATE' => implode(',', $date->exceptions->pluck('date')), - 'PRIORITY' => 5, - 'RRULE' => [ - 'type' => $date->repetition_type, - 'offset' => $date->offset, - 'interval' => $date->interval, - 'days' => $date->days, - 'count' => $date->number_of_dates, - 'expire' => $date->repetition_end, - 'month' => $date->month - ], - 'UID' => $date->unique_id - ]; - } - - /** - * @param CalendarDate | CourseExDate $date - * @return array - */ - public function prepareCourseDate($date): array - { - $summary = $date->course->getFullName(); - $categories = $date->getTypeName(); - if ($date instanceof CourseExDate) { - $summary .= ' ' . _('(fällt aus)'); - $categories = ''; - } - return [ - 'SUMMARY' => $summary, - 'DESCRIPTION' => '', - 'LOCATION' => $date->getRoomName(), - 'CATEGORIES' => $categories, - 'LAST-MODIFIED' => $date->chdate, - 'CREATED' => $date->mkdate, - 'DTSTAMP' => $this->time, - 'DTSTART' => $date->date, - 'DTEND' => $date->end_time, - 'PRIORITY' => '', - 'UID' => 'Stud.IP-SEM-' . $date->id . '@' . ($_SERVER['SERVER_NAME'] ?? '') - ]; - } - - /** - * Returns an iCalendar header with a rudimentary time zone definition. - * - * @return string The iCalendar header. - */ - public function writeHeader() - { - // Default values - $header = "BEGIN:VCALENDAR" . self::NEWLINE; - $header .= "VERSION:2.0" . self::NEWLINE; - if (isset($this->client_identifier)) { - $header .= "PRODID:" . $this->client_identifier . self::NEWLINE; - } else { - $server_name = $_SERVER['SERVER_NAME'] ?? 'unknown'; - - $header .= "PRODID:-//Stud.IP@{$server_name}//Stud.IP_iCalendar Library"; - $header .= " //EN" . self::NEWLINE; - } - $header .= "METHOD:PUBLISH" . self::NEWLINE; - - // time zone definition CET/CEST - $header .= 'CALSCALE:GREGORIAN' . self::NEWLINE - . 'BEGIN:VTIMEZONE' . self::NEWLINE - . 'TZID:Europe/Berlin' . self::NEWLINE - . 'BEGIN:DAYLIGHT' . self::NEWLINE - . 'TZOFFSETFROM:+0100' . self::NEWLINE - . 'TZOFFSETTO:+0200' . self::NEWLINE - . 'TZNAME:CEST' . self::NEWLINE - . 'DTSTART:19700329T020000' . self::NEWLINE - . 'RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3' . self::NEWLINE - . 'END:DAYLIGHT' . self::NEWLINE - . 'BEGIN:STANDARD' . self::NEWLINE - . 'TZOFFSETFROM:+0200' . self::NEWLINE - . 'TZOFFSETTO:+0100' . self::NEWLINE - . 'TZNAME:CET' . self::NEWLINE - . 'DTSTART:19701025T030000' . self::NEWLINE - . 'RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10' . self::NEWLINE - . 'END:STANDARD' . self::NEWLINE - . 'END:VTIMEZONE' .self::NEWLINE; - - return $header; - } - - /** - * Returns the footer. - * - * @return string - */ - public function writeFooter() - { - return "END:VCALENDAR" . self::NEWLINE; - } - - /** - * Export prepared calendar data as iCalendar. - * - * @param array $properties The event to export. - * @return string iCalendar formatted data - */ - public function writeICalEvent(array $properties): string - { - $result = "BEGIN:VEVENT" . self::NEWLINE; - - foreach ($properties as $name => $value) { - $params = []; - $params_str = ''; - if ($value === '' || is_null($value)) { - continue; - } - switch ($name) { - // not supported event properties - case 'SEMNAME': - continue 2; - - // Text fields - case 'SUMMARY': - $value = $this->quoteText($value); - break; - case 'DESCRIPTION': - $value = $this->quoteText($value); - break; - case 'LOCATION': - $value = $this->quoteText($value); - break; - case 'CATEGORIES': - $value = $this->quoteText($value); - break; - - // Date fields - case 'LAST-MODIFIED': - case 'CREATED': - case 'COMPLETED': - $value = $this->_exportDateTime($value, true); - break; - - case 'DTSTAMP': - $value = $this->_exportDateTime(time(), true); - break; - - case 'DTSTART': - $exdate_time = $value; - case 'DTEND': - case 'DUE': - case 'RECURRENCE-ID': - if (array_key_exists('VALUE', $params)) { - if ($params['VALUE'] == 'DATE') { - $value = $this->_exportDate($value); - } else { - $value = $this->_exportDateTime($value); - $params_str = ';TZID=Europe/Berlin'; - } - } else { - $value = $this->_exportDateTime($value); - $params_str = ';TZID=Europe/Berlin'; - } - break; - - case 'EXDATE': - if (array_key_exists('VALUE', $params)) { - $value = $this->exportExDate($value); - } else { - $value = $this->exportExDateTime($value); - } - $params_str = ';TZID=Europe/Berlin'; - break; - - // Integer fields - case 'PERCENT-COMPLETE': - case 'REPEAT': - case 'SEQUENCE': - $value = "$value"; - break; - - case 'PRIORITY': - switch ($value) { - case 1: - $value = '1'; - break; - case 2: - $value = '5'; - break; - case 3: - $value = '9'; - break; - default: - $value = '0'; - } - break; - - // Geo fields - case 'GEO': - $value = $value['latitude'] . ',' . $value['longitude']; - break; - - // Recursion fields - case 'EXRULE': - case 'RRULE': - if ($value['type'] !== 'SINGLE') { - $value = $this->_exportRecurrence($value); - } - break; - - case "UID": - $value = "$value"; - } - if ($name && !is_array($value)) { - $attr_string = $name . $params_str . ':' . $value; - $result .= $this->foldLine($attr_string) . self::NEWLINE; - } - } - if (isset($properties['GROUP_EVENT'])) { - $result .= $this->exportGroupEventProperties($properties['GROUP_EVENT']); - } - $result .= "END:VEVENT" . self::NEWLINE; - - return $result; - } - - /** - * Quotes some characters accordingly to iCalendar format. - * - * @param string $text The text to quote. - * @return string The quoted text. - */ - public function quoteText(string $text): string - { - $match = ['\\', '\n', ';', ',']; - $replace = ['\\\\', '\\n', '\;', '\,']; - return str_replace($match, $replace, $text); - } - - /** - * Export a DateTime field - * - * @param int $value Unix timestamp - * @return String Date and time (UTC) iCalendar formatted - */ - public function _exportDateTime($value, $utc = false) - { - $date_time = new DateTime(); - $date_time->setTimestamp($value); - //transform local time in UTC - if ($utc) { - $tz_utc = new DateTimeZone('UTC'); - $date_time->setTimezone($tz_utc); - return $date_time->format('Ymd\THis\Z'); - } - return $date_time->format('Ymd\THis'); - } - - /** - * Export a Time field - * - * @param int $value Unix timestamp - * @return String Time (UTC) iCalendar formatted - */ - public function _exportTime($value, $utc = false) - { - $time = date("His", $value); - if ($utc) { - $time .= 'Z'; - } - - return $time; - } - - /** - * Export a Date field - */ - public function _exportDate($value) - { - return date("Ymd", $value); - } - - /** - * Export a recurrence rule - */ - public function _exportRecurrence($value) - { - $rrule = []; - // the last day of week in a MONTHLY or YEARLY recurrence in the - // Stud.IP calendar is 5, in iCalendar it is -1 - if ($value['offset'] == '5') { - $value['offset'] = '-1'; - } - - if ($value['count']) { - unset($value['expire']); - } - - foreach ($value as $r_param => $r_value) { - if ($r_value) { - switch ($r_param) { - case 'type': - $rrule[] = 'FREQ=' . $r_value; - break; - case 'expire': - if ($r_value < CalendarDate::NEVER_ENDING) - $rrule[] = 'UNTIL=' . $this->_exportDateTime($r_value, true); - break; - case 'interval': - $rrule[] = 'INTERVAL=' . $r_value; - break; - case 'days': - switch ($value['type']) { - case 'WEEKLY': - $rrule[] = 'BYDAY=' . $this->_exportWdays($r_value); - break; - // Some CUAs (e.g. Outlook) don't understand the nWDAY syntax - // (where n is the nth ocurrence of the day in a given period of - // time and WDAY is the day of week) the RRULE uses the BYSETPOS - // rule. - case 'MONTHLY': - case 'YEARLY': - $rrule[] = 'BYDAY=' . $value['offset'] . $this->_exportWdays($r_value); - $rrule[] = 'BYDAY=' . $this->_exportWdays($r_value); - if ($value['offset']) { - $rrule[] = 'BYSETPOS=' . $value['offset']; - } - break; - } - break; - case 'day': - $rrule[] = 'BYMONTHDAY=' . $r_value; - break; - case 'month': - $rrule[] = 'BYMONTH=' . $r_value; - break; - case 'count': - $rrule[] = 'COUNT=' . $r_value; - break; - } - } - } - - if ($value['type'] === 'WEEKLY' && self::WEEKSTART != 'MO') { - $rrule[] = 'WKST=' . self::WEEKSTART; - } - - return implode(';', $rrule); - } - - /** - * Return the days from CalendarDate::days as attribute of a event recurrence. - * - * @param string $value - * @return string - */ - public function _exportWdays(string $value): string - { - $wdays_map = ['1' => 'MO', '2' => 'TU', '3' => 'WE', '4' => 'TH', '5' => 'FR', - '6' => 'SA', '7' => 'SU']; - $wdays = []; - preg_match_all('/(\d)/', $value, $matches); - foreach ($matches[1] as $match) { - $wdays[] = $wdays_map[$match]; - } - return implode(',', $wdays); - } - - /** - * Formats dates of exception. - * - * @param string $value Unix timestamps as csv list. - * @return string The formatted Exceptions. - */ - public function exportExDate(string $value): string - { - $exdates = []; - $date_times = explode(',', $value); - foreach ($date_times as $date_time) { - $exdates[] = $this->_exportDate($date_time); - } - return implode(',', $exdates); - } - - /** - * Formats date times of exception. - * - * @param string $value Unix timestamps as csv list. - * @return string The formatted Exceptions. - */ - public function exportExDateTime(string $value): string - { - $ex_dates = []; - $ex_date_times = explode(',', $value); - foreach ($ex_date_times as $ex_date_time) { - $date_time = new DateTime(); - $date_time->setTimestamp($ex_date_time); - $ex_dates[] = $date_time->format('Ymd\THis'); - } - return implode(',', $ex_dates); - } - - /** - * Returns iCalendar group event properties if the date has mor than one attendee. - * - * @param CalendarDate $date The date object to extract the group data from. - * @return string The formatted group event properties. - */ - private function exportGroupEventProperties(CalendarDate $date): string - { - if (!count($date->calendars)) { - return ''; - } - $organizer = $date->author; - if ($organizer) { - $properties = $this->foldLine('ORGANIZER;CN="' - . $organizer->getFullName() - . '":mailto:' . $organizer->Email) - . self::NEWLINE; - } else { - $properties = $this->foldLine('ORGANIZER;CN="' - . _('unbekannt') - . '":mailto:' . $GLOBALS['user']->email) - . self::NEWLINE; - } - foreach ($date->calendars as $calendar) { - if ($date->author_id === $calendar->range_id) { - if ($calendar->user) { - $properties .= $this->foldLine('ATTENDEE;' - . 'ROLE=REQ-PARTICIPANT;' - . 'CN="' . $calendar->user->getFullName() - . '":mailto:' . $calendar->user->Email) - . self::NEWLINE; - } else { - $properties = ''; - } - } else { - if ($calendar->user) { - switch ($calendar->participation) { - case 'ACCEPTED' : - $attendee = 'ATTENDEE;ROLE=REQ-PARTICIPANT' - . ';PARTSTAT=ACCEPTED'; - break; - case 'ACKNOWLEDGED' : - $attendee = 'ATTENDEE;ROLE=NON-PARTICIPANT' - . ';PARTSTAT=ACCEPTED' - . ';DELEGATED-TO="mailto:' - . $this->getFacultyEmail($organizer->id) - . '"'; - break; - case 'DECLINED' : - $attendee = 'ATTENDEE;ROLE=REQ-PARTICIPANT' - . ';PARTSTAT=DECLINED'; - break; - default : - $attendee = 'ATTENDEE;ROLE=REQ-PARTICIPANT'; - $attendee .= ';PARTSTAT=TENTATIVE'; - $attendee .= ';RSVP=TRUE'; - - } - $attendee .= ';CN="' . $calendar->user->getFullName() - . '":mailto:' . $calendar->user->Email; - $properties .= $this->foldLine($attendee) . self::NEWLINE; - } - } - } - return $properties; - } - - /** - * @param string $user_id - * @return string - */ - private function getFacultyEmail(string $user_id): string - { - return DBManager::get()->fetchColumn(' - SELECT `email` - FROM `Institute` - LEFT JOIN `user_inst` USING(`institut_id`) - WHERE `Institute`.`Institut_id` = `fakultaets_id` - AND `user_id` = ?', [$user_id]); - } - - /** - * Returns the folded version of a text line. - * - * @param string $line - * @return string - */ - private function foldLine(string $line): string - { - $line = preg_replace('/(\r\n|\n|\r)/', '\n', $line); - if (mb_strlen($line) > 75) { - $foldedline = ''; - while ($line !== '') { - $maxLine = mb_substr($line, 0, 75); - $cutPoint = max(60, max(mb_strrpos($maxLine, ';'), mb_strrpos($maxLine, ':')) + 1); - - $foldedline .= ( empty($foldedline)) ? - mb_substr($line, 0, $cutPoint) : - self::NEWLINE . ' ' . mb_substr($line, 0, $cutPoint); - - $line = (mb_strlen($line) <= $cutPoint) ? '' : mb_substr($line, $cutPoint); - } - return $foldedline; - } - return $line; - } -} |
