From fecebc4d5eae2febfef688157176613bd03dc7c5 Mon Sep 17 00:00:00 2001 From: ryan599 Date: Mon, 3 Aug 2026 16:08:59 +0200 Subject: [PATCH] FIX: [Shop] #202 stop decoding entities in rendered content elements output --- .../ContentElementRendererStrategy.php | 2 +- .../elements/multiple_media.html.twig | 2 +- .../elements/pages_collection.html.twig | 2 +- .../elements/single_media.html.twig | 2 +- .../elements/textarea.html.twig | 2 +- .../ContentElementRendererStrategyTest.php | 26 +++++++++++++++++-- 6 files changed, 29 insertions(+), 7 deletions(-) diff --git a/src/Renderer/ContentElementRendererStrategy.php b/src/Renderer/ContentElementRendererStrategy.php index 62de0910..a856bca5 100644 --- a/src/Renderer/ContentElementRendererStrategy.php +++ b/src/Renderer/ContentElementRendererStrategy.php @@ -42,7 +42,7 @@ public function render(ContentElementsAwareInterface $item): string foreach ($this->renderers as $renderer) { if ($renderer->supports($contentElement)) { - $content .= html_entity_decode($renderer->render($contentElement), \ENT_QUOTES); + $content .= $renderer->render($contentElement); break; } diff --git a/templates/shop/content_element/elements/multiple_media.html.twig b/templates/shop/content_element/elements/multiple_media.html.twig index 0092fd2c..775e0859 100644 --- a/templates/shop/content_element/elements/multiple_media.html.twig +++ b/templates/shop/content_element/elements/multiple_media.html.twig @@ -1,5 +1,5 @@
{% for element in media %} - {{ element.renderedContent }} + {{ element.renderedContent|raw }} {% endfor %}
diff --git a/templates/shop/content_element/elements/pages_collection.html.twig b/templates/shop/content_element/elements/pages_collection.html.twig index 9074bb0d..aa0b3f15 100644 --- a/templates/shop/content_element/elements/pages_collection.html.twig +++ b/templates/shop/content_element/elements/pages_collection.html.twig @@ -21,7 +21,7 @@ {% endif %} {% if page.teaserContent %}

- {{ page.teaserContent }} + {{ page.teaserContent|raw }}

{{ 'sylius_cms.ui.read_more'|trans }} diff --git a/templates/shop/content_element/elements/single_media.html.twig b/templates/shop/content_element/elements/single_media.html.twig index 56902427..1743151f 100644 --- a/templates/shop/content_element/elements/single_media.html.twig +++ b/templates/shop/content_element/elements/single_media.html.twig @@ -1,3 +1,3 @@
- {{ media.renderedContent }} + {{ media.renderedContent|raw }}
diff --git a/templates/shop/content_element/elements/textarea.html.twig b/templates/shop/content_element/elements/textarea.html.twig index f6c27481..2b628a48 100644 --- a/templates/shop/content_element/elements/textarea.html.twig +++ b/templates/shop/content_element/elements/textarea.html.twig @@ -1,3 +1,3 @@
- {{ content }} + {{ content|raw }}
diff --git a/tests/Unit/Renderer/ContentElementRendererStrategyTest.php b/tests/Unit/Renderer/ContentElementRendererStrategyTest.php index fe8304dc..55227b7f 100644 --- a/tests/Unit/Renderer/ContentElementRendererStrategyTest.php +++ b/tests/Unit/Renderer/ContentElementRendererStrategyTest.php @@ -63,12 +63,34 @@ public function testRendersAPageContentElementCorrectly(): void $contentElementMock->expects(self::once())->method('getLocale')->willReturn('en_US'); $this->rendererMock->expects(self::once())->method('supports')->with($contentElementMock)->willReturn(true); - $this->rendererMock->expects(self::once())->method('render')->with($contentElementMock)->willReturn('<p>Hello World</p>'); + $this->rendererMock->expects(self::once())->method('render')->with($contentElementMock)->willReturn('

Hello World

'); $this->contentParserMock->expects(self::once())->method('parse')->with('

Hello World

')->willReturn('

Hello World

'); self::assertSame('

Hello World

', $this->contentElementRendererStrategy->render($pageMock)); } + public function testEmitsRenderedMarkupUntouched(): void + { + /** @var PageInterface&MockObject $pageMock */ + $pageMock = $this->createMock(PageInterface::class); + /** @var ContentConfigurationInterface&MockObject $contentElementMock */ + $contentElementMock = $this->createMock(ContentConfigurationInterface::class); + + $pageMock->expects(self::once())->method('getContentElements')->willReturn(new ArrayCollection([$contentElementMock])); + $this->localeContextMock->expects(self::once())->method('getLocaleCode')->willReturn('en_US'); + $contentElementMock->expects(self::once())->method('getLocale')->willReturn('en_US'); + + // Escaped text must stay escaped and attributes carrying entities must survive: + // decoding would unescape user input and cut the attribute at the first quote. + $markup = '

Summer <em>offer</em>

'; + + $this->rendererMock->expects(self::once())->method('supports')->with($contentElementMock)->willReturn(true); + $this->rendererMock->expects(self::once())->method('render')->with($contentElementMock)->willReturn($markup); + $this->contentParserMock->expects(self::once())->method('parse')->with($markup)->willReturn($markup); + + self::assertSame($markup, $this->contentElementRendererStrategy->render($pageMock)); + } + public function testSkipsContentElementWithNonMatchingLocale(): void { /** @var BlockInterface&MockObject $blockMock */ @@ -99,7 +121,7 @@ public function testRendersOnlySupportedContentElements(): void $supportedElementMock->expects(self::once())->method('getLocale')->willReturn('en_US'); $unsupportedElementMock->expects(self::once())->method('getLocale')->willReturn('en_US'); - $this->rendererMock->expects(self::once())->method('render')->with($supportedElementMock)->willReturn('<p>Supported</p>'); + $this->rendererMock->expects(self::once())->method('render')->with($supportedElementMock)->willReturn('

Supported

'); $this->rendererMock->expects(self::exactly(2))->method('supports')->willReturnOnConsecutiveCalls(true, false); $this->contentParserMock->expects(self::once())->method('parse')->with('

Supported

')->willReturn('

Supported

');