From 461ffbf73b4362cda0984303ab30005ca968ebbc Mon Sep 17 00:00:00 2001 From: flashwave Date: Thu, 27 Jul 2023 23:26:05 +0000 Subject: [PATCH] Rewrote user role handling. --- public-legacy/auth/register.php | 6 +- public-legacy/manage/users/role.php | 154 ++++++++++-------- public-legacy/manage/users/roles.php | 15 +- public-legacy/manage/users/user.php | 76 ++++----- public-legacy/members.php | 29 ++-- public-legacy/settings/account.php | 26 ++- src/AuditLog/AuditLogInfo.php | 3 + src/MisuzuContext.php | 14 ++ src/Users/Bans.php | 2 + src/Users/ModNotes.php | 2 + src/Users/RoleInfo.php | 91 +++++++++++ src/Users/Roles.php | 226 +++++++++++++++++++++++++++ src/Users/User.php | 83 ++-------- src/Users/UserRole.php | 223 -------------------------- src/Users/UserRoleRelation.php | 88 ----------- src/Users/Users.php | 142 +++++++++++++++++ src/Users/Warnings.php | 2 + templates/manage/users/role.twig | 31 ++-- templates/manage/users/roles.twig | 16 +- templates/manage/users/user.twig | 3 +- templates/settings/account.twig | 8 +- templates/user/listing.twig | 6 +- 22 files changed, 710 insertions(+), 536 deletions(-) create mode 100644 src/Users/RoleInfo.php create mode 100644 src/Users/Roles.php delete mode 100644 src/Users/UserRole.php delete mode 100644 src/Users/UserRoleRelation.php create mode 100644 src/Users/Users.php diff --git a/public-legacy/auth/register.php b/public-legacy/auth/register.php index 7f66853..9015c3b 100644 --- a/public-legacy/auth/register.php +++ b/public-legacy/auth/register.php @@ -3,7 +3,6 @@ namespace Misuzu; use RuntimeException; use Misuzu\Users\User; -use Misuzu\Users\UserRole; use Misuzu\Users\UserSession; if(UserSession::hasCurrent()) { @@ -11,6 +10,9 @@ if(UserSession::hasCurrent()) { return; } +$users = $msz->getUsers(); +$roles = $msz->getRoles(); + $register = !empty($_POST['register']) && is_array($_POST['register']) ? $_POST['register'] : []; $notices = []; $ipAddress = $_SERVER['REMOTE_ADDR']; @@ -95,7 +97,7 @@ while(!$restricted && !empty($register)) { break; } - $createUser->addRole(UserRole::byDefault()); + $users->addRoles($createUser, $roles->getDefaultRole()); url_redirect('auth-login-welcome', ['username' => $createUser->getUsername()]); return; diff --git a/public-legacy/manage/users/role.php b/public-legacy/manage/users/role.php index 30e3321..6947b94 100644 --- a/public-legacy/manage/users/role.php +++ b/public-legacy/manage/users/role.php @@ -5,22 +5,25 @@ use RuntimeException; use Index\Colour\Colour; use Index\Colour\ColourRGB; 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)) { echo render_error(403); 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 { - $roleInfo = UserRole::byId($roleId); + $isNew = false; + $roleInfo = $roles->getRole($roleId); } catch(RuntimeException $ex) { echo render_error(404); return; } +} else $isNew = true; $currentUser = User::getCurrent(); $currentUserId = $currentUser->getId(); @@ -29,70 +32,92 @@ $canEditPerms = perms_check_user(MSZ_PERMS_USER, $currentUserId, MSZ_PERM_USER_M if($canEditPerms) $permissions = manage_perms_list(perms_get_role_raw($roleId ?? 0)); -if(!empty($_POST['role']) && is_array($_POST['role']) && CSRF::validateRequest()) { - $roleHierarchy = (int)($_POST['role']['hierarchy'] ?? -1); - - if(!$currentUser->isSuper() && (isset($roleInfo) ? $roleInfo->hasAuthorityOver($currentUser) : $currentUser->getRank() <= $roleHierarchy)) { - echo 'You don\'t hold authority over this role.'; - return; +while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) { + if(!$isNew && !$currentUser->isSuper() && $roleInfo->getRank() >= $currentUser->getRank()) { + echo 'You aren\'t allowed to edit this role.'; + break; } - $roleName = $_POST['role']['name'] ?? ''; - $roleNameLength = strlen($roleName); + $roleName = (string)filter_input(INPUT_POST, 'ur_name'); + $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) { - echo 'invalid name length'; - return; + Template::set([ + 'role_ur_name' => $roleName, + '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']); - - if($roleHierarchy < 1 || $roleHierarchy > 100) { - echo 'Invalid hierarchy value.'; - return; + $roleNameLength = mb_strlen($roleName); + if($roleNameLength < 1 || $roleNameLength > 100) { + echo 'Provided role name is either too long or too short.'; + break; } - if(!empty($_POST['role']['colour']['inherit'])) { - $roleColour = \Index\Colour\Colour::none(); + if($roleRank < 1 || $roleRank > 100) { + 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 { - $roleColour = new ColourRGB( - (int)($_POST['role']['colour']['red'] ?? -1), - (int)($_POST['role']['colour']['green'] ?? -1), - (int)($_POST['role']['colour']['blue'] ?? -1) - ); + if($roleName === $roleInfo->getName()) + $roleName = null; + if($roleHide === $roleInfo->isHidden()) + $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); } - $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(); + $msz->createAuditLog( + $isNew ? 'ROLE_CREATE' : 'ROLE_UPDATE', + [$roleInfo->getId()] + ); if(!empty($permissions) && !empty($_POST['perms']) && is_array($_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) { $permKeys = array_keys($perms); $setPermissions = DB::prepare(' - REPLACE INTO `msz_permissions` - (`role_id`, `user_id`, `' . implode('`, `', $permKeys) . '`) - VALUES - (:role_id, NULL, :' . implode(', :', $permKeys) . ') + REPLACE INTO `msz_permissions` (`role_id`, `user_id`, `' . implode('`, `', $permKeys) . '`) + VALUES (:role_id, NULL, :' . implode(', :', $permKeys) . ') '); $setPermissions->bind('role_id', $roleInfo->getId()); @@ -113,11 +136,7 @@ if(!empty($_POST['role']) && is_array($_POST['role']) && CSRF::validateRequest() $setPermissions->execute(); } else { - $deletePermissions = DB::prepare(' - DELETE FROM `msz_permissions` - WHERE `role_id` = :role_id - AND `user_id` IS NULL - '); + $deletePermissions = DB::prepare('DELETE FROM `msz_permissions` WHERE `role_id` = :role_id AND `user_id` IS NULL'); $deletePermissions->bind('role_id', $roleInfo->getId()); $deletePermissions->execute(); } @@ -128,6 +147,7 @@ if(!empty($_POST['role']) && is_array($_POST['role']) && CSRF::validateRequest() } Template::render('manage.users.role', [ + 'role_new' => $isNew, 'role_info' => $roleInfo ?? null, 'can_manage_perms' => $canEditPerms, 'permissions' => $permissions ?? [], diff --git a/public-legacy/manage/users/roles.php b/public-legacy/manage/users/roles.php index 859e876..03ed610 100644 --- a/public-legacy/manage/users/roles.php +++ b/public-legacy/manage/users/roles.php @@ -2,21 +2,30 @@ namespace Misuzu; 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)) { echo render_error(403); return; } -$pagination = new Pagination(UserRole::countAll(true), 10); +$roles = $msz->getRoles(); +$pagination = new Pagination($roles->countRoles(), 10); if(!$pagination->hasValidOffset()) { echo render_error(404); return; } +$rolesAll = []; +$roleInfos = $roles->getRoles(pagination: $pagination); + +foreach($roleInfos as $roleInfo) + $rolesAll[] = [ + 'info' => $roleInfo, + 'members' => $roles->countRoleUsers($roleInfo), + ]; + Template::render('manage.users.roles', [ - 'manage_roles' => UserRole::all(true, $pagination), + 'manage_roles' => $rolesAll, 'manage_roles_pagination' => $pagination, ]); diff --git a/public-legacy/manage/users/user.php b/public-legacy/manage/users/user.php index 7633b4c..23cea50 100644 --- a/public-legacy/manage/users/user.php +++ b/public-legacy/manage/users/user.php @@ -4,13 +4,15 @@ namespace Misuzu; use RuntimeException; use Index\Colour\Colour; use Misuzu\Users\User; -use Misuzu\Users\UserRole; if(!User::hasCurrent()) { echo render_error(403); return; } +$users = $msz->getUsers(); +$roles = $msz->getRoles(); + $currentUser = User::getCurrent(); $currentUserId = $currentUser->getId(); @@ -77,60 +79,53 @@ if(CSRF::validateRequest() && $canEdit) { foreach($_POST['roles'] as $item) { if(!ctype_digit($item)) die('Invalid item encountered in roles list.'); - $applyRoles[] = (int)$item; + $applyRoles[] = (string)$item; } - // Fetch existing roles - $existingRoles = $userInfo->getRoles(); + $existingRoles = []; + 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 = []; - // STEP 1: Check for roles to be removed in the existing set. - // Roles that the current users isn't allowed to touch (hierarchy) will stay. - foreach($setRoles as $role) { - // Also prevent the main role from being removed. - if($role->isDefault() || !$currentUser->hasAuthorityOver($role)) + foreach($existingRoles as $roleInfo) { + if($roleInfo->isDefault() || !($currentUser->isSuper() || $currentUser->getRank() > $roleInfo->getRank())) 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. - $setRoles = array_diff($setRoles, $removeRoles); + if(!empty($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) { try { - $role = $existingRoles[$roleId] ?? UserRole::byId($roleId); + $roleInfo = $existingRoles[$roleId] ?? $roles->getRole($roleId); } catch(RuntimeException $ex) { continue; } - if(!$currentUser->hasAuthorityOver($role)) + + if(!$currentUser->isSuper() && $currentUser->getRank() <= $roleInfo->getRank()) continue; - if(!in_array($role, $setRoles)) - $setRoles[] = $role; + + if(!in_array($roleInfo, $existingRoles)) + $addRoles[] = $roleInfo; } - foreach($removeRoles as $role) - $userInfo->removeRole($role); - - foreach($setRoles as $role) - $userInfo->addRole($role); + if(!empty($addRoles)) + $users->addRoles($userInfo, $addRoles); } if(!empty($_POST['user']) && is_array($_POST['user'])) { $setCountry = (string)($_POST['user']['country'] ?? ''); $setTitle = (string)($_POST['user']['title'] ?? ''); - $displayRole = (int)($_POST['user']['display_role'] ?? 0); - - try { - $userInfo->setDisplayRole(UserRole::byId($displayRole)); - } catch(RuntimeException $ex) {} + $displayRole = (string)($_POST['user']['display_role'] ?? 0); + if(!$users->hasRole($userInfo, $displayRole)) + $notices[] = 'User does not have the role you\'re trying to assign as primary.'; $countryValidation = strlen($setCountry) === 2 && ctype_alpha($setCountry) @@ -142,10 +137,15 @@ if(CSRF::validateRequest() && $canEdit) { if(strlen($setTitle) > 64) $notices[] = 'User title was invalid.'; - if(empty($notices)) + if(empty($notices)) { + $users->updateUser( + userInfo: $userInfo, + displayRoleInfo: $displayRole, + ); + $userInfo->setCountry((string)($_POST['user']['country'] ?? '')) - ->setTitle((string)($_POST['user']['title'] ?? '')) - ->setDisplayRole(UserRole::byId((int)($_POST['user']['display_role'] ?? 0))); + ->setTitle((string)($_POST['user']['title'] ?? '')); + } } 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', [ 'user_info' => $userInfo, 'manage_notices' => $notices, - 'manage_roles' => UserRole::all(true), + 'manage_roles' => $rolesAll, + 'manage_user_has_roles' => $userRoleIds, 'can_edit_user' => $canEdit, 'can_edit_perms' => $canEdit && $canEditPerms, 'can_manage_notes' => $canManageNotes, diff --git a/public-legacy/members.php b/public-legacy/members.php index 2fae05d..e24736f 100644 --- a/public-legacy/members.php +++ b/public-legacy/members.php @@ -3,11 +3,12 @@ namespace Misuzu; use RuntimeException; use Misuzu\Users\User; -use Misuzu\Users\UserRole; -$roleId = !empty($_GET['r']) && is_string($_GET['r']) ? (int)$_GET['r'] : UserRole::DEFAULT; -$orderBy = !empty($_GET['ss']) && is_string($_GET['ss']) ? mb_strtolower($_GET['ss']) : ''; -$orderDir = !empty($_GET['sd']) && is_string($_GET['sd']) ? mb_strtolower($_GET['sd']) : ''; +$roles = $msz->getRoles(); + +$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 = [ '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); -try { - $roleInfo = UserRole::byId($roleId); -} catch(RuntimeException $ex) { - echo render_error(404); - return; +if($roleId === null) { + $roleInfo = $roles->getDefaultRole(); +} else { + try { + $roleInfo = $roles->getRole($roleId); + } catch(RuntimeException $ex) { + echo render_error(404); + 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( ' @@ -124,7 +129,7 @@ if(empty($users)) http_response_code(404); Template::render('user.listing', [ - 'roles' => $roles, + 'roles' => $rolesAll, 'role' => $roleInfo, 'users' => $users, 'order_fields' => $orderFields, diff --git a/public-legacy/settings/account.php b/public-legacy/settings/account.php index 6fbe35a..199a943 100644 --- a/public-legacy/settings/account.php +++ b/public-legacy/settings/account.php @@ -3,7 +3,6 @@ namespace Misuzu; use RuntimeException; use Misuzu\Users\User; -use Misuzu\Users\UserRole; use Misuzu\Users\UserSession; use chillerlan\QRCode\QRCode; use chillerlan\QRCode\QROptions; @@ -14,6 +13,8 @@ if(!UserSession::hasCurrent()) { } $errors = []; +$users = $msz->getUsers(); +$roles = $msz->getRoles(); $currentUser = User::getCurrent(); $currentUserId = $currentUser->getId(); $isRestricted = $msz->hasActiveBan(); @@ -21,20 +22,23 @@ $isVerifiedRequest = CSRF::validateRequest(); if(!$isRestricted && $isVerifiedRequest && !empty($_POST['role'])) { try { - $roleInfo = UserRole::byId((int)($_POST['role']['id'] ?? 0)); + $roleInfo = $roles->getRole(($_POST['role']['id'] ?? 0)); } 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."; else { switch($_POST['role']['mode'] ?? '') { case 'display': - $currentUser->setDisplayRole($roleInfo); + $users->updateUser( + $currentUser, + displayRoleInfo: $roleInfo + ); break; case 'leave': - if($roleInfo->getCanLeave()) - $currentUser->removeRole($roleInfo); + if($roleInfo->isLeavable()) + $users->removeRoles($currentUser, $roleInfo); else $errors[] = "You're not allow to leave this role, an administrator has to remove it for you."; break; @@ -124,11 +128,19 @@ if($isVerifiedRequest && !empty($_POST['current_password'])) { } // THIS FUCKING SUCKS AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -if($_SERVER['REQUEST_METHOD'] === 'POST' && $isVerifiedRequest) +if($_SERVER['REQUEST_METHOD'] === 'POST' && $isVerifiedRequest) { $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', [ 'errors' => $errors, 'settings_user' => $currentUser, + 'settings_roles' => $userRoles, 'is_restricted' => $isRestricted, ]); diff --git a/src/AuditLog/AuditLogInfo.php b/src/AuditLog/AuditLogInfo.php index e2413ee..33212f9 100644 --- a/src/AuditLog/AuditLogInfo.php +++ b/src/AuditLog/AuditLogInfo.php @@ -132,5 +132,8 @@ class AuditLogInfo { 'WARN_CREATE' => 'Added warning #%d to user #%d.', 'WARN_DELETE' => 'Removed warning #%d from user #%d.', + + 'ROLE_CREATE' => 'Created role #%d.', + 'ROLE_UPDATE' => 'Updated role #%d.', ]; } diff --git a/src/MisuzuContext.php b/src/MisuzuContext.php index bf0e79b..f22c416 100644 --- a/src/MisuzuContext.php +++ b/src/MisuzuContext.php @@ -15,7 +15,9 @@ use Misuzu\SharpChat\SharpChatRoutes; use Misuzu\Users\Bans; use Misuzu\Users\BanInfo; use Misuzu\Users\ModNotes; +use Misuzu\Users\Roles; use Misuzu\Users\User; +use Misuzu\Users\Users; use Misuzu\Users\Warnings; use Index\Data\IDbConnection; use Index\Data\Migration\IDbMigrationRepo; @@ -46,6 +48,8 @@ class MisuzuContext { private Bans $bans; private Warnings $warnings; private TwoFactorAuthSessions $tfaSessions; + private Roles $roles; + private Users $users; public function __construct(IDbConnection $dbConn, IConfig $config) { $this->dbConn = $dbConn; @@ -61,6 +65,8 @@ class MisuzuContext { $this->bans = new Bans($this->dbConn); $this->warnings = new Warnings($this->dbConn); $this->tfaSessions = new TwoFactorAuthSessions($this->dbConn); + $this->roles = new Roles($this->dbConn); + $this->users = new Users($this->dbConn); } public function getDbConn(): IDbConnection { @@ -132,6 +138,14 @@ class MisuzuContext { return $this->tfaSessions; } + public function getRoles(): Roles { + return $this->roles; + } + + public function getUsers(): Users { + return $this->users; + } + private array $activeBansCache = []; public function tryGetActiveBan(User|string|null $userInfo = null): ?BanInfo { diff --git a/src/Users/Bans.php b/src/Users/Bans.php index 787abca..6711b5a 100644 --- a/src/Users/Bans.php +++ b/src/Users/Bans.php @@ -172,6 +172,8 @@ class Bans { public function deleteBans(BanInfo|string|array $banInfos): void { if(!is_array($banInfos)) $banInfos = [$banInfos]; + elseif(empty($banInfos)) + return; $stmt = $this->cache->get(sprintf( 'DELETE FROM msz_users_bans WHERE ban_id IN (%s)', diff --git a/src/Users/ModNotes.php b/src/Users/ModNotes.php index 69ce7dd..73dce5e 100644 --- a/src/Users/ModNotes.php +++ b/src/Users/ModNotes.php @@ -139,6 +139,8 @@ class ModNotes { public function deleteNotes(ModNoteInfo|string|array $noteInfos): void { if(!is_array($noteInfos)) $noteInfos = [$noteInfos]; + elseif(empty($noteInfos)) + return; $stmt = $this->cache->get(sprintf( 'DELETE FROM msz_users_modnotes WHERE note_id IN (%s)', diff --git a/src/Users/RoleInfo.php b/src/Users/RoleInfo.php new file mode 100644 index 0000000..b7cffef --- /dev/null +++ b/src/Users/RoleInfo.php @@ -0,0 +1,91 @@ +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; + } +} diff --git a/src/Users/Roles.php b/src/Users/Roles.php new file mode 100644 index 0000000..5983012 --- /dev/null +++ b/src/Users/Roles.php @@ -0,0 +1,226 @@ +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; + } +} diff --git a/src/Users/User.php b/src/Users/User.php index 0afa3a9..7579981 100644 --- a/src/Users/User.php +++ b/src/Users/User.php @@ -126,8 +126,12 @@ class User implements HasRankInterface { public function getColour(): Colour { // Swaps role colour in if user has no personal colour if($this->realColour === null) { $this->realColour = $this->getUserColour(); - if($this->realColour->shouldInherit()) - $this->realColour = $this->getDisplayRole()->getColour(); + if($this->realColour->shouldInherit()) { + $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; } @@ -165,38 +169,20 @@ class User implements HasRankInterface { if($this->userRank === null) $this->userRank = (int)DB::prepare( 'SELECT MAX(`role_hierarchy`)' - . ' FROM `' . DB::PREFIX . UserRole::TABLE . '`' - . ' WHERE `role_id` IN (SELECT `role_id` FROM `' . DB::PREFIX . UserRoleRelation::TABLE . '` WHERE `user_id` = :user)' + . ' FROM `msz_roles`' + . ' WHERE `role_id` IN (SELECT `role_id` FROM `msz_users_roles` WHERE `user_id` = :user)' )->bind('user', $this->getId())->fetchColumn(); return $this->userRank; } public function hasAuthorityOver(HasRankInterface $other): bool { - // Don't even bother checking if we're a super user - if($this->isSuper()) - return true; - if($other instanceof self && $other->getId() === $this->getId()) - return true; - return $this->getRank() > $other->getRank(); + return $this->isSuper() + || $other instanceof self && $other->getId() === $this->getId() + || $this->getRank() > $other->getRank(); } public function getDisplayRoleId(): int { 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 { return !empty($this->user_totp_key); @@ -417,50 +403,6 @@ class User implements HasRankInterface { 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 * ***************/ @@ -621,7 +563,7 @@ class User implements HasRankInterface { 'UPDATE `' . DB::PREFIX . self::TABLE . '`' . ' SET `username` = :username, `email` = :email, `password` = :password' . ', `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' ) ->bind('user', $this->user_id) ->bind('username', $this->username) @@ -630,7 +572,6 @@ class User implements HasRankInterface { ->bind('is_super', $this->user_super) ->bind('country', $this->user_country) ->bind('colour', $this->user_colour) - ->bind('display_role', $this->display_role) ->bind('totp', $this->user_totp_key) ->bind('title', $this->user_title) ->execute(); diff --git a/src/Users/UserRole.php b/src/Users/UserRole.php deleted file mode 100644 index f1bcdc9..0000000 --- a/src/Users/UserRole.php +++ /dev/null @@ -1,223 +0,0 @@ -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 {} -} diff --git a/src/Users/UserRoleRelation.php b/src/Users/UserRoleRelation.php deleted file mode 100644 index 4c63294..0000000 --- a/src/Users/UserRoleRelation.php +++ /dev/null @@ -1,88 +0,0 @@ -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); - } -} diff --git a/src/Users/Users.php b/src/Users/Users.php new file mode 100644 index 0000000..1a39704 --- /dev/null +++ b/src/Users/Users.php @@ -0,0 +1,142 @@ +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(); + } +} diff --git a/src/Users/Warnings.php b/src/Users/Warnings.php index f9093ff..ca6ed46 100644 --- a/src/Users/Warnings.php +++ b/src/Users/Warnings.php @@ -156,6 +156,8 @@ class Warnings { public function deleteWarnings(WarningInfo|string|array $warnInfos): void { if(!is_array($warnInfos)) $warnInfos = [$warnInfos]; + elseif(empty($warnInfos)) + return; $stmt = $this->cache->get(sprintf( 'DELETE FROM msz_users_warnings WHERE warn_id IN (%s)', diff --git a/templates/manage/users/role.twig b/templates/manage/users/role.twig index 1f3491e..4668cb7 100644 --- a/templates/manage/users/role.twig +++ b/templates/manage/users/role.twig @@ -4,7 +4,7 @@ {% from '_layout/input.twig' import input_csrf, input_text, input_checkbox %} {% block manage_content %} -
+ {{ input_csrf() }}
@@ -13,28 +13,35 @@ + + @@ -46,28 +53,28 @@ @@ -79,7 +86,7 @@
diff --git a/templates/manage/users/roles.twig b/templates/manage/users/roles.twig index 06a4121..c6982dc 100644 --- a/templates/manage/users/roles.twig +++ b/templates/manage/users/roles.twig @@ -32,8 +32,8 @@ {% for role in manage_roles %} -
- +
+
@@ -44,23 +44,23 @@
- {{ role.name }} + {{ role.info.name }}
- {% if role.userCount > 0 %} + {% if role.members > 0 %}
- {{ role.userCount|number_format }} + {{ role.members|number_format }}
{% endif %} - {% if role.title is not empty %} + {% if role.info.title is not empty %}
- {{ role.title }} + {{ role.info.title }}
{% endif %}
diff --git a/templates/manage/users/user.twig b/templates/manage/users/user.twig index 2733240..ced3b81 100644 --- a/templates/manage/users/user.twig +++ b/templates/manage/users/user.twig @@ -106,14 +106,13 @@ {{ input_colour(can_edit_user ? 'colour[hex]' : '', '', user_info.userColour) }}
- {# TODO: if the hierarchy of the current user is too low to touch the role then opacity should be lowered and input disabled #}
{% for role in manage_roles %}