- Turnstile für Gäste und unbestätigte Konten (TurnstileVerifier, <x-turnstile>); ist Cloudflare nicht erreichbar, wird durchgelassen und im Verlauf, in der Kontakt-Queue und in der Betriebsüberwachung markiert - Inhaltsfilter des Kontaktformulars (fremde Links, Floskel mit Seitentitel, Zeichensalat, Sperrliste, Absenden unter 3 Sekunden); Treffer landen in der Spam-Quarantäne ohne Mail, Löschung nach 30 Tagen - Admin: Filter „Spam“, Sammelaktion, „Kein Spam“ holt die Mail nach - Registrierung lehnt Zufallsnamen ab; users:prune-bot-registrations (täglicher Lauf erst mit USERS_PRUNE_BOT_REGISTRATIONS=true) - contact-messages:quarantine-spam für den Altbestand - CSP erlaubt challenges.cloudflare.com, Log-Kanal spam - Datenschutzerklärung: Abschnitt 6a Cloudflare Turnstile (Vorschlag) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
72 lines
3.1 KiB
PHP
72 lines
3.1 KiB
PHP
<?php
|
|
|
|
namespace App\Observers;
|
|
|
|
use App\Enums\ActivityType;
|
|
use App\Enums\ContactMessageStatus;
|
|
use App\Models\ContactMessage;
|
|
use App\Models\LegalRequest;
|
|
use App\Models\NewsletterSubscription;
|
|
use App\Models\TerminationRequest;
|
|
use App\Models\WithdrawalRequest;
|
|
use App\Services\ActivityLog;
|
|
use Illuminate\Database\Eloquent\Model;
|
|
|
|
/**
|
|
* Protokolliert Formular-Eingänge im Aktivitäts-Verlauf — überall dort,
|
|
* wo das Anlegen des Datensatzes selbst das Ereignis ist (Kontakt,
|
|
* Rechtsanfrage, Widerruf, Kündigung, Newsletter-Anmeldung).
|
|
*/
|
|
class ActivityLogObserver
|
|
{
|
|
public function __construct(private readonly ActivityLog $activityLog) {}
|
|
|
|
public function created(Model $model): void
|
|
{
|
|
match (true) {
|
|
// Spam-Quarantäne gehört nicht in den Verlauf.
|
|
$model instanceof ContactMessage && $model->status === ContactMessageStatus::Spam => null,
|
|
$model instanceof ContactMessage => $this->activityLog->record(
|
|
ActivityType::ContactRequest,
|
|
sprintf('Kontaktanfrage „%s“ von %s (%s).', $model->topic->label(), $model->name, $model->email),
|
|
user: $model->user,
|
|
subject: $model,
|
|
actorLabel: (string) ($model->name ?: $model->email),
|
|
meta: ['portal' => $this->stringable($model->portal)],
|
|
),
|
|
$model instanceof LegalRequest => $this->activityLog->record(
|
|
ActivityType::LegalRequest,
|
|
sprintf('Rechtsanfrage (%s) von %s.', $model->type->label(), $model->requester_email),
|
|
subject: $model,
|
|
actorLabel: (string) $model->requester_email,
|
|
meta: ['type' => $model->type->value, 'press_release_id' => $model->press_release_id],
|
|
),
|
|
$model instanceof WithdrawalRequest => $this->activityLog->record(
|
|
ActivityType::WithdrawalRequest,
|
|
sprintf('Widerruf eingegangen von %s (%s).', $model->name, $model->email),
|
|
subject: $model,
|
|
actorLabel: (string) ($model->name ?: $model->email),
|
|
),
|
|
$model instanceof TerminationRequest => $this->activityLog->record(
|
|
ActivityType::TerminationRequest,
|
|
sprintf('Kündigung (%s) von %s (%s).', $model->kind, $model->name, $model->email),
|
|
subject: $model,
|
|
actorLabel: (string) ($model->name ?: $model->email),
|
|
meta: ['kind' => $model->kind],
|
|
),
|
|
$model instanceof NewsletterSubscription => $this->activityLog->record(
|
|
ActivityType::NewsletterSubscription,
|
|
sprintf('Newsletter-Anmeldung von %s.', $model->email),
|
|
subject: $model,
|
|
actorLabel: (string) $model->email,
|
|
meta: ['portal' => $this->stringable($model->portal)],
|
|
),
|
|
default => null,
|
|
};
|
|
}
|
|
|
|
private function stringable(mixed $value): ?string
|
|
{
|
|
return $value instanceof \BackedEnum ? (string) $value->value : ($value !== null ? (string) $value : null);
|
|
}
|
|
}
|