getLastChangeTs($uuid)); } elseif ($get === "machines") { $locationIds = getLocationsOr404($uuid); $output = array(); appendMachineData($output, $locationIds, false); $output = array_values($output); } elseif ($get === "config") { $output = getConfig($uuid); } elseif ($get === "pcstates") { $locationIds = getLocationsOr404($uuid); $output = getPcStates($locationIds); } elseif ($get === "locationtree") { $locationIds = getLocationsOr404($uuid); $output = getLocationTree($locationIds); } elseif ($get === "calendar") { $locationIds = getLocationsOr404($uuid); $output = getCalendar($locationIds); } if ($output !== false) { Header('Content-Type: application/json; charset=utf-8'); echo json_encode($output); } else { http_response_code(404); echo 'Unknown get option'; } } /** * Return list of locationids associated with given panel. * @param string $paneluuid panel * @return int[] locationIds */ function getLocationsOr404($paneluuid) { $panel = Database::queryFirst('SELECT locationids FROM locationinfo_panel WHERE paneluuid = :paneluuid', compact('paneluuid')); if ($panel !== false) { return array_map('intval', explode(',', $panel['locationids'])); } http_response_code(404); die('Panel not found'); } // ########## ########## /** * Gets the location info of the given locations. * Append to passed array which is expected to * map location ids to properties of that location. * A new key 'machines' will be created in each * entry of $array that will take all the machine data. * * @param array $array location list to populate with machine data * @param bool $withPosition Defines if coords should be included or not. */ function appendMachineData(&$array, $idList = false, $withPosition = false) { if (empty($array) && $idList === false) return; if ($idList === false) { $idList = array_keys($array); } $positionCol = $withPosition ? 'm.position,' : ''; $query = "SELECT m.locationid, m.machineuuid, $positionCol m.logintime, m.lastseen, m.lastboot FROM machine m WHERE m.locationid IN (:idlist)"; $dbquery = Database::simpleQuery($query, array('idlist' => $idList)); // Iterate over matching machines while ($row = $dbquery->fetch(PDO::FETCH_ASSOC)) { settype($row['locationid'], 'int'); if (!isset($array[$row['locationid']])) { $array[$row['locationid']] = array('id' => $row['locationid'], 'machines' => array()); } if (!isset($array[$row['locationid']]['machines'])) { $array[$row['locationid']]['machines'] = array(); } // Compact the pc data in one array. $pc = array('id' => $row['machineuuid']); if ($withPosition && !empty($row['position'])) { $position = json_decode($row['position'], true); if (isset($position['gridCol']) && isset($position['gridRow'])) { $pc['x'] = $position['gridCol']; $pc['y'] = $position['gridRow']; if (!empty($position['overlays']) && is_array($position['overlays'])) { $pc['overlays'] = $position['overlays']; } } } $pc['pcState'] = LocationInfo::getPcState($row); //$pc['pcState'] = ['BROKEN', 'OFF', 'IDLE', 'OCCUPIED'][mt_rand(0,3)]; // XXX // Add the array to the machines list. $array[$row['locationid']]['machines'][] = $pc; } } // ########## ########### /** * Returns all the passed location ids and appends * all their direct and indirect parent location ids. * * @param int[] $idList location ids * @return int[] more location ids */ function getLocationsWithParents($idList) { $locations = Location::getLocationsAssoc(); $allIds = $idList; foreach ($idList as $id) { if (isset($locations[$id]) && isset($locations[$id]['parents'])) { $allIds = array_merge($allIds, $locations[$id]['parents']); } } return array_map('intval', $allIds); } // ########## ########## /** * Gets the Opening time of the given locations. * * @param array $array list of locations, indexed by locationId * @param int[] $idList list of locations */ function appendOpeningTimes(&$array, $idList) { // First, lets get all the parent ids for the given locations // in case we need to get inherited opening times $allIds = getLocationsWithParents($idList); if (empty($allIds)) return; $res = Database::simpleQuery("SELECT locationid, openingtime FROM locationinfo_locationconfig WHERE locationid IN (:lids)", array('lids' => $allIds)); $openingTimes = array(); while ($row = $res->fetch(PDO::FETCH_ASSOC)) { $openingTimes[(int)$row['locationid']] = $row; } // Now we got all the calendars for locations and parents // Iterate over the locations we're actually interested in $locations = Location::getLocationsAssoc(); foreach ($idList as $locationId) { // Start checking at actual location... $currentId = $locationId; while ($currentId !== 0) { if (!empty($openingTimes[$currentId]['openingtime'])) { $cal = json_decode($openingTimes[$currentId]['openingtime'], true); if (is_array($cal)) { $cal = formatOpeningtime($cal); } if (!empty($cal)) { // Got a valid calendar if (!isset($array[$locationId])) { $array[$locationId] = array('id' => $locationId); } $array[$locationId]['openingtime'] = $cal; break; } } // Keep trying with parent $currentId = $locations[$currentId]['parentlocationid']; } } return; } /** * Format the openingtime in the frontend needed format. * One key per week day, wich contains an array of { * 'HourOpen' => hh, 'MinutesOpen' => mm, * '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. */ function formatOpeningtime($openingtime) { $result = array(); foreach ($openingtime as $entry) { $openTime = explode(':', $entry['openingtime']); $closeTime = explode(':', $entry['closingtime']); if (count($openTime) !== 2 || count($closeTime) !== 2) continue; $convertedTime = array( 'HourOpen' => $openTime[0], 'MinutesOpen' => $openTime[1], 'HourClose' => $closeTime[0], 'MinutesClose' => $closeTime[1], ); foreach ($entry['days'] as $day) { if (!isset($result[$day])) { $result[$day] = array(); } $result[$day][] = $convertedTime; } } return $result; } // ########## ########## /** * Gets the config of the location. * * @param int $locationID ID of the location * @return array configuration struct */ function getConfig($paneluuid) { $panel = Database::queryFirst('SELECT panelconfig, paneltype, locationids, lastchange FROM locationinfo_panel WHERE paneluuid = :paneluuid', compact('paneluuid')); if ($panel === false || empty($panel['locationids'])) { http_response_code(404); die('Panel not found'); } $config = LocationInfo::defaultPanelConfig($panel['paneltype']); $locations = Location::getLocationsAssoc(); $overrides = false; if (!empty($panel['panelconfig'])) { $json = json_decode($panel['panelconfig'], true); if (is_array($json)) { if (isset($json['overrides']) && is_array($json['overrides'])) { $overrides = $json['overrides']; } unset($json['overrides']); $config = $json + $config; } } $config['locations'] = array(); $lids = array_map('intval', explode(',', $panel['locationids'])); foreach ($lids as $lid) { $config['locations'][$lid] = array( 'id' => $lid, 'name' => isset($locations[$lid]) ? $locations[$lid]['locationname'] : 'noname00.pas', ); if (isset($overrides[$lid]) && is_array($overrides[$lid])) { $config['locations'][$lid]['config'] = $overrides[$lid]; } } appendMachineData($config['locations'], $lids, true); appendOpeningTimes($config['locations'], $lids); $config['ts'] = (int)$panel['lastchange']; $config['locations'] = array_values($config['locations']); $config['time'] = date('Y-n-j-G-') . (int)date('i') . '-' . (int)(date('s')); return $config; } /** * Get last config modification timestamp for given panel. * This was planned to be smart and check the involved locations, * even going up the location tree if the opening time schedule * is inherited, but this would still be incomplete by design, as * it wouldn't react to the linked room plan being considered * for changes, or added/removed PCs etc. So rather than giving * an incomplete "clever" design for detecting changes, we only * consider direct editing of the panel now. So the advice would * simply be "if you want the panel to reload automatically, hit * the edit button and click save". Might even add a shortcut * reload-button to the list of panels at some point. * * @param string $paneluuid panels uuid * @return int UNIX_TIMESTAMP */ function getLastChangeTs($paneluuid) { $panel = Database::queryFirst('SELECT lastchange FROM locationinfo_panel WHERE paneluuid = :paneluuid', compact('paneluuid')); if ($panel === false) { http_response_code(404); die('Panel not found'); } return (int)$panel['lastchange']; } /** * Gets the pc states of the given locations. * * @param int[] $idList list of the location ids. * @return array aggregated PC states */ function getPcStates($idList) { $pcStates = array(); foreach ($idList as $id) { $pcStates[$id] = array( 'id' => $id, 'idle' => 0, 'occupied' => 0, 'off' => 0, 'broken' => 0, ); } $locationInfoList = array(); appendMachineData($locationInfoList, $idList); foreach ($locationInfoList as $locationInfo) { $id = $locationInfo['id']; foreach ($locationInfo['machines'] as $pc) { $key = strtolower($pc['pcState']); if (isset($pcStates[$id][$key])) { $pcStates[$id][$key]++; } } } return array_values($pcStates); } /** * Gets the location tree of the given locations. * * @param int[] $idList Array list of the locations. * @return array location tree data */ function getLocationTree($idList) { if (in_array(0, $idList)) { return array_values(Location::getTree()); } $locations = Location::getTree(); $ret = findLocations($locations, $idList); return $ret; } function findLocations($locations, $idList) { $ret = array(); foreach ($locations as $location) { if (in_array($location['locationid'], $idList)) { $ret[] = $location; } elseif (!empty($location['children'])) { $ret = array_merge($ret, findLocations($location['children'], $idList)); } } return $ret; } // ########## ########### /** * Gets the calendar of the given ids. * * @param int[] $idList list with the location ids. * @return array Calendar. */ function getCalendar($idList) { if (empty($idList)) return []; // Build SQL query for multiple ids. $query = "SELECT l.locationid, l.serverid, l.serverlocationid, s.servertype, s.credentials FROM `locationinfo_locationconfig` AS l INNER JOIN locationinfo_coursebackend AS s ON (s.serverid = l.serverid) WHERE l.locationid IN (:idlist) ORDER BY s.servertype ASC"; $dbquery = Database::simpleQuery($query, array('idlist' => array_values($idList))); $serverList = array(); while ($dbresult = $dbquery->fetch(PDO::FETCH_ASSOC)) { if (!isset($serverList[$dbresult['serverid']])) { $serverList[$dbresult['serverid']] = array( 'credentials' => json_decode($dbresult['credentials'], true), 'type' => $dbresult['servertype'], 'idlist' => array() ); } $serverList[$dbresult['serverid']]['idlist'][] = $dbresult['locationid']; } $resultArray = array(); foreach ($serverList as $serverid => $server) { $serverInstance = CourseBackend::getInstance($server['type']); if ($serverInstance === false) { EventLog::warning('Cannot fetch schedule for locationid ' . $server['locationid'] . ': Backend type ' . $server['type'] . ' unknown. Disabling location.'); Database::exec("UPDATE locationinfo_locationconfig SET serverid = 0 WHERE locationid = :lid", array('lid' => $server['locationid'])); continue; } $credentialsOk = $serverInstance->setCredentials($serverid, $server['credentials']); if ($credentialsOk) { $calendarFromBackend = $serverInstance->fetchSchedule($server['idlist']); } else { $calendarFromBackend = array(); } LocationInfo::setServerError($serverid, $serverInstance->getError()); if (is_array($calendarFromBackend)) { foreach ($calendarFromBackend as $key => $value) { $resultArray[] = array( 'id' => $key, 'calendar' => $value, ); } } } return $resultArray; } // ########## ##########