*/ public function supportedTargetTypes(): array { return [ 'message', 'conversation', 'user', 'story', 'collection', 'collection_comment', 'collection_submission', ...$this->novaCardTargetTypes(), ]; } /** * @return array */ public function novaCardTargetTypes(): array { return [ 'nova_card', 'nova_card_comment', 'nova_card_challenge', 'nova_card_challenge_entry', ]; } public function validateForReporter(User $user, string $targetType, int $targetId): void { switch ($targetType) { case 'message': $message = Message::query()->findOrFail($targetId); $allowed = ConversationParticipant::query() ->where('conversation_id', $message->conversation_id) ->where('user_id', $user->id) ->whereNull('left_at') ->exists(); abort_unless($allowed, 403, 'You are not allowed to report this message.'); return; case 'conversation': $allowed = ConversationParticipant::query() ->where('conversation_id', $targetId) ->where('user_id', $user->id) ->whereNull('left_at') ->exists(); abort_unless($allowed, 403, 'You are not allowed to report this conversation.'); return; case 'user': User::query()->findOrFail($targetId); return; case 'story': Story::query()->findOrFail($targetId); return; case 'collection': $collection = Collection::query()->findOrFail($targetId); abort_unless($collection->canBeViewedBy($user), 403, 'You are not allowed to report this collection.'); return; case 'collection_comment': $comment = CollectionComment::query()->with('collection')->findOrFail($targetId); abort_unless($comment->collection && $comment->collection->canBeViewedBy($user), 403, 'You are not allowed to report this comment.'); return; case 'collection_submission': $submission = CollectionSubmission::query()->with('collection')->findOrFail($targetId); abort_unless($submission->collection && $submission->collection->canBeViewedBy($user), 403, 'You are not allowed to report this submission.'); return; case 'nova_card': $card = NovaCard::query()->with('user')->findOrFail($targetId); abort_unless($card->canBeViewedBy($user), 403, 'You are not allowed to report this card.'); return; case 'nova_card_comment': $comment = NovaCardComment::query()->with('card.user')->findOrFail($targetId); abort_unless($comment->card !== null && $comment->card->canBeViewedBy($user), 403, 'You are not allowed to report this card comment.'); return; case 'nova_card_challenge': $challenge = NovaCardChallenge::query()->findOrFail($targetId); abort_unless(in_array($challenge->status, [NovaCardChallenge::STATUS_ACTIVE, NovaCardChallenge::STATUS_COMPLETED, NovaCardChallenge::STATUS_ARCHIVED], true), 404); return; case 'nova_card_challenge_entry': $entry = NovaCardChallengeEntry::query()->with(['challenge', 'card.user'])->findOrFail($targetId); abort_unless($entry->challenge !== null && $entry->card !== null, 404); abort_unless(in_array($entry->challenge->status, [NovaCardChallenge::STATUS_ACTIVE, NovaCardChallenge::STATUS_COMPLETED, NovaCardChallenge::STATUS_ARCHIVED], true), 404); abort_unless($entry->card->canBeViewedBy($user), 403, 'You are not allowed to report this challenge entry.'); return; default: throw (new ModelNotFoundException())->setModel(Report::class, [$targetId]); } } public function summarize(Report $report): array { $summary = [ 'exists' => false, 'type' => (string) $report->target_type, 'id' => (int) $report->target_id, 'label' => 'Unavailable target', 'subtitle' => 'The original target could not be loaded.', 'public_url' => null, 'moderation_url' => null, 'moderation_target' => null, ]; try { return match ($report->target_type) { 'nova_card' => $this->summarizeNovaCard($report->target_id), 'nova_card_comment' => $this->summarizeNovaCardComment($report->target_id), 'nova_card_challenge' => $this->summarizeNovaCardChallenge($report->target_id), 'nova_card_challenge_entry' => $this->summarizeNovaCardChallengeEntry($report->target_id), default => $summary, }; } catch (ModelNotFoundException) { return $summary; } } public function resolveModerationCard(Report $report): ?NovaCard { return match ($report->target_type) { 'nova_card' => NovaCard::query()->find($report->target_id), 'nova_card_comment' => NovaCardComment::query()->with('card')->find($report->target_id)?->card, 'nova_card_challenge_entry' => NovaCardChallengeEntry::query()->with('card')->find($report->target_id)?->card, default => null, }; } private function summarizeNovaCard(int $targetId): array { $card = NovaCard::query() ->with(['user', 'category']) ->findOrFail($targetId); return [ 'exists' => true, 'type' => 'nova_card', 'id' => (int) $card->id, 'label' => (string) $card->title, 'subtitle' => trim(sprintf('@%s%s', (string) $card->user?->username, $card->category ? ' • '.$card->category->name : '')), 'public_url' => $card->publicUrl(), 'moderation_url' => route('cp.cards.index'), 'moderation_target' => $this->summarizeModerationCard($card), ]; } private function summarizeNovaCardChallenge(int $targetId): array { $challenge = NovaCardChallenge::query()->findOrFail($targetId); return [ 'exists' => true, 'type' => 'nova_card_challenge', 'id' => (int) $challenge->id, 'label' => (string) $challenge->title, 'subtitle' => sprintf('%s challenge • %d entries', ucfirst((string) $challenge->status), (int) $challenge->entries_count), 'public_url' => route('cards.challenges.show', ['slug' => $challenge->slug]), 'moderation_url' => route('cp.cards.challenges.index'), 'moderation_target' => null, ]; } private function summarizeNovaCardComment(int $targetId): array { $comment = NovaCardComment::query() ->with(['card.user']) ->findOrFail($targetId); return [ 'exists' => $comment->card !== null, 'type' => 'nova_card_comment', 'id' => (int) $comment->id, 'label' => 'Comment on ' . ($comment->card?->title ?? 'Nova Card'), 'subtitle' => trim(sprintf('@%s • %s', (string) $comment->user?->username, str($comment->body)->limit(80))), 'public_url' => $comment->card ? $comment->card->publicUrl() . '#comment-' . $comment->id : null, 'moderation_url' => route('cp.cards.index'), 'moderation_target' => $comment->card ? $this->summarizeModerationCard($comment->card, 'Card attached to this comment') : null, ]; } private function summarizeNovaCardChallengeEntry(int $targetId): array { $entry = NovaCardChallengeEntry::query() ->with(['challenge', 'card.user']) ->findOrFail($targetId); return [ 'exists' => $entry->challenge !== null && $entry->card !== null, 'type' => 'nova_card_challenge_entry', 'id' => (int) $entry->id, 'label' => (string) ($entry->card?->title ?? 'Challenge entry'), 'subtitle' => trim(sprintf('Entry for %s%s', $entry->challenge?->title ? '"'.$entry->challenge->title.'"' : 'challenge', $entry->card?->user?->username ? ' • @'.$entry->card->user->username : '')), 'public_url' => $entry->card?->publicUrl(), 'moderation_url' => $entry->challenge ? route('cp.cards.challenges.index') : route('cp.cards.index'), 'moderation_target' => $entry->card ? $this->summarizeModerationCard($entry->card, 'Card attached to this challenge entry') : null, ]; } private function summarizeModerationCard(NovaCard $card, ?string $context = null): array { $moderationReasons = $this->moderation->storedReasons($card); $moderationOverride = $this->moderation->latestOverride($card); return [ 'kind' => 'nova_card', 'card_id' => (int) $card->id, 'title' => (string) $card->title, 'context' => $context, 'status' => (string) $card->status, 'moderation_status' => (string) $card->moderation_status, 'moderation_reasons' => $moderationReasons, 'moderation_reason_labels' => $this->moderation->labelsFor($moderationReasons), 'moderation_source' => $this->moderation->storedSource($card), 'moderation_override' => $moderationOverride, 'moderation_override_history' => $this->moderation->overrideHistory($card), 'available_actions' => [ ['action' => 'approve_card', 'label' => 'Approve card'], ['action' => 'flag_card', 'label' => 'Flag card'], ['action' => 'reject_card', 'label' => 'Reject card'], ], ]; } }