storeUploadedEntityImage($group, $file, $variant === 'banner' ? 'banner' : 'avatar'); } public function storeUploadedEntityImage(Group $group, UploadedFile $file, string $section): string { $mime = strtolower((string) ($file->getMimeType() ?: '')); $extension = $this->safeExtension($file, $mime); $path = sprintf( 'groups/%d/%s/%s.%s', (int) $group->id, trim($section) !== '' ? trim($section) : 'media', (string) Str::uuid(), $extension, ); $stream = fopen((string) ($file->getRealPath() ?: $file->getPathname()), 'rb'); if ($stream === false) { throw new \RuntimeException('Unable to open uploaded group image.'); } try { $written = Storage::disk($this->diskName())->put($path, $stream, [ 'visibility' => 'public', 'CacheControl' => 'public, max-age=31536000, immutable', 'ContentType' => $mime !== '' ? $mime : $this->mimeTypeForExtension($extension), ]); } finally { fclose($stream); } if ($written !== true) { throw new \RuntimeException('Unable to store uploaded group image.'); } return $path; } public function deleteIfManaged(?string $path): void { $trimmed = trim((string) $path); if ($trimmed === '' || str_starts_with($trimmed, 'http://') || str_starts_with($trimmed, 'https://')) { return; } if (! str_starts_with($trimmed, 'groups/')) { return; } Storage::disk($this->diskName())->delete($trimmed); } private function diskName(): string { return (string) config('uploads.object_storage.disk', 's3'); } private function safeExtension(UploadedFile $file, string $mime): string { $extension = strtolower((string) $file->getClientOriginalExtension()); if (! in_array($mime, self::ALLOWED_MIME_TYPES, true)) { throw new \RuntimeException('Unsupported group image upload type.'); } return match ($extension) { 'jpg', 'jpeg' => 'jpg', 'png' => 'png', default => 'webp', }; } private function mimeTypeForExtension(string $extension): string { return match ($extension) { 'jpg' => 'image/jpeg', 'png' => 'image/png', default => 'image/webp', }; } }