denyActionIfBuiltin($id); PermissionDbUpdate::deleteRole($id); } elseif ($action === 'saveRole') { User::assertPermission('roles.edit'); $roleID = Request::post("roleid", Request::REQUIRED_EMPTY, 'int'); $this->denyActionIfBuiltin($roleID); $roleName = Request::post("rolename", '', 'string'); if (empty($roleName)) { Message::addError('main.parameter-empty', 'rolename'); Util::redirect('?do=permissionmanager'); } $roleDescription = Request::post('roledescription', '', 'string'); $locations = self::processLocations(Request::post("locations", [], 'array')); $permissions = self::processPermissions(Request::post("permissions")); PermissionDbUpdate::saveRole($roleName, $roleDescription, $locations, $permissions, $roleID); } if (Request::isPost()) { Util::redirect('?do=permissionmanager&show=' . Request::get("show", "roles")); } } /** * Menu etc. has already been generated, now it's time to generate page content. */ protected function doRender() { $show = Request::get("show", false, 'string'); // "Public" page -- nice "permission denied" message if ($show === 'denied') { Render::addTemplate('page-permission-denied', [ 'name' => User::getName(), 'permission' => Request::get('permission', false, 'string'), ]); return; } if ($show === false) { foreach (['roles', 'users', 'locations'] as $show) { if (User::hasPermission($show . '.*')) break; } } // switch between tables, but always show menu to switch tables // get menu button colors $data = array(); if ($show === "roleEditor") { $data['groupClass'] = 'btn-group-muted'; $data['rolesButtonClass'] = 'active'; } else { $data[$show . 'ButtonClass'] = 'active'; } Permission::addGlobalTags($data['perms'], null, ['roles.*', 'users.*', 'locations.*']); Render::addtemplate('header-menu', $data); if ($show === "roles") { User::assertPermission('roles.*'); $data = array("roles" => GetPermissionData::getRoles(GetPermissionData::WITH_USER_COUNT)); Permission::addGlobalTags($data['perms'], null, ['roles.edit']); Render::addTemplate('rolestable', $data); } elseif ($show === "users") { User::assertPermission('users.*'); $data = array("user" => GetPermissionData::getUserData()); if (User::hasPermission('users.edit-roles')) { $data['allroles'] = GetPermissionData::getRoles(); } Permission::addGlobalTags($data['perms'], null, ['users.edit-roles']); Render::addTemplate('role-filter-selectize', $data); Render::addTemplate('userstable', $data); } elseif ($show === "locations") { User::assertPermission('locations.*'); $data = array("location" => GetPermissionData::getLocationData(), "allroles" => GetPermissionData::getRoles()); Render::addTemplate('role-filter-selectize', $data); Render::addTemplate('locationstable', $data); } elseif ($show === "roleEditor") { User::assertPermission('roles.*'); $data = array("cancelShow" => Request::get("cancel", "roles", 'string')); Permission::addGlobalTags($data['perms'], null, ['roles.edit']); $selectedPermissions = array(); $selectedLocations = array(); $roleid = Request::get("roleid", false, 'int'); if ($roleid !== false) { $role = GetPermissionData::getRoleData($roleid); if ($role === null) { Message::addError('invalid-role-id', $roleid); Util::redirect('?do=permissionmanager'); } if ($role['builtin']) { // Copy the role, as it's builtin $role['roleid'] = ''; $role['rolename'] .= ' (2)'; } $data += $role; $selectedPermissions = $data["permissions"]; $selectedLocations = $data["locations"]; } $data["permissionHTML"] = self::generatePermissionHTML(PermissionUtil::getPermissions(), $selectedPermissions, false, '', ['perms' => $data['perms']]); $data["locationHTML"] = self::generateLocationHTML(Location::getTree(), $selectedLocations, $roleid === false, true, ['perms' => $data['perms']]); Render::addTemplate('roleeditor', $data); } } /** * Recursively generate HTML code for the permission selection tree. * * @param array $permissions the permission tree * @param array $selectedPermissions permissions that should be preselected * @param bool $selectAll true if all permissions should be preselected, false if only those in $selectedPermissions * @param string $permString the prefix permission string with which all permissions in the permission tree should start * @return string generated html code */ private static function generatePermissionHTML(array $permissions, array $selectedPermissions = [], bool $selectAll = false, string $permString = "", array $tags = []): string { $res = ""; $toplevel = $permString == ""; if ($toplevel && in_array("*", $selectedPermissions)) { $selectAll = true; } foreach ($permissions as $k => $v) { $selected = $selectAll; $nextPermString = $permString ? $permString . "." . $k : $k; if ($toplevel) { $displayName = Module::get($k)->getDisplayName(); } else { $displayName = $k; } do { $leaf = isset($v['isLeaf']) && $v['isLeaf']; $id = $leaf ? $nextPermString : $nextPermString . ".*"; $selected = $selected || in_array($id, $selectedPermissions); if ($leaf || count($v) !== 1) break; reset($v); $k = key($v); $v = $v[$k]; $nextPermString .= '.' . $k; $displayName .= '.' . $k; } while (true); $data = array( "id" => $id, "name" => $displayName, "toplevel" => $toplevel, "checkboxname" => "permissions", "selected" => $selected, "HTML" => $leaf ? "" : self::generatePermissionHTML($v, $selectedPermissions, $selected, $nextPermString, $tags), ); if ($leaf) { $data += $v; } $res .= Render::parse("treenode", $data + $tags); } if ($toplevel) { $res = Render::parse("treepanel", array("id" => "*", "name" => Dictionary::translateFile("template-tags", "lang_permissions"), "checkboxname" => "permissions", "selected" => $selectAll, "HTML" => $res) + $tags); } return $res; } /** * Recursively generate HTML code for the location selection tree. * * @param array $locations the location tree * @param array $selectedLocations locations that should be preselected * @param bool $selectAll true if all locations should be preselected, false if only those in $selectedLocations * @param bool $toplevel true if the location tree are the children of the root location, false if not * @return string generated html code */ private static function generateLocationHTML(array $locations, array $selectedLocations = [], bool $selectAll = false, bool $toplevel = true, array $tags = []): string { $res = ""; if ($toplevel && in_array(0, $selectedLocations)) { $selectAll = true; } foreach ($locations as $location) { $selected = $selectAll || in_array($location["locationid"], $selectedLocations); $res .= Render::parse("treenode", array("id" => $location["locationid"], "name" => $location["locationname"], "toplevel" => $toplevel, "checkboxname" => "locations", "selected" => $selected, "HTML" => array_key_exists("children", $location) ? self::generateLocationHTML($location["children"], $selectedLocations, $selected, false, $tags) : "") + $tags); } if ($toplevel) { $res = Render::parse("treepanel", array("id" => 0, "name" => Dictionary::translateFile("template-tags", "lang_locations"), "checkboxname" => "locations", "selected" => $selectAll, "HTML" => $res) + $tags); } return $res; } /** * Remove locations that are already covered by parent locations from the array. * * @param array $locations the locationid array * @return array the locationid array without redundant locationids */ private static function processLocations(array $locations): array { if (in_array(0, $locations)) return array(null); $result = array(); foreach ($locations as $location) { $rootchain = array_reverse(Location::getLocationRootChain($location)); foreach ($rootchain as $l) { if (in_array($l, $result)) break; if (in_array($l, $locations)) { $result[] = $l; break; } } } return $result; } /** * Remove permissions that are already covered by parent permissions from the array. * * @param array $permissions the permissionid array * @return array the permissionid array without redundant permissionids */ private static function processPermissions(array $permissions): array { if (in_array("*", $permissions)) return array("*"); $result = array(); foreach ($permissions as $permission) { $x =& $result; foreach (explode(".", $permission) as $p) { $x =& $x[$p]; } } return self::extractPermissions($result); } /** * Convert a multidimensional array of permissions to a flat array of permissions. * * @param array $permissions multidimensional array of permissionids * @return array flat array of permissionids */ private static function extractPermissions(array $permissions): array { $result = array(); foreach ($permissions as $permission => $a) { if (is_array($a)) { if (array_key_exists("*", $a)) { $result[] = $permission . ".*"; } else { foreach (self::extractPermissions($a) as $subPermission) { $result[] = $permission . "." . $subPermission; } } } else { $result[] = $permission; } } return $result; } private function denyActionIfBuiltin(string $roleID): void { if ($roleID) { $existing = GetPermissionData::getRole($roleID); if ($existing === false) { Message::addError('invalid-role-id', $roleID); Util::redirect('?do=permissionmanager'); } if ($existing['builtin']) { Message::addError('builtin-role', $existing['rolename']); Util::redirect('?do=permissionmanager'); } } } }