From 5c42da97a3249c6fe89dabf68289a1f30c669d83 Mon Sep 17 00:00:00 2001 From: Spine Date: Thu, 30 Nov 2023 10:09:27 +0000 Subject: [PATCH] PMs are sent by creating a conversation in an Inbox --- app/Applicant.php | 9 +- app/Contest.php | 4 +- app/Login.php | 25 ++- app/Manager/Recovery.php | 3 +- app/Manager/User.php | 90 ++++------- app/PM.php | 133 ++++++++------- app/Request.php | 18 +-- app/Task/DemoteUsersRatio.php | 12 +- app/Torrent.php | 13 +- app/Torrent/Reaper.php | 46 +++--- app/User.php | 46 +++--- app/User/Bonus.php | 4 +- app/User/Donor.php | 8 +- app/User/Inbox.php | 89 +++++++++- app/User/Notification/Inbox.php | 2 +- bin/remove-upload | 2 +- sections/ajax/info.php | 16 +- sections/comments/edit_handle.php | 12 +- sections/comments/warn_handle.php | 1 + sections/forums/edit_handle.php | 10 +- sections/inbox/compose_handle.php | 3 +- sections/inbox/conversation.php | 2 +- sections/inbox/inbox.php | 2 +- sections/inbox/massdelete_handle.php | 2 +- sections/register/index.php | 6 +- sections/reports/compose_handle.php | 3 +- sections/reportsv2/ajax_switch.php | 2 +- sections/reportsv2/ajax_take_pm.php | 7 +- sections/reportsv2/report_handle.php | 4 +- sections/reportsv2/resolve_handle.php | 2 +- sections/requests/delete_handle.php | 13 +- sections/tools/managers/take_mass_pm.php | 14 +- sections/torrents/reseed.php | 2 +- sections/user/moderate_handle.php | 16 +- ...oader.twig => payout-uploader.bbcode.twig} | 0 ...res.twig => too-many-failures.bbcode.twig} | 0 ....twig => removed-never-seeded.bbcode.twig} | 0 ...ig => removed-unseeded-snatch.bbcode.twig} | 0 ...eded.twig => removed-unseeded.bbcode.twig} | 0 .../{reseed.twig => reseed.bbcode.twig} | 0 .../{welcome.twig => welcome.bbcode.twig} | 0 .../reportsv2/{new.twig => new.bbcode.twig} | 0 .../{reseed-pm.twig => reseed-pm.bbcode.twig} | 0 tests/helper.php | 21 ++- tests/phpunit/CollageTest.php | 12 +- tests/phpunit/DonorTest.php | 18 +-- tests/phpunit/ForumTest.php | 4 +- tests/phpunit/InboxTest.php | 153 +++++++++--------- tests/phpunit/ReaperTest.php | 43 ++--- tests/phpunit/SearchReportTest.php | 11 +- tests/phpunit/UserActivityTest.php | 26 +-- tests/phpunit/UserTest.php | 1 - tests/phpunit/manager/ReportManagerTest.php | 11 +- tests/phpunit/manager/UserManagerTest.php | 27 ++-- 54 files changed, 517 insertions(+), 431 deletions(-) rename templates/contest/{payout-uploader.twig => payout-uploader.bbcode.twig} (100%) rename templates/login/{too-many-failures.twig => too-many-failures.bbcode.twig} (100%) rename templates/notification/{removed-never-seeded.twig => removed-never-seeded.bbcode.twig} (100%) rename templates/notification/{removed-unseeded-snatch.twig => removed-unseeded-snatch.bbcode.twig} (100%) rename templates/notification/{removed-unseeded.twig => removed-unseeded.bbcode.twig} (100%) rename templates/notification/{reseed.twig => reseed.bbcode.twig} (100%) rename templates/register/{welcome.twig => welcome.bbcode.twig} (100%) rename templates/reportsv2/{new.twig => new.bbcode.twig} (100%) rename templates/torrent/{reseed-pm.twig => reseed-pm.bbcode.twig} (100%) diff --git a/app/Applicant.php b/app/Applicant.php index 358d311cd..fbfdb7b01 100644 --- a/app/Applicant.php +++ b/app/Applicant.php @@ -94,8 +94,13 @@ public function saveNote(User $poster, string $body, string $visibility): int { } $noteId = $this->thread()->saveNote($poster, $body, $visibility); if ($visibility == 'public' && $this->role()->isStaffViewer($poster)) { - (new Manager\User)->sendPM( - $this->userId(), 0, + /** + * We could send from the poster account, but that could + * direct the conversation away from the application page. + * Sending from System ensures the discussion is not fragmented + * between inboxes. + */ + (new User($this->userId()))->inbox()->createSystem( "You have a reply to your {$this->role()->title()} application", self::$twig->render('applicant/pm-reply.bbcode.twig', [ 'applicant' => $this, diff --git a/app/Contest.php b/app/Contest.php index abed0ee4d..d68e04eed 100644 --- a/app/Contest.php +++ b/app/Contest.php @@ -323,9 +323,9 @@ public function doPayout(Manager\User $userMan): int { if (DEBUG_CONTEST_PAYOUT) { continue; } - $userMan->sendPM($p['ID'], 0, + $user->inbox()->createSystem( "You have received " . number_format($totalGain, 2) . " bonus points!", - self::$twig->render('contest/payout-uploader.twig', [ + self::$twig->render('contest/payout-uploader.bbcode.twig', [ 'contest' => $this, 'contest_bonus' => $contestBonus, 'enabled_bonus' => $enabledUserBonus, diff --git a/app/Login.php b/app/Login.php index f14cbe76b..f6c9f4071 100644 --- a/app/Login.php +++ b/app/Login.php @@ -6,6 +6,7 @@ class Login extends Base { final public const NO_ERROR = 0; final public const ERR_CREDENTIALS = 1; final public const ERR_UNCONFIRMED = 2; + final public const FLOOD_COUNT = 'login_flood_total_%d'; protected int $error = self::NO_ERROR; protected bool $persistent = false; @@ -62,16 +63,30 @@ public function login( if ($user) { $this->watch->clearAttempts(); $user->toggleAttr('inactive-warning-sent', false); + self::$cache->delete_value(sprintf(self::FLOOD_COUNT, $user->id())); } else { // we might not have an authenticated user, but still have the id of the username $this->watch->increment($this->userId, $this->username); if ($this->watch->nrAttempts() > 10) { $this->watch->ban($this->username); - (new Manager\User)->sendPM($this->userId, 0, "Too many login attempts on your account", - self::$twig->render('login/too-many-failures.twig', [ - 'ipaddr' => $this->ipaddr, - 'username' => $this->username, - ])); + + $key = 'login_flood_' . $this->userId; + if (self::$cache->get_value($key) === false) { + self::$cache->cache_value($key, true, 86400); + // fake a user object temporarily to send them some email + (new User($this->userId))->inbox()->createSystem( + "Too many login attempts on your account", + self::$twig->render('login/too-many-failures.bbcode.twig', [ + 'ipaddr' => $this->ipaddr, + 'username' => $this->username, + ]) + ); + } + $key = sprintf(self::FLOOD_COUNT, $this->userId); + if (self::$cache->get_value($key) === false) { + self::$cache->cache_value($key, 0, 86400 * 7); + } + self::$cache->increment($key); } elseif ($this->watch->nrBans() > 3) { (new Manager\IPv4)->createBan( $this->userId, $this->ipaddr, $this->ipaddr, 'Automated ban, too many failed login attempts' diff --git a/app/Manager/Recovery.php b/app/Manager/Recovery.php index 59a60e9e8..21dc5894f 100644 --- a/app/Manager/Recovery.php +++ b/app/Manager/Recovery.php @@ -554,7 +554,8 @@ public function boostUpload(): void { --OPS Staff END_MSG; } - $userMan->sendPM($siteUserId, 0, "Your buffer stats have been updated", $Body); + (new \Gazelle\User($siteUserId))->inbox() + ->createSystem("Your buffer stats have been updated", $Body); } /* insert this first to avoid a potential reallocation */ diff --git a/app/Manager/User.php b/app/Manager/User.php index b86ecc896..e795e4f9c 100644 --- a/app/Manager/User.php +++ b/app/Manager/User.php @@ -455,45 +455,6 @@ public function flushEnabledUsersCount(): static { return $this; } - /** - * Sends a PM from $FromId to $ToId. - * - * @return int conversation Id - */ - public function sendPM(int $toId, int $fromId, string $subject, string $body): int { - if ($toId === 0 || $toId === $fromId) { - // Don't allow users to send messages to the system or themselves - return 0; - } - - $qid = self::$db->get_query_id(); - self::$db->begin_transaction(); - self::$db->prepared_query(" - INSERT INTO pm_conversations (Subject) VALUES (?) - ", mb_substr($subject, 0, 255) - ); - $convId = self::$db->inserted_id(); - - $placeholders = ["(?, ?, '1', '0', '1')"]; - $args = [$toId, $convId]; - if ($fromId !== 0) { - $placeholders[] = "(?, ?, '0', '1', '0')"; - $args = array_merge($args, [$fromId, $convId]); - } - - self::$db->prepared_query(" - INSERT INTO pm_conversations_users - (UserID, ConvID, InInbox, InSentbox, UnRead) - VALUES - " . implode(', ', $placeholders), ...$args - ); - $this->deliverPM($toId, $fromId, $subject, $body, $convId); - self::$db->commit(); - self::$db->set_query_id($qid); - - return $convId; - } - /** * Send a reply from $FromId to $ToId. * @@ -565,7 +526,7 @@ public function sendCustomPM(\Gazelle\User $sender, string $subject, string $tem continue; } $message = preg_replace('/%USERNAME%/', $user->username(), $template); - $this->sendPM($userId, $sender->id(), $subject, $message); + $user->inbox()->create($sender, $subject, $message); $total++; } return $total; @@ -579,7 +540,10 @@ public function sendSnatchPm(\Gazelle\User $viewer, \Gazelle\Torrent $torrent, $snatchers = self::$db->collect(0, false); foreach ($snatchers as $userId) { - $this->sendPM($userId, 0, $subject, $body); + $user = $this->findById($userId); + if ($user) { + $user->inbox()->createSystem($subject, $body); + } } $total = count($snatchers); (new \Gazelle\Log)->general($viewer->username() . " sent a mass PM to $total snatcher" . plural($total) @@ -600,7 +564,10 @@ public function sendRemovalPm(int $torrentId, int $uploaderId, string $name, str . "."; if ($pmUploader) { - $this->sendPM($uploaderId, 0, $subject, sprintf($message, 'you uploaded')); + $user = $this->findById($uploaderId); + if ($user) { + $user->inbox()->createSystem($subject, sprintf($message, 'you uploaded')); + } } $seen = [$uploaderId]; @@ -615,7 +582,10 @@ public function sendRemovalPm(int $torrentId, int $uploaderId, string $name, str ); $ids = self::$db->collect('uid'); foreach ($ids as $userId) { - $this->sendPM($userId, 0, $subject, sprintf($message, 'you are seeding')); + $user = $this->findById($userId); + if ($user) { + $user->inbox()->createSystem($subject, sprintf($message, 'you are seeding')); + } } $seen = array_merge($seen, $ids); @@ -630,7 +600,10 @@ public function sendRemovalPm(int $torrentId, int $uploaderId, string $name, str ); $ids = self::$db->collect('uid'); foreach ($ids as $userId) { - $this->sendPM($userId, 0, $subject, sprintf($message, 'you have snatched')); + $user = $this->findById($userId); + if ($user) { + $user->inbox()->createSystem($subject, sprintf($message, 'you have snatched')); + } } $seen = array_merge($seen, $ids); @@ -645,7 +618,10 @@ public function sendRemovalPm(int $torrentId, int $uploaderId, string $name, str ); $ids = self::$db->collect('UserID'); foreach ($ids as $userId) { - $this->sendPM($userId, 0, $subject, sprintf($message, 'you have downloaded')); + $user = $this->findById($userId); + if ($user) { + $user->inbox()->createSystem($subject, sprintf($message, 'you have downloaded')); + } } return count(array_merge($seen, $ids)); @@ -989,7 +965,7 @@ public function promote(\Gazelle\Task $task = null, bool $commit = true): int { $user->setField('PermissionID', $level['To']) ->addStaffNote("Class changed to $toClass by System") ->modify(); - $this->sendPM($userId, 0, + $user->inbox()->createSystem( "You have been promoted to $toClass", "Congratulations on your promotion to $toClass!\n\nTo read more about " . SITE_NAME @@ -1046,7 +1022,7 @@ public function demote(\Gazelle\Task $task = null, bool $commit = true): int { $user->setField('PermissionID', $level['From']) ->addStaffNote("Class changed to $toClass by System") ->modify(); - $this->sendPM($userId, 0, + $user->inbox()->createSystem( "You have been demoted to $toClass", "You now only qualify for the \"$toClass\" user class.\n\nTo read more about " . SITE_NAME @@ -1371,13 +1347,13 @@ public function ratioWatchBlock(\Gazelle\Tracker $tracker, ?\Gazelle\Task $task $user->setField('can_leech', 0) ->addStaffNote("Leeching privileges suspended by ratio watch system (required ratio: $ratio) for downloading more than 10 GBs on ratio watch.") ->modify(); - $tracker->update_tracker('update_user', ['passkey' => $user->announceKey(), 'can_leech' => '0']); - $this->sendPM( $userId, 0, + $user->inbox()->createSystem( 'Your download privileges have been removed', 'You have downloaded more than 10 GB while on Ratio Watch. Your leeching privileges have been suspended. Please reread the rules and refer to this guide on [url=wiki.php?action=article&name=ratiotips]how to improve your ratio[/url]', ); - $processed++; + $tracker->update_tracker('update_user', ['passkey' => $user->announceKey(), 'can_leech' => '0']); $task?->debug("Disabling leech for {$user->label()}", $userId); + $processed++; } self::$db->commit(); return $processed; @@ -1409,13 +1385,12 @@ public function ratioWatchClear(\Gazelle\Tracker $tracker, ?\Gazelle\Task $task if (is_null($user)) { continue; } - $user->flush(); - $tracker->update_tracker('update_user', ['passkey' => $user->announceKey(), 'can_leech' => '1']); - $task?->debug("Taking {$user->label()} off ratio watch", $userId); - $this->sendPM($userId, 0, + $user->inbox()->createSystem( 'You have been taken off Ratio Watch', "Congratulations! Feel free to begin downloading again.\n To ensure that you do not get put on ratio watch again, please read the rules located [url=rules.php?p=ratio]here[/url].\n" ); + $tracker->update_tracker('update_user', ['passkey' => $user->announceKey(), 'can_leech' => '1']); + $task?->debug("Taking {$user->label()} off ratio watch", $userId); $processed++; } self::$db->commit(); @@ -1445,7 +1420,7 @@ public function ratioWatchEngage(\Gazelle\Tracker $tracker, \Gazelle\Task $task ->addStaffNote("Leeching ability suspended by ratio watch system (required ratio: $ratio)") ->modify(); $tracker->update_tracker('update_user', ['passkey' => $user->announceKey(), 'can_leech' => '0']); - $this->sendPM($userId, 0, + $user->inbox()->createSystem( 'Your downloading privileges have been suspended', "As you did not raise your ratio in time, your downloading privileges have been revoked. You will not be able to download any torrents until your ratio is above your new required ratio." ); @@ -1481,13 +1456,12 @@ public function ratioWatchSet(?\Gazelle\Task $task = null): int { if (is_null($user)) { continue; } - $user->flush(); - $this->sendPM($userId, 0, + $user->inbox()->createSystem( 'You have been put on Ratio Watch', "This happens when your ratio falls below the requirements outlined in the rules located [url=rules.php?p=ratio]here[/url].\n For information about ratio watch, click the link above." ); - $processed++; $task?->debug("Putting $userId on ratio watch", $userId); + $processed++; } self::$db->commit(); return $processed; diff --git a/app/PM.php b/app/PM.php index 74d5fdd0d..49f4bfad6 100644 --- a/app/PM.php +++ b/app/PM.php @@ -2,15 +2,24 @@ namespace Gazelle; +/** + * A PM object is created from an inbox, either from a user with + * User\Inbox::create(), or a system PM via User\Inbox::createSystem() + * + * Instantiating a PM object afterwards necessarily requires a user + * object. This is the simplest, more secure method to ensure some + * random user cannot access another PM object by accident. + */ + class PM extends Base { protected const CACHE_KEY = 'pm_%d_%d'; - protected array $info = []; + protected array|null $info; public function __construct( protected int $id, protected User $user - ) { } + ) {} public function id(): int { return $this->id; @@ -18,75 +27,75 @@ public function id(): int { public function flush(): static { self::$cache->delete_value(sprintf(self::CACHE_KEY, $this->id, $this->user->id())); - $this->info = []; + unset($this->info); return $this; } public function info(): array { - if (empty($this->info)) { - $key = sprintf(self::CACHE_KEY, $this->id, $this->user->id()); - $info = self::$cache->get_value($key); - if ($info === false) { - $info = self::$db->rowAssoc(" - SELECT c.Subject AS subject, - cu.Sticky AS pinned, - cu.UnRead AS unread, - cu.ForwardedTo AS forwarded_to, - cu.SentDate AS sent_date, - cu2.UserID AS sender_id - FROM pm_conversations AS c - INNER JOIN pm_conversations_users cu ON (cu.ConvID = c.ID) - LEFT JOIN pm_conversations_users cu2 ON (cu2.ConvID = c.ID AND cu2.UserID != ?) - WHERE c.ID = ? - AND cu.UserID = ? - ", $this->user->id(), $this->id, $this->user->id() - ); - foreach (['pinned', 'unread'] as $field) { - $info[$field] = ($info[$field] == '1'); - } - - // get the senders who have sent a message in this thread - self::$db->prepared_query(" - SELECT DISTINCT pm.SenderID - FROM pm_messages pm - WHERE pm.ConvID = ? - ", $this->id - ); - $info['sender_list'] = self::$db->collect(0, false); - - // get the recipients of messages in this thread. - self::$db->prepared_query(" - SELECT DISTINCT cu.UserID - FROM pm_conversations_users cu - WHERE cu.ForwardedTo IN (0, cu.UserID) - AND cu.ConvID = ? - AND cu.UserID != ? - ", $this->id, $this->user->id() - ); - $info['recipient_list'] = [$this->user->id(), ...self::$db->collect(0, false)]; - self::$cache->cache_value($key, $info, 86400); - $info['from_cache'] = false; + if (isset($this->info)) { + return $this->info; + } + $key = sprintf(self::CACHE_KEY, $this->id, $this->user->id()); + $info = self::$cache->get_value($key); + if ($info === false) { + $info = self::$db->rowAssoc(" + SELECT c.Subject AS subject, + cu.Sticky AS pinned, + cu.UnRead AS unread, + cu.ForwardedTo AS forwarded_to, + cu.SentDate AS sent_date, + cu2.UserID AS sender_id + FROM pm_conversations AS c + INNER JOIN pm_conversations_users cu ON (cu.ConvID = c.ID) + LEFT JOIN pm_conversations_users cu2 ON (cu2.ConvID = c.ID AND cu2.UserID != ?) + WHERE c.ID = ? + AND cu.UserID = ? + ", $this->user->id(), $this->id, $this->user->id() + ); + foreach (['pinned', 'unread'] as $field) { + $info[$field] = ($info[$field] == '1'); } - $this->info = $info; - $manager = new Manager\User; - $this->info['forwarded_to'] = $manager->findById($this->info['forwarded_to'] ?? 0); + // get the senders who have sent a message in this thread + self::$db->prepared_query(" + SELECT DISTINCT pm.SenderID + FROM pm_messages pm + WHERE pm.ConvID = ? + ", $this->id + ); + $info['sender_list'] = self::$db->collect(0, false); - $this->info['sender'] = []; - foreach ($this->info['sender_list'] as $userId) { - $this->info['sender'][$userId] = $manager->findById($userId); - } + // get the recipients of messages in this thread. + self::$db->prepared_query(" + SELECT DISTINCT cu.UserID + FROM pm_conversations_users cu + WHERE cu.ForwardedTo IN (0, cu.UserID) + AND cu.ConvID = ? + AND cu.UserID != ? + ", $this->id, $this->user->id() + ); + $info['recipient_list'] = [$this->user->id(), ...self::$db->collect(0, false)]; + self::$cache->cache_value($key, $info, 86400); + } + $this->info = $info; + + $manager = new Manager\User; + $this->info['forwarded_to'] = $manager->findById($this->info['forwarded_to'] ?? 0); + + $this->info['sender'] = []; + foreach ($this->info['sender_list'] as $userId) { + $this->info['sender'][$userId] = $manager->findById($userId); + } - // If the viewer has lost PM privileges, non-Staff recipients are filtered out - $this->info['recipient'] = []; - $pmRevoked = $this->user->disablePm(); - foreach ($this->info['recipient_list'] as $userId) { - $recipient = $manager->findById($userId); - if ($pmRevoked && !$recipient->isStaff()) { - continue; - } - $this->info['recipient'][] = $userId; + // If the viewer has lost PM privileges, non-Staff recipients are filtered out + $this->info['recipient'] = []; + $pmRevoked = $this->user->disablePm(); + foreach ($this->info['recipient_list'] as $userId) { + $recipient = $manager->findById($userId); + if ($pmRevoked && !$recipient->isStaff()) { + continue; } + $this->info['recipient'][] = $userId; } return $this->info; } diff --git a/app/Request.php b/app/Request.php index a1982a694..ce1e52760 100644 --- a/app/Request.php +++ b/app/Request.php @@ -569,14 +569,12 @@ public function fill(User $user, Torrent $torrent): int { SELECT UserID FROM requests_votes WHERE RequestID = ? ", $this->id ); - $ids = self::$db->collect(0, false); - $userMan = new Manager\User; - foreach ($ids as $userId) { - $userMan->sendPM($userId, 0, "The request \"$name\" has been filled", $message); + foreach (self::$db->collect(0, false) as $userId) { + (new User($userId))->inbox()->createSystem("The request \"$name\" has been filled", $message); } - (new Log)->general("Request " . $this->id . " ($name) was filled by " . $user->label() - . " with the torrent " . $torrent->id() . " for a " + (new Log)->general( + "Request {$this->id} ($name) was filled by {$user->label()} with the torrent {$torrent->id()} for a " . byte_format($bounty) . ' bounty.' ); @@ -617,7 +615,8 @@ public function unfill(User $admin, string $reason, Manager\Torrent $torMan): in ); if ($filler->id() !== $admin->id()) { - (new Manager\User)->sendPM($filler->id(), 0, 'A request you filled has been unfilled', + $filler->inbox()->createSystem( + 'A request you filled has been unfilled', self::$twig->render('request/unfill-pm.bbcode.twig', [ 'name' => $name, 'reason' => $reason, @@ -749,8 +748,9 @@ public function informRequestFillerReduction(int $bounty, string $staffName): in ); $affected = self::$db->affected_rows(); if ($affected) { - (new Manager\User)->sendPM($fillerId, 0, "Bounty was reduced on a request you filled", - self::$twig->render('request/bounty-reduction.twig', [ + (new User($fillerId))->inbox()->createSystem( + "Bounty was reduced on a request you filled", + self::$twig->render('request/bounty-reduction.bbcode.twig', [ 'bounty' => $bounty, 'fill_date' => $fillDate, 'request_url' => $this->url(), diff --git a/app/Task/DemoteUsersRatio.php b/app/Task/DemoteUsersRatio.php index c239a684a..c776712f3 100644 --- a/app/Task/DemoteUsersRatio.php +++ b/app/Task/DemoteUsersRatio.php @@ -58,11 +58,15 @@ private function demote(int $newClass, float $ratio, int $upload, array $demoteC self::$db->set_query_id($query); $demotions = 0; - while ([$userID] = self::$db->next_record()) { + while ([$userId] = self::$db->next_record()) { + $user = $userMan->findById($userId); + if (is_null($user)) { + continue; + } $demotions++; - $this->debug("Demoting $userID to $classString for insufficient ratio", $userID); - self::$cache->delete_value("u_$userID"); - $userMan->sendPM($userID, 0, + $this->debug("Demoting $userId to $classString for insufficient ratio", $userId); + $user->flush(); + $user->inbox()->createSystem( "You have been demoted to $classString", "You now only meet the requirements for the \"$classString\" user class.\n\nTo read more about " . SITE_NAME diff --git a/app/Torrent.php b/app/Torrent.php index df29fd9bc..2bf6bd551 100644 --- a/app/Torrent.php +++ b/app/Torrent.php @@ -254,7 +254,7 @@ public function removeLogDb(): int { * * @return int number of people messaged */ - public function issueReseedRequest(User $viewer): int { + public function issueReseedRequest(User $viewer, Manager\User $userMan): int { self::$db->prepared_query(' UPDATE torrents SET LastReseedRequest = now() @@ -281,21 +281,24 @@ public function issueReseedRequest(User $viewer): int { 'tdate' => $this->created(), ]; - $userMan = new Manager\User; $groupId = $this->groupId(); $name = $this->group()->text(); $torrentId = $this->id; foreach ($notify as $userId => $info) { - $userMan->sendPM($userId, 0, + $user = $userMan->findById($userId); + if (is_null($user)) { + continue; + } + $user->inbox()->createSystem( "Re-seed request for torrent $name", - self::$twig->render('torrent/reseed-pm.twig', [ + self::$twig->render('torrent/reseed-pm.bbcode.twig', [ 'action' => $info['action'], 'date' => $info['tdate'], 'group_id' => $groupId, 'torrent_id' => $torrentId, 'name' => $name, - 'user' => new User($userId), + 'user' => $user, 'viewer' => $viewer, ]) ); diff --git a/app/Torrent/Reaper.php b/app/Torrent/Reaper.php index 2ecdca2d5..aa785782f 100644 --- a/app/Torrent/Reaper.php +++ b/app/Torrent/Reaper.php @@ -42,10 +42,8 @@ public function process(array $userList, ReaperState $state, ReaperNotify $notif /** * Send a PM to a seeder listing their unseeded items - * - * @return int PM conversation id */ - public function notifySeeder(\Gazelle\User $user, array $ids, ReaperState $state, ReaperNotify $notify): ?int { + public function notifySeeder(\Gazelle\User $user, array $ids, ReaperState $state, ReaperNotify $notify): ?\Gazelle\PM { if ($user->hasAttr($state->notifyAttr())) { // No conversation will be created as they didn't ask for one return null; @@ -53,12 +51,12 @@ public function notifySeeder(\Gazelle\User $user, array $ids, ReaperState $state $never = $state === ReaperState::NEVER; $final = $notify === ReaperNotify::FINAL; $total = count($ids); - $subject = ($never ? "You have " : "There " . ($total > 1 ? 'are' : 'is') . " ") - . article($total, $never ? 'a' : 'an') // "a non-seeded" versus "an unseeded" - . ($never ? " non-seeded new upload" : " unseeded upload") - . plural($total) - . ($final ? " scheduled for deletion very soon" : " to rescue"); - return $this->userMan->sendPM($user->id(), 0, $subject, + return $user->inbox()->createSystem( + ($never ? "You have " : "There " . ($total > 1 ? 'are' : 'is') . " ") + . article($total, $never ? 'a' : 'an') // "a non-seeded" versus "an unseeded" + . ($never ? " non-seeded new upload" : " unseeded upload") + . plural($total) + . ($final ? " scheduled for deletion very soon" : " to rescue"), self::$twig->render('notification/unseeded.bbcode.twig', [ 'final' => $final, 'never' => $never, @@ -148,12 +146,10 @@ public function initialUnseededList(): array { * Send a PM to a snatcher with their unseeded uploads. * NB: A message is sent only on the initial phase. On the final round, * messages are sent only to the seeders. - * - * @return int PM conversation id */ - public function notifySnatcher(\Gazelle\User $user, array $ids): int { + public function notifySnatcher(\Gazelle\User $user, array $ids): \Gazelle\PM { $total = count($ids); - return $this->userMan->sendPM($user->id(), 0, + return $user->inbox()->createSystem( "You have " . article($total, 'an') . " unseeded snatch" . plural($total, 'es') . ' to save', self::$twig->render('notification/unseeded-snatch.bbcode.twig', [ 'list' => $ids, @@ -279,7 +275,7 @@ public function removeNeverSeeded(): int { return $this->remove( reaperList: $this->reaperList(ReaperState::NEVER, REMOVE_NEVER_SEEDED_HOUR), reason: 'inactivity (never seeded)', - template: 'notification/removed-never-seeded.twig', + template: 'notification/removed-never-seeded.bbcode.twig', ); } @@ -287,7 +283,7 @@ public function removeUnseeded(): int { return $this->remove( reaperList: $this->reaperList(ReaperState::UNSEEDED, REMOVE_UNSEEDED_HOUR), reason: 'inactivity (unseeded)', - template: 'notification/removed-unseeded.twig', + template: 'notification/removed-unseeded.bbcode.twig', ); } @@ -362,8 +358,11 @@ protected function remove(array $reaperList, string $reason, string $template): if ($notes) { $total = count($notes); $user = $this->userMan->findById($userId); - if ($user?->isEnabled()) { - $this->userMan->sendPM($userId, 0, + if (is_null($user)) { + continue; + } + if ($user->isEnabled()) { + $user->inbox()->createSystem( "$total of your uploads " . ($total == 1 ? 'has' : 'have') . " been deleted for $reason", self::$twig->render($template, [ 'notes' => $notes, @@ -377,11 +376,14 @@ protected function remove(array $reaperList, string $reason, string $template): // now inform the snatchers of all the torrents that were reaped during this run foreach ($userList as $userId => $torrentList) { $user = $this->userMan->findById($userId); - if ($user?->isEnabled()) { + if (is_null($user)) { + continue; + } + if ($user->isEnabled()) { $total = count($torrentList); - $this->userMan->sendPM($userId, 0, + $user->inbox()->createSystem( "$total of your snatches " . ($total == 1 ? 'was' : 'were') . " deleted for inactivity", - self::$twig->render('notification/removed-unseeded-snatch.twig', [ + self::$twig->render('notification/removed-unseeded-snatch.bbcode.twig', [ 'list' => $torrentList, 'user' => $user, ]) @@ -516,9 +518,9 @@ public function notifyWinner(\Gazelle\Torrent $torrent, \Gazelle\User\Bonus $bo $points = REAPER_RESEED_REWARD_FACTOR * $bonus->torrentValue($torrent); $bonus->addPoints($points); $bonus->user()->addStaffNote("Awarded {$points} BP for reseeding [pl]{$torrent->id()}[/pl]")->modify(); - $this->userMan->sendPM($bonus->user()->id(), 0, + $bonus->user()->inbox()->createSystem( "Thank you for reseeding {$torrent->group()->name()}!", - self::$twig->render('notification/reseed.twig', [ + self::$twig->render('notification/reseed.bbcode.twig', [ 'points' => $points, 'torrent' => $torrent, 'user' => $bonus->user(), diff --git a/app/User.php b/app/User.php index 0af226717..5b860da3c 100644 --- a/app/User.php +++ b/app/User.php @@ -48,6 +48,15 @@ public function flush(): static { public function link(): string { return sprintf('%s', $this->url(), html_escape($this->username())); } public function location(): string { return 'user.php?id=' . $this->id; } + /** + * Delegate snatch status methods to the User\Inbox class. + * A new object is instantiated each time. This is nearly + * always what you need, if just creating a new conversation. + */ + public function inbox(): User\Inbox { + return new User\Inbox($this); + } + /** * Delegate snatch status methods to the User\Snatch class */ @@ -1024,27 +1033,32 @@ public function warn(int $duration, string $reason, \Gazelle\User $staff, string . "The warning is set to expire on $warnTime. Remember, repeated warnings may jeopardize " . "your account.\nReason: $userMessage"; } - (new \Gazelle\Manager\User)->sendPM($this->id(), 0, $subject, $message); + $this->inbox()->createSystem($subject, $message); return $warning->add($reason, "$duration week" . plural($duration), $staff); } /** * Issue a warning for a comment or forum post */ - public function warnPost(BaseObject $post, int $weekDuration, \Gazelle\User $staffer, - string $staffReason, string $userMessage): void { + public function warnPost( + BaseObject $post, + int $weekDuration, + \Gazelle\User $staffer, + string $staffReason, + string $userMessage +): void { if (!$weekDuration) { // verbal warning - $subject = 'You have received a verbal warning'; - $body = "You have received a verbal warning by [user]{$staffer->username()}[/user] for {$post->publicLocation()}.\n\n[quote]{$userMessage}[/quote]"; $warned = "Verbally warned"; - (new \Gazelle\Manager\User)->sendPM($this->id(), 0, $subject, $body); + $this->inbox()->createSystem( + "You have received a verbal warning", + "You have received a verbal warning by [user]{$staffer->username()}[/user] for {$post->publicLocation()}.\n\n[quote]{$userMessage}[/quote]" + ); } else { $message = "for {$post->publicLocation()}.\n\n[quote]{$userMessage}[/quote]"; $expiry = $this->warn($weekDuration, "{$post->publicLocation()} - $staffReason", $staffer, $message); $warned = "Warned until $expiry"; } - $this->addForumWarning( - "$warned by {$staffer->username()} for {$post->publicLocation()}\nReason: $staffReason") + $this->addForumWarning("$warned by {$staffer->username()} for {$post->publicLocation()}\nReason: $staffReason") ->modify(); } @@ -1274,22 +1288,6 @@ public function announceKeyHistory(): array { return self::$db->to_array(false, MYSQLI_ASSOC, false); } - public function inboxUnreadCount(): int { - $unread = self::$cache->get_value('inbox_new_' . $this->id); - if ($unread === false) { - $unread = (int)self::$db->scalar(" - SELECT count(*) - FROM pm_conversations_users - WHERE UnRead = '1' - AND InInbox = '1' - AND UserID = ? - ", $this->id - ); - self::$cache->cache_value('inbox_new_' . $this->id, $unread, 0); - } - return $unread; - } - public function supportCount(int $newClassId, int $levelClassId): int { return (int)self::$db->scalar(" SELECT count(DISTINCT DisplayStaff) diff --git a/app/User/Bonus.php b/app/User/Bonus.php index 5d90b5076..5e1231fd3 100644 --- a/app/User/Bonus.php +++ b/app/User/Bonus.php @@ -378,8 +378,8 @@ public function purchaseTokenOther(\Gazelle\User $receiver, string $label, strin return $amount; } - public function sendPmToOther(\Gazelle\User $receiver, int $amount, string $message): int { - return (new \Gazelle\Manager\User)->sendPM($receiver->id(), 0, + public function sendPmToOther(\Gazelle\User $receiver, int $amount, string $message): \Gazelle\PM { + return $receiver->inbox()->createSystem( "Here " . ($amount == 1 ? 'is' : 'are') . ' ' . article($amount) . " freeleech token" . plural($amount) . "!", self::$twig->render('bonus/token-other-message.bbcode.twig', [ 'to' => $receiver->username(), diff --git a/app/User/Donor.php b/app/User/Donor.php index 8864b100d..5420570b2 100644 --- a/app/User/Donor.php +++ b/app/User/Donor.php @@ -513,7 +513,7 @@ public function donate( } // Send them a thank you PM - $id = (new \Gazelle\Manager\User)->sendPM($this->id(), 0, + $this->user()->inbox()->createSystem( 'Your contribution has been received and credited. Thank you!', $this->messageBody($currency, $amount, $rankDelta, $this->rank()) ); @@ -576,7 +576,7 @@ public function calculateSpecialRank(): int { } if ($specialRank < 1 && $totalRank >= 10) { - (new \Gazelle\Manager\User)->sendPM($this->id(), 0, + $this->user()->inbox()->createSystem( "You have Reached Special Donor Rank #1! You've Earned: One Donor Pick. Details Inside.", self::$twig->render('donation/special-rank-1.twig', [ 'forum_url' => 'forums.php?action=viewthread&threadid=178640&postid=4839790#post4839790', @@ -587,7 +587,7 @@ public function calculateSpecialRank(): int { } if ($specialRank < 2 && $totalRank >= 20) { - (new \Gazelle\Manager\User)->sendPM($this->id(), 0, + $this->user()->inbox()->createSystem( "You have Reached Special Donor Rank #2! You've Earned: The Double-Avatar. Details Inside.", self::$twig->render('donation/special-rank-2.twig', [ 'forum_url' => 'forums.php?action=viewthread&threadid=178640&postid=4839790#post4839790', @@ -597,7 +597,7 @@ public function calculateSpecialRank(): int { } if ($specialRank < 3 && $totalRank >= 50) { - (new \Gazelle\Manager\User)->sendPM($this->id(), 0, + $this->user()->inbox()->createSystem( "You have Reached Special Donor Rank #3! You've Earned: Diamond Rank. Details Inside.", self::$twig->render('donation/special-rank-3.twig', [ 'forum_url' => 'forums.php?action=viewthread&threadid=178640&postid=4839790#post4839790', diff --git a/app/User/Inbox.php b/app/User/Inbox.php index c374cbb53..9e8ddcd35 100644 --- a/app/User/Inbox.php +++ b/app/User/Inbox.php @@ -5,13 +5,81 @@ class Inbox extends \Gazelle\BaseUser { final const tableName = 'pm_conversations_users'; + final const CACHE_NEW = 'inbox_new_%d'; + protected bool $unreadFirst; protected string $filter; protected string $folder = 'inbox'; protected string $searchField = 'user'; protected string $searchTerm; - public function flush(): static { $this->user()->flush(); return $this; } + public function __construct(\Gazelle\User $user) { + parent::__construct($user); + } + + public function flush(): static { + self::$cache->delete_value(sprintf(self::CACHE_NEW, $this->id())); + $this->user->flush(); + return $this; + } + + public function createSystem(string $subject, string $body): ?\Gazelle\PM { + return $this->create(null, $subject, $body); + } + + /** + * To send a message to a user, you instantiate their inbox and + * create() a PM + */ + public function create(?\Gazelle\User $from, string $subject, string $body): ?\Gazelle\PM { + $fromId = $from?->id() ?? 0; + if ($this->id() === $fromId) { + // Don't allow users to send messages to the system or themselves + return null; + } + + $qid = self::$db->get_query_id(); + self::$db->begin_transaction(); + self::$db->prepared_query(" + INSERT INTO pm_conversations (Subject) VALUES (?) + ", mb_substr($subject, 0, 255) + ); + $convId = self::$db->inserted_id(); + + $placeholders = [ + "(?, ?, '1', '0', '1')", + "(?, ?, '0', '1', '0')", + ]; + $args = [$this->id(), $convId, $fromId, $convId]; + + self::$db->prepared_query(" + INSERT INTO pm_conversations_users + (UserID, ConvID, InInbox, InSentbox, UnRead) + VALUES + " . implode(', ', $placeholders), ...$args + ); + self::$db->prepared_query(" + INSERT INTO pm_messages + (SenderID, ConvID, Body) + VALUES (?, ?, ?) + ", $fromId, $convId, $body + ); + self::$db->commit(); + self::$db->set_query_id($qid); + + $senderName = $from?->username() ?? 'System'; + (new \Gazelle\Manager\Notification)->push( + [$this->id()], + "Message from $senderName, Subject: $subject", $body, SITE_URL . '/inbox.php', \Gazelle\Manager\Notification::INBOX, + ); + $this->flush(); + self::$cache->delete_multi([ + "pm_{$convId}_{$fromId}", + "pm_{$convId}_{$this->id()}", + ]); + + return new \Gazelle\PM($convId, $this->user); + } public function setFilter(string $filter): static { $this->filter = $filter; @@ -119,6 +187,23 @@ protected function configure(): array { return [$cond, $args]; } + public function unreadTotal(): int { + $key = sprintf(self::CACHE_NEW, $this->id()); + $unread = self::$cache->get_value($key); + if ($unread === false) { + $unread = (int)self::$db->scalar(" + SELECT count(*) + FROM pm_conversations_users + WHERE UnRead = '1' + AND InInbox = '1' + AND UserID = ? + ", $this->id() + ); + self::$cache->cache_value($key, $unread, 0); + } + return $unread; + } + public function messageTotal(): int { [$cond, $args] = $this->configure(); $where = $cond ? ("WHERE " . implode(' AND ', $cond)) : '/* all */'; @@ -156,7 +241,7 @@ public function messageList(\Gazelle\Manager\PM $pmMan, int $limit, int $offset) protected function massFlush(array $ids): void { $userId = $this->user->id(); - self::$cache->delete_multi(["inbox_new_$userId", ...array_map(fn ($id) => "pm_{$id}_{$userId}", $ids)]); + self::$cache->delete_multi([sprintf(self::CACHE_NEW, $userId), ...array_map(fn ($id) => "pm_{$id}_{$userId}", $ids)]); } public function massRemove(array $ids): int { diff --git a/app/User/Notification/Inbox.php b/app/User/Notification/Inbox.php index 137a5825f..5302de577 100644 --- a/app/User/Notification/Inbox.php +++ b/app/User/Notification/Inbox.php @@ -20,7 +20,7 @@ public function clear(): int { } public function load(): bool { - $total = $this->user->inboxUnreadCount(); + $total = $this->user->inbox()->unreadTotal(); if ($total) { $this->title = 'You have ' . article($total) . ' new message' . plural($total); $this->url = "inbox.php"; diff --git a/bin/remove-upload b/bin/remove-upload index 14941e1fc..2ad44f55f 100755 --- a/bin/remove-upload +++ b/bin/remove-upload @@ -39,7 +39,7 @@ foreach ($argv as $torrentId) { } $bonus->addPoints($points); - $userMan->sendPM($uploader->id(), 0, + $uploader->inbox()->createSystem( "Your upload $name has been removed", "Due to a transient bug, your upload $name was not completed by the backend. Since it did not break any rules (we hope), please feel free to re-upload it. Since this is on us, you have been gifted $points points." ); diff --git a/sections/ajax/info.php b/sections/ajax/info.php index df29243b3..15a5dded9 100644 --- a/sections/ajax/info.php +++ b/sections/ajax/info.php @@ -12,8 +12,8 @@ $Ratio = number_format(max($Viewer->uploadedSize() / $Viewer->downloadedSize() - 0.005, 0), 2); //Subtract .005 to floor to 2 decimals } -$ClassLevels = (new Gazelle\Manager\User)->classLevelList(); -$latestBlog = (new Gazelle\Manager\Blog)->latest(); +$ClassLevels = (new \Gazelle\Manager\User)->classLevelList(); +$latestBlog = (new \Gazelle\Manager\Blog)->latest(); json_print("success", [ 'username' => $Viewer->username(), @@ -21,11 +21,11 @@ 'authkey' => $Viewer->auth(), 'passkey' => $Viewer->announceKey(), 'notifications' => [ - 'messages' => $Viewer->inboxUnreadCount(), - 'notifications' => (new Gazelle\User\Notification\Torrent($Viewer))->unread(), - 'newAnnouncement' => (new Gazelle\Manager\News)->latestId() < (new Gazelle\WitnessTable\UserReadNews)->lastRead($Viewer->id()), - 'newBlog' => $latestBlog && $latestBlog->createdEpoch() < (new Gazelle\WitnessTable\UserReadBlog)->lastRead($Viewer->id()), - 'newSubscriptions' => (new Gazelle\User\Subscription($Viewer))->unread() > 0, + 'messages' => $Viewer->inbox()->unreadTotal(), + 'notifications' => (new \Gazelle\User\Notification\Torrent($Viewer))->unread(), + 'newAnnouncement' => (new \Gazelle\Manager\News)->latestId() < (new \Gazelle\WitnessTable\UserReadNews)->lastRead($Viewer->id()), + 'newBlog' => $latestBlog && $latestBlog->createdEpoch() < (new \Gazelle\WitnessTable\UserReadBlog)->lastRead($Viewer->id()), + 'newSubscriptions' => (new \Gazelle\User\Subscription($Viewer))->unread() > 0, ], 'userstats' => [ 'uploaded' => $Viewer->uploadedSize(), @@ -33,7 +33,7 @@ 'ratio' => (float)$Ratio, 'requiredratio' => $Viewer->requiredRatio(), 'bonusPoints' => $Viewer->bonusPointsTotal(), - 'bonusPointsPerHour' => round((new Gazelle\User\Bonus($Viewer))->hourlyRate(), 2), + 'bonusPointsPerHour' => round((new \Gazelle\User\Bonus($Viewer))->hourlyRate(), 2), 'class' => $Viewer->userclassName(), ] ]); diff --git a/sections/comments/edit_handle.php b/sections/comments/edit_handle.php index 3191bb36e..26d35b558 100644 --- a/sections/comments/edit_handle.php +++ b/sections/comments/edit_handle.php @@ -17,16 +17,18 @@ if ($comment->userId() != $Viewer->id() && !$Viewer->permitted('site_moderate_forums')) { error(403, true); } +$user = (new Gazelle\Manager\User)->findById($comment->userId()); +if (is_null($user)) { + error(0, true); +} $comment->setField('Body', $body)->setField('EditedUserID', $Viewer->id())->modify(); if ((bool)($_POST['pm'] ?? false) && !$comment->isAuthor($Viewer->id())) { // Send a PM to the user to notify them of the edit - $id = $comment->id(); $url = $comment->publicUrl('action=jump'); - $moderator = "[url=" . $Viewer->url() . "]" . $Viewer->username() . "[/url]"; - (new Gazelle\Manager\User)-> sendPM($comment->userId(), 0, - "Your comment #$id has been edited", - "One of your comments has been edited by $moderator: [url]{$url}[/url]" + $user->inbox()->createSystem( + "Your comment #{$comment->id()} has been edited", + "One of your comments has been edited by [url={$Viewer->url()}]{$Viewer->username()}[/url]: [url]{$url}[/url]" ); } diff --git a/sections/comments/warn_handle.php b/sections/comments/warn_handle.php index 974c100f2..8b2d269cd 100644 --- a/sections/comments/warn_handle.php +++ b/sections/comments/warn_handle.php @@ -1,6 +1,7 @@ setField('Body', $body) ->setField('EditedUserID', $Viewer->id())->modify(); diff --git a/sections/forums/edit_handle.php b/sections/forums/edit_handle.php index bdca3ae0c..da68a93a2 100644 --- a/sections/forums/edit_handle.php +++ b/sections/forums/edit_handle.php @@ -22,11 +22,13 @@ error("You cannot edit someone else's post", true); } if ($_POST['pm'] ?? 0) { - (new Gazelle\Manager\User)->sendPM($post->userId(), 0, + $user = (new Gazelle\Manager\User)->findById($post->userId()); + if (is_null($user)) { + error(0); + } + $user->inbox()->createSystem( "Your post #{$post->id()} has been edited", - sprintf('One of your posts has been edited by [url=%s]%s[/url]: [url]%s[/url]', - $Viewer->url(), $Viewer->username(), $post->url() - ) + "One of your posts has been edited by [url={$Viewer->url()}]{$Viewer->username()}[/url]: [url]{$post->url()}[/url]" ); } } diff --git a/sections/inbox/compose_handle.php b/sections/inbox/compose_handle.php index f06bfcef1..cc58f1480 100644 --- a/sections/inbox/compose_handle.php +++ b/sections/inbox/compose_handle.php @@ -25,8 +25,7 @@ if (empty($subject)) { error('You cannot send a message without a subject.'); } - $pmId = $userMan->sendPM($recipient->id(), $Viewer->id(), $subject, $body); - $pm = new Gazelle\PM($pmId, $Viewer); + $pm = $recipient->inbox()->create($Viewer, $subject, $body); } (new Gazelle\Manager\Notification)->push([$recipient->id()], diff --git a/sections/inbox/conversation.php b/sections/inbox/conversation.php index df4b1a5ed..bae4eb359 100644 --- a/sections/inbox/conversation.php +++ b/sections/inbox/conversation.php @@ -11,7 +11,7 @@ $paginator->setTotal($postTotal); echo $Twig->render('inbox/conversation.twig', [ - 'inbox' => (new Gazelle\User\Inbox($Viewer))->setFolder($_GET['section'] ?? 'inbox'), + 'inbox' => $Viewer->inbox()->setFolder($_GET['section'] ?? 'inbox'), 'paginator' => $paginator, 'pm' => $pm, 'post_list' => $pm->postList($paginator->limit(), $paginator->offset()), diff --git a/sections/inbox/inbox.php b/sections/inbox/inbox.php index f5e9069d9..4d0f8c51b 100644 --- a/sections/inbox/inbox.php +++ b/sections/inbox/inbox.php @@ -1,6 +1,6 @@ inbox(); $inbox->setFolder($_GET['section'] ?? $_GET['action'] ?? 'inbox'); if (isset($_GET['searchtype'])) { $inbox->setSearchField($_GET['searchtype']); diff --git a/sections/inbox/massdelete_handle.php b/sections/inbox/massdelete_handle.php index 368a5cec5..681535c2a 100644 --- a/sections/inbox/massdelete_handle.php +++ b/sections/inbox/massdelete_handle.php @@ -15,7 +15,7 @@ error("You forgot to select any messages to $action."); } -$inbox = new Gazelle\User\Inbox($Viewer); +$inbox = $Viewer->inbox(); $inbox->setFolder($_POST['section'] ?? 'inbox'); if (isset($_POST['delete'])) { diff --git a/sections/register/index.php b/sections/register/index.php index c8bbffcc2..b36f54f69 100644 --- a/sections/register/index.php +++ b/sections/register/index.php @@ -13,11 +13,9 @@ } $user = $token->user(); $user->setField('Enabled', UserStatus::enabled->value)->modify(); - (new Gazelle\Manager\User)->sendPM($user->id(), 0, + $user->inbox()->createSystem( "Welcome to " . SITE_NAME, - $Twig->render('register/welcome.twig', [ - 'user' => $user, - ]) + $Twig->render('register/welcome.bbcode.twig', ['user' => $user]) ); (new Gazelle\Tracker)->update_tracker('add_user', ['id' => $user->id(), 'passkey' => $user->announceKey()]); $Cache->increment('stats_user_count'); diff --git a/sections/reports/compose_handle.php b/sections/reports/compose_handle.php index d689ba89a..3bf9e9150 100644 --- a/sections/reports/compose_handle.php +++ b/sections/reports/compose_handle.php @@ -12,12 +12,11 @@ if (empty($subject)) { error("You can't send a message without a subject."); } - $body = trim($_POST['body'] ?? ''); if ($body === '') { error("You can't send a message without a body!"); } -$userMan->sendPM($recipient->id(), $Viewer->id(), $subject, $body); +$recipient->inbox()->create($Viewer, $subject, $body); header('Location: reports.php'); diff --git a/sections/reportsv2/ajax_switch.php b/sections/reportsv2/ajax_switch.php index 377f64e96..adef53de8 100644 --- a/sections/reportsv2/ajax_switch.php +++ b/sections/reportsv2/ajax_switch.php @@ -39,7 +39,7 @@ ); if ($other->uploaderId() != $Viewer->id()) { - (new Gazelle\Manager\User)->sendPM($other->uploaderId(), 0, + $other->uploader()->inbox()->createSystem( "One of your torrents has been reported", $Twig->render('reportsv2/new.twig', [ 'id' => $other->id(), diff --git a/sections/reportsv2/ajax_take_pm.php b/sections/reportsv2/ajax_take_pm.php index 58f26cfb4..0b0e32628 100644 --- a/sections/reportsv2/ajax_take_pm.php +++ b/sections/reportsv2/ajax_take_pm.php @@ -48,12 +48,13 @@ json_error("no recipient target"); } -if (!$ToID) { +$recipient = (new Gazelle\Manager\User)->findById($ToID); +if (is_null($recipient)) { json_error("bad recipient id"); } elseif ($ToID == $Viewer->id()) { json_error("message to self"); } -$convId = (new Gazelle\Manager\User)->sendPM($ToID, $Viewer->id(), $_POST['raw_name'] ?? "Report", $Message); +$pm = $recipient->inbox()->create($Viewer, $_POST['raw_name'] ?? "Report", $Message); -echo "PM delivered, msg id $convId"; +echo "PM delivered, msg id {$pm->id()}"; diff --git a/sections/reportsv2/report_handle.php b/sections/reportsv2/report_handle.php index 9f97039f0..7d1951203 100644 --- a/sections/reportsv2/report_handle.php +++ b/sections/reportsv2/report_handle.php @@ -113,9 +113,9 @@ ); if (!$reportType->isInvisible() && $torrent->uploaderId() != $Viewer->id()) { - (new Gazelle\Manager\User)->sendPM($torrent->uploaderId(), 0, + $torrent->uploader()->inbox()->createSystem( "One of your torrents has been reported", - $Twig->render('reportsv2/new.twig', [ + $Twig->render('reportsv2/new.bbcode.twig', [ 'id' => $torrent->id(), 'title' => $reportType->name(), 'reason' => $report->reason(), diff --git a/sections/reportsv2/resolve_handle.php b/sections/reportsv2/resolve_handle.php index caf483ea7..ff4f68a16 100644 --- a/sections/reportsv2/resolve_handle.php +++ b/sections/reportsv2/resolve_handle.php @@ -183,7 +183,7 @@ } $message[] = "Report was handled by [user]" . $Viewer->username() . "[/user]."; - $userMan->sendPM($uploader->id(), 0, $name, implode("\n\n", $message)); + $uploader->inbox()->createSystem($name, implode("\n\n", $message)); } $Cache->delete_value("reports_torrent_$torrentId"); diff --git a/sections/requests/delete_handle.php b/sections/requests/delete_handle.php index 045960352..8c812507d 100644 --- a/sections/requests/delete_handle.php +++ b/sections/requests/delete_handle.php @@ -13,11 +13,14 @@ $reason = trim($_POST['reason']); $title = $request->text(); if ($request->userId() !== $Viewer->id()) { - (new Gazelle\Manager\User)->sendPM($request->userId(), 0, - 'A request you created has been deleted', - "The request \"$title\" was deleted by [url=" . $Viewer->url() . "]" - . $Viewer->username() . "[/url] for the reason: [quote]{$reason}[/quote]" - ); + $user = (new Gazelle\Manager\User)->findById($request->userId()); + if ($user) { + $user->inbox()->createSystem( + 'A request you created has been deleted', + "The request \"$title\" was deleted by [url=" . $Viewer->url() . "]" + . $Viewer->username() . "[/url] for the reason: [quote]{$reason}[/quote]" + ); + } } $requestId = $request->id(); $request->remove(); diff --git a/sections/tools/managers/take_mass_pm.php b/sections/tools/managers/take_mass_pm.php index d7bc6075d..bf84490b3 100644 --- a/sections/tools/managers/take_mass_pm.php +++ b/sections/tools/managers/take_mass_pm.php @@ -13,20 +13,26 @@ set_time_limit(0); $permissionId = $_POST['class_id']; -$fromId = empty($_POST['from_system']) ? $Viewer->id() : 0; $db = Gazelle\DB::DB(); $db->prepared_query(" (SELECT ID AS UserID FROM users_main WHERE PermissionID = ? AND ID != ?) UNION DISTINCT (SELECT UserID FROM users_levels WHERE PermissionID = ? AND UserID != ?) - ", $permissionId, $fromId, $permissionId, $fromId + ", $permissionId, $Viewer->id(), $permissionId, $Viewer->id() ); $userMan = new Gazelle\Manager\User; $subject = trim($_POST['subject']); -$body = trim($_POST['body']); +$body = trim($_POST['body']); while ([$userId] = $db->next_record()) { - $userMan->sendPM($userId, $fromId, $subject, $body); + $user = $userMan->findById($userId); + if ($user) { + if (empty($_POST['from_system'])) { + $user->inbox()->createSystem($subject, $body); + } else { + $user->inbox()->create($Viewer, $subject, $body); + } + } } header("Location: tools.php"); diff --git a/sections/torrents/reseed.php b/sections/torrents/reseed.php index a853cc827..d9f72c22c 100644 --- a/sections/torrents/reseed.php +++ b/sections/torrents/reseed.php @@ -15,5 +15,5 @@ echo $Twig->render('torrent/reseed-result.twig', [ 'torrent' => $torrent, - 'total' => $torrent->issueReseedRequest($Viewer), + 'total' => $torrent->issueReseedRequest($Viewer, new \Gazelle\Manager\User), ]); diff --git a/sections/user/moderate_handle.php b/sections/user/moderate_handle.php index 4a999421e..e3704a85b 100644 --- a/sections/user/moderate_handle.php +++ b/sections/user/moderate_handle.php @@ -283,13 +283,14 @@ function revoked(bool $state): string { $duration = 'week' . plural($reduce); $expiry = $warning->add($reason, "$reduce $duration", $Viewer); $userMessage = trim($_POST['WarnReason'] ?? ''); - $subject = "Your warning has been reduced to $reduce $duration"; - $body = "Your warning has been reduced to $reduce $duration, set to expire at $expiry, " - . "by [user]{$Viewer->username()}[/user]."; + $body = "Your warning has been reduced to $reduce $duration, set to expire at $expiry, by [user]{$Viewer->username()}[/user]."; if ($userMessage) { $body .= " Reason:\n[quote]{$userMessage}[/quote]."; } - $userMan->sendPM($userId, 0, $subject, $body); + $user->inbox()->createSystem( + "Your warning has been reduced to $reduce $duration", + $body, + ); } elseif ($weeks || $extend) { $staffReason = $reason ?: ($extend ? 'warning extension' : 'no reason'); $user->warn($extend ?: $weeks, $staffReason, $Viewer, $_POST['WarnReason'] ?? 'none given'); @@ -462,8 +463,7 @@ function revoked(bool $state): string { if ($privChange && $userReason) { sort($privChange); - $userMan->sendPM( - $userId, 0, + $user->inbox()->createSystem( count($privChange) == 1 ? $privChange[0] : 'Multiple privileges have changed on your account', $Twig->render('user/pm-privilege.twig', [ 'privs' => $privChange, @@ -475,8 +475,8 @@ function revoked(bool $state): string { } $userStatus = match ($_POST['UserStatus']) { - '1' => UserStatus::enabled, - '2' => UserStatus::disabled, + '1' => UserStatus::enabled, + '2' => UserStatus::disabled, default => UserStatus::unconfirmed, }; if ($userStatus != $user->userStatus() && $Viewer->permitted('users_disable_users')) { diff --git a/templates/contest/payout-uploader.twig b/templates/contest/payout-uploader.bbcode.twig similarity index 100% rename from templates/contest/payout-uploader.twig rename to templates/contest/payout-uploader.bbcode.twig diff --git a/templates/login/too-many-failures.twig b/templates/login/too-many-failures.bbcode.twig similarity index 100% rename from templates/login/too-many-failures.twig rename to templates/login/too-many-failures.bbcode.twig diff --git a/templates/notification/removed-never-seeded.twig b/templates/notification/removed-never-seeded.bbcode.twig similarity index 100% rename from templates/notification/removed-never-seeded.twig rename to templates/notification/removed-never-seeded.bbcode.twig diff --git a/templates/notification/removed-unseeded-snatch.twig b/templates/notification/removed-unseeded-snatch.bbcode.twig similarity index 100% rename from templates/notification/removed-unseeded-snatch.twig rename to templates/notification/removed-unseeded-snatch.bbcode.twig diff --git a/templates/notification/removed-unseeded.twig b/templates/notification/removed-unseeded.bbcode.twig similarity index 100% rename from templates/notification/removed-unseeded.twig rename to templates/notification/removed-unseeded.bbcode.twig diff --git a/templates/notification/reseed.twig b/templates/notification/reseed.bbcode.twig similarity index 100% rename from templates/notification/reseed.twig rename to templates/notification/reseed.bbcode.twig diff --git a/templates/register/welcome.twig b/templates/register/welcome.bbcode.twig similarity index 100% rename from templates/register/welcome.twig rename to templates/register/welcome.bbcode.twig diff --git a/templates/reportsv2/new.twig b/templates/reportsv2/new.bbcode.twig similarity index 100% rename from templates/reportsv2/new.twig rename to templates/reportsv2/new.bbcode.twig diff --git a/templates/torrent/reseed-pm.twig b/templates/torrent/reseed-pm.bbcode.twig similarity index 100% rename from templates/torrent/reseed-pm.twig rename to templates/torrent/reseed-pm.bbcode.twig diff --git a/tests/helper.php b/tests/helper.php index 7e3211452..aa0942f9c 100644 --- a/tests/helper.php +++ b/tests/helper.php @@ -1,5 +1,7 @@ setUsername($username) ->setEmail(randomString(6) . "@{$tag}.example.com") ->setPassword(randomString()) ->setIpaddr('127.0.0.1') ->setAdminComment("Created by tests/helper/User($tag)") ->create(); + if ($enable) { + $user->setField('Enabled', UserStatus::enabled->value)->modify(); + } + if ($clearInbox) { + $user = self::clearInbox($user); + } + return $user; + } + + public static function clearInbox(\Gazelle\User $user): \Gazelle\User { + $pmMan = new \Gazelle\Manager\PM($user); + foreach ($user->inbox()->messageList($pmMan, 1, 0) as $pm) { + $pm->remove(); + } + return $user; } public static function makeUserByInvite(string $username, string $key): \Gazelle\User { diff --git a/tests/phpunit/CollageTest.php b/tests/phpunit/CollageTest.php index 2ac363844..3e9b64311 100644 --- a/tests/phpunit/CollageTest.php +++ b/tests/phpunit/CollageTest.php @@ -25,16 +25,10 @@ protected function tagList(int $n): array { public function setUp(): void { $this->userList = [ - 'u1' => Helper::makeUser('u1.' . randomString(6), 'collage'), - 'u2' => Helper::makeUser('u2.' . randomString(6), 'collage'), - 'u3' => Helper::makeUser('u3.' . randomString(6), 'collage'), + 'u1' => Helper::makeUser('u1.' . randomString(6), 'collage', clearInbox: true), + 'u2' => Helper::makeUser('u2.' . randomString(6), 'collage', clearInbox: true), + 'u3' => Helper::makeUser('u3.' . randomString(6), 'collage', clearInbox: true), ]; - foreach ($this->userList as $user) { - $pmMan = new Gazelle\Manager\PM($user); - foreach ((new Gazelle\User\Inbox($user))->messageList($pmMan, 1, 0) as $pm) { - $pm->remove(); - } - } $this->artistName = [ 'The phpunit ' . randomString(8) . ' Band', 'The phpunit ' . randomString(8) . ' Sisters', diff --git a/tests/phpunit/DonorTest.php b/tests/phpunit/DonorTest.php index 3bfcfb917..10f86690b 100644 --- a/tests/phpunit/DonorTest.php +++ b/tests/phpunit/DonorTest.php @@ -8,16 +8,10 @@ class DonorTest extends TestCase { protected Gazelle\User\Donor $donor; - protected function clearInbox(Gazelle\User $user): void { - $pmMan = new Gazelle\Manager\PM($user); - foreach ((new Gazelle\User\Inbox($user))->messageList($pmMan, 50, 0) as $pm) { - $pm->remove(); - } - } - public function setUp(): void { - $this->donor = new Gazelle\User\Donor(Helper::makeUser('donor.' . randomString(6), 'donor')); - $this->clearInbox($this->donor->user()); + $this->donor = new Gazelle\User\Donor( + Helper::makeUser('donor.' . randomString(6), 'donor', clearInbox: true) + ); } public function tearDown(): void { @@ -65,11 +59,11 @@ public function testDonorCreate(): void { $this->assertTrue($donor->isDonor(), 'donor-is-now-new'); $this->assertEquals(0, $donor->invitesReceived(), 'donor-no-invites'); - $inbox = new Gazelle\User\Inbox($donor->user()); + $inbox = $donor->user()->inbox(); $this->assertEquals(1, $inbox->messageTotal(), 'donor-inbox-small-total'); $list = $inbox->messageList(new Gazelle\Manager\PM($donor->user()), 1, 0); $this->assertStringContainsString('Your contribution has been received and credited', $list[0]->subject(), 'inbox-pm-subject'); - $this->clearInbox($donor->user()); + Helper::clearInbox($donor->user()); // second donation $this->assertEquals( @@ -197,7 +191,7 @@ public function testDonorRank(): void { 'donor-donate-rank-2' ); - $this->assertEquals(1, (new Gazelle\User\Inbox($donor->user()))->messageTotal(), 'donor-inbox-rank-2'); + $this->assertEquals(1, $donor->user()->inbox()->messageTotal(), 'donor-inbox-rank-2'); $this->assertEquals(2, $donor->rank(), 'donor-rank-2'); $this->assertEquals(2, $donor->totalRank(), 'donor-total-rank-2'); $this->assertEquals(0, $donor->specialRank(), 'donor-not-special-rank-2'); diff --git a/tests/phpunit/ForumTest.php b/tests/phpunit/ForumTest.php index 6e7aa9e43..ee8052f3c 100644 --- a/tests/phpunit/ForumTest.php +++ b/tests/phpunit/ForumTest.php @@ -283,7 +283,7 @@ public function testForumWarn(): void { autoLockWeeks: 42, ); $pmMan = new Gazelle\Manager\PM($user); - foreach ((new Gazelle\User\Inbox($user))->messageList($pmMan, 1, 0) as $pm) { + foreach ($user->inbox()->messageList($pmMan, 1, 0) as $pm) { $pm->remove(); } @@ -301,7 +301,7 @@ public function testForumWarn(): void { $this->assertStringStartsWith(date('Y-m-d H'), $user->forumWarning(), 'forum-user-warning-history-start'); /** @phpstan-ignore-line */ $this->assertStringEndsWith($message, $user->forumWarning(), 'forum-user-warning-history-end'); /** @phpstan-ignore-line */ - $inbox = new \Gazelle\User\Inbox($user); + $inbox = $user->inbox(); $pmReceiverManager = new Gazelle\Manager\PM($inbox->user()); $this->assertEquals(1, $inbox->messageTotal(), 'warn-user-inbox-total'); $pm = $inbox->messageList($pmReceiverManager, 1, 0)[0]; diff --git a/tests/phpunit/InboxTest.php b/tests/phpunit/InboxTest.php index 8a5bface3..0225fc2c5 100644 --- a/tests/phpunit/InboxTest.php +++ b/tests/phpunit/InboxTest.php @@ -13,75 +13,68 @@ public function tearDown(): void { $user->remove(); } } + public function testInbox(): void { $this->userList = [ - 'sender' => Helper::makeUser('inbox.send.' . randomString(6), 'inbox'), - 'receiver' => Helper::makeUser('inbox.recv.' . randomString(6), 'inbox'), + 'sender' => Helper::makeUser('inbox.send.' . randomString(6), 'inbox', clearInbox: true), + 'receiver' => Helper::makeUser('inbox.recv.' . randomString(6), 'inbox', clearInbox: true), ]; $senderId = $this->userList['sender']->id(); $receiverId = $this->userList['receiver']->id(); - // wipe their inboxes (there is only one message) - foreach ($this->userList as $user) { - $pmMan = new Gazelle\Manager\PM($user); - foreach ((new Gazelle\User\Inbox($user))->messageList($pmMan, 1, 0) as $pm) { - $pm->remove(); - } - } - $sender = new Gazelle\User\Inbox($this->userList['sender']); - $this->assertEquals('inbox.php?sort=latest', $sender->folderLink('inbox', false), 'inbox-folder-latest'); - $this->assertEquals('inbox.php?sort=unread', $sender->folderLink('inbox', true), 'inbox-folder-unread-first'); - $this->assertEquals('inbox.php?sort=latest§ion=sentbox', $sender->folderLink('sentbox', false), 'inbox-sentbox-folder-latest'); - $this->assertEquals('inbox.php?sort=unread§ion=sentbox', $sender->folderLink('sentbox', true), 'inbox-sentbox-folder-unread-first'); - $this->assertEquals('inbox', $sender->folder(), 'inbox-sender-inbox'); - $this->assertEquals('Inbox', $sender->folderTitle($sender->folder()), 'inbox-sender-folder-title-inbox'); - $this->assertEquals('Inbox', $sender->title(), 'inbox-sender-title-inbox'); - $this->assertEquals(0, $sender->messageTotal(), 'inbox-initial-message-count'); - - $sender->setFolder('sentbox'); - $this->assertEquals('sentbox', $sender->folder(), 'inbox-sender-sentbox'); - $this->assertEquals('Sentbox', $sender->folderTitle($sender->folder()), 'inbox-sender-folder-title-sentbox'); - $this->assertEquals('Sentbox', $sender->title(), 'inbox-sender-title-sentbox'); - $this->assertEquals(0, $sender->messageTotal(), 'inbox-sentbox-message-count'); + $senderInbox = $this->userList['sender']->inbox(); + $this->assertEquals('inbox.php?sort=latest', $senderInbox->folderLink('inbox', false), 'inbox-folder-latest'); + $this->assertEquals('inbox.php?sort=unread', $senderInbox->folderLink('inbox', true), 'inbox-folder-unread-first'); + $this->assertEquals('inbox.php?sort=latest§ion=sentbox', $senderInbox->folderLink('sentbox', false), 'inbox-sentbox-folder-latest'); + $this->assertEquals('inbox.php?sort=unread§ion=sentbox', $senderInbox->folderLink('sentbox', true), 'inbox-sentbox-folder-unread-first'); + $this->assertEquals('inbox', $senderInbox->folder(), 'inbox-sender-inbox'); + $this->assertEquals('Inbox', $senderInbox->folderTitle($senderInbox->folder()), 'inbox-sender-folder-title-inbox'); + $this->assertEquals('Inbox', $senderInbox->title(), 'inbox-sender-title-inbox'); + $this->assertEquals(0, $senderInbox->messageTotal(), 'inbox-initial-message-count'); + + $senderInbox->setFolder('sentbox'); + $this->assertEquals('sentbox', $senderInbox->folder(), 'inbox-sender-sentbox'); + $this->assertEquals('Sentbox', $senderInbox->folderTitle($senderInbox->folder()), 'inbox-sender-folder-title-sentbox'); + $this->assertEquals('Sentbox', $senderInbox->title(), 'inbox-sender-title-sentbox'); + $this->assertEquals(0, $senderInbox->messageTotal(), 'inbox-sentbox-message-count'); // send a PM - $subject = "phpunit pm subject " . randomString(12); - $body = "phpunit pm body " . randomString(12); - $userMan = new Gazelle\Manager\User; - $convId = $userMan->sendPM($receiverId, $sender->user()->id(), $subject, $body); - $sender->setFolder('inbox'); - $this->assertEquals(0, $sender->messageTotal(), 'inbox-still-0-message-count'); + $receiverInbox = $this->userList['receiver']->inbox(); + $subject = "phpunit pm subject " . randomString(12); + $body = "phpunit pm body " . randomString(12); + $pmSent = $receiverInbox->create($senderInbox->user(), $subject, $body); + $senderInbox->setFolder('inbox'); + $this->assertEquals(0, $senderInbox->messageTotal(), 'inbox-still-0-message-count'); // details of the PM - $pmSenderManager = new Gazelle\Manager\PM($sender->user()); - $pmSent = $pmSenderManager->findById($convId); + $pmSenderManager = new Gazelle\Manager\PM($senderInbox->user()); $this->assertInstanceOf(\Gazelle\PM::class, $pmSent, 'inbox-have-a-pm'); $this->assertEquals($subject, $pmSent->subject(), 'inbox-pm-subject'); $postList = $pmSent->postList(2, 0); $this->assertCount(1, $postList, 'inbox-pm-one-post'); $this->assertEquals($body, $postList[0]['body'], 'inbox-pm-one-body'); - $this->assertEquals([$senderId, $receiverId], $pmSent->recipientList(), 'pm-recipient-list'); + $this->assertEquals([$receiverId, $senderId], $pmSent->recipientList(), 'pm-recipient-list'); // receive the PM - $receiver = new Gazelle\User\Inbox($this->userList['receiver']); - $pmReceiverManager = new Gazelle\Manager\PM($receiver->user()); - $this->assertEquals(1, $receiver->messageTotal(), 'inbox-initial-message-count'); - $list = $receiver->messageList($pmReceiverManager, 2, 0); + $receiverInbox = $this->userList['receiver']->inbox(); + $pmReceiverManager = new Gazelle\Manager\PM($receiverInbox->user()); + $this->assertEquals(1, $receiverInbox->messageTotal(), 'inbox-initial-message-count'); + $list = $receiverInbox->messageList($pmReceiverManager, 2, 0); $this->assertCount(1, $list, 'inbox-recv-list'); $recvPm = $list[0]; $this->assertEquals($subject, $recvPm->subject(), 'inbox-pm-subject'); $recvList = $recvPm->postList(2, 0); $this->assertCount(1, $recvList, 'inbox-pm-recv-post'); $this->assertEquals($body, $recvList[0]['body'], 'inbox-pm-recv-body'); - $this->assertEquals(1, $receiver->user()->inboxUnreadCount(), 'inbox-unread-count'); - $this->assertEquals(1, $receiver->massRead([$convId]), 'inbox-mark-read'); - $this->assertEquals(0, $receiver->user()->inboxUnreadCount(), 'inbox-none-unread'); + $this->assertEquals(1, $receiverInbox->unreadTotal(), 'inbox-unread-count'); + $this->assertEquals(1, $receiverInbox->massRead([$pmSent->id()]), 'inbox-mark-read'); + $this->assertEquals(0, $receiverInbox->unreadTotal(), 'inbox-none-unread'); // sentbox - $sender->setFolder('sentbox'); - $this->assertEquals('sentbox', $sender->folder(), 'inbox-sender-sentbox'); - $this->assertEquals(1, $sender->messageTotal(), 'inbox-sentbox-message-count'); - $msgList = $sender->messageList($pmSenderManager, 2, 0); + $senderInbox->setFolder('sentbox'); + $this->assertEquals('sentbox', $senderInbox->folder(), 'inbox-sender-sentbox'); + $this->assertEquals(1, $senderInbox->messageTotal(), 'inbox-sentbox-message-count'); + $msgList = $senderInbox->messageList($pmSenderManager, 2, 0); $this->assertCount(1, $msgList, 'inbox-sentbox-list'); $this->assertEquals($subject, $msgList[0]->subject(), 'inbox-sentbox-subject'); $pmList = $msgList[0]->postList(2, 0); @@ -92,13 +85,14 @@ public function testInbox(): void { // It could be as simple as $pm->reply('body'); // reply + $userMan = new Gazelle\Manager\User; $replyBody = 'reply two ' . randomString(10); - $replyId = $userMan->replyPM($senderId, $receiverId, $subject, 'reply one', $convId); - $replyId = $userMan->replyPM($senderId, $receiverId, $subject, $replyBody, $convId); - $this->assertEquals($replyId, $convId, 'inbox-recv-reply'); - $senderList = $sender->messageList($pmSenderManager, 2, 0); + $replyId = $userMan->replyPM($senderId, $receiverId, $subject, 'reply one', $pmSent->id()); + $replyId = $userMan->replyPM($senderId, $receiverId, $subject, $replyBody, $pmSent->id()); + $this->assertEquals($replyId, $pmSent->id(), 'inbox-recv-reply'); + $senderList = $senderInbox->messageList($pmSenderManager, 2, 0); $this->assertCount(1, $senderList, 'inbox-sender-replylist'); - $msgList = $sender->messageList($pmSenderManager, 2, 0); + $msgList = $senderInbox->messageList($pmSenderManager, 2, 0); $this->assertCount(1, $msgList, 'inbox-sentbox-still-1-list'); $pmList = $msgList[0]->postList(4, 0); $this->assertCount(3, $pmList, 'inbox-sender-replies'); @@ -107,7 +101,7 @@ public function testInbox(): void { // several $subject = "phpunit multi " . randomString(12); $bodyList = [randomString(), randomString(), randomString(), randomString()]; - $convList = []; + $pmList = []; foreach ($bodyList as $body) { // Unfortunately there is no other way that works reliably. // Since the Unread flag applies to the entire conversation and the Sent date @@ -115,11 +109,13 @@ public function testInbox(): void { // whenever the wallclock second rolls over from one second to the next // between first and last message sent. This problem never show up in real life. sleep(1); - $convList[] = $userMan->sendPM($receiverId, $senderId, $subject, $body); + $pmList[] = $receiverInbox->create($senderInbox->user(), $subject, $body); } - $this->assertEquals(4, $receiver->user()->inboxUnreadCount(), 'inbox-more-count'); - $this->assertEquals(5, $receiver->messageTotal(), 'inbox-more-message-count'); - $rlist = $receiver->messageList($pmReceiverManager, 6, 0); + $convList = array_map(fn($p) => $p->id(), $pmList); + + $this->assertEquals(4, $receiverInbox->unreadTotal(), 'inbox-more-count'); + $this->assertEquals(5, $receiverInbox->messageTotal(), 'inbox-more-message-count'); + $rlist = $receiverInbox->messageList($pmReceiverManager, 6, 0); $flaky = implode(", ", array_map(fn($m) => "id={$m->id()} sent={$m->sentDate()} unr=" . ($m->isUnread() ? 'y' : 'n'), $rlist)); $this->assertFalse($rlist[4]->isUnread(), "inbox-last-is-read $flaky"); $this->assertTrue($rlist[3]->isUnread(), 'inbox-second-last-is-unread'); @@ -131,40 +127,40 @@ public function testInbox(): void { $this->assertEquals($bodyList[1], $pm->postBody($postId), 'inbox-pm-post-body'); // unread first - $receiver->setUnreadFirst(true); - $rlist = $receiver->messageList($pmReceiverManager, 6, 0); + $receiverInbox->setUnreadFirst(true); + $rlist = $receiverInbox->messageList($pmReceiverManager, 6, 0); $this->assertTrue($rlist[0]->isUnread(), 'inbox-unread-first-is-unread'); $this->assertFalse(end($rlist)->isUnread(), 'inbox-unread-last-is-read'); // search body - $receiver->setSearchField('message')->setSearchTerm($bodyList[1]); - $this->assertEquals(1, $receiver->messageTotal(), 'inbox-search-body'); - $rlist = $receiver->messageList($pmReceiverManager, 2, 0); + $receiverInbox->setSearchField('message')->setSearchTerm($bodyList[1]); + $this->assertEquals(1, $receiverInbox->messageTotal(), 'inbox-search-body'); + $rlist = $receiverInbox->messageList($pmReceiverManager, 2, 0); $this->assertCount(1, $rlist, 'inbox-search-list-body'); $this->assertEquals($convList[1], $rlist[0]->id(), 'inbox-search-found'); // search user - $receiver->setSearchField('user')->setSearchTerm('nobody-here'); - $this->assertCount(0, $receiver->messageList($pmReceiverManager, 1, 0), 'inbox-search-no-user'); - $receiver->setSearchField('user')->setSearchTerm($this->userList['sender']->username()); - $this->assertEquals(5, $receiver->messageTotal(), 'inbox-search-sender-name'); + $receiverInbox->setSearchField('user')->setSearchTerm('nobody-here'); + $this->assertCount(0, $receiverInbox->messageList($pmReceiverManager, 1, 0), 'inbox-search-no-user'); + $receiverInbox->setSearchField('user')->setSearchTerm($this->userList['sender']->username()); + $this->assertEquals(5, $receiverInbox->messageTotal(), 'inbox-search-sender-name'); // search subject - $receiver->setSearchField('subject')->setSearchTerm($subject); - $this->assertEquals(4, $receiver->messageTotal(), 'inbox-search-subject'); - $rlist = $receiver->messageList($pmReceiverManager, 6, 0); + $receiverInbox->setSearchField('subject')->setSearchTerm($subject); + $this->assertEquals(4, $receiverInbox->messageTotal(), 'inbox-search-subject'); + $rlist = $receiverInbox->messageList($pmReceiverManager, 6, 0); $this->assertCount(4, $rlist, 'inbox-search-list-pm'); // pin - $this->assertEquals(2, $receiver->massTogglePinned([$rlist[1]->id(), $rlist[2]->id()]), 'inbox-pin-2'); - $receiver->setSearchField('subject')->setSearchTerm('')->setUnreadFirst(false); - $rlist = $receiver->messageList($pmReceiverManager, 6, 0); + $this->assertEquals(2, $receiverInbox->massTogglePinned([$rlist[1]->id(), $rlist[2]->id()]), 'inbox-pin-2'); + $receiverInbox->setSearchField('subject')->setSearchTerm('')->setUnreadFirst(false); + $rlist = $receiverInbox->messageList($pmReceiverManager, 6, 0); $this->assertEquals( [$convList[2], $convList[1], $convList[3], $convList[0], $pmSent->id()], [$rlist[0]->id(), $rlist[1]->id(), $rlist[2]->id(), $rlist[3]->id(), $rlist[4]->id()], 'inbox-pinned-order-regular' ); - $rlist = $receiver->setUnreadFirst(true)->messageList($pmReceiverManager, 6, 0); + $rlist = $receiverInbox->setUnreadFirst(true)->messageList($pmReceiverManager, 6, 0); $this->assertEquals( [$convList[2], $convList[1], $convList[3], $convList[0], $pmSent->id()], [$rlist[0]->id(), $rlist[1]->id(), $rlist[2]->id(), $rlist[3]->id(), $rlist[4]->id()], @@ -174,7 +170,7 @@ public function testInbox(): void { // mass unread $this->assertEquals( 1, // $rlist[4] a.k.a $pmSent has been read - $receiver->massUnread([ + $receiverInbox->massUnread([ $rlist[2]->id(), $rlist[3]->id(), $rlist[4]->id() ]), 'inbox-toggle-unread' @@ -183,14 +179,14 @@ public function testInbox(): void { // mass remove $this->assertEquals( 2, // $rlist[4] a.k.a $pmSent has been read - $receiver->massRemove([ + $receiverInbox->massRemove([ $rlist[1]->id(), $rlist[3]->id() ]), 'inbox-mass-remove' ); - $this->assertEquals(3, $receiver->messageTotal(), 'inbox-after-remove'); + $this->assertEquals(3, $receiverInbox->messageTotal(), 'inbox-after-remove'); - $json = new Gazelle\Json\Inbox($receiver->user(), 'inbox', 1, true, $userMan); + $json = new Gazelle\Json\Inbox($receiverInbox->user(), 'inbox', 1, true, $userMan); $payload = $json->payload(); $this->assertCount(3, $payload, 'inbox-json-payload'); $this->assertEquals(1, $payload['currentPage'], 'inbox-json-current-page'); @@ -198,4 +194,13 @@ public function testInbox(): void { $this->assertCount(3, $payload['messages'], 'inbox-json-message-list'); $this->assertEquals($convList[2], $payload['messages'][0]['convId'], 'inbox-json-first-message-id'); } + + public function testSystem(): void { + $this->userList = [ + Helper::makeUser('inbox.recv.' . randomString(6), 'inbox'), + ]; + $pm = $this->userList[0]->inbox()->createSystem('system', 'body'); + $this->assertEquals(0, $pm->senderId(), 'inbox-system-sender-id'); + $this->assertEquals(1, $pm->remove(), 'inbox-system-remove'); + } } diff --git a/tests/phpunit/ReaperTest.php b/tests/phpunit/ReaperTest.php index 280b873ca..f72ec2482 100644 --- a/tests/phpunit/ReaperTest.php +++ b/tests/phpunit/ReaperTest.php @@ -32,17 +32,9 @@ class ReaperTest extends TestCase { public function setUp(): void { // we need two users, one who uploads and one who snatches $this->userList = [ - Helper::makeUser('reaper.' . randomString(10), 'reaper'), - Helper::makeUser('reaper.' . randomString(10), 'reaper'), + Helper::makeUser('reaper.' . randomString(10), 'reaper', enable: true, clearInbox: true), + Helper::makeUser('reaper.' . randomString(10), 'reaper', enable: true, clearInbox: true), ]; - // enable them and wipe their inboxes (there is only one message) - foreach ($this->userList as $user) { - $user->setField('Enabled', '1')->modify(); - $pmMan = new Gazelle\Manager\PM($user); - foreach ((new Gazelle\User\Inbox($user))->messageList($pmMan, 1, 0) as $pm) { - $pm->remove(); - } - } // create a torrent group $this->tgroupName = 'phpunit reaper ' . randomString(6); @@ -191,8 +183,7 @@ public function testExpand(): void { public function testNeverSeeded(): void { $user = $this->torrentList[0]->uploader(); $pmMan = new Gazelle\Manager\PM($user); - $inbox = new Gazelle\User\Inbox($user); - $this->assertEquals(0, $inbox->messageTotal(), 'never-inbox-initial'); + $this->assertEquals(0, $user->inbox()->messageTotal(), 'never-inbox-initial'); $torMan = new Gazelle\Manager\Torrent; $userMan = new Gazelle\Manager\User; @@ -228,8 +219,8 @@ public function testNeverSeeded(): void { ); // check the notification - $this->assertEquals(1, $inbox->messageTotal(), 'never-message-initial-count'); - $pm = $inbox->messageList($pmMan, 1, 0)[0]; + $this->assertEquals(1, $user->inbox()->messageTotal(), 'never-message-initial-count'); + $pm = $user->inbox()->messageList($pmMan, 1, 0)[0]; $this->assertEquals('You have a non-seeded new upload to rescue', $pm->subject(), 'never-message-initial-subject'); $pm->remove(); @@ -258,8 +249,8 @@ public function testNeverSeeded(): void { 'reaper-unseeded-stats-1' ); - $this->assertEquals(1, $inbox->messageTotal(), 'never-message-2'); - $pm = $inbox->messageList($pmMan, 1, 0)[0]; + $this->assertEquals(1, $user->inbox()->messageTotal(), 'never-message-2'); + $pm = $user->inbox()->messageList($pmMan, 1, 0)[0]; $body = $pm->postList(1, 0)[0]['body']; $this->assertEquals('You have 2 non-seeded new uploads to rescue', $pm->subject(), 'never-message-2'); $this->assertStringContainsString("Dear {$this->userList[0]->username()}", $body, 'never-body-2-dear'); @@ -277,7 +268,7 @@ public function testNeverSeeded(): void { // we no longer need to consider the torrent creation date. $this->modifyUnseededInterval($this->torrentList[1], NOTIFY_NEVER_SEEDED_FINAL_HOUR + 1); - $this->assertEquals(0, $inbox->messageTotal(), 'never-message-final-none'); + $this->assertEquals(0, $user->inbox()->messageTotal(), 'never-message-final-none'); $neverFinal = $reaper->finalNeverSeededList(); $this->assertCount(1, $neverFinal, 'never-final-0'); // one user ... $this->assertEquals(1, // ... with one upload @@ -299,8 +290,8 @@ public function testNeverSeeded(): void { 'reaper-unseeded-stats-final' ); - $this->assertEquals(1, $inbox->messageTotal(), 'never-message-final-count'); - $pm = $inbox->messageList($pmMan, 1, 0)[0]; + $this->assertEquals(1, $user->inbox()->messageTotal(), 'never-message-final-count'); + $pm = $user->inbox()->messageList($pmMan, 1, 0)[0]; $body = $pm->postList(1, 0)[0]['body']; $this->assertEquals('You have a non-seeded new upload scheduled for deletion very soon', $pm->subject(), 'never-message-final-subject'); $this->assertStringContainsString("Dear {$this->userList[0]->username()}", $body, 'never-body-3-dear'); @@ -316,7 +307,7 @@ public function testNeverSeeded(): void { $this->assertCount(1, $list, 'never-reap-list'); $this->assertEquals(1, $reaper->removeNeverSeeded(), 'never-reap-remove'); - $pm = $inbox->messageList($pmMan, 1, 0)[0]; + $pm = $user->inbox()->messageList($pmMan, 1, 0)[0]; $body = $pm->postList(1, 0)[0]['body']; $this->assertEquals('1 of your uploads has been deleted for inactivity (never seeded)', $pm->subject(), 'never-remove-message'); $this->assertStringContainsString("Dear {$this->userList[0]->username()}", $body, 'never-remove-body-dear'); @@ -371,8 +362,8 @@ public function testUnseeded(): void { ); $inboxList = [ - new Gazelle\User\Inbox($this->userList[0]), - new Gazelle\User\Inbox($this->userList[1]), + $this->userList[0]->inbox(), + $this->userList[1]->inbox(), ]; $pmMan = [ new Gazelle\Manager\PM($this->userList[0]), @@ -515,8 +506,8 @@ public function testNeverNotify(): void { Gazelle\Torrent\ReaperNotify::INITIAL ); - $this->assertEquals(0, (new Gazelle\User\Inbox($this->userList[0]))->messageTotal(), 'never-uploader-no-notify'); - $this->assertEquals(0, (new Gazelle\User\Inbox($this->userList[1]))->messageTotal(), 'never-snatcher-no-notify'); + $this->assertEquals(0, $this->userList[0]->inbox()->messageTotal(), 'never-uploader-no-notify'); + $this->assertEquals(0, $this->userList[1]->inbox()->messageTotal(), 'never-snatcher-no-notify'); } public function testUnseededNotify(): void { @@ -543,8 +534,8 @@ public function testUnseededNotify(): void { Gazelle\Torrent\ReaperNotify::INITIAL ); - $this->assertEquals(1, (new Gazelle\User\Inbox($this->userList[0]))->messageTotal(), 'unseeded-uploader-no-notify'); - $this->assertEquals(0, (new Gazelle\User\Inbox($this->userList[1]))->messageTotal(), 'unseeded-snatcher-no-notify'); + $this->assertEquals(1, $this->userList[0]->inbox()->messageTotal(), 'unseeded-uploader-no-notify'); + $this->assertEquals(0, $this->userList[1]->inbox()->messageTotal(), 'unseeded-snatcher-no-notify'); $timeline = $reaper->timeline(); $this->assertEquals([2], array_values($timeline), 'reaper-two-today'); diff --git a/tests/phpunit/SearchReportTest.php b/tests/phpunit/SearchReportTest.php index d961a531c..987b9e01b 100644 --- a/tests/phpunit/SearchReportTest.php +++ b/tests/phpunit/SearchReportTest.php @@ -13,16 +13,9 @@ class SearchReportTest extends TestCase { public function setUp(): void { $this->userList = [ - Helper::makeUser('searchrep.' . randomString(10), 'searchrep'), - Helper::makeUser('searchrep.' . randomString(10), 'searchrep'), + Helper::makeUser('searchrep.' . randomString(10), 'searchrep', enable: true, clearInbox: true), + Helper::makeUser('searchrep.' . randomString(10), 'searchrep', enable: true, clearInbox: true), ]; - foreach ($this->userList as $user) { - $user->setField('Enabled', '1')->modify(); - $pmMan = new \Gazelle\Manager\PM($user); - foreach ((new \Gazelle\User\Inbox($user))->messageList($pmMan, 1, 0) as $pm) { - $pm->remove(); - } - } $this->collage = (new \Gazelle\Manager\Collage)->create( user: $this->userList[0], diff --git a/tests/phpunit/UserActivityTest.php b/tests/phpunit/UserActivityTest.php index 77cb0052d..570145889 100644 --- a/tests/phpunit/UserActivityTest.php +++ b/tests/phpunit/UserActivityTest.php @@ -107,19 +107,19 @@ public function testInbox(): void { $this->userList['admin']->setField('PermissionID', SYSOP)->modify(); $this->userList['user'] = Helper::makeUser('user.' . randomString(10), 'activity'); - $inbox = new Gazelle\User\Notification\Inbox($this->userList['user']); - $this->assertInstanceOf(Gazelle\User\Notification\Inbox::class, $inbox, 'alert-notification-inbox-instance'); - if ($inbox->load()) { - // there are some messages in the inbox, mark them as read - $inbox->clear(); - } + $admin = $this->userList['admin']; + $user = $this->userList['user']; + $notification = new Gazelle\User\Notification\Inbox($user); + $this->assertInstanceOf(Gazelle\User\Notification\Inbox::class, $notification, 'alert-notification-inbox-instance'); + $this->assertFalse($notification->load(), 'alert-notification-inbox-none'); + $this->assertEquals(0, $notification->clear(), 'alert-notification-inbox-clear'); - // send a message - $convId = $userMan->sendPM($this->userList['user']->id(), $this->userList['admin']->id(), 'unit test message', 'unit test body'); - $this->assertGreaterThan(0, $convId, 'alert-inbox-send'); + // send a message from admin to user + $pm = $user->inbox()->create($admin, 'unit test message', 'unit test body'); + $this->assertGreaterThan(0, $pm->id(), 'alert-inbox-send'); // check out the notifications - $notifier = new Gazelle\User\Notification($this->userList['user']); + $notifier = new Gazelle\User\Notification($user); // if this fails, the CI database has drifted (or another UT has clobbered the expected value here) $this->assertTrue($notifier->isActive('Inbox'), 'activity-notified-inbox'); @@ -131,9 +131,9 @@ public function testInbox(): void { $this->assertEquals('You have a new message', $alertInbox->title(), 'alert-inbox-unread'); // read it - $pm = (new Gazelle\Manager\PM($this->userList['user']))->findById($convId); - $this->assertInstanceOf(Gazelle\PM::class, $pm, 'inbox-unread-pm'); - $this->assertEquals(1, $pm->markRead(), 'alert-pm-read'); + $read = (new Gazelle\Manager\PM($user))->findById($pm->id()); + $this->assertInstanceOf(Gazelle\PM::class, $read, 'inbox-unread-pm'); + $this->assertEquals(1, $read->markRead(), 'alert-pm-read'); } public function testNews(): void { diff --git a/tests/phpunit/UserTest.php b/tests/phpunit/UserTest.php index 2a29b3286..cd92667f3 100644 --- a/tests/phpunit/UserTest.php +++ b/tests/phpunit/UserTest.php @@ -145,7 +145,6 @@ public function testUser(): void { $this->assertEquals(0, $this->user->collagesCreated(), 'utest-collage-created'); $this->assertEquals(0, $this->user->collageUnreadCount(), 'utest-collage-unread-count'); $this->assertEquals(0, $this->user->forumCatchupEpoch(), 'utest-forum-catchup-epoch'); - $this->assertEquals(0, $this->user->inboxUnreadCount(), 'utest-inbox-unread'); $this->assertEquals(0, $this->user->invite()->pendingTotal(), 'utest-pending-invite-count'); $this->assertEquals(0, $this->user->downloadedOnRatioWatch(), 'utest-download-ratio-watch'); $this->assertEquals(0, $this->user->seedingSize(), 'utest-seeding-size'); diff --git a/tests/phpunit/manager/ReportManagerTest.php b/tests/phpunit/manager/ReportManagerTest.php index 489588724..95699890b 100644 --- a/tests/phpunit/manager/ReportManagerTest.php +++ b/tests/phpunit/manager/ReportManagerTest.php @@ -13,16 +13,9 @@ class ReportManagerTest extends TestCase { public function setUp(): void { $this->userList = [ - Helper::makeUser('report.' . randomString(10), 'report'), - Helper::makeUser('report.' . randomString(10), 'report'), + Helper::makeUser('report.' . randomString(10), 'report', enable: true, clearInbox: true), + Helper::makeUser('report.' . randomString(10), 'report', enable: true, clearInbox: true), ]; - foreach ($this->userList as $user) { - $user->setField('Enabled', '1')->modify(); - $pmMan = new Gazelle\Manager\PM($user); - foreach ((new Gazelle\User\Inbox($user))->messageList($pmMan, 1, 0) as $pm) { - $pm->remove(); - } - } } public function tearDown(): void { diff --git a/tests/phpunit/manager/UserManagerTest.php b/tests/phpunit/manager/UserManagerTest.php index 3b48403df..c81c991ca 100644 --- a/tests/phpunit/manager/UserManagerTest.php +++ b/tests/phpunit/manager/UserManagerTest.php @@ -10,17 +10,10 @@ class UserManagerTest extends TestCase { public function setUp(): void { $this->userList = [ - Helper::makeUser('um1.' . randomString(10), 'userman'), - Helper::makeUser('um2.' . randomString(10), 'userman'), - Helper::makeUser('um3.' . randomString(10), 'userman'), + Helper::makeUser('um1.' . randomString(10), 'userman', enable: true, clearInbox: true), + Helper::makeUser('um2.' . randomString(10), 'userman', enable: true, clearInbox: true), + Helper::makeUser('um3.' . randomString(10), 'userman', enable: true, clearInbox: true), ]; - foreach ($this->userList as $user) { - $user->setField('Enabled', '1')->modify(); - $pmMan = new Gazelle\Manager\PM($user); - foreach ((new Gazelle\User\Inbox($user))->messageList($pmMan, 1, 0) as $pm) { - $pm->remove(); - } - } } public function tearDown(): void { @@ -164,7 +157,7 @@ public function testUserRatioWatch(): void { $user->flush(); } - $receiver = new \Gazelle\User\Inbox($this->userList[0]); + $receiver = $this->userList[0]->inbox(); $pmMan = new \Gazelle\Manager\PM($receiver->user()); $this->assertEquals(1, $receiver->messageTotal(), 'uman-ratiowatch-pm-count'); $list = $receiver->messageList($pmMan, 2, 0); @@ -174,7 +167,7 @@ public function testUserRatioWatch(): void { foreach ($this->userList as $user) { $user->setField('Enabled', '1')->modify(); $pmMan = new Gazelle\Manager\PM($user); - foreach ((new Gazelle\User\Inbox($user))->messageList($pmMan, 1, 0) as $pm) { + foreach ($user->inbox()->messageList($pmMan, 1, 0) as $pm) { $pm->remove(); } } @@ -194,7 +187,7 @@ public function testUserRatioWatch(): void { $this->assertEquals(1, $userMan->ratioWatchBlock($tracker), 'uman-ratiowatch-do-block'); $this->userList[0]->flush(); - $receiver = new \Gazelle\User\Inbox($this->userList[0]); + $receiver = $this->userList[0]->inbox(); $pmMan = new \Gazelle\Manager\PM($receiver->user()); $this->assertEquals(1, $receiver->messageTotal(), 'uman-ratiowatch-pm-block-count'); $list = $receiver->messageList($pmMan, 2, 0); @@ -226,7 +219,7 @@ public function testUserRatioWatch(): void { $this->assertEquals(0, $userMan->ratioWatchClear($tracker), 'uman-ratiowatch-reprocess-clear'); $this->userList[1]->flush(); - $receiver = new \Gazelle\User\Inbox($this->userList[1]); + $receiver = $this->userList[1]->inbox(); $pmMan = new \Gazelle\Manager\PM($receiver->user()); $this->assertEquals(1, $receiver->messageTotal(), 'uman-ratiowatch-pm-clear-count'); $list = $receiver->messageList($pmMan, 2, 0); @@ -239,7 +232,7 @@ public function testUserRatioWatch(): void { $this->assertEquals(0, $userMan->ratioWatchEngage($tracker), 'uman-ratiowatch-reprocess-engage'); $this->userList[2]->flush(); - $receiver = new \Gazelle\User\Inbox($this->userList[2]); + $receiver = $this->userList[2]->inbox(); $pmMan = new \Gazelle\Manager\PM($receiver->user()); $this->assertEquals(1, $receiver->messageTotal(), 'uman-ratiowatch-pm-engage-count'); $list = $receiver->messageList($pmMan, 2, 0); @@ -260,7 +253,7 @@ public function testSendCustomPM(): void { 'uman-send-custom-pm' ); - $receiver = new \Gazelle\User\Inbox($this->userList[1]); + $receiver = $this->userList[2]->inbox(); $pmMan = new \Gazelle\Manager\PM($receiver->user()); $this->assertEquals(1, $receiver->messageTotal(), 'uman-custom-pm-count'); $list = $receiver->messageList($pmMan, 2, 0); @@ -268,7 +261,7 @@ public function testSendCustomPM(): void { $postlist = $list[0]->postlist(10, 0); $postId = $postlist[0]['id']; $pm = $pmMan->findByPostId($postId); - $this->assertStringContainsString($this->userList[1]->username(), $pm->postBody($postId), 'uman-custom-pm-body'); + $this->assertStringContainsString($this->userList[2]->username(), $pm->postBody($postId), 'uman-custom-pm-body'); } public function testUserclassFlush(): void {