manager = extension_loaded('gd') ? ImageManager::gd() : ImageManager::imagick(); $this->imageAvailable = true; } catch (\Throwable $e) { logger()->warning('Intervention Image present but configuration failed: ' . $e->getMessage()); $this->imageAvailable = false; $this->manager = null; } } public function storeOriginal(string $sourcePath, string $hash, ?string $originalFileName = null): string { $this->assertImageAvailable(); // Preserve original file extension and store with filename = . $dir = $this->storage->ensureHashDirectory('original', $hash); $origExt = $this->resolveOriginalExtension($sourcePath, $originalFileName); $target = $dir . DIRECTORY_SEPARATOR . $hash . ($origExt !== '' ? '.' . $origExt : ''); // Try a direct copy first (works for images and archives). If that fails, // fall back to re-encoding image to webp as a last resort. try { if (! File::copy($sourcePath, $target)) { throw new \RuntimeException('Copy failed'); } } catch (\Throwable $e) { // Fallback: encode to webp $quality = (int) config('uploads.quality', 85); /** @var InterventionImageInterface $img */ $img = $this->manager->read($sourcePath); $encoder = new \Intervention\Image\Encoders\WebpEncoder($quality); $encoded = (string) $img->encode($encoder); $target = $dir . DIRECTORY_SEPARATOR . $hash . '.webp'; File::put($target, $encoded); } return $target; } private function resolveOriginalExtension(string $sourcePath, ?string $originalFileName): string { $fromClientName = strtolower((string) pathinfo((string) $originalFileName, PATHINFO_EXTENSION)); if ($fromClientName !== '' && preg_match('/^[a-z0-9]{1,12}$/', $fromClientName) === 1) { return $fromClientName; } $fromSource = strtolower((string) pathinfo($sourcePath, PATHINFO_EXTENSION)); if ($fromSource !== '' && $fromSource !== 'upload' && preg_match('/^[a-z0-9]{1,12}$/', $fromSource) === 1) { return $fromSource; } $mime = File::exists($sourcePath) ? (string) (File::mimeType($sourcePath) ?? '') : ''; return $this->extensionFromMime($mime); } private function extensionFromMime(string $mime): string { return match (strtolower($mime)) { 'image/jpeg', 'image/jpg' => 'jpg', 'image/png' => 'png', 'image/webp' => 'webp', 'image/gif' => 'gif', 'image/bmp' => 'bmp', 'image/tiff' => 'tif', 'application/zip', 'application/x-zip-compressed' => 'zip', 'application/x-rar-compressed', 'application/vnd.rar' => 'rar', 'application/x-7z-compressed' => '7z', 'application/x-tar' => 'tar', 'application/gzip', 'application/x-gzip' => 'gz', default => 'bin', }; } public function generatePublicDerivatives(string $sourcePath, string $hash): array { $this->assertImageAvailable(); $quality = (int) config('uploads.quality', 85); $variants = (array) config('uploads.derivatives', []); $written = []; foreach ($variants as $variant => $options) { $variant = (string) $variant; $dir = $this->storage->ensureHashDirectory($variant, $hash); // store derivative filename as .webp per variant directory $path = $dir . DIRECTORY_SEPARATOR . $hash . '.webp'; /** @var InterventionImageInterface $img */ $img = $this->manager->read($sourcePath); if (isset($options['size'])) { $size = (int) $options['size']; $out = $img->cover($size, $size); } else { $max = (int) ($options['max'] ?? 0); if ($max <= 0) { $max = 2560; } $out = $img->scaleDown($max, $max); } $encoder = new \Intervention\Image\Encoders\WebpEncoder($quality); $encoded = (string) $out->encode($encoder); File::put($path, $encoded); $written[$variant] = $path; } return $written; } private function assertImageAvailable(): void { if (! $this->imageAvailable) { throw new RuntimeException('Intervention Image is not available.'); } } }