Commit 4d500c50 authored by Cristina Chumillas's avatar Cristina Chumillas
Browse files

Issue #3301853 by matthieuscarset, mherchel, lauriii, pdureau, dww, Chi,...

Issue #3301853 by matthieuscarset, mherchel, lauriii, pdureau, dww, Chi, smustgrave: Create twig filters: |add_class and |set_attribute
parent 706b0006
Loading
Loading
Loading
Loading
+54 −0
Original line number Diff line number Diff line
@@ -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']),
@@ -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;
  }

}
+127 −1
Original line number Diff line number Diff line
@@ -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;
@@ -438,7 +440,7 @@ public function testTwigAddSuggestionFilter($original_render_array, $suggestion,
  }

  /**
   * A data provider for ::testTwigAddSuggestionFilter().
   * Provides data for ::testTwigAddSuggestionFilter().
   *
   * @return \Iterator
   */
@@ -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 {