Loading core/lib/Drupal/Core/Template/TwigExtension.php +54 −0 Original line number Diff line number Diff line Loading @@ -140,6 +140,8 @@ public function getFilters() { // CSS class and ID filters. new TwigFilter('clean_class', '\Drupal\Component\Utility\Html::getClass'), new TwigFilter('clean_id', '\Drupal\Component\Utility\Html::getId'), new TwigFilter('add_class', [$this, 'addClass']), new TwigFilter('set_attribute', [$this, 'setAttribute']), // This filter will render a renderable array to use the string results. new TwigFilter('render', [$this, 'renderVar']), new TwigFilter('format_date', [$this->dateFormatter, 'format']), Loading Loading @@ -706,4 +708,56 @@ public function suggestThemeHook(?array $element, string|\Stringable $suggestion return $element; } /** * Adds a value into the class attributes of a given element. * * Assumes element is an array. * * @param array $element * A render element. * @param string[]|string ...$classes * The class(es) to add to the element. Arguments can include string keys * directly, or arrays of string keys. * * @return array * The element with the given class(es) in attributes. */ public function addClass(array $element, ...$classes): array { $attributes = new Attribute($element['#attributes'] ?? []); $attributes->addClass(...$classes); $element['#attributes'] = $attributes->toArray(); // Make sure element gets rendered again. unset($element['#printed']); return $element; } /** * Sets an attribute on a given element. * * Assumes the element is an array. * * @param array $element * A render element. * @param string $name * The attribute name. * @param mixed $value * (optional) The attribute value. * * @return array * The element with the given sanitized attribute's value. */ public function setAttribute(array $element, string $name, mixed $value = NULL): array { $element['#attributes'] = AttributeHelper::mergeCollections( $element['#attributes'] ?? [], new Attribute([$name => $value]) ); // Make sure element gets rendered again. unset($element['#printed']); return $element; } } core/tests/Drupal/Tests/Core/Template/TwigExtensionTest.php +127 −1 Original line number Diff line number Diff line Loading @@ -4,8 +4,10 @@ // cspell:ignore mila use Drupal\Component\Serialization\Json; use Drupal\Core\File\FileUrlGeneratorInterface; use Drupal\Core\GeneratedLink; use Drupal\Core\Render\Markup; use Drupal\Core\Render\RenderableInterface; use Drupal\Core\StringTranslation\TranslatableMarkup; use Drupal\Core\Template\Loader\StringLoader; Loading Loading @@ -438,7 +440,7 @@ public function testTwigAddSuggestionFilter($original_render_array, $suggestion, } /** * A data provider for ::testTwigAddSuggestionFilter(). * Provides data for ::testTwigAddSuggestionFilter(). * * @return \Iterator */ Loading Loading @@ -542,6 +544,130 @@ public function providerTestTwigAddSuggestionFilter(): \Iterator { ]; } /** * Tests Twig 'add_class' filter. * * @covers ::addClass * @dataProvider providerTestTwigAddClass */ public function testTwigAddClass($element, $classes, $expected_result) { $processed = $this->systemUnderTest->addClass($element, $classes); $this->assertEquals($expected_result, $processed); } /** * Provides data for ::testTwigAddClass(). * * @return \Iterator */ public function providerTestTwigAddClass(): \Iterator { yield 'should add a class on element' => [ ['#type' => 'container'], 'my-class', ['#type' => 'container', '#attributes' => ['class' => ['my-class']]], ]; yield 'should add a class from a array of string keys on element' => [ ['#type' => 'container'], ['my-class'], ['#type' => 'container', '#attributes' => ['class' => ['my-class']]], ]; yield 'should add a class from a Markup value' => [ ['#type' => 'container'], [Markup::create('my-class')], ['#type' => 'container', '#attributes' => ['class' => ['my-class']]], ]; yield '#printed should be removed after class(es) added' => [ [ '#markup' => 'This content is already is rendered', '#printed' => TRUE, ], '', [ '#markup' => 'This content is already is rendered', '#attributes' => [ 'class' => [''], ], ], ]; } /** * Tests Twig 'set_attribute' filter. * * @covers ::setAttribute * @dataProvider providerTestTwigSetAttribute */ public function testTwigSetAttribute($element, $key, $value, $expected_result) { $processed = $this->systemUnderTest->setAttribute($element, $key, $value); $this->assertEquals($expected_result, $processed); } /** * A data provider for ::testTwigSetAttribute(). * * @return \Iterator */ public function providerTestTwigSetAttribute(): \Iterator { yield 'should add attributes on element' => [ ['#theme' => 'image'], 'title', 'Aloha', [ '#theme' => 'image', '#attributes' => [ 'title' => 'Aloha', ], ], ]; yield 'should merge existing attributes on element' => [ [ '#theme' => 'image', '#attributes' => [ 'title' => 'Aloha', ], ], 'title', 'Bonjour', [ '#theme' => 'image', '#attributes' => [ 'title' => 'Bonjour', ], ], ]; yield 'should add JSON attribute value correctly on element' => [ ['#type' => 'container'], 'data-slider', Json::encode(['autoplay' => TRUE]), [ '#type' => 'container', '#attributes' => [ 'data-slider' => '{"autoplay":true}', ], ], ]; yield '#printed should be removed after setting attribute' => [ [ '#markup' => 'This content is already is rendered', '#printed' => TRUE, ], 'title', NULL, [ '#markup' => 'This content is already is rendered', '#attributes' => [ 'title' => NULL, ], ], ]; } } class TwigExtensionTestString { Loading Loading
core/lib/Drupal/Core/Template/TwigExtension.php +54 −0 Original line number Diff line number Diff line Loading @@ -140,6 +140,8 @@ public function getFilters() { // CSS class and ID filters. new TwigFilter('clean_class', '\Drupal\Component\Utility\Html::getClass'), new TwigFilter('clean_id', '\Drupal\Component\Utility\Html::getId'), new TwigFilter('add_class', [$this, 'addClass']), new TwigFilter('set_attribute', [$this, 'setAttribute']), // This filter will render a renderable array to use the string results. new TwigFilter('render', [$this, 'renderVar']), new TwigFilter('format_date', [$this->dateFormatter, 'format']), Loading Loading @@ -706,4 +708,56 @@ public function suggestThemeHook(?array $element, string|\Stringable $suggestion return $element; } /** * Adds a value into the class attributes of a given element. * * Assumes element is an array. * * @param array $element * A render element. * @param string[]|string ...$classes * The class(es) to add to the element. Arguments can include string keys * directly, or arrays of string keys. * * @return array * The element with the given class(es) in attributes. */ public function addClass(array $element, ...$classes): array { $attributes = new Attribute($element['#attributes'] ?? []); $attributes->addClass(...$classes); $element['#attributes'] = $attributes->toArray(); // Make sure element gets rendered again. unset($element['#printed']); return $element; } /** * Sets an attribute on a given element. * * Assumes the element is an array. * * @param array $element * A render element. * @param string $name * The attribute name. * @param mixed $value * (optional) The attribute value. * * @return array * The element with the given sanitized attribute's value. */ public function setAttribute(array $element, string $name, mixed $value = NULL): array { $element['#attributes'] = AttributeHelper::mergeCollections( $element['#attributes'] ?? [], new Attribute([$name => $value]) ); // Make sure element gets rendered again. unset($element['#printed']); return $element; } }
core/tests/Drupal/Tests/Core/Template/TwigExtensionTest.php +127 −1 Original line number Diff line number Diff line Loading @@ -4,8 +4,10 @@ // cspell:ignore mila use Drupal\Component\Serialization\Json; use Drupal\Core\File\FileUrlGeneratorInterface; use Drupal\Core\GeneratedLink; use Drupal\Core\Render\Markup; use Drupal\Core\Render\RenderableInterface; use Drupal\Core\StringTranslation\TranslatableMarkup; use Drupal\Core\Template\Loader\StringLoader; Loading Loading @@ -438,7 +440,7 @@ public function testTwigAddSuggestionFilter($original_render_array, $suggestion, } /** * A data provider for ::testTwigAddSuggestionFilter(). * Provides data for ::testTwigAddSuggestionFilter(). * * @return \Iterator */ Loading Loading @@ -542,6 +544,130 @@ public function providerTestTwigAddSuggestionFilter(): \Iterator { ]; } /** * Tests Twig 'add_class' filter. * * @covers ::addClass * @dataProvider providerTestTwigAddClass */ public function testTwigAddClass($element, $classes, $expected_result) { $processed = $this->systemUnderTest->addClass($element, $classes); $this->assertEquals($expected_result, $processed); } /** * Provides data for ::testTwigAddClass(). * * @return \Iterator */ public function providerTestTwigAddClass(): \Iterator { yield 'should add a class on element' => [ ['#type' => 'container'], 'my-class', ['#type' => 'container', '#attributes' => ['class' => ['my-class']]], ]; yield 'should add a class from a array of string keys on element' => [ ['#type' => 'container'], ['my-class'], ['#type' => 'container', '#attributes' => ['class' => ['my-class']]], ]; yield 'should add a class from a Markup value' => [ ['#type' => 'container'], [Markup::create('my-class')], ['#type' => 'container', '#attributes' => ['class' => ['my-class']]], ]; yield '#printed should be removed after class(es) added' => [ [ '#markup' => 'This content is already is rendered', '#printed' => TRUE, ], '', [ '#markup' => 'This content is already is rendered', '#attributes' => [ 'class' => [''], ], ], ]; } /** * Tests Twig 'set_attribute' filter. * * @covers ::setAttribute * @dataProvider providerTestTwigSetAttribute */ public function testTwigSetAttribute($element, $key, $value, $expected_result) { $processed = $this->systemUnderTest->setAttribute($element, $key, $value); $this->assertEquals($expected_result, $processed); } /** * A data provider for ::testTwigSetAttribute(). * * @return \Iterator */ public function providerTestTwigSetAttribute(): \Iterator { yield 'should add attributes on element' => [ ['#theme' => 'image'], 'title', 'Aloha', [ '#theme' => 'image', '#attributes' => [ 'title' => 'Aloha', ], ], ]; yield 'should merge existing attributes on element' => [ [ '#theme' => 'image', '#attributes' => [ 'title' => 'Aloha', ], ], 'title', 'Bonjour', [ '#theme' => 'image', '#attributes' => [ 'title' => 'Bonjour', ], ], ]; yield 'should add JSON attribute value correctly on element' => [ ['#type' => 'container'], 'data-slider', Json::encode(['autoplay' => TRUE]), [ '#type' => 'container', '#attributes' => [ 'data-slider' => '{"autoplay":true}', ], ], ]; yield '#printed should be removed after setting attribute' => [ [ '#markup' => 'This content is already is rendered', '#printed' => TRUE, ], 'title', NULL, [ '#markup' => 'This content is already is rendered', '#attributes' => [ 'title' => NULL, ], ], ]; } } class TwigExtensionTestString { Loading