WIP: Streamlining the old and new html lvplan preview

This commit is contained in:
Ivymaster
2026-07-21 13:54:37 +02:00
parent 5288c93882
commit e39658bcf8
11 changed files with 792 additions and 87 deletions
+93 -6
View File
@@ -19,6 +19,8 @@ class LvPlanCSVDownload extends Auth_Controller
]);
$this->load->model('person/Benutzer_model', 'BenutzerModel');
$this->load->model('ressource/Ort_model', 'OrtModel');
$this->load->model('organisation/Studiengang_model', 'StudiengangModel');
}
/**
@@ -29,20 +31,31 @@ class LvPlanCSVDownload extends Auth_Controller
public function index()
{
$type = $this->getType();
$uid = $this->getUid();
$uid = getAuthUID();
$ortKurzbz = null;
$verbandFilters = null;
if ($type === 'ort')
$ortKurzbz = $this->getRoom();
elseif ($type === 'verband')
$verbandFilters = $this->getVerbandFilters();
else
{
$uid = $this->getUid();
$this->authorizeUid($uid);
$this->validateUid($uid);
}
$target = $this->getTarget();
list($begin, $ende) = $this->getDateRange();
$this->authorizeUid($uid);
$this->validateUid($uid);
$this->load->library('LvPlanCSVLib');
$content = $this->lvplancsvlib->getContent(
$uid,
$begin,
$ende,
$type,
$target
$target,
$ortKurzbz,
$verbandFilters
);
$filename = 'FH-Kalender_'.date('m_Y', $begin);
@@ -65,7 +78,7 @@ class LvPlanCSVDownload extends Auth_Controller
{
$type = $this->input->get('type', true);
if (!in_array($type, array('student', 'lektor'), true))
if (!in_array($type, array('student', 'lektor', 'ort', 'verband'), true))
show_error('Ungueltiger Parameter: type', 400);
return $type;
@@ -84,6 +97,80 @@ class LvPlanCSVDownload extends Auth_Controller
return $uid;
}
/**
* @return string Requested room identifier.
*/
private function getRoom()
{
$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');
$room = $this->OrtModel->load($ortKurzbz);
if (isError($room))
show_error(getError($room), 500);
if (!hasData($room))
show_error('Ungueltiger Parameter: ort_kurzbz', 400);
return $ortKurzbz;
}
/**
* @return array Requested Lehrverband filters.
*/
private function getVerbandFilters()
{
$stgKz = $this->getInteger('stg_kz', true);
$semester = $this->getInteger('sem', false);
$verband = $this->getOptionalFilter('ver');
$gruppe = $this->getOptionalFilter('grp');
$this->StudiengangModel->addSelect('studiengang_kz');
$studiengang = $this->StudiengangModel->load($stgKz);
if (isError($studiengang))
show_error(getError($studiengang), 500);
if (!hasData($studiengang))
show_error('Ungueltiger Parameter: stg_kz', 400);
return array(
'stg_kz' => $stgKz,
'sem' => $semester,
'ver' => $verband,
'grp' => $gruppe
);
}
private function getInteger($name, $signed)
{
$value = $this->input->get($name, true);
$pattern = $signed ? '/^-?\d+$/' : '/^\d+$/';
if (!is_string($value) || !preg_match($pattern, $value))
show_error('Ungueltiger Parameter: '.$name, 400);
$integer = filter_var($value, FILTER_VALIDATE_INT);
if ($integer === false || (!$signed && $integer < 0))
show_error('Ungueltiger Parameter: '.$name, 400);
return $integer;
}
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;
}
/**
* @return string|null Requested CSV target.
*/
@@ -19,6 +19,8 @@ class LvPlanExcelDownload extends Auth_Controller
]);
$this->load->model('person/Benutzer_model', 'BenutzerModel');
$this->load->model('ressource/Ort_model', 'OrtModel');
$this->load->model('organisation/Studiengang_model', 'StudiengangModel');
}
/**
@@ -29,18 +31,29 @@ class LvPlanExcelDownload extends Auth_Controller
public function index()
{
$type = $this->getType();
$uid = $this->getUid();
$uid = getAuthUID();
$ortKurzbz = null;
$verbandFilters = null;
if ($type === 'ort')
$ortKurzbz = $this->getRoom();
elseif ($type === 'verband')
$verbandFilters = $this->getVerbandFilters();
else
{
$uid = $this->getUid();
$this->authorizeUid($uid);
$this->validateUid($uid);
}
list($begin, $ende) = $this->getDateRange();
$this->authorizeUid($uid);
$this->validateUid($uid);
$this->load->library('LvPlanExcelLib');
$content = $this->lvplanexcellib->getContent(
$uid,
$begin,
$ende,
$type
$type,
$ortKurzbz,
$verbandFilters
);
$filename = 'FH-Kalender_'.date('m_Y', $begin).'.xls';
@@ -60,7 +73,7 @@ class LvPlanExcelDownload extends Auth_Controller
{
$type = $this->input->get('type', true);
if (!in_array($type, array('student', 'lektor'), true))
if (!in_array($type, array('student', 'lektor', 'ort', 'verband'), true))
show_error('Ungueltiger Parameter: type', 400);
return $type;
@@ -79,6 +92,80 @@ class LvPlanExcelDownload extends Auth_Controller
return $uid;
}
/**
* @return string Requested room identifier.
*/
private function getRoom()
{
$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');
$room = $this->OrtModel->load($ortKurzbz);
if (isError($room))
show_error(getError($room), 500);
if (!hasData($room))
show_error('Ungueltiger Parameter: ort_kurzbz', 400);
return $ortKurzbz;
}
/**
* @return array Requested Lehrverband filters.
*/
private function getVerbandFilters()
{
$stgKz = $this->getInteger('stg_kz', true);
$semester = $this->getInteger('sem', false);
$verband = $this->getOptionalFilter('ver');
$gruppe = $this->getOptionalFilter('grp');
$this->StudiengangModel->addSelect('studiengang_kz');
$studiengang = $this->StudiengangModel->load($stgKz);
if (isError($studiengang))
show_error(getError($studiengang), 500);
if (!hasData($studiengang))
show_error('Ungueltiger Parameter: stg_kz', 400);
return array(
'stg_kz' => $stgKz,
'sem' => $semester,
'ver' => $verband,
'grp' => $gruppe
);
}
private function getInteger($name, $signed)
{
$value = $this->input->get($name, true);
$pattern = $signed ? '/^-?\d+$/' : '/^\d+$/';
if (!is_string($value) || !preg_match($pattern, $value))
show_error('Ungueltiger Parameter: '.$name, 400);
$integer = filter_var($value, FILTER_VALIDATE_INT);
if ($integer === false || (!$signed && $integer < 0))
show_error('Ungueltiger Parameter: '.$name, 400);
return $integer;
}
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;
}
/**
* Validates and expands the requested range to complete weeks.
*
+93 -13
View File
@@ -19,7 +19,9 @@ class LvPlanHTMLPreview extends Auth_Controller
]);
$this->load->model('person/Benutzer_model', 'BenutzerModel');
$this->load->model('crm/Student_model', 'StudentModel');
$this->load->model('organisation/Studiengang_model', 'StudiengangModel');
$this->load->model('ressource/Ort_model', 'OrtModel');
}
/**
@@ -31,9 +33,12 @@ class LvPlanHTMLPreview extends Auth_Controller
{
$type = $this->getType();
list($begin, $ende) = $this->getDateRange();
$filters = $type === 'verband'
? $this->getVerbandFilters()
: $this->getPersonalFilters();
if ($type === 'verband')
$filters = $this->getVerbandFilters();
elseif ($type === 'ort')
$filters = $this->getRoomFilters();
else
$filters = $this->getPersonalFilters($type);
$this->load->library('LvPlanHTMLLib');
$content = $this->lvplanhtmllib->getContent(
@@ -56,7 +61,7 @@ class LvPlanHTMLPreview extends Auth_Controller
{
$type = $this->input->get('type', true);
if (!in_array($type, array('student', 'lektor', 'verband'), true))
if (!in_array($type, array('student', 'lektor', 'verband', 'ort'), true))
show_error('Ungueltiger Parameter: type', 400);
return $type;
@@ -65,8 +70,10 @@ class LvPlanHTMLPreview extends Auth_Controller
/**
* @return array Personal preview filters.
*/
private function getPersonalFilters()
private function getPersonalFilters($type)
{
// Legacy wochenplan calendar links include empty Lehrverband parameters
// for personal plans. They are irrelevant when type is student or lektor.
if ($this->hasVerbandParameters())
show_error('Ungueltige Parameterkombination', 400);
@@ -80,15 +87,81 @@ class LvPlanHTMLPreview extends Auth_Controller
show_error('Forbidden', 403);
}
$this->BenutzerModel->addSelect('uid');
$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');
$user = $this->BenutzerModel->load(array($uid));
if (isError($user))
show_error(getError($user), 500);
if (!hasData($user))
show_error('Ungueltiger Parameter: pers_uid', 400);
$users = getData($user);
$userData = reset($users);
$nameParts = array();
foreach (array('titelpre', 'vorname', 'nachname', 'titelpost') as $property)
{
if (isset($userData->{$property}) && trim($userData->{$property}) !== '')
$nameParts[] = trim($userData->{$property});
}
return array('uid' => $uid);
$filters = array(
'uid' => $uid,
'full_name' => implode(' ', $nameParts)
);
if ($type === 'student')
{
$this->StudentModel->addSelect(
'studiengang_kz, semester, verband, gruppe'
);
$student = $this->StudentModel->load(array($uid));
if (isError($student))
show_error(getError($student), 500);
if (!hasData($student))
show_error('Ungueltiger Parameter: pers_uid', 400);
$students = getData($student);
$studentData = reset($students);
$filters['stg_kz'] = $studentData->studiengang_kz;
$filters['sem'] = $studentData->semester;
$filters['ver'] = $studentData->verband;
$filters['grp'] = $studentData->gruppe;
}
return $filters;
}
/**
* Legacy room links also carry pers_uid, but the UID does not filter or
* authorize a room plan. Only ort_kurzbz identifies the requested plan.
*
* @return array Room preview 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');
$room = $this->OrtModel->load($ortKurzbz);
if (isError($room))
show_error(getError($room), 500);
if (!hasData($room))
show_error('Ungueltiger Parameter: ort_kurzbz', 400);
$rooms = getData($room);
$roomData = reset($rooms);
return array(
'ort_kurzbz' => $ortKurzbz,
'bezeichnung' => isset($roomData->bezeichnung) ? $roomData->bezeichnung : ''
);
}
/**
@@ -96,24 +169,30 @@ class LvPlanHTMLPreview extends Auth_Controller
*/
private function getVerbandFilters()
{
if ($this->input->get('pers_uid', true) !== null)
show_error('Ungueltige Parameterkombination', 400);
// 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);
$verband = $this->getOptionalFilter('ver');
$gruppe = $this->getOptionalFilter('grp');
$this->StudiengangModel->addSelect('studiengang_kz');
$this->StudiengangModel->addSelect(
'studiengang_kz, UPPER(typ || kurzbz) AS stg_kurzbz'
);
$studiengang = $this->StudiengangModel->load($stgKz);
if (isError($studiengang))
show_error(getError($studiengang), 500);
if (!hasData($studiengang))
show_error('Ungueltiger Parameter: stg_kz', 400);
$studiengaenge = getData($studiengang);
$studiengangData = reset($studiengaenge);
return array(
'stg_kz' => $stgKz,
'stg_kurzbz' => isset($studiengangData->stg_kurzbz)
? $studiengangData->stg_kurzbz
: '',
'sem' => $semester,
'ver' => $verband,
'grp' => $gruppe
@@ -121,13 +200,14 @@ class LvPlanHTMLPreview extends Auth_Controller
}
/**
* @return bool Whether any Lehrverband-only parameters were supplied.
* @return bool Whether any non-empty Lehrverband-only parameters were supplied.
*/
private function hasVerbandParameters()
{
foreach (array('stg_kz', 'sem', 'ver', 'grp') as $name)
{
if ($this->input->get($name, true) !== null)
$value = $this->input->get($name, true);
if ($value !== null && $value !== '')
return true;
}
+93 -6
View File
@@ -19,6 +19,8 @@ class LvPlanICALDownload extends Auth_Controller
]);
$this->load->model('person/Benutzer_model', 'BenutzerModel');
$this->load->model('ressource/Ort_model', 'OrtModel');
$this->load->model('organisation/Studiengang_model', 'StudiengangModel');
}
/**
@@ -29,20 +31,31 @@ class LvPlanICALDownload extends Auth_Controller
public function index()
{
$type = $this->getType();
$uid = $this->getUid();
$uid = getAuthUID();
$ortKurzbz = null;
$verbandFilters = null;
if ($type === 'ort')
$ortKurzbz = $this->getRoom();
elseif ($type === 'verband')
$verbandFilters = $this->getVerbandFilters();
else
{
$uid = $this->getUid();
$this->authorizeUid($uid);
$this->validateUid($uid);
}
$version = $this->getVersion();
list($begin, $ende) = $this->getDateRange();
$this->authorizeUid($uid);
$this->validateUid($uid);
$this->load->library('LvPlanICALLib');
$content = $this->lvplanicallib->getContent(
$uid,
$begin,
$ende,
$type,
$version
$version,
$ortKurzbz,
$verbandFilters
);
$filename = 'FH-Kalender_'.date('m_Y', $begin).'_ical'.$version.'.ics';
@@ -62,7 +75,7 @@ class LvPlanICALDownload extends Auth_Controller
{
$type = $this->input->get('type', true);
if (!in_array($type, array('student', 'lektor'), true))
if (!in_array($type, array('student', 'lektor', 'ort', 'verband'), true))
show_error('Ungueltiger Parameter: type', 400);
return $type;
@@ -81,6 +94,80 @@ class LvPlanICALDownload extends Auth_Controller
return $uid;
}
/**
* @return string Requested room identifier.
*/
private function getRoom()
{
$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');
$room = $this->OrtModel->load($ortKurzbz);
if (isError($room))
show_error(getError($room), 500);
if (!hasData($room))
show_error('Ungueltiger Parameter: ort_kurzbz', 400);
return $ortKurzbz;
}
/**
* @return array Requested Lehrverband filters.
*/
private function getVerbandFilters()
{
$stgKz = $this->getInteger('stg_kz', true);
$semester = $this->getInteger('sem', false);
$verband = $this->getOptionalFilter('ver');
$gruppe = $this->getOptionalFilter('grp');
$this->StudiengangModel->addSelect('studiengang_kz');
$studiengang = $this->StudiengangModel->load($stgKz);
if (isError($studiengang))
show_error(getError($studiengang), 500);
if (!hasData($studiengang))
show_error('Ungueltiger Parameter: stg_kz', 400);
return array(
'stg_kz' => $stgKz,
'sem' => $semester,
'ver' => $verband,
'grp' => $gruppe
);
}
private function getInteger($name, $signed)
{
$value = $this->input->get($name, true);
$pattern = $signed ? '/^-?\d+$/' : '/^\d+$/';
if (!is_string($value) || !preg_match($pattern, $value))
show_error('Ungueltiger Parameter: '.$name, 400);
$integer = filter_var($value, FILTER_VALIDATE_INT);
if ($integer === false || (!$signed && $integer < 0))
show_error('Ungueltiger Parameter: '.$name, 400);
return $integer;
}
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;
}
/**
* @return int Requested iCalendar major version.
*/
@@ -460,21 +460,6 @@ class MigrateKalender extends CLI_Controller
}
}
$rooms = is_array($block->orte_kurzbz) ? $block->orte_kurzbz : explode(',', $block->orte_kurzbz ?? '');
foreach ($rooms as $room_kurzbz)
{
$room_kurzbz = trim($room_kurzbz);
if (!empty($room_kurzbz))
{
$this->KalenderOrtModel->insert(
array (
'kalender_id' => $kalender_id,
'ort_kurzbz' => $room_kurzbz
)
);
}
}
foreach ($block->svg_kombis as $kombi_str)
{
$kombi_str = trim($kombi_str, '()');
+14
View File
@@ -299,6 +299,20 @@ class KalenderLib
return $this->_mapEvents($data);
}
/**
* Returns the published timetable for one room without collision checks.
*/
public function getPlanForRoom($start_date, $end_date, $ort)
{
$this->_getBasePlan($start_date, $end_date);
$this->_ci->KalenderModel->db->where('tbl_kalender_ort.ort_kurzbz', $ort);
$this->_ci->KalenderModel->db->where('tbl_kalender.status_kurzbz', 'live');
$data = $this->_ci->KalenderModel->load();
return $this->_mapEvents($data, false);
}
public function getForRaumvorschlag($start_date, $end_date, $lektor_uids = [], $gruppen_kurzbz = [], $lehrverband_gruppen = [])
+39 -5
View File
@@ -62,16 +62,34 @@ class LvPlanCSVLib
* @param string $uid User UID.
* @param int $begin First included date as Unix timestamp.
* @param int $ende Last included date as Unix timestamp.
* @param string $type Timetable type (student or lektor).
* @param string $type Timetable type (student, lektor, ort or verband).
* @param string|null $target Optional CSV target (outlook).
* @param string|null $ortKurzbz Room identifier for room plans.
* @param array|null $verbandFilters Lehrverband filters.
* @return string
*/
public function getContent($uid, $begin, $ende, $type, $target = null)
{
if (!in_array($type, array('student', 'lektor'), true))
public function getContent(
$uid,
$begin,
$ende,
$type,
$target = null,
$ortKurzbz = null,
$verbandFilters = null
) {
if (!in_array($type, array('student', 'lektor', 'ort', 'verband'), true))
throw new InvalidArgumentException('Unsupported timetable type');
if ($target !== null && $target !== self::TARGET_OUTLOOK)
throw new InvalidArgumentException('Unsupported CSV target');
if ($type === 'ort' && (!is_string($ortKurzbz) || $ortKurzbz === ''))
throw new InvalidArgumentException('Missing room timetable filter');
if ($type === 'verband'
&& (!is_array($verbandFilters)
|| !array_key_exists('stg_kz', $verbandFilters)
|| !array_key_exists('sem', $verbandFilters)))
{
throw new InvalidArgumentException('Missing Lehrverband timetable filters');
}
$isLecturer = $type === 'lektor';
$this->ci->load->library('KalenderLib', array('uid' => $uid));
@@ -79,7 +97,23 @@ class LvPlanCSVLib
$startDate = date('Y-m-d', $begin);
$endDate = date('Y-m-d', $ende);
if ($isLecturer)
if ($type === 'verband')
{
$events = $this->ci->kalenderlib->getPlanForVerband(
$startDate,
$endDate,
$verbandFilters['stg_kz'],
$verbandFilters['sem'],
isset($verbandFilters['ver']) ? $verbandFilters['ver'] : null,
isset($verbandFilters['grp']) ? $verbandFilters['grp'] : null
);
}
elseif ($type === 'ort')
{
$events = $this->ci->kalenderlib
->getPlanForRoom($startDate, $endDate, $ortKurzbz);
}
elseif ($isLecturer)
{
$events = $this->ci->kalenderlib
->getPlanForLecturerByLecturer($startDate, $endDate, $uid);
+38 -5
View File
@@ -35,13 +35,30 @@ class LvPlanExcelLib
* @param string $uid User UID.
* @param int $begin First included date as Unix timestamp.
* @param int $ende Last included date as Unix timestamp.
* @param string $type Timetable type (student or lektor).
* @param string $type Timetable type (student, lektor, ort or verband).
* @param string|null $ortKurzbz Room identifier for room plans.
* @param array|null $verbandFilters Lehrverband filters.
* @return string Binary workbook content.
*/
public function getContent($uid, $begin, $ende, $type)
{
if (!in_array($type, array('student', 'lektor'), true))
public function getContent(
$uid,
$begin,
$ende,
$type,
$ortKurzbz = null,
$verbandFilters = null
) {
if (!in_array($type, array('student', 'lektor', 'ort', 'verband'), true))
throw new InvalidArgumentException('Unsupported timetable type');
if ($type === 'ort' && (!is_string($ortKurzbz) || $ortKurzbz === ''))
throw new InvalidArgumentException('Missing room timetable filter');
if ($type === 'verband'
&& (!is_array($verbandFilters)
|| !array_key_exists('stg_kz', $verbandFilters)
|| !array_key_exists('sem', $verbandFilters)))
{
throw new InvalidArgumentException('Missing Lehrverband timetable filters');
}
$isLecturer = $type === 'lektor';
$this->ci->load->library('KalenderLib', array('uid' => $uid));
@@ -50,7 +67,23 @@ class LvPlanExcelLib
$startDate = date('Y-m-d', $begin);
$endDate = date('Y-m-d', $ende);
if ($isLecturer)
if ($type === 'verband')
{
$events = $this->ci->kalenderlib->getPlanForVerband(
$startDate,
$endDate,
$verbandFilters['stg_kz'],
$verbandFilters['sem'],
isset($verbandFilters['ver']) ? $verbandFilters['ver'] : null,
isset($verbandFilters['grp']) ? $verbandFilters['grp'] : null
);
}
elseif ($type === 'ort')
{
$events = $this->ci->kalenderlib
->getPlanForRoom($startDate, $endDate, $ortKurzbz);
}
elseif ($isLecturer)
{
$events = $this->ci->kalenderlib
->getPlanForLecturerByLecturer($startDate, $endDate, $uid);
+201 -26
View File
@@ -6,6 +6,8 @@ if (!defined('BASEPATH'))
class LvPlanHTMLLib
{
const TIMEZONE = 'Europe/Vienna';
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';
private $ci;
@@ -25,12 +27,16 @@ class LvPlanHTMLLib
*/
public function getContent($begin, $ende, $type, array $filters)
{
if (!in_array($type, array('student', 'lektor', 'verband'), true))
if (!in_array($type, array('student', 'lektor', 'verband', 'ort'), true))
throw new InvalidArgumentException('Unsupported timetable type');
$events = $type === 'verband'
? $this->getVerbandEvents($begin, $ende, $filters)
: $this->getPersonalEvents($begin, $ende, $type, $filters);
if ($type === 'verband')
$events = $this->getVerbandEvents($begin, $ende, $filters);
elseif ($type === 'ort')
$events = $this->getRoomEvents($begin, $ende, $filters);
else
$events = $this->getPersonalEvents($begin, $ende, $type, $filters);
$hourRaster = $this->loadHourRaster();
$normalizedEvents = $this->normalizeEvents($events);
@@ -73,6 +79,28 @@ class LvPlanHTMLLib
return is_array($events) ? $events : array();
}
/**
* @return array Room plan events.
*/
private function getRoomEvents($begin, $ende, array $filters)
{
if (!isset($filters['ort_kurzbz'])
|| !is_string($filters['ort_kurzbz'])
|| $filters['ort_kurzbz'] === '')
{
throw new InvalidArgumentException('Missing room timetable filter');
}
$this->ci->load->library('KalenderLib', array('uid' => getAuthUID()));
$events = $this->ci->kalenderlib->getPlanForRoom(
$this->formatLocalDate($begin),
$this->formatLocalDate($ende),
$filters['ort_kurzbz']
);
return is_array($events) ? $events : array();
}
/**
* @return array Lehrverband plan events.
*/
@@ -168,6 +196,9 @@ class LvPlanHTMLLib
isset($event->ort_kurzbz) ? $event->ort_kurzbz : '',
' / '
),
'detail_ort_kurzbz' => $this->getFirstScalarValue(
isset($event->ort_kurzbz) ? $event->ort_kurzbz : ''
),
'lecturers' => $this->getLecturers($event),
'groups' => $this->getGroups($event),
'note' => $this->getEventNote($event, $isReservation),
@@ -210,7 +241,7 @@ class LvPlanHTMLLib
$html .= '<body id="inhalt"><h1>'.$this->escape($title).'</h1>';
foreach ($weeks as $week)
$html .= $this->renderWeek($week, $hourRaster, $type);
$html .= $this->renderWeek($week, $hourRaster, $type, $filters);
if (empty($weeks))
$html .= '<p>Keine Termine im gewählten Zeitraum.</p>';
@@ -300,7 +331,7 @@ class LvPlanHTMLLib
/**
* Renders one printable week.
*/
private function renderWeek(array $week, array $raster, $type)
private function renderWeek(array $week, array $raster, $type, array $filters)
{
$weekdayNames = array(
1 => 'Montag',
@@ -331,7 +362,7 @@ class LvPlanHTMLLib
$html .= '<tr><td>'.$weekdayNames[$weekday].'<br>';
$html .= $day['date']->format('d.m.Y').'<br></td>';
foreach ($day['cells'] as $events)
foreach ($day['cells'] as $slotIndex => $events)
{
if (empty($events))
{
@@ -340,8 +371,18 @@ class LvPlanHTMLLib
}
$html .= '<td nowrap valign="top" align="center">';
$hourNumber = isset($raster[$slotIndex]['number'])
? $raster[$slotIndex]['number']
: null;
foreach ($events as $event)
$html .= $this->renderEvent($event, $type, false);
$html .= $this->renderEvent(
$event,
$type,
false,
$filters,
$hourNumber
);
$html .= '</td>';
}
@@ -354,7 +395,15 @@ class LvPlanHTMLLib
{
$html .= '<div class="outside"><h3>Termine außerhalb des Stundenrasters</h3>';
foreach ($week['outside'] as $event)
$html .= $this->renderEvent($event, $type, true);
{
$html .= $this->renderEvent(
$event,
$type,
true,
$filters,
$this->getNearestHourNumber($event, $raster)
);
}
$html .= '</div>';
}
@@ -364,26 +413,40 @@ class LvPlanHTMLLib
/**
* Renders an escaped event card.
*/
private function renderEvent(array $event, $type, $showDate)
{
private function renderEvent(
array $event,
$type,
$showDate,
array $filters,
$hourNumber
) {
$style = $event['color'] !== ''
? ' style="background-color: #'.$event['color'].'; margin-bottom: 3px;"'
: '';
$html = '<div align="center"'.$style.'>';
$html .= '<a class="stpl_detail" title="'.$this->escape($event['note']).'">';
$detailUrl = $this->buildDetailUrl($event, $type, $filters, $hourNumber);
$html .= '<a class="stpl_detail" title="'.$this->escape($event['note']).'"';
if ($detailUrl !== null)
{
$onclick = 'window.open('
.$this->encodeJavaScriptString($detailUrl).','
.$this->encodeJavaScriptString(self::DETAIL_WINDOW_NAME).','
.$this->encodeJavaScriptString(self::DETAIL_WINDOW_OPTIONS)
.');return false;';
$html .= ' href="'.$this->escape($detailUrl).'"';
$html .= ' target="'.self::DETAIL_WINDOW_NAME.'"';
$html .= ' onclick="'.$this->escape($onclick).'"';
}
$html .= '>';
if ($showDate)
$html .= $event['start']->format('d.m.Y H:i').'–'.$event['end']->format('H:i').'<br>';
$html .= $this->escape($event['title']);
if ($event['note'] !== '')
{
$html .= '<img src="'.$this->escape($this->getAppRoot().'skin/images/sticky.png').'"';
$html .= ' title="'.$this->escape($event['note']).'" alt="">';
}
if (($type === 'lektor' || $type === 'verband') && $event['groups'] !== '')
$html .= '<br>'.$this->escape($event['groups']);
if (($type === 'lektor' || $type === 'verband' || $type === 'ort')
&& $event['groups'] !== '')
$html .= '<br>'.$this->renderLines($event['groups']);
if ($type !== 'lektor' && $event['lecturers'] !== '')
$html .= '<br>'.$this->renderLines($event['lecturers']);
if ($event['location'] !== '')
@@ -394,19 +457,107 @@ class LvPlanHTMLLib
return $html.'</a></div>';
}
/**
* Builds the URL consumed by the legacy timetable detail popup.
*/
private function buildDetailUrl(array $event, $type, array $filters, $hourNumber)
{
if ($hourNumber === null || !preg_match('/^\d+$/', (string)$hourNumber))
return null;
$uid = isset($filters['uid']) && $filters['uid'] !== ''
? $filters['uid']
: getAuthUID();
$room = $type === 'ort' && isset($filters['ort_kurzbz'])
? $filters['ort_kurzbz']
: $event['detail_ort_kurzbz'];
$parameters = array(
'type' => $type,
'datum' => $event['date'],
'stunde' => (string)$hourNumber,
'pers_uid' => $uid,
'stg_kz' => isset($filters['stg_kz']) ? $filters['stg_kz'] : '',
'sem' => isset($filters['sem']) ? $filters['sem'] : '',
'ver' => isset($filters['ver']) && $filters['ver'] !== null
? $filters['ver']
: '',
'grp' => isset($filters['grp']) && $filters['grp'] !== null
? $filters['grp']
: '',
'ort_kurzbz' => $room
);
return $this->getAppRoot().'cis/private/lvplan/stpl_detail.php?'
.http_build_query($parameters, '', '&', PHP_QUERY_RFC3986);
}
/**
* Returns the hour whose configured start is closest to the event start.
*/
private function getNearestHourNumber(array $event, array $raster)
{
if (empty($raster))
return null;
$eventStart = $this->timeToSeconds($event['start']->format('H:i:s'));
if ($eventStart === null)
return null;
$nearestNumber = null;
$nearestDistance = null;
foreach ($raster as $hour)
{
$distance = abs($eventStart - $hour['begin']);
if ($nearestDistance === null || $distance < $nearestDistance)
{
$nearestDistance = $distance;
$nearestNumber = $hour['number'];
}
}
return $nearestNumber;
}
private function getDocumentTitle($type, array $filters)
{
if ($type === 'verband')
if ($type === 'ort')
{
$title = 'Lehrverband: '.$filters['stg_kz'].'-'.$filters['sem'];
if (isset($filters['ver']) && $filters['ver'] !== null)
$title .= $filters['ver'];
if (isset($filters['grp']) && $filters['grp'] !== null)
$title .= $filters['grp'];
$title = 'Ort: '.$filters['ort_kurzbz'];
if (isset($filters['bezeichnung']) && $filters['bezeichnung'] !== '')
$title .= ' - '.$filters['bezeichnung'];
return $title;
}
return ($type === 'lektor' ? 'Lektor: ' : 'Student: ').$filters['uid'];
if ($type === 'verband')
{
$studiengang = isset($filters['stg_kurzbz'])
&& $filters['stg_kurzbz'] !== ''
? $filters['stg_kurzbz']
: $filters['stg_kz'];
$title = 'Lehrverband: '.$studiengang.'-'.$filters['sem'];
if (isset($filters['ver']) && $filters['ver'] !== null)
$title .= $filters['ver'];
else
$title .= 0;
if (isset($filters['grp']) && $filters['grp'] !== null)
$title .= $filters['grp'];
else
$title .= 0;
return $title;
}
if ($type === 'lektor')
{
$name = isset($filters['full_name']) && $filters['full_name'] !== ''
? $filters['full_name']
: $filters['uid'];
return 'Lektor: '.$name;
}
return 'Student: '.$filters['uid'];
}
private function getEventDateTime($event, $isoProperty, $dateProperty, $timeProperty)
@@ -508,6 +659,22 @@ class LvPlanHTMLLib
: '';
}
private function getFirstScalarValue($value)
{
if (is_scalar($value))
return (string)$value;
if (!is_array($value))
return '';
foreach ($value as $item)
{
if (is_scalar($item) && (string)$item !== '')
return (string)$item;
}
return '';
}
private function joinValue($value, $separator = ', ')
{
if (!is_array($value))
@@ -550,6 +717,14 @@ class LvPlanHTMLLib
return htmlspecialchars((string)$value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
private function encodeJavaScriptString($value)
{
return json_encode(
(string)$value,
JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT
);
}
private function renderLines($value)
{
$lines = array_map('trim', explode(', ', (string)$value));
+39 -5
View File
@@ -30,16 +30,34 @@ class LvPlanICALLib
* @param string $uid User UID.
* @param int $begin First included date as Unix timestamp.
* @param int $ende Last included date as Unix timestamp.
* @param string|null $type Timetable type (student or lektor).
* @param string|null $type Timetable type (student, lektor, ort or verband).
* @param int $version iCalendar major version (1 or 2).
* @param string|null $ortKurzbz Room identifier for room plans.
* @param array|null $verbandFilters Lehrverband filters.
* @return string
*/
public function getContent($uid, $begin, $ende, $type = null, $version = 2)
{
public function getContent(
$uid,
$begin,
$ende,
$type = null,
$version = 2,
$ortKurzbz = null,
$verbandFilters = null
) {
if (!in_array($version, array(1, 2), true))
throw new InvalidArgumentException('Unsupported iCalendar version');
if ($type !== null && !in_array($type, array('student', 'lektor'), true))
if ($type !== null && !in_array($type, array('student', 'lektor', 'ort', 'verband'), true))
throw new InvalidArgumentException('Unsupported timetable type');
if ($type === 'ort' && (!is_string($ortKurzbz) || $ortKurzbz === ''))
throw new InvalidArgumentException('Missing room timetable filter');
if ($type === 'verband'
&& (!is_array($verbandFilters)
|| !array_key_exists('stg_kz', $verbandFilters)
|| !array_key_exists('sem', $verbandFilters)))
{
throw new InvalidArgumentException('Missing Lehrverband timetable filters');
}
$this->ci->load->library('KalenderLib', array('uid' => $uid));
@@ -58,7 +76,23 @@ class LvPlanICALLib
// KalenderLib uses an exclusive end date; $ende is inclusive here.
$endDate = date('Y-m-d', strtotime('+1 day', $ende));
if ($isLecturer)
if ($type === 'verband')
{
$events = $this->ci->kalenderlib->getPlanForVerband(
$startDate,
$endDate,
$verbandFilters['stg_kz'],
$verbandFilters['sem'],
isset($verbandFilters['ver']) ? $verbandFilters['ver'] : null,
isset($verbandFilters['grp']) ? $verbandFilters['grp'] : null
);
}
elseif ($type === 'ort')
{
$events = $this->ci->kalenderlib
->getPlanForRoom($startDate, $endDate, $ortKurzbz);
}
elseif ($isLecturer)
{
$events = $this->ci->kalenderlib
->getPlanForLecturerByLecturer($startDate, $endDate, $uid);
+89
View File
@@ -512,6 +512,69 @@ class wochenplan extends basis_db
$this->kal_link.='&pers_uid='.$this->pers_uid.'&ort_kurzbz='.$this->ort_kurzbz.'&stg_kz='.$this->stg_kz.'&sem='.$this->sem.'&ver='.$this->ver.'&grp='.$this->grp.'&gruppe_kurzbz='.$this->gruppe_kurzbz.'&lva='.$this->lva;
$kal_link_ws=$this->kal_link.'&begin='.$this->studiensemester_now->start.'&ende='.$this->studiensemester_now->ende;
$kal_link_ss=$this->kal_link.'&begin='.$this->studiensemester_next->start.'&ende='.$this->studiensemester_next->ende;
$kal_link_new_ws = null;
$kal_link_new_ss = null;
$kal_link_new_downloads = false;
// Build only the type-specific parameters accepted by the new endpoints.
if ($this->type == 'student' || $this->type == 'lektor')
{
$kal_link_new_downloads = true;
$kal_link_new_ws = http_build_query(array(
'type' => $this->type,
'pers_uid' => $this->pers_uid,
'begin' => $this->studiensemester_now->start,
'ende' => $this->studiensemester_now->ende
), '', '&');
$kal_link_new_ss = http_build_query(array(
'type' => $this->type,
'pers_uid' => $this->pers_uid,
'begin' => $this->studiensemester_next->start,
'ende' => $this->studiensemester_next->ende
), '', '&');
}
elseif ($this->type == 'ort' && $this->ort_kurzbz != '' && $this->ort_kurzbz != 'all')
{
$kal_link_new_downloads = true;
$kal_link_new_ws = http_build_query(array(
'type' => $this->type,
'pers_uid' => $this->pers_uid,
'ort_kurzbz' => $this->ort_kurzbz,
'begin' => $this->studiensemester_now->start,
'ende' => $this->studiensemester_now->ende
), '', '&');
$kal_link_new_ss = http_build_query(array(
'type' => $this->type,
'pers_uid' => $this->pers_uid,
'ort_kurzbz' => $this->ort_kurzbz,
'begin' => $this->studiensemester_next->start,
'ende' => $this->studiensemester_next->ende
), '', '&');
}
elseif ($this->type == 'verband')
{
$kal_link_new_downloads = true;
$kal_link_new_ws = http_build_query(array(
'type' => $this->type,
'pers_uid' => $this->pers_uid,
'stg_kz' => $this->stg_kz,
'sem' => $this->sem,
'ver' => $this->ver,
'grp' => $this->grp,
'begin' => $this->studiensemester_now->start,
'ende' => $this->studiensemester_now->ende
), '', '&');
$kal_link_new_ss = http_build_query(array(
'type' => $this->type,
'pers_uid' => $this->pers_uid,
'stg_kz' => $this->stg_kz,
'sem' => $this->sem,
'ver' => $this->ver,
'grp' => $this->grp,
'begin' => $this->studiensemester_next->start,
'ende' => $this->studiensemester_next->ende
), '', '&');
}
echo $this->crlf;
echo $this->studiensemester_now->name.'<br>'.$this->crlf;
@@ -521,6 +584,19 @@ class wochenplan extends basis_db
echo ' <A href="'.$kal_link_ws.'&format=csv&target=outlook" title="CSV-Outlook"><IMG src="../../../skin/images/outlook.png" height="30" alt="CSV-Outlook" border="0"></A>'.$this->crlf;
echo ' <A href="'.$kal_link_ws.'&format=ical&version=1&target=ical" title="iCal Version 1.0"><IMG src="../../../skin/images/ical1.0.png" height="30" alt="vCal Version 1.0" border="0"></A>'.$this->crlf;
echo ' <A href="'.$kal_link_ws.'&format=ical&version=2&target=ical" title="iCal Version 2.0"><IMG src="../../../skin/images/ical2.0.png" height="30" alt="vCal Version 2.0" border="0"></A>'.$this->crlf;
if ($kal_link_new_ws !== null)
{
echo ' <br>'.$this->crlf;
echo ' <A href="'.APP_ROOT.'index.ci.php/lvplan/html/preview?'.htmlspecialchars($kal_link_new_ws, ENT_QUOTES, 'UTF-8').'" target="_blank" title="HTML-new">HTML-new</A> '.$this->crlf;
if ($kal_link_new_downloads)
{
echo ' <A href="'.APP_ROOT.'index.ci.php/lvplan/excel/download?'.htmlspecialchars($kal_link_new_ws, ENT_QUOTES, 'UTF-8').'" title="excel-new">excel-new</A> '.$this->crlf;
echo ' <A href="'.APP_ROOT.'index.ci.php/lvplan/csv/download?'.htmlspecialchars($kal_link_new_ws, ENT_QUOTES, 'UTF-8').'" title="CSV-new">CSV-new</A> '.$this->crlf;
echo ' <A href="'.APP_ROOT.'index.ci.php/lvplan/csv/download?'.htmlspecialchars($kal_link_new_ws, ENT_QUOTES, 'UTF-8').'&amp;target=outlook" title="CSV-Outlook-new">CSV-Outlook-new</A> '.$this->crlf;
echo ' <A href="'.APP_ROOT.'index.ci.php/lvplan/ical/download?'.htmlspecialchars($kal_link_new_ws, ENT_QUOTES, 'UTF-8').'&amp;version=1" title="iCal Version 1.0-new">iCal1-new</A> '.$this->crlf;
echo ' <A href="'.APP_ROOT.'index.ci.php/lvplan/ical/download?'.htmlspecialchars($kal_link_new_ws, ENT_QUOTES, 'UTF-8').'&amp;version=2" title="iCal Version 2.0-new">iCal2-new</A>'.$this->crlf;
}
}
echo ' </td><td class="stdplan" style="padding:8px;" align="center">'.$p->t('lvplan/semesterplaene').'</td><td style="padding:3px 15px 0px 15px;" align="center">';
echo ' <span style="color:#999">'.$this->studiensemester_next->name.'</span><br>'.$this->crlf;
echo ' <A href="'.$kal_link_ss.'&format=html" target="_blank" title="HTML"><IMG src="../../../skin/images/html_light.png" height="30" alt="HTML" border="0"></A>'.$this->crlf;
@@ -529,6 +605,19 @@ class wochenplan extends basis_db
echo ' <A href="'.$kal_link_ss.'&format=csv&target=outlook" title="CSV-Outlook"><IMG src="../../../skin/images/outlook_light.png" height="30" alt="CSV-Outlook" border="0"></A>'.$this->crlf;
echo ' <A href="'.$kal_link_ss.'&format=ical&version=1&target=ical" title="iCal Version 1.0"><IMG src="../../../skin/images/ical1.0_light.png" height="30" alt="iCal Version 1.0" border="0"></A>'.$this->crlf;
echo ' <A href="'.$kal_link_ss.'&format=ical&version=2&target=ical" title="iCal Version 2.0"><IMG src="../../../skin/images/ical2.0_light.png" height="30" alt="iCal Version 2.0" border="0"></A>'.$this->crlf;
if ($kal_link_new_ss !== null)
{
echo ' <br>'.$this->crlf;
echo ' <A href="'.APP_ROOT.'index.ci.php/lvplan/html/preview?'.htmlspecialchars($kal_link_new_ss, ENT_QUOTES, 'UTF-8').'" target="_blank" title="HTML-new">HTML-new</A> '.$this->crlf;
if ($kal_link_new_downloads)
{
echo ' <A href="'.APP_ROOT.'index.ci.php/lvplan/excel/download?'.htmlspecialchars($kal_link_new_ss, ENT_QUOTES, 'UTF-8').'" title="excel-new">excel-new</A> '.$this->crlf;
echo ' <A href="'.APP_ROOT.'index.ci.php/lvplan/csv/download?'.htmlspecialchars($kal_link_new_ss, ENT_QUOTES, 'UTF-8').'" title="CSV-new">CSV-new</A> '.$this->crlf;
echo ' <A href="'.APP_ROOT.'index.ci.php/lvplan/csv/download?'.htmlspecialchars($kal_link_new_ss, ENT_QUOTES, 'UTF-8').'&amp;target=outlook" title="CSV-Outlook-new">CSV-Outlook-new</A> '.$this->crlf;
echo ' <A href="'.APP_ROOT.'index.ci.php/lvplan/ical/download?'.htmlspecialchars($kal_link_new_ss, ENT_QUOTES, 'UTF-8').'&amp;version=1" title="iCal Version 1.0-new">iCal1-new</A> '.$this->crlf;
echo ' <A href="'.APP_ROOT.'index.ci.php/lvplan/ical/download?'.htmlspecialchars($kal_link_new_ss, ENT_QUOTES, 'UTF-8').'&amp;version=2" title="iCal Version 2.0-new">iCal2-new</A>'.$this->crlf;
}
}
echo ' </td></tr></table>'.$this->crlf;
echo ' </TD>'.$this->crlf;