photo-upload und ansicht im admin gefixt
This commit is contained in:
@@ -2789,6 +2789,7 @@ class EventPublicController extends BaseController
|
||||
|
||||
$photoId = DB::table('photos')->insertGetId([
|
||||
'event_id' => $eventId,
|
||||
'tenant_id' => $tenantModel->id,
|
||||
'task_id' => $validated['task_id'] ?? null,
|
||||
'guest_name' => $validated['guest_name'] ?? $deviceId,
|
||||
'file_path' => $url,
|
||||
|
||||
@@ -23,6 +23,7 @@ use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class PhotoController extends Controller
|
||||
@@ -136,6 +137,117 @@ class PhotoController extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
public function asset(Request $request, Event $event, Photo $photo, string $variant): StreamedResponse|JsonResponse
|
||||
{
|
||||
if ($photo->event_id !== $event->id) {
|
||||
return ApiError::response(
|
||||
'photo_not_found',
|
||||
'Foto nicht gefunden',
|
||||
'Das Foto gehört nicht zu diesem Event.',
|
||||
Response::HTTP_NOT_FOUND,
|
||||
['photo_id' => $photo->id]
|
||||
);
|
||||
}
|
||||
|
||||
$preferOriginals = (bool) ($event->settings['watermark_serve_originals'] ?? false);
|
||||
[$disk, $path, $mime] = $this->resolvePhotoVariant($photo, $variant, $preferOriginals);
|
||||
|
||||
if (! $path) {
|
||||
return ApiError::response(
|
||||
'photo_unavailable',
|
||||
'Photo Unavailable',
|
||||
'The requested photo could not be loaded.',
|
||||
Response::HTTP_NOT_FOUND,
|
||||
[
|
||||
'photo_id' => $photo->id,
|
||||
'event_id' => $event->id,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
if (Str::startsWith($path, ['http://', 'https://'])) {
|
||||
return redirect()->away($path);
|
||||
}
|
||||
|
||||
try {
|
||||
$storage = Storage::disk($disk);
|
||||
} catch (\Throwable $e) {
|
||||
Log::warning('Tenant photo asset disk unavailable', [
|
||||
'event_id' => $event->id,
|
||||
'photo_id' => $photo->id,
|
||||
'disk' => $disk,
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
|
||||
return ApiError::response(
|
||||
'photo_unavailable',
|
||||
'Photo Unavailable',
|
||||
'The requested photo could not be loaded.',
|
||||
Response::HTTP_NOT_FOUND,
|
||||
[
|
||||
'photo_id' => $photo->id,
|
||||
'event_id' => $event->id,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
foreach ($this->storagePathCandidates($path) as $candidate) {
|
||||
if (! $storage->exists($candidate)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$stream = $storage->readStream($candidate);
|
||||
if (! $stream) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$size = null;
|
||||
try {
|
||||
$size = $storage->size($candidate);
|
||||
} catch (\Throwable $e) {
|
||||
$size = null;
|
||||
}
|
||||
|
||||
if (! $mime) {
|
||||
try {
|
||||
$mime = $storage->mimeType($candidate);
|
||||
} catch (\Throwable $e) {
|
||||
$mime = null;
|
||||
}
|
||||
}
|
||||
|
||||
$extension = pathinfo($candidate, PATHINFO_EXTENSION) ?: ($photo->mime_type ? explode('/', $photo->mime_type)[1] ?? 'jpg' : 'jpg');
|
||||
$suffix = $variant === 'thumbnail' ? '-thumb' : '';
|
||||
$filename = sprintf('fotospiel-event-%s-photo-%s%s.%s', $event->id, $photo->id, $suffix, $extension ?: 'jpg');
|
||||
|
||||
$headers = [
|
||||
'Content-Type' => $mime ?? 'image/jpeg',
|
||||
'Cache-Control' => 'private, max-age=1800',
|
||||
'Content-Disposition' => 'inline; filename="'.$filename.'"',
|
||||
];
|
||||
|
||||
if ($size) {
|
||||
$headers['Content-Length'] = $size;
|
||||
}
|
||||
|
||||
return response()->stream(function () use ($stream) {
|
||||
fpassthru($stream);
|
||||
fclose($stream);
|
||||
}, 200, $headers);
|
||||
}
|
||||
|
||||
return ApiError::response(
|
||||
'photo_unavailable',
|
||||
'Photo Unavailable',
|
||||
'The requested photo could not be loaded.',
|
||||
Response::HTTP_NOT_FOUND,
|
||||
[
|
||||
'photo_id' => $photo->id,
|
||||
'event_id' => $event->id,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly uploaded photo.
|
||||
*/
|
||||
@@ -856,4 +968,57 @@ class PhotoController extends Controller
|
||||
'status' => 'pending',
|
||||
], 201);
|
||||
}
|
||||
|
||||
private function resolvePhotoVariant(Photo $record, string $variant, bool $preferOriginals = false): array
|
||||
{
|
||||
if ($variant === 'thumbnail') {
|
||||
$asset = EventMediaAsset::where('photo_id', $record->id)->where('variant', 'thumbnail')->first();
|
||||
$watermarked = $preferOriginals
|
||||
? null
|
||||
: EventMediaAsset::where('photo_id', $record->id)->where('variant', 'watermarked_thumbnail')->first();
|
||||
$disk = $asset?->disk ?? $record->mediaAsset?->disk;
|
||||
$path = $watermarked?->path ?? $asset?->path ?? ($record->thumbnail_path ?: $record->file_path);
|
||||
$mime = $watermarked?->mime_type ?? $asset?->mime_type ?? 'image/jpeg';
|
||||
} else {
|
||||
$watermarked = $preferOriginals
|
||||
? null
|
||||
: EventMediaAsset::where('photo_id', $record->id)->where('variant', 'watermarked')->first();
|
||||
$asset = $record->mediaAsset ?? $watermarked ?? EventMediaAsset::where('photo_id', $record->id)->where('variant', 'original')->first();
|
||||
$disk = $asset?->disk ?? $record->mediaAsset?->disk;
|
||||
$path = $watermarked?->path ?? $asset?->path ?? ($record->file_path ?? null);
|
||||
$mime = $watermarked?->mime_type ?? $asset?->mime_type ?? ($record->mime_type ?? 'image/jpeg');
|
||||
}
|
||||
|
||||
return [
|
||||
$disk ?: config('filesystems.default', 'public'),
|
||||
$path,
|
||||
$mime,
|
||||
];
|
||||
}
|
||||
|
||||
private function storagePathCandidates(string $path): array
|
||||
{
|
||||
$normalized = str_replace('\\', '/', $path);
|
||||
$candidates = [$normalized];
|
||||
|
||||
$trimmed = ltrim($normalized, '/');
|
||||
if ($trimmed !== $normalized) {
|
||||
$candidates[] = $trimmed;
|
||||
}
|
||||
|
||||
if (str_starts_with($trimmed, 'storage/')) {
|
||||
$candidates[] = substr($trimmed, strlen('storage/'));
|
||||
}
|
||||
|
||||
if (str_starts_with($trimmed, 'public/')) {
|
||||
$candidates[] = substr($trimmed, strlen('public/'));
|
||||
}
|
||||
|
||||
$needle = '/storage/app/public/';
|
||||
if (str_contains($normalized, $needle)) {
|
||||
$candidates[] = substr($normalized, strpos($normalized, $needle) + strlen($needle));
|
||||
}
|
||||
|
||||
return array_values(array_unique(array_filter($candidates)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,15 +54,11 @@ class PhotoResource extends JsonResource
|
||||
return null;
|
||||
}
|
||||
|
||||
$route = $variant === 'thumbnail'
|
||||
? 'api.v1.gallery.photos.asset'
|
||||
: 'api.v1.gallery.photos.asset';
|
||||
|
||||
return \URL::temporarySignedRoute(
|
||||
$route,
|
||||
'api.v1.tenant.events.photos.asset',
|
||||
now()->addMinutes(30),
|
||||
[
|
||||
'token' => $this->event->slug, // tenant/admin views are trusted; token not used server-side for signed validation
|
||||
'event' => $this->event->slug,
|
||||
'photo' => $this->id,
|
||||
'variant' => $variant === 'thumbnail' ? 'thumbnail' : 'full',
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user