presseportale/app/Policies/PressReleasePolicy.php
Kevin Adametz 2a2215b3c3 audit: Nacharbeit Umsetzungsaudit 25.09.2026 — Bildfreigabe, Beweisarchiv, KI-Atomarität, Berichte (Block 8, N01–N05, N07, N08, N10–N21, 5.1, 5.2)
Bilder (N01, N05, N07, N08): gemeinsamer Freigabezustand
PressReleaseImage::isPubliclyDeliverable() für Seiten, OG und Medienroute
(Bestand bleibt sichtbar, v2 nur mit Bestätigung, Admin-Upload bis zum
Kundenklick nicht öffentlich); eigene Ability confirmImageRights nur für den
Kontoinhaber, ohne Super-Admin-Kurzschluss, Identität und ausstehende
Bestätigung auch im Service; Status-Regel unter der PM-Sperre; Cache-Flush
nach dem Commit außerhalb der Dateibereinigung; Snapshot nach Bestätigung an
veröffentlichter PM; Bestätigungstexte an die gezeigte Kennung gebunden.

Beweisarchiv (N02, N10, N11, N12, N13, 5.1): Tombstone unter Zeilensperre mit
erneuter Hold-/Fristprüfung; Dateioperationen ausgewertet, Commands mit
FAILURE; record() über .tmp + move; Marker archive_pending_at mit
archive:retry-pending und archive:sweep-orphans; Anonymisierung entfernt Bilder
über den Remover mit Antragsbezug; Draft-Purge räumt Medien nie
veröffentlichter abgelehnter Einreichungen; App-Kopien fallen mit dem
Archiv-Tombstone; label_corrected nur für veröffentlichte Bilder;
archive_entries.legal_request_id; FileDeletion-Helfer; Systemstatus-Checks.

KI und Hintergrund (N03, N04, N14, N20, N21): Ergebnis-UPDATE und Audit in
einer Transaktion; erwarteter Hash bis in reject()/holdForManualReview();
Ablaufmail per beanspruchtem Marker in einer Transaktion; strikte
Schemaprüfung der Vision-Antwort; Fortschrittsmarke ungecacht.

Berichte und Canonical (N15–N19, 5.2): lückenlose Alterskohorten;
Feed-Identität mit Portal und Sprache; Sitemap-Rolle in der Kundenansicht;
Indexverteilung auf einer Grundgesamtheit; Kopien nach Wegfall der
Erstquelle neu bewertet; Kategorie-Admin leert Seiten-Caches; Dry-Run
zählt alle Tabellen.

Gesamtsuite: 2.122 Tests grün, einer übersprungen; Larastan und Pint sauber.
Zwei Migrationen (nullable Spalten, INSTANT). N09 offen.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-25 14:26:49 +00:00

130 lines
4.1 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace App\Policies;
use App\Enums\PressReleaseStatus;
use App\Models\PressRelease;
use App\Models\User;
class PressReleasePolicy
{
/**
* Kurzschluss nur für AKTIVE Super-Admins (Audit 7.2): ein deaktiviertes
* Konto fällt auf die regulären Prüfungen zurück, die es ablehnen.
*/
public function before(User $user, string $ability): ?bool
{
// Die Kundenbestätigung eines Admin-Uploads ist an die Identität des
// Kontoinhabers gebunden (DU E1/E10, Audit N05) — kein Kurzschluss.
if ($ability === 'confirmImageRights') {
return null;
}
return ($user->is_super_admin && $user->is_active) ? true : null;
}
public function viewAny(User $user): bool
{
return $user->canAccessCustomer();
}
public function view(User $user, PressRelease $pressRelease): bool
{
if ($user->canAccessAdmin()) {
return true;
}
return $this->canManage($user, $pressRelease);
}
public function create(User $user): bool
{
return $user->canAccessCustomer();
}
public function update(User $user, PressRelease $pressRelease): bool
{
if (! $this->canManage($user, $pressRelease) && ! $user->canAccessAdmin()) {
return false;
}
return in_array(
$pressRelease->status,
[PressReleaseStatus::Draft, PressReleaseStatus::Rejected, PressReleaseStatus::Review],
true,
) || $user->canAccessAdmin();
}
public function submitForReview(User $user, PressRelease $pressRelease): bool
{
return $this->canManage($user, $pressRelease)
&& in_array($pressRelease->status, [PressReleaseStatus::Draft, PressReleaseStatus::Rejected], true);
}
public function delete(User $user, PressRelease $pressRelease): bool
{
if ($user->canAccessAdmin()) {
return true;
}
return $this->canManage($user, $pressRelease)
&& $pressRelease->status !== PressReleaseStatus::Published;
}
public function restore(User $user, PressRelease $pressRelease): bool
{
return $user->canAccessAdmin();
}
public function forceDelete(User $user, PressRelease $pressRelease): bool
{
return $user->is_super_admin === true;
}
/**
* „Bild bestätigen" nach einem Admin-Upload (Bildkennzeichnung B.4,
* DU E10): ausschließlich der Kontoinhaber der Meldung — er ist der
* Vertragspartner (DU E1) —, auch bei bereits veröffentlichter PM
* (Admin-Bildtausch). Nicht von `update` geerbt (Audit N05).
*/
public function confirmImageRights(User $user, PressRelease $pressRelease): bool
{
return $this->isAuthor($user, $pressRelease)
&& in_array(
$pressRelease->status,
[PressReleaseStatus::Draft, PressReleaseStatus::Rejected, PressReleaseStatus::Review, PressReleaseStatus::Published],
true,
);
}
public function publish(User $user, PressRelease $pressRelease): bool
{
return $user->canAccessAdmin() && $user->can('press-releases:publish');
}
/**
* Veröffentlichungsnachweis darf laden, wer die PM verwalten darf (Autor
* oder Firmenmitglied) bzw. ein Admin. Ob der Nachweis bereits gekauft
* wurde, prüft der ProofPdfService separat.
*/
public function downloadProof(User $user, PressRelease $pressRelease): bool
{
return $user->canAccessAdmin() || $this->canManage($user, $pressRelease);
}
/**
* Zugriff auf eine PM hat der Autor ODER ein Mitglied der zugeordneten
* Firma (Owner/Team-Mitglied). So sehen/bearbeiten Firmenkontakte – inkl.
* der per Magic-Link lazy angelegten Accounts – die PMs ihrer Firma.
*/
private function canManage(User $user, PressRelease $pressRelease): bool
{
return $this->isAuthor($user, $pressRelease)
|| $user->canAccessCompany($pressRelease->company_id);
}
private function isAuthor(User $user, PressRelease $pressRelease): bool
{
return $pressRelease->user_id === $user->id;
}
}