where(function (Builder $q) use ($user) { $q->where('owner_id', $user->id) ->orWhereHas('users', fn (Builder $q2) => $q2->where('users.id', $user->id)) ->orWhereHas('groups', fn (Builder $q2) => $q2->whereIn( 'groups.id', $user->groups()->pluck('groups.id') )); }); } public function owner(): BelongsTo { return $this->belongsTo(User::class, 'owner_id'); } public function users(): BelongsToMany { return $this->belongsToMany(User::class) ->withPivot('role') ->withTimestamps(); } public function groups(): BelongsToMany { return $this->belongsToMany(Group::class, 'investigation_group') ->withPivot('role') ->withTimestamps(); } public function targets(): HasMany { return $this->hasMany(Target::class); } /** * The role a user has on this investigation, or null if they have no access. * Owner implicitly has the 'owner' role; direct grants win over group grants, * and among group grants 'editor' wins over 'viewer'. */ public function roleFor(User $user): ?string { if ($this->owner_id === $user->id) { return 'owner'; } $directRole = $this->users()->where('users.id', $user->id)->value('investigation_user.role'); if ($directRole !== null) { return $directRole; } $groupRoles = $this->groups() ->whereIn('groups.id', $user->groups()->pluck('groups.id')) ->pluck('investigation_group.role'); if ($groupRoles->isEmpty()) { return null; } return $groupRoles->contains('editor') ? 'editor' : 'viewer'; } public function isAccessibleBy(User $user): bool { return $this->roleFor($user) !== null; } public function isEditableBy(User $user): bool { return in_array($this->roleFor($user), ['owner', 'editor'], true); } }