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; } } /** * @return array{local_path: string, object_path: string, filename: string, mime: string, size: int, ext: string} */ public function storeOriginal(string $sourcePath, string $hash, ?string $originalFileName = null): array { $origExt = $this->resolveOriginalExtension($sourcePath, $originalFileName); $filename = $hash . ($origExt !== '' ? '.' . $origExt : ''); $target = $this->storage->localOriginalPath($hash, $filename); if (! File::copy($sourcePath, $target)) { throw new RuntimeException('Unable to copy original file into local artwork originals storage.'); } $mime = (string) (File::mimeType($target) ?: 'application/octet-stream'); $size = (int) filesize($target); $objectPath = $this->storage->objectPathForVariant('original', $hash, $filename); $this->storage->putObjectFromPath($target, $objectPath, $mime); return [ 'local_path' => $target, 'object_path' => $objectPath, 'filename' => $filename, 'mime' => $mime, 'size' => $size, 'ext' => $origExt, ]; } 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', }; } /** * @return array */ 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; if ($variant === 'sq') { $written[$variant] = $this->generateSquareDerivative($sourcePath, $hash); continue; } $filename = $hash . '.webp'; $objectPath = $this->storage->objectPathForVariant($variant, $hash, $filename); /** @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); $this->storage->putObjectContents($objectPath, $encoded, 'image/webp'); $written[$variant] = [ 'path' => $objectPath, 'size' => strlen($encoded), 'mime' => 'image/webp', ]; } return $written; } /** * @param array $options * @return array{path: string, size: int, mime: string, result: SquareThumbnailResultData} */ public function generateSquareDerivative(string $sourcePath, string $hash, array $options = []): array { $filename = $hash . '.webp'; $objectPath = $this->storage->objectPathForVariant('sq', $hash, $filename); $temporaryPath = tempnam(sys_get_temp_dir(), 'sq-derivative-'); if ($temporaryPath === false) { throw new RuntimeException('Unable to allocate a temporary file for square derivative generation.'); } $temporaryWebp = $temporaryPath . '.webp'; rename($temporaryPath, $temporaryWebp); try { $result = $this->squareThumbnails->generateFromPath($sourcePath, $temporaryWebp, $options); $mime = 'image/webp'; $size = (int) filesize($temporaryWebp); $this->storage->putObjectFromPath($temporaryWebp, $objectPath, $mime); return [ 'path' => $objectPath, 'size' => $size, 'mime' => $mime, 'result' => $result, ]; } finally { File::delete($temporaryWebp); } } private function assertImageAvailable(): void { if (! $this->imageAvailable) { throw new RuntimeException('Intervention Image is not available.'); } } }