diff --git a/Image/Image.php b/Image/Image.php index 81aa1ed..8840385 100644 --- a/Image/Image.php +++ b/Image/Image.php @@ -14,16 +14,24 @@ class Image */ private $url; + /** + * @var ?string + */ + private $srcSet = null; + /** * @param string $path * @param string $url + * @param ?string $srcSet */ public function __construct( string $path, - string $url + string $url, + ?string $srcSet = null ) { $this->path = $path; $this->url = $url; + $this->srcSet = $srcSet; } /** @@ -42,6 +50,22 @@ public function getUrl(): string return $this->url; } + /** + * @return ?string + */ + public function getSrcSet(): ?string + { + return $this->srcSet; + } + + /** + * @param ?string + */ + public function setSrcSet(?string $srcSet): void + { + $this->srcSet = $srcSet; + } + /** * @return string */ diff --git a/Image/ImageCollector.php b/Image/ImageCollector.php index ab85aa8..5643e86 100644 --- a/Image/ImageCollector.php +++ b/Image/ImageCollector.php @@ -40,10 +40,10 @@ public function __construct( } /** - * @param string $imageUrl + * @param string|array $imageUrl * @return Image[] */ - public function collect(string $imageUrl): array + public function collect($imageUrl): array { try { $image = $this->imageFactory->createFromUrl($imageUrl); diff --git a/Image/ImageFactory.php b/Image/ImageFactory.php index ca920ea..2336138 100644 --- a/Image/ImageFactory.php +++ b/Image/ImageFactory.php @@ -37,17 +37,35 @@ public function createFromPath(string $path): Image } /** - * @param string $url + * @param array|string $url * @return Image * @throws FileSystemException */ - public function createFromUrl(string $url): Image + public function createFromUrl($url): Image + { + $urls = is_array($url) ? $url : [$url]; + $baseUrl = $this->cleanUrl($urls[0] ?? ''); + $srcSet = $this->getSrcSet($urls); + $path = $this->urlConvertor->getFilenameFromUrl($baseUrl); + return $this->objectManager->create(Image::class, ['path' => $path, 'url' => $baseUrl, 'srcSet' => $srcSet]); + } + + private function cleanUrl(string $url): string { if (strpos($url, 'http') !== false) { - $url = explode('?', $url)[0]; + return explode('?', $url)[0]; } - - $path = $this->urlConvertor->getFilenameFromUrl($url); - return $this->objectManager->create(Image::class, ['path' => $path, 'url' => $url]); + + return $url; + } + + private function getSrcSet(array $urls): string + { + $srcSetPieces = []; + foreach ($urls as $key => $url) { + $srcSetPieces[] = $this->cleanUrl($url) . ($key !== 0 ? (' ' . $key) : ''); + } + + return implode(',', $srcSetPieces); } } diff --git a/Test/Unit/Image/ImageFactoryTest.php b/Test/Unit/Image/ImageFactoryTest.php index 4d0496d..526552f 100644 --- a/Test/Unit/Image/ImageFactoryTest.php +++ b/Test/Unit/Image/ImageFactoryTest.php @@ -42,6 +42,26 @@ public function testCreateFromUrl() $this->assertEquals('/foo/bar.jpg', $image->getUrl()); } + public function testCreateFromUrlWithMultipleUrls() + { + $urlConvertor = $this->getUrlConvertor(); + $urlConvertor->method('getFilenameFromUrl')->willReturn('/tmp/pub/foo/bar.jpg'); + $urlConvertor->method('getUrlFromFilename')->willReturn('/foo/bar.jpg'); + $objectManager = $this->getObjectManager(); + $objectManager->method('create')->willReturn( + new Image('/tmp/pub/foo/bar.jpg', '/foo/bar.jpg', '/foo/bar.jpg,/baz/qux.jpg 500w') + ); + + // @phpstan-ignore-next-line + $imageFactory = new ImageFactory($objectManager, $urlConvertor); + $image = $imageFactory->createFromUrl(['/foo/bar.jpg', '500w' => '/baz/qux.jpg']); + + $this->assertInstanceOf(Image::class, $image); + $this->assertEquals('/tmp/pub/foo/bar.jpg', $image->getPath()); + $this->assertEquals('/foo/bar.jpg', $image->getUrl()); + $this->assertEquals('/foo/bar.jpg,/baz/qux.jpg 500w', $image->getSrcSet()); + } + private function getObjectManager(): MockObject { return $this->getMockBuilder(ObjectManagerInterface::class) diff --git a/Test/Unit/Image/ImageTest.php b/Test/Unit/Image/ImageTest.php index 8881c4e..b8e7f2a 100644 --- a/Test/Unit/Image/ImageTest.php +++ b/Test/Unit/Image/ImageTest.php @@ -20,6 +20,12 @@ public function testGetUrl() $this->assertEquals('/media/foobar.jpg', $image->getUrl()); } + public function testGetSrcSet() + { + $image = new Image('/tmp/pub/foobar.jpg', '/media/foobar.jpg', '/foo/bar.jpg,/baz/qux.jpg 500w'); + $this->assertEquals('/foo/bar.jpg,/baz/qux.jpg 500w', $image->getSrcSet()); + } + public function testGetMimetype() { $image = new Image('/tmp/pub/foobar.gif', 'foobar.gif'); diff --git a/Util/HtmlReplacer.php b/Util/HtmlReplacer.php index afa32da..d9b6687 100644 --- a/Util/HtmlReplacer.php +++ b/Util/HtmlReplacer.php @@ -113,7 +113,7 @@ private function getImageHtmlFromImage(DOMElement $image, string $html): string return ''; } - $regex = '/"]*)(?:"[^"]*"[^>"]*)*>/'; + $regex = '/]+)>/'; if (!preg_match($regex, $html, $imageHtmlMatch)) { return ''; } @@ -136,15 +136,35 @@ private function getPictureHtmlFromImage(DOMElement $image, string $html): strin if (!$this->isAllowedByParentNode($image)) { return ''; } + + $imageSrcSet = $image->getAttribute('srcset'); + if ($imageSrcSet) { + $srcSetImages = explode(',', $imageSrcSet); + $imageUrls = []; + foreach ($srcSetImages as $srcSetImage) { + $pieces = explode(' ', trim($srcSetImage)); + if (isset($pieces[1])) { + $descriptor = $pieces[1]; + $imageUrls[$descriptor] = $pieces[0]; + } else { + $descriptor = 0; + $imageUrl = $imageUrls[$descriptor] = $pieces[0]; + } + } + $images = $this->imageCollector->collect($imageUrls); + if (!count($images) > 0) { + return ''; + } + } else { + $imageUrl = $this->getImageUrlFromElement($image); + if (!$this->isAllowedByImageUrl($imageUrl)) { + return ''; + } - $imageUrl = $this->getImageUrlFromElement($image); - if (!$this->isAllowedByImageUrl($imageUrl)) { - return ''; - } - - $images = $this->imageCollector->collect($imageUrl); - if (!count($images) > 0) { - return ''; + $images = $this->imageCollector->collect($imageUrl); + if (!count($images) > 0) { + return ''; + } } $imageHtml = $this->getImageHtmlFromImage($image, $html); @@ -271,6 +291,7 @@ private function replaceInlineCssBackgroundImages(string $html): string private function getImageUrlFromElement(DOMElement $image): string { $attributes = $this->getAllowedSrcAttributes(); + $imageUrl = ''; foreach ($attributes as $attribute) { $imageUrl = $image->getAttribute($attribute); diff --git a/view/frontend/templates/picture.phtml b/view/frontend/templates/picture.phtml index 709ab31..55cb853 100644 --- a/view/frontend/templates/picture.phtml +++ b/view/frontend/templates/picture.phtml @@ -1 +1,22 @@ -getWidth()) ? ' width="'.$block->getWidth().'"' : ''; $height = ($block->getHeight()) ? ' height="'.$block->getHeight().'"' : ''; $class = ($block->getClass()) ? ' class="'.$block->getClass().'"' : ''; $lazyLoading = ($block->getLazyLoading()) ? ' loading="lazy" ' : ''; $originalTag = trim($block->getOriginalTag()); $originalTag = preg_replace('/(\/?)>$/', $lazyLoading.'\1>', $originalTag); $originalImage = $block->getOriginalImage(); $originalImageType = $block->getOriginalImageType(); $srcAttribute = $block->getSrcAttribute() ?? 'src'; $srcSetAttribute = $srcAttribute.'set'; ?> getDebug()): ?> >getDebug()): ?> getImages() as $image): ?> ="getUrl() ?>" getSourceAttributesAsString() ?> > \ No newline at end of file +getWidth()) ? ' width="'.$block->getWidth().'"' : ''; +$height = ($block->getHeight()) ? ' height="'.$block->getHeight().'"' : ''; +$class = ($block->getClass()) ? ' class="'.$block->getClass().'"' : ''; +$lazyLoading = ($block->getLazyLoading()) ? ' loading="lazy" ' : ''; +$originalTag = trim($block->getOriginalTag()); +$originalTag = preg_replace('/(\/?)>$/', $lazyLoading.'\1>', $originalTag); +$originalImage = $block->getOriginalImage(); +$originalImageType = $block->getOriginalImageType(); +$srcAttribute = $block->getSrcAttribute() ?? 'src'; +$srcSetAttribute = $srcAttribute.'set'; ?> + +getDebug()): ?> +> + getDebug()): ?> + getImages() as $image): ?> + ="getSrcSet() ?: $image->getUrl() ?>" getSourceAttributesAsString() ?> > + + +