<?php
class LocationInfo
{
/**
* Gets the pc data and returns it's state.
*
* @param array $pc The pc data from the db. Array('state' => xx, 'lastseen' => xxx)
* @return string pc state
*/
public static function getPcState(array $pc): string
{
$lastseen = (int)$pc['lastseen'];
$NOW = time();
if ($pc['state'] === 'OFFLINE' && $NOW - $lastseen > 30 * 86400) {
return "BROKEN";
}
return $pc['state'];
}
/**
* Return list of locationids associated with given panel.
*
* @param string $paneluuid panel
* @param bool $recursive if true and paneltype == SUMMARY the result is recursive with all child room ids.
* @return int[] locationIds
*/
public static function getLocationsOr404(string $paneluuid, bool $recursive = true): array
{
$panel = Database::queryFirst('SELECT paneltype, locationids FROM locationinfo_panel WHERE paneluuid = :paneluuid',
compact('paneluuid'));
if ($panel !== false) {
$idArray = array_map('intval', explode(',', $panel['locationids']));
if ($panel['paneltype'] == "SUMMARY" && $recursive) {
$idList = Location::getRecursiveFlat($idArray);
$idArray = array_keys($idList);
}
return $idArray;
}
http_response_code(404);
die('Panel not found');
}
/**
* Set current error message of given server. Pass null or false to clear.
*
* @param int $serverId id of server
* @param string|array $message error message to set, array of error message struct, null or false clears error.
*/
public static function setServerError(int $serverId, $message): void
{
if (is_array($message)) {
$fatal = false;
foreach ($message as $m) {
if ($m['fatal']) {
$fatal = $m['message'];
}
Database::exec('INSERT INTO locationinfo_backendlog (serverid, dateline, message)
VALUES (:sid, :dateline, :message)', [
'sid' => $serverId,
'dateline' => $m['time'],
'message' => ($m['fatal'] ? '[F]' : '[W]') . $m['message'],
]);
}
$message = $fatal;
}
if ($message === false || $message === null) {
Database::exec("UPDATE `locationinfo_coursebackend` SET error = NULL
WHERE serverid = :id", array('id' => $serverId));
} else {
if (empty($message)) {
$message = '<empty error message>';
}
$error = json_encode(array(
'timestamp' => time(),
'error' => (string)$message
));
Database::exec("UPDATE `locationinfo_coursebackend` SET error = :error
WHERE serverid = :id", array('id' => $serverId, 'error' => $error));
}
}
/**
* Creates and returns a default config for room that didn't save a config yet.
*
* @return array Return a default config.
*/
public static function defaultPanelConfig(string $type): array
{
if ($type === 'DEFAULT') {
return array(
'language' => defined('LANG') ? LANG : 'en',
'mode' => 1,
'vertical' => false,
'eco' => false,
'prettytime' => true,
'roomplanner' => true,
'scaledaysauto' => true,
'startday' => 0,
'daystoshow' => 7,
'rotation' => 0,
'scale' => 50,
'switchtime' => 20,
'calupdate' => 30,
'roomupdate' => 15,
'configupdate' => 180,
'overrides' => [],
'hostname' => false,
);
}
if ($type === 'SUMMARY') {
return array(
'language' => defined('LANG') ? LANG : 'en',
'roomplanner' => true,
'eco' => false,
'panelupdate' => 60,
);
}
if ($type === 'URL') {
return array(
'whitelist' => '*',
'blacklist' => '',
'insecure-ssl' => 0,
'reload-minutes' => 0,
'split-login' => 0,
'browser' => 'firefox',
'interactive' => 0,
'hw-video' => 0,
'bookmarks' => '',
'allow-tty' => '',
'url' => '',
'zoom-factor' => 100,
);
}
return array();
}
/**
* Gets the calendar of the given ids.
*
* @param int[] $idList list with the location ids.
* @return array Calendar.
*/
public static function getCalendar(array $idList, bool $forceCached = false): array
{
if (empty($idList))
return [];
$resultArray = array();
if ($forceCached) {
$res = Database::simpleQuery("SELECT locationid, calendar FROM locationinfo_locationconfig
WHERE Length(calendar) > 10 AND lastcalendarupdate > UNIX_TIMESTAMP() - 86400*3");
foreach ($res as $row) {
$resultArray[] = [
'id' => (int)$row['locationid'],
'calendar' => json_decode($row['calendar'], true),
];
}
return $resultArray;
}
// Build SQL query for multiple ids.
$query = "SELECT l.locationid, l.serverid, l.serverlocationid, s.servertype, s.credentials
FROM `locationinfo_locationconfig` AS l
INNER JOIN locationinfo_coursebackend AS s ON (s.serverid = l.serverid)
WHERE l.locationid IN (:idlist)
ORDER BY s.servertype ASC";
$dbquery = Database::simpleQuery($query, array('idlist' => array_values($idList)));
$serverList = array();
foreach ($dbquery as $dbresult) {
if (!isset($serverList[$dbresult['serverid']])) {
$serverList[$dbresult['serverid']] = array(
'credentials' => (array)json_decode($dbresult['credentials'], true),
'type' => $dbresult['servertype'],
'idlist' => array()
);
}
$serverList[$dbresult['serverid']]['idlist'][] = $dbresult['locationid'];
}
foreach ($serverList as $serverid => $server) {
$serverInstance = CourseBackend::getInstance($server['type']);
if ($serverInstance === false) {
EventLog::warning('Cannot fetch schedule for location (' . implode(', ', $server['idlist']) . ')'
. ': Backend type ' . $server['type'] . ' unknown. Disabling location.');
Database::exec("UPDATE locationinfo_locationconfig SET serverid = NULL WHERE locationid IN (:lid)",
array('lid' => $server['idlist']));
continue;
}
$credentialsOk = $serverInstance->setCredentials($serverid, $server['credentials']);
if ($credentialsOk) {
$calendarFromBackend = $serverInstance->fetchSchedule($server['idlist']);
} else {
$calendarFromBackend = array();
}
LocationInfo::setServerError($serverid, $serverInstance->getErrors());
foreach ($calendarFromBackend as $key => $value) {
$resultArray[] = array(
'id' => (int)$key,
'calendar' => $value,
);
}
}
return $resultArray;
}
public static function getAllCalendars(bool $forceCached): array
{
$locations = Database::queryColumnArray("SELECT locationid FROM location");
$calendars = [];
foreach (LocationInfo::getCalendar($locations, $forceCached) as $cal) {
if (empty($cal['calendar']))
continue;
$calendars[$cal['id']] = $cal['calendar'];
}
return $calendars;
}
public static function extractCurrentEvent(array $calendar): string
{
$NOW = time();
foreach ($calendar as $event) {
$start = strtotime($event['start']);
$end = strtotime($event['end']) + 60;
if ($NOW >= $start && $NOW <= $end)
return $event['title'];
}
return '';
}
/**
* Transform legacy url filter config if found, remove no-op filter setups.
* @param array $config The configuration array to be cleaned up. By reference.
*/
public static function cleanupUrlFilter(array &$config): void
{
// First, simple trimming of white-space around the list, and making sure the keys exist
$config['blacklist'] = trim($config['blacklist'] ?? '');
$config['whitelist'] = trim($config['whitelist'] ?? '');
if (empty($config['blacklist']) && empty($config['whitelist'])) {
// Mangle non-upgraded configurations: They only have one list and a bool specifying if it's a black- or whitelist
if (!empty($config['urllist'])) {
if ($config['iswhitelist'] ?? false) {
$config['whitelist'] = str_replace(' ', "\n", $config['urllist']);
} else {
$config['blacklist'] = str_replace(' ', "\n", $config['urllist']);
}
unset($config['urllist'], $config['iswhitelist']);
}
} elseif ((empty($config['blacklist']) || self::isAnyUrlMatch($config['blacklist']))
&& self::isAnyUrlMatch($config['whitelist'])) {
// Blocking everything/nothing and allowing everything is a no-op for all three browsers, so clear both lists
$config['blacklist'] = '';
$config['whitelist'] = '';
}
}
/**
* Check if the given pattern would be interpreted as matching any URL.
* @param string $url The URL to check
*/
private static function isAnyUrlMatch(string $url): bool
{
return $url === '*' || $url === '*://*' || $url === '*://*/' || $url === '*://*/*';
}
}