<?php
class Page_LocationInfo extends Page
{
private $show;
/**
* Called before any page rendering happens - early hook to check parameters etc.
*/
protected function doPreprocess()
{
$this->show = Request::any('show', false, 'string');
if ($this->show === 'panel') {
$this->showPanel();
exit(0);
}
User::load();
if (!User::isLoggedIn()) {
Message::addError('main.no-permission');
Util::redirect('?do=Main'); // does not return
}
$action = Request::post('action');
if ($action === 'writePanelConfig') {
$this->writePanelConfig();
$show = 'panels';
} elseif ($action === 'writeLocationConfig') {
$this->writeLocationConfig();
$show = 'locations';
} elseif ($action === 'deletePanel') {
$this->deletePanel();
$show = 'panels';
} elseif ($action === 'updateServerSettings') {
$this->updateServerSettings();
$show = 'backends';
} else {
if (($id = Request::post('del-serverid', false, 'int')) !== false) {
$this->deleteServer($id);
$show = 'backends';
} elseif (($id = Request::post('chk-serverid', false, 'int')) !== false) {
$this->checkConnection($id);
$show = 'backends';
} elseif (Request::isPost()) {
Message::addWarning('main.invalid-action', $action);
}
}
if (Request::isPost() || $this->show === false) {
if (!empty($show)) {
//
} elseif (User::hasPermission('panel.list')) {
$show = 'panels';
} elseif (User::hasPermission('location.*')) {
$show = 'locations';
} elseif (User::hasPermission('backend.*')) {
$show = 'backends';
} else {
User::assertPermission('panel.list');
}
Util::redirect('?do=locationinfo&show=' . $show);
}
}
/**
* Menu etc. has already been generated, now it's time to generate page content.
*/
protected function doRender()
{
$data = array('class-' . $this->show => 'active', 'errors' => []);
// Do this here so we always see backend errors
if (User::hasPermission('backend.*')) {
$backends = $this->loadBackends();
foreach ($backends as $backend) {
if (!empty($backend['error'])) {
$data['errors'][] = $backend;
}
}
}
Permission::addGlobalTags($data['perms'], null, ['backend.*', 'location.*', 'panel.list']);
Render::addTemplate('page-tabs', $data);
switch ($this->show) {
case 'locations':
$this->showLocationsTable();
break;
case 'backends':
$this->showBackendsTable($backends ?? []);
break;
case 'edit-panel':
$this->showPanelConfig();
break;
case 'panels':
$this->showPanelsTable();
break;
case 'backendlog':
$this->showBackendLog();
break;
default:
Util::redirect('?do=locationinfo');
}
}
/**
* Deletes the server from the db.
*/
private function deleteServer($id): void
{
User::assertPermission('backend.edit');
if ($id === 0) {
Message::addError('server-id-missing');
return;
}
$res = Database::exec("DELETE FROM `locationinfo_coursebackend` WHERE serverid=:id", array('id' => $id));
if ($res !== 1) {
Message::addWarning('invalid-server-id', $id);
}
}
private function deletePanel(): void
{
$id = Request::post('uuid', false, 'string');
if ($id === false) {
Message::addError('main.parameter-missing', 'uuid');
return;
}
$this->assertPanelPermission($id, 'panel.edit');
$res = Database::exec("DELETE FROM `locationinfo_panel` WHERE paneluuid = :id", array('id' => $id));
if ($res !== 1) {
Message::addWarning('invalid-panel-id', $id);
}
if (Module::isAvailable('runmode')) {
RunMode::deleteMode(Page::getModule(), $id);
}
}
private static function getTime(string $str): ?int
{
$str = explode(':', $str);
if (count($str) !== 2)
return null;
if ($str[0] < 0 || $str[0] > 23 || $str[1] < 0 || $str[1] > 59)
return null;
return $str[0] * 60 + $str[1];
}
private function writeLocationConfig(): void
{
// Check locations
$locationid = Request::post('locationid', false, 'int');
if ($locationid === false) {
Message::addError('main.parameter-missing', 'locationid');
return;
}
if (Location::get($locationid) === false) {
Message::addError('location.invalid-location-id', $locationid);
return;
}
User::assertPermission('location.edit', $locationid);
$serverid = Request::post('serverid', 0, 'int');
if ($serverid === 0) {
$serverid = null;
}
$serverlocationid = Request::post('serverlocationid', '', 'string');
$changeServerRecursive = (Request::post('recursive', '', 'string') !== '');
if (empty($serverlocationid) && !$changeServerRecursive) {
$insertServerId = null;
$ignoreServer = 1;
} else {
$insertServerId = $serverid;
$ignoreServer = 0;
}
$NOW = time();
Database::exec("INSERT INTO `locationinfo_locationconfig` (locationid, serverid, serverlocationid, lastcalendarupdate, lastchange)
VALUES (:id, :insertserverid, :serverlocationid, 0, :now)
ON DUPLICATE KEY UPDATE serverid = IF(:ignore_server AND serverid IS NULL, NULL, :serverid), serverlocationid = VALUES(serverlocationid),
lastcalendarupdate = 0, lastchange = VALUES(lastchange)", array(
'id' => $locationid,
'insertserverid' => $insertServerId,
'serverid' => $serverid,
'serverlocationid' => $serverlocationid,
'ignore_server' => $ignoreServer,
'now' => $NOW,
));
if ($changeServerRecursive) {
// Recursive overwriting of serverid
$children = Location::getRecursiveFlat($locationid);
$array = array();
foreach ($children as $loc) {
$array[] = $loc['locationid'];
}
if (!empty($array)) {
Database::exec("UPDATE locationinfo_locationconfig
SET serverid = :serverid, lastcalendarupdate = IF(serverid <> :serverid, 0, lastcalendarupdate), lastchange = :now
WHERE locationid IN (:locations)", array(
'serverid' => $serverid,
'locations' => $array,
'now' => $NOW,
));
}
}
}
/**
* Get all location ids from the locationids parameter, which is comma separated, then split
* and remove any ids that don't exist. The cleaned list will be returned.
* Will show error and redirect to main page if parameter is missing
*
* @param bool $failIfEmpty Show error and redirect to main page if parameter is missing or list is empty
* @return array list of locations from parameter
*/
private function getLocationIdsFromRequest(): array
{
$locationids = Request::post('locationids', Request::REQUIRED_EMPTY, 'string');
$locationids = explode(',', $locationids);
$all = array_map(function ($item) { return $item['locationid']; }, Location::queryLocations());
$locationids = array_filter($locationids, function ($item) use ($all) { return in_array($item, $all); });
if (empty($locationids)) {
Message::addError('main.parameter-empty', 'locationids');
Util::redirect('?do=locationinfo');
}
return $locationids;
}
/**
* Updated the config in the db.
*/
private function writePanelConfig(): void
{
// UUID - existing or new
$paneluuid = Request::post('uuid', false, 'string');
if (($paneluuid === false || strlen($paneluuid) !== 36) && $paneluuid !== 'new') {
Message::addError('invalid-panel-id', $paneluuid);
Util::redirect('?do=locationinfo');
}
// Check panel type
$paneltype = Request::post('ptype', false, 'string');
if ($paneltype === 'DEFAULT') {
$params = $this->preparePanelConfigDefault();
} elseif ($paneltype === 'URL') {
$params = $this->preparePanelConfigUrl();
} elseif ($paneltype === 'SUMMARY') {
$params = $this->preparePanelConfigSummary();
} else {
Message::addError('invalid-panel-type', $paneltype);
Util::redirect('?do=locationinfo');
}
// Permission
$this->assertPanelPermission($paneluuid, 'panel.edit', $params['locationids'] ?? []);
if ($paneluuid === 'new') {
$paneluuid = Util::randomUuid();
$query = "INSERT INTO `locationinfo_panel` (paneluuid, panelname, locationids, paneltype, panelconfig, lastchange)
VALUES (:id, :name, :locationids, :type, :config, :now)";
} else {
$query = "UPDATE `locationinfo_panel`
SET panelname = :name, locationids = :locationids, paneltype = :type, panelconfig = :config, lastchange = :now
WHERE paneluuid = :id";
}
$params['id'] = $paneluuid;
$params['name'] = Request::post('name', '-', 'string');
$params['type'] = $paneltype;
$params['now'] = time();
$params['config'] = json_encode($params['config']);
$params['locationids'] = implode(',', $params['locationids']);
Database::exec($query, $params);
Message::addSuccess('config-saved');
Util::redirect('?do=locationinfo');
}
/**
* @return array{config: array, locationids: array}
*/
private function preparePanelConfigDefault(): array
{
// Check locations
$locationids = self::getLocationIdsFromRequest();
if (count($locationids) > 4) {
$locationids = array_slice($locationids, 0, 4);
}
// Build struct from POST
$conf = array(
'language' => Request::post('language', 'en', 'string'),
'mode' => Request::post('mode', 1, 'int'),
'vertical' => Request::post('vertical', false, 'bool'),
'eco' => Request::post('eco', false, 'bool'),
'prettytime' => Request::post('prettytime', false, 'bool'),
'roomplanner' => Request::post('roomplanner', true, 'bool'),
'startday' => Request::post('startday', 0, 'int'),
'scaledaysauto' => Request::post('scaledaysauto', false, 'bool'),
'daystoshow' => Request::post('daystoshow', 7, 'int'),
'rotation' => Request::post('rotation', 0, 'int'),
'scale' => Request::post('scale', 50, 'int'),
'switchtime' => Request::post('switchtime', 20, 'int'),
'calupdate' => Request::post('calupdate', 120, 'int'),
'roomupdate' => Request::post('roomupdate', 30, 'int'),
'hostname' => Request::post('hostname', false, 'bool'),
);
if ($conf['roomupdate'] < 15) {
$conf['roomupdate'] = 15;
}
if ($conf['calupdate'] < 30) {
$conf['calupdate'] = 30;
}
$overrides = array();
for ($i = 0; $i < sizeof($locationids); $i++) {
$overrideLoc = Request::post('override'.$locationids[$i], false, 'bool');
if ($overrideLoc) {
$overrideArray = array(
'mode' => Request::post('override'.$locationids[$i].'mode', 1, 'int'),
'roomplanner' => Request::post('override'.$locationids[$i].'roomplanner', false, 'bool'),
'vertical' => Request::post('override'.$locationids[$i].'vertical', false, 'bool'),
'startday' => Request::post('override'.$locationids[$i].'startday', 0, 'int'),
'scaledaysauto' => Request::post('override'.$locationids[$i].'scaledaysauto', false, 'bool'),
'daystoshow' => Request::post('override'.$locationids[$i].'daystoshow', 7, 'int'),
'rotation' => Request::post('override'.$locationids[$i].'rotation', 0, 'int'),
'scale' => Request::post('override'.$locationids[$i].'scale', 50, 'int'),
'switchtime' => Request::post('override'.$locationids[$i].'switchtime', 60, 'int'),
);
$overrides[$locationids[$i]] = $overrideArray;
}
}
$conf['overrides'] = $overrides;
return array('config' => $conf, 'locationids' => $locationids);
}
/**
* @return array{config: array, locationids: array}
*/
private function preparePanelConfigUrl(): array
{
$bookmarkNames = Request::post('bookmarkNames', [], 'array');
$bookmarkUrls = Request::post('bookmarkUrls', [], 'array');
$bookmarkString = '';
for ($i = 0; $i < count($bookmarkNames); $i++) {
if ($bookmarkNames[$i] == '' || $bookmarkUrls[$i] == '') continue;
$bookmarkString .= rawurlencode($bookmarkNames[$i]);
$bookmarkString .= ",";
$bookmarkString .= rawurlencode($bookmarkUrls[$i]);
$bookmarkString .= " ";
}
$bookmarkString = substr($bookmarkString, 0, -1);
$conf = array(
'url' => Request::post('url', 'https://www.bwlehrpool.de/', 'string'),
'insecure-ssl' => Request::post('insecure-ssl', 0, 'int'),
'reload-minutes' => max(0, Request::post('reloadminutes', 0, 'int')),
'whitelist' => preg_replace("/[\r\n]+/m", "\n", Request::post('whitelist', '', 'string')),
'blacklist' => preg_replace("/[\r\n]+/m", "\n", Request::post('blacklist', '', 'string')),
'split-login' => Request::post('split-login', 0, 'bool'),
'browser' => Request::post('browser', 'firefox', 'string'),
'interactive' => Request::post('interactive', '0', 'bool'),
'hw-video' => Request::post('hw-video', '0', 'bool'),
'bookmarks' => $bookmarkString ?: '',
'allow-tty' => Request::post('allow-tty', '', 'string'),
'zoom-factor' => Request::post('zoom-factor', 100, 'int'),
);
return array('config' => $conf, 'locationids' => []);
}
/**
* @return array{config: array, locationids: array}
*/
private function preparePanelConfigSummary(): array
{
// Build json structure
$conf = array(
'language' => Request::post('language', 'en', 'string'),
'eco' => Request::post('eco', false, 'bool'),
'roomplanner' => Request::post('roomplanner', false, 'bool'),
'panelupdate' => Request::post('panelupdate', 30, 'int')
);
if ($conf['panelupdate'] < 15) {
$conf['panelupdate'] = 15;
}
// Check locations
$locationids = self::getLocationIdsFromRequest();
return array('config' => $conf, 'locationids' => $locationids);
}
/**
* Updates the server settings in the db.
*/
private function updateServerSettings(): void
{
User::assertPermission('backend.edit');
$serverid = Request::post('id', -1, 'int');
$servername = Request::post('name', 'unnamed', 'string');
$servertype = Request::post('type', '', 'string');
$backend = CourseBackend::getInstance($servertype);
if ($backend === false) {
Message::addError('invalid-backend-type', $servertype);
Util::redirect('?do=locationinfo');
}
$tmptypeArray = $backend->getCredentialDefinitions();
$credentialsJson = array();
foreach ($tmptypeArray as $cred) {
$credentialsJson[$cred->property] = Request::post('prop-' . $cred->property);
}
$params = array(
'name' => $servername,
'type' => $servertype,
'credentials' => json_encode($credentialsJson)
);
if ($serverid === 0) {
Database::exec('INSERT INTO `locationinfo_coursebackend` (servername, servertype, credentials)
VALUES (:name, :type, :credentials)', $params);
$this->checkConnection(Database::lastInsertId());
} else {
$params['id'] = $serverid;
Database::exec('UPDATE `locationinfo_coursebackend`
SET servername = :name, servertype = :type, credentials = :credentials
WHERE serverid = :id', $params);
$this->checkConnection($serverid);
}
}
/**
* Checks if the server connection to a backend is valid.
*
* @param int $id Server id which connection should be checked.
*/
private function checkConnection(int $serverid = 0): void
{
if ($serverid === 0) {
ErrorHandler::traceError('checkConnection called with no server id');
}
User::assertPermission('backend.check');
$dbresult = Database::queryFirst("SELECT servertype, credentials
FROM `locationinfo_coursebackend`
WHERE serverid = :serverid", array('serverid' => $serverid));
$serverInstance = CourseBackend::getInstance($dbresult['servertype']);
if ($serverInstance === false) {
LocationInfo::setServerError($serverid, 'Unknown backend type: ' . $dbresult['servertype']);
return;
}
$credentialsOk = $serverInstance->setCredentials($serverid,
(array)json_decode($dbresult['credentials'], true));
if ($credentialsOk) {
$serverInstance->checkConnection();
}
LocationInfo::setServerError($serverid, $serverInstance->getErrors());
}
private function loadBackends(): array
{
// Get a list of all the backend types.
$servertypes = array();
$s_list = CourseBackend::getList();
foreach ($s_list as $s) {
$typeInstance = CourseBackend::getInstance($s);
$servertypes[$s] = $typeInstance->getDisplayName();
}
// Build list of defined backends
$serverlist = array();
$dbquery2 = Database::simpleQuery("SELECT * FROM `locationinfo_coursebackend` ORDER BY servername ASC");
foreach ($dbquery2 as $row) {
if (isset($servertypes[$row['servertype']])) {
$row['typename'] = $servertypes[$row['servertype']];
} else {
$row['typename'] = '[' . $row['servertype'] . ']';
$row['disabled'] = 'disabled';
}
if (!empty($row['error'])) {
$error = json_decode($row['error'], true);
if (isset($error['timestamp'])) {
$time = date('Y-m-d H:i', $error['timestamp']);
} else {
$time = '???';
}
$row['error'] = $error['error'];
$row['errtime'] = $time;
}
$serverlist[] = $row;
}
return $serverlist;
}
/**
* Show the list of backends
*/
private function showBackendsTable(array $serverlist): void
{
User::assertPermission('backend.*');
$data = array(
'serverlist' => $serverlist,
);
Permission::addGlobalTags($data['perms'], null, ['backend.edit', 'backend.check']);
// Pass the data to the html and render it.
Render::addTemplate('page-servers', $data);
}
private function showBackendLog(): void
{
$id = Request::get('serverid', false, 'int');
if ($id === false) {
Message::addError('main.parameter-missing', 'serverid');
Util::redirect('?do=locationinfo');
}
$server = Database::queryFirst('SELECT servername FROM locationinfo_coursebackend
WHERE serverid = :id', ['id' => $id]);
if ($server === false) {
Message::addError('invalid-server-id', $id);
Util::redirect('?do=locationinfo');
}
$server['list'] = [];
$res = Database::simpleQuery('SELECT dateline, message FROM locationinfo_backendlog
WHERE serverid = :id ORDER BY logid DESC LIMIT 100', ['id' => $id]);
foreach ($res as $row) {
$row['dateline_s'] = Util::prettyTime($row['dateline']);
$row['class'] = substr($row['message'], 0, 3) === '[F]' ? 'text-danger' : 'text-warning';
$row['message'] = Substr($row['message'], 3);
$server['list'][] = $row;
}
Render::addTemplate('page-server-log', $server);
}
private function showLocationsTable(): void
{
$allowedLocations = User::getAllowedLocations('location.edit');
if (empty($allowedLocations)) {
Message::addError('main.no-permission');
return;
}
$locations = Location::getLocations(0, 0, false, true);
// Get hidden state of all locations
$dbquery = Database::simpleQuery("SELECT li.locationid, li.serverid, li.serverlocationid, loc.openingtime, li.lastcalendarupdate, cb.servertype, cb.servername
FROM `locationinfo_locationconfig` AS li
LEFT JOIN `locationinfo_coursebackend` AS cb USING (serverid)
LEFT JOIN `location` AS loc USING (locationid)");
foreach ($dbquery as $row) {
$locid = (int)$row['locationid'];
if (!isset($locations[$locid]) || !in_array($locid, $allowedLocations))
continue;
$glyph = !empty($row['openingtime']) ? 'ok' : '';
$backend = '';
if (!empty($row['serverid']) && !empty($row['serverlocationid'])) {
$backend = $row['servername'] . '(' . $row['serverlocationid'] . ')';
}
$locations[$locid] += array(
'openingGlyph' => $glyph,
'strong' => $glyph === 'ok',
'backend' => $backend,
'lastCalendarUpdate' => Util::prettyTime($row['lastcalendarupdate']), // TODO
'backendMissing' => !CourseBackend::exists($row['servertype']),
);
}
$stack = array();
$depth = -1;
foreach ($locations as &$location) {
$location['allowed'] = in_array($location['locationid'], $allowedLocations);
while ($location['depth'] <= $depth) {
array_pop($stack);
$depth--;
}
while ($location['depth'] > $depth) {
array_push($stack, empty($location['openingGlyph']) && ($depth === -1 || empty($stack[$depth])) ? '' : 'arrow-up');
$depth++;
}
if ($depth > 0 && empty($location['openingGlyph'])) {
$location['openingGlyph'] = $stack[$depth - 1];
}
}
Render::addTemplate('page-locations', array(
'list' => array_values($locations),
));
}
private function showPanelsTable(): void
{
$visibleLocations = User::getAllowedLocations('panel.list');
if (in_array(0, $visibleLocations)) {
$visibleLocations = true;
}
$editLocations = User::getAllowedLocations('panel.edit');
if (in_array(0, $editLocations)) {
$editLocations = true;
}
$assignLocations = USer::getAllowedLocations('panel.assign-client');
if (in_array(0, $assignLocations)) {
$assignLocations = true;
}
if (empty($visibleLocations)) {
Message::addError('main.no-permission');
return;
}
$res = Database::simpleQuery('SELECT p.paneluuid, p.panelname, p.locationids, p.panelconfig,
p.paneltype FROM locationinfo_panel p
ORDER BY panelname ASC');
$hasRunmode = Module::isAvailable('runmode');
if ($hasRunmode) {
$runmodes = RunMode::getForModule(Page::getModule(), true);
}
$panels = array();
$locations = Location::getLocationsAssoc();
foreach ($res as $row) {
if ($row['paneltype'] === 'URL') {
$url = json_decode($row['panelconfig'], true)['url'];
$row['locations'] = $row['locationurl'] = $url;
$row['edit_disabled'] = empty($editLocations) ? 'disabled' : '';
$row['runmode_disabled'] = empty($assignLocations) ? 'disabled' : '';
} else {
$lids = explode(',', $row['locationids']);
// Permissions
if ($visibleLocations !== true && !empty(array_diff($lids, $visibleLocations))) {
continue;
}
$row['edit_disabled'] = $editLocations !== true && !empty(array_diff($lids, $editLocations))
? 'disabled' : '';
$row['runmode_disabled'] = $assignLocations !== true && !empty(array_diff($lids, $assignLocations))
? 'disabled' : '';
// Locations
$locs = array_map(function ($id) use ($locations) {
return isset($locations[$id]) ? $locations[$id]['locationname'] : "<<deleted=$id>>";
}, $lids);
$row['locations'] = implode(', ', $locs);
}
$len = mb_strlen($row['panelname']);
if ($len < 3) {
$row['panelname'] .= str_repeat(' ', 3 - $len);
}
if ($hasRunmode && isset($runmodes[$row['paneluuid']])) {
$row['assignedMachineCount'] = count($runmodes[$row['paneluuid']]);
}
$panels[] = $row;
}
Render::addTemplate('page-panels', compact('panels', 'hasRunmode'));
}
/**
* AJAX
*/
protected function doAjax()
{
User::load();
if (!User::isLoggedIn()) {
die('Unauthorized');
}
$action = Request::any('action');
$id = Request::any('id', 0, 'int');
if ($action === 'config-location') {
$this->ajaxConfigLocation($id);
} elseif ($action === 'serverSettings') {
$this->ajaxServerSettings($id);
}
}
/**
* Ajax the server settings.
*
* @param int $id Serverid
*/
private function ajaxServerSettings(int $id): void
{
User::assertPermission('backend.edit');
$oldConfig = Database::queryFirst('SELECT servername, servertype, credentials
FROM `locationinfo_coursebackend` WHERE serverid = :id', array('id' => $id));
// Credentials stuff.
if ($oldConfig !== false) {
$oldCredentials = json_decode($oldConfig['credentials'], true);
} else {
$oldCredentials = array();
}
// Get a list of all the backend types.
$serverBackends = array();
$s_list = CourseBackend::getList();
foreach ($s_list as $s) {
$backendInstance = CourseBackend::getInstance($s);
$backend = array(
'backendtype' => $s,
'display' => $backendInstance->getDisplayName(),
'active' => ($oldConfig !== false && $s === $oldConfig['servertype']),
);
$backend['credentials'] = $backendInstance->getCredentialDefinitions();
foreach ($backend['credentials'] as $cred) {
/* @var BackendProperty $cred */
if ($backend['active'] && isset($oldCredentials[$cred->property])) {
$cred->initForRender($backendInstance->mangleProperty($cred->property, $oldCredentials[$cred->property]));
} else {
$cred->initForRender();
}
$cred->title = Dictionary::translateFile('backend-' . $s, $cred->property);
$cred->helptext = Dictionary::translateFile('backend-' . $s, $cred->property . "_helptext");
$cred->credentialsHtml = Render::parse('server-prop-' . $cred->template, (array)$cred);
}
$serverBackends[] = $backend;
}
echo Render::parse('ajax-config-server', array('id' => $id,
'name' => $oldConfig['servername'],
'currentbackend' => $oldConfig['servertype'],
'backendList' => $serverBackends,
'defaultBlank' => $oldConfig === false));
}
/**
* Ajax the time table
*
* @param int $id id of the location
*/
private function ajaxConfigLocation(int $id): void
{
User::assertPermission('location.edit', $id);
$locConfig = Database::queryFirst("SELECT info.serverid, info.serverlocationid, loc.openingtime
FROM `locationinfo_locationconfig` AS info
LEFT JOIN `location` AS loc USING (locationid)
WHERE locationid = :id", array('id' => $id));
if ($locConfig !== false) {
$openingtimes = json_decode($locConfig['openingtime'], true);
} else {
$locConfig = array('serverid' => null, 'serverlocationid' => '');
}
if (!isset($openingtimes) || !is_array($openingtimes)) {
$openingtimes = array();
}
// Preset serverid from parent if none is set
if (is_null($locConfig['serverid'])) {
$chain = Location::getLocationRootChain($id);
if (!empty($chain)) {
$res = Database::simpleQuery("SELECT serverid, locationid FROM locationinfo_locationconfig
WHERE locationid IN (:locations) AND serverid IS NOT NULL", array('locations' => $chain));
$chain = array_flip($chain);
$best = false;
foreach ($res as $row) {
if ($best === false || $chain[$row['locationid']] < $chain[$best['locationid']]) {
$best = $row;
}
}
if ($best !== false) {
$locConfig['serverid'] = $best['serverid'];
}
}
}
// get Server / ID list
$res = Database::simpleQuery("SELECT serverid, servername FROM locationinfo_coursebackend ORDER BY servername ASC");
$serverList = array();
foreach ($res as $row) {
if ($row['serverid'] == $locConfig['serverid']) {
$row['selected'] = 'selected';
}
$serverList[] = $row;
}
$data = array(
'id' => $id,
'serverlist' => $serverList,
'serverlocationid' => $locConfig['serverlocationid'],
'openingtimes' => $this->compressTimes($openingtimes),
);
echo Render::parse('ajax-config-location', $data);
}
/**
* Checks if simple mode or expert mode is active.
* Tries to merge/compact the opening times schedule, and
* will actually modify the passed array iff it can be
* transformed into simple opening times.
*
* @return array new optimized openingtimes
*/
private function compressTimes(array $array): array
{
if (empty($array))
return [];
// Decompose by day
$DAYLIST = array_flip(['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']);
$new = [];
foreach ($array as $row) {
$s = Page_LocationInfo::getTime($row['openingtime']);
$e = Page_LocationInfo::getTime($row['closingtime']);
if ($s === null || $e === null || $e <= $s)
continue;
foreach ($row['days'] as $day) {
$day = $DAYLIST[$day] ?? -1;
if ($day === -1)
continue;
$this->addDay($new, $day, $s, $e);
}
}
// Merge by timespan
$merged = [];
foreach ($new as $day => $ranges) {
foreach ($ranges as $range) {
$range = $range[0] . '#' . $range[1];
if (!isset($merged[$range])) {
$merged[$range] = [];
}
$merged[$range][$day] = true;
}
}
// Finally transform to display struct
$new = [];
foreach ($merged as $span => $days) {
$out = explode('#', $span);
$new[] = [
'days' => $this->buildDaysString(array_keys($days)),
'open' => sprintf('%02d:%02d', ($out[0] / 60), ($out[0] % 60)),
'close' => sprintf('%02d:%02d', ($out[1] / 60), ($out[1] % 60)),
];
}
return $new;
}
/**
* @param array $daysArray List of days, "Monday", "Tuesday" etc. Must not contain duplicates.
* @return string Human-readable representation of list of days
*/
private function buildDaysString(array $daysArray): string
{
/* Dictionary::translate('monday') Dictionary::translate('tuesday') Dictionary::translate('wednesday')
* Dictionary::translate('thursday') Dictionary::translate('friday') Dictionary::translate('saturday')
* Dictionary::translate('sunday')
*/
$DAYLIST = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'];
$output = [];
$first = $last = -1;
sort($daysArray);
$daysArray[] = -1; // One trailing element to enforce a flush
foreach ($daysArray as $day) {
if ($first === -1) {
$first = $last = $day;
} elseif ($last + 1 === $day) {
// Chain
$last++;
} else {
$string = Dictionary::translate($DAYLIST[$first]);
if ($first !== $last) {
$string .= ($first + 1 === $last ? ",\xe2\x80\x89" : "\xe2\x80\x89-\xe2\x80\x89")
. Dictionary::translate($DAYLIST[$last]);
}
$output[] = $string;
$first = $last = $day;
}
}
return implode(', ', $output);
}
private function addDay(&$array, $day, $s, $e)
{
if (!isset($array[$day])) {
$array[$day] = array(array($s, $e));
return;
}
foreach (array_keys($array[$day]) as $key) {
$current = $array[$day][$key];
if ($s <= $current[0] && $e >= $current[1]) {
// Fully dominated
unset($array[$day][$key]);
continue; // Might partially overlap with additional ranges, keep going
}
if ($current[0] <= $s && $current[1] >= $s) {
// $start lies within existing range
if ($current[0] <= $e && $current[1] >= $e)
return; // Fully in existing range, do nothing
// $end seems to extend range we're checking against but $start lies within this range, update and keep going
$s = $current[0];
unset($array[$day][$key]);
continue;
}
// Last possibility: $start is before range, $end within range
if ($current[0] <= $e && $current[1] >= $e) {
// $start must lie before range start, otherwise we'd have hit the case above
$e = $current[1];
unset($array[$day][$key]);
//continue;
}
}
$array[$day][] = array($s, $e);
}
/**
* Ajax the config of a panel.
*/
private function showPanelConfig(): void
{
$id = Request::get('uuid', false, 'string');
if ($id === false) {
Message::addError('main.parameter-missing', 'uuid');
return;
}
$config = false;
if ($id === 'new-default') {
// Creating new panel
$panel = array(
'panelname' => '',
'locationids' => '',
'paneltype' => 'DEFAULT',
);
$id = 'new';
} elseif ($id === 'new-summary') {
// Creating new panel
$panel = array(
'panelname' => '',
'locationids' => '',
'paneltype' => 'SUMMARY',
);
$id = 'new';
} elseif ($id === 'new-url') {
// Creating new panel
$panel = array(
'panelname' => '',
'paneltype' => 'URL',
);
$id = 'new';
} else {
// Get Config data from db
$panel = Database::queryFirst("SELECT panelname, locationids, paneltype, panelconfig
FROM locationinfo_panel
WHERE paneluuid = :id", array('id' => $id));
if ($panel === false) {
Message::addError('invalid-panel-id', $id);
return;
}
$config = json_decode($panel['panelconfig'], true);
if (!isset($config['roomplanner'])) {
$config['roomplanner'] = true;
}
}
// Permission
$this->assertPanelPermission($panel, 'panel.edit');
$def = LocationInfo::defaultPanelConfig($panel['paneltype']);
if (!is_array($config)) {
$config = $def;
} else {
$config += $def;
}
$langs = Dictionary::getLanguages(true);
if (isset($config['language'])) {
foreach ($langs as &$lang) {
if ($lang['cc'] === $config['language']) {
$lang['selected'] = 'selected';
}
}
}
if ($panel['paneltype'] === 'DEFAULT') {
Render::addTemplate('page-config-panel-default', array(
'new' => $id === 'new',
'uuid' => $id,
'panelname' => $panel['panelname'],
'languages' => $langs,
'mode' => $config['mode'],
'vertical_checked' => $config['vertical'] ? 'checked' : '',
'eco_checked' => $config['eco'] ? 'checked' : '',
'prettytime_checked' => $config['prettytime'] ? 'checked' : '',
'roomplanner' => $config['roomplanner'],
'startday' => $config['startday'],
'scaledaysauto_checked' => $config['scaledaysauto'] ? 'checked' : '',
'daystoshow' => $config['daystoshow'],
'rotation' => $config['rotation'],
'scale' => $config['scale'],
'switchtime' => $config['switchtime'],
'calupdate' => $config['calupdate'],
'roomupdate' => $config['roomupdate'],
'locations' => Location::getLocations(),
'locationids' => $panel['locationids'],
'overrides' => json_encode($config['overrides']),
'hostname_checked' => $config['hostname'] ? 'checked' : '',
));
} elseif ($panel['paneltype'] === 'URL') {
$bookmarksArray = [];
if ($config['bookmarks'] !== '') {
$bookmarksConfig = explode(' ', $config['bookmarks']);
foreach ($bookmarksConfig AS $bookmark) {
$bookmark = explode(',', $bookmark);
$name = rawurldecode($bookmark[0]);
$url = rawurldecode($bookmark[1]);
$bookmarksArray[] = [
'name' => $name,
'url' => $url,
];
}
}
LocationInfo::cleanupUrlFilter($config);
Render::addTemplate('page-config-panel-url', array(
'new' => $id === 'new',
'uuid' => $id,
'panelname' => $panel['panelname'],
'url' => $config['url'],
'zoom-factor' => $config['zoom-factor'],
'ssl_checked' => $config['insecure-ssl'] ? 'checked' : '',
'reloadminutes' => (int)$config['reload-minutes'],
'whitelist' => str_replace("\n", "\r\n", $config['whitelist']),
'blacklist' => str_replace("\n", "\r\n", $config['blacklist']),
'split-login_checked' => $config['split-login'] ? 'checked' : '',
'browser' => $config['browser'],
'interactive_checked' => $config['interactive'] ? 'checked' : '',
'hwvideo_checked' => $config['hw-video'] ? 'checked' : '',
'bookmarks' => $bookmarksArray,
'allow-tty_' . $config['allow-tty'] . '_checked' => 'checked',
));
} else {
Render::addTemplate('page-config-panel-summary', array(
'new' => $id === 'new',
'uuid' => $id,
'panelname' => $panel['panelname'],
'languages' => $langs,
'panelupdate' => $config['panelupdate'],
'roomplanner' => $config['roomplanner'],
'locations' => Location::getLocations(),
'locationids' => $panel['locationids'],
'eco_checked' => $config['eco'] ? 'checked' : '',
));
}
}
private function showPanel(): void
{
$uuid = Request::get('uuid', false, 'string');
if ($uuid === false) {
http_response_code(400);
die('Missing parameter uuid');
}
$type = InfoPanel::getConfig($uuid, $config);
if ($type === null) {
http_response_code(404);
die('Panel with given uuid not found');
}
if ($type === 'URL') {
Util::redirect($config['url']);
}
$data = array();
preg_match('#^/(.*)/#', $_SERVER['PHP_SELF'], $script);
preg_match('#^/([^?]+)/#', $_SERVER['REQUEST_URI'], $request);
if ($script[1] !== $request[1]) {
// Working with server-side redirects
$data['api'] = 'api/';
} else {
// 1:1
$data['api'] = 'api.php?do=locationinfo&';
}
if ($type === 'DEFAULT') {
$data += array(
'uuid' => $uuid,
'config' => json_encode($config),
'language' => $config['language'],
);
die(Render::parse('frontend-default', $data, null, $config['language']));
}
if ($type === 'SUMMARY') {
$locations = LocationInfo::getLocationsOr404($uuid, false);
$config['tree'] = Location::getRecursive($locations);
$data += array(
'uuid' => $uuid,
'config' => json_encode($config),
'language' => $config['language'],
);
die(Render::parse('frontend-summary', $data, null, $config['language']));
}
http_response_code(500);
die('Unknown panel type ' . $type);
}
/**
* @param string|array $panelOrUuid UUID of panel, or array with keys paneltype and locationds
* @param int[] $additionalLocations
*/
private function assertPanelPermission($panelOrUuid, string $permission, array $additionalLocations = null): void
{
if (is_array($panelOrUuid)) {
$panel = $panelOrUuid;
} else {
$panel = Database::queryFirst('SELECT paneltype, locationids FROM locationinfo_panel
WHERE paneluuid = :uuid', ['uuid' => $panelOrUuid]);
}
if ($panel === false || $panel['paneltype'] === 'URL' || empty($panel['locationids'])) {
if (empty($additionalLocations)) {
User::assertPermission($permission, null, '?do=locationinfo');
return;
}
}
$allowed = User::getAllowedLocations($permission);
if (in_array(0, $allowed))
return;
if (!empty($allowed)) {
if (isset($panel['locationids'])) {
$locations = explode(',', $panel['locationids']);
} else {
$locations = [];
}
if (!empty($additionalLocations)) {
$locations = array_merge($locations, $additionalLocations);
}
if (empty(array_diff($locations, $allowed)))
return;
}
Message::addError('main.no-permission');
Util::redirect('?do=locationinfo');
}
}