diff options
author | Simon Rettberg | 2025-07-07 13:37:19 +0200 |
---|---|---|
committer | Simon Rettberg | 2025-07-07 13:37:19 +0200 |
commit | 9a5106c288519b008e0dfe5e85371701af32c0f3 (patch) | |
tree | fcfdc6efaf16d2ecefb2bc5822324e9324d6f29f | |
parent | [locations] Copy permissions to children when deleting location (diff) | |
download | slx-admin-9a5106c288519b008e0dfe5e85371701af32c0f3.tar.gz slx-admin-9a5106c288519b008e0dfe5e85371701af32c0f3.tar.xz slx-admin-9a5106c288519b008e0dfe5e85371701af32c0f3.zip |
[locations] Cleanup Location class
There was a mess of functions which mostly, but not quite, did the same
things. Get rid of a couple of them and fix call sites to use
alternative ones that also fit the job.
While at it, add phpdoc and comments to the remaining functions, trying
to clarify what they were designed for.
Lastly, the return type of functions that retrieve a location id has
been changed from false|int to ?int (nullable types are just nicer).
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); } |