From 383e2ed0e01d4f76a7e25b4c917c8685ef09e8e7 Mon Sep 17 00:00:00 2001 From: flashwave Date: Wed, 2 Aug 2023 22:12:47 +0000 Subject: [PATCH] Rewrote the user information class. This one took multiple days and it pretty invasive into the core of Misuzu so issue might (will) arise, there's also some features that have gone temporarily missing in the mean time and some inefficiencies introduced that will be fixed again at a later time. The old class isn't gone entirely because I still have to figure out what I'm gonna do about validation, but for the most part this knocks out one of the "layers of backwards compatibility", as I've been referring to it, and is moving us closer to a future where Flashii actually gets real updates. If you run into anything that's broken and you're inhibited from reporting it through the forum, do it through chat or mail me at flashii-issues@flash.moe. --- public-legacy/auth/login.php | 28 +- public-legacy/auth/logout.php | 4 +- public-legacy/auth/password.php | 20 +- public-legacy/auth/register.php | 15 +- public-legacy/auth/revert.php | 5 +- public-legacy/auth/twofactor.php | 17 +- public-legacy/comments.php | 11 +- public-legacy/forum/forum.php | 7 +- public-legacy/forum/index.php | 7 +- public-legacy/forum/leaderboard.php | 4 +- public-legacy/forum/post.php | 8 +- public-legacy/forum/posting.php | 6 +- public-legacy/forum/topic.php | 9 +- public-legacy/manage/changelog/change.php | 4 +- public-legacy/manage/changelog/index.php | 8 +- public-legacy/manage/changelog/tag.php | 3 +- public-legacy/manage/changelog/tags.php | 4 +- public-legacy/manage/forum/category.php | 4 +- public-legacy/manage/forum/index.php | 4 +- public-legacy/manage/forum/redirs.php | 6 +- public-legacy/manage/general/emoticon.php | 3 +- public-legacy/manage/general/emoticons.php | 3 +- public-legacy/manage/general/logs.php | 13 +- .../manage/general/setting-delete.php | 4 +- public-legacy/manage/general/setting.php | 4 +- public-legacy/manage/general/settings.php | 5 +- public-legacy/manage/news/categories.php | 4 +- public-legacy/manage/news/category.php | 3 +- public-legacy/manage/news/post.php | 5 +- public-legacy/manage/news/posts.php | 4 +- public-legacy/manage/users/ban.php | 9 +- public-legacy/manage/users/bans.php | 44 +- public-legacy/manage/users/index.php | 30 +- public-legacy/manage/users/note.php | 14 +- public-legacy/manage/users/notes.php | 44 +- public-legacy/manage/users/role.php | 15 +- public-legacy/manage/users/roles.php | 4 +- public-legacy/manage/users/user.php | 55 +- public-legacy/manage/users/warning.php | 9 +- public-legacy/manage/users/warnings.php | 44 +- public-legacy/members.php | 112 ++-- public-legacy/profile.php | 82 +-- public-legacy/search.php | 18 +- public-legacy/settings/account.php | 62 +- public-legacy/settings/data.php | 58 +- public-legacy/settings/index.php | 4 +- public-legacy/settings/logs.php | 4 +- public-legacy/settings/sessions.php | 7 +- public/index.php | 61 +- src/AuditLog/AuditLog.php | 20 +- src/AuditLog/AuditLogInfo.php | 1 - src/Auth/LoginAttempts.php | 20 +- src/Auth/RecoveryTokens.php | 14 +- src/Auth/Sessions.php | 30 +- src/Auth/TwoFactorAuthSessions.php | 8 +- src/AuthToken.php | 32 +- src/Changelog/Changelog.php | 26 +- src/Comments/Comments.php | 56 +- src/Comments/CommentsCategoryInfo.php | 8 +- src/Comments/CommentsEx.php | 34 +- src/Comments/CommentsParser.php | 63 -- src/Forum/forum.php | 47 +- src/Http/Handlers/AssetsHandler.php | 32 +- src/Http/Handlers/ChangelogHandler.php | 22 +- src/Http/Handlers/ForumHandler.php | 7 +- src/Http/Handlers/HomeHandler.php | 75 ++- src/Http/Handlers/NewsHandler.php | 23 +- src/Memoizer.php | 30 - src/MisuzuContext.php | 187 +++++- src/News/News.php | 14 +- src/Profile/ProfileFields.php | 18 +- src/Satori/SatoriRoutes.php | 26 +- src/SharpChat/SharpChatPerms.php | 15 +- src/SharpChat/SharpChatRoutes.php | 79 +-- src/TwigMisuzu.php | 2 - src/Users/Assets/UserAvatarAsset.php | 13 +- src/Users/Assets/UserBackgroundAsset.php | 45 +- src/Users/Assets/UserImageAsset.php | 12 +- src/Users/Bans.php | 31 +- src/Users/ModNotes.php | 37 +- src/Users/RoleInfo.php | 4 + src/Users/Roles.php | 12 +- src/Users/User.php | 594 ------------------ src/Users/UserInfo.php | 231 +++++++ src/Users/Users.php | 441 ++++++++++++- src/Users/Warnings.php | 27 +- src/perms.php | 36 +- templates/_layout/comments.twig | 15 +- templates/_layout/footer.twig | 2 +- templates/_layout/header.twig | 74 +-- templates/auth/password_reset.twig | 2 +- templates/changelog/change.twig | 6 +- templates/changelog/index.twig | 2 +- templates/changelog/macros.twig | 5 +- templates/errors/401.twig | 8 + templates/forum/forum.twig | 2 +- templates/forum/index.twig | 2 +- templates/forum/posting.twig | 6 +- templates/forum/topic.twig | 2 +- templates/home/home.twig | 27 +- templates/home/landing.twig | 22 +- templates/manage/changelog/change.twig | 2 +- templates/manage/general/logs.twig | 2 +- templates/manage/users/ban.twig | 2 +- templates/manage/users/bans.twig | 12 +- templates/manage/users/note.twig | 14 +- templates/manage/users/notes.twig | 12 +- templates/manage/users/user.twig | 22 +- templates/manage/users/users.twig | 30 +- templates/manage/users/warning.twig | 2 +- templates/manage/users/warnings.twig | 12 +- templates/master.twig | 94 --- templates/news/macros.twig | 14 +- templates/news/post.twig | 2 +- templates/profile/_layout/header.twig | 24 +- templates/profile/index.twig | 46 +- templates/profile/master.twig | 6 +- templates/user/listing.twig | 10 +- templates/user/macros.twig | 57 +- 119 files changed, 1992 insertions(+), 1816 deletions(-) delete mode 100644 src/Comments/CommentsParser.php delete mode 100644 src/Memoizer.php create mode 100644 src/Users/UserInfo.php create mode 100644 templates/errors/401.twig diff --git a/public-legacy/auth/login.php b/public-legacy/auth/login.php index 89dea63..77a650d 100644 --- a/public-legacy/auth/login.php +++ b/public-legacy/auth/login.php @@ -1,21 +1,24 @@ isLoggedIn()) { url_redirect('index'); return; } +$users = $msz->getUsers(); +$sessions = $msz->getSessions(); +$loginAttempts = $msz->getLoginAttempts(); + if(!empty($_GET['resolve'])) { header('Content-Type: application/json; charset=utf-8'); try { // Only works for usernames, this is by design - $userInfo = User::byUsername((string)filter_input(INPUT_GET, 'name')); - } catch(RuntimeException $ex) { + $userInfo = $users->getUser((string)filter_input(INPUT_GET, 'name'), 'name'); + } catch(Exception $ex) { echo json_encode([ 'id' => 0, 'name' => '', @@ -25,8 +28,8 @@ if(!empty($_GET['resolve'])) { } echo json_encode([ - 'id' => $userInfo->getId(), - 'name' => $userInfo->getUsername(), + 'id' => (int)$userInfo->getId(), + 'name' => $userInfo->getName(), 'avatar' => url('user-avatar', ['user' => $userInfo->getId(), 'res' => 200]), ]); return; @@ -37,9 +40,6 @@ $ipAddress = $_SERVER['REMOTE_ADDR']; $countryCode = $_SERVER['COUNTRY_CODE'] ?? 'XX'; $userAgent = $_SERVER['HTTP_USER_AGENT'] ?? ''; -$sessions = $msz->getSessions(); -$loginAttempts = $msz->getLoginAttempts(); - $remainingAttempts = $loginAttempts->countRemainingAttempts($ipAddress); $siteIsPrivate = $cfg->getBoolean('private.enable'); @@ -91,26 +91,26 @@ while(!empty($_POST['login']) && is_array($_POST['login'])) { $loginFailedError = "Invalid username or password, {$attemptsRemainingError}."; try { - $userInfo = User::byUsernameOrEMailAddress($_POST['login']['username']); + $userInfo = $users->getUser($_POST['login']['username'], 'login'); } catch(RuntimeException $ex) { $loginAttempts->recordAttempt(false, $ipAddress, $countryCode, $userAgent, $clientInfo); $notices[] = $loginFailedError; break; } - if(!$userInfo->hasPassword()) { + if(!$userInfo->hasPasswordHash()) { $notices[] = 'Your password has been invalidated, please reset it.'; break; } - if($userInfo->isDeleted() || !$userInfo->checkPassword($_POST['login']['password'])) { + if($userInfo->isDeleted() || !$userInfo->verifyPassword($_POST['login']['password'])) { $loginAttempts->recordAttempt(false, $ipAddress, $countryCode, $userAgent, $clientInfo, $userInfo); $notices[] = $loginFailedError; break; } if($userInfo->passwordNeedsRehash()) - $userInfo->setPassword($_POST['login']['password'])->save(); + $users->updateUser($userInfo, password: $_POST['login']['password']); if(!empty($loginPermCat) && $loginPermVal > 0 && !perms_check_user($loginPermCat, $userInfo->getId(), $loginPermVal)) { $notices[] = "Login succeeded, but you're not allowed to browse the site right now."; diff --git a/public-legacy/auth/logout.php b/public-legacy/auth/logout.php index 41d8d63..2dfa5e1 100644 --- a/public-legacy/auth/logout.php +++ b/public-legacy/auth/logout.php @@ -1,9 +1,7 @@ isLoggedIn()) { url_redirect('index'); return; } diff --git a/public-legacy/auth/password.php b/public-legacy/auth/password.php index 2c746fa..a29085b 100644 --- a/public-legacy/auth/password.php +++ b/public-legacy/auth/password.php @@ -4,11 +4,15 @@ namespace Misuzu; use RuntimeException; use Misuzu\Users\User; -if(User::hasCurrent()) { +if($msz->isLoggedIn()) { url_redirect('settings-account'); return; } +$users = $msz->getUsers(); +$recoveryTokens = $msz->getRecoveryTokens(); +$loginAttempts = $msz->getLoginAttempts(); + $reset = !empty($_POST['reset']) && is_array($_POST['reset']) ? $_POST['reset'] : []; $forgot = !empty($_POST['forgot']) && is_array($_POST['forgot']) ? $_POST['forgot'] : []; $userId = !empty($reset['user']) ? (int)$reset['user'] : ( @@ -17,7 +21,7 @@ $userId = !empty($reset['user']) ? (int)$reset['user'] : ( if($userId > 0) try { - $userInfo = User::byId($userId); + $userInfo = $users->getUser((string)$userId, 'id'); } catch(RuntimeException $ex) { url_redirect('auth-forgot'); return; @@ -28,8 +32,6 @@ $ipAddress = $_SERVER['REMOTE_ADDR']; $siteIsPrivate = $cfg->getBoolean('private.enable'); $canResetPassword = $siteIsPrivate ? $cfg->getBoolean('private.allow_password_reset', true) : true; -$recoveryTokens = $msz->getRecoveryTokens(); -$loginAttempts = $msz->getLoginAttempts(); $remainingAttempts = $loginAttempts->countRemainingAttempts($ipAddress); while($canResetPassword) { @@ -69,9 +71,7 @@ while($canResetPassword) { // also disables two factor auth to prevent getting locked out of account entirely // this behaviour should really be replaced with recovery keys... - $userInfo->setPassword($passwordNew) - ->removeTOTPKey() - ->save(); + $users->updateUser($userInfo, password: $passwordNew, totpKey: ''); $msz->createAuditLog('PASSWORD_RESET', [], $userInfo); @@ -98,7 +98,7 @@ while($canResetPassword) { } try { - $forgotUser = User::byEMailAddress($forgot['email']); + $forgotUser = $users->getUser($forgot['email'], 'email'); } catch(RuntimeException $ex) { unset($forgotUser); } @@ -114,12 +114,12 @@ while($canResetPassword) { $tokenInfo = $recoveryTokens->createToken($forgotUser, $ipAddress); $recoveryMessage = Mailer::template('password-recovery', [ - 'username' => $forgotUser->getUsername(), + 'username' => $forgotUser->getName(), 'token' => $tokenInfo->getCode(), ]); $recoveryMail = Mailer::sendMessage( - [$forgotUser->getEMailAddress() => $forgotUser->getUsername()], + [$forgotUser->getEMailAddress() => $forgotUser->getName()], $recoveryMessage['subject'], $recoveryMessage['message'] ); diff --git a/public-legacy/auth/register.php b/public-legacy/auth/register.php index 525c2e4..03e4280 100644 --- a/public-legacy/auth/register.php +++ b/public-legacy/auth/register.php @@ -4,13 +4,14 @@ namespace Misuzu; use RuntimeException; use Misuzu\Users\User; -if(User::hasCurrent()) { +if($msz->isLoggedIn()) { url_redirect('index'); return; } $users = $msz->getUsers(); $roles = $msz->getRoles(); +$config = $msz->getConfig(); $register = !empty($_POST['register']) && is_array($_POST['register']) ? $_POST['register'] : []; $notices = []; @@ -83,22 +84,26 @@ while(!$restricted && !empty($register)) { if(!empty($notices)) break; + $defaultRoleInfo = $roles->getDefaultRole(); + try { - $createUser = User::create( + $userInfo = $users->createUser( $register['username'], $register['password'], $register['email'], $ipAddress, - $countryCode + $countryCode, + $defaultRoleInfo ); } catch(RuntimeException $ex) { $notices[] = 'Something went wrong while creating your account, please alert an administrator or a developer about this!'; break; } - $users->addRoles($createUser, $roles->getDefaultRole()); + $users->addRoles($userInfo, $defaultRoleInfo); + $config->setString('users.newest', $userInfo->getId()); - url_redirect('auth-login-welcome', ['username' => $createUser->getUsername()]); + url_redirect('auth-login-welcome', ['username' => $userInfo->getName()]); return; } diff --git a/public-legacy/auth/revert.php b/public-legacy/auth/revert.php index 4424c03..b10f36b 100644 --- a/public-legacy/auth/revert.php +++ b/public-legacy/auth/revert.php @@ -1,18 +1,15 @@ hasImpersonatedUserId() || !CSRF::validateRequest()) { url_redirect('index'); return; } +$impUserId = $authToken->getImpersonatedUserId(); $authToken->removeImpersonatedUserId(); $authToken->applyCookie(); -$impUserId = User::hasCurrent() ? User::getCurrent()->getId() : 0; - url_redirect( $impUserId > 0 ? 'manage-user' : 'index', ['user' => $impUserId] diff --git a/public-legacy/auth/twofactor.php b/public-legacy/auth/twofactor.php index 1e8e476..7d1a57a 100644 --- a/public-legacy/auth/twofactor.php +++ b/public-legacy/auth/twofactor.php @@ -2,23 +2,24 @@ namespace Misuzu; use RuntimeException; -use Misuzu\Users\User; +use Misuzu\TOTPGenerator; -if(User::hasCurrent()) { +if($msz->isLoggedIn()) { url_redirect('index'); return; } +$users = $msz->getUsers(); +$sessions = $msz->getSessions(); +$tfaSessions = $msz->getTFASessions(); +$loginAttempts = $msz->getLoginAttempts(); + $ipAddress = $_SERVER['REMOTE_ADDR']; $countryCode = $_SERVER['COUNTRY_CODE'] ?? 'XX'; $userAgent = $_SERVER['HTTP_USER_AGENT'] ?? ''; $twofactor = !empty($_POST['twofactor']) && is_array($_POST['twofactor']) ? $_POST['twofactor'] : []; $notices = []; -$sessions = $msz->getSessions(); -$tfaSessions = $msz->getTFASessions(); -$loginAttempts = $msz->getLoginAttempts(); - $remainingAttempts = $loginAttempts->countRemainingAttempts($ipAddress); $tokenString = !empty($_GET['token']) && is_string($_GET['token']) ? $_GET['token'] : ( @@ -31,7 +32,7 @@ if(empty($tokenUserId)) { return; } -$userInfo = User::byId((int)$tokenUserId); +$userInfo = $users->getUser($tokenUserId, 'id'); // checking user_totp_key specifically because there's a fringe chance that // there's a token present, but totp is actually disabled @@ -60,7 +61,7 @@ while(!empty($twofactor)) { } $clientInfo = ClientInfo::fromRequest(); - $totp = $userInfo->createTOTPGenerator(); + $totp = new TOTPGenerator($userInfo->getTOTPKey()); if(!in_array($twofactor['code'], $totp->generateRange())) { $notices[] = sprintf( diff --git a/public-legacy/comments.php b/public-legacy/comments.php index 68da310..fa5a401 100644 --- a/public-legacy/comments.php +++ b/public-legacy/comments.php @@ -2,7 +2,6 @@ namespace Misuzu; use RuntimeException; -use Misuzu\Users\User; // basing whether or not this is an xhr request on whether a referrer header is present // this page is never directy accessed, under normal circumstances @@ -18,9 +17,8 @@ if(!CSRF::validateRequest()) { return; } -$currentUserInfo = User::getCurrent(); -if($currentUserInfo === null) { - echo render_info('You must be logged in to manage comments.', 401); +if(!$msz->isLoggedIn()) { + echo render_info('You must be logged in to manage comments.', 403); return; } @@ -29,8 +27,9 @@ if($msz->hasActiveBan()) { return; } -$comments = $msz->getComments(); +$currentUserInfo = $msz->getActiveUser(); +$comments = $msz->getComments(); $commentPerms = perms_for_comments($currentUserInfo->getId()); $commentId = (string)filter_input(INPUT_GET, 'c', FILTER_SANITIZE_NUMBER_INT); @@ -127,7 +126,7 @@ switch($commentMode) { break; } - $isOwnComment = $commentInfo->getUserId() === (string)$currentUserInfo->getId(); + $isOwnComment = $commentInfo->getUserId() === $currentUserInfo->getId(); $isModAction = $commentPerms['can_delete_any'] && !$isOwnComment; if(!$isModAction && !$isOwnComment) { diff --git a/public-legacy/forum/forum.php b/public-legacy/forum/forum.php index 9fb7bc9..11b0a36 100644 --- a/public-legacy/forum/forum.php +++ b/public-legacy/forum/forum.php @@ -1,8 +1,6 @@ getId(); +$forumUser = $msz->getActiveUser(); +$forumUserId = $forumUser === null ? '0' : $forumUser->getId(); if(empty($forum) || ($forum['forum_type'] == MSZ_FORUM_TYPE_LINK && empty($forum['forum_link']))) { echo render_error(404); @@ -75,4 +73,5 @@ Template::render('forum.forum', [ 'forum_info' => $forum, 'forum_topics' => $topics, 'forum_pagination' => $forumPagination, + 'forum_show_mark_as_read' => $forumUser !== null, ]); diff --git a/public-legacy/forum/index.php b/public-legacy/forum/index.php index cf4aa3e..45cd0b2 100644 --- a/public-legacy/forum/index.php +++ b/public-legacy/forum/index.php @@ -1,13 +1,11 @@ getId(); +$currentUser = $msz->getActiveUser(); +$currentUserId = $currentUser === null ? '0' : $currentUser->getId(); switch($indexMode) { case 'mark': @@ -34,6 +32,7 @@ switch($indexMode) { Template::render('forum.index', [ 'forum_categories' => $categories, 'forum_empty' => $blankForum, + 'forum_show_mark_as_read' => $currentUser !== null, ]); break; } diff --git a/public-legacy/forum/leaderboard.php b/public-legacy/forum/leaderboard.php index bcce292..7f2f912 100644 --- a/public-legacy/forum/leaderboard.php +++ b/public-legacy/forum/leaderboard.php @@ -1,9 +1,7 @@ getId(), MSZ_PERM_FORUM_VIEW_LEADERBOARD)) { +if(!$msz->isLoggedIn() || !perms_check_user(MSZ_PERMS_FORUM, $msz->getActiveUser()->getId(), MSZ_PERM_FORUM_VIEW_LEADERBOARD)) { echo render_error(403); return; } diff --git a/public-legacy/forum/post.php b/public-legacy/forum/post.php index a69f3b1..b5cc74b 100644 --- a/public-legacy/forum/post.php +++ b/public-legacy/forum/post.php @@ -1,21 +1,19 @@ isLoggedIn()) { echo render_info('You must be logged in to manage posts.', 401); return; } -$currentUser = User::getCurrent(); -$currentUserId = $currentUser === null ? 0 : $currentUser->getId(); +$currentUser = $msz->getActiveUser(); +$currentUserId = $currentUser === null ? '0' : $currentUser->getId(); if($postMode !== '' && $msz->hasActiveBan()) { echo render_info('You have been banned, check your profile for more information.', 403); diff --git a/public-legacy/forum/posting.php b/public-legacy/forum/posting.php index 38d566d..ded3118 100644 --- a/public-legacy/forum/posting.php +++ b/public-legacy/forum/posting.php @@ -2,15 +2,13 @@ namespace Misuzu; use Misuzu\Parsers\Parser; -use Misuzu\Users\User; -$currentUser = User::getCurrent(); - -if($currentUser === null) { +if(!$msz->isLoggedIn()) { echo render_error(401); return; } +$currentUser = $msz->getActiveUser(); $currentUserId = $currentUser->getId(); if($msz->hasActiveBan()) { echo render_error(403); diff --git a/public-legacy/forum/topic.php b/public-legacy/forum/topic.php index eede247..90cedfd 100644 --- a/public-legacy/forum/topic.php +++ b/public-legacy/forum/topic.php @@ -1,15 +1,13 @@ getId(); +$topicUser = $msz->getActiveUser(); +$topicUserId = $topicUser === null ? '0' : $topicUser->getId(); if($topicId < 1 && $postId > 0) { $postInfo = forum_post_find($postId, $topicUserId); @@ -77,7 +75,7 @@ if(in_array($moderationMode, $validModerationModes, true)) { return; } - if(!User::hasCurrent()) { + if(!$msz->isLoggedIn()) { echo render_info('You must be logged in to manage posts.', 401); return; } @@ -329,4 +327,5 @@ Template::render('forum.topic', [ 'topic_can_nuke_or_restore' => $canNukeOrRestore, 'topic_can_bump' => $canBumpTopic, 'topic_can_lock' => $canLockTopic, + 'topic_user_id' => $topicUserId, ]); diff --git a/public-legacy/manage/changelog/change.php b/public-legacy/manage/changelog/change.php index 4093762..bb6ccce 100644 --- a/public-legacy/manage/changelog/change.php +++ b/public-legacy/manage/changelog/change.php @@ -5,9 +5,8 @@ use DateTimeInterface; use RuntimeException; use Index\DateTime; use Misuzu\Changelog\Changelog; -use Misuzu\Users\User; -if(!User::hasCurrent() || !perms_check_user(MSZ_PERMS_CHANGELOG, User::getCurrent()->getId(), MSZ_PERM_CHANGELOG_MANAGE_CHANGES)) { +if(!$msz->isLoggedIn() || !perms_check_user(MSZ_PERMS_CHANGELOG, $msz->getActiveUser()->getId(), MSZ_PERM_CHANGELOG_MANAGE_CHANGES)) { echo render_error(403); return; } @@ -115,4 +114,5 @@ Template::render('manage.changelog.change', [ 'change_info' => $changeInfo ?? null, 'change_tags' => $changeTags, 'change_actions' => $changeActions, + 'change_author_id' => $msz->getActiveUser()->getId(), ]); diff --git a/public-legacy/manage/changelog/index.php b/public-legacy/manage/changelog/index.php index b1756ab..70ca10b 100644 --- a/public-legacy/manage/changelog/index.php +++ b/public-legacy/manage/changelog/index.php @@ -2,9 +2,8 @@ namespace Misuzu; use RuntimeException; -use Misuzu\Users\User; -if(!User::hasCurrent() || !perms_check_user(MSZ_PERMS_CHANGELOG, User::getCurrent()->getId(), MSZ_PERM_CHANGELOG_MANAGE_CHANGES)) { +if(!$msz->isLoggedIn() || !perms_check_user(MSZ_PERMS_CHANGELOG, $msz->getActiveUser()->getId(), MSZ_PERM_CHANGELOG_MANAGE_CHANGES)) { echo render_error(403); return; } @@ -20,6 +19,7 @@ if(!$changelogPagination->hasValidOffset()) { $changeInfos = $changelog->getAllChanges(withTags: true, pagination: $changelogPagination); $changes = []; $userInfos = []; +$userColours = []; foreach($changeInfos as $changeInfo) { $userId = $changeInfo->getUserId(); @@ -28,7 +28,8 @@ foreach($changeInfos as $changeInfo) { $userInfo = $userInfos[$userId]; } else { try { - $userInfo = User::byId($userId); + $userInfo = $users->getUser($userId, 'id'); + $userColours[$userId] = $users->getUserColour($userInfo); } catch(RuntimeException $ex) { $userInfo = null; } @@ -39,6 +40,7 @@ foreach($changeInfos as $changeInfo) { $changes[] = [ 'change' => $changeInfo, 'user' => $userInfo, + 'user_colour' => $userColours[$userId] ?? \Index\Colour\Colour::none(), ]; } diff --git a/public-legacy/manage/changelog/tag.php b/public-legacy/manage/changelog/tag.php index 9d7c082..20c2043 100644 --- a/public-legacy/manage/changelog/tag.php +++ b/public-legacy/manage/changelog/tag.php @@ -2,9 +2,8 @@ namespace Misuzu; use RuntimeException; -use Misuzu\Users\User; -if(!User::hasCurrent() || !perms_check_user(MSZ_PERMS_CHANGELOG, User::getCurrent()->getId(), MSZ_PERM_CHANGELOG_MANAGE_TAGS)) { +if(!$msz->isLoggedIn() || !perms_check_user(MSZ_PERMS_CHANGELOG, $msz->getActiveUser()->getId(), MSZ_PERM_CHANGELOG_MANAGE_TAGS)) { echo render_error(403); return; } diff --git a/public-legacy/manage/changelog/tags.php b/public-legacy/manage/changelog/tags.php index 67d1107..768af53 100644 --- a/public-legacy/manage/changelog/tags.php +++ b/public-legacy/manage/changelog/tags.php @@ -1,9 +1,7 @@ getId(), MSZ_PERM_CHANGELOG_MANAGE_TAGS)) { +if(!$msz->isLoggedIn() || !perms_check_user(MSZ_PERMS_CHANGELOG, $msz->getActiveUser()->getId(), MSZ_PERM_CHANGELOG_MANAGE_TAGS)) { echo render_error(403); return; } diff --git a/public-legacy/manage/forum/category.php b/public-legacy/manage/forum/category.php index 7767bcf..16f62c9 100644 --- a/public-legacy/manage/forum/category.php +++ b/public-legacy/manage/forum/category.php @@ -1,9 +1,7 @@ getId(), MSZ_PERM_FORUM_MANAGE_FORUMS)) { +if(!$msz->isLoggedIn() || !perms_check_user(MSZ_PERMS_GENERAL, $msz->getActiveUser()->getId(), MSZ_PERM_FORUM_MANAGE_FORUMS)) { echo render_error(403); return; } diff --git a/public-legacy/manage/forum/index.php b/public-legacy/manage/forum/index.php index fe96e11..2032c11 100644 --- a/public-legacy/manage/forum/index.php +++ b/public-legacy/manage/forum/index.php @@ -1,9 +1,7 @@ getId(), MSZ_PERM_FORUM_MANAGE_FORUMS)) { +if(!$msz->isLoggedIn() || !perms_check_user(MSZ_PERMS_GENERAL, $msz->getActiveUser()->getId(), MSZ_PERM_FORUM_MANAGE_FORUMS)) { echo render_error(403); return; } diff --git a/public-legacy/manage/forum/redirs.php b/public-legacy/manage/forum/redirs.php index 988dd0c..4454ec2 100644 --- a/public-legacy/manage/forum/redirs.php +++ b/public-legacy/manage/forum/redirs.php @@ -1,9 +1,7 @@ getId(), MSZ_PERM_FORUM_TOPIC_REDIRS)) { +if(!$msz->isLoggedIn() || !perms_check_user(MSZ_PERMS_GENERAL, $msz->getActiveUser()->getId(), MSZ_PERM_FORUM_TOPIC_REDIRS)) { echo render_error(403); return; } @@ -19,7 +17,7 @@ if($_SERVER['REQUEST_METHOD'] === 'POST') { throw new \Exception("Invalid topic id."); $msz->createAuditLog('FORUM_TOPIC_REDIR_CREATE', [$rTopicId]); - forum_topic_redir_create($rTopicId, User::getCurrent()->getId(), $rTopicURL); + forum_topic_redir_create($rTopicId, $msz->getActiveUser()->getId(), $rTopicURL); url_redirect('manage-forum-topic-redirs'); return; } diff --git a/public-legacy/manage/general/emoticon.php b/public-legacy/manage/general/emoticon.php index ef37bab..e9c4e18 100644 --- a/public-legacy/manage/general/emoticon.php +++ b/public-legacy/manage/general/emoticon.php @@ -2,9 +2,8 @@ namespace Misuzu; use RuntimeException; -use Misuzu\Users\User; -if(!User::hasCurrent() || !perms_check_user(MSZ_PERMS_GENERAL, User::getCurrent()->getId(), MSZ_PERM_GENERAL_MANAGE_EMOTES)) { +if(!$msz->isLoggedIn() || !perms_check_user(MSZ_PERMS_GENERAL, $msz->getActiveUser()->getId(), MSZ_PERM_GENERAL_MANAGE_EMOTES)) { echo render_error(403); return; } diff --git a/public-legacy/manage/general/emoticons.php b/public-legacy/manage/general/emoticons.php index 9caadfb..eca8e9e 100644 --- a/public-legacy/manage/general/emoticons.php +++ b/public-legacy/manage/general/emoticons.php @@ -2,9 +2,8 @@ namespace Misuzu; use RuntimeException; -use Misuzu\Users\User; -if(!User::hasCurrent() || !perms_check_user(MSZ_PERMS_GENERAL, User::getCurrent()->getId(), MSZ_PERM_GENERAL_MANAGE_EMOTES)) { +if(!$msz->isLoggedIn() || !perms_check_user(MSZ_PERMS_GENERAL, $msz->getActiveUser()->getId(), MSZ_PERM_GENERAL_MANAGE_EMOTES)) { echo render_error(403); return; } diff --git a/public-legacy/manage/general/logs.php b/public-legacy/manage/general/logs.php index 81405fc..f113124 100644 --- a/public-legacy/manage/general/logs.php +++ b/public-legacy/manage/general/logs.php @@ -2,13 +2,13 @@ namespace Misuzu; use Misuzu\Pagination; -use Misuzu\Users\User; -if(!User::hasCurrent() || !perms_check_user(MSZ_PERMS_GENERAL, User::getCurrent()->getId(), MSZ_PERM_GENERAL_VIEW_LOGS)) { +if(!$msz->isLoggedIn() || !perms_check_user(MSZ_PERMS_GENERAL, $msz->getActiveUser()->getId(), MSZ_PERM_GENERAL_VIEW_LOGS)) { echo render_error(403); return; } +$users = $msz->getUsers(); $auditLog = $msz->getAuditLog(); $pagination = new Pagination($auditLog->countLogs(), 50); @@ -19,15 +19,20 @@ if(!$pagination->hasValidOffset()) { $logs = $auditLog->getLogs(pagination: $pagination); $userInfos = []; +$userColours = []; + foreach($logs as $log) if($log->hasUserId()) { $userId = $log->getUserId(); - if(!array_key_exists($userId, $userInfos)) - $userInfos[$userId] = User::byId($userId); + if(!array_key_exists($userId, $userInfos)) { + $userInfos[$userId] = $users->getUser($userId, 'id'); + $userColours[$userId] = $users->getUserColour($userInfos[$userId]); + } } Template::render('manage.general.logs', [ 'global_logs' => $logs, 'global_logs_pagination' => $pagination, 'global_logs_users' => $userInfos, + 'global_logs_users_colours' => $userColours, ]); diff --git a/public-legacy/manage/general/setting-delete.php b/public-legacy/manage/general/setting-delete.php index 5d598bb..0d3224a 100644 --- a/public-legacy/manage/general/setting-delete.php +++ b/public-legacy/manage/general/setting-delete.php @@ -2,10 +2,8 @@ namespace Misuzu; use Misuzu\Config\CfgTools; -use Misuzu\Users\User; -if(!User::hasCurrent() - || !perms_check_user(MSZ_PERMS_GENERAL, User::getCurrent()->getId(), MSZ_PERM_GENERAL_MANAGE_CONFIG)) { +if(!$msz->isLoggedIn() || !perms_check_user(MSZ_PERMS_GENERAL, $msz->getActiveUser()->getId(), MSZ_PERM_GENERAL_MANAGE_CONFIG)) { echo render_error(403); return; } diff --git a/public-legacy/manage/general/setting.php b/public-legacy/manage/general/setting.php index 10256fb..496b3f3 100644 --- a/public-legacy/manage/general/setting.php +++ b/public-legacy/manage/general/setting.php @@ -2,10 +2,8 @@ namespace Misuzu; use Misuzu\Config\DbConfig; -use Misuzu\Users\User; -if(!User::hasCurrent() - || !perms_check_user(MSZ_PERMS_GENERAL, User::getCurrent()->getId(), MSZ_PERM_GENERAL_MANAGE_CONFIG)) { +if(!$msz->isLoggedIn() || !perms_check_user(MSZ_PERMS_GENERAL, $msz->getActiveUser()->getId(), MSZ_PERM_GENERAL_MANAGE_CONFIG)) { echo render_error(403); return; } diff --git a/public-legacy/manage/general/settings.php b/public-legacy/manage/general/settings.php index 07670e6..e63eafe 100644 --- a/public-legacy/manage/general/settings.php +++ b/public-legacy/manage/general/settings.php @@ -1,10 +1,7 @@ getId(), MSZ_PERM_GENERAL_MANAGE_CONFIG)) { +if(!$msz->isLoggedIn() || !perms_check_user(MSZ_PERMS_GENERAL, $msz->getActiveUser()->getId(), MSZ_PERM_GENERAL_MANAGE_CONFIG)) { echo render_error(403); return; } diff --git a/public-legacy/manage/news/categories.php b/public-legacy/manage/news/categories.php index 58e117e..130541c 100644 --- a/public-legacy/manage/news/categories.php +++ b/public-legacy/manage/news/categories.php @@ -1,9 +1,7 @@ getId(), MSZ_PERM_NEWS_MANAGE_CATEGORIES)) { +if(!$msz->isLoggedIn() || !perms_check_user(MSZ_PERMS_NEWS, $msz->getActiveUser()->getId(), MSZ_PERM_NEWS_MANAGE_CATEGORIES)) { echo render_error(403); return; } diff --git a/public-legacy/manage/news/category.php b/public-legacy/manage/news/category.php index 9e5e538..93bc76b 100644 --- a/public-legacy/manage/news/category.php +++ b/public-legacy/manage/news/category.php @@ -2,9 +2,8 @@ namespace Misuzu; use RuntimeException; -use Misuzu\Users\User; -if(!User::hasCurrent() || !perms_check_user(MSZ_PERMS_NEWS, User::getCurrent()->getId(), MSZ_PERM_NEWS_MANAGE_CATEGORIES)) { +if(!$msz->isLoggedIn() || !perms_check_user(MSZ_PERMS_NEWS, $msz->getActiveUser()->getId(), MSZ_PERM_NEWS_MANAGE_CATEGORIES)) { echo render_error(403); return; } diff --git a/public-legacy/manage/news/post.php b/public-legacy/manage/news/post.php index 2914a66..c848ab0 100644 --- a/public-legacy/manage/news/post.php +++ b/public-legacy/manage/news/post.php @@ -2,9 +2,8 @@ namespace Misuzu; use RuntimeException; -use Misuzu\Users\User; -if(!User::hasCurrent() || !perms_check_user(MSZ_PERMS_NEWS, User::getCurrent()->getId(), MSZ_PERM_NEWS_MANAGE_POSTS)) { +if(!$msz->isLoggedIn() || !perms_check_user(MSZ_PERMS_NEWS, $msz->getActiveUser()->getId(), MSZ_PERM_NEWS_MANAGE_POSTS)) { echo render_error(403); return; } @@ -40,7 +39,7 @@ while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) { $body = trim((string)filter_input(INPUT_POST, 'np_body')); if($isNew) { - $postInfo = $news->createPost($category, $title, $body, $featured, User::getCurrent()); + $postInfo = $news->createPost($category, $title, $body, $featured, $msz->getActiveUser()); } else { if($category === $postInfo->getCategoryId()) $category = null; diff --git a/public-legacy/manage/news/posts.php b/public-legacy/manage/news/posts.php index fc2645c..a36f717 100644 --- a/public-legacy/manage/news/posts.php +++ b/public-legacy/manage/news/posts.php @@ -1,9 +1,7 @@ getId(), MSZ_PERM_NEWS_MANAGE_POSTS)) { +if(!$msz->isLoggedIn() || !perms_check_user(MSZ_PERMS_NEWS, $msz->getActiveUser()->getId(), MSZ_PERM_NEWS_MANAGE_POSTS)) { echo render_error(403); return; } diff --git a/public-legacy/manage/users/ban.php b/public-legacy/manage/users/ban.php index fc26dc3..a3bd208 100644 --- a/public-legacy/manage/users/ban.php +++ b/public-legacy/manage/users/ban.php @@ -4,9 +4,8 @@ namespace Misuzu; use DateTimeInterface; use RuntimeException; use Index\DateTime; -use Misuzu\Users\User; -if(!User::hasCurrent() || !perms_check_user(MSZ_PERMS_USER, User::getCurrent()->getId(), MSZ_PERM_USER_MANAGE_BANS)) { +if(!$msz->isLoggedIn() || !perms_check_user(MSZ_PERMS_USER, $msz->getActiveUser()->getId(), MSZ_PERM_USER_MANAGE_BANS)) { echo render_error(403); return; } @@ -29,14 +28,16 @@ if($_SERVER['REQUEST_METHOD'] === 'GET' && filter_has_var(INPUT_GET, 'delete')) return; } +$users = $msz->getUsers(); + try { - $userInfo = User::byId((int)filter_input(INPUT_GET, 'u', FILTER_SANITIZE_NUMBER_INT)); + $userInfo = $users->getUser(filter_input(INPUT_GET, 'u', FILTER_SANITIZE_NUMBER_INT), 'id'); } catch(RuntimeException $ex) { echo render_error(404); return; } -$modInfo = User::getCurrent(); +$modInfo = $msz->getActiveUser(); while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) { $expires = (int)filter_input(INPUT_POST, 'ub_expires', FILTER_SANITIZE_NUMBER_INT); diff --git a/public-legacy/manage/users/bans.php b/public-legacy/manage/users/bans.php index 50bf1ff..a1b6a0b 100644 --- a/public-legacy/manage/users/bans.php +++ b/public-legacy/manage/users/bans.php @@ -2,23 +2,28 @@ namespace Misuzu; use RuntimeException; -use Misuzu\Users\User; -if(!User::hasCurrent() || !perms_check_user(MSZ_PERMS_USER, User::getCurrent()->getId(), MSZ_PERM_USER_MANAGE_BANS)) { +if(!$msz->isLoggedIn() || !perms_check_user(MSZ_PERMS_USER, $msz->getActiveUser()->getId(), MSZ_PERM_USER_MANAGE_BANS)) { echo render_error(403); return; } +$users = $msz->getUsers(); + $userInfos = [ - (string)User::getCurrent()->getId() => User::getCurrent(), + $msz->getActiveUser()->getId() => $msz->getActiveUser(), +]; +$userColours = [ + $msz->getActiveUser()->getId() => $users->getUserColour($msz->getActiveUser()), ]; $filterUser = null; if(filter_has_var(INPUT_GET, 'u')) { - $filterUserId = (int)filter_input(INPUT_GET, 'u', FILTER_SANITIZE_NUMBER_INT); + $filterUserId = filter_input(INPUT_GET, 'u', FILTER_SANITIZE_NUMBER_INT); try { - $filterUser = User::byId($filterUserId); - $userInfos[(string)$filterUser->getId()] = $filterUser; + $filterUser = $users->getUser($filterUserId, 'id'); + $userInfos[$filterUserId] = $filterUser; + $userColours[$filterUserId] = $users->getUserColour($filterUser); } catch(RuntimeException $ex) { echo render_error(404); return; @@ -40,19 +45,34 @@ foreach($banInfos as $banInfo) { if(array_key_exists($banInfo->getUserId(), $userInfos)) $userInfo = $userInfos[$banInfo->getUserId()]; else - $userInfos[$banInfo->getUserId()] = $userInfo = User::byId((int)$banInfo->getUserId()); + $userInfos[$banInfo->getUserId()] = $userInfo = $users->getUser($banInfo->getUserId(), 'id'); - if(!$banInfo->hasModId()) - $modInfo = null; - elseif(array_key_exists($banInfo->getModId(), $userInfos)) - $modInfo = $userInfos[$banInfo->getModId()]; + if(array_key_exists($userInfo->getId(), $userColours)) + $userColour = $userColours[$userInfo->getId()]; else - $userInfos[$banInfo->getModId()] = $modInfo = User::byId((int)$banInfo->getModId()); + $userColours[$userInfo->getId()] = $userColour = $users->getUserColour($userInfo); + + if(!$banInfo->hasModId()) { + $modInfo = null; + $modColour = null; + } else { + if(array_key_exists($banInfo->getModId(), $userInfos)) + $modInfo = $userInfos[$banInfo->getModId()]; + else + $userInfos[$banInfo->getModId()] = $modInfo = $users->getUser($banInfo->getModId(), 'id'); + + if(array_key_exists($modInfo->getId(), $userColours)) + $modColour = $userColours[$modInfo->getId()]; + else + $userColours[$modInfo->getId()] = $modColour = $users->getUserColour($modInfo); + } $banList[] = [ 'info' => $banInfo, 'user' => $userInfo, + 'user_colour' => $userColour, 'mod' => $modInfo, + 'mod_colour' => $modColour, ]; } diff --git a/public-legacy/manage/users/index.php b/public-legacy/manage/users/index.php index 13ef647..89601d5 100644 --- a/public-legacy/manage/users/index.php +++ b/public-legacy/manage/users/index.php @@ -1,21 +1,41 @@ getId(), MSZ_PERM_USER_MANAGE_USERS)) { +if(!$msz->isLoggedIn() || !perms_check_user(MSZ_PERMS_USER, $msz->getActiveUser()->getId(), MSZ_PERM_USER_MANAGE_USERS)) { echo render_error(403); return; } -$pagination = new Pagination(User::countAll(true), 30); +$users = $msz->getUsers(); +$roles = $msz->getRoles(); +$pagination = new Pagination($users->countUsers(), 30); if(!$pagination->hasValidOffset()) { echo render_error(404); return; } +$userList = []; +$userInfos = $users->getUsers(pagination: $pagination, orderBy: 'id'); +$roleInfos = []; + +foreach($userInfos as $userInfo) { + $displayRoleId = $userInfo->getDisplayRoleId() ?? '1'; + if(array_key_exists($displayRoleId, $roleInfos)) + $roleInfo = $roleInfos[$displayRoleId]; + else + $roleInfos[$displayRoleId] = $roleInfo = $roles->getRole($displayRoleId); + + $colour = $userInfo->hasColour() ? $userInfo->getColour() : $roleInfo->getColour(); + + $userList[] = [ + 'info' => $userInfo, + 'role' => $roleInfo, + 'colour' => $colour, + ]; +} + Template::render('manage.users.users', [ - 'manage_users' => User::all(true, $pagination), + 'manage_users' => $userList, 'manage_users_pagination' => $pagination, ]); diff --git a/public-legacy/manage/users/note.php b/public-legacy/manage/users/note.php index 8e422c4..21f8d21 100644 --- a/public-legacy/manage/users/note.php +++ b/public-legacy/manage/users/note.php @@ -2,9 +2,8 @@ namespace Misuzu; use RuntimeException; -use Misuzu\Users\User; -if(!User::hasCurrent() || !perms_check_user(MSZ_PERMS_USER, User::getCurrent()->getId(), MSZ_PERM_USER_MANAGE_NOTES)) { +if(!$msz->isLoggedIn() || !perms_check_user(MSZ_PERMS_USER, $msz->getActiveUser()->getId(), MSZ_PERM_USER_MANAGE_NOTES)) { echo render_error(403); return; } @@ -17,19 +16,20 @@ if((!$hasNoteId && !$hasUserId) || ($hasNoteId && $hasUserId)) { return; } +$users = $msz->getUsers(); $modNotes = $msz->getModNotes(); if($hasUserId) { $isNew = true; try { - $userInfo = User::byId((int)filter_input(INPUT_GET, 'u', FILTER_SANITIZE_NUMBER_INT)); + $userInfo = $users->getUser(filter_input(INPUT_GET, 'u', FILTER_SANITIZE_NUMBER_INT), 'id'); } catch(RuntimeException $ex) { echo render_error(404); return; } - $authorInfo = User::getCurrent(); + $authorInfo = $msz->getActiveUser(); } elseif($hasNoteId) { $isNew = false; @@ -49,8 +49,8 @@ if($hasUserId) { return; } - $userInfo = User::byId((int)$noteInfo->getUserId()); - $authorInfo = $noteInfo->hasAuthorId() ? User::byId((int)$noteInfo->getAuthorId()) : null; + $userInfo = $users->getUser($noteInfo->getUserId(), 'id'); + $authorInfo = $noteInfo->hasAuthorId() ? $users->getUser($noteInfo->getAuthorId(), 'id') : null; } while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) { @@ -83,5 +83,7 @@ Template::render('manage.users.note', [ 'note_new' => $isNew, 'note_info' => $noteInfo ?? null, 'note_user' => $userInfo, + 'note_user_colour' => $users->getUserColour($userInfo), 'note_author' => $authorInfo, + 'note_author_colour' => $users->getUserColour($authorInfo), ]); diff --git a/public-legacy/manage/users/notes.php b/public-legacy/manage/users/notes.php index 7279ba8..9199f61 100644 --- a/public-legacy/manage/users/notes.php +++ b/public-legacy/manage/users/notes.php @@ -2,23 +2,28 @@ namespace Misuzu; use RuntimeException; -use Misuzu\Users\User; -if(!User::hasCurrent() || !perms_check_user(MSZ_PERMS_USER, User::getCurrent()->getId(), MSZ_PERM_USER_MANAGE_NOTES)) { +if(!$msz->isLoggedIn() || !perms_check_user(MSZ_PERMS_USER, $msz->getActiveUser()->getId(), MSZ_PERM_USER_MANAGE_NOTES)) { echo render_error(403); return; } +$users = $msz->getUsers(); + $userInfos = [ - (string)User::getCurrent()->getId() => User::getCurrent(), + $msz->getActiveUser()->getId() => $msz->getActiveUser(), +]; +$userColours = [ + $msz->getActiveUser()->getId() => $users->getUserColour($msz->getActiveUser()), ]; $filterUser = null; if(filter_has_var(INPUT_GET, 'u')) { - $filterUserId = (int)filter_input(INPUT_GET, 'u', FILTER_SANITIZE_NUMBER_INT); + $filterUserId = filter_input(INPUT_GET, 'u', FILTER_SANITIZE_NUMBER_INT); try { - $filterUser = User::byId($filterUserId); - $userInfos[(string)$filterUser->getId()] = $filterUser; + $filterUser = $users->getUser($filterUserId, 'id'); + $userInfos[$filterUserId] = $filterUser; + $userColours[$filterUserId] = $users->getUserColour($filterUser); } catch(RuntimeException $ex) { echo render_error(404); return; @@ -40,19 +45,34 @@ foreach($noteInfos as $noteInfo) { if(array_key_exists($noteInfo->getUserId(), $userInfos)) $userInfo = $userInfos[$noteInfo->getUserId()]; else - $userInfos[$noteInfo->getUserId()] = $userInfo = User::byId((int)$noteInfo->getUserId()); + $userInfos[$noteInfo->getUserId()] = $userInfo = $users->getUser($noteInfo->getUserId(), 'id'); - if(!$noteInfo->hasAuthorId()) - $authorInfo = null; - elseif(array_key_exists($noteInfo->getAuthorId(), $userInfos)) - $authorInfo = $userInfos[$noteInfo->getAuthorId()]; + if(array_key_exists($userInfo->getId(), $userColours)) + $userColour = $userColours[$userInfo->getId()]; else - $userInfos[$noteInfo->getAuthorId()] = $authorInfo = User::byId((int)$noteInfo->getAuthorId()); + $userColours[$userInfo->getId()] = $userColour = $users->getUserColour($userInfo); + + if(!$noteInfo->hasAuthorId()) { + $authorInfo = null; + $authorColour = null; + } else { + if(array_key_exists($noteInfo->getAuthorId(), $userInfos)) + $authorInfo = $userInfos[$noteInfo->getAuthorId()]; + else + $userInfos[$noteInfo->getAuthorId()] = $modInfo = $users->getUser($noteInfo->getAuthorId(), 'id'); + + if(array_key_exists($authorInfo->getId(), $userColours)) + $authorColour = $userColours[$authorInfo->getId()]; + else + $userColours[$authorInfo->getId()] = $authorColour = $users->getUserColour($authorInfo); + } $notes[] = [ 'info' => $noteInfo, 'user' => $userInfo, + 'user_colour' => $userColour, 'author' => $authorInfo, + 'author_colour' => $authorColour, ]; } diff --git a/public-legacy/manage/users/role.php b/public-legacy/manage/users/role.php index 6947b94..9304515 100644 --- a/public-legacy/manage/users/role.php +++ b/public-legacy/manage/users/role.php @@ -4,13 +4,13 @@ namespace Misuzu; use RuntimeException; use Index\Colour\Colour; use Index\Colour\ColourRGB; -use Misuzu\Users\User; -if(!User::hasCurrent() || !perms_check_user(MSZ_PERMS_USER, User::getCurrent()->getId(), MSZ_PERM_USER_MANAGE_ROLES)) { +if(!$msz->isLoggedIn() || !perms_check_user(MSZ_PERMS_USER, $msz->getActiveUser()->getId(), MSZ_PERM_USER_MANAGE_ROLES)) { echo render_error(403); return; } +$users = $msz->getUsers(); $roles = $msz->getRoles(); if(filter_has_var(INPUT_GET, 'r')) { @@ -25,15 +25,16 @@ if(filter_has_var(INPUT_GET, 'r')) { } } else $isNew = true; -$currentUser = User::getCurrent(); -$currentUserId = $currentUser->getId(); -$canEditPerms = perms_check_user(MSZ_PERMS_USER, $currentUserId, MSZ_PERM_USER_MANAGE_PERMS); +$currentUser = $msz->getActiveUser(); +$canEditPerms = perms_check_user(MSZ_PERMS_USER, $currentUser->getId(), MSZ_PERM_USER_MANAGE_PERMS); if($canEditPerms) $permissions = manage_perms_list(perms_get_role_raw($roleId ?? 0)); while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) { - if(!$isNew && !$currentUser->isSuper() && $roleInfo->getRank() >= $currentUser->getRank()) { + $userRank = $users->getUserRank($currentUser); + + if(!$isNew && !$currentUser->isSuperUser() && $roleInfo->getRank() >= $userRank) { echo 'You aren\'t allowed to edit this role.'; break; } @@ -62,7 +63,7 @@ while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) { 'role_ur_col_blue' => $colourBlue, ]); - if(!$currentUser->isSuper() && $roleRank >= $currentUser->getRank()) { + if(!$currentUser->isSuperUser() && $roleRank >= $userRank) { echo 'You aren\'t allowed to make a role with equal rank to your own.'; break; } diff --git a/public-legacy/manage/users/roles.php b/public-legacy/manage/users/roles.php index 03ed610..c11652f 100644 --- a/public-legacy/manage/users/roles.php +++ b/public-legacy/manage/users/roles.php @@ -1,9 +1,7 @@ getId(), MSZ_PERM_USER_MANAGE_ROLES)) { +if(!$msz->isLoggedIn() || !perms_check_user(MSZ_PERMS_USER, $msz->getActiveUser()->getId(), MSZ_PERM_USER_MANAGE_ROLES)) { echo render_error(403); return; } diff --git a/public-legacy/manage/users/user.php b/public-legacy/manage/users/user.php index 0b8b912..880aac4 100644 --- a/public-legacy/manage/users/user.php +++ b/public-legacy/manage/users/user.php @@ -5,7 +5,7 @@ use RuntimeException; use Index\Colour\Colour; use Misuzu\Users\User; -if(!User::hasCurrent()) { +if(!$msz->isLoggedIn()) { echo render_error(403); return; } @@ -13,15 +13,15 @@ if(!User::hasCurrent()) { $users = $msz->getUsers(); $roles = $msz->getRoles(); -$currentUser = User::getCurrent(); -$currentUserId = $currentUser->getId(); +$currentUser = $msz->getActiveUser(); -$canManageUsers = perms_check_user(MSZ_PERMS_USER, $currentUserId, MSZ_PERM_USER_MANAGE_USERS); -$canManagePerms = perms_check_user(MSZ_PERMS_USER, $currentUserId, MSZ_PERM_USER_MANAGE_PERMS); -$canManageNotes = perms_check_user(MSZ_PERMS_USER, $currentUserId, MSZ_PERM_USER_MANAGE_NOTES); -$canManageWarnings = perms_check_user(MSZ_PERMS_USER, $currentUserId, MSZ_PERM_USER_MANAGE_WARNINGS); -$canManageBans = perms_check_user(MSZ_PERMS_USER, $currentUserId, MSZ_PERM_USER_MANAGE_BANS); -$canImpersonate = perms_check_user(MSZ_PERMS_USER, $currentUserId, MSZ_PERM_USER_IMPERSONATE); +$canManageUsers = perms_check_user(MSZ_PERMS_USER, $currentUser->getId(), MSZ_PERM_USER_MANAGE_USERS); +$canManagePerms = perms_check_user(MSZ_PERMS_USER, $currentUser->getId(), MSZ_PERM_USER_MANAGE_PERMS); +$canManageNotes = perms_check_user(MSZ_PERMS_USER, $currentUser->getId(), MSZ_PERM_USER_MANAGE_NOTES); +$canManageWarnings = perms_check_user(MSZ_PERMS_USER, $currentUser->getId(), MSZ_PERM_USER_MANAGE_WARNINGS); +$canManageBans = perms_check_user(MSZ_PERMS_USER, $currentUser->getId(), MSZ_PERM_USER_MANAGE_BANS); +$canImpersonate = perms_check_user(MSZ_PERMS_USER, $currentUser->getId(), MSZ_PERM_USER_IMPERSONATE); +$canSendTestMail = $currentUser->isSuperUser(); $hasAccess = $canManageUsers || $canManageNotes || $canManageWarnings || $canManageBans; if(!$hasAccess) { @@ -33,13 +33,16 @@ $notices = []; $userId = (int)filter_input(INPUT_GET, 'u', FILTER_SANITIZE_NUMBER_INT); try { - $userInfo = User::byId($userId); + $userInfo = $users->getUser($userId, 'id'); } catch(RuntimeException $ex) { echo render_error(404); return; } -$canEdit = $canManageUsers && ($currentUser->isSuper() || $currentUser->getId() === $userInfo->getId() || $currentUser->getRank() > $userInfo->getRank()); +$currentUserRank = $users->getUserRank($currentUser); +$userRank = $users->getUserRank($userInfo); + +$canEdit = $canManageUsers && ($currentUser->isSuperUser() || (string)$currentUser->getId() === $userInfo->getId() || $currentUserRank > $userRank); $canEditPerms = $canEdit && $canManagePerms; $permissions = $canEditPerms ? manage_perms_list(perms_get_user_raw($userId)) : []; @@ -50,15 +53,15 @@ if(CSRF::validateRequest() && $canEdit) { } elseif(!is_string($_POST['impersonate_user']) || $_POST['impersonate_user'] !== 'meow') { $notices[] = 'You didn\'t say the magic word.'; } else { - $allowToImpersonate = $currentUser->isSuper(); + $allowToImpersonate = $currentUser->isSuperUser(); if(!$allowToImpersonate) { $allowImpersonateUsers = $msz->getConfig()->getArray(sprintf('impersonate.allow.u%s', $currentUser->getId())); - $allowToImpersonate = in_array((string)$userInfo->getId(), $allowImpersonateUsers, true); + $allowToImpersonate = in_array($userInfo->getId(), $allowImpersonateUsers, true); } if($allowToImpersonate) { - $msz->createAuditLog('USER_IMPERSONATE', [$userInfo->getId(), $userInfo->getUsername()]); + $msz->createAuditLog('USER_IMPERSONATE', [$userInfo->getId(), $userInfo->getName()]); $authToken->setImpersonatedUserId($userInfo->getId()); $authToken->applyCookie(); url_redirect('index'); @@ -68,13 +71,13 @@ if(CSRF::validateRequest() && $canEdit) { } if(!empty($_POST['send_test_email'])) { - if(!$currentUser->isSuper()) { + if(!$canSendTestMail) { $notices[] = 'You must be a super user to do this.'; } elseif(!is_string($_POST['send_test_email']) || $_POST['send_test_email'] !== 'yes_send_it') { $notices[] = 'Invalid request thing shut the fuck up.'; } else { $testMail = Mailer::sendMessage( - [$userInfo->getEMailAddress() => $userInfo->getUsername()], + [$userInfo->getEMailAddress() => $userInfo->getName()], 'Flashii Test E-mail', 'You were sent this e-mail to validate if you can receive e-mails from Flashii. You may discard it.' ); @@ -100,7 +103,7 @@ if(CSRF::validateRequest() && $canEdit) { $removeRoles = []; foreach($existingRoles as $roleInfo) { - if($roleInfo->isDefault() || !($currentUser->isSuper() || $currentUser->getRank() > $roleInfo->getRank())) + if($roleInfo->isDefault() || !($currentUser->isSuperUser() || $userRank > $roleInfo->getRank())) continue; if(!in_array($roleInfo->getId(), $applyRoles)) @@ -119,7 +122,7 @@ if(CSRF::validateRequest() && $canEdit) { continue; } - if(!$currentUser->isSuper() && $currentUser->getRank() <= $roleInfo->getRank()) + if(!$currentUser->isSuperUser() && $userRank <= $roleInfo->getRank()) continue; if(!in_array($roleInfo, $existingRoles)) @@ -152,10 +155,9 @@ if(CSRF::validateRequest() && $canEdit) { $users->updateUser( userInfo: $userInfo, displayRoleInfo: $displayRole, + countryCode: (string)($_POST['user']['country'] ?? 'XX'), + title: (string)($_POST['user']['title'] ?? '') ); - - $userInfo->setCountry((string)($_POST['user']['country'] ?? '')) - ->setTitle((string)($_POST['user']['title'] ?? '')); } } @@ -169,7 +171,7 @@ if(CSRF::validateRequest() && $canEdit) { } if(empty($notices)) - $userInfo->setColour($setColour); + $users->updateUser(userInfo: $userInfo, colour: $setColour); } if(!empty($_POST['password']) && is_array($_POST['password'])) { @@ -182,13 +184,10 @@ if(CSRF::validateRequest() && $canEdit) { elseif(!empty(User::validatePassword($passwordNewValue))) $notices[] = 'New password is too weak.'; else - $userInfo->setPassword($passwordNewValue); + $users->updateUser(userInfo: $userInfo, password: $passwordNewValue); } } - if(empty($notices)) - $userInfo->save(); - if($canEditPerms && !empty($_POST['perms']) && is_array($_POST['perms'])) { $perms = manage_perms_apply($permissions, $_POST['perms']); @@ -203,6 +202,9 @@ if(CSRF::validateRequest() && $canEdit) { // this smells, make it refresh/apply in a non-retarded way $permissions = manage_perms_list(perms_get_user_raw($userId)); } + + url_redirect('manage-user', ['user' => $userInfo->getId()]); + return; } $rolesAll = $roles->getRoles(); @@ -219,5 +221,6 @@ Template::render('manage.users.user', [ 'can_manage_warnings' => $canManageWarnings, 'can_manage_bans' => $canManageBans, 'can_impersonate' => $canImpersonate, + 'can_send_test_mail' => $canSendTestMail, 'permissions' => $permissions ?? [], ]); diff --git a/public-legacy/manage/users/warning.php b/public-legacy/manage/users/warning.php index 1696984..623953b 100644 --- a/public-legacy/manage/users/warning.php +++ b/public-legacy/manage/users/warning.php @@ -2,9 +2,8 @@ namespace Misuzu; use RuntimeException; -use Misuzu\Users\User; -if(!User::hasCurrent() || !perms_check_user(MSZ_PERMS_USER, User::getCurrent()->getId(), MSZ_PERM_USER_MANAGE_WARNINGS)) { +if(!$msz->isLoggedIn() || !perms_check_user(MSZ_PERMS_USER, $msz->getActiveUser()->getId(), MSZ_PERM_USER_MANAGE_WARNINGS)) { echo render_error(403); return; } @@ -27,14 +26,16 @@ if($_SERVER['REQUEST_METHOD'] === 'GET' && filter_has_var(INPUT_GET, 'delete')) return; } +$users = $msz->getUsers(); + try { - $userInfo = User::byId((int)filter_input(INPUT_GET, 'u', FILTER_SANITIZE_NUMBER_INT)); + $userInfo = $users->getUser(filter_input(INPUT_GET, 'u', FILTER_SANITIZE_NUMBER_INT), 'id'); } catch(RuntimeException $ex) { echo render_error(404); return; } -$modInfo = User::getCurrent(); +$modInfo = $msz->getActiveUser(); while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) { $body = trim((string)filter_input(INPUT_POST, 'uw_body')); diff --git a/public-legacy/manage/users/warnings.php b/public-legacy/manage/users/warnings.php index b2ab99a..d022e9f 100644 --- a/public-legacy/manage/users/warnings.php +++ b/public-legacy/manage/users/warnings.php @@ -2,23 +2,28 @@ namespace Misuzu; use RuntimeException; -use Misuzu\Users\User; -if(!User::hasCurrent() || !perms_check_user(MSZ_PERMS_USER, User::getCurrent()->getId(), MSZ_PERM_USER_MANAGE_WARNINGS)) { +if(!$msz->isLoggedIn() || !perms_check_user(MSZ_PERMS_USER, $msz->getActiveUser()->getId(), MSZ_PERM_USER_MANAGE_WARNINGS)) { echo render_error(403); return; } +$users = $msz->getUsers(); + $userInfos = [ - (string)User::getCurrent()->getId() => User::getCurrent(), + $msz->getActiveUser()->getId() => $msz->getActiveUser(), +]; +$userColours = [ + $msz->getActiveUser()->getId() => $users->getUserColour($msz->getActiveUser()), ]; $filterUser = null; if(filter_has_var(INPUT_GET, 'u')) { - $filterUserId = (int)filter_input(INPUT_GET, 'u', FILTER_SANITIZE_NUMBER_INT); + $filterUserId = filter_input(INPUT_GET, 'u', FILTER_SANITIZE_NUMBER_INT); try { - $filterUser = User::byId($filterUserId); - $userInfos[(string)$filterUser->getId()] = $filterUser; + $filterUser = $users->getUser($filterUserId, 'id'); + $userInfos[$filterUserId] = $filterUser; + $userColours[$filterUserId] = $users->getUserColour($filterUser); } catch(RuntimeException $ex) { echo render_error(404); return; @@ -40,19 +45,34 @@ foreach($warnInfos as $warnInfo) { if(array_key_exists($warnInfo->getUserId(), $userInfos)) $userInfo = $userInfos[$warnInfo->getUserId()]; else - $userInfos[$warnInfo->getUserId()] = $userInfo = User::byId((int)$warnInfo->getUserId()); + $userInfos[$warnInfo->getUserId()] = $userInfo = $users->getUser($warnInfo->getUserId(), 'id'); - if(!$warnInfo->hasModId()) - $modInfo = null; - elseif(array_key_exists($warnInfo->getModId(), $userInfos)) - $modInfo = $userInfos[$warnInfo->getModId()]; + if(array_key_exists($userInfo->getId(), $userColours)) + $userColour = $userColours[$userInfo->getId()]; else - $userInfos[$warnInfo->getModId()] = $modInfo = User::byId((int)$warnInfo->getModId()); + $userColours[$userInfo->getId()] = $userColour = $users->getUserColour($userInfo); + + if(!$warnInfo->hasModId()) { + $modInfo = null; + $modColour = null; + } else { + if(array_key_exists($warnInfo->getModId(), $userInfos)) + $modInfo = $userInfos[$warnInfo->getModId()]; + else + $userInfos[$warnInfo->getModId()] = $modInfo = $users->getUser($warnInfo->getModId(), 'id'); + + if(array_key_exists($modInfo->getId(), $userColours)) + $modColour = $userColours[$modInfo->getId()]; + else + $userColours[$modInfo->getId()] = $modColour = $users->getUserColour($modInfo); + } $warnList[] = [ 'info' => $warnInfo, 'user' => $userInfo, + 'user_colour' => $userColour, 'mod' => $modInfo, + 'mod_colour' => $modColour, ]; } diff --git a/public-legacy/members.php b/public-legacy/members.php index e24736f..37e97c0 100644 --- a/public-legacy/members.php +++ b/public-legacy/members.php @@ -2,8 +2,15 @@ namespace Misuzu; use RuntimeException; -use Misuzu\Users\User; +if(!$msz->isLoggedIn()) { + echo render_error(403); + return; +} + +// TODO: restore forum-topics and forum-posts orderings + +$users = $msz->getUsers(); $roles = $msz->getRoles(); $roleId = filter_has_var(INPUT_GET, 'r') ? (string)filter_input(INPUT_GET, 'r') : null; @@ -11,47 +18,35 @@ $orderBy = strtolower((string)filter_input(INPUT_GET, 'ss')); $orderDir = strtolower((string)filter_input(INPUT_GET, 'sd')); $orderDirs = [ - 'asc' => 'Ascending', - 'desc' => 'Descending', + 'asc' => 'In Order', + 'desc' => 'Reverse Order', ]; -$defaultOrder = 'last-online'; +$defaultOrder = 'active'; $orderFields = [ 'id' => [ - 'column' => 'u.`user_id`', - 'default-dir' => 'asc', 'title' => 'User ID', ], 'name' => [ - 'column' => 'u.`username`', - 'default-dir' => 'asc', 'title' => 'Username', ], 'country' => [ - 'column' => 'u.`user_country`', - 'default-dir' => 'asc', 'title' => 'Country', ], + 'created' => [ + 'title' => 'Registration Date', + ], + 'active' => [ + 'title' => 'Last Online', + ], 'registered' => [ - 'column' => 'u.`user_created`', - 'default-dir' => 'desc', + 'alt' => 'created', 'title' => 'Registration Date', ], 'last-online' => [ - 'column' => 'u.`user_active`', - 'default-dir' => 'desc', + 'alt' => 'active', 'title' => 'Last Online', ], - 'forum-topics' => [ - 'column' => '`user_count_topics`', - 'default-dir' => 'desc', - 'title' => 'Forum Topics', - ], - 'forum-posts' => [ - 'column' => '`user_count_posts`', - 'default-dir' => 'desc', - 'title' => 'Forum Posts', - ], ]; if(empty($orderBy)) { @@ -61,14 +56,17 @@ if(empty($orderBy)) { return; } +if(array_key_exists('alt', $orderFields[$orderBy])) + $orderBy = $orderFields[$orderBy]['alt']; + if(empty($orderDir)) { - $orderDir = $orderFields[$orderBy]['default-dir']; + $orderDir = 'asc'; } elseif(!array_key_exists($orderDir, $orderDirs)) { echo render_error(400); return; } -$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, $msz->getActiveUser()->getId(), MSZ_PERM_USER_MANAGE_USERS); if($roleId === null) { $roleInfo = $roles->getDefaultRole(); @@ -81,57 +79,35 @@ if($roleId === null) { } } +$deleted = $canManageUsers ? null : false; -$pagination = new Pagination($roles->countRoleUsers($roleInfo), 15); $rolesAll = $roles->getRoles(hidden: false); +$pagination = new Pagination($users->countUsers(roleInfo: $roleInfo, deleted: $deleted), 15); -$getUsers = DB::prepare(sprintf( - ' - SELECT - :current_user_id AS `current_user_id`, - u.`user_id`, u.`username`, u.`user_country`, - u.`user_created`, u.`user_active`, r.`role_id`, - COALESCE(u.`user_title`, r.`role_title`) AS `user_title`, - COALESCE(u.`user_colour`, r.`role_colour`) AS `user_colour`, - ( - SELECT COUNT(`topic_id`) - FROM `msz_forum_topics` - WHERE `user_id` = u.`user_id` - AND `topic_deleted` IS NULL - ) AS `user_count_topics`, - ( - SELECT COUNT(`post_Id`) - FROM `msz_forum_posts` - WHERE `user_id` = u.`user_id` - AND `post_deleted` IS NULL - ) AS `user_count_posts` - FROM `msz_users` AS u - LEFT JOIN `msz_roles` AS r - ON r.`role_id` = u.`display_role` - LEFT JOIN `msz_users_roles` AS ur - ON ur.`user_id` = u.`user_id` - WHERE ur.`role_id` = :role_id - %1$s - ORDER BY %2$s %3$s - LIMIT %4$d, %5$d - ', - $canManageUsers ? '' : 'AND u.`user_deleted` IS NULL', - $orderFields[$orderBy]['column'], - $orderDir, - $pagination->getOffset(), - $pagination->getRange() -)); -$getUsers->bind('role_id', $roleInfo->getId()); -$getUsers->bind('current_user_id', User::hasCurrent() ? User::getCurrent()->getId() : 0); -$users = $getUsers->fetchAll(); +$userList = []; +$userInfos = $users->getUsers( + roleInfo: $roleInfo, + deleted: $deleted, + orderBy: $orderBy, + reverseOrder: $orderDir !== 'asc', + pagination: $pagination, +); -if(empty($users)) +foreach($userInfos as $userInfo) + $userList[] = [ + 'info' => $userInfo, + 'colour' => $users->getUserColour($userInfo), + 'ftopics' => forum_get_user_topic_count($userInfo), + 'fposts' => forum_get_user_post_count($userInfo), + ]; + +if(empty($userList)) http_response_code(404); Template::render('user.listing', [ 'roles' => $rolesAll, 'role' => $roleInfo, - 'users' => $users, + 'users' => $userList, 'order_fields' => $orderFields, 'order_directions' => $orderDirs, 'order_field' => $orderBy, diff --git a/public-legacy/profile.php b/public-legacy/profile.php index 010bad0..a8bc161 100644 --- a/public-legacy/profile.php +++ b/public-legacy/profile.php @@ -4,50 +4,61 @@ namespace Misuzu; use InvalidArgumentException; use RuntimeException; use Index\ByteFormat; +use Index\DateTime; use Misuzu\Parsers\Parser; use Misuzu\Users\User; +use Misuzu\Users\Assets\UserAvatarAsset; use Misuzu\Users\Assets\UserBackgroundAsset; $userId = !empty($_GET['u']) && is_string($_GET['u']) ? trim($_GET['u']) : 0; $profileMode = !empty($_GET['m']) && is_string($_GET['m']) ? (string)$_GET['m'] : ''; $isEditing = !empty($_GET['edit']) && is_string($_GET['edit']) ? (bool)$_GET['edit'] : !empty($_POST) && is_array($_POST); -$currentUser = User::getCurrent(); -$viewingAsGuest = $currentUser === null; -$currentUserId = $viewingAsGuest ? 0 : $currentUser->getId(); +$users = $msz->getUsers(); + +$viewerInfo = $msz->getActiveUser(); +$viewingAsGuest = $viewerInfo === null; +$viewerId = $viewingAsGuest ? '0' : $viewerInfo->getId(); try { - $profileUser = User::findForProfile($userId); + $userInfo = $users->getUser($userId, 'profile'); } catch(RuntimeException $ex) { http_response_code(404); Template::render('profile.index', [ 'profile_is_guest' => $viewingAsGuest, 'profile_is_deleted' => true, + 'profile_is_banned' => false, ]); return; } -if($profileUser->isDeleted()) { +if($userInfo->isDeleted()) { http_response_code(404); Template::render('profile.index', [ 'profile_is_guest' => $viewingAsGuest, 'profile_is_deleted' => true, + 'profile_is_banned' => false, ]); return; } $notices = []; -$activeBanInfo = $msz->tryGetActiveBan($profileUser); +$userRank = $users->getUserRank($userInfo); +$viewerRank = $users->getUserRank($viewerInfo); + +$activeBanInfo = $msz->tryGetActiveBan($userInfo); $isBanned = $activeBanInfo !== null; $profileFields = $msz->getProfileFields(); -$viewingOwnProfile = $currentUserId === $profileUser->getId(); -$userPerms = perms_get_user($currentUserId)[MSZ_PERMS_USER]; +$viewingOwnProfile = (string)$viewerId === $userInfo->getId(); +$userPerms = perms_get_user($viewerId)[MSZ_PERMS_USER]; $canManageWarnings = perms_check($userPerms, MSZ_PERM_USER_MANAGE_WARNINGS); -$canEdit = !$viewingAsGuest && ((!$isBanned && $viewingOwnProfile) || $currentUser->isSuper() || ( +$canEdit = !$viewingAsGuest && ((!$isBanned && $viewingOwnProfile) || $viewerInfo->isSuperUser() || ( perms_check($userPerms, MSZ_PERM_USER_MANAGE_USERS) - && ($currentUser->getId() === $profileUser->getId() || $currentUser->getRank() > $profileUser->getRank()) + && ($viewingOwnProfile || $viewerRank > $userRank) )); +$avatarInfo = new UserAvatarAsset($userInfo); +$backgroundInfo = new UserBackgroundAsset($userInfo); if($isEditing) { if(!$canEdit) { @@ -103,9 +114,9 @@ if($isEditing) { } if(!empty($profileFieldsRemove)) - $profileFields->removeFieldValues($profileUser, $profileFieldsRemove); + $profileFields->removeFieldValues($userInfo, $profileFieldsRemove); if(!empty($profileFieldsSetInfos)) - $profileFields->setFieldValues($profileUser, $profileFieldsSetInfos, $profileFieldsSetValues); + $profileFields->setFieldValues($userInfo, $profileFieldsSetInfos, $profileFieldsSetValues); } } @@ -118,7 +129,7 @@ if($isEditing) { $aboutValid = User::validateProfileAbout($aboutParse, $aboutText); if($aboutValid === '') - $profileUser->setProfileAboutText($aboutText)->setProfileAboutParser($aboutParse); + $users->updateUser($userInfo, aboutContent: $aboutText, aboutParser: $aboutParse); else switch($aboutValid) { case 'parser': $notices[] = 'The selected about section parser is invalid.'; @@ -142,7 +153,7 @@ if($isEditing) { $sigValid = User::validateForumSignature($sigParse, $sigText); if($sigValid === '') - $profileUser->setForumSignatureText($sigText)->setForumSignatureParser($sigParse); + $users->updateUser($userInfo, signatureContent: $sigText, signatureParser: $sigParse); else switch($sigValid) { case 'parser': $notices[] = 'The selected forum signature parser is invalid.'; @@ -167,7 +178,7 @@ if($isEditing) { $birthValid = User::validateBirthdate($birthYear, $birthMonth, $birthDay); if($birthValid === '') - $profileUser->setBirthdate($birthYear, $birthMonth, $birthDay); + $users->updateUser($userInfo, birthYear: $birthYear, birthMonth: $birthMonth, birthDay: $birthDay); else switch($birthValid) { case 'year': $notices[] = 'The given birth year is invalid.'; @@ -183,8 +194,6 @@ if($isEditing) { } if(!empty($_FILES['avatar'])) { - $avatarInfo = $profileUser->getAvatarInfo(); - if(!empty($_POST['avatar']['delete'])) { $avatarInfo->delete(); } else { @@ -230,8 +239,6 @@ if($isEditing) { } if(!empty($_FILES['background'])) { - $backgroundInfo = $profileUser->getBackgroundInfo(); - if((int)($_POST['background']['attach'] ?? -1) === 0) { $backgroundInfo->delete(); } else { @@ -278,9 +285,9 @@ if($isEditing) { ->setSlide(!empty($_POST['background']['attr']['slide'])); } } - } - $profileUser->saveProfile(); + $users->updateUser($userInfo, backgroundSettings: $backgroundInfo->getSettings()); + } } // Unset $isEditing and hope the user doesn't refresh their profile! @@ -315,7 +322,7 @@ $profileStats = DB::prepare(' ) AS `comments_count` FROM `msz_users` AS u WHERE `user_id` = :user_id -')->bind('user_id', $profileUser->getId())->fetch(); +')->bind('user_id', $userInfo->getId())->fetch(); switch($profileMode) { default: @@ -324,7 +331,7 @@ switch($profileMode) { case 'forum-topics': $template = 'profile.topics'; - $topicsCount = forum_topic_count_user($profileUser->getId(), $currentUserId); + $topicsCount = forum_topic_count_user($userInfo->getId(), $viewerId); $topicsPagination = new Pagination($topicsCount, 20); if(!$topicsPagination->hasValidOffset()) { @@ -333,13 +340,13 @@ switch($profileMode) { } $topics = forum_topic_listing_user( - $profileUser->getId(), $currentUserId, + $userInfo->getId(), $viewerId, $topicsPagination->getOffset(), $topicsPagination->getRange() ); Template::set([ - 'title' => $profileUser->getUsername() . ' / topics', - 'canonical_url' => url('user-profile-forum-topics', ['user' => $profileUser->getId(), 'page' => Pagination::param()]), + 'title' => $userInfo->getName() . ' / topics', + 'canonical_url' => url('user-profile-forum-topics', ['user' => $userInfo->getId(), 'page' => Pagination::param()]), 'profile_topics' => $topics, 'profile_topics_pagination' => $topicsPagination, ]); @@ -347,7 +354,7 @@ switch($profileMode) { case 'forum-posts': $template = 'profile.posts'; - $postsCount = forum_post_count_user($profileUser->getId()); + $postsCount = forum_post_count_user($userInfo->getId()); $postsPagination = new Pagination($postsCount, 20); if(!$postsPagination->hasValidOffset()) { @@ -356,7 +363,7 @@ switch($profileMode) { } $posts = forum_post_listing( - $profileUser->getId(), + $userInfo->getId(), $postsPagination->getOffset(), $postsPagination->getRange(), false, @@ -364,8 +371,8 @@ switch($profileMode) { ); Template::set([ - 'title' => $profileUser->getUsername() . ' / posts', - 'canonical_url' => url('user-profile-forum-posts', ['user' => $profileUser->getId(), 'page' => Pagination::param()]), + 'title' => $userInfo->getName() . ' / posts', + 'canonical_url' => url('user-profile-forum-posts', ['user' => $userInfo->getId(), 'page' => Pagination::param()]), 'profile_posts' => $posts, 'profile_posts_pagination' => $postsPagination, ]); @@ -375,16 +382,16 @@ switch($profileMode) { $template = 'profile.index'; if(!$viewingAsGuest) { - Template::set('profile_warnings', $msz->getWarnings()->getWarningsWithDefaultBacklog($profileUser)); + Template::set('profile_warnings', $msz->getWarnings()->getWarningsWithDefaultBacklog($userInfo)); if((!$isBanned || $canEdit)) { - $activeCategoryStats = forum_get_user_most_active_category_info($profileUser->getId()); + $activeCategoryStats = forum_get_user_most_active_category_info($userInfo->getId()); $activeCategoryInfo = empty($activeCategoryStats->forum_id) ? null : forum_get($activeCategoryStats->forum_id); - $activeTopicStats = forum_get_user_most_active_topic_info($profileUser->getId()); + $activeTopicStats = forum_get_user_most_active_topic_info($userInfo->getId()); $activeTopicInfo = empty($activeTopicStats->topic_id) ? null : forum_topic_get($activeTopicStats->topic_id); - $profileFieldValues = $profileFields->getFieldValues($profileUser); + $profileFieldValues = $profileFields->getFieldValues($userInfo); $profileFieldInfos = $profileFieldInfos ?? $profileFields->getFields(fieldValueInfos: $isEditing ? null : $profileFieldValues); $profileFieldFormats = $profileFields->getFieldFormats(fieldValueInfos: $profileFieldValues); @@ -435,8 +442,9 @@ switch($profileMode) { if(!empty($template)) { Template::render($template, [ - 'profile_viewer' => $currentUser, - 'profile_user' => $profileUser, + 'profile_viewer' => $viewerInfo, + 'profile_user' => $userInfo, + 'profile_colour' => $users->getUserColour($userInfo), 'profile_stats' => $profileStats, 'profile_mode' => $profileMode, 'profile_notices' => $notices, @@ -446,5 +454,7 @@ if(!empty($template)) { 'profile_is_guest' => $viewingAsGuest, 'profile_is_deleted' => false, 'profile_ban_info' => $activeBanInfo, + 'profile_avatar_info' => $avatarInfo, + 'profile_background_info' => $backgroundInfo, ]); } diff --git a/public-legacy/search.php b/public-legacy/search.php index 4c9343b..a34d51b 100644 --- a/public-legacy/search.php +++ b/public-legacy/search.php @@ -3,20 +3,26 @@ namespace Misuzu; use RuntimeException; use Misuzu\Comments\CommentsCategory; -use Misuzu\Users\User; + +if(!$msz->isLoggedIn()) { + echo render_error(403); + return; +} $searchQuery = !empty($_GET['q']) && is_string($_GET['q']) ? $_GET['q'] : ''; if(!empty($searchQuery)) { - $forumTopics = forum_topic_listing_search($searchQuery, User::hasCurrent() ? User::getCurrent()->getId() : 0); + $forumTopics = forum_topic_listing_search($searchQuery, $msz->getActiveUser()->getId()); $forumPosts = forum_post_search($searchQuery); // this sure is an expansion $news = $msz->getNews(); + $users = $msz->getUsers(); $comments = $msz->getComments(); $newsPosts = []; $newsPostInfos = $news->getPostsBySearchQuery($searchQuery); $newsUserInfos = []; + $newsUserColours = []; $newsCategoryInfos = []; foreach($newsPostInfos as $postInfo) { @@ -27,7 +33,8 @@ if(!empty($searchQuery)) { $userInfo = $newsUserInfos[$userId]; } else { try { - $userInfo = User::byId($userId); + $userInfo = $users->getUser($userId, 'id'); + $newsUserColours[$userId] = $users->getUserColour($userInfo); } catch(RuntimeException $ex) { $userInfo = null; } @@ -47,6 +54,7 @@ if(!empty($searchQuery)) { 'post' => $postInfo, 'category' => $categoryInfo, 'user' => $userInfo, + 'user_colour' => $newsUserColours[$userId] ?? \Index\Colour\Colour::none(), 'comments_count' => $commentsCount, ]; } @@ -77,13 +85,13 @@ if(!empty($searchQuery)) { GROUP BY u.`user_id` '); $findUsers->bind('query', $searchQuery); - $users = $findUsers->fetchAll(); + $userList = $findUsers->fetchAll(); } Template::render('home.search', [ 'search_query' => $searchQuery, 'forum_topics' => $forumTopics ?? [], 'forum_posts' => $forumPosts ?? [], - 'users' => $users ?? [], + 'users' => $userList ?? [], 'news_posts' => $newsPosts ?? [], ]); diff --git a/public-legacy/settings/account.php b/public-legacy/settings/account.php index 8fc9834..277bca2 100644 --- a/public-legacy/settings/account.php +++ b/public-legacy/settings/account.php @@ -6,7 +6,7 @@ use Misuzu\Users\User; use chillerlan\QRCode\QRCode; use chillerlan\QRCode\QROptions; -if(!User::hasCurrent()) { +if(!$msz->isLoggedIn()) { echo render_error(401); return; } @@ -14,8 +14,7 @@ if(!User::hasCurrent()) { $errors = []; $users = $msz->getUsers(); $roles = $msz->getRoles(); -$currentUser = User::getCurrent(); -$currentUserId = $currentUser->getId(); +$userInfo = $msz->getActiveUser(); $isRestricted = $msz->hasActiveBan(); $isVerifiedRequest = CSRF::validateRequest(); @@ -30,14 +29,14 @@ if(!$isRestricted && $isVerifiedRequest && !empty($_POST['role'])) { switch($_POST['role']['mode'] ?? '') { case 'display': $users->updateUser( - $currentUser, + $userInfo, displayRoleInfo: $roleInfo ); break; case 'leave': if($roleInfo->isLeavable()) - $users->removeRoles($currentUser, $roleInfo); + $users->removeRoles($userInfo, $roleInfo); else $errors[] = "You're not allow to leave this role, an administrator has to remove it for you."; break; @@ -45,39 +44,39 @@ if(!$isRestricted && $isVerifiedRequest && !empty($_POST['role'])) { } } -if($isVerifiedRequest && isset($_POST['tfa']['enable']) && $currentUser->hasTOTPKey() !== (bool)$_POST['tfa']['enable']) { +if($isVerifiedRequest && isset($_POST['tfa']['enable']) && $userInfo->hasTOTPKey() !== (bool)$_POST['tfa']['enable']) { + $totpKey = ''; + if((bool)$_POST['tfa']['enable']) { - $tfaKey = TOTPGenerator::generateKey(); - $tfaIssuer = $cfg->getString('site.name', 'Misuzu'); - $tfaQrcode = (new QRCode(new QROptions([ + $totpKey = TOTPGenerator::generateKey(); + $totpIssuer = $cfg->getString('site.name', 'Misuzu'); + $totpQrcode = (new QRCode(new QROptions([ 'version' => 5, 'outputType' => QRCode::OUTPUT_IMAGE_JPG, 'eccLevel' => QRCode::ECC_L, - ])))->render(sprintf('otpauth://totp/%s:%s?%s', $tfaIssuer, $currentUser->getUsername(), http_build_query([ - 'secret' => $tfaKey, - 'issuer' => $tfaIssuer, + ])))->render(sprintf('otpauth://totp/%s:%s?%s', $totpIssuer, $userInfo->getName(), http_build_query([ + 'secret' => $totpKey, + 'issuer' => $totpIssuer, ]))); Template::set([ - 'settings_2fa_code' => $tfaKey, - 'settings_2fa_image' => $tfaQrcode, + 'settings_2fa_code' => $totpKey, + 'settings_2fa_image' => $totpQrcode, ]); - - $currentUser->setTOTPKey($tfaKey); - } else { - $currentUser->removeTOTPKey(); } + + $users->updateUser(userInfo: $userInfo, totpKey: $totpKey); } if($isVerifiedRequest && !empty($_POST['current_password'])) { - if(!$currentUser->checkPassword($_POST['current_password'] ?? '')) { + if(!$userInfo->verifyPassword($_POST['current_password'] ?? '')) { $errors[] = 'Your password was incorrect.'; } else { // Changing e-mail if(!empty($_POST['email']['new'])) { if(empty($_POST['email']['confirm']) || $_POST['email']['new'] !== $_POST['email']['confirm']) { $errors[] = 'The addresses you entered did not match each other.'; - } elseif($currentUser->getEMailAddress() === mb_strtolower($_POST['email']['confirm'])) { + } elseif($userInfo->getEMailAddress() === mb_strtolower($_POST['email']['confirm'])) { $errors[] = 'This is already your e-mail address!'; } else { $checkMail = User::validateEMailAddress($_POST['email']['new'], true); @@ -100,10 +99,8 @@ if($isVerifiedRequest && !empty($_POST['current_password'])) { $errors[] = 'Unknown e-mail validation error.'; } } else { - $currentUser->setEMailAddress($_POST['email']['new']); - $msz->createAuditLog('PERSONAL_EMAIL_CHANGE', [ - $_POST['email']['new'], - ]); + $users->updateUser(userInfo: $userInfo, emailAddr: $_POST['email']['new']); + $msz->createAuditLog('PERSONAL_EMAIL_CHANGE', [$_POST['email']['new']]); } } } @@ -118,7 +115,7 @@ if($isVerifiedRequest && !empty($_POST['current_password'])) { if($checkPassword !== '') { $errors[] = 'The given passwords was too weak.'; } else { - $currentUser->setPassword($_POST['password']['new']); + $users->updateUser(userInfo: $userInfo, password: $_POST['password']['new']); $msz->createAuditLog('PERSONAL_PASSWORD_CHANGE'); } } @@ -126,20 +123,15 @@ if($isVerifiedRequest && !empty($_POST['current_password'])) { } } -// THIS FUCKING SUCKS AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -if($_SERVER['REQUEST_METHOD'] === 'POST' && $isVerifiedRequest) { - $currentUser->save(); +// reload $userInfo object +if($_SERVER['REQUEST_METHOD'] === 'POST' && $isVerifiedRequest) + $userInfo = $users->getUser($userInfo->getId(), 'id'); - // 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); +$userRoles = $roles->getRoles(userInfo: $userInfo); Template::render('settings.account', [ 'errors' => $errors, - 'settings_user' => $currentUser, + 'settings_user' => $userInfo, 'settings_roles' => $userRoles, 'is_restricted' => $isRestricted, ]); diff --git a/public-legacy/settings/data.php b/public-legacy/settings/data.php index f0fce52..21edd72 100644 --- a/public-legacy/settings/data.php +++ b/public-legacy/settings/data.php @@ -4,18 +4,19 @@ namespace Misuzu; use ZipArchive; use Index\XString; use Index\IO\FileStream; -use Misuzu\Users\User; +use Misuzu\Users\UserInfo; -if(!User::hasCurrent()) { +if(!$msz->isLoggedIn()) { echo render_error(401); return; } $dbConn = $msz->getDbConn(); -function db_to_zip(ZipArchive $archive, int $userId, string $baseName, array $fieldInfos, string $userIdField = 'user_id'): string { +function db_to_zip(ZipArchive $archive, UserInfo $userInfo, string $baseName, array $fieldInfos, string $userIdField = 'user_id'): string { global $dbConn; + $userId = $userInfo->getId(); $fields = []; foreach($fieldInfos as $key => $fieldInfo) { @@ -41,7 +42,7 @@ function db_to_zip(ZipArchive $archive, int $userId, string $baseName, array $fi $fieldInfos[$key] = $fieldInfo; } - $tmpName = sys_get_temp_dir() . DIRECTORY_SEPARATOR . sprintf('msz-user-data-%d-%s-%s.tmp', $userId, $baseName, XString::random(8)); + $tmpName = sys_get_temp_dir() . DIRECTORY_SEPARATOR . sprintf('msz-user-data-%s-%s-%s.tmp', $userId, $baseName, XString::random(8)); $tmpStream = FileStream::newWrite($tmpName); try { @@ -99,18 +100,17 @@ function db_to_zip(ZipArchive $archive, int $userId, string $baseName, array $fi } $errors = []; -$currentUser = User::getCurrent(); -$currentUserId = $currentUser->getId(); +$userInfo = $msz->getActiveUser(); if(isset($_POST['action']) && is_string($_POST['action'])) { if(isset($_POST['password']) && is_string($_POST['password']) - && ($currentUser->checkPassword($_POST['password'] ?? ''))) { + && ($userInfo->verifyPassword($_POST['password'] ?? ''))) { switch($_POST['action']) { case 'data': $msz->createAuditLog('PERSONAL_DATA_DOWNLOAD'); $timeStamp = floor(time() / 3600) * 3600; - $fileName = sprintf('msz-user-data-%d-%d.zip', $currentUserId, $timeStamp); + $fileName = sprintf('msz-user-data-%d-%d.zip', $userInfo->getId(), $timeStamp); $filePath = sys_get_temp_dir() . DIRECTORY_SEPARATOR . $fileName; $archive = new ZipArchive; @@ -119,27 +119,27 @@ if(isset($_POST['action']) && is_string($_POST['action'])) { $tmpFiles = []; try { - $tmpFiles[] = db_to_zip($archive, $currentUserId, 'audit_log', ['user_id:s:n', 'log_action:s', 'log_params:j', 'log_created:t', 'log_ip:a:n', 'log_country:s']); - $tmpFiles[] = db_to_zip($archive, $currentUserId, 'auth_tfa', ['user_id:s', 'tfa_token:n', 'tfa_created:t']); - $tmpFiles[] = db_to_zip($archive, $currentUserId, 'changelog_changes', ['change_id:s', 'user_id:s:n', 'change_action:s:n', 'change_created:t', 'change_log:s', 'change_text:s:n']); - $tmpFiles[] = db_to_zip($archive, $currentUserId, 'comments_categories', ['category_id:s', 'category_name:s', 'owner_id:s:n', 'category_created:t', 'category_locked:t:n'], 'owner_id'); - $tmpFiles[] = db_to_zip($archive, $currentUserId, 'comments_posts', ['comment_id:s', 'category_id:s', 'user_id:s:n', 'comment_reply_to:s:n', 'comment_text:s', 'comment_created:t', 'comment_pinned:t:n', 'comment_edited:t:n', 'comment_deleted:t:n']); - $tmpFiles[] = db_to_zip($archive, $currentUserId, 'comments_votes', ['comment_id:s', 'user_id:s', 'comment_vote:i']); - $tmpFiles[] = db_to_zip($archive, $currentUserId, 'forum_permissions', ['user_id:s:n', 'role_id:s:n', 'forum_id:s', 'forum_perms_allow:i', 'forum_perms_deny:i']); - $tmpFiles[] = db_to_zip($archive, $currentUserId, 'forum_posts', ['post_id:s', 'topic_id:s', 'forum_id:s', 'user_id:s:n', 'post_ip:a', 'post_text:s', 'post_parse:i', 'post_display_signature:b', 'post_created:t', 'post_edited:t:n', 'post_deleted:t:n']); - $tmpFiles[] = db_to_zip($archive, $currentUserId, 'forum_topics', ['topic_id:s', 'forum_id:s', 'user_id:s:n', 'topic_type:i', 'topic_title:s', 'topic_count_views:i', 'topic_created:t', 'topic_bumped:t', 'topic_deleted:t:n', 'topic_locked:t:n']); - $tmpFiles[] = db_to_zip($archive, $currentUserId, 'forum_topics_redirects', ['topic_id:s', 'user_id:s:n', 'topic_redir_url:s', 'topic_redir_created:t']); - $tmpFiles[] = db_to_zip($archive, $currentUserId, 'forum_topics_track', ['user_id:s', 'topic_id:s', 'forum_id:s', 'track_last_read:t']); - $tmpFiles[] = db_to_zip($archive, $currentUserId, 'login_attempts', ['user_id:s:n', 'attempt_success:b', 'attempt_ip:a', 'attempt_country:s', 'attempt_created:t', 'attempt_user_agent:s']); - $tmpFiles[] = db_to_zip($archive, $currentUserId, 'news_posts', ['post_id:s', 'category_id:s', 'user_id:s:n', 'comment_section_id:s:n', 'post_is_featured:b', 'post_title:s', 'post_text:s', 'post_scheduled:t', 'post_created:t', 'post_updated:t', 'post_deleted:t:n']); - $tmpFiles[] = db_to_zip($archive, $currentUserId, 'permissions', ['user_id:s:n', 'role_id:s:n', 'general_perms_allow:i', 'general_perms_deny:i', 'user_perms_allow:i', 'user_perms_deny:i', 'changelog_perms_allow:i', 'changelog_perms_deny:i', 'news_perms_allow:i', 'news_perms_deny:i', 'forum_perms_allow:i', 'forum_perms_deny:i', 'comments_perms_allow:i', 'comments_perms_deny:i']); - $tmpFiles[] = db_to_zip($archive, $currentUserId, 'profile_fields_values', ['field_id:s', 'user_id:s', 'format_id:s', 'field_value:s']); - $tmpFiles[] = db_to_zip($archive, $currentUserId, 'sessions', ['session_id:s', 'user_id:s', 'session_key:n', 'session_ip:a', 'session_ip_last:a:n', 'session_user_agent:s', 'session_country:s', 'session_expires:t', 'session_expires_bump:b', 'session_created:t', 'session_active:t:n']); - $tmpFiles[] = db_to_zip($archive, $currentUserId, 'users', ['user_id:s', 'username:s', 'password:n', 'email:s', 'register_ip:a', 'last_ip:a', 'user_super:b', 'user_country:s', 'user_colour:i:n', 'user_created:t', 'user_active:t:n', 'user_deleted:t:n', 'display_role:s:n', 'user_totp_key:n', 'user_about_content:s:n', 'user_about_parser:i', 'user_signature_content:s:n', 'user_signature_parser:i', 'user_birthdate:s:n', 'user_background_settings:i:n', 'user_title:s:n']); - $tmpFiles[] = db_to_zip($archive, $currentUserId, 'users_bans', ['ban_id:s', 'user_id:s', 'mod_id:n', 'ban_severity:i', 'ban_reason_public:s', 'ban_reason_private:s', 'ban_created:t', 'ban_expires:t:n']); - $tmpFiles[] = db_to_zip($archive, $currentUserId, 'users_password_resets', ['user_id:s', 'reset_ip:a', 'reset_requested:t', 'verification_code:n']); - $tmpFiles[] = db_to_zip($archive, $currentUserId, 'users_warnings', ['warn_id:s', 'user_id:s', 'mod_id:n', 'warn_body:s', 'warn_created:t']); - $tmpFiles[] = db_to_zip($archive, $currentUserId, 'users_roles', ['user_id:s', 'role_id:s']); + $tmpFiles[] = db_to_zip($archive, $userInfo, 'audit_log', ['user_id:s:n', 'log_action:s', 'log_params:j', 'log_created:t', 'log_ip:a:n', 'log_country:s']); + $tmpFiles[] = db_to_zip($archive, $userInfo, 'auth_tfa', ['user_id:s', 'tfa_token:n', 'tfa_created:t']); + $tmpFiles[] = db_to_zip($archive, $userInfo, 'changelog_changes', ['change_id:s', 'user_id:s:n', 'change_action:s:n', 'change_created:t', 'change_log:s', 'change_text:s:n']); + $tmpFiles[] = db_to_zip($archive, $userInfo, 'comments_categories', ['category_id:s', 'category_name:s', 'owner_id:s:n', 'category_created:t', 'category_locked:t:n'], 'owner_id'); + $tmpFiles[] = db_to_zip($archive, $userInfo, 'comments_posts', ['comment_id:s', 'category_id:s', 'user_id:s:n', 'comment_reply_to:s:n', 'comment_text:s', 'comment_created:t', 'comment_pinned:t:n', 'comment_edited:t:n', 'comment_deleted:t:n']); + $tmpFiles[] = db_to_zip($archive, $userInfo, 'comments_votes', ['comment_id:s', 'user_id:s', 'comment_vote:i']); + $tmpFiles[] = db_to_zip($archive, $userInfo, 'forum_permissions', ['user_id:s:n', 'role_id:s:n', 'forum_id:s', 'forum_perms_allow:i', 'forum_perms_deny:i']); + $tmpFiles[] = db_to_zip($archive, $userInfo, 'forum_posts', ['post_id:s', 'topic_id:s', 'forum_id:s', 'user_id:s:n', 'post_ip:a', 'post_text:s', 'post_parse:i', 'post_display_signature:b', 'post_created:t', 'post_edited:t:n', 'post_deleted:t:n']); + $tmpFiles[] = db_to_zip($archive, $userInfo, 'forum_topics', ['topic_id:s', 'forum_id:s', 'user_id:s:n', 'topic_type:i', 'topic_title:s', 'topic_count_views:i', 'topic_created:t', 'topic_bumped:t', 'topic_deleted:t:n', 'topic_locked:t:n']); + $tmpFiles[] = db_to_zip($archive, $userInfo, 'forum_topics_redirects', ['topic_id:s', 'user_id:s:n', 'topic_redir_url:s', 'topic_redir_created:t']); + $tmpFiles[] = db_to_zip($archive, $userInfo, 'forum_topics_track', ['user_id:s', 'topic_id:s', 'forum_id:s', 'track_last_read:t']); + $tmpFiles[] = db_to_zip($archive, $userInfo, 'login_attempts', ['user_id:s:n', 'attempt_success:b', 'attempt_ip:a', 'attempt_country:s', 'attempt_created:t', 'attempt_user_agent:s']); + $tmpFiles[] = db_to_zip($archive, $userInfo, 'news_posts', ['post_id:s', 'category_id:s', 'user_id:s:n', 'comment_section_id:s:n', 'post_is_featured:b', 'post_title:s', 'post_text:s', 'post_scheduled:t', 'post_created:t', 'post_updated:t', 'post_deleted:t:n']); + $tmpFiles[] = db_to_zip($archive, $userInfo, 'permissions', ['user_id:s:n', 'role_id:s:n', 'general_perms_allow:i', 'general_perms_deny:i', 'user_perms_allow:i', 'user_perms_deny:i', 'changelog_perms_allow:i', 'changelog_perms_deny:i', 'news_perms_allow:i', 'news_perms_deny:i', 'forum_perms_allow:i', 'forum_perms_deny:i', 'comments_perms_allow:i', 'comments_perms_deny:i']); + $tmpFiles[] = db_to_zip($archive, $userInfo, 'profile_fields_values', ['field_id:s', 'user_id:s', 'format_id:s', 'field_value:s']); + $tmpFiles[] = db_to_zip($archive, $userInfo, 'sessions', ['session_id:s', 'user_id:s', 'session_key:n', 'session_ip:a', 'session_ip_last:a:n', 'session_user_agent:s', 'session_country:s', 'session_expires:t', 'session_expires_bump:b', 'session_created:t', 'session_active:t:n']); + $tmpFiles[] = db_to_zip($archive, $userInfo, 'users', ['user_id:s', 'username:s', 'password:n', 'email:s', 'register_ip:a', 'last_ip:a', 'user_super:b', 'user_country:s', 'user_colour:i:n', 'user_created:t', 'user_active:t:n', 'user_deleted:t:n', 'display_role:s:n', 'user_totp_key:n', 'user_about_content:s:n', 'user_about_parser:i', 'user_signature_content:s:n', 'user_signature_parser:i', 'user_birthdate:s:n', 'user_background_settings:i:n', 'user_title:s:n']); + $tmpFiles[] = db_to_zip($archive, $userInfo, 'users_bans', ['ban_id:s', 'user_id:s', 'mod_id:n', 'ban_severity:i', 'ban_reason_public:s', 'ban_reason_private:s', 'ban_created:t', 'ban_expires:t:n']); + $tmpFiles[] = db_to_zip($archive, $userInfo, 'users_password_resets', ['user_id:s', 'reset_ip:a', 'reset_requested:t', 'verification_code:n']); + $tmpFiles[] = db_to_zip($archive, $userInfo, 'users_warnings', ['warn_id:s', 'user_id:s', 'mod_id:n', 'warn_body:s', 'warn_created:t']); + $tmpFiles[] = db_to_zip($archive, $userInfo, 'users_roles', ['user_id:s', 'role_id:s']); $archive->close(); } finally { diff --git a/public-legacy/settings/index.php b/public-legacy/settings/index.php index 0e73dc0..9ae3d3a 100644 --- a/public-legacy/settings/index.php +++ b/public-legacy/settings/index.php @@ -1,9 +1,7 @@ isLoggedIn()) { echo render_error(401); return; } diff --git a/public-legacy/settings/logs.php b/public-legacy/settings/logs.php index c9f4bd5..a5832e2 100644 --- a/public-legacy/settings/logs.php +++ b/public-legacy/settings/logs.php @@ -2,10 +2,8 @@ namespace Misuzu; use Misuzu\Pagination; -use Misuzu\Users\User; - -$currentUser = User::getCurrent(); +$currentUser = $msz->getActiveUser(); if($currentUser === null) { echo render_error(401); return; diff --git a/public-legacy/settings/sessions.php b/public-legacy/settings/sessions.php index 89c4517..9c74abe 100644 --- a/public-legacy/settings/sessions.php +++ b/public-legacy/settings/sessions.php @@ -2,16 +2,15 @@ namespace Misuzu; use RuntimeException; -use Misuzu\Users\User; -if(!User::hasCurrent()) { +if(!$msz->isLoggedIn()) { echo render_error(401); return; } $errors = []; $sessions = $msz->getSessions(); -$currentUser = User::getCurrent(); +$currentUser = $msz->getActiveUser(); $activeSessionToken = $authToken->getSessionToken(); while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) { @@ -27,7 +26,7 @@ while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) { $sessionInfo = $sessions->getSession(sessionId: $sessionId); } catch(RuntimeException $ex) {} - if(empty($sessionInfo) || $sessionInfo->getUserId() !== (string)$currentUser->getId()) { + if(empty($sessionInfo) || $sessionInfo->getUserId() !== $currentUser->getId()) { $errors[] = "That session doesn't exist."; break; } diff --git a/public/index.php b/public/index.php index 9d3fb76..406ee21 100644 --- a/public/index.php +++ b/public/index.php @@ -2,7 +2,6 @@ namespace Misuzu; use RuntimeException; -use Misuzu\Users\User; require_once __DIR__ . '/../misuzu.php'; @@ -50,7 +49,6 @@ $globals = $cfg->getValues([ ['site.name:s', 'Misuzu'], 'site.desc:s', 'site.url:s', - 'sockChat.chatPath.normal:s', 'eeprom.path:s', 'eeprom.app:s', ['auth.secret:s', 'meow'], @@ -63,7 +61,6 @@ Template::set('globals', [ 'site_name' => $globals['site.name'], 'site_description' => $globals['site.desc'], 'site_url' => $globals['site.url'], - 'site_chat' => $globals['sockChat.chatPath.normal'], 'eeprom' => [ 'path' => $globals['eeprom.path'], 'app' => $globals['eeprom.app'], @@ -81,7 +78,7 @@ AuthToken::setSecretKey($globals['auth.secret']); if(isset($_COOKIE['msz_uid']) && isset($_COOKIE['msz_sid'])) { $authToken = new AuthToken; - $authToken->setUserId(filter_input(INPUT_COOKIE, 'msz_uid', FILTER_SANITIZE_NUMBER_INT) ?? 0); + $authToken->setUserId(filter_input(INPUT_COOKIE, 'msz_uid', FILTER_SANITIZE_NUMBER_INT) ?? '0'); $authToken->setSessionToken(filter_input(INPUT_COOKIE, 'msz_sid') ?? ''); if($authToken->isValid()) @@ -93,27 +90,26 @@ if(isset($_COOKIE['msz_uid']) && isset($_COOKIE['msz_sid'])) { if(!isset($authToken)) $authToken = AuthToken::unpack(filter_input(INPUT_COOKIE, 'msz_auth') ?? ''); -if($authToken->isValid()) { - $sessions = $msz->getSessions(); - $authToken->setCurrent(); +$users = $msz->getUsers(); +$sessions = $msz->getSessions(); +if($authToken->isValid()) { try { $sessionInfo = $sessions->getSession(sessionToken: $authToken->getSessionToken()); if($sessionInfo->hasExpired()) { $sessions->deleteSessions(sessionInfos: $sessionInfo); - } elseif($sessionInfo->getUserId() === (string)$authToken->getUserId()) { - $userInfo = User::byId((int)$sessionInfo->getUserId()); + } elseif($sessionInfo->getUserId() === $authToken->getUserId()) { + $userInfo = $users->getUser($authToken->getUserId(), 'id'); if(!$userInfo->isDeleted()) { - $userInfo->setCurrent(); - $userInfo->bumpActivity($_SERVER['REMOTE_ADDR']); - $sessions->updateSession(sessionInfo: $sessionInfo, remoteAddr: $_SERVER['REMOTE_ADDR']); + $users->recordUserActivity($userInfo, remoteAddr: $_SERVER['REMOTE_ADDR']); + $sessions->recordSessionActivity(sessionInfo: $sessionInfo, remoteAddr: $_SERVER['REMOTE_ADDR']); if($sessionInfo->shouldBumpExpires()) $authToken->applyCookie($sessionInfo->getExpiresTime()); if($authToken->hasImpersonatedUserId()) { - $allowToImpersonate = $userInfo->isSuper(); + $allowToImpersonate = $userInfo->isSuperUser(); $impersonatedUserId = $authToken->getImpersonatedUserId(); if(!$allowToImpersonate) { @@ -126,13 +122,11 @@ if($authToken->isValid()) { $userInfoReal = $userInfo; try { - $userInfo = User::byId($impersonatedUserId); + $userInfo = $users->getUser($impersonatedUserId, 'id'); } catch(RuntimeException $ex) { $userInfo = $userInfoReal; $removeImpersonationData = true; } - - $userInfo->setCurrent(); } if($removeImpersonationData) { @@ -140,46 +134,51 @@ if($authToken->isValid()) { $authToken->applyCookie(); } } + + $msz->setAuthInfo($authToken, $userInfo, $userInfoReal ?? null); } } } catch(RuntimeException $ex) { - User::unsetCurrent(); - } - - if(!User::hasCurrent()) AuthToken::nukeCookie(); + } } +if(!empty($userInfo)) + $userInfo = $users->getUser((string)$userInfo->getId(), 'id'); +if(!empty($userInfoReal)) + $userInfoReal = $users->getUser((string)$userInfoReal->getId(), 'id'); + CSRF::init( $globals['csrf.secret'], - (User::hasCurrent() ? $authToken->getSessionToken() : $_SERVER['REMOTE_ADDR']) + ($msz->isLoggedIn() ? $authToken->getSessionToken() : $_SERVER['REMOTE_ADDR']) ); if(!empty($userInfo)) { Template::set('current_user', $userInfo); Template::set('current_user_ban_info', $msz->tryGetActiveBan()); } -if(!empty($userInfoReal)) + +if(!empty($userInfoReal)) { Template::set('current_user_real', $userInfoReal); + Template::set('current_user_real_colour', $users->getUserColour($userInfoReal)); +} $inManageMode = str_starts_with($_SERVER['REQUEST_URI'], '/manage'); -$hasManageAccess = User::hasCurrent() - && !$msz->hasActiveBan() - && perms_check_user(MSZ_PERMS_GENERAL, User::getCurrent()->getId(), MSZ_PERM_GENERAL_CAN_MANAGE); -Template::set('has_manage_access', $hasManageAccess); -$canViewForumLeaderboard = User::hasCurrent() - && !$msz->hasActiveBan() - && perms_check_user(MSZ_PERMS_GENERAL, User::getCurrent()->getId(), MSZ_PERM_FORUM_VIEW_LEADERBOARD); -Template::set('can_view_forum_leaderboard', $canViewForumLeaderboard); +Template::set('header_menu', $msz->getHeaderMenu($userInfo ?? null)); +Template::set('user_menu', $msz->getUserMenu($userInfo ?? null, $inManageMode)); +Template::set('display_debug_info', MSZ_DEBUG || (!empty($userInfo) && $userInfo->isSuperUser())); if($inManageMode) { + $hasManageAccess = $msz->isLoggedIn() && !$msz->hasActiveBan() + && perms_check_user(MSZ_PERMS_GENERAL, $msz->getActiveUser()->getId(), MSZ_PERM_GENERAL_CAN_MANAGE); + if(!$hasManageAccess) { echo render_error(403); exit; } - Template::set('manage_menu', manage_get_menu(User::getCurrent()->getId())); + Template::set('manage_menu', manage_get_menu($userInfo->getId())); } $mszRequestPath = $request->getPath(); diff --git a/src/AuditLog/AuditLog.php b/src/AuditLog/AuditLog.php index 0ba029a..912403a 100644 --- a/src/AuditLog/AuditLog.php +++ b/src/AuditLog/AuditLog.php @@ -7,7 +7,7 @@ use Index\Data\IDbConnection; use Index\Data\IDbResult; use Index\Net\IPAddress; use Misuzu\Pagination; -use Misuzu\Users\User; +use Misuzu\Users\UserInfo; class AuditLog { private DbStatementCache $cache; @@ -17,11 +17,11 @@ class AuditLog { } public function countLogs( - User|string|null $userInfo = null, + UserInfo|string|null $userInfo = null, IPAddress|string|null $remoteAddr = null ): int { - if($userInfo instanceof User) - $userInfo = (string)$userInfo->getId(); + if($userInfo instanceof UserInfo) + $userInfo = $userInfo->getId(); if($remoteAddr instanceof IPAddress) $remoteAddr = (string)$remoteAddr; @@ -58,12 +58,12 @@ class AuditLog { } public function getLogs( - User|string|null $userInfo = null, + UserInfo|string|null $userInfo = null, IPAddress|string|null $remoteAddr = null, ?Pagination $pagination = null ): array { - if($userInfo instanceof User) - $userInfo = (string)$userInfo->getId(); + if($userInfo instanceof UserInfo) + $userInfo = $userInfo->getId(); if($remoteAddr instanceof IPAddress) $remoteAddr = (string)$remoteAddr; @@ -108,14 +108,14 @@ class AuditLog { } public function createLog( - User|string|null $userInfo, + UserInfo|string|null $userInfo, string $action, array $params = [], IPAddress|string $remoteAddr = '::1', string $countryCode = 'XX' ): void { - if($userInfo instanceof User) - $userInfo = (string)$userInfo->getId(); + if($userInfo instanceof UserInfo) + $userInfo = $userInfo->getId(); if($remoteAddr instanceof IPAddress) $remoteAddr = (string)$remoteAddr; diff --git a/src/AuditLog/AuditLogInfo.php b/src/AuditLog/AuditLogInfo.php index 2358ff7..5b82ae6 100644 --- a/src/AuditLog/AuditLogInfo.php +++ b/src/AuditLog/AuditLogInfo.php @@ -5,7 +5,6 @@ use ValueError; use Index\DateTime; use Index\Data\IDbResult; use Index\Net\IPAddress; -use Misuzu\Users\User; class AuditLogInfo { private ?string $userId; diff --git a/src/Auth/LoginAttempts.php b/src/Auth/LoginAttempts.php index bc40fc9..91cd410 100644 --- a/src/Auth/LoginAttempts.php +++ b/src/Auth/LoginAttempts.php @@ -7,7 +7,7 @@ use Index\Data\IDbConnection; use Index\Net\IPAddress; use Misuzu\ClientInfo; use Misuzu\Pagination; -use Misuzu\Users\User; +use Misuzu\Users\UserInfo; class LoginAttempts { public const REMAINING_MAX = 5; @@ -21,12 +21,12 @@ class LoginAttempts { public function countAttempts( ?bool $success = null, - User|string|null $userInfo = null, + UserInfo|string|null $userInfo = null, IPAddress|string|null $remoteAddr = null, TimeSpan|int|null $timeRange = null ): int { - if($userInfo instanceof User) - $userInfo = (string)$userInfo->getId(); + if($userInfo instanceof UserInfo) + $userInfo = $userInfo->getId(); if($remoteAddr instanceof IPAddress) $remoteAddr = (string)$remoteAddr; if($timeRange instanceof TimeSpan) @@ -79,13 +79,13 @@ class LoginAttempts { public function getAttempts( ?bool $success = null, - User|string|null $userInfo = null, + UserInfo|string|null $userInfo = null, IPAddress|string|null $remoteAddr = null, TimeSpan|int|null $timeRange = null, ?Pagination $pagination = null ): array { - if($userInfo instanceof User) - $userInfo = (string)$userInfo->getId(); + if($userInfo instanceof UserInfo) + $userInfo = $userInfo->getId(); if($remoteAddr instanceof IPAddress) $remoteAddr = (string)$remoteAddr; if($timeRange instanceof TimeSpan) @@ -142,12 +142,12 @@ class LoginAttempts { string $countryCode, string $userAgentString, ?ClientInfo $clientInfo = null, - User|string|null $userInfo = null + UserInfo|string|null $userInfo = null ): void { if($remoteAddr instanceof IPAddress) $remoteAddr = (string)$remoteAddr; - if($userInfo instanceof User) - $userInfo = (string)$userInfo->getId(); + if($userInfo instanceof UserInfo) + $userInfo = $userInfo->getId(); $hasUserInfo = $userInfo !== null; $clientInfo = json_encode($clientInfo ?? ClientInfo::parse($userAgentString)); diff --git a/src/Auth/RecoveryTokens.php b/src/Auth/RecoveryTokens.php index af7e169..213e8a6 100644 --- a/src/Auth/RecoveryTokens.php +++ b/src/Auth/RecoveryTokens.php @@ -9,7 +9,7 @@ use Index\Net\IPAddress; use Index\Serialisation\Base32; use Misuzu\ClientInfo; use Misuzu\Pagination; -use Misuzu\Users\User; +use Misuzu\Users\UserInfo; class RecoveryTokens { private DbStatementCache $cache; @@ -24,13 +24,13 @@ class RecoveryTokens { } public function getToken( - User|string|null $userInfo = null, + UserInfo|string|null $userInfo = null, IPAddress|string|null $remoteAddr = null, ?string $verifyCode = null, ?bool $isUnused = null ): RecoveryTokenInfo { - if($userInfo instanceof User) - $userInfo = (string)$userInfo->getId(); + if($userInfo instanceof UserInfo) + $userInfo = $userInfo->getId(); if($remoteAddr instanceof IPAddress) $remoteAddr = (string)$remoteAddr; @@ -75,11 +75,11 @@ class RecoveryTokens { } public function createToken( - User|string $userInfo, + UserInfo|string $userInfo, IPAddress|string $remoteAddr ): RecoveryTokenInfo { - if($userInfo instanceof User) - $userInfo = (string)$userInfo->getId(); + if($userInfo instanceof UserInfo) + $userInfo = $userInfo->getId(); if($remoteAddr instanceof IPAddress) $remoteAddr = (string)$remoteAddr; $verifyCode = self::generateCode(); diff --git a/src/Auth/Sessions.php b/src/Auth/Sessions.php index 66b325d..a7aeb39 100644 --- a/src/Auth/Sessions.php +++ b/src/Auth/Sessions.php @@ -10,7 +10,7 @@ use Index\Data\IDbConnection; use Index\Net\IPAddress; use Misuzu\ClientInfo; use Misuzu\Pagination; -use Misuzu\Users\User; +use Misuzu\Users\UserInfo; class Sessions { private IDbConnection $dbConn; @@ -26,10 +26,10 @@ class Sessions { } public function countSessions( - User|string|null $userInfo = null + UserInfo|string|null $userInfo = null ): int { - if($userInfo instanceof User) - $userInfo = (string)$userInfo->getId(); + if($userInfo instanceof UserInfo) + $userInfo = $userInfo->getId(); $hasUserInfo = $userInfo !== null; @@ -56,11 +56,11 @@ class Sessions { } public function getSessions( - User|string|null $userInfo = null, + UserInfo|string|null $userInfo = null, ?Pagination $pagination = null ): array { - if($userInfo instanceof User) - $userInfo = (string)$userInfo->getId(); + if($userInfo instanceof UserInfo) + $userInfo = $userInfo->getId(); $hasUserInfo = $userInfo !== null; $hasPagination = $pagination !== null; @@ -128,14 +128,14 @@ class Sessions { } public function createSession( - User|string $userInfo, + UserInfo|string $userInfo, IPAddress|string $remoteAddr, string $countryCode, string $userAgentString, ?ClientInfo $clientInfo = null ): SessionInfo { - if($userInfo instanceof User) - $userInfo = (string)$userInfo->getId(); + if($userInfo instanceof UserInfo) + $userInfo = $userInfo->getId(); if($remoteAddr instanceof IPAddress) $remoteAddr = (string)$remoteAddr; @@ -157,7 +157,7 @@ class Sessions { public function deleteSessions( SessionInfo|string|array|null $sessionInfos = null, string|array|null $sessionTokens = null, - User|string|array|null $userInfos = null + UserInfo|string|array|null $userInfos = null ): void { $hasSessionInfos = $sessionInfos !== null; $hasSessionTokens = $sessionTokens !== null; @@ -235,10 +235,10 @@ class Sessions { if($hasUserInfos) foreach($userInfos as $userInfo) { - if($userInfo instanceof User) - $userInfo = (string)$userInfo->getId(); + if($userInfo instanceof UserInfo) + $userInfo = $userInfo->getId(); elseif(!is_string($userInfo)) - throw new InvalidArgumentException('$userInfos must be strings or instances of User.'); + throw new InvalidArgumentException('$userInfos must be strings or instances of UserInfo.'); $stmt->addParameter(++$args, $userInfo); } @@ -246,7 +246,7 @@ class Sessions { $stmt->execute(); } - public function updateSession( + public function recordSessionActivity( SessionInfo|string|null $sessionInfo = null, ?string $sessionToken = null, IPAddress|string|null $remoteAddr = null diff --git a/src/Auth/TwoFactorAuthSessions.php b/src/Auth/TwoFactorAuthSessions.php index 51c377b..afafd7c 100644 --- a/src/Auth/TwoFactorAuthSessions.php +++ b/src/Auth/TwoFactorAuthSessions.php @@ -4,7 +4,7 @@ namespace Misuzu\Auth; use Index\XString; use Index\Data\DbStatementCache; use Index\Data\IDbConnection; -use Misuzu\Users\User; +use Misuzu\Users\UserInfo; class TwoFactorAuthSessions { private DbStatementCache $cache; @@ -17,9 +17,9 @@ class TwoFactorAuthSessions { return XString::random(32); } - public function createToken(User|string $userInfo): string { - if($userInfo instanceof User) - $userInfo = (string)$userInfo->getId(); + public function createToken(UserInfo|string $userInfo): string { + if($userInfo instanceof UserInfo) + $userInfo = $userInfo->getId(); $token = self::generateToken(); diff --git a/src/AuthToken.php b/src/AuthToken.php index 66cb002..ef92363 100644 --- a/src/AuthToken.php +++ b/src/AuthToken.php @@ -4,7 +4,7 @@ namespace Misuzu; use Index\IO\MemoryStream; use Index\Serialisation\UriBase64; use Misuzu\Auth\SessionInfo; -use Misuzu\Users\User; +use Misuzu\Users\UserInfo; /* Map of props * u - User ID @@ -54,12 +54,11 @@ class AuthToken { return true; } - public function getUserId(): int { - $value = (int)$this->getProperty('u'); - return $value < 1 ? -1 : $value; + public function getUserId(): string { + return $this->getProperty('u'); } - public function setUserId(int $userId): self { - $this->setProperty('u', (string)$userId); + public function setUserId(string $userId): self { + $this->setProperty('u', $userId); return $this; } @@ -177,7 +176,7 @@ class AuthToken { return time() - self::EPOCH; } - public static function create(User $userInfo, SessionInfo $sessionInfo): self { + public static function create(UserInfo $userInfo, SessionInfo $sessionInfo): self { $token = new AuthToken; $token->setUserId($userInfo->getId()); $token->setSessionToken($sessionInfo->getToken()); @@ -213,23 +212,4 @@ class AuthToken { setcookie('msz_uid', '', -3600, '/', '', !empty($_SERVER['HTTPS']), true); setcookie('msz_sid', '', -3600, '/', '', !empty($_SERVER['HTTPS']), true); } - - // please never use the below functions beyond the scope of the sharpchat auth stuff - // a better mechanism for keeping a global instance of this available - // that isn't a $GLOBAL variable or static instance needs to be established, for User as well - - private static $localToken = null; - - public function setCurrent(): void { - self::$localToken = $this; - } - public static function unsetCurrent(): void { - self::$localToken = null; - } - public static function getCurrent(): ?self { - return self::$localToken; - } - public static function hasCurrent(): bool { - return self::$localToken !== null; - } } diff --git a/src/Changelog/Changelog.php b/src/Changelog/Changelog.php index 6d39201..11571fc 100644 --- a/src/Changelog/Changelog.php +++ b/src/Changelog/Changelog.php @@ -9,7 +9,7 @@ use Index\Data\DbTools; use Index\Data\IDbConnection; use Index\Data\IDbResult; use Misuzu\Pagination; -use Misuzu\Users\User; +use Misuzu\Users\UserInfo; class Changelog { // not a strict list but useful to have @@ -96,12 +96,12 @@ class Changelog { } public function countAllChanges( - User|string|null $userInfo = null, + UserInfo|string|null $userInfo = null, DateTime|int|null $dateTime = null, ?array $tags = null ): int { - if($userInfo instanceof User) - $userInfo = (string)$userInfo->getId(); + if($userInfo instanceof UserInfo) + $userInfo = $userInfo->getId(); if($dateTime instanceof DateTime) $dateTime = $dateTime->getUnixTimeSeconds(); @@ -149,13 +149,13 @@ class Changelog { public function getAllChanges( bool $withTags = false, - User|string|null $userInfo = null, + UserInfo|string|null $userInfo = null, DateTime|int|null $dateTime = null, ?array $tags = null, ?Pagination $pagination = null ): array { - if($userInfo instanceof User) - $userInfo = (string)$userInfo->getId(); + if($userInfo instanceof UserInfo) + $userInfo = $userInfo->getId(); if($dateTime instanceof DateTime) $dateTime = $dateTime->getUnixTimeSeconds(); @@ -224,13 +224,13 @@ class Changelog { string|int $action, string $summary, string $body = '', - User|string|null $userInfo = null, + UserInfo|string|null $userInfo = null, DateTime|int|null $createdAt = null ): ChangeInfo { if(is_string($action)) $action = self::convertToActionId($action); - if($userInfo instanceof User) - $userInfo = (string)$userInfo->getId(); + if($userInfo instanceof UserInfo) + $userInfo = $userInfo->getId(); if($createdAt instanceof DateTime) $createdAt = $createdAt->getUnixTimeSeconds(); @@ -268,7 +268,7 @@ class Changelog { ?string $summary = null, ?string $body = null, bool $updateUserInfo = false, - User|string|null $userInfo = null, + UserInfo|string|null $userInfo = null, DateTime|int|null $createdAt = null ): void { if($infoOrId instanceof ChangeInfo) @@ -276,8 +276,8 @@ class Changelog { if(is_string($action)) $action = self::convertToActionId($action); - if($userInfo instanceof User) - $userInfo = (string)$userInfo->getId(); + if($userInfo instanceof UserInfo) + $userInfo = $userInfo->getId(); if($createdAt instanceof DateTime) $createdAt = $createdAt->getUnixTimeSeconds(); diff --git a/src/Comments/Comments.php b/src/Comments/Comments.php index f264942..4aa345e 100644 --- a/src/Comments/Comments.php +++ b/src/Comments/Comments.php @@ -7,7 +7,7 @@ use Index\Data\DbStatementCache; use Index\Data\IDbConnection; use Index\Data\IDbResult; use Misuzu\Pagination; -use Misuzu\Users\User; +use Misuzu\Users\UserInfo; class Comments { private IDbConnection $dbConn; @@ -18,9 +18,9 @@ class Comments { $this->cache = new DbStatementCache($dbConn); } - public function countAllCategories(User|string|null $owner = null): int { - if($owner instanceof User) - $owner = (string)$owner->getId(); + public function countAllCategories(UserInfo|string|null $owner = null): int { + if($owner instanceof UserInfo) + $owner = $owner->getId(); $hasOwner = $owner !== null; @@ -42,11 +42,11 @@ class Comments { } public function getCategories( - User|string|null $owner = null, + UserInfo|string|null $owner = null, ?Pagination $pagination = null ): array { - if($owner instanceof User) - $owner = (string)$owner->getId(); + if($owner instanceof UserInfo) + $owner = $owner->getId(); $hasOwner = $owner !== null; $hasPagination = $pagination !== null; @@ -139,15 +139,15 @@ class Comments { return $count > 0; } - public function ensureCategory(string $name, User|string|null $owner = null): CommentsCategoryInfo { + public function ensureCategory(string $name, UserInfo|string|null $owner = null): CommentsCategoryInfo { if($this->checkCategoryNameExists($name)) return $this->getCategoryByName($name); return $this->createCategory($name, $owner); } - public function createCategory(string $name, User|string|null $owner = null): CommentsCategoryInfo { - if($owner instanceof User) - $owner = (string)$owner->getId(); + public function createCategory(string $name, UserInfo|string|null $owner = null): CommentsCategoryInfo { + if($owner instanceof UserInfo) + $owner = $owner->getId(); $name = trim($name); if(empty($name)) @@ -174,12 +174,12 @@ class Comments { CommentsCategoryInfo|string $category, ?string $name = null, bool $updateOwner = false, - User|string|null $owner = null + UserInfo|string|null $owner = null ): void { if($category instanceof CommentsCategoryInfo) $category = $category->getId(); - if($owner instanceof User) - $owner = (string)$owner->getId(); + if($owner instanceof UserInfo) + $owner = $owner->getId(); if($name !== null) { $name = trim($name); @@ -368,7 +368,7 @@ class Comments { public function createPost( CommentsCategoryInfo|string|null $category, CommentsPostInfo|string|null $parent, - User|string|null $user, + UserInfo|string|null $user, string $body, bool $pin = false ): CommentsPostInfo { @@ -383,8 +383,8 @@ class Comments { } if($category === null) throw new InvalidArgumentException('$category is null; at least a $category or $parent must be specified.'); - if($user instanceof User) - $user = (string)$user->getId(); + if($user instanceof UserInfo) + $user = $user->getId(); if(empty(trim($body))) throw new InvalidArgumentException('$body may not be empty.'); @@ -459,12 +459,12 @@ class Comments { public function getPostVote( CommentsPostInfo|string $post, - User|string|null $user + UserInfo|string|null $user ): CommentsPostVoteInfo { if($post instanceof CommentsPostInfo) $post = $post->getId(); - if($user instanceof User) - $user = (string)$user->getId(); + if($user instanceof UserInfo) + $user = $user->getId(); // SUM() here makes it so a result row is always returned, albeit with just NULLs $stmt = $this->cache->get('SELECT comment_id, user_id, SUM(comment_vote) FROM msz_comments_votes WHERE comment_id = ? AND user_id = ?'); @@ -481,15 +481,15 @@ class Comments { public function addPostVote( CommentsPostInfo|string $post, - User|string $user, + UserInfo|string $user, int $weight ): void { if($weight === 0) return; if($post instanceof CommentsPostInfo) $post = $post->getId(); - if($user instanceof User) - $user = (string)$user->getId(); + if($user instanceof UserInfo) + $user = $user->getId(); $stmt = $this->cache->get('REPLACE INTO msz_comments_votes (comment_id, user_id, comment_vote) VALUES (?, ?, ?)'); $stmt->addParameter(1, $post); @@ -498,22 +498,22 @@ class Comments { $stmt->execute(); } - public function addPostPositiveVote(CommentsPostInfo|string $post, User|string $user): void { + public function addPostPositiveVote(CommentsPostInfo|string $post, UserInfo|string $user): void { $this->addPostVote($post, $user, 1); } - public function addPostNegativeVote(CommentsPostInfo|string $post, User|string $user): void { + public function addPostNegativeVote(CommentsPostInfo|string $post, UserInfo|string $user): void { $this->addPostVote($post, $user, -1); } public function removePostVote( CommentsPostInfo|string $post, - User|string $user + UserInfo|string $user ): void { if($post instanceof CommentsPostInfo) $post = $post->getId(); - if($user instanceof User) - $user = (string)$user->getId(); + if($user instanceof UserInfo) + $user = $user->getId(); $stmt = $this->cache->get('DELETE FROM msz_comments_votes WHERE comment_id = ? AND user_id = ?'); $stmt->addParameter(1, $post); diff --git a/src/Comments/CommentsCategoryInfo.php b/src/Comments/CommentsCategoryInfo.php index 71631d9..103bb1a 100644 --- a/src/Comments/CommentsCategoryInfo.php +++ b/src/Comments/CommentsCategoryInfo.php @@ -3,7 +3,7 @@ namespace Misuzu\Comments; use Index\DateTime; use Index\Data\IDbResult; -use Misuzu\Users\User; +use Misuzu\Users\UserInfo; class CommentsCategoryInfo { private string $id; @@ -38,11 +38,11 @@ class CommentsCategoryInfo { return $this->ownerId; } - public function isOwner(User|string $user): bool { + public function isOwner(UserInfo|string $user): bool { if($this->ownerId === null) return false; - if($user instanceof User) - $user = (string)$user->getId(); + if($user instanceof UserInfo) + $user = $user->getId(); return $user === $this->ownerId; } diff --git a/src/Comments/CommentsEx.php b/src/Comments/CommentsEx.php index dcffad5..e8716fd 100644 --- a/src/Comments/CommentsEx.php +++ b/src/Comments/CommentsEx.php @@ -3,24 +3,26 @@ namespace Misuzu\Comments; use stdClass; use RuntimeException; -use Misuzu\Users\User; +use Misuzu\MisuzuContext; +use Misuzu\Users\Users; class CommentsEx { - private Comments $comments; - private array $userInfos; - - public function __construct(Comments $comments, array $userInfos = []) { - $this->comments = $comments; - $this->userInfos = $userInfos; - } + public function __construct( + private MisuzuContext $context, + private Comments $comments, + private Users $users, + private array $userInfos = [], + private array $userColours = [] + ) {} public function getCommentsForLayout(CommentsCategoryInfo|string $category): object { $info = new stdClass; if(is_string($category)) $category = $this->comments->ensureCategory($category); - $hasUser = User::hasCurrent(); - $info->user = $hasUser ? User::getCurrent() : null; + $hasUser = $this->context->isLoggedIn(); + $info->user = $hasUser ? $this->context->getActiveUser() : null; + $info->colour = $hasUser ? $this->users->getUserColour($info->user) : null; $info->perms = $hasUser ? perms_for_comments($info->user->getId()) : []; $info->category = $category; $info->posts = []; @@ -37,20 +39,28 @@ class CommentsEx { $userId = $postInfo->getUserId(); if(array_key_exists($userId, $this->userInfos)) { $userInfo = $this->userInfos[$userId]; + $userColour = $this->userColours[$userId]; } else { try { - $userInfo = User::byId($userId); + $userInfo = $this->users->getUser($userId, 'id'); + $userColour = $this->users->getUserColour($userInfo); } catch(RuntimeException $ex) { $userInfo = null; + $userColour = null; } $this->userInfos[$userId] = $userInfo; + $this->userColours[$userId] = $userColour; } - } else $userInfo = null; + } else { + $userInfo = null; + $userColour = null; + } $info = new stdClass; $info->post = $postInfo; $info->user = $userInfo; + $info->colour = $userColour; $info->vote = $this->comments->getPostVote($postInfo, $userInfo); $info->replies = []; diff --git a/src/Comments/CommentsParser.php b/src/Comments/CommentsParser.php deleted file mode 100644 index 9017dca..0000000 --- a/src/Comments/CommentsParser.php +++ /dev/null @@ -1,63 +0,0 @@ -getId()); - } catch(RuntimeException $ex) { - return $matches[0]; - } - }, $text); - } - - public static function parseForDisplay(string $text): string { - $text = htmlentities($text); - - $text = preg_replace_callback( - '/(^|[\n ])([\w]*?)([\w]*?:\/\/[\w]+[^ \,\"\n\r\t<]*)/is', - function ($matches) { - $matches[0] = trim($matches[0]); - $url = parse_url($matches[0]); - if(empty($url['scheme']) || !in_array(mb_strtolower($url['scheme']), ['http', 'https'], true)) - return $matches[0]; - return sprintf(' %1$s', $matches[0]); - }, - $text - ); - - $text = preg_replace_callback(self::MARKUP_USERID, function ($matches) { - $getInfo = DB::prepare(' - SELECT - u.`user_id`, u.`username`, - COALESCE(u.`user_colour`, r.`role_colour`) as `user_colour` - FROM `msz_users` as u - LEFT JOIN `msz_roles` as r - ON u.`display_role` = r.`role_id` - WHERE `user_id` = :user_id - '); - $getInfo->bind('user_id', $matches[1]); - $info = $getInfo->fetch(); - - if(empty($info)) - return $matches[0]; - - return sprintf( - '@%s', - url('user-profile', ['user' => $info['user_id']]), - html_colour($info['user_colour']), - $info['username'] - ); - }, $text); - - return nl2br($text); - } -} diff --git a/src/Forum/forum.php b/src/Forum/forum.php index 6a8ef6c..4c7ed88 100644 --- a/src/Forum/forum.php +++ b/src/Forum/forum.php @@ -424,7 +424,8 @@ function forum_mark_read(?int $forumId, int $userId): void { function forum_posting_info(int $userId): array { $getPostingInfo = \Misuzu\DB::prepare(' SELECT - u.`user_country`, u.`user_created`, + u.`user_id`, u.`username`, u.`user_country`, u.`user_created`, + COALESCE(u.`user_colour`, r.`role_colour`) AS `colour`, ( SELECT COUNT(`post_id`) FROM `msz_forum_posts` @@ -440,6 +441,8 @@ function forum_posting_info(int $userId): array { LIMIT 1 ) AS `user_post_parse` FROM `msz_users` as u + LEFT JOIN `msz_roles` AS r + ON r.`role_id` = u.`display_role` WHERE `user_id` = :user_id '); $getPostingInfo->bind('user_id', $userId); @@ -527,7 +530,9 @@ function forum_count_synchronise(int $forumId = MSZ_FORUM_ROOT, bool $save = tru return compact('topics', 'posts'); } -function forum_get_user_most_active_category_info(int $userId): ?object { +function forum_get_user_most_active_category_info(string|int $userId): ?object { + if(is_string($userId)) + $userId = (int)$userId; if($userId < 1) return null; @@ -541,3 +546,41 @@ function forum_get_user_most_active_category_info(int $userId): ?object { return $getActiveForum->fetchObject(); } + +function forum_get_user_topic_count(\Misuzu\Users\UserInfo|string|int $userId): int { + if(is_int($userId)) + $userId = (string)$userId; + elseif($userId instanceof \Misuzu\Users\UserInfo) + $userId = $userId->getId(); + + global $db; + static $stmt = null; + if($stmt === null) + $stmt = $db->prepare('SELECT COUNT(*) FROM msz_forum_topics WHERE user_id = ? AND topic_deleted IS NULL'); + else + $stmt->reset(); + $stmt->addParameter(1, $userId); + $stmt->execute(); + + $result = $stmt->getResult(); + return $result->next() ? $result->getInteger(0) : 0; +} + +function forum_get_user_post_count(\Misuzu\Users\UserInfo|string|int $userId): int { + if(is_int($userId)) + $userId = (string)$userId; + elseif($userId instanceof \Misuzu\Users\UserInfo) + $userId = $userId->getId(); + + global $db; + static $stmt = null; + if($stmt === null) + $stmt = $db->prepare('SELECT COUNT(*) FROM msz_forum_posts WHERE user_id = ? AND post_deleted IS NULL'); + else + $stmt->reset(); + $stmt->addParameter(1, $userId); + $stmt->execute(); + + $result = $stmt->getResult(); + return $result->next() ? $result->getInteger(0) : 0; +} diff --git a/src/Http/Handlers/AssetsHandler.php b/src/Http/Handlers/AssetsHandler.php index 2b67f9d..9329dab 100644 --- a/src/Http/Handlers/AssetsHandler.php +++ b/src/Http/Handlers/AssetsHandler.php @@ -3,17 +3,19 @@ namespace Misuzu\Http\Handlers; use RuntimeException; use Misuzu\GitInfo; -use Misuzu\Users\User; +use Misuzu\Users\UserInfo; use Misuzu\Users\Assets\StaticUserImageAsset; -use Misuzu\Users\Assets\UserImageAssetInterface; use Misuzu\Users\Assets\UserAssetScalableInterface; +use Misuzu\Users\Assets\UserAvatarAsset; +use Misuzu\Users\Assets\UserBackgroundAsset; +use Misuzu\Users\Assets\UserImageAssetInterface; final class AssetsHandler extends Handler { - private function canViewAsset($request, User $assetUser): bool { + private function canViewAsset($request, UserInfo $assetUser): bool { return !$this->context->hasActiveBan($assetUser) || ( - User::hasCurrent() + $this->context->isLoggedIn() && parse_url($request->getHeaderFirstLine('Referer'), PHP_URL_PATH) === url('user-profile') - && perms_check_user(MSZ_PERMS_USER, User::getCurrent()->getId(), MSZ_PERM_USER_MANAGE_USERS) + && perms_check_user(MSZ_PERMS_USER, $this->context->getActiveUser()->getId(), MSZ_PERM_USER_MANAGE_USERS) ); } @@ -48,12 +50,14 @@ final class AssetsHandler extends Handler { $assetInfo = new StaticUserImageAsset(MSZ_PUBLIC . '/images/no-avatar.png', MSZ_PUBLIC); try { - $userInfo = User::byId($userId); + $userInfo = $this->context->getUsers()->getUser($userId, 'id'); if(!$this->canViewAsset($request, $userInfo)) { $assetInfo = new StaticUserImageAsset(MSZ_PUBLIC . '/images/banned-avatar.png', MSZ_PUBLIC); - } elseif($userInfo->hasAvatar()) { - $assetInfo = $userInfo->getAvatarInfo(); + } else { + $userAssetInfo = new UserAvatarAsset($userInfo); + if($userAssetInfo->isPresent()) + $assetInfo = $userAssetInfo; } } catch(RuntimeException $ex) {} @@ -68,15 +72,21 @@ final class AssetsHandler extends Handler { return 404; try { - $userInfo = User::byId($userId); + $userInfo = $this->context->getUsers()->getUser($userId, 'id'); } catch(RuntimeException $ex) {} - if(empty($userInfo) || !$userInfo->hasBackground() || !$this->canViewAsset($request, $userInfo)) { + if(!empty($userInfo)) { + $userAssetInfo = new UserBackgroundAsset($userInfo); + if($userAssetInfo->isPresent() && $this->canViewAsset($request, $userInfo)) + $assetInfo = $userAssetInfo; + } + + if(!isset($assetInfo)) { $response->setContent(''); return 404; } - $this->serveUserAsset($response, $request, $userInfo->getBackgroundInfo()); + $this->serveUserAsset($response, $request, $assetInfo); } public function serveLegacy($response, $request) { diff --git a/src/Http/Handlers/ChangelogHandler.php b/src/Http/Handlers/ChangelogHandler.php index 83a7633..c920464 100644 --- a/src/Http/Handlers/ChangelogHandler.php +++ b/src/Http/Handlers/ChangelogHandler.php @@ -10,16 +10,18 @@ use Misuzu\Feeds\Feed; use Misuzu\Feeds\FeedItem; use Misuzu\Feeds\AtomFeedSerializer; use Misuzu\Feeds\RssFeedSerializer; -use Misuzu\Users\User; class ChangelogHandler extends Handler { private array $userInfos = []; + private array $userColours = []; public function index($response, $request) { $filterDate = (string)$request->getParam('date'); $filterUser = (int)$request->getParam('user', FILTER_SANITIZE_NUMBER_INT); $filterTags = (string)$request->getParam('tags'); + $users = $this->context->getUsers(); + if(empty($filterDate)) $filterDate = null; else @@ -32,7 +34,7 @@ class ChangelogHandler extends Handler { if($filterUser > 0) try { - $filterUser = User::byId($filterUser); + $filterUser = $users->getUser((string)$filterUser, 'id'); } catch(RuntimeException $ex) { return 404; } @@ -64,19 +66,24 @@ class ChangelogHandler extends Handler { if(array_key_exists($userId, $this->userInfos)) { $userInfo = $this->userInfos[$userId]; + $userColour = $this->userColours[$userId]; } else { try { - $userInfo = User::byId($userId); + $userInfo = $users->getUser($userId, 'id'); + $userColour = $users->getUserColour($userInfo); } catch(RuntimeException $ex) { $userInfo = null; + $userColour = null; } $this->userInfos[$userId] = $userInfo; + $this->userColours[$userId] = $userColour; } $changes[] = [ 'change' => $changeInfo, 'user' => $userInfo, + 'user_colour' => $userColour, ]; } @@ -91,7 +98,7 @@ class ChangelogHandler extends Handler { } private function getCommentsInfo(string $categoryName): object { - $comments = new CommentsEx($this->context->getComments(), $this->userInfos); + $comments = new CommentsEx($this->context, $this->context->getComments(), $this->context->getUsers(), $this->userInfos, $this->userColours); return $comments->getCommentsForLayout($categoryName); } @@ -102,15 +109,20 @@ class ChangelogHandler extends Handler { return 404; } + $users = $this->context->getUsers(); + try { - $userInfo = User::byId($changeInfo->getUserId()); + $userInfo = $users->getUser($changeInfo->getUserId(), 'id'); + $userColour = $users->getUserColour($userInfo); } catch(RuntimeException $ex) { $userInfo = null; + $userColour = null; } $response->setContent(Template::renderRaw('changelog.change', [ 'change_info' => $changeInfo, 'change_user_info' => $userInfo, + 'change_user_colour' => $userColour, 'comments_info' => $this->getCommentsInfo($changeInfo->getCommentsCategoryName()), ])); } diff --git a/src/Http/Handlers/ForumHandler.php b/src/Http/Handlers/ForumHandler.php index 9ba7709..ebf38dc 100644 --- a/src/Http/Handlers/ForumHandler.php +++ b/src/Http/Handlers/ForumHandler.php @@ -3,11 +3,10 @@ namespace Misuzu\Http\Handlers; use Misuzu\CSRF; use Misuzu\Template; -use Misuzu\Users\User; final class ForumHandler extends Handler { public function markAsReadGET($response, $request) { - if(!User::hasCurrent()) + if(!$this->context->isLoggedIn()) return 403; $forumId = (int)$request->getParam('forum', FILTER_SANITIZE_NUMBER_INT); @@ -22,7 +21,7 @@ final class ForumHandler extends Handler { } public function markAsReadPOST($response, $request) { - if(!User::hasCurrent()) + if(!$this->context->isLoggedIn()) return 403; if(!$request->isFormContent()) @@ -33,7 +32,7 @@ final class ForumHandler extends Handler { return 400; $forumId = (int)$request->getContent()->getParam('forum', FILTER_SANITIZE_NUMBER_INT); - forum_mark_read($forumId, User::getCurrent()->getId()); + forum_mark_read($forumId, $this->context->getActiveUser()->getId()); $redirect = url($forumId ? 'forum-category' : 'forum-index', ['forum' => $forumId]); $response->redirect($redirect, false); diff --git a/src/Http/Handlers/HomeHandler.php b/src/Http/Handlers/HomeHandler.php index 1de3d56..7f3e3e1 100644 --- a/src/Http/Handlers/HomeHandler.php +++ b/src/Http/Handlers/HomeHandler.php @@ -2,11 +2,11 @@ namespace Misuzu\Http\Handlers; use RuntimeException; +use Index\DateTime; use Misuzu\DB; use Misuzu\Pagination; use Misuzu\Template; use Misuzu\Comments\CommentsCategory; -use Misuzu\Users\User; final class HomeHandler extends Handler { private const STATS = [ @@ -19,13 +19,14 @@ final class HomeHandler extends Handler { ]; public function index($response, $request): void { - if(User::hasCurrent()) + if($this->context->isLoggedIn()) $this->home($response, $request); else $this->landing($response, $request); } public function landing($response, $request): void { + $users = $this->context->getUsers(); $config = $this->context->getConfig(); $counters = $this->context->getCounters(); @@ -50,15 +51,14 @@ final class HomeHandler extends Handler { ); $stats = $counters->get(self::STATS); - $onlineUsers = DB::query( - 'SELECT u.`user_id`, u.`username`, COALESCE(u.`user_colour`, r.`role_colour`) AS `user_colour`' - . ' FROM `msz_users` AS u' - . ' LEFT JOIN `msz_roles` AS r' - . ' ON r.`role_id` = u.`display_role`' - . ' WHERE u.`user_active` >= DATE_SUB(NOW(), INTERVAL 5 MINUTE)' - . ' ORDER BY u.`user_active` DESC, RAND()' - . ' LIMIT 50' - )->fetchAll(); + $onlineUserInfos = $users->getUsers( + lastActiveInMinutes: 5, + deleted: false, + orderBy: 'random', + ); + + // can also chat here, whoa + $stats['users:online:recent'] = count($onlineUserInfos); // TODO: don't hardcode forum ids $featuredForums = $config->getArray('landing.forum_categories'); @@ -103,7 +103,7 @@ final class HomeHandler extends Handler { $response->setContent(Template::renderRaw('home.landing', [ 'statistics' => $stats, - 'online_users' => $onlineUsers, + 'online_users' => $onlineUserInfos, 'featured_news' => $featuredNews, 'linked_data' => $linkedData, 'forum_popular' => $popularTopics, @@ -113,10 +113,13 @@ final class HomeHandler extends Handler { public function home($response, $request): void { $news = $this->context->getNews(); + $users = $this->context->getUsers(); + $config = $this->context->getConfig(); $comments = $this->context->getComments(); $counters = $this->context->getCounters(); $featuredNews = []; $userInfos = []; + $userColours = []; $categoryInfos = []; $featuredNewsInfos = $news->getAllPosts( onlyFeatured: true, @@ -129,11 +132,14 @@ final class HomeHandler extends Handler { if(array_key_exists($userId, $userInfos)) { $userInfo = $userInfos[$userId]; + $userColour = $userColours[$userId]; } else { try { - $userInfo = User::byId($userId); + $userInfo = $users->getUser($userId, 'id'); + $userColour = $userColours[$userId] = $users->getUserColour($userInfo); } catch(RuntimeException $ex) { $userInfo = null; + $userColour = $userColours[$userId] = null; } $userInfos[$userId] = $userInfo; @@ -151,6 +157,7 @@ final class HomeHandler extends Handler { 'post' => $postInfo, 'category' => $categoryInfo, 'user' => $userInfo, + 'user_colour' => $userColour, 'comments_count' => $commentsCount, ]; } @@ -158,25 +165,41 @@ final class HomeHandler extends Handler { $stats = $counters->get(self::STATS); $changelog = $this->context->getChangelog()->getAllChanges(pagination: new Pagination(10)); - $birthdays = User::byBirthdate(); - $latestUser = !empty($birthdays) ? null : User::byLatest(); + $birthdays = []; + $birthdayInfos = $users->getUsers(deleted: false, birthdate: DateTime::now(), orderBy: 'random'); + foreach($birthdayInfos as $birthdayInfo) + $birthdays[] = [ + 'info' => $birthdayInfo, + 'colour' => $users->getUserColour($birthdayInfo), + ]; - $onlineUsers = DB::query( - 'SELECT u.`user_id`, u.`username`, COALESCE(u.`user_colour`, r.`role_colour`) AS `user_colour`' - . ' FROM `msz_users` AS u' - . ' LEFT JOIN `msz_roles` AS r' - . ' ON r.`role_id` = u.`display_role`' - . ' WHERE u.`user_active` >= DATE_SUB(NOW(), INTERVAL 5 MINUTE)' - . ' ORDER BY u.`user_active` DESC, RAND()' - )->fetchAll(); + $newestMember = []; + if(empty($birthdays)) { + $newestMemberId = $config->getString('users.newest'); + if(!empty($newestMemberId)) + try { + $newestMemberInfo = $users->getUser($newestMemberId, 'id'); + $newestMember['info'] = $newestMemberInfo; + $newestMember['colour'] = $users->getUserColour($newestMemberInfo); + } catch(RuntimeException $ex) { + $newestMember = []; + $config->removeValues('users.newest'); + } + } + + $onlineUserInfos = $users->getUsers( + lastActiveInMinutes: 5, + deleted: false, + orderBy: 'random', + ); // today we cheat - $stats['users:online:recent'] = count($onlineUsers); + $stats['users:online:recent'] = count($onlineUserInfos); $response->setContent(Template::renderRaw('home.home', [ 'statistics' => $stats, - 'latest_user' => $latestUser, - 'online_users' => $onlineUsers, + 'newest_member' => $newestMember, + 'online_users' => $onlineUserInfos, 'birthdays' => $birthdays, 'featured_changelog' => $changelog, 'featured_news' => $featuredNews, diff --git a/src/Http/Handlers/NewsHandler.php b/src/Http/Handlers/NewsHandler.php index 5f492ea..0008785 100644 --- a/src/Http/Handlers/NewsHandler.php +++ b/src/Http/Handlers/NewsHandler.php @@ -13,14 +13,15 @@ use Misuzu\Feeds\AtomFeedSerializer; use Misuzu\Feeds\RssFeedSerializer; use Misuzu\News\NewsCategoryInfo; use Misuzu\Parsers\Parser; -use Misuzu\Users\User; final class NewsHandler extends Handler { private function fetchPostInfo(array $postInfos, array $categoryInfos = []): array { $news = $this->context->getNews(); + $users = $this->context->getUsers(); $comments = $this->context->getComments(); $posts = []; $userInfos = []; + $userColours = []; foreach($postInfos as $postInfo) { $userId = $postInfo->getUserId(); @@ -28,14 +29,18 @@ final class NewsHandler extends Handler { if(array_key_exists($userId, $userInfos)) { $userInfo = $userInfos[$userId]; + $userColour = $userColours[$userId]; } else { try { - $userInfo = User::byId($userId); + $userInfo = $users->getUser($userId, 'id'); + $userColour = $users->getUserColour($userInfo); } catch(RuntimeException $ex) { $userInfo = null; + $userColour = null; } $userInfos[$userId] = $userInfo; + $userColours[$userId] = $userColour; } if(array_key_exists($categoryId, $categoryInfos)) @@ -50,6 +55,7 @@ final class NewsHandler extends Handler { 'post' => $postInfo, 'category' => $categoryInfo, 'user' => $userInfo, + 'user_colour' => $userColour, 'comments_count' => $commentsCount, ]; } @@ -111,6 +117,7 @@ final class NewsHandler extends Handler { public function viewPost($response, $request, string $postId) { $news = $this->context->getNews(); + $users = $this->context->getUsers(); $comments = $this->context->getComments(); try { @@ -137,17 +144,20 @@ final class NewsHandler extends Handler { } $userInfo = null; + $userColour = null; if($postInfo->hasUserId()) try { - $userInfo = User::byId($postInfo->getUserId()); + $userInfo = $users->getUser($postInfo->getUserId(), 'id'); + $userColour = $users->getUserColour($userInfo); } catch(RuntimeException $ex) {} - $comments = new CommentsEx($comments); + $comments = new CommentsEx($this->context, $comments, $users); $response->setContent(Template::renderRaw('news.post', [ 'post_info' => $postInfo, 'post_category_info' => $categoryInfo, 'post_user_info' => $userInfo, + 'post_user_colour' => $userColour, 'comments_info' => $comments->getCommentsForLayout($commentsCategory), ])); } @@ -170,7 +180,7 @@ final class NewsHandler extends Handler { $userName = 'Author'; if($userInfo !== null) { $userId = $userInfo->getId(); - $userName = $userInfo->getUsername(); + $userName = $userInfo->getName(); } $postUrl = url_prefix(false) . url('news-post', ['post' => $postInfo->getId()]); @@ -199,6 +209,7 @@ final class NewsHandler extends Handler { private function fetchPostInfoForFeed(array $postInfos): array { $news = $this->context->getNews(); + $users = $this->context->getUsers(); $posts = []; $userInfos = []; @@ -209,7 +220,7 @@ final class NewsHandler extends Handler { $userInfo = $userInfos[$userId]; } else { try { - $userInfo = User::byId($userId); + $userInfo = $users->getUser($userId, 'id'); } catch(RuntimeException $ex) { $userInfo = null; } diff --git a/src/Memoizer.php b/src/Memoizer.php deleted file mode 100644 index fa0f419..0000000 --- a/src/Memoizer.php +++ /dev/null @@ -1,30 +0,0 @@ -collection[$find])) - $this->collection[$find] = $create(); - return $this->collection[$find]; - } - - if(is_callable($find)) { - $item = XArray::first($this->collection, $find) ?? $create(); - if(method_exists($item, 'getId')) - $this->collection[$item->getId()] = $item; - else - $this->collection[] = $item; - return $item; - } - - throw new InvalidArgumentException('Wasn\'t able to figure out your $find argument.'); - } -} diff --git a/src/MisuzuContext.php b/src/MisuzuContext.php index 77ae569..ea62414 100644 --- a/src/MisuzuContext.php +++ b/src/MisuzuContext.php @@ -20,8 +20,8 @@ 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\UserInfo; use Misuzu\Users\Warnings; use Index\Data\IDbConnection; use Index\Data\Migration\IDbMigrationRepo; @@ -168,12 +168,61 @@ class MisuzuContext { return $this->profileFields; } + private ?AuthToken $authToken = null; + private ?UserInfo $activeUser = null; + private ?UserInfo $activeUserReal = null; + + public function setAuthInfo(AuthToken $authToken, ?UserInfo $userInfo, ?UserInfo $realUserInfo): void { + $this->authToken = $authToken; + $this->activeUser = $userInfo; + $this->activeUserReal = $realUserInfo; + } + + public function removeAuthInfo(): void { + $this->authToken = null; + $this->activeUser = null; + $this->activeUserReal = null; + } + + public function hasAuthToken(): bool { + return $this->authToken !== null; + } + + public function getAuthToken(): ?AuthToken { + return $this->authToken; + } + + public function isLoggedIn(): bool { + return $this->authToken !== null && $this->activeUser !== null; + } + + public function isImpersonating(): bool { + return $this->activeUser !== null && $this->activeUserReal !== null + && $this->activeUser->getId() !== $this->activeUserReal->getId(); + } + + public function hasActiveUser(): bool { + return $this->activeUser !== null; + } + + public function getActiveUser(): ?UserInfo { + return $this->activeUser; + } + + public function hasRealActiveUser(): bool { + return $this->activeUserReal !== null; + } + + public function getRealActiveUser(): ?UserInfo { + return $this->activeUserReal; + } + private array $activeBansCache = []; - public function tryGetActiveBan(User|string|null $userInfo = null): ?BanInfo { + public function tryGetActiveBan(UserInfo|string|null $userInfo = null): ?BanInfo { if($userInfo === null) { - if(User::hasCurrent()) - $userInfo = User::getCurrent(); + if($this->isLoggedIn()) + $userInfo = $this->getActiveUser(); else return null; } @@ -184,13 +233,13 @@ class MisuzuContext { return $this->activeBansCache[$userId] = $this->bans->tryGetActiveBan($userId); } - public function hasActiveBan(User|string|null $userInfo = null): bool { + public function hasActiveBan(UserInfo|string|null $userInfo = null): bool { return $this->tryGetActiveBan($userInfo) !== null; } - public function createAuditLog(string $action, array $params = [], User|string|null $userInfo = null): void { - if($userInfo === null && User::hasCurrent()) - $userInfo = User::getCurrent(); + public function createAuditLog(string $action, array $params = [], UserInfo|string|null $userInfo = null): void { + if($userInfo === null && $this->isLoggedIn()) + $userInfo = $this->getActiveUser(); $this->auditLog->createLog( $userInfo, @@ -201,6 +250,124 @@ class MisuzuContext { ); } + public function getHeaderMenu(?UserInfo $userInfo): array { + $hasUserInfo = $userInfo?->isDeleted() === false; + $menu = []; + + $home = [ + 'title' => 'Home', + 'url' => url('index'), + 'menu' => [], + ]; + + if($hasUserInfo) + $home['menu'][] = [ + 'title' => 'Members', + 'url' => url('user-list'), + ]; + + $home['menu'][] = [ + 'title' => 'Changelog', + 'url' => url('changelog-index'), + ]; + $home['menu'][] = [ + 'title' => 'Contact', + 'url' => url('info', ['title' => 'contact']), + ]; + $home['menu'][] = [ + 'title' => 'Rules', + 'url' => url('info', ['title' => 'rules']), + ]; + + $menu[] = $home; + + $menu[] = [ + 'title' => 'News', + 'url' => url('news-index'), + ]; + + $forum = [ + 'title' => 'Forum', + 'url' => url('forum-index'), + 'menu' => [], + ]; + + if($hasUserInfo && perms_check_user(MSZ_PERMS_GENERAL, $userInfo->getId(), MSZ_PERM_FORUM_VIEW_LEADERBOARD)) + $forum['menu'][] = [ + 'title' => 'Leaderboard', + 'url' => url('forum-leaderboard'), + ]; + + $menu[] = $forum; + + $chatPath = $this->config->getString('sockChat.chatPath.normal'); + if(!empty($chatPath)) + $menu[] = [ + 'title' => 'Chat', + 'url' => $chatPath, + ]; + + return $menu; + } + + public function getUserMenu(?UserInfo $userInfo, bool $inBroomCloset): array { + $menu = []; + + if($userInfo === null) { + $menu[] = [ + 'title' => 'Register', + 'url' => url('auth-register'), + 'icon' => 'fas fa-user-plus fa-fw', + ]; + $menu[] = [ + 'title' => 'Log in', + 'url' => url('auth-login'), + 'icon' => 'fas fa-sign-in-alt fa-fw', + ]; + } else { + $menu[] = [ + 'title' => 'Profile', + 'url' => url('user-profile', ['user' => $userInfo->getId()]), + 'icon' => 'fas fa-user fa-fw', + ]; + $menu[] = [ + 'title' => 'Settings', + 'url' => url('settings-index'), + 'icon' => 'fas fa-cog fa-fw', + ]; + $menu[] = [ + 'title' => 'Search', + 'url' => url('search-index'), + 'icon' => 'fas fa-search fa-fw', + ]; + + if(!$this->hasActiveBan($userInfo) && perms_check_user(MSZ_PERMS_GENERAL, $userInfo->getId(), MSZ_PERM_GENERAL_CAN_MANAGE)) { + // restore behaviour where clicking this button switches between + // site version and broom version + if($inBroomCloset) + $menu[] = [ + 'title' => 'Exit Broom Closet', + 'url' => url('index'), + 'icon' => 'fas fa-door-open fa-fw', + ]; + else + $menu[] = [ + 'title' => 'Enter Broom Closet', + 'url' => url('manage-index'), + 'icon' => 'fas fa-door-closed fa-fw', + ]; + } + + $menu[] = [ + 'title' => 'Log out', + 'url' => url('auth-logout'), + 'icon' => 'fas fa-sign-out-alt fa-fw', + ]; + } + + return $menu; + } + public function setUpHttp(bool $legacy = false): void { $this->router = new HttpFx; $this->router->use('/', function($response) { @@ -263,8 +430,8 @@ class MisuzuContext { $this->router->get('/forum/mark-as-read', $mszCompatHandler('Forum', 'markAsReadGET')); $this->router->post('/forum/mark-as-read', $mszCompatHandler('Forum', 'markAsReadPOST')); - new SharpChatRoutes($this->router, $this->config->scopeTo('sockChat'), $this->bans, $this->emotes, $this->sessions); - new SatoriRoutes($this->dbConn, $this->config->scopeTo('satori'), $this->router, $this->profileFields); + new SharpChatRoutes($this->router, $this->config->scopeTo('sockChat'), $this, $this->bans, $this->emotes, $this->users, $this->sessions); + new SatoriRoutes($this->dbConn, $this->config->scopeTo('satori'), $this->router, $this->users, $this->profileFields); } private function registerLegacyRedirects(): void { diff --git a/src/News/News.php b/src/News/News.php index 2042fa0..5cb6942 100644 --- a/src/News/News.php +++ b/src/News/News.php @@ -9,7 +9,7 @@ use Index\Data\IDbConnection; use Index\Data\IDbResult; use Misuzu\Pagination; use Misuzu\Comments\CommentsCategoryInfo; -use Misuzu\Users\User; +use Misuzu\Users\UserInfo; class News { private IDbConnection $dbConn; @@ -359,13 +359,13 @@ class News { string $title, string $body, bool $featured = false, - User|string|null $userInfo = null, + UserInfo|string|null $userInfo = null, DateTime|int|null $schedule = null ): NewsPostInfo { if($categoryInfo instanceof NewsCategoryInfo) $categoryInfo = $categoryInfo->getId(); - if($userInfo instanceof User) - $userInfo = (string)$userInfo->getId(); + if($userInfo instanceof UserInfo) + $userInfo = $userInfo->getId(); if($schedule instanceof DateTime) $schedule = $schedule->getUnixTimeSeconds(); @@ -424,15 +424,15 @@ class News { ?string $body = null, ?bool $featured = null, bool $updateUserInfo = false, - User|string|null $userInfo = null, + UserInfo|string|null $userInfo = null, DateTime|int|null $schedule = null ): void { if($postInfo instanceof NewsPostInfo) $postInfo = $postInfo->getId(); if($categoryInfo instanceof NewsCategoryInfo) $categoryInfo = $categoryInfo->getId(); - if($userInfo instanceof User) - $userInfo = (string)$userInfo->getId(); + if($userInfo instanceof UserInfo) + $userInfo = $userInfo->getId(); if($schedule instanceof DateTime) $schedule = $schedule->getUnixTimeSeconds(); diff --git a/src/Profile/ProfileFields.php b/src/Profile/ProfileFields.php index 1268035..326d90d 100644 --- a/src/Profile/ProfileFields.php +++ b/src/Profile/ProfileFields.php @@ -7,7 +7,7 @@ use Index\Data\DbStatementCache; use Index\Data\DbTools; use Index\Data\IDbConnection; use Index\Data\IDbResult; -use Misuzu\Users\User; +use Misuzu\Users\UserInfo; class ProfileFields { private DbStatementCache $cache; @@ -141,8 +141,8 @@ class ProfileFields { return new ProfileFieldFormatInfo($result); } - public function getFieldValues(User|string $userInfo): array { - if($userInfo instanceof User) + public function getFieldValues(UserInfo|string $userInfo): array { + if($userInfo instanceof UserInfo) $userInfo = $userInfo->getId(); // i don't really want to bother with the join for the ordering so i'll just do that somewhere in PHP for now @@ -162,11 +162,11 @@ class ProfileFields { public function getFieldValue( ProfileFieldInfo|string $fieldInfo, - User|string $userInfo + UserInfo|string $userInfo ): ProfileFieldValueInfo { if($fieldInfo instanceof ProfileFieldInfo) $fieldInfo = $fieldInfo->getId(); - if($userInfo instanceof User) + if($userInfo instanceof UserInfo) $userInfo = $userInfo->getId(); $stmt = $this->cache->get('SELECT field_id, user_id, format_id, field_value FROM msz_profile_fields_values WHERE field_id = ? AND user_id = ?'); @@ -183,7 +183,7 @@ class ProfileFields { } public function setFieldValues( - User|string $userInfo, + UserInfo|string $userInfo, ProfileFieldInfo|string|array $fieldInfos, string|array $values ): void { @@ -202,7 +202,7 @@ class ProfileFields { if($fieldsCount !== count($values)) throw new InvalidArgumentException('$fieldsInfos and $values have the same amount of values and be in the same order.'); - if($userInfo instanceof User) + if($userInfo instanceof UserInfo) $userInfo = $userInfo->getId(); $rows = []; @@ -241,12 +241,12 @@ class ProfileFields { } public function removeFieldValues( - User|string $userInfo, + UserInfo|string $userInfo, ProfileFieldInfo|string|array $fieldInfos ): void { if(empty($fieldInfos)) return; - if($userInfo instanceof User) + if($userInfo instanceof UserInfo) $userInfo = $userInfo->getId(); if(!is_array($fieldInfos)) diff --git a/src/Satori/SatoriRoutes.php b/src/Satori/SatoriRoutes.php index aa81a8e..eefaff8 100644 --- a/src/Satori/SatoriRoutes.php +++ b/src/Satori/SatoriRoutes.php @@ -6,22 +6,27 @@ use Index\Data\DbTools; use Index\Data\IDbConnection; use Index\Http\HttpFx; use Index\Routing\IRouter; +use Misuzu\Pagination; use Misuzu\Config\IConfig; use Misuzu\Profile\ProfileFields; +use Misuzu\Users\Users; final class SatoriRoutes { private IDbConnection $dbConn; private IConfig $config; + private Users $users; private ProfileFields $profileFields; public function __construct( IDbConnection $dbConn, IConfig $config, IRouter $router, + Users $users, ProfileFields $profileFields ) { $this->dbConn = $dbConn; $this->config = $config; + $this->users = $users; $this->profileFields = $profileFields; // Simplify default error pages @@ -138,23 +143,20 @@ final class SatoriRoutes { $backlogDays = $this->config->getInteger('users.backlog', 7); $startId = (string)$request->getParam('start', FILTER_SANITIZE_NUMBER_INT); - $stmt = $this->dbConn->prepare( - 'SELECT user_id, username FROM msz_users' - . ' WHERE user_id > ? AND user_created >= NOW() - INTERVAL ? DAY' - . ' ORDER BY user_id LIMIT ?' + $userInfos = $this->users->getUsers( + after: $startId, + newerThanDays: $backlogDays, + orderBy: 'id', + pagination: new Pagination($batchSize), + deleted: false ); - $stmt->addParameter(1, $startId); - $stmt->addParameter(2, $backlogDays); - $stmt->addParameter(3, $batchSize); - $stmt->execute(); $users = []; - $result = $stmt->getResult(); - while($result->next()) + foreach($userInfos as $userInfo) $users[] = [ - 'user_id' => $result->getInteger(0), - 'username' => $result->getString(1), + 'user_id' => (int)$userInfo->getId(), + 'username' => $userInfo->getName(), ]; return $users; diff --git a/src/SharpChat/SharpChatPerms.php b/src/SharpChat/SharpChatPerms.php index e7ae259..3ce6fd2 100644 --- a/src/SharpChat/SharpChatPerms.php +++ b/src/SharpChat/SharpChatPerms.php @@ -1,7 +1,7 @@ getId(); $perms = self::PERMS_DEFAULT; - if(perms_check_user(MSZ_PERMS_USER, $userInfo->getId(), MSZ_PERM_USER_MANAGE_USERS)) + if(perms_check_user(MSZ_PERMS_USER, $userInfo, MSZ_PERM_USER_MANAGE_USERS)) $perms |= self::PERMS_MANAGE_USERS; - if(perms_check_user(MSZ_PERMS_USER, $userInfo->getId(), MSZ_PERM_USER_MANAGE_WARNINGS)) + if(perms_check_user(MSZ_PERMS_USER, $userInfo, MSZ_PERM_USER_MANAGE_WARNINGS)) $perms |= self::P_KICK_USER; - if(perms_check_user(MSZ_PERMS_USER, $userInfo->getId(), MSZ_PERM_USER_MANAGE_BANS)) + if(perms_check_user(MSZ_PERMS_USER, $userInfo, MSZ_PERM_USER_MANAGE_BANS)) $perms |= self::P_BAN_USER; - if(perms_check_user(MSZ_PERMS_USER, $userInfo->getId(), MSZ_PERM_USER_CHANGE_BACKGROUND)) + if(perms_check_user(MSZ_PERMS_USER, $userInfo, MSZ_PERM_USER_CHANGE_BACKGROUND)) $perms |= self::PERMS_CHANGE_BACKG; - if(perms_check_user(MSZ_PERMS_FORUM, $userInfo->getId(), MSZ_PERM_FORUM_MANAGE_FORUMS)) + if(perms_check_user(MSZ_PERMS_FORUM, $userInfo, MSZ_PERM_FORUM_MANAGE_FORUMS)) $perms |= self::PERMS_MANAGE_FORUM; return $perms; diff --git a/src/SharpChat/SharpChatRoutes.php b/src/SharpChat/SharpChatRoutes.php index bf58eee..2831f86 100644 --- a/src/SharpChat/SharpChatRoutes.php +++ b/src/SharpChat/SharpChatRoutes.php @@ -6,31 +6,36 @@ use Index\Colour\Colour; use Index\Routing\IRouter; use Index\Http\HttpFx; use Misuzu\AuthToken; +use Misuzu\MisuzuContext; use Misuzu\Auth\Sessions; use Misuzu\Config\IConfig; use Misuzu\Emoticons\Emotes; use Misuzu\Users\Bans; - -// Replace -use Misuzu\Users\User; +use Misuzu\Users\Users; final class SharpChatRoutes { private IConfig $config; + private MisuzuContext $context; private Bans $bans; private Emotes $emotes; + private Users $users; private Sessions $sessions; private string $hashKey; public function __construct( IRouter $router, IConfig $config, + MisuzuContext $context, Bans $bans, Emotes $emotes, + Users $users, Sessions $sessions ) { $this->config = $config; + $this->context = $context; $this->bans = $bans; $this->emotes = $emotes; + $this->users = $users; $this->sessions = $sessions; $this->hashKey = $this->config->getString('hashKey', 'woomy'); @@ -93,15 +98,15 @@ final class SharpChatRoutes { } public function getLogin($response, $request): void { - $currentUser = User::getCurrent(); - $configKey = $request->hasParam('legacy') ? 'chatPath.legacy' : 'chatPath.normal'; - $chatPath = $this->config->getString($configKey, '/'); + if(!$this->context->isLoggedIn()) { + $response->redirect(url('auth-login')); + return; + } - $response->redirect( - $currentUser === null - ? url('auth-login') - : $chatPath - ); + $response->redirect($this->config->getString( + ($request->hasParam('legacy') ? 'chatPath.legacy' : 'chatPath.normal'), + '/' + )); } public function getToken($response, $request) { @@ -127,13 +132,13 @@ final class SharpChatRoutes { if($request->getMethod() === 'OPTIONS') return 204; - if(!AuthToken::hasCurrent()) + if(!$this->context->hasAuthToken()) return ['ok' => false, 'err' => 'token']; - $token = AuthToken::getCurrent(); + $tokenInfo = $this->context->getAuthToken(); try { - $sessionInfo = $this->sessions->getSession(sessionToken: $token->getSessionToken()); + $sessionInfo = $this->sessions->getSession(sessionToken: $tokenInfo->getSessionToken()); } catch(RuntimeException $ex) { return ['ok' => false, 'err' => 'session']; } @@ -141,15 +146,15 @@ final class SharpChatRoutes { if($sessionInfo->hasExpired()) return ['ok' => false, 'err' => 'expired']; - $userInfo = User::byId((int)$sessionInfo->getUserId()); - $userId = $token->hasImpersonatedUserId() && $userInfo->isSuper() - ? $token->getImpersonatedUserId() + $userInfo = $this->users->getUser($sessionInfo->getUserId(), 'id'); + $userId = $tokenInfo->hasImpersonatedUserId() && $userInfo->isSuperUser() + ? $tokenInfo->getImpersonatedUserId() : $userInfo->getId(); return [ 'ok' => true, 'usr' => (int)$userId, - 'tkn' => $token->pack(), + 'tkn' => $tokenInfo->pack(), ]; } @@ -179,7 +184,7 @@ final class SharpChatRoutes { return 403; foreach($bumpList as $userId => $ipAddr) - User::byId($userId)->bumpActivity($ipAddr); + $this->users->recordUserActivity($userId, remoteAddr: $ipAddr); } public function postVerify($response, $request) { @@ -222,14 +227,14 @@ final class SharpChatRoutes { return ['success' => false, 'reason' => 'expired']; } - $this->sessions->updateSession(sessionInfo: $sessionInfo, remoteAddr: $ipAddress); + $this->sessions->recordSessionActivity(sessionInfo: $sessionInfo, remoteAddr: $ipAddress); - $userInfo = User::byId((int)$sessionInfo->getUserId()); - if($authTokenInfo->hasImpersonatedUserId() && $userInfo->isSuper()) { + $userInfo = $this->users->getUser($sessionInfo->getUserId(), 'id'); + if($authTokenInfo->hasImpersonatedUserId() && $userInfo->isSuperUser()) { $userInfoReal = $userInfo; try { - $userInfo = User::byId($authTokenInfo->getImpersonatedUserId()); + $userInfo = $this->users->getUser($authTokenInfo->getImpersonatedUserId(), 'id'); } catch(RuntimeException $ex) { $userInfo = $userInfoReal; } @@ -241,15 +246,17 @@ final class SharpChatRoutes { if(empty($userInfo)) return ['success' => false, 'reason' => 'user']; - $userInfo->bumpActivity($ipAddress); + $this->users->recordUserActivity($userInfo, remoteAddr: $ipAddress); + $userColour = $this->users->getUserColour($userInfo); + $userRank = $this->users->getUserRank($userInfo); return [ 'success' => true, - 'user_id' => $userInfo->getId(), - 'username' => $userInfo->getUsername(), - 'colour_raw' => Colour::toMisuzu($userInfo->getColour()), - 'rank' => $rank = $userInfo->getRank(), - 'hierarchy' => $rank, + 'user_id' => (int)$userInfo->getId(), + 'username' => $userInfo->getName(), + 'colour_raw' => Colour::toMisuzu($userColour), + 'rank' => $userRank, + 'hierarchy' => $userRank, 'perms' => SharpChatPerms::convert($userInfo), ]; } @@ -274,14 +281,15 @@ final class SharpChatRoutes { if(array_key_exists($userId, $userInfos)) $userInfo = $userInfos[$userId]; else - $userInfos[$userId] = $userInfo = User::byId((int)$userId); + $userInfos[$userId] = $userInfo = $this->users->getUser($userId, 'id'); + $userColour = $this->users->getUserColour($userInfo); $isPerma = $banInfo->isPermanent(); $list[] = [ 'is_ban' => true, 'user_id' => $userId, - 'user_name' => $userInfo->getUsername(), - 'user_colour' => Colour::toMisuzu($userInfo->getColour()), + 'user_name' => $userInfo->getName(), + 'user_colour' => Colour::toMisuzu($userColour), 'ip_addr' => '::', 'is_perma' => $isPerma, 'expires' => date('c', $isPerma ? 0x7FFFFFFF : $banInfo->getExpiresTime()) @@ -307,7 +315,7 @@ final class SharpChatRoutes { if($userIdIsName) try { - $userInfo = User::byUsername($userId); + $userInfo = $this->users->getUser($userId, 'name'); $userId = (string)$userInfo->getId(); } catch(RuntimeException $ex) { $userId = ''; @@ -355,6 +363,7 @@ final class SharpChatRoutes { if(empty($reason)) $reason = 'Banned through chat.'; + // maybe also adds the last couple lines of the chat log to the private reason $comment = sprintf('User IP address: %s, Moderator IP address: %s', $userAddr, $modAddr); if($isPermanent) @@ -374,13 +383,13 @@ final class SharpChatRoutes { $modId = 69; try { - $modInfo = User::byId((int)$modId); + $modInfo = $this->users->getUser($modId, 'id'); } catch(RuntimeException $ex) { return 404; } try { - $userInfo = User::byId((int)$userId); + $userInfo = $this->users->getUser($userId, 'id'); } catch(RuntimeException $ex) { return 404; } diff --git a/src/TwigMisuzu.php b/src/TwigMisuzu.php index 87a8348..231b674 100644 --- a/src/TwigMisuzu.php +++ b/src/TwigMisuzu.php @@ -5,7 +5,6 @@ use Index\ByteFormat; use Index\DateTime; use Index\Environment; use Misuzu\MisuzuContext; -use Misuzu\Comments\CommentsParser; use Misuzu\Parsers\Parser; use Twig\Extension\AbstractExtension; use Twig\TwigFilter; @@ -24,7 +23,6 @@ final class TwigMisuzu extends AbstractExtension { new TwigFilter('html_colour', 'html_colour'), new TwigFilter('country_name', 'get_country_name'), new TwigFilter('parse_text', fn(string $text, int $parser): string => Parser::instance($parser)->parseText($text)), - new TwigFilter('parse_comment', fn(string $text): string => CommentsParser::parseForDisplay($text)), new TwigFilter('perms_check', 'perms_check'), new TwigFilter('time_format', [$this, 'timeFormat']), ]; diff --git a/src/Users/Assets/UserAvatarAsset.php b/src/Users/Assets/UserAvatarAsset.php index 6dbc4cd..961fa4b 100644 --- a/src/Users/Assets/UserAvatarAsset.php +++ b/src/Users/Assets/UserAvatarAsset.php @@ -2,7 +2,6 @@ namespace Misuzu\Users\Assets; use Misuzu\Imaging\Image; -use Misuzu\Users\User; class UserAvatarAsset extends UserImageAsset implements UserAssetScalableInterface { private const FORMAT = 'avatars/%s/%d.msz'; @@ -30,10 +29,10 @@ class UserAvatarAsset extends UserImageAsset implements UserAssetScalableInterfa } public function getUrl(): string { - return url('user-avatar', ['user' => $this->getUser()->getId()]); + return url('user-avatar', ['user' => $this->getUserId()]); } public function getScaledUrl(int $dims): string { - return url('user-avatar', ['user' => $this->getUser()->getId(), 'res' => $dims]); + return url('user-avatar', ['user' => $this->getUserId(), 'res' => $dims]); } public static function clampDimensions(int $dimensions): int { @@ -45,10 +44,10 @@ class UserAvatarAsset extends UserImageAsset implements UserAssetScalableInterfa } public function getFileName(): string { - return sprintf('avatar-%1$d.%2$s', $this->getUser()->getId(), $this->getFileExtension()); + return sprintf('avatar-%1$d.%2$s', $this->getUserId(), $this->getFileExtension()); } public function getScaledFileName(int $dims): string { - return sprintf('avatar-%1$d-%3$dx%3$d.%2$s', $this->getUser()->getId(), $this->getScaledFileExtension($dims), self::clampDimensions($dims)); + return sprintf('avatar-%1$d-%3$dx%3$d.%2$s', $this->getUserId(), $this->getScaledFileExtension($dims), self::clampDimensions($dims)); } public function getScaledMimeType(int $dims): string { @@ -64,11 +63,11 @@ class UserAvatarAsset extends UserImageAsset implements UserAssetScalableInterfa } public function getRelativePath(): string { - return sprintf(self::FORMAT, self::DIR_ORIG, $this->getUser()->getId()); + return sprintf(self::FORMAT, self::DIR_ORIG, $this->getUserId()); } public function getScaledRelativePath(int $dims): string { $dims = self::clampDimensions($dims); - return sprintf(self::FORMAT, sprintf(self::DIR_SIZE, $dims), $this->getUser()->getId()); + return sprintf(self::FORMAT, sprintf(self::DIR_SIZE, $dims), $this->getUserId()); } public function getScaledPath(int $dims): string { diff --git a/src/Users/Assets/UserBackgroundAsset.php b/src/Users/Assets/UserBackgroundAsset.php index 0165b7e..39c8562 100644 --- a/src/Users/Assets/UserBackgroundAsset.php +++ b/src/Users/Assets/UserBackgroundAsset.php @@ -2,7 +2,7 @@ namespace Misuzu\Users\Assets; use InvalidArgumentException; -use Misuzu\Users\User; +use Misuzu\Users\UserInfo; // attachment and attributes are to be stored in the same byte // left half is for attributes, right half is for attachments @@ -46,6 +46,17 @@ class UserBackgroundAsset extends UserImageAsset { ]; } + private int $settings; + + public function __construct(UserInfo $userInfo) { + parent::__construct($userInfo); + $this->settings = (int)$userInfo->getBackgroundSettings(); + } + + public function getSettings(): int { + return $this->settings; + } + public function getMaxWidth(): int { global $cfg; return $cfg->getInteger('background.max_width', self::MAX_WIDTH); @@ -60,25 +71,25 @@ class UserBackgroundAsset extends UserImageAsset { } public function getUrl(): string { - return url('user-background', ['user' => $this->getUser()->getId()]); + return url('user-background', ['user' => $this->getUserId()]); } public function getFileName(): string { - return sprintf('background-%1$d.%2$s', $this->getUser()->getId(), $this->getFileExtension()); + return sprintf('background-%1$d.%2$s', $this->getUserId(), $this->getFileExtension()); } public function getRelativePath(): string { - return sprintf(self::FORMAT, $this->getUser()->getId()); + return sprintf(self::FORMAT, $this->getUserId()); } public function getAttachment(): int { - return $this->getUser()->getBackgroundSettings() & 0x0F; + return $this->settings & 0x0F; } public function getAttachmentString(): string { return self::ATTACHMENT_STRINGS[$this->getAttachment()] ?? ''; } public function setAttachment(int $attach): self { - $this->getUser()->setBackgroundSettings($this->getAttributes() | ($attach & 0x0F)); + $this->settings = $this->getAttributes() | ($attach & 0x0F); return $this; } public function setAttachmentString(string $attach): self { @@ -89,32 +100,28 @@ class UserBackgroundAsset extends UserImageAsset { } public function getAttributes(): int { - return $this->getUser()->getBackgroundSettings() & 0xF0; + return $this->settings & 0xF0; } public function setAttributes(int $attrib): self { - $this->getUser()->setBackgroundSettings($this->getAttachment() | ($attrib & 0xF0)); + $this->settings = $this->getAttachment() | ($attrib & 0xF0); return $this; } public function isBlend(): bool { return ($this->getAttributes() & self::ATTRIB_BLEND) > 0; } public function setBlend(bool $blend): self { - $this->getUser()->setBackgroundSettings( - $blend - ? ($this->getUser()->getBackgroundSettings() | self::ATTRIB_BLEND) - : ($this->getUser()->getBackgroundSettings() & ~self::ATTRIB_BLEND) - ); + $this->settings = $blend + ? ($this->settings | self::ATTRIB_BLEND) + : ($this->settings & ~self::ATTRIB_BLEND); return $this; } public function isSlide(): bool { return ($this->getAttributes() & self::ATTRIB_SLIDE) > 0; } public function setSlide(bool $slide): self { - $this->getUser()->setBackgroundSettings( - $slide - ? ($this->getUser()->getBackgroundSettings() | self::ATTRIB_SLIDE) - : ($this->getUser()->getBackgroundSettings() & ~self::ATTRIB_SLIDE) - ); + $this->settings = $slide + ? ($this->settings | self::ATTRIB_SLIDE) + : ($this->settings & ~self::ATTRIB_SLIDE); return $this; } @@ -135,6 +142,6 @@ class UserBackgroundAsset extends UserImageAsset { public function delete(): void { parent::delete(); - $this->getUser()->setBackgroundSettings(0); + $this->settings = 0; } } diff --git a/src/Users/Assets/UserImageAsset.php b/src/Users/Assets/UserImageAsset.php index 54f213b..cb41a34 100644 --- a/src/Users/Assets/UserImageAsset.php +++ b/src/Users/Assets/UserImageAsset.php @@ -3,7 +3,7 @@ namespace Misuzu\Users\Assets; use InvalidArgumentException; use RuntimeException; -use Misuzu\Users\User; +use Misuzu\Users\UserInfo; abstract class UserImageAsset implements UserImageAssetInterface { public const PUBLIC_STORAGE = '/msz-storage'; @@ -18,14 +18,14 @@ abstract class UserImageAsset implements UserImageAssetInterface { self::TYPE_GIF => 'gif', ]; - private $user; + protected string $userId; - public function __construct(User $user) { - $this->user = $user; + public function __construct(UserInfo $userInfo) { + $this->userId = (string)$userInfo->getId(); } - public function getUser(): User { - return $this->user; + public function getUserId(): string { + return $this->userId; } public abstract function getMaxWidth(): int; diff --git a/src/Users/Bans.php b/src/Users/Bans.php index 6711b5a..2b9e9c7 100644 --- a/src/Users/Bans.php +++ b/src/Users/Bans.php @@ -8,7 +8,6 @@ use Index\Data\DbStatementCache; use Index\Data\DbTools; use Index\Data\IDbConnection; use Misuzu\Pagination; -use Misuzu\Users\User; class Bans { public const SEVERITY_MAX = 10; @@ -24,11 +23,11 @@ class Bans { } public function countBans( - User|string|null $userInfo = null, + UserInfo|string|null $userInfo = null, ?bool $activeOnly = null ): int { - if($userInfo instanceof User) - $userInfo = (string)$userInfo->getId(); + if($userInfo instanceof UserInfo) + $userInfo = $userInfo->getId(); $hasUserInfo = $userInfo !== null; $hasActiveOnly = $activeOnly !== null; @@ -61,13 +60,13 @@ class Bans { } public function getBans( - User|string|null $userInfo = null, + UserInfo|string|null $userInfo = null, ?bool $activeOnly = null, ?bool $activeFirst = null, ?Pagination $pagination = null ): array { - if($userInfo instanceof User) - $userInfo = (string)$userInfo->getId(); + if($userInfo instanceof UserInfo) + $userInfo = $userInfo->getId(); $hasUserInfo = $userInfo !== null; $hasActiveOnly = $activeOnly !== null; @@ -124,11 +123,11 @@ class Bans { } public function tryGetActiveBan( - User|string $userInfo, + UserInfo|string $userInfo, int $minimumSeverity = self::SEVERITY_MIN ): ?BanInfo { - if($userInfo instanceof User) - $userInfo = (string)$userInfo->getId(); + if($userInfo instanceof UserInfo) + $userInfo = $userInfo->getId(); // orders by ban_expires descending with NULLs (permanent) first $stmt = $this->cache->get('SELECT ban_id, user_id, mod_id, ban_severity, ban_reason_public, ban_reason_private, UNIX_TIMESTAMP(ban_created), UNIX_TIMESTAMP(ban_expires) FROM msz_users_bans WHERE user_id = ? AND ban_severity >= ? AND (ban_expires IS NULL OR ban_expires > NOW()) ORDER BY ban_expires IS NULL DESC, ban_expires DESC'); @@ -141,19 +140,19 @@ class Bans { } public function createBan( - User|string $userInfo, + UserInfo|string $userInfo, DateTime|int|null $expires, string $publicReason, string $privateReason, int $severity = self::SEVERITY_DEFAULT, - User|string|null $modInfo = null + UserInfo|string|null $modInfo = null ): BanInfo { if($severity < self::SEVERITY_MIN || $severity > self::SEVERITY_MAX) throw new InvalidArgumentException('$severity may not be less than -10 or more than 10.'); - if($userInfo instanceof User) - $userInfo = (string)$userInfo->getId(); - if($modInfo instanceof User) - $modInfo = (string)$modInfo->getId(); + if($userInfo instanceof UserInfo) + $userInfo = $userInfo->getId(); + if($modInfo instanceof UserInfo) + $modInfo = $modInfo->getId(); if($expires instanceof DateTime) $expires = $expires->getUnixTimeSeconds(); diff --git a/src/Users/ModNotes.php b/src/Users/ModNotes.php index 73dce5e..95c0ef7 100644 --- a/src/Users/ModNotes.php +++ b/src/Users/ModNotes.php @@ -7,7 +7,6 @@ use Index\Data\DbStatementCache; use Index\Data\DbTools; use Index\Data\IDbConnection; use Misuzu\Pagination; -use Misuzu\Users\User; class ModNotes { private IDbConnection $dbConn; @@ -19,13 +18,13 @@ class ModNotes { } public function countNotes( - User|string|null $userInfo = null, - User|string|null $authorInfo = null + UserInfo|string|null $userInfo = null, + UserInfo|string|null $authorInfo = null ): int { - if($userInfo instanceof User) - $userInfo = (string)$userInfo->getId(); - if($authorInfo instanceof User) - $authorInfo = (string)$authorInfo->getId(); + if($userInfo instanceof UserInfo) + $userInfo = $userInfo->getId(); + if($authorInfo instanceof UserInfo) + $authorInfo = $authorInfo->getId(); $hasUserInfo = $userInfo !== null; $hasAuthorInfo = $authorInfo !== null; @@ -57,14 +56,14 @@ class ModNotes { } public function getNotes( - User|string|null $userInfo = null, - User|string|null $authorInfo = null, + UserInfo|string|null $userInfo = null, + UserInfo|string|null $authorInfo = null, ?Pagination $pagination = null ): array { - if($userInfo instanceof User) - $userInfo = (string)$userInfo->getId(); - if($authorInfo instanceof User) - $authorInfo = (string)$authorInfo->getId(); + if($userInfo instanceof UserInfo) + $userInfo = $userInfo->getId(); + if($authorInfo instanceof UserInfo) + $authorInfo = $authorInfo->getId(); $hasUserInfo = $userInfo !== null; $hasAuthorInfo = $authorInfo !== null; @@ -116,15 +115,15 @@ class ModNotes { } public function createNote( - User|string $userInfo, + UserInfo|string $userInfo, string $title, string $body, - User|string|null $authorInfo = null + UserInfo|string|null $authorInfo = null ): ModNoteInfo { - if($userInfo instanceof User) - $userInfo = (string)$userInfo->getId(); - if($authorInfo instanceof User) - $authorInfo = (string)$authorInfo->getId(); + if($userInfo instanceof UserInfo) + $userInfo = $userInfo->getId(); + if($authorInfo instanceof UserInfo) + $authorInfo = $authorInfo->getId(); $stmt = $this->cache->get('INSERT INTO msz_users_modnotes (user_id, author_id, note_title, note_body) VALUES (?, ?, ?, ?)'); $stmt->addParameter(1, $userInfo); diff --git a/src/Users/RoleInfo.php b/src/Users/RoleInfo.php index b7cffef..ba1bd00 100644 --- a/src/Users/RoleInfo.php +++ b/src/Users/RoleInfo.php @@ -73,6 +73,10 @@ class RoleInfo implements Stringable { return $this->colour !== null && ($this->colour & 0x40000000) === 0; } + public function getColourRaw(): ?int { + return $this->colour; + } + public function getColour(): Colour { return $this->colour === null ? Colour::none() : Colour::fromMisuzu($this->colour); } diff --git a/src/Users/Roles.php b/src/Users/Roles.php index 84fc093..9938c5f 100644 --- a/src/Users/Roles.php +++ b/src/Users/Roles.php @@ -21,11 +21,11 @@ class Roles { } public function countRoles( - User|string|null $userInfo = null, + UserInfo|string|null $userInfo = null, ?bool $hidden = null ): int { - if($userInfo instanceof User) - $userInfo = (string)$userInfo->getId(); + if($userInfo instanceof UserInfo) + $userInfo = $userInfo->getId(); $hasUserInfo = $userInfo !== null; $hasHidden = $hidden !== null; @@ -55,12 +55,12 @@ class Roles { } public function getRoles( - User|string|null $userInfo = null, + UserInfo|string|null $userInfo = null, ?bool $hidden = null, ?Pagination $pagination = null ): array { - if($userInfo instanceof User) - $userInfo = (string)$userInfo->getId(); + if($userInfo instanceof UserInfo) + $userInfo = $userInfo->getId(); $hasUserInfo = $userInfo !== null; $hasHidden = $hidden !== null; diff --git a/src/Users/User.php b/src/Users/User.php index 1542b7a..8a4bca1 100644 --- a/src/Users/User.php +++ b/src/Users/User.php @@ -1,29 +1,10 @@ user_id < 1 ? -1 : $this->user_id; - } - - public function getUsername(): string { - return $this->username; - } - - public function getEmailAddress(): string { - return $this->email; - } - - public function getRegisterRemoteAddress(): string { - return $this->register_ip ?? '::1'; - } - public function getLastRemoteAddress(): string { - return $this->last_ip ?? '::1'; - } - - public function isSuper(): bool { - return boolval($this->user_super); - } - - public function hasCountry(): bool { - return $this->user_country !== 'XX'; - } - public function getCountry(): string { - return $this->user_country ?? 'XX'; - } - public function setCountry(string $country): self { - $this->user_country = strtoupper(substr($country, 0, 2)); - return $this; - } - public function getCountryName(): string { - return get_country_name($this->getCountry()); - } - - private $userColour = null; - private $realColour = null; - - 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()) { - $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; - } - public function setColour(?Colour $colour): self { - return $this->setColourRaw($colour === null ? null : Colour::toMisuzu($colour)); - } - public function getUserColour(): Colour { // Only ever gets the user's actual colour - if($this->userColour === null) - $this->userColour = Colour::fromMisuzu($this->getColourRaw()); - return $this->userColour; - } - public function getColourRaw(): int { - return $this->user_colour ?? 0x40000000; - } - public function setColourRaw(?int $colour): self { - $this->user_colour = $colour; - $this->userColour = null; - $this->realColour = null; - return $this; - } - - public function getCreatedTime(): int { - return $this->user_created === null ? -1 : $this->user_created; - } - - public function hasBeenActive(): bool { - return $this->user_active !== null; - } - public function getActiveTime(): int { - return $this->user_active === null ? -1 : $this->user_active; - } - - private $userRank = null; - public function getRank(): int { - if($this->userRank === null) - $this->userRank = (int)DB::prepare( - 'SELECT MAX(`role_hierarchy`)' - . ' 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 getDisplayRoleId(): int { - return $this->display_role < 1 ? -1 : $this->display_role; - } - - public function createTOTPGenerator(): TOTPGenerator { - return new TOTPGenerator($this->user_totp_key); - } - public function hasTOTPKey(): bool { - return !empty($this->user_totp_key); - } - public function getTOTPKey(): string { - return $this->user_totp_key ?? ''; - } - public function setTOTPKey(string $key): self { - $this->user_totp_key = $key; - return $this; - } - public function removeTOTPKey(): self { - $this->user_totp_key = null; - return $this; - } - - public function hasProfileAbout(): bool { - return !empty($this->user_about_content); - } - public function getProfileAboutText(): string { - return $this->user_about_content ?? ''; - } - public function setProfileAboutText(string $text): self { - $this->user_about_content = empty($text) ? null : $text; - return $this; - } - public function getProfileAboutParser(): int { - return $this->hasProfileAbout() ? $this->user_about_parser : Parser::BBCODE; - } - public function setProfileAboutParser(int $parser): self { - $this->user_about_parser = $parser; - return $this; - } - public function getProfileAboutParsed(): string { - if(!$this->hasProfileAbout()) - return ''; - return Parser::instance($this->getProfileAboutParser()) - ->parseText(htmlspecialchars($this->getProfileAboutText())); - } - - public function hasForumSignature(): bool { - return !empty($this->user_signature_content); - } - public function getForumSignatureText(): string { - return $this->user_signature_content ?? ''; - } - public function setForumSignatureText(string $text): self { - $this->user_signature_content = empty($text) ? null : $text; - return $this; - } - public function getForumSignatureParser(): int { - return $this->hasForumSignature() ? $this->user_signature_parser : Parser::BBCODE; - } - public function setForumSignatureParser(int $parser): self { - $this->user_signature_parser = $parser; - return $this; - } - public function getForumSignatureParsed(): string { - if(!$this->hasForumSignature()) - return ''; - return Parser::instance($this->getForumSignatureParser()) - ->parseText(htmlspecialchars($this->getForumSignatureText())); - } - - // Address these through getBackgroundInfo() - public function getBackgroundSettings(): int { - return $this->user_background_settings; - } - public function setBackgroundSettings(int $settings): self { - $this->user_background_settings = $settings; - return $this; - } - - public function hasTitle(): bool { - return !empty($this->user_title); - } - public function getTitle(): string { - return $this->user_title ?? ''; - } - public function setTitle(string $title): self { - $this->user_title = empty($title) ? null : $title; - return $this; - } - - public function hasBirthdate(): bool { - return $this->user_birthdate !== null; - } - public function getBirthdate(): DateTime { - return new DateTime($this->user_birthdate ?? '0000-01-01', new DateTimeZone('UTC')); - } - public function setBirthdate(int $year, int $month, int $day): self { - // lowest leap year mariadb supports lol - // should probably split the date field and year field in the db but i'm afraid of internal dependencies rn - if($year < 1004) - $year = 1004; - $this->user_birthdate = $month < 1 || $day < 1 ? null : sprintf('%04d-%02d-%02d', $year, $month, $day); - return $this; - } - public function hasAge(): bool { - return $this->hasBirthdate() && (int)$this->getBirthdate()->format('Y') > 1900; - } - public function getAge(): int { - if(!$this->hasAge()) - return -1; - return (int)$this->getBirthdate()->diff(new DateTime('now', new DateTimeZone('UTC')))->format('%y'); - } - - public function bumpActivity(string $lastRemoteAddress): void { - $this->user_active = time(); - $this->last_ip = $lastRemoteAddress; - - DB::prepare( - 'UPDATE `msz_users`' - . ' SET `user_active` = FROM_UNIXTIME(:active), `last_ip` = INET6_ATON(:address)' - . ' WHERE `user_id` = :user' - ) ->bind('user', $this->user_id) - ->bind('active', $this->user_active) - ->bind('address', $this->last_ip) - ->execute(); - } - - /************ - * PASSWORD * - ************/ - - public static function hashPassword(string $password): string { - return password_hash($password, self::PASSWORD_ALGO); - } - public function hasPassword(): bool { - return !empty($this->password); - } - public function checkPassword(string $password): bool { - return $this->hasPassword() && password_verify($password, $this->password); - } - public function passwordNeedsRehash(): bool { - return password_needs_rehash($this->password, self::PASSWORD_ALGO); - } - public function removePassword(): self { - $this->password = null; - return $this; - } - public function setPassword(string $password): self { - $this->password = self::hashPassword($password); - return $this; - } - - /************ - * DELETING * - ************/ - - public function getDeletedTime(): int { - return $this->user_deleted === null ? -1 : $this->user_deleted; - } - public function isDeleted(): bool { - return $this->getDeletedTime() >= 0; - } - - /********** - * ASSETS * - **********/ - - private $avatarAsset = null; - public function getAvatarInfo(): UserAvatarAsset { - if($this->avatarAsset === null) - $this->avatarAsset = new UserAvatarAsset($this); - return $this->avatarAsset; - } - public function hasAvatar(): bool { - return $this->getAvatarInfo()->isPresent(); - } - - private $backgroundAsset = null; - public function getBackgroundInfo(): UserBackgroundAsset { - if($this->backgroundAsset === null) - $this->backgroundAsset = new UserBackgroundAsset($this); - return $this->backgroundAsset; - } - public function hasBackground(): bool { - return $this->getBackgroundInfo()->isPresent(); - } - - /*************** - * FORUM STATS * - ***************/ - - private $forumTopicCount = -1; - private $forumPostCount = -1; - - public function getForumTopicCount(): int { - if($this->forumTopicCount < 0) - $this->forumTopicCount = (int)DB::prepare('SELECT COUNT(*) FROM `msz_forum_topics` WHERE `user_id` = :user AND `topic_deleted` IS NULL') - ->bind('user', $this->getId()) - ->fetchColumn(); - return $this->forumTopicCount; - } - public function getForumPostCount(): int { - if($this->forumPostCount < 0) - $this->forumPostCount = (int)DB::prepare('SELECT COUNT(*) FROM `msz_forum_posts` WHERE `user_id` = :user AND `post_deleted` IS NULL') - ->bind('user', $this->getId()) - ->fetchColumn(); - return $this->forumPostCount; - } - - /************** - * LOCAL USER * - **************/ - - public function setCurrent(): void { - self::$localUser = $this; - } - public static function unsetCurrent(): void { - self::$localUser = null; - } - public static function getCurrent(): ?self { - return self::$localUser; - } - public static function hasCurrent(): bool { - return self::$localUser !== null; - } - - /************** - * VALIDATION * - **************/ - public static function validateUsername(string $name): string { if($name !== trim($name)) return 'trim'; @@ -508,217 +127,4 @@ class User { return ''; } - - /********************* - * CREATION + SAVING * - *********************/ - - public function save(): void { - $save = DB::prepare( - 'UPDATE `msz_users`' - . ' SET `username` = :username, `email` = :email, `password` = :password' - . ', `user_super` = :is_super, `user_country` = :country, `user_colour` = :colour, `user_title` = :title' - . ', `user_totp_key` = :totp' - . ' WHERE `user_id` = :user' - ) ->bind('user', $this->user_id) - ->bind('username', $this->username) - ->bind('email', $this->email) - ->bind('password', $this->password) - ->bind('is_super', $this->user_super) - ->bind('country', $this->user_country) - ->bind('colour', $this->user_colour) - ->bind('totp', $this->user_totp_key) - ->bind('title', $this->user_title) - ->execute(); - } - - public function saveProfile(): void { - $save = DB::prepare( - 'UPDATE `msz_users`' - . ' SET `user_about_content` = :about_content, `user_about_parser` = :about_parser' - . ', `user_signature_content` = :signature_content, `user_signature_parser` = :signature_parser' - . ', `user_background_settings` = :background_settings, `user_birthdate` = :birthdate' - . ' WHERE `user_id` = :user' - ) ->bind('user', $this->user_id) - ->bind('about_content', $this->user_about_content) - ->bind('about_parser', $this->user_about_parser) - ->bind('signature_content', $this->user_signature_content) - ->bind('signature_parser', $this->user_signature_parser) - ->bind('background_settings', $this->user_background_settings) - ->bind('birthdate', $this->user_birthdate) - ->execute(); - } - - public static function create( - string $username, - string $password, - string $email, - string $ipAddress, - string $countryCode = 'XX' - ): self { - $createUser = DB::prepare( - 'INSERT INTO `msz_users` (`username`, `password`, `email`, `register_ip`, `last_ip`, `user_country`, `display_role`)' - . ' VALUES (:username, :password, LOWER(:email), INET6_ATON(:register_ip), INET6_ATON(:last_ip), :user_country, 1)' - ) ->bind('username', $username) - ->bind('email', $email) - ->bind('register_ip', $ipAddress) - ->bind('last_ip', $ipAddress) - ->bind('password', self::hashPassword($password)) - ->bind('user_country', $countryCode) - ->executeGetId(); - - if($createUser < 1) - throw new RuntimeException('User creation failed.'); - - return self::byId($createUser); - } - - /************ - * FETCHING * - ************/ - - private static function countQueryBase(): string { - return sprintf(self::QUERY_SELECT, 'COUNT(*)'); - } - public static function countAll(bool $showDeleted = false): int { - return (int)DB::prepare( - self::countQueryBase() - . ($showDeleted ? '' : ' WHERE `user_deleted` IS NULL') - )->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, self::SELECT); - } - public static function byId(string|int $userId): ?self { - // newer classes all treat ids as if they're strings - // php plays nice with it but may as well be sure since phpstan screams about it - if(is_string($userId)) - $userId = (int)$userId; - - return self::memoizer()->find($userId, function() use ($userId) { - $user = DB::prepare(self::byQueryBase() . ' WHERE `user_id` = :user_id') - ->bind('user_id', $userId) - ->fetchObject(self::class); - if(!$user) - throw new RuntimeException('Failed to fetch user by ID.'); - return $user; - }); - } - public static function byUsername(string $username): ?self { - if(empty($username)) - throw new InvalidArgumentException('$username may not be empty.'); - - $username = mb_strtolower($username); - - if(str_starts_with($username, 'flappyzor')) - return self::byId(14); - - return self::memoizer()->find(function($user) use ($username) { - return mb_strtolower($user->getUsername()) === $username; - }, function() use ($username) { - $user = DB::prepare(self::byQueryBase() . ' WHERE LOWER(`username`) = :username') - ->bind('username', $username) - ->fetchObject(self::class); - if(!$user) - throw new RuntimeException('Failed to find user by ID.'); - return $user; - }); - } - public static function byEMailAddress(string $address): ?self { - if(empty($address)) - throw new InvalidArgumentException('$address may not be empty.'); - - $address = mb_strtolower($address); - - return self::memoizer()->find(function($user) use ($address) { - return mb_strtolower($user->getEmailAddress()) === $address; - }, function() use ($address) { - $user = DB::prepare(self::byQueryBase() . ' WHERE LOWER(`email`) = :email') - ->bind('email', $address) - ->fetchObject(self::class); - if(!$user) - throw new RuntimeException('Failed to find user by e-mail address.'); - return $user; - }); - } - public static function byUsernameOrEMailAddress(string $usernameOrAddress): self { - if(empty($usernameOrAddress)) - throw new InvalidArgumentException('$usernameOrAddress may not be empty.'); - - $usernameOrAddressLower = mb_strtolower($usernameOrAddress); - - if(!str_contains($usernameOrAddressLower, '@') && str_starts_with($usernameOrAddressLower, 'flappyzor')) - return self::byId(14); - - return self::memoizer()->find(function($user) use ($usernameOrAddressLower) { - return mb_strtolower($user->getUsername()) === $usernameOrAddressLower - || mb_strtolower($user->getEmailAddress()) === $usernameOrAddressLower; - }, function() use ($usernameOrAddressLower) { - $user = DB::prepare(self::byQueryBase() . ' WHERE LOWER(`email`) = :email OR LOWER(`username`) = :username') - ->bind('email', $usernameOrAddressLower) - ->bind('username', $usernameOrAddressLower) - ->fetchObject(self::class); - if(!$user) - throw new RuntimeException('Failed to find user by name or e-mail address.'); - return $user; - }); - } - public static function byLatest(): ?self { - return DB::prepare(self::byQueryBase() . ' WHERE `user_deleted` IS NULL ORDER BY `user_id` DESC LIMIT 1') - ->fetchObject(self::class); - } - public static function findForProfile($userIdOrName): ?self { - if(empty($userIdOrName)) - throw new InvalidArgumentException('$userIdOrName may not be empty.'); - - $userIdOrNameLower = mb_strtolower($userIdOrName); - - if(str_starts_with($userIdOrNameLower, 'flappyzor')) - return self::byId(14); - - return self::memoizer()->find(function($user) use ($userIdOrNameLower) { - return $user->getId() == $userIdOrNameLower || mb_strtolower($user->getUsername()) === $userIdOrNameLower; - }, function() use ($userIdOrName) { - $user = DB::prepare(self::byQueryBase() . ' WHERE `user_id` = :user_id OR LOWER(`username`) = LOWER(:username)') - ->bind('user_id', (int)$userIdOrName) - ->bind('username', (string)$userIdOrName) - ->fetchObject(self::class); - if(!$user) - throw new RuntimeException('Failed to find user by ID or name.'); - return $user; - }); - } - public static function byBirthdate(?DateTime $date = null): array { - $date = $date === null ? new DateTime('now', new DateTimeZone('UTC')) : (clone $date)->setTimezone(new DateTimeZone('UTC')); - return DB::prepare(self::byQueryBase() . ' WHERE `user_deleted` IS NULL AND `user_birthdate` LIKE :date') - ->bind('date', $date->format('%-m-d')) - ->fetchObjects(self::class); - } - public static function all(bool $showDeleted = false, ?Pagination $pagination = null): array { - $query = self::byQueryBase(); - - if(!$showDeleted) - $query .= ' WHERE `user_deleted` IS NULL'; - - $query .= ' ORDER BY `user_id` ASC'; - - 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); - } } diff --git a/src/Users/UserInfo.php b/src/Users/UserInfo.php new file mode 100644 index 0000000..f67a43a --- /dev/null +++ b/src/Users/UserInfo.php @@ -0,0 +1,231 @@ +id = (string)$result->getInteger(0); + $this->name = $result->getString(1); + $this->passwordHash = $result->isNull(2) ? null : $result->getString(2); + $this->emailAddr = $result->getString(3); + $this->registerRemoteAddr = $result->getString(4); + $this->lastRemoteAddr = $result->isNull(5) ? null : $result->getString(5); + $this->super = $result->getInteger(6) !== 0; + $this->countryCode = $result->getString(7); + $this->colour = $result->isNull(8) ? null : $result->getInteger(8); + $this->created = $result->getInteger(9); + $this->lastActive = $result->isNull(10) ? null : $result->getInteger(10); + $this->deleted = $result->isNull(11) ? null : $result->getInteger(11); + $this->displayRoleId = $result->isNull(12) ? null : (string)$result->getInteger(12); + $this->totpKey = $result->isNull(13) ? null : $result->getString(13); + $this->aboutContent = $result->isNull(14) ? null : $result->getString(14); + $this->aboutParser = $result->getInteger(15); + $this->signatureContent = $result->isNull(16) ? null : $result->getString(16); + $this->signatureParser = $result->getInteger(17); + $this->birthdate = $result->isNull(18) ? null : $result->getString(18); + $this->backgroundSettings = $result->isNull(19) ? null : $result->getInteger(19); + $this->title = $result->getString(20); + } + + public function getId(): string { + return $this->id; + } + + public function getName(): string { + return $this->name; + } + + public function hasPasswordHash(): bool { + return $this->passwordHash !== null && $this->passwordHash !== ''; + } + + public function getPasswordHash(): ?string { + return $this->passwordHash; + } + + public function passwordNeedsRehash(): bool { + return $this->hasPasswordHash() && Users::passwordNeedsRehash($this->passwordHash); + } + + public function verifyPassword(string $password): bool { + return $this->hasPasswordHash() && password_verify($password, $this->passwordHash); + } + + public function getEMailAddress(): string { + return $this->emailAddr; + } + + public function getRegisterRemoteAddressRaw(): string { + return $this->registerRemoteAddr; + } + + public function getRegisterRemoteAddress(): IPAddress { + return IPAddress::parse($this->registerRemoteAddr); + } + + public function getLastRemoteAddressRaw(): string { + return $this->lastRemoteAddr; + } + + public function getLastRemoteAddress(): IPAddress { + return IPAddress::parse($this->lastRemoteAddr); + } + + public function isSuperUser(): bool { + return $this->super; + } + + public function hasCountryCode(): bool { + return $this->countryCode !== 'XX'; + } + + public function getCountryCode(): string { + return $this->countryCode; + } + + public function hasColour(): bool { + return $this->colour !== null && ($this->colour & 0x40000000) === 0; + } + + public function getColourRaw(): ?int { + return $this->colour; + } + + 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 hasLastActive(): bool { + return $this->lastActive !== null; + } + + public function getLastActiveTime(): ?int { + return $this->lastActive; + } + + public function getLastActiveAt(): ?DateTime { + return $this->lastActive === null ? null : DateTime::fromUnixTimeSeconds($this->lastActive); + } + + public function isDeleted(): bool { + return $this->deleted !== null; + } + + public function getDeletedTime(): ?int { + return $this->deleted; + } + + public function getDeletedAt(): ?DateTime { + return $this->deleted === null ? null : DateTime::fromUnixTimeSeconds($this->deleted); + } + + public function hasDisplayRoleId(): bool { + return $this->displayRoleId !== null; + } + + public function getDisplayRoleId(): ?string { + return $this->displayRoleId; + } + + public function hasTOTPKey(): bool { + return $this->totpKey !== null; + } + + public function getTOTPKey(): ?string { + return $this->totpKey; + } + + public function hasAboutContent(): bool { + return $this->aboutContent !== null && $this->aboutContent !== ''; + } + + public function getAboutContent(): ?string { + return $this->aboutContent; + } + + public function getAboutParser(): int { + return $this->aboutParser; + } + + public function hasSignatureContent(): bool { + return $this->signatureContent !== null && $this->signatureContent !== ''; + } + + public function getSignatureContent(): ?string { + return $this->signatureContent; + } + + public function getSignatureParser(): int { + return $this->signatureParser; + } + + public function hasBirthdate(): bool { + return $this->birthdate !== null; + } + + public function getBirthdateRaw(): ?string { + return $this->birthdate; + } + + public function getBirthdate(): ?DateTime { + return $this->birthdate === null ? null : DateTime::createFromFormat('Y-m-d', $this->birthdate, TimeZoneInfo::utc()); + } + + public function getAge(): int { + $birthdate = $this->getBirthdate(); + if($birthdate === null || $birthdate->getYear() < 1900) + return -1; + return (int)$birthdate->diff(DateTime::now())->format('%y'); + } + + public function hasBackgroundSettings(): bool { + return $this->backgroundSettings !== null; + } + + public function getBackgroundSettings(): ?int { + return $this->backgroundSettings; + } + + public function hasTitle(): bool { + return $this->title !== null && $this->title !== ''; + } + + public function getTitle(): ?string { + return $this->title; + } +} diff --git a/src/Users/Users.php b/src/Users/Users.php index f84ea73..9b4850a 100644 --- a/src/Users/Users.php +++ b/src/Users/Users.php @@ -2,40 +2,406 @@ namespace Misuzu\Users; use InvalidArgumentException; +use RuntimeException; +use Index\DateTime; +use Index\Colour\Colour; use Index\Data\DbStatementCache; use Index\Data\DbTools; use Index\Data\IDbConnection; +use Index\Net\IPAddress; +use Misuzu\Pagination; class Users { - //private IDbConnection $dbConn; + private IDbConnection $dbConn; private DbStatementCache $cache; public function __construct(IDbConnection $dbConn) { - //$this->dbConn = $dbConn; + $this->dbConn = $dbConn; $this->cache = new DbStatementCache($dbConn); } - public function updateUser( - User|string $userInfo, + private const PASSWORD_ALGO = PASSWORD_ARGON2ID; + private const PASSWORD_OPTS = []; + + public static function passwordHash(string $password): string { + return password_hash($password, self::PASSWORD_ALGO, self::PASSWORD_OPTS); + } + + public static function passwordNeedsRehash(string $passwordHash): bool { + return password_needs_rehash($passwordHash, self::PASSWORD_ALGO, self::PASSWORD_OPTS); + } + + public function countUsers( + RoleInfo|string|null $roleInfo = null, + UserInfo|string|null $after = null, + ?int $lastActiveInMinutes = null, + ?int $newerThanDays = null, + ?DateTime $birthdate = null, + ?bool $deleted = null + ): int { + if($roleInfo instanceof RoleInfo) + $roleInfo = $roleInfo->getId(); + if($after instanceof UserInfo) + $after = $after->getId(); + + $hasRoleInfo = $roleInfo !== null; + $hasAfter = $after !== null; + $hasLastActiveInMinutes = $lastActiveInMinutes !== null; + $hasNewerThanDays = $newerThanDays !== null; + $hasBirthdate = $birthdate !== null; + $hasDeleted = $deleted !== null; + + $args = 0; + $query = 'SELECT COUNT(*) FROM msz_users'; + if($hasRoleInfo) { + ++$args; + $query .= ' WHERE user_id IN (SELECT user_id FROM msz_users_roles WHERE role_id = ?)'; + } + if($hasAfter) + $query .= sprintf(' %s user_id > ?', ++$args > 1 ? 'AND' : 'WHERE'); + if($hasLastActiveInMinutes) + $query .= sprintf(' %s user_active > NOW() - INTERVAL ? MINUTE', ++$args > 1 ? 'AND' : 'WHERE'); + if($hasNewerThanDays) + $query .= sprintf(' %s user_created > NOW() - INTERVAL ? DAY', ++$args > 1 ? 'AND' : 'WHERE'); + if($hasDeleted) + $query .= sprintf(' %s user_deleted %s NULL', ++$args > 1 ? 'AND' : 'WHERE', $deleted ? 'IS NOT' : 'IS'); + + $args = 0; + $stmt = $this->cache->get($query); + if($hasRoleInfo) + $stmt->addParameter(++$args, $roleInfo); + if($hasAfter) + $stmt->addParameter(++$args, $after); + if($hasLastActiveInMinutes) + $stmt->addParameter(++$args, $lastActiveInMinutes); + if($hasNewerThanDays) + $stmt->addParameter(++$args, $newerThanDays); + $stmt->execute(); + + $result = $stmt->getResult(); + return $result->next() ? $result->getInteger(0) : 0; + } + + private const GET_USERS_SORT = [ + 'id' => ['user_id', false], + 'name' => ['username', false], + 'country' => ['user_country', false], + 'created' => ['user_created', true], + 'active' => ['user_active', true], + 'random' => ['RAND()', null], + ]; + + public function getUsers( + RoleInfo|string|null $roleInfo = null, + UserInfo|string|null $after = null, + ?int $lastActiveInMinutes = null, + ?int $newerThanDays = null, + ?DateTime $birthdate = null, + ?bool $deleted = null, + ?string $orderBy = null, + ?bool $reverseOrder = null, + ?Pagination $pagination = null + ): array { + if($roleInfo instanceof RoleInfo) + $roleInfo = $roleInfo->getId(); + if($after instanceof UserInfo) + $after = $after->getId(); + + $hasRoleInfo = $roleInfo !== null; + $hasAfter = $after !== null; + $hasLastActiveInMinutes = $lastActiveInMinutes !== null; + $hasNewerThanDays = $newerThanDays !== null; + $hasBirthdate = $birthdate !== null; + $hasDeleted = $deleted !== null; + $hasOrderBy = $orderBy !== null; + $hasReverseOrder = $reverseOrder !== null; + $hasPagination = $pagination !== null; + + if($hasOrderBy) { + if(!array_key_exists($orderBy, self::GET_USERS_SORT)) + throw new InvalidArgumentException('Invalid sort specified.'); + $orderBy = self::GET_USERS_SORT[$orderBy]; + + if($hasReverseOrder && $reverseOrder && $orderBy[1] !== null) + $orderBy[1] = !$orderBy[1]; + } + + $args = 0; + $query = 'SELECT user_id, username, password, email, INET6_NTOA(register_ip), INET6_NTOA(last_ip), user_super, user_country, user_colour, UNIX_TIMESTAMP(user_created), UNIX_TIMESTAMP(user_active), UNIX_TIMESTAMP(user_deleted), display_role, user_totp_key, user_about_content, user_about_parser, user_signature_content, user_signature_parser, user_birthdate, user_background_settings, user_title FROM msz_users'; + if($hasRoleInfo) { + ++$args; + $query .= ' WHERE user_id IN (SELECT user_id FROM msz_users_roles WHERE role_id = ?)'; + } + if($hasAfter) + $query .= sprintf(' %s user_id > ?', ++$args > 1 ? 'AND' : 'WHERE'); + if($hasLastActiveInMinutes) + $query .= sprintf(' %s user_active > NOW() - INTERVAL ? MINUTE', ++$args > 1 ? 'AND' : 'WHERE'); + if($hasNewerThanDays) + $query .= sprintf(' %s user_created > NOW() - INTERVAL ? DAY', ++$args > 1 ? 'AND' : 'WHERE'); + if($hasDeleted) + $query .= sprintf(' %s user_deleted %s NULL', ++$args > 1 ? 'AND' : 'WHERE', $deleted ? 'IS NOT' : 'IS'); + if($hasBirthdate) + $query .= sprintf(' %s user_birthdate LIKE ?', ++$args > 1 ? 'AND' : 'WHERE'); + if($hasOrderBy) { + $query .= sprintf(' ORDER BY %s', $orderBy[0]); + if($orderBy !== null) + $query .= ' ' . ($orderBy[1] ? 'DESC' : 'ASC'); + } + if($hasPagination) + $query .= ' LIMIT ? OFFSET ?'; + + $args = 0; + $stmt = $this->cache->get($query); + if($hasRoleInfo) + $stmt->addParameter(++$args, $roleInfo); + if($hasAfter) + $stmt->addParameter(++$args, $after); + if($hasLastActiveInMinutes) + $stmt->addParameter(++$args, $lastActiveInMinutes); + if($hasNewerThanDays) + $stmt->addParameter(++$args, $newerThanDays); + if($hasBirthdate) + $stmt->addParameter(++$args, $birthdate->format('%-m-d')); + if($hasPagination) { + $stmt->addParameter(++$args, $pagination->getRange()); + $stmt->addParameter(++$args, $pagination->getOffset()); + } + $stmt->execute(); + + $users = []; + $result = $stmt->getResult(); + + while($result->next()) + $users[] = new UserInfo($result); + + return $users; + } + + public const GET_USER_ID = 0x01; + public const GET_USER_NAME = 0x02; + public const GET_USER_MAIL = 0x04; + + private const GET_USER_SELECT_ALIASES = [ + 'id' => self::GET_USER_ID, + 'name' => self::GET_USER_NAME, + 'email' => self::GET_USER_MAIL, + 'profile' => self::GET_USER_ID | self::GET_USER_NAME, + 'login' => self::GET_USER_NAME | self::GET_USER_MAIL, + 'recovery' => self::GET_USER_MAIL, + ]; + + public function getUser(string $value, int|string $select = self::GET_USER_ID): UserInfo { + if($value === '') + throw new InvalidArgumentException('$value may not be empty.'); + + if(is_string($select)) { + if(!array_key_exists($select, self::GET_USER_SELECT_ALIASES)) + throw new InvalidArgumentException('Invalid $select alias.'); + $select = self::GET_USER_SELECT_ALIASES[$select]; + } elseif($select === 0) + throw new InvalidArgumentException('$select may not be zero.'); + + $selectId = ($select & self::GET_USER_ID) > 0; + $selectName = ($select & self::GET_USER_NAME) > 0; + $selectMail = ($select & self::GET_USER_MAIL) > 0; + + if(!$selectId && !$selectName && !$selectMail) + throw new InvalidArgumentException('$select flagset is invalid.'); + + $args = 0; + $query = 'SELECT user_id, username, password, email, INET6_NTOA(register_ip), INET6_NTOA(last_ip), user_super, user_country, user_colour, UNIX_TIMESTAMP(user_created), UNIX_TIMESTAMP(user_active), UNIX_TIMESTAMP(user_deleted), display_role, user_totp_key, user_about_content, user_about_parser, user_signature_content, user_signature_parser, user_birthdate, user_background_settings, user_title FROM msz_users'; + if($selectId) { + ++$args; + $query .= ' WHERE user_id = ?'; + } + if($selectName) // change the collation for both name and email to a case insensitive one + $query .= sprintf(' %s LOWER(username) = LOWER(?)', ++$args > 1 ? 'OR' : 'WHERE'); + if($selectMail) + $query .= sprintf(' %s LOWER(email) = LOWER(?)', ++$args > 1 ? 'OR' : 'WHERE'); + + $args = 0; + $stmt = $this->cache->get($query); + if($selectId) + $stmt->addParameter(++$args, $value); + if($selectName) + $stmt->addParameter(++$args, $value); + if($selectMail) + $stmt->addParameter(++$args, $value); + $stmt->execute(); + + $result = $stmt->getResult(); + if(!$result->next()) + throw new RuntimeException('User not found.'); + + return new UserInfo($result); + } + + public function createUser( + string $name, + string $password, + string $email, + IPAddress|string $remoteAddr, + string $countryCode, RoleInfo|string|null $displayRoleInfo = null + ): UserInfo { + if($remoteAddr instanceof IPAddress) + $remoteAddr = (string)$remoteAddr; + if($displayRoleInfo instanceof RoleInfo) + $displayRoleInfo = $displayRoleInfo->getId(); + elseif($displayRoleInfo === null) + $displayRoleInfo = Roles::DEFAULT_ROLE; + + $password = self::passwordHash($password); + + // todo: validation + + $stmt = $this->cache->get('INSERT INTO msz_users (username, password, email, register_ip, last_ip, user_country, display_role) VALUES (?, ?, ?, INET6_ATON(?), INET6_ATON(?), ?, ?)'); + $stmt->addParameter(1, $name); + $stmt->addParameter(2, $password); + $stmt->addParameter(3, $email); + $stmt->addParameter(4, $remoteAddr); + $stmt->addParameter(5, $remoteAddr); + $stmt->addParameter(6, $countryCode); + $stmt->addParameter(7, $displayRoleInfo); + $stmt->execute(); + + return $this->getUser((string)$this->dbConn->getLastInsertId(), self::GET_USER_ID); + } + + public function updateUser( + UserInfo|string $userInfo, + ?string $name = null, + ?string $emailAddr = null, + ?string $password = null, + ?string $countryCode = null, + ?Colour $colour = null, + RoleInfo|string|null $displayRoleInfo = null, + ?string $totpKey = null, + ?string $aboutContent = null, + ?int $aboutParser = null, + ?string $signatureContent = null, + ?int $signatureParser = null, + ?int $birthYear = null, + ?int $birthMonth = null, + ?int $birthDay = null, + ?int $backgroundSettings = null, + ?string $title = null ): void { - if($userInfo instanceof User) - $userInfo = (string)$userInfo->getId(); + if($userInfo instanceof UserInfo) + $userInfo = $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); + // do sanity checks on values at some point lol + $fields = []; + $values = []; + + if($name !== null) { + $fields[] = 'username = ?'; + $values[] = $name; + } + + if($emailAddr !== null) { + $fields[] = 'email = ?'; + $values[] = $emailAddr; + } + + if($password !== null) { + $fields[] = 'password = ?'; + $values[] = $password === '' ? null : self::passwordHash($password); + } + + if($countryCode !== null) { + $fields[] = 'user_country = ?'; + $values[] = $countryCode; + } + + if($colour !== null) { + $fields[] = 'user_colour = ?'; + $values[] = $colour->shouldInherit() ? null : Colour::toMisuzu($colour); + } + + if($displayRoleInfo !== null) { + $fields[] = 'display_role = ?'; + $values[] = $displayRoleInfo; + } + + if($totpKey !== null) { + $fields[] = 'user_totp_key = ?'; + $values[] = $totpKey === '' ? null : $totpKey; + } + + if($aboutContent !== null) { + $fields[] = 'user_about_content = ?'; + $values[] = $aboutContent; + } + + if($aboutParser !== null) { + $fields[] = 'user_about_parser = ?'; + $values[] = $aboutParser; + } + + if($signatureContent !== null) { + $fields[] = 'user_signature_content = ?'; + $values[] = $signatureContent; + } + + if($signatureParser !== null) { + $fields[] = 'user_signature_parser = ?'; + $values[] = $signatureParser; + } + + if($birthMonth !== null && $birthDay !== null) { + // lowest leap year MariaDB accepts, used a 'no year' value + if($birthYear < 1004) + $birthYear = 1004; + + $fields[] = 'user_birthdate = ?'; + $values[] = $birthMonth < 1 || $birthDay < 1 ? null : sprintf('%04d-%02d-%02d', $birthYear, $birthMonth, $birthDay); + } + + if($backgroundSettings !== null) { + $fields[] = 'user_background_settings = ?'; + $values[] = $backgroundSettings; + } + + if($title !== null) { + $fields[] = 'user_title = ?'; + $values[] = $title; + } + + if(empty($fields)) + return; + + $args = 0; + $stmt = $this->cache->get(sprintf('UPDATE msz_users SET %s WHERE user_id = ?', implode(', ', $fields))); + foreach($values as $value) + $stmt->addParameter(++$args, $value); + $stmt->addParameter(++$args, $userInfo); + $stmt->execute(); + } + + public function recordUserActivity( + UserInfo|string $userInfo, + IPAddress|string $remoteAddr + ): void { + if($userInfo instanceof UserInfo) + $userInfo = $userInfo->getId(); + if($remoteAddr instanceof IPAddress) + $remoteAddr = (string)$remoteAddr; + + $stmt = $this->cache->get('UPDATE msz_users SET user_active = NOW(), last_ip = INET6_ATON(?) WHERE user_id = ?'); + $stmt->addParameter(1, $remoteAddr); $stmt->addParameter(2, $userInfo); $stmt->execute(); } public function hasRole( - User|string $userInfo, + UserInfo|string $userInfo, RoleInfo|string $roleInfo ): bool { - if($userInfo instanceof User) - $userInfo = (string)$userInfo->getId(); + if($userInfo instanceof UserInfo) + $userInfo = $userInfo->getId(); if($roleInfo instanceof RoleInfo) $roleInfo = $roleInfo->getId(); @@ -43,11 +409,11 @@ class Users { } public function hasRoles( - User|string $userInfo, + UserInfo|string $userInfo, RoleInfo|string|array $roleInfos ): array { - if($userInfo instanceof User) - $userInfo = (string)$userInfo->getId(); + if($userInfo instanceof UserInfo) + $userInfo = $userInfo->getId(); if(!is_array($roleInfos)) $roleInfos = [$roleInfos]; elseif(empty($roleInfos)) @@ -81,11 +447,11 @@ class Users { } public function addRoles( - User|string $userInfo, + UserInfo|string $userInfo, RoleInfo|string|array $roleInfos ): void { - if($userInfo instanceof User) - $userInfo = (string)$userInfo->getId(); + if($userInfo instanceof UserInfo) + $userInfo = $userInfo->getId(); if(!is_array($roleInfos)) $roleInfos = [$roleInfos]; elseif(empty($roleInfos)) @@ -111,11 +477,11 @@ class Users { } public function removeRoles( - User|string $userInfo, + UserInfo|string $userInfo, RoleInfo|string|array $roleInfos ): void { - if($userInfo instanceof User) - $userInfo = (string)$userInfo->getId(); + if($userInfo instanceof UserInfo) + $userInfo = $userInfo->getId(); if(!is_array($roleInfos)) $roleInfos = [$roleInfos]; elseif(empty($roleInfos)) @@ -139,4 +505,39 @@ class Users { $stmt->execute(); } + + // the below two funcs should probably be moved to a higher level location so caching can be introduced + // without cluttering the data source interface <-- real words i just wrote + + public function getUserColour(UserInfo|string $userInfo): Colour { + if($userInfo instanceof UserInfo) { + if($userInfo->hasColour()) + return $userInfo->getColour(); + + $query = '?'; + $value = $userInfo->getDisplayRoleId(); + } else { + $query = '(SELECT display_role FROM msz_users WHERE user_id = ?)'; + $value = $userInfo; + } + + $stmt = $this->cache->get(sprintf('SELECT role_colour FROM msz_roles WHERE role_id = %s', $query)); + $stmt->addParameter(1, $value); + $stmt->execute(); + + $result = $stmt->getResult(); + return $result->next() ? Colour::fromMisuzu($result->getInteger(0)) : Colour::none(); + } + + public function getUserRank(UserInfo|string $userInfo): int { + if($userInfo instanceof UserInfo) + $userInfo = $userInfo->getId(); + + $stmt = $this->cache->get('SELECT MAX(role_hierarchy) FROM msz_roles WHERE role_id IN (SELECT role_id FROM msz_users_roles WHERE user_id = ?)'); + $stmt->addParameter(1, $userInfo); + $stmt->execute(); + + $result = $stmt->getResult(); + return $result->next() ? $result->getInteger(0) : 0; + } } diff --git a/src/Users/Warnings.php b/src/Users/Warnings.php index ca6ed46..e3fc3ee 100644 --- a/src/Users/Warnings.php +++ b/src/Users/Warnings.php @@ -7,7 +7,6 @@ use Index\Data\DbStatementCache; use Index\Data\DbTools; use Index\Data\IDbConnection; use Misuzu\Pagination; -use Misuzu\Users\User; // this system is currently kinda useless because it only silently shows up on profiles // planning a notification system anyway so that should probably hook into @@ -24,11 +23,11 @@ class Warnings { } public function countWarnings( - User|string|null $userInfo = null, + UserInfo|string|null $userInfo = null, ?int $backlog = null ): int { - if($userInfo instanceof User) - $userInfo = (string)$userInfo->getId(); + if($userInfo instanceof UserInfo) + $userInfo = $userInfo->getId(); $hasUserInfo = $userInfo !== null; $hasBacklog = $backlog !== null; @@ -63,7 +62,7 @@ class Warnings { } public function getWarningsWithDefaultBacklog( - User|string|null $userInfo = null, + UserInfo|string|null $userInfo = null, ?Pagination $pagination = null ): array { return $this->getWarnings( @@ -74,12 +73,12 @@ class Warnings { } public function getWarnings( - User|string|null $userInfo = null, + UserInfo|string|null $userInfo = null, ?int $backlog = null, ?Pagination $pagination = null ): array { - if($userInfo instanceof User) - $userInfo = (string)$userInfo->getId(); + if($userInfo instanceof UserInfo) + $userInfo = $userInfo->getId(); $hasUserInfo = $userInfo !== null; $hasBacklog = $backlog !== null; @@ -135,14 +134,14 @@ class Warnings { } public function createWarning( - User|string $userInfo, + UserInfo|string $userInfo, string $body, - User|string|null $modInfo + UserInfo|string|null $modInfo ): WarningInfo { - if($userInfo instanceof User) - $userInfo = (string)$userInfo->getId(); - if($modInfo instanceof User) - $modInfo = (string)$modInfo->getId(); + if($userInfo instanceof UserInfo) + $userInfo = $userInfo->getId(); + if($modInfo instanceof UserInfo) + $modInfo = $modInfo->getId(); $stmt = $this->cache->get('INSERT INTO msz_users_warnings (user_id, mod_id, warn_body) VALUES (?, ?, ?)'); $stmt->addParameter(1, $userInfo); diff --git a/src/perms.php b/src/perms.php index 635955f..65c81c5 100644 --- a/src/perms.php +++ b/src/perms.php @@ -95,10 +95,11 @@ function perms_get_blank(array $modes = MSZ_PERM_MODES): array { return array_fill_keys($modes, 0); } -function perms_get_user(int $user): array { - if($user < 1) { +function perms_get_user(int|string $user): array { + if(is_string($user)) + $user = (int)$user; + if($user < 1) return perms_get_blank(); - } static $memo = []; @@ -128,10 +129,11 @@ function perms_get_user(int $user): array { return $memo[$user] = $getPerms->fetch(); } -function perms_delete_user(int $user): bool { - if($user < 1) { +function perms_delete_user(int|string $user): bool { + if(is_string($user)) + $user = (int)$user; + if($user < 1) return false; - } $deletePermissions = \Misuzu\DB::prepare(' DELETE FROM `msz_permissions` @@ -167,10 +169,11 @@ function perms_get_role(int $role): array { return $memo[$role] = $getPerms->fetch(); } -function perms_get_user_raw(int $user): array { - if($user < 1) { +function perms_get_user_raw(int|string $user): array { + if(is_string($user)) + $user = (int)$user; + if($user < 1) return perms_create(); - } $getPerms = \Misuzu\DB::prepare(sprintf(' SELECT `%s` @@ -188,10 +191,11 @@ function perms_get_user_raw(int $user): array { return $perms; } -function perms_set_user_raw(int $user, array $perms): bool { - if($user < 1) { +function perms_set_user_raw(int|string $user, array $perms): bool { + if(is_string($user)) + $user = (int)$user; + if($user < 1) return false; - } $realPerms = perms_create(); $permKeys = array_keys($realPerms); @@ -245,7 +249,9 @@ function perms_check(?int $perms, ?int $perm, bool $strict = false): bool { return $strict ? $and === $perm : $and > 0; } -function perms_check_user(string $prefix, ?int $userId, int $perm, bool $strict = false): bool { +function perms_check_user(string $prefix, int|string|null $userId, int $perm, bool $strict = false): bool { + if(is_string($userId)) + $userId = (int)$userId; return $userId > 0 && perms_check(perms_get_user($userId)[$prefix] ?? 0, $perm, $strict); } @@ -257,13 +263,13 @@ function perms_check_bulk(int $perms, array $set, bool $strict = false): array { return $set; } -function perms_check_user_bulk(string $prefix, ?int $userId, array $set, bool $strict = false): array { +function perms_check_user_bulk(string $prefix, int|string|null $userId, array $set, bool $strict = false): array { $perms = perms_get_user($userId)[$prefix] ?? 0; return perms_check_bulk($perms, $set, $strict); } function perms_for_comments(string|int $userId): array { - return perms_check_user_bulk(MSZ_PERMS_COMMENTS, (int)$userId, [ + return perms_check_user_bulk(MSZ_PERMS_COMMENTS, $userId, [ 'can_comment' => MSZ_PERM_COMMENTS_CREATE, 'can_delete' => MSZ_PERM_COMMENTS_DELETE_OWN | MSZ_PERM_COMMENTS_DELETE_ANY, 'can_delete_any' => MSZ_PERM_COMMENTS_DELETE_ANY, diff --git a/templates/_layout/comments.twig b/templates/_layout/comments.twig index 1b2d3c0..1a5fe67 100644 --- a/templates/_layout/comments.twig +++ b/templates/_layout/comments.twig @@ -16,7 +16,7 @@
- {{ avatar(user.id, reply_mode ? 40 : 50, user.username) }} + {{ avatar(user.id, reply_mode ? 40 : 50, user.name) }}
+ {{ input_select('about[parser]', constant('\\Misuzu\\Parsers\\Parser::NAMES'), profile_user.aboutParser, '', '', false, 'profile__about__select') }} +
{% else %} -
- {{ profile_user.profileAboutParsed|raw }} +
+ {{ profile_user.aboutContent|parse_text(profile_user.aboutParser)|raw }}
{% endif %}
{% endif %} - {% if (not profile_is_banned or profile_can_edit) and ((profile_is_editing and perms.edit_signature) or profile_user.hasForumSignature) %} + {% if (not profile_is_banned or profile_can_edit) and ((profile_is_editing and perms.edit_signature) or profile_user.hasSignatureContent) %}
{{ container_title('Signature') }} {% if profile_is_editing %}
- {{ input_select('signature[parser]', constant('\\Misuzu\\Parsers\\Parser::NAMES'), profile_user.forumSignatureParser, '', '', false, 'profile__signature__select') }} - + {{ input_select('signature[parser]', constant('\\Misuzu\\Parsers\\Parser::NAMES'), profile_user.signatureParser, '', '', false, 'profile__signature__select') }} +
{% else %} -
- {{ profile_user.forumSignatureParsed|raw }} +
+ {{ profile_user.signatureContent|parse_text(profile_user.signatureParser)|raw }}
{% endif %}
diff --git a/templates/profile/master.twig b/templates/profile/master.twig index ed38a30..3066372 100644 --- a/templates/profile/master.twig +++ b/templates/profile/master.twig @@ -3,8 +3,8 @@ {% if profile_user is defined %} {% set image = url('user-avatar', {'user': profile_user.id, 'res': 200}) %} {% set manage_link = url('manage-user', {'user': profile_user.id}) %} - {% if (not profile_is_banned or profile_can_edit) and profile_user.hasBackground %} - {% set site_background = profile_user.backgroundInfo %} + {% if (not profile_is_banned or profile_can_edit) %} + {% set site_background = profile_background_info %} {% endif %} {% set stats = [ { @@ -15,7 +15,7 @@ { 'title': 'Last seen', 'is_date': true, - 'value': profile_user.activeTime, + 'value': profile_user.lastActiveTime|default(0), }, { 'title': 'Topics', diff --git a/templates/user/listing.twig b/templates/user/listing.twig index 58e4d78..c975b17 100644 --- a/templates/user/listing.twig +++ b/templates/user/listing.twig @@ -4,7 +4,7 @@ {% set url_role = role.id > 1 ? role.id : 0 %} {% set url_sort = order_field == order_default ? '' : order_field %} -{% set url_direction = order_fields[order_field]['default-dir'] == order_direction ? '' : order_direction %} +{% set url_direction = order_direction == 'asc' ? '' : order_direction %} {% set canonical_url = url('user-list', { 'role': url_role, 'sort': url_sort, @@ -25,7 +25,13 @@ {% endfor %} - {{ input_select('ss', orders, order, 'title', null, false, 'userlist__select') }} + {{ input_select('sd', directions, direction, null, null, false, 'userlist__select') }}