mirror of
https://github.com/Combodo/iTop.git
synced 2026-02-13 07:24:13 +01:00
N°5809 Update Symfony artifacts from 6.4.0 to 6.4.2
symfony/console symfony/dotenv symfony/framework-bundle symfony/http-foundation symfony/http-kernel symfony/var-dumper symfony/web-profiler-bundle
This commit is contained in:
@@ -1,18 +0,0 @@
|
||||
; top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
; Unix-style newlines
|
||||
[*]
|
||||
end_of_line = LF
|
||||
|
||||
[*.php]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
[*.test]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
[*.rst]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
4
lib/twig/twig/.gitattributes
vendored
4
lib/twig/twig/.gitattributes
vendored
@@ -1,4 +0,0 @@
|
||||
/doc/ export-ignore
|
||||
/extra/ export-ignore
|
||||
/tests/ export-ignore
|
||||
/phpunit.xml.dist export-ignore
|
||||
149
lib/twig/twig/.github/workflows/ci.yml
vendored
149
lib/twig/twig/.github/workflows/ci.yml
vendored
@@ -1,149 +0,0 @@
|
||||
name: "CI"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- '3.x'
|
||||
|
||||
env:
|
||||
SYMFONY_PHPUNIT_DISABLE_RESULT_CACHE: 1
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
tests:
|
||||
name: "PHP ${{ matrix.php-version }}"
|
||||
|
||||
runs-on: 'ubuntu-latest'
|
||||
|
||||
continue-on-error: ${{ matrix.experimental }}
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
php-version:
|
||||
- '7.2.5'
|
||||
- '7.3'
|
||||
- '7.4'
|
||||
- '8.0'
|
||||
- '8.1'
|
||||
experimental: [false]
|
||||
|
||||
steps:
|
||||
- name: "Checkout code"
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: "Install PHP with extensions"
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
coverage: "none"
|
||||
php-version: ${{ matrix.php-version }}
|
||||
ini-values: memory_limit=-1
|
||||
|
||||
- name: "Add PHPUnit matcher"
|
||||
run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"
|
||||
|
||||
- run: composer install
|
||||
|
||||
- name: "Install PHPUnit"
|
||||
run: vendor/bin/simple-phpunit install
|
||||
|
||||
- name: "PHPUnit version"
|
||||
run: vendor/bin/simple-phpunit --version
|
||||
|
||||
- name: "Run tests"
|
||||
run: vendor/bin/simple-phpunit
|
||||
|
||||
extension-tests:
|
||||
needs:
|
||||
- 'tests'
|
||||
|
||||
name: "${{ matrix.extension }} with PHP ${{ matrix.php-version }}"
|
||||
|
||||
runs-on: 'ubuntu-latest'
|
||||
|
||||
continue-on-error: true
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
php-version:
|
||||
- '7.2.5'
|
||||
- '7.3'
|
||||
- '7.4'
|
||||
- '8.0'
|
||||
- '8.1'
|
||||
extension:
|
||||
- 'extra/cache-extra'
|
||||
- 'extra/cssinliner-extra'
|
||||
- 'extra/html-extra'
|
||||
- 'extra/inky-extra'
|
||||
- 'extra/intl-extra'
|
||||
- 'extra/markdown-extra'
|
||||
- 'extra/string-extra'
|
||||
- 'extra/twig-extra-bundle'
|
||||
experimental: [false]
|
||||
|
||||
steps:
|
||||
- name: "Checkout code"
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: "Install PHP with extensions"
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
coverage: "none"
|
||||
php-version: ${{ matrix.php-version }}
|
||||
ini-values: memory_limit=-1
|
||||
|
||||
- name: "Add PHPUnit matcher"
|
||||
run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"
|
||||
|
||||
- run: composer install
|
||||
|
||||
- name: "Install PHPUnit"
|
||||
run: vendor/bin/simple-phpunit install
|
||||
|
||||
- name: "PHPUnit version"
|
||||
run: vendor/bin/simple-phpunit --version
|
||||
|
||||
- name: "Composer install"
|
||||
working-directory: ${{ matrix.extension}}
|
||||
run: composer install
|
||||
|
||||
- name: "Run tests"
|
||||
working-directory: ${{ matrix.extension}}
|
||||
run: ../../vendor/bin/simple-phpunit
|
||||
|
||||
#
|
||||
# Drupal does not support Twig 3 now!
|
||||
#
|
||||
# integration-tests:
|
||||
# needs:
|
||||
# - 'tests'
|
||||
#
|
||||
# name: "Integration tests with PHP ${{ matrix.php-version }}"
|
||||
#
|
||||
# runs-on: 'ubuntu-20.04'
|
||||
#
|
||||
# continue-on-error: true
|
||||
#
|
||||
# strategy:
|
||||
# matrix:
|
||||
# php-version:
|
||||
# - '7.3'
|
||||
#
|
||||
# steps:
|
||||
# - name: "Checkout code"
|
||||
# uses: actions/checkout@v2
|
||||
#
|
||||
# - name: "Install PHP with extensions"
|
||||
# uses: shivammathur/setup-php@2
|
||||
# with:
|
||||
# coverage: "none"
|
||||
# extensions: "gd, pdo_sqlite"
|
||||
# php-version: ${{ matrix.php-version }}
|
||||
# ini-values: memory_limit=-1
|
||||
# tools: composer:v2
|
||||
#
|
||||
# - run: bash ./tests/drupal_test.sh
|
||||
# shell: "bash"
|
||||
@@ -1,64 +0,0 @@
|
||||
name: "Documentation"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- '2.x'
|
||||
- '3.x'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: "Build"
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: "Checkout code"
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: "Set-up PHP"
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: 8.1
|
||||
coverage: none
|
||||
tools: "composer:v2"
|
||||
|
||||
- name: Get composer cache directory
|
||||
id: composercache
|
||||
working-directory: doc/_build
|
||||
run: echo "::set-output name=dir::$(composer config cache-files-dir)"
|
||||
|
||||
- name: Cache dependencies
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ${{ steps.composercache.outputs.dir }}
|
||||
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
|
||||
restore-keys: ${{ runner.os }}-composer-
|
||||
|
||||
- name: "Install dependencies"
|
||||
working-directory: doc/_build
|
||||
run: composer install --prefer-dist --no-progress
|
||||
|
||||
- name: "Build the docs"
|
||||
working-directory: doc/_build
|
||||
run: php build.php --disable-cache
|
||||
|
||||
doctor-rst:
|
||||
name: "DOCtor-RST"
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: "Checkout code"
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: "Run DOCtor-RST"
|
||||
uses: docker://oskarstark/doctor-rst
|
||||
with:
|
||||
args: --short
|
||||
env:
|
||||
DOCS_DIR: 'doc/'
|
||||
6
lib/twig/twig/.gitignore
vendored
6
lib/twig/twig/.gitignore
vendored
@@ -1,6 +0,0 @@
|
||||
/doc/_build/vendor
|
||||
/doc/_build/output
|
||||
/composer.lock
|
||||
/phpunit.xml
|
||||
/vendor
|
||||
.phpunit.result.cache
|
||||
@@ -1,20 +0,0 @@
|
||||
<?php
|
||||
|
||||
return (new PhpCsFixer\Config())
|
||||
->setRules([
|
||||
'@Symfony' => true,
|
||||
'@Symfony:risky' => true,
|
||||
'@PHPUnit75Migration:risky' => true,
|
||||
'php_unit_dedicate_assert' => ['target' => '5.6'],
|
||||
'array_syntax' => ['syntax' => 'short'],
|
||||
'php_unit_fqcn_annotation' => true,
|
||||
'no_unreachable_default_argument_value' => false,
|
||||
'braces' => ['allow_single_line_closure' => true],
|
||||
'heredoc_to_nowdoc' => false,
|
||||
'ordered_imports' => true,
|
||||
'phpdoc_types_order' => ['null_adjustment' => 'always_last', 'sort_algorithm' => 'none'],
|
||||
'native_function_invocation' => ['include' => ['@compiler_optimized'], 'scope' => 'all'],
|
||||
])
|
||||
->setRiskyAllowed(true)
|
||||
->setFinder((new PhpCsFixer\Finder())->in(__DIR__))
|
||||
;
|
||||
@@ -1,3 +1,48 @@
|
||||
# 3.8.0 (2023-11-21)
|
||||
|
||||
* Catch errors thrown during template rendering
|
||||
* Fix IntlExtension::formatDateTime use of date formatter prototype
|
||||
* Fix premature loop exit in Security Policy lookup of allowed methods/properties
|
||||
* Remove NumberFormatter::TYPE_CURRENCY (deprecated in PHP 8.3)
|
||||
* Restore return type annotations
|
||||
* Allow Symfony 7 packages to be installed
|
||||
* Deprecate `twig_test_iterable` function. Use the native `is_iterable` instead.
|
||||
|
||||
# 3.7.1 (2023-08-28)
|
||||
|
||||
* Fix some phpdocs
|
||||
|
||||
# 3.7.0 (2023-07-26)
|
||||
|
||||
* Add support for the ...spread operator on arrays and hashes
|
||||
|
||||
# 3.6.1 (2023-06-08)
|
||||
|
||||
* Suppress some native return type deprecation messages
|
||||
|
||||
# 3.6.0 (2023-05-03)
|
||||
|
||||
* Allow psr/container 2.0
|
||||
* Add the new PHP 8.0 IntlDateFormatter::RELATIVE_* constants for date formatting
|
||||
* Make the Lexer initialize itself lazily
|
||||
|
||||
# 3.5.1 (2023-02-08)
|
||||
|
||||
* Arrow functions passed to the "reduce" filter now accept the current key as a third argument
|
||||
* Restores the leniency of the matches twig comparison
|
||||
* Fix error messages in sandboxed mode for "has some" and "has every"
|
||||
|
||||
# 3.5.0 (2022-12-27)
|
||||
|
||||
* Make Twig\ExpressionParser non-internal
|
||||
* Add "has some" and "has every" operators
|
||||
* Add Compile::reset()
|
||||
* Throw a better runtime error when the "matches" regexp is not valid
|
||||
* Add "twig *_names" intl functions
|
||||
* Fix optimizing closures callbacks
|
||||
* Add a better exception when getting an undefined constant via `constant`
|
||||
* Fix `if` nodes when outside of a block and with an empty body
|
||||
|
||||
# 3.4.3 (2022-09-28)
|
||||
|
||||
* Fix a security issue on filesystem loader (possibility to load a template outside a configured directory)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2009-2022 by the Twig Team.
|
||||
Copyright (c) 2009-present by the Twig Team.
|
||||
|
||||
All rights reserved.
|
||||
|
||||
|
||||
@@ -25,12 +25,13 @@
|
||||
],
|
||||
"require": {
|
||||
"php": ">=7.2.5",
|
||||
"symfony/polyfill-php80": "^1.22",
|
||||
"symfony/polyfill-mbstring": "^1.3",
|
||||
"symfony/polyfill-ctype": "^1.8"
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/phpunit-bridge": "^4.4.9|^5.0.9|^6.0",
|
||||
"psr/container": "^1.0"
|
||||
"symfony/phpunit-bridge": "^5.4.9|^6.3|^7.0",
|
||||
"psr/container": "^1.0|^2.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4" : {
|
||||
@@ -41,10 +42,5 @@
|
||||
"psr-4" : {
|
||||
"Twig\\Tests\\" : "tests/"
|
||||
}
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "3.4-dev"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ class FilesystemCache implements CacheInterface
|
||||
|
||||
if (self::FORCE_BYTECODE_INVALIDATION == ($this->options & self::FORCE_BYTECODE_INVALIDATION)) {
|
||||
// Compile cached file into bytecode cache
|
||||
if (\function_exists('opcache_invalidate') && filter_var(ini_get('opcache.enable'), \FILTER_VALIDATE_BOOLEAN)) {
|
||||
if (\function_exists('opcache_invalidate') && filter_var(\ini_get('opcache.enable'), \FILTER_VALIDATE_BOOLEAN)) {
|
||||
@opcache_invalidate($key, true);
|
||||
} elseif (\function_exists('apc_compile_file')) {
|
||||
apc_compile_file($key);
|
||||
|
||||
@@ -46,7 +46,7 @@ class Compiler
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function compile(Node $node, int $indentation = 0)
|
||||
public function reset(int $indentation = 0)
|
||||
{
|
||||
$this->lastLine = null;
|
||||
$this->source = '';
|
||||
@@ -57,6 +57,15 @@ class Compiler
|
||||
$this->indentation = $indentation;
|
||||
$this->varNameSalt = 0;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function compile(Node $node, int $indentation = 0)
|
||||
{
|
||||
$this->reset($indentation);
|
||||
$node->compile($this);
|
||||
|
||||
return $this;
|
||||
|
||||
@@ -25,6 +25,8 @@ use Twig\Extension\OptimizerExtension;
|
||||
use Twig\Loader\ArrayLoader;
|
||||
use Twig\Loader\ChainLoader;
|
||||
use Twig\Loader\LoaderInterface;
|
||||
use Twig\Node\Expression\Binary\AbstractBinary;
|
||||
use Twig\Node\Expression\Unary\AbstractUnary;
|
||||
use Twig\Node\ModuleNode;
|
||||
use Twig\Node\Node;
|
||||
use Twig\NodeVisitor\NodeVisitorInterface;
|
||||
@@ -38,11 +40,11 @@ use Twig\TokenParser\TokenParserInterface;
|
||||
*/
|
||||
class Environment
|
||||
{
|
||||
public const VERSION = '3.4.3';
|
||||
public const VERSION_ID = 30403;
|
||||
public const VERSION = '3.8.0';
|
||||
public const VERSION_ID = 30800;
|
||||
public const MAJOR_VERSION = 3;
|
||||
public const MINOR_VERSION = 4;
|
||||
public const RELEASE_VERSION = 3;
|
||||
public const MINOR_VERSION = 8;
|
||||
public const RELEASE_VERSION = 0;
|
||||
public const EXTRA_VERSION = '';
|
||||
|
||||
private $charset;
|
||||
@@ -53,6 +55,7 @@ class Environment
|
||||
private $lexer;
|
||||
private $parser;
|
||||
private $compiler;
|
||||
/** @var array<string, mixed> */
|
||||
private $globals = [];
|
||||
private $resolvedGlobals;
|
||||
private $loadedTemplates;
|
||||
@@ -342,7 +345,6 @@ class Environment
|
||||
$this->cache->load($key);
|
||||
}
|
||||
|
||||
$source = null;
|
||||
if (!class_exists($cls, false)) {
|
||||
$source = $this->getLoader()->getSourceContext($name);
|
||||
$content = $this->compileSource($source);
|
||||
@@ -775,6 +777,8 @@ class Environment
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function getGlobals(): array
|
||||
{
|
||||
@@ -804,6 +808,8 @@ class Environment
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @return array<string, array{precedence: int, class: class-string<AbstractUnary>}>
|
||||
*/
|
||||
public function getUnaryOperators(): array
|
||||
{
|
||||
@@ -812,6 +818,8 @@ class Environment
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @return array<string, array{precedence: int, class: class-string<AbstractBinary>, associativity: ExpressionParser::OPERATOR_*}>
|
||||
*/
|
||||
public function getBinaryOperators(): array
|
||||
{
|
||||
|
||||
@@ -53,7 +53,7 @@ class Error extends \Exception
|
||||
* @param int $lineno The template line where the error occurred
|
||||
* @param Source|null $source The source context where the error occurred
|
||||
*/
|
||||
public function __construct(string $message, int $lineno = -1, Source $source = null, \Exception $previous = null)
|
||||
public function __construct(string $message, int $lineno = -1, Source $source = null, \Throwable $previous = null)
|
||||
{
|
||||
parent::__construct('', 0, $previous);
|
||||
|
||||
@@ -130,13 +130,13 @@ class Error extends \Exception
|
||||
}
|
||||
|
||||
$dot = false;
|
||||
if ('.' === substr($this->message, -1)) {
|
||||
if (str_ends_with($this->message, '.')) {
|
||||
$this->message = substr($this->message, 0, -1);
|
||||
$dot = true;
|
||||
}
|
||||
|
||||
$questionMark = false;
|
||||
if ('?' === substr($this->message, -1)) {
|
||||
if (str_ends_with($this->message, '?')) {
|
||||
$this->message = substr($this->message, 0, -1);
|
||||
$questionMark = true;
|
||||
}
|
||||
@@ -172,7 +172,7 @@ class Error extends \Exception
|
||||
foreach ($backtrace as $trace) {
|
||||
if (isset($trace['object']) && $trace['object'] instanceof Template) {
|
||||
$currentClass = \get_class($trace['object']);
|
||||
$isEmbedContainer = null === $templateClass ? false : 0 === strpos($templateClass, $currentClass);
|
||||
$isEmbedContainer = null === $templateClass ? false : str_starts_with($templateClass, $currentClass);
|
||||
if (null === $this->name || ($this->name == $trace['object']->getTemplateName() && !$isEmbedContainer)) {
|
||||
$template = $trace['object'];
|
||||
$templateClass = \get_class($trace['object']);
|
||||
|
||||
@@ -30,7 +30,7 @@ class SyntaxError extends Error
|
||||
$alternatives = [];
|
||||
foreach ($items as $item) {
|
||||
$lev = levenshtein($name, $item);
|
||||
if ($lev <= \strlen($name) / 3 || false !== strpos($item, $name)) {
|
||||
if ($lev <= \strlen($name) / 3 || str_contains($item, $name)) {
|
||||
$alternatives[$item] = $lev;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ use Twig\Node\Expression\AbstractExpression;
|
||||
use Twig\Node\Expression\ArrayExpression;
|
||||
use Twig\Node\Expression\ArrowFunctionExpression;
|
||||
use Twig\Node\Expression\AssignNameExpression;
|
||||
use Twig\Node\Expression\Binary\AbstractBinary;
|
||||
use Twig\Node\Expression\Binary\ConcatBinary;
|
||||
use Twig\Node\Expression\BlockReferenceExpression;
|
||||
use Twig\Node\Expression\ConditionalExpression;
|
||||
@@ -26,6 +27,7 @@ use Twig\Node\Expression\MethodCallExpression;
|
||||
use Twig\Node\Expression\NameExpression;
|
||||
use Twig\Node\Expression\ParentExpression;
|
||||
use Twig\Node\Expression\TestExpression;
|
||||
use Twig\Node\Expression\Unary\AbstractUnary;
|
||||
use Twig\Node\Expression\Unary\NegUnary;
|
||||
use Twig\Node\Expression\Unary\NotUnary;
|
||||
use Twig\Node\Expression\Unary\PosUnary;
|
||||
@@ -40,8 +42,6 @@ use Twig\Node\Node;
|
||||
* @see https://en.wikipedia.org/wiki/Operator-precedence_parser
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class ExpressionParser
|
||||
{
|
||||
@@ -50,7 +50,9 @@ class ExpressionParser
|
||||
|
||||
private $parser;
|
||||
private $env;
|
||||
/** @var array<string, array{precedence: int, class: class-string<AbstractUnary>}> */
|
||||
private $unaryOperators;
|
||||
/** @var array<string, array{precedence: int, class: class-string<AbstractBinary>, associativity: self::OPERATOR_*}> */
|
||||
private $binaryOperators;
|
||||
|
||||
public function __construct(Parser $parser, Environment $env)
|
||||
@@ -80,7 +82,7 @@ class ExpressionParser
|
||||
} elseif (isset($op['callable'])) {
|
||||
$expr = $op['callable']($this->parser, $expr);
|
||||
} else {
|
||||
$expr1 = $this->parseExpression(self::OPERATOR_LEFT === $op['associativity'] ? $op['precedence'] + 1 : $op['precedence']);
|
||||
$expr1 = $this->parseExpression(self::OPERATOR_LEFT === $op['associativity'] ? $op['precedence'] + 1 : $op['precedence'], true);
|
||||
$class = $op['class'];
|
||||
$expr = new $class($expr, $expr1, $token->getLine());
|
||||
}
|
||||
@@ -181,11 +183,14 @@ class ExpressionParser
|
||||
if (!$this->parser->getStream()->nextIf(/* Token::PUNCTUATION_TYPE */ 9, ':')) {
|
||||
$expr2 = $this->parseExpression();
|
||||
if ($this->parser->getStream()->nextIf(/* Token::PUNCTUATION_TYPE */ 9, ':')) {
|
||||
// Ternary operator (expr ? expr2 : expr3)
|
||||
$expr3 = $this->parseExpression();
|
||||
} else {
|
||||
// Ternary without else (expr ? expr2)
|
||||
$expr3 = new ConstantExpression('', $this->parser->getCurrentToken()->getLine());
|
||||
}
|
||||
} else {
|
||||
// Ternary without then (expr ?: expr3)
|
||||
$expr2 = $expr;
|
||||
$expr3 = $this->parseExpression();
|
||||
}
|
||||
@@ -332,7 +337,14 @@ class ExpressionParser
|
||||
}
|
||||
$first = false;
|
||||
|
||||
$node->addElement($this->parseExpression());
|
||||
if ($stream->test(/* Token::SPREAD_TYPE */ 13)) {
|
||||
$stream->next();
|
||||
$expr = $this->parseExpression();
|
||||
$expr->setAttribute('spread', true);
|
||||
$node->addElement($expr);
|
||||
} else {
|
||||
$node->addElement($this->parseExpression());
|
||||
}
|
||||
}
|
||||
$stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ']', 'An opened array is not properly closed');
|
||||
|
||||
@@ -357,6 +369,14 @@ class ExpressionParser
|
||||
}
|
||||
$first = false;
|
||||
|
||||
if ($stream->test(/* Token::SPREAD_TYPE */ 13)) {
|
||||
$stream->next();
|
||||
$value = $this->parseExpression();
|
||||
$value->setAttribute('spread', true);
|
||||
$node->addElement($value);
|
||||
continue;
|
||||
}
|
||||
|
||||
// a hash key can be:
|
||||
//
|
||||
// * a number -- 12
|
||||
@@ -489,10 +509,6 @@ class ExpressionParser
|
||||
}
|
||||
|
||||
if ($node instanceof NameExpression && null !== $this->parser->getImportedSymbol('template', $node->getAttribute('name'))) {
|
||||
if (!$arg instanceof ConstantExpression) {
|
||||
throw new SyntaxError(sprintf('Dynamic macro names are not supported (called on "%s").', $node->getAttribute('name')), $token->getLine(), $stream->getSourceContext());
|
||||
}
|
||||
|
||||
$name = $arg->getAttribute('value');
|
||||
|
||||
$node = new MethodCallExpression($node, 'macro_'.$name, $arguments, $lineno);
|
||||
|
||||
@@ -23,6 +23,8 @@ use Twig\Node\Expression\Binary\EqualBinary;
|
||||
use Twig\Node\Expression\Binary\FloorDivBinary;
|
||||
use Twig\Node\Expression\Binary\GreaterBinary;
|
||||
use Twig\Node\Expression\Binary\GreaterEqualBinary;
|
||||
use Twig\Node\Expression\Binary\HasEveryBinary;
|
||||
use Twig\Node\Expression\Binary\HasSomeBinary;
|
||||
use Twig\Node\Expression\Binary\InBinary;
|
||||
use Twig\Node\Expression\Binary\LessBinary;
|
||||
use Twig\Node\Expression\Binary\LessEqualBinary;
|
||||
@@ -249,7 +251,7 @@ final class CoreExtension extends AbstractExtension
|
||||
new TwigTest('divisible by', null, ['node_class' => DivisiblebyTest::class, 'one_mandatory_argument' => true]),
|
||||
new TwigTest('constant', null, ['node_class' => ConstantTest::class]),
|
||||
new TwigTest('empty', 'twig_test_empty'),
|
||||
new TwigTest('iterable', 'twig_test_iterable'),
|
||||
new TwigTest('iterable', 'is_iterable'),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -284,6 +286,8 @@ final class CoreExtension extends AbstractExtension
|
||||
'matches' => ['precedence' => 20, 'class' => MatchesBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
|
||||
'starts with' => ['precedence' => 20, 'class' => StartsWithBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
|
||||
'ends with' => ['precedence' => 20, 'class' => EndsWithBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
|
||||
'has some' => ['precedence' => 20, 'class' => HasSomeBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
|
||||
'has every' => ['precedence' => 20, 'class' => HasEveryBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
|
||||
'..' => ['precedence' => 25, 'class' => RangeBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
|
||||
'+' => ['precedence' => 30, 'class' => AddBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
|
||||
'-' => ['precedence' => 30, 'class' => SubBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
|
||||
@@ -339,9 +343,9 @@ function twig_cycle($values, $position)
|
||||
* @param \Traversable|array|int|float|string $values The values to pick a random item from
|
||||
* @param int|null $max Maximum value used when $values is an int
|
||||
*
|
||||
* @throws RuntimeError when $values is an empty array (does not apply to an empty string which is returned as is)
|
||||
*
|
||||
* @return mixed A random value from the given sequence
|
||||
*
|
||||
* @throws RuntimeError when $values is an empty array (does not apply to an empty string which is returned as is)
|
||||
*/
|
||||
function twig_random(Environment $env, $values = null, $max = null)
|
||||
{
|
||||
@@ -360,7 +364,6 @@ function twig_random(Environment $env, $values = null, $max = null)
|
||||
}
|
||||
} else {
|
||||
$min = $values;
|
||||
$max = $max;
|
||||
}
|
||||
|
||||
return mt_rand((int) $min, (int) $max);
|
||||
@@ -388,7 +391,7 @@ function twig_random(Environment $env, $values = null, $max = null)
|
||||
}
|
||||
}
|
||||
|
||||
if (!twig_test_iterable($values)) {
|
||||
if (!is_iterable($values)) {
|
||||
return $values;
|
||||
}
|
||||
|
||||
@@ -525,7 +528,7 @@ function twig_date_converter(Environment $env, $date = null, $timezone = null)
|
||||
*/
|
||||
function twig_replace_filter($str, $from)
|
||||
{
|
||||
if (!twig_test_iterable($from)) {
|
||||
if (!is_iterable($from)) {
|
||||
throw new RuntimeError(sprintf('The "replace" filter expects an array or "Traversable" as replace values, got "%s".', \is_object($from) ? \get_class($from) : \gettype($from)));
|
||||
}
|
||||
|
||||
@@ -605,32 +608,34 @@ function twig_urlencode_filter($url)
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges an array with another one.
|
||||
* Merges any number of arrays or Traversable objects.
|
||||
*
|
||||
* {% set items = { 'apple': 'fruit', 'orange': 'fruit' } %}
|
||||
*
|
||||
* {% set items = items|merge({ 'peugeot': 'car' }) %}
|
||||
* {% set items = items|merge({ 'peugeot': 'car' }, { 'banana': 'fruit' }) %}
|
||||
*
|
||||
* {# items now contains { 'apple': 'fruit', 'orange': 'fruit', 'peugeot': 'car' } #}
|
||||
* {# items now contains { 'apple': 'fruit', 'orange': 'fruit', 'peugeot': 'car', 'banana': 'fruit' } #}
|
||||
*
|
||||
* @param array|\Traversable $arr1 An array
|
||||
* @param array|\Traversable $arr2 An array
|
||||
* @param array|\Traversable ...$arrays Any number of arrays or Traversable objects to merge
|
||||
*
|
||||
* @return array The merged array
|
||||
*/
|
||||
function twig_array_merge($arr1, $arr2)
|
||||
function twig_array_merge(...$arrays)
|
||||
{
|
||||
if (!twig_test_iterable($arr1)) {
|
||||
throw new RuntimeError(sprintf('The merge filter only works with arrays or "Traversable", got "%s" as first argument.', \gettype($arr1)));
|
||||
$result = [];
|
||||
|
||||
foreach ($arrays as $argNumber => $array) {
|
||||
if (!is_iterable($array)) {
|
||||
throw new RuntimeError(sprintf('The merge filter only works with arrays or "Traversable", got "%s" for argument %d.', \gettype($array), $argNumber + 1));
|
||||
}
|
||||
|
||||
$result = array_merge($result, twig_to_array($array));
|
||||
}
|
||||
|
||||
if (!twig_test_iterable($arr2)) {
|
||||
throw new RuntimeError(sprintf('The merge filter only works with arrays or "Traversable", got "%s" as second argument.', \gettype($arr2)));
|
||||
}
|
||||
|
||||
return array_merge(twig_to_array($arr1), twig_to_array($arr2));
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Slices a variable.
|
||||
*
|
||||
@@ -650,7 +655,7 @@ function twig_slice(Environment $env, $item, $start, $length = null, $preserveKe
|
||||
|
||||
if ($start >= 0 && $length >= 0 && $item instanceof \Iterator) {
|
||||
try {
|
||||
return iterator_to_array(new \LimitIterator($item, $start, null === $length ? -1 : $length), $preserveKeys);
|
||||
return iterator_to_array(new \LimitIterator($item, $start, $length ?? -1), $preserveKeys);
|
||||
} catch (\OutOfBoundsException $e) {
|
||||
return [];
|
||||
}
|
||||
@@ -663,7 +668,7 @@ function twig_slice(Environment $env, $item, $start, $length = null, $preserveKe
|
||||
return \array_slice($item, $start, $length, $preserveKeys);
|
||||
}
|
||||
|
||||
return (string) mb_substr((string) $item, $start, $length, $env->getCharset());
|
||||
return mb_substr((string) $item, $start, $length, $env->getCharset());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -716,7 +721,7 @@ function twig_last(Environment $env, $item)
|
||||
*/
|
||||
function twig_join_filter($value, $glue = '', $and = null)
|
||||
{
|
||||
if (!twig_test_iterable($value)) {
|
||||
if (!is_iterable($value)) {
|
||||
$value = (array) $value;
|
||||
}
|
||||
|
||||
@@ -762,7 +767,7 @@ function twig_split_filter(Environment $env, $value, $delimiter, $limit = null)
|
||||
{
|
||||
$value = $value ?? '';
|
||||
|
||||
if (\strlen($delimiter) > 0) {
|
||||
if ('' !== $delimiter) {
|
||||
return null === $limit ? explode($delimiter, $value) : explode($delimiter, $value, $limit);
|
||||
}
|
||||
|
||||
@@ -920,7 +925,7 @@ function twig_in_filter($value, $compare)
|
||||
|
||||
if (\is_string($compare)) {
|
||||
if (\is_string($value) || \is_int($value) || \is_float($value)) {
|
||||
return '' === $value || false !== strpos($compare, (string) $value);
|
||||
return '' === $value || str_contains($compare, (string) $value);
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -1015,6 +1020,23 @@ function twig_compare($a, $b)
|
||||
return $a <=> $b;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*
|
||||
* @throws RuntimeError When an invalid pattern is used
|
||||
*/
|
||||
function twig_matches(string $regexp, ?string $str)
|
||||
{
|
||||
set_error_handler(function ($t, $m) use ($regexp) {
|
||||
throw new RuntimeError(sprintf('Regexp "%s" passed to "matches" is not valid', $regexp).substr($m, 12));
|
||||
});
|
||||
try {
|
||||
return preg_match($regexp, $str ?? '');
|
||||
} finally {
|
||||
restore_error_handler();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a trimmed string.
|
||||
*
|
||||
@@ -1097,7 +1119,7 @@ function twig_length_filter(Environment $env, $thing)
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (is_scalar($thing)) {
|
||||
if (\is_scalar($thing)) {
|
||||
return mb_strlen($thing, $env->getCharset());
|
||||
}
|
||||
|
||||
@@ -1207,7 +1229,7 @@ function twig_call_macro(Template $template, string $method, array $args, int $l
|
||||
*/
|
||||
function twig_ensure_traversable($seq)
|
||||
{
|
||||
if ($seq instanceof \Traversable || \is_array($seq)) {
|
||||
if (is_iterable($seq)) {
|
||||
return $seq;
|
||||
}
|
||||
|
||||
@@ -1270,21 +1292,23 @@ function twig_test_empty($value)
|
||||
* @param mixed $value A variable
|
||||
*
|
||||
* @return bool true if the value is traversable
|
||||
*
|
||||
* @deprecated since Twig 3.8, to be removed in 4.0 (use the native "is_iterable" function instead)
|
||||
*/
|
||||
function twig_test_iterable($value)
|
||||
{
|
||||
return $value instanceof \Traversable || \is_array($value);
|
||||
return is_iterable($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a template.
|
||||
*
|
||||
* @param array $context
|
||||
* @param string|array $template The template to render or an array of templates to try consecutively
|
||||
* @param array $variables The variables to pass to the template
|
||||
* @param bool $withContext
|
||||
* @param bool $ignoreMissing Whether to ignore missing templates or not
|
||||
* @param bool $sandboxed Whether to sandbox the template or not
|
||||
* @param array $context
|
||||
* @param string|array|TemplateWrapper $template The template to render or an array of templates to try consecutively
|
||||
* @param array $variables The variables to pass to the template
|
||||
* @param bool $withContext
|
||||
* @param bool $ignoreMissing Whether to ignore missing templates or not
|
||||
* @param bool $sandboxed Whether to sandbox the template or not
|
||||
*
|
||||
* @return string The rendered template
|
||||
*/
|
||||
@@ -1366,6 +1390,10 @@ function twig_constant($constant, $object = null)
|
||||
$constant = \get_class($object).'::'.$constant;
|
||||
}
|
||||
|
||||
if (!\defined($constant)) {
|
||||
throw new RuntimeError(sprintf('Constant "%s" is undefined.', $constant));
|
||||
}
|
||||
|
||||
return \constant($constant);
|
||||
}
|
||||
|
||||
@@ -1401,7 +1429,7 @@ function twig_constant_is_defined($constant, $object = null)
|
||||
*/
|
||||
function twig_array_batch($items, $size, $fill = null, $preserveKeys = true)
|
||||
{
|
||||
if (!twig_test_iterable($items)) {
|
||||
if (!is_iterable($items)) {
|
||||
throw new RuntimeError(sprintf('The "batch" filter expects an array or "Traversable", got "%s".', \is_object($items) ? \get_class($items) : \gettype($items)));
|
||||
}
|
||||
|
||||
@@ -1543,13 +1571,13 @@ function twig_get_attribute(Environment $env, Source $source, $object, $item, ar
|
||||
$classCache[$method] = $method;
|
||||
$classCache[$lcName = $lcMethods[$i]] = $method;
|
||||
|
||||
if ('g' === $lcName[0] && 0 === strpos($lcName, 'get')) {
|
||||
if ('g' === $lcName[0] && str_starts_with($lcName, 'get')) {
|
||||
$name = substr($method, 3);
|
||||
$lcName = substr($lcName, 3);
|
||||
} elseif ('i' === $lcName[0] && 0 === strpos($lcName, 'is')) {
|
||||
} elseif ('i' === $lcName[0] && str_starts_with($lcName, 'is')) {
|
||||
$name = substr($method, 2);
|
||||
$lcName = substr($lcName, 2);
|
||||
} elseif ('h' === $lcName[0] && 0 === strpos($lcName, 'has')) {
|
||||
} elseif ('h' === $lcName[0] && str_starts_with($lcName, 'has')) {
|
||||
$name = substr($method, 3);
|
||||
$lcName = substr($lcName, 3);
|
||||
if (\in_array('is'.$lcName, $lcMethods)) {
|
||||
@@ -1645,7 +1673,7 @@ function twig_array_column($array, $name, $index = null): array
|
||||
|
||||
function twig_array_filter(Environment $env, $array, $arrow)
|
||||
{
|
||||
if (!twig_test_iterable($array)) {
|
||||
if (!is_iterable($array)) {
|
||||
throw new RuntimeError(sprintf('The "filter" filter expects an array or "Traversable", got "%s".', \is_object($array) ? \get_class($array) : \gettype($array)));
|
||||
}
|
||||
|
||||
@@ -1675,15 +1703,42 @@ function twig_array_reduce(Environment $env, $array, $arrow, $initial = null)
|
||||
{
|
||||
twig_check_arrow_in_sandbox($env, $arrow, 'reduce', 'filter');
|
||||
|
||||
if (!\is_array($array)) {
|
||||
if (!$array instanceof \Traversable) {
|
||||
throw new RuntimeError(sprintf('The "reduce" filter only works with arrays or "Traversable", got "%s" as first argument.', \gettype($array)));
|
||||
}
|
||||
|
||||
$array = iterator_to_array($array);
|
||||
if (!\is_array($array) && !$array instanceof \Traversable) {
|
||||
throw new RuntimeError(sprintf('The "reduce" filter only works with arrays or "Traversable", got "%s" as first argument.', \gettype($array)));
|
||||
}
|
||||
|
||||
return array_reduce($array, $arrow, $initial);
|
||||
$accumulator = $initial;
|
||||
foreach ($array as $key => $value) {
|
||||
$accumulator = $arrow($accumulator, $value, $key);
|
||||
}
|
||||
|
||||
return $accumulator;
|
||||
}
|
||||
|
||||
function twig_array_some(Environment $env, $array, $arrow)
|
||||
{
|
||||
twig_check_arrow_in_sandbox($env, $arrow, 'has some', 'operator');
|
||||
|
||||
foreach ($array as $k => $v) {
|
||||
if ($arrow($v, $k)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function twig_array_every(Environment $env, $array, $arrow)
|
||||
{
|
||||
twig_check_arrow_in_sandbox($env, $arrow, 'has every', 'operator');
|
||||
|
||||
foreach ($array as $k => $v) {
|
||||
if (!$arrow($v, $k)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function twig_check_arrow_in_sandbox(Environment $env, $arrow, $thing, $type)
|
||||
|
||||
@@ -19,10 +19,10 @@ final class DebugExtension extends AbstractExtension
|
||||
// dump is safe if var_dump is overridden by xdebug
|
||||
$isDumpOutputHtmlSafe = \extension_loaded('xdebug')
|
||||
// false means that it was not set (and the default is on) or it explicitly enabled
|
||||
&& (false === ini_get('xdebug.overload_var_dump') || ini_get('xdebug.overload_var_dump'))
|
||||
&& (false === \ini_get('xdebug.overload_var_dump') || \ini_get('xdebug.overload_var_dump'))
|
||||
// false means that it was not set (and the default is on) or it explicitly enabled
|
||||
// xdebug.overload_var_dump produces HTML only when html_errors is also enabled
|
||||
&& (false === ini_get('html_errors') || ini_get('html_errors'))
|
||||
&& (false === \ini_get('html_errors') || \ini_get('html_errors'))
|
||||
|| 'cli' === \PHP_SAPI
|
||||
;
|
||||
|
||||
|
||||
@@ -341,7 +341,7 @@ function twig_escape_filter(Environment $env, $string, $strategy = 'html', $char
|
||||
* The following replaces characters undefined in HTML with the
|
||||
* hex entity for the Unicode replacement character.
|
||||
*/
|
||||
if (($ord <= 0x1f && "\t" != $chr && "\n" != $chr && "\r" != $chr) || ($ord >= 0x7f && $ord <= 0x9f)) {
|
||||
if (($ord <= 0x1F && "\t" != $chr && "\n" != $chr && "\r" != $chr) || ($ord >= 0x7F && $ord <= 0x9F)) {
|
||||
return '�';
|
||||
}
|
||||
|
||||
@@ -388,7 +388,7 @@ function twig_escape_filter(Environment $env, $string, $strategy = 'html', $char
|
||||
|
||||
default:
|
||||
$escapers = $env->getExtension(EscaperExtension::class)->getEscapers();
|
||||
if (array_key_exists($strategy, $escapers)) {
|
||||
if (\array_key_exists($strategy, $escapers)) {
|
||||
return $escapers[$strategy]($env, $string, $charset);
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,9 @@
|
||||
|
||||
namespace Twig\Extension;
|
||||
|
||||
use Twig\ExpressionParser;
|
||||
use Twig\Node\Expression\Binary\AbstractBinary;
|
||||
use Twig\Node\Expression\Unary\AbstractUnary;
|
||||
use Twig\NodeVisitor\NodeVisitorInterface;
|
||||
use Twig\TokenParser\TokenParserInterface;
|
||||
use Twig\TwigFilter;
|
||||
@@ -63,6 +66,11 @@ interface ExtensionInterface
|
||||
* Returns a list of operators to add to the existing list.
|
||||
*
|
||||
* @return array<array> First array of unary operators, second array of binary operators
|
||||
*
|
||||
* @psalm-return array{
|
||||
* array<string, array{precedence: int, class: class-string<AbstractUnary>}>,
|
||||
* array<string, array{precedence: int, class: class-string<AbstractBinary>, associativity: ExpressionParser::OPERATOR_*}>
|
||||
* }
|
||||
*/
|
||||
public function getOperators();
|
||||
}
|
||||
|
||||
@@ -21,5 +21,8 @@ namespace Twig\Extension;
|
||||
*/
|
||||
interface GlobalsInterface
|
||||
{
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function getGlobals(): array;
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@ use Twig\Error\RuntimeError;
|
||||
use Twig\Extension\ExtensionInterface;
|
||||
use Twig\Extension\GlobalsInterface;
|
||||
use Twig\Extension\StagingExtension;
|
||||
use Twig\Node\Expression\Binary\AbstractBinary;
|
||||
use Twig\Node\Expression\Unary\AbstractUnary;
|
||||
use Twig\NodeVisitor\NodeVisitorInterface;
|
||||
use Twig\TokenParser\TokenParserInterface;
|
||||
|
||||
@@ -31,11 +33,17 @@ final class ExtensionSet
|
||||
private $staging;
|
||||
private $parsers;
|
||||
private $visitors;
|
||||
/** @var array<string, TwigFilter> */
|
||||
private $filters;
|
||||
/** @var array<string, TwigTest> */
|
||||
private $tests;
|
||||
/** @var array<string, TwigFunction> */
|
||||
private $functions;
|
||||
/** @var array<string, array{precedence: int, class: class-string<AbstractUnary>}> */
|
||||
private $unaryOperators;
|
||||
/** @var array<string, array{precedence: int, class: class-string<AbstractBinary>, associativity: ExpressionParser::OPERATOR_*}> */
|
||||
private $binaryOperators;
|
||||
/** @var array<string, mixed> */
|
||||
private $globals;
|
||||
private $functionCallbacks = [];
|
||||
private $filterCallbacks = [];
|
||||
@@ -305,6 +313,9 @@ final class ExtensionSet
|
||||
$this->parserCallbacks[] = $callable;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function getGlobals(): array
|
||||
{
|
||||
if (null !== $this->globals) {
|
||||
@@ -379,6 +390,9 @@ final class ExtensionSet
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, array{precedence: int, class: class-string<AbstractUnary>}>
|
||||
*/
|
||||
public function getUnaryOperators(): array
|
||||
{
|
||||
if (!$this->initialized) {
|
||||
@@ -388,6 +402,9 @@ final class ExtensionSet
|
||||
return $this->unaryOperators;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, array{precedence: int, class: class-string<AbstractBinary>, associativity: ExpressionParser::OPERATOR_*}>
|
||||
*/
|
||||
public function getBinaryOperators(): array
|
||||
{
|
||||
if (!$this->initialized) {
|
||||
|
||||
@@ -37,7 +37,7 @@ class FileExtensionEscapingStrategy
|
||||
return 'html'; // return html for directories
|
||||
}
|
||||
|
||||
if ('.twig' === substr($name, -5)) {
|
||||
if (str_ends_with($name, '.twig')) {
|
||||
$name = substr($name, 0, -5);
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,8 @@ use Twig\Error\SyntaxError;
|
||||
*/
|
||||
class Lexer
|
||||
{
|
||||
private $isInitialized = false;
|
||||
|
||||
private $tokens;
|
||||
private $code;
|
||||
private $cursor;
|
||||
@@ -61,6 +63,15 @@ class Lexer
|
||||
'whitespace_line_chars' => ' \t\0\x0B',
|
||||
'interpolation' => ['#{', '}'],
|
||||
], $options);
|
||||
}
|
||||
|
||||
private function initialize()
|
||||
{
|
||||
if ($this->isInitialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->isInitialized = true;
|
||||
|
||||
// when PHP 7.3 is the min version, we will be able to remove the '#' part in preg_quote as it's part of the default
|
||||
$this->regexes = [
|
||||
@@ -153,6 +164,8 @@ class Lexer
|
||||
|
||||
public function tokenize(Source $source): TokenStream
|
||||
{
|
||||
$this->initialize();
|
||||
|
||||
$this->source = $source;
|
||||
$this->code = str_replace(["\r\n", "\r"], "\n", $source->getCode());
|
||||
$this->cursor = 0;
|
||||
@@ -302,8 +315,13 @@ class Lexer
|
||||
}
|
||||
}
|
||||
|
||||
// spread operator
|
||||
if ('.' === $this->code[$this->cursor] && ($this->cursor + 2 < $this->end) && '.' === $this->code[$this->cursor + 1] && '.' === $this->code[$this->cursor + 2]) {
|
||||
$this->pushToken(Token::SPREAD_TYPE, '...');
|
||||
$this->moveCursor('...');
|
||||
}
|
||||
// arrow function
|
||||
if ('=' === $this->code[$this->cursor] && '>' === $this->code[$this->cursor + 1]) {
|
||||
elseif ('=' === $this->code[$this->cursor] && '>' === $this->code[$this->cursor + 1]) {
|
||||
$this->pushToken(Token::ARROW_TYPE, '=>');
|
||||
$this->moveCursor('=>');
|
||||
}
|
||||
@@ -327,13 +345,13 @@ class Lexer
|
||||
$this->moveCursor($match[0]);
|
||||
}
|
||||
// punctuation
|
||||
elseif (false !== strpos(self::PUNCTUATION, $this->code[$this->cursor])) {
|
||||
elseif (str_contains(self::PUNCTUATION, $this->code[$this->cursor])) {
|
||||
// opening bracket
|
||||
if (false !== strpos('([{', $this->code[$this->cursor])) {
|
||||
if (str_contains('([{', $this->code[$this->cursor])) {
|
||||
$this->brackets[] = [$this->code[$this->cursor], $this->lineno];
|
||||
}
|
||||
// closing bracket
|
||||
elseif (false !== strpos(')]}', $this->code[$this->cursor])) {
|
||||
elseif (str_contains(')]}', $this->code[$this->cursor])) {
|
||||
if (empty($this->brackets)) {
|
||||
throw new SyntaxError(sprintf('Unexpected "%s".', $this->code[$this->cursor]), $this->lineno, $this->source);
|
||||
}
|
||||
@@ -404,7 +422,7 @@ class Lexer
|
||||
$this->pushToken(/* Token::INTERPOLATION_START_TYPE */ 10);
|
||||
$this->moveCursor($match[0]);
|
||||
$this->pushState(self::STATE_INTERPOLATION);
|
||||
} elseif (preg_match(self::REGEX_DQ_STRING_PART, $this->code, $match, 0, $this->cursor) && \strlen($match[0]) > 0) {
|
||||
} elseif (preg_match(self::REGEX_DQ_STRING_PART, $this->code, $match, 0, $this->cursor) && '' !== $match[0]) {
|
||||
$this->pushToken(/* Token::STRING_TYPE */ 7, stripcslashes($match[0]));
|
||||
$this->moveCursor($match[0]);
|
||||
} elseif (preg_match(self::REGEX_DQ_STRING_DELIM, $this->code, $match, 0, $this->cursor)) {
|
||||
|
||||
@@ -36,7 +36,7 @@ class FilesystemLoader implements LoaderInterface
|
||||
*/
|
||||
public function __construct($paths = [], string $rootPath = null)
|
||||
{
|
||||
$this->rootPath = (null === $rootPath ? getcwd() : $rootPath).\DIRECTORY_SEPARATOR;
|
||||
$this->rootPath = ($rootPath ?? getcwd()).\DIRECTORY_SEPARATOR;
|
||||
if (null !== $rootPath && false !== ($realPath = realpath($rootPath))) {
|
||||
$this->rootPath = $realPath.\DIRECTORY_SEPARATOR;
|
||||
}
|
||||
@@ -250,7 +250,7 @@ class FilesystemLoader implements LoaderInterface
|
||||
|
||||
private function validateName(string $name): void
|
||||
{
|
||||
if (false !== strpos($name, "\0")) {
|
||||
if (str_contains($name, "\0")) {
|
||||
throw new LoaderError('A template name cannot contain NUL bytes.');
|
||||
}
|
||||
|
||||
|
||||
@@ -66,20 +66,70 @@ class ArrayExpression extends AbstractExpression
|
||||
|
||||
public function compile(Compiler $compiler): void
|
||||
{
|
||||
$keyValuePairs = $this->getKeyValuePairs();
|
||||
$needsArrayMergeSpread = \PHP_VERSION_ID < 80100 && $this->hasSpreadItem($keyValuePairs);
|
||||
|
||||
if ($needsArrayMergeSpread) {
|
||||
$compiler->raw('twig_array_merge(');
|
||||
}
|
||||
$compiler->raw('[');
|
||||
$first = true;
|
||||
foreach ($this->getKeyValuePairs() as $pair) {
|
||||
$reopenAfterMergeSpread = false;
|
||||
$nextIndex = 0;
|
||||
foreach ($keyValuePairs as $pair) {
|
||||
if ($reopenAfterMergeSpread) {
|
||||
$compiler->raw(', [');
|
||||
$reopenAfterMergeSpread = false;
|
||||
}
|
||||
|
||||
if ($needsArrayMergeSpread && $pair['value']->hasAttribute('spread')) {
|
||||
$compiler->raw('], ')->subcompile($pair['value']);
|
||||
$first = true;
|
||||
$reopenAfterMergeSpread = true;
|
||||
continue;
|
||||
}
|
||||
if (!$first) {
|
||||
$compiler->raw(', ');
|
||||
}
|
||||
$first = false;
|
||||
|
||||
$compiler
|
||||
->subcompile($pair['key'])
|
||||
->raw(' => ')
|
||||
->subcompile($pair['value'])
|
||||
;
|
||||
if ($pair['value']->hasAttribute('spread') && !$needsArrayMergeSpread) {
|
||||
$compiler->raw('...')->subcompile($pair['value']);
|
||||
++$nextIndex;
|
||||
} else {
|
||||
$key = $pair['key'] instanceof ConstantExpression ? $pair['key']->getAttribute('value') : null;
|
||||
|
||||
if ($nextIndex !== $key) {
|
||||
if (\is_int($key)) {
|
||||
$nextIndex = $key + 1;
|
||||
}
|
||||
$compiler
|
||||
->subcompile($pair['key'])
|
||||
->raw(' => ')
|
||||
;
|
||||
} else {
|
||||
++$nextIndex;
|
||||
}
|
||||
|
||||
$compiler->subcompile($pair['value']);
|
||||
}
|
||||
}
|
||||
$compiler->raw(']');
|
||||
if (!$reopenAfterMergeSpread) {
|
||||
$compiler->raw(']');
|
||||
}
|
||||
if ($needsArrayMergeSpread) {
|
||||
$compiler->raw(')');
|
||||
}
|
||||
}
|
||||
|
||||
private function hasSpreadItem(array $pairs): bool
|
||||
{
|
||||
foreach ($pairs as $pair) {
|
||||
if ($pair['value']->hasAttribute('spread')) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ class EndsWithBinary extends AbstractBinary
|
||||
->subcompile($this->getNode('left'))
|
||||
->raw(sprintf(') && is_string($%s = ', $right))
|
||||
->subcompile($this->getNode('right'))
|
||||
->raw(sprintf(') && (\'\' === $%2$s || $%2$s === substr($%1$s, -strlen($%2$s))))', $left, $right))
|
||||
->raw(sprintf(') && str_ends_with($%1$s, $%2$s))', $left, $right))
|
||||
;
|
||||
}
|
||||
|
||||
|
||||
33
lib/twig/twig/src/Node/Expression/Binary/HasEveryBinary.php
Normal file
33
lib/twig/twig/src/Node/Expression/Binary/HasEveryBinary.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\Binary;
|
||||
|
||||
use Twig\Compiler;
|
||||
|
||||
class HasEveryBinary extends AbstractBinary
|
||||
{
|
||||
public function compile(Compiler $compiler): void
|
||||
{
|
||||
$compiler
|
||||
->raw('twig_array_every($this->env, ')
|
||||
->subcompile($this->getNode('left'))
|
||||
->raw(', ')
|
||||
->subcompile($this->getNode('right'))
|
||||
->raw(')')
|
||||
;
|
||||
}
|
||||
|
||||
public function operator(Compiler $compiler): Compiler
|
||||
{
|
||||
return $compiler->raw('');
|
||||
}
|
||||
}
|
||||
33
lib/twig/twig/src/Node/Expression/Binary/HasSomeBinary.php
Normal file
33
lib/twig/twig/src/Node/Expression/Binary/HasSomeBinary.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\Binary;
|
||||
|
||||
use Twig\Compiler;
|
||||
|
||||
class HasSomeBinary extends AbstractBinary
|
||||
{
|
||||
public function compile(Compiler $compiler): void
|
||||
{
|
||||
$compiler
|
||||
->raw('twig_array_some($this->env, ')
|
||||
->subcompile($this->getNode('left'))
|
||||
->raw(', ')
|
||||
->subcompile($this->getNode('right'))
|
||||
->raw(')')
|
||||
;
|
||||
}
|
||||
|
||||
public function operator(Compiler $compiler): Compiler
|
||||
{
|
||||
return $compiler->raw('');
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,7 @@ class MatchesBinary extends AbstractBinary
|
||||
public function compile(Compiler $compiler): void
|
||||
{
|
||||
$compiler
|
||||
->raw('preg_match(')
|
||||
->raw('twig_matches(')
|
||||
->subcompile($this->getNode('right'))
|
||||
->raw(', ')
|
||||
->subcompile($this->getNode('left'))
|
||||
|
||||
@@ -24,7 +24,7 @@ class StartsWithBinary extends AbstractBinary
|
||||
->subcompile($this->getNode('left'))
|
||||
->raw(sprintf(') && is_string($%s = ', $right))
|
||||
->subcompile($this->getNode('right'))
|
||||
->raw(sprintf(') && (\'\' === $%2$s || 0 === strpos($%1$s, $%2$s)))', $left, $right))
|
||||
->raw(sprintf(') && str_starts_with($%1$s, $%2$s))', $left, $right))
|
||||
;
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ abstract class CallExpression extends AbstractExpression
|
||||
{
|
||||
$callable = $this->getAttribute('callable');
|
||||
|
||||
if (\is_string($callable) && false === strpos($callable, '::')) {
|
||||
if (\is_string($callable) && !str_contains($callable, '::')) {
|
||||
$compiler->raw($callable);
|
||||
} else {
|
||||
[$r, $callable] = $this->reflectCallable($callable);
|
||||
@@ -297,14 +297,16 @@ abstract class CallExpression extends AbstractExpression
|
||||
}
|
||||
$r = new \ReflectionFunction($closure);
|
||||
|
||||
if (false !== strpos($r->name, '{closure}')) {
|
||||
if (str_contains($r->name, '{closure}')) {
|
||||
return $this->reflector = [$r, $callable, 'Closure'];
|
||||
}
|
||||
|
||||
if ($object = $r->getClosureThis()) {
|
||||
$callable = [$object, $r->name];
|
||||
$callableName = (\function_exists('get_debug_type') ? get_debug_type($object) : \get_class($object)).'::'.$r->name;
|
||||
} elseif ($class = $r->getClosureScopeClass()) {
|
||||
$callableName = get_debug_type($object).'::'.$r->name;
|
||||
} elseif (\PHP_VERSION_ID >= 80111 && $class = $r->getClosureCalledClass()) {
|
||||
$callableName = $class->name.'::'.$r->name;
|
||||
} elseif (\PHP_VERSION_ID < 80111 && $class = $r->getClosureScopeClass()) {
|
||||
$callableName = (\is_array($callable) ? $callable[0] : $class->name).'::'.$r->name;
|
||||
} else {
|
||||
$callable = $callableName = $r->name;
|
||||
|
||||
@@ -23,14 +23,23 @@ class ConditionalExpression extends AbstractExpression
|
||||
|
||||
public function compile(Compiler $compiler): void
|
||||
{
|
||||
$compiler
|
||||
->raw('((')
|
||||
->subcompile($this->getNode('expr1'))
|
||||
->raw(') ? (')
|
||||
->subcompile($this->getNode('expr2'))
|
||||
->raw(') : (')
|
||||
->subcompile($this->getNode('expr3'))
|
||||
->raw('))')
|
||||
;
|
||||
// Ternary with no then uses Elvis operator
|
||||
if ($this->getNode('expr1') === $this->getNode('expr2')) {
|
||||
$compiler
|
||||
->raw('((')
|
||||
->subcompile($this->getNode('expr1'))
|
||||
->raw(') ?: (')
|
||||
->subcompile($this->getNode('expr3'))
|
||||
->raw('))');
|
||||
} else {
|
||||
$compiler
|
||||
->raw('((')
|
||||
->subcompile($this->getNode('expr1'))
|
||||
->raw(') ? (')
|
||||
->subcompile($this->getNode('expr2'))
|
||||
->raw(') : (')
|
||||
->subcompile($this->getNode('expr3'))
|
||||
->raw('))');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,8 +50,11 @@ class IfNode extends Node
|
||||
->subcompile($this->getNode('tests')->getNode($i))
|
||||
->raw(") {\n")
|
||||
->indent()
|
||||
->subcompile($this->getNode('tests')->getNode($i + 1))
|
||||
;
|
||||
// The node might not exists if the content is empty
|
||||
if ($this->getNode('tests')->hasNode($i + 1)) {
|
||||
$compiler->subcompile($this->getNode('tests')->getNode($i + 1));
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->hasNode('else')) {
|
||||
|
||||
@@ -355,6 +355,9 @@ final class ModuleNode extends Node
|
||||
protected function compileGetTemplateName(Compiler $compiler)
|
||||
{
|
||||
$compiler
|
||||
->write("/**\n")
|
||||
->write(" * @codeCoverageIgnore\n")
|
||||
->write(" */\n")
|
||||
->write("public function getTemplateName()\n", "{\n")
|
||||
->indent()
|
||||
->write('return ')
|
||||
@@ -409,6 +412,9 @@ final class ModuleNode extends Node
|
||||
}
|
||||
|
||||
$compiler
|
||||
->write("/**\n")
|
||||
->write(" * @codeCoverageIgnore\n")
|
||||
->write(" */\n")
|
||||
->write("public function isTraitable()\n", "{\n")
|
||||
->indent()
|
||||
->write(sprintf("return %s;\n", $traitable ? 'true' : 'false'))
|
||||
@@ -420,6 +426,9 @@ final class ModuleNode extends Node
|
||||
protected function compileDebugInfo(Compiler $compiler)
|
||||
{
|
||||
$compiler
|
||||
->write("/**\n")
|
||||
->write(" * @codeCoverageIgnore\n")
|
||||
->write(" */\n")
|
||||
->write("public function getDebugInfo()\n", "{\n")
|
||||
->indent()
|
||||
->write(sprintf("return %s;\n", str_replace("\n", '', var_export(array_reverse($compiler->getDebugInfo(), true), true))))
|
||||
|
||||
@@ -27,7 +27,6 @@ class Node implements \Countable, \IteratorAggregate
|
||||
protected $lineno;
|
||||
protected $tag;
|
||||
|
||||
private $name;
|
||||
private $sourceContext;
|
||||
|
||||
/**
|
||||
|
||||
@@ -45,7 +45,7 @@ class WithNode extends Node
|
||||
->write(sprintf('$%s = ', $varsName))
|
||||
->subcompile($node)
|
||||
->raw(";\n")
|
||||
->write(sprintf("if (!twig_test_iterable(\$%s)) {\n", $varsName))
|
||||
->write(sprintf("if (!is_iterable(\$%s)) {\n", $varsName))
|
||||
->indent()
|
||||
->write("throw new RuntimeError('Variables passed to the \"with\" tag must be a hash.', ")
|
||||
->repr($node->getTemplateLine())
|
||||
|
||||
@@ -57,7 +57,7 @@ final class EscaperNodeVisitor implements NodeVisitorInterface
|
||||
} elseif ($node instanceof AutoEscapeNode) {
|
||||
$this->statusStack[] = $node->getAttribute('value');
|
||||
} elseif ($node instanceof BlockNode) {
|
||||
$this->statusStack[] = isset($this->blocks[$node->getAttribute('name')]) ? $this->blocks[$node->getAttribute('name')] : $this->needEscaping($env);
|
||||
$this->statusStack[] = $this->blocks[$node->getAttribute('name')] ?? $this->needEscaping();
|
||||
} elseif ($node instanceof ImportNode) {
|
||||
$this->safeVars[] = $node->getNode('var')->getAttribute('name');
|
||||
}
|
||||
@@ -73,7 +73,7 @@ final class EscaperNodeVisitor implements NodeVisitorInterface
|
||||
$this->blocks = [];
|
||||
} elseif ($node instanceof FilterExpression) {
|
||||
return $this->preEscapeFilterNode($node, $env);
|
||||
} elseif ($node instanceof PrintNode && false !== $type = $this->needEscaping($env)) {
|
||||
} elseif ($node instanceof PrintNode && false !== $type = $this->needEscaping()) {
|
||||
$expression = $node->getNode('expr');
|
||||
if ($expression instanceof ConditionalExpression && $this->shouldUnwrapConditional($expression, $env, $type)) {
|
||||
return new DoNode($this->unwrapConditional($expression, $env, $type), $expression->getTemplateLine());
|
||||
@@ -85,7 +85,7 @@ final class EscaperNodeVisitor implements NodeVisitorInterface
|
||||
if ($node instanceof AutoEscapeNode || $node instanceof BlockNode) {
|
||||
array_pop($this->statusStack);
|
||||
} elseif ($node instanceof BlockReferenceNode) {
|
||||
$this->blocks[$node->getAttribute('name')] = $this->needEscaping($env);
|
||||
$this->blocks[$node->getAttribute('name')] = $this->needEscaping();
|
||||
}
|
||||
|
||||
return $node;
|
||||
@@ -183,13 +183,13 @@ final class EscaperNodeVisitor implements NodeVisitorInterface
|
||||
return \in_array($type, $safe) || \in_array('all', $safe);
|
||||
}
|
||||
|
||||
private function needEscaping(Environment $env)
|
||||
private function needEscaping()
|
||||
{
|
||||
if (\count($this->statusStack)) {
|
||||
return $this->statusStack[\count($this->statusStack) - 1];
|
||||
}
|
||||
|
||||
return $this->defaultStrategy ? $this->defaultStrategy : false;
|
||||
return $this->defaultStrategy ?: false;
|
||||
}
|
||||
|
||||
private function getEscaperFilter(string $type, Node $node): FilterExpression
|
||||
|
||||
@@ -50,10 +50,10 @@ final class MacroAutoImportNodeVisitor implements NodeVisitorInterface
|
||||
}
|
||||
} elseif ($this->inAModule) {
|
||||
if (
|
||||
$node instanceof GetAttrExpression &&
|
||||
$node->getNode('node') instanceof NameExpression &&
|
||||
'_self' === $node->getNode('node')->getAttribute('name') &&
|
||||
$node->getNode('attribute') instanceof ConstantExpression
|
||||
$node instanceof GetAttrExpression
|
||||
&& $node->getNode('node') instanceof NameExpression
|
||||
&& '_self' === $node->getNode('node')->getAttribute('name')
|
||||
&& $node->getNode('attribute') instanceof ConstantExpression
|
||||
) {
|
||||
$this->hasMacroCalls = true;
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ final class OptimizerNodeVisitor implements NodeVisitorInterface
|
||||
public function enterNode(Node $node, Environment $env): Node
|
||||
{
|
||||
if (self::OPTIMIZE_FOR === (self::OPTIMIZE_FOR & $this->optimizers)) {
|
||||
$this->enterOptimizeFor($node, $env);
|
||||
$this->enterOptimizeFor($node);
|
||||
}
|
||||
|
||||
return $node;
|
||||
@@ -72,14 +72,14 @@ final class OptimizerNodeVisitor implements NodeVisitorInterface
|
||||
public function leaveNode(Node $node, Environment $env): ?Node
|
||||
{
|
||||
if (self::OPTIMIZE_FOR === (self::OPTIMIZE_FOR & $this->optimizers)) {
|
||||
$this->leaveOptimizeFor($node, $env);
|
||||
$this->leaveOptimizeFor($node);
|
||||
}
|
||||
|
||||
if (self::OPTIMIZE_RAW_FILTER === (self::OPTIMIZE_RAW_FILTER & $this->optimizers)) {
|
||||
$node = $this->optimizeRawFilter($node, $env);
|
||||
$node = $this->optimizeRawFilter($node);
|
||||
}
|
||||
|
||||
$node = $this->optimizePrintNode($node, $env);
|
||||
$node = $this->optimizePrintNode($node);
|
||||
|
||||
return $node;
|
||||
}
|
||||
@@ -91,7 +91,7 @@ final class OptimizerNodeVisitor implements NodeVisitorInterface
|
||||
*
|
||||
* * "echo $this->render(Parent)Block()" with "$this->display(Parent)Block()"
|
||||
*/
|
||||
private function optimizePrintNode(Node $node, Environment $env): Node
|
||||
private function optimizePrintNode(Node $node): Node
|
||||
{
|
||||
if (!$node instanceof PrintNode) {
|
||||
return $node;
|
||||
@@ -99,8 +99,8 @@ final class OptimizerNodeVisitor implements NodeVisitorInterface
|
||||
|
||||
$exprNode = $node->getNode('expr');
|
||||
if (
|
||||
$exprNode instanceof BlockReferenceExpression ||
|
||||
$exprNode instanceof ParentExpression
|
||||
$exprNode instanceof BlockReferenceExpression
|
||||
|| $exprNode instanceof ParentExpression
|
||||
) {
|
||||
$exprNode->setAttribute('output', true);
|
||||
|
||||
@@ -113,7 +113,7 @@ final class OptimizerNodeVisitor implements NodeVisitorInterface
|
||||
/**
|
||||
* Removes "raw" filters.
|
||||
*/
|
||||
private function optimizeRawFilter(Node $node, Environment $env): Node
|
||||
private function optimizeRawFilter(Node $node): Node
|
||||
{
|
||||
if ($node instanceof FilterExpression && 'raw' == $node->getNode('filter')->getAttribute('value')) {
|
||||
return $node->getNode('node');
|
||||
@@ -125,7 +125,7 @@ final class OptimizerNodeVisitor implements NodeVisitorInterface
|
||||
/**
|
||||
* Optimizes "for" tag by removing the "loop" variable creation whenever possible.
|
||||
*/
|
||||
private function enterOptimizeFor(Node $node, Environment $env): void
|
||||
private function enterOptimizeFor(Node $node): void
|
||||
{
|
||||
if ($node instanceof ForNode) {
|
||||
// disable the loop variable by default
|
||||
@@ -166,7 +166,7 @@ final class OptimizerNodeVisitor implements NodeVisitorInterface
|
||||
&& 'include' === $node->getAttribute('name')
|
||||
&& (!$node->getNode('arguments')->hasNode('with_context')
|
||||
|| false !== $node->getNode('arguments')->getNode('with_context')->getAttribute('value')
|
||||
)
|
||||
)
|
||||
) {
|
||||
$this->addLoopToAll();
|
||||
}
|
||||
@@ -175,12 +175,12 @@ final class OptimizerNodeVisitor implements NodeVisitorInterface
|
||||
elseif ($node instanceof GetAttrExpression
|
||||
&& (!$node->getNode('attribute') instanceof ConstantExpression
|
||||
|| 'parent' === $node->getNode('attribute')->getAttribute('value')
|
||||
)
|
||||
)
|
||||
&& (true === $this->loops[0]->getAttribute('with_loop')
|
||||
|| ($node->getNode('node') instanceof NameExpression
|
||||
&& 'loop' === $node->getNode('node')->getAttribute('name')
|
||||
)
|
||||
)
|
||||
|| ($node->getNode('node') instanceof NameExpression
|
||||
&& 'loop' === $node->getNode('node')->getAttribute('name')
|
||||
)
|
||||
)
|
||||
) {
|
||||
$this->addLoopToAll();
|
||||
}
|
||||
@@ -189,7 +189,7 @@ final class OptimizerNodeVisitor implements NodeVisitorInterface
|
||||
/**
|
||||
* Optimizes "for" tag by removing the "loop" variable creation whenever possible.
|
||||
*/
|
||||
private function leaveOptimizeFor(Node $node, Environment $env): void
|
||||
private function leaveOptimizeFor(Node $node): void
|
||||
{
|
||||
if ($node instanceof ForNode) {
|
||||
array_shift($this->loops);
|
||||
|
||||
@@ -303,10 +303,9 @@ class Parser
|
||||
// check that the body does not contain non-empty output nodes
|
||||
if (
|
||||
($node instanceof TextNode && !ctype_space($node->getAttribute('data')))
|
||||
||
|
||||
(!$node instanceof TextNode && !$node instanceof BlockReferenceNode && $node instanceof NodeOutputInterface)
|
||||
|| (!$node instanceof TextNode && !$node instanceof BlockReferenceNode && $node instanceof NodeOutputInterface)
|
||||
) {
|
||||
if (false !== strpos((string) $node, \chr(0xEF).\chr(0xBB).\chr(0xBF))) {
|
||||
if (str_contains((string) $node, \chr(0xEF).\chr(0xBB).\chr(0xBF))) {
|
||||
$t = substr($node->getAttribute('data'), 3);
|
||||
if ('' === $t || ctype_space($t)) {
|
||||
// bypass empty nodes starting with a BOM
|
||||
|
||||
@@ -37,7 +37,7 @@ final class HtmlDumper extends BaseDumper
|
||||
|
||||
protected function formatNonTemplate(Profile $profile, $prefix): string
|
||||
{
|
||||
return sprintf('%s└ %s::%s(<span style="background-color: %s">%s</span>)', $prefix, $profile->getTemplate(), $profile->getType(), isset(self::$colors[$profile->getType()]) ? self::$colors[$profile->getType()] : 'auto', $profile->getName());
|
||||
return sprintf('%s└ %s::%s(<span style="background-color: %s">%s</span>)', $prefix, $profile->getTemplate(), $profile->getType(), self::$colors[$profile->getType()] ?? 'auto', $profile->getName());
|
||||
}
|
||||
|
||||
protected function formatTime(Profile $profile, $percent): string
|
||||
|
||||
@@ -32,7 +32,7 @@ final class Profile implements \IteratorAggregate, \Serializable
|
||||
{
|
||||
$this->template = $template;
|
||||
$this->type = $type;
|
||||
$this->name = 0 === strpos($name, '__internal_') ? 'INTERNAL' : $name;
|
||||
$this->name = str_starts_with($name, '__internal_') ? 'INTERNAL' : $name;
|
||||
$this->enter();
|
||||
}
|
||||
|
||||
|
||||
@@ -94,9 +94,8 @@ final class SecurityPolicy implements SecurityPolicyInterface
|
||||
$allowed = false;
|
||||
$method = strtr($method, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz');
|
||||
foreach ($this->allowedMethods as $class => $methods) {
|
||||
if ($obj instanceof $class) {
|
||||
$allowed = \in_array($method, $methods);
|
||||
|
||||
if ($obj instanceof $class && \in_array($method, $methods)) {
|
||||
$allowed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -111,9 +110,8 @@ final class SecurityPolicy implements SecurityPolicyInterface
|
||||
{
|
||||
$allowed = false;
|
||||
foreach ($this->allowedProperties as $class => $properties) {
|
||||
if ($obj instanceof $class) {
|
||||
$allowed = \in_array($property, \is_array($properties) ? $properties : [$properties]);
|
||||
|
||||
if ($obj instanceof $class && \in_array($property, \is_array($properties) ? $properties : [$properties])) {
|
||||
$allowed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,7 +181,7 @@ abstract class Template
|
||||
}
|
||||
|
||||
throw $e;
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
$e = new RuntimeError(sprintf('An exception has been thrown during the rendering of a template ("%s").', $e->getMessage()), -1, $template->getSourceContext(), $e);
|
||||
$e->guess();
|
||||
|
||||
@@ -404,7 +404,7 @@ abstract class Template
|
||||
}
|
||||
|
||||
throw $e;
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
$e = new RuntimeError(sprintf('An exception has been thrown during the rendering of a template ("%s").', $e->getMessage()), -1, $this->getSourceContext(), $e);
|
||||
$e->guess();
|
||||
|
||||
|
||||
@@ -35,9 +35,7 @@ final class TemplateWrapper
|
||||
|
||||
public function render(array $context = []): string
|
||||
{
|
||||
// using func_get_args() allows to not expose the blocks argument
|
||||
// as it should only be used by internal code
|
||||
return $this->template->render($context, \func_get_args()[1] ?? []);
|
||||
return $this->template->render($context);
|
||||
}
|
||||
|
||||
public function display(array $context = [])
|
||||
|
||||
@@ -35,6 +35,7 @@ final class Token
|
||||
public const INTERPOLATION_START_TYPE = 10;
|
||||
public const INTERPOLATION_END_TYPE = 11;
|
||||
public const ARROW_TYPE = 12;
|
||||
public const SPREAD_TYPE = 13;
|
||||
|
||||
public function __construct(int $type, $value, int $lineno)
|
||||
{
|
||||
@@ -67,9 +68,9 @@ final class Token
|
||||
}
|
||||
|
||||
return ($this->type === $type) && (
|
||||
null === $values ||
|
||||
(\is_array($values) && \in_array($this->value, $values)) ||
|
||||
$this->value == $values
|
||||
null === $values
|
||||
|| (\is_array($values) && \in_array($this->value, $values))
|
||||
|| $this->value == $values
|
||||
);
|
||||
}
|
||||
|
||||
@@ -133,6 +134,9 @@ final class Token
|
||||
case self::ARROW_TYPE:
|
||||
$name = 'ARROW_TYPE';
|
||||
break;
|
||||
case self::SPREAD_TYPE:
|
||||
$name = 'SPREAD_TYPE';
|
||||
break;
|
||||
default:
|
||||
throw new \LogicException(sprintf('Token of type "%s" does not exist.', $type));
|
||||
}
|
||||
@@ -171,6 +175,8 @@ final class Token
|
||||
return 'end of string interpolation';
|
||||
case self::ARROW_TYPE:
|
||||
return 'arrow function';
|
||||
case self::SPREAD_TYPE:
|
||||
return 'spread operator';
|
||||
default:
|
||||
throw new \LogicException(sprintf('Token of type "%s" does not exist.', $type));
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ final class FromTokenParser extends AbstractTokenParser
|
||||
$stream->expect(/* Token::NAME_TYPE */ 5, 'import');
|
||||
|
||||
$targets = [];
|
||||
do {
|
||||
while (true) {
|
||||
$name = $stream->expect(/* Token::NAME_TYPE */ 5)->getValue();
|
||||
|
||||
$alias = $name;
|
||||
@@ -45,7 +45,7 @@ final class FromTokenParser extends AbstractTokenParser
|
||||
if (!$stream->nextIf(/* Token::PUNCTUATION_TYPE */ 9, ',')) {
|
||||
break;
|
||||
}
|
||||
} while (true);
|
||||
}
|
||||
|
||||
$stream->expect(/* Token::BLOCK_END_TYPE */ 3);
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ final class UseTokenParser extends AbstractTokenParser
|
||||
|
||||
$targets = [];
|
||||
if ($stream->nextIf('with')) {
|
||||
do {
|
||||
while (true) {
|
||||
$name = $stream->expect(/* Token::NAME_TYPE */ 5)->getValue();
|
||||
|
||||
$alias = $name;
|
||||
@@ -56,7 +56,7 @@ final class UseTokenParser extends AbstractTokenParser
|
||||
if (!$stream->nextIf(/* Token::PUNCTUATION_TYPE */ 9, ',')) {
|
||||
break;
|
||||
}
|
||||
} while (true);
|
||||
}
|
||||
}
|
||||
|
||||
$stream->expect(/* Token::BLOCK_END_TYPE */ 3);
|
||||
|
||||
@@ -29,7 +29,7 @@ final class TwigFilter
|
||||
private $arguments = [];
|
||||
|
||||
/**
|
||||
* @param callable|null $callable A callable implementing the filter. If null, you need to overwrite the "node_class" option to customize compilation.
|
||||
* @param callable|array{class-string, string}|null $callable A callable implementing the filter. If null, you need to overwrite the "node_class" option to customize compilation.
|
||||
*/
|
||||
public function __construct(string $name, $callable = null, array $options = [])
|
||||
{
|
||||
@@ -57,7 +57,7 @@ final class TwigFilter
|
||||
/**
|
||||
* Returns the callable to execute for this filter.
|
||||
*
|
||||
* @return callable|null
|
||||
* @return callable|array{class-string, string}|null
|
||||
*/
|
||||
public function getCallable()
|
||||
{
|
||||
|
||||
@@ -29,7 +29,7 @@ final class TwigFunction
|
||||
private $arguments = [];
|
||||
|
||||
/**
|
||||
* @param callable|null $callable A callable implementing the function. If null, you need to overwrite the "node_class" option to customize compilation.
|
||||
* @param callable|array{class-string, string}|null $callable A callable implementing the function. If null, you need to overwrite the "node_class" option to customize compilation.
|
||||
*/
|
||||
public function __construct(string $name, $callable = null, array $options = [])
|
||||
{
|
||||
@@ -55,7 +55,7 @@ final class TwigFunction
|
||||
/**
|
||||
* Returns the callable to execute for this function.
|
||||
*
|
||||
* @return callable|null
|
||||
* @return callable|array{class-string, string}|null
|
||||
*/
|
||||
public function getCallable()
|
||||
{
|
||||
|
||||
@@ -28,7 +28,7 @@ final class TwigTest
|
||||
private $arguments = [];
|
||||
|
||||
/**
|
||||
* @param callable|null $callable A callable implementing the test. If null, you need to overwrite the "node_class" option to customize compilation.
|
||||
* @param callable|array{class-string, string}|null $callable A callable implementing the test. If null, you need to overwrite the "node_class" option to customize compilation.
|
||||
*/
|
||||
public function __construct(string $name, $callable = null, array $options = [])
|
||||
{
|
||||
@@ -51,7 +51,7 @@ final class TwigTest
|
||||
/**
|
||||
* Returns the callable to execute for this test.
|
||||
*
|
||||
* @return callable|null
|
||||
* @return callable|array{class-string, string}|null
|
||||
*/
|
||||
public function getCallable()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user