Files
fotospiel-app/app/Http/Controllers/Tenant/EventPhotoArchiveController.php
Codex Agent fa33e7cbcf Fix Event & EventType resource issues and apply formatting
- Fix EventType deletion error handling (constraint violations)
- Fix Event update error (package_id column missing)
- Fix Event Type dropdown options (JSON display issue)
- Fix EventPackagesRelationManager query error
- Add missing translations for deletion errors
- Apply Pint formatting
2026-01-21 10:34:06 +01:00

132 lines
4.1 KiB
PHP

<?php
namespace App\Http\Controllers\Tenant;
use App\Http\Controllers\Controller;
use App\Models\Event;
use App\Models\EventMediaAsset;
use App\Models\Photo;
use Illuminate\Contracts\Filesystem\FileNotFoundException;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Symfony\Component\HttpFoundation\StreamedResponse;
use ZipArchive;
class EventPhotoArchiveController extends Controller
{
public function __invoke(Request $request, Event $event): StreamedResponse
{
$user = Auth::user();
if (! $user || (int) $user->tenant_id !== (int) $event->tenant_id) {
abort(403, 'Unauthorized');
}
$photos = Photo::query()
->with('mediaAsset')
->where('event_id', $event->id)
->where('status', 'approved')
->orderBy('created_at')
->get();
if ($photos->isEmpty()) {
abort(404, 'No approved photos available for this event.');
}
$zip = new ZipArchive;
$tempPath = tempnam(sys_get_temp_dir(), 'fotospiel-photos-');
if ($tempPath === false || $zip->open($tempPath, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== true) {
abort(500, 'Unable to generate archive.');
}
foreach ($photos as $photo) {
$filename = $this->buildFilename($event, $photo);
$asset = $photo->mediaAsset ?? EventMediaAsset::query()
->where('photo_id', $photo->id)
->where('variant', 'original')
->first();
$added = false;
if ($asset && $asset->path) {
$added = $this->addDiskFileToArchive($zip, $asset->disk ?? config('filesystems.default'), $asset->path, $filename);
}
if (! $added && $photo->file_path) {
$added = $this->addDiskFileToArchive($zip, config('filesystems.default'), $photo->file_path, $filename);
}
if (! $added) {
Log::warning('Skipping photo in archive build', [
'event_id' => $event->id,
'photo_id' => $photo->id,
]);
}
}
$zip->close();
$downloadName = sprintf('fotospiel-event-%s-photos.zip', $event->slug ?? $event->id);
return response()->streamDownload(function () use ($tempPath) {
$stream = fopen($tempPath, 'rb');
if (! $stream) {
throw new FileNotFoundException('Archive could not be opened.');
}
fpassthru($stream);
fclose($stream);
unlink($tempPath);
}, $downloadName, [
'Content-Type' => 'application/zip',
]);
}
private function buildFilename(Event $event, Photo $photo): string
{
$timestamp = $photo->created_at?->format('Ymd_His') ?? now()->format('Ymd_His');
$extension = pathinfo($photo->file_path ?? '', PATHINFO_EXTENSION) ?: 'jpg';
return sprintf('%s-photo-%d.%s', $timestamp, $photo->id, $extension);
}
private function addDiskFileToArchive(ZipArchive $zip, ?string $diskName, string $path, string $filename): bool
{
$disk = $diskName ? Storage::disk($diskName) : Storage::disk(config('filesystems.default'));
try {
if (method_exists($disk, 'path')) {
$absolute = $disk->path($path);
if (is_file($absolute)) {
return $zip->addFile($absolute, $filename);
}
}
$stream = $disk->readStream($path);
if ($stream) {
$contents = stream_get_contents($stream);
fclose($stream);
if ($contents !== false) {
return $zip->addFromString($filename, $contents);
}
}
} catch (\Throwable $e) {
Log::warning('Failed to add file to archive', [
'disk' => $diskName,
'path' => $path,
'error' => $e->getMessage(),
]);
}
return false;
}
}