'array', 'license_type' => ImageLicenseType::class, 'persons_consent' => 'boolean', 'rights_confirmed_at' => 'datetime', 'is_ai_generated' => 'boolean', 'ai_declaration' => AiImageDeclaration::class, 'is_preview' => 'boolean', 'sort_order' => 'integer', 'width' => 'integer', 'height' => 'integer', 'deleted_at' => 'datetime', ]; } /** * @return BelongsTo */ public function pressRelease(): BelongsTo { return $this->belongsTo(PressRelease::class); } public function url(bool $signed = false): ?string { if (blank($this->path)) { return null; } return $this->resolveDiskUrl($this->path, signed: $signed); } public function variantUrl(string $key, bool $signed = false): ?string { $variantPath = $this->pathForVariant($key); if (! is_string($variantPath) || blank($variantPath)) { return null; } return $this->resolveDiskUrl($variantPath, $key, $signed); } public function pathForVariant(?string $key = null): ?string { if ($key === null || $key === 'original') { return $this->path; } $variants = $this->getAttribute('variants'); return is_array($variants) && is_string($variants[$key] ?? null) ? $variants[$key] : null; } /** * Löst die URL je nach Disk auf: public → direkt, sonst über die geschützte * Media-Route. `signed=true` erzeugt eine temporär signierte Route, damit * die anonyme Token-Vorschau (unveröffentlichte PM) die Bilder laden kann, * ohne die Media-Route generell zu öffnen. */ private function resolveDiskUrl(string $relativePath, ?string $variant = null, bool $signed = false): string { if ($this->disk === 'public') { return asset('storage/'.ltrim($relativePath, '/')); } $parameters = array_filter([ 'image' => $this->getKey(), 'variant' => $variant, ]); if ($signed) { return URL::temporarySignedRoute('media.press-release-images.show', now()->addHours(6), $parameters); } return route('media.press-release-images.show', $parameters); } }