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'] : "<>"; }, $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, ]; } } if (empty($config['blacklist']) && $config['whitelist'] === '*' && !empty($config['urllist'])) { if ($config['iswhitelist']) { $config['whitelist'] = str_replace(' ', "\n", $config['urllist']); } else { $config['blacklist'] = str_replace(' ', "\n", $config['urllist']); } } 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'); } }