]> BookStack Code Mirror - bookstack/blob - app/Activity/Tools/EntityWatchers.php
Dockerfile: Don't cache 50MB of lists and use a single layer, make it pretty
[bookstack] / app / Activity / Tools / EntityWatchers.php
1 <?php
2
3 namespace BookStack\Activity\Tools;
4
5 use BookStack\Activity\Models\Watch;
6 use BookStack\Entities\Models\BookChild;
7 use BookStack\Entities\Models\Entity;
8 use BookStack\Entities\Models\Page;
9 use Illuminate\Database\Eloquent\Builder;
10
11 class EntityWatchers
12 {
13     /**
14      * @var int[]
15      */
16     protected array $watchers = [];
17
18     /**
19      * @var int[]
20      */
21     protected array $ignorers = [];
22
23     public function __construct(
24         protected Entity $entity,
25         protected int $watchLevel,
26     ) {
27         $this->build();
28     }
29
30     public function getWatcherUserIds(): array
31     {
32         return $this->watchers;
33     }
34
35     public function isUserIgnoring(int $userId): bool
36     {
37         return in_array($userId, $this->ignorers);
38     }
39
40     protected function build(): void
41     {
42         $watches = $this->getRelevantWatches();
43
44         // Sort before de-duping, so that the order looped below follows book -> chapter -> page ordering
45         usort($watches, function (Watch $watchA, Watch $watchB) {
46             $entityTypeDiff = $watchA->watchable_type <=> $watchB->watchable_type;
47             return $entityTypeDiff === 0 ? ($watchA->user_id <=> $watchB->user_id) : $entityTypeDiff;
48         });
49
50         // De-dupe by user id to get their most relevant level
51         $levelByUserId = [];
52         foreach ($watches as $watch) {
53             $levelByUserId[$watch->user_id] = $watch->level;
54         }
55
56         // Populate the class arrays
57         $this->watchers = array_keys(array_filter($levelByUserId, fn(int $level) => $level >= $this->watchLevel));
58         $this->ignorers = array_keys(array_filter($levelByUserId, fn(int $level) => $level === 0));
59     }
60
61     /**
62      * @return Watch[]
63      */
64     protected function getRelevantWatches(): array
65     {
66         /** @var Entity[] $entitiesInvolved */
67         $entitiesInvolved = array_filter([
68             $this->entity,
69             $this->entity instanceof BookChild ? $this->entity->book : null,
70             $this->entity instanceof Page ? $this->entity->chapter : null,
71         ]);
72
73         $query = Watch::query()->where(function (Builder $query) use ($entitiesInvolved) {
74             foreach ($entitiesInvolved as $entity) {
75                 $query->orWhere(function (Builder $query) use ($entity) {
76                     $query->where('watchable_type', '=', $entity->getMorphClass())
77                         ->where('watchable_id', '=', $entity->id);
78                 });
79             }
80         });
81
82         return $query->get([
83             'level', 'watchable_id', 'watchable_type', 'user_id'
84         ])->all();
85     }
86 }