diff --git a/misuzu.php b/misuzu.php index 7dbd6c92..2670deb9 100644 --- a/misuzu.php +++ b/misuzu.php @@ -221,8 +221,6 @@ if($authToken->isValid()) { user_bump_last_active($userInfo->getId()); $userDisplayInfo['perms'] = perms_get_user($userInfo->getId()); - $userDisplayInfo['ban_expiration'] = user_warning_check_expiration($userInfo->getId(), MSZ_WARN_BAN); - $userDisplayInfo['silence_expiration'] = $userDisplayInfo['ban_expiration'] > 0 ? 0 : user_warning_check_expiration($userInfo->getId(), MSZ_WARN_SILENCE); } } @@ -254,12 +252,15 @@ if(Config::get('private.enabled', Config::TYPE_BOOL)) { } } -if(!empty($userDisplayInfo)) // delete this +// delete these +if(!empty($userDisplayInfo)) Template::set('current_user', $userDisplayInfo); +if(!empty($userInfo)) + Template::set('current_user2', $userInfo); $inManageMode = starts_with($_SERVER['REQUEST_URI'], '/manage'); $hasManageAccess = User::hasCurrent() - && !user_warning_check_restriction(User::getCurrent()->getId()) + && !User::getCurrent()->hasActiveWarning() && perms_check_user(MSZ_PERMS_GENERAL, User::getCurrent()->getId(), MSZ_PERM_GENERAL_CAN_MANAGE); Template::set('has_manage_access', $hasManageAccess); diff --git a/public/auth/register.php b/public/auth/register.php index 11ef4fc9..b278326d 100644 --- a/public/auth/register.php +++ b/public/auth/register.php @@ -6,6 +6,7 @@ use Misuzu\Net\IPAddressBlacklist; use Misuzu\Users\User; use Misuzu\Users\UserLoginAttempt; use Misuzu\Users\UserSession; +use Misuzu\Users\UserWarning; require_once '../../misuzu.php'; @@ -19,7 +20,7 @@ $notices = []; $ipAddress = IPAddress::remote(); $remainingAttempts = UserLoginAttempt::remaining(); $restricted = IPAddressBlacklist::check($ipAddress) ? 'blacklist' - : (user_warning_check_ip($ipAddress) ? 'ban' : ''); + : (UserWarning::countByRemoteAddress() > 0 ? 'ban' : ''); while(!$restricted && !empty($register)) { if(!CSRF::validateRequest()) { diff --git a/public/comments.php b/public/comments.php index 556976dc..6d426f4b 100644 --- a/public/comments.php +++ b/public/comments.php @@ -36,11 +36,11 @@ if($currentUserInfo === null) { return; } -if(user_warning_check_expiration($currentUserInfo->getId(), MSZ_WARN_BAN) > 0) { +if($currentUserInfo->isBanned()) { echo render_info_or_json($isXHR, 'You have been banned, check your profile for more information.', 403); return; } -if(user_warning_check_expiration($currentUserInfo->getId(), MSZ_WARN_SILENCE) > 0) { +if($currentUserInfo->isSilenced()) { echo render_info_or_json($isXHR, 'You have been silenced, check your profile for more information.', 403); return; } diff --git a/public/forum/forum.php b/public/forum/forum.php index 4e4d29eb..4734e74c 100644 --- a/public/forum/forum.php +++ b/public/forum/forum.php @@ -29,9 +29,8 @@ if(!perms_check($perms, MSZ_FORUM_PERM_VIEW_FORUM)) { return; } -if(user_warning_check_restriction($forumUserId)) { +if($forumUser->hasActiveWarning()) $perms &= ~MSZ_FORUM_PERM_SET_WRITE; -} Template::set('forum_perms', $perms); diff --git a/public/forum/poll.php b/public/forum/poll.php index fe3832ef..db26181f 100644 --- a/public/forum/poll.php +++ b/public/forum/poll.php @@ -29,11 +29,11 @@ if($currentUser === null) { $currentUserId = $currentUser->getId(); -if(user_warning_check_expiration($currentUserId, MSZ_WARN_BAN) > 0) { +if($currentUser->isBanned()) { echo render_info_or_json($isXHR, 'You have been banned, check your profile for more information.', 403); return; } -if(user_warning_check_expiration($currentUserId, MSZ_WARN_SILENCE) > 0) { +if($currentUser->isSilenced()) { echo render_info_or_json($isXHR, 'You have been silenced, check your profile for more information.', 403); return; } diff --git a/public/forum/post.php b/public/forum/post.php index 2663142c..6553af55 100644 --- a/public/forum/post.php +++ b/public/forum/post.php @@ -33,11 +33,11 @@ if(!empty($postMode) && !UserSession::hasCurrent()) { $currentUser = User::getCurrent(): $currentUserId = $currentUser === null ? 0 : $currentUser->getId(); -if(user_warning_check_expiration($currentUserId, MSZ_WARN_BAN) > 0) { +if($currentUser->isBanned()) { echo render_info_or_json($isXHR, 'You have been banned, check your profile for more information.', 403); return; } -if(user_warning_check_expiration($currentUserId, MSZ_WARN_SILENCE) > 0) { +if($currentUser->isSilenced()) { echo render_info_or_json($isXHR, 'You have been silenced, check your profile for more information.', 403); return; } diff --git a/public/forum/posting.php b/public/forum/posting.php index efaf5908..0538d0e7 100644 --- a/public/forum/posting.php +++ b/public/forum/posting.php @@ -16,7 +16,7 @@ if($currentUser === null) { $currentUserId = $currentUser->getId(); -if(user_warning_check_restriction($currentUserId)) { +if($currentUser->hasActiveWarning()) { echo render_error(403); return; } diff --git a/public/forum/topic-priority.php b/public/forum/topic-priority.php index 8f206158..4f593c53 100644 --- a/public/forum/topic-priority.php +++ b/public/forum/topic-priority.php @@ -2,6 +2,7 @@ namespace Misuzu; use Misuzu\Users\User; +use Misuzu\Users\UserNotFoundException; require_once '../../misuzu.php'; @@ -22,9 +23,8 @@ $perms = $topic ? forum_perms_get_user($topic['forum_id'], $topicUserId)[MSZ_FORUM_PERMS_GENERAL] : 0; -if(user_warning_check_restriction($topicUserId)) { +if($topicUser->hasActiveWarning()) $perms &= ~MSZ_FORUM_PERM_SET_WRITE; -} $topicIsDeleted = !empty($topic['topic_deleted']); $canDeleteAny = perms_check($perms, MSZ_FORUM_PERM_DELETE_ANY_POST); diff --git a/public/forum/topic.php b/public/forum/topic.php index 177bdd52..899ac116 100644 --- a/public/forum/topic.php +++ b/public/forum/topic.php @@ -28,9 +28,8 @@ $perms = $topic ? forum_perms_get_user($topic['forum_id'], $topicUserId)[MSZ_FORUM_PERMS_GENERAL] : 0; -if(user_warning_check_restriction($topicUserId)) { +if($topicUser->hasActiveWarning()) $perms &= ~MSZ_FORUM_PERM_SET_WRITE; -} $topicIsDeleted = !empty($topic['topic_deleted']); $canDeleteAny = perms_check($perms, MSZ_FORUM_PERM_DELETE_ANY_POST); @@ -99,11 +98,11 @@ if(in_array($moderationMode, $validModerationModes, true)) { return; } - if(user_warning_check_expiration($topicUserId, MSZ_WARN_BAN) > 0) { + if($topicUser->isBanned()) { echo render_info_or_json($isXHR, 'You have been banned, check your profile for more information.', 403); return; } - if(user_warning_check_expiration($topicUserId, MSZ_WARN_SILENCE) > 0) { + if($topicUser->isSilenced()) { echo render_info_or_json($isXHR, 'You have been silenced, check your profile for more information.', 403); return; } diff --git a/public/manage/users/warnings.php b/public/manage/users/warnings.php index f87bcaa0..a8e65cd8 100644 --- a/public/manage/users/warnings.php +++ b/public/manage/users/warnings.php @@ -1,8 +1,13 @@ delete(); + } catch(UserWarningNotFoundException $ex) {} redirect($_SERVER['HTTP_REFERER'] ?? url('manage-users-warnings')); return; } if(!empty($_POST['warning']) && is_array($_POST['warning'])) { $warningType = (int)($_POST['warning']['type'] ?? 0); + $warningDuration = 0; + $warningDuration = (int)($_POST['warning']['duration'] ?? 0); - if(user_warning_type_is_valid($warningType)) { - $warningDuration = 0; + if($warningDuration < -1) { + $customDuration = $_POST['warning']['duration_custom'] ?? ''; - if(user_warning_has_duration($warningType)) { - $duration = (int)($_POST['warning']['duration'] ?? 0); + if(!empty($customDuration)) { + switch($warningDuration) { + case -100: // YYYY-MM-DD + $splitDate = array_apply(explode('-', $customDuration, 3), function ($a) { + return (int)$a; + }); - if($duration > 0) { - $warningDuration = time() + $duration; - } elseif($duration < 0) { - $customDuration = $_POST['warning']['duration_custom'] ?? ''; - - if(!empty($customDuration)) { - switch($duration) { - case -1: // YYYY-MM-DD - $splitDate = array_apply(explode('-', $customDuration, 3), function ($a) { - return (int)$a; - }); - - if(checkdate($splitDate[1], $splitDate[2], $splitDate[0])) { - $warningDuration = mktime(0, 0, 0, $splitDate[1], $splitDate[2], $splitDate[0]); - } - break; - - case -2: // Raw seconds - $warningDuration = time() + (int)$customDuration; - break; - - case -3: // strtotime - $warningDuration = strtotime($customDuration); - break; - } - } - } - - if($warningDuration <= time()) { - $notices[] = 'The duration supplied was invalid.'; - } - } - - $warningsUser = (int)($_POST['warning']['user'] ?? 0); - - if(!user_check_super($currentUserId) && !user_check_authority($currentUserId, $warningsUser)) { - $notices[] = 'You do not have authority over this user.'; - } - - if(empty($notices) && $warningsUser > 0) { - $warningId = user_warning_add( - $warningsUser, - user_get_last_ip($warningsUser), - $currentUserId, - IPAddress::remote(), - $warningType, - $_POST['warning']['note'], - $_POST['warning']['private'], - $warningDuration - ); - } - - if(!empty($warningId) && $warningId < 0) { - switch($warningId) { - case MSZ_E_WARNING_ADD_DB: - $notices[] = 'Failed to record the warning in the database.'; + if(checkdate($splitDate[1], $splitDate[2], $splitDate[0])) + $warningDuration = mktime(0, 0, 0, $splitDate[1], $splitDate[2], $splitDate[0]) - time(); break; - case MSZ_E_WARNING_ADD_TYPE: - $notices[] = 'The warning type provided was invalid.'; + case -200: // Raw seconds + $warningDuration = (int)$customDuration; break; - case MSZ_E_WARNING_ADD_USER: - $notices[] = 'The User ID provided was invalid.'; - break; - - case MSZ_E_WARNING_ADD_DURATION: - $notices[] = 'The duration specified was invalid.'; + case -300: // strtotime + $warningDuration = strtotime($customDuration) - time(); break; } } } + + try { + $warningsUserInfo = User::byId((int)($_POST['warning']['user'] ?? 0)); + $warningsUser = $warningsUserInfo->getId(); + } catch(UserNotFoundException $ex) { + $warningsUserInfo = null; + } + + if(!user_check_super($currentUserId) && !user_check_authority($currentUserId, $warningsUser)) { + $notices[] = 'You do not have authority over this user.'; + } + + if(empty($notices) && $warningsUser > 0) { + try { + $warningInfo = UserWarning::create( + $warningsUserInfo, + User::getCurrent(), + $warningType, + $warningDuration, + $_POST['warning']['note'], + $_POST['warning']['private'] + ); + } catch(InvalidArgumentException $ex) { + $notices[] = $ex->getMessage(); + } catch(UserWarningCreationFailedException $ex) { + $notices[] = 'Warning creation failed.'; + } + } } -if(empty($warningsUser)) { +if(empty($warningsUser)) $warningsUser = max(0, (int)($_GET['u'] ?? 0)); -} -$warningsPagination = new Pagination(user_warning_global_count($warningsUser), 50); +if(empty($warningsUserInfo)) + try { + $warningsUserInfo = User::byId($warningsUser); + } catch(UserNotFoundException $ex) { + $warningsUserInfo = null; + } + +$warningsPagination = new Pagination(UserWarning::countAll($warningsUserInfo), 10); if(!$warningsPagination->hasValidOffset()) { echo render_error(404); return; } -$warningsList = user_warning_global_fetch( - $warningsPagination->getOffset(), - $warningsPagination->getRange(), - $warningsUser -); - // calling array_flip since the input_select macro wants value => display, but this looks cuter $warningDurations = array_flip([ 'Pick a duration...' => 0, @@ -148,19 +131,24 @@ $warningDurations = array_flip([ '6 Months' => 60 * 60 * 24 * 365 / 12 * 6, '9 Months' => 60 * 60 * 24 * 365 / 12 * 9, '1 Year' => 60 * 60 * 24 * 365, - 'Until (YYYY-MM-DD) ->' => -1, - 'Until (Seconds) ->' => -2, - 'Until (strtotime) ->' => -3, + 'Permanent' => -1, + 'Until (YYYY-MM-DD) ->' => -100, + 'Until (Seconds) ->' => -200, + 'Until (strtotime) ->' => -300, ]); Template::render('manage.users.warnings', [ 'warnings' => [ 'notices' => $notices, 'pagination' => $warningsPagination, - 'list' => $warningsList, - 'user_id' => $warningsUser, - 'username' => user_username_from_id($warningsUser), - 'types' => user_warning_get_types(), + 'list' => UserWarning::all($warningsUserInfo, $warningsPagination), + 'user' => $warningsUserInfo, 'durations' => $warningDurations, + 'types' => [ + UserWarning::TYPE_NOTE => 'Note', + UserWarning::TYPE_WARN => 'Warning', + UserWarning::TYPE_MUTE => 'Silence', + UserWarning::TYPE_BAHN => 'Ban', + ], ], ]); diff --git a/public/profile.php b/public/profile.php index 83ba3fd3..4a6eee0d 100644 --- a/public/profile.php +++ b/public/profile.php @@ -26,8 +26,8 @@ $currentUser = User::getCurrent(); $viewingAsGuest = $currentUser === null; $currentUserId = $viewingAsGuest ? 0 : $currentUser->getId(); $viewingOwnProfile = $currentUserId === $profileUser->getId(); - -$isBanned = user_warning_check_restriction($profileUser->getId()); +define('DUMB_SHIT', true); +$isBanned = $profileUser->hasActiveWarning(); $userPerms = perms_get_user($currentUserId)[MSZ_PERMS_USER]; $canManageWarnings = perms_check($userPerms, MSZ_PERM_USER_MANAGE_WARNINGS); $canEdit = !$isBanned @@ -406,23 +406,11 @@ switch($profileMode) { case '': $template = 'profile.index'; - $warnings = $viewingAsGuest - ? [] - : user_warning_fetch( - $profileUser->getId(), - 90, - $canManageWarnings - ? MSZ_WARN_TYPES_VISIBLE_TO_STAFF - : ( - $viewingOwnProfile - ? MSZ_WARN_TYPES_VISIBLE_TO_USER - : MSZ_WARN_TYPES_VISIBLE_TO_PUBLIC - ) - ); + $warnings = $profileUser->getProfileWarnings($currentUser); Template::set([ 'profile_warnings' => $warnings, - 'profile_warnings_view_private' => $viewingOwnProfile, + 'profile_warnings_view_private' => $canManageWarnings, 'profile_warnings_can_manage' => $canManageWarnings, ]); break; diff --git a/public/relations.php b/public/relations.php index 03ba8521..0ff3b22c 100644 --- a/public/relations.php +++ b/public/relations.php @@ -34,7 +34,7 @@ if($currentUser === null) { return; } -if(user_warning_check_expiration($currentUser->getId(), MSZ_WARN_BAN) > 0) { +if($currentUser->isBanned()) { echo render_info_or_json($isXHR, 'You have been banned, check your profile for more information.', 403); return; } diff --git a/public/settings/account.php b/public/settings/account.php index 9fa7f3bf..07e070b7 100644 --- a/public/settings/account.php +++ b/public/settings/account.php @@ -18,7 +18,7 @@ if(!UserSession::hasCurrent()) { $errors = []; $currentUser = User::getCurrent(); $currentUserId = $currentUser->getId(); -$isRestricted = user_warning_check_restriction($currentUserId); +$isRestricted = $currentUser->hasActiveWarning(); $twoFactorInfo = user_totp_info($currentUserId); $isVerifiedRequest = CSRF::validateRequest(); diff --git a/public/user-assets.php b/public/user-assets.php index 73d07867..c25a0d64 100644 --- a/public/user-assets.php +++ b/public/user-assets.php @@ -19,7 +19,7 @@ try { $userId = $userExists ? $userInfo->getId() : 0; $canViewImages = !$userExists - || !user_warning_check_expiration($userId, MSZ_WARN_BAN) + || !$userInfo->isBanned() || ( parse_url($_SERVER['HTTP_REFERER'] ?? '', PHP_URL_PATH) === url('user-profile') && perms_check_user(MSZ_PERMS_USER, User::hasCurrent() ? User::getCurrent()->getId() : 0, MSZ_PERM_USER_MANAGE_USERS) diff --git a/src/Http/Handlers/SockChatHandler.php b/src/Http/Handlers/SockChatHandler.php index ba8dd084..a7c16bd5 100644 --- a/src/Http/Handlers/SockChatHandler.php +++ b/src/Http/Handlers/SockChatHandler.php @@ -143,7 +143,7 @@ final class SockChatHandler extends Handler { try { $token = UserChatToken::create($currentUser); - } catch(UserChatTokenNotFoundException $ex) { + } catch(UserChatTokenCreationFailedException $ex) { return 500; } @@ -249,7 +249,7 @@ final class SockChatHandler extends Handler { } else { try { $token = UserChatToken::byExact($userInfo, $authInfo->token); - } catch(UserChatTokenCreationFailedException $ex) { + } catch(UserChatTokenNotFoundException $ex) { return ['success' => false, 'reason' => 'token']; } @@ -276,7 +276,7 @@ final class SockChatHandler extends Handler { 'username' => $userInfo->getUsername(), 'colour_raw' => $userInfo->getColourRaw(), 'hierarchy' => $userInfo->getHierarchy(), - 'is_silenced' => date('c', user_warning_check_expiration($userInfo->getId(), MSZ_WARN_SILENCE)), + 'is_silenced' => date('c', $userInfo->isSilenced() || $userInfo->isBanned() ? ($userInfo->isActiveWarningPermanent() ? strtotime('10 years') : $userInfo->getActiveWarningExpiration()) : 0), 'perms' => $perms, ]; } diff --git a/src/TwigMisuzu.php b/src/TwigMisuzu.php index ebe0353b..1c5104a6 100644 --- a/src/TwigMisuzu.php +++ b/src/TwigMisuzu.php @@ -24,7 +24,6 @@ final class TwigMisuzu extends Twig_Extension { public function getFunctions() { return [ new Twig_Function('url_construct', 'url_construct'), - new Twig_Function('warning_has_duration', 'user_warning_has_duration'), new Twig_Function('url', 'url'), new Twig_Function('url_list', 'url_list'), new Twig_Function('html_avatar', 'html_avatar'), diff --git a/src/Users/User.php b/src/Users/User.php index b4db3e0d..28cefea2 100644 --- a/src/Users/User.php +++ b/src/Users/User.php @@ -301,6 +301,32 @@ class User { return $this->forumPostCount; } + private $activeWarning = -1; + + public function getActiveWarning(): ?UserWarning { + if($this->activeWarning === -1) + $this->activeWarning = UserWarning::byUserActive($this); + return $this->activeWarning; + } + public function hasActiveWarning(): bool { + return $this->getActiveWarning() !== null && !$this->getActiveWarning()->hasExpired(); + } + public function isSilenced(): bool { + return $this->hasActiveWarning() && $this->getActiveWarning()->isSilence(); + } + public function isBanned(): bool { + return $this->hasActiveWarning() && $this->getActiveWarning()->isBan(); + } + public function getActiveWarningExpiration(): int { + return !$this->hasActiveWarning() ? 0 : $this->getActiveWarning()->getExpirationTime(); + } + public function isActiveWarningPermanent(): bool { + return $this->hasActiveWarning() && $this->getActiveWarning()->isPermanent(); + } + public function getProfileWarnings(?self $viewer): array { + return UserWarning::byProfile($this, $viewer); + } + public function setCurrent(): void { self::$localUser = $this; } diff --git a/src/Users/UserWarning.php b/src/Users/UserWarning.php new file mode 100644 index 00000000..a0c8f944 --- /dev/null +++ b/src/Users/UserWarning.php @@ -0,0 +1,281 @@ +warning_id; + } + + public function getUserId(): int { + return $this->user_id; + } + public function getUser(): User { + if($this->user === null) + $this->user = User::byId($this->getUserId()); + return $this->user; + } + + public function getUserRemoteAddress(): string { + return $this->user_ip; + } + + public function getIssuerId(): int { + return $this->issuer_id; + } + public function getIssuer(): User { + if($this->issuer === null) + $this->issuer = User::byId($this->getIssuerId()); + return $this->issuer; + } + + public function getIssuerRemoteAddress(): string { + return $this->issuer_ip; + } + + public function getCreatedTime(): int { + return $this->warning_created === null ? -1 : $this->warning_created; + } + + public function getExpirationTime(): int { + return $this->warning_duration === null ? -1 : $this->warning_duration; + } + public function hasExpired(): bool { + return $this->hasDuration() && ($this->getExpirationTime() > 0 && $this->getExpirationTime() < time()); + } + + public function hasDuration(): bool { + return in_array($this->getType(), self::HAS_DURATION); + } + public function getDuration(): int { + return max(-1, $this->getExpirationTime() - $this->getCreatedTime()); + } + + private const DURATION_DIVS = [ + 31536000 => 'year', + 2592000 => 'month', + 604800 => 'week', + 86400 => 'day', + 3600 => 'hour', + 60 => 'minute', + 1 => 'second', + ]; + + public function getDurationString(): string { + $duration = $this->getDuration(); + if($duration < 1) + return 'permanent'; + + foreach(self::DURATION_DIVS as $span => $name) { + $display = floor($duration / $span); + if($display > 0) + return number_format($display) . ' ' . $name . ($display == 1 ? '' : 's'); + } + + return 'an amount of time'; + } + + public function isPermanent(): bool { + return $this->hasDuration() && $this->getDuration() < 0; + } + + public function getType(): int { return $this->warning_type; } + public function isNote(): bool { return $this->getType() === self::TYPE_NOTE; } + public function isWarning(): bool { return $this->getType() === self::TYPE_WARN; } + public function isSilence(): bool { return $this->getType() === self::TYPE_MUTE; } + public function isBan(): bool { return $this->getType() === self::TYPE_BAHN; } + + public function isVisibleToUser(): bool { + return in_array($this->getType(), self::VISIBLE_TO_USER); + } + public function isVisibleToPublic(): bool { + return in_array($this->getType(), self::VISIBLE_TO_PUBLIC); + } + + public function getPublicNote(): string { + return $this->warning_note; + } + + public function getPrivateNote(): string { + return $this->warning_note_private ?? ''; + } + public function hasPrivateNote(): bool { + return !empty($this->warning_note_private); + } + + public function delete(): void { + DB::prepare('DELETE FROM `' . DB::PREFIX . self::TABLE . '` WHERE `warning_id` = :warning') + ->bind('warning', $this->warning_id) + ->execute(); + } + + public static function create(User $user, User $issuer, int $type, int $duration, string $publicNote, ?string $privateNote = null): self { + if(!in_array($type, self::TYPES)) + throw new InvalidArgumentException('Type was invalid.'); + + if(!in_array($type, self::HAS_DURATION)) + $duration = 0; + else { + if($duration === 0) + throw new InvalidArgumentException('Duration must be non-zero.'); + if($duration < 0) + $duration = -1; + } + + $warningId = DB::prepare( + 'INSERT INTO `' . DB::PREFIX . self::TABLE . '` (`user_id`, `user_ip`, `issuer_id`, `issuer_ip`, `warning_created`, `warning_duration`, `warning_type`, `warning_note`, `warning_note_private`)' + . ' VALUES (:user, INET6_ATON(:user_addr), :issuer, INET6_ATON(:issuer_addr), NOW(), IF(:set_duration, NOW() + INTERVAL :duration SECOND, NULL), :type, :public_note, :private_note)' + ) ->bind('user', $user->getId()) + ->bind('user_addr', $user->getLastRemoteAddress()) + ->bind('issuer', $issuer->getId()) + ->bind('issuer_addr', $issuer->getLastRemoteAddress()) + ->bind('set_duration', $duration > 0 ? 1 : 0) + ->bind('duration', $duration) + ->bind('type', $type) + ->bind('public_note', $publicNote) + ->bind('private_note', $privateNote) + ->executeGetId(); + + if($warningId < 1) + throw new UserWarningCreationFailedException; + + return self::byId($warningId); + } + + private static function countQueryBase(): string { + return sprintf(self::QUERY_SELECT, sprintf('COUNT(*)', self::TABLE)); + } + public static function countByRemoteAddress(?string $address = null, bool $withDuration = true): int { + $address = $address ?? IPAddress::remote(); + return (int)DB::prepare( + self::countQueryBase() + . ' WHERE `user_ip` = INET6_ATON(:address)' + . ' AND `warning_duration` >= NOW()' + . ($withDuration ? ' AND `warning_type` IN (' . implode(',', self::HAS_DURATION) . ')' : '') + )->bind('address', $address)->fetchColumn(); + } + public static function countAll(?User $user = null): int { + $getCount = DB::prepare(self::countQueryBase() . ($user === null ? '' : ' WHERE `user_id` = :user')); + if($user !== null) + $getCount->bind('user', $user->getId()); + return (int)$getCount->fetchColumn(); + } + + private static function byQueryBase(): string { + return sprintf(self::QUERY_SELECT, sprintf(self::SELECT, self::TABLE)); + } + public static function byId(int $warningId): self { + $object = DB::prepare( + self::byQueryBase() . ' WHERE `warning_id` = :warning' + ) ->bind('warning', $warningId) + ->fetchObject(self::class); + if(!$object) + throw new UserWarningNotFoundException; + return $object; + } + public static function byUserActive(User $user): ?self { + return DB::prepare( + self::byQueryBase() + . ' WHERE `user_id` = :user' + . ' AND `warning_type` IN (' . implode(',', self::HAS_DURATION) . ')' + . ' AND (`warning_duration` IS NULL OR `warning_duration` >= NOW())' + . ' ORDER BY `warning_type` DESC, `warning_duration` DESC' + ) ->bind('user', $user->getId()) + ->fetchObject(self::class); + } + public static function byProfile(User $user, ?User $viewer = null): array { + if($viewer === null) + return []; + + $types = self::VISIBLE_TO_PUBLIC; + if(perms_check_user(MSZ_PERMS_USER, $viewer->getId(), MSZ_PERM_USER_MANAGE_WARNINGS)) + $types = self::VISIBLE_TO_STAFF; + elseif($user->getId() === $viewer->getId()) + $types = self::VISIBLE_TO_USER; + + $getObjects = DB::prepare( + self::byQueryBase() + . ' WHERE `user_id` = :user' + . ' AND `warning_type` IN (' . implode(',', $types) . ')' + . ' AND (`warning_type` = 0 OR `warning_created` >= NOW() - INTERVAL ' . self::PROFILE_BACKLOG . ' DAY OR (`warning_duration` IS NOT NULL AND `warning_duration` >= NOW()))' + . ' ORDER BY `warning_created` DESC' + ); + + $getObjects->bind('user', $user->getId()); + + return $getObjects->fetchObjects(self::class); + } + public static function all(?User $user = null, ?Pagination $pagination = null): array { + $query = self::byQueryBase() + . ($user === null ? '' : ' WHERE `user_id` = :user') + . ' ORDER BY `warning_created` DESC'; + + if($pagination !== null) + $query .= ' LIMIT :range OFFSET :offset'; + + $getObjects = DB::prepare($query); + + if($user !== null) + $getObjects->bind('user', $user->getId()); + + if($pagination !== null) + $getObjects->bind('range', $pagination->getRange()) + ->bind('offset', $pagination->getOffset()); + + return $getObjects->fetchObjects(self::class); + } +} diff --git a/src/Users/warning.php b/src/Users/warning.php index 09870fe4..7d7e8063 100644 --- a/src/Users/warning.php +++ b/src/Users/warning.php @@ -1,211 +1,11 @@ 'Note', - MSZ_WARN_WARNING => 'Warning', - MSZ_WARN_SILENCE => 'Silence', - MSZ_WARN_BAN => 'Ban', -]); - -function user_warning_type_is_valid(int $type): bool { - return in_array($type, MSZ_WARN_TYPES, true); -} - -function user_warning_type_get_name(int $type): string { - return user_warning_type_is_valid($type) ? MSZ_WARN_TYPE_NAMES[$type] : ''; -} - -function user_warning_get_types(): array { - return MSZ_WARN_TYPE_NAMES; -} - -function user_warning_has_duration(int $type): bool { - return in_array($type, MSZ_WARN_TYPES_HAS_DURATION, true); -} - -define('MSZ_E_WARNING_ADD_DB', -1); -define('MSZ_E_WARNING_ADD_TYPE', -2); -define('MSZ_E_WARNING_ADD_USER', -3); -define('MSZ_E_WARNING_ADD_DURATION', -4); - -function user_warning_add( - int $userId, - string $userIp, - int $issuerId, - string $issuerIp, - int $type, - string $publicNote, - string $privateNote, - ?int $duration = null -): int { - if(!user_warning_type_is_valid($type)) - return MSZ_E_WARNING_ADD_TYPE; - - if($userId < 1) - return MSZ_E_WARNING_ADD_USER; - - if(user_warning_has_duration($type)) { - if($duration <= time()) - return MSZ_E_WARNING_ADD_DURATION; - } else - $duration = 0; - - $addWarning = \Misuzu\DB::prepare(' - INSERT INTO `msz_user_warnings` - (`user_id`, `user_ip`, `issuer_id`, `issuer_ip`, `warning_type`, `warning_note`, `warning_note_private`, `warning_duration`) - VALUES - (:user_id, INET6_ATON(:user_ip), :issuer_id, INET6_ATON(:issuer_ip), :type, :note, :note_private, :duration) - '); - $addWarning->bind('user_id', $userId); - $addWarning->bind('user_ip', $userIp); - $addWarning->bind('issuer_id', $issuerId); - $addWarning->bind('issuer_ip', $issuerIp); - $addWarning->bind('type', $type); - $addWarning->bind('note', $publicNote); - $addWarning->bind('note_private', $privateNote); - $addWarning->bind('duration', $duration < 1 ? null : date('Y-m-d H:i:s', $duration)); - - if(!$addWarning->execute()) - return MSZ_E_WARNING_ADD_DB; - - return \Misuzu\DB::lastId(); -} - -function user_warning_count(int $userId): int { - if($userId < 1) - return 0; - - $countWarnings = \Misuzu\DB::prepare(' - SELECT COUNT(`warning_id`) - FROM `msz_user_warnings` - WHERE `user_id` = :user_id - '); - $countWarnings->bind('user_id', $userId); - return (int)$countWarnings->fetchColumn(0, 0); -} - -function user_warning_remove(int $warningId): bool { - if($warningId < 1) - return false; - - $removeWarning = \Misuzu\DB::prepare(' - DELETE FROM `msz_user_warnings` - WHERE `warning_id` = :warning_id - '); - $removeWarning->bind('warning_id', $warningId); - return $removeWarning->execute(); -} - -function user_warning_fetch( - int $userId, - ?int $days = null, - array $displayTypes = MSZ_WARN_TYPES -): array { - $fetchWarnings = \Misuzu\DB::prepare(sprintf( - ' - SELECT - uw.`warning_id`, uw.`warning_created`, uw.`warning_type`, uw.`warning_note`, - uw.`warning_note_private`, uw.`user_id`, uw.`issuer_id`, uw.`warning_duration`, - INET6_NTOA(uw.`user_ip`) AS `user_ip`, INET6_NTOA(uw.`issuer_ip`) AS `issuer_ip`, - iu.`username` AS `issuer_username` - FROM `msz_user_warnings` AS uw - LEFT JOIN `msz_users` AS iu - ON iu.`user_id` = uw.`issuer_id` - WHERE uw.`user_id` = :user_id - AND uw.`warning_type` IN (%1$s) - %2$s - ORDER BY uw.`warning_id` DESC - ', - implode(',', array_apply($displayTypes, 'intval')), - $days !== null ? 'AND (uw.`warning_created` >= NOW() - INTERVAL :days DAY OR (uw.`warning_duration` IS NOT NULL AND uw.`warning_duration` > NOW()))' : '' - )); - $fetchWarnings->bind('user_id', $userId); - - if($days !== null) - $fetchWarnings->bind('days', $days); - - return $fetchWarnings->fetchAll(); -} - -function user_warning_global_count(?int $userId = null): int { - $countWarnings = \Misuzu\DB::prepare(sprintf(' - SELECT COUNT(`warning_id`) - FROM `msz_user_warnings` - %s - ', $userId > 0 ? 'WHERE `user_id` = :user_id' : '')); - - if($userId > 0) - $countWarnings->bind('user_id', $userId); - - return (int)$countWarnings->fetchColumn(0, 0); -} - -function user_warning_global_fetch(int $offset = 0, int $take = 50, ?int $userId = null): array { - $fetchWarnings = \Misuzu\DB::prepare(sprintf( - ' - SELECT - uw.`warning_id`, uw.`warning_created`, uw.`warning_type`, uw.`warning_note`, - uw.`warning_note_private`, uw.`user_id`, uw.`issuer_id`, uw.`warning_duration`, - INET6_NTOA(uw.`user_ip`) AS `user_ip`, INET6_NTOA(uw.`issuer_ip`) AS `issuer_ip`, - iu.`username` AS `issuer_username`, wu.`username` AS `username` - FROM `msz_user_warnings` AS uw - LEFT JOIN `msz_users` AS iu - ON iu.`user_id` = uw.`issuer_id` - LEFT JOIN `msz_users` AS wu - ON wu.`user_id` = uw.`user_id` - %1$s - ORDER BY uw.`warning_id` DESC - LIMIT :offset, :take - ', - $userId > 0 ? 'WHERE uw.`user_id` = :user_id' : '' - )); - $fetchWarnings->bind('offset', $offset); - $fetchWarnings->bind('take', $take); - - if($userId > 0) - $fetchWarnings->bind('user_id', $userId); - - return $fetchWarnings->fetchAll(); -} - -function user_warning_check_ip(string $address): bool { - $checkAddress = \Misuzu\DB::prepare(sprintf( - ' - SELECT COUNT(`warning_id`) > 0 - FROM `msz_user_warnings` - WHERE `warning_type` IN (%s) - AND `user_ip` = INET6_ATON(:address) - AND `warning_duration` IS NOT NULL - AND `warning_duration` >= NOW() - ', - implode(',', MSZ_WARN_TYPES_HAS_DURATION) - )); - $checkAddress->bind('address', $address); - return (bool)$checkAddress->fetchColumn(0, false); -} +define('MSZ_WARN_TYPES_HAS_DURATION', [MSZ_WARN_SILENCE, MSZ_WARN_BAN]); function user_warning_check_expiration(int $userId, int $type): int { - if($userId < 1 || !user_warning_has_duration($type)) + if($userId < 1 || !in_array($type, MSZ_WARN_TYPES_HAS_DURATION, true)) return 0; static $memo = []; @@ -230,27 +30,3 @@ function user_warning_check_expiration(int $userId, int $type): int { return $memo[$memoId] = (empty($expiration) ? 0 : strtotime($expiration)); } - -function user_warning_check_restriction(int $userId): bool { - if($userId < 1) - return false; - - static $memo = []; - - if(array_key_exists($userId, $memo)) - return $memo[$userId]; - - $checkAddress = \Misuzu\DB::prepare(sprintf( - ' - SELECT COUNT(`warning_id`) > 0 - FROM `msz_user_warnings` - WHERE `warning_type` IN (%s) - AND `user_id` = :user - AND `warning_duration` IS NOT NULL - AND `warning_duration` >= NOW() - ', - implode(',', MSZ_WARN_TYPES_HAS_DURATION) - )); - $checkAddress->bind('user', $userId); - return $memo[$userId] = (bool)$checkAddress->fetchColumn(0, false); -} diff --git a/templates/manage/users/warnings.twig b/templates/manage/users/warnings.twig index d9dfabf5..776d0722 100644 --- a/templates/manage/users/warnings.twig +++ b/templates/manage/users/warnings.twig @@ -6,7 +6,7 @@ {% block manage_content %}
@@ -20,11 +20,11 @@ {% endif %} - {% if warnings.user_id > 0 and warnings.username|length > 0 %}{# shittiest validation in the world, but it should work #} + {% if warnings.user is not null %}