diff options
Diffstat (limited to 'modules-available/locationinfo')
22 files changed, 704 insertions, 617 deletions
diff --git a/modules-available/locationinfo/api.inc.php b/modules-available/locationinfo/api.inc.php index d3ff9ebd..24919ba1 100644 --- a/modules-available/locationinfo/api.inc.php +++ b/modules-available/locationinfo/api.inc.php @@ -14,7 +14,7 @@ function HandleParameters() $get = Request::get('get', 0, 'string'); $uuid = Request::get('uuid', false, 'string'); - $output = false; + $output = null; if ($get === "timestamp") { $output = array('ts' => getLastChangeTs($uuid)); } elseif ($get === "machines") { @@ -24,7 +24,7 @@ function HandleParameters() $output = array_values($output); } elseif ($get === "config") { $type = InfoPanel::getConfig($uuid, $output); - if ($type === false) { + if ($type === null) { http_response_code(404); die('Panel not found'); } @@ -36,9 +36,9 @@ function HandleParameters() $output = getLocationTree($locationIds); } elseif ($get === "calendar") { $locationIds = LocationInfo::getLocationsOr404($uuid); - $output = getCalendar($locationIds); + $output = LocationInfo::getCalendar($locationIds); } - if ($output !== false) { + if ($output !== null) { Header('Content-Type: application/json; charset=utf-8'); echo json_encode($output); } else { @@ -59,7 +59,7 @@ function HandleParameters() * @param string $paneluuid panels uuid * @return int UNIX_TIMESTAMP */ -function getLastChangeTs($paneluuid) +function getLastChangeTs(string $paneluuid): int { $panel = Database::queryFirst('SELECT lastchange, locationids FROM locationinfo_panel WHERE paneluuid = :paneluuid', compact('paneluuid')); @@ -84,7 +84,7 @@ function getLastChangeTs($paneluuid) * @param int[] $idList list of the location ids. * @return array aggregated PC states */ -function getPcStates($idList, $paneluuid) +function getPcStates(array $idList, string $paneluuid): array { $pcStates = array(); foreach ($idList as $id) { @@ -130,18 +130,17 @@ function getPcStates($idList, $paneluuid) * @param int[] $idList Array list of the locations. * @return array location tree data */ -function getLocationTree($idList) +function getLocationTree(array $idList): array { if (in_array(0, $idList)) { return array_values(Location::getTree()); } $locations = Location::getTree(); - $ret = findLocations($locations, $idList); - return $ret; + return findLocations($locations, $idList); } -function findLocations($locations, $idList) +function findLocations(array $locations, array $idList): array { $ret = array(); foreach ($locations as $location) { @@ -153,69 +152,3 @@ function findLocations($locations, $idList) } return $ret; } - -// ########## <Calendar> ########### -/** - * Gets the calendar of the given ids. - * - * @param int[] $idList list with the location ids. - * @return array Calendar. - */ -function getCalendar($idList) -{ - if (empty($idList)) - return []; - - // 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(); - while ($dbresult = $dbquery->fetch(PDO::FETCH_ASSOC)) { - if (!isset($serverList[$dbresult['serverid']])) { - $serverList[$dbresult['serverid']] = array( - 'credentials' => json_decode($dbresult['credentials'], true), - 'type' => $dbresult['servertype'], - 'idlist' => array() - ); - } - $serverList[$dbresult['serverid']]['idlist'][] = $dbresult['locationid']; - } - - $resultArray = array(); - 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()); - - if (is_array($calendarFromBackend)) { - foreach ($calendarFromBackend as $key => $value) { - $resultArray[] = array( - 'id' => $key, - 'calendar' => $value, - ); - } - } - } - return $resultArray; -} - -// ########## </Calendar> ########## diff --git a/modules-available/locationinfo/exchange-includes/jamesiarmes/PhpEws/ArrayType/ArrayOfStringsType.php b/modules-available/locationinfo/exchange-includes/jamesiarmes/PhpEws/ArrayType/ArrayOfStringsType.php index 6443d31d..28792929 100644 --- a/modules-available/locationinfo/exchange-includes/jamesiarmes/PhpEws/ArrayType/ArrayOfStringsType.php +++ b/modules-available/locationinfo/exchange-includes/jamesiarmes/PhpEws/ArrayType/ArrayOfStringsType.php @@ -32,6 +32,6 @@ class ArrayOfStringsType extends ArrayType */ public function __toString() { - return $this->String; + return implode(' + ', $this->String); } } diff --git a/modules-available/locationinfo/exchange-includes/jamesiarmes/PhpEws/Autodiscover.php b/modules-available/locationinfo/exchange-includes/jamesiarmes/PhpEws/Autodiscover.php index 8198137d..8c60a4c8 100644 --- a/modules-available/locationinfo/exchange-includes/jamesiarmes/PhpEws/Autodiscover.php +++ b/modules-available/locationinfo/exchange-includes/jamesiarmes/PhpEws/Autodiscover.php @@ -891,6 +891,6 @@ class Autodiscover protected function tryViaUrl($url, $timeout = 6) { $result = $this->doNTLMPost($url, $timeout); - return ($result ? true : false); + return (bool)$result; } } diff --git a/modules-available/locationinfo/exchange-includes/jamesiarmes/PhpNtlm/SoapClient.php b/modules-available/locationinfo/exchange-includes/jamesiarmes/PhpNtlm/SoapClient.php index 21c77cbf..98f23dfa 100644 --- a/modules-available/locationinfo/exchange-includes/jamesiarmes/PhpNtlm/SoapClient.php +++ b/modules-available/locationinfo/exchange-includes/jamesiarmes/PhpNtlm/SoapClient.php @@ -26,6 +26,9 @@ class SoapClient extends \SoapClient */ protected $options; + protected $__last_response; + protected $__last_request_headers; + /** * {@inheritdoc} * @@ -68,7 +71,7 @@ class SoapClient extends \SoapClient /** * {@inheritdoc} */ - public function __doRequest($request, $location, $action, $version, $one_way = 0) + public function __doRequest($request, $location, $action, $version, $oneWay = 0) { $headers = $this->buildHeaders($action); $this->__last_request = $request; diff --git a/modules-available/locationinfo/inc/coursebackend.inc.php b/modules-available/locationinfo/inc/coursebackend.inc.php index 6e4d77ac..ea1bebac 100644 --- a/modules-available/locationinfo/inc/coursebackend.inc.php +++ b/modules-available/locationinfo/inc/coursebackend.inc.php @@ -39,7 +39,7 @@ abstract class CourseBackend $this->errors = []; } - protected final function addError($message, $fatal) + protected final function addError(string $message, bool $fatal) { $this->errors[] = ['time' => time(), 'message' => $message, 'fatal' => $fatal]; } @@ -55,7 +55,7 @@ abstract class CourseBackend self::$backendTypes = array(); foreach (glob(dirname(__FILE__) . '/coursebackend/coursebackend_*.inc.php', GLOB_NOSORT) as $file) { require_once $file; - preg_match('#coursebackend_([^/\.]+)\.inc\.php$#i', $file, $out); + preg_match('#coursebackend_([^/.]+)\.inc\.php$#i', $file, $out); $className = 'CourseBackend_' . $out[1]; if (!class_exists($className)) { trigger_error("Backend type source unit $file doesn't seem to define class $className", E_USER_ERROR); @@ -71,13 +71,13 @@ abstract class CourseBackend * * @return array list of backends */ - public static function getList() + public static function getList(): array { self::loadDb(); return array_keys(self::$backendTypes); } - public static function exists($backendType) + public static function exists($backendType): bool { self::loadDb(); return isset(self::$backendTypes[$backendType]); @@ -89,7 +89,7 @@ abstract class CourseBackend * @param string $backendType name of module type * @return \CourseBackend|false module instance */ - public static function getInstance($backendType) + public static function getInstance(string $backendType) { self::loadDb(); if (!isset(self::$backendTypes[$backendType])) { @@ -106,18 +106,18 @@ abstract class CourseBackend /** * @return string return display name of backend */ - public abstract function getDisplayName(); + public abstract function getDisplayName(): string; /** * @returns \BackendProperty[] list of properties that need to be set */ - public abstract function getCredentialDefinitions(); + public abstract function getCredentialDefinitions(): array; /** * @return boolean true if the connection works, false otherwise */ - public abstract function checkConnection(); + public abstract function checkConnection(): bool; /** * uses json to setCredentials, the json must follow the form given in @@ -126,42 +126,39 @@ abstract class CourseBackend * @param array $data assoc array with data required by backend * @returns bool if the credentials were in the correct format */ - public abstract function setCredentialsInternal($data); + public abstract function setCredentialsInternal(array $data): bool; /** * @return int desired caching time of results, in seconds. 0 = no caching */ - public abstract function getCacheTime(); + public abstract function getCacheTime(): int; /** * @return int age after which timetables are no longer refreshed should be * greater then CacheTime */ - public abstract function getRefreshTime(); + public abstract function getRefreshTime(): int; /** * Internal version of fetch, to be overridden by subclasses. * - * @param $roomIds array with remote IDs for wanted rooms + * @param $requestedRoomIds array with remote IDs for wanted rooms * @return array a recursive array that uses the roomID as key * and has the schedule array as value. A schedule array contains an array in this format: * ["start"=>'JJJJ-MM-DD"T"HH:MM:SS',"end"=>'JJJJ-MM-DD"T"HH:MM:SS',"title"=>string] */ - protected abstract function fetchSchedulesInternal($roomId); + protected abstract function fetchSchedulesInternal(array $requestedRoomIds): array; /** * In case you want to sanitize or otherwise mangle a property for your backend, * override this. - * @param string $prop - * @param $value - * @return mixed */ - public function mangleProperty($prop, $value) + public function mangleProperty(string $prop, $value) { return $value; } - private static function fixTime(&$start, &$end) + private static function fixTime(string &$start, string &$end): bool { if (!preg_match('/^(\d{2}|\d{4})-?\d{2}-?\d{2}-?T\d{1,2}:?\d{2}:?(\d{2})?$/', $start)) return false; @@ -178,14 +175,10 @@ abstract class CourseBackend * Method for fetching the schedule of the given rooms on a server. * * @param array $requestedLocationIds array of room ID to fetch - * @return array|bool array containing the timetables as value and roomid as key as result, or false on error + * @return array array containing the timetables as value and roomid as key as result, or false on error */ - public final function fetchSchedule($requestedLocationIds) + public final function fetchSchedule(array $requestedLocationIds): array { - if (!is_array($requestedLocationIds)) { - $this->addError('No array of roomids was given to fetchSchedule', false); - return false; - } if (empty($requestedLocationIds)) return array(); $requestedLocationIds = array_values($requestedLocationIds); @@ -195,7 +188,7 @@ abstract class CourseBackend array('locations' => $requestedLocationIds)); $returnValue = []; $remoteIds = []; - while ($row = $dbquery1->fetch(PDO::FETCH_ASSOC)) { + foreach ($dbquery1 as $row) { // Check if in cache - if lastUpdate is null then it is interpreted as 1970 if ($row['lastcalendarupdate'] + $this->getCacheTime() < $NOW) { $remoteIds[$row['locationid']] = $row['serverlocationid']; @@ -222,7 +215,7 @@ abstract class CourseBackend 'lastuse' => $NOW - $this->getRefreshTime(), 'minage' => $NOW - $this->getCacheTime(), )); - while ($row = $dbquery4->fetch(PDO::FETCH_ASSOC)) { + foreach ($dbquery4 as $row) { $remoteIds[$row['locationid']] = $row['serverlocationid']; } } @@ -239,9 +232,6 @@ abstract class CourseBackend ]); } $backendResponse = $this->fetchSchedulesInternal(array_unique($remoteIds)); - if ($backendResponse === false) { - return false; - } // Fetching might have taken a while, get current time again $NOW = time(); @@ -282,7 +272,7 @@ abstract class CourseBackend return $returnValue; } - public final function setCredentials($serverId, $data) + public final function setCredentials(int $serverId, array $data): bool { foreach ($this->getCredentialDefinitions() as $prop) { if (!isset($data[$prop->property])) { @@ -302,18 +292,9 @@ abstract class CourseBackend } /** - * @return false if there was no error string with error message if there was one - */ - public final function getError() - { - trigger_error('getError() is legacy; use getErrors()'); - return $this->error; - } - - /** * @return array list of errors that occurred during processing. */ - public final function getErrors() + public final function getErrors(): array { return $this->errors; } @@ -378,7 +359,7 @@ abstract class CourseBackend * @param string $response xml document to convert * @return bool|array array representation of the xml if possible, false otherwise */ - protected function xmlStringToArray($response, &$error) + protected function xmlStringToArray(string $response, &$error) { $cleanresponse = preg_replace('/(<\/?)(\w+):([^>]*>)/', '$1$2$3', $response); try { @@ -390,8 +371,7 @@ abstract class CourseBackend } return false; } - $array = json_decode(json_encode((array)$xml), true); - return $array; + return json_decode(json_encode((array)$xml), true); } } @@ -414,7 +394,6 @@ class BackendProperty { * Initialize additional fields of this class that are only required * for rendering the server configuration dialog. * - * @param string $backendId target backend id * @param mixed $current current value of this property. */ public function initForRender($current = null) { diff --git a/modules-available/locationinfo/inc/coursebackend/coursebackend_davinci.inc.php b/modules-available/locationinfo/inc/coursebackend/coursebackend_davinci.inc.php index 07c8457d..786ab459 100644 --- a/modules-available/locationinfo/inc/coursebackend/coursebackend_davinci.inc.php +++ b/modules-available/locationinfo/inc/coursebackend/coursebackend_davinci.inc.php @@ -11,7 +11,7 @@ class CourseBackend_Davinci extends CourseBackend */ private $curlHandle = false; - public function setCredentialsInternal($data) + public function setCredentialsInternal(array $data): bool { if (empty($data['baseUrl'])) { $this->addError("No url is given", true); @@ -24,7 +24,7 @@ class CourseBackend_Davinci extends CourseBackend return true; } - public function checkConnection() + public function checkConnection(): bool { if (empty($this->location)) { $this->addError("Credentials are not set", true); @@ -40,7 +40,7 @@ class CourseBackend_Davinci extends CourseBackend return true; } - public function getCredentialDefinitions() + public function getCredentialDefinitions(): array { return [ new BackendProperty('baseUrl', 'string'), @@ -49,17 +49,17 @@ class CourseBackend_Davinci extends CourseBackend ]; } - public function getDisplayName() + public function getDisplayName(): string { return 'Davinci'; } - public function getCacheTime() + public function getCacheTime(): int { return 30 * 60; } - public function getRefreshTime() + public function getRefreshTime(): int { return 0; } @@ -68,9 +68,9 @@ class CourseBackend_Davinci extends CourseBackend * @param string $roomId unique name of the room, as used by davinci * @param \DateTime $startDate start date to fetch * @param \DateTime $endDate end date of range to fetch - * @return array|bool if successful the arrayrepresentation of the timetable + * @return false|string if successful the array representation of the timetable */ - private function fetchRoomRaw($roomId, $startDate, $endDate) + private function fetchRoomRaw(string $roomId, DateTime $startDate, DateTime $endDate) { $url = $this->location . "content=xml&type=room&name=" . urlencode($roomId) . "&startdate=" . $startDate->format('d.m.Y') . "&enddate=" . $endDate->format('d.m.Y'); @@ -97,7 +97,7 @@ class CourseBackend_Davinci extends CourseBackend } - public function fetchSchedulesInternal($requestedRoomIds) + public function fetchSchedulesInternal(array $requestedRoomIds): array { $startDate = new DateTime('last Monday 0:00'); $endDate = new DateTime('+14 days 0:00'); @@ -134,7 +134,7 @@ class CourseBackend_Davinci extends CourseBackend $start = substr($start, 0, 2) . ':' . substr($start, 2, 2); $end = $lesson['Finish']; $end = substr($end, 0, 2) . ':' . substr($end, 2, 2); - $subject = isset($lesson['Subject']) ? $lesson['Subject'] : '???'; + $subject = $lesson['Subject'] ?? '???'; $timetable[] = array( 'title' => $subject, 'start' => $date . "T" . $start . ':00', diff --git a/modules-available/locationinfo/inc/coursebackend/coursebackend_dummy.inc.php b/modules-available/locationinfo/inc/coursebackend/coursebackend_dummy.inc.php index 2cb2be18..4588bf7c 100644 --- a/modules-available/locationinfo/inc/coursebackend/coursebackend_dummy.inc.php +++ b/modules-available/locationinfo/inc/coursebackend/coursebackend_dummy.inc.php @@ -15,9 +15,9 @@ class CourseBackend_Dummy extends CourseBackend * @param int $serverId ID of the server * @returns bool if the credentials were in the correct format */ - public function setCredentialsInternal($json) + public function setCredentialsInternal(array $data): bool { - $x = $json; + $x = $data; $this->pw = $x['password']; if ($this->pw === "mfg") { @@ -30,7 +30,7 @@ class CourseBackend_Dummy extends CourseBackend /** * @return boolean true if the connection works, false otherwise */ - public function checkConnection() + public function checkConnection(): bool { if ($this->pw == "mfg") { return true; @@ -42,7 +42,7 @@ class CourseBackend_Dummy extends CourseBackend /** * @returns array with parameter name as key and and an array with type, help text and mask as value */ - public function getCredentialDefinitions() + public function getCredentialDefinitions(): array { $options = ["opt1", "opt2", "opt3", "opt4", "opt5", "opt6", "opt7", "opt8"]; return [ @@ -58,7 +58,7 @@ class CourseBackend_Dummy extends CourseBackend /** * @return string return display name of backend */ - public function getDisplayName() + public function getDisplayName(): string { return 'Dummy with array'; } @@ -66,7 +66,7 @@ class CourseBackend_Dummy extends CourseBackend /** * @return int desired caching time of results, in seconds. 0 = no caching */ - public function getCacheTime() + public function getCacheTime(): int { return 0; } @@ -75,7 +75,7 @@ class CourseBackend_Dummy extends CourseBackend * @return int age after which timetables are no longer refreshed should be * greater then CacheTime */ - public function getRefreshTime() + public function getRefreshTime(): int { return 0; } @@ -83,15 +83,15 @@ class CourseBackend_Dummy extends CourseBackend /** * Internal version of fetch, to be overridden by subclasses. * - * @param $roomIds array with local ID as key and serverId as value + * @param $requestedRoomIds array with local ID as key and serverId as value * @return array a recursive array that uses the roomID as key * and has the schedule array as value. A schedule array contains an array in this format: * ["start"=>'YYYY-MM-DD<T>HH:MM:SS',"end"=>'YYYY-MM-DD<T>HH:MM:SS',"title"=>string] */ - public function fetchSchedulesInternal($roomId) + public function fetchSchedulesInternal(array $requestedRoomIds): array { $a = array(); - foreach ($roomId as $id) { + foreach ($requestedRoomIds as $id) { if ($id == 1) { $now = time(); return array($id => array( diff --git a/modules-available/locationinfo/inc/coursebackend/coursebackend_exchange.inc.php b/modules-available/locationinfo/inc/coursebackend/coursebackend_exchange.inc.php index 44847ce2..df33dadd 100755 --- a/modules-available/locationinfo/inc/coursebackend/coursebackend_exchange.inc.php +++ b/modules-available/locationinfo/inc/coursebackend/coursebackend_exchange.inc.php @@ -12,6 +12,7 @@ spl_autoload_register(function ($class) { require_once $file; }); +use jamesiarmes\PhpEws\ArrayType\NonEmptyArrayOfBaseFolderIdsType; use jamesiarmes\PhpEws\Client; use jamesiarmes\PhpEws\Enumeration\DefaultShapeNamesType; use jamesiarmes\PhpEws\Enumeration\DistinguishedFolderIdNameType; @@ -38,7 +39,7 @@ class CourseBackend_Exchange extends CourseBackend /** * @return string return display name of backend */ - public function getDisplayName() + public function getDisplayName(): string { return "Microsoft Exchange"; } @@ -46,7 +47,7 @@ class CourseBackend_Exchange extends CourseBackend /** * @returns \BackendProperty[] list of properties that need to be set */ - public function getCredentialDefinitions() + public function getCredentialDefinitions(): array { $options = [ Client::VERSION_2007, @@ -72,7 +73,7 @@ class CourseBackend_Exchange extends CourseBackend /** * @return boolean true if the connection works, false otherwise */ - public function checkConnection() + public function checkConnection(): bool { $client = $this->getClient(); $request = new ResolveNamesType(); @@ -104,7 +105,7 @@ class CourseBackend_Exchange extends CourseBackend * @param array $data assoc array with data required by backend * @returns bool if the credentials were in the correct format */ - public function setCredentialsInternal($data) + public function setCredentialsInternal(array $data): bool { foreach (['username', 'password'] as $field) { if (empty($data[$field])) { @@ -133,7 +134,7 @@ class CourseBackend_Exchange extends CourseBackend /** * @return int desired caching time of results, in seconds. 0 = no caching */ - public function getCacheTime() + public function getCacheTime(): int { return 15 * 60; } @@ -142,7 +143,7 @@ class CourseBackend_Exchange extends CourseBackend * @return int age after which timetables are no longer refreshed. should be * greater than CacheTime. */ - public function getRefreshTime() + public function getRefreshTime(): int { return 30 * 60; } @@ -150,12 +151,12 @@ class CourseBackend_Exchange extends CourseBackend /** * Internal version of fetch, to be overridden by subclasses. * - * @param $roomIds array with local ID as key and serverId as value + * @param $requestedRoomIds array with local ID as key and serverId as value * @return array a recursive array that uses the roomID as key * and has the schedule array as value. A schedule array contains an array in this format: * ["start"=>'JJJJ-MM-DD HH:MM:SS',"end"=>'JJJJ-MM-DD HH:MM:SS',"title"=>string] */ - protected function fetchSchedulesInternal($requestedRoomIds) + protected function fetchSchedulesInternal(array $requestedRoomIds): array { $startDate = new DateTime('last Monday 0:00'); $endDate = new DateTime('+14 days 0:00'); @@ -172,8 +173,13 @@ class CourseBackend_Exchange extends CourseBackend // Iterate over the events that were found, printing some data for each. foreach ($items as $item) { - $start = new DateTime($item->Start); - $end = new DateTime($item->End); + try { + $start = new DateTime($item->Start); + $end = new DateTime($item->End); + } catch (Exception $e) { + $this->addError("Invalid date range: '{$item->Start}' -> '{$item->End}'", false); + continue; + } $schedules[$roomId][] = array( 'title' => $item->Subject, @@ -186,13 +192,9 @@ class CourseBackend_Exchange extends CourseBackend } /** - * @param \jamesiarmes\PhpEws\Client $client - * @param \DateTime $startDate - * @param \DateTime $endDate - * @param string $roomAddress * @return \jamesiarmes\PhpEws\Type\CalendarItemType[] */ - public function findEventsForRoom($client, $startDate, $endDate, $roomAddress) + public function findEventsForRoom(Client $client, DateTime $startDate, DateTime $endDate, string $roomAddress): array { $request = new FindItemType(); $request->Traversal = ItemQueryTraversalType::SHALLOW; @@ -206,12 +208,20 @@ class CourseBackend_Exchange extends CourseBackend $folderId->Id = DistinguishedFolderIdNameType::CALENDAR; $folderId->Mailbox = new EmailAddressType(); $folderId->Mailbox->EmailAddress = $roomAddress; + $request->ParentFolderIds = new NonEmptyArrayOfBaseFolderIdsType(); $request->ParentFolderIds->DistinguishedFolderId[] = $folderId; - $response = $client->FindItem($request); - $response_messages = $response->ResponseMessages->FindItemResponseMessage; - + try { + $response = $client->FindItem($request); + } catch (Exception $e) { + $this->addError('Exception calling FindItem: ' . $e->getMessage(), true); + return []; + } + if (!is_object($response->ResponseMessages)) { + $this->addError('FindItem returned response without ResponseMessages', true); + return []; + } $items = []; - foreach ($response_messages as $response_message) { + foreach ($response->ResponseMessages->FindItemResponseMessage as $response_message) { // Make sure the request succeeded. if ($response_message->ResponseClass !== ResponseClassType::SUCCESS) { $code = $response_message->ResponseCode; @@ -224,10 +234,7 @@ class CourseBackend_Exchange extends CourseBackend return $items; } - /** - * @return \jamesiarmes\PhpEws\Client - */ - public function getClient() + public function getClient(): Client { $client = new Client($this->serverAddress, $this->username, $this->password, $this->clientVersion); $client->setTimezone($this->timezone); diff --git a/modules-available/locationinfo/inc/coursebackend/coursebackend_hisinone.inc.php b/modules-available/locationinfo/inc/coursebackend/coursebackend_hisinone.inc.php index 8bd18169..55d5ed4b 100644 --- a/modules-available/locationinfo/inc/coursebackend/coursebackend_hisinone.inc.php +++ b/modules-available/locationinfo/inc/coursebackend/coursebackend_hisinone.inc.php @@ -3,7 +3,7 @@ class CourseBackend_HisInOne extends ICalCourseBackend { - public function setCredentialsInternal($data) + public function setCredentialsInternal(array $data): bool { if (empty($data['baseUrl'])) { $this->addError("No url is given", true); @@ -16,7 +16,7 @@ class CourseBackend_HisInOne extends ICalCourseBackend return true; } - public function getCredentialDefinitions() + public function getCredentialDefinitions(): array { return [ new BackendProperty('baseUrl', 'string'), @@ -25,7 +25,7 @@ class CourseBackend_HisInOne extends ICalCourseBackend ]; } - public function mangleProperty($prop, $value) + public function mangleProperty(string $prop, $value) { if ($prop === 'baseUrl') { // Update form SOAP to iCal url @@ -43,7 +43,15 @@ class CourseBackend_HisInOne extends ICalCourseBackend return $value; } - public function checkConnection() + protected function toTitle(ICalEvent $event): string + { + $title = parent::toTitle($event); + // His in one seems to prefix *some* (but *not* all) of the lectures by their ID/("Nummer") + // No clue what that format is supposed to be, this regex is some guesswork after observing this for a while + return preg_replace('#^[0-9][0-9A-ZÄÖÜ]{3,9}-[A-Za-z0-9/_ÄÖÜäöüß.-]{4,30}\s+#u', '', $title); + } + + public function checkConnection(): bool { if (!$this->isOK()) return false; @@ -57,18 +65,18 @@ class CourseBackend_HisInOne extends ICalCourseBackend return false; } - public function getCacheTime() + public function getCacheTime(): int { return 30 * 60; } - public function getRefreshTime() + public function getRefreshTime(): int { return 60 * 60; } - public function getDisplayName() + public function getDisplayName(): string { return "HisInOne"; } diff --git a/modules-available/locationinfo/inc/coursebackend/coursebackend_ical.inc.php b/modules-available/locationinfo/inc/coursebackend/coursebackend_ical.inc.php index 98dca1cb..f1791c4e 100644 --- a/modules-available/locationinfo/inc/coursebackend/coursebackend_ical.inc.php +++ b/modules-available/locationinfo/inc/coursebackend/coursebackend_ical.inc.php @@ -6,7 +6,7 @@ class CourseBackend_ICal extends ICalCourseBackend /** @var string room ID for testing connection */ private $testId; - public function setCredentialsInternal($data) + public function setCredentialsInternal(array $data): bool { if (empty($data['baseUrl'])) { $this->addError("No url is given", true); @@ -20,7 +20,7 @@ class CourseBackend_ICal extends ICalCourseBackend return true; } - public function getCredentialDefinitions() + public function getCredentialDefinitions(): array { return [ new BackendProperty('baseUrl', 'string'), @@ -33,7 +33,7 @@ class CourseBackend_ICal extends ICalCourseBackend ]; } - public function checkConnection() + public function checkConnection(): bool { if (!$this->isOK()) return false; @@ -42,18 +42,18 @@ class CourseBackend_ICal extends ICalCourseBackend return ($this->downloadIcal($this->testId) !== null); } - public function getCacheTime() + public function getCacheTime(): int { return 30 * 60; } - public function getRefreshTime() + public function getRefreshTime(): int { return 60 * 60; } - public function getDisplayName() + public function getDisplayName(): string { return "iCal"; } diff --git a/modules-available/locationinfo/inc/icalcoursebackend.inc.php b/modules-available/locationinfo/inc/icalcoursebackend.inc.php index fba0866c..838d18b7 100644 --- a/modules-available/locationinfo/inc/icalcoursebackend.inc.php +++ b/modules-available/locationinfo/inc/icalcoursebackend.inc.php @@ -18,18 +18,9 @@ abstract class ICalCourseBackend extends CourseBackend /** @var bool|resource */ private $curlHandle = false; - /** - * Initialize values - * - * @param string $location - * @param bool $verifyCert - * @param bool $verifyHostname - * @param string $authMethod - * @param string $user - * @param string $pass - */ - protected function init($location, $verifyCert, $verifyHostname, - $authMethod = 'NONE', $user = '', $pass = '') + protected function init( + string $location, bool $verifyCert, bool $verifyHostname, + string $authMethod = 'NONE', string $user = '', string $pass = '') { $this->verifyCert = $verifyCert; $this->verifyHostname = $verifyHostname; @@ -43,19 +34,26 @@ abstract class ICalCourseBackend extends CourseBackend } /** - * @param int $roomId room id + * @param string $roomId room id * @param callable $errorFunc * @return ICalEvent[]|null all events for this room in the range -7 days to +7 days, or NULL on error */ - protected function downloadIcal($roomId) + protected function downloadIcal(string $roomId): ?array { if (!$this->isOK()) return null; + + try { + $ical = new ICalParser(['filterDaysBefore' => 7, 'filterDaysAfter' => 7]); + } catch (Exception $e) { + $this->addError('Error instantiating ICalParser: ' . $e->getMessage(), true); + return null; + } + if ($this->curlHandle === false) { $this->curlHandle = curl_init(); } - $ical = new ICalParser(['filterDaysBefore' => 7, 'filterDaysAfter' => 7]); $options = [ CURLOPT_WRITEFUNCTION => function ($ch, $data) use ($ical) { $ical->feedData($data); @@ -92,7 +90,7 @@ abstract class ICalCourseBackend extends CourseBackend return $ical->events(); } - public function fetchSchedulesInternal($requestedRoomIds): array + public function fetchSchedulesInternal(array $requestedRoomIds): array { if (empty($requestedRoomIds) || !$this->isOK()) { return array(); @@ -118,9 +116,8 @@ abstract class ICalCourseBackend extends CourseBackend /** * Get a usable title from either SUMMARY or DESCRIPTION - * @param ICalEvent $event */ - private function toTitle($event): string + protected function toTitle(ICalEvent $event): string { $title = $event->summary; if (empty($title)) { diff --git a/modules-available/locationinfo/inc/icalevent.inc.php b/modules-available/locationinfo/inc/icalevent.inc.php index 1fb586ee..c5aea349 100644 --- a/modules-available/locationinfo/inc/icalevent.inc.php +++ b/modules-available/locationinfo/inc/icalevent.inc.php @@ -2,199 +2,253 @@ class ICalEvent { - // phpcs:disable Generic.Arrays.DisallowLongArraySyntax - - const HTML_TEMPLATE = '<p>%s: %s</p>'; - - /** - * https://www.kanzaki.com/docs/ical/summary.html - * - * @var $summary - */ - public $summary; - - /** - * https://www.kanzaki.com/docs/ical/dtstart.html - * - * @var $dtstart - */ - public $dtstart; - - /** - * https://www.kanzaki.com/docs/ical/dtend.html - * - * @var $dtend - */ - public $dtend; - - /** - * https://www.kanzaki.com/docs/ical/duration.html - * - * @var $duration - */ - public $duration; - - /** - * https://www.kanzaki.com/docs/ical/dtstamp.html - * - * @var $dtstamp - */ - public $dtstamp; - - /** - * https://www.kanzaki.com/docs/ical/uid.html - * - * @var $uid - */ - public $uid; - - /** - * https://www.kanzaki.com/docs/ical/created.html - * - * @var $created - */ - public $created; - - /** - * https://www.kanzaki.com/docs/ical/lastModified.html - * - * @var $lastmodified - */ - public $lastmodified; - - /** - * https://www.kanzaki.com/docs/ical/description.html - * - * @var $description - */ - public $description; - - /** - * https://www.kanzaki.com/docs/ical/location.html - * - * @var $location - */ - public $location; - - /** - * https://www.kanzaki.com/docs/ical/sequence.html - * - * @var $sequence - */ - public $sequence; - - /** - * https://www.kanzaki.com/docs/ical/status.html - * - * @var $status - */ - public $status; - - /** - * https://www.kanzaki.com/docs/ical/transp.html - * - * @var $transp - */ - public $transp; - - /** - * https://www.kanzaki.com/docs/ical/organizer.html - * - * @var $organizer - */ - public $organizer; - - /** - * https://www.kanzaki.com/docs/ical/attendee.html - * - * @var $attendee - */ - public $attendee; - - /** - * Creates the Event object - * - * @param array $data - * @return void - */ - public function __construct(array $data = array()) - { - foreach ($data as $key => $value) { - $variable = self::snakeCase($key); - $this->{$variable} = self::prepareData($value); - } - } - - /** - * Prepares the data for output - * - * @param mixed $value - * @return mixed - */ - protected function prepareData($value) - { - if (is_string($value)) { - return stripslashes(trim(str_replace('\n', "\n", $value))); - } elseif (is_array($value)) { - return array_map('self::prepareData', $value); - } - - return $value; - } - - /** - * Returns Event data excluding anything blank - * within an HTML template - * - * @param string $html HTML template to use - * @return string - */ - public function printData($html = self::HTML_TEMPLATE) - { - $data = array( - 'SUMMARY' => $this->summary, - 'DTSTART' => $this->dtstart, - 'DTEND' => $this->dtend, - 'DURATION' => $this->duration, - 'DTSTAMP' => $this->dtstamp, - 'UID' => $this->uid, - 'CREATED' => $this->created, - 'LAST-MODIFIED' => $this->lastmodified, - 'DESCRIPTION' => $this->description, - 'LOCATION' => $this->location, - 'SEQUENCE' => $this->sequence, - 'STATUS' => $this->status, - 'TRANSP' => $this->transp, - 'ORGANISER' => $this->organizer, - 'ATTENDEE(S)' => $this->attendee, - ); - - // Remove any blank values - $data = array_filter($data); - - $output = ''; - - foreach ($data as $key => $value) { - $output .= sprintf($html, $key, $value); - } - - return $output; - } - - /** - * Converts the given input to snake_case - * - * @param string $input - * @param string $glue - * @param string $separator - * @return string - */ - protected static function snakeCase($input, $glue = '_', $separator = '-') - { - $input = preg_split('/(?<=[a-z])(?=[A-Z])/x', $input); - $input = implode($glue, $input); - $input = str_replace($separator, $glue, $input); - - return strtolower($input); - } + // phpcs:disable Generic.Arrays.DisallowLongArraySyntax + + const HTML_TEMPLATE = '<p>%s: %s</p>'; + + /** + * https://www.kanzaki.com/docs/ical/summary.html + * + * @var string + */ + public $summary; + + /** + * https://www.kanzaki.com/docs/ical/dtstart.html + * + * @var string + */ + public $dtstart; + + /** + * https://www.kanzaki.com/docs/ical/dtend.html + * + * @var string + */ + public $dtend; + + /** + * https://www.kanzaki.com/docs/ical/duration.html + * + * @var string + */ + public $duration; + + /** + * https://www.kanzaki.com/docs/ical/dtstamp.html + * + * @var string + */ + public $dtstamp; + + /** + * When the event starts, represented as a timezone-adjusted string + * + * @var string + */ + public $dtstart_tz; + + /** + * When the event ends, represented as a timezone-adjusted string + * + * @var string + */ + public $dtend_tz; + + /** + * https://www.kanzaki.com/docs/ical/uid.html + * + * @var string + */ + public $uid; + + /** + * https://www.kanzaki.com/docs/ical/created.html + * + * @var string + */ + public $created; + + /** + * https://www.kanzaki.com/docs/ical/lastModified.html + * + * @var string + */ + public $last_modified; + + /** + * https://www.kanzaki.com/docs/ical/description.html + * + * @var string + */ + public $description; + + /** + * https://www.kanzaki.com/docs/ical/location.html + * + * @var string + */ + public $location; + + /** + * https://www.kanzaki.com/docs/ical/sequence.html + * + * @var string + */ + public $sequence; + + /** + * https://www.kanzaki.com/docs/ical/status.html + * + * @var string + */ + public $status; + + /** + * https://www.kanzaki.com/docs/ical/transp.html + * + * @var string + */ + public $transp; + + /** + * https://www.kanzaki.com/docs/ical/organizer.html + * + * @var string + */ + public $organizer; + + /** + * https://www.kanzaki.com/docs/ical/attendee.html + * + * @var string + */ + public $attendee; + + /** + * Manage additional properties + * + * @var array<string, mixed> + */ + private $additionalProperties = array(); + + /** + * Creates the Event object + * + * @param array $data + * @return void + */ + public function __construct(array $data = array()) + { + foreach ($data as $key => $value) { + $variable = self::snakeCase($key); + if (property_exists($this, $variable)) { + $this->{$variable} = $this->prepareData($value); + } else { + $this->additionalProperties[$variable] = $this->prepareData($value); + } + } + } + + /** + * Magic getter method + * + * @param string $additionalPropertyName + * @return mixed + */ + public function __get(string $additionalPropertyName) + { + if (array_key_exists($additionalPropertyName, $this->additionalProperties)) { + return $this->additionalProperties[$additionalPropertyName]; + } + + return null; + } + + /** + * Magic isset method + */ + public function __isset(string $name): bool + { + return is_null($this->$name) === false; + } + + /** + * Prepares the data for output + * + * @param mixed $value + * @return mixed + */ + protected function prepareData($value) + { + if (is_string($value)) { + return stripslashes(trim(str_replace('\n', "\n", $value))); + } + + if (is_array($value)) { + return array_map(function ($value) { + return $this->prepareData($value); + }, $value); + } + + return $value; + } + + /** + * Returns Event data excluding anything blank + * within an HTML template + * + * @param string $html HTML template to use + * @return string + */ + public function printData($html = self::HTML_TEMPLATE) + { + $data = array( + 'SUMMARY' => $this->summary, + 'DTSTART' => $this->dtstart, + 'DTEND' => $this->dtend, + 'DTSTART_TZ' => $this->dtstart_tz, + 'DTEND_TZ' => $this->dtend_tz, + 'DURATION' => $this->duration, + 'DTSTAMP' => $this->dtstamp, + 'UID' => $this->uid, + 'CREATED' => $this->created, + 'LAST-MODIFIED' => $this->last_modified, + 'DESCRIPTION' => $this->description, + 'LOCATION' => $this->location, + 'SEQUENCE' => $this->sequence, + 'STATUS' => $this->status, + 'TRANSP' => $this->transp, + 'ORGANISER' => $this->organizer, + 'ATTENDEE(S)' => $this->attendee, + ); + + // Remove any blank values + $data = array_filter($data); + + $output = ''; + + foreach ($data as $key => $value) { + $output .= sprintf($html, $key, $value); + } + + return $output; + } + + /** + * Converts the given input to snake_case + * + * @param string $input + * @param string $glue + * @param string $separator + * @return string + */ + protected static function snakeCase($input, $glue = '_', $separator = '-') + { + $input = preg_split('/(?<=[a-z])(?=[A-Z])/x', $input); + $input = implode($glue, $input); + $input = str_replace($separator, $glue, $input); + + return strtolower($input); + } } diff --git a/modules-available/locationinfo/inc/icalparser.inc.php b/modules-available/locationinfo/inc/icalparser.inc.php index 0be8777b..eacb67b1 100644 --- a/modules-available/locationinfo/inc/icalparser.inc.php +++ b/modules-available/locationinfo/inc/icalparser.inc.php @@ -23,7 +23,6 @@ class ICalParser const ICAL_DATE_TIME_TEMPLATE = 'TZID=%s:'; const ISO_8601_WEEK_START = 'MO'; const RECURRENCE_EVENT = 'Generated recurrence event'; - const TIME_FORMAT = 'His'; const TIME_ZONE_UTC = 'UTC'; const UNIX_FORMAT = 'U'; @@ -481,15 +480,12 @@ class ICalParser /** * Creates the ICal object * - * @param mixed $files * @param array $options * @return void * @throws Exception */ public function __construct(array $options = array()) { - ini_set('auto_detect_line_endings', '1'); - foreach ($options as $option => $value) { if (in_array($option, self::$configurableOptions)) { $this->{$option} = $value; @@ -501,10 +497,7 @@ class ICalParser $this->defaultTimeZone = date_default_timezone_get(); } - // Ideally you would use `PHP_INT_MIN` from PHP 7 - $php_int_min = -2147483648; - - $this->windowMinTimestamp = is_null($this->filterDaysBefore) ? $php_int_min : (new DateTime('now'))->sub(new DateInterval('P' . $this->filterDaysBefore . 'D'))->getTimestamp(); + $this->windowMinTimestamp = is_null($this->filterDaysBefore) ? PHP_INT_MIN : (new DateTime('now'))->sub(new DateInterval('P' . $this->filterDaysBefore . 'D'))->getTimestamp(); $this->windowMaxTimestamp = is_null($this->filterDaysAfter) ? PHP_INT_MAX : (new DateTime('now'))->add(new DateInterval('P' . $this->filterDaysAfter . 'D'))->getTimestamp(); $this->shouldFilterByWindow = !is_null($this->filterDaysBefore) || !is_null($this->filterDaysAfter); @@ -516,7 +509,7 @@ class ICalParser * * @param string $data */ - public function feedData($data) + public function feedData(string $data) { $this->feedBuffer .= $data; $start = 0; @@ -581,7 +574,7 @@ class ICalParser * * @return bool */ - public function isValid() + public function isValid(): bool { return $this->hasSeenStart; } @@ -591,7 +584,7 @@ class ICalParser * * @param string $line */ - protected function handleLine($line) + protected function handleLine(string $line) { $line = rtrim($line); // Trim trailing whitespace $line = $this->removeUnprintableChars($line); @@ -602,18 +595,18 @@ class ICalParser $add = $this->keyValueFromString($line); - if ($add === false) { + if ($add === null) { return; } - $keyword = $add[0]; + $keyword = $add[0]; // string $values = $add[1]; // May be an array containing multiple values if (!is_array($values)) { if (!empty($values)) { $values = array($values); // Make an array as not already $blankArray = array(); // Empty placeholder array - array_push($values, $blankArray); + $values[] = $blankArray; } else { $values = array(); // Use blank array to ignore this line } @@ -752,16 +745,9 @@ class ICalParser foreach ($events as $key => $anEvent) { if ($anEvent === null) { unset($events[$key]); - - continue; - } - - if ($this->doesEventStartOutsideWindow($anEvent)) { + } elseif ($this->doesEventStartOutsideWindow($anEvent)) { $this->eventCount--; - unset($events[$key]); - - continue; } } @@ -776,7 +762,7 @@ class ICalParser * @param array $event * @return boolean */ - protected function doesEventStartOutsideWindow(array $event) + protected function doesEventStartOutsideWindow(array $event): bool { return !isset($event['DTSTART']) || !$this->isValidDate($event['DTSTART']) || $this->isOutOfRange($event['DTSTART'], $this->windowMinTimestamp, $this->windowMaxTimestamp); @@ -790,7 +776,7 @@ class ICalParser * @param integer $maxTimestamp * @return boolean */ - protected function isOutOfRange($calendarDate, $minTimestamp, $maxTimestamp) + protected function isOutOfRange(string $calendarDate, int $minTimestamp, int $maxTimestamp): bool { $timestamp = strtotime(explode('T', $calendarDate)[0]); @@ -798,36 +784,15 @@ class ICalParser } /** - * Unfolds an iCal file in preparation for parsing - * (https://icalendar.org/iCalendar-RFC-5545/3-1-content-lines.html) - * - * @param array $lines - * @return array - */ - protected function unfold(array $lines) - { - $string = implode(PHP_EOL, $lines); - $string = preg_replace('/' . PHP_EOL . '[ \t]/', '', $string); - - $lines = explode(PHP_EOL, $string); - - return $lines; - } - - /** * Add one key and value pair to the `$this->cal` array * * @param string $component - * @param string|boolean $keyword - * @param string $value + * @param string $keyword + * @param string|string[] $value * @return void */ - protected function addCalendarComponentWithKeyAndValue($component, $keyword, $value) + protected function addCalendarComponentWithKeyAndValue(string $component, string $keyword, $value) { - if ($keyword == false) { - $keyword = $this->lastKeyword; - } - switch ($component) { case 'VALARM': $key1 = 'VEVENT'; @@ -840,7 +805,7 @@ class ICalParser if (is_array($value)) { // Add array of properties to the end - array_push($this->cal[$key1][$key2][$key3]["{$keyword}_array"], $value); + $this->cal[$key1][$key2][$key3]["{$keyword}_array"][] = $value; } else { if (!isset($this->cal[$key1][$key2][$key3][$keyword])) { $this->cal[$key1][$key2][$key3][$keyword] = $value; @@ -862,7 +827,7 @@ class ICalParser if (is_array($value)) { // Add array of properties to the end - array_push($this->cal[$key1][$key2]["{$keyword}_array"], $value); + $this->cal[$key1][$key2]["{$keyword}_array"][] = $value; } else { if (!isset($this->cal[$key1][$key2][$keyword])) { $this->cal[$key1][$key2][$keyword] = $value; @@ -882,7 +847,7 @@ class ICalParser if ($keyword === 'DURATION') { try { $duration = new DateInterval($value); - array_push($this->cal[$key1][$key2]["{$keyword}_array"], $duration); + $this->cal[$key1][$key2]["{$keyword}_array"][] = $duration; } catch (Exception $e) { error_log('Ignoring invalid duration ' . $value); } @@ -928,6 +893,7 @@ class ICalParser break; } + // Remove? $this->lastKeyword = $keyword; } @@ -935,9 +901,9 @@ class ICalParser * Gets the key value pair from an iCal string * * @param string $text - * @return array|boolean + * @return ?array */ - protected function keyValueFromString($text) + protected function keyValueFromString(string $text): ?array { $text = htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8'); @@ -974,14 +940,14 @@ class ICalParser } if (count($matches) === 0) { - return false; + return null; } - if (preg_match('/^([A-Z-]+)([;][\w\W]*)?$/', $matches[1])) { + if (preg_match('/^([A-Z-]+)(;[\w\W]*)?$/', $matches[1])) { $matches = array_splice($matches, 1, 2); // Remove first match and re-align ordering // Process properties - if (preg_match('/([A-Z-]+)[;]([\w\W]*)/', $matches[0], $properties)) { + if (preg_match('/([A-Z-]+);([\w\W]*)/', $matches[0], $properties)) { // Remove first match array_shift($properties); // Fix to ignore everything in keyword after a ; (e.g. Language, TZID, etc.) @@ -1023,9 +989,8 @@ class ICalParser } return $matches; - } else { - return false; // Ignore this match } + return null; // Ignore this match } /** @@ -1034,7 +999,7 @@ class ICalParser * @param string $icalDate * @return DateTime */ - public function iCalDateToDateTime($icalDate) + public function iCalDateToDateTime(string $icalDate): DateTime { /** * iCal times may be in 3 formats, (https://www.kanzaki.com/docs/ical/dateTime.html) @@ -1091,7 +1056,7 @@ class ICalParser * @param string $icalDate * @return integer */ - public function iCalDateToUnixTimestamp($icalDate) + public function iCalDateToUnixTimestamp(string $icalDate): int { return $this->iCalDateToDateTime($icalDate)->getTimestamp(); } @@ -1104,7 +1069,7 @@ class ICalParser * @param string $format * @return string|boolean */ - public function iCalDateWithTimeZone(array $event, $key, $format = self::DATE_TIME_FORMAT) + public function iCalDateWithTimeZone(array $event, string $key, string $format = self::DATE_TIME_FORMAT) { if (!isset($event["{$key}_array"]) || !isset($event[$key])) { return false; @@ -1141,7 +1106,6 @@ class ICalParser if (empty($this->cal['VEVENT'])) return; $events =& $this->cal['VEVENT']; - $checks = null; foreach ($events as $key => $anEvent) { foreach (array('DTSTART', 'DTEND', 'RECURRENCE-ID') as $type) { @@ -1175,23 +1139,20 @@ class ICalParser $eventKeysToRemove = array(); foreach ($events as $key => $event) { - $checks[] = !isset($event['RECURRENCE-ID']); - $checks[] = isset($event['UID']); - $checks[] = isset($event['UID']) && isset($this->alteredRecurrenceInstances[$event['UID']]); + $checks = !isset($event['RECURRENCE-ID']) + && isset($event['UID']) && isset($this->alteredRecurrenceInstances[$event['UID']]); - if ((bool)array_product($checks)) { + if ($checks) { $eventDtstartUnix = $this->iCalDateToUnixTimestamp($event['DTSTART_array'][3]); // phpcs:ignore CustomPHPCS.ControlStructures.AssignmentInCondition if (($alteredEventKey = array_search($eventDtstartUnix, $this->alteredRecurrenceInstances[$event['UID']])) !== false) { $eventKeysToRemove[] = $alteredEventKey; - $alteredEvent = array_replace_recursive($events[$key], $events[$alteredEventKey]); + $alteredEvent = array_replace_recursive($event, $events[$alteredEventKey]); $this->alteredRecurrenceInstances[$event['UID']]['altered-event'] = array($key => $alteredEvent); } } - - unset($checks); } foreach ($eventKeysToRemove as $eventKeyToRemove) { @@ -1568,7 +1529,7 @@ class ICalParser * @param DateTime $initialDateTime * @return array */ - protected function getDaysOfMonthMatchingByDayRRule(array $byDays, $initialDateTime) + protected function getDaysOfMonthMatchingByDayRRule(array $byDays, DateTime $initialDateTime): array { $matchingDays = array(); @@ -1628,7 +1589,7 @@ class ICalParser * @param array $valuesList * @return array */ - protected function filterValuesUsingBySetPosRRule(array $bySetPos, array $valuesList) + protected function filterValuesUsingBySetPosRRule(array $bySetPos, array $valuesList): array { $filteredMatches = array(); @@ -1688,7 +1649,7 @@ class ICalParser * * @return ICalEvent[] */ - public function events() + public function events(): array { if (empty($this->cal) || empty($this->cal['VEVENT'])) return []; @@ -1706,9 +1667,9 @@ class ICalParser * * @return string */ - public function calendarName() + public function calendarName(): string { - return isset($this->cal['VCALENDAR']['X-WR-CALNAME']) ? $this->cal['VCALENDAR']['X-WR-CALNAME'] : ''; + return $this->cal['VCALENDAR']['X-WR-CALNAME'] ?? ''; } /** @@ -1716,9 +1677,9 @@ class ICalParser * * @return string */ - public function calendarDescription() + public function calendarDescription(): string { - return isset($this->cal['VCALENDAR']['X-WR-CALDESC']) ? $this->cal['VCALENDAR']['X-WR-CALDESC'] : ''; + return $this->cal['VCALENDAR']['X-WR-CALDESC'] ?? ''; } /** @@ -1727,7 +1688,7 @@ class ICalParser * @param boolean $ignoreUtc * @return string */ - public function calendarTimeZone($ignoreUtc = false) + public function calendarTimeZone(bool $ignoreUtc = false): ?string { if (isset($this->cal['VCALENDAR']['X-WR-TIMEZONE'])) { $timeZone = $this->cal['VCALENDAR']['X-WR-TIMEZONE']; @@ -1754,11 +1715,11 @@ class ICalParser * * @return array */ - public function freeBusyEvents() + public function freeBusyEvents(): array { $array = $this->cal; - return isset($array['VFREEBUSY']) ? $array['VFREEBUSY'] : array(); + return $array['VFREEBUSY'] ?? array(); } /** @@ -1784,7 +1745,7 @@ class ICalParser * @return array * @throws Exception */ - public function eventsFromRange($rangeStart = null, $rangeEnd = null) + public function eventsFromRange(string $rangeStart = null, string $rangeEnd = null): array { // Sort events before processing range $events = $this->sortEventsWithOrder($this->events()); @@ -1857,7 +1818,7 @@ class ICalParser * @param integer $sortOrder Either SORT_ASC, SORT_DESC, SORT_REGULAR, SORT_NUMERIC, SORT_STRING * @return array */ - public function sortEventsWithOrder(array $events, $sortOrder = SORT_ASC) + public function sortEventsWithOrder(array $events, int $sortOrder = SORT_ASC): array { $extendedEvents = array(); $timestamp = array(); @@ -1878,7 +1839,7 @@ class ICalParser * @param string $timeZone * @return boolean */ - protected function isValidTimeZoneId($timeZone) + protected function isValidTimeZoneId(string $timeZone): bool { return $this->isValidIanaTimeZoneId($timeZone) !== false || $this->isValidCldrTimeZoneId($timeZone) !== false @@ -1891,7 +1852,7 @@ class ICalParser * @param string $timeZone * @return boolean */ - protected function isValidIanaTimeZoneId($timeZone) + protected function isValidIanaTimeZoneId(string $timeZone): bool { if (in_array($timeZone, $this->validIanaTimeZones)) { return true; @@ -1923,7 +1884,7 @@ class ICalParser * @param string $timeZone * @return boolean */ - public function isValidCldrTimeZoneId($timeZone) + public function isValidCldrTimeZoneId(string $timeZone): bool { return array_key_exists(html_entity_decode($timeZone), self::$cldrTimeZonesMap); } @@ -1934,7 +1895,7 @@ class ICalParser * @param string $timeZone * @return boolean */ - public function isValidWindowsTimeZoneId($timeZone) + public function isValidWindowsTimeZoneId(string $timeZone): bool { return array_key_exists(html_entity_decode($timeZone), self::$windowsTimeZonesMap); } @@ -1942,12 +1903,9 @@ class ICalParser /** * Parses a duration and applies it to a date * - * @param string $date - * @param DateInterval $duration - * @param string $format * @return integer|DateTime */ - protected function parseDuration($date, $duration, $format = self::UNIX_FORMAT) + protected function parseDuration(string $date, DateInterval $duration, ?string $format = self::UNIX_FORMAT) { $dateTime = date_create($date); $dateTime->modify("{$duration->y} year"); @@ -1974,7 +1932,7 @@ class ICalParser * @param string $data * @return string */ - protected function removeUnprintableChars($data) + protected function removeUnprintableChars(string $data): string { return preg_replace('/[\x00-\x1F\x7F\xA0]/u', '', $data); } @@ -1986,7 +1944,7 @@ class ICalParser * @param string $candidateText * @return string */ - protected function escapeParamText($candidateText) + protected function escapeParamText(string $candidateText): string { if (strpbrk($candidateText, ':;,') !== false) { return '"' . $candidateText . '"'; @@ -2002,13 +1960,12 @@ class ICalParser * @param array $event * @return array */ - public function parseExdates(array $event) + public function parseExdates(array $event): array { if (empty($event['EXDATE_array'])) { return array(); - } else { - $exdates = $event['EXDATE_array']; } + $exdates = $event['EXDATE_array']; $output = array(); $currentTimeZone = $this->defaultTimeZone; @@ -2046,7 +2003,7 @@ class ICalParser * @param string $value * @return boolean */ - public function isValidDate($value) + public function isValidDate(string $value): bool { if (!$value) { return false; @@ -2065,10 +2022,10 @@ class ICalParser * Returns a `DateTimeZone` object based on a string containing a time zone name. * Falls back to the default time zone if string passed not a recognised time zone. * - * @param string $timeZoneString + * @param DateTimeZone|string $timeZoneString * @return DateTimeZone */ - public function timeZoneStringToDateTimeZone($timeZoneString) + public function timeZoneStringToDateTimeZone($timeZoneString): DateTimeZone { if ($timeZoneString instanceof DateTimeZone) return $timeZoneString; diff --git a/modules-available/locationinfo/inc/infopanel.inc.php b/modules-available/locationinfo/inc/infopanel.inc.php index 6deb9db5..1a0e9b67 100644 --- a/modules-available/locationinfo/inc/infopanel.inc.php +++ b/modules-available/locationinfo/inc/infopanel.inc.php @@ -7,16 +7,16 @@ class InfoPanel * Gets the config of the location. * * @param int $locationID ID of the location - * @param mixed $config the panel config will be returned here - * @return string|bool paneltype, false if not exists + * @param ?array $config the panel config will be returned here + * @return ?string panel type, null if not exists */ - public static function getConfig($paneluuid, &$config) + public static function getConfig(string $paneluuid, ?array &$config): ?string { $panel = Database::queryFirst('SELECT panelname, panelconfig, paneltype, locationids FROM locationinfo_panel WHERE paneluuid = :paneluuid', compact('paneluuid')); if ($panel === false) { - return false; + return null; } $config = LocationInfo::defaultPanelConfig($panel['paneltype']); @@ -87,13 +87,10 @@ class InfoPanel * @param array $array location list to populate with machine data * @param bool $withPosition Defines if coords should be included or not. */ - public static function appendMachineData(&$array, $idList = false, $withPosition = false, $withHostname = false) + public static function appendMachineData(array &$array, array $idList, bool $withPosition = false, bool $withHostname = false): void { - if (empty($array) && $idList === false) + if (empty($idList)) return; - if ($idList === false) { - $idList = array_keys($array); - } $ignoreList = array(); if (Module::isAvailable('runmode')) { @@ -115,7 +112,7 @@ class InfoPanel $dbquery = Database::simpleQuery($query, array('idlist' => $idList)); // Iterate over matching machines - while ($row = $dbquery->fetch(PDO::FETCH_ASSOC)) { + foreach ($dbquery as $row) { if (isset($ignoreList[$row['machineuuid']])) continue; settype($row['locationid'], 'int'); @@ -165,7 +162,7 @@ class InfoPanel * @param array $array list of locations, indexed by locationId * @param int[] $idList list of locations */ - public static function appendOpeningTimes(&$array, $idList) + public static function appendOpeningTimes(array &$array, array $idList): void { // First, lets get all the parent ids for the given locations // in case we need to get inherited opening times @@ -175,7 +172,7 @@ class InfoPanel $res = Database::simpleQuery("SELECT locationid, openingtime FROM location WHERE locationid IN (:lids)", array('lids' => $allIds)); $openingTimes = array(); - while ($row = $res->fetch(PDO::FETCH_ASSOC)) { + foreach ($res as $row) { $openingTimes[(int)$row['locationid']] = $row; } // Now we got all the calendars for locations and parents @@ -207,7 +204,6 @@ class InfoPanel $currentId = $locations[$currentId]['parentlocationid']; } } - return; } @@ -218,12 +214,12 @@ class InfoPanel * @param int[] $idList location ids * @return int[] more location ids */ - private static function getLocationsWithParents($idList) + private static function getLocationsWithParents(array $idList): array { $locations = Location::getLocationsAssoc(); $allIds = $idList; foreach ($idList as $id) { - if (isset($locations[$id]) && isset($locations[$id]['parents'])) { + if (isset($locations[$id]['parents'])) { $allIds = array_merge($allIds, $locations[$id]['parents']); } } @@ -239,9 +235,9 @@ class InfoPanel * 'HourClose' => hh, 'MinutesClose' => mm } * * @param array $openingtime The opening time in the db saved format. - * @return mixed The opening time in the frontend needed format. + * @return array The opening time in the frontend needed format. */ - private static function formatOpeningtime($openingtime) + private static function formatOpeningtime(array $openingtime): array { $result = array(); foreach ($openingtime as $entry) { diff --git a/modules-available/locationinfo/inc/locationinfo.inc.php b/modules-available/locationinfo/inc/locationinfo.inc.php index 6427cc1a..42829a18 100644 --- a/modules-available/locationinfo/inc/locationinfo.inc.php +++ b/modules-available/locationinfo/inc/locationinfo.inc.php @@ -7,14 +7,14 @@ 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 int pc state + * @return string pc state */ - public static function getPcState($pc) + public static function getPcState(array $pc): string { $lastseen = (int)$pc['lastseen']; $NOW = time(); - if ($pc['state'] === 'OFFLINE' && $NOW - $lastseen > 21 * 86400) { + if ($pc['state'] === 'OFFLINE' && $NOW - $lastseen > 30 * 86400) { return "BROKEN"; } return $pc['state']; @@ -22,11 +22,12 @@ class LocationInfo /** * 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($paneluuid, $recursive = true) + public static function getLocationsOr404(string $paneluuid, bool $recursive = true): array { $panel = Database::queryFirst('SELECT paneltype, locationids FROM locationinfo_panel WHERE paneluuid = :paneluuid', compact('paneluuid')); @@ -48,7 +49,7 @@ class LocationInfo * @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($serverId, $message) + public static function setServerError(int $serverId, $message): void { if (is_array($message)) { $fatal = false; @@ -86,7 +87,7 @@ class LocationInfo * * @return array Return a default config. */ - public static function defaultPanelConfig($type) + public static function defaultPanelConfig(string $type): array { if ($type === 'DEFAULT') { return array( @@ -97,6 +98,7 @@ class LocationInfo 'prettytime' => true, 'roomplanner' => true, 'scaledaysauto' => true, + 'startday' => 0, 'daystoshow' => 7, 'rotation' => 0, 'scale' => 50, @@ -127,9 +129,111 @@ class LocationInfo 'interactive' => 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()); + + if (is_array($calendarFromBackend)) { + 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 ''; + } + } diff --git a/modules-available/locationinfo/inc/locationinfohooks.inc.php b/modules-available/locationinfo/inc/locationinfohooks.inc.php index 8e4975e9..8ec217cc 100644 --- a/modules-available/locationinfo/inc/locationinfohooks.inc.php +++ b/modules-available/locationinfo/inc/locationinfohooks.inc.php @@ -5,9 +5,9 @@ class LocationInfoHooks /** * @param string $uuid panel uuid - * @return bool|string panel name if exists, false otherwise + * @return false|string panel name if exists, false otherwise */ - public static function getPanelName($uuid) + public static function getPanelName(string $uuid) { $ret = Database::queryFirst('SELECT panelname FROM locationinfo_panel WHERE paneluuid = :uuid', compact('uuid')); if ($ret === false) @@ -18,14 +18,11 @@ class LocationInfoHooks /** * Hook called by runmode module where we should modify the client config according to our * needs. Disable standby/logout timeouts, enable autologin, set URL. - * - * @param $machineUuid - * @param $panelUuid */ - public static function configHook($machineUuid, $panelUuid) + public static function configHook(string $machineUuid, string $panelUuid): void { $type = InfoPanel::getConfig($panelUuid, $data); - if ($type === false) + if ($type === null) return; // TODO: Invalid panel - what should we do? if ($type === 'URL') { // Check if we should set the insecure SSL mode (accept invalid/self signed certs etc.) @@ -51,7 +48,7 @@ class LocationInfoHooks RunMode::updateClientFlag($machineUuid, 'locationinfo', true); } else { // Automatic login RunMode::updateClientFlag($machineUuid, 'locationinfo', false); - ConfigHolder::add('SLX_AUTOLOGIN', '1', 1000); + ConfigHolder::add('SLX_AUTOLOGIN', 'ON', 1000); ConfigHolder::add('SLX_ADDONS', '', 1000); } if (!empty($data['browser'])) { @@ -72,13 +69,19 @@ class LocationInfoHooks if ($data['allow-tty'] === 'yes' || $data['allow-tty'] === 'no') { ConfigHolder::add('SLX_TTY_SWITCH', $data['allow-tty'], 1000); } + if (($data['zoom-factor'] ?? 100) != 100) { + ConfigHolder::add('SLX_BROWSER_ZOOM', $data['zoom-factor']); + } } else { // Not URL panel ConfigHolder::add('SLX_BROWSER_URL', 'http://' . $_SERVER['SERVER_ADDR'] . '/panel/' . $panelUuid); - ConfigHolder::add('SLX_BROWSER_INSECURE', '1'); // TODO: Sat server might redirect to HTTPS, which in turn could have a self-signed cert - push to client - ConfigHolder::add('SLX_AUTOLOGIN', '1', 1000); + ConfigHolder::add('SLX_AUTOLOGIN', 'ON', 1000); ConfigHolder::add('SLX_ADDONS', '', 1000); } + $al = ConfigHolder::get('SLX_AUTOLOGIN'); + if (!empty($al) && $al !== 'OFF' && $al != 0) { + ConfigHolder::add('SLX_SHUTDOWN_TIMEOUT', '', 1000); + } ConfigHolder::add('SLX_LOGOUT_TIMEOUT', '', 1000); ConfigHolder::add('SLX_SCREEN_STANDBY_TIMEOUT', '', 1000); ConfigHolder::add('SLX_SYSTEM_STANDBY_TIMEOUT', '', 1000); @@ -87,10 +90,8 @@ class LocationInfoHooks /** * Turn multiline list into space separated list, removing any * comments (starting with #) - * @param string $list - * @return string */ - private static function mangleList($list) + private static function mangleList(string $list): string { return preg_replace('/\s*(#[^\n]*)?(\n|$)/', ' ', $list); } diff --git a/modules-available/locationinfo/lang/de/template-tags.json b/modules-available/locationinfo/lang/de/template-tags.json index 1f781721..fe6a3e53 100644 --- a/modules-available/locationinfo/lang/de/template-tags.json +++ b/modules-available/locationinfo/lang/de/template-tags.json @@ -50,7 +50,7 @@ "lang_ignoreSslTooltip": "Akzeptiere ung\u00fcltige, abgelaufene oder selbstsignierte SSL-Zertifikate", "lang_insecureSsl": "Unsicheres SSL", "lang_interactive": "Interaktiver Browser", - "lang_interactiveTooltip": "Aktivieren, um regul\u00e4res Surfen zuzulassen", + "lang_interactiveTooltip": "Volles UI anzeigen (tabs, bookmarks, ...)", "lang_language": "Sprache", "lang_languageTooltip": "Legt die Sprache der angezeigten Oberfl\u00e4che fest", "lang_lastCalendarUpdate": "Kalender Update", @@ -149,5 +149,7 @@ "lang_verticalTooltip": "Legt fest, ob Kalender und Raum \u00fcbereinander angezeigt werden sollen", "lang_wednesday": "Mittwoch", "lang_when": "Wann", - "lang_whitelist": "Whitelist" + "lang_whitelist": "Whitelist", + "lang_zoomFactor": "Zoom-Faktor", + "lang_zoomFactorTooltip": "Initialer Zoom-Faktor beim Start des Panels. Je nach gew\u00e4hltem Browser kann der Faktor vom Benutzer angepasst werden." }
\ No newline at end of file diff --git a/modules-available/locationinfo/lang/en/template-tags.json b/modules-available/locationinfo/lang/en/template-tags.json index ce0eac98..5f612d16 100644 --- a/modules-available/locationinfo/lang/en/template-tags.json +++ b/modules-available/locationinfo/lang/en/template-tags.json @@ -50,7 +50,7 @@ "lang_ignoreSslTooltip": "Accept invalid, expired or self-signed ssl certificates", "lang_insecureSsl": "Insecure SSL", "lang_interactive": "Interactive Browser", - "lang_interactiveTooltip": "Activate to allow regular websurfing", + "lang_interactiveTooltip": "Show full browser UI (tabs, bookmarks, ...)", "lang_language": "Language", "lang_languageTooltip": "The language the frontend uses", "lang_lastCalendarUpdate": "Calendar update", @@ -149,5 +149,7 @@ "lang_verticalTooltip": "Defines whether the room and calendar are shown above each other", "lang_wednesday": "Wednesday", "lang_when": "When", - "lang_whitelist": "Whitelist" + "lang_whitelist": "Whitelist", + "lang_zoomFactor": "Zoom level", + "lang_zoomFactorTooltip": "Initial zoom level at startup. Depending on selected browser, this can later be changed by the user." }
\ No newline at end of file diff --git a/modules-available/locationinfo/page.inc.php b/modules-available/locationinfo/page.inc.php index 9e7a704e..63a02ba2 100644 --- a/modules-available/locationinfo/page.inc.php +++ b/modules-available/locationinfo/page.inc.php @@ -81,7 +81,7 @@ class Page_LocationInfo extends Page $this->showLocationsTable(); break; case 'backends': - $this->showBackendsTable($backends); + $this->showBackendsTable($backends ?? []); break; case 'edit-panel': $this->showPanelConfig(); @@ -100,7 +100,7 @@ class Page_LocationInfo extends Page /** * Deletes the server from the db. */ - private function deleteServer($id) + private function deleteServer($id): void { User::assertPermission('backend.edit'); if ($id === 0) { @@ -113,7 +113,7 @@ class Page_LocationInfo extends Page } } - private function deletePanel() + private function deletePanel(): void { $id = Request::post('uuid', false, 'string'); if ($id === false) { @@ -130,25 +130,27 @@ class Page_LocationInfo extends Page } } - private function getTime($str) + private static function getTime(string $str): ?int { $str = explode(':', $str); - if (count($str) !== 2) return false; - if ($str[0] < 0 || $str[0] > 23 || $str[1] < 0 || $str[1] > 59) return false; + 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() + private function writeLocationConfig(): void { // Check locations $locationid = Request::post('locationid', false, 'int'); if ($locationid === false) { Message::addError('main.parameter-missing', 'locationid'); - return false; + return; } if (Location::get($locationid) === false) { Message::addError('location.invalid-location-id', $locationid); - return false; + return; } User::assertPermission('location.edit', $locationid); @@ -198,29 +200,23 @@ class Page_LocationInfo extends Page )); } } - - 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 + * 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($failIfEmpty) + private function getLocationIdsFromRequest(): array { - $locationids = Request::post('locationids', false, 'string'); - if ($locationids === false) { - if (!$failIfEmpty) - return array(); - Message::addError('main.parameter-missing', 'locationids'); - Util::redirect('?do=locationinfo'); - } + $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 ($failIfEmpty && empty($locationids)) { + if (empty($locationids)) { Message::addError('main.parameter-empty', 'locationids'); Util::redirect('?do=locationinfo'); } @@ -230,7 +226,7 @@ class Page_LocationInfo extends Page /** * Updated the config in the db. */ - private function writePanelConfig() + private function writePanelConfig(): void { // UUID - existing or new $paneluuid = Request::post('uuid', false, 'string'); @@ -253,7 +249,7 @@ class Page_LocationInfo extends Page } // Permission - $this->assertPanelPermission($paneluuid, 'panel.edit', $params['locationids']); + $this->assertPanelPermission($paneluuid, 'panel.edit', $params['locationids'] ?? []); if ($paneluuid === 'new') { $paneluuid = Util::randomUuid(); @@ -276,10 +272,13 @@ class Page_LocationInfo extends Page Util::redirect('?do=locationinfo'); } - private function preparePanelConfigDefault() + /** + * @return array{config: array, locationids: array} + */ + private function preparePanelConfigDefault(): array { // Check locations - $locationids = self::getLocationIdsFromRequest(true); + $locationids = self::getLocationIdsFromRequest(); if (count($locationids) > 4) { $locationids = array_slice($locationids, 0, 4); } @@ -331,7 +330,10 @@ class Page_LocationInfo extends Page return array('config' => $conf, 'locationids' => $locationids); } - private function preparePanelConfigUrl() + /** + * @return array{config: array, locationids: array} + */ + private function preparePanelConfigUrl(): array { $bookmarkNames = Request::post('bookmarkNames', [], 'array'); $bookmarkUrls = Request::post('bookmarkUrls', [], 'array'); @@ -349,18 +351,22 @@ class Page_LocationInfo extends Page '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]+/ms", "\n", Request::post('whitelist', '', 'string')), - 'blacklist' => preg_replace("/[\r\n]+/ms", "\n", Request::post('blacklist', '', 'string')), + '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', 'slx-browser', 'string'), 'interactive' => Request::post('interactive', '0', 'bool'), - 'bookmarks' => $bookmarkString ? $bookmarkString : '', + 'bookmarks' => $bookmarkString ?: '', 'allow-tty' => Request::post('allow-tty', '', 'string'), + 'zoom-factor' => Request::post('zoom-factor', 100, 'int'), ); return array('config' => $conf, 'locationids' => []); } - private function preparePanelConfigSummary() + /** + * @return array{config: array, locationids: array} + */ + private function preparePanelConfigSummary(): array { // Build json structure $conf = array( @@ -373,14 +379,14 @@ class Page_LocationInfo extends Page $conf['panelupdate'] = 15; } // Check locations - $locationids = self::getLocationIdsFromRequest(true); + $locationids = self::getLocationIdsFromRequest(); return array('config' => $conf, 'locationids' => $locationids); } /** * Updates the server settings in the db. */ - private function updateServerSettings() + private function updateServerSettings(): void { User::assertPermission('backend.edit'); $serverid = Request::post('id', -1, 'int'); @@ -422,10 +428,10 @@ class Page_LocationInfo extends Page * * @param int $id Server id which connection should be checked. */ - private function checkConnection($serverid = 0) + private function checkConnection(int $serverid = 0): void { if ($serverid === 0) { - Util::traceError('checkConnection called with no server id'); + ErrorHandler::traceError('checkConnection called with no server id'); } User::assertPermission('backend.check'); @@ -438,7 +444,8 @@ class Page_LocationInfo extends Page LocationInfo::setServerError($serverid, 'Unknown backend type: ' . $dbresult['servertype']); return; } - $credentialsOk = $serverInstance->setCredentials($serverid, json_decode($dbresult['credentials'], true)); + $credentialsOk = $serverInstance->setCredentials($serverid, + (array)json_decode($dbresult['credentials'], true)); if ($credentialsOk) { $serverInstance->checkConnection(); @@ -447,7 +454,7 @@ class Page_LocationInfo extends Page LocationInfo::setServerError($serverid, $serverInstance->getErrors()); } - private function loadBackends() + private function loadBackends(): array { // Get a list of all the backend types. $servertypes = array(); @@ -459,7 +466,7 @@ class Page_LocationInfo extends Page // 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)) { + foreach ($dbquery2 as $row) { if (isset($servertypes[$row['servertype']])) { $row['typename'] = $servertypes[$row['servertype']]; } else { @@ -485,7 +492,7 @@ class Page_LocationInfo extends Page /** * Show the list of backends */ - private function showBackendsTable($serverlist) + private function showBackendsTable(array $serverlist): void { User::assertPermission('backend.*'); $data = array( @@ -496,7 +503,7 @@ class Page_LocationInfo extends Page Render::addTemplate('page-servers', $data); } - private function showBackendLog() + private function showBackendLog(): void { $id = Request::get('serverid', false, 'int'); if ($id === false) { @@ -512,7 +519,7 @@ class Page_LocationInfo extends Page $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)) { + 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); @@ -521,7 +528,7 @@ class Page_LocationInfo extends Page Render::addTemplate('page-server-log', $server); } - private function showLocationsTable() + private function showLocationsTable(): void { $allowedLocations = User::getAllowedLocations('location.edit'); if (empty($allowedLocations)) { @@ -536,7 +543,7 @@ class Page_LocationInfo extends Page LEFT JOIN `locationinfo_coursebackend` AS cb USING (serverid) LEFT JOIN `location` AS loc USING (locationid)"); - while ($row = $dbquery->fetch(PDO::FETCH_ASSOC)) { + foreach ($dbquery as $row) { $locid = (int)$row['locationid']; if (!isset($locations[$locid]) || !in_array($locid, $allowedLocations)) continue; @@ -547,6 +554,7 @@ class Page_LocationInfo extends Page } $locations[$locid] += array( 'openingGlyph' => $glyph, + 'strong' => $glyph === 'ok', 'backend' => $backend, 'lastCalendarUpdate' => Util::prettyTime($row['lastcalendarupdate']), // TODO 'backendMissing' => !CourseBackend::exists($row['servertype']), @@ -575,11 +583,20 @@ class Page_LocationInfo extends Page )); } - private function showPanelsTable() + 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; @@ -593,7 +610,7 @@ class Page_LocationInfo extends Page } $panels = array(); $locations = Location::getLocationsAssoc(); - while ($row = $res->fetch(PDO::FETCH_ASSOC)) { + foreach ($res as $row) { if ($row['paneltype'] === 'URL') { $url = json_decode($row['panelconfig'], true)['url']; $row['locations'] = $row['locationurl'] = $url; @@ -602,14 +619,16 @@ class Page_LocationInfo extends Page } else { $lids = explode(',', $row['locationids']); // Permissions - if (!empty(array_diff($lids, $visibleLocations))) { + if ($visibleLocations !== true && !empty(array_diff($lids, $visibleLocations))) { continue; } - $row['edit_disabled'] = !empty(array_diff($lids, $editLocations)) ? 'disabled' : ''; - $row['runmode_disabled'] = !empty(array_diff($lids, $assignLocations)) ? 'disabled' : ''; + $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'] : $id; + return isset($locations[$id]) ? $locations[$id]['locationname'] : "<<deleted=$id>>"; }, $lids); $row['locations'] = implode(', ', $locs); } @@ -648,7 +667,7 @@ class Page_LocationInfo extends Page * * @param int $id Serverid */ - private function ajaxServerSettings($id) + private function ajaxServerSettings(int $id): void { User::assertPermission('backend.edit'); $oldConfig = Database::queryFirst('SELECT servername, servertype, credentials @@ -679,7 +698,7 @@ class Page_LocationInfo extends Page } else { $cred->initForRender(); } - $cred->title = Dictionary::translateFile('backend-' . $s, $cred->property, true); + $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); } @@ -697,7 +716,7 @@ class Page_LocationInfo extends Page * * @param int $id id of the location */ - private function ajaxConfigLocation($id) + private function ajaxConfigLocation(int $id): void { User::assertPermission('location.edit', $id); $locConfig = Database::queryFirst("SELECT info.serverid, info.serverlocationid, loc.openingtime @@ -721,7 +740,7 @@ class Page_LocationInfo extends Page 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)) { + foreach ($res as $row) { if ($best === false || $chain[$row['locationid']] < $chain[$best['locationid']]) { $best = $row; } @@ -735,7 +754,7 @@ class Page_LocationInfo extends Page // 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)) { + foreach ($res as $row) { if ($row['serverid'] == $locConfig['serverid']) { $row['selected'] = 'selected'; } @@ -752,12 +771,6 @@ class Page_LocationInfo extends Page echo Render::parse('ajax-config-location', $data); } - private function fmtTime($time) - { - $t = explode(':', $time); - return sprintf('%02d:%02d', $t[0], $t[1]); - } - /** * Checks if simple mode or expert mode is active. * Tries to merge/compact the opening times schedule, and @@ -766,7 +779,7 @@ class Page_LocationInfo extends Page * * @return array new optimized openingtimes */ - private function compressTimes(&$array) + private function compressTimes(array $array): array { if (empty($array)) return []; @@ -774,9 +787,9 @@ class Page_LocationInfo extends Page $DAYLIST = array_flip(['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']); $new = []; foreach ($array as $row) { - $s = $this->getTime($row['openingtime']); - $e = $this->getTime($row['closingtime']); - if ($s === false || $e === false || $e <= $s) + $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; @@ -811,9 +824,9 @@ class Page_LocationInfo extends Page /** * @param array $daysArray List of days, "Monday", "Tuesday" etc. Must not contain duplicates. - * @return string Human readable representation of list of days + * @return string Human-readable representation of list of days */ - private function buildDaysString(array $daysArray) + private function buildDaysString(array $daysArray): string { /* Dictionary::translate('monday') Dictionary::translate('tuesday') Dictionary::translate('wednesday') * Dictionary::translate('thursday') Dictionary::translate('friday') Dictionary::translate('saturday') @@ -831,10 +844,10 @@ class Page_LocationInfo extends Page // Chain $last++; } else { - $string = Dictionary::translate($DAYLIST[$first], true); + $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], true); + . Dictionary::translate($DAYLIST[$last]); } $output[] = $string; $first = $last = $day; @@ -870,7 +883,7 @@ class Page_LocationInfo extends Page // $start must lie before range start, otherwise we'd have hit the case above $e = $current[1]; unset($array[$day][$key]); - continue; + //continue; } } $array[$day][] = array($s, $e); @@ -878,10 +891,8 @@ class Page_LocationInfo extends Page /** * Ajax the config of a panel. - * - * @param $id Location ID */ - private function showPanelConfig() + private function showPanelConfig(): void { $id = Request::get('uuid', false, 'string'); if ($id === false) { @@ -1000,6 +1011,7 @@ class Page_LocationInfo extends Page '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']), @@ -1025,7 +1037,7 @@ class Page_LocationInfo extends Page } } - private function showPanel() + private function showPanel(): void { $uuid = Request::get('uuid', false, 'string'); if ($uuid === false) { @@ -1033,7 +1045,7 @@ class Page_LocationInfo extends Page die('Missing parameter uuid'); } $type = InfoPanel::getConfig($uuid, $config); - if ($type === false) { + if ($type === null) { http_response_code(404); die('Panel with given uuid not found'); } @@ -1060,7 +1072,7 @@ class Page_LocationInfo extends Page 'language' => $config['language'], ); - die(Render::parse('frontend-default', $data, $module = false, $lang = $config['language'])); + die(Render::parse('frontend-default', $data, null, $config['language'])); } if ($type === 'SUMMARY') { @@ -1072,7 +1084,7 @@ class Page_LocationInfo extends Page 'language' => $config['language'], ); - die(Render::parse('frontend-summary', $data, $module = false, $lang = $config['language'])); + die(Render::parse('frontend-summary', $data, null, $config['language'])); } http_response_code(500); @@ -1081,10 +1093,9 @@ class Page_LocationInfo extends Page /** * @param string|array $panelOrUuid UUID of panel, or array with keys paneltype and locationds - * @param string $permission - * @param null|int[] $additionalLocations + * @param int[] $additionalLocations */ - private function assertPanelPermission($panelOrUuid, $permission, $additionalLocations = null) + private function assertPanelPermission($panelOrUuid, string $permission, array $additionalLocations = null): void { if (is_array($panelOrUuid)) { $panel = $panelOrUuid; diff --git a/modules-available/locationinfo/templates/frontend-default.html b/modules-available/locationinfo/templates/frontend-default.html index 6e04550d..cc62075e 100755 --- a/modules-available/locationinfo/templates/frontend-default.html +++ b/modules-available/locationinfo/templates/frontend-default.html @@ -564,6 +564,8 @@ optional: putInRange(config, 'rotation', 0, 3, 0); } + var updateTimer = null; + /** * generates the Room divs and calls the needed functions depending on the rooms mode */ @@ -651,7 +653,7 @@ optional: } mainUpdateLoop(); - setInterval(mainUpdateLoop, 10000); + updateTimer = setInterval(mainUpdateLoop, 10000); setInterval(updateHeaders, globalConfig.eco ? 10000 : 1000); } @@ -679,7 +681,14 @@ optional: var today = date.getDate(); if (lastDate !== false) { if (lastDate !== today) { - location.reload(true); + if (updateTimer !== null) { + clearInterval(updateTimer); + updateTimer = null; + } + // Delay by a minute, sometimes the calendar shows the previous day if we load too quickly. + setTimeout(function() { + location.reload(true); + }, 60000); } } else { lastDate = today; @@ -1326,7 +1335,6 @@ optional: /========================================== Room Layout ============================================= */ - const picSizeX = 3.8; const picSizeY = 3; diff --git a/modules-available/locationinfo/templates/page-config-panel-url.html b/modules-available/locationinfo/templates/page-config-panel-url.html index 365e15db..3aaf8620 100644 --- a/modules-available/locationinfo/templates/page-config-panel-url.html +++ b/modules-available/locationinfo/templates/page-config-panel-url.html @@ -188,10 +188,10 @@ </div> <div class="col-sm-3"> <input class="form-control" name="bookmarkUrls[]" type="text" value="" - placeholder="http://www.bwlehrpool.de/" pattern=".*://.*"> + placeholder="https://www.bwlehrpool.de/" pattern=".*://.*"> </div> <div class="col-sm-1"> - <button type="button" class="btn btn-danger" onclick="this.closest('.row').remove()"> + <button type="button" class="btn btn-danger" onclick="$(this).closest('.row').remove()"> <span class="glyphicon glyphicon-minus"></span> </button> </div> @@ -208,7 +208,7 @@ placeholder="http://www.bwlehrpool.de/" pattern=".*://.*" required> </div> <div class="col-sm-1"> - <button type="button" class="btn btn-danger" onclick="this.closest('.row').remove()"> + <button type="button" class="btn btn-danger" onclick="$(this).closest('.row').remove()"> <span class="glyphicon glyphicon-minus"></span> </button> </div> @@ -216,6 +216,24 @@ {{/bookmarks}} </div> + <div class="list-group-item"> + <div class="row"> + <div class="col-sm-4"> + <label for="zoom-factor">{{lang_zoomFactor}}</label> + </div> + <div class="col-sm-7 col-xs-10"> + <input class="form-control" id="zoom-factor" type="range" min="50" max="300" step="5" + name="zoom-factor" value="{{zoom-factor}}"> + </div> + <div class="col-sm-1 col-xs-2" id="zoom-value"> + + </div> + <div class="col-sm-12 small text-muted spacebottop"> + {{lang_zoomFactorTooltip}} + </div> + </div> + </div> + </div> </div> <div class="text-right"> @@ -227,12 +245,19 @@ </div> </form> -<script type="text/javascript"><!-- +<script> document.addEventListener("DOMContentLoaded", function () { // load value to dropdown menus - $('#browser option[value="{{browser}}"]').attr("selected", "selected"); + $('#browser option[value="{{browser}}"]').prop("selected", true); browserChange(); + var $zv = $('#zoom-value'); + var $zf = $('#zoom-factor'); + var sliderUpdate = function() { + $zv.text($zf.val() + '%'); + }; + $zf.on('input', sliderUpdate); + sliderUpdate(); }); // Hide interactive-input if slx-browser is selected @@ -257,4 +282,4 @@ function addBookmark() { $('#bookmarks').append(rowCopy); } -//--></script> +</script> diff --git a/modules-available/locationinfo/templates/page-locations.html b/modules-available/locationinfo/templates/page-locations.html index fa2e3a2d..c09b5336 100644 --- a/modules-available/locationinfo/templates/page-locations.html +++ b/modules-available/locationinfo/templates/page-locations.html @@ -35,7 +35,7 @@ {{/backend}} </td> <td class="text-center"> - <span class="glyphicon glyphicon-{{openingGlyph}}"></span> + <span class="glyphicon glyphicon-{{openingGlyph}} {{^strong}}text-muted{{/strong}}"></span> </td> </tr> {{/list}} |