diff --git a/src/Latte/Compiler/Escaper.php b/src/Latte/Compiler/Escaper.php index 0b2c7011b..e2e1f2179 100644 --- a/src/Latte/Compiler/Escaper.php +++ b/src/Latte/Compiler/Escaper.php @@ -74,6 +74,12 @@ public function enterContentType(string $type): void } + public function enterSubType(string $type): void + { + $this->subType = $type; + } + + public function enterHtmlText(?ElementNode $node): void { $this->state = self::HtmlText; @@ -110,10 +116,6 @@ public function enterHtmlAttribute(?string $name = null): void $this->subType = self::JavaScript; } elseif ($name === 'style') { $this->subType = self::Css; - } elseif ((in_array($name, ['href', 'src', 'action', 'formaction'], true) - || ($name === 'data' && $this->tag === 'object')) - ) { - $this->subType = self::Url; } } } diff --git a/src/Latte/Compiler/Nodes/Html/AttributeNode.php b/src/Latte/Compiler/Nodes/Html/AttributeNode.php index 22dd7f98f..9a8965c10 100644 --- a/src/Latte/Compiler/Nodes/Html/AttributeNode.php +++ b/src/Latte/Compiler/Nodes/Html/AttributeNode.php @@ -38,18 +38,8 @@ public function print(PrintContext $context): string $res .= "echo '=';"; $quote = $this->quote ?? ($this->value instanceof TextNode ? null : '"'); $res .= $quote ? 'echo ' . var_export($quote, true) . ';' : ''; - - $escaper = $context->beginEscape(); - $escaper->enterHtmlAttribute(NodeHelpers::toText($this->name)); - if ($this->value instanceof FragmentNode && $escaper->export() === 'html/attr/url') { - foreach ($this->value->children as $child) { - $res .= $child->print($context); - $escaper->enterHtmlAttribute(null); - } - } else { - $res .= $this->value->print($context); - } - + $context->beginEscape()->enterHtmlAttribute(NodeHelpers::toText($this->name)); + $res .= $this->value->print($context); $context->restoreEscape(); $res .= $quote ? 'echo ' . var_export($quote, true) . ';' : ''; return $res; diff --git a/src/Latte/Compiler/Nodes/UrlNode.php b/src/Latte/Compiler/Nodes/UrlNode.php new file mode 100644 index 000000000..ad00b4b44 --- /dev/null +++ b/src/Latte/Compiler/Nodes/UrlNode.php @@ -0,0 +1,46 @@ +position = $value->position; + } + + + public function print(PrintContext $context): string + { + $escaper = $context->beginEscape(); + $escaper->enterSubType($escaper::Url); + $res = ''; + if ($this->value instanceof FragmentNode) { + foreach ($this->value->children as $child) { + $res .= $child->print($context); + $escaper->enterSubType(''); + } + } else { + $res .= $this->value->print($context); + } + $context->restoreEscape(); + return $res; + } + + + public function &getIterator(): \Generator + { + yield $this->value; + } +} diff --git a/src/Latte/Compiler/TemplateParserHtml.php b/src/Latte/Compiler/TemplateParserHtml.php index 0f05ee45d..951245abf 100644 --- a/src/Latte/Compiler/TemplateParserHtml.php +++ b/src/Latte/Compiler/TemplateParserHtml.php @@ -14,6 +14,7 @@ use Latte\Compiler\Nodes\AreaNode; use Latte\Compiler\Nodes\FragmentNode; use Latte\Compiler\Nodes\Html; +use Latte\Compiler\Nodes\UrlNode; use Latte\ContentType; use Latte\Helpers; use Latte\SecurityViolationException; @@ -262,6 +263,10 @@ private function parseAttribute(): ?Node } [$value, $quote] = $this->parseAttributeValue(); + if ($value && $this->isUrlAttribute(NodeHelpers::toText($name), $this->element)) { + $value = new UrlNode($value); + } + return new Html\AttributeNode( name: $name, value: $value, @@ -501,4 +506,14 @@ private function getPrefix(string $name): string default => Tag::PrefixNone, }; } + + + private function isUrlAttribute(?string $name, ?Html\ElementNode $elem): bool + { + $name = strtolower($name ?? ''); + return $this->parser->getContentType() === ContentType::Html + && ((in_array($name, ['href', 'src', 'action', 'formaction'], true) + || ($name === 'data' && strtolower($elem->name) === 'object')) + ); + } }