Skip to content

v1.7.0

Latest

Choose a tag to compare

@github-actions github-actions released this 09 Oct 15:57
· 1 commit to main since this release

What's Changed

Read before updating. Two changes can break existing code. Everything else is new or fixed behavior that needs no change on your side.

  1. Attribute names are validated (see Security below). What: a name that is empty or contains whitespace, a control character or any of " ' < > / = now throws \InvalidArgumentException. Why: Drupal escapes a name as text only, so a name such as x onmouseover rendered as two attributes. You are affected only if your code passes such a name. Names like @click, :class and [hidden] still work.
  2. AttributeExtension.php moved to src/Twig/ (see Changed below). What: the file has a new path. Why: the root autoload mapping made the autoloader scan the whole repository root, and the file shipped next to config files. You are affected only if you load the file by path instead of through the Composer autoloader, or if a compiled Symfony container holds the old path. Clear var/cache/ after composer update.

Added

  • without Twig filter, with the semantics of Drupal core's TwigExtension::withoutFilter(). {{ attributes|without('class') }} renders a copy of the collection (or a plain array) without the named keys and leaves the original unchanged. The README and the AttributeCollection docblock already described this filter, but the package did not register it.

Changed

  • Layout change: AttributeExtension.php moved from the repository root to src/Twig/AttributeExtension.php. The Composer autoload maps Parisek\Twig\ to src/Twig now. The namespace, the class name and the public API do not change, so code that loads the class through the Composer autoloader needs no change.
    • Do not load the file by path. require_once 'vendor/parisek/twig-attribute/AttributeExtension.php' is an anti-pattern, and it fails after this update. Use the autoloader (vendor/autoload.php) and new \Parisek\Twig\AttributeExtension().
    • Clear compiled caches after updating. A compiled Symfony container (var/cache/) can hold the old path. Run bin/console cache:clear, or delete var/cache/, if the container fails to boot after composer update.

Security

  • Attribute names are validated. A name such as x onmouseover or a="1" onclick used to render as two attributes, because Drupal escapes the name as text only. AttributeValueBase now throws \InvalidArgumentException for an empty name, ASCII whitespace, control characters (NUL included) and any of " ' < > / =. Names for Alpine, Vue and Angular (@click, :class, x-on:click.prevent, [hidden], (click), *ngIf, #ref) stay valid. This is a behavior change for code that passed such a name. It is a deliberate difference from Drupal core, and the upstream watch knows about it. Applies to the constructor, setAttribute(), array access and the create_attribute() Twig function. Custom AttributeValueBase subclasses stay trusted developer input.

Fixed

  • An attribute collection kept in a variable, for example {% set a = create_attribute() %}<div{{ a.addClass("x") }}>, was escaped a second time under Twig autoescape. New AttributeExtension::registerSafeClass($twig) declares MarkupInterface objects as HTML in the Twig escaper. Call it right after addExtension(), before anything is rendered, when autoescape is on. Objects that Twig already escaped in that environment keep being escaped, including subclasses of AttributeCollection. Strings, other Stringable values and arrays stay escaped. As a fallback the extension calls it on the first create_attribute() or without call; that fallback depends on the order of calls. Timber and timber-kit (autoescape off) need nothing. A MarkupInterface class is trusted HTML; custom AttributeValueBase subclasses stay trusted developer input, as in Drupal.

Pull Requests

  • #12 — ci: bump actions/checkout to v5 (Node 20 → 24)
  • #13 — qa: raise PHPStan to level 8 (our code, no baseline)
  • #15 — docs: add security policy, contributing guide, GitHub templates and badges
  • #17 — chore(deps): bump actions/dependency-review-action from 3 to 5
  • #18 — chore(deps): bump actions/checkout from 5 to 7
  • #19 — chore: stop tracking composer.lock
  • #20 — docs: add Timber 2 instantiation example to README
  • #21 — docs(agents): bring AGENTS.md in line with the repository
  • #29 — feat: add the without Twig filter
  • #31 — fix: treat MarkupInterface objects as HTML-safe under Twig autoescape
  • #33 — docs: fix stale README requirements and move the 1.6.0 upgrade guide to the CHANGELOG
  • #34 — ci: add PHP 8.5 and Twig 4 alpha legs, allow PHPUnit 12, widen Dependabot
  • #35 — test: raise coverage of the vendored classes and shims to 99%
  • #36 — ci: watch upstream Drupal for changes to the vendored Attribute classes
  • #37 — refactor: move AttributeExtension to src/Twig
  • #38 — fix: reject unsafe attribute names
  • #39 — docs: refresh README and record the baseline reviewer
  • #40 — docs: add upgrade notes for 1.7.0

Full Changelog: v1.6.1...v1.7.0