summaryrefslogblamecommitdiffstats
path: root/modules-available/roomplanner/page.inc.php
blob: 94bc3f78a4abb417dd325cb684f6faae2a349679 (plain) (tree)
1
2
3
4
5
6
7
8
9



                                   
 
           
                                                                                     
           
                                   
 


                                                       
                                 
 


                                        
                                
 




                                                                                                             

                                                

                                                                                




                                                                                   

                 
 


                                         
 
                                          


                                                                
 
                                                                         
                                               
                                                 

                                                             
                 
                                               
                                                                                              

                                                        
 



                                                                                                     
                                                                  
         
 


                                               
                                        
                                             

                                                        
                                
                                                            
                         

                                                                                
                 
         
 

                                         


                                                                                              
                                        
                                                                         
                 
                                                                                           


                                                        





                                                                                                          

                                                                
                                                                                 



                                                                          

                                                                                     



                                                                         


                                                                                                                                











                                                                      



                                                                        



                                                      
                                                                       
                  
                                           

                                                               
                                                                   


                                                      
                                                                     






                                                                         
                                                                    






                                                                                          
         
 

                                   
                                                                        
 
                                                      
                                                                                                     
                                     




                                                                       
                                                                               
                                                                        
                                                                               







                                                                        
 
                                                                                                                                           




                                                                               
                                                                                                                          


                                                           
                                                   

                                                                                                               


                                                                            
                                                                   

                                                                           

                                                        
                                                     
                                                                                                 
                                                       
                                                         

                                                                       
                                                       

                                                                                                  








                                                       
                                                                  



                                                                                                                  
                         

                                                                                                     









                                                         
                                                                      




                                                                                                         
                         

                                                                                                     
                 







                                                                                                                                         
                                 

                                                                                                             


                                                                        
                                                                                 

         

                                                  
                                               


                                                                                                          
                                     

                                                                  
                         

                                                                                                     


                 








                                                                 



                                                                                                             
                                                                                    


                               
                                                              







                                                            







                                                                                                                                                                                                          
                                                                             



                                                           
                                                                             

                         



                                                                                   
                                                                                                                                           
                                                                                                                             

                 
                                                                                                   


                                                             
                                                                                                                                             


                 
                                                                                

                                                                


                                                                                                           
                                                     
                                                                                                                   

                                                          
                                                  
                                                  
                   
                                                           
                                                                                  
                                         








                                                                                                                                          

         
                                                             
         


                                                                 
                                       
                                  


                               


                                          
                                                                       
         


                                                                                                          
                                                             
                               
                                           

                                                                   





                                                                                                                                    









                                                                   


                                                                 




                                                  
                                                        
         
                                                                                                                                                        


                                                                                                                              


                               
                                           


                                                                    




                                           
 
<?php

class Page_Roomplanner extends Page
{

	/**
	 * @var ?int locationid of location we're editing, or null if unknown/not set
	 */
	private $locationid = null;

	/**
	 * @var array location data from location table
	 */
	private $location = null;

	/**
	 * @var string action to perform
	 */
	private $action = false;

	/**
	 * @var bool is this a leaf node, with a real room plan, or something in between with a composed plan
	 */
	private $isLeaf;

	private function loadRequestedLocation()
	{
		$this->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;
	}
}