Mods found during manual testing

This commit is contained in:
Ivymaster
2026-07-29 14:10:59 +02:00
parent 8a3909f4d5
commit 3076c3b2c8
14 changed files with 471 additions and 81 deletions
+1 -1
View File
@@ -125,7 +125,7 @@ $route['api/frontend/v1/stv/[sS]tudents/([WS]S[0-9]{4})/person/(:num)'] = 'api/f
$route['lvplan/caldav'] = 'LvPlanCalDAV';
$route['lvplan/caldav/.*'] = 'LvPlanCalDAV';
$route['lvplan/ical/subscription'] = 'LvPlanICALSubscription';
$route['lvplan/ical/subscription/google'] = 'LvPlanICALSubscription';
$route['lvplan/ical/download'] = 'LvPlanICALDownload';
$route['lvplan/csv/download'] = 'LvPlanCSVDownload';
$route['lvplan/excel/download'] = 'LvPlanExcelDownload';
@@ -58,7 +58,7 @@ class LvPlanCSVDownload extends Auth_Controller
$verbandFilters
);
$filename = 'FH-Kalender_'.date('m_Y', $begin);
$filename = 'FH-Kalender_'.date('m_Y');
if ($target === 'outlook')
$filename .= '_outlook';
$filename .= '.csv';
@@ -122,7 +122,7 @@ class LvPlanCSVDownload extends Auth_Controller
private function getVerbandFilters()
{
$stgKz = $this->getInteger('stg_kz', true);
$semester = $this->getInteger('sem', false);
$semester = $this->getInteger('sem', false, true);
$verband = $this->getOptionalFilter('ver');
$gruppe = $this->getOptionalFilter('grp');
@@ -141,9 +141,12 @@ class LvPlanCSVDownload extends Auth_Controller
);
}
private function getInteger($name, $signed)
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);
@@ -56,7 +56,7 @@ class LvPlanExcelDownload extends Auth_Controller
$verbandFilters
);
$filename = 'FH-Kalender_'.date('m_Y', $begin).'.xls';
$filename = 'Termine_'.date('d_m_Y').'.xls';
$this->output
->set_content_type('application/vnd.ms-excel')
@@ -117,7 +117,7 @@ class LvPlanExcelDownload extends Auth_Controller
private function getVerbandFilters()
{
$stgKz = $this->getInteger('stg_kz', true);
$semester = $this->getInteger('sem', false);
$semester = $this->getInteger('sem', false, true);
$verband = $this->getOptionalFilter('ver');
$gruppe = $this->getOptionalFilter('grp');
@@ -136,9 +136,12 @@ class LvPlanExcelDownload extends Auth_Controller
);
}
private function getInteger($name, $signed)
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);
+50 -5
View File
@@ -8,6 +8,8 @@ require_once(FHCPATH.'include/functions.inc.php');
class LvPlanHTMLPreview extends Auth_Controller
{
const MAX_DATE_RANGE = 34560000; // 400 days
const DEFAULT_RESERVATION_MIN_DAYS = 1;
const DEFAULT_RESERVATION_END_DATE = '2099-01-01';
/**
* Object initialization.
@@ -22,6 +24,14 @@ class LvPlanHTMLPreview extends Auth_Controller
$this->load->model('crm/Student_model', 'StudentModel');
$this->load->model('organisation/Studiengang_model', 'StudiengangModel');
$this->load->model('ressource/Ort_model', 'OrtModel');
$this->loadPhrases(
array(
'lvplan' => array(
'room_not_reservable',
'room_reservation_restricted_to_period'
)
)
);
}
/**
@@ -147,7 +157,7 @@ class LvPlanHTMLPreview extends Auth_Controller
if (!is_string($ortKurzbz) || $ortKurzbz === '' || !check_utf8($ortKurzbz))
show_error('Ungueltiger Parameter: ort_kurzbz', 400);
$this->OrtModel->addSelect('ort_kurzbz, bezeichnung');
$this->OrtModel->addSelect('ort_kurzbz, bezeichnung, reservieren');
$room = $this->OrtModel->load($ortKurzbz);
if (isError($room))
@@ -157,10 +167,41 @@ class LvPlanHTMLPreview extends Auth_Controller
$rooms = getData($room);
$roomData = reset($rooms);
$reservationMinDays = defined('RES_TAGE_LEKTOR_MIN')
? (int)constant('RES_TAGE_LEKTOR_MIN')
: self::DEFAULT_RESERVATION_MIN_DAYS;
$reservationEndDate = defined('RES_TAGE_LEKTOR_BIS')
? constant('RES_TAGE_LEKTOR_BIS')
: self::DEFAULT_RESERVATION_END_DATE;
$reservationStart = jump_day(
time(),
$reservationMinDays - 1
);
$reservationEnd = strtotime($reservationEndDate);
return array(
'ort_kurzbz' => $ortKurzbz,
'bezeichnung' => isset($roomData->bezeichnung) ? $roomData->bezeichnung : ''
'bezeichnung' => isset($roomData->bezeichnung) ? $roomData->bezeichnung : '',
'room_reservable' => isset($roomData->reservieren)
&& $roomData->reservieren === true,
'room_reservation_permitted' => $this->permissionlib->isBerechtigt(
'lehre/reservierung:begrenzt',
'sui'
),
'room_reservation_start' => $reservationStart,
'room_reservation_end' => $reservationEnd,
'room_not_reservable_message' => $this->p->t(
'lvplan',
'room_not_reservable'
),
'room_reservation_restricted_message' => $this->p->t(
'lvplan',
'room_reservation_restricted_to_period',
array(
'start_date' => date('d.m.Y', $reservationStart),
'end_date' => date('d.m.Y', $reservationEnd)
)
)
);
}
@@ -172,7 +213,7 @@ class LvPlanHTMLPreview extends Auth_Controller
// Legacy wochenplan links always carry pers_uid. It does not filter or
// authorize a Lehrverband plan and is intentionally ignored here.
$stgKz = $this->getInteger('stg_kz', true);
$semester = $this->getInteger('sem', false);
$semester = $this->getInteger('sem', false, true);
$verband = $this->getOptionalFilter('ver');
$gruppe = $this->getOptionalFilter('grp');
@@ -217,11 +258,15 @@ class LvPlanHTMLPreview extends Auth_Controller
/**
* @param string $name GET parameter name.
* @param bool $signed Whether negative values are allowed.
* @return int
* @param bool $optional Whether the parameter may be omitted or empty.
* @return int|null
*/
private function getInteger($name, $signed)
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))
@@ -119,7 +119,7 @@ class LvPlanICALDownload extends Auth_Controller
private function getVerbandFilters()
{
$stgKz = $this->getInteger('stg_kz', true);
$semester = $this->getInteger('sem', false);
$semester = $this->getInteger('sem', false, true);
$verband = $this->getOptionalFilter('ver');
$gruppe = $this->getOptionalFilter('grp');
@@ -138,9 +138,12 @@ class LvPlanICALDownload extends Auth_Controller
);
}
private function getInteger($name, $signed)
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);
@@ -63,7 +63,12 @@ class LvPlanICALSubscription extends FHC_Controller
$content = $this->lvplanicallib->getContent(
$this->uid,
$this->begin,
$this->ende
$this->ende,
null,
2,
null,
null,
'FH-Kalender'
);
$filename = 'FH-Kalender_'.date('m_Y', $this->begin).'_ical.ics';
@@ -264,6 +264,58 @@ class MigrateKalender extends CLI_Controller
}
}
/**
* Removes calendar entries created by the Stundenplan and Reservierung migrations,
* so that both imports can be started from scratch.
*/
public function resetBeforeNewStundenplandevImport()
{
$db = new DB_Model();
$db->db->trans_start();
$db->db->query('DELETE FROM sync.tbl_stundenplandev_kalender');
$db->db->query('DELETE FROM sync.tbl_reservierung_kalender');
$db->db->query("DELETE FROM lehre.tbl_kalender_event_teilnehmer
WHERE kalender_id IN (
SELECT kalender_id
FROM lehre.tbl_kalender
WHERE typ = 'reservierung'
)");
$db->db->query("DELETE FROM lehre.tbl_kalender_event
WHERE kalender_id IN (
SELECT kalender_id
FROM lehre.tbl_kalender
WHERE typ = 'reservierung'
)");
$db->db->query("DELETE FROM lehre.tbl_kalender_lehreinheit
WHERE kalender_id IN (
SELECT kalender_id
FROM lehre.tbl_kalender
WHERE typ = 'lehreinheit' OR typ = 'reservierung'
)");
$db->db->query("DELETE FROM lehre.tbl_kalender_ort
WHERE kalender_id IN (
SELECT kalender_id
FROM lehre.tbl_kalender
WHERE typ IN ('lehreinheit', 'reservierung')
)");
$db->db->query("DELETE FROM lehre.tbl_kalender
WHERE typ IN ('lehreinheit', 'reservierung')");
$db->db->trans_complete();
if ($db->db->trans_status() === false)
return error('Reset before new import failed');
return success('Migration data reset successfully');
}
public function migrateStundenplanBetriebsmittelEntries() {
$this->setKalendarEntriesGroupIDs();
$this->setKalendarEntriesGroupIDsForChildren();
+26 -12
View File
@@ -63,11 +63,12 @@ class KalenderLib
lehrfach.kurzbz AS lehrfach_kurzbz,
lehrfach.bezeichnung AS lehrfach_bezeichnung,
lehrfach.farbe,
COALESCE(orginasator.uid, tbl_lehreinheitmitarbeiter.mitarbeiter_uid) as mitarbeiter_uid,
COALESCE(organisator.uid, tbl_lehreinheitmitarbeiter.mitarbeiter_uid) as mitarbeiter_uid,
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,
teilnehmergruppe.gruppe_kurzbz as teilnehmerg_grp,
teilnehmergruppe.bezeichnung as teilnehmerg_grp_bezeichnung,
COALESCE (
UPPER(tbl_studiengang.typ || tbl_studiengang.kurzbz) ||
COALESCE(verbandgruppe.semester::varchar, \'\') ||
@@ -103,7 +104,7 @@ 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('lehre.tbl_kalender_event_teilnehmer orginasator', 'tbl_kalender_event.kalender_id = orginasator.kalender_id AND orginasator.rolle_kurzbz = \'organisator\'', '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');
$this->_ci->KalenderModel->addJoin('public.tbl_benutzer teilbenutzer', 'teilnehmer.uid = teilbenutzer.uid', 'LEFT');
@@ -129,7 +130,7 @@ class KalenderLib
$this->_ci->KalenderModel->addJoin('public.tbl_benutzer', 'tbl_mitarbeiter.mitarbeiter_uid = tbl_benutzer.uid', 'LEFT');
$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 = orginasator.uid', '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');
@@ -239,6 +240,7 @@ class KalenderLib
{
$events[$id]->teilnehmer_gruppe[] = [
'gruppe_kurzbz' => $row->teilnehmerg_grp,
'bezeichnung' => 222,
];
}
}
@@ -307,7 +309,7 @@ class KalenderLib
$this->_ci->KalenderModel->db->where('tbl_kalender_ort.ort_kurzbz', $ort);
$this->_ci->KalenderModel->db->where('tbl_kalender.status_kurzbz', 'live');
$this->_ci->KalenderModel->addOrder('tbl_kalender.eindeutige_gruppen_id', 'DESC');
$this->_ci->KalenderModel->addOrder('tbl_kalender.von', 'ASC');
$data = $this->_ci->KalenderModel->load();
return $this->_mapEvents($data, false);
@@ -501,13 +503,13 @@ class KalenderLib
return $this->_mapEvents($data);
}
public function getPlanForVerband($start_date, $end_date, $studiengangKz, $semester, $verband = null, $gruppe = null)
public function getPlanForVerband($start_date, $end_date, $studiengangKz, $semester = null, $verband = null, $gruppe = null)
{
$this->_getBasePlan($start_date, $end_date);
$db = $this->_ci->KalenderModel->db;
$escapedStudiengangKz = $db->escape($studiengangKz);
$escapedSemester = $db->escape($semester);
$escapedSemester = is_null($semester) ? null : $db->escape($semester);
if (is_null($verband) || trim((string)$verband) === '' || (string)$verband === '0')
$verband = null;
@@ -523,9 +525,13 @@ class KalenderLib
) {
$lehrverbandConditions = array(
$assignmentAlias.'.gruppe_kurzbz IS NULL',
$assignmentAlias.'.studiengang_kz = '.$escapedStudiengangKz,
'('.$assignmentAlias.'.semester = '.$escapedSemester.' OR '.$assignmentAlias.'.semester IS NULL)'
$assignmentAlias.'.studiengang_kz = '.$escapedStudiengangKz
);
if (!is_null($escapedSemester))
{
$lehrverbandConditions[] = '('.$assignmentAlias.'.semester = '
.$escapedSemester.' OR '.$assignmentAlias.'.semester IS NULL)';
}
if (!is_null($verband))
{
@@ -545,14 +551,22 @@ class KalenderLib
OR '.$assignmentAlias.'.gruppe::text = \'0\')';
}
$specialGroupConditions = array(
$specialGroupAlias.'.gruppe_kurzbz = '.$assignmentAlias.'.gruppe_kurzbz',
$specialGroupAlias.'.studiengang_kz = '.$escapedStudiengangKz,
$specialGroupAlias.'.direktinskription = FALSE'
);
if (!is_null($escapedSemester))
{
$specialGroupConditions[] = '('.$specialGroupAlias.'.semester = '
.$escapedSemester.' OR '.$specialGroupAlias.'.semester IS NULL)';
}
$specialGroupCondition = $assignmentAlias.'.gruppe_kurzbz IS NOT NULL
AND EXISTS (
SELECT 1
FROM public.tbl_gruppe '.$specialGroupAlias.'
WHERE '.$specialGroupAlias.'.gruppe_kurzbz = '.$assignmentAlias.'.gruppe_kurzbz
AND '.$specialGroupAlias.'.studiengang_kz = '.$escapedStudiengangKz.'
AND ('.$specialGroupAlias.'.semester = '.$escapedSemester.' OR '.$specialGroupAlias.'.semester IS NULL)
AND '.$specialGroupAlias.'.direktinskription = FALSE
WHERE '.implode("\n\t\t\t\t\t\tAND ", $specialGroupConditions).'
)';
return "((
+41 -18
View File
@@ -133,7 +133,7 @@ class LvPlanCSVLib
foreach ($events as $event)
{
$eventData = $this->getEventData($event);
$eventData = $this->getEventData($event, $type);
if ($eventData === null)
continue;
@@ -151,7 +151,7 @@ class LvPlanCSVLib
* @param object $event Calendar event.
* @return array|null Normalized CSV values or null for invalid dates.
*/
private function getEventData($event)
private function getEventData($event, $type = null)
{
$start = $this->createDateTime(isset($event->isostart) ? $event->isostart : null);
$end = $this->createDateTime(isset($event->isoend) ? $event->isoend : null);
@@ -164,38 +164,38 @@ class LvPlanCSVLib
$end->setTimezone($timezone);
$isReservation = isset($event->type) && $event->type === 'reservierung';
$title = $isReservation
? (isset($event->titel) ? $event->titel : '')
: $this->joinValues(isset($event->topic) ? $event->topic : array());
if ($type === 'verband') {
$title = '';
}
$location = $this->joinValues(
isset($event->ort_kurzbz) ? $event->ort_kurzbz : array(),
' / '
);
$groups = $this->getGroups($event);
$lecturers = $this->getLecturers($event);
$participants = $this->getParticipants($event);
if ($isReservation)
{
$description = isset($event->beschreibung) ? $event->beschreibung : '';
}
else
{
$descriptionParts = array(
$descriptionParts = array(
'Stundenplan',
$title,
isset($event->lehrfach_bez) ? $event->lehrfach_bez : '',
$lecturers,
$isReservation ? '' : (isset($event->lehrfach_bez) ? $event->lehrfach_bez : '') ,
$isReservation ? $participants : $lecturers,
$groups,
$location,
isset($event->beschreibung) ? $event->beschreibung : ''
);
$description = implode("\r\n", array_values(array_filter(
$descriptionParts,
function ($value) {
return $value !== '';
}
)));
}
$description = implode("\r\n", array_values(array_filter(
$descriptionParts,
function ($value) {
return $value !== '';
}
)));
return array(
'title' => $title,
@@ -288,6 +288,8 @@ class LvPlanCSVLib
}
}
sort($lecturers, SORT_NATURAL | SORT_FLAG_CASE);
return implode(' / ', array_unique($lecturers));
}
@@ -317,6 +319,8 @@ class LvPlanCSVLib
}
}
sort($groups, SORT_NATURAL | SORT_FLAG_CASE);
return implode(' / ', array_unique($groups));
}
@@ -370,4 +374,23 @@ class LvPlanCSVLib
return null;
}
}
private function getParticipants($event)
{
$values = array();
if (!isset($event->teilnehmer_person) || !is_array($event->teilnehmer_person))
return '';
foreach ($event->teilnehmer_person as $participant)
{
$value = $participant['kurzbz'];
if ($value !== '')
$values[] = $value;
}
sort($values, SORT_NATURAL | SORT_FLAG_CASE);
return implode(', ', array_unique($values));
}
}
+45 -6
View File
@@ -164,7 +164,7 @@ class LvPlanExcelLib
$isReservation = isset($event->type) && $event->type === 'reservierung';
$subject = $isReservation
? (isset($event->titel) ? $event->titel : '')
? $event->titel . " " . $this->joinValues(isset($event->ort_kurzbz) ? $event->ort_kurzbz : array()) . " - " . $this->getGroups($event)
: (isset($event->lehrfach_bez) ? $event->lehrfach_bez : '');
if ($subject === '')
@@ -179,12 +179,12 @@ class LvPlanExcelLib
$start->format('H:i:s'),
$end->format('H:i:s'),
$this->joinValues(isset($event->ort_kurzbz) ? $event->ort_kurzbz : array()),
$this->getLecturers($event),
!$isReservation ? $this->getParticipants($event) : $this->getLecturers($event),
$this->getGroups($event),
$subject,
$note,
$this->findHour($start->format('H:i:s'), $hourRaster),
$this->findHour($end->format('H:i:s'), $hourRaster)
$this->findHour($start->format('H:i:s'), $hourRaster, "start"),
$this->findHour($end->format('H:i:s'), $hourRaster, "end")
);
}
@@ -274,6 +274,8 @@ class LvPlanExcelLib
}
}
sort($lecturers, SORT_NATURAL | SORT_FLAG_CASE);
return implode(', ', array_unique($lecturers));
}
@@ -303,6 +305,8 @@ class LvPlanExcelLib
}
}
sort($groups, SORT_NATURAL | SORT_FLAG_CASE);
return implode(',', array_unique($groups));
}
@@ -311,7 +315,7 @@ class LvPlanExcelLib
* @param array $hourRaster Timetable-hour raster.
* @return string|int Timetable-hour number or an empty string.
*/
private function findHour($time, array $hourRaster)
private function findHour($time, array $hourRaster, string $context = '')
{
$seconds = $this->timeToSeconds($time);
if ($seconds === null || count($hourRaster) === 0)
@@ -324,7 +328,10 @@ class LvPlanExcelLib
foreach ($hourRaster as $hour)
{
if ($seconds <= $hour['end'])
if (
($seconds < $hour['end'] && $context === "start") ||
($seconds <= $hour['end'] && $context === "end")
)
return $hour['stunde'];
}
@@ -417,4 +424,36 @@ class LvPlanExcelLib
return null;
}
}
private function getParticipants($event)
{
$values = array();
if (!isset($event->teilnehmer_person) || !is_array($event->teilnehmer_person))
return '';
foreach ($event->teilnehmer_person as $participant)
{
$value = $participant['vorname'] . ' ' . $participant['nachname'];
if ($value !== '')
$values[] = $value;
}
sort($values, SORT_NATURAL | SORT_FLAG_CASE);
return implode(', ', array_unique($values));
}
private function getNestedValue($value, array $properties)
{
foreach ($properties as $property)
{
if (is_array($value) && isset($value[$property]))
return (string)$value[$property];
if (is_object($value) && isset($value->{$property}))
return (string)$value->{$property};
}
return is_scalar($value) ? (string)$value : '';
}
}
+87 -9
View File
@@ -9,6 +9,8 @@ class LvPlanHTMLLib
const DETAIL_WINDOW_NAME = 'Details';
const DETAIL_WINDOW_OPTIONS = 'height=320,width=550,left=0,top=0,hotkeys=0,resizable=yes,status=no,scrollbars=no,toolbar=no,location=no,menubar=no,dependent=yes';
const ICON_EXAM = '📝';
private $ci;
public function __construct()
@@ -39,7 +41,7 @@ class LvPlanHTMLLib
$hourRaster = $this->loadHourRaster();
$normalizedEvents = $this->normalizeEvents($events);
return $this->renderDocument(
$begin,
$ende,
@@ -180,9 +182,16 @@ class LvPlanHTMLLib
continue;
$isReservation = isset($event->type) && $event->type === 'reservierung';
$title = $isReservation
? $this->getScalar($event, 'titel')
: $this->joinValue(isset($event->topic) ? $event->topic : '');
$title = '';
if ($isReservation) {
$title = $this->getScalar($event, 'titel');
} else {
$title = $this->joinValue(isset($event->topic) ? $event->topic : '');
if ($title !== '' && strtoupper($event->lehrform) === 'EXAM')
$title = $title . ' ' . self::ICON_EXAM;
}
if ($title === '')
$title = $this->getScalar($event, 'lehrfach_bez');
@@ -200,6 +209,7 @@ class LvPlanHTMLLib
isset($event->ort_kurzbz) ? $event->ort_kurzbz : ''
),
'lecturers' => $this->getLecturers($event),
'participants' => $this->getParticipants($event),
'groups' => $this->getGroups($event),
'note' => $this->getEventNote($event, $isReservation),
'color' => $this->getColor($event)
@@ -227,13 +237,14 @@ class LvPlanHTMLLib
$timezone = new DateTimeZone(self::TIMEZONE);
$rangeStart = (new DateTime('@'.$begin))->setTimezone($timezone)->setTime(0, 0, 0);
$rangeEnd = (new DateTime('@'.$ende))->setTimezone($timezone)->setTime(23, 59, 59);
$weeks = $this->buildWeeks($rangeStart, $rangeEnd, $hourRaster, $events);
$title = $this->getDocumentTitle($type, $filters);
$appRoot = $this->getAppRoot();
$html = '<!DOCTYPE html><html lang="de"><head><meta charset="UTF-8">';
$html .= '<meta name="viewport" content="width=device-width, initial-scale=1">';
$html .= '<title>'.$this->escape($title).'</title>';
$html .= '<title>'.$this->escape('Kalender').'</title>';
$html .= '<link rel="stylesheet" href="'.$this->escape($appRoot.'skin/style.css.php').'" type="text/css">';
$html .= '<link rel="stylesheet" media="print" href="'.$this->escape($appRoot.'skin/cis.css').'" type="text/css">';
$html .= '<link rel="stylesheet" media="print" href="'.$this->escape($appRoot.'skin/print.css').'" type="text/css">';
@@ -360,7 +371,7 @@ class LvPlanHTMLLib
{
$weekday = (int)$day['date']->format('N');
$html .= '<tr><td>'.$weekdayNames[$weekday].'<br>';
$html .= $day['date']->format('d.m.Y').'<br></td>';
$html .= $day['date']->format('j.M Y').'<br></td>';
foreach ($day['cells'] as $slotIndex => $events)
{
@@ -407,9 +418,51 @@ class LvPlanHTMLLib
$html .= '</div>';
}
if ($type === 'ort')
$html .= $this->renderRoomReservationNotice($week, $filters);
return $html.'</div>';
}
/**
* Renders the room reservation warning used by the legacy weekly plan.
*/
private function renderRoomReservationNotice(array $week, array $filters)
{
$requiredFilters = array(
'room_reservable',
'room_reservation_permitted',
'room_reservation_start',
'room_reservation_end',
'room_not_reservable_message',
'room_reservation_restricted_message'
);
foreach ($requiredFilters as $filter)
{
if (!array_key_exists($filter, $filters))
return '';
}
$weekEnd = (clone $week['end'])->modify('+1 day')->getTimestamp();
$canReserveInWeek = $filters['room_reservation_permitted']
&& $filters['room_reservable']
&& $weekEnd >= time()
&& $weekEnd >= $filters['room_reservation_start']
&& $week['start']->getTimestamp() <= $filters['room_reservation_end'];
if ($canReserveInWeek)
return '';
$message = $filters['room_reservation_permitted']
&& !$filters['room_reservable']
? $filters['room_not_reservable_message']
: $filters['room_reservation_restricted_message'];
return '<table><tr><td><br><span style="color: orange">'
.$this->escape($message)
.'</span></td></tr></table>';
}
/**
* Renders an escaped event card.
*/
@@ -447,9 +500,11 @@ class LvPlanHTMLLib
if (($type === 'lektor' || $type === 'verband' || $type === 'ort')
&& $event['groups'] !== '')
$html .= '<br>'.$this->renderLines($event['groups']);
if ($type !== 'lektor' && $event['lecturers'] !== '')
if ($type !== 'lektor' && $type !== 'ort' && $event['lecturers'] !== '')
$html .= '<br>'.$this->renderLines($event['lecturers']);
if ($event['location'] !== '')
if ($type === 'ort' && $event['participants'] !== '')
$html .= '<br>'.$this->renderLines($event['participants']);
if ($event['location'] !== '' && $type !== 'ort')
$html .= '<br>'.$this->escape($event['location']);
if ($event['note'] !== '' && $this->shouldShowNotes())
$html .= '<br>'.$this->escape($event['note']);
@@ -520,6 +575,7 @@ class LvPlanHTMLLib
private function getDocumentTitle($type, array $filters)
{
//return 'Kalender';
if ($type === 'ort')
{
$title = 'Ort: '.$filters['ort_kurzbz'];
@@ -598,13 +654,33 @@ class LvPlanHTMLLib
$values[] = $value;
}
sort($values, SORT_STRING | SORT_FLAG_CASE);
return implode(', ', array_unique($values));
}
private function getParticipants($event)
{
$values = array();
if (!isset($event->teilnehmer_person) || !is_array($event->teilnehmer_person))
return '';
foreach ($event->teilnehmer_person as $participant)
{
$value = $this->getNestedValue($participant, array('kurzbz', 'student_uid'));
if ($value !== '')
$values[] = $value;
}
sort($values, SORT_STRING | SORT_FLAG_CASE);
return implode(', ', array_unique($values));
}
private function getGroups($event)
{
$values = array();
foreach (array('gruppe', 'teilnehmer_gruppe') as $property)
foreach (array('teilnehmer_gruppe', 'gruppe') as $property)
{
if (!isset($event->{$property}) || !is_array($event->{$property}))
continue;
@@ -620,6 +696,8 @@ class LvPlanHTMLLib
}
}
sort($values, SORT_STRING | SORT_FLAG_CASE);
return implode(', ', array_unique($values));
}
+104 -21
View File
@@ -34,6 +34,7 @@ class LvPlanICALLib
* @param int $version iCalendar major version (1 or 2).
* @param string|null $ortKurzbz Room identifier for room plans.
* @param array|null $verbandFilters Lehrverband filters.
* @param string|null $calendarName Display name for calendar subscriptions.
* @return string
*/
public function getContent(
@@ -43,7 +44,8 @@ class LvPlanICALLib
$type = null,
$version = 2,
$ortKurzbz = null,
$verbandFilters = null
$verbandFilters = null,
$calendarName = null
) {
if (!in_array($version, array(1, 2), true))
throw new InvalidArgumentException('Unsupported iCalendar version');
@@ -106,7 +108,7 @@ class LvPlanICALLib
if (!is_array($events))
$events = array();
$content = $this->buildCalendarHeader($version);
$content = $this->buildCalendarHeader($version, $calendarName);
foreach ($events as $event)
$content .= $this->buildEvent($event);
@@ -114,15 +116,23 @@ class LvPlanICALLib
return $content.$this->buildICalLine('END', 'VCALENDAR');
}
private function buildCalendarHeader($version)
private function buildCalendarHeader($version, $calendarName = null)
{
$productId = defined('CAMPUS_NAME') && CAMPUS_NAME !== ''
? CAMPUS_NAME
: 'FHComplete';
return $this->buildICalLine('BEGIN', 'VCALENDAR')
$header = $this->buildICalLine('BEGIN', 'VCALENDAR')
.$this->buildICalLine('VERSION', $version.'.0')
.$this->buildICalTextLine('PRODID', $productId)
.$this->buildICalTextLine('PRODID', $productId);
if (is_string($calendarName) && $calendarName !== '')
{
$header .= $this->buildICalTextLine('NAME', $calendarName);
$header .= $this->buildICalTextLine('X-WR-CALNAME', $calendarName);
}
return $header
.$this->buildICalLine('BEGIN', 'VTIMEZONE')
.$this->buildICalLine('TZID', 'Europe/Vienna')
.$this->buildICalLine('BEGIN', 'DAYLIGHT')
@@ -144,26 +154,27 @@ class LvPlanICALLib
private function buildEvent($event)
{
$isReservation = $event->type === 'reservierung';
$summaryIcon = '';
$summary = $event->type === 'reservierung'
? $event->titel
$summary = $isReservation
? implode(' - ', array_filter(array($event->titel, implode('/', $event->ort_kurzbz), $this->getGroups($event))))
: $this->joinValues($event->topic);
if ($event->type === 'reservierung')
{
$description = $event->beschreibung;
}
else
{
$description = $event->lehrfach_bez."\r\n";
if (isset($event->lektor) && is_array($event->lektor) && count($event->lektor) > 0)
{
$description .= 'Lektor*in: '.implode(', ', array_map(function ($teacher) {
return $teacher['kurzbz'];
}, $event->lektor))."\r\n";
$descriptionParts = array(
$event->titel,
isset($event->lehrfach_bez) ? $event->lehrfach_bez : '',
$isReservation ? $this->getParticipants($event) : $this->getLecturers($event),
$this->getGroups($event),
implode('/', $event->ort_kurzbz),
isset($event->beschreibung) ? $event->beschreibung : ''
);
$description = implode("\r\n", array_values(array_filter(
$descriptionParts,
function ($value) {
return $value !== '';
}
}
)));
$category = self::CATEGORY_TIMETABLE;
if ($event->type === 'lehreinheit')
@@ -319,4 +330,76 @@ class LvPlanICALLib
return null;
}
}
/**
* @param object $event Calendar event.
* @return string Participant group labels.
*/
private function getGroups($event)
{
$groups = array();
if (isset($event->gruppe) && is_array($event->gruppe))
{
foreach ($event->gruppe as $group)
{
if (isset($group['bezeichnung']) && $group['bezeichnung'] !== '')
$groups[] = $group['bezeichnung'];
}
}
if (isset($event->teilnehmer_gruppe) && is_array($event->teilnehmer_gruppe))
{
foreach ($event->teilnehmer_gruppe as $group)
{
if (isset($group['gruppe_kurzbz']) && $group['gruppe_kurzbz'] !== '')
$groups[] = $group['gruppe_kurzbz'];
}
}
sort($groups, SORT_NATURAL | SORT_FLAG_CASE);
return implode(',', array_unique($groups));
}
/**
* @param object $event Calendar event.
* @return string Lecturer abbreviations.
*/
private function getLecturers($event)
{
$lecturers = array();
if (isset($event->lektor) && is_array($event->lektor))
{
foreach ($event->lektor as $lecturer)
{
if (isset($lecturer['kurzbz']) && $lecturer['kurzbz'] !== '')
$lecturers[] = $lecturer['kurzbz'];
}
}
sort($lecturers, SORT_NATURAL | SORT_FLAG_CASE);
return implode(' / ', array_unique($lecturers));
}
private function getParticipants($event)
{
$values = array();
if (!isset($event->teilnehmer_person) || !is_array($event->teilnehmer_person))
return '';
foreach ($event->teilnehmer_person as $participant)
{
$value = $participant['vorname'] . ' ' . $participant['nachname'];
if ($value !== '')
$values[] = $value;
}
sort($values, SORT_NATURAL | SORT_FLAG_CASE);
return implode(', ', array_unique($values));
}
}
+1
View File
@@ -337,6 +337,7 @@ function LoadGruppe(type)
echo '<li><a class="Item" href="'.$caldavurl.'">'.$p->t('lvplan/caldavURL').'</a></li>';
echo '<li><a class="Item" href="'.APP_ROOT.'webdav/lvplan.php/principals/'.$uid.'">'.$p->t('lvplan/caldavURLMac').'</a></li>';
echo '<li><a class="Item" href="'.APP_ROOT.'webdav/google.php?cal='.encryptData($uid,LVPLAN_CYPHER_KEY).'&'.microtime(true).'">'.$p->t('lvplan/googleURL').'</a></li>';
echo '<li><a class="Item" href="'.APP_ROOT.'index.ci.php/lvplan/ical/subscription/google?cal='.encryptData($uid,LVPLAN_CYPHER_KEY).'&'.microtime(true).'">NEW '.$p->t('lvplan/googleURL').'</a></li>';
echo '</ul>';
echo ' </div>';
}
+41
View File
@@ -61219,6 +61219,47 @@ I have been informed that I am under no obligation to consent to the transmissio
)
)
),
//******************************* CORE/lvplan
array(
'app' => 'core',
'category' => 'lvplan',
'phrase' => 'room_not_reservable',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Die Möglichkeit der Reservierung ist für diesen Raum deaktiviert.',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Reservation is disabled for this room.',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'lvplan',
'phrase' => 'room_reservation_restricted_to_period',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Die Raumreservierung ist auf den Zeitraum vom {start_date} bis {end_date} eingeschränkt',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Room reservation is limited to the period from {start_date} to {end_date}',
'description' => '',
'insertvon' => 'system'
)
)
),
// generic tabulator phrases END
);