summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSimon Rettberg2021-01-19 14:59:13 +0100
committerSimon Rettberg2021-01-19 14:59:13 +0100
commit028d4d2b07c50f2854d3cfc06f05855fa358ead3 (patch)
treedf83e31aa0446331fcb1ab338e894dd21e9bf93c
parent[roomplanner] WOL pvsmgr if tutor machine boots or a user logs in (diff)
downloadslx-admin-028d4d2b07c50f2854d3cfc06f05855fa358ead3.tar.gz
slx-admin-028d4d2b07c50f2854d3cfc06f05855fa358ead3.tar.xz
slx-admin-028d4d2b07c50f2854d3cfc06f05855fa358ead3.zip
[locationinfo] Add generic ical backend
Closes #3824
-rw-r--r--modules-available/locationinfo/inc/coursebackend/coursebackend_hisinone.inc.php109
-rw-r--r--modules-available/locationinfo/inc/coursebackend/coursebackend_ical.inc.php61
-rw-r--r--modules-available/locationinfo/inc/icalcoursebackend.inc.php151
-rw-r--r--modules-available/locationinfo/lang/de/backend-ical.json16
-rw-r--r--modules-available/locationinfo/lang/en/backend-ical.json16
-rw-r--r--modules-available/locationinfo/lang/en/template-tags.json2
6 files changed, 252 insertions, 103 deletions
diff --git a/modules-available/locationinfo/inc/coursebackend/coursebackend_hisinone.inc.php b/modules-available/locationinfo/inc/coursebackend/coursebackend_hisinone.inc.php
index 82efae87..8bd18169 100644
--- a/modules-available/locationinfo/inc/coursebackend/coursebackend_hisinone.inc.php
+++ b/modules-available/locationinfo/inc/coursebackend/coursebackend_hisinone.inc.php
@@ -1,15 +1,7 @@
<?php
-class CourseBackend_HisInOne extends CourseBackend
+class CourseBackend_HisInOne extends ICalCourseBackend
{
- private $location;
- private $verifyHostname = true;
- private $verifyCert = true;
- /**
- * @var bool|resource
- */
- private $curlHandle = false;
-
public function setCredentialsInternal($data)
{
@@ -18,9 +10,8 @@ class CourseBackend_HisInOne extends CourseBackend
return false;
}
- $this->location = $this->mangleProperty('baseUrl', $data['baseUrl']);
- $this->verifyHostname = $data['verifyHostname'];
- $this->verifyCert = $data['verifyCert'];
+ $this->init($this->mangleProperty('baseUrl', $data['baseUrl']),
+ $data['verifyCert'], $data['verifyHostname']);
return true;
}
@@ -39,14 +30,14 @@ class CourseBackend_HisInOne extends CourseBackend
if ($prop === 'baseUrl') {
// Update form SOAP to iCal url
if (preg_match(',^(http.*?)/qisserver,', $value, $out)) {
- $value = $out[1] . '/qisserver/pages/cm/exa/timetable/roomScheduleCalendarExport.faces?roomId=';
+ $value = $out[1] . '/qisserver/pages/cm/exa/timetable/roomScheduleCalendarExport.faces?roomId=%ID%';
} elseif (preg_match(',(.*[/=])\d*$', $value, $out)) {
- $value = $out[1];
+ $value = $out[1] . '%ID%';
} elseif (substr_count($value, '/') <= 3) {
if (substr($value, -1) !== '/') {
$value .= '/';
}
- $value .= 'qisserver/pages/cm/exa/timetable/roomScheduleCalendarExport.faces?roomId=';
+ $value .= 'qisserver/pages/cm/exa/timetable/roomScheduleCalendarExport.faces?roomId=%ID%';
}
}
return $value;
@@ -54,10 +45,8 @@ class CourseBackend_HisInOne extends CourseBackend
public function checkConnection()
{
- if (empty($this->location)) {
- $this->addError("Credentials are not set", true);
+ if (!$this->isOK())
return false;
- }
// Unfortunately HisInOne returns an internal server error if you pass an invalid roomId.
// So we just try a bunch and see if anything works. Even if this fails, using
// the backend should work, given the URL is actually correct.
@@ -68,43 +57,6 @@ class CourseBackend_HisInOne extends CourseBackend
return false;
}
- /**
- * @param int $roomId room id
- * @return ICalEvent[]|null all events for this room in the range -7 days to +7 days, or NULL on error
- */
- private function downloadIcal($roomId)
- {
- if ($this->curlHandle === false) {
- $this->curlHandle = curl_init();
- }
-
- $ical = new ICalParser(['filterDaysBefore' => 7, 'filterDaysAfter' => 7]);
- $options = array(
- CURLOPT_WRITEFUNCTION => function ($ch, $data) use ($ical) {
- $ical->feedData($data);
- return strlen($data);
- },
- CURLOPT_FOLLOWLOCATION => true,
- CURLOPT_SSL_VERIFYHOST => $this->verifyHostname ? 2 : 0,
- CURLOPT_SSL_VERIFYPEER => $this->verifyCert ? 1 : 0,
- CURLOPT_URL => $this->location . $roomId,
- CURLOPT_TIMEOUT => 60,
- CURLOPT_CONNECTTIMEOUT => 4,
- );
-
- curl_setopt_array($this->curlHandle, $options);
-
- if (!curl_exec($this->curlHandle)) {
- $this->addError('Curl error: ' . curl_error($this->curlHandle), false);
- }
- $ical->finish();
- if (!$ical->isValid()) {
- $this->addError("Did not find a VCALENDAR in returned data for {$this->location}$roomId", false);
- return null;
- }
- return $ical->events();
- }
-
public function getCacheTime()
{
return 30 * 60;
@@ -121,51 +73,4 @@ class CourseBackend_HisInOne extends CourseBackend
return "HisInOne";
}
- public function fetchSchedulesInternal($requestedRoomIds)
- {
- if (empty($requestedRoomIds)) {
- return array();
- }
- $tTables = [];
- foreach ($requestedRoomIds as $roomId) {
- $data = $this->downloadIcal($roomId);
- if ($data === null) {
- $this->addError("Downloading ical for $roomId failed", false);
- continue;
- }
- foreach ($data as $event) {
- $tTables[$roomId][] = array(
- 'title' => $this->toTitle($event),
- 'start' => $event->dtstart,
- 'end' => $event->dtend,
- 'cancelled' => false, // ??? How
- );
- }
- }
- return $tTables;
- }
-
- /**
- * Get a usable title from either SUMMARY or DESCRIPTION
- * @param ICalEvent $event
- */
- private function toTitle($event)
- {
- $title = $event->summary;
- if (empty($title)) {
- $title = $event->description;
- }
- if (empty($title)) {
- $title = 'Unknown';
- }
- return preg_replace([',(\s*<br\s*/?>\s*|\r|\n|\\\r|\\\n)+,', '/\\\\([,;:])/'], ["\n", '$1'], $title);
- }
-
- public function __destruct()
- {
- if ($this->curlHandle !== false) {
- curl_close($this->curlHandle);
- }
- }
-
}
diff --git a/modules-available/locationinfo/inc/coursebackend/coursebackend_ical.inc.php b/modules-available/locationinfo/inc/coursebackend/coursebackend_ical.inc.php
new file mode 100644
index 00000000..98dca1cb
--- /dev/null
+++ b/modules-available/locationinfo/inc/coursebackend/coursebackend_ical.inc.php
@@ -0,0 +1,61 @@
+<?php
+
+class CourseBackend_ICal extends ICalCourseBackend
+{
+
+ /** @var string room ID for testing connection */
+ private $testId;
+
+ public function setCredentialsInternal($data)
+ {
+ if (empty($data['baseUrl'])) {
+ $this->addError("No url is given", true);
+ return false;
+ }
+
+ $this->init($data['baseUrl'], $data['verifyCert'], $data['verifyHostname'], $data['authMethod'],
+ $data['user'], $data['pass']);
+ $this->testId = $data['testId'];
+
+ return true;
+ }
+
+ public function getCredentialDefinitions()
+ {
+ return [
+ new BackendProperty('baseUrl', 'string'),
+ new BackendProperty('verifyCert', 'bool', true),
+ new BackendProperty('verifyHostname', 'bool', true),
+ new BackendProperty('testId', 'string'),
+ new BackendProperty('authMethod', ['NONE', 'BASIC', 'DIGEST', 'GSSNEGOTIATE', 'NTLM'], 'NONE'),
+ new BackendProperty('user', 'string'),
+ new BackendProperty('pass', 'string'),
+ ];
+ }
+
+ public function checkConnection()
+ {
+ if (!$this->isOK())
+ return false;
+ if (empty($this->testId))
+ return true;
+ return ($this->downloadIcal($this->testId) !== null);
+ }
+
+ public function getCacheTime()
+ {
+ return 30 * 60;
+ }
+
+ public function getRefreshTime()
+ {
+ return 60 * 60;
+ }
+
+
+ public function getDisplayName()
+ {
+ return "iCal";
+ }
+
+}
diff --git a/modules-available/locationinfo/inc/icalcoursebackend.inc.php b/modules-available/locationinfo/inc/icalcoursebackend.inc.php
new file mode 100644
index 00000000..fba0866c
--- /dev/null
+++ b/modules-available/locationinfo/inc/icalcoursebackend.inc.php
@@ -0,0 +1,151 @@
+<?php
+
+abstract class ICalCourseBackend extends CourseBackend
+{
+
+ /** @var string */
+ private $location;
+ /** @var string */
+ private $authMethod;
+ /** @var string */
+ private $user;
+ /** @var string */
+ private $pass;
+ /** @var bool */
+ private $verifyHostname;
+ /** @var bool */
+ private $verifyCert;
+ /** @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 = '')
+ {
+ $this->verifyCert = $verifyCert;
+ $this->verifyHostname = $verifyHostname;
+ if (strpos($location, '%ID%') === false) {
+ $location .= '%ID%';
+ }
+ $this->location = $location;
+ $this->authMethod = $authMethod;
+ $this->user = $user;
+ $this->pass = $pass;
+ }
+
+ /**
+ * @param int $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)
+ {
+ if (!$this->isOK())
+ 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);
+ return strlen($data);
+ },
+ CURLOPT_FOLLOWLOCATION => true,
+ CURLOPT_SSL_VERIFYHOST => $this->verifyHostname ? 2 : 0,
+ CURLOPT_SSL_VERIFYPEER => $this->verifyCert ? 1 : 0,
+ CURLOPT_URL => str_replace('%ID%', $roomId, $this->location),
+ CURLOPT_TIMEOUT => 60,
+ CURLOPT_CONNECTTIMEOUT => 4,
+ ];
+ if ($this->authMethod !== 'NONE' && defined('CURLAUTH_' . $this->authMethod)) {
+ $options[CURLOPT_HTTPAUTH] = constant('CURLAUTH_' . $this->authMethod);
+ $options[CURLOPT_USERPWD] = $this->user;
+ if (!empty($this->pass)) {
+ $options[CURLOPT_USERPWD] .= ':' . $this->pass;
+ }
+ }
+
+ curl_setopt_array($this->curlHandle, $options);
+
+ $curlRet = curl_exec($this->curlHandle);
+ if (!$curlRet) {
+ $this->addError('Curl error: ' . curl_error($this->curlHandle) . ' for ' . $roomId, false);
+ }
+ $ical->finish();
+ if (!$ical->isValid()) {
+ if ($curlRet) {
+ $this->addError("Did not find a VCALENDAR in returned data for $roomId", false);
+ }
+ return null;
+ }
+ return $ical->events();
+ }
+
+ public function fetchSchedulesInternal($requestedRoomIds): array
+ {
+ if (empty($requestedRoomIds) || !$this->isOK()) {
+ return array();
+ }
+ $tTables = [];
+ foreach ($requestedRoomIds as $roomId) {
+ $data = $this->downloadIcal($roomId);
+ if ($data === null) {
+ $this->addError("Downloading ical for $roomId failed", false);
+ continue;
+ }
+ foreach ($data as $event) {
+ $tTables[$roomId][] = array(
+ 'title' => $this->toTitle($event),
+ 'start' => $event->dtstart,
+ 'end' => $event->dtend,
+ 'cancelled' => false, // ??? How
+ );
+ }
+ }
+ return $tTables;
+ }
+
+ /**
+ * Get a usable title from either SUMMARY or DESCRIPTION
+ * @param ICalEvent $event
+ */
+ private function toTitle($event): string
+ {
+ $title = $event->summary;
+ if (empty($title)) {
+ $title = $event->description;
+ }
+ if (empty($title)) {
+ $title = 'Unknown';
+ }
+ return (string)preg_replace([',(\s*<br\s*/?>\s*|\r|\n|\\\r|\\\n)+,', '/\\\\([,;:])/'], ["\n", '$1'], $title);
+ }
+
+ protected function isOK(): bool
+ {
+ if (empty($this->location)) {
+ $this->addError("Credentials are not set", true);
+ return false;
+ }
+ return true;
+ }
+
+ public function __destruct()
+ {
+ if ($this->curlHandle !== false) {
+ curl_close($this->curlHandle);
+ }
+ }
+
+} \ No newline at end of file
diff --git a/modules-available/locationinfo/lang/de/backend-ical.json b/modules-available/locationinfo/lang/de/backend-ical.json
new file mode 100644
index 00000000..9a91eb9f
--- /dev/null
+++ b/modules-available/locationinfo/lang/de/backend-ical.json
@@ -0,0 +1,16 @@
+{
+ "authMethod": "Authentifizierung",
+ "authMethod_helptext": "Falls eine Authentifizierung per HTTP-Header erforderlich ist, kann hier die gew\u00fcnschte Methode gew\u00e4hlt werden.",
+ "baseUrl": "Basis-URL",
+ "baseUrl_helptext": "URL zum iCal-File f\u00fcr diesen Raum. Ersetzen Sie den Part, der den Raum identifiziert durch %ID%, z.B. \"http:\/\/example.com\/calendars\/%ID%\". Die spezifische ID tragen Sie dann in der Raum\u00fcbersicht f\u00fcr jeden Raum individuell ein.",
+ "pass": "Passwort",
+ "pass_helptext": "Optional. Passwort f\u00fcr Authentifizierung.",
+ "testId": "Testraum",
+ "testId_helptext": "Optional. Tragen Sie hier eine g\u00fcltige Raum-ID f\u00fcr dieses Backend ein, um diese f\u00fcr Verbondungs-checks zu nutzen (Klick auf blauen \"Verbindung pr\u00fcfen\" Button in Backend-\u00dcbersicht).",
+ "user": "Nutzername f\u00fcr Authentifizierung",
+ "user_helptext": "Optional. Nutzername f\u00fcr Authentifizierung.",
+ "verifyCert": "Zertifikat pr\u00fcfen",
+ "verifyCert_helptext": "Wenn das Zertifikat abgelaufen ist, oder von keiner bekannten CA ausgestellt wurde, wird die Verbindung abgelehnt.",
+ "verifyHostname": "Hostnamen pr\u00fcfen",
+ "verifyHostname_helptext": "Der im Zertifikat angegebene Hostname muss mit dem Hostnamen aus der URL \u00fcbereinstimmen, sonst wird die Verbindung abgelehnt."
+} \ No newline at end of file
diff --git a/modules-available/locationinfo/lang/en/backend-ical.json b/modules-available/locationinfo/lang/en/backend-ical.json
new file mode 100644
index 00000000..a5b26efd
--- /dev/null
+++ b/modules-available/locationinfo/lang/en/backend-ical.json
@@ -0,0 +1,16 @@
+{
+ "authMethod": "Athentication",
+ "authMethod_helptext": "If backend requires authentication via HTTP header, select appropriate method here.",
+ "baseUrl": "Base URL",
+ "baseUrl_helptext": "URL to iCal file for this room. Replace the part of the URL that identifies a specific room by %ID%, f.i. \"http:\/\/example.com\/locations\/%ID%\". Then switch to the \"location-specific settings\" tab and add the according ID to each room.",
+ "pass": "Password",
+ "pass_helptext": "Optional. Password for authentication.",
+ "testId": "Test room",
+ "testId_helptext": "Optional. Provide a valid room id for this backend for use when clicking the blue \"check connection\" button in the backend list.",
+ "user": "Username",
+ "user_helptext": "Optional. Username for authentication.",
+ "verifyCert": "Verify certificate",
+ "verifyCert_helptext": "If the certificate expired or was not signed by a known CA, the connection will be aborted.",
+ "verifyHostname": "Verify host name",
+ "verifyHostname_helptext": "The certificate's host name must match the host name given in the URL, otherwise the connection will be aborted."
+} \ 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 5a87e42c..ce0eac98 100644
--- a/modules-available/locationinfo/lang/en/template-tags.json
+++ b/modules-available/locationinfo/lang/en/template-tags.json
@@ -55,7 +55,7 @@
"lang_languageTooltip": "The language the frontend uses",
"lang_lastCalendarUpdate": "Calendar update",
"lang_locationName": "Name",
- "lang_locationSettings": "Location specific settings",
+ "lang_locationSettings": "Location-specific settings",
"lang_locations": "Locations",
"lang_locationsTable": "Rooms \/ Locations",
"lang_locationsTableHints": "Here you can link the location ID to a configured backend (e.g. HISinOne) to show calendar events.",