Rewrote user role handling.
This commit is contained in:
parent
26a0e11253
commit
461ffbf73b
22 changed files with 710 additions and 536 deletions
|
@ -3,7 +3,6 @@ namespace Misuzu;
|
||||||
|
|
||||||
use RuntimeException;
|
use RuntimeException;
|
||||||
use Misuzu\Users\User;
|
use Misuzu\Users\User;
|
||||||
use Misuzu\Users\UserRole;
|
|
||||||
use Misuzu\Users\UserSession;
|
use Misuzu\Users\UserSession;
|
||||||
|
|
||||||
if(UserSession::hasCurrent()) {
|
if(UserSession::hasCurrent()) {
|
||||||
|
@ -11,6 +10,9 @@ if(UserSession::hasCurrent()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$users = $msz->getUsers();
|
||||||
|
$roles = $msz->getRoles();
|
||||||
|
|
||||||
$register = !empty($_POST['register']) && is_array($_POST['register']) ? $_POST['register'] : [];
|
$register = !empty($_POST['register']) && is_array($_POST['register']) ? $_POST['register'] : [];
|
||||||
$notices = [];
|
$notices = [];
|
||||||
$ipAddress = $_SERVER['REMOTE_ADDR'];
|
$ipAddress = $_SERVER['REMOTE_ADDR'];
|
||||||
|
@ -95,7 +97,7 @@ while(!$restricted && !empty($register)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
$createUser->addRole(UserRole::byDefault());
|
$users->addRoles($createUser, $roles->getDefaultRole());
|
||||||
|
|
||||||
url_redirect('auth-login-welcome', ['username' => $createUser->getUsername()]);
|
url_redirect('auth-login-welcome', ['username' => $createUser->getUsername()]);
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -5,22 +5,25 @@ use RuntimeException;
|
||||||
use Index\Colour\Colour;
|
use Index\Colour\Colour;
|
||||||
use Index\Colour\ColourRGB;
|
use Index\Colour\ColourRGB;
|
||||||
use Misuzu\Users\User;
|
use Misuzu\Users\User;
|
||||||
use Misuzu\Users\UserRole;
|
|
||||||
|
|
||||||
if(!User::hasCurrent() || !perms_check_user(MSZ_PERMS_USER, User::getCurrent()->getId(), MSZ_PERM_USER_MANAGE_ROLES)) {
|
if(!User::hasCurrent() || !perms_check_user(MSZ_PERMS_USER, User::getCurrent()->getId(), MSZ_PERM_USER_MANAGE_ROLES)) {
|
||||||
echo render_error(403);
|
echo render_error(403);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$roleId = (int)filter_input(INPUT_GET, 'r', FILTER_SANITIZE_NUMBER_INT);
|
$roles = $msz->getRoles();
|
||||||
|
|
||||||
|
if(filter_has_var(INPUT_GET, 'r')) {
|
||||||
|
$roleId = (string)filter_input(INPUT_GET, 'r', FILTER_SANITIZE_NUMBER_INT);
|
||||||
|
|
||||||
if($roleId > 0)
|
|
||||||
try {
|
try {
|
||||||
$roleInfo = UserRole::byId($roleId);
|
$isNew = false;
|
||||||
|
$roleInfo = $roles->getRole($roleId);
|
||||||
} catch(RuntimeException $ex) {
|
} catch(RuntimeException $ex) {
|
||||||
echo render_error(404);
|
echo render_error(404);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
} else $isNew = true;
|
||||||
|
|
||||||
$currentUser = User::getCurrent();
|
$currentUser = User::getCurrent();
|
||||||
$currentUserId = $currentUser->getId();
|
$currentUserId = $currentUser->getId();
|
||||||
|
@ -29,70 +32,92 @@ $canEditPerms = perms_check_user(MSZ_PERMS_USER, $currentUserId, MSZ_PERM_USER_M
|
||||||
if($canEditPerms)
|
if($canEditPerms)
|
||||||
$permissions = manage_perms_list(perms_get_role_raw($roleId ?? 0));
|
$permissions = manage_perms_list(perms_get_role_raw($roleId ?? 0));
|
||||||
|
|
||||||
if(!empty($_POST['role']) && is_array($_POST['role']) && CSRF::validateRequest()) {
|
while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
|
||||||
$roleHierarchy = (int)($_POST['role']['hierarchy'] ?? -1);
|
if(!$isNew && !$currentUser->isSuper() && $roleInfo->getRank() >= $currentUser->getRank()) {
|
||||||
|
echo 'You aren\'t allowed to edit this role.';
|
||||||
if(!$currentUser->isSuper() && (isset($roleInfo) ? $roleInfo->hasAuthorityOver($currentUser) : $currentUser->getRank() <= $roleHierarchy)) {
|
break;
|
||||||
echo 'You don\'t hold authority over this role.';
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$roleName = $_POST['role']['name'] ?? '';
|
$roleName = (string)filter_input(INPUT_POST, 'ur_name');
|
||||||
$roleNameLength = strlen($roleName);
|
$roleHide = !empty($_POST['ur_hidden']);
|
||||||
|
$roleLeavable = !empty($_POST['ur_leavable']);
|
||||||
|
$roleRank = (int)filter_input(INPUT_POST, 'ur_rank', FILTER_SANITIZE_NUMBER_INT);
|
||||||
|
$roleTitle = (string)filter_input(INPUT_POST, 'ur_title');
|
||||||
|
$roleDesc = (string)filter_input(INPUT_POST, 'ur_desc');
|
||||||
|
$colourInherit = !empty($_POST['ur_col_inherit']);
|
||||||
|
$colourRed = (int)filter_input(INPUT_POST, 'ur_col_red', FILTER_SANITIZE_NUMBER_INT);
|
||||||
|
$colourGreen = (int)filter_input(INPUT_POST, 'ur_col_green', FILTER_SANITIZE_NUMBER_INT);
|
||||||
|
$colourBlue = (int)filter_input(INPUT_POST, 'ur_col_blue', FILTER_SANITIZE_NUMBER_INT);
|
||||||
|
|
||||||
if($roleNameLength < 1 || $roleNameLength > 255) {
|
Template::set([
|
||||||
echo 'invalid name length';
|
'role_ur_name' => $roleName,
|
||||||
return;
|
'role_ur_hidden' => $roleHide,
|
||||||
|
'role_ur_leavable' => $roleLeavable,
|
||||||
|
'role_ur_rank' => $roleRank,
|
||||||
|
'role_ur_title' => $roleTitle,
|
||||||
|
'role_ur_desc' => $roleDesc,
|
||||||
|
'role_ur_col_inherit' => $colourInherit,
|
||||||
|
'role_ur_col_red' => $colourRed,
|
||||||
|
'role_ur_col_green' => $colourGreen,
|
||||||
|
'role_ur_col_blue' => $colourBlue,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if(!$currentUser->isSuper() && $roleRank >= $currentUser->getRank()) {
|
||||||
|
echo 'You aren\'t allowed to make a role with equal rank to your own.';
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
$roleSecret = !empty($_POST['role']['secret']);
|
$roleNameLength = mb_strlen($roleName);
|
||||||
|
if($roleNameLength < 1 || $roleNameLength > 100) {
|
||||||
if($roleHierarchy < 1 || $roleHierarchy > 100) {
|
echo 'Provided role name is either too long or too short.';
|
||||||
echo 'Invalid hierarchy value.';
|
break;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!empty($_POST['role']['colour']['inherit'])) {
|
if($roleRank < 1 || $roleRank > 100) {
|
||||||
$roleColour = \Index\Colour\Colour::none();
|
echo 'Role rank may not be less than 1 or more than 100.';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$roleColour = $colourInherit
|
||||||
|
? Colour::none()
|
||||||
|
: new ColourRGB($colourRed, $colourGreen, $colourBlue);
|
||||||
|
|
||||||
|
if(mb_strlen($roleDesc) > 1000) {
|
||||||
|
echo 'Description may not be longer than 1000 characters.';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mb_strlen($roleTitle) > 64) {
|
||||||
|
echo 'Role title may not be longer than 64 characters.';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if($isNew) {
|
||||||
|
$roleInfo = $roles->createRole($roleName, $roleRank, $roleColour, $roleTitle, $roleDesc, $roleHide, $roleLeavable);
|
||||||
} else {
|
} else {
|
||||||
$roleColour = new ColourRGB(
|
if($roleName === $roleInfo->getName())
|
||||||
(int)($_POST['role']['colour']['red'] ?? -1),
|
$roleName = null;
|
||||||
(int)($_POST['role']['colour']['green'] ?? -1),
|
if($roleHide === $roleInfo->isHidden())
|
||||||
(int)($_POST['role']['colour']['blue'] ?? -1)
|
$roleHide = null;
|
||||||
|
if($roleLeavable === $roleInfo->isLeavable())
|
||||||
|
$roleLeavable = null;
|
||||||
|
if($roleRank === $roleInfo->getRank())
|
||||||
|
$roleRank = null;
|
||||||
|
if($roleTitle === $roleInfo->getTitle())
|
||||||
|
$roleTitle = null;
|
||||||
|
if($roleDesc === $roleInfo->getDescription())
|
||||||
|
$roleDesc = null;
|
||||||
|
// local genius did not implement colour comparison
|
||||||
|
if((string)$roleColour === (string)$roleInfo->getColour())
|
||||||
|
$roleColour = null;
|
||||||
|
|
||||||
|
$roles->updateRole($roleInfo, $roleName, $roleRank, $roleColour, $roleTitle, $roleDesc, $roleHide, $roleLeavable);
|
||||||
|
}
|
||||||
|
|
||||||
|
$msz->createAuditLog(
|
||||||
|
$isNew ? 'ROLE_CREATE' : 'ROLE_UPDATE',
|
||||||
|
[$roleInfo->getId()]
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
$roleDescription = $_POST['role']['description'] ?? '';
|
|
||||||
$roleTitle = $_POST['role']['title'] ?? '';
|
|
||||||
|
|
||||||
if($roleDescription !== null) {
|
|
||||||
$rdLength = strlen($roleDescription);
|
|
||||||
|
|
||||||
if($rdLength > 1000) {
|
|
||||||
echo 'description is too long';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if($roleTitle !== null) {
|
|
||||||
$rtLength = strlen($roleTitle);
|
|
||||||
|
|
||||||
if($rtLength > 64) {
|
|
||||||
echo 'title is too long';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!isset($roleInfo))
|
|
||||||
$roleInfo = new UserRole;
|
|
||||||
|
|
||||||
$roleInfo->setName($roleName)
|
|
||||||
->setRank($roleHierarchy)
|
|
||||||
->setHidden($roleSecret)
|
|
||||||
->setColour($roleColour)
|
|
||||||
->setDescription($roleDescription)
|
|
||||||
->setTitle($roleTitle)
|
|
||||||
->save();
|
|
||||||
|
|
||||||
if(!empty($permissions) && !empty($_POST['perms']) && is_array($_POST['perms'])) {
|
if(!empty($permissions) && !empty($_POST['perms']) && is_array($_POST['perms'])) {
|
||||||
$perms = manage_perms_apply($permissions, $_POST['perms']);
|
$perms = manage_perms_apply($permissions, $_POST['perms']);
|
||||||
|
@ -100,10 +125,8 @@ if(!empty($_POST['role']) && is_array($_POST['role']) && CSRF::validateRequest()
|
||||||
if($perms !== null) {
|
if($perms !== null) {
|
||||||
$permKeys = array_keys($perms);
|
$permKeys = array_keys($perms);
|
||||||
$setPermissions = DB::prepare('
|
$setPermissions = DB::prepare('
|
||||||
REPLACE INTO `msz_permissions`
|
REPLACE INTO `msz_permissions` (`role_id`, `user_id`, `' . implode('`, `', $permKeys) . '`)
|
||||||
(`role_id`, `user_id`, `' . implode('`, `', $permKeys) . '`)
|
VALUES (:role_id, NULL, :' . implode(', :', $permKeys) . ')
|
||||||
VALUES
|
|
||||||
(:role_id, NULL, :' . implode(', :', $permKeys) . ')
|
|
||||||
');
|
');
|
||||||
$setPermissions->bind('role_id', $roleInfo->getId());
|
$setPermissions->bind('role_id', $roleInfo->getId());
|
||||||
|
|
||||||
|
@ -113,11 +136,7 @@ if(!empty($_POST['role']) && is_array($_POST['role']) && CSRF::validateRequest()
|
||||||
|
|
||||||
$setPermissions->execute();
|
$setPermissions->execute();
|
||||||
} else {
|
} else {
|
||||||
$deletePermissions = DB::prepare('
|
$deletePermissions = DB::prepare('DELETE FROM `msz_permissions` WHERE `role_id` = :role_id AND `user_id` IS NULL');
|
||||||
DELETE FROM `msz_permissions`
|
|
||||||
WHERE `role_id` = :role_id
|
|
||||||
AND `user_id` IS NULL
|
|
||||||
');
|
|
||||||
$deletePermissions->bind('role_id', $roleInfo->getId());
|
$deletePermissions->bind('role_id', $roleInfo->getId());
|
||||||
$deletePermissions->execute();
|
$deletePermissions->execute();
|
||||||
}
|
}
|
||||||
|
@ -128,6 +147,7 @@ if(!empty($_POST['role']) && is_array($_POST['role']) && CSRF::validateRequest()
|
||||||
}
|
}
|
||||||
|
|
||||||
Template::render('manage.users.role', [
|
Template::render('manage.users.role', [
|
||||||
|
'role_new' => $isNew,
|
||||||
'role_info' => $roleInfo ?? null,
|
'role_info' => $roleInfo ?? null,
|
||||||
'can_manage_perms' => $canEditPerms,
|
'can_manage_perms' => $canEditPerms,
|
||||||
'permissions' => $permissions ?? [],
|
'permissions' => $permissions ?? [],
|
||||||
|
|
|
@ -2,21 +2,30 @@
|
||||||
namespace Misuzu;
|
namespace Misuzu;
|
||||||
|
|
||||||
use Misuzu\Users\User;
|
use Misuzu\Users\User;
|
||||||
use Misuzu\Users\UserRole;
|
|
||||||
|
|
||||||
if(!User::hasCurrent() || !perms_check_user(MSZ_PERMS_USER, User::getCurrent()->getId(), MSZ_PERM_USER_MANAGE_ROLES)) {
|
if(!User::hasCurrent() || !perms_check_user(MSZ_PERMS_USER, User::getCurrent()->getId(), MSZ_PERM_USER_MANAGE_ROLES)) {
|
||||||
echo render_error(403);
|
echo render_error(403);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$pagination = new Pagination(UserRole::countAll(true), 10);
|
$roles = $msz->getRoles();
|
||||||
|
$pagination = new Pagination($roles->countRoles(), 10);
|
||||||
|
|
||||||
if(!$pagination->hasValidOffset()) {
|
if(!$pagination->hasValidOffset()) {
|
||||||
echo render_error(404);
|
echo render_error(404);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$rolesAll = [];
|
||||||
|
$roleInfos = $roles->getRoles(pagination: $pagination);
|
||||||
|
|
||||||
|
foreach($roleInfos as $roleInfo)
|
||||||
|
$rolesAll[] = [
|
||||||
|
'info' => $roleInfo,
|
||||||
|
'members' => $roles->countRoleUsers($roleInfo),
|
||||||
|
];
|
||||||
|
|
||||||
Template::render('manage.users.roles', [
|
Template::render('manage.users.roles', [
|
||||||
'manage_roles' => UserRole::all(true, $pagination),
|
'manage_roles' => $rolesAll,
|
||||||
'manage_roles_pagination' => $pagination,
|
'manage_roles_pagination' => $pagination,
|
||||||
]);
|
]);
|
||||||
|
|
|
@ -4,13 +4,15 @@ namespace Misuzu;
|
||||||
use RuntimeException;
|
use RuntimeException;
|
||||||
use Index\Colour\Colour;
|
use Index\Colour\Colour;
|
||||||
use Misuzu\Users\User;
|
use Misuzu\Users\User;
|
||||||
use Misuzu\Users\UserRole;
|
|
||||||
|
|
||||||
if(!User::hasCurrent()) {
|
if(!User::hasCurrent()) {
|
||||||
echo render_error(403);
|
echo render_error(403);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$users = $msz->getUsers();
|
||||||
|
$roles = $msz->getRoles();
|
||||||
|
|
||||||
$currentUser = User::getCurrent();
|
$currentUser = User::getCurrent();
|
||||||
$currentUserId = $currentUser->getId();
|
$currentUserId = $currentUser->getId();
|
||||||
|
|
||||||
|
@ -77,60 +79,53 @@ if(CSRF::validateRequest() && $canEdit) {
|
||||||
foreach($_POST['roles'] as $item) {
|
foreach($_POST['roles'] as $item) {
|
||||||
if(!ctype_digit($item))
|
if(!ctype_digit($item))
|
||||||
die('Invalid item encountered in roles list.');
|
die('Invalid item encountered in roles list.');
|
||||||
$applyRoles[] = (int)$item;
|
$applyRoles[] = (string)$item;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch existing roles
|
$existingRoles = [];
|
||||||
$existingRoles = $userInfo->getRoles();
|
foreach($roles->getRoles(userInfo: $userInfo) as $roleInfo)
|
||||||
|
$existingRoles[$roleInfo->getId()] = $roleInfo;
|
||||||
|
|
||||||
// Initialise set array with existing roles
|
|
||||||
$setRoles = $existingRoles;
|
|
||||||
|
|
||||||
// Storage array for roles to dump
|
|
||||||
$removeRoles = [];
|
$removeRoles = [];
|
||||||
|
|
||||||
// STEP 1: Check for roles to be removed in the existing set.
|
foreach($existingRoles as $roleInfo) {
|
||||||
// Roles that the current users isn't allowed to touch (hierarchy) will stay.
|
if($roleInfo->isDefault() || !($currentUser->isSuper() || $currentUser->getRank() > $roleInfo->getRank()))
|
||||||
foreach($setRoles as $role) {
|
|
||||||
// Also prevent the main role from being removed.
|
|
||||||
if($role->isDefault() || !$currentUser->hasAuthorityOver($role))
|
|
||||||
continue;
|
continue;
|
||||||
if(!in_array($role->getId(), $applyRoles))
|
|
||||||
$removeRoles[] = $role;
|
if(!in_array($roleInfo->getId(), $applyRoles))
|
||||||
|
$removeRoles[] = $roleInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
// STEP 2: Purge the ones marked for removal.
|
if(!empty($removeRoles))
|
||||||
$setRoles = array_diff($setRoles, $removeRoles);
|
$users->removeRoles($userInfo, $removeRoles);
|
||||||
|
|
||||||
|
$addRoles = [];
|
||||||
|
|
||||||
// STEP 3: Add roles to the set array from the user input, if the user has authority over the given roles.
|
|
||||||
foreach($applyRoles as $roleId) {
|
foreach($applyRoles as $roleId) {
|
||||||
try {
|
try {
|
||||||
$role = $existingRoles[$roleId] ?? UserRole::byId($roleId);
|
$roleInfo = $existingRoles[$roleId] ?? $roles->getRole($roleId);
|
||||||
} catch(RuntimeException $ex) {
|
} catch(RuntimeException $ex) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if(!$currentUser->hasAuthorityOver($role))
|
|
||||||
|
if(!$currentUser->isSuper() && $currentUser->getRank() <= $roleInfo->getRank())
|
||||||
continue;
|
continue;
|
||||||
if(!in_array($role, $setRoles))
|
|
||||||
$setRoles[] = $role;
|
if(!in_array($roleInfo, $existingRoles))
|
||||||
|
$addRoles[] = $roleInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach($removeRoles as $role)
|
if(!empty($addRoles))
|
||||||
$userInfo->removeRole($role);
|
$users->addRoles($userInfo, $addRoles);
|
||||||
|
|
||||||
foreach($setRoles as $role)
|
|
||||||
$userInfo->addRole($role);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!empty($_POST['user']) && is_array($_POST['user'])) {
|
if(!empty($_POST['user']) && is_array($_POST['user'])) {
|
||||||
$setCountry = (string)($_POST['user']['country'] ?? '');
|
$setCountry = (string)($_POST['user']['country'] ?? '');
|
||||||
$setTitle = (string)($_POST['user']['title'] ?? '');
|
$setTitle = (string)($_POST['user']['title'] ?? '');
|
||||||
|
|
||||||
$displayRole = (int)($_POST['user']['display_role'] ?? 0);
|
$displayRole = (string)($_POST['user']['display_role'] ?? 0);
|
||||||
|
if(!$users->hasRole($userInfo, $displayRole))
|
||||||
try {
|
$notices[] = 'User does not have the role you\'re trying to assign as primary.';
|
||||||
$userInfo->setDisplayRole(UserRole::byId($displayRole));
|
|
||||||
} catch(RuntimeException $ex) {}
|
|
||||||
|
|
||||||
$countryValidation = strlen($setCountry) === 2
|
$countryValidation = strlen($setCountry) === 2
|
||||||
&& ctype_alpha($setCountry)
|
&& ctype_alpha($setCountry)
|
||||||
|
@ -142,10 +137,15 @@ if(CSRF::validateRequest() && $canEdit) {
|
||||||
if(strlen($setTitle) > 64)
|
if(strlen($setTitle) > 64)
|
||||||
$notices[] = 'User title was invalid.';
|
$notices[] = 'User title was invalid.';
|
||||||
|
|
||||||
if(empty($notices))
|
if(empty($notices)) {
|
||||||
|
$users->updateUser(
|
||||||
|
userInfo: $userInfo,
|
||||||
|
displayRoleInfo: $displayRole,
|
||||||
|
);
|
||||||
|
|
||||||
$userInfo->setCountry((string)($_POST['user']['country'] ?? ''))
|
$userInfo->setCountry((string)($_POST['user']['country'] ?? ''))
|
||||||
->setTitle((string)($_POST['user']['title'] ?? ''))
|
->setTitle((string)($_POST['user']['title'] ?? ''));
|
||||||
->setDisplayRole(UserRole::byId((int)($_POST['user']['display_role'] ?? 0)));
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!empty($_POST['colour']) && is_array($_POST['colour'])) {
|
if(!empty($_POST['colour']) && is_array($_POST['colour'])) {
|
||||||
|
@ -194,10 +194,14 @@ if(CSRF::validateRequest() && $canEdit) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$rolesAll = $roles->getRoles();
|
||||||
|
$userRoleIds = $users->hasRoles($userInfo, $rolesAll);
|
||||||
|
|
||||||
Template::render('manage.users.user', [
|
Template::render('manage.users.user', [
|
||||||
'user_info' => $userInfo,
|
'user_info' => $userInfo,
|
||||||
'manage_notices' => $notices,
|
'manage_notices' => $notices,
|
||||||
'manage_roles' => UserRole::all(true),
|
'manage_roles' => $rolesAll,
|
||||||
|
'manage_user_has_roles' => $userRoleIds,
|
||||||
'can_edit_user' => $canEdit,
|
'can_edit_user' => $canEdit,
|
||||||
'can_edit_perms' => $canEdit && $canEditPerms,
|
'can_edit_perms' => $canEdit && $canEditPerms,
|
||||||
'can_manage_notes' => $canManageNotes,
|
'can_manage_notes' => $canManageNotes,
|
||||||
|
|
|
@ -3,11 +3,12 @@ namespace Misuzu;
|
||||||
|
|
||||||
use RuntimeException;
|
use RuntimeException;
|
||||||
use Misuzu\Users\User;
|
use Misuzu\Users\User;
|
||||||
use Misuzu\Users\UserRole;
|
|
||||||
|
|
||||||
$roleId = !empty($_GET['r']) && is_string($_GET['r']) ? (int)$_GET['r'] : UserRole::DEFAULT;
|
$roles = $msz->getRoles();
|
||||||
$orderBy = !empty($_GET['ss']) && is_string($_GET['ss']) ? mb_strtolower($_GET['ss']) : '';
|
|
||||||
$orderDir = !empty($_GET['sd']) && is_string($_GET['sd']) ? mb_strtolower($_GET['sd']) : '';
|
$roleId = filter_has_var(INPUT_GET, 'r') ? (string)filter_input(INPUT_GET, 'r') : null;
|
||||||
|
$orderBy = strtolower((string)filter_input(INPUT_GET, 'ss'));
|
||||||
|
$orderDir = strtolower((string)filter_input(INPUT_GET, 'sd'));
|
||||||
|
|
||||||
$orderDirs = [
|
$orderDirs = [
|
||||||
'asc' => 'Ascending',
|
'asc' => 'Ascending',
|
||||||
|
@ -69,16 +70,20 @@ if(empty($orderDir)) {
|
||||||
|
|
||||||
$canManageUsers = perms_check_user(MSZ_PERMS_USER, User::hasCurrent() ? User::getCurrent()->getId() : 0, MSZ_PERM_USER_MANAGE_USERS);
|
$canManageUsers = perms_check_user(MSZ_PERMS_USER, User::hasCurrent() ? User::getCurrent()->getId() : 0, MSZ_PERM_USER_MANAGE_USERS);
|
||||||
|
|
||||||
try {
|
if($roleId === null) {
|
||||||
$roleInfo = UserRole::byId($roleId);
|
$roleInfo = $roles->getDefaultRole();
|
||||||
} catch(RuntimeException $ex) {
|
} else {
|
||||||
|
try {
|
||||||
|
$roleInfo = $roles->getRole($roleId);
|
||||||
|
} catch(RuntimeException $ex) {
|
||||||
echo render_error(404);
|
echo render_error(404);
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$pagination = new Pagination($roleInfo->getUserCount(), 15);
|
|
||||||
|
|
||||||
$roles = UserRole::all();
|
$pagination = new Pagination($roles->countRoleUsers($roleInfo), 15);
|
||||||
|
$rolesAll = $roles->getRoles(hidden: false);
|
||||||
|
|
||||||
$getUsers = DB::prepare(sprintf(
|
$getUsers = DB::prepare(sprintf(
|
||||||
'
|
'
|
||||||
|
@ -124,7 +129,7 @@ if(empty($users))
|
||||||
http_response_code(404);
|
http_response_code(404);
|
||||||
|
|
||||||
Template::render('user.listing', [
|
Template::render('user.listing', [
|
||||||
'roles' => $roles,
|
'roles' => $rolesAll,
|
||||||
'role' => $roleInfo,
|
'role' => $roleInfo,
|
||||||
'users' => $users,
|
'users' => $users,
|
||||||
'order_fields' => $orderFields,
|
'order_fields' => $orderFields,
|
||||||
|
|
|
@ -3,7 +3,6 @@ namespace Misuzu;
|
||||||
|
|
||||||
use RuntimeException;
|
use RuntimeException;
|
||||||
use Misuzu\Users\User;
|
use Misuzu\Users\User;
|
||||||
use Misuzu\Users\UserRole;
|
|
||||||
use Misuzu\Users\UserSession;
|
use Misuzu\Users\UserSession;
|
||||||
use chillerlan\QRCode\QRCode;
|
use chillerlan\QRCode\QRCode;
|
||||||
use chillerlan\QRCode\QROptions;
|
use chillerlan\QRCode\QROptions;
|
||||||
|
@ -14,6 +13,8 @@ if(!UserSession::hasCurrent()) {
|
||||||
}
|
}
|
||||||
|
|
||||||
$errors = [];
|
$errors = [];
|
||||||
|
$users = $msz->getUsers();
|
||||||
|
$roles = $msz->getRoles();
|
||||||
$currentUser = User::getCurrent();
|
$currentUser = User::getCurrent();
|
||||||
$currentUserId = $currentUser->getId();
|
$currentUserId = $currentUser->getId();
|
||||||
$isRestricted = $msz->hasActiveBan();
|
$isRestricted = $msz->hasActiveBan();
|
||||||
|
@ -21,20 +22,23 @@ $isVerifiedRequest = CSRF::validateRequest();
|
||||||
|
|
||||||
if(!$isRestricted && $isVerifiedRequest && !empty($_POST['role'])) {
|
if(!$isRestricted && $isVerifiedRequest && !empty($_POST['role'])) {
|
||||||
try {
|
try {
|
||||||
$roleInfo = UserRole::byId((int)($_POST['role']['id'] ?? 0));
|
$roleInfo = $roles->getRole(($_POST['role']['id'] ?? 0));
|
||||||
} catch(RuntimeException $ex) {}
|
} catch(RuntimeException $ex) {}
|
||||||
|
|
||||||
if(empty($roleInfo) || !$currentUser->hasRole($roleInfo))
|
if(empty($roleInfo) || !$users->hasRole($userInfo, $roleInfo))
|
||||||
$errors[] = "You're trying to modify a role that hasn't been assigned to you.";
|
$errors[] = "You're trying to modify a role that hasn't been assigned to you.";
|
||||||
else {
|
else {
|
||||||
switch($_POST['role']['mode'] ?? '') {
|
switch($_POST['role']['mode'] ?? '') {
|
||||||
case 'display':
|
case 'display':
|
||||||
$currentUser->setDisplayRole($roleInfo);
|
$users->updateUser(
|
||||||
|
$currentUser,
|
||||||
|
displayRoleInfo: $roleInfo
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'leave':
|
case 'leave':
|
||||||
if($roleInfo->getCanLeave())
|
if($roleInfo->isLeavable())
|
||||||
$currentUser->removeRole($roleInfo);
|
$users->removeRoles($currentUser, $roleInfo);
|
||||||
else
|
else
|
||||||
$errors[] = "You're not allow to leave this role, an administrator has to remove it for you.";
|
$errors[] = "You're not allow to leave this role, an administrator has to remove it for you.";
|
||||||
break;
|
break;
|
||||||
|
@ -124,11 +128,19 @@ if($isVerifiedRequest && !empty($_POST['current_password'])) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// THIS FUCKING SUCKS AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
// THIS FUCKING SUCKS AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||||
if($_SERVER['REQUEST_METHOD'] === 'POST' && $isVerifiedRequest)
|
if($_SERVER['REQUEST_METHOD'] === 'POST' && $isVerifiedRequest) {
|
||||||
$currentUser->save();
|
$currentUser->save();
|
||||||
|
|
||||||
|
// force a page refresh for now to deal with the User object and new shit desyncing
|
||||||
|
url_redirect('settings-account');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$userRoles = $roles->getRoles(userInfo: $currentUser);
|
||||||
|
|
||||||
Template::render('settings.account', [
|
Template::render('settings.account', [
|
||||||
'errors' => $errors,
|
'errors' => $errors,
|
||||||
'settings_user' => $currentUser,
|
'settings_user' => $currentUser,
|
||||||
|
'settings_roles' => $userRoles,
|
||||||
'is_restricted' => $isRestricted,
|
'is_restricted' => $isRestricted,
|
||||||
]);
|
]);
|
||||||
|
|
|
@ -132,5 +132,8 @@ class AuditLogInfo {
|
||||||
|
|
||||||
'WARN_CREATE' => 'Added warning #%d to user #%d.',
|
'WARN_CREATE' => 'Added warning #%d to user #%d.',
|
||||||
'WARN_DELETE' => 'Removed warning #%d from user #%d.',
|
'WARN_DELETE' => 'Removed warning #%d from user #%d.',
|
||||||
|
|
||||||
|
'ROLE_CREATE' => 'Created role #%d.',
|
||||||
|
'ROLE_UPDATE' => 'Updated role #%d.',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,9 @@ use Misuzu\SharpChat\SharpChatRoutes;
|
||||||
use Misuzu\Users\Bans;
|
use Misuzu\Users\Bans;
|
||||||
use Misuzu\Users\BanInfo;
|
use Misuzu\Users\BanInfo;
|
||||||
use Misuzu\Users\ModNotes;
|
use Misuzu\Users\ModNotes;
|
||||||
|
use Misuzu\Users\Roles;
|
||||||
use Misuzu\Users\User;
|
use Misuzu\Users\User;
|
||||||
|
use Misuzu\Users\Users;
|
||||||
use Misuzu\Users\Warnings;
|
use Misuzu\Users\Warnings;
|
||||||
use Index\Data\IDbConnection;
|
use Index\Data\IDbConnection;
|
||||||
use Index\Data\Migration\IDbMigrationRepo;
|
use Index\Data\Migration\IDbMigrationRepo;
|
||||||
|
@ -46,6 +48,8 @@ class MisuzuContext {
|
||||||
private Bans $bans;
|
private Bans $bans;
|
||||||
private Warnings $warnings;
|
private Warnings $warnings;
|
||||||
private TwoFactorAuthSessions $tfaSessions;
|
private TwoFactorAuthSessions $tfaSessions;
|
||||||
|
private Roles $roles;
|
||||||
|
private Users $users;
|
||||||
|
|
||||||
public function __construct(IDbConnection $dbConn, IConfig $config) {
|
public function __construct(IDbConnection $dbConn, IConfig $config) {
|
||||||
$this->dbConn = $dbConn;
|
$this->dbConn = $dbConn;
|
||||||
|
@ -61,6 +65,8 @@ class MisuzuContext {
|
||||||
$this->bans = new Bans($this->dbConn);
|
$this->bans = new Bans($this->dbConn);
|
||||||
$this->warnings = new Warnings($this->dbConn);
|
$this->warnings = new Warnings($this->dbConn);
|
||||||
$this->tfaSessions = new TwoFactorAuthSessions($this->dbConn);
|
$this->tfaSessions = new TwoFactorAuthSessions($this->dbConn);
|
||||||
|
$this->roles = new Roles($this->dbConn);
|
||||||
|
$this->users = new Users($this->dbConn);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getDbConn(): IDbConnection {
|
public function getDbConn(): IDbConnection {
|
||||||
|
@ -132,6 +138,14 @@ class MisuzuContext {
|
||||||
return $this->tfaSessions;
|
return $this->tfaSessions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getRoles(): Roles {
|
||||||
|
return $this->roles;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUsers(): Users {
|
||||||
|
return $this->users;
|
||||||
|
}
|
||||||
|
|
||||||
private array $activeBansCache = [];
|
private array $activeBansCache = [];
|
||||||
|
|
||||||
public function tryGetActiveBan(User|string|null $userInfo = null): ?BanInfo {
|
public function tryGetActiveBan(User|string|null $userInfo = null): ?BanInfo {
|
||||||
|
|
|
@ -172,6 +172,8 @@ class Bans {
|
||||||
public function deleteBans(BanInfo|string|array $banInfos): void {
|
public function deleteBans(BanInfo|string|array $banInfos): void {
|
||||||
if(!is_array($banInfos))
|
if(!is_array($banInfos))
|
||||||
$banInfos = [$banInfos];
|
$banInfos = [$banInfos];
|
||||||
|
elseif(empty($banInfos))
|
||||||
|
return;
|
||||||
|
|
||||||
$stmt = $this->cache->get(sprintf(
|
$stmt = $this->cache->get(sprintf(
|
||||||
'DELETE FROM msz_users_bans WHERE ban_id IN (%s)',
|
'DELETE FROM msz_users_bans WHERE ban_id IN (%s)',
|
||||||
|
|
|
@ -139,6 +139,8 @@ class ModNotes {
|
||||||
public function deleteNotes(ModNoteInfo|string|array $noteInfos): void {
|
public function deleteNotes(ModNoteInfo|string|array $noteInfos): void {
|
||||||
if(!is_array($noteInfos))
|
if(!is_array($noteInfos))
|
||||||
$noteInfos = [$noteInfos];
|
$noteInfos = [$noteInfos];
|
||||||
|
elseif(empty($noteInfos))
|
||||||
|
return;
|
||||||
|
|
||||||
$stmt = $this->cache->get(sprintf(
|
$stmt = $this->cache->get(sprintf(
|
||||||
'DELETE FROM msz_users_modnotes WHERE note_id IN (%s)',
|
'DELETE FROM msz_users_modnotes WHERE note_id IN (%s)',
|
||||||
|
|
91
src/Users/RoleInfo.php
Normal file
91
src/Users/RoleInfo.php
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
<?php
|
||||||
|
namespace Misuzu\Users;
|
||||||
|
|
||||||
|
use Stringable;
|
||||||
|
use Index\DateTime;
|
||||||
|
use Index\Colour\Colour;
|
||||||
|
use Index\Data\IDbResult;
|
||||||
|
|
||||||
|
class RoleInfo implements Stringable {
|
||||||
|
private string $id;
|
||||||
|
private int $rank;
|
||||||
|
private string $name;
|
||||||
|
private ?string $title;
|
||||||
|
private ?string $description;
|
||||||
|
private bool $hidden;
|
||||||
|
private bool $leavable;
|
||||||
|
private ?int $colour;
|
||||||
|
private int $created;
|
||||||
|
|
||||||
|
public function __construct(IDbResult $result) {
|
||||||
|
$this->id = (string)$result->getInteger(0);
|
||||||
|
$this->rank = $result->getInteger(1);
|
||||||
|
$this->name = $result->getString(2);
|
||||||
|
$this->title = $result->isNull(3) ? null : $result->getString(3);
|
||||||
|
$this->description = $result->isNull(4) ? null : $result->getString(4);
|
||||||
|
$this->hidden = $result->getInteger(5) !== 0;
|
||||||
|
$this->leavable = $result->getInteger(6) !== 0;
|
||||||
|
$this->colour = $result->isNull(7) ? null : $result->getInteger(7);
|
||||||
|
$this->created = $result->getInteger(8);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getId(): string {
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isDefault(): bool {
|
||||||
|
return $this->id === Roles::DEFAULT_ROLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRank(): int {
|
||||||
|
return $this->rank;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getName(): string {
|
||||||
|
return $this->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hasTitle(): bool {
|
||||||
|
return $this->title !== null && $this->title !== '';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTitle(): ?string {
|
||||||
|
return $this->title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hasDescription(): bool {
|
||||||
|
return $this->description !== null && $this->description !== '';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDescription(): ?string {
|
||||||
|
return $this->description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isHidden(): bool {
|
||||||
|
return $this->hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isLeavable(): bool {
|
||||||
|
return $this->leavable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hasColour(): bool {
|
||||||
|
return $this->colour !== null && ($this->colour & 0x40000000) === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getColour(): Colour {
|
||||||
|
return $this->colour === null ? Colour::none() : Colour::fromMisuzu($this->colour);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCreatedTime(): int {
|
||||||
|
return $this->created;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCreatedAt(): DateTime {
|
||||||
|
return DateTime::fromUnixTimeSeconds($this->created);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __toString(): string {
|
||||||
|
return 'r' . $this->id;
|
||||||
|
}
|
||||||
|
}
|
226
src/Users/Roles.php
Normal file
226
src/Users/Roles.php
Normal file
|
@ -0,0 +1,226 @@
|
||||||
|
<?php
|
||||||
|
namespace Misuzu\Users;
|
||||||
|
|
||||||
|
use InvalidArgumentException;
|
||||||
|
use RuntimeException;
|
||||||
|
use Index\Colour\Colour;
|
||||||
|
use Index\Data\DbStatementCache;
|
||||||
|
use Index\Data\DbTools;
|
||||||
|
use Index\Data\IDbConnection;
|
||||||
|
use Misuzu\Pagination;
|
||||||
|
|
||||||
|
class Roles {
|
||||||
|
public const DEFAULT_ROLE = '1';
|
||||||
|
|
||||||
|
private IDbConnection $dbConn;
|
||||||
|
private DbStatementCache $cache;
|
||||||
|
|
||||||
|
public function __construct(IDbConnection $dbConn) {
|
||||||
|
$this->dbConn = $dbConn;
|
||||||
|
$this->cache = new DbStatementCache($dbConn);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function countRoles(
|
||||||
|
User|string|null $userInfo = null,
|
||||||
|
?bool $hidden = null
|
||||||
|
): int {
|
||||||
|
if($userInfo instanceof User)
|
||||||
|
$userInfo = (string)$userInfo->getId();
|
||||||
|
|
||||||
|
$hasUserInfo = $userInfo !== null;
|
||||||
|
$hasHidden = $hidden !== null;
|
||||||
|
|
||||||
|
$args = 0;
|
||||||
|
$query = 'SELECT COUNT(*) FROM msz_roles';
|
||||||
|
if($hasUserInfo) {
|
||||||
|
++$args;
|
||||||
|
$query .= ' WHERE role_id IN (SELECT role_id FROM msz_users_roles WHERE user_id = ?)';
|
||||||
|
}
|
||||||
|
if($hasHidden)
|
||||||
|
$query .= sprintf(' %s role_hidden %s 0', ++$args > 0 ? 'AND' : 'WHERE', $hidden ? '=' : '<>');
|
||||||
|
|
||||||
|
$args = 0;
|
||||||
|
$stmt = $this->cache->get($query);
|
||||||
|
if($hasUserInfo)
|
||||||
|
$stmt->addParameter(++$args, $userInfo);
|
||||||
|
$stmt->execute();
|
||||||
|
|
||||||
|
$count = 0;
|
||||||
|
$result = $stmt->getResult();
|
||||||
|
|
||||||
|
if($result->next())
|
||||||
|
$count = $result->getInteger(0);
|
||||||
|
|
||||||
|
return $count;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRoles(
|
||||||
|
User|string|null $userInfo = null,
|
||||||
|
?bool $hidden = null,
|
||||||
|
?Pagination $pagination = null
|
||||||
|
): array {
|
||||||
|
if($userInfo instanceof User)
|
||||||
|
$userInfo = (string)$userInfo->getId();
|
||||||
|
|
||||||
|
$hasUserInfo = $userInfo !== null;
|
||||||
|
$hasHidden = $hidden !== null;
|
||||||
|
$hasPagination = $pagination !== null;
|
||||||
|
|
||||||
|
$args = 0;
|
||||||
|
$query = 'SELECT role_id, role_hierarchy, role_name, role_title, role_description, role_hidden, role_can_leave, role_colour, UNIX_TIMESTAMP(role_created) FROM msz_roles';
|
||||||
|
if($hasUserInfo) {
|
||||||
|
++$args;
|
||||||
|
$query .= ' WHERE role_id IN (SELECT role_id FROM msz_users_roles WHERE user_id = ?)';
|
||||||
|
}
|
||||||
|
if($hasHidden)
|
||||||
|
$query .= sprintf(' %s role_hidden %s 0', ++$args > 1 ? 'AND' : 'WHERE', $hidden ? '<>' : '=');
|
||||||
|
if($hasPagination)
|
||||||
|
$query .= ' LIMIT ? OFFSET ?';
|
||||||
|
|
||||||
|
$args = 0;
|
||||||
|
$stmt = $this->cache->get($query);
|
||||||
|
if($hasUserInfo)
|
||||||
|
$stmt->addParameter(++$args, $userInfo);
|
||||||
|
if($hasPagination) {
|
||||||
|
$stmt->addParameter(++$args, $pagination->getRange());
|
||||||
|
$stmt->addParameter(++$args, $pagination->getOffset());
|
||||||
|
}
|
||||||
|
$stmt->execute();
|
||||||
|
|
||||||
|
$roles = [];
|
||||||
|
$result = $stmt->getResult();
|
||||||
|
|
||||||
|
while($result->next())
|
||||||
|
$roles[] = new RoleInfo($result);
|
||||||
|
|
||||||
|
return $roles;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRole(string $roleId): RoleInfo {
|
||||||
|
$stmt = $this->cache->get('SELECT role_id, role_hierarchy, role_name, role_title, role_description, role_hidden, role_can_leave, role_colour, UNIX_TIMESTAMP(role_created) FROM msz_roles WHERE role_id = ?');
|
||||||
|
$stmt->addParameter(1, $roleId);
|
||||||
|
$stmt->execute();
|
||||||
|
|
||||||
|
$result = $stmt->getResult();
|
||||||
|
if(!$result->next())
|
||||||
|
throw new RuntimeException('Could not find role with ID $roleId.');
|
||||||
|
|
||||||
|
return new RoleInfo($result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createRole(
|
||||||
|
string $name,
|
||||||
|
int $rank,
|
||||||
|
Colour $colour,
|
||||||
|
string $title = '',
|
||||||
|
string $description = '',
|
||||||
|
bool $hidden = false,
|
||||||
|
bool $leavable = false
|
||||||
|
): RoleInfo {
|
||||||
|
$colour = $colour->shouldInherit() ? null : Colour::toMisuzu($colour);
|
||||||
|
|
||||||
|
// should these continue to accept NULL?
|
||||||
|
if($title === '') $title = null;
|
||||||
|
if($description === '') $description = null;
|
||||||
|
|
||||||
|
$stmt = $this->cache->get('INSERT INTO msz_roles (role_hierarchy, role_name, role_title, role_description, role_hidden, role_can_leave, role_colour) VALUES (?, ?, ?, ?, ?, ?, ?)');
|
||||||
|
$stmt->addParameter(1, $rank);
|
||||||
|
$stmt->addParameter(2, $name);
|
||||||
|
$stmt->addParameter(3, $title);
|
||||||
|
$stmt->addParameter(4, $description);
|
||||||
|
$stmt->addParameter(5, $hidden ? 1 : 0);
|
||||||
|
$stmt->addParameter(6, $leavable ? 1 : 0);
|
||||||
|
$stmt->addParameter(7, $colour);
|
||||||
|
$stmt->execute();
|
||||||
|
|
||||||
|
return $this->getRole((string)$this->dbConn->getLastInsertId());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function deleteRoles(RoleInfo|string|array $roleInfos): void {
|
||||||
|
if(!is_array($roleInfos))
|
||||||
|
$roleInfos = [$roleInfos];
|
||||||
|
elseif(empty($roleInfos))
|
||||||
|
return;
|
||||||
|
|
||||||
|
$stmt = $this->cache->get(sprintf(
|
||||||
|
'DELETE FROM msz_roles WHERE role_id IN (%s)',
|
||||||
|
DbTools::prepareListString($roleInfos)
|
||||||
|
));
|
||||||
|
|
||||||
|
$args = 0;
|
||||||
|
foreach($roleInfos as $roleInfo) {
|
||||||
|
if($roleInfo instanceof RoleInfo)
|
||||||
|
$roleInfo = $roleInfo->getId();
|
||||||
|
elseif(!is_string($roleInfo))
|
||||||
|
throw new InvalidArgumentException('$roleInfos must be strings of instances of RoleInfo.');
|
||||||
|
|
||||||
|
$stmt->addParameter(++$args, $roleInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateRole(
|
||||||
|
RoleInfo|string $roleInfo,
|
||||||
|
?string $name = null,
|
||||||
|
?int $rank = null,
|
||||||
|
?Colour $colour = null,
|
||||||
|
?string $title = null,
|
||||||
|
?string $description = null,
|
||||||
|
?bool $hidden = null,
|
||||||
|
?bool $leavable = null
|
||||||
|
): void {
|
||||||
|
if($roleInfo instanceof RoleInfo)
|
||||||
|
$roleInfo = $roleInfo->getId();
|
||||||
|
|
||||||
|
$applyTitle = $title !== null;
|
||||||
|
$applyDescription = $description !== null;
|
||||||
|
$applyColour = $colour !== null;
|
||||||
|
$applyHidden = $hidden !== null;
|
||||||
|
$applyLeavable = $leavable !== null;
|
||||||
|
|
||||||
|
if($applyColour)
|
||||||
|
$colour = $colour->shouldInherit() ? null : Colour::toMisuzu($colour);
|
||||||
|
|
||||||
|
// should these continue to accept NULL?
|
||||||
|
if($title === '') $title = null;
|
||||||
|
if($description === '') $description = null;
|
||||||
|
|
||||||
|
$stmt = $this->cache->get('UPDATE msz_roles SET role_hierarchy = COALESCE(?, role_hierarchy), role_name = COALESCE(?, role_name), role_title = IF(?, ?, role_title), role_description = IF(?, ?, role_description), role_hidden = IF(?, ?, role_hidden), role_can_leave = IF(?, ?, role_can_leave), role_colour = IF(?, ?, role_colour) WHERE role_id = ?');
|
||||||
|
$stmt->addParameter(1, $rank);
|
||||||
|
$stmt->addParameter(2, $name);
|
||||||
|
$stmt->addParameter(3, $applyTitle ? 1 : 0);
|
||||||
|
$stmt->addParameter(4, $title);
|
||||||
|
$stmt->addParameter(5, $applyDescription ? 1 : 0);
|
||||||
|
$stmt->addParameter(6, $description);
|
||||||
|
$stmt->addParameter(7, $applyHidden ? 1 : 0);
|
||||||
|
$stmt->addParameter(8, $hidden ? 1 : 0);
|
||||||
|
$stmt->addParameter(9, $applyLeavable ? 1 : 0);
|
||||||
|
$stmt->addParameter(10, $leavable ? 1 : 0);
|
||||||
|
$stmt->addParameter(11, $applyColour ? 1 : 0);
|
||||||
|
$stmt->addParameter(12, $colour);
|
||||||
|
$stmt->addParameter(13, $roleInfo);
|
||||||
|
$stmt->execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDefaultRole(): RoleInfo {
|
||||||
|
return $this->getRole(self::DEFAULT_ROLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function countRoleUsers(RoleInfo|string $roleInfo): int {
|
||||||
|
if($roleInfo instanceof RoleInfo)
|
||||||
|
$roleInfo = $roleInfo->getId();
|
||||||
|
|
||||||
|
$stmt = $this->cache->get('SELECT COUNT(*) FROM msz_users_roles WHERE role_id = ?');
|
||||||
|
$stmt->addParameter(1, $roleInfo);
|
||||||
|
$stmt->execute();
|
||||||
|
|
||||||
|
$count = 0;
|
||||||
|
$result = $stmt->getResult();
|
||||||
|
|
||||||
|
if($result->next())
|
||||||
|
$count = $result->getInteger(0);
|
||||||
|
|
||||||
|
return $count;
|
||||||
|
}
|
||||||
|
}
|
|
@ -126,8 +126,12 @@ class User implements HasRankInterface {
|
||||||
public function getColour(): Colour { // Swaps role colour in if user has no personal colour
|
public function getColour(): Colour { // Swaps role colour in if user has no personal colour
|
||||||
if($this->realColour === null) {
|
if($this->realColour === null) {
|
||||||
$this->realColour = $this->getUserColour();
|
$this->realColour = $this->getUserColour();
|
||||||
if($this->realColour->shouldInherit())
|
if($this->realColour->shouldInherit()) {
|
||||||
$this->realColour = $this->getDisplayRole()->getColour();
|
$stmt = DB::prepare('SELECT role_colour FROM msz_roles WHERE role_id = (SELECT display_role FROM msz_users WHERE user_id = :user)');
|
||||||
|
$stmt->bind('user', $this->user_id);
|
||||||
|
$rawColour = $stmt->fetchColumn();
|
||||||
|
$this->realColour = $rawColour === null ? Colour::none() : Colour::fromMisuzu($rawColour);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return $this->realColour;
|
return $this->realColour;
|
||||||
}
|
}
|
||||||
|
@ -165,38 +169,20 @@ class User implements HasRankInterface {
|
||||||
if($this->userRank === null)
|
if($this->userRank === null)
|
||||||
$this->userRank = (int)DB::prepare(
|
$this->userRank = (int)DB::prepare(
|
||||||
'SELECT MAX(`role_hierarchy`)'
|
'SELECT MAX(`role_hierarchy`)'
|
||||||
. ' FROM `' . DB::PREFIX . UserRole::TABLE . '`'
|
. ' FROM `msz_roles`'
|
||||||
. ' WHERE `role_id` IN (SELECT `role_id` FROM `' . DB::PREFIX . UserRoleRelation::TABLE . '` WHERE `user_id` = :user)'
|
. ' WHERE `role_id` IN (SELECT `role_id` FROM `msz_users_roles` WHERE `user_id` = :user)'
|
||||||
)->bind('user', $this->getId())->fetchColumn();
|
)->bind('user', $this->getId())->fetchColumn();
|
||||||
return $this->userRank;
|
return $this->userRank;
|
||||||
}
|
}
|
||||||
public function hasAuthorityOver(HasRankInterface $other): bool {
|
public function hasAuthorityOver(HasRankInterface $other): bool {
|
||||||
// Don't even bother checking if we're a super user
|
return $this->isSuper()
|
||||||
if($this->isSuper())
|
|| $other instanceof self && $other->getId() === $this->getId()
|
||||||
return true;
|
|| $this->getRank() > $other->getRank();
|
||||||
if($other instanceof self && $other->getId() === $this->getId())
|
|
||||||
return true;
|
|
||||||
return $this->getRank() > $other->getRank();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getDisplayRoleId(): int {
|
public function getDisplayRoleId(): int {
|
||||||
return $this->display_role < 1 ? -1 : $this->display_role;
|
return $this->display_role < 1 ? -1 : $this->display_role;
|
||||||
}
|
}
|
||||||
public function setDisplayRoleId(int $roleId): self {
|
|
||||||
$this->display_role = $roleId < 1 ? -1 : $roleId;
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
public function getDisplayRole(): UserRole {
|
|
||||||
return $this->getRoleRelations()[$this->getDisplayRoleId()]->getRole();
|
|
||||||
}
|
|
||||||
public function setDisplayRole(UserRole $role): self {
|
|
||||||
if($this->hasRole($role))
|
|
||||||
$this->setDisplayRoleId($role->getId());
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
public function isDisplayRole(UserRole $role): bool {
|
|
||||||
return $this->getDisplayRoleId() === $role->getId();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function hasTOTP(): bool {
|
public function hasTOTP(): bool {
|
||||||
return !empty($this->user_totp_key);
|
return !empty($this->user_totp_key);
|
||||||
|
@ -417,50 +403,6 @@ class User implements HasRankInterface {
|
||||||
return $this->getBackgroundInfo()->isPresent();
|
return $this->getBackgroundInfo()->isPresent();
|
||||||
}
|
}
|
||||||
|
|
||||||
/*********
|
|
||||||
* ROLES *
|
|
||||||
*********/
|
|
||||||
|
|
||||||
private $roleRelations = null;
|
|
||||||
|
|
||||||
public function addRole(UserRole $role, bool $display = false): void {
|
|
||||||
if(!$this->hasRole($role))
|
|
||||||
$this->roleRelations[$role->getId()] = UserRoleRelation::create($this, $role);
|
|
||||||
|
|
||||||
if($display && $this->isDisplayRole($role))
|
|
||||||
$this->setDisplayRole($role);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function removeRole(UserRole $role): void {
|
|
||||||
if(!$this->hasRole($role))
|
|
||||||
return;
|
|
||||||
UserRoleRelation::destroy($this, $role);
|
|
||||||
unset($this->roleRelations[$role->getId()]);
|
|
||||||
|
|
||||||
if($this->isDisplayRole($role))
|
|
||||||
$this->setDisplayRoleId(UserRole::DEFAULT);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getRoleRelations(): array {
|
|
||||||
if($this->roleRelations === null) {
|
|
||||||
$this->roleRelations = [];
|
|
||||||
foreach(UserRoleRelation::byUser($this) as $rel)
|
|
||||||
$this->roleRelations[$rel->getRoleId()] = $rel;
|
|
||||||
}
|
|
||||||
return $this->roleRelations;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getRoles(): array {
|
|
||||||
$roles = [];
|
|
||||||
foreach($this->getRoleRelations() as $rel)
|
|
||||||
$roles[$rel->getRoleId()] = $rel->getRole();
|
|
||||||
return $roles;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function hasRole(UserRole $role): bool {
|
|
||||||
return array_key_exists($role->getId(), $this->getRoleRelations());
|
|
||||||
}
|
|
||||||
|
|
||||||
/***************
|
/***************
|
||||||
* FORUM STATS *
|
* FORUM STATS *
|
||||||
***************/
|
***************/
|
||||||
|
@ -621,7 +563,7 @@ class User implements HasRankInterface {
|
||||||
'UPDATE `' . DB::PREFIX . self::TABLE . '`'
|
'UPDATE `' . DB::PREFIX . self::TABLE . '`'
|
||||||
. ' SET `username` = :username, `email` = :email, `password` = :password'
|
. ' SET `username` = :username, `email` = :email, `password` = :password'
|
||||||
. ', `user_super` = :is_super, `user_country` = :country, `user_colour` = :colour, `user_title` = :title'
|
. ', `user_super` = :is_super, `user_country` = :country, `user_colour` = :colour, `user_title` = :title'
|
||||||
. ', `display_role` = :display_role, `user_totp_key` = :totp'
|
. ', `user_totp_key` = :totp'
|
||||||
. ' WHERE `user_id` = :user'
|
. ' WHERE `user_id` = :user'
|
||||||
) ->bind('user', $this->user_id)
|
) ->bind('user', $this->user_id)
|
||||||
->bind('username', $this->username)
|
->bind('username', $this->username)
|
||||||
|
@ -630,7 +572,6 @@ class User implements HasRankInterface {
|
||||||
->bind('is_super', $this->user_super)
|
->bind('is_super', $this->user_super)
|
||||||
->bind('country', $this->user_country)
|
->bind('country', $this->user_country)
|
||||||
->bind('colour', $this->user_colour)
|
->bind('colour', $this->user_colour)
|
||||||
->bind('display_role', $this->display_role)
|
|
||||||
->bind('totp', $this->user_totp_key)
|
->bind('totp', $this->user_totp_key)
|
||||||
->bind('title', $this->user_title)
|
->bind('title', $this->user_title)
|
||||||
->execute();
|
->execute();
|
||||||
|
|
|
@ -1,223 +0,0 @@
|
||||||
<?php
|
|
||||||
namespace Misuzu\Users;
|
|
||||||
|
|
||||||
use ArrayAccess;
|
|
||||||
use RuntimeException;
|
|
||||||
use Index\Colour\Colour;
|
|
||||||
use Misuzu\DB;
|
|
||||||
use Misuzu\HasRankInterface;
|
|
||||||
use Misuzu\Memoizer;
|
|
||||||
use Misuzu\Pagination;
|
|
||||||
|
|
||||||
class UserRole implements ArrayAccess, HasRankInterface {
|
|
||||||
public const DEFAULT = 1;
|
|
||||||
|
|
||||||
// Database fields
|
|
||||||
private $role_id = -1;
|
|
||||||
private $role_hierarchy = 1;
|
|
||||||
private $role_name = '';
|
|
||||||
private $role_title = null;
|
|
||||||
private $role_description = null;
|
|
||||||
private $role_hidden = 0;
|
|
||||||
private $role_can_leave = 0;
|
|
||||||
private $role_colour = null;
|
|
||||||
private $role_created = null;
|
|
||||||
|
|
||||||
public const TABLE = 'roles';
|
|
||||||
private const QUERY_SELECT = 'SELECT %1$s FROM `' . DB::PREFIX . self::TABLE . '` AS '. self::TABLE;
|
|
||||||
private const SELECT = '%1$s.`role_id`, %1$s.`role_hierarchy`, %1$s.`role_name`, %1$s.`role_title`, %1$s.`role_description`'
|
|
||||||
. ', %1$s.`role_hidden`, %1$s.`role_can_leave`, %1$s.`role_colour`'
|
|
||||||
. ', UNIX_TIMESTAMP(%1$s.`role_created`) AS `role_created`';
|
|
||||||
|
|
||||||
private $colour = null;
|
|
||||||
private $userCount = -1;
|
|
||||||
|
|
||||||
public function getId(): int {
|
|
||||||
return $this->role_id < 1 ? -1 : $this->role_id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getRank(): int {
|
|
||||||
return $this->role_hierarchy;
|
|
||||||
}
|
|
||||||
public function setRank(int $rank): self {
|
|
||||||
$this->role_hierarchy = $rank;
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getName(): string {
|
|
||||||
return $this->role_name;
|
|
||||||
}
|
|
||||||
public function setName(string $name): self {
|
|
||||||
$this->role_name = $name;
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getTitle(): string {
|
|
||||||
return $this->role_title ?? '';
|
|
||||||
}
|
|
||||||
public function setTitle(string $title): self {
|
|
||||||
$this->role_title = empty($title) ? null : $title;
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getDescription(): string {
|
|
||||||
return $this->role_description ?? '';
|
|
||||||
}
|
|
||||||
public function setDescription(string $description): self {
|
|
||||||
$this->role_description = empty($description) ? null : $description;
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function isHidden(): bool {
|
|
||||||
return boolval($this->role_hidden);
|
|
||||||
}
|
|
||||||
public function setHidden(bool $hidden): self {
|
|
||||||
$this->role_hidden = $hidden ? 1 : 0;
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getCanLeave(): bool {
|
|
||||||
return boolval($this->role_can_leave);
|
|
||||||
}
|
|
||||||
public function setCanLeave(bool $canLeave): self {
|
|
||||||
$this->role_can_leave = $canLeave ? 1 : 0;
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Provided just because, avoid using these for validations sake
|
|
||||||
public function getColourRaw(): ?int {
|
|
||||||
return $this->role_colour;
|
|
||||||
}
|
|
||||||
public function setColourRaw(?int $colour): self {
|
|
||||||
$this->role_colour = $colour;
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getColour(): Colour {
|
|
||||||
if($this->colour === null || ($this->role_colour ?? 0x40000000) !== Colour::toMisuzu($this->colour))
|
|
||||||
$this->colour = Colour::fromMisuzu($this->role_colour ?? 0x40000000);
|
|
||||||
return $this->colour;
|
|
||||||
}
|
|
||||||
public function setColour(Colour $colour): self {
|
|
||||||
$this->role_colour = $colour->shouldInherit() ? null : Colour::toMisuzu($colour);
|
|
||||||
$this->colour = $this->colour;
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getCreatedTime(): int {
|
|
||||||
return $this->role_created === null ? -1 : $this->role_created;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getUserCount(): int {
|
|
||||||
if($this->userCount < 0)
|
|
||||||
$this->userCount = UserRoleRelation::countUsers($this);
|
|
||||||
return $this->userCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function isDefault(): bool {
|
|
||||||
return $this->getId() === self::DEFAULT;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function hasAuthorityOver(HasRankInterface $other): bool {
|
|
||||||
if($other instanceof User && $other->isSuper())
|
|
||||||
return false;
|
|
||||||
return $this->getRank() > $other->getRank();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function save(): void {
|
|
||||||
$isInsert = $this->role_id < 1;
|
|
||||||
if($isInsert) {
|
|
||||||
$set = DB::prepare(
|
|
||||||
'INSERT INTO `' . DB::PREFIX . self::TABLE . '` (`role_hierarchy`, `role_name`, `role_title`, `role_description`, `role_hidden`, `role_can_leave`, `role_colour`)'
|
|
||||||
. ' VALUES (:rank, :name, :title, :desc, :hide, :can_leave, :colour)'
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
$set = DB::prepare(
|
|
||||||
'UPDATE `' . DB::PREFIX . self::TABLE . '` SET'
|
|
||||||
. ' `role_hierarchy` = :rank, `role_name` = :name, `role_title` = :title,'
|
|
||||||
. ' `role_description` = :desc, `role_hidden` = :hide, `role_can_leave` = :can_leave, `role_colour` = :colour'
|
|
||||||
. ' WHERE `role_id` = :role'
|
|
||||||
)->bind('role', $this->role_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
$set->bind('rank', $this->role_hierarchy)
|
|
||||||
->bind('name', $this->role_name)
|
|
||||||
->bind('title', $this->role_title)
|
|
||||||
->bind('desc', $this->role_description)
|
|
||||||
->bind('hide', $this->role_hidden)
|
|
||||||
->bind('can_leave', $this->role_can_leave)
|
|
||||||
->bind('colour', $this->role_colour);
|
|
||||||
|
|
||||||
if($isInsert) {
|
|
||||||
$this->role_id = $set->executeGetId();
|
|
||||||
$this->role_created = time();
|
|
||||||
} else $set->execute();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static function countQueryBase(): string {
|
|
||||||
return sprintf(self::QUERY_SELECT, 'COUNT(*)');
|
|
||||||
}
|
|
||||||
public static function countAll(bool $showHidden = false): int {
|
|
||||||
return (int)DB::prepare(
|
|
||||||
self::countQueryBase()
|
|
||||||
. ($showHidden ? '' : ' WHERE `role_hidden` = 0')
|
|
||||||
)->fetchColumn();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static function memoizer() {
|
|
||||||
static $memoizer = null;
|
|
||||||
if($memoizer === null)
|
|
||||||
$memoizer = new Memoizer;
|
|
||||||
return $memoizer;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static function byQueryBase(): string {
|
|
||||||
return sprintf(self::QUERY_SELECT, sprintf(self::SELECT, self::TABLE));
|
|
||||||
}
|
|
||||||
public static function byId(int $roleId): self {
|
|
||||||
return self::memoizer()->find($roleId, function() use ($roleId) {
|
|
||||||
$object = DB::prepare(
|
|
||||||
self::byQueryBase() . ' WHERE `role_id` = :role'
|
|
||||||
) ->bind('role', $roleId)
|
|
||||||
->fetchObject(self::class);
|
|
||||||
if(!$object)
|
|
||||||
throw new RuntimeException('No role found with that ID.');
|
|
||||||
return $object;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
public static function byDefault(): self {
|
|
||||||
return self::byId(self::DEFAULT);
|
|
||||||
}
|
|
||||||
public static function all(bool $showHidden = false, ?Pagination $pagination = null): array {
|
|
||||||
$query = self::byQueryBase();
|
|
||||||
|
|
||||||
if(!$showHidden)
|
|
||||||
$query .= ' WHERE `role_hidden` = 0';
|
|
||||||
|
|
||||||
if($pagination !== null)
|
|
||||||
$query .= ' LIMIT :range OFFSET :offset';
|
|
||||||
|
|
||||||
$getObjects = DB::prepare($query);
|
|
||||||
|
|
||||||
if($pagination !== null)
|
|
||||||
$getObjects->bind('range', $pagination->getRange())
|
|
||||||
->bind('offset', $pagination->getOffset());
|
|
||||||
|
|
||||||
return $getObjects->fetchObjects(self::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
// to satisfy the fucked behaviour array_diff has
|
|
||||||
public function __toString() {
|
|
||||||
return md5($this->getId() . '#' . $this->getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Twig shim for the roles list on the members page, don't use this class as an array normally.
|
|
||||||
public function offsetExists($offset): bool {
|
|
||||||
return $offset === 'name' || $offset === 'id';
|
|
||||||
}
|
|
||||||
public function offsetGet($offset): mixed {
|
|
||||||
return $this->{'get' . ucfirst($offset)}();
|
|
||||||
}
|
|
||||||
public function offsetSet($offset, $value): void {}
|
|
||||||
public function offsetUnset($offset): void {}
|
|
||||||
}
|
|
|
@ -1,88 +0,0 @@
|
||||||
<?php
|
|
||||||
namespace Misuzu\Users;
|
|
||||||
|
|
||||||
use Misuzu\DB;
|
|
||||||
use Misuzu\Pagination;
|
|
||||||
|
|
||||||
class UserRoleRelation {
|
|
||||||
// Database fields
|
|
||||||
private $user_id = -1;
|
|
||||||
private $role_id = -1;
|
|
||||||
|
|
||||||
public const TABLE = 'users_roles';
|
|
||||||
private const QUERY_SELECT = 'SELECT %1$s FROM `' . DB::PREFIX . self::TABLE . '` AS '. self::TABLE;
|
|
||||||
private const SELECT = '%1$s.`user_id`, %1$s.`role_id`';
|
|
||||||
|
|
||||||
private $user = null;
|
|
||||||
private $role = null;
|
|
||||||
|
|
||||||
public function getUserId(): int {
|
|
||||||
return $this->user_id < 1 ? -1 : $this->user_id;
|
|
||||||
}
|
|
||||||
public function getUser(): User {
|
|
||||||
if($this->user === null)
|
|
||||||
$this->user = User::byId($this->getUserId());
|
|
||||||
return $this->user;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getRoleId(): int {
|
|
||||||
return $this->role_id < 1 ? -1 : $this->role_id;
|
|
||||||
}
|
|
||||||
public function getRole(): UserRole {
|
|
||||||
if($this->role === null)
|
|
||||||
$this->role = UserRole::byId($this->getRoleId());
|
|
||||||
return $this->role;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function delete(): void {
|
|
||||||
self::destroy($this->getUser(), $this->getRole());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function destroy(User $user, UserRole $role): void {
|
|
||||||
DB::prepare('DELETE FROM `' . DB::PREFIX . self::TABLE . '` WHERE `user_id` = :user AND `role_id` = :role')
|
|
||||||
->bind('user', $user->getId())
|
|
||||||
->bind('role', $role->getId())
|
|
||||||
->execute();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function purge(User $user): void {
|
|
||||||
DB::prepare('DELETE FROM `' . DB::PREFIX . self::TABLE . '` WHERE `user_id` = :user')
|
|
||||||
->bind('user', $user->getId())
|
|
||||||
->execute();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function create(User $user, UserRole $role): self {
|
|
||||||
$create = DB::prepare(
|
|
||||||
'REPLACE INTO `' . DB::PREFIX . self::TABLE . '` (`user_id`, `role_id`)'
|
|
||||||
. ' VALUES (:user, :role)'
|
|
||||||
) ->bind('user', $user->getId())
|
|
||||||
->bind('role', $role->getId())
|
|
||||||
->execute();
|
|
||||||
|
|
||||||
// data is predictable, just create a "fake"
|
|
||||||
$object = new UserRoleRelation;
|
|
||||||
$object->user = $user;
|
|
||||||
$object->user_id = $user->getId();
|
|
||||||
$object->role = $role;
|
|
||||||
$object->role_id = $role->getId();
|
|
||||||
return $object;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static function countQueryBase(): string {
|
|
||||||
return sprintf(self::QUERY_SELECT, 'COUNT(*)');
|
|
||||||
}
|
|
||||||
public static function countUsers(UserRole $role): int {
|
|
||||||
return (int)DB::prepare(self::countQueryBase() . ' WHERE `role_id` = :role')
|
|
||||||
->bind('role', $role->getId())
|
|
||||||
->fetchColumn();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static function byQueryBase(): string {
|
|
||||||
return sprintf(self::QUERY_SELECT, sprintf(self::SELECT, self::TABLE));
|
|
||||||
}
|
|
||||||
public static function byUser(User $user): array {
|
|
||||||
return DB::prepare(self::byQueryBase() . ' WHERE `user_id` = :user')
|
|
||||||
->bind('user', $user->getId())
|
|
||||||
->fetchObjects(self::class);
|
|
||||||
}
|
|
||||||
}
|
|
142
src/Users/Users.php
Normal file
142
src/Users/Users.php
Normal file
|
@ -0,0 +1,142 @@
|
||||||
|
<?php
|
||||||
|
namespace Misuzu\Users;
|
||||||
|
|
||||||
|
use InvalidArgumentException;
|
||||||
|
use Index\Data\DbStatementCache;
|
||||||
|
use Index\Data\DbTools;
|
||||||
|
use Index\Data\IDbConnection;
|
||||||
|
|
||||||
|
class Users {
|
||||||
|
private IDbConnection $dbConn;
|
||||||
|
private DbStatementCache $cache;
|
||||||
|
|
||||||
|
public function __construct(IDbConnection $dbConn) {
|
||||||
|
$this->dbConn = $dbConn;
|
||||||
|
$this->cache = new DbStatementCache($dbConn);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateUser(
|
||||||
|
User|string $userInfo,
|
||||||
|
RoleInfo|string|null $displayRoleInfo = null
|
||||||
|
): void {
|
||||||
|
if($userInfo instanceof User)
|
||||||
|
$userInfo = (string)$userInfo->getId();
|
||||||
|
if($displayRoleInfo instanceof RoleInfo)
|
||||||
|
$displayRoleInfo = $displayRoleInfo->getId();
|
||||||
|
|
||||||
|
$stmt = $this->cache->get('UPDATE msz_users SET display_role = COALESCE(?, display_role) WHERE user_id = ?');
|
||||||
|
$stmt->addParameter(1, $displayRoleInfo);
|
||||||
|
$stmt->addParameter(2, $userInfo);
|
||||||
|
$stmt->execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hasRole(
|
||||||
|
User|string $userInfo,
|
||||||
|
RoleInfo|string $roleInfo
|
||||||
|
): bool {
|
||||||
|
if($userInfo instanceof User)
|
||||||
|
$userInfo = (string)$userInfo->getId();
|
||||||
|
if($roleInfo instanceof RoleInfo)
|
||||||
|
$roleInfo = $roleInfo->getId();
|
||||||
|
|
||||||
|
return in_array($roleInfo, $this->hasRoles($userInfo, $roleInfo));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hasRoles(
|
||||||
|
User|string $userInfo,
|
||||||
|
RoleInfo|string|array $roleInfos
|
||||||
|
): array {
|
||||||
|
if($userInfo instanceof User)
|
||||||
|
$userInfo = (string)$userInfo->getId();
|
||||||
|
if(!is_array($roleInfos))
|
||||||
|
$roleInfos = [$roleInfos];
|
||||||
|
elseif(empty($roleInfos))
|
||||||
|
return [];
|
||||||
|
|
||||||
|
$args = 0;
|
||||||
|
$stmt = $this->cache->get(sprintf(
|
||||||
|
'SELECT role_id FROM msz_users_roles WHERE user_id = ? AND role_id IN (%s)',
|
||||||
|
DbTools::prepareListString($roleInfos)
|
||||||
|
));
|
||||||
|
$stmt->addParameter(++$args, $userInfo);
|
||||||
|
|
||||||
|
foreach($roleInfos as $roleInfo) {
|
||||||
|
if($roleInfo instanceof RoleInfo)
|
||||||
|
$roleInfo = $roleInfo->getId();
|
||||||
|
elseif(!is_string($roleInfo))
|
||||||
|
throw new InvalidArgumentException('$roleInfos must be strings of instances of RoleInfo.');
|
||||||
|
|
||||||
|
$stmt->addParameter(++$args, $roleInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->execute();
|
||||||
|
|
||||||
|
$roleIds = [];
|
||||||
|
$result = $stmt->getResult();
|
||||||
|
|
||||||
|
while($result->next())
|
||||||
|
$roleIds[] = (string)$result->getInteger(0);
|
||||||
|
|
||||||
|
return $roleIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addRoles(
|
||||||
|
User|string $userInfo,
|
||||||
|
RoleInfo|string|array $roleInfos
|
||||||
|
): void {
|
||||||
|
if($userInfo instanceof User)
|
||||||
|
$userInfo = (string)$userInfo->getId();
|
||||||
|
if(!is_array($roleInfos))
|
||||||
|
$roleInfos = [$roleInfos];
|
||||||
|
elseif(empty($roleInfos))
|
||||||
|
return;
|
||||||
|
|
||||||
|
$stmt = $this->cache->get(sprintf(
|
||||||
|
'REPLACE INTO msz_users_roles (user_id, role_id) VALUES %s',
|
||||||
|
DbTools::prepareListString($roleInfos, '(?, ?)')
|
||||||
|
));
|
||||||
|
|
||||||
|
$args = 0;
|
||||||
|
foreach($roleInfos as $roleInfo) {
|
||||||
|
if($roleInfo instanceof RoleInfo)
|
||||||
|
$roleInfo = $roleInfo->getId();
|
||||||
|
elseif(!is_string($roleInfo))
|
||||||
|
throw new InvalidArgumentException('$roleInfos must be strings of instances of RoleInfo.');
|
||||||
|
|
||||||
|
$stmt->addParameter(++$args, $userInfo);
|
||||||
|
$stmt->addParameter(++$args, $roleInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function removeRoles(
|
||||||
|
User|string $userInfo,
|
||||||
|
RoleInfo|string|array $roleInfos
|
||||||
|
): void {
|
||||||
|
if($userInfo instanceof User)
|
||||||
|
$userInfo = (string)$userInfo->getId();
|
||||||
|
if(!is_array($roleInfos))
|
||||||
|
$roleInfos = [$roleInfos];
|
||||||
|
elseif(empty($roleInfos))
|
||||||
|
return;
|
||||||
|
|
||||||
|
$args = 0;
|
||||||
|
$stmt = $this->cache->get(sprintf(
|
||||||
|
'DELETE FROM msz_users_roles WHERE user_id = ? AND role_id IN (%s)',
|
||||||
|
DbTools::prepareListString($roleInfos)
|
||||||
|
));
|
||||||
|
$stmt->addParameter(++$args, $userInfo);
|
||||||
|
|
||||||
|
foreach($roleInfos as $roleInfo) {
|
||||||
|
if($roleInfo instanceof RoleInfo)
|
||||||
|
$roleInfo = $roleInfo->getId();
|
||||||
|
elseif(!is_string($roleInfo))
|
||||||
|
throw new InvalidArgumentException('$roleInfos must be strings of instances of RoleInfo.');
|
||||||
|
|
||||||
|
$stmt->addParameter(++$args, $roleInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->execute();
|
||||||
|
}
|
||||||
|
}
|
|
@ -156,6 +156,8 @@ class Warnings {
|
||||||
public function deleteWarnings(WarningInfo|string|array $warnInfos): void {
|
public function deleteWarnings(WarningInfo|string|array $warnInfos): void {
|
||||||
if(!is_array($warnInfos))
|
if(!is_array($warnInfos))
|
||||||
$warnInfos = [$warnInfos];
|
$warnInfos = [$warnInfos];
|
||||||
|
elseif(empty($warnInfos))
|
||||||
|
return;
|
||||||
|
|
||||||
$stmt = $this->cache->get(sprintf(
|
$stmt = $this->cache->get(sprintf(
|
||||||
'DELETE FROM msz_users_warnings WHERE warn_id IN (%s)',
|
'DELETE FROM msz_users_warnings WHERE warn_id IN (%s)',
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
{% from '_layout/input.twig' import input_csrf, input_text, input_checkbox %}
|
{% from '_layout/input.twig' import input_csrf, input_text, input_checkbox %}
|
||||||
|
|
||||||
{% block manage_content %}
|
{% block manage_content %}
|
||||||
<form action="?v=role{{ role_info is not null ? '&r=' ~ role_info.id : '' }}" method="post"{% if role_info is not null %} style="--accent-colour: {{ role_info.colour }}"{% endif %}>
|
<form action="{{ url('manage-role', {'role': role_info.id|default(0)}) }}" method="post"{% if role_info is not null %} style="--accent-colour: {{ role_info.colour }}"{% endif %}>
|
||||||
{{ input_csrf() }}
|
{{ input_csrf() }}
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
@ -13,28 +13,35 @@
|
||||||
<label class="form__label">
|
<label class="form__label">
|
||||||
<div class="form__label__text">Role Name</div>
|
<div class="form__label__text">Role Name</div>
|
||||||
<div class="form__label__input">
|
<div class="form__label__input">
|
||||||
{{ input_text('role[name]', '', role_info.name|default(''), 'text', '', true, {'maxlength':255}) }}
|
{{ input_text('ur_name', '', role_ur_name|default(role_info.name|default()), 'text', '', true, {'maxlength':255}) }}
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<label class="form__label">
|
<label class="form__label">
|
||||||
<div class="form__label__text">Hide Rank</div>
|
<div class="form__label__text">Hide Role</div>
|
||||||
<div class="form__label__input">
|
<div class="form__label__input">
|
||||||
{{ input_checkbox('role[secret]', '', role_info is not null and role_info.hidden) }}
|
{{ input_checkbox('ur_hidden', '', role_ur_hidden is defined ? role_ur_hidden : (role_info is not null ? role_info.hidden : false)) }}
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<label class="form__label">
|
<label class="form__label">
|
||||||
<div class="form__label__text">Hierarchy</div>
|
<div class="form__label__text">Role can be removed by the user themselves</div>
|
||||||
<div class="form__label__input">
|
<div class="form__label__input">
|
||||||
{{ input_text('role[hierarchy]', '', role_info.rank|default(1), 'number', '', true) }}
|
{{ input_checkbox('ur_leavable', '', role_ur_leavable is defined ? role_ur_leavable : (role_info is not null ? role_info.leavable : false)) }}
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="form__label">
|
||||||
|
<div class="form__label__text">Rank</div>
|
||||||
|
<div class="form__label__input">
|
||||||
|
{{ input_text('ur_rank', '', role_ur_rank|default(role_info.rank|default(1)), 'number', '', true) }}
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<label class="form__label">
|
<label class="form__label">
|
||||||
<div class="form__label__text">Title</div>
|
<div class="form__label__text">Title</div>
|
||||||
<div class="form__label__input">
|
<div class="form__label__input">
|
||||||
{{ input_text('role[title]', '', role_info.title|default(''), 'text', '', false, {'maxlength':64}) }}
|
{{ input_text('ur_title', '', role_ur_title|default(role_info.title|default()), 'text', '', false, {'maxlength':64}) }}
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
|
@ -46,28 +53,28 @@
|
||||||
<label class="form__label">
|
<label class="form__label">
|
||||||
<div class="form__label__text">Inherit Colour</div>
|
<div class="form__label__text">Inherit Colour</div>
|
||||||
<div class="form__label__input">
|
<div class="form__label__input">
|
||||||
{{ input_checkbox('role[colour][inherit]', '', role_info is not null ? role_info.colour.shouldInherit : true) }}
|
{{ input_checkbox('ur_col_inherit', '', role_ur_col_inherit is defined ? role_ur_col_inherit : (role_info is not null and role_info.hasColour ? role_info.colour.shouldInherit : true)) }}
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<label class="form__label">
|
<label class="form__label">
|
||||||
<div class="form__label__text">Red</div>
|
<div class="form__label__text">Red</div>
|
||||||
<div class="form__label__input">
|
<div class="form__label__input">
|
||||||
{{ input_text('role[colour][red]', '', role_info.colour.red|default(0), 'number', '', false, {'min':0,'max':255}) }}
|
{{ input_text('ur_col_red', '', role_ur_col_red|default(role_info.colour.red|default(0)), 'number', '', false, {'min':0,'max':255}) }}
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<label class="form__label">
|
<label class="form__label">
|
||||||
<div class="form__label__text">Green</div>
|
<div class="form__label__text">Green</div>
|
||||||
<div class="form__label__input">
|
<div class="form__label__input">
|
||||||
{{ input_text('role[colour][green]', '', role_info.colour.green|default(0), 'number', '', false, {'min':0,'max':255}) }}
|
{{ input_text('ur_col_green', '', role_ur_col_green|default(role_info.colour.green|default(0)), 'number', '', false, {'min':0,'max':255}) }}
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<label class="form__label">
|
<label class="form__label">
|
||||||
<div class="form__label__text">Blue</div>
|
<div class="form__label__text">Blue</div>
|
||||||
<div class="form__label__input">
|
<div class="form__label__input">
|
||||||
{{ input_text('role[colour][blue]', '', role_info.colour.blue|default(0), 'number', '', false, {'min':0,'max':255}) }}
|
{{ input_text('ur_col_blue', '', role_ur_col_blue|default(role_info.colour.blue|default(0)), 'number', '', false, {'min':0,'max':255}) }}
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
|
@ -79,7 +86,7 @@
|
||||||
<label class="form__label">
|
<label class="form__label">
|
||||||
<div class="form__label__text">Description</div>
|
<div class="form__label__text">Description</div>
|
||||||
<div class="form__label__input">
|
<div class="form__label__input">
|
||||||
<textarea class="input__textarea" name="role[description]" maxlength="1000">{{ role_info.description|default('') }}</textarea>
|
<textarea class="input__textarea" name="ur_desc" maxlength="1000">{{ role_ur_desc|default(role_info.description|default()) }}</textarea>
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -32,8 +32,8 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% for role in manage_roles %}
|
{% for role in manage_roles %}
|
||||||
<div class="manage__role-item" style="--accent-colour: {{ role.colour }}">
|
<div class="manage__role-item" style="--accent-colour: {{ role.info.colour }}">
|
||||||
<a href="{{ url('manage-role', {'role': role.id}) }}" class="manage__role-item__background"></a>
|
<a href="{{ url('manage-role', {'role': role.info.id}) }}" class="manage__role-item__background"></a>
|
||||||
|
|
||||||
<div class="manage__role-item__container">
|
<div class="manage__role-item__container">
|
||||||
<div class="manage__role-item__icon">
|
<div class="manage__role-item__icon">
|
||||||
|
@ -44,23 +44,23 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="manage__role-item__info">
|
<div class="manage__role-item__info">
|
||||||
<div class="manage__role-item__name">
|
<div class="manage__role-item__name">
|
||||||
{{ role.name }}
|
{{ role.info.name }}
|
||||||
</div>
|
</div>
|
||||||
<div class="manage__role-item__details">
|
<div class="manage__role-item__details">
|
||||||
{% if role.userCount > 0 %}
|
{% if role.members > 0 %}
|
||||||
<div class="manage__role-item__users">
|
<div class="manage__role-item__users">
|
||||||
<i class="fas fa-users fa-fw"></i> {{ role.userCount|number_format }}
|
<i class="fas fa-users fa-fw"></i> {{ role.members|number_format }}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if role.title is not empty %}
|
{% if role.info.title is not empty %}
|
||||||
<div class="manage__role-item__title">
|
<div class="manage__role-item__title">
|
||||||
{{ role.title }}
|
{{ role.info.title }}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="manage__role-item__actions">
|
<div class="manage__role-item__actions">
|
||||||
<a href="{{ url('user-list', {'role': role.id}) }}" class="manage__role-item__action" title="Members">
|
<a href="{{ url('user-list', {'role': role.info.id}) }}" class="manage__role-item__action" title="Members">
|
||||||
<i class="fas fa-users fa-fw"></i>
|
<i class="fas fa-users fa-fw"></i>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -106,14 +106,13 @@
|
||||||
{{ input_colour(can_edit_user ? 'colour[hex]' : '', '', user_info.userColour) }}
|
{{ input_colour(can_edit_user ? 'colour[hex]' : '', '', user_info.userColour) }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# TODO: if the hierarchy of the current user is too low to touch the role then opacity should be lowered and input disabled #}
|
|
||||||
<div class="manage__user__details">
|
<div class="manage__user__details">
|
||||||
<div class="manage__tags manage__tags--fixed">
|
<div class="manage__tags manage__tags--fixed">
|
||||||
{% for role in manage_roles %}
|
{% for role in manage_roles %}
|
||||||
<label class="manage__tag" style="--accent-colour: {{ role.colour }}">
|
<label class="manage__tag" style="--accent-colour: {{ role.colour }}">
|
||||||
<div class="manage__tag__background"></div>
|
<div class="manage__tag__background"></div>
|
||||||
<div class="manage__tag__content">
|
<div class="manage__tag__content">
|
||||||
{{ input_checkbox('roles[]', '', user_info.hasRole(role), 'manage__tag__checkbox', role.id, false, null, not can_edit_user) }}
|
{{ input_checkbox('roles[]', '', role.id in manage_user_has_roles, 'manage__tag__checkbox', role.id, false, null, not can_edit_user) }}
|
||||||
<div class="manage__tag__title">
|
<div class="manage__tag__title">
|
||||||
{{ role.name }}
|
{{ role.name }}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -71,8 +71,8 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="settings__role__collection">
|
<div class="settings__role__collection">
|
||||||
{% for role in settings_user.roles %}
|
{% for role in settings_roles %}
|
||||||
{% set is_display_role = settings_user.isDisplayRole(role) %}
|
{% set is_display_role = role.id == settings_user.displayRoleId %}
|
||||||
|
|
||||||
<div class="settings__role" style="--accent-colour: {{ role.colour }}">
|
<div class="settings__role" style="--accent-colour: {{ role.colour }}">
|
||||||
<div class="settings__role__content">
|
<div class="settings__role__content">
|
||||||
|
@ -94,10 +94,10 @@
|
||||||
<i class="far {{ is_display_role ? 'fa-check-square' : 'fa-square' }}"></i>
|
<i class="far {{ is_display_role ? 'fa-check-square' : 'fa-square' }}"></i>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button class="settings__role__option{% if not role.canLeave %} settings__role__option--disabled{% endif %}"
|
<button class="settings__role__option{% if not role.leavable %} settings__role__option--disabled{% endif %}"
|
||||||
name="role[mode]" value="leave" title="Leave this role"
|
name="role[mode]" value="leave" title="Leave this role"
|
||||||
onclick="return confirm('Are you sure you want to remove {{ role.name|replace({"'": "\'"}) }} from your account?')"
|
onclick="return confirm('Are you sure you want to remove {{ role.name|replace({"'": "\'"}) }} from your account?')"
|
||||||
{% if not role.canLeave %}disabled{% endif %}>
|
{% if not role.leavable %}disabled{% endif %}>
|
||||||
<i class="fas fa-times-circle"></i>
|
<i class="fas fa-times-circle"></i>
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -20,7 +20,11 @@
|
||||||
|
|
||||||
<div class="userlist__navigation">
|
<div class="userlist__navigation">
|
||||||
<form onchange="this.submit()" class="userlist__sorting">
|
<form onchange="this.submit()" class="userlist__sorting">
|
||||||
{{ input_select('r', roles, role_id, 'name', 'id', false, 'userlist__select') }}
|
<select class="input__select userlist__select" name="r">
|
||||||
|
{% for role in roles %}
|
||||||
|
<option value="{{ role.id }}"{% if role.id == role_id %}selected{% endif %} style="background-color: #222; {% if role.hasColour %}color: {{ role.colour }}{% endif %}">{{ role.name }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
{{ input_select('ss', orders, order, 'title', null, false, 'userlist__select') }}
|
{{ input_select('ss', orders, order, 'title', null, false, 'userlist__select') }}
|
||||||
{{ input_select('sd', directions, direction, null, null, false, 'userlist__select') }}
|
{{ input_select('sd', directions, direction, null, null, false, 'userlist__select') }}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue