mirror of
https://github.com/getgrav/grav.git
synced 2026-01-28 10:20:02 +01:00
more robust deferred logic + deprecated fixes
Signed-off-by: Andy Miller <rhuk@mac.com>
This commit is contained in:
8
composer.lock
generated
8
composer.lock
generated
@@ -4228,12 +4228,12 @@
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/getgrav/Twig.git",
|
||||
"reference": "b987134b5cc7553fcdc41840799df5318504e0c7"
|
||||
"reference": "5a01e60e351f4c8d41765970c412d2500288339b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/getgrav/Twig/zipball/b987134b5cc7553fcdc41840799df5318504e0c7",
|
||||
"reference": "b987134b5cc7553fcdc41840799df5318504e0c7",
|
||||
"url": "https://api.github.com/repos/getgrav/Twig/zipball/5a01e60e351f4c8d41765970c412d2500288339b",
|
||||
"reference": "5a01e60e351f4c8d41765970c412d2500288339b",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -4293,7 +4293,7 @@
|
||||
"support": {
|
||||
"source": "https://github.com/getgrav/Twig/tree/3.x"
|
||||
},
|
||||
"time": "2025-11-04T11:37:42+00:00"
|
||||
"time": "2025-11-21T11:38:05+00:00"
|
||||
},
|
||||
{
|
||||
"name": "willdurand/negotiation",
|
||||
|
||||
@@ -31,7 +31,7 @@ class TwigNodeSwitch extends Node
|
||||
$nodes = ['value' => $value, 'cases' => $cases, 'default' => $default];
|
||||
$nodes = array_filter($nodes);
|
||||
|
||||
parent::__construct($nodes, [], $lineno, $tag);
|
||||
parent::__construct($nodes, [], $lineno);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -44,9 +44,9 @@ class TwigTokenParserCache extends AbstractTokenParser
|
||||
$lifetime = null;
|
||||
while (!$stream->test(Token::BLOCK_END_TYPE)) {
|
||||
if ($stream->test(Token::STRING_TYPE)) {
|
||||
$key = $this->parser->getExpressionParser()->parseExpression();
|
||||
$key = $this->parser->parseExpression();
|
||||
} elseif ($stream->test(Token::NUMBER_TYPE)) {
|
||||
$lifetime = $this->parser->getExpressionParser()->parseExpression();
|
||||
$lifetime = $this->parser->parseExpression();
|
||||
} else {
|
||||
throw new \Twig\Error\SyntaxError("Unexpected token type in cache tag.", $token->getLine(), $stream->getSourceContext());
|
||||
}
|
||||
|
||||
@@ -73,23 +73,23 @@ class TwigTokenParserLink extends AbstractTokenParser
|
||||
|
||||
$file = null;
|
||||
if (!$stream->test(Token::NAME_TYPE) && !$stream->test(Token::BLOCK_END_TYPE)) {
|
||||
$file = $this->parser->getExpressionParser()->parseExpression();
|
||||
$file = $this->parser->parseExpression();
|
||||
}
|
||||
|
||||
$group = null;
|
||||
if ($stream->nextIf(Token::NAME_TYPE, 'at')) {
|
||||
$group = $this->parser->getExpressionParser()->parseExpression();
|
||||
$group = $this->parser->parseExpression();
|
||||
}
|
||||
|
||||
$priority = null;
|
||||
if ($stream->nextIf(Token::NAME_TYPE, 'priority')) {
|
||||
$stream->expect(Token::PUNCTUATION_TYPE, ':');
|
||||
$priority = $this->parser->getExpressionParser()->parseExpression();
|
||||
$priority = $this->parser->parseExpression();
|
||||
}
|
||||
|
||||
$attributes = null;
|
||||
if ($stream->nextIf(Token::NAME_TYPE, 'with')) {
|
||||
$attributes = $this->parser->getExpressionParser()->parseExpression();
|
||||
$attributes = $this->parser->parseExpression();
|
||||
}
|
||||
|
||||
$stream->expect(Token::BLOCK_END_TYPE);
|
||||
|
||||
@@ -44,17 +44,17 @@ class TwigTokenParserRender extends AbstractTokenParser
|
||||
{
|
||||
$stream = $this->parser->getStream();
|
||||
|
||||
$object = $this->parser->getExpressionParser()->parseExpression();
|
||||
$object = $this->parser->parseExpression();
|
||||
|
||||
$layout = null;
|
||||
if ($stream->nextIf(Token::NAME_TYPE, 'layout')) {
|
||||
$stream->expect(Token::PUNCTUATION_TYPE, ':');
|
||||
$layout = $this->parser->getExpressionParser()->parseExpression();
|
||||
$layout = $this->parser->parseExpression();
|
||||
}
|
||||
|
||||
$context = null;
|
||||
if ($stream->nextIf(Token::NAME_TYPE, 'with')) {
|
||||
$context = $this->parser->getExpressionParser()->parseExpression();
|
||||
$context = $this->parser->parseExpression();
|
||||
}
|
||||
|
||||
$stream->expect(Token::BLOCK_END_TYPE);
|
||||
|
||||
@@ -87,23 +87,23 @@ class TwigTokenParserScript extends AbstractTokenParser
|
||||
|
||||
$file = null;
|
||||
if (!$stream->test(Token::NAME_TYPE) && !$stream->test(Token::OPERATOR_TYPE, 'in') && !$stream->test(Token::BLOCK_END_TYPE)) {
|
||||
$file = $this->parser->getExpressionParser()->parseExpression();
|
||||
$file = $this->parser->parseExpression();
|
||||
}
|
||||
|
||||
$group = null;
|
||||
if ($stream->nextIf(Token::NAME_TYPE, 'at') || $stream->nextIf(Token::OPERATOR_TYPE, 'in')) {
|
||||
$group = $this->parser->getExpressionParser()->parseExpression();
|
||||
$group = $this->parser->parseExpression();
|
||||
}
|
||||
|
||||
$priority = null;
|
||||
if ($stream->nextIf(Token::NAME_TYPE, 'priority')) {
|
||||
$stream->expect(Token::PUNCTUATION_TYPE, ':');
|
||||
$priority = $this->parser->getExpressionParser()->parseExpression();
|
||||
$priority = $this->parser->parseExpression();
|
||||
}
|
||||
|
||||
$attributes = null;
|
||||
if ($stream->nextIf(Token::NAME_TYPE, 'with')) {
|
||||
$attributes = $this->parser->getExpressionParser()->parseExpression();
|
||||
$attributes = $this->parser->parseExpression();
|
||||
}
|
||||
|
||||
$stream->expect(Token::BLOCK_END_TYPE);
|
||||
|
||||
@@ -74,23 +74,23 @@ class TwigTokenParserStyle extends AbstractTokenParser
|
||||
|
||||
$file = null;
|
||||
if (!$stream->test(Token::NAME_TYPE) && !$stream->test(Token::OPERATOR_TYPE, 'in') && !$stream->test(Token::BLOCK_END_TYPE)) {
|
||||
$file = $this->parser->getExpressionParser()->parseExpression();
|
||||
$file = $this->parser->parseExpression();
|
||||
}
|
||||
|
||||
$group = null;
|
||||
if ($stream->nextIf(Token::NAME_TYPE, 'at') || $stream->nextIf(Token::OPERATOR_TYPE, 'in')) {
|
||||
$group = $this->parser->getExpressionParser()->parseExpression();
|
||||
$group = $this->parser->parseExpression();
|
||||
}
|
||||
|
||||
$priority = null;
|
||||
if ($stream->nextIf(Token::NAME_TYPE, 'priority')) {
|
||||
$stream->expect(Token::PUNCTUATION_TYPE, ':');
|
||||
$priority = $this->parser->getExpressionParser()->parseExpression();
|
||||
$priority = $this->parser->parseExpression();
|
||||
}
|
||||
|
||||
$attributes = null;
|
||||
if ($stream->nextIf(Token::NAME_TYPE, 'with')) {
|
||||
$attributes = $this->parser->getExpressionParser()->parseExpression();
|
||||
$attributes = $this->parser->parseExpression();
|
||||
}
|
||||
|
||||
$stream->expect(Token::BLOCK_END_TYPE);
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace Grav\Common\Twig\TokenParser;
|
||||
use Grav\Common\Twig\Node\TwigNodeSwitch;
|
||||
use Twig\Error\SyntaxError;
|
||||
use Twig\Node\Node;
|
||||
use Twig\Node\Nodes;
|
||||
use Twig\Token;
|
||||
use Twig\TokenParser\AbstractTokenParser;
|
||||
|
||||
@@ -40,21 +41,22 @@ class TwigTokenParserSwitch extends AbstractTokenParser
|
||||
$lineno = $token->getLine();
|
||||
$stream = $this->parser->getStream();
|
||||
|
||||
$name = $this->parser->getExpressionParser()->parseExpression();
|
||||
$name = $this->parser->parseExpression();
|
||||
$stream->expect(Token::BLOCK_END_TYPE);
|
||||
|
||||
// There can be some whitespace between the {% switch %} and first {% case %} tag.
|
||||
while ($stream->getCurrent()->getType() === Token::TEXT_TYPE && trim((string) $stream->getCurrent()->getValue()) === '') {
|
||||
while ($stream->getCurrent()->test(Token::TEXT_TYPE) && trim((string) $stream->getCurrent()->getValue()) === '') {
|
||||
$stream->next();
|
||||
}
|
||||
|
||||
$stream->expect(Token::BLOCK_START_TYPE);
|
||||
|
||||
$expressionParser = $this->parser->getExpressionParser();
|
||||
|
||||
$default = null;
|
||||
$cases = [];
|
||||
$end = false;
|
||||
|
||||
// 'or' operator precedence is 10. We want to stop parsing if we encounter it.
|
||||
$orPrecedence = 10;
|
||||
|
||||
while (!$end) {
|
||||
$next = $stream->next();
|
||||
@@ -64,7 +66,7 @@ class TwigTokenParserSwitch extends AbstractTokenParser
|
||||
$values = [];
|
||||
|
||||
while (true) {
|
||||
$values[] = $expressionParser->parsePrimaryExpression();
|
||||
$values[] = $this->parser->parseExpression($orPrecedence + 1);
|
||||
// Multiple allowed values?
|
||||
if ($stream->test(Token::OPERATOR_TYPE, 'or')) {
|
||||
$stream->next();
|
||||
@@ -75,10 +77,10 @@ class TwigTokenParserSwitch extends AbstractTokenParser
|
||||
|
||||
$stream->expect(Token::BLOCK_END_TYPE);
|
||||
$body = $this->parser->subparse($this->decideIfFork(...));
|
||||
$cases[] = new Node([
|
||||
'values' => new Node($values),
|
||||
$cases[] = new class([
|
||||
'values' => new Nodes($values),
|
||||
'body' => $body
|
||||
]);
|
||||
]) extends Node {};
|
||||
break;
|
||||
|
||||
case 'default':
|
||||
@@ -97,7 +99,7 @@ class TwigTokenParserSwitch extends AbstractTokenParser
|
||||
|
||||
$stream->expect(Token::BLOCK_END_TYPE);
|
||||
|
||||
return new TwigNodeSwitch($name, new Node($cases), $default, $lineno, $this->getTag());
|
||||
return new TwigNodeSwitch($name, new Nodes($cases), $default, $lineno, $this->getTag());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -37,7 +37,7 @@ class TwigTokenParserThrow extends AbstractTokenParser
|
||||
$stream = $this->parser->getStream();
|
||||
|
||||
$code = $stream->expect(Token::NUMBER_TYPE)->getValue();
|
||||
$message = $this->parser->getExpressionParser()->parseExpression();
|
||||
$message = $this->parser->parseExpression();
|
||||
$stream->expect(Token::BLOCK_END_TYPE);
|
||||
|
||||
return new TwigNodeThrow((int)$code, $message, $lineno, $this->getTag());
|
||||
|
||||
@@ -11,8 +11,11 @@ namespace Grav\Common\Twig;
|
||||
|
||||
use Twig\Environment;
|
||||
use Twig\Error\LoaderError;
|
||||
use Twig\Extension\EscaperExtension;
|
||||
use Twig\Extension\ExtensionInterface;
|
||||
use Twig\Loader\ExistsLoaderInterface;
|
||||
use Twig\Loader\LoaderInterface;
|
||||
use Twig\Runtime\EscaperRuntime;
|
||||
use Twig\Template;
|
||||
use Twig\TemplateWrapper;
|
||||
|
||||
@@ -22,6 +25,39 @@ use Twig\TemplateWrapper;
|
||||
*/
|
||||
class TwigEnvironment extends Environment
|
||||
{
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getExtension(string $name): ExtensionInterface
|
||||
{
|
||||
$extension = parent::getExtension($name);
|
||||
|
||||
if ($name === EscaperExtension::class && class_exists(EscaperRuntime::class)) {
|
||||
return new class($extension, $this) extends EscaperExtension {
|
||||
private $original;
|
||||
private $env;
|
||||
|
||||
public function __construct($original, $env)
|
||||
{
|
||||
$this->original = $original;
|
||||
$this->env = $env;
|
||||
}
|
||||
|
||||
public function setEscaper($strategy, $callable)
|
||||
{
|
||||
$this->env->getRuntime(EscaperRuntime::class)->setEscaper($strategy, $callable);
|
||||
}
|
||||
|
||||
public function getDefaultStrategy($filename)
|
||||
{
|
||||
return $this->original->getDefaultStrategy($filename);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return $extension;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*
|
||||
|
||||
@@ -13,66 +13,70 @@ declare(strict_types=1);
|
||||
|
||||
namespace Twig\DeferredExtension;
|
||||
|
||||
use Twig\Error\SyntaxError;
|
||||
use Twig\Node\BlockNode;
|
||||
use Twig\Node\BlockReferenceNode;
|
||||
use Twig\Node\EmptyNode;
|
||||
use Twig\Node\Node;
|
||||
use Twig\Parser;
|
||||
use Twig\Node\Nodes;
|
||||
use Twig\Node\PrintNode;
|
||||
use Twig\Token;
|
||||
use Twig\TokenParser\AbstractTokenParser;
|
||||
use Twig\TokenParser\BlockTokenParser;
|
||||
|
||||
final class DeferredTokenParser extends AbstractTokenParser
|
||||
{
|
||||
private $blockTokenParser;
|
||||
|
||||
public function setParser(Parser $parser) : void
|
||||
{
|
||||
parent::setParser($parser);
|
||||
|
||||
$this->blockTokenParser = new BlockTokenParser();
|
||||
$this->blockTokenParser->setParser($parser);
|
||||
}
|
||||
|
||||
public function parse(Token $token) : Node
|
||||
{
|
||||
$lineno = $token->getLine();
|
||||
$stream = $this->parser->getStream();
|
||||
$nameToken = $stream->next();
|
||||
$deferredToken = $stream->nextIf(Token::NAME_TYPE, 'deferred');
|
||||
$stream->injectTokens([$nameToken]);
|
||||
$name = $stream->expect(Token::NAME_TYPE)->getValue();
|
||||
|
||||
$deferred = $stream->nextIf(Token::NAME_TYPE, 'deferred');
|
||||
|
||||
$node = $this->blockTokenParser->parse($token);
|
||||
|
||||
if ($deferredToken) {
|
||||
$this->replaceBlockNode($nameToken->getValue());
|
||||
if ($this->parser->hasBlock($name)) {
|
||||
throw new SyntaxError(\sprintf("The block '%s' has already been defined line %d.", $name, $this->parser->getBlock($name)->getTemplateLine()), $stream->getCurrent()->getLine(), $stream->getSourceContext());
|
||||
}
|
||||
|
||||
return $node;
|
||||
if ($deferred) {
|
||||
$block = new DeferredBlockNode($name, new EmptyNode(), $lineno);
|
||||
} else {
|
||||
$block = new BlockNode($name, new EmptyNode(), $lineno);
|
||||
}
|
||||
|
||||
$this->parser->setBlock($name, $block);
|
||||
$this->parser->pushLocalScope();
|
||||
$this->parser->pushBlockStack($name);
|
||||
|
||||
if ($stream->nextIf(Token::BLOCK_END_TYPE)) {
|
||||
$body = $this->parser->subparse([$this, 'decideBlockEnd'], true);
|
||||
if ($token = $stream->nextIf(Token::NAME_TYPE)) {
|
||||
$value = $token->getValue();
|
||||
|
||||
if ($value != $name) {
|
||||
throw new SyntaxError(\sprintf('Expected endblock for block "%s" (but "%s" given).', $name, $value), $stream->getCurrent()->getLine(), $stream->getSourceContext());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$body = new Nodes([
|
||||
new PrintNode($this->parser->parseExpression(), $lineno),
|
||||
]);
|
||||
}
|
||||
$stream->expect(Token::BLOCK_END_TYPE);
|
||||
|
||||
$block->setNode('body', $body);
|
||||
$this->parser->popBlockStack();
|
||||
$this->parser->popLocalScope();
|
||||
|
||||
return new BlockReferenceNode($name, $lineno, $this->getTag());
|
||||
}
|
||||
|
||||
public function decideBlockEnd(Token $token): bool
|
||||
{
|
||||
return $token->test('endblock');
|
||||
}
|
||||
|
||||
public function getTag() : string
|
||||
{
|
||||
return 'block';
|
||||
}
|
||||
|
||||
private function replaceBlockNode(string $name) : void
|
||||
{
|
||||
$blockContainer = $this->parser->getBlock($name);
|
||||
$block = $blockContainer->getNode('0');
|
||||
$blockContainer->setNode('0', $this->createDeferredBlockNode($block));
|
||||
}
|
||||
|
||||
private function createDeferredBlockNode(BlockNode $block) : DeferredBlockNode
|
||||
{
|
||||
$name = $block->getAttribute('name');
|
||||
$deferredBlock = new DeferredBlockNode($name, new Node([]), $block->getTemplateLine());
|
||||
|
||||
foreach ($block as $nodeName => $node) {
|
||||
$deferredBlock->setNode($nodeName, $node);
|
||||
}
|
||||
|
||||
if ($sourceContext = $block->getSourceContext()) {
|
||||
$deferredBlock->setSourceContext($sourceContext);
|
||||
}
|
||||
|
||||
return $deferredBlock;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user