diff --git a/modules/oe_bootstrap_theme_helper/src/TwigExtension/TwigExtension.php b/modules/oe_bootstrap_theme_helper/src/TwigExtension/TwigExtension.php index 6985438ff..48006109e 100644 --- a/modules/oe_bootstrap_theme_helper/src/TwigExtension/TwigExtension.php +++ b/modules/oe_bootstrap_theme_helper/src/TwigExtension/TwigExtension.php @@ -5,6 +5,7 @@ namespace Drupal\oe_bootstrap_theme_helper\TwigExtension; use Drupal\Component\Utility\Html; +use Drupal\Component\Utility\Xss; use Drupal\Core\Language\LanguageManagerInterface; use Drupal\Core\Link; use Drupal\Core\Render\BubbleableMetadata; @@ -139,7 +140,7 @@ public function bclCardList(array $items): array { } if (isset($item['text'])) { $bcl_card['text'] = [ - 'content' => $item['text'], + 'content' => $this->normalizeCardTextContent($item['text']), 'classes' => 'mb-2', 'tag' => 'div', ]; @@ -164,6 +165,27 @@ public function bclCardList(array $items): array { return $bcl_cards; } + /** + * Normalizes card text values so processed HTML keeps rendering as markup. + * + * Formatted text can reach the listing/card templates as an already filtered + * HTML string. Wrap those strings in safe markup so Twig does not escape + * them back into raw HTML source. + * + * @param mixed $content + * The card text value. + * + * @return mixed + * The normalized text value. + */ + private function normalizeCardTextContent(mixed $content): mixed { + if (!is_string($content) || !preg_match('/<[a-zA-Z\\/][^>]*>/', $content)) { + return $content; + } + + return Markup::create(Xss::filterAdmin($content)); + } + /** * Get file icon class given its extension. * diff --git a/modules/oe_bootstrap_theme_helper/tests/src/Kernel/TwigExtensionTest.php b/modules/oe_bootstrap_theme_helper/tests/src/Kernel/TwigExtensionTest.php index c65370d14..24a0de335 100644 --- a/modules/oe_bootstrap_theme_helper/tests/src/Kernel/TwigExtensionTest.php +++ b/modules/oe_bootstrap_theme_helper/tests/src/Kernel/TwigExtensionTest.php @@ -4,6 +4,7 @@ namespace Drupal\Tests\oe_bootstrap_theme_helper\Kernel; +use Drupal\Component\Render\MarkupInterface; use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\Render\BubbleableMetadata; use Drupal\Core\Render\RenderContext; @@ -458,6 +459,22 @@ public function testBclCardList(): void { } } + /** + * Tests BCL card list preserves formatted text as safe markup. + */ + public function testBclCardListFormattedText(): void { + $extension = $this->container->get('oe_bootstrap_theme_helper.twig_extension'); + $result = $extension->bclCardList([ + [ + 'text' => '

I add a text with bolds, italic and loopy link

', + ], + ]); + + $content = $result[0]['text']['content'] ?? NULL; + $this->assertInstanceOf(MarkupInterface::class, $content); + $this->assertSame('

I add a text with bolds, italic and loopy link

', (string) $content); + } + /** * Provides data for testBclCardList(). * diff --git a/tests/src/Kernel/fixtures/markup_rendering_patterns/listing.yml b/tests/src/Kernel/fixtures/markup_rendering_patterns/listing.yml index 16e539d72..70d0761f1 100644 --- a/tests/src/Kernel/fixtures/markup_rendering_patterns/listing.yml +++ b/tests/src/Kernel/fixtures/markup_rendering_patterns/listing.yml @@ -615,3 +615,55 @@ listing_default_2_col_highlight_with_image_copyright: 'article.listing-item--highlight .card-body.pt-3': 1 equals: 'article.listing-item--highlight .bcl-copyright': '© 2024 Example Organisation' +listing_default_1_col_with_formatted_text: + render: + '#type': pattern + '#id': 'listing' + '#variant': 'default' + '#fields': + columns: '1' + title: 'Listing item block title' + items: + - title: + '#markup': 'Card title 1' + text: '

I add a text with bolds, italic and loopy link

' + image: + src: 'https://picsum.photos/600/400/' + alt: 'alt img' + assertions: + count: + 'article.listing-item': 1 + 'article.listing-item .card-text p': 1 + 'article.listing-item .card-text strong': 1 + 'article.listing-item .card-text em': 1 + 'article.listing-item .card-text a[href="https://www.google.es"]': 1 + equals: + 'article.listing-item .card-text strong': 'bolds' + 'article.listing-item .card-text em': 'italic' + 'article.listing-item .card-text a[href="https://www.google.es"]': 'loopy link' +listing_highlight_1_col_with_formatted_text: + render: + '#type': pattern + '#id': 'listing' + '#variant': 'highlight' + '#fields': + columns: '1' + title: 'Listing item block title' + items: + - title: + '#markup': 'Card title 1' + text: '

I add a text with bolds, italic and loopy link

' + image: + src: 'https://picsum.photos/600/400/' + alt: 'alt img' + assertions: + count: + 'article.listing-item--highlight': 1 + 'article.listing-item--highlight .card-text p': 1 + 'article.listing-item--highlight .card-text strong': 1 + 'article.listing-item--highlight .card-text em': 1 + 'article.listing-item--highlight .card-text a[href="https://www.google.es"]': 1 + equals: + 'article.listing-item--highlight .card-text strong': 'bolds' + 'article.listing-item--highlight .card-text em': 'italic' + 'article.listing-item--highlight .card-text a[href="https://www.google.es"]': 'loopy link'