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') }}