updateFinding($finding, ModerationStatus::ReviewedSafe, $reviewer, $notes, ModerationActionType::MarkSafe); $this->feedback->record($finding->fresh(), 'marked_safe', $reviewer, $notes); } public function confirmSpam(ContentModerationFinding $finding, User $reviewer, ?string $notes = null): void { $this->updateFinding($finding, ModerationStatus::ConfirmedSpam, $reviewer, $notes, ModerationActionType::ConfirmSpam); $this->domains->trackDomains((array) $finding->matched_domains_json, true, true); $this->feedback->record($finding->fresh(), 'confirmed_spam', $reviewer, $notes); } public function ignore(ContentModerationFinding $finding, User $reviewer, ?string $notes = null): void { $this->updateFinding($finding, ModerationStatus::Ignored, $reviewer, $notes, ModerationActionType::Ignore); } public function resolve(ContentModerationFinding $finding, User $reviewer, ?string $notes = null): void { $this->updateFinding($finding, ModerationStatus::Resolved, $reviewer, $notes, ModerationActionType::Resolve); $this->feedback->record($finding->fresh(), 'resolved', $reviewer, $notes); } public function markFalsePositive(ContentModerationFinding $finding, User $reviewer, ?string $notes = null): void { DB::transaction(function () use ($finding, $reviewer, $notes): void { $oldVisibility = null; $newVisibility = null; if (in_array($finding->content_type, [ModerationContentType::ArtworkComment, ModerationContentType::ArtworkDescription, ModerationContentType::ArtworkTitle], true)) { [$action, $oldVisibility, $newVisibility] = match ($finding->content_type) { ModerationContentType::ArtworkComment => $this->restoreComment($finding), default => $this->restoreArtwork($finding), }; $actionType = $action; } else { $actionType = ModerationActionType::MarkFalsePositive; } $oldStatus = $finding->status->value; $finding->forceFill([ 'status' => ModerationStatus::ReviewedSafe, 'reviewed_by' => $reviewer->id, 'reviewed_at' => now(), 'resolved_by' => $reviewer->id, 'resolved_at' => now(), 'restored_by' => $oldVisibility !== null ? $reviewer->id : $finding->restored_by, 'restored_at' => $oldVisibility !== null ? now() : $finding->restored_at, 'is_auto_hidden' => false, 'is_false_positive' => true, 'false_positive_count' => ((int) $finding->false_positive_count) + 1, 'action_taken' => ModerationActionType::MarkFalsePositive->value, 'admin_notes' => $this->normalizeNotes($notes, $finding->admin_notes), ])->save(); $this->actionLogs->log( $finding, $finding->content_type->value, $finding->content_id, ModerationActionType::MarkFalsePositive->value, $reviewer, $oldStatus, ModerationStatus::ReviewedSafe->value, $oldVisibility, $newVisibility, $notes, ['restored_action' => $actionType->value], ); $this->feedback->record($finding->fresh(), 'false_positive', $reviewer, $notes, ['restored' => $oldVisibility !== null]); }); } public function hideContent(ContentModerationFinding $finding, User $reviewer, ?string $notes = null): ModerationActionType { return DB::transaction(function () use ($finding, $reviewer, $notes): ModerationActionType { [$action, $oldVisibility, $newVisibility] = match ($finding->content_type) { ModerationContentType::ArtworkComment => $this->hideComment($finding, false), ModerationContentType::ArtworkDescription, ModerationContentType::ArtworkTitle => $this->hideArtwork($finding, false), }; $this->updateFinding($finding, ModerationStatus::ConfirmedSpam, $reviewer, $notes, $action, $oldVisibility, $newVisibility); $this->domains->trackDomains((array) $finding->matched_domains_json, true, true); return $action; }); } public function autoHideContent(ContentModerationFinding $finding, ?string $notes = null): ModerationActionType { return DB::transaction(function () use ($finding, $notes): ModerationActionType { [$action, $oldVisibility, $newVisibility] = match ($finding->content_type) { ModerationContentType::ArtworkComment => $this->hideComment($finding, true), ModerationContentType::ArtworkDescription, ModerationContentType::ArtworkTitle => $this->hideArtwork($finding, true), }; $finding->forceFill([ 'is_auto_hidden' => true, 'auto_action_taken' => $action->value, 'auto_hidden_at' => \now(), 'action_taken' => $action->value, 'admin_notes' => $this->normalizeNotes($notes, $finding->admin_notes), ])->save(); $this->actionLogs->log( $finding, $finding->content_type->value, $finding->content_id, $action->value, null, $finding->status->value, $finding->status->value, $oldVisibility, $newVisibility, $notes, ['automated' => true], ); $this->domains->trackDomains((array) $finding->matched_domains_json, true, false); return $action; }); } public function restoreContent(ContentModerationFinding $finding, User $reviewer, ?string $notes = null): ModerationActionType { return DB::transaction(function () use ($finding, $reviewer, $notes): ModerationActionType { [$action, $oldVisibility, $newVisibility] = match ($finding->content_type) { ModerationContentType::ArtworkComment => $this->restoreComment($finding), ModerationContentType::ArtworkDescription, ModerationContentType::ArtworkTitle => $this->restoreArtwork($finding), }; $oldStatus = $finding->status->value; $finding->forceFill([ 'status' => ModerationStatus::ReviewedSafe, 'reviewed_by' => $reviewer->id, 'reviewed_at' => \now(), 'resolved_by' => $reviewer->id, 'resolved_at' => \now(), 'restored_by' => $reviewer->id, 'restored_at' => \now(), 'is_auto_hidden' => false, 'action_taken' => $action->value, 'admin_notes' => $this->normalizeNotes($notes, $finding->admin_notes), ])->save(); $this->actionLogs->log( $finding, $finding->content_type->value, $finding->content_id, $action->value, $reviewer, $oldStatus, ModerationStatus::ReviewedSafe->value, $oldVisibility, $newVisibility, $notes, ['restored' => true], ); return $action; }); } /** * @return array{0:ModerationActionType,1:string,2:string} */ private function hideComment(ContentModerationFinding $finding, bool $automated): array { $comment = ArtworkComment::query()->find($finding->content_id); $oldVisibility = $comment && $comment->is_approved ? 'visible' : 'hidden'; if ($comment) { $comment->forceFill(['is_approved' => false])->save(); } return [$automated ? ModerationActionType::AutoHideComment : ModerationActionType::HideComment, $oldVisibility, 'hidden']; } /** * @return array{0:ModerationActionType,1:string,2:string} */ private function hideArtwork(ContentModerationFinding $finding, bool $automated): array { $artworkId = (int) ($finding->artwork_id ?? $finding->content_id); $artwork = Artwork::query()->find($artworkId); $oldVisibility = $artwork && $artwork->is_public ? 'visible' : 'hidden'; if ($artwork) { $artwork->forceFill(['is_public' => false])->save(); } return [$automated ? ModerationActionType::AutoHideArtwork : ModerationActionType::HideArtwork, $oldVisibility, 'hidden']; } /** * @return array{0:ModerationActionType,1:string,2:string} */ private function restoreComment(ContentModerationFinding $finding): array { $comment = ArtworkComment::query()->find($finding->content_id); $oldVisibility = $comment && $comment->is_approved ? 'visible' : 'hidden'; if ($comment) { $comment->forceFill(['is_approved' => true])->save(); } return [ModerationActionType::RestoreComment, $oldVisibility, 'visible']; } /** * @return array{0:ModerationActionType,1:string,2:string} */ private function restoreArtwork(ContentModerationFinding $finding): array { $artworkId = (int) ($finding->artwork_id ?? $finding->content_id); $artwork = Artwork::query()->find($artworkId); $oldVisibility = $artwork && $artwork->is_public ? 'visible' : 'hidden'; if ($artwork) { $artwork->forceFill(['is_public' => true])->save(); } return [ModerationActionType::RestoreArtwork, $oldVisibility, 'visible']; } private function updateFinding( ContentModerationFinding $finding, ModerationStatus $status, User $reviewer, ?string $notes, ModerationActionType $action, ?string $oldVisibility = null, ?string $newVisibility = null, ): void { $oldStatus = $finding->status->value; $finding->forceFill([ 'status' => $status, 'reviewed_by' => $reviewer->id, 'reviewed_at' => \now(), 'resolved_by' => $reviewer->id, 'resolved_at' => \now(), 'action_taken' => $action->value, 'admin_notes' => $this->normalizeNotes($notes, $finding->admin_notes), ])->save(); $this->actionLogs->log( $finding, $finding->content_type->value, $finding->content_id, $action->value, $reviewer, $oldStatus, $status->value, $oldVisibility, $newVisibility, $notes, ); } private function normalizeNotes(?string $incoming, ?string $existing): ?string { $normalized = is_string($incoming) ? trim($incoming) : ''; return $normalized !== '' ? $normalized : $existing; } }