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) { 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() { $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 function getTime($str) { $str = explode(':', $str); if (count($str) !== 2) return false; if ($str[0] < 0 || $str[0] > 23 || $str[1] < 0 || $str[1] > 59) return false; return $str[0] * 60 + $str[1]; } private function writeLocationConfig() { // Check locations $locationid = Request::post('locationid', false, 'int'); if ($locationid === false) { Message::addError('main.parameter-missing', 'locationid'); return false; } if (Location::get($locationid) === false) { Message::addError('location.invalid-location-id', $locationid); return false; } 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; } // Opening times $openingtimes = Request::post('openingtimes', '', 'string'); if ($openingtimes !== '') { $openingtimes = json_decode($openingtimes, true); if (!is_array($openingtimes)) { $openingtimes = ''; } else { $mangled = array(); foreach (array_keys($openingtimes) as $key) { $entry = $openingtimes[$key]; if (!isset($entry['days']) || !is_array($entry['days']) || empty($entry['days'])) { Message::addError('ignored-line-no-days'); continue; } $s = $this->getTime($entry['openingtime']); $e = $this->getTime($entry['closingtime']); if ($s === false) { Message::addError('ignored-invalid-start', $entry['openingtime']); continue; } if ($e === false) { Message::addError('ignored-invalid-end', $entry['closingtime']); continue; } if ($e <= $s) { Message::addError('ignored-invalid-range', $entry['openingtime'], $entry['closingtime']); continue; } unset($entry['tag']); $mangled[] = $entry; } if (empty($mangled)) { $openingtimes = null; } else { $openingtimes = json_encode($mangled); } } } $NOW = time(); // Check if openingtimes changed $res = Database::queryFirst('SELECT openingtime FROM locationinfo_locationconfig WHERE locationid = :locationid', compact('locationid')); $otChanged = $res === false || $res['openingtime'] !== $openingtimes; Database::exec("INSERT INTO `locationinfo_locationconfig` (locationid, serverid, serverlocationid, openingtime, lastcalendarupdate, lastchange) VALUES (:id, :insertserverid, :serverlocationid, :openingtimes, 0, :now) ON DUPLICATE KEY UPDATE serverid = IF(:ignore_server AND serverid IS NULL, NULL, :serverid), serverlocationid = VALUES(serverlocationid), openingtime = VALUES(openingtime), lastcalendarupdate = 0, lastchange = VALUES(lastchange)", array( 'id' => $locationid, 'insertserverid' => $insertServerId, 'serverid' => $serverid, 'openingtimes' => $openingtimes, 'serverlocationid' => $serverlocationid, 'ignore_server' => $ignoreServer, 'now' => $NOW, )); if ($otChanged) { $tree = Location::getLocationsAssoc(); $todo = array(); $done = array(); foreach ($tree as $l) { if ($l['parentlocationid'] == $locationid) { $todo[] = $l['locationid']; } } while (!empty($todo)) { $loc = array_pop($todo); if (in_array($loc, $done)) continue; $done[] = $loc; // See if this one inherits $res = Database::queryFirst('SELECT openingtime FROM locationinfo_locationconfig WHERE locationid = :loc', compact('loc')); if ($res === false) { $res = Database::exec('INSERT INTO locationinfo_locationconfig (locationid, lastchange) VALUES (:locationid, :now) ON DUPLICATE KEY UPDATE lastchange = :now', array('locationid' => $loc, 'now' => $NOW)); } elseif (strlen($res['openingtime']) < 5) { $res = Database::exec('UPDATE locationinfo_locationconfig SET lastchange = :now, openingtime = NULL WHERE locationid = :locationid', array('locationid' => $loc, 'now' => $NOW)); } else { $res = 0; } if ($res > 0) { // Row was updated, which means the openingtime column was empty, which means the openingtime is inherited, descend further $todo = array_merge($todo, $tree[$loc]['children']); foreach ($tree as $l) { if ($l['parentlocationid'] == $loc) { $todo[] = $l['locationid']; } } } } } 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, )); } } return true; } /** * 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 * @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($failIfEmpty) { $locationids = Request::post('locationids', false, 'string'); if ($locationids === false) { if (!$failIfEmpty) return array(); Message::addError('main.parameter-missing', 'locationids'); Util::redirect('?do=locationinfo'); } $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 ($failIfEmpty && empty($locationids)) { Message::addError('main.parameter-empty', 'locationids'); Util::redirect('?do=locationinfo'); } return $locationids; } /** * Updated the config in the db. */ private function writePanelConfig() { // 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'); } private function preparePanelConfigDefault() { // Check locations $locationids = self::getLocationIdsFromRequest(true); 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); } private function preparePanelConfigUrl() { $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')), 'iswhitelist' => Request::post('iswhitelist', 0, 'int'), 'urllist' => preg_replace("/[\r\n\\s]+/ms", ' ', Request::post('urllist', '', 'string')), 'split-login' => Request::post('split-login', 0, 'bool'), 'browser' => Request::post('browser', 'slx-browser', 'string'), 'interactive' => Request::post('interactive', '0', 'bool'), 'bookmarks' => $bookmarkString ? $bookmarkString : '', ); return array('config' => $conf, 'locationids' => []); } private function preparePanelConfigSummary() { // 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(true); return array('config' => $conf, 'locationids' => $locationids); } /** * Updates the server settings in the db. */ private function updateServerSettings() { 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($serverid = 0) { if ($serverid === 0) { Util::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, json_decode($dbresult['credentials'], true)); if ($credentialsOk) { $serverInstance->checkConnection(); } LocationInfo::setServerError($serverid, $serverInstance->getErrors()); } private function loadBackends() { // 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"); while ($row = $dbquery2->fetch(PDO::FETCH_ASSOC)) { 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($serverlist) { 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() { $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]); while ($row = $res->fetch(PDO::FETCH_ASSOC)) { $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() { $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, li.openingtime, li.lastcalendarupdate, cb.servertype, cb.servername FROM `locationinfo_locationconfig` AS li LEFT JOIN `locationinfo_coursebackend` AS cb USING (serverid)"); while ($row = $dbquery->fetch(PDO::FETCH_ASSOC)) { $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, '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() { $visibleLocations = User::getAllowedLocations('panel.list'); $editLocations = User::getAllowedLocations('panel.edit'); $assignLocations = USer::getAllowedLocations('panel.assign-client'); 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(); while ($row = $res->fetch(PDO::FETCH_ASSOC)) { 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 (!empty(array_diff($lids, $visibleLocations))) { continue; } $row['edit_disabled'] = !empty(array_diff($lids, $editLocations)) ? 'disabled' : ''; $row['runmode_disabled'] = !empty(array_diff($lids, $assignLocations)) ? 'disabled' : ''; // Locations $locs = array_map(function ($id) use ($locations) { return isset($locations[$id]) ? $locations[$id]['locationname'] : $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($id) { 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) { if ($backend['active'] && isset($oldCredentials[$cred->property])) { $cred->initForRender($oldCredentials[$cred->property]); } else { $cred->initForRender(); } $cred->title = Dictionary::translateFile('backend-' . $s, $cred->property, true); $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($id) { User::assertPermission('location.edit', $id); $locConfig = Database::queryFirst("SELECT serverid, serverlocationid, openingtime FROM `locationinfo_locationconfig` 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; while ($row = $res->fetch(PDO::FETCH_ASSOC)) { 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(); while ($row = $res->fetch(PDO::FETCH_ASSOC)) { if ($row['serverid'] == $locConfig['serverid']) { $row['selected'] = 'selected'; } $serverList[] = $row; } $data = array( 'id' => $id, 'serverlist' => $serverList, 'serverlocationid' => $locConfig['serverlocationid'], ); $data['expertMode'] = !$this->isSimpleMode($openingtimes); // !! isSimpleMode might have changed $openingtimes, so order is important here... $data['schedule_data'] = json_encode($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 easy opening times. * * @param array $array of the saved openingtimes. * @return bool True if simple mode, false if expert mode */ private function isSimpleMode(&$array) { if (empty($array)) return true; // Decompose by day $new = array(); foreach ($array as $row) { $s = $this->getTime($row['openingtime']); $e = $this->getTime($row['closingtime']); if ($s === false || $e === false || $e <= $s) continue; foreach ($row['days'] as $day) { $this->addDay($new, $day, $s, $e); } } // Merge by timespan, but always keep saturday and sunday separate $merged = array(); foreach ($new as $day => $ranges) { foreach ($ranges as $range) { if ($day === 'Saturday' || $day === 'Sunday') { $add = $day; } else { $add = ''; } $key = '#' . $range[0] . '#' . $range[1] . '#' . $add; if (!isset($merged[$key])) { $merged[$key] = array(); } $merged[$key][$day] = true; } } // Check if it passes as simple mode if (count($merged) > 3) return false; foreach ($merged as $days) { if (count($days) === 5) { $res = array_keys($days); $res = array_intersect($res, array("Monday", "Tuesday", "Wednesday", "Thursday", "Friday")); if (count($res) !== 5) return false; } elseif (count($days) === 1) { if (!isset($days['Saturday']) && !isset($days['Sunday'])) { return false; } } else { return false; } } // Valid simple mode, finally transform back to what we know $new = array(); foreach ($merged as $span => $days) { preg_match('/^#(\d+)#(\d+)#/', $span, $out); $new[] = array( 'days' => array_keys($days), 'openingtime' => floor($out[1] / 60) . ':' . ($out[1] % 60), 'closingtime' => floor($out[2] / 60) . ':' . ($out[2] % 60), ); } $array = $new; return true; } 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. * * @param $id Location ID */ private function showPanelConfig() { $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, ]; } } Render::addTemplate('page-config-panel-url', array( 'new' => $id === 'new', 'uuid' => $id, 'panelname' => $panel['panelname'], 'url' => $config['url'], 'ssl_checked' => $config['insecure-ssl'] ? 'checked' : '', 'reloadminutes' => (int)$config['reload-minutes'], 'iswhitelist_' . $config['iswhitelist'] . '_checked' => 'checked', 'urllist' => str_replace(' ', "\r\n", $config['urllist']), 'split-login_checked' => $config['split-login'] ? 'checked' : '', 'browser' => $config['browser'], 'interactive_checked' => $config['interactive'] ? 'checked' : '', 'bookmarks' => $bookmarksArray, )); } 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() { $uuid = Request::get('uuid', false, 'string'); if ($uuid === false) { http_response_code(400); die('Missing parameter uuid'); } $type = InfoPanel::getConfig($uuid, $config); if ($type === false) { 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]) { $data['dirprefix'] = $script[1] . '/'; } if ($type === 'DEFAULT') { $data += array( 'uuid' => $uuid, 'config' => json_encode($config), 'language' => $config['language'], ); die(Render::parse('frontend-default', $data, $module = false, $lang = $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, $module = false, $lang = $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 string $permission * @param null|int[] $additionalLocations */ private function assertPanelPermission($panelOrUuid, $permission, $additionalLocations = null) { 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'); } }