summaryrefslogtreecommitdiffstats
path: root/modules-available/locationinfo/inc
diff options
context:
space:
mode:
Diffstat (limited to 'modules-available/locationinfo/inc')
-rw-r--r--modules-available/locationinfo/inc/coursebackend.inc.php326
-rw-r--r--modules-available/locationinfo/inc/coursebackend/coursebackend_davinci.inc.php128
-rw-r--r--modules-available/locationinfo/inc/coursebackend/coursebackend_dummy.inc.php113
-rw-r--r--modules-available/locationinfo/inc/coursebackend/coursebackend_hisinone.inc.php352
-rw-r--r--modules-available/locationinfo/inc/locationinfo.inc.php63
5 files changed, 982 insertions, 0 deletions
diff --git a/modules-available/locationinfo/inc/coursebackend.inc.php b/modules-available/locationinfo/inc/coursebackend.inc.php
new file mode 100644
index 00000000..0d84b0fb
--- /dev/null
+++ b/modules-available/locationinfo/inc/coursebackend.inc.php
@@ -0,0 +1,326 @@
+<?php
+
+/**
+ * Base class for course query backends
+ */
+abstract class CourseBackend
+{
+
+ /*
+ * Static part for handling interfaces
+ */
+
+ /**
+ * @var array list of known backends
+ */
+ private static $backendTypes = false;
+ /**
+ * @var boolean|string false = no error, error message otherwise
+ */
+ protected $error;
+ /**
+ * @var int as internal serverId
+ */
+ protected $serverId;
+ /**
+ * @const int max number of additional locations to fetch (for backends that benefit from request coalesc.)
+ */
+ const MAX_ADDIDIONAL_LOCATIONS = 5;
+
+ /**
+ * CourseBackend constructor.
+ */
+ public final function __construct()
+ {
+ $this->error = false;
+ }
+
+ /**
+ * Load all known backend types. This is done
+ * by including *.inc.php from inc/coursebackend/.
+ */
+ public static function loadDb()
+ {
+ if (self::$backendTypes !== false)
+ return;
+ 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);
+ if (!class_exists('coursebackend_' . $out[1])) {
+ trigger_error("Backend type source unit $file doesn't seem to define class CourseBackend_{$out[1]}", E_USER_ERROR);
+ }
+ self::$backendTypes[$out[1]] = true;
+ }
+ }
+
+ /**
+ * Get all known config module types.
+ *
+ * @return array list of modules
+ */
+ public static function getList()
+ {
+ self::loadDb();
+ return array_keys(self::$backendTypes);
+ }
+
+ /**
+ * Get fresh instance of ConfigModule subclass for given module type.
+ *
+ * @param string $moduleType name of module type
+ * @return \CourseBackend module instance
+ */
+ public static function getInstance($moduleType)
+ {
+ self::loadDb();
+ if (!isset(self::$backendTypes[$moduleType])) {
+ error_log('Unknown module type: ' . $moduleType);
+ return false;
+ }
+ if (!is_object(self::$backendTypes[$moduleType])) {
+ $class = "coursebackend_$moduleType";
+ self::$backendTypes[$moduleType] = new $class;
+ }
+ return self::$backendTypes[$moduleType];
+ }
+
+ /**
+ * @return string return display name of backend
+ */
+ public abstract function getDisplayName();
+
+
+ /**
+ * @returns \BackendProperty[] list of properties that need to be set
+ */
+ public abstract function getCredentials();
+
+ /**
+ * @return boolean true if the connection works, false otherwise
+ */
+ public abstract function checkConnection();
+
+ /**
+ * uses json to setCredentials, the json must follow the form given in
+ * getCredentials
+ *
+ * @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);
+
+ /**
+ * @return int desired caching time of results, in seconds. 0 = no caching
+ */
+ public abstract function getCacheTime();
+
+ /**
+ * @return int age after which timetables are no longer refreshed should be
+ * greater then CacheTime
+ */
+ public abstract function getRefreshTime();
+
+ /**
+ * Internal version of fetch, to be overridden by subclasses.
+ *
+ * @param $roomIds 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 shedule array contains an array in this format:
+ * ["start"=>'JJJJ-MM-DD HH:MM:SS',"end"=>'JJJJ-MM-DD HH:MM:SS',"title"=>string]
+ */
+ protected abstract function fetchSchedulesInternal($roomId);
+
+ /**
+ * Method for fetching the schedule of the given rooms on a server.
+ *
+ * @param array $roomId 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
+ */
+ public final function fetchSchedule($requestedLocationIds)
+ {
+ if (!is_array($requestedLocationIds)) {
+ $this->error = 'No array of roomids was given to fetchSchedule';
+ return false;
+ }
+ if (empty($requestedLocationIds))
+ return array();
+ $NOW = time();
+ $dbquery1 = Database::simpleQuery("SELECT locationid, calendar, serverroomid, lastcalendarupdate
+ FROM location_info WHERE locationid IN (:locations)",
+ array('locations' => array_values($requestedLocationIds)));
+ $returnValue = [];
+ $remoteIds = [];
+ while ($row = $dbquery1->fetch(PDO::FETCH_ASSOC)) {
+ //Check if in cache if lastUpdate is null then it is interpreted as 1970
+ if ($row['lastcalendarupdate'] + $this->getCacheTime() > $NOW) {
+ $returnValue[$row['locationid']] = json_decode($row['calendar']);
+ } else {
+ $remoteIds[$row['locationid']] = $row['serverroomid'];
+ }
+
+ }
+ // No need for additional round trips to backend
+ if (empty($remoteIds)) {
+ return $returnValue;
+ }
+ // Check if we should refresh other rooms recently requested by front ends
+ if ($this->getRefreshTime() > $this->getCacheTime()) {
+ $dbquery4 = Database::simpleQuery("SELECT locationid, serverroomid FROM location_info
+ WHERE serverid = :serverid AND serverroomid NOT IN (:skiplist)
+ AND lastcalendarupdate BETWEEN :lowerage AND :upperage
+ LIMIT " . self::MAX_ADDIDIONAL_LOCATIONS, array(
+ 'serverid' => $this->serverId,
+ 'skiplist' => array_values($remoteIds),
+ 'lowerage' => $NOW - $this->getRefreshTime(),
+ 'upperage' => $NOW - $this->getCacheTime(),
+ ));
+ while ($row = $dbquery4->fetch(PDO::FETCH_ASSOC)) {
+ $remoteIds[$row['locationid']] = $row['serverroomid'];
+ }
+ }
+ $backendResponse = $this->fetchSchedulesInternal($remoteIds);
+ if ($backendResponse === false) {
+ return false;
+ }
+
+ if ($this->getCacheTime() > 0) {
+ // Caching requested by backend, write to DB
+ foreach ($backendResponse as $serverRoomId => $calendar) {
+ $value = json_encode($calendar);
+ Database::simpleQuery("UPDATE location_info SET calendar = :ttable, lastcalendarupdate = :now
+ WHERE serverid = :serverid AND serverroomid = :serverroomid", array(
+ 'serverid' => $this->serverId,
+ 'serverroomid' => $serverRoomId,
+ 'ttable' => $value,
+ 'now' => $NOW
+ ));
+ }
+ }
+ // Add rooms that were requested to the final return value
+ foreach ($remoteIds as $location => $serverRoomId) {
+ if (isset($backendResponse[$serverRoomId]) && in_array($location, $requestedLocationIds)) {
+ // Only add if we can map it back to our location id AND it was not an unsolicited coalesced refresh
+ $returnValue[$location] = $backendResponse[$serverRoomId];
+ }
+ }
+
+ return $returnValue;
+ }
+
+ public final function setCredentials($serverId, $data)
+ {
+ foreach ($this->getCredentials() as $prop) {
+ if (!isset($data[$prop->property])) {
+ $data[$prop->property] = $prop->default;
+ }
+ if (in_array($prop->type, ['string', 'bool', 'int'])) {
+ settype($data[$prop->property], $prop->type);
+ } else {
+ settype($data[$prop->property], 'string');
+ }
+ }
+ if ($this->setCredentialsInternal($data)) {
+ $this->serverId = $serverId;
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * @return false if there was no error string with error message if there was one
+ */
+ public final function getError()
+ {
+ return $this->error;
+ }
+
+ /**
+ * Query path in array-representation of XML document.
+ * e.g. 'path/syntax/foo/wanteditem'
+ * This works for intermediate nodes (that have more children)
+ * and leaf nodes. The result is always an array on success, or
+ * false if not found.
+ */
+ protected function getAttributes($array, $path)
+ {
+ if (!is_array($path)) {
+ // Convert 'path/syntax/foo/wanteditem' to array for further processing and recursive calls
+ $path = explode('/', $path);
+ }
+ do {
+ // Get next element from array, loop to ignore empty elements (so double slashes in the path are allowed)
+ $element = array_shift($path);
+ } while (empty($element) && !empty($path));
+ if (!isset($array[$element])) {
+ // Current path element does not exist - error
+ return false;
+ }
+ if (empty($path)) {
+ // Path is now empty which means we're at 'wanteditem' from out example above
+ if (!is_array($array[$element]) || !isset($array[$element][0])) {
+ // If it's a leaf node of the array, wrap it in plain array, so the function will
+ // always return an array on success
+ return array($array[$element]);
+ }
+ // 'wanteditem' is not a unique leaf node, return as is
+ // This means it's either a plain array, in case there are multiple 'wanteditem' elements on the same level
+ // or it's an associative array if 'wanteditem' has any sub-nodes
+ return $array[$element];
+ }
+ // Recurse
+ if (!is_array($array[$element])) {
+ // We're in the middle of the requested path, but the current element is already a leaf node with no
+ // children - error
+ return false;
+ }
+ if (isset($array[$element][0])) {
+ // The currently handled element of the path exists multiple times on the current level, so it is
+ // wrapped in a plain array - recurse into each one of them and merge the results
+ $return = [];
+ foreach ($array[$element] as $item) {
+ $test = $this->getAttributes($item, $path);
+ If (gettype($test) == "array") {
+ $return = array_merge($return, $test);
+ }
+
+ }
+ return $return;
+ }
+ // Unique non-leaf node - simple recursion
+ return $this->getAttributes($array[$element], $path);
+ }
+
+ /**
+ * @param string $response xml document to convert
+ * @return bool|array array representation of the xml if possible, false otherwise
+ */
+ protected function toArray($response)
+ {
+ $cleanresponse = preg_replace('/(<\/?)(\w+):([^>]*>)/', '$1$2$3', $response);
+ try {
+ $xml = new SimpleXMLElement($cleanresponse);
+ } catch (Exception $e) {
+ $this->error = 'Could not parse reply as XML, got ' . get_class($e) . ': ' . $e->getMessage();
+ return false;
+ }
+ $array = json_decode(json_encode((array)$xml), true);
+ return $array;
+ }
+
+}
+
+/**
+ * Class BackendProperty describes a property a backend requires to define its functionality
+ */
+class BackendProperty {
+ public $property;
+ public $type;
+ public $default;
+ public function __construct($property, $type, $default = '')
+ {
+ $this->property = $property;
+ $this->type = $type;
+ $this->default = $default;
+ }
+}
diff --git a/modules-available/locationinfo/inc/coursebackend/coursebackend_davinci.inc.php b/modules-available/locationinfo/inc/coursebackend/coursebackend_davinci.inc.php
new file mode 100644
index 00000000..77928b39
--- /dev/null
+++ b/modules-available/locationinfo/inc/coursebackend/coursebackend_davinci.inc.php
@@ -0,0 +1,128 @@
+<?php
+
+class CourseBackend_Davinci extends CourseBackend
+{
+
+ private $location;
+
+ public function setCredentialsInternal($data)
+ {
+ if (empty($data['baseUrl'])) {
+ $this->error = "No url is given";
+ return false;
+ }
+ $location = preg_replace('#/+(davinciis\.dll)?\W*$#i', '', $data['baseUrl']);
+ $this->location = $location . "/DAVINCIIS.dll?";
+ //Davinci doesn't have credentials
+ return true;
+ }
+
+ public function checkConnection()
+ {
+ if (empty($this->location)) {
+ $this->error = "Credentials are not set";
+ } else {
+ $data = $this->fetchRoomRaw('someroomid123');
+ if (strpos($data, 'DAVINCI SERVER') === false) {
+ $this->error = "This doesn't seem to be a DAVINCI server";
+ }
+ }
+ return $this->error === false;
+ }
+
+ public function getCredentials()
+ {
+ return [
+ new BackendProperty('baseUrl', 'string')
+ ];
+ }
+
+ public function getDisplayName()
+ {
+ return 'Davinci';
+ }
+
+ public function getCacheTime()
+ {
+ return 30 * 60;
+ }
+
+ public function getRefreshTime()
+ {
+ return 0;
+ }
+
+ /**
+ * @param $roomId string name of the room
+ * @return array|bool if successful the arrayrepresentation of the timetable
+ */
+ private function fetchRoomRaw($roomId)
+ {
+ $startDate = new DateTime('today 0:00');
+ $endDate = new DateTime('+7 days 0:00');
+ $url = $this->location . "content=xml&type=room&name=" . urlencode($roomId)
+ . "&startdate=" . $startDate->format('d.m.Y') . "&enddate=" . $endDate->format('d.m.Y');
+ $ch = curl_init();
+ $options = array(
+ CURLOPT_RETURNTRANSFER => true,
+ CURLOPT_FOLLOWLOCATION => true,
+ CURLOPT_SSL_VERIFYHOST => false,
+ CURLOPT_SSL_VERIFYPEER => false,
+ CURLOPT_URL => $url,
+ );
+
+ curl_setopt_array($ch, $options);
+ $output = curl_exec($ch);
+ if ($output === false) {
+ $this->error = 'Curl error: ' . curl_error($ch);
+ return false;
+ } else {
+ $this->error = false;
+ ///Operation completed successfully
+ }
+ curl_close($ch);
+ return $output;
+
+ }
+
+ public function fetchSchedulesInternal($requestedRoomIds)
+ {
+ $schedules = [];
+ foreach ($requestedRoomIds as $roomId) {
+ $return = $this->fetchRoomRaw($roomId);
+ if ($return === false) {
+ continue;
+ }
+ $return = $this->toArray($return);
+ if ($return === false) {
+ continue;
+ }
+ $lessons = $this->getAttributes($return, '/Lessons/Lesson');
+ if ($lessons === false) {
+ $this->error = "Cannot find /Lessons/Lesson in XML";
+ continue;
+ }
+ $timetable = [];
+ foreach ($lessons as $lesson) {
+ if (!isset($lesson['Date']) || !isset($lesson['Start']) || !isset($lesson['Finish'])) {
+ $this->error = 'Lesson is missing Date, Start or Finish';
+ continue;
+ }
+ $date = $lesson['Date'];
+ $date = substr($date, 0, 4) . '-' . substr($date, 4, 2) . '-' . substr($date, 6, 2);
+ $start = $lesson['Start'];
+ $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'] : '???';
+ $timetable[] = array(
+ 'title' => $subject,
+ 'start' => $date . " " . $start . ':00',
+ 'end' => $date . " " . $end . ':00'
+ );
+ }
+ $schedules[$roomId] = $timetable;
+ }
+ return $schedules;
+ }
+}
diff --git a/modules-available/locationinfo/inc/coursebackend/coursebackend_dummy.inc.php b/modules-available/locationinfo/inc/coursebackend/coursebackend_dummy.inc.php
new file mode 100644
index 00000000..b8329196
--- /dev/null
+++ b/modules-available/locationinfo/inc/coursebackend/coursebackend_dummy.inc.php
@@ -0,0 +1,113 @@
+<?php
+
+class CourseBackend_Dummy extends CourseBackend
+{
+ private $pw;
+
+ /**
+ * uses json to setCredentials, the json must follow the form given in
+ * getCredentials
+ *
+ * @param array $data with the credentials
+ * @param string $url address of the server
+ * @param int $serverId ID of the server
+ * @returns bool if the credentials were in the correct format
+ */
+ public function setCredentialsInternal($json)
+ {
+ $x = $json;
+ $this->pw = $x['password'];
+
+ if ($this->pw === "mfg") {
+ $this->error = false;
+ return true;
+ } else {
+ $this->error = "USE mfg as password!";
+ return false;
+ }
+ }
+
+ /**
+ * @return boolean true if the connection works, false otherwise
+ */
+ public function checkConnection()
+ {
+ if ($this->pw == "mfg") {
+ $this->error = false;
+ return true;
+ } else {
+ $this->error = "USE mfg as password!";
+ return false;
+ }
+ }
+
+ /**
+ * @returns array with parameter name as key and and an array with type, help text and mask as value
+ */
+ public function getCredentials()
+ {
+ $options = ["opt1", "opt2", "opt3", "opt4", "opt5", "opt6", "opt7", "opt8"];
+ return [
+ new BackendProperty('username', 'string', 'default-user'),
+ new BackendProperty('password', 'password'),
+ new BackendProperty('integer', 'int', 7),
+ new BackendProperty('option', $options),
+ new BackendProperty('CheckTheBox', 'bool'),
+ new BackendProperty('CB2t', 'bool', true)
+ ];
+ }
+
+ /**
+ * @return string return display name of backend
+ */
+ public function getDisplayName()
+ {
+ return 'Dummy with array';
+ }
+
+ /**
+ * @return int desired caching time of results, in seconds. 0 = no caching
+ */
+ public function getCacheTime()
+ {
+ return 0;
+ }
+
+ /**
+ * @return int age after which timetables are no longer refreshed should be
+ * greater then CacheTime
+ */
+ public function getRefreshTime()
+ {
+ return 0;
+ }
+
+ /**
+ * Internal version of fetch, to be overridden by subclasses.
+ *
+ * @param $roomIds 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 shedule array contains an array in this format:
+ * ["start"=>'JJJJ-MM-DD HH:MM:SS',"end"=>'JJJJ-MM-DD HH:MM:SS',"title"=>string]
+ */
+ public function fetchSchedulesInternal($roomId)
+ {
+ $a = array();
+ foreach ($roomId as $id) {
+ $x['id'] = $id;
+ $calendar['title'] = "test exam";
+ $calendar['start'] = "2017-3-08 13:00:00";
+ $calendar['end'] = "2017-3-08 16:00:00";
+ $calarray = array();
+ $calarray[] = $calendar;
+ $x['calendar'] = $calarray;
+ $a[$id] = $calarray;
+ }
+
+
+ return $a;
+ }
+
+}
+
+?>
diff --git a/modules-available/locationinfo/inc/coursebackend/coursebackend_hisinone.inc.php b/modules-available/locationinfo/inc/coursebackend/coursebackend_hisinone.inc.php
new file mode 100644
index 00000000..b01146a8
--- /dev/null
+++ b/modules-available/locationinfo/inc/coursebackend/coursebackend_hisinone.inc.php
@@ -0,0 +1,352 @@
+<?php
+
+class CourseBackend_HisInOne extends CourseBackend
+{
+ private $username = '';
+ private $password = '';
+ private $open = true;
+ private $location;
+ private $verifyHostname = true;
+ private $verifyCert = true;
+
+
+ public function setCredentialsInternal($data)
+ {
+ if (!$data['open']) {
+ // If not using OpenCourseService, require credentials
+ foreach (['username', 'password'] as $field) {
+ if (empty($data[$field])) {
+ $this->error = 'setCredentials: Missing field ' . $field;
+ return false;
+ }
+ }
+ }
+ if (empty($data['baseUrl'])) {
+ $this->error = "No url is given";
+ return false;
+ }
+
+ $this->error = false;
+ $this->username = $data['username'] . "\t" . $data['role'];
+ $this->password = $data['password'];
+ $this->open = $data['open'];
+ $url = preg_replace('#(/+qisserver(/+services\d+(/+OpenCourseService)?)?)?\W*$#i', '', $data['baseUrl']);
+ if ($this->open) {
+ $this->location = $url . "/qisserver/services2/OpenCourseService";
+ } else {
+ $this->location = $url . "/qisserver/services2/CourseService";
+ }
+ $this->verifyHostname = $data['verifyHostname'];
+ $this->verifyCert = $data['verifyCert'];
+
+ return true;
+ }
+
+ public function getCredentials()
+ {
+ return [
+ new BackendProperty('baseUrl', 'string'),
+ new BackendProperty('username', 'string'),
+ new BackendProperty('role', 'string'),
+ new BackendProperty('password', 'password'),
+ new BackendProperty('open', 'bool', true),
+ new BackendProperty('verifyCert', 'bool', true),
+ new BackendProperty('verifyHostname', 'bool', true)
+ ];
+ }
+
+ public function checkConnection()
+ {
+ if (empty($this->location)) {
+ $this->error = "Credentials are not set";
+ } else {
+ $this->findUnit(123456789, true);
+ }
+ return $this->error === false;
+ }
+
+ /**
+ * @param int $roomId his in one room id to get
+ * @param bool $connectionCheckOnly true will only check if no soapError is returned, return value will be empty
+ * @return array|bool if successful an array with the event ids that take place in the room
+ */
+ public function findUnit($roomId, $connectionCheckOnly = false)
+ {
+ $termYear = date('Y');
+ $termType1 = date('n');
+ if ($termType1 > 3 && $termType1 < 10) {
+ $termType = 2;
+ } elseif ($termType1 > 10) {
+ $termType = 1;
+ $termYear = $termYear + 1;
+ } else {
+ $termType = 1;
+ }
+ $doc = new DOMDocument('1.0', 'utf-8');
+ $doc->formatOutput = true;
+ $envelope = $doc->createElementNS('http://schemas.xmlsoap.org/soap/envelope/', 'SOAP-ENV:Envelope');
+ $doc->appendChild($envelope);
+ if ($this->open) {
+ $envelope->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:ns1', 'http://www.his.de/ws/OpenCourseService');
+ } else {
+ $envelope->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:ns1', 'http://www.his.de/ws/CourseService');
+ $header = $this->getHeader($doc);
+ $envelope->appendChild($header);
+ }
+ //Body of the request
+ $body = $doc->createElement('SOAP-ENV:Body');
+ $envelope->appendChild($body);
+ $findUnit = $doc->createElement('ns1:findUnit');
+ $body->appendChild($findUnit);
+ $findUnit->appendChild($doc->createElement('termYear', $termYear));
+ if ($termType1 != 3 && $termType1 != 10) {
+ $findUnit->appendChild($doc->createElement('termTypeValueId', $termType));
+ }
+ $findUnit->appendChild($doc->createElement('ns1:roomId', $roomId));
+
+ $soap_request = $doc->saveXML();
+ $response1 = $this->__doRequest($soap_request, "findUnit");
+ if ($this->error !== false) {
+ return false;
+ }
+ $response2 = $this->toArray($response1);
+ if (!is_array($response2)) {
+ if ($this->error === false) {
+ $this->error = 'Cannot convert XML response to array';
+ }
+ return false;
+ }
+ if (!isset($response2['soapenvBody'])) {
+ $this->error = 'findUnit(' . $roomId . '): Backend reply is missing element soapenvBody';
+ return false;
+ }
+ if (isset($response2['soapenvBody']['soapenvFault'])) {
+ $this->error = $response2['soapenvBody']['soapenvFault']['faultcode'] . " " . $response2['soapenvBody']['soapenvFault']['faultstring'];
+ return false;
+ }
+ // We only need to check if the connection is working (URL ok, credentials ok, ..) so bail out early
+ if ($connectionCheckOnly) {
+ return array();
+ }
+ if ($this->open) {
+ $path = '/soapenvBody/hisfindUnitResponse/hisunits/hisunit/hisid';
+ } else {
+ $path = '/soapenvBody/hisfindUnitResponse/hisunitIds/hisid';
+ }
+ $id = $this->getAttributes($response2, $path);
+ if ($id === false) {
+ $this->error = 'Cannot find ' . $path;
+ }
+ return $id;
+ }
+
+ /**
+ * @param $doc DOMDocument
+ * @return DOMElement
+ */
+ private function getHeader($doc)
+ {
+ $header = $doc->createElement('SOAP-ENV:Header');
+ $security = $doc->createElementNS('http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd', 'ns2:Security');
+ $mustunderstand = $doc->createAttribute('SOAP-ENV:mustUnderstand');
+ $mustunderstand->value = 1;
+ $security->appendChild($mustunderstand);
+ $header->appendChild($security);
+ $token = $doc->createElement('ns2:UsernameToken');
+ $security->appendChild($token);
+ $user = $doc->createElement('ns2:Username', $this->username);
+ $token->appendChild($user);
+ $pass = $doc->createElement('ns2:Password', $this->password);
+ $type = $doc->createAttribute('Type');
+ $type->value = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText';
+ $pass->appendChild($type);
+ $token->appendChild($pass);
+ return $header;
+ }
+
+ /**
+ * @param $request string with xml SOAP request
+ * @param $action string with the name of the SOAP action
+ * @return bool|string if successful the answer xml from the SOAP server
+ */
+ private function __doRequest($request, $action)
+ {
+ $header = array(
+ "Content-type: text/xml;charset=\"utf-8\"",
+ "SOAPAction: \"" . $action . "\"",
+ "Content-length: " . strlen($request),
+ );
+
+ $soap_do = curl_init();
+
+ $options = array(
+ CURLOPT_RETURNTRANSFER => true,
+ CURLOPT_FOLLOWLOCATION => true,
+ CURLOPT_SSL_VERIFYHOST => $this->verifyHostname,
+ CURLOPT_SSL_VERIFYPEER => $this->verifyCert,
+ CURLOPT_URL => $this->location,
+ CURLOPT_POSTFIELDS => $request,
+ CURLOPT_HTTPHEADER => $header,
+ );
+
+ curl_setopt_array($soap_do, $options);
+
+ $output = curl_exec($soap_do);
+
+ if ($output === false) {
+ $this->error = 'Curl error: ' . curl_error($soap_do);
+ } else {
+ $this->error = false;
+ ///Operation completed successfully
+ }
+ curl_close($soap_do);
+ return $output;
+ }
+
+ public function getCacheTime()
+ {
+ return 30 * 60;
+ }
+
+
+ public function getRefreshTime()
+ {
+ return 60 * 60;
+ }
+
+
+ public function getDisplayName()
+ {
+ return "HisInOne";
+ }
+
+ public function fetchSchedulesInternal($requestedRoomIds)
+ {
+ if (empty($requestedRoomIds)) {
+ return array();
+ }
+ $tTables = [];
+ //get all eventIDs in a given room
+ $eventIds = [];
+ foreach ($requestedRoomIds as $roomId) {
+ $roomEventIds = $this->findUnit($roomId);
+ if ($roomEventIds === false) {
+ error_log($this->error);
+ $this->error = false;
+ // TODO: Error gets swallowed
+ continue;
+ }
+ $tTables[$roomId] = [];
+ $eventIds = array_merge($eventIds, $roomEventIds);
+ }
+ $eventIds = array_unique($eventIds);
+ if (empty($eventIds)) {
+ return $tTables;
+ }
+ $eventDetails = [];
+ //get all information on each event
+ foreach ($eventIds as $eventId) {
+ $event = $this->readUnit(intval($eventId));
+ if ($event === false) {
+ error_log($this->error);
+ $this->error = false;
+ // TODO: Error gets swallowed
+ continue;
+ }
+ $eventDetails = array_merge($eventDetails, $event);
+ }
+ $currentWeek = $this->getCurrentWeekDates();
+ foreach ($eventDetails as $event) {
+ foreach (array('/hisdefaulttext',
+ '/hisshorttext',
+ '/hisshortcomment',
+ '/hisplanelements/hisplanelement/hisdefaulttext') as $path) {
+ $name = $this->getAttributes($event, $path);
+ if (!empty($name) && !empty($name[0]))
+ break;
+ $name = false;
+ }
+ if ($name === false) {
+ $name = ['???'];
+ }
+ $unitPlannedDates = $this->getAttributes($event,
+ '/hisplanelements/hisplanelement/hisplannedDates/hisplannedDate/hisindividualDates/hisindividualDate');
+ if ($unitPlannedDates === false) {
+ $this->error = 'Cannot find ./hisplanelements/hisplanelement/hisplannedDates/hisplannedDate/hisindividualDates/hisindividualDate';
+ return false;
+ }
+ foreach ($unitPlannedDates as $plannedDate) {
+ $eventRoomId = $this->getAttributes($plannedDate, '/hisroomId')[0];
+ $eventDate = $this->getAttributes($plannedDate, '/hisexecutiondate')[0];
+ if (in_array($eventRoomId, $requestedRoomIds) && in_array($eventDate, $currentWeek)) {
+ $startTime = $this->getAttributes($plannedDate, '/hisstarttime')[0];
+ $endTime = $this->getAttributes($plannedDate, '/hisendtime')[0];
+ $tTables[$eventRoomId][] = array(
+ 'title' => $name[0],
+ 'start' => $eventDate . " " . $startTime,
+ 'end' => $eventDate . " " . $endTime
+ );
+ }
+ }
+ }
+ return $tTables;
+ }
+
+
+ /**
+ * @param $unit int ID of the subject in HisInOne database
+ * @return bool|array false if there was an error otherwise an array with the information about the subject
+ */
+ public function readUnit($unit)
+ {
+ $doc = new DOMDocument('1.0', 'utf-8');
+ $doc->formatOutput = true;
+ $envelope = $doc->createElementNS('http://schemas.xmlsoap.org/soap/envelope/', 'SOAP-ENV:Envelope');
+ $doc->appendChild($envelope);
+ if ($this->open) {
+ $envelope->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:ns1', 'http://www.his.de/ws/OpenCourseService');
+ } else {
+ $envelope->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:ns1', 'http://www.his.de/ws/CourseService');
+ $header = $this->getHeader($doc);
+ $envelope->appendChild($header);
+ }
+ //body of the request
+ $body = $doc->createElement('SOAP-ENV:Body');
+ $envelope->appendChild($body);
+ $readUnit = $doc->createElement('ns1:readUnit');
+ $body->appendChild($readUnit);
+ $readUnit->appendChild($doc->createElement('ns1:unitId', $unit));
+
+ $soap_request = $doc->saveXML();
+ $response1 = $this->__doRequest($soap_request, "readUnit");
+ if ($response1 === false) {
+ return false;
+ }
+ $response2 = $this->toArray($response1);
+ if ($response2 === false)
+ return false;
+ if (!isset($response2['soapenvBody'])) {
+ $this->error = 'findUnit(' . $unit . '): Backend reply is missing element soapenvBody';
+ return false;
+ }
+ if (isset($response2['soapenvBody']['soapenvFault'])) {
+ $this->error = 'SOAP-Fault' . $response2['soapenvBody']['soapenvFault']['faultcode'] . " " . $response2['soapenvBody']['soapenvFault']['faultstring'];
+ return false;
+ }
+ return $this->getAttributes($response2, '/soapenvBody/hisreadUnitResponse/hisunit');
+ }
+
+ /**
+ * @return array with days of the current week in datetime format
+ */
+ private function getCurrentWeekDates()
+ {
+ $returnValue = array();
+ $startDate = time();
+ for ($i = 0; $i <= 7; $i++) {
+ $returnValue[] = date('Y-m-d', strtotime("+{$i} day 12:00", $startDate));
+ }
+ return $returnValue;
+ }
+
+}
diff --git a/modules-available/locationinfo/inc/locationinfo.inc.php b/modules-available/locationinfo/inc/locationinfo.inc.php
new file mode 100644
index 00000000..7617d143
--- /dev/null
+++ b/modules-available/locationinfo/inc/locationinfo.inc.php
@@ -0,0 +1,63 @@
+<?php
+
+class LocationInfo
+{
+
+ /**
+ * Gets the pc data and returns it's state.
+ *
+ * @param array $pc The pc data from the db. Array('logintime' =>, 'lastseen' =>, 'lastboot' =>)
+ * @return int pc state
+ */
+ public static function getPcState($pc)
+ {
+ /* pcState:
+ * [0] = IDLE (NOT IN USE)
+ * [1] = OCCUPIED (IN USE)
+ * [2] = OFF
+ * [3] = 10 days offline (BROKEN?)
+ */
+ // TODO USE STATE NAME instead of numbers
+
+ $logintime = (int)$pc['logintime'];
+ $lastseen = (int)$pc['lastseen'];
+ $lastboot = (int)$pc['lastboot'];
+ $NOW = time();
+
+ if ($NOW - $lastseen > 14 * 86400) {
+ return "BROKEN";
+ } elseif (($NOW - $lastseen > 610) || $lastboot === 0) {
+ return "OFF";
+ } elseif ($logintime === 0) {
+ return "IDLE";
+ } elseif ($logintime > 0) {
+ return "OCCUPIED";
+ }
+ return -1;
+ }
+
+ /**
+ * Set current error message of given server. Pass null or false to clear.
+ *
+ * @param int $serverId id of server
+ * @param string $message error message to set, null or false clears error.
+ */
+ public static function setServerError($serverId, $message)
+ {
+ if ($message === false || $message === null) {
+ Database::exec("UPDATE `setting_location_info` SET error = NULL
+ WHERE serverid = :id", array('id' => $serverId));
+ } else {
+ if (empty($message)) {
+ $message = '<empty error message>';
+ }
+ $error = json_encode(array(
+ 'timestamp' => time(),
+ 'error' => (string)$message
+ ));
+ Database::exec("UPDATE `setting_location_info` SET error = :error
+ WHERE serverid = :id", array('id' => $serverId, 'error' => $error));
+ }
+ }
+
+}