1<?php
2
3// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the GNU Affero General Public License v3.0.
4// See the LICENCE file in the repository root for full licence text.
5
6namespace App\Libraries;
7
8use App\Models\Chat\Channel;
9use App\Models\Chat\UserChannel;
10use App\Models\User;
11use App\Transformers\Chat\ChannelTransformer;
12use Ds\Map;
13use Ds\Set;
14use Illuminate\Support\Collection;
15
16class UserChannelList
17{
18 private Collection $channels;
19
20 public function __construct(private User $user)
21 {
22 }
23
24 public function get()
25 {
26 $this->loadChannels();
27 $this->preloadUsers();
28
29 $filteredChannels = $this->channels->filter(fn (Channel $channel) => $channel->isVisibleFor($this->user));
30
31 $transformer = ChannelTransformer::forUser($this->user);
32
33 return json_collection($filteredChannels, $transformer, ChannelTransformer::LISTING_INCLUDES);
34 }
35
36 public function getChannels(): Collection
37 {
38 if (!isset($this->channels)) {
39 $this->loadChannels();
40 }
41
42 return $this->channels;
43 }
44
45 private function loadChannels()
46 {
47 $userChannels = UserChannel::where('user_id', $this->user->getKey())
48 ->where('hidden', false)
49 ->whereHas('channel')
50 ->with('channel')
51 ->limit($GLOBALS['cfg']['osu']['chat']['channel_limit'])
52 ->get();
53
54 foreach ($userChannels as $userChannel) {
55 // preset userChannel for getting last_read_id.
56 $userChannel->channel->setUserChannel($userChannel);
57 }
58
59 $this->channels = $userChannels->pluck('channel');
60 }
61
62 private function preloadUsers()
63 {
64 // Getting user list; Limited to PM channels due to large size of public channels.
65 $userIds = new Set();
66 $pmChannels = $this->channels->filter(fn ($channel) => $channel->isPM());
67 foreach ($pmChannels as $channel) {
68 $userIds->add(...$channel->userIds());
69 }
70
71 $users = User::default()
72 ->whereIn('user_id', $userIds->toArray())
73 ->with([
74 // only fetch data related to $user, to be used by ChatPmStart/ChatChannelCanMessage privilege check
75 'friends' => fn ($query) => $query->where('zebra_id', $this->user->getKey()),
76 'blocks' => fn ($query) => $query->where('zebra_id', $this->user->getKey()),
77 ])
78 ->get();
79
80 // If any channel users are blocked, preload the user groups of those users for the isModerator check.
81 $blockedIds = $users->pluck('user_id')->intersect($this->user->blocks->pluck('user_id'));
82 if ($blockedIds->isNotEmpty()) {
83 // Yes, the sql will look stupid.
84 $users->load(['userGroups' => fn ($query) => $query->whereIn('user_id', $blockedIds)]);
85 }
86
87 $usersMap = new Map();
88 foreach ($users as $user) {
89 $usersMap->put($user->getKey(), $user);
90 }
91
92 foreach ($pmChannels as $channel) {
93 $channel->setPmUsers(array_map(fn ($id) => $usersMap->get($id, null), $channel->userIds()));
94 }
95 }
96}