3 namespace BookStack\Activity\Tools;
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;
16 protected array $watchers = [];
21 protected array $ignorers = [];
23 public function __construct(
24 protected Entity $entity,
25 protected int $watchLevel,
30 public function getWatcherUserIds(): array
32 return $this->watchers;
35 public function isUserIgnoring(int $userId): bool
37 return in_array($userId, $this->ignorers);
40 protected function build(): void
42 $watches = $this->getRelevantWatches();
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;
50 // De-dupe by user id to get their most relevant level
52 foreach ($watches as $watch) {
53 $levelByUserId[$watch->user_id] = $watch->level;
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));
64 protected function getRelevantWatches(): array
66 /** @var Entity[] $entitiesInvolved */
67 $entitiesInvolved = array_filter([
69 $this->entity instanceof BookChild ? $this->entity->book : null,
70 $this->entity instanceof Page ? $this->entity->chapter : null,
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);
83 'level', 'watchable_id', 'watchable_type', 'user_id'