images: nur freigegebene Bilder in Feed und PDF, versionierte Veröffentlichungssnapshots, Bestätigung an das gezeigte Bild gebunden, getrennte Rückhaltegründe (Umsetzungsaudit 29.09.: B-01 bis B-14, N09, OPT-3 bis OPT-5; 02.10.: O03)
- B-01/B-02: Startseiten-Feed, Card-Helfer und Nachweis-PDF nur mit isPubliclyDeliverable() - B-03: Livewire-Temp-Upload 16 MiB - N09: Snapshot je Bytes- und Metadaten-Hash inkl. OG-Variante, put() geprüft, alle Fassungen ins Archiv - O03: published_ever_at in der Freigabetransaktion (markPublishedIfNowPublic) — Kundenbestätigung, Vorab-Sichtung, Korrektur - B-04/B-14: Bestätigung an Bild-ID und SHA gebunden, unter Sperre - B-05/B-06: Entfernen unter Sperre mit frischem Stand und Archivauftrag; Korrektur archiviert über den Auftrag, Nacharbeiten abgesichert - B-07: precheck_withheld_at getrennt von withheld_at (Migration + Übernahme) - B-08, B-09, B-11, B-12: published_ever_at nur bei Status published, Admin-Eigentümer mit Kundenregel, Sweep verwaister Originale, Verarbeitungsfehler als Formularmeldung - K-02-Teil: nach Kundenbestätigung erneut routen - OPT-3 bis OPT-5: pingImage für JPEG, Hash durchgereicht, erstes Frame Admin-PM-Detailseite enthält zusätzlich K-06 (Autorisierung) und A-08 (Nachweis per POST), weil die Datei ungeteilt committet ist. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
60650baeb8
commit
f1c0e2766b
23 changed files with 1675 additions and 149 deletions
|
|
@ -9,6 +9,7 @@ use App\Exceptions\InsufficientCreditsException;
|
|||
use App\Models\BookingConfirmation;
|
||||
use App\Models\LegalDocument;
|
||||
use App\Models\PressRelease;
|
||||
use App\Models\PressReleaseImage;
|
||||
use App\Models\SinglePurchase;
|
||||
use App\Services\Auth\MagicLinkGenerator;
|
||||
use App\Services\Billing\CreditPricingService;
|
||||
|
|
@ -121,6 +122,17 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung')] class extends
|
|||
#[Locked]
|
||||
public ?string $imageLegalTextKey = null;
|
||||
|
||||
/**
|
||||
* Bild, das die Seite zur Bestätigung anzeigt (Umsetzungsaudit 29.09.,
|
||||
* B-04): Kennung und Datei-Hash werden beim Rendern gesetzt und beim
|
||||
* Bestätigen gegen das aktuelle Titelbild geprüft.
|
||||
*/
|
||||
#[Locked]
|
||||
public ?int $confirmImageId = null;
|
||||
|
||||
#[Locked]
|
||||
public ?string $confirmImageSha = null;
|
||||
|
||||
public function confirmImageRights(CustomerImageConfirmation $confirmation, ImageRightsTextRenderer $texts): void
|
||||
{
|
||||
$pr = $this->getMyPR();
|
||||
|
|
@ -134,6 +146,14 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung')] class extends
|
|||
return;
|
||||
}
|
||||
|
||||
// Bestätigt wird nur das Bild, das der Kunde gesehen hat (B-04).
|
||||
if ($this->confirmImageId !== $image->id || $this->confirmImageSha === null || $image->original_sha256 === null || ! hash_equals($this->confirmImageSha, $image->original_sha256)) {
|
||||
$this->imageRightsConfirmed = false;
|
||||
$this->addError('imageRightsConfirmed', __('Das Bild wurde inzwischen geändert. Bitte prüfen Sie das aktuelle Titelbild und bestätigen Sie erneut.'));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->validate(
|
||||
['imageRightsConfirmed' => ['accepted']],
|
||||
['imageRightsConfirmed.accepted' => __('Die Bestätigung ist erforderlich, damit das Bild veröffentlicht werden kann.')],
|
||||
|
|
@ -157,6 +177,7 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung')] class extends
|
|||
request()->ip(),
|
||||
request()->userAgent(),
|
||||
route('agb', ['edition' => 'de']),
|
||||
$this->confirmImageSha,
|
||||
);
|
||||
} catch (ImageUploadException $exception) {
|
||||
$this->addError('imageRightsConfirmed', $exception->getMessage());
|
||||
|
|
@ -169,6 +190,17 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung')] class extends
|
|||
Flux::toast(text: __('Bildrechte bestätigt. Das Titelbild kann jetzt veröffentlicht werden.'), variant: 'success');
|
||||
}
|
||||
|
||||
/**
|
||||
* Angezeigtes Titelbild für die Bestätigung merken (B-04).
|
||||
*/
|
||||
private function rememberConfirmationImage(?PressReleaseImage $image): ?PressReleaseImage
|
||||
{
|
||||
$this->confirmImageId = $image?->id;
|
||||
$this->confirmImageSha = $image?->original_sha256;
|
||||
|
||||
return $image;
|
||||
}
|
||||
|
||||
public function mount(int $id): void
|
||||
{
|
||||
$this->id = $id;
|
||||
|
|
@ -891,7 +923,7 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung')] class extends
|
|||
'categoryName' => $categoryName,
|
||||
'coverUrl' => $cover->coverUrl($pr, 'cover'),
|
||||
'coverIsPlaceholder' => $cover->coverIsPlaceholder($pr),
|
||||
'titleImage' => $titleImage = $pr->images()->with('latestRightsConfirmation')->orderByDesc('is_preview')->orderBy('sort_order')->orderBy('id')->first(),
|
||||
'titleImage' => $titleImage = $this->rememberConfirmationImage($pr->images()->with('latestRightsConfirmation')->orderByDesc('is_preview')->orderBy('sort_order')->orderBy('id')->first()),
|
||||
'awaitsImageConfirmation' => $titleImage?->awaitsCustomerConfirmation() === true && auth()->user()->can('confirmImageRights', $pr),
|
||||
'imageLegalShort' => $imageLegalText
|
||||
? app(ImageRightsTextRenderer::class)->renderShort($imageLegalText, route('agb', ['edition' => 'de']))
|
||||
|
|
@ -1671,6 +1703,9 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung')] class extends
|
|||
{{ __('Nicht in der Sitemap — für Suchmaschinen ist die Erstfassung auf dem anderen Portal die maßgebliche Adresse (rel=canonical). Leser finden die Meldung weiterhin hier.') }}
|
||||
@elseif ($visibility['in_sitemap_since'])
|
||||
{{ __('Gelistet seit :date (:portals) — Veröffentlichung und Sitemap-Eintrag fallen zusammen.', ['date' => $visibility['in_sitemap_since']->format('d.m.Y'), 'portals' => implode(', ', $visibility['sitemap_portals'] ?? [])]) }}
|
||||
@elseif (($visibility['sitemap_role'] ?? null) === 'source')
|
||||
{{-- Fassung auch auf dem anderen Portal: Die Rolle kann gewechselt haben, ein Datum wäre geraten (N17). --}}
|
||||
{{ __('In der Sitemap gelistet (:portals). Die Meldung liegt auch auf dem anderen Portal; seit wann diese Fassung die maßgebliche ist, lässt sich nicht genau sagen.', ['portals' => implode(', ', $visibility['sitemap_portals'] ?? [])]) }}
|
||||
@else
|
||||
{{ __('Derzeit nicht gelistet — die Meldung ist nicht öffentlich.') }}
|
||||
@endif
|
||||
|
|
@ -1711,7 +1746,7 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung')] class extends
|
|||
</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
<p class="mt-2 mb-0 text-[11px] text-[color:var(--color-ink-4)]">{{ __('Ein Abruf belegt, dass der Crawler des Dienstes die Seite geholt hat — nicht, dass sie dort angezeigt oder zitiert wird.') }}</p>
|
||||
<p class="mt-2 mb-0 text-[11px] text-[color:var(--color-ink-4)]">{{ __('Zugeordnet laut Kennung des Abrufers (User-Agent), die sich nicht überprüfen lässt. Ein Abruf belegt, dass der Crawler des Dienstes die Seite geholt hat — nicht, dass sie dort angezeigt oder zitiert wird. Crawler-Abrufe zählen nicht als Aufrufe.') }}</p>
|
||||
@else
|
||||
<p class="mt-1 mb-0 text-[12.5px] text-[color:var(--color-ink-3)]">
|
||||
{{ __('Noch kein Abruf protokolliert.') }}
|
||||
|
|
@ -1851,7 +1886,8 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung')] class extends
|
|||
:confirm-label="__('Zur Prüfung freigeben')"
|
||||
:quota-total="$quotaTotal"
|
||||
:quota-remaining="$quotaRemaining"
|
||||
:has-title-image="! $coverIsPlaceholder" />
|
||||
:has-title-image="! $coverIsPlaceholder"
|
||||
:legal-documents="$this->legalDocumentsForSubmit()" />
|
||||
@endif
|
||||
|
||||
{{-- ============== BOILERPLATE-OVERRIDE ============== --}}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue