diff options
| author | Jan-Hendrik Willms <tleilax+github@gmail.com> | 2021-07-22 16:07:19 +0200 |
|---|---|---|
| committer | Jan-Hendrik Willms <tleilax+github@gmail.com> | 2021-07-22 16:19:12 +0200 |
| commit | a3da1483a9e689846179159355badfec8073dbec (patch) | |
| tree | 770dcca6bdf5f6f2a11b0e7fcbbeda6919a3fc52 /lib/calendar | |
current code from svn, revision 62608
Diffstat (limited to 'lib/calendar')
| -rw-r--r-- | lib/calendar/CalendarColumn.class.php | 364 | ||||
| -rw-r--r-- | lib/calendar/CalendarExport.class.php | 87 | ||||
| -rw-r--r-- | lib/calendar/CalendarExportException.class.php | 18 | ||||
| -rw-r--r-- | lib/calendar/CalendarExportFile.class.php | 122 | ||||
| -rw-r--r-- | lib/calendar/CalendarImport.class.php | 91 | ||||
| -rw-r--r-- | lib/calendar/CalendarImportFile.class.php | 141 | ||||
| -rw-r--r-- | lib/calendar/CalendarParser.class.php | 132 | ||||
| -rw-r--r-- | lib/calendar/CalendarParserICalendar.class.php | 632 | ||||
| -rw-r--r-- | lib/calendar/CalendarView.class.php | 340 | ||||
| -rw-r--r-- | lib/calendar/CalendarWeekView.class.php | 123 | ||||
| -rw-r--r-- | lib/calendar/CalendarWriter.class.php | 53 | ||||
| -rw-r--r-- | lib/calendar/CalendarWriterICalendar.class.php | 628 | ||||
| -rw-r--r-- | lib/calendar/EventData.class.php | 92 | ||||
| -rw-r--r-- | lib/calendar/EventSource.interface.php | 64 | ||||
| -rw-r--r-- | lib/calendar/lib/CalendarError.class.php | 56 | ||||
| -rw-r--r-- | lib/calendar/lib/ErrorHandler.class.php | 121 |
16 files changed, 3064 insertions, 0 deletions
diff --git a/lib/calendar/CalendarColumn.class.php b/lib/calendar/CalendarColumn.class.php new file mode 100644 index 0000000..2a0821f --- /dev/null +++ b/lib/calendar/CalendarColumn.class.php @@ -0,0 +1,364 @@ +<?php +# Lifter010: TODO +/** + * CalendarColumn.class.php - a column for a CalendarView + * + * This class represents an entry-column like "monday" in the calendar + * + * 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 Rasmus Fuhse <fuhse@data-quest.de> + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + */ + +class CalendarColumn { + protected static $number = 0; + protected $title = ""; + protected $id = ""; + public $entries = []; + protected $url = ""; + protected $grouped = false; + + /** + * creates instance of type CalendarColumn + * + * @param string $id necessary if you want JavaScript enabled for this calendar + * @return CalendarColumn + */ + static public function create($id = null) { + $column = new CalendarColumn($id); + return $column; + } + + /** + * constructor + * + * @param string $id necessary if you want JavaScript enabled for this column + */ + public function __construct($id = null) { + $id !== null || $id = md5(uniqid("CalendarColumn_".self::$number++)); + $this->setId($id); + } + + /** + * returns the id of the column + * + * @return string + */ + public function getId() { + return $this->id; + } + + /** + * sets the id for this column, which is only necessary if you want + * Javascript to be enabled for this calendar + * + * @param string $id new id for this column + * @return CalendarColumn + */ + public function setId($id) { + $this->id = $id; + return $this; + } + + /** + * sets a title like "monday" for this column, which will be displayed in the calendar + * + * @param string $new_title new title + * @return CalendarColumn + */ + public function setTitle($new_title) { + $this->title = $new_title; + return $this; + } + + /** + * returns the title of this column like "monday" + * + * @return string title of column + */ + public function getTitle() { + return $this->title; + } + + /** + * sets the url to be directed to when clicking on the title of the column. + * Usually this is a single-day-view of the calendar. + * + * @param string $new_url an url + * @return CalendarColumn + */ + public function setURL($new_url) { + $this->url = $new_url; + return $this; + } + + /** + * returns the URL of the column (see setURL) + * + * @return string an url + */ + public function getURL() { + return $this->url; + } + + /** + * adds a new entry in the column. The entry needs to be an associative array + * with parameters as follows: + * + * @param array $entry_array associative array for an entry in the column like + * array ( + * 'color' => the color in hex (css-like, without the #) + * 'start' => the (start hour * 100) + (start minute) + * 'end' => the (end hour * 100) + (end minute) + * 'title' => the entry`s title + * 'content' => whatever shall be the content of the entry as a string + * ) + */ + public function addEntry($entry_array) { + if (!isset($entry_array['start']) || !isset($entry_array['end']) + || !isset($entry_array['title']) ) { + throw new InvalidArgumentException('The entry '. print_r($entry_array, true) .' does not follow the specifications!'); + } else { + $this->entries[] = $entry_array; + } + return $this; + } + + /** + * adds many entries to the column. For the syntax of an entry see addEntry() + * + * @param array $entries_array + * @return CalendarColumn + */ + public function addEntries($entries_array = []) { + foreach ($entries_array as $entry_array) { + $this->addEntry($entry_array); + } + return $this; + } + + /** + * returns all entries of this column + * + * @return array of arrays like + * array ( + * 'color' => the color in hex (css-like, without the #) + * 'start' => the (start hour * 100) + (start minute) + * 'end' => the (end hour * 100) + (end minute) + * 'title' => the entry`s title + * 'content' => whatever shall be the content of the entry as a string + * ) + */ + public function getEntries() { + return $this->entries; + } + + /** + * deletes all entries of this column. So the only way to edit an entry is + * getting all entries with getEntries, edit this entry, eraseEntries() and + * addEntries(). Not very short, but at least it works. + * + * @return CalendarColumn + */ + public function eraseEntries() { + $this->entries = []; + return $this; + } + + /** + * Returns an array of calendar-entries, grouped by day and additionally grouped by same start and end + * if groupEntries(true) has been called. + * + * @return mixed the (double-)grouped entries + */ + public function getGroupedEntries() + { + if (!$this->sorted_entries) { + if ($this->isGrouped()) { + $this->sorted_entries = $this->sortAndGroupEntries(); + } else { + $this->sorted_entries = $this->sortEntries(); + } + } + + return $this->sorted_entries; + } + + /** + * sorts and groups entries and returns them + * only used by columns with grouped entries like instituteschedules + * + * @return array + */ + public function sortAndGroupEntries() + { + $day = $this->getTitle(); + + $entries_for_column = $this->getEntries(); + $result = []; + $new_entries = []; + + // 1st step - group all entries with the same duration + foreach ($entries_for_column as $entry_id => $entry) { + $new_entries[$entry['start'] .'_'. $entry['end']][] = $entry; + } + + $column = 0; + + // 2nd step - optimize the groups + while (sizeof($new_entries) > 0) { + $lstart = 2399; $lend = 0; + + foreach ($new_entries as $time => $grouped_entries) { + list($start, $end) = explode('_', $time); + if ($start < $lstart /*&& ($end - $start) >= ($lend - $lstart)*/ ) { + $lstart = $start; + $lend = $end; + } + } + + $result['col_'. $column][] = $new_entries[$lstart .'_'. $lend]; + unset($new_entries[$lstart .'_'. $lend]); + + $hit = true; + + while ($hit) { + $hit = false; + $hstart = 2399; $hend = 2399; + + // check, if there is something, that can be placed after + foreach ($new_entries as $time => $grouped_entries) { + list($start, $end) = explode('_', $time); + + if ( ($start >= $lend) && ($start < $hstart) ) { + $hstart = $start; + $hend = $end; + $hit = true; + } + } + + if ($hit) { + $lend = $hend; + $result['col_'. $column][] = $new_entries[$hstart .'_'. $hend]; + unset($new_entries[$hstart .'_'. $hend]); + } + } + + $column++; + } // 2nd step + + return $result; + + } + + /** + * sorts entries and returns them + * + * @return array + */ + public function sortEntries() + { + $entries_for_column = $this->getEntries(); + + $result = []; + $column = 0; + + // 2nd step - optimize the groups + while (sizeof($entries_for_column) > 0) { + $lstart = 2399; $lend = 0; $lkey = null; + + foreach ($entries_for_column as $entry_key => $entry) { + if ($entry['start'] < $lstart /*&& ($end - $start) >= ($lend - $lstart)*/ ) { + $lstart = $entry['start']; + $lend = $entry['end']; + $lkey = $entry_key; + } + } + + $result['col_'. $column][] = $entries_for_column[$lkey]; + unset($entries_for_column[$lkey]); + + $hit = true; + + while ($hit) { + $hit = false; + $hstart = 2399; $hend = 2399; $hkey = null; + + // check, if there is something, that can be placed after + foreach ($entries_for_column as $entry_key => $entry) { + if ( ($entry['start'] >= $lend) && ($entry['start'] < $hstart) ) { + // && (($end - $start) > ($hend - $hstart)) ) { + $hstart = $entry['start']; + $hend = $entry['end']; + $hkey = $entry_key; + $hit = true; + } + } + + if ($hit) { + $lend = $hend; + $result['col_'. $column][] = $entries_for_column[$hkey]; + unset($entries_for_column[$hkey]); + } + } + + $column++; + } // 2nd step + return $result; + + } + + /** + * returns a matrix that tells the number of entries for a given timeslot + * + * @return array + */ + public function getMatrix() { + $group_matrix = []; + foreach ($this->getGroupedEntries() as $groups) { + foreach ($groups as $group) { + if (is_array($group[0])) { + $data = $group[0]; + } else { + $data = $group; + } + + for ($i = floor($data['start'] / 100); $i <= floor($data['end'] / 100); $i++) { + for ($j = 0; $j < 60; $j++) { + if (($i * 100) + $j >= $data['start'] && ($i * 100) + $j < $data['end']) { + $group_matrix[($i * 100) + $j]++; + } + } + } + } + } + return $group_matrix; + } + + /** + * check, if a grouped view of the entries is requested + * + * @return bool true if grouped, false otherwise + */ + public function isGrouped() + { + return $this->grouped; + } + + /** + * Call this function th enable/disable the grouping of entries with the same start and end. + * + * @param bool $group optional, defaults to true + * @return void + */ + public function groupEntries($grouped = true) + { + $this->grouped = $grouped; + } + +}
\ No newline at end of file diff --git a/lib/calendar/CalendarExport.class.php b/lib/calendar/CalendarExport.class.php new file mode 100644 index 0000000..c9c10f6 --- /dev/null +++ b/lib/calendar/CalendarExport.class.php @@ -0,0 +1,87 @@ +<? +# Lifter002: TODO +# Lifter007: TODO + +/** + * CalendarExport.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>, Suchi & Berg GmbH <info@data-quest.de> + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * @package calendar + */ + +class CalendarExport +{ + protected $_writer; + protected $export; + private $count; + + public function __construct(&$writer) + { + $this->_writer = $writer; + } + + public function exportFromDatabase($range_id = null, $start = 0, $end = Calendar::CALENDAR_END, $event_types = null, $except = NULL) + { + global $_calendar_error, $user; + + if (!$range_id) { + $range_id = $user->id; + } + $calendar = new SingleCalendar($range_id); + + $this->_export($this->_writer->writeHeader()); + $calendar->getEvents($event_types, $start, $end); + + foreach ($calendar->events as $event) { + $this->_export($this->_writer->write($event)); + } + $this->count = sizeof($calendar->events); + + $this->_export($this->_writer->writeFooter()); + } + + public function exportFromObjects($events) + { + global $_calendar_error; + + $this->_export($this->_writer->writeHeader()); + + $this->count = 0; + foreach ($events as $event) { + $this->_export($this->_writer->write($event)); + $this->count++; + } + + if (!sizeof($events)) { + $message = _('Es wurden keine Termine exportiert.'); + } else { + $message = sprintf(ngettext('Es wurde 1 Termin exportiert', 'Es wurden %s Termine exportiert', sizeof($events)), sizeof($events)); + } + + $this->_export($this->_writer->writeFooter()); + } + + public function _export($exp) + { + if (!empty($exp)) { + $this->export[] = $exp; + } + } + + public function getExport() + { + return $this->export; + } + + public function getCount() + { + return $this->count; + } +} diff --git a/lib/calendar/CalendarExportException.class.php b/lib/calendar/CalendarExportException.class.php new file mode 100644 index 0000000..fbdabda --- /dev/null +++ b/lib/calendar/CalendarExportException.class.php @@ -0,0 +1,18 @@ +<?php +/** + * CalendarExportException.php - indicates an error during + * calendar export or import + * + * 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> + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP +*/ + +class CalendarExportException extends Exception +{ +} diff --git a/lib/calendar/CalendarExportFile.class.php b/lib/calendar/CalendarExportFile.class.php new file mode 100644 index 0000000..1e0f3fb --- /dev/null +++ b/lib/calendar/CalendarExportFile.class.php @@ -0,0 +1,122 @@ +<? +# Lifter002: TODO +# Lifter007: TODO + +/** + * CalendarExportFile.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>, Suchi & Berg GmbH <info@data-quest.de> + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * @package calendar + */ + +class CalendarExportFile extends CalendarExport +{ + private $file_name = 'studip'; + private $tmp_file_name; + private $path; + + public function __construct(&$writer, $path = null, $file_name = null) + { + global $TMP_PATH; + + parent::__construct($writer); + + if (!$file_name) { + $this->tmp_file_name = $this->makeUniqueFilename(); + $this->file_name .= '.' . $writer->getDefaultFileNameSuffix(); + } else { + $this->file_name = $file_name; + $this->tmp_file_name = $file_name; + } + + if (!$path) { + $this->path = $TMP_PATH . '/'; + } + + $this->_writer = $writer; + } + + public function exportFromDatabase($range_id = null, $start = 0, $end = Calendar::CALENDAR_END, $event_types = null, $except = null) + { + $this->_createFile(); + parent::exportFromDatabase($range_id, $start, $end, $event_types, $except); + $this->_closeFile(); + } + + public function exportFromObjects($events) + { + $this->_createFile(); + parent::exportFromObjects($events); + $this->_closeFile(); + } + + public function sendFile() + { + if (file_exists($this->path . $this->tmp_file_name)) { + header('Location: ' . FileManager::getDownloadURLForTemporaryFile($this->tmp_file_name, $this->file_name)); + } else { + throw new CalendarExportException(_('Die Export-Datei konnte nicht erstellt werden!')); + } + } + + public function makeUniqueFileName() + { + return md5(uniqid(rand() . "Stud.IP Calendar")); + } + + // returns file handle + public function getExport() + { + return $this->export; + } + + public function getFileName() + { + return $this->file_name; + } + + public function getTempFileName() + { + return $this->tmp_file_name; + } + + public function _createFile() + { + if (!(is_dir($this->path))) { + if (!mkdir($this->path)) { + var_dump($this->path); exit; + throw new CalendarExportException(_('Das Export-Verzeichnis konnte nicht angelegt werden!')); + } else { + if (!chmod($this->path, 0777)) { + throw new CalendarExportException(_('Die Zugriffsrechte auf das Export-Verzeichnis konnten nicht geändert werden!')); + } + } + } + if (file_exists($this->path . $this->tmp_file_name)) { + if (!unlink($this->path . $this->tmp_file_name)) { + throw new CalendarExportException(_('Eine bestehende Export-Datei konnte nicht gelöscht werden!')); + } + } + $this->export = fopen($this->path . $this->tmp_file_name, "wb"); + if (!$this->export) { + throw new CalendarExportException(_("Die Export-Datei konnte nicht erstellt werden!")); + } + } + + public function _export($exp) + { + fwrite($this->export, $exp); + } + + public function _closeFile() + { + fclose($this->export); + } +} diff --git a/lib/calendar/CalendarImport.class.php b/lib/calendar/CalendarImport.class.php new file mode 100644 index 0000000..605d747 --- /dev/null +++ b/lib/calendar/CalendarImport.class.php @@ -0,0 +1,91 @@ +<? +# Lifter002: TODO +# Lifter007: TODO + +/** + * CalendarImport.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>, Suchi & Berg GmbH <info@data-quest.de> + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * @package calendar + */ + +class CalendarImport +{ + + const IGNORE_ERRORS = 1; + + protected $_parser; + private $data; + private $public_to_private = false; + + public function __construct(&$parser, $data = null) + { + $this->_parser = $parser; + $this->data = $data; + } + + public function getContent() + { + return $this->data; + } + + public function importIntoDatabase($range_id, $ignore = CalendarImport::IGNORE_ERRORS) + { + $this->_parser->changePublicToPrivate($this->public_to_private); + if ($this->_parser->parseIntoDatabase($range_id, $this->getContent(), $ignore)) { + return true; + } + + return false; + } + + public function importIntoObjects($ignore = CalendarImport::IGNORE_ERRORS) + { + $this->_parser->changePublicToPrivate($this->public_to_private); + if ($this->_parser->parseIntoObjects($this->getContent(), $ignore)) { + return true; + } + + return false; + } + + public function getObjects() + { + return $objects =& $this->_parser->getObjects(); + } + + public function getCount() + { + return $this->_parser->getCount($this->getContent()); + } + + public function changePublicToPrivate($value = TRUE) + { + $this->public_to_private = $value; + } + + public function getClientIdentifier() + { + if (!$client_identifier = $this->_parser->getClientIdentifier()) { + return $this->_parser->getClientIdentifier($this->getContent()); + } + return $client_identifier; + } + + public function setImportSem($do_import) + { + if ($do_import) { + $this->_parser->import_sem = true; + } else { + $this->_parser->import_sem = false; + } + } + +} diff --git a/lib/calendar/CalendarImportFile.class.php b/lib/calendar/CalendarImportFile.class.php new file mode 100644 index 0000000..3e2ba15 --- /dev/null +++ b/lib/calendar/CalendarImportFile.class.php @@ -0,0 +1,141 @@ +<? +# Lifter002: TODO +# Lifter007: TODO + +/** + * CalendarImportFile.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>, Suchi & Berg GmbH <info@data-quest.de> + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * @package calendar + */ + +class CalendarImportFile extends CalendarImport +{ + + private $file; + private $path; + + /** + * + */ + public function __construct(&$parser, $file, $path = '') + { + parent::__construct($parser); + $this->file = $file; + $this->path = $path; + } + + /** + * + */ + public function getContent() + { + $data = ''; + if (!$file = @fopen($this->file['tmp_name'], 'rb')) { + throw new CalendarExportException(_("Die Import-Datei konnte nicht geöffnet werden!")); + return false; + } + if ($file) { + while (!feof($file)) { + $data .= fread($file, 1024); + } + fclose($file); + } + return $data; + } + + /** + * + */ + public function getFileName() + { + return $this->file['name']; + } + + /** + * + */ + public function getFileType() + { + return $this->_parser->getType(); + } + + /** + * + */ + public function getFileSize() + { + if (file_exists($this->file['tmp_name'])) { + return filesize($this->file['tmp_name']); + } + return false; + } + + /** + * + */ + public function checkFile() + { + return true; + } + + /** + * + */ + public function importIntoDatabase($range_id, $ignore = CalendarImport::IGNORE_ERRORS) + { + if ($this->checkFile()) { + parent::importIntoDatabase($range_id, $ignore); + return true; + } + throw new CalendarExportException(_('Die Datei konnte nicht gelesen werden!')); + return false; + } + + /** + * + */ + public function importIntoObjects($ignore = CalendarImport::IGNORE_ERRORS) + { + global $_calendar_error; + + if ($this->checkFile()) { + parent::importIntoObjects($ignore); + return true; + } + throw new CalendarExportException(_('Die Datei konnte nicht gelesen werden!')); + } + + /** + * + */ + public function deleteFile() + { + if (!unlink($this->file['tmp_name'])) { + throw new CalendarExportException(_("Die Datei konnte nicht gelöscht werden!")); + return false; + } + return true; + } + + /** + * + */ + public function _getFileExtension() + { + $i = mb_strrpos($this->file['name'], '.'); + if (!$i) { + return ''; + } + $l = mb_strlen($this->file['name']) - $i; + $ext = mb_substr($this->file['name'], $i + 1, $l); + return $ext; + } +} diff --git a/lib/calendar/CalendarParser.class.php b/lib/calendar/CalendarParser.class.php new file mode 100644 index 0000000..07e556f --- /dev/null +++ b/lib/calendar/CalendarParser.class.php @@ -0,0 +1,132 @@ +<? +# Lifter002: TODO +# Lifter007: TODO + +/** + * CalendarParser.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>, Suchi & Berg GmbH <info@data-quest.de> + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * @package calendar + */ + +class CalendarParser +{ + private $events = []; + protected $components; + private $type; + private $number_of_events; + protected $public_to_private = false; + protected $client_identifier; + private $time; + + public function __construct() + { + $this->client_identifier = ''; + } + + public function parse($data, $ignore = null) + { + foreach ($data as $properties) { + if ($this->public_to_private && $properties['CLASS'] == 'PUBLIC') { + $properties['CLASS'] = 'PRIVATE'; + } + $properties['CATEGORIES'] = implode(', ', $properties['CATEGORIES']); + $this->components[] = $properties; + } + } + + public function getCount($data) + { + return 0; + } + + public function parseIntoDatabase($range_id, $data, $ignore) + { + if ($this->parseIntoObjects($range_id, $data, $ignore)) { + foreach ($this->events as $event) { + $event->store(); + } + return true; + } + + return false; + } + + public function parseIntoObjects($range_id, $data, $ignore) + { + $this->time = time(); + if ($this->parse($data, $ignore)) { + if (is_array($this->components)) { + foreach ($this->components as $component) { + $calendar_event = CalendarEvent::findByUid($component['UID'], $range_id); + if ($calendar_event) { + $this->setProperties($calendar_event, $component); + $calendar_event->setRecurrence($component['RRULE']); + $this->events[] = $calendar_event; + } else { + $calendar_event = new CalendarEvent(); + $event = new EventData(); + $event->author_id = $GLOBALS['user']->id; + $event->event_id = $event->getNewId(); + $event->uid = $component['UID']; + $calendar_event->range_id = $range_id; + $calendar_event->event_id = $event->event_id; + $calendar_event->event = $event; + $this->setProperties($calendar_event, $component); + $calendar_event->setRecurrence($component['RRULE']); + $this->events[] = $calendar_event; + } + } + } + return true; + } + $message = _('Die Import-Daten konnten nicht verarbeitet werden!'); + + return false; + } + + private function setProperties($calendar_event, $component) + { + $calendar_event->setStart($component['DTSTART']); + $calendar_event->setEnd($component['DTEND']); + $calendar_event->setTitle($component['SUMMARY']); + $calendar_event->event->description = $component['DESCRIPTION']; + $calendar_event->setAccessibility($component['CLASS']); + $calendar_event->setUserDefinedCategories($component['CATEGORIES']); + $calendar_event->event->category_intern = $component['STUDIP_CATEGORY'] ?: 1; + $calendar_event->setPriority($component['PRIORITY']); + $calendar_event->event->location = $component['LOCATION']; + $calendar_event->setExceptions($component['EXDATE']); + $calendar_event->event->mkdate = $component['CREATED']; + $calendar_event->event->chdate = $component['LAST-MODIFIED'] ?: $component['CREATED']; + $calendar_event->event->importdate = $this->time; + } + + public function getType() + { + return $this->type; + } + + public function &getObjects() + { + return $objects =& $this->events; + } + + public function changePublicToPrivate($value = true) + { + $this->public_to_private = $value; + } + + public function getClientIdentifier($data = null) + { + return $this->client_identifier; + } +} + diff --git a/lib/calendar/CalendarParserICalendar.class.php b/lib/calendar/CalendarParserICalendar.class.php new file mode 100644 index 0000000..241580a --- /dev/null +++ b/lib/calendar/CalendarParserICalendar.class.php @@ -0,0 +1,632 @@ +<? +# Lifter002: TODO +# Lifter007: TODO + +/** + * CalendarParserICalendar.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>, Suchi & Berg GmbH <info@data-quest.de> + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * @package calendar + */ + +class CalendarParserICalendar extends CalendarParser +{ + public $type = ''; + protected $count = null; + + public function __construct() + { + parent::__construct(); + $this->type = 'iCalendar'; + // initialize error handler + $GLOBALS['_calendar_error'] = new ErrorHandler(); + } + + public function getCount($data) + { + $matches = []; + if (is_null($this->count)) { + // Unfold any folded lines + $data = preg_replace('/\x0D?\x0A[\x20\x09]/', '', $data); + preg_match_all('/(BEGIN:VEVENT(\r\n|\r|\n)[\W\w]*?END:VEVENT\r?\n?)/', $data, $matches); + $this->count = sizeof($matches[1]); + } + + return $this->count; + } + + /** + * Parse a string containing vCalendar data. + * + * @access private + * @param String $data The data to parse + * + */ + public function parse($data, $ignore = null) + { + global $_calendar_error, $PERS_TERMIN_KAT; + + // match categories + $studip_categories = []; + $i = 1; + foreach ($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 = preg_replace('/\x0D?\x0A[\x20\x09]/', '', $data); + + if (!preg_match('/BEGIN:VCALENDAR(\r\n|\r|\n)([\W\w]*)END:VCALENDAR\r?\n?/', $data, $matches)) { + $_calendar_error->throwError(ErrorHandler::ERROR_CRITICAL, _("Die Import-Datei ist keine gültige iCalendar-Datei!")); + return false; + } + + // client identifier + if (!$this->_parseClientIdentifier($matches[2])) { + return false; + } + + // 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)) { + $_calendar_error->throwError(ErrorHandler::ERROR_MESSAGE, _("Die importierte Datei enthält keine Termine.")); + return true; + } + + if ($this->count) { + $this->count = 0; + } + foreach ($v_events[2] as $v_event) { + $properties['CLASS'] = 'PRIVATE'; + // Parse the remain attributes + + 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" + $tag = "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['CREATED']; + + if (!$properties['DTSTART'] || ($properties['EXDATE'] && !$properties['RRULE'])) { + $_calendar_error->throwError(ErrorHandler::ERROR_CRITICAL, _("Die Datei ist keine gültige iCalendar-Datei!")); + $this->count = 0; + return false; + } + + 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->public_to_private && $properties['CLASS'] == 'PUBLIC')) { + $properties['CLASS'] = 'PRIVATE'; + } + + /* + if (isset($studip_categories[$properties['CATEGORIES']])) { + $properties['STUDIP_CATEGORY'] = $studip_categories[$properties['CATEGORIES']]; + $properties['CATEGORIES'] = ''; + } + * + */ + + $this->components[] = $properties; + } else { + $_calendar_error->throwError(ErrorHandler::ERROR_CRITICAL, _("Die Datei ist keine gültige iCalendar-Datei!")); + $this->count = 0; + return false; + } + $this->count++; + } + + return true; + } + + /** + * Parse a UTC Offset field + */ + private function _parseUtcOffset($text) + { + $offset = 0; + if (preg_match('/(\+|-)([0-9]{2})([0-9]{2})([0-9]{2})?/', $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; + } else { + return false; + } + } + + /** + * Parse a Time Period field + */ + private function _parsePeriod($text) + { + $matches = explode('/', $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]; + } + } + + /** + * Parse a DateTime field + */ + private function _parseDateTime($text) + { + $dateParts = explode('T', $text); + if (count($dateParts) != 2 && !empty($text)) { + // not a date time field but may be just a date field + if (!$date = $this->_parseDate($text)) { + return $date; + } + $date = $this->_parseDate($text); + return mktime(0, 0, 0, $date['month'], $date['mday'], $date['year']); + } + + if (!$date = $this->_parseDate($dateParts[0])) { + return $date; + } + if (!$time = $this->_parseTime($dateParts[1])) { + return $time; + } + + if ($time['zone'] == 'UTC') { + return gmmktime($time['hour'], $time['minute'], $time['second'], $date['month'], $date['mday'], $date['year']); + } else { + return mktime($time['hour'], $time['minute'], $time['second'], $date['month'], $date['mday'], $date['year']); + } + } + + /** + * Parse a Time field + */ + private function _parseTime($text) + { + if (preg_match('/([0-9]{2})([0-9]{2})([0-9]{2})(Z)?/', $text, $matches)) { + $time['hour'] = intval($matches[1]); + $time['minute'] = intval($matches[2]); + $time['second'] = intval($matches[3]); + if (array_key_exists(4, $matches)) { + $time['zone'] = 'UTC'; + } else { + $time['zone'] = 'LOCAL'; + } + return $time; + } else { + return false; + } + } + + /** + * Parse a Date field + */ + private function _parseDate($text) + { + if (mb_strlen(trim($text)) !== 8) { + return false; + } + + $date['year'] = intval(mb_substr($text, 0, 4)); + $date['month'] = intval(mb_substr($text, 4, 2)); + $date['mday'] = intval(mb_substr($text, 6, 2)); + + return $date; + } + + /** + * Parse a Duration Value field + */ + private function _parseDuration($text) + { + if (preg_match('/([+]?|[-])P(([0-9]+W)|([0-9]+D)|)(T(([0-9]+H)|([0-9]+M)|([0-9]+S))+)?/', trim($text), $matches)) { + // weeks + $duration = 7 * 86400 * intval($matches[3]); + if (count($matches) > 4) { + // days + $duration += 86400 * intval($matches[4]); + } + if (count($matches) > 5) { + // hours + $duration += 3600 * intval($matches[7]); + // mins + if (array_key_exists(8, $matches)) { + $duration += 60 * intval($matches[8]); + } + // secs + if (array_key_exists(9, $matches)) { + $duration += intval($matches[9]); + } + } + // sign + if ($matches[1] == "-") { + $duration *= - 1; + } + + return $duration; + } else { + return false; + } + } + + private function _parsePriority($value) + { + $value = intval($value); + if ($value > 0 && $value < 5) { + return 1; + } + + if ($value == 5) { + return 2; + } + + if ($value > 5 && $value < 10) { + return 3; + } + + return 0; + } + + /** + * Parse a Recurrence field + */ + private function _parseRecurrence($text) + { + 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: + $_calendar_error->throwSingleError('parse', ErrorHandler::ERROR_WARNING, _("Der Import enthält Kalenderdaten, die Stud.IP nicht korrekt darstellen kann.")); + break; + } + 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' : + $_calendar_error->throwSingleError('parse', ErrorHandler::ERROR_WARNING, _("Der Import enthält Kalenderdaten, die Stud.IP nicht korrekt darstellen kann.")); + break; + + 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 { + $_calendar_error->throwSingleError('parse', ErrorHandler::ERROR_WARNING, _("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 (sizeof($days) > 1 || ((int) $days[0]) < 0) { + return false; + } + + return $days[0]; + } + + private function _parseByMonth($text) + { + $months = explode(',', $text); + if (sizeof($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)) { + $_calendar_error->throwError(ErrorHandler::ERROR_CRITICAL, _("Die Datei ist keine gültige iCalendar-Datei!")); + return false; + } elseif (!trim($matches[3])) { + $_calendar_error->throwError(ErrorHandler::ERROR_CRITICAL, _("Die Datei ist keine gültige iCalendar-Datei!")); + return false; + } 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; + } + +} diff --git a/lib/calendar/CalendarView.class.php b/lib/calendar/CalendarView.class.php new file mode 100644 index 0000000..0b8f905 --- /dev/null +++ b/lib/calendar/CalendarView.class.php @@ -0,0 +1,340 @@ +<?php +# Lifter010: TODO + + /** + * CalendarView.class.php - generates a calendar + * + * This class takes and checks all necessary parameters to display a calendar/schedule/time-table. + * + * 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 Rasmus Fuhse <fuhse@data-quest.de> + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + */ + +/** + * Kind of bean class for the calendar view. + * + * Example of use: + * + * // create a calendar-view and add a column + * $plan = new CalendarView(); + * $plan->addColumn(_('Spalte 1')) + * ->addEntry(array( + * 'id' => 1, + * 'color' => '#5C2D64', + * 'start' => '0930', + * 'end' => '1100', + * 'title' => 'Mathe 2', + * 'content' => 'Die Mathematiker kreiden sich mal wieder was an.' + * ) + * ); + * + * // display the calendar (containing one column) + * print $plan->render(); + * + * @since 2.0 + */ + +class CalendarView +{ + + protected $entries = []; + protected $entry_columns = []; + protected $height = 40; + protected $grouped = false; + protected $start_hour = 8; + protected $end_hour = 21; + protected $insertFunction = ""; + protected $templates = []; + protected $read_only = false; + + static protected $number_of_instances = 1; + protected $view_id; + + + /** + * You need to pass an instance of this class to the template. The constructor + * expects an array of entries of the following type: + * array( + * $day_number => array(array ( + * 'color' => the color in hex (css-like, without the #) + * 'start' => the (start hour * 100) + (start minute) + * 'end' => the (end hour * 100) + (end minute) + * //'day' => day of week (0 = Sunday, ... , 6 = Saturday) + * 'title' => the entry`s title + * 'content' => whatever shall be the content of the entry as a string + * ) ...) ... + * ) + * + * @param mixed $entries an array of entries (see above) + * @param string $controller the name of the controller. Used to create links. + */ + public function __construct($entries = []) + { + if (!is_array($entries)) { + throw new Exception('You need to pass some entries to the CalendarView!'); + } + $this->view_id = self::$number_of_instances++; + $this->checkEntries($entries); + $this->entries = $entries; + } + + /** + * set the height for one hour. This value is used to calculate the whole height of the schedule. + * + * @param int $entry_height the height of one hour in the schedule + */ + public function setHeight($height) + { + $this->height = $height; + } + + /** + * set the range of hours to be displayed. the start_hour has to be smaller than the end_hour + * + * @param int $start_hour the hour to start displaying at + * @param int $end_hour the hour to stop displaying at + */ + public function setRange($start_hour, $end_hour) + { + $this->start_hour = $start_hour; + $this->end_hour = $end_hour; + } + + /** + * does some plausability checks on an array of calendar-entries + * + * @param mixed $entries an array of calendar-entries + * + * @return bool false if check failed, true otherwise + */ + protected function checkEntries($entries) + { + foreach ($entries as $column) { + if (!$column instanceof CalendarColumn) { + throw new Exception('A column of the entries in the CalenarView is not of type CalendarColumn.'); + } + } + return true; + } + + /** + * adds a new column to this view. All entries created with addEntry will be + * added to this column. + * + * @param string $title like "monday" to be displayed on top of the column + * @param string $url to be called when clicked on the title of the column + * @param string $id any kind of id of the column + * @return CalendarView + */ + public function addColumn($title, $url = "", $id = null) + { + $this->entries[] = CalendarColumn::create($id) + ->setTitle($title) + ->setURL($url); + return $this; + } + + + /** + * adds a new entry to the last current column. The entry needs to be an + * associative array with parameters as follows: + * @param array $entry_array: associative array for an entry in the column like + * array ( + * 'color' => the color in hex (css-like, without the #) + * 'start' => the (start hour * 100) + (start minute) + * 'end' => the (end hour * 100) + (end minute) + * 'title' => the entry`s title + * 'content' => whatever shall be the content of the entry as a string + * ) + * @return CalendarView + */ + public function addEntry($entry_array) + { + if (count($this->entries)) { + $this->entries[count($this->entries)-1]->addEntry($entry_array); + } else { + throw new InvalidArgumentException(_("Es existiert noch keine Spalte in der Ansicht, zu der der Eintrag hinzugefügt werden kann.")); + } + return $this; + } + + + /** + * Call this function to enable/disable the grouping of entries with the same start and end. + * + * @param bool $group optional, defaults to true + */ + public function groupEntries($grouped = true) + { + $this->grouped = $grouped; + foreach($this->getColumns() as $entry_column) { + $entry_column->groupEntries(); + } + } + + /** + * When a column is clicked at no entry this function is called. + * First the templates generates a new entry at the clicked time. Then this + * js-function is called which gets the parameters + * "function (new_entry_dom_object, column_id, hour) { ... }" + * with new_entry_dom_object: a real dom-object of the div of the entry + * column_id: id of the column + * hour: integer number from 0 to 23 + * If js_function_object is an empty string, nothing will be done. + * + * @param string $js_function_object name of js-function or anonymous js-function + * @return CalendarView + */ + public function setInsertFunction($js_function_object) + { + $this->insertFunction = $js_function_object; + return $this; + } + + /** + * outputs the CalendarView with all (grouped) dates in columns. + * + * @param array $params you can pass some additional variables to the templates + * + * @return string + */ + public function render($params = []) + { + $style_parameters = [ + 'whole_height' => $this->getOverallHeight(), + 'entry_height' => $this->getHeight() + ]; + $factory = new Flexi_TemplateFactory(dirname(__file__).'/../../app/views'); + PageLayout::addStyle($factory->render('calendar/stylesheet', $style_parameters)); + + $template = $GLOBALS['template_factory']->open("calendar/calendar_view.php"); + $template->set_attribute("calendar_view", $this); + $template->set_attribute("view_id", $this->view_id); + return $template->render($params); + } + + + /* * * * * * * * * * * * * * * + * * * G E T T E R S * * * + * * * * * * * * * * * * * * */ + + /** + * Returns an array of calendar-entries, grouped by day and additionally grouped by same start and end + * if groupEntries(true) has been called. + * + * @return mixed the (double-)grouped entries + */ + public function getEntries() + { + $this->sorted_entries = []; + foreach ($this->getColumns() as $entry_column) { + $this->sorted_entries['day_'. $entry_column->getId()] = $entry_column->getGroupedEntries(); + } + return $this->sorted_entries; + } + + /** + * Returns an array where for each hour the number of concurrent entries is denoted. + * Used by the calendar to display the entries in parallel. + * + * @return mixed concurrent entries at each hour + */ + public function getMatrix() + { + $matrix = []; + foreach ($this->getColumns() as $day => $entry_column) { + $matrix['day_'.$day] = $entry_column->getMatrix(); + } + return $matrix; + } + + + /** + * returns the previously set start- and end-hour, denoting the + * range of entries to be displayed in the current calendar-view + * + * @return array consisting of the start and end hour + */ + public function getRange() + { + return [$this->start_hour, $this->end_hour]; + } + + /** + * the calendar can be used in two modes. Use this function to check, + * if the grouping-mode is enabled for the current calendar-view + * + * @return bool true if grouped, false otherwise + */ + public function isGrouped() + { + return $this->grouped; + } + + /** + * returns the previously set height for one hour + * + * @return mixed the height + */ + public function getHeight() + { + return $this->height; + } + + /** + * returns the overall height of the calendar + * + * @return mixed the overall height + */ + public function getOverallHeight() + { + return $this->height * ($this->end_hour - $this->start_hour) + $this->height + + 2 + ($this->end_hour - $this->start_hour) * 2; + } + + /** + * returns the previously set javasscript insert-function + * + * @return string name of js-function or anonymous js-function + */ + public function getInsertFunction() { + return $this->insertFunction; + } + + /** + * returns all columns of the calendar-view + * + * @return array of CalendarColumn + */ + public function getColumns() { + return $this->entries; + } + + /** + * Set the read-only status of the calendar + * + * @param bool $readonly true to make it read only, false otherwise + * + * @return void + */ + public function setReadOnly($readonly = true) + { + $this->read_only = $readonly; + } + + /** + * Return the read-only status of this calendar + * + * @return bool the read-only status of this calendar + */ + public function getReadOnly() { + return $this->read_only; + } + +} diff --git a/lib/calendar/CalendarWeekView.class.php b/lib/calendar/CalendarWeekView.class.php new file mode 100644 index 0000000..e9455ea --- /dev/null +++ b/lib/calendar/CalendarWeekView.class.php @@ -0,0 +1,123 @@ +<?php +# Lifter010: TODO + +/** + * CalendarWeekView.class.php - a specialized calendar view for displaying weeks + * + * This class takes and checks all necessary parameters to display a calendar/schedule/time-table. + * + * 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 Till Glöggler <tgloeggl@uos.de> & Rasmus Fuhse <fuhse@data-quest.de> + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + */ + +/** + * Kind of bean class for the calendar view. + * + * @since 2.0 + */ + +class CalendarWeekView extends CalendarView +{ + protected $days = [1,2,3,4,5]; + protected static $day_names = ["Montag","Dienstag","Mittwoch","Donnerstag","Freitag","Samstag","Sonntag"]; + + + + /** + * You need to pass an instance of this class to the template. The constructor + * expects an array of entries of the following type: + * array( + * $day_number => array(array ( + * 'color' => the color in hex (css-like, without the #) + * 'start' => the (start hour * 100) + (start minute) + * 'end' => the (end hour * 100) + (end minute) + * //'day' => day of week (0 = Sunday, ... , 6 = Saturday) + * 'title' => the entry`s title + * 'content' => whatever shall be the content of the entry as a string + * ) ...) ... + * ) + * + * @param mixed $entries an array of entries (see above) + * @param string $controller the name of the controller. Used to create links. + */ + public function __construct($entries, $controller) + { + parent::__construct($entries); + $this->context = $controller; + } + + /** + * Call this function th enable/disable the grouping of entries with the same start and end. + * + * @param bool $group optional, defaults to true + */ + public function groupEntries($grouped = true) + { + $this->grouped = $grouped; + foreach($this->entries as $entry_column) { + $entry_column->groupEntries(); + } + } + + /* * * * * * * * * * * * * * * + * * * G E T T E R S * * * + * * * * * * * * * * * * * * */ + + /** + * @return mixed the context + */ + public function getContext() + { + return $this->context; + } + + /** + * @return mixed the days + */ + public function getDays() + { + return $this->days; + } + + /** + * returns the previously set javasscript insert-function only + * if read_only is not set. + * + * @return string name of js-function or anonymous js-function + */ + public function getInsertFunction() { + if (!$this->read_only) { + return parent::getInsertFunction(); + } + + return false; + } + + /** + * returns all columns of the calendar-view nad removes the url if + * read_only is set + * + * @return array of CalendarColumn + */ + public function getColumns() { + // remove links and urls if calendar-view is read-only + if ($this->read_only) { + foreach ($this->entries as $column) { + $column->setURL(false); + foreach ($column->entries as $key => $entry) { + unset($column->entries[$key]['url']); + unset($column->entries[$key]['onClick']); + unset($column->entries[$key]['icons']); + } + } + } + + return parent::getColumns(); + } +} diff --git a/lib/calendar/CalendarWriter.class.php b/lib/calendar/CalendarWriter.class.php new file mode 100644 index 0000000..941c123 --- /dev/null +++ b/lib/calendar/CalendarWriter.class.php @@ -0,0 +1,53 @@ +<? +# Lifter002: TODO +# Lifter007: TODO + +/** + * CalendarWriter.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>, Suchi & Berg GmbH <info@data-quest.de> + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * @package calendar + */ + +class CalendarWriter +{ + var $default_filename_suffix; + var $format; + var $client_identifier; + + public function __construct() + { + // initialize error handler + $GLOBALS['_calendar_error'] = new ErrorHandler(); + } + + public function write(Event &$event) + { + return $event->properties; + } + + public function writeHeader() + { + } + + public function writeFooter() + { + } + + public function getDefaultFilenameSuffix() + { + return $this->default_filename_suffix; + } + + public function getFormat() + { + return $this->format; + } +} diff --git a/lib/calendar/CalendarWriterICalendar.class.php b/lib/calendar/CalendarWriterICalendar.class.php new file mode 100644 index 0000000..fdc32b3 --- /dev/null +++ b/lib/calendar/CalendarWriterICalendar.class.php @@ -0,0 +1,628 @@ +<? +# Lifter002: TODO +# Lifter007: TODO + +/** + * CalendarWriterICalendar.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>, Suchi & Berg GmbH <info@data-quest.de> + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * @package calendar + */ + +define('CALENDAR_WEEKSTART', 'MO'); + +class CalendarWriteriCalendar extends CalendarWriter +{ + var $newline = "\r\n"; + + public function __construct() + { + + parent::__construct(); + $this->default_filename_suffix = "ics"; + $this->format = "iCalendar"; + } + + public function writeHeader() + { + + // Default values + $header = "BEGIN:VCALENDAR" . $this->newline; + $header .= "VERSION:2.0" . $this->newline; + if ($this->client_identifier) { + $header .= "PRODID:" . $this->client_identifier . $this->newline; + } else { + $header .= "PRODID:-//Stud.IP@{$_SERVER['SERVER_NAME']}//Stud.IP_iCalendar Library"; + $header .= " //EN" . $this->newline; + } + $header .= "METHOD:PUBLISH" . $this->newline; + + // time zone definition CET/CEST + $header .= 'CALSCALE:GREGORIAN' . $this->newline + . 'BEGIN:VTIMEZONE' . $this->newline + . 'TZID:Europe/Berlin' . $this->newline + . 'BEGIN:DAYLIGHT' . $this->newline + . 'TZOFFSETFROM:+0100' . $this->newline + . 'TZOFFSETTO:+0200' . $this->newline + . 'TZNAME:CEST' . $this->newline + . 'DTSTART:19700329T020000' . $this->newline + . 'RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3' . $this->newline + . 'END:DAYLIGHT' . $this->newline + . 'BEGIN:STANDARD' . $this->newline + . 'TZOFFSETFROM:+0200' . $this->newline + . 'TZOFFSETTO:+0100' . $this->newline + . 'TZNAME:CET' . $this->newline + . 'DTSTART:19701025T030000' . $this->newline + . 'RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10' . $this->newline + . 'END:STANDARD' . $this->newline + . 'END:VTIMEZONE' . $this->newline; + + return $header; + } + + public function writeFooter() + { + return "END:VCALENDAR" . $this->newline; + } + + /** + * Export this component as iCalendar format + * + * @param object $event The event to export. + * @return String iCalendar formatted data + */ + public function write(Event &$event) + { + + $match_pattern_1 = ['\\', '\n', ';', ',']; + $replace_pattern_1 = ['\\\\', '\\n', '\;', '\,']; + $match_pattern_2 = ['\\', '\n', ';']; + $replace_pattern_2 = ['\\\\', '\\n', '\;']; + $exdate_time = 0; + + $result = "BEGIN:VEVENT" . $this->newline; + + foreach ($event->getProperties() as $name => $value) { + $params = []; + $params_str = ''; + + if ($name === 'SUMMARY') { + $value = $event->getTitle(); + } + if ($value === '' || is_null($value)) { + continue; + } + + switch ($name) { + // not supported event properties + case 'SEMNAME': + case 'EXPIRE': + case 'STUDIP_AUTHOR_ID': + case 'STUDIP_EDITOR_ID': + case 'STUDIP_ID': + case 'BEGIN': + case 'END': + case 'EVENT_TYPE': + case 'SEM_ID': + case 'STUDIP_GROUP_STATUS': + case 'STUDIP_CATEGORY': + continue 2; + + // text fields + case 'SUMMARY': + $value = str_replace($match_pattern_1, $replace_pattern_1, $value); + break; + case 'DESCRIPTION': + $value = str_replace($match_pattern_1, $replace_pattern_1, $event->getDescription()); + break; + case 'LOCATION': + $value = str_replace($match_pattern_1, $replace_pattern_1, $event->getLocation()); + break; + + case 'CATEGORIES': + $value = $this->_exportCategories($event); + 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': + if ($event->isDayEvent()) { + $params['VALUE'] = 'DATE'; + $params_str = ';VALUE=DATE'; + $value++; + } + 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, $params['VALUE']); + } else { + $value = $this->_exportExDateTime($value, $exdate_time); + } + $params_str = ';TZID=Europe/Berlin'; + break; + + case 'RDATE': + if (array_key_exists('VALUE', $params)) { + if ($params['VALUE'] == 'DATE') { + $value = $this->_exportDate($value); + } else if ($params['VALUE'] == 'PERIOD') { + $value = $this->_exportPeriod($value); + } else { + $value = $this->_exportDateTime($value); + } + } else { + $value = $this->_exportDateTime($value); + } + break; + + case 'TRIGGER': + if (array_key_exists('VALUE', $params)) { + if ($params['VALUE'] == 'DATE-TIME') { + $value = $this->_exportDateTime($value); + } else if ($params['VALUE'] == 'DURATION') { + $value = $this->_exportDuration($value); + } + } else { + $value = $this->_exportDuration($value); + } + break; + + // Duration fields + case 'DURATION': + $value = $this->_exportDuration($value); + break; + + // Period of time fields + case 'FREEBUSY': + $value_str = ''; + foreach ($value as $period) { + $value_str .= empty($value_str) ? '' : ','; + $value_str .= $this->_exportPeriod($period); + } + $value = $value_str; + break; + + + // UTC offset fields + case 'TZOFFSETFROM': + case 'TZOFFSETTO': + $value = $this->_exportUtcOffset($value); + break; + + // Integer fields + case 'PERCENT-COMPLETE': + if ($event->getPermission() == Event::PERMISSION_CONFIDENTIAL) + $value = ''; + case 'REPEAT': + case 'SEQUENCE': + $value = "$value"; + break; + + case 'PRIORITY': + if ($event->getPermission() == Event::PERMISSION_CONFIDENTIAL) + $value = '0'; + else { + 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': + if ($event->getPermission() == Event::PERMISSION_CONFIDENTIAL) + $value = ''; + else + $value = $value['latitude'] . ',' . $value['longitude']; + break; + + // Recursion fields + case 'EXRULE': + case 'RRULE': + if ($event->getRecurrence('rtype') != 'SINGLE') + $value = $this->_exportRecurrence($value); + else + continue 2; + break; + + case "UID": + $value = "$value"; + } + if ($name) { + $attr_string = "$name$params_str:$value"; + $result .= $this->_foldLine($attr_string) . $this->newline; + } + } + // if ($event->isGroupEvent()) { + if ($event instanceof CalendarEvent && $event->attendees->count() > 1) { + $result .= $this->_exportGroupEventProperties($event); + } + // $result .= 'DTSTAMP:' . $this->_exportDateTime(time()) . $this->newline; + $result .= "END:VEVENT" . $this->newline; + + return $result; + } + + /** + * Export a UTC Offset field + * + * @param array $value + * @return String UTC offset field iCalendar formatted + */ + public function _exportUtcOffset($value) + { + $offset = $value['ahead'] ? '+' : '-'; + $offset .= sprintf('%02d%02d', $value['hour'], $value['minute']); + if (array_key_exists('second', $value)) { + $offset .= sprintf('%02d', $value['second']); + } + + return $offset; + } + + /** + * Export a Time Period field + * + * @param array $value + * @return String Period field iCalendar formatted + */ + public function _exportPeriod($value) + { + $period = $this->_exportDateTime($value['start']); + $period .= '/'; + if (array_key_exists('duration', $value)) { + $period .= $this->_exportDuration($value['duration']); + } else { + $period .= $this->_exportDateTime($value['end']); + } + return $period; + } + + /** + * Export a DateTime field + * + * @param int $value Unix timestamp + * @return String Date and time (UTC) iCalendar formatted + */ + public function _exportDateTime($value, $utc = false) + { + +// $TZOffset = 3600 * mb_substr(date('O', $value), 0, 3); +// $TZOffset += 60 * mb_substr(date('O', $value), 3, 2); + //transform local time in UTC + if ($utc) { + $value -= date('Z', $value); + } + + return $this->_exportDate($value) . 'T' . $this->_exportTime($value, $utc); + } + + /** + * 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 duration value + */ + public function _exportDuration($value) + { + $duration = ''; + if ($value < 0) { + $value *= - 1; + $duration .= '-'; + } + $duration .= 'P'; + + $weeks = floor($value / (7 * 86400)); + $value = $value % (7 * 86400); + if ($weeks) { + $duration .= $weeks . 'W'; + } + + $days = floor($value / (86400)); + $value = $value % (86400); + if ($days) { + $duration .= $days . 'D'; + } + + if ($value) { + $duration .= 'T'; + + $hours = floor($value / 3600); + $value = $value % 3600; + if ($hours) { + $duration .= $hours . 'H'; + } + + $mins = floor($value / 60); + $value = $value % 60; + if ($mins) { + $duration .= $mins . 'M'; + } + + if ($value) { + $duration .= $value . 'S'; + } + } + + return $duration; + } + + /** + * 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['sinterval'] == '5') + $value['sinterval'] = '-1'; + + if ($value['count']) + unset($value['expire']); + + foreach ($value as $r_param => $r_value) { + if ($r_value) { + switch ($r_param) { + case 'rtype': + $rrule[] = 'FREQ=' . $r_value; + break; + case 'expire': + // end of unix epoche (this is also the end of Stud.IP epoche ;-) ) + if ($r_value < Calendar::CALENDAR_END) + $rrule[] = 'UNTIL=' . $this->_exportDateTime($r_value, true); + break; + case 'linterval': + $rrule[] = 'INTERVAL=' . $r_value; + break; + case 'wdays': + switch ($value['rtype']) { + 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['sinterval'] . $this->_exportWdays($r_value); + $rrule[] = 'BYDAY=' . $this->_exportWdays($r_value); + // The Stud.IP calendar don't support multiple values in a + // comma separated list. + + if ($value['sinterval']) + $rrule[] = 'BYSETPOS=' . $value['sinterval']; + + 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['rtype'] == 'WEEKLY' && CALENDAR_WEEKSTART != 'MO') { + $rrule[] = 'WKST=' . CALENDAR_WEEKSTART; + } + + return implode(';', $rrule); + } + + /** + * Return the Stud.IP calendar wdays attribute of a event recurrence + */ + public function _exportWdays($value) + { + $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); + } + + public function _exportExDate($value, $param) + { + $exdates = []; + $date_times = explode(',', $value); + foreach ($date_times as $date_time) { + $exdates[] = $this->_exportDate($date_time); + } + + return implode(',', $exdates); + } + + public function _exportExDateTime($value, $param) + { + $exdates = []; + $date_times = explode(',', $value); + foreach ($date_times as $date_time) { + $exdates[] = $this->_exportDate($date_time) . 'T' . $this->_exportTime($param); + } + + return implode(',', $exdates); + } + + private function _exportGroupEventProperties(Event $event) + { + $organizer = User::find($event->getAuthorId()); + if ($organizer) { + $properties = $this->_foldLine('ORGANIZER;CN="' + . $organizer->getFullName() + . '":mailto:' . $organizer->Email) + . $this->newline; + } else { + $properties = $this->_foldLine('ORGANIZER;CN="' + . _('unbekannt') + . '":mailto:' . $GLOBALS['user']->email) + . $this->newline; + } + foreach ($event->attendees as $event_member) { + if ($event->getAuthorId() == $event_member->user->id) { + if ($event_member->user) { + $properties .= $this->_foldLine('ATTENDEE;' + . 'ROLE=REQ-PARTICIPANT;' + . 'CN="' . $event_member->user->getFullName() + . '":mailto:' . $event_member->user->Email) + . $this->newline; + } else { + $properties = ''; + /* + $properties .= $this->_foldLine('ATTENDEE;' + . 'ROLE=REQ-PARTICIPANT;' + . 'CN="' . _('unbekannt') . '"') + . $this->newline; + * + */ + } + } else { + if ($event_member->user) { + switch ($event_member->group_status) { + case CalendarEvent::PARTSTAT_ACCEPTED : + $attendee = 'ATTENDEE;ROLE=REQ-PARTICIPANT' + . ';PARTSTAT=ACCEPTED'; + break; + case CalendarEvent::PARTSTAT_DELEGATED : + $attendee = 'ATTENDEE;ROLE=NON-PARTICIPANT' + . ';PARTSTAT=ACCEPTED' + . ';DELEGATED-TO="mailto:' + . $this->getFacultyEmail($organizer->getId()) + . '"'; + break; + case CalendarEvent::PARTSTAT_DECLINED : + $attendee = 'ATTENDEE;ROLE=REQ-PARTICIPANT' + . ';PARTSTAT=DECLINED'; + break; + default : + $attendee = 'ATTENDEE;ROLE=REQ-PARTICIPANT'; + $attendee .= ';PARTSTAT=TENTATIVE'; + $attendee .= ';RSVP=TRUE'; + + } + $attendee .= ';CN="' . $event_member->user->getFullName() + . '":mailto:' . $event_member->user->Email; + /* + } else { + $attendee .= ';CN="' . _('unbekannt') . '"'; + } + * + */ + $properties .= $this->_foldLine($attendee) . $this->newline; + } + } + } + return $properties; + } + + public function getFacultyEmail($user_id) + { + $stmt = DBManager::get()->prepare('SELECT email FROM Institute i ' + . 'LEFT JOIN user_inst ui USING(institut_id) ' + . 'WHERE i.Institut_id = fakultaets_id AND user_id = ?'); + $stmt->execute([$user_id]); + return $stmt->fetchColumn(); + } + + public function _exportCategories($event) + { + return implode(',' ,$event->toStringCategories(true)); + } + + /** + * Return the folded version of a line + */ + public function _foldLine($line) + { + $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) : + $this->newline . ' ' . mb_substr($line, 0, $cutPoint); + + $line = (mb_strlen($line) <= $cutPoint) ? '' : mb_substr($line, $cutPoint); + } + return $foldedline; + } + return $line; + } +} diff --git a/lib/calendar/EventData.class.php b/lib/calendar/EventData.class.php new file mode 100644 index 0000000..9ab4377 --- /dev/null +++ b/lib/calendar/EventData.class.php @@ -0,0 +1,92 @@ +<?php + + +namespace Studip\Calendar; + + +class EventData +{ + public $begin; + public $end; + public $title; + public $event_classes; + public $text_colour; + public $background_colour; + public $editable; + public $object_class; + public $object_id; + public $parent_object_class; + public $parent_object_id; + public $range_type; + public $range_id; + public $view_urls; + public $api_urls; + public $icon; + + public function __construct( + \DateTime $begin, + \DateTime $end, + string $title, + Array $event_classes, + string $text_colour, + string $background_colour, + bool $editable, + string $object_class, + string $object_id, + string $parent_object_class, + string $parent_object_id, + string $range_type, + string $range_id, + Array $view_urls = [], + Array $api_urls = [], + string $icon = '' + ) + { + $this->begin = $begin; + $this->end = $end; + $this->title = $title; + $this->event_classes = $event_classes; + $this->text_colour = $text_colour; + $this->background_colour = $background_colour; + $this->editable = $editable; + $this->object_class = $object_class; + $this->object_id = $object_id; + $this->parent_object_class = $parent_object_class; + $this->parent_object_id = $parent_object_id; + $this->range_type = $range_type; + $this->range_id = $range_id; + $this->view_urls = $view_urls; + $this->api_urls = $api_urls; + $this->icon = $icon; + } + + + public function toFullcalendarEvent() + { + //Note: The timezone must not be transmitted or + //the events may be shifted when there is a timezone + //or daylight saving time difference between the server + //and the client! + return [ + 'resourceId' => $this->range_id, + 'start' => $this->begin->format('Y-m-d\TH:i:s'), + 'end' => $this->end->format('Y-m-d\TH:i:s'), + 'title' => $this->title, + 'classNames' => $this->event_classes, + 'textColor' => $this->text_colour, + 'color' => $this->background_colour, + 'editable' => $this->editable, + 'studip_weekday_begin' => $this->begin->format('N'), + 'studip_weekday_end' => $this->end->format('N'), + 'studip_object_class' => $this->object_class, + 'studip_object_id' => $this->object_id, + 'studip_parent_object_class' => $this->parent_object_class, + 'studip_parent_object_id' => $this->parent_object_id, + 'studip_range_type' => $this->range_type, + 'studip_range_id' => $this->range_id, + 'studip_view_urls' => $this->view_urls, + 'studip_api_urls' => $this->api_urls, + 'icon' => $this->icon + ]; + } +} diff --git a/lib/calendar/EventSource.interface.php b/lib/calendar/EventSource.interface.php new file mode 100644 index 0000000..48506d7 --- /dev/null +++ b/lib/calendar/EventSource.interface.php @@ -0,0 +1,64 @@ +<?php + + +namespace Studip\Calendar; + + +/** + * This interface defines methods that can be implemented by classes + * that can provide calendar event data in a standardised format. + * The methods are meant to be called on a "per-object base", + * meaning that filtering of the event sources has to be done + * before calling methods of this interface. + */ +interface EventSource +{ + /** + * Returns all event data this event source can provide. + * + * @returns EventData[] An array of Studip\Calendar\EventData objects. + */ + public function getAllEventData(); + + + /** + * Returns event data that fall in the specified time range. + * + * @returns EventData[] An array of Studip\Calendar\EventData objects. + */ + public function getEventDataForTimeRange(\DateTime $begin, \DateTime $end); + + + /** + * Allows a filtered output of event data based on a specified user, + * a specified range-ID and range type (must be supported by the + * Context class) or a specified time range. + * If no filters are applied the result should be the same as if + * the getAllEventData method from this interface is called. + * + * @param string $user_id The user for whom the event data shall be + * retrieved. Depending on the implementation, this parameter + * might be necessary to check permissions for event objects. + * + * @param string $range_id An optional range-ID that may be necessary + * for an implementation to check for permissions. + * + * @param string $range_type An optional range type that may be + * necessary for an implementation to check for permissions. + * + * @param DateTime|int|string $begin The begin date as DateTime object + * or unix timestamp. + * + * @param DateTime|int|string $end The end date as DateTime object + * or unix timestamp. + * + * @returns EventData[] An array of Studip\Calendar\EventData objects. + */ + public function getFilteredEventData( + $user_id = null, + $range_id = null, + $range_type = null, + $begin = null, + $end = null + ); +} diff --git a/lib/calendar/lib/CalendarError.class.php b/lib/calendar/lib/CalendarError.class.php new file mode 100644 index 0000000..f796e1a --- /dev/null +++ b/lib/calendar/lib/CalendarError.class.php @@ -0,0 +1,56 @@ +<? +# Lifter002: TODO +# Lifter007: TODO + +/** + * Error.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>, Suchi & Berg GmbH <info@data-quest.de> + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * @package calendar + */ + + +class CalendarError +{ + + var $status; + var $message; + var $file; + var $line; + + public function __construct($status, $message, $file = '', $line = '') + { + $this->status = $status; + $this->message = $message; + $this->file = $file; + $this->line = $line; + } + + public function getStatus() + { + return $this->status; + } + + public function getMessage() + { + return $this->message; + } + + public function getFile() + { + return $this->file; + } + + public function getLine() + { + return $this->line; + } + +} diff --git a/lib/calendar/lib/ErrorHandler.class.php b/lib/calendar/lib/ErrorHandler.class.php new file mode 100644 index 0000000..710bdd1 --- /dev/null +++ b/lib/calendar/lib/ErrorHandler.class.php @@ -0,0 +1,121 @@ +<? +# Lifter002: TODO +# Lifter007: TODO + +/** + * ErrorHandler.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>, Suchi & Berg GmbH <info@data-quest.de> + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * @package calendar + */ + +class ErrorHandler +{ + // this is the state of the error handling if no error has occured + const ERROR_NORMAL = 1; + // this is the state of the error handling a message has to be displayed + const ERROR_MESSAGE = 2; + // this is the state of the error handling if something was going wrong but without data-loss + const ERROR_WARNING = 4; + // this is the state of the error handling if a critical error occured (maybe with data loss) + const ERROR_CRITICAL = 8; + // this is the state of the error handling if a fatal error occured and the execution of the process (e.g. import of events) was stopped + const ERROR_FATAL = 16; + + var $errors; + var $status; + + public function __construct() + { + + $this->errors = []; + $this->status = ErrorHandler::ERROR_NORMAL; + $this->_is_instantiated = true; + } + + public function getStatus($status = NULL) + { + if ($status === NULL) + return $this->status; + + return $status & $this->status; + } + + public function getMaxStatus($status) + { + if ($status <= $this->status) + return true; + + return false; + } + + public function getMinStatus($status) + { + if ($status >= $this->status) + return true; + + return false; + } + + public function getErrors($status = NULL) + { + if ($status === NULL) + return $this->errors; + + return $this->errors[$status]; + } + + public function getAllErrors() + { + $status = [ErrorHandler::ERROR_FATAL, ErrorHandler::ERROR_CRITICAL, ErrorHandler::ERROR_WARNING, + ErrorHandler::ERROR_MESSAGE, ErrorHandler::ERROR_NORMAL]; + $errors = []; + foreach ($status as $stat) { + if (is_array($this->errors[$stat])) { + $errors = array_merge($errors, $this->errors[$stat]); + } + } + return $errors; + } + + public function nextError($status) + { + if(is_array($this->errors[$status])) { + return reset($this->errors[$status]); + } + return false; + } + + public function throwError($status, $message, $file = '', $line = '') + { + $this->errors[$status][] = new CalendarError($status, $message, $file, $line); + $this->status |= $status; + reset($this->errors[$status]); + if ($status == ErrorHandler::ERROR_FATAL) { + echo '<b>'; + while ($error = $this->nextError(ErrorHandler::ERROR_FATAL)) { + echo '<br />' . $error->getMessage(); + } + echo '</b><br />'; + page_close(); + exit; + } + } + + public function throwSingleError($index, $status, $message, $file = '', $line = '') + { + static $index_list = []; + + if ($index_list[$index] != 1) { + $this->throwError($status, $message, $file, $line); + $index_list[$index] = 1; + } + } +} |
