diff --git a/src/Breakpoint.php b/src/Breakpoint.php index b5177e8..103d3e1 100644 --- a/src/Breakpoint.php +++ b/src/Breakpoint.php @@ -5,6 +5,7 @@ use Illuminate\Contracts\Support\Arrayable; use Illuminate\Support\Collection; use Illuminate\Support\Facades\Config; +use League\Flysystem\FilesystemException; use Spatie\ResponsiveImages\Jobs\GenerateImageJob; use Statamic\Contracts\Assets\Asset; use Statamic\Facades\Blink; @@ -59,7 +60,13 @@ public function getSrcSet(bool $includePlaceholder = true, string $format = null return "{$this->buildImageJob($width, $format, $this->ratio)->handle()} {$width}w"; }) ->when($includePlaceholder, function (Collection $widths) { - return $widths->prepend($this->placeholderSrc()); + $placeholderSrc = $this->placeholderSrc(); + + if (empty($placeholderSrc)) { + return $widths; + } + + return $widths->prepend($placeholderSrc); }) ->implode(', '); } @@ -210,13 +217,6 @@ private function getGlideParams(): array ->toArray(); } - public function placeholder(): string - { - $base64Svg = base64_encode($this->placeholderSvg()); - - return "data:image/svg+xml;base64,{$base64Svg}"; - } - public function toGql(array $args): array { $data = [ @@ -241,12 +241,7 @@ public function toGql(array $args): array return $data; } - private function placeholderSrc(): string - { - return $this->placeholder() . ' 32w'; - } - - private function placeholderSvg(): string + public function placeholder(): string { return Blink::once("placeholder-{$this->asset->id()}-{$this->ratio}", function () { $imageGenerator = app(ImageGenerator::class); @@ -261,20 +256,55 @@ private function placeholderSvg(): string 'cache' => Config::get('statamic.assets.image_manipulation.cache', false), ]); - /** - * Glide tag has undocumented method for generating data URL that we borrow from - * @see \Statamic\Tags\Glide::generateGlideDataUrl - */ - $cache = GlideManager::cacheDisk(); - $assetContentEncoded = base64_encode($cache->read($manipulationPath)); - $base64Placeholder = 'data:'.$cache->mimeType($manipulationPath).';base64,'.$assetContentEncoded; + $base64Image = $this->readImageToBase64($manipulationPath); + + if (! $base64Image) { + return ''; + } - return view('responsive-images::placeholderSvg', [ + $placeholderSvg = view('responsive-images::placeholderSvg', [ 'width' => 32, 'height' => round(32 / $this->ratio), - 'image' => $base64Placeholder, + 'image' => $base64Image, 'asset' => $this->asset->toAugmentedArray(), ])->render(); + + return 'data:image/svg+xml;base64,' . base64_encode($placeholderSvg); }); } + + private function placeholderSrc(): string + { + $placeholder = $this->placeholder(); + + if (empty($placeholder)) { + return ''; + } + + return $placeholder . ' 32w'; + } + + private function readImageToBase64($assetPath): string|null + { + /** + * Glide tag has undocumented method for generating data URL that we borrow from + * @see \Statamic\Tags\Glide::generateGlideDataUrl + */ + $cache = GlideManager::cacheDisk(); + + try { + $assetContent = $cache->read($assetPath); + $assetMimeType = $cache->mimeType($assetPath); + } catch (FilesystemException $e) { + if (config('app.debug')) { + throw $e; + } + + logger()->error($e->getMessage()); + + return null; + } + + return 'data:' . $assetMimeType . ';base64,' . base64_encode($assetContent); + } } diff --git a/tests/Feature/BreakpointTest.php b/tests/Feature/BreakpointTest.php index d3eef14..70debfa 100644 --- a/tests/Feature/BreakpointTest.php +++ b/tests/Feature/BreakpointTest.php @@ -122,7 +122,7 @@ /** * We use Blink cache for placeholder generation that we need to clear just in case * @see https://statamic.dev/extending/blink-cache - * @see Breakpoint::placeholderSvg() + * @see Breakpoint::placeholder() */ Blink::store()->flush(); @@ -141,3 +141,20 @@ expect($secondPlaceholder)->toEqual($firstPlaceholder) ->and($cacheDiskPathAfter)->not->toEqual($cacheDiskPathBefore); }); + +it("doesn't crash when the placeholder image cannot be read", function () { + $responsive = new Breakpoint($this->asset, 'default', 0, []); + + // Generate placeholder to trigger caching + $responsive->placeholder(); + + // Forget cached files + $pathPrefix = \Statamic\Imaging\ImageGenerator::assetCachePathPrefix($this->asset); + + \Statamic\Facades\Glide::server()->deleteCache($pathPrefix.'/'.$this->asset->path()); + + Blink::store()->flush(); + + // Generate new placeholder + $responsive->placeholder(); +})->expectNotToPerformAssertions();