locationid = Request::get('locationid', null, 'integer'); if ($this->locationid !== null) { $locs = Location::getLocationsAssoc(); if (isset($locs[$this->locationid])) { $this->location = $locs[$this->locationid]; $this->isLeaf = empty($this->location['children']); } } } protected function doPreprocess() { User::load(); if (!User::isLoggedIn()) { Message::addError('main.no-permission'); Util::redirect('?do=Main'); } $this->action = Request::any('action', 'show', 'string'); $this->loadRequestedLocation(); if ($this->locationid === null) { Message::addError('need-locationid'); Util::redirect('?do=locations'); } if ($this->location === null) { Message::addError('locations.invalid-location-id', $this->locationid); Util::redirect('?do=locations'); } if ($this->action === 'save') { $this->handleSaveRequest(false); Util::redirect("?do=roomplanner&locationid={$this->locationid}&action=show"); } Render::setTitle($this->location['locationname']); } protected function doRender() { if ($this->action === 'show') { /* do nothing */ Dashboard::disable(); if ($this->isLeaf) { $this->showLeafEditor(); } else { $this->showComposedEditor(); } } else { Message::addError('main.invalid-action', $this->action); } } private function showLeafEditor() { $config = Database::queryFirst('SELECT roomplan, managerip, tutoruuid FROM location_roomplan WHERE locationid = :locationid', ['locationid' => $this->locationid]); if ($config === false) { $config = ['managerip' => '', 'tutoruuid' => '']; } $runmode = RunMode::getForMode(Page::getModule(), $this->locationid, true); if (empty($runmode)) { $config['dedicatedmgr'] = false; } else { $runmode = array_pop($runmode); $config['managerip'] = $runmode['clientip']; $config['manageruuid'] = $runmode['machineuuid']; $data = json_decode($runmode['modedata'], true); $config['dedicatedmgr'] = (isset($data['dedicatedmgr']) && $data['dedicatedmgr']); } $furniture = $this->getFurniture($config); $subnetMachines = $this->getPotentialMachines(); $machinesOnPlan = $this->getMachinesOnPlan($config['tutoruuid']); $roomConfig = array_merge($furniture, $machinesOnPlan); $canEdit = User::hasPermission('edit', $this->locationid); $params = [ 'location' => $this->location, 'managerip' => $config['managerip'], 'dediMgrChecked' => $config['dedicatedmgr'] ? 'checked' : '', 'subnetMachines' => json_encode($subnetMachines), 'locationid' => $this->locationid, 'roomConfiguration' => json_encode($roomConfig), 'edit_disabled' => $canEdit ? '' : 'disabled', 'statistics_disabled' => (Module::get('statistics') !== false && User::hasPermission('.statistics.machine.view-details')) ? '' : 'disabled', ]; Render::addTemplate('header', $params); if ($canEdit) { Render::addTemplate('item-selector', $params); } Render::addTemplate('main-roomplan', $params); Render::addTemplate('footer', $params); } private function showComposedEditor() { // Load settings $row = Database::queryFirst("SELECT locationid, roomplan FROM location_roomplan WHERE locationid = :lid", ['lid' => $this->locationid]); $room = new ComposedRoom($row); $params = [ 'location' => $this->location, 'locations' => [], $room->orientation() . '_checked' => 'checked', ]; if (!$room->shouldSkip()) { $params['enabled_checked'] = 'checked'; } $inverseList = array_flip($room->subLocationIds()); $sortList = []; // Load locations $locs = Location::getLocationsAssoc(); foreach ($this->location['directchildren'] as $loc) { if (isset($locs[$loc])) { $data = $locs[$loc]; if (isset($inverseList[$loc])) { $sortList[] = $inverseList[$loc]; } else { $sortList[] = 1000 + $loc; } if ($loc === $room->controlRoom()) { $data['checked'] = 'checked'; } $params['locations'][] = $data; } } array_multisort($sortList, SORT_ASC | SORT_NUMERIC, $params['locations']); Render::addTemplate('edit-composed-room', $params); } protected function doAjax() { $this->action = Request::any('action', false, 'string'); if ($this->action === 'getmachines') { // Load suggestions when typing in the search box of the "add machine" pop-up User::load(); $locations = User::getAllowedLocations('edit'); if (empty($locations)) { die('{"machines":[]}'); } $roomLocationId = Request::any('locationid', 0, 'int'); $query = Request::get('query', false, 'string'); $aquery = preg_replace('/[^\x01-\x7f]+/', '%', $query); if (strlen(str_replace('%', '', $aquery)) < 2) { $aquery = $query; } $condition = 'locationid IN (:locations)'; if (in_array(0, $locations)) { $condition .= ' OR locationid IS NULL'; } $result = Database::simpleQuery("SELECT machineuuid, macaddr, clientip, hostname, fixedlocationid, subnetlocationid FROM machine WHERE ($condition) AND machineuuid LIKE :aquery OR macaddr LIKE :aquery OR clientip LIKE :aquery OR hostname LIKE :query LIMIT 500", ['query' => "%$query%", 'aquery' => "%$aquery%", 'locations' => $locations]); $returnObject = ['machines' => []]; foreach ($result as $row) { if (!Location::isFixedLocationValid($roomLocationId, $row['subnetlocationid'])) continue; if (empty($row['hostname'])) { $row['hostname'] = $row['clientip']; } $returnObject['machines'][] = $row; if (count($returnObject['machines']) > 100) break; } echo json_encode($returnObject); } elseif ($this->action === 'save') { // Save roomplan - give feedback if it failed so the window can stay open $this->loadRequestedLocation(); if ($this->locationid === null) { die('Missing locationid in save data'); } if ($this->location === null) { die('Location with id ' . $this->locationid . ' does not exist.'); } $this->handleSaveRequest(true); die('SUCCESS'); } else { echo 'Invalid AJAX action'; } } private function handleSaveRequest($isAjax) { User::assertPermission('edit', $this->locationid); $leaf = (bool)Request::post('isleaf', 1, 'int'); if ($leaf !== $this->isLeaf) { if ($isAjax) { die('Leaf mode mismatch. Did you restructure locations while editing this room?'); } Message::addError('leaf-mode-mismatch'); Util::redirect("?do=roomplanner&locationid={$this->locationid}&action=show"); } if ($this->isLeaf) { $this->saveLeafRoom($isAjax); } else { $this->saveComposedRoom($isAjax); } } private function saveLeafRoom($isAjax) { $machinesOnPlan = $this->getMachinesOnPlan('invalid'); $config = Request::post('serializedRoom', null, 'string'); $config = json_decode($config, true); if (!is_array($config) || !isset($config['furniture']) || !isset($config['computers'])) { if ($isAjax) { die('JSON data incomplete'); } Message::addError('json-data-invalid'); Util::redirect("?do=roomplanner&locationid={$this->locationid}&action=show"); } $tutorUuid = Request::post('tutoruuid', '', 'string'); if (empty($tutorUuid)) { $tutorUuid = null; } else { $ret = Database::queryFirst('SELECT machineuuid FROM machine WHERE machineuuid = :uuid', ['uuid' => $tutorUuid]); if ($ret === false) { if ($isAjax) { die('Invalid tutor UUID'); } Message::addError('invalid-tutor-uuid'); Util::redirect("?do=roomplanner&locationid={$this->locationid}&action=show"); } } $this->saveRoomConfig($config['furniture'], $tutorUuid); $this->saveComputerConfig($config['computers'], $machinesOnPlan); } private function saveComposedRoom($isAjax) { $room = new ComposedRoom(true); $res = Database::exec('INSERT INTO location_roomplan (locationid, roomplan) VALUES (:lid, :plan) ON DUPLICATE KEY UPDATE roomplan = VALUES(roomplan)', ['lid' => $this->locationid, 'plan' => $room->serialize()]); if ($res === false) { if ($isAjax) { die('Error writing config to DB'); } Message::addError('db-error'); Util::redirect("?do=roomplanner&locationid={$this->locationid}&action=show"); } } private function sanitizeNumber(&$number, $lower, $upper) { if (!is_numeric($number) || $number < $lower) { $number = $lower; } elseif ($number > $upper) { $number = $upper; } } /** * @param array $computers Deserialized json from browser with all the computers * @param array $oldComputers Deserialized old roomplan from database, used to find removed computers */ protected function saveComputerConfig(array $computers, array $oldComputers) { $oldUuids = []; /* collect all uuids from the old roomplan */ foreach ($oldComputers['computers'] as $c) { $oldUuids[] = $c['muuid']; } $newUuids = []; foreach ($computers as $computer) { $newUuids[] = $computer['muuid']; // Fix/sanitize properties // TODO: The list of items, computers, etc. in general is copied and pasted in multiple places. We need a central definition with generators for the various formats we need it in if (!isset($computer['itemlook']) || !in_array($computer['itemlook'], ['pc-north', 'pc-south', 'pc-west', 'pc-east', 'copier', 'telephone'])) { $computer['itemlook'] = 'pc-north'; } if (!isset($computer['gridRow'])) { $computer['gridRow'] = 0; } else { Util::clamp($computer['gridRow'], 0, 32 * 4); } if (!isset($computer['gridCol'])) { $computer['gridCol'] = 0; } else { Util::clamp($computer['gridCol'], 0, 32 * 4); } $position = json_encode(['gridRow' => $computer['gridRow'], 'gridCol' => $computer['gridCol'], 'itemlook' => $computer['itemlook']]); Database::exec('UPDATE machine SET position = :position, fixedlocationid = :locationid WHERE machineuuid = :muuid', ['locationid' => $this->locationid, 'muuid' => $computer['muuid'], 'position' => $position]); } // Get all computers that were removed from the roomplan and reset their data in DB $toDelete = array_diff($oldUuids, $newUuids); foreach ($toDelete as $d) { Database::exec("UPDATE machine SET position = '', fixedlocationid = NULL WHERE machineuuid = :uuid", ['uuid' => $d]); } } protected function saveRoomConfig(?array $furniture, ?string $tutorUuid) { $obj = json_encode(['furniture' => $furniture]); $managerIp = Request::post('managerip', '', 'string'); Database::exec('INSERT INTO location_roomplan (locationid, roomplan, managerip, tutoruuid)' . ' VALUES (:locationid, :roomplan, :managerip, :tutoruuid)' . ' ON DUPLICATE KEY UPDATE ' . ' roomplan=VALUES(roomplan), managerip=VALUES(managerip), tutoruuid=VALUES(tutoruuid)', [ 'locationid' => $this->locationid, 'roomplan' => $obj, 'managerip' => $managerIp, 'tutoruuid' => $tutorUuid, ]); // See if the client is known, set run-mode RunMode::deleteMode(Page::getModule(), (string)$this->locationid); if (!empty($managerIp)) { $pc = Statistics::getMachinesByIp($managerIp, Machine::NO_DATA, 'lastseen DESC'); if (!empty($pc)) { $dedicated = (Request::post('dedimgr') === 'on'); $pc = array_shift($pc); RunMode::setRunMode($pc->machineuuid, Page::getModule()->getIdentifier(), $this->locationid, json_encode([ 'dedicatedmgr' => $dedicated ]), !$dedicated); } } } protected function getFurniture(array $config): array { if (empty($config['roomplan'])) return []; $config = json_decode($config['roomplan'], true); if (!is_array($config)) return []; return $config; } /** * @return array{computers: array} */ protected function getMachinesOnPlan(?string $tutorUuid): array { $result = Database::simpleQuery('SELECT machineuuid, macaddr, clientip, hostname, position FROM machine WHERE fixedlocationid = :locationid', ['locationid' => $this->locationid]); $machines = []; foreach ($result as $row) { $machine = []; $pos = json_decode($row['position'], true); if ($pos === false || !isset($pos['gridRow']) || !isset($pos['gridCol'])) { // Missing/incomplete position information - reset Database::exec("UPDATE machine SET fixedlocationid = NULL, position = '' WHERE machineuuid = :uuid", array('uuid' => $row['machineuuid'])); continue; } $machine['muuid'] = $row['machineuuid']; $machine['ip'] = $row['clientip']; $machine['mac_address'] = $row['macaddr']; $machine['hostname'] = $row['hostname']; $machine['gridRow'] = (int)$pos['gridRow']; $machine['gridCol'] = (int)$pos['gridCol']; $machine['itemlook'] = $pos['itemlook']; $machine['data-width'] = 100; $machine['data-height'] = 100; if ($row['machineuuid'] === $tutorUuid) { $machine['istutor'] = 'true'; } $machines[] = $machine; } return ['computers' => $machines]; } protected function getPotentialMachines(): array { $result = Database::simpleQuery('SELECT m.machineuuid, m.macaddr, m.clientip, m.hostname, l.locationname AS otherroom, m.fixedlocationid FROM machine m LEFT JOIN location l ON (m.fixedlocationid = l.locationid AND m.subnetlocationid <> m.fixedlocationid) WHERE subnetlocationid = :locationid', ['locationid' => $this->locationid]); $machines = []; foreach ($result as $row) { if (empty($row['hostname'])) { $row['hostname'] = $row['clientip']; } $machines[] = $row; } return $machines; } }