diff options
23 files changed, 292 insertions, 309 deletions
diff --git a/inc/user.inc.php b/inc/user.inc.php index cd35ac29..088f12c6 100644 --- a/inc/user.inc.php +++ b/inc/user.inc.php @@ -112,8 +112,7 @@ class User } if (self::$user['permissions'] & Permission::get('superadmin')) { if (Module::isAvailable('locations')) { - $a = array_keys(Location::getLocationsAssoc()); - $a[] = 0; + $a = Location::getAllLocationIds(0, true); } else { $a = [0]; } diff --git a/modules-available/dnbd3/baseconfig/getconfig.inc.php b/modules-available/dnbd3/baseconfig/getconfig.inc.php index b962106d..d93c3f4a 100644 --- a/modules-available/dnbd3/baseconfig/getconfig.inc.php +++ b/modules-available/dnbd3/baseconfig/getconfig.inc.php @@ -51,7 +51,7 @@ foreach ($res as $row) { if ($defPrio === 1000 && is_null($row['locationid'])) { // Server is not assigned to this location, try to guess it by its IP address $serverLoc = Location::getFromIp($ip); - if ($serverLoc !== false) { + if ($serverLoc !== null) { $row['locationid'] = $serverLoc; } } diff --git a/modules-available/dnbd3/inc/dnbd3util.inc.php b/modules-available/dnbd3/inc/dnbd3util.inc.php index fe27f572..d79ae54f 100644 --- a/modules-available/dnbd3/inc/dnbd3util.inc.php +++ b/modules-available/dnbd3/inc/dnbd3util.inc.php @@ -126,11 +126,8 @@ class Dnbd3Util { if (!empty($assignedLocs) && ($modeData['firewall'] ?? false)) { // Get all sub-locations too $recursiveLocs = $assignedLocs; - $locations = Location::getLocationsAssoc(); foreach ($assignedLocs as $l) { - if (isset($locations[$l])) { - $recursiveLocs = array_merge($recursiveLocs, $locations[$l]['children']); - } + $recursiveLocs = array_merge($recursiveLocs, Location::getAllLocationIds($l)); } $res = Database::simpleQuery('SELECT startaddr, endaddr FROM subnet WHERE locationid IN (:locs)', array('locs' => array_values($recursiveLocs))); diff --git a/modules-available/dozmod/api.inc.php b/modules-available/dozmod/api.inc.php index 5a8f6302..11a542ef 100644 --- a/modules-available/dozmod/api.inc.php +++ b/modules-available/dozmod/api.inc.php @@ -343,7 +343,11 @@ if (substr($ip, 0, 7) === '::ffff:') { /* lookup location id(s) */ $location_ids = Location::getFromIp($ip, true); -$location_ids = Location::getLocationRootChain($location_ids); +if ($location_ids === null) { + $location_ids = []; +} else { + $location_ids = Location::getLocationRootChain($location_ids); +} if ($resource === 'list') { outputLectureXmlForLocation($location_ids); diff --git a/modules-available/locationinfo/api.inc.php b/modules-available/locationinfo/api.inc.php index ef462d83..b066779b 100644 --- a/modules-available/locationinfo/api.inc.php +++ b/modules-available/locationinfo/api.inc.php @@ -33,7 +33,7 @@ function HandleParameters() $output = getPcStates($locationIds, $uuid); } elseif ($get === "locationtree") { $locationIds = LocationInfo::getLocationsOr404($uuid); - $output = getLocationTree($locationIds); + $output = Location::getTree(...$locationIds); } elseif ($get === "calendar") { $locationIds = LocationInfo::getLocationsOr404($uuid); $output = LocationInfo::getCalendar($locationIds); @@ -127,36 +127,6 @@ function getPcStates(array $idList, string $paneluuid): array } /** - * Gets the location tree of the given locations. - * - * @param int[] $idList Array list of the locations. - * @return array location tree data - */ -function getLocationTree(array $idList): array -{ - if (in_array(0, $idList)) { - return array_values(Location::getTree()); - } - $locations = Location::getTree(); - - return findLocations($locations, $idList); -} - -function findLocations(array $locations, array $idList): array -{ - $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; -} - - -/** * Generates a web application manifest for a panel. * * @param string $uuid The UUID of the panel. diff --git a/modules-available/locationinfo/inc/infopanel.inc.php b/modules-available/locationinfo/inc/infopanel.inc.php index 1a0e9b67..2557149d 100644 --- a/modules-available/locationinfo/inc/infopanel.inc.php +++ b/modules-available/locationinfo/inc/infopanel.inc.php @@ -27,7 +27,6 @@ class InfoPanel return $panel['paneltype']; } - $locations = Location::getLocationsAssoc(); $overrides = false; if (!empty($panel['panelconfig'])) { @@ -48,17 +47,17 @@ class InfoPanel $lids = array_map('intval', explode(',', $panel['locationids'])); // Locations - if ($panel['paneltype'] === 'SUMMARY') { - $lids = Location::getRecursiveFlat($lids); - $lids = array_keys($lids); foreach ($lids as $lid) { - $config['locations'][$lid] = array('id' => $lid); + foreach (Location::getAllLocationIds($lid, true) as $lid2) { + $config['locations'][$lid2] = ['id' => $lid2]; + } } } if ($panel['paneltype'] === 'DEFAULT') { foreach ($lids as $lid) { $config['locations'][$lid] = array( 'id' => $lid, - 'name' => isset($locations[$lid]) ? $locations[$lid]['locationname'] : 'noname00.pas', + 'name' => Location::getName($lid) ?: 'noname00.pas', ); // Now apply any overrides from above if (isset($overrides[$lid]) && is_array($overrides[$lid])) { diff --git a/modules-available/locationinfo/inc/locationinfo.inc.php b/modules-available/locationinfo/inc/locationinfo.inc.php index 2ce557f4..b41f3a33 100644 --- a/modules-available/locationinfo/inc/locationinfo.inc.php +++ b/modules-available/locationinfo/inc/locationinfo.inc.php @@ -34,8 +34,9 @@ class LocationInfo if ($panel !== false) { $idArray = array_map('intval', explode(',', $panel['locationids'])); if ($panel['paneltype'] == "SUMMARY" && $recursive) { - $idList = Location::getRecursiveFlat($idArray); - $idArray = array_keys($idList); + foreach ($idArray as $lid) { + $idArray = array_merge($idArray, Location::getAllLocationIds($lid)); + } } return $idArray; } diff --git a/modules-available/locationinfo/page.inc.php b/modules-available/locationinfo/page.inc.php index b5171200..fd9abb18 100644 --- a/modules-available/locationinfo/page.inc.php +++ b/modules-available/locationinfo/page.inc.php @@ -185,11 +185,7 @@ class Page_LocationInfo extends Page if ($changeServerRecursive) { // Recursive overwriting of serverid - $children = Location::getRecursiveFlat($locationid); - $array = array(); - foreach ($children as $loc) { - $array[] = $loc['locationid']; - } + $array = Location::getAllLocationIds($locationid); if (!empty($array)) { Database::exec("UPDATE locationinfo_locationconfig SET serverid = :serverid, lastcalendarupdate = IF(serverid <> :serverid, 0, lastcalendarupdate), lastchange = :now @@ -214,7 +210,7 @@ class Page_LocationInfo extends Page { $locationids = Request::post('locationids', Request::REQUIRED_EMPTY, 'string'); $locationids = explode(',', $locationids); - $all = array_map(function ($item) { return $item['locationid']; }, Location::queryLocations()); + $all = Location::getAllLocationIds(); $locationids = array_filter($locationids, function ($item) use ($all) { return in_array($item, $all); }); if (empty($locationids)) { Message::addError('main.parameter-empty', 'locationids'); @@ -617,7 +613,6 @@ class Page_LocationInfo extends Page $runmodes = RunMode::getForModule(Page::getModule(), true); } $panels = array(); - $locations = Location::getLocationsAssoc(); foreach ($res as $row) { if ($row['paneltype'] === 'URL') { $url = json_decode($row['panelconfig'], true)['url']; @@ -635,8 +630,8 @@ class Page_LocationInfo extends Page $row['runmode_disabled'] = $assignLocations !== true && !empty(array_diff($lids, $assignLocations)) ? 'disabled' : ''; // Locations - $locs = array_map(function ($id) use ($locations) { - return isset($locations[$id]) ? $locations[$id]['locationname'] : "<<deleted=$id>>"; + $locs = array_map(function ($id) { + return Location::getName($id) ?: "<<deleted=$id>>"; }, $lids); $row['locations'] = implode(', ', $locs); } @@ -1080,9 +1075,19 @@ class Page_LocationInfo extends Page die(Render::parse('frontend-default', $data, null, $config['language'])); } + if ($type === 'UPCOMING') { + $data += array( + 'uuid' => $uuid, + 'config' => json_encode($config), + 'language' => $config['language'], + ); + + die(Render::parse('frontend-kg2-upcoming', $data, null, $config['language'])); + } + if ($type === 'SUMMARY') { $locations = LocationInfo::getLocationsOr404($uuid, false); - $config['tree'] = Location::getRecursive($locations); + $config['tree'] = Location::getTree(...$locations); $data += array( 'uuid' => $uuid, 'config' => json_encode($config), diff --git a/modules-available/locations/baseconfig/getconfig.inc.php b/modules-available/locations/baseconfig/getconfig.inc.php index 1bed5de7..b6db699a 100644 --- a/modules-available/locations/baseconfig/getconfig.inc.php +++ b/modules-available/locations/baseconfig/getconfig.inc.php @@ -4,7 +4,7 @@ /** @var ?string $ip */ // Location handling: figure out location -$locationId = false; +$locationId = null; if (BaseConfig::hasOverride('locationid')) { $locationId = BaseConfig::getOverride('locationid'); } elseif (Request::any('force', 0, 'int') === 1 && Request::any('module', false, 'string') === 'locations') { @@ -14,14 +14,14 @@ if (BaseConfig::hasOverride('locationid')) { } } -if ($locationId === false) { +if ($locationId === null) { if ($ip === null) // Required at this point, bail out if not given return; $locationId = Location::getFromIpAndUuid($ip, $uuid); } $matchingLocations = array(); -if ($locationId !== false) { +if ($locationId !== null) { // Get all parents $matchingLocations = Location::getLocationRootChain($locationId); ConfigHolder::add("SLX_LOCATIONS", implode(' ', $matchingLocations), 100); diff --git a/modules-available/locations/inc/autolocation.inc.php b/modules-available/locations/inc/autolocation.inc.php index f77cf714..73fa9042 100644 --- a/modules-available/locations/inc/autolocation.inc.php +++ b/modules-available/locations/inc/autolocation.inc.php @@ -9,7 +9,7 @@ class AutoLocation * * @param int[]|false $locations Locations to rebuild, or false for everything */ - public static function rebuildAll($locations = false) + public static function rebuildAll($locations = false): void { if (Module::get('statistics') === false) return; // Nothing to do @@ -24,7 +24,7 @@ class AutoLocation $nulls = array(); foreach ($res as $row) { $loc = Location::mapIpToLocation($row['clientip']); - if ($loc === false) { + if ($loc === null) { $nulls[] = $row['machineuuid']; } else { if (!isset($updates[$loc])) { diff --git a/modules-available/locations/inc/location.inc.php b/modules-available/locations/inc/location.inc.php index 807f8577..d4978068 100644 --- a/modules-available/locations/inc/location.inc.php +++ b/modules-available/locations/inc/location.inc.php @@ -3,28 +3,111 @@ class Location { - private static $flatLocationCache = false; - private static $assocLocationCache = false; - private static $treeCache = false; - private static $subnetMapCache = false; + /** @var ?array */ + private static $flatLocationCache = null; + /** @var ?array */ + private static $assocLocationCache = null; - public static function getTree(): array + /** + * Get a nested array of locations. Each element in the array represents a top-level + * location, and locations with have child-nodes will have a 'children' key that again + * contains an array of location elements, with 'children' as appropriate, and so on. + * If you pass one or more startLocationIds, the tree will be cut down to only contain + * the locations given (plus any subtrees they have). + * @return array<array{locationid: int, locationname: string, parentlocationd: int, children: array}> + */ + public static function getTree(int ...$startLocationId): array { - if (self::$treeCache === false) { - self::$treeCache = self::queryLocations(); - self::$treeCache = self::buildTree(self::$treeCache); + static $treeCache = null; + if ($treeCache === null) { + $treeCache = self::buildTree(self::queryLocations()); } - return self::$treeCache; + if (empty($startLocationId) || in_array(0, $startLocationId)) + return $treeCache; + // Subtree, search + return self::filterTreeByLocations($treeCache, $startLocationId); } - public static function queryLocations(): array + /** + * Return a filtered tree that only contains the locations present in $idList, plus their respective + * child locations in the 'children' array key, if applicable. Top level elements will only be those + * contained in $idList, but not every location given in $idList will be a top level element, as they + * might already be present in one of the nested 'children' arrays. + * @param array $locationTree part of tree to search in + * @param array $idList list of wanted ids + * @return array<array{locationid: int, locationname: string, parentlocationd: int, children: array}> filtered tree + */ + private static function filterTreeByLocations(array $locationTree, array $idList): array { - $res = Database::simpleQuery("SELECT locationid, parentlocationid, locationname FROM location"); - $rows = array(); - foreach ($res as $row) { - $rows[] = $row; + $ret = []; + foreach ($locationTree as $location) { + if (in_array($location['locationid'], $idList)) { + $ret[] = $location; + } elseif (!empty($location['children'])) { + $ret = array_merge($ret, self::filterTreeByLocations($location['children'], $idList)); + } + } + return $ret; + } + + /** + * @return array<array{locationid: int, locationname: string, parentlocationd: int, children: array}> + */ + private static function buildTree(array $elements, int $parentId = 0): array + { + $branch = array(); + foreach ($elements as $lid => $element) { + $lid = (int)$lid; + if ($lid === 0 || $lid === $parentId) + continue; + if ($element['parentlocationid'] === $parentId) { + $children = self::buildTree($elements, $lid); + if (!empty($children)) { + $element['children'] = $children; + } + $element['locationid'] = $lid; + $branch[] = $element; + } + } + ArrayUtil::sortByColumn($branch, 'locationname'); + return $branch; + } + + private static function queryLocations(): array + { + static $locationDbCache = null; + if ($locationDbCache === null) { + $locationDbCache = Database::queryIndexedList("SELECT locationid, parentlocationid, locationname FROM location"); + foreach ($locationDbCache as &$loc) { + $loc['parentlocationid'] = (int)$loc['parentlocationid']; + } + } + return $locationDbCache; + } + + /** + * Get all location ids starting from the specified location id. + * If $startId is 0, it will return all available location ids. + * If $withStart is true, the starting location id will be included in the result. + * + * @param int $startId The location id to start from (default is 0 to get all location ids). + * @param bool $withStart True to include the starting location id in the result (default is false). + * @return int[] An array of location ids, including the starting location id if requested. + */ + public static function getAllLocationIds(int $startId = 0, bool $withStart = false): array + { + if ($startId === 0) { + $locs = array_keys(self::queryLocations()); + } else { + $locs = self::getLocationsAssoc(); + if (!isset($locs[$startId])) + return []; + $locs = $locs[$startId]['children'] ?? []; + } + if ($withStart) { + $locs[] = $startId; } - return $rows; + return $locs; } /** @@ -43,79 +126,79 @@ class Location */ public static function getName(int $locationId) { - if (self::$assocLocationCache === false) { - self::getLocationsAssoc(); - } - if (!isset(self::$assocLocationCache[$locationId])) - return false; - return self::$assocLocationCache[$locationId]['locationname']; + $locs = self::queryLocations(); + return $locs[$locationId]['locationname'] ?? false; } /** * Get all the names of the given location and its parents, up * to the root element. Array keys will be locationids, value the names. - * @return array|false locations, from furthest to nearest or false if locationId doesn't exist + * @return ?array locations, from furthest to nearest or false if locationId doesn't exist */ - public static function getNameChain(int $locationId) + public static function getNameChain(int $locationId): ?array { - if (self::$assocLocationCache === false) { - self::getLocationsAssoc(); - } - if (!isset(self::$assocLocationCache[$locationId])) - return false; - $ret = array(); - while (isset(self::$assocLocationCache[$locationId])) { - $ret[$locationId] = self::$assocLocationCache[$locationId]['locationname']; - $locationId = self::$assocLocationCache[$locationId]['parentlocationid']; + $locs = self::queryLocations(); + $ret = []; + while (isset($locs[$locationId])) { + $ret[$locationId] = $locs[$locationId]['locationname']; + $locationId = $locs[$locationId]['parentlocationid']; } + if (empty($ret)) + return null; return array_reverse($ret, true); } - public static function getLocationsAssoc() + /** + * Get associative array of locations. Mostly used for working with locations, in contrast to ::getLocations() + * @return array<array{locationid: int, locationname: string, parentlocationid: int, parents: int[], children: int[], directchildren: int[], depth: int, isleaf: bool}> All the locations + */ + public static function getLocationsAssoc(): array { - if (self::$assocLocationCache === false) { - $rows = self::getTree(); - self::$assocLocationCache = self::flattenTreeAssoc($rows); + if (self::$assocLocationCache === null) { + self::$assocLocationCache = self::flattenTreeAssoc(self::getTree()); } return self::$assocLocationCache; } - private static function flattenTreeAssoc($tree, $parents = array(), $depth = 0): array + /** + * @return array<array{locationid: int, locationname: string, parentlocationid: int, parents: int[], children: int[], directchildren: int[], depth: int, isleaf: bool}> All the locations + */ + private static function flattenTreeAssoc(array $tree, array $parents = [], int $depth = 0): array { - if ($depth > 20) { - ErrorHandler::traceError('Recursive location definition detected at ' . print_r($tree, true)); - } - $output = array(); + $output = []; 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'], + $cc = empty($node['children']) ? [] : array_column($node['children'], 'locationid'); + $output[$node['locationid']] = [ + 'locationid' => $node['locationid'], + 'parentlocationid' => $node['parentlocationid'], 'parents' => $parents, 'children' => $cc, 'directchildren' => $cc, 'locationname' => $node['locationname'], 'depth' => $depth, - 'isleaf' => true, - ); + 'isleaf' => empty($cc), + ]; if (!empty($node['children'])) { - $childNodes = self::flattenTreeAssoc($node['children'], array_merge($parents, array((int)$node['locationid'])), $depth + 1); - $output[(int)$node['locationid']]['children'] = array_merge($output[(int)$node['locationid']]['children'], + $childNodes = self::flattenTreeAssoc($node['children'], array_merge($parents, [$node['locationid']]), $depth + 1); + $output[$node['locationid']]['children'] = array_merge($output[$node['locationid']]['children'], array_reduce($childNodes, function ($carry, $item) { return array_merge($carry, $item['children']); - }, array())); + }, [])); $output += $childNodes; } } - foreach ($output as &$entry) { - if (!isset($output[$entry['parentlocationid']])) - continue; - $output[$entry['parentlocationid']]['isleaf'] = false; - } return $output; } + private static function buildFlatTree(): void + { + if (self::$flatLocationCache === null) { + self::$flatLocationCache = self::flattenTree(self::getTree()); + } + } + /** + * get locations as flat array - mostly used for rendering a <select>. * @param int|int[] $selected Which locationIDs to mark as selected * @param int $excludeId Which locationID to exclude * @param bool $addNoParent Add entry for "no location" at the top @@ -124,13 +207,8 @@ class Location */ public static function getLocations($selected = 0, int $excludeId = 0, bool $addNoParent = false, bool $keepArrayKeys = false): array { - if (self::$flatLocationCache === false) { - $rows = self::getTree(); - $rows = self::flattenTree($rows); - self::$flatLocationCache = $rows; - } else { - $rows = self::$flatLocationCache; - } + self::buildFlatTree(); + $rows = self::$flatLocationCache; $del = false; unset($row); $index = 0; @@ -150,94 +228,37 @@ class Location $row['sortIndex'] = $index++; } if ($addNoParent) { - array_unshift($rows, array( + $noParent = [ 'locationid' => 0, 'locationname' => '-----', 'selected' => $selected === 0, 'locationpad' => '', - )); + ]; + if ($keepArrayKeys) { + $rows = array_reverse($rows, true); + $rows[0] = $noParent; + $rows = array_reverse($rows, true); + } else { + array_unshift($rows, $noParent); + } } if ($keepArrayKeys) return $rows; return array_values($rows); } - /** - * Get nested array of all the locations and children of given locationid(s). - * - * @param int[]|int $idList List of location ids - * @param ?array $locationTree used in recursive calls, don't pass - * @return array list of passed locations plus their children - */ - public static function getRecursive($idList, ?array $locationTree = null): array - { - if (!is_array($idList)) { - $idList = array($idList); - } - if ($locationTree === null) { - $locationTree = self::getTree(); - } - $ret = array(); - foreach ($locationTree as $location) { - if (in_array($location['locationid'], $idList)) { - $ret[] = $location; - } elseif (!empty($location['children'])) { - $ret = array_merge($ret, self::getRecursive($idList, $location['children'])); - } - } - return $ret; - } - - /** - * Get flat array of all the locations and children of given locationid(s). - * - * @param int[]|int $idList List of location ids - * @return array list of passed locations plus their children - */ - public static function getRecursiveFlat($idList): array - { - $ret = self::getRecursive($idList); - if (!empty($ret)) { - $ret = self::flattenTree($ret); - } - return $ret; - } - - public static function buildTree(array $elements, int $parentId = 0): array - { - $branch = array(); - $sort = array(); - foreach ($elements as $element) { - if ($element['locationid'] == 0 || $element['locationid'] == $parentId) - continue; - if ($element['parentlocationid'] == $parentId) { - $children = self::buildTree($elements, $element['locationid']); - if (!empty($children)) { - $element['children'] = $children; - } - $branch[] = $element; - $sort[] = $element['locationname']; - } - } - array_multisort($sort, SORT_ASC, $branch); - return $branch; - } - private static function flattenTree(array $tree, int $depth = 0): array { - if ($depth > 20) { - ErrorHandler::traceError('Recursive location definition detected at ' . print_r($tree, true)); - } - $output = array(); + $output = []; foreach ($tree as $node) { - $output[(int)$node['locationid']] = array( + $output[$node['locationid']] = [ 'locationid' => $node['locationid'], 'parentlocationid' => $node['parentlocationid'], 'locationname' => $node['locationname'], 'locationpad' => str_repeat('--', $depth), 'isleaf' => empty($node['children']), - 'depth' => $depth - ); + 'depth' => $depth, + ]; if (!empty($node['children'])) { $output += self::flattenTree($node['children'], $depth + 1); } @@ -247,39 +268,28 @@ class Location public static function isLeaf(int $locationid): bool { - $result = Database::queryFirst('SELECT COUNT(locationid) = 0 AS isleaf ' - . 'FROM location ' - . 'WHERE parentlocationid = :locationid', ['locationid' => $locationid]); - $result = $result['isleaf']; - return (bool)$result; - } - - public static function extractIds(array $tree): array - { - $ids = array(); - foreach ($tree as $node) { - $ids[] = (int)$node['locationid']; - if (!empty($node['children'])) { - $ids = array_merge($ids, self::extractIds($node['children'])); - } - } - return $ids; + if (self::$flatLocationCache !== null) + return self::$flatLocationCache[$locationid]['isleaf'] ?? false; + if (self::$assocLocationCache !== null) + return self::$assocLocationCache[$locationid]['isleaf'] ?? false; + return !empty(self::getAllLocationIds($locationid)); } /** * Get location id for given machine (by uuid) * * @param string $uuid machine uuid - * @return false|int locationid, false if no match + * @return ?int locationid, null if no match */ - public static function getFromMachineUuid(string $uuid) + public static function getFromMachineUuid(string $uuid): ?int { // Only if we have the statistics module which supplies the machine table if (Module::get('statistics') === false) - return false; - $ret = Database::queryFirst("SELECT locationid FROM machine WHERE machineuuid = :uuid", compact('uuid')); + return null; + $ret = Database::queryFirst("SELECT locationid FROM machine WHERE machineuuid = :uuid", + ['uuid' => $uuid]); if ($ret === false || !$ret['locationid']) - return false; + return null; return (int)$ret['locationid']; } @@ -289,9 +299,9 @@ class Location * * @param string $ip IP address of client * @param bool $honorRoomPlanner consider a fixed location assigned manually by roomplanner - * @return false|int locationid, or false if no match + * @return ?int locationid, or null if no match */ - public static function getFromIp(string $ip, bool $honorRoomPlanner = false) + public static function getFromIp(string $ip, bool $honorRoomPlanner = false): ?int { if (Module::get('statistics') !== false) { // Shortcut - try to use subnetlocationid in machine table @@ -308,7 +318,7 @@ class Location if ($ret['loc'] > 0) { return (int)$ret['loc']; } - return false; + return null; } } return self::mapIpToLocation($ip); @@ -321,13 +331,13 @@ class Location * * @param string $ip IP address of client * @param ?string $uuid System-UUID of client - * @return int|false location id, or false if none matches + * @return ?int location id, or null if none matches */ - public static function getFromIpAndUuid(string $ip, ?string $uuid) + public static function getFromIpAndUuid(string $ip, ?string $uuid): ?int { $locationId = false; $ipLoc = self::getFromIp($ip); - if ($ipLoc !== false) { + if ($ipLoc !== null) { // Set locationId to ipLoc for now, it will be overwritten later if another case applies. $locationId = $ipLoc; if ($uuid !== null) { @@ -341,7 +351,7 @@ class Location return $locationId; } - public static function isFixedLocationValid($uuidLoc, $ipLoc): bool + public static function isFixedLocationValid(?int $uuidLoc, int $ipLoc): bool { if ($uuidLoc === false) return false; @@ -368,18 +378,16 @@ class Location */ public static function getLocationRootChain(int $locationId): array { - if (self::$assocLocationCache === false) { - self::getLocationsAssoc(); - } - if (!isset(self::$assocLocationCache[$locationId])) + $locs = self::getLocationsAssoc(); + if (!isset($locs[$locationId])) return []; - $chain = self::$assocLocationCache[$locationId]['parents']; + $chain = $locs[$locationId]['parents']; $chain[] = $locationId; return array_reverse($chain); } /** - * @return array list of subnets as numeric array + * @return array<array{locationid: int, startaddr: int, endaddr: int}> list of subnets */ public static function getSubnets(): array { @@ -393,7 +401,7 @@ class Location } /** - * @return array assoc array mapping from locationid to subnets + * @return array<array{locationid: int, subnets: array<array{startaddr:int, endaddr: int}>}> assoc array mapping from locationid to subnets */ public static function getSubnetsByLocation($recursive = false): array { @@ -429,48 +437,44 @@ class Location * random one will be returned. * * @param string $ip IP to look up - * @return false|int locationid ip matches, false = no match + * @return ?int locationid ip matches, null = no match */ - public static function mapIpToLocation(string $ip) + public static function mapIpToLocation(string $ip): ?int { - if (self::$subnetMapCache === false) { - self::$subnetMapCache = self::getSubnetsByLocation(); + static $subnetMapCache = null; + if ($subnetMapCache === null) { + $subnetMapCache = self::getSubnetsByLocation(); } $long = sprintf('%u', ip2long($ip)); - $best = false; + $best = null; $bestSize = 0; - foreach (self::$subnetMapCache as $lid => $data) { - if ($best !== false && self::$subnetMapCache[$lid]['depth'] < self::$subnetMapCache[$best]['depth']) + foreach ($subnetMapCache as $lid => $curLocation) { + if ($best !== null && $curLocation['depth'] < $subnetMapCache[$best]['depth']) continue; // Don't even need to take a look - foreach ($data['subnets'] as $subnet) { + foreach ($curLocation['subnets'] as $subnet) { if ($long < $subnet['startaddr'] || $long > $subnet['endaddr']) continue; // Nope - if ($best !== false // Already have a best candidate - && self::$subnetMapCache[$lid]['depth'] === self::$subnetMapCache[$best]['depth'] // Same depth + if ($best !== null // Already have a best candidate + && $curLocation['depth'] === $subnetMapCache[$best]['depth'] // Same depth && $bestSize < $subnet['endaddr'] - $subnet['startaddr']) { // Old candidate has smaller subnet // So we ignore this one as the old one is more specific continue; } $bestSize = $subnet['endaddr'] - $subnet['startaddr']; - $best = $lid; + $best = (int)$lid; } } - if ($best === false) - return false; - return (int)$best; + return $best; } /** - * @return false|int newly determined location + * @return ?int newly determined location */ - public static function updateMapIpToLocation(string $uuid, string $ip) + public static function updateMapIpToLocation(string $uuid, string $ip): ?int { $loc = self::mapIpToLocation($ip); - if ($loc === false) { - Database::exec("UPDATE machine SET subnetlocationid = NULL WHERE machineuuid = :uuid", compact('uuid')); - } else { - Database::exec("UPDATE machine SET subnetlocationid = :loc WHERE machineuuid = :uuid", compact('loc', 'uuid')); - } + Database::exec("UPDATE machine SET subnetlocationid = :loc WHERE machineuuid = :uuid", + ['loc' => $loc, 'uuid' => $uuid]); return $loc; } diff --git a/modules-available/locations/inc/locationutil.inc.php b/modules-available/locations/inc/locationutil.inc.php index 91117445..6114b0fb 100644 --- a/modules-available/locations/inc/locationutil.inc.php +++ b/modules-available/locations/inc/locationutil.inc.php @@ -149,8 +149,8 @@ class LocationUtil $data += [ 'iplocationid' => $slid, 'iplocationname' => $locs[$slid]['locationname'], - 'ipisleaf' => empty($locs[$slid]['children']), - 'canmove' => empty($locs[$slid]['children']) + 'ipisleaf' => $locs[$slid]['isleaf'], + 'canmove' => $locs[$slid]['isleaf'] && (!$checkPerms || $ipLocs === true || in_array($slid, $ipLocs)), // Can machine be moved to subnet's locationid? ]; } diff --git a/modules-available/locations/pages/details.inc.php b/modules-available/locations/pages/details.inc.php index 26f32e68..7f3586ad 100644 --- a/modules-available/locations/pages/details.inc.php +++ b/modules-available/locations/pages/details.inc.php @@ -207,20 +207,21 @@ class SubPage $newName = $location['locationname']; } if ($newParent === false || !User::hasPermission('location.edit.parent', $locationId) - || !User::hasPermission('location.edit.parent', $newParent) + || !User::hasPermission('location.edit.*', $newParent) || !User::hasPermission('location.edit.*', $location['parentlocationid'])) { - $newParent = $location['parentlocationid']; + $newParent = (int)$location['parentlocationid']; } else if ($newParent !== 0) { - $rows = Location::queryLocations(); - $all = Location::extractIds(Location::buildTree($rows)); + $all = Location::getAllLocationIds(); if (!in_array($newParent, $all) || $newParent === $locationId) { + // New parent location doesn't exist Message::addWarning('main.value-invalid', 'parent', $newParent); - $newParent = $location['parentlocationid']; + $newParent = (int)$location['parentlocationid']; } else { - $rows = Location::extractIds(Location::buildTree($rows, $locationId)); + $rows = Location::getAllLocationIds($locationId); if (in_array($newParent, $rows)) { + // New parent location is a child location of the location itself Message::addWarning('main.value-invalid', 'parent', $newParent); - $newParent = $location['parentlocationid']; + $newParent = (int)$location['parentlocationid']; } } } diff --git a/modules-available/news/api.inc.php b/modules-available/news/api.inc.php index 3b56c70d..e7c3129f 100644 --- a/modules-available/news/api.inc.php +++ b/modules-available/news/api.inc.php @@ -9,8 +9,12 @@ if (Module::isAvailable('locations')) { if ($locationId === 0) { $locationId = Location::getFromIp($_SERVER['REMOTE_ADDR']); } - $locations = Location::getLocationRootChain($locationId); - $locations[] = 0; + if ($locationId === null) { + $locations = [0]; + } else { + $locations = Location::getLocationRootChain($locationId); + $locations[] = 0; + } } else { $locations = [0]; } diff --git a/modules-available/permissionmanager/inc/getpermissiondata.inc.php b/modules-available/permissionmanager/inc/getpermissiondata.inc.php index a51619e0..83752a05 100644 --- a/modules-available/permissionmanager/inc/getpermissiondata.inc.php +++ b/modules-available/permissionmanager/inc/getpermissiondata.inc.php @@ -44,20 +44,33 @@ class GetPermissionData */ public static function getLocationData(): array { - $res = Database::simpleQuery("SELECT role.roleid AS roleid, rolename, GROUP_CONCAT(COALESCE(locationid, 0)) AS locationids FROM role - INNER JOIN role_x_location ON role.roleid = role_x_location.roleid GROUP BY roleid ORDER BY rolename ASC"); - $locations = Location::getLocations(0, 0, false, true); - foreach ($res as $row) { - $locationids = explode(",", $row['locationids']); - if (in_array("0", $locationids)) { - $locationids = array_map("intval", Location::extractIds(Location::getTree())); + $res = Database::simpleQuery("SELECT role.roleid AS roleid, rolename, GROUP_CONCAT(COALESCE(locationid, 0)) AS locationids + FROM role + INNER JOIN role_x_location ON (role.roleid = role_x_location.roleid) + GROUP BY roleid, rolename ORDER BY rolename ASC"); + $locations = Location::getLocations(0, 0, true, true); + $tree = Location::getLocationsAssoc(); + $locations[0]['locationname'] = Dictionary::translate('global', true); + foreach ($res as $role) { + $locationids = explode(",", $role['locationids']); + if (in_array(0, $locationids)) { + $locationids = [0]; } else { - $locationids = PermissionUtil::getSublocations(Location::getTree(), $locationids); + foreach ($locationids as $locationId) { + if (isset($tree[$locationId])) { + $locationids = array_merge($locationids, $tree[$locationId]['children']); + } else { + error_log("Unknown $locationId: " . json_encode($role)); + } + } } foreach ($locationids as $locationid) { + if (!isset($locations[$locationid])) { + $locations[$locationid] = ['locationname' => $locationid, 'locationid' => $locationid]; + } $locations[$locationid]['roles'][] = array( - 'roleid' => $row['roleid'], - 'rolename' => $row['rolename'] + 'roleid' => $role['roleid'], + 'rolename' => $role['rolename'] ); } } diff --git a/modules-available/permissionmanager/inc/permissionutil.inc.php b/modules-available/permissionmanager/inc/permissionutil.inc.php index 170fd699..2dcd4d3c 100644 --- a/modules-available/permissionmanager/inc/permissionutil.inc.php +++ b/modules-available/permissionmanager/inc/permissionutil.inc.php @@ -165,7 +165,7 @@ class PermissionUtil $parts = explode('.', $permissionid); // Special case: To prevent lockout, userid === 1 always has permissionmanager.* if ($parts[0] === 'permissionmanager' && User::getId() === 1) { - $allowedLocations = [true]; + $allowedLocations = [0 => true]; } else { // Limit query to first part of permissionid, which is always the module id $prefix = $parts[0] . '.%'; @@ -185,38 +185,18 @@ class PermissionUtil } } } - $locations = Location::getTree(); if (isset($allowedLocations[0])) { - // Trivial case - have permission for all locations, so populate list with all valid locationds - $allowedLocations = Location::extractIds($locations); - $allowedLocations[] = 0; // .. plus 0 to show that we have global perms + // Trivial case - have permission for all locations, so populate list with all valid locationids + // .. plus 0 to show that we have global perms + $allowedLocations = Location::getAllLocationIds(0, true); } else { // We have a specific list of locationds - add any sublocations to list - $allowedLocations = self::getSublocations($locations, $allowedLocations); - } - return $allowedLocations; - } - - /** - * Extend an array of locations by adding all sublocations. - * - * @param array $tree tree of all locations (structured like Location::getTree()) - * @param int[] $allowedLocations the array of locationids to extend - * @return array extended array of locationids - */ - public static function getSublocations(array $tree, array $allowedLocations): array - { - $result = $allowedLocations; - foreach ($tree as $location) { - if (array_key_exists("children", $location)) { - if (isset($allowedLocations[$location["locationid"]])) { - $result += array_flip(Location::extractIds($location["children"])); - } else { - $result += array_flip(self::getSublocations($location["children"], $allowedLocations)); - } + $allowedLocations = array_keys($allowedLocations); + foreach ($allowedLocations as $lid) { + $allowedLocations = array_merge($allowedLocations, Location::getAllLocationIds($lid)); } } - return array_keys($result); + return $allowedLocations; } /** diff --git a/modules-available/roomplanner/inc/pvsgenerator.inc.php b/modules-available/roomplanner/inc/pvsgenerator.inc.php index f3c5c838..3a699a60 100644 --- a/modules-available/roomplanner/inc/pvsgenerator.inc.php +++ b/modules-available/roomplanner/inc/pvsgenerator.inc.php @@ -180,7 +180,7 @@ class PvsGenerator public static function getManagerName(int $locationId): ?string { $names = Location::getNameChain($locationId); - if ($names === false) + if ($names === null) return null; return implode(' / ', $names); } diff --git a/modules-available/roomplanner/inc/room.inc.php b/modules-available/roomplanner/inc/room.inc.php index 880cb6d0..c37dc24a 100644 --- a/modules-available/roomplanner/inc/room.inc.php +++ b/modules-available/roomplanner/inc/room.inc.php @@ -96,9 +96,8 @@ abstract class Room public function __construct($row) { - $locations = Location::getLocationsAssoc(); $this->locationId = (int)$row['locationid']; - $this->locationName = $locations[$this->locationId]['locationname']; + $this->locationName = Location::getName($this->locationId); } /** diff --git a/modules-available/serversetup-bwlp-ipxe/inc/ipxemenu.inc.php b/modules-available/serversetup-bwlp-ipxe/inc/ipxemenu.inc.php index 759f4cad..fd9de9ed 100644 --- a/modules-available/serversetup-bwlp-ipxe/inc/ipxemenu.inc.php +++ b/modules-available/serversetup-bwlp-ipxe/inc/ipxemenu.inc.php @@ -179,7 +179,7 @@ class IPxeMenu { $locationId = 0; if (Module::isAvailable('locations')) { - $locationId = Location::getFromIpAndUuid($ip, $uuid); + $locationId = Location::getFromIpAndUuid($ip, $uuid) ?? 0; } $menu = self::forLocation($locationId); if ($uuid !== null) { diff --git a/modules-available/statistics/api.inc.php b/modules-available/statistics/api.inc.php index 8bc2c5ef..8f0f0810 100644 --- a/modules-available/statistics/api.inc.php +++ b/modules-available/statistics/api.inc.php @@ -171,7 +171,7 @@ if ($type[0] === '~') { if (($old === false || $old['clientip'] !== $ip) && Module::isAvailable('locations')) { // New, or ip changed (dynamic pool?), update subnetlicationid $loc = Location::updateMapIpToLocation($uuid, $ip); - $new['locationid'] = $loc; // For Filter Event + $new['locationid'] = $loc ?? 0; // For Filter Event } if ($json !== false) { diff --git a/modules-available/statistics/inc/statisticsfilter.inc.php b/modules-available/statistics/inc/statisticsfilter.inc.php index dbbb39e1..72b0cbf9 100644 --- a/modules-available/statistics/inc/statisticsfilter.inc.php +++ b/modules-available/statistics/inc/statisticsfilter.inc.php @@ -585,7 +585,7 @@ class LocationStatisticsFilter extends EnumStatisticsFilter ErrorHandler::traceError('Cannot use ~ operator for location with array'); } if ($recursive) { - $argument = array_keys(Location::getRecursiveFlat($argument)); + $argument = Location::getAllLocationIds((int)$argument, true); } elseif ($argument == 0) { return 'locationid IS ' . ($operator === '!=' ? 'NOT' : '') . ' NULL'; } diff --git a/modules-available/statistics/pages/summary.inc.php b/modules-available/statistics/pages/summary.inc.php index 5d6a58a8..af0e1e6a 100644 --- a/modules-available/statistics/pages/summary.inc.php +++ b/modules-available/statistics/pages/summary.inc.php @@ -78,7 +78,14 @@ class SubPage $locations = null; $op = null; } elseif ($locFilter->op === '~') { - $locations = array_keys(Location::getRecursiveFlat($locFilter->argument)); + if (!is_array($locFilter->argument)) { + $locations = Location::getAllLocationIds((int)$locFilter->argument, true); + } else { + $locations = []; + foreach ($locFilter->argument as $lid) { + $locations = array_merge($locations, Location::getAllLocationIds((int)$lid, true)); + } + } $op = $locFilter->op; } else { if (is_array($locFilter->argument)) { diff --git a/modules-available/sysconfig/api.inc.php b/modules-available/sysconfig/api.inc.php index 90383240..209bb758 100644 --- a/modules-available/sysconfig/api.inc.php +++ b/modules-available/sysconfig/api.inc.php @@ -24,15 +24,15 @@ function deliverEmpty($message) die('Config file could not be found or read!'); } -$locationId = false; +$locationId = null; if (Module::isAvailable('locations')) { $locationId = Location::getFromIpAndUuid($ip, $uuid); - if ($locationId !== false) { + if ($locationId !== null) { $locationChain = Location::getLocationRootChain($locationId); $locationChain[] = 0; } } -if ($locationId === false) { +if ($locationId === null) { $locationId = 0; $locationChain = array(0); } |
