firstOrCreate([ 'file_id' => $file->id, 'assignable_type' => $assignable->getMorphClass(), 'assignable_id' => $assignable->getKey(), ]); $this->activity->log(Action::FileAssigned, subject: $file, context: ['target' => $targetName]); // Sharing itself is never held up — the assignment above is // written, and the file is theirs the moment it can be had. What // waits is the telling: a file still being checked for viruses // cannot be downloaded, so an email now would send somebody to a // page that refuses them, and a file about to be quarantined would // have been announced to everyone before anybody knew. The // announcement goes out from AnnounceAvailableFile instead, on the // event that says the file can be handed over. if (! $this->availability->isAvailable($file)) { return; } $recipients = $this->recipients($assignable); $this->notifier->send('file_shared', $recipients, subject: $file, data: ['itemName' => $file->name]); // The master switch and each recipient's own preference are the // digester's job now — every caller was repeating them. $this->digester->queue('file_shared', $recipients, $file->name, ['is_folder' => false]); } /** * @return bool whether an assignment was actually removed */ public function unassign(File $file, User|Group $assignable, string $targetName): bool { $deleted = FileAssignment::query() ->where('file_id', $file->id) ->where('assignable_type', $assignable->getMorphClass()) ->where('assignable_id', $assignable->getKey()) ->delete(); if ($deleted > 0) { $this->activity->log(Action::FileUnassigned, subject: $file, context: ['target' => $targetName]); } return $deleted > 0; } /** * Notifier performs no authorization of its own — see its SECURITY * CONTRACT docblock — so the recipient list is resolved here, from the * assignment itself. * * @return iterable */ private function recipients(User|Group $assignable): iterable { return $assignable instanceof Group ? $assignable->members : [$assignable]; } }