mirror of
https://github.com/Combodo/iTop.git
synced 2026-05-18 23:08:46 +02:00
N°8910 - Upgrade Symfony packages (#811)
This commit is contained in:
@@ -1,3 +1,34 @@
|
||||
# 3.23.0 (2026-01-23)
|
||||
|
||||
* Add `=` assignment operator (allows to set variables in expression or to replace the short-form of the set tag)
|
||||
* Add sequence, mapping, and object destructuring
|
||||
* Add `?.` null-safe operator
|
||||
* Add `===` and `!==` operators (equivalent to the `same as` and `not same as` tests)
|
||||
* Fix opcache preload warning for unlinked anonymous class
|
||||
* Fix spread operator behavior
|
||||
|
||||
# 3.22.2 (2025-12-14)
|
||||
|
||||
* Fix "cycle" with non-countable ArrayAccess + Traversable objects
|
||||
* Use "getShareDir" as an indicator of Symfony version in Symfony bundle
|
||||
* Fix escaper compatibility with PHP 8.5
|
||||
|
||||
# 3.22.1 (2025-11-16)
|
||||
|
||||
* Add support for Symfony 8
|
||||
|
||||
# 3.22.0 (2025-10-29)
|
||||
|
||||
* Add support for two words test in guard tag
|
||||
* Add `Environment::registerUndefinedTestCallback()`
|
||||
* Fix compatibility with Symfony 8
|
||||
* Fix accessing arrays with stringable objects as key
|
||||
* Avoid errors when failing to guess the template info for an error
|
||||
* Fix expression parser compatibility layer
|
||||
* Fix compiling 'index' with repr (not string) in EmbedNode
|
||||
* Update configuration keys + allow extra keys for CommonMark extensions
|
||||
* Allow usage of other Markdown converters than CommonMark in LeagueMarkdown
|
||||
|
||||
# 3.21.1 (2025-05-03)
|
||||
|
||||
* Fix ExtensionSet usage of BinaryOperatorExpressionParser
|
||||
@@ -44,6 +75,7 @@
|
||||
|
||||
# 3.18.0 (2024-12-29)
|
||||
|
||||
* Support for invoking closures
|
||||
* Fix unary operator precedence change
|
||||
* Ignore `SyntaxError` exceptions from undefined handlers when using the `guard` tag
|
||||
* Add a way to stream template rendering (`TemplateWrapper::stream()` and `TemplateWrapper::streamBlock()`)
|
||||
@@ -52,7 +84,6 @@
|
||||
|
||||
* Fix the null coalescing operator when the test returns null
|
||||
* Fix the Elvis operator when used as '? :' instead of '?:'
|
||||
* Support for invoking closures
|
||||
|
||||
# 3.17.0 (2024-12-10)
|
||||
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
parameters:
|
||||
ignoreErrors:
|
||||
- # The method is dynamically generated by the CheckSecurityNode
|
||||
message: '#^Call to an undefined method Twig\\Template\:\:checkSecurity\(\)\.$#'
|
||||
identifier: method.notFound
|
||||
count: 1
|
||||
path: src/Extension/CoreExtension.php
|
||||
|
||||
- # 2 parameters will be required
|
||||
message: '#^Method Twig\\Node\\IncludeNode\:\:addGetTemplate\(\) invoked with 2 parameters, 1 required\.$#'
|
||||
identifier: arguments.count
|
||||
count: 1
|
||||
path: src/Node/IncludeNode.php
|
||||
|
||||
- # int|string will be supported in 4.x
|
||||
message: '#^PHPDoc tag @param for parameter $name with type int|string is not subtype of native type string\.$#'
|
||||
identifier: parameter.phpDocType
|
||||
count: 5
|
||||
path: src/Node/Node.php
|
||||
|
||||
- # Adding 0 to the string representation of a number is valid and what we want here
|
||||
message: '#^Binary operation "\+" between 0 and string results in an error\.$#'
|
||||
identifier: binaryOp.invalid
|
||||
count: 1
|
||||
path: src/Lexer.php
|
||||
@@ -1,9 +0,0 @@
|
||||
includes:
|
||||
- phpstan-baseline.neon
|
||||
|
||||
parameters:
|
||||
level: 3
|
||||
paths:
|
||||
- src
|
||||
excludePaths:
|
||||
- src/Test
|
||||
15
lib/twig/twig/splitsh.json
Normal file
15
lib/twig/twig/splitsh.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"subtrees": {
|
||||
"twig-extra-bundle": "extra/twig-extra-bundle",
|
||||
"cache-extra": "extra/cache-extra",
|
||||
"cssinliner-extra": "extra/cssinliner-extra",
|
||||
"html-extra": "extra/html-extra",
|
||||
"inky-extra": "extra/inky-extra",
|
||||
"intl-extra": "extra/intl-extra",
|
||||
"markdown-extra": "extra/markdown-extra",
|
||||
"string-extra": "extra/string-extra"
|
||||
},
|
||||
"defaults": {
|
||||
"git_constraint": "<1.8.2"
|
||||
}
|
||||
}
|
||||
@@ -31,15 +31,15 @@ use Twig\TwigFilter;
|
||||
final class AsTwigFilter
|
||||
{
|
||||
/**
|
||||
* @param non-empty-string $name The name of the filter in Twig.
|
||||
* @param bool|null $needsCharset Whether the filter needs the charset passed as the first argument.
|
||||
* @param bool|null $needsEnvironment Whether the filter needs the environment passed as the first argument, or after the charset.
|
||||
* @param bool|null $needsContext Whether the filter needs the context array passed as the first argument, or after the charset and the environment.
|
||||
* @param string[]|null $isSafe List of formats in which you want the raw output to be printed unescaped.
|
||||
* @param string|array|null $isSafeCallback Function called at compilation time to determine if the filter is safe.
|
||||
* @param string|null $preEscape Some filters may need to work on input that is already escaped or safe
|
||||
* @param string[]|null $preservesSafety Preserves the safety of the value that the filter is applied to.
|
||||
* @param DeprecatedCallableInfo|null $deprecationInfo Information about the deprecation
|
||||
* @param non-empty-string $name The name of the filter in Twig
|
||||
* @param bool|null $needsCharset Whether the filter needs the charset passed as the first argument
|
||||
* @param bool|null $needsEnvironment Whether the filter needs the environment passed as the first argument, or after the charset
|
||||
* @param bool|null $needsContext Whether the filter needs the context array passed as the first argument, or after the charset and the environment
|
||||
* @param string[]|null $isSafe List of formats in which you want the raw output to be printed unescaped
|
||||
* @param string|array|null $isSafeCallback Function called at compilation time to determine if the filter is safe
|
||||
* @param string|null $preEscape Some filters may need to work on input that is already escaped or safe
|
||||
* @param string[]|null $preservesSafety Preserves the safety of the value that the filter is applied to
|
||||
* @param DeprecatedCallableInfo|null $deprecationInfo Information about the deprecation
|
||||
*/
|
||||
public function __construct(
|
||||
public string $name,
|
||||
|
||||
@@ -31,13 +31,13 @@ use Twig\TwigFunction;
|
||||
final class AsTwigFunction
|
||||
{
|
||||
/**
|
||||
* @param non-empty-string $name The name of the function in Twig.
|
||||
* @param bool|null $needsCharset Whether the function needs the charset passed as the first argument.
|
||||
* @param bool|null $needsEnvironment Whether the function needs the environment passed as the first argument, or after the charset.
|
||||
* @param bool|null $needsContext Whether the function needs the context array passed as the first argument, or after the charset and the environment.
|
||||
* @param string[]|null $isSafe List of formats in which you want the raw output to be printed unescaped.
|
||||
* @param string|array|null $isSafeCallback Function called at compilation time to determine if the function is safe.
|
||||
* @param DeprecatedCallableInfo|null $deprecationInfo Information about the deprecation
|
||||
* @param non-empty-string $name The name of the function in Twig
|
||||
* @param bool|null $needsCharset Whether the function needs the charset passed as the first argument
|
||||
* @param bool|null $needsEnvironment Whether the function needs the environment passed as the first argument, or after the charset
|
||||
* @param bool|null $needsContext Whether the function needs the context array passed as the first argument, or after the charset and the environment
|
||||
* @param string[]|null $isSafe List of formats in which you want the raw output to be printed unescaped
|
||||
* @param string|array|null $isSafeCallback Function called at compilation time to determine if the function is safe
|
||||
* @param DeprecatedCallableInfo|null $deprecationInfo Information about the deprecation
|
||||
*/
|
||||
public function __construct(
|
||||
public string $name,
|
||||
|
||||
@@ -31,11 +31,11 @@ use Twig\TwigTest;
|
||||
final class AsTwigTest
|
||||
{
|
||||
/**
|
||||
* @param non-empty-string $name The name of the test in Twig.
|
||||
* @param bool|null $needsCharset Whether the test needs the charset passed as the first argument.
|
||||
* @param bool|null $needsEnvironment Whether the test needs the environment passed as the first argument, or after the charset.
|
||||
* @param bool|null $needsContext Whether the test needs the context array passed as the first argument, or after the charset and the environment.
|
||||
* @param DeprecatedCallableInfo|null $deprecationInfo Information about the deprecation
|
||||
* @param non-empty-string $name The name of the test in Twig
|
||||
* @param bool|null $needsCharset Whether the test needs the charset passed as the first argument
|
||||
* @param bool|null $needsEnvironment Whether the test needs the environment passed as the first argument, or after the charset
|
||||
* @param bool|null $needsContext Whether the test needs the context array passed as the first argument, or after the charset and the environment
|
||||
* @param DeprecatedCallableInfo|null $deprecationInfo Information about the deprecation
|
||||
*/
|
||||
public function __construct(
|
||||
public string $name,
|
||||
|
||||
@@ -43,11 +43,11 @@ use Twig\TokenParser\TokenParserInterface;
|
||||
*/
|
||||
class Environment
|
||||
{
|
||||
public const VERSION = '3.21.1';
|
||||
public const VERSION_ID = 32101;
|
||||
public const VERSION = '3.23.0';
|
||||
public const VERSION_ID = 32300;
|
||||
public const MAJOR_VERSION = 3;
|
||||
public const MINOR_VERSION = 21;
|
||||
public const RELEASE_VERSION = 1;
|
||||
public const MINOR_VERSION = 23;
|
||||
public const RELEASE_VERSION = 0;
|
||||
public const EXTRA_VERSION = '';
|
||||
|
||||
private $charset;
|
||||
@@ -827,6 +827,14 @@ class Environment
|
||||
return $this->extensionSet->getTest($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callable(string): (TwigTest|false) $callable
|
||||
*/
|
||||
public function registerUndefinedTestCallback(callable $callable): void
|
||||
{
|
||||
$this->extensionSet->registerUndefinedTestCallback($callable);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
|
||||
@@ -148,6 +148,10 @@ class Error extends \Exception
|
||||
}
|
||||
}
|
||||
|
||||
if (null === $template) {
|
||||
return; // Impossible to guess the info as the template was not found in the backtrace
|
||||
}
|
||||
|
||||
$r = new \ReflectionObject($template);
|
||||
$file = $r->getFileName();
|
||||
|
||||
@@ -158,7 +162,7 @@ class Error extends \Exception
|
||||
|
||||
while ($e = array_pop($exceptions)) {
|
||||
$traces = $e->getTrace();
|
||||
array_unshift($traces, ['file' => $e instanceof Error ? $e->phpFile : $e->getFile(), 'line' => $e instanceof Error ? $e->phpLine : $e->getLine()]);
|
||||
array_unshift($traces, ['file' => $e instanceof self ? $e->phpFile : $e->getFile(), 'line' => $e instanceof self ? $e->phpLine : $e->getLine()]);
|
||||
while ($trace = array_shift($traces)) {
|
||||
if (!isset($trace['file']) || !isset($trace['line']) || $file != $trace['file']) {
|
||||
continue;
|
||||
|
||||
@@ -214,7 +214,7 @@ class ExpressionParser
|
||||
{
|
||||
trigger_deprecation('twig/twig', '3.19', \sprintf('The "%s()" method is deprecated, use "Twig\ExpressionParser\Infix\ArgumentsTrait::parseNamedArguments()" instead.', __METHOD__));
|
||||
|
||||
$parsePrimary = new \ReflectionMethod($this->parser, 'parsePrimary');
|
||||
$parsePrimaryExpression = new \ReflectionMethod($this->parser, 'parsePrimaryExpression');
|
||||
|
||||
$namedArguments = false;
|
||||
$definition = false;
|
||||
@@ -263,7 +263,7 @@ class ExpressionParser
|
||||
$name = $value->getAttribute('name');
|
||||
|
||||
if ($definition) {
|
||||
$value = $parsePrimary->invoke($this->parser);
|
||||
$value = $parsePrimaryExpression->invoke($this->parser);
|
||||
|
||||
if (!$this->checkConstantExpression($value)) {
|
||||
throw new SyntaxError('A default value for an argument must be a constant (a boolean, a string, a number, a sequence, or a mapping).', $token->getLine(), $stream->getSourceContext());
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace Twig\ExpressionParser\Infix;
|
||||
|
||||
use Twig\Error\SyntaxError;
|
||||
use Twig\Node\Expression\ArrayExpression;
|
||||
use Twig\Node\Expression\Binary\SetBinary;
|
||||
use Twig\Node\Expression\Unary\SpreadUnary;
|
||||
use Twig\Node\Expression\Variable\ContextVariable;
|
||||
use Twig\Node\Expression\Variable\LocalVariable;
|
||||
@@ -58,7 +59,10 @@ trait ArgumentsTrait
|
||||
}
|
||||
|
||||
$name = null;
|
||||
if (($token = $stream->nextIf(Token::OPERATOR_TYPE, '=')) || ($token = $stream->nextIf(Token::PUNCTUATION_TYPE, ':'))) {
|
||||
if ($value instanceof SetBinary) {
|
||||
$name = $value->getNode('left')->getAttribute('name');
|
||||
$value = $value->getNode('right');
|
||||
} elseif (($token = $stream->nextIf(Token::OPERATOR_TYPE, '=')) || ($token = $stream->nextIf(Token::PUNCTUATION_TYPE, ':'))) {
|
||||
if (!$value instanceof ContextVariable) {
|
||||
throw new SyntaxError(\sprintf('A parameter name must be a string, "%s" given.', $value::class), $token->getLine(), $stream->getSourceContext());
|
||||
}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\ExpressionParser\Infix;
|
||||
|
||||
use Twig\Error\SyntaxError;
|
||||
use Twig\ExpressionParser\InfixAssociativity;
|
||||
use Twig\Node\Expression\AbstractExpression;
|
||||
use Twig\Node\Expression\ArrayExpression;
|
||||
use Twig\Node\Expression\Binary\AbstractBinary;
|
||||
use Twig\Node\Expression\Binary\ObjectDestructuringSetBinary;
|
||||
use Twig\Node\Expression\Binary\SequenceDestructuringSetBinary;
|
||||
use Twig\Node\Expression\Binary\SetBinary;
|
||||
use Twig\Node\Expression\Variable\ContextVariable;
|
||||
use Twig\Parser;
|
||||
use Twig\Token;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class AssignmentExpressionParser extends BinaryOperatorExpressionParser
|
||||
{
|
||||
public function __construct(
|
||||
string $name,
|
||||
) {
|
||||
parent::__construct(SetBinary::class, $name, 0, InfixAssociativity::Right);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return AbstractBinary
|
||||
*/
|
||||
public function parse(Parser $parser, AbstractExpression $left, Token $token): AbstractExpression
|
||||
{
|
||||
if (!$left instanceof ContextVariable && !$left instanceof ArrayExpression) {
|
||||
throw new SyntaxError(\sprintf('Cannot assign to "%s", only variables can be assigned.', $left::class), $token->getLine(), $parser->getStream()->getSourceContext());
|
||||
}
|
||||
$right = $parser->parseExpression(InfixAssociativity::Left === $this->getAssociativity() ? $this->getPrecedence() + 1 : $this->getPrecedence());
|
||||
$right = match ($this->getName()) {
|
||||
'=' => $right,
|
||||
default => throw new \LogicException(\sprintf('Unknown operator: %s.', $this->getName())),
|
||||
};
|
||||
|
||||
if ($left instanceof ArrayExpression) {
|
||||
if ($left->isSequence()) {
|
||||
return new SequenceDestructuringSetBinary($left, $right, $token->getLine());
|
||||
} else {
|
||||
return new ObjectDestructuringSetBinary($left, $right, $token->getLine());
|
||||
}
|
||||
} else {
|
||||
return new SetBinary($left, $right, $token->getLine());
|
||||
}
|
||||
}
|
||||
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Assignment operator';
|
||||
}
|
||||
}
|
||||
@@ -37,6 +37,7 @@ final class DotExpressionParser extends AbstractExpressionParser implements Infi
|
||||
|
||||
public function parse(Parser $parser, AbstractExpression $expr, Token $token): AbstractExpression
|
||||
{
|
||||
$nullSafe = '?.' === $token->getValue();
|
||||
$stream = $parser->getStream();
|
||||
$token = $stream->getCurrent();
|
||||
$lineno = $token->getLine();
|
||||
@@ -55,7 +56,7 @@ final class DotExpressionParser extends AbstractExpressionParser implements Infi
|
||||
) {
|
||||
$attribute = new ConstantExpression($token->getValue(), $token->getLine());
|
||||
} else {
|
||||
throw new SyntaxError(\sprintf('Expected name or number, got value "%s" of type %s.', $token->getValue(), $token->toEnglish()), $token->getLine(), $stream->getSourceContext());
|
||||
throw new SyntaxError(\sprintf('Expected name or number, got value "%s" of type "%s".', $token->getValue(), $token->toEnglish()), $token->getLine(), $stream->getSourceContext());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,7 +75,7 @@ final class DotExpressionParser extends AbstractExpressionParser implements Infi
|
||||
return new MacroReferenceExpression(new TemplateVariable($expr->getAttribute('name'), $expr->getTemplateLine()), 'macro_'.$attribute->getAttribute('value'), $arguments, $expr->getTemplateLine());
|
||||
}
|
||||
|
||||
return new GetAttrExpression($expr, $attribute, $arguments, $type, $lineno);
|
||||
return new GetAttrExpression($expr, $attribute, $arguments, $type, $lineno, $nullSafe);
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
@@ -82,6 +83,11 @@ final class DotExpressionParser extends AbstractExpressionParser implements Infi
|
||||
return '.';
|
||||
}
|
||||
|
||||
public function getAliases(): array
|
||||
{
|
||||
return ['?.'];
|
||||
}
|
||||
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Get an attribute on a variable';
|
||||
|
||||
@@ -11,12 +11,16 @@
|
||||
|
||||
namespace Twig\ExpressionParser;
|
||||
|
||||
use Twig\Error\SyntaxError;
|
||||
use Twig\Node\Expression\AbstractExpression;
|
||||
use Twig\Parser;
|
||||
use Twig\Token;
|
||||
|
||||
interface InfixExpressionParserInterface extends ExpressionParserInterface
|
||||
{
|
||||
/**
|
||||
* @throws SyntaxError
|
||||
*/
|
||||
public function parse(Parser $parser, AbstractExpression $left, Token $token): AbstractExpression;
|
||||
|
||||
public function getAssociativity(): InfixAssociativity;
|
||||
|
||||
@@ -20,6 +20,7 @@ use Twig\Node\Expression\AbstractExpression;
|
||||
use Twig\Node\Expression\ArrayExpression;
|
||||
use Twig\Node\Expression\Binary\ConcatBinary;
|
||||
use Twig\Node\Expression\ConstantExpression;
|
||||
use Twig\Node\Expression\EmptyExpression;
|
||||
use Twig\Node\Expression\Variable\ContextVariable;
|
||||
use Twig\Parser;
|
||||
use Twig\Token;
|
||||
@@ -100,10 +101,6 @@ final class LiteralExpressionParser extends AbstractExpressionParser implements
|
||||
return new ContextVariable($token->getValue(), $token->getLine());
|
||||
}
|
||||
|
||||
if ('=' === $token->getValue() && ('==' === $stream->look(-1)->getValue() || '!=' === $stream->look(-1)->getValue())) {
|
||||
throw new SyntaxError(\sprintf('Unexpected operator of value "%s". Did you try to use "===" or "!==" for strict comparison? Use "is same as(value)" instead.', $token->getValue()), $token->getLine(), $stream->getSourceContext());
|
||||
}
|
||||
|
||||
// no break
|
||||
default:
|
||||
throw new SyntaxError(\sprintf('Unexpected token "%s" of value "%s".', $token->toEnglish(), $token->getValue()), $token->getLine(), $stream->getSourceContext());
|
||||
@@ -174,7 +171,12 @@ final class LiteralExpressionParser extends AbstractExpressionParser implements
|
||||
}
|
||||
$first = false;
|
||||
|
||||
$node->addElement($parser->parseExpression());
|
||||
// Check for empty slots (comma with no expression)
|
||||
if ($stream->test(Token::PUNCTUATION_TYPE, ',')) {
|
||||
$node->addElement(new EmptyExpression($stream->getCurrent()->getLine()));
|
||||
} else {
|
||||
$node->addElement($parser->parseExpression());
|
||||
}
|
||||
}
|
||||
$stream->expect(Token::PUNCTUATION_TYPE, ']', 'An opened sequence is not properly closed');
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ final class UnaryOperatorExpressionParser extends AbstractExpressionParser imple
|
||||
private ?PrecedenceChange $precedenceChange = null,
|
||||
private ?string $description = null,
|
||||
private array $aliases = [],
|
||||
private ?int $operandPrecedence = null,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -41,7 +42,7 @@ final class UnaryOperatorExpressionParser extends AbstractExpressionParser imple
|
||||
*/
|
||||
public function parse(Parser $parser, Token $token): AbstractExpression
|
||||
{
|
||||
return new ($this->nodeClass)($parser->parseExpression($this->precedence), $token->getLine());
|
||||
return new ($this->nodeClass)($parser->parseExpression($this->operandPrecedence ?? $this->precedence), $token->getLine());
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
|
||||
@@ -11,11 +11,15 @@
|
||||
|
||||
namespace Twig\ExpressionParser;
|
||||
|
||||
use Twig\Error\SyntaxError;
|
||||
use Twig\Node\Expression\AbstractExpression;
|
||||
use Twig\Parser;
|
||||
use Twig\Token;
|
||||
|
||||
interface PrefixExpressionParserInterface extends ExpressionParserInterface
|
||||
{
|
||||
/**
|
||||
* @throws SyntaxError
|
||||
*/
|
||||
public function parse(Parser $parser, Token $token): AbstractExpression;
|
||||
}
|
||||
|
||||
@@ -104,7 +104,7 @@ final class AttributeExtension extends AbstractExtension
|
||||
]);
|
||||
|
||||
if ($callable->getMinimalNumberOfRequiredArguments() > $method->getNumberOfParameters()) {
|
||||
throw new \LogicException(sprintf('"%s::%s()" needs at least %d arguments to be used AsTwigFilter, but only %d defined.', $reflectionClass->getName(), $method->getName(), $callable->getMinimalNumberOfRequiredArguments(), $method->getNumberOfParameters()));
|
||||
throw new \LogicException(\sprintf('"%s::%s()" needs at least %d arguments to be used AsTwigFilter, but only %d defined.', $reflectionClass->getName(), $method->getName(), $callable->getMinimalNumberOfRequiredArguments(), $method->getNumberOfParameters()));
|
||||
}
|
||||
|
||||
$filters[$attribute->name] = $callable;
|
||||
@@ -125,14 +125,13 @@ final class AttributeExtension extends AbstractExtension
|
||||
]);
|
||||
|
||||
if ($callable->getMinimalNumberOfRequiredArguments() > $method->getNumberOfParameters()) {
|
||||
throw new \LogicException(sprintf('"%s::%s()" needs at least %d arguments to be used AsTwigFunction, but only %d defined.', $reflectionClass->getName(), $method->getName(), $callable->getMinimalNumberOfRequiredArguments(), $method->getNumberOfParameters()));
|
||||
throw new \LogicException(\sprintf('"%s::%s()" needs at least %d arguments to be used AsTwigFunction, but only %d defined.', $reflectionClass->getName(), $method->getName(), $callable->getMinimalNumberOfRequiredArguments(), $method->getNumberOfParameters()));
|
||||
}
|
||||
|
||||
$functions[$attribute->name] = $callable;
|
||||
}
|
||||
|
||||
foreach ($method->getAttributes(AsTwigTest::class) as $reflectionAttribute) {
|
||||
|
||||
/** @var AsTwigTest $attribute */
|
||||
$attribute = $reflectionAttribute->newInstance();
|
||||
|
||||
@@ -145,7 +144,7 @@ final class AttributeExtension extends AbstractExtension
|
||||
]);
|
||||
|
||||
if ($callable->getMinimalNumberOfRequiredArguments() > $method->getNumberOfParameters()) {
|
||||
throw new \LogicException(sprintf('"%s::%s()" needs at least %d arguments to be used AsTwigTest, but only %d defined.', $reflectionClass->getName(), $method->getName(), $callable->getMinimalNumberOfRequiredArguments(), $method->getNumberOfParameters()));
|
||||
throw new \LogicException(\sprintf('"%s::%s()" needs at least %d arguments to be used AsTwigTest, but only %d defined.', $reflectionClass->getName(), $method->getName(), $callable->getMinimalNumberOfRequiredArguments(), $method->getNumberOfParameters()));
|
||||
}
|
||||
|
||||
$tests[$attribute->name] = $callable;
|
||||
|
||||
@@ -17,6 +17,7 @@ use Twig\Error\LoaderError;
|
||||
use Twig\Error\RuntimeError;
|
||||
use Twig\Error\SyntaxError;
|
||||
use Twig\ExpressionParser\Infix\ArrowExpressionParser;
|
||||
use Twig\ExpressionParser\Infix\AssignmentExpressionParser;
|
||||
use Twig\ExpressionParser\Infix\BinaryOperatorExpressionParser;
|
||||
use Twig\ExpressionParser\Infix\ConditionalTernaryExpressionParser;
|
||||
use Twig\ExpressionParser\Infix\DotExpressionParser;
|
||||
@@ -55,10 +56,12 @@ use Twig\Node\Expression\Binary\ModBinary;
|
||||
use Twig\Node\Expression\Binary\MulBinary;
|
||||
use Twig\Node\Expression\Binary\NotEqualBinary;
|
||||
use Twig\Node\Expression\Binary\NotInBinary;
|
||||
use Twig\Node\Expression\Binary\NotSameAsBinary;
|
||||
use Twig\Node\Expression\Binary\NullCoalesceBinary;
|
||||
use Twig\Node\Expression\Binary\OrBinary;
|
||||
use Twig\Node\Expression\Binary\PowerBinary;
|
||||
use Twig\Node\Expression\Binary\RangeBinary;
|
||||
use Twig\Node\Expression\Binary\SameAsBinary;
|
||||
use Twig\Node\Expression\Binary\SpaceshipBinary;
|
||||
use Twig\Node\Expression\Binary\StartsWithBinary;
|
||||
use Twig\Node\Expression\Binary\SubBinary;
|
||||
@@ -333,7 +336,7 @@ final class CoreExtension extends AbstractExtension
|
||||
return [
|
||||
// unary operators
|
||||
new UnaryOperatorExpressionParser(NotUnary::class, 'not', 50, new PrecedenceChange('twig/twig', '3.15', 70)),
|
||||
new UnaryOperatorExpressionParser(SpreadUnary::class, '...', 512, description: 'Spread operator'),
|
||||
new UnaryOperatorExpressionParser(SpreadUnary::class, '...', 512, description: 'Spread operator', operandPrecedence: 0),
|
||||
new UnaryOperatorExpressionParser(NegUnary::class, '-', 500),
|
||||
new UnaryOperatorExpressionParser(PosUnary::class, '+', 500),
|
||||
|
||||
@@ -360,6 +363,8 @@ final class CoreExtension extends AbstractExtension
|
||||
new BinaryOperatorExpressionParser(EndsWithBinary::class, 'ends with', 20),
|
||||
new BinaryOperatorExpressionParser(HasSomeBinary::class, 'has some', 20),
|
||||
new BinaryOperatorExpressionParser(HasEveryBinary::class, 'has every', 20),
|
||||
new BinaryOperatorExpressionParser(SameAsBinary::class, '===', 20),
|
||||
new BinaryOperatorExpressionParser(NotSameAsBinary::class, '!==', 20),
|
||||
new BinaryOperatorExpressionParser(RangeBinary::class, '..', 25),
|
||||
new BinaryOperatorExpressionParser(AddBinary::class, '+', 30),
|
||||
new BinaryOperatorExpressionParser(SubBinary::class, '-', 30),
|
||||
@@ -373,6 +378,9 @@ final class CoreExtension extends AbstractExtension
|
||||
// ternary operator
|
||||
new ConditionalTernaryExpressionParser(),
|
||||
|
||||
// assignment operator
|
||||
new AssignmentExpressionParser('='),
|
||||
|
||||
// Twig callables
|
||||
new IsExpressionParser(),
|
||||
new IsNotExpressionParser(),
|
||||
@@ -417,10 +425,8 @@ final class CoreExtension extends AbstractExtension
|
||||
|
||||
trigger_deprecation('twig/twig', '3.12', 'Passing a non-countable sequence of values to "%s()" is deprecated.', __METHOD__);
|
||||
|
||||
return $values;
|
||||
$values = self::toArray($values, false);
|
||||
}
|
||||
|
||||
$values = self::toArray($values, false);
|
||||
}
|
||||
|
||||
if (!$count = \count($values)) {
|
||||
@@ -1694,7 +1700,7 @@ final class CoreExtension extends AbstractExtension
|
||||
}
|
||||
|
||||
if (match (true) {
|
||||
\is_array($object) => \array_key_exists($arrayItem, $object),
|
||||
\is_array($object) => \array_key_exists($arrayItem = (string) $arrayItem, $object),
|
||||
$object instanceof \ArrayAccess => $object->offsetExists($arrayItem),
|
||||
default => false,
|
||||
}) {
|
||||
@@ -1715,9 +1721,13 @@ final class CoreExtension extends AbstractExtension
|
||||
}
|
||||
|
||||
if ($object instanceof \ArrayAccess) {
|
||||
$message = \sprintf('Key "%s" in object with ArrayAccess of class "%s" does not exist.', $arrayItem, $object::class);
|
||||
if (\is_object($arrayItem) || \is_array($arrayItem)) {
|
||||
$message = \sprintf('Key of type "%s" does not exist in ArrayAccess-able object of class "%s".', get_debug_type($arrayItem), get_debug_type($object));
|
||||
} else {
|
||||
$message = \sprintf('Key "%s" does not exist in ArrayAccess-able object of class "%s".', $arrayItem, get_debug_type($object));
|
||||
}
|
||||
} elseif (\is_object($object)) {
|
||||
$message = \sprintf('Impossible to access a key "%s" on an object of class "%s" that does not implement ArrayAccess interface.', $item, $object::class);
|
||||
$message = \sprintf('Impossible to access a key "%s" on an object of class "%s" that does not implement ArrayAccess interface.', $item, get_debug_type($object));
|
||||
} elseif (\is_array($object)) {
|
||||
if (!$object) {
|
||||
$message = \sprintf('Key "%s" does not exist as the sequence/mapping is empty.', $arrayItem);
|
||||
@@ -1880,7 +1890,7 @@ final class CoreExtension extends AbstractExtension
|
||||
return;
|
||||
}
|
||||
|
||||
throw new RuntimeError(\sprintf('Neither the property "%1$s" nor one of the methods "%1$s()", "get%1$s()"/"is%1$s()"/"has%1$s()" or "__call()" exist and have public access in class "%2$s".', $item, $class), $lineno, $source);
|
||||
throw new RuntimeError(\sprintf('Neither the property "%1$s" nor one of the methods "%1$s()", "get%1$s()", "is%1$s()", "has%1$s()" or "__call()" exist and have public access in class "%2$s".', $item, $class), $lineno, $source);
|
||||
}
|
||||
|
||||
if ($sandboxed) {
|
||||
|
||||
@@ -11,8 +11,11 @@
|
||||
|
||||
namespace Twig\Extension;
|
||||
|
||||
use Twig\ExpressionParser;
|
||||
use Twig\ExpressionParser\ExpressionParserInterface;
|
||||
use Twig\ExpressionParser\PrecedenceChange;
|
||||
use Twig\Node\Expression\Binary\AbstractBinary;
|
||||
use Twig\Node\Expression\Unary\AbstractUnary;
|
||||
use Twig\NodeVisitor\NodeVisitorInterface;
|
||||
use Twig\TokenParser\TokenParserInterface;
|
||||
use Twig\TwigFilter;
|
||||
|
||||
@@ -27,6 +27,10 @@ use Twig\Node\Expression\AbstractExpression;
|
||||
use Twig\NodeVisitor\NodeVisitorInterface;
|
||||
use Twig\TokenParser\TokenParserInterface;
|
||||
|
||||
// Help opcache.preload discover always-needed symbols
|
||||
// @see https://github.com/php/php-src/issues/10131
|
||||
class_exists(BinaryOperatorExpressionParser::class);
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
@@ -59,6 +63,8 @@ final class ExtensionSet
|
||||
private $functionCallbacks = [];
|
||||
/** @var array<callable(string): (TwigFilter|false)> */
|
||||
private $filterCallbacks = [];
|
||||
/** @var array<callable(string): (TwigTest|false)> */
|
||||
private $testCallbacks = [];
|
||||
/** @var array<callable(string): (TokenParserInterface|false)> */
|
||||
private $parserCallbacks = [];
|
||||
private $lastModified = 0;
|
||||
@@ -410,9 +416,23 @@ final class ExtensionSet
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->testCallbacks as $callback) {
|
||||
if (false !== $test = $callback($name)) {
|
||||
return $test;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callable(string): (TwigTest|false) $callable
|
||||
*/
|
||||
public function registerUndefinedTestCallback(callable $callable): void
|
||||
{
|
||||
$this->testCallbacks[] = $callable;
|
||||
}
|
||||
|
||||
public function getExpressionParsers(): ExpressionParsers
|
||||
{
|
||||
if (!$this->initialized) {
|
||||
|
||||
@@ -525,7 +525,7 @@ class Lexer
|
||||
|
||||
private function getOperatorRegex(): string
|
||||
{
|
||||
$expressionParsers = ['='];
|
||||
$expressionParsers = [];
|
||||
foreach ($this->env->getExpressionParsers() as $expressionParser) {
|
||||
$expressionParsers = array_merge($expressionParsers, [$expressionParser->getName()], $expressionParser->getAliases());
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ class EmbedNode extends IncludeNode
|
||||
->raw(', ')
|
||||
->repr($this->getTemplateLine())
|
||||
->raw(', ')
|
||||
->string($this->getAttribute('index'))
|
||||
->repr($this->getAttribute('index'))
|
||||
->raw(')')
|
||||
;
|
||||
if ($this->getAttribute('ignore_missing')) {
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
namespace Twig\Node\Expression;
|
||||
|
||||
use Twig\Compiler;
|
||||
use Twig\Error\SyntaxError;
|
||||
use Twig\Node\Expression\Unary\SpreadUnary;
|
||||
use Twig\Node\Expression\Unary\StringCastUnary;
|
||||
use Twig\Node\Expression\Variable\ContextVariable;
|
||||
@@ -60,6 +61,31 @@ class ArrayExpression extends AbstractExpression implements SupportDefinedTestIn
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the array is a sequence (keys are sequential integers starting from 0).
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function isSequence(): bool
|
||||
{
|
||||
foreach ($this->getKeyValuePairs() as $i => $pair) {
|
||||
$key = $pair['key'];
|
||||
if ($key instanceof TempNameExpression) {
|
||||
$keyValue = $key->getAttribute('name');
|
||||
} elseif ($key instanceof ConstantExpression) {
|
||||
$keyValue = $key->getAttribute('value');
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($keyValue !== $i) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function addElement(AbstractExpression $value, ?AbstractExpression $key = null): void
|
||||
{
|
||||
if (null === $key) {
|
||||
@@ -77,6 +103,13 @@ class ArrayExpression extends AbstractExpression implements SupportDefinedTestIn
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for empty expressions which are only allowed in destructuring
|
||||
foreach ($this->getKeyValuePairs() as $pair) {
|
||||
if ($pair['value'] instanceof EmptyExpression) {
|
||||
throw new SyntaxError('Empty array elements are only allowed in destructuring assignments.', $pair['value']->getTemplateLine(), $this->getSourceContext());
|
||||
}
|
||||
}
|
||||
|
||||
$compiler->raw('[');
|
||||
$isSequence = true;
|
||||
foreach ($this->getKeyValuePairs() as $i => $pair) {
|
||||
|
||||
@@ -13,8 +13,8 @@ namespace Twig\Node\Expression\Binary;
|
||||
|
||||
use Twig\Compiler;
|
||||
use Twig\Error\SyntaxError;
|
||||
use Twig\Node\Expression\ReturnBoolInterface;
|
||||
use Twig\Node\Expression\ConstantExpression;
|
||||
use Twig\Node\Expression\ReturnBoolInterface;
|
||||
use Twig\Node\Node;
|
||||
|
||||
class MatchesBinary extends AbstractBinary implements ReturnBoolInterface
|
||||
|
||||
23
lib/twig/twig/src/Node/Expression/Binary/NotSameAsBinary.php
Normal file
23
lib/twig/twig/src/Node/Expression/Binary/NotSameAsBinary.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Node\Expression\Binary;
|
||||
|
||||
use Twig\Compiler;
|
||||
use Twig\Node\Expression\ReturnBoolInterface;
|
||||
|
||||
class NotSameAsBinary extends AbstractBinary implements ReturnBoolInterface
|
||||
{
|
||||
public function operator(Compiler $compiler): Compiler
|
||||
{
|
||||
return $compiler->raw('!==');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Node\Expression\Binary;
|
||||
|
||||
use Twig\Compiler;
|
||||
use Twig\Error\SyntaxError;
|
||||
use Twig\Node\Expression\AbstractExpression;
|
||||
use Twig\Node\Expression\ArrayExpression;
|
||||
use Twig\Node\Expression\Variable\ContextVariable;
|
||||
use Twig\Node\Node;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class ObjectDestructuringSetBinary extends AbstractBinary
|
||||
{
|
||||
private array $properties = [];
|
||||
|
||||
/**
|
||||
* @param ArrayExpression $left The array expression containing object/mapping destructuring properties
|
||||
* @param AbstractExpression $right The expression providing values for assignment
|
||||
*/
|
||||
public function __construct(Node $left, Node $right, int $lineno)
|
||||
{
|
||||
if (!$left instanceof ArrayExpression) {
|
||||
throw new \LogicException('Left side must be ArrayExpression for object/mapping destructuring.');
|
||||
}
|
||||
foreach ($left->getKeyValuePairs() as $pair) {
|
||||
if (!$pair['value'] instanceof ContextVariable) {
|
||||
throw new SyntaxError(\sprintf('Cannot assign to "%s", only variables can be assigned in object/mapping destructuring.', $pair['value']::class), $lineno);
|
||||
}
|
||||
$this->properties[] = $pair['value']->getAttribute('name');
|
||||
}
|
||||
|
||||
parent::__construct($left, $right, $lineno);
|
||||
}
|
||||
|
||||
public function compile(Compiler $compiler): void
|
||||
{
|
||||
$compiler->addDebugInfo($this);
|
||||
$compiler->raw('[');
|
||||
foreach ($this->properties as $i => $property) {
|
||||
if ($i) {
|
||||
$compiler->raw(', ');
|
||||
}
|
||||
$compiler->raw('$context[')->repr($property)->raw(']');
|
||||
}
|
||||
$compiler->raw('] = [');
|
||||
foreach ($this->properties as $i => $property) {
|
||||
if ($i) {
|
||||
$compiler->raw(', ');
|
||||
}
|
||||
$compiler->raw('CoreExtension::getAttribute($this->env, $this->source, ')->subcompile($this->getNode('right'))->raw(', ')->repr($property)->raw(', [], \\Twig\\Template::ANY_CALL, false, false, false, ')->repr($this->getNode('right')->getTemplateLine())->raw(')');
|
||||
}
|
||||
$compiler->raw(']');
|
||||
}
|
||||
|
||||
public function operator(Compiler $compiler): Compiler
|
||||
{
|
||||
return $compiler->raw('=');
|
||||
}
|
||||
}
|
||||
23
lib/twig/twig/src/Node/Expression/Binary/SameAsBinary.php
Normal file
23
lib/twig/twig/src/Node/Expression/Binary/SameAsBinary.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Node\Expression\Binary;
|
||||
|
||||
use Twig\Compiler;
|
||||
use Twig\Node\Expression\ReturnBoolInterface;
|
||||
|
||||
class SameAsBinary extends AbstractBinary implements ReturnBoolInterface
|
||||
{
|
||||
public function operator(Compiler $compiler): Compiler
|
||||
{
|
||||
return $compiler->raw('===');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Node\Expression\Binary;
|
||||
|
||||
use Twig\Compiler;
|
||||
use Twig\Error\SyntaxError;
|
||||
use Twig\Node\Expression\AbstractExpression;
|
||||
use Twig\Node\Expression\ArrayExpression;
|
||||
use Twig\Node\Expression\EmptyExpression;
|
||||
use Twig\Node\Expression\Variable\ContextVariable;
|
||||
use Twig\Node\Node;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class SequenceDestructuringSetBinary extends AbstractBinary
|
||||
{
|
||||
private array $variables = [];
|
||||
|
||||
/**
|
||||
* @param ArrayExpression $left The array expression containing variables to assign to
|
||||
* @param AbstractExpression $right The expression providing values for assignment
|
||||
*/
|
||||
public function __construct(Node $left, Node $right, int $lineno)
|
||||
{
|
||||
foreach ($left->getKeyValuePairs() as $pair) {
|
||||
if ($pair['value'] instanceof EmptyExpression) {
|
||||
$this->variables[] = null;
|
||||
} elseif ($pair['value'] instanceof ContextVariable) {
|
||||
$this->variables[] = $pair['value']->getAttribute('name');
|
||||
} else {
|
||||
throw new SyntaxError(\sprintf('Cannot assign to "%s", only variables can be assigned in sequence destructuring.', $pair['value']::class), $lineno);
|
||||
}
|
||||
}
|
||||
|
||||
parent::__construct($left, $right, $lineno);
|
||||
}
|
||||
|
||||
public function compile(Compiler $compiler): void
|
||||
{
|
||||
$compiler->addDebugInfo($this);
|
||||
$compiler->raw('[');
|
||||
foreach ($this->variables as $i => $name) {
|
||||
if ($i) {
|
||||
$compiler->raw(', ');
|
||||
}
|
||||
if (null !== $name) {
|
||||
$compiler->raw('$context[')->repr($name)->raw(']');
|
||||
}
|
||||
}
|
||||
$compiler->raw('] = array_pad(')->subcompile($this->getNode('right'))->raw(', ')->repr(\count($this->variables))->raw(', null)');
|
||||
}
|
||||
|
||||
public function operator(Compiler $compiler): Compiler
|
||||
{
|
||||
return $compiler->raw('=');
|
||||
}
|
||||
}
|
||||
44
lib/twig/twig/src/Node/Expression/Binary/SetBinary.php
Normal file
44
lib/twig/twig/src/Node/Expression/Binary/SetBinary.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Node\Expression\Binary;
|
||||
|
||||
use Twig\Compiler;
|
||||
use Twig\Node\Expression\AbstractExpression;
|
||||
use Twig\Node\Expression\Variable\AssignContextVariable;
|
||||
use Twig\Node\Expression\Variable\ContextVariable;
|
||||
use Twig\Node\Node;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class SetBinary extends AbstractBinary
|
||||
{
|
||||
/**
|
||||
* @param ContextVariable $left
|
||||
* @param AbstractExpression $right
|
||||
*/
|
||||
public function __construct(Node $left, Node $right, int $lineno)
|
||||
{
|
||||
$name = $left->getAttribute('name');
|
||||
if (!\is_string($name)) {
|
||||
throw new \LogicException('The "name" attribute must be a string.');
|
||||
}
|
||||
$left = new AssignContextVariable($name, $left->getTemplateLine());
|
||||
|
||||
parent::__construct($left, $right, $lineno);
|
||||
}
|
||||
|
||||
public function operator(Compiler $compiler): Compiler
|
||||
{
|
||||
return $compiler->raw('=');
|
||||
}
|
||||
}
|
||||
33
lib/twig/twig/src/Node/Expression/EmptyExpression.php
Normal file
33
lib/twig/twig/src/Node/Expression/EmptyExpression.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Node\Expression;
|
||||
|
||||
use Twig\Compiler;
|
||||
|
||||
/**
|
||||
* Represents an empty slot in an array.
|
||||
*
|
||||
* This is currently only used in destructuring contexts.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class EmptyExpression extends AbstractExpression
|
||||
{
|
||||
public function __construct(int $lineno)
|
||||
{
|
||||
parent::__construct([], [], $lineno);
|
||||
}
|
||||
|
||||
public function compile(Compiler $compiler): void
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,14 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Node\Expression\FunctionNode;
|
||||
|
||||
use Twig\Compiler;
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Node\Expression\FunctionNode;
|
||||
|
||||
use Twig\Compiler;
|
||||
|
||||
@@ -25,7 +25,7 @@ class GetAttrExpression extends AbstractExpression implements SupportDefinedTest
|
||||
/**
|
||||
* @param ArrayExpression|NameExpression|null $arguments
|
||||
*/
|
||||
public function __construct(AbstractExpression $node, AbstractExpression $attribute, ?AbstractExpression $arguments, string $type, int $lineno)
|
||||
public function __construct(AbstractExpression $node, AbstractExpression $attribute, ?AbstractExpression $arguments, string $type, int $lineno, bool $nullSafe = false)
|
||||
{
|
||||
$nodes = ['node' => $node, 'attribute' => $attribute];
|
||||
if (null !== $arguments) {
|
||||
@@ -36,7 +36,7 @@ class GetAttrExpression extends AbstractExpression implements SupportDefinedTest
|
||||
trigger_deprecation('twig/twig', '3.15', \sprintf('Not passing a "%s" instance as the "arguments" argument of the "%s" constructor is deprecated ("%s" given).', ArrayExpression::class, static::class, $arguments::class));
|
||||
}
|
||||
|
||||
parent::__construct($nodes, ['type' => $type, 'ignore_strict_check' => false, 'optimizable' => true], $lineno);
|
||||
parent::__construct($nodes, ['type' => $type, 'ignore_strict_check' => false, 'optimizable' => !$nullSafe, 'null_safe' => $nullSafe], $lineno);
|
||||
}
|
||||
|
||||
public function enableDefinedTest(): void
|
||||
@@ -49,6 +49,8 @@ class GetAttrExpression extends AbstractExpression implements SupportDefinedTest
|
||||
{
|
||||
$env = $compiler->getEnvironment();
|
||||
$arrayAccessSandbox = false;
|
||||
$nullSafe = $this->getAttribute('null_safe');
|
||||
$objectVar = null;
|
||||
|
||||
// optimize array calls
|
||||
if (
|
||||
@@ -93,14 +95,27 @@ class GetAttrExpression extends AbstractExpression implements SupportDefinedTest
|
||||
;
|
||||
}
|
||||
|
||||
$compiler->raw('CoreExtension::getAttribute($this->env, $this->source, ');
|
||||
|
||||
if ($this->getAttribute('ignore_strict_check')) {
|
||||
$this->getNode('node')->setAttribute('ignore_strict_check', true);
|
||||
}
|
||||
|
||||
if ($nullSafe) {
|
||||
$objectVar = '$'.$compiler->getVarName();
|
||||
$compiler
|
||||
->raw('((null === ('.$objectVar.' = ')
|
||||
->subcompile($this->getNode('node'))
|
||||
->raw(')) ? null : ');
|
||||
}
|
||||
|
||||
$compiler->raw('CoreExtension::getAttribute($this->env, $this->source, ');
|
||||
|
||||
if ($nullSafe) {
|
||||
$compiler->raw($objectVar);
|
||||
} else {
|
||||
$compiler->subcompile($this->getNode('node'));
|
||||
}
|
||||
|
||||
$compiler
|
||||
->subcompile($this->getNode('node'))
|
||||
->raw(', ')
|
||||
->subcompile($this->getNode('attribute'))
|
||||
;
|
||||
@@ -123,14 +138,18 @@ class GetAttrExpression extends AbstractExpression implements SupportDefinedTest
|
||||
if ($arrayAccessSandbox) {
|
||||
$compiler->raw(')');
|
||||
}
|
||||
|
||||
if ($nullSafe) {
|
||||
$compiler->raw(')');
|
||||
}
|
||||
}
|
||||
|
||||
private function changeIgnoreStrictCheck(GetAttrExpression $node): void
|
||||
private function changeIgnoreStrictCheck(self $node): void
|
||||
{
|
||||
$node->setAttribute('optimizable', false);
|
||||
$node->setAttribute('ignore_strict_check', true);
|
||||
|
||||
if ($node->getNode('node') instanceof GetAttrExpression) {
|
||||
if ($node->getNode('node') instanceof self) {
|
||||
$this->changeIgnoreStrictCheck($node->getNode('node'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,16 +15,8 @@ use Twig\Attribute\FirstClassTwigCallableReady;
|
||||
use Twig\Compiler;
|
||||
use Twig\Error\SyntaxError;
|
||||
use Twig\Node\Expression\AbstractExpression;
|
||||
use Twig\Node\Expression\ArrayExpression;
|
||||
use Twig\Node\Expression\BlockReferenceExpression;
|
||||
use Twig\Node\Expression\ConstantExpression;
|
||||
use Twig\Node\Expression\FunctionExpression;
|
||||
use Twig\Node\Expression\GetAttrExpression;
|
||||
use Twig\Node\Expression\MacroReferenceExpression;
|
||||
use Twig\Node\Expression\MethodCallExpression;
|
||||
use Twig\Node\Expression\SupportDefinedTestInterface;
|
||||
use Twig\Node\Expression\TestExpression;
|
||||
use Twig\Node\Expression\Variable\ContextVariable;
|
||||
use Twig\Node\Node;
|
||||
use Twig\TwigTest;
|
||||
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Twig\Node;
|
||||
|
||||
use Twig\Attribute\YieldReady;
|
||||
|
||||
@@ -76,6 +76,9 @@ class Parser
|
||||
return \sprintf('__internal_parse_%d', $this->varNameSalt++);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SyntaxError
|
||||
*/
|
||||
public function parse(TokenStream $stream, $test = null, bool $dropNeedle = false): ModuleNode
|
||||
{
|
||||
$vars = get_object_vars($this);
|
||||
@@ -158,6 +161,9 @@ class Parser
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SyntaxError
|
||||
*/
|
||||
public function subparse($test, bool $dropNeedle = false): Node
|
||||
{
|
||||
$lineno = $this->getCurrentToken()->getLine();
|
||||
@@ -494,11 +500,26 @@ class Parser
|
||||
// try 2-words tests
|
||||
$name = $name.' '.$this->getCurrentToken()->getValue();
|
||||
|
||||
if ($test = $this->env->getTest($name)) {
|
||||
$this->stream->next();
|
||||
try {
|
||||
$test = $this->env->getTest($name);
|
||||
} catch (SyntaxError $e) {
|
||||
if (!$this->shouldIgnoreUnknownTwigCallables()) {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$test = null;
|
||||
}
|
||||
$this->stream->next();
|
||||
} else {
|
||||
$test = $this->env->getTest($name);
|
||||
try {
|
||||
$test = $this->env->getTest($name);
|
||||
} catch (SyntaxError $e) {
|
||||
if (!$this->shouldIgnoreUnknownTwigCallables()) {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$test = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$test) {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
* (c) Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
* (c) Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
|
||||
@@ -17,7 +17,7 @@ use Twig\Markup;
|
||||
|
||||
final class EscaperRuntime implements RuntimeExtensionInterface
|
||||
{
|
||||
/** @var array<string, callable(string $string, string $charset): string> */
|
||||
/** @var array<string, callable(string, string): string> */
|
||||
private $escapers = [];
|
||||
|
||||
/** @internal */
|
||||
@@ -140,6 +140,10 @@ final class EscaperRuntime implements RuntimeExtensionInterface
|
||||
case 'html':
|
||||
// see https://www.php.net/htmlspecialchars
|
||||
|
||||
if ('UTF-8' === $charset) {
|
||||
return htmlspecialchars($string, \ENT_QUOTES | \ENT_SUBSTITUTE, 'UTF-8');
|
||||
}
|
||||
|
||||
// Using a static variable to avoid initializing the array
|
||||
// each time the function is called. Moving the declaration on the
|
||||
// top of the function slow downs other escaping strategies.
|
||||
@@ -195,7 +199,7 @@ final class EscaperRuntime implements RuntimeExtensionInterface
|
||||
* Escape sequences supported only by JavaScript, not JSON, are omitted.
|
||||
* \" is also supported but omitted, because the resulting string is not HTML safe.
|
||||
*/
|
||||
static $shortMap = [
|
||||
$short = match ($char) {
|
||||
'\\' => '\\\\',
|
||||
'/' => '\\/',
|
||||
"\x08" => '\b',
|
||||
@@ -203,10 +207,11 @@ final class EscaperRuntime implements RuntimeExtensionInterface
|
||||
"\x0A" => '\n',
|
||||
"\x0D" => '\r',
|
||||
"\x09" => '\t',
|
||||
];
|
||||
default => false,
|
||||
};
|
||||
|
||||
if (isset($shortMap[$char])) {
|
||||
return $shortMap[$char];
|
||||
if ($short) {
|
||||
return $short;
|
||||
}
|
||||
|
||||
$codepoint = mb_ord($char, 'UTF-8');
|
||||
@@ -267,7 +272,7 @@ final class EscaperRuntime implements RuntimeExtensionInterface
|
||||
* @license https://framework.zend.com/license/new-bsd New BSD License
|
||||
*/
|
||||
$chr = $matches[0];
|
||||
$ord = \ord($chr);
|
||||
$ord = \ord($chr[0]);
|
||||
|
||||
/*
|
||||
* The following replaces characters undefined in HTML with the
|
||||
@@ -288,18 +293,13 @@ final class EscaperRuntime implements RuntimeExtensionInterface
|
||||
* entities that XML supports. Using HTML entities would result in this error:
|
||||
* XML Parsing Error: undefined entity
|
||||
*/
|
||||
static $entityMap = [
|
||||
return match ($ord) {
|
||||
34 => '"', /* quotation mark */
|
||||
38 => '&', /* ampersand */
|
||||
60 => '<', /* less-than sign */
|
||||
62 => '>', /* greater-than sign */
|
||||
];
|
||||
|
||||
if (isset($entityMap[$ord])) {
|
||||
return $entityMap[$ord];
|
||||
}
|
||||
|
||||
return \sprintf('&#x%02X;', $ord);
|
||||
default => \sprintf('&#x%02X;', $ord),
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -270,7 +270,7 @@ abstract class Template
|
||||
/**
|
||||
* @param string|TemplateWrapper|array<string|TemplateWrapper> $template
|
||||
*/
|
||||
protected function load(string|TemplateWrapper|array $template, int $line, int|null $index = null): self
|
||||
protected function load(string|TemplateWrapper|array $template, int $line, ?int $index = null): self
|
||||
{
|
||||
try {
|
||||
if (\is_array($template)) {
|
||||
@@ -315,7 +315,7 @@ abstract class Template
|
||||
*
|
||||
* @deprecated since Twig 3.21 and will be removed in 4.0. Use Template::load() instead.
|
||||
*/
|
||||
protected function loadTemplate($template, $templateName = null, int|null $line = null, int|null $index = null): self|TemplateWrapper
|
||||
protected function loadTemplate($template, $templateName = null, ?int $line = null, ?int $index = null): self|TemplateWrapper
|
||||
{
|
||||
trigger_deprecation('twig/twig', '3.21', 'The "%s" method is deprecated.', __METHOD__);
|
||||
|
||||
|
||||
@@ -32,9 +32,15 @@ final class GuardTokenParser extends AbstractTokenParser
|
||||
$method = 'get'.$typeToken->getValue();
|
||||
|
||||
$nameToken = $stream->expect(Token::NAME_TYPE);
|
||||
$name = $nameToken->getValue();
|
||||
if ('test' === $typeToken->getValue() && $stream->test(Token::NAME_TYPE)) {
|
||||
// try 2-words tests
|
||||
$name .= ' '.$stream->getCurrent()->getValue();
|
||||
$stream->next();
|
||||
}
|
||||
|
||||
try {
|
||||
$exists = null !== $this->parser->getEnvironment()->$method($nameToken->getValue());
|
||||
$exists = null !== $this->parser->getEnvironment()->$method($name);
|
||||
} catch (SyntaxError) {
|
||||
$exists = false;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user