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
');