From 882ea0dc8b0c6044a64d8eff74a54f52fe000f91 Mon Sep 17 00:00:00 2001 From: Jakub Vrana Date: Fri, 2 Oct 2026 22:26:56 +0200 Subject: [PATCH] Passes: null in URL attribute drops it like in other attributes (BC break) Since 3.1, a null value of an attribute means the attribute is omitted. URL attributes were an exception: the checkUrls pass appends |checkUrl, which converts null to '', so with null rendered href="". That makes the element a link to the current page. The pass now appends the filter as nullsafe ?|checkUrl, so null skips it and the attribute is dropped. With migration warnings, the change is reported like for other attributes. Co-Authored-By: Claude Opus 5.5 --- docs/reference/filters.md | 2 +- src/Latte/Essential/Passes.php | 2 +- tests/common/Engine.migrationWarnings.phpt | 6 ++++++ tests/common/Safe.url.phpt | 9 ++++++++- 4 files changed, 16 insertions(+), 3 deletions(-) diff --git a/docs/reference/filters.md b/docs/reference/filters.md index 6deb49372..7dd276c1d 100644 --- a/docs/reference/filters.md +++ b/docs/reference/filters.md @@ -24,7 +24,7 @@ survives regeneration. | `bytes` | `Filters::bytes` | | | | `capitalize` | `Filters::capitalize` | | needs ext-mbstring | | `ceil` | `Filters::ceil` | | | -| `checkUrl` | `Filters::checkUrl` | | appended automatically to URL attributes by the `checkUrls` pass; opt out with `nocheck` | +| `checkUrl` | `Filters::checkUrl` | | appended automatically to URL attributes by the `checkUrls` pass as nullsafe `?\|checkUrl`, so null still drops the attribute; opt out with `nocheck` | | `clamp` | `Filters::clamp` | | | | `column` | `Filters::column` | | | | `commas` | `Filters::commas` | | | diff --git a/src/Latte/Essential/Passes.php b/src/Latte/Essential/Passes.php index 70406e9c3..62b791cf6 100644 --- a/src/Latte/Essential/Passes.php +++ b/src/Latte/Essential/Passes.php @@ -127,7 +127,7 @@ public function checkUrlsPass(TemplateNode $node): void && !$node->modifier->removeFilter('nocheck') && !$node->modifier->removeFilter('noCheck') && !$node->modifier->hasFilter('datastream') && !$node->modifier->hasFilter('dataStream') ) { - $node->modifier->filters[] = new Php\FilterNode(new Php\IdentifierNode('checkUrl')); + $node->modifier->filters[] = new Php\FilterNode(new Php\IdentifierNode('checkUrl'), nullsafe: true); // null drops the attribute } }); } diff --git a/tests/common/Engine.migrationWarnings.phpt b/tests/common/Engine.migrationWarnings.phpt index 28b096480..a7b46379b 100644 --- a/tests/common/Engine.migrationWarnings.phpt +++ b/tests/common/Engine.migrationWarnings.phpt @@ -15,6 +15,12 @@ Assert::error( 'Behavior change for attribute \'title\' with value null: previously it rendered as title="", now the attribute is omitted (on line 1 at column 17)', ); +Assert::error( + fn() => $latte->renderToString(''), + E_USER_WARNING, + 'Behavior change for attribute \'href\' with value null: previously it rendered as href="", now the attribute is omitted (on line 1 at column 12)', +); + Assert::error( fn() => $latte->renderToString(''), E_USER_WARNING, diff --git a/tests/common/Safe.url.phpt b/tests/common/Safe.url.phpt index a50ad881a..ea9cb011c 100644 --- a/tests/common/Safe.url.phpt +++ b/tests/common/Safe.url.phpt @@ -71,11 +71,18 @@ Assert::match( Assert::contains( - 'LR\HtmlHelpers::formatAttribute(\' href\', ($this->filters->checkUrl)(($this->filters->upper)($url1)))', + 'LR\HtmlHelpers::formatAttribute(\' href\', (($ʟ_tmp = ($this->filters->upper)($url1)) === null ? null : ($this->filters->checkUrl)($ʟ_tmp)))', $latte->compile(''), ); +// null drops the attribute like in other attributes +Assert::match( + ' ', + $latte->renderToString(' ', ['url' => null]), +); + + // xlink:href in inline SVG Assert::match( 'x',