261 lines
11 KiB
PHP
261 lines
11 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Support\Moderation;
|
|
|
|
use App\Models\Collection;
|
|
use App\Models\CollectionComment;
|
|
use App\Models\CollectionSubmission;
|
|
use App\Models\ConversationParticipant;
|
|
use App\Models\Message;
|
|
use App\Models\NovaCard;
|
|
use App\Models\NovaCardChallenge;
|
|
use App\Models\NovaCardChallengeEntry;
|
|
use App\Models\NovaCardComment;
|
|
use App\Models\Report;
|
|
use App\Models\Story;
|
|
use App\Models\User;
|
|
use App\Services\NovaCards\NovaCardPublishModerationService;
|
|
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
|
|
|
class ReportTargetResolver
|
|
{
|
|
public function __construct(
|
|
private readonly NovaCardPublishModerationService $moderation,
|
|
) {
|
|
}
|
|
|
|
/**
|
|
* @return array<int, string>
|
|
*/
|
|
public function supportedTargetTypes(): array
|
|
{
|
|
return [
|
|
'message',
|
|
'conversation',
|
|
'user',
|
|
'story',
|
|
'collection',
|
|
'collection_comment',
|
|
'collection_submission',
|
|
...$this->novaCardTargetTypes(),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @return array<int, string>
|
|
*/
|
|
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'],
|
|
],
|
|
];
|
|
}
|
|
} |