Skip to content
Snippets Groups Projects
Commit 99e98c73 authored by Pierre Dureau's avatar Pierre Dureau Committed by Florent Torregrosa
Browse files

Issue #3311480 by pdureau, Grimreaper, DuaelFr: Reduce preprocess hooks usage...

Issue #3311480 by pdureau, Grimreaper, DuaelFr: Reduce preprocess hooks usage by adding add_class() & set_attributes() filters
parent 8e38f1d4
No related branches found
No related tags found
1 merge request!25Resolve #3311480 "add set_attribute and add_class filters"
<?php
namespace Drupal\ui_patterns\Template;
use Drupal\Core\Template\Attribute;
use Drupal\Core\Template\AttributeHelper;
/**
* Methods for the set_attribute and add_class filters.
*
* In a trait, to be usable in other places.
*/
trait AttributesFilterTrait {
/**
* Add a value into the class attributes of a given element.
*
* @param mixed $element
* A render array.
* @param string[]|string ...$classes
* The classes to add on element. Arguments can include string keys directly
* or arrays of string keys.
*
* @return mixed
* The element with the given class(es) in attributes or the unchanged
* element if passed value is not an array.
*
* @see Drupal\Core\Template\TwigExtension::addClass()
*/
public function addClass(mixed $element, ...$classes): mixed {
if (!\is_array($element)) {
return $element;
}
if ($this->arrayIsList($element)) {
foreach ($element as $index => $item) {
if (!\is_array($item)) {
continue;
}
$element[$index] = $this->addClass($item, ...$classes);
}
return $element;
}
$attributes = new Attribute($element['#attributes'] ?? []);
$attributes->addClass(...$classes);
$element['#attributes'] = $attributes->toArray();
// Make sure element gets rendered again.
unset($element['#printed']);
return $element;
}
/**
* Set attribute on a given element.
*
* @param mixed $element
* A render array.
* @param string $name
* The attribute name.
* @param mixed $value
* (optional) The attribute value.
*
* @return mixed
* The element with the given sanitized attribute's value or the unchanged
* element if passed value is not an array.
*
* @see Drupal\Core\Template\TwigExtension::setAttribute()
*/
public function setAttribute(mixed $element, string $name, mixed $value = NULL): mixed {
if (!\is_array($element)) {
return $element;
}
if ($this->arrayIsList($element)) {
foreach ($element as $index => $item) {
if (!\is_array($item)) {
continue;
}
$element[$index] = $this->setAttribute($item, $name, $value);
}
return $element;
}
$element['#attributes'] = AttributeHelper::mergeCollections(
$element['#attributes'] ?? [],
new Attribute([$name => $value])
);
// Make sure element gets rendered again.
unset($element['#printed']);
return $element;
}
/**
* Checks whether a given array is a list.
*
* Same as array_is_list() but compatible with PHP8.
*
* @param array $array
* The array being evaluated.
*
* @return bool
* Returns true if array is a list, false otherwise.
*
* @see https://www.php.net/manual/en/function.array-is-list.php#126794
*/
private function arrayIsList(array $array): bool {
$i = -1;
foreach ($array as $k => $v) {
++$i;
if ($k !== $i) {
return FALSE;
}
}
return TRUE;
}
}
......@@ -4,6 +4,7 @@ namespace Drupal\ui_patterns\Template;
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;
use Twig\TwigFilter;
/**
* Twig extension providing UI Patterns-specific functionalities.
......@@ -12,6 +13,8 @@ use Twig\TwigFunction;
*/
class TwigExtension extends AbstractExtension {
use AttributesFilterTrait;
/**
* {@inheritdoc}
*/
......@@ -35,6 +38,16 @@ class TwigExtension extends AbstractExtension {
];
}
/**
* {@inheritdoc}
*/
public function getFilters() {
return [
new TwigFilter('add_class', [$this, 'addClass']),
new TwigFilter('set_attribute', [$this, 'setAttribute']),
];
}
/**
* Render given pattern.
*
......
<?php
namespace Drupal\Tests\ui_patterns\Unit\Template;
// cspell:ignore mila
use Drupal\Component\Serialization\Json;
use Drupal\Core\File\FileUrlGeneratorInterface;
use Drupal\Core\Render\Markup;
use Drupal\ui_patterns\Template\TwigExtension;
use Drupal\Tests\UnitTestCase;
use Drupal\Core\Template\Attribute;
/**
* Tests the twig extension.
*
* @group Template
*
* @coversDefaultClass \Drupal\Core\Template\TwigExtension
*/
class TwigExtensionTest extends UnitTestCase {
/**
* The system under test.
*
* @var \Drupal\Core\Template\TwigExtension
*/
protected $systemUnderTest;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$renderer = $this->createMock('\Drupal\Core\Render\RendererInterface');
$urlGenerator = $this->createMock('\Drupal\Core\Routing\UrlGeneratorInterface');
$themeManager = $this->createMock('\Drupal\Core\Theme\ThemeManagerInterface');
$dateFormatter = $this->createMock('\Drupal\Core\Datetime\DateFormatterInterface');
$fileUrlGenerator = $this->createMock(FileUrlGeneratorInterface::class);
$this->systemUnderTest = new TwigExtension($renderer, $urlGenerator, $themeManager, $dateFormatter, $fileUrlGenerator);
}
/**
* 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);
}
/**
* A data provider for ::testTwigAddClass().
*
* @return \Iterator
* An 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 'should add a class when an attributes array is already present' => [
[
'#type' =>
'container',
'#attributes' => [
'foo' => 'bar',
],
],
[Markup::create('my-class')],
[
'#type' => 'container',
'#attributes' => [
'class' => ['my-class'],
'foo' => 'bar',
],
],
];
yield 'should add a class when an attributes object is already present' => [
[
'#type' =>
'container',
'#attributes' => new Attribute([
'foo' => 'bar',
]),
],
[Markup::create('my-class')],
[
'#type' => 'container',
'#attributes' => [
'class' => ['my-class'],
'foo' => 'bar',
],
],
];
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
* An 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,
],
],
];
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment