diff options
Diffstat (limited to 'modules-available/locationinfo/inc')
12 files changed, 549 insertions, 446 deletions
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); } |