From 06a5bf532f753cf5138e7a48d45c75a4d0a2e409 Mon Sep 17 00:00:00 2001 From: Hugo Alliaume Date: Sat, 13 Jan 2024 14:00:44 +0100 Subject: [PATCH] =?UTF-8?q?[TwigComponent]=C2=A0Fix=20usage=20of=20{%=20em?= =?UTF-8?q?bed=20%}=20with=20{%=20block=20%}=20in=20=20components,?= =?UTF-8?q?=20close=20#952?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/TwigComponent/src/Twig/TwigPreLexer.php | 41 +++++++++++++++++-- .../tests/Unit/TwigPreLexerTest.php | 39 ++++++++++++++++++ 2 files changed, 76 insertions(+), 4 deletions(-) diff --git a/src/TwigComponent/src/Twig/TwigPreLexer.php b/src/TwigComponent/src/Twig/TwigPreLexer.php index fe4e7d2c680..13b4ceb5470 100644 --- a/src/TwigComponent/src/Twig/TwigPreLexer.php +++ b/src/TwigComponent/src/Twig/TwigPreLexer.php @@ -12,6 +12,7 @@ namespace Symfony\UX\TwigComponent\Twig; use Twig\Error\SyntaxError; +use Twig\Lexer; /** * Rewrites syntaxes to {% component %} syntaxes. @@ -42,6 +43,8 @@ public function preLexComponents(string $input): string $this->length = \strlen($input); $output = ''; + $inTwigEmbed = false; + while ($this->position < $this->length) { // ignore content inside verbatim block #947 if ($this->consume('{% verbatim %}')) { @@ -67,24 +70,54 @@ public function preLexComponents(string $input): string } } + if ($this->consume('{% embed')) { + $inTwigEmbed = true; + $output .= '{% embed'; + $output .= $this->consumeUntil('%}'); + + continue; + } + + if ($this->consume('{% endembed %}')) { + $inTwigEmbed = false; + $output .= '{% endembed %}'; + + continue; + } + $isTwigHtmlOpening = $this->consume('currentComponents) && $isTraditionalBlockOpening = $this->consume('{% block'))) { $componentName = $isTraditionalBlockOpening ? 'block' : $this->consumeComponentName(); if ('block' === $componentName) { // if we're already inside the "default" block, let's close it - if (!empty($this->currentComponents) && $this->currentComponents[\count($this->currentComponents) - 1]['hasDefaultBlock']) { + if (!empty($this->currentComponents) && $this->currentComponents[\count($this->currentComponents) - 1]['hasDefaultBlock'] && !$inTwigEmbed) { $output .= '{% endblock %}'; $this->currentComponents[\count($this->currentComponents) - 1]['hasDefaultBlock'] = false; } if ($isTraditionalBlockOpening) { + $prevPosition = $this->position; + // add what we've consumed so far $output .= '{% block'; $output .= $this->consumeUntil('%}'); - $output .= $this->consumeUntilEndBlock(); + + // If the last consumed string does not match the Twig's block name regex, we assume the block is self-closing + $isBlockSelfClosing = '' !== preg_replace( + Lexer::REGEX_NAME, + '', + trim(mb_substr($this->input, $prevPosition, $this->position - $prevPosition))) + ; + + if ($isBlockSelfClosing && $this->consume('%}')) { + $output .= '%}'; + } else { + $output .= $this->consumeUntilEndBlock(); + } continue; } @@ -420,8 +453,8 @@ private function consumeUntilEndBlock(): string if (!$inComment && '{% endblock %}' === substr($this->input, $this->position, 14)) { if (1 === $depth) { - // in this case, we want to advanced ALL the way beyond the endblock - $this->position += 14; + // in this case, we want to advance ALL the way beyond the endblock + $this->position += 14 /* strlen('{% endblock %}') */; break; } else { --$depth; diff --git a/src/TwigComponent/tests/Unit/TwigPreLexerTest.php b/src/TwigComponent/tests/Unit/TwigPreLexerTest.php index 99b88c7801b..2674b84fb2b 100644 --- a/src/TwigComponent/tests/Unit/TwigPreLexerTest.php +++ b/src/TwigComponent/tests/Unit/TwigPreLexerTest.php @@ -183,6 +183,45 @@ public static function getLexTests(): iterable EOF ]; + yield 'component_where_entire_default_block_is_twig_embed' => [ + << +

+ {% embed "my_embed.html.twig" with { foo: 'bar' } %}{% endembed %} +

+ + EOF, + << + {% embed "my_embed.html.twig" with { foo: 'bar' } %}{% endembed %} +

+ {% endblock %}{% endcomponent %} + EOF, + ]; + yield 'component_where_entire_default_block_is_twig_embed_with_blocks' => [ + << +

+ {% embed "my_embed.html.twig" %} + {% block my_embed_block_1 "foo" %} + {% block my_embed_block_2 %}bar{% endblock %} + {% endembed %} +

+ + EOF, + << + {% embed "my_embed.html.twig" %} + {% block my_embed_block_1 "foo" %} + {% block my_embed_block_2 %}bar{% endblock %} + {% endembed %} +

+ {% endblock %}{% endcomponent %} + EOF, + ]; + yield 'string_inside_of_twig_code_not_escaped' => [ <<