diff --git a/application/config/routes.php b/application/config/routes.php index d55266326..1aba832b0 100644 --- a/application/config/routes.php +++ b/application/config/routes.php @@ -130,6 +130,7 @@ $route['lvplan/ical/download'] = 'LvPlanICALDownload'; $route['lvplan/csv/download'] = 'LvPlanCSVDownload'; $route['lvplan/excel/download'] = 'LvPlanExcelDownload'; $route['lvplan/html/preview'] = 'LvPlanHTMLPreview'; +$route['lvplan/html/detail'] = 'LvPlanHTMLDetail'; // load routes from extensions, also look for environment-specific configs $subdirs = ['application/config/extensions', 'application/config/' . ENVIRONMENT . '/extensions']; diff --git a/application/controllers/LvPlanHTMLDetail.php b/application/controllers/LvPlanHTMLDetail.php new file mode 100644 index 000000000..47ccc0a08 --- /dev/null +++ b/application/controllers/LvPlanHTMLDetail.php @@ -0,0 +1,333 @@ + array('basis/cis:r') + )); + + $this->load->model('person/Benutzer_model', 'BenutzerModel'); + $this->load->model('organisation/Studiengang_model', 'StudiengangModel'); + $this->load->model('ressource/Ort_model', 'OrtModel'); + $this->load->model('ressource/Stunde_model', 'StundeModel'); + $this->loadPhrases(array( + 'global' => array('termine'), + 'ui' => array('organisierende', 'teilnehmende') + )); + } + + /** + * Serves details for one hour in the HTML timetable preview. + * + * @return void + */ + public function index() + { + $type = $this->getType(); + $datum = $this->getDate(); + $hour = $this->getHour(); + + if ($type === 'verband') + $filters = $this->getVerbandFilters(); + elseif ($type === 'ort') + $filters = $this->getRoomFilters(); + else + $filters = $this->getPersonalFilters(); + + $this->load->library('LvPlanHTMLDetailLib'); + $content = $this->lvplanhtmldetaillib->getContent( + $datum, + $hour, + $type, + $filters + ); + + $this->output + ->set_content_type('text/html', 'UTF-8') + ->set_header('X-Content-Type-Options: nosniff') + ->set_output($content); + } + + /** + * @return string Requested timetable type. + */ + private function getType() + { + $type = $this->input->get('type', true); + + if (!in_array($type, array('student', 'lektor', 'verband', 'ort'), true)) + show_error('Ungueltiger Parameter: type', 400); + + return $type; + } + + /** + * @return string Date in YYYY-MM-DD format. + */ + private function getDate() + { + $value = $this->input->get('datum', true); + if (!is_string($value) || !preg_match('/^\d{4}-\d{2}-\d{2}$/', $value)) + show_error('Ungueltiger Parameter: datum', 400); + + $date = DateTime::createFromFormat( + '!Y-m-d', + $value, + new DateTimeZone(self::TIMEZONE) + ); + $errors = DateTime::getLastErrors(); + if ($date === false + || ($errors !== false + && ($errors['warning_count'] > 0 || $errors['error_count'] > 0)) + || $date->format('Y-m-d') !== $value) + { + show_error('Ungueltiger Parameter: datum', 400); + } + + return $value; + } + + /** + * @return array Selected timetable hour. + */ + private function getHour() + { + $number = $this->getInteger('stunde', false); + $this->StundeModel->addSelect('stunde, beginn, ende'); + $result = $this->StundeModel->load($number); + + if (isError($result)) + show_error(getError($result), 500); + if (!hasData($result)) + show_error('Ungueltiger Parameter: stunde', 400); + + $hours = getData($result); + $hour = reset($hours); + $begin = isset($hour->beginn) ? (string)$hour->beginn : ''; + $end = isset($hour->ende) ? (string)$hour->ende : ''; + $beginSeconds = $this->timeToSeconds($begin); + $endSeconds = $this->timeToSeconds($end); + + if ($beginSeconds === null || $endSeconds === null || $endSeconds <= $beginSeconds) + show_error('Ungueltiges Stundenraster', 500); + + return array( + 'number' => (string)$hour->stunde, + 'begin' => $begin, + 'end' => $end + ); + } + + /** + * @return array Personal timetable filters. + */ + private function getPersonalFilters() + { + $uid = $this->input->get('pers_uid', true); + if (!is_string($uid) || $uid === '' || !check_utf8($uid)) + show_error('Ungueltiger Parameter: pers_uid', 400); + + if ($uid !== getAuthUID() + && !$this->permissionlib->isBerechtigt('basis/other_lv_plan')) + { + show_error('Forbidden', 403); + } + + $this->BenutzerModel->addSelect( + 'tbl_benutzer.uid, tbl_person.titelpre, tbl_person.vorname, ' + .'tbl_person.nachname, tbl_person.titelpost' + ); + $this->BenutzerModel->addJoin('public.tbl_person', 'person_id', 'LEFT'); + $result = $this->BenutzerModel->load(array($uid)); + + if (isError($result)) + show_error(getError($result), 500); + if (!hasData($result)) + show_error('Ungueltiger Parameter: pers_uid', 400); + + $users = getData($result); + $user = reset($users); + $nameParts = array(); + foreach (array('titelpre', 'vorname', 'nachname', 'titelpost') as $property) + { + if (isset($user->{$property}) && trim($user->{$property}) !== '') + $nameParts[] = trim($user->{$property}); + } + + return array( + 'uid' => $uid, + 'full_name' => implode(' ', $nameParts) + ); + } + + /** + * @return array Room timetable filters. + */ + private function getRoomFilters() + { + $ortKurzbz = $this->input->get('ort_kurzbz', true); + if (!is_string($ortKurzbz) || $ortKurzbz === '' || !check_utf8($ortKurzbz)) + show_error('Ungueltiger Parameter: ort_kurzbz', 400); + + $this->OrtModel->addSelect('ort_kurzbz, bezeichnung, content_id'); + $result = $this->OrtModel->load($ortKurzbz); + + if (isError($result)) + show_error(getError($result), 500); + if (!hasData($result)) + show_error('Ungueltiger Parameter: ort_kurzbz', 400); + + $rooms = getData($result); + $room = reset($rooms); + + return array( + 'ort_kurzbz' => $ortKurzbz, + 'bezeichnung' => isset($room->bezeichnung) ? $room->bezeichnung : '', + 'content_id' => isset($room->content_id) ? $room->content_id : null + ); + } + + /** + * @return array Lehrverband timetable filters. + */ + private function getVerbandFilters() + { + $stgKz = $this->getInteger('stg_kz', true); + $semester = $this->getInteger('sem', false, true); + $verband = $this->getOptionalFilter('ver'); + $gruppe = $this->getOptionalFilter('grp'); + $ortKurzbz = $this->getOptionalRoomFilter(); + + $this->StudiengangModel->addSelect( + 'studiengang_kz, UPPER(typ || kurzbz) AS stg_kurzbz' + ); + $result = $this->StudiengangModel->load($stgKz); + + if (isError($result)) + show_error(getError($result), 500); + if (!hasData($result)) + show_error('Ungueltiger Parameter: stg_kz', 400); + + $programs = getData($result); + $program = reset($programs); + + return array( + 'stg_kz' => $stgKz, + 'stg_kurzbz' => isset($program->stg_kurzbz) + ? $program->stg_kurzbz + : '', + 'sem' => $semester, + 'ver' => $verband, + 'grp' => $gruppe, + 'ort_kurzbz' => $ortKurzbz + ); + } + + /** + * Returns the room selected in a Verband preview cell, if present. + * + * @return string|null + */ + private function getOptionalRoomFilter() + { + $ortKurzbz = $this->input->get('ort_kurzbz', true); + if ($ortKurzbz === null || $ortKurzbz === '') + return null; + if (!is_string($ortKurzbz) || !check_utf8($ortKurzbz)) + show_error('Ungueltiger Parameter: ort_kurzbz', 400); + + $this->OrtModel->addSelect('ort_kurzbz'); + $result = $this->OrtModel->load($ortKurzbz); + + if (isError($result)) + show_error(getError($result), 500); + if (!hasData($result)) + show_error('Ungueltiger Parameter: ort_kurzbz', 400); + + return $ortKurzbz; + } + + /** + * @param string $name GET parameter name. + * @param bool $signed Whether negative values are allowed. + * @param bool $optional Whether the parameter may be omitted or empty. + * @return int|null + */ + private function getInteger($name, $signed, $optional = false) + { + $value = $this->input->get($name, true); + if ($optional && ($value === null || $value === '')) + return null; + + $pattern = $signed ? '/^-?\d+$/' : '/^\d+$/'; + if (!is_string($value) || !preg_match($pattern, $value)) + show_error('Ungueltiger Parameter: '.$name, 400); + + $isNegative = $signed && substr($value, 0, 1) === '-'; + $digits = $isNegative ? substr($value, 1) : $value; + $digits = ltrim($digits, '0'); + if ($digits === '') + $digits = '0'; + $normalizedValue = $isNegative && $digits !== '0' ? '-'.$digits : $digits; + + $integer = filter_var($normalizedValue, FILTER_VALIDATE_INT); + if ($integer === false || (!$signed && $integer < 0)) + show_error('Ungueltiger Parameter: '.$name, 400); + + return $integer; + } + + /** + * Returns null for the legacy wildcard values "" and "0". + * + * @param string $name GET parameter name. + * @return string|null + */ + private function getOptionalFilter($name) + { + $value = $this->input->get($name, true); + if ($value === null || $value === '' || $value === '0') + return null; + + if (!is_string($value) + || !check_utf8($value) + || mb_strlen($value) > 20 + || !preg_match('/^[[:alnum:]_-]+$/u', $value)) + { + show_error('Ungueltiger Parameter: '.$name, 400); + } + + return $value; + } + + /** + * @param string $time PostgreSQL time value. + * @return int|null + */ + private function timeToSeconds($time) + { + if (!preg_match('/^(\d{1,2}):(\d{2})(?::(\d{2})(?:\.\d+)?)?$/', $time, $matches)) + return null; + + $hours = (int)$matches[1]; + $minutes = (int)$matches[2]; + $seconds = isset($matches[3]) ? (int)$matches[3] : 0; + + if ($hours > 23 || $minutes > 59 || $seconds > 59) + return null; + + return $hours * 3600 + $minutes * 60 + $seconds; + } +} diff --git a/application/libraries/KalenderLib.php b/application/libraries/KalenderLib.php index f353d1e06..c047ad4ea 100644 --- a/application/libraries/KalenderLib.php +++ b/application/libraries/KalenderLib.php @@ -52,10 +52,18 @@ class KalenderLib tbl_kalender.typ, tbl_kalender.von, tbl_kalender.bis, + tbl_kalender.insertvon AS created_by_uid, + kalender_ersteller_person.titelpre AS created_by_titelpre, + kalender_ersteller_person.titelpost AS created_by_titelpost, + kalender_ersteller_person.vorname AS created_by_vorname, + kalender_ersteller_person.nachname AS created_by_nachname, tbl_kalender_ort.ort_kurzbz, tbl_kalender_ort.ort_kurzbz as ko_ort_kurzbz, tbl_kalender_ort.location as ko_location, + kalender_ort.bezeichnung AS ko_ort_bezeichnung, + kalender_ort.content_id AS ko_ort_content_id, tbl_lehreinheit.lehreinheit_id, + tbl_lehreinheit.unr, tbl_lehreinheit.lehrveranstaltung_id, tbl_lehreinheit.lehrfach_id, tbl_lehreinheit.lehrform_kurzbz, @@ -64,9 +72,12 @@ class KalenderLib lehrfach.bezeichnung AS lehrfach_bezeichnung, lehrfach.farbe, COALESCE(organisator.uid, tbl_lehreinheitmitarbeiter.mitarbeiter_uid) as mitarbeiter_uid, + COALESCE(reservierung_person.titelpre, tbl_person.titelpre) as titelpre, + COALESCE(reservierung_person.titelpost, tbl_person.titelpost) as titelpost, COALESCE(reservierung_person.vorname, tbl_person.vorname) as vorname, COALESCE(reservierung_person.nachname, tbl_person.nachname) as nachname, COALESCE(reservierung_ma.kurzbz, tbl_mitarbeiter.kurzbz) AS ma_kurzbz, + COALESCE(reservierung_ma.personalnummer, tbl_mitarbeiter.personalnummer) AS personalnummer, teilnehmergruppe.gruppe_kurzbz as teilnehmerg_grp, teilnehmergruppe.bezeichnung as teilnehmerg_grp_bezeichnung, COALESCE ( @@ -78,6 +89,8 @@ class KalenderLib tbl_kalender_event.beschreibung, tbl_kalender_event.titel, teilmitarbeiter.kurzbz as teilnehmer_kurzbz, + teilperson.titelpre as teilnehmer_titelpre, + teilperson.titelpost as teilnehmer_titelpost, teilperson.vorname as teilnehmer_vorname, teilperson.nachname as teilnehmer_nachname, teilbenutzer.uid as teilnehmer_uid, @@ -103,6 +116,12 @@ class KalenderLib $this->_ci->KalenderModel->addJoin('lehre.tbl_kalender_lehreinheit', 'tbl_kalender.kalender_id = tbl_kalender_lehreinheit.kalender_id', 'LEFT'); $this->_ci->KalenderModel->addJoin('lehre.tbl_kalender_event', 'tbl_kalender.kalender_id = tbl_kalender_event.kalender_id', 'LEFT'); + $this->_ci->KalenderModel->addJoin('public.tbl_benutzer kalender_ersteller', 'tbl_kalender.insertvon = kalender_ersteller.uid', 'LEFT'); + $this->_ci->KalenderModel->addJoin( + 'public.tbl_person kalender_ersteller_person', + 'kalender_ersteller.person_id = kalender_ersteller_person.person_id', + 'LEFT' + ); $this->_ci->KalenderModel->addJoin('lehre.tbl_kalender_event_teilnehmer organisator', 'tbl_kalender_event.kalender_id = organisator.kalender_id AND organisator.rolle_kurzbz = \'organisator\'', 'LEFT'); $this->_ci->KalenderModel->addJoin('lehre.tbl_kalender_event_teilnehmer teilnehmer', 'tbl_kalender_event.kalender_id = teilnehmer.kalender_id AND teilnehmer.rolle_kurzbz = \'teilnehmer\'', 'LEFT'); @@ -124,6 +143,7 @@ class KalenderLib $this->_ci->KalenderModel->addJoin('lehre.tbl_lehrveranstaltung', 'tbl_lehreinheit.lehrveranstaltung_id = tbl_lehrveranstaltung.lehrveranstaltung_id', 'LEFT'); $this->_ci->KalenderModel->addJoin('lehre.tbl_lehrveranstaltung lehrfach', 'tbl_lehreinheit.lehrfach_id = lehrfach.lehrveranstaltung_id', 'LEFT'); $this->_ci->KalenderModel->addJoin('lehre.tbl_kalender_ort', 'tbl_kalender.kalender_id = tbl_kalender_ort.kalender_id', 'LEFT'); + $this->_ci->KalenderModel->addJoin('public.tbl_ort kalender_ort', 'tbl_kalender_ort.ort_kurzbz = kalender_ort.ort_kurzbz', 'LEFT'); $this->_ci->KalenderModel->addJoin('lehre.tbl_lehreinheitmitarbeiter', 'tbl_lehreinheit.lehreinheit_id = tbl_lehreinheitmitarbeiter.lehreinheit_id', 'LEFT'); $this->_ci->KalenderModel->addJoin('public.tbl_mitarbeiter', 'tbl_mitarbeiter.mitarbeiter_uid = tbl_lehreinheitmitarbeiter.mitarbeiter_uid', 'LEFT'); @@ -131,8 +151,12 @@ class KalenderLib $this->_ci->KalenderModel->addJoin('public.tbl_person', 'tbl_person.person_id = tbl_benutzer.person_id', 'LEFT'); $this->_ci->KalenderModel->addJoin('public.tbl_mitarbeiter reservierung_ma', 'reservierung_ma.mitarbeiter_uid = organisator.uid', 'LEFT'); - $this->_ci->KalenderModel->addJoin('public.tbl_benutzer resevierung_benutzer', 'reservierung_ma.mitarbeiter_uid = resevierung_benutzer.uid', 'LEFT'); - $this->_ci->KalenderModel->addJoin('public.tbl_person reservierung_person', 'reservierung_person.person_id = resevierung_benutzer.person_id', 'LEFT'); + $this->_ci->KalenderModel->addJoin('public.tbl_benutzer reservierung_benutzer', 'organisator.uid = reservierung_benutzer.uid', 'LEFT'); + $this->_ci->KalenderModel->addJoin( + 'public.tbl_person reservierung_person', + 'reservierung_person.person_id = reservierung_benutzer.person_id', + 'LEFT' + ); $this->_ci->KalenderModel->addJoin('lehre.tbl_lehreinheitgruppe', 'tbl_lehreinheit.lehreinheit_id = tbl_lehreinheitgruppe.lehreinheit_id', 'LEFT'); $this->_ci->KalenderModel->addJoin('public.tbl_gruppe le_gruppe', 'tbl_lehreinheitgruppe.gruppe_kurzbz = le_gruppe.gruppe_kurzbz', 'LEFT'); @@ -179,19 +203,32 @@ class KalenderLib 'tooltip' => 'tip', 'status_kurzbz' => $row->status_kurzbz, 'ort_kurzbz' => [], + 'ort_details' => [], + 'locations' => [], 'ko_ort_kurzbz' => isset($row->ko_ort_kurzbz) ? $row->ko_ort_kurzbz : '', 'ko_location' => isset($row->ko_location) ? $row->ko_location : '', 'lehrform' => isset($row->lehrform_kurzbz) ? $row->lehrform_kurzbz : '', 'lehrfach' => isset($row->lehrfach_kurzbz) ? $row->lehrfach_kurzbz : '', 'lehrfach_bez' => isset($row->lehrfach_bezeichnung) ? $row->lehrfach_bezeichnung : '', + 'lehrfach_details' => [], 'farbe' => isset($row->farbe) ? $row->farbe : '', 'lehrveranstaltung_id' => $row->lehrveranstaltung_id, 'organisationseinheit' => isset($row->oe_kurzbz) ? $row->oe_kurzbz : '', 'lehreinheit_id' => [], + 'unr' => [], 'lektor' => [], 'teilnehmer_gruppe' => [], 'teilnehmer_person' => [], 'gruppe' => [], + 'created_by' => isset($row->created_by_uid) && $row->created_by_uid !== '' + ? [ + 'uid' => $row->created_by_uid, + 'titelpre' => $row->created_by_titelpre, + 'titelpost' => $row->created_by_titelpost, + 'vorname' => $row->created_by_vorname, + 'nachname' => $row->created_by_nachname, + ] + : null, 'titel' => isset($row->titel) ? $row->titel : '', 'beschreibung' => isset($row->beschreibung) ? $row->beschreibung : '', 'topic' => [], @@ -204,9 +241,58 @@ class KalenderLib if ($row->lehreinheit_id && !in_array($row->lehreinheit_id, $events[$id]->lehreinheit_id)) $events[$id]->lehreinheit_id[] = $row->lehreinheit_id; + if (isset($row->unr) && $row->unr !== null && $row->unr !== '' + && !in_array($row->unr, $events[$id]->unr)) + { + $events[$id]->unr[] = $row->unr; + } + if (isset($row->ort_kurzbz) && $row->ort_kurzbz !== '' && !in_array($row->ort_kurzbz, $events[$id]->ort_kurzbz)) $events[$id]->ort_kurzbz[] = $row->ort_kurzbz; + if (isset($row->ort_kurzbz) && $row->ort_kurzbz !== '' + && !in_array( + $row->ort_kurzbz, + array_column($events[$id]->ort_details, 'ort_kurzbz') + )) + { + $events[$id]->ort_details[] = [ + 'ort_kurzbz' => $row->ort_kurzbz, + 'bezeichnung' => isset($row->ko_ort_bezeichnung) + ? $row->ko_ort_bezeichnung + : '', + 'content_id' => isset($row->ko_ort_content_id) + ? $row->ko_ort_content_id + : null, + ]; + } + + if (isset($row->ko_location) && $row->ko_location !== '' + && !in_array($row->ko_location, $events[$id]->locations)) + { + $events[$id]->locations[] = $row->ko_location; + } + + if (isset($row->lehrfach_id) && $row->lehrfach_id + && !in_array( + $row->lehrfach_id, + array_column($events[$id]->lehrfach_details, 'lehrfach_id') + )) + { + $events[$id]->lehrfach_details[] = [ + 'lehrfach_id' => $row->lehrfach_id, + 'kurzbz' => isset($row->lehrfach_kurzbz) + ? $row->lehrfach_kurzbz + : '', + 'bezeichnung' => isset($row->lehrfach_bezeichnung) + ? $row->lehrfach_bezeichnung + : '', + 'lehrform' => isset($row->lehrform_kurzbz) + ? $row->lehrform_kurzbz + : '', + ]; + } + $topic = trim((isset($row->lehrfach_kurzbz) ? $row->lehrfach_kurzbz : '').' '.(isset($row->lehrform_kurzbz) ? $row->lehrform_kurzbz : '')); if ($topic !== '' && !in_array($topic, $events[$id]->topic)) $events[$id]->topic[] = $topic; @@ -217,16 +303,19 @@ class KalenderLib { $events[$id]->lektor[] = [ 'mitarbeiter_uid' => $row->mitarbeiter_uid, + 'titelpre' => $row->titelpre, + 'titelpost' => $row->titelpost, 'vorname' => $row->vorname, 'nachname' => $row->nachname, 'kurzbz' => $row->ma_kurzbz, + 'personalnummer' => $row->personalnummer, ]; } } if ($row->verband_grp) { - if (!in_array($row->teilnehmerg_grp, array_column($events[$id]->teilnehmer_gruppe, 'gruppe_kurzbz'))) + if (!in_array($row->verband_grp, array_column($events[$id]->teilnehmer_gruppe, 'gruppe_kurzbz'))) { $events[$id]->teilnehmer_gruppe[] = [ 'gruppe_kurzbz' => $row->verband_grp, @@ -240,7 +329,7 @@ class KalenderLib { $events[$id]->teilnehmer_gruppe[] = [ 'gruppe_kurzbz' => $row->teilnehmerg_grp, - 'bezeichnung' => 222, + 'bezeichnung' => $row->teilnehmerg_grp_bezeichnung, ]; } } @@ -267,6 +356,8 @@ class KalenderLib { $events[$id]->teilnehmer_person[] = [ 'uid' => $row->teilnehmer_uid, + 'titelpre' => $row->teilnehmer_titelpre, + 'titelpost' => $row->teilnehmer_titelpost, 'vorname' => $row->teilnehmer_vorname, 'nachname' => $row->teilnehmer_nachname, 'kurzbz' => $row->teilnehmer_kurzbz, diff --git a/application/libraries/LvPlanHTMLDetailLib.php b/application/libraries/LvPlanHTMLDetailLib.php new file mode 100644 index 000000000..2b7ff4be3 --- /dev/null +++ b/application/libraries/LvPlanHTMLDetailLib.php @@ -0,0 +1,776 @@ +ci =& get_instance(); + $this->legacyPhrases = new phrasen(getSprache()); + } + + /** + * Builds a timetable detail document for one date and hour. + * + * @param string $datum Date in YYYY-MM-DD format. + * @param array $hour Selected timetable hour. + * @param string $type Timetable type. + * @param array $filters Type-specific filters. + * @return string + */ + public function getContent($datum, array $hour, $type, array $filters) + { + if (!in_array($type, array('student', 'lektor', 'verband', 'ort'), true)) + throw new InvalidArgumentException('Unsupported timetable type'); + foreach (array('number', 'begin', 'end') as $property) + { + if (!isset($hour[$property]) || !is_scalar($hour[$property])) + throw new InvalidArgumentException('Invalid timetable hour'); + } + + $events = $this->loadEvents($datum, $type, $filters); + $events = $this->filterEventsByHour($events, $datum, $hour); + if ($type === 'verband' + && isset($filters['ort_kurzbz']) + && $filters['ort_kurzbz'] !== '') + { + $events = $this->filterEventsByRoom( + $events, + $filters['ort_kurzbz'] + ); + } + $sections = array( + 'teaching' => array(), + 'reservations' => array(), + 'events' => array() + ); + + foreach ($events as $event) + { + if ($event->type === 'lehreinheit') + $sections['teaching'][] = $event; + elseif ($event->type === 'reservierung') + $sections['reservations'][] = $event; + else + $sections['events'][] = $event; + } + + return $this->renderDocument($datum, $hour, $sections); + } + + /** + * Loads the same published calendar data used by the HTML preview. + * + * @return array + */ + private function loadEvents($datum, $type, array $filters) + { + $uid = isset($filters['uid']) && is_string($filters['uid']) + ? $filters['uid'] + : getAuthUID(); + $this->ci->load->library('KalenderLib', array('uid' => $uid)); + + if ($type === 'verband') + { + $events = $this->ci->kalenderlib->getPlanForVerband( + $datum, + $datum, + $filters['stg_kz'], + $filters['sem'], + isset($filters['ver']) ? $filters['ver'] : null, + isset($filters['grp']) ? $filters['grp'] : null + ); + } + elseif ($type === 'ort') + { + $events = $this->ci->kalenderlib->getPlanForRoom( + $datum, + $datum, + $filters['ort_kurzbz'] + ); + } + elseif ($type === 'lektor') + { + $events = $this->ci->kalenderlib->getPlanForLecturerByLecturer( + $datum, + $datum, + $filters['uid'] + ); + } + else + { + $events = $this->ci->kalenderlib->getPlanForStudentByStudent( + $datum, + $datum, + $filters['uid'] + ); + } + + return is_array($events) ? $events : array(); + } + + /** + * Keeps events that overlap the selected timetable hour. + * + * @return array + */ + private function filterEventsByHour(array $events, $datum, array $hour) + { + $timezone = new DateTimeZone(self::TIMEZONE); + $hourStart = DateTime::createFromFormat( + '!Y-m-d H:i:s', + $datum.' '.$this->normalizeTime($hour['begin']), + $timezone + ); + $hourEnd = DateTime::createFromFormat( + '!Y-m-d H:i:s', + $datum.' '.$this->normalizeTime($hour['end']), + $timezone + ); + + if ($hourStart === false || $hourEnd === false || $hourEnd <= $hourStart) + throw new InvalidArgumentException('Invalid timetable hour range'); + + $matches = array(); + foreach ($events as $event) + { + $start = $this->getEventDateTime($event, 'isostart', 'datum', 'beginn'); + $end = $this->getEventDateTime($event, 'isoend', 'datum', 'ende'); + + if ($start === null || $end === null || $end <= $start) + continue; + if ($start < $hourEnd && $end > $hourStart) + { + $matches[] = array( + 'event' => $event, + 'start' => $start, + 'end' => $end + ); + } + } + + usort($matches, function ($first, $second) { + $comparison = $first['start']->getTimestamp() + <=> $second['start']->getTimestamp(); + if ($comparison !== 0) + return $comparison; + + $firstId = isset($first['event']->kalender_id) + ? (int)$first['event']->kalender_id + : 0; + $secondId = isset($second['event']->kalender_id) + ? (int)$second['event']->kalender_id + : 0; + return $firstId <=> $secondId; + }); + + return array_map(function ($match) { + return $match['event']; + }, $matches); + } + + /** + * Keeps Verband events assigned to the room selected in the preview cell. + * + * @return array + */ + private function filterEventsByRoom(array $events, $ortKurzbz) + { + $matches = array(); + foreach ($events as $event) + { + $rooms = isset($event->ort_kurzbz) + ? $event->ort_kurzbz + : array(); + if (!is_array($rooms)) + $rooms = array($rooms); + + foreach ($rooms as $room) + { + if (is_scalar($room) && (string)$room === (string)$ortKurzbz) + { + $matches[] = $event; + continue 2; + } + } + + if (!isset($event->ort_details) || !is_array($event->ort_details)) + continue; + + foreach ($event->ort_details as $room) + { + if ($this->getNestedValue($room, 'ort_kurzbz') === (string)$ortKurzbz) + { + $matches[] = $event; + continue 2; + } + } + } + + return $matches; + } + + /** + * Renders the complete HTML document. + */ + private function renderDocument($datum, array $hour, array $sections) + { + $title = $this->phrase('lvplan', 'lehrveranstaltungsplanDetails'); + $html = '
'; + $html .= ''; + $html .= '0 ' + .$this->escape($this->phrase('global', 'termine')).'
'; + } + + if (defined('MAIL_LVPLAN')) + { + $html .= ''.$this->phrase( + 'lvplan', + 'FragenZuLvPlan', + array(constant('MAIL_LVPLAN')) + ).'
'; + } + + return $html.''; + } + + /** + * Renders teaching-unit details. + */ + private function renderTeachingTable(array $events) + { + $html = '| '.$this->escape($this->phrase($heading[0], $heading[1])).' | '; + } + $html .= '
|---|
| '.$this->escape($this->phrase($heading[0], $heading[1])).' | '; + } + $html .= '
|---|
| '.$this->escape($this->phrase($heading[0], $heading[1])).' | '; + } + $html .= '
|---|