summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--modules-available/locations/inc/location.inc.php4
-rw-r--r--modules-available/roomplanner/api.inc.php17
-rw-r--r--modules-available/roomplanner/inc/composedroom.inc.php175
-rw-r--r--modules-available/roomplanner/inc/pvsgenerator.inc.php206
-rw-r--r--modules-available/roomplanner/inc/room.inc.php176
-rw-r--r--modules-available/roomplanner/inc/simpleroom.inc.php148
-rw-r--r--modules-available/roomplanner/page.inc.php20
-rw-r--r--modules-available/roomplanner/templates/edit-composed-room.html2
-rw-r--r--modules-available/roomplanner/templates/svg-plan.html6
9 files changed, 540 insertions, 214 deletions
diff --git a/modules-available/locations/inc/location.inc.php b/modules-available/locations/inc/location.inc.php
index 8db0c5f3..bd4be245 100644
--- a/modules-available/locations/inc/location.inc.php
+++ b/modules-available/locations/inc/location.inc.php
@@ -87,11 +87,13 @@ class Location
}
$output = array();
foreach ($tree as $node) {
+ $cc = empty($node['children']) ? array() : array_map(function ($item) { return (int)$item['locationid']; }, $node['children']);
$output[(int)$node['locationid']] = array(
'locationid' => (int)$node['locationid'],
'parentlocationid' => (int)$node['parentlocationid'],
'parents' => $parents,
- 'children' => empty($node['children']) ? array() : array_map(function ($item) { return (int)$item['locationid']; }, $node['children']),
+ 'children' => $cc,
+ 'directchildren' => $cc,
'locationname' => $node['locationname'],
'depth' => $depth,
'isleaf' => true,
diff --git a/modules-available/roomplanner/api.inc.php b/modules-available/roomplanner/api.inc.php
index f964bea1..8ddb945c 100644
--- a/modules-available/roomplanner/api.inc.php
+++ b/modules-available/roomplanner/api.inc.php
@@ -1,16 +1,29 @@
<?php
+// SVG
if (Request::any('show') === 'svg') {
$ret = PvsGenerator::generateSvg(Request::any('locationid', false, 'int'),
Request::any('machineuuid', false, 'string'),
Request::any('rotate', 0, 'int'),
Request::any('scale', 1, 'float'));
if ($ret === false) {
- Header('HTTP/1.1 404 Not Found');
- exit;
+ if (Request::any('fallback', 0, 'int') === 0) {
+ Header('HTTP/1.1 404 Not Found');
+ exit;
+ }
+ $ret = <<<EOF
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 64 64" height="64" width="64">
+ <g>
+ <path d="M 0,0 64,64 Z" style="stroke:#ff0000;stroke-width:5" />
+ <path d="M 0,64 64,0 Z" style="stroke:#ff0000;stroke-width:5" />
+ </g>
+</svg>
+EOF;
}
Header('Content-Type: image/svg+xml');
die($ret);
}
+// PVS.ini
die(PvsGenerator::generate());
diff --git a/modules-available/roomplanner/inc/composedroom.inc.php b/modules-available/roomplanner/inc/composedroom.inc.php
index 11e8455e..cdd50984 100644
--- a/modules-available/roomplanner/inc/composedroom.inc.php
+++ b/modules-available/roomplanner/inc/composedroom.inc.php
@@ -1,65 +1,80 @@
<?php
-class ComposedRoom
+class ComposedRoom extends Room
{
/**
* @var string How to compose contained rooms. Value is either horizontal or vertical.
*/
- public $orientation = 'horizontal';
+ private $orientation = 'horizontal';
/**
* @var int[] Order in which contained rooms are composed. List of locationid.
*/
- public $list;
+ private $list;
/**
* @var bool Whether composed room is active, ie. visible in PVS.
*/
- public $enabled;
+ private $enabled;
/**
* @var int locationid of contained room that is the controlling room;
*/
- public $controlRoom;
+ private $controlRoom;
- public function __construct($data)
+ /**
+ * ComposedRoom constructor.
+ *
+ * @param array|true $row DB row to instantiate from, or true to read from $_POST
+ */
+ public function __construct($row, $sanitize = true)
{
- if ($data instanceof ComposedRoom) {
- foreach ($data as $k => $v) {
- $this->{$k} = $v;
- }
+ if ($row === true) {
+ $this->orientation = Request::post('orientation', 'horizontal', 'string');
+ $this->enabled = (bool)Request::post('enabled', 0, 'int');
+ $this->controlRoom = Request::post('controlroom', 0, 'int');
+ $vals = Request::post('sort', [], 'array');
+ asort($vals, SORT_ASC | SORT_NUMERIC);
+ $this->list = array_keys($vals);
} else {
- if (is_array($data) && isset($data['roomplan'])) {
+ parent::__construct($row);
+ if (is_array($row) && isset($row['roomplan'])) {
// From DB
- $data = json_decode($data['roomplan'], true);
- } elseif (is_string($data)) {
- // Just JSON
- $data = json_decode($data, true);
+ $row = json_decode($row['roomplan'], true);
}
- if (is_array($data)) {
+ if (is_array($row)) {
foreach ($this as $k => $v) {
- if (isset($data[$k])) {
- $this->{$k} = $data[$k];
+ if (isset($row[$k])) {
+ $this->{$k} = $row[$k];
}
}
}
}
- $this->sanitize();
+ if ($sanitize) {
+ $this->sanitize();
+ }
}
/**
* Make sure all member vars have the proper type
*/
- private function sanitize()
+ protected function sanitize()
{
$this->orientation = ($this->orientation === 'horizontal' ? 'horizontal' : 'vertical');
settype($this->enabled, 'bool');
settype($this->list, 'array');
settype($this->controlRoom, 'int');
- foreach ($this->list as &$v) {
+ self::init();
+ //error_log('List: ' . print_r($this->list, true));
+ //error_log('Rooms: ' . print_r(self::$rooms, true));
+ $old = $this->list;
+ $this->list = [];
+ foreach ($old as $v) {
settype($v, 'int');
+ if (isset(self::$rooms[$v])) {
+ $this->list[] = $v;
+ }
}
- $this->list = array_values($this->list);
if (!empty($this->list) && !in_array($this->controlRoom, $this->list)) {
$this->controlRoom = $this->list[0];
}
@@ -71,7 +86,121 @@ class ComposedRoom
public function serialize()
{
$this->sanitize();
- return json_encode($this);
+ $out = [];
+ foreach ($this as $k => $v) {
+ $out[$k] = $v;
+ }
+ return json_encode($out);
+ }
+
+ public function orientation()
+ {
+ return $this->orientation;
+ }
+
+ public function subLocationIds()
+ {
+ return $this->list;
}
+ public function controlRoom()
+ {
+ return $this->controlRoom;
+ }
+
+ public function machineCount()
+ {
+ $sum = 0;
+ foreach ($this->list as $lid) {
+ $sum += self::$rooms[$lid]->machineCount();
+ }
+ return $sum;
+ }
+
+ public function getSize(&$width, &$height)
+ {
+ $horz = ($this->orientation == 'horizontal');
+ $width = $height = 0;
+ foreach ($this->list as $locId) {
+ self::$rooms[$locId]->getSize($w, $h);
+ $width = $horz ? $width + $w : max($width, $w);
+ $height = !$horz ? $height + $h : max($height, $h);
+ }
+ }
+
+ public function getIniClientSection(&$i, $offX = 0, $offY = 0)
+ {
+ if (!$this->enabled)
+ return false;
+ if ($this->orientation == 'horizontal') {
+ $x = 1;
+ $y = 0;
+ } else {
+ $x = 0;
+ $y = 1;
+ }
+ $out = '';
+ foreach ($this->list as $locId) {
+ $ret = self::$rooms[$locId]->getIniClientSection($i, $offX, $offY);
+ if ($ret !== false) {
+ $out .= $ret;
+ self::$rooms[$locId]->getSize($w, $h);
+ $offX += $w * $x;
+ $offY += $h * $y;
+ }
+ }
+ if (empty($out))
+ return false;
+ return $out;
+ }
+
+ public function getShiftedArray($offX = 0, $offY = 0)
+ {
+ if (!$this->enabled)
+ return false;
+ if ($this->orientation == 'horizontal') {
+ $x = 1;
+ $y = 0;
+ } else {
+ $x = 0;
+ $y = 1;
+ }
+ $ret = [];
+ foreach ($this->list as $locId) {
+ $new = self::$rooms[$locId]->getShiftedArray($offX, $offY);
+ if ($new !== false) {
+ $ret = array_merge($ret, $new);
+ self::$rooms[$locId]->getSize($w, $h);
+ $offX += $w * $x;
+ $offY += $h * $y;
+ }
+ }
+ if (empty($ret))
+ return false;
+ return $ret;
+ }
+
+ public function getManagerIp()
+ {
+ if (isset(self::$rooms[$this->controlRoom]))
+ return self::$rooms[$this->controlRoom]->getManagerIp();
+ return false;
+ }
+
+ public function getTutorIp()
+ {
+ if (isset(self::$rooms[$this->controlRoom]))
+ return self::$rooms[$this->controlRoom]->getTutorIp();
+ return false;
+ }
+
+ public function isLeaf()
+ {
+ return false;
+ }
+
+ public function shouldSkip()
+ {
+ return !$this->enabled;
+ }
}
diff --git a/modules-available/roomplanner/inc/pvsgenerator.inc.php b/modules-available/roomplanner/inc/pvsgenerator.inc.php
index cfb38fd2..3646ae6a 100644
--- a/modules-available/roomplanner/inc/pvsgenerator.inc.php
+++ b/modules-available/roomplanner/inc/pvsgenerator.inc.php
@@ -1,77 +1,31 @@
<?php
-define('X', 0);
-define('Y', 1);
-
class PvsGenerator
{
public static function generate()
{
- /* get all rooms */
- $rooms = array();
- // Use left joins everywhere so we still have the complete list of locations below
- // for figuring out which locations are leafs and which aren't
- $ret = Database::simpleQuery(
- 'SELECT l.locationid, l.parentlocationid, l.locationname, lr.locationid AS notnull, lr.managerip, lr.tutoruuid, m.clientip as tutorip '
- . 'FROM location l '
- . 'LEFT JOIN location_roomplan lr ON (l.locationid = lr.locationid)'
- . 'LEFT JOIN machine m ON (lr.tutoruuid = m.machineuuid)');
- while ($row = $ret->fetch(PDO::FETCH_ASSOC)) {
- $row['locationname'] = str_replace(',', ';', $row['locationname']); // comma probably not the best sep here
- settype($row['locationid'], 'int');
- settype($row['parentlocationid'], 'int');
- $rooms[$row['locationid']] = $row;
- }
- // Mark all non-leafs as skip
- foreach ($rooms as &$room) {
- if ($room['parentlocationid'] > 0 && isset($rooms[$room['parentlocationid']])) {
- $rooms[$room['parentlocationid']]['skip'] = true; // Don't just unset, might be wrong order
- }
- }
- // Now un-mark all where there's at least one child without valid room plan
- foreach ($rooms as &$room) {
- if (!isset($room['skip']) && (is_null($room['notnull']) || empty($room['managerip']))) {
- $room['skip'] = true;
- $r2 =& $room;
- while ($r2['parentlocationid'] > 0) {
- $r2 =& $rooms[$r2['parentlocationid']];
- if (!(is_null($room['notnull']) || empty($room['managerip']))) {
- unset($r2['skip']);
- break;
- }
- }
- }
- }
- unset($room, $r2); // refd!
-
/* collect names and build room blocks - filter empty rooms while at it */
$roomNames = array();
$roomBlocks = '';
+ $rooms = Room::getAll();
foreach ($rooms as $room) {
- if (is_null($room['notnull']) || isset($room['skip'])) // Not leaf
+ if ($room->shouldSkip())
continue;
- if (Module::isAvailable('runmode')) {
- $pc = RunMode::getForMode('roomplanner', $room['locationid'], true);
- if (!empty($pc)) {
- $pc = array_pop($pc);
- $room['managerip'] = $pc['clientip'];
- }
- }
- if (empty($room['managerip'])) // rooms without managerips don't make sense
+ if ($room->getManagerIp() === false) // No .ini entry for rooms without manager (do we want this?)
continue;
$roomBlock = PvsGenerator::generateRoomBlock($room);
if ($roomBlock === false)
continue; // Room nonexistent or empty
- $roomNames[] = md5($room['locationname']);
- $roomBlocks .= $roomBlock;
+ $section = substr(md5($room->locationId() . '-' . $room->locationName()), 0, 10);
+ $roomNames[] = $section;
+ $roomBlocks .= "[$section]\n" . $roomBlock;
}
/* output room plus [General]-block */
return "[General]\n"
. 'rooms=' . implode(', ', $roomNames) . "\n"
. "allowClientQuit=False\n" // TODO: configurable
- . "showLockDesktopButton=True\n" // TODO: Make this configurable (or not)
. "\n\n"
. $roomBlocks;
}
@@ -79,69 +33,36 @@ class PvsGenerator
/**
* Generate .ini section for specific room.
*
- * @param $room array room/location data as fetched from db
- * @return string|bool .ini section for room, or false if room is empty
+ * @param Room $room room/location data as fetched from db
+ * @return string|false .ini section for room, or false if room is empty
*/
private static function generateRoomBlock($room)
{
- $out = '[' . md5($room['locationname']) . "]\n";
-
-
- /* find all clients in that room */
- $machines = PvsGenerator::getMachines($room['locationid']);
- if (empty($machines))
+ $room->getSize($sizeX, $sizeY);
+ if ($sizeX === 0 || $sizeY === 0)
+ return false;
+ $count = 0;
+ $section = $room->getIniClientSection($count);
+ if ($section === false)
return false;
- $out .= "name=" . $room['locationname'] . "\n";
+ $cs = SimpleRoom::CLIENT_SIZE;
+ $out = "name=" . $room->locationName() . "\n";
/* manager */
- $mgr = $room['managerip'];
- $tutor = $room['tutorip'];
- if ($mgr) {
- $out .= 'mgrIP=' . $mgr . "\n";
- }
+ $out .= 'mgrIP=' . $room->getManagerIp() . "\n";
/* tutor */
- if ($tutor) {
- $out .= 'tutorIP=' . $tutor . "\n";
+ if ($room->getTutorIp() !== false) {
+ $out .= 'tutorIP=' . $room->getTutorIp() . "\n";
}
- /* grid */
- $out .= PvsGenerator::generateGrid($machines);
-
- return $out . "\n";
- }
-
- /**
- * Generate grid size information and client position data for given clients.
- *
- * @param $machines array list of clients
- * @return string grid and position data as required for a room's .ini section
- */
- private static function generateGrid($machines)
- {
- $out = "";
-
- /* find bounding box */
- PvsGenerator::boundingBox($machines, $minX, $minY, $maxX, $maxY);
- $clientSizeX = 4; /* TODO: optimize */
- $clientSizeY = 4; /* TODO: optimize */
- $sizeX = max($maxX - $minX + $clientSizeX, 1); /* never negative */
- $sizeY = max($maxY - $minY + $clientSizeY, 1); /* and != 0 to avoid divide-by-zero in pvsmgr */
-
- /* output basic settings for this room */
+ /* basic settings for this room */
$out .= "gridSize=@Size($sizeX $sizeY)\n";
- $out .= "clientSize=@Size($clientSizeX $clientSizeY)\n";
- $out .= "client\\size=" . count($machines) . "\n";
-
- /* output individual client positions, shift coordinates to origin */
- $i = 1;
- foreach ($machines as $pos) {
- $out .= "client\\$i\\ip={$pos['clientip']}\n";
- $out .= "client\\$i\\pos=@Point(" . ($pos['gridCol'] - $minX) . ' ' . ($pos['gridRow'] - $minY) . ")\n";
- $i++;
- }
+ $out .= "clientSize=@Size($cs $cs)\n";
+ $out .= "client\\size=$count\n";
- return $out;
+ /* output with grid */
+ return $out . $section . "\n";
}
/**
@@ -154,6 +75,7 @@ class PvsGenerator
* @param int|false $locationId
* @param string|false $highlightUuid
* @param int $rotate rotate plan (0-3 for N E S W up, -1 for "auto" if highlightUuid is given)
+ * @param float $scale scaling factor for output
* @return string SVG
*/
public static function generateSvg($locationId = false, $highlightUuid = false, $rotate = 0, $scale = 1)
@@ -167,10 +89,15 @@ class PvsGenerator
return false;
$locationId = $locationId['fixedlocationid'];
}
- $machines = self::getMachines($locationId);
- if (empty($machines))
+ // Load room
+ $room = Room::get($locationId);
+ if ($room === false)
return false;
+ $room->getSize($sizeX, $sizeY);
+ if ($sizeX === 0 || $sizeY === 0)
+ return false; // Empty
+ $machines = $room->getShiftedArray();
$ORIENTATION = ['north' => 2, 'east' => 3, 'south' => 0, 'west' => 1];
if (is_string($highlightUuid)) {
$highlightUuid = strtoupper($highlightUuid);
@@ -186,22 +113,13 @@ class PvsGenerator
}
}
$rotate %= 4;
- // Highlight given machine, rotate it's "keyboard"
+ // Highlight given machine, rotate its "keyboard"
foreach ($machines as &$machine) {
if ($machine['machineuuid'] === $highlightUuid) {
$machine['class'] = 'hl';
}
$machine['rotation'] = $ORIENTATION[$machine['rotation']] * 90;
}
- PvsGenerator::boundingBox($machines, $minX, $minY, $maxX, $maxY);
- $clientSizeX = 4; /* this is optimal */
- $clientSizeY = 4;
- $minX--;
- $minY--;
- $maxX++;
- $maxY++;
- $sizeX = max($maxX - $minX + $clientSizeX, 1); /* never negative */
- $sizeY = max($maxY - $minY + $clientSizeY, 1); /* and != 0 to avoid divide-by-zero in pvsmgr */
if ($rotate === 0) {
$centerY = $centerX = 0;
} elseif ($rotate === 1) {
@@ -221,11 +139,8 @@ class PvsGenerator
'centerX' => $centerX,
'centerY' => $centerY,
'rotate' => $rotate * 90,
- 'shiftX' => -$minX,
- 'shiftY' => -$minY,
'machines' => $machines,
- 'line' => ['x1' => $minX, 'y1' => $maxY + $clientSizeY,
- 'x2' => $maxX + $clientSizeX, 'y2' => $maxY + $clientSizeY],
+ 'line' => ['x' => $sizeX, 'y' => $sizeY],
], 'roomplanner'); // FIXME: Needs module param if called from api.inc.php
}
@@ -236,58 +151,6 @@ class PvsGenerator
$b = $tmp;
}
- /**
- * Get all clients for given room with IP and position.
- *
- * @param $roomid int locationid of room
- * @return array
- */
- private static function getMachines($roomid)
- {
- $ret = Database::simpleQuery(
- 'SELECT machineuuid, clientip, position FROM machine WHERE fixedlocationid = :locationid',
- ['locationid' => $roomid]);
-
- $machines = array();
-
- while ($row = $ret->fetch(PDO::FETCH_ASSOC)) {
- $position = json_decode($row['position'], true);
-
- if ($position === false || !isset($position['gridRow']) || !isset($position['gridCol']))
- continue; // TODO: Remove entry/set to NULL?
-
- $rotation = 'north';
- if (preg_match('/(north|east|south|west)/', $position['itemlook'], $out)) {
- $rotation = $out[1];
- }
- $machines[] = array(
- 'machineuuid' => $row['machineuuid'],
- 'clientip' => $row['clientip'],
- 'gridRow' => $position['gridRow'],
- 'gridCol' => $position['gridCol'],
- 'rotation' => $rotation,
- );
- }
-
- return $machines;
-
- }
-
- private static function boundingBox($machines, &$minX, &$minY, &$maxX, &$maxY)
- {
- $minX = PHP_INT_MAX; /* PHP_INT_MIN is only available since PHP 7 */
- $maxX = ~PHP_INT_MAX;
- $minY = PHP_INT_MAX;
- $maxY = ~PHP_INT_MAX;
-
- foreach ($machines as $pos) {
- $minX = min($minX, $pos['gridCol']);
- $maxX = max($maxX, $pos['gridCol']);
- $minY = min($minY, $pos['gridRow']);
- $maxY = max($maxY, $pos['gridRow']);
- }
- }
-
public static function runmodeConfigHook($machineUuid, $locationId, $data)
{
if (!empty($data)) {
@@ -308,6 +171,7 @@ class PvsGenerator
/**
* Get display name for manager of given locationId.
+ * Hook for "runmode" module to resolve mode name.
* @param $locationId
* @return bool|string
*/
diff --git a/modules-available/roomplanner/inc/room.inc.php b/modules-available/roomplanner/inc/room.inc.php
new file mode 100644
index 00000000..855bdbcf
--- /dev/null
+++ b/modules-available/roomplanner/inc/room.inc.php
@@ -0,0 +1,176 @@
+<?php
+
+abstract class Room
+{
+
+ /**
+ * @var Room[] list of all rooms
+ */
+ protected static $rooms = null;
+
+ /**
+ * @var int id for this room
+ */
+ private $locationId;
+
+ /**
+ * @var string name of this room
+ */
+ private $locationName;
+
+ protected static function init()
+ {
+ if (self::$rooms !== null)
+ return;
+ /* get all rooms */
+ self::$rooms = [];
+ $ret = Database::simpleQuery(
+ 'SELECT lr.locationid, lr.managerip, lr.tutoruuid, lr.roomplan, m.clientip as tutorip
+ FROM location_roomplan lr
+ LEFT JOIN machine m ON (lr.tutoruuid = m.machineuuid)');
+ while ($row = $ret->fetch(PDO::FETCH_ASSOC)) {
+ $row = self::loadSingleRoom($row);
+ if ($row === false)
+ continue;
+ self::$rooms[$row->locationId] = $row;
+ }
+ foreach (self::$rooms as $room) {
+ $room->sanitize();
+ }
+ }
+
+ /**
+ * Instantiate ComposedRoom or MachineGroup depending on contents of $row
+ * @param array $row DB row from location_roomplan.
+ * @return Room|false Room instance, false on error
+ */
+ private static function loadSingleRoom($row)
+ {
+ $locations = Location::getLocationsAssoc();
+ settype($row['locationid'], 'int');
+ if (!isset($locations[$row['locationid']]))
+ return false;
+ if ($locations[$row['locationid']]['isleaf'])
+ return new SimpleRoom($row);
+ return new ComposedRoom($row, false);
+ }
+
+ /**
+ * Get array of all rooms with room plan
+ * @return Room[]
+ */
+ public static function getAll()
+ {
+ self::init();
+ return self::$rooms;
+ }
+
+ /**
+ * Get room instance for given location
+ * @param int $locationId room to get
+ * @return Room|false requested room, false if not configured or not found
+ */
+ public static function get($locationId)
+ {
+ if (self::$rooms === null) {
+ $room = Database::queryFirst(
+ 'SELECT lr.locationid, lr.managerip, lr.tutoruuid, lr.roomplan, m.clientip as tutorip
+ FROM location_roomplan lr
+ LEFT JOIN machine m ON (lr.tutoruuid = m.machineuuid)
+ WHERE lr.locationid = :lid', ['lid' => $locationId]);
+ if ($room === false)
+ return false;
+ $room = self::loadSingleRoom($room);
+ // If it's a leaf room we probably don't need any other rooms, return it
+ if ($room->isLeaf())
+ return $room;
+ // Otherwise init the full tree so we can resolve composed rooms later
+ self::init();
+ }
+ if (isset(self::$rooms[$locationId]))
+ return self::$rooms[$locationId];
+ return false;
+ }
+
+ public function __construct($row)
+ {
+ $locations = Location::getLocationsAssoc();
+ $this->locationId = (int)$row['locationid'];
+ $this->locationName = $locations[$this->locationId]['locationname'];
+ }
+
+ /**
+ * @return int number of machines in this room
+ */
+ abstract public function machineCount();
+
+ /**
+ * Size of this room, returned by reference.
+ * @param int $width OUT width of room
+ * @param int $height OUT height of room
+ */
+ abstract public function getSize(&$width, &$height);
+
+ /**
+ * Get clients in this room in .ini format for PVS.
+ * Adjusted so the top/left client is at (0|0), which
+ * is further adjustable with $offX and $offY.
+ * @param int $i offset for indexing clients
+ * @param int $offX positional X offset for clients
+ * @param int $offY positional Y offset for clients
+ * @return string|false
+ */
+ abstract public function getIniClientSection(&$i, $offX = 0, $offY = 0);
+
+ /**
+ * Get clients in this room as array.
+ * Adjusted so the top/left client is at (0|0), which
+ *is further adjustable with $offX and $offY.
+ * @param int $offX
+ * @param int $offY
+ * @return array
+ */
+ abstract public function getShiftedArray($offX = 0, $offY = 0);
+
+ /**
+ * @return string|false IP address of manager.
+ */
+ abstract public function getManagerIp();
+
+ /**
+ * @return string|false IP address of tutor client.
+ */
+ abstract public function getTutorIp();
+
+ /**
+ * @return bool true if this is a simple/leaf room, false for composed rooms.
+ */
+ abstract public function isLeaf();
+
+ /**
+ * @return bool should this room be skipped from output? true for empty SimpleRoom or disabled ComposedRoom.
+ */
+ abstract public function shouldSkip();
+
+ /**
+ * Sanitize this room's data.
+ */
+ abstract protected function sanitize();
+
+ /**
+ * @return string get room's name.
+ */
+ public function locationName()
+ {
+ return $this->locationName;
+ }
+
+ /**
+ * @return int get room's id.
+ */
+ public function locationId()
+ {
+ return $this->locationId;
+ }
+
+} \ No newline at end of file
diff --git a/modules-available/roomplanner/inc/simpleroom.inc.php b/modules-available/roomplanner/inc/simpleroom.inc.php
new file mode 100644
index 00000000..78db6c4a
--- /dev/null
+++ b/modules-available/roomplanner/inc/simpleroom.inc.php
@@ -0,0 +1,148 @@
+<?php
+
+class SimpleRoom extends Room
+{
+
+ const CLIENT_SIZE = 4;
+
+ private $machines = [];
+
+ private $bb = false;
+
+ private $tutorIp = false;
+
+ private $managerIp = false;
+
+ public function __construct($row)
+ {
+ parent::__construct($row);
+ $locationId = (int)$row['locationid'];
+ $ret = Database::simpleQuery(
+ 'SELECT machineuuid, clientip, position FROM machine WHERE fixedlocationid = :locationid',
+ ['locationid' => $locationId]);
+
+ while ($clientRow = $ret->fetch(PDO::FETCH_ASSOC)) {
+ $position = json_decode($clientRow['position'], true);
+
+ if ($position === false || !isset($position['gridRow']) || !isset($position['gridCol']))
+ continue; // TODO: Remove entry/set to NULL?
+
+ $rotation = 'north';
+ if (preg_match('/(north|east|south|west)/', $position['itemlook'], $out)) {
+ $rotation = $out[1];
+ }
+ $this->machines[] = array(
+ 'machineuuid' => $clientRow['machineuuid'],
+ 'clientip' => $clientRow['clientip'],
+ 'gridRow' => $position['gridRow'],
+ 'gridCol' => $position['gridCol'],
+ 'rotation' => $rotation,
+ );
+ }
+ // Runmode info overrides IP given
+ if (Module::isAvailable('runmode')) {
+ $pc = RunMode::getForMode('roomplanner', $locationId, true);
+ if (!empty($pc)) {
+ $pc = array_pop($pc);
+ $row['managerip'] = $pc['clientip'];
+ }
+ }
+ if (!empty($row['managerip'])) {
+ $this->managerIp = $row['managerip'];
+ }
+ if (!empty($row['tutorip'])) {
+ $this->tutorIp = $row['tutorip'];
+ }
+ }
+
+ public function machineCount()
+ {
+ return count($this->machines);
+ }
+
+ public function getSize(&$width, &$height)
+ {
+ if (empty($this->machines)) {
+ $width = $height = 0;
+ return;
+ }
+ $this->boundingBox($minX, $minY, $maxX, $maxY);
+ // client's size that cannot be configured as of today
+ $width = max($maxX - $minX + self::CLIENT_SIZE, 1);
+ $height = max($maxY - $minY + self::CLIENT_SIZE, 1);
+ }
+
+ public function getIniClientSection(&$i, $offX = 0, $offY = 0)
+ {
+ /* output individual client positions, shift coordinates to requested position */
+ $out = '';
+ $this->boundingBox($minX, $minY, $maxX, $maxY);
+ foreach ($this->machines as $pos) {
+ $i++;
+ $out .= "client\\$i\\ip={$pos['clientip']}\n"
+ . "client\\$i\\pos=@Point(" . ($pos['gridCol'] + $offX -$minX) . ' ' . ($pos['gridRow'] + $offY - $minY) . ")\n";
+ }
+
+ return $out;
+ }
+
+ public function getShiftedArray($offX = 0, $offY = 0)
+ {
+ /* output individual client positions, shift coordinates to requested position */
+ $ret = [];
+ $this->boundingBox($minX, $minY, $maxX, $maxY);
+ foreach ($this->machines as $pos) {
+ $pos['gridCol'] += $offX - $minX;
+ $pos['gridRow'] += $offY - $minY;
+ $ret[] = $pos;
+ }
+
+ return $ret;
+ }
+
+ private function boundingBox(&$minX, &$minY, &$maxX, &$maxY)
+ {
+ if ($this->bb !== false) {
+ $minX = $this->bb[0];
+ $minY = $this->bb[1];
+ $maxX = $this->bb[2];
+ $maxY = $this->bb[3];
+ } else {
+ $minX = $minY = PHP_INT_MAX; /* PHP_INT_MIN is only available since PHP 7 */
+ $maxX = $maxY = ~PHP_INT_MAX;
+ foreach ($this->machines as $pos) {
+ $minX = min($minX, $pos['gridCol']);
+ $maxX = max($maxX, $pos['gridCol']);
+ $minY = min($minY, $pos['gridRow']);
+ $maxY = max($maxY, $pos['gridRow']);
+ }
+ $this->bb = [$minX, $minY, $maxX, $maxY];
+ }
+ }
+
+ public function getManagerIp()
+ {
+ return $this->managerIp;
+ }
+
+ public function getTutorIp()
+ {
+ return $this->tutorIp;
+ }
+
+ public function isLeaf()
+ {
+ return true;
+ }
+
+ public function shouldSkip()
+ {
+ return empty($this->machines);
+ }
+
+ protected function sanitize()
+ {
+ // Nothing
+ }
+
+}
diff --git a/modules-available/roomplanner/page.inc.php b/modules-available/roomplanner/page.inc.php
index d1543a9e..de917a1a 100644
--- a/modules-available/roomplanner/page.inc.php
+++ b/modules-available/roomplanner/page.inc.php
@@ -122,23 +122,23 @@ class Page_Roomplanner extends Page
private function showComposedEditor()
{
// Load settings
- $row = Database::queryFirst("SELECT roomplan FROM location_roomplan WHERE locationid = :lid", [
+ $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',
+ $room->orientation() . '_checked' => 'checked',
];
- if ($room->enabled) {
+ if (!$room->shouldSkip()) {
$params['enabled_checked'] = 'checked';
}
- $inverseList = array_flip($room->list);
+ $inverseList = array_flip($room->subLocationIds());
$sortList = [];
// Load locations
$locs = Location::getLocationsAssoc();
- foreach ($this->location['children'] as $loc) {
+ foreach ($this->location['directchildren'] as $loc) {
if (isset($locs[$loc])) {
$data = $locs[$loc];
if (isset($inverseList[$loc])) {
@@ -146,7 +146,7 @@ class Page_Roomplanner extends Page
} else {
$sortList[] = 1000 + $loc;
}
- if ($loc === $room->controlRoom) {
+ if ($loc === $room->controlRoom()) {
$data['checked'] = 'checked';
}
$params['locations'][] = $data;
@@ -264,13 +264,7 @@ class Page_Roomplanner extends Page
private function saveComposedRoom($isAjax)
{
- $room = new ComposedRoom(null);
- $room->orientation = Request::post('orientation', 'horizontal', 'string');
- $room->enabled = (bool)Request::post('enabled', 0, 'int');
- $room->controlRoom = Request::post('controlroom', 0, 'int');
- $vals = Request::post('sort', [], 'array');
- asort($vals, SORT_ASC | SORT_NUMERIC);
- $room->list = array_keys($vals);
+ $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()]);
diff --git a/modules-available/roomplanner/templates/edit-composed-room.html b/modules-available/roomplanner/templates/edit-composed-room.html
index 64a02d61..3b87cfea 100644
--- a/modules-available/roomplanner/templates/edit-composed-room.html
+++ b/modules-available/roomplanner/templates/edit-composed-room.html
@@ -31,7 +31,7 @@
<div class="name text-nowrap small">{{locationname}}</div>
<div class="img">
<input type="hidden" class="sort-val" name="sort[{{locationid}}]">
- <img src="api.php?do=roomplanner&amp;show=svg&amp;locationid={{locationid}}&amp;scale=2.2">
+ <img src="api.php?do=roomplanner&amp;show=svg&amp;locationid={{locationid}}&amp;scale=2.2&amp;fallback=1">
</div>
<div class="clearfix"></div>
</div>
diff --git a/modules-available/roomplanner/templates/svg-plan.html b/modules-available/roomplanner/templates/svg-plan.html
index 16899e5c..a2ecd5a7 100644
--- a/modules-available/roomplanner/templates/svg-plan.html
+++ b/modules-available/roomplanner/templates/svg-plan.html
@@ -33,9 +33,9 @@
<stop offset="100%" stop-color="#074" />
</radialGradient>
</defs>
- <g transform="scale({{scale}}) rotate({{rotate}} {{centerX}} {{centerY}}) translate({{shiftX}} {{shiftY}})">
- <line x1="{{line.x1}}" y1="{{line.y1}}"
- x2="{{line.x2}}" y2="{{line.y2}}"
+ <g transform="scale({{scale}}) rotate({{rotate}} {{centerX}} {{centerY}})">
+ <line x1="0" y1="{{line.y}}"
+ x2="{{line.x}}" y2="{{line.y}}"
style="stroke:#555;stroke-width:.2;opacity:.5" />
{{#machines}}
<g transform="translate({{gridCol}} {{gridRow}})">