mirror of
https://github.com/FH-Complete/FHC-Core.git
synced 2026-10-12 15:04:04 +00:00
Mods found during manual testing
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 "((
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 : '';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>';
|
||||
}
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user