mirror of
https://github.com/Combodo/iTop.git
synced 2026-02-12 23:14:18 +01:00
N°7854 - ⬆️ Bump twig version
This commit is contained in:
25
lib/twig/twig/phpstan-baseline.neon
Normal file
25
lib/twig/twig/phpstan-baseline.neon
Normal file
@@ -0,0 +1,25 @@
|
||||
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
|
||||
|
||||
- # Avoid BC-break
|
||||
message: '#^Constructor of class Twig\\Node\\ForNode has an unused parameter \$ifexpr\.$#'
|
||||
identifier: constructor.unusedParameter
|
||||
count: 1
|
||||
path: src/Node/ForNode.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
|
||||
9
lib/twig/twig/phpstan.neon.dist
Normal file
9
lib/twig/twig/phpstan.neon.dist
Normal file
@@ -0,0 +1,9 @@
|
||||
includes:
|
||||
- phpstan-baseline.neon
|
||||
|
||||
parameters:
|
||||
level: 3
|
||||
paths:
|
||||
- src
|
||||
excludePaths:
|
||||
- src/Test
|
||||
184
lib/twig/twig/src/AbstractTwigCallable.php
Normal file
184
lib/twig/twig/src/AbstractTwigCallable.php
Normal file
@@ -0,0 +1,184 @@
|
||||
<?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;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
abstract class AbstractTwigCallable implements TwigCallableInterface
|
||||
{
|
||||
protected $options;
|
||||
|
||||
private $name;
|
||||
private $dynamicName;
|
||||
private $callable;
|
||||
private $arguments;
|
||||
|
||||
public function __construct(string $name, $callable = null, array $options = [])
|
||||
{
|
||||
$this->name = $this->dynamicName = $name;
|
||||
$this->callable = $callable;
|
||||
$this->arguments = [];
|
||||
$this->options = array_merge([
|
||||
'needs_environment' => false,
|
||||
'needs_context' => false,
|
||||
'needs_charset' => false,
|
||||
'is_variadic' => false,
|
||||
'deprecation_info' => null,
|
||||
'deprecated' => false,
|
||||
'deprecating_package' => '',
|
||||
'alternative' => null,
|
||||
], $options);
|
||||
|
||||
if ($this->options['deprecation_info'] && !$this->options['deprecation_info'] instanceof DeprecatedCallableInfo) {
|
||||
throw new \LogicException(\sprintf('The "deprecation_info" option must be an instance of "%s".', DeprecatedCallableInfo::class));
|
||||
}
|
||||
|
||||
if ($this->options['deprecated']) {
|
||||
if ($this->options['deprecation_info']) {
|
||||
throw new \LogicException('When setting the "deprecation_info" option, you need to remove the obsolete deprecated options.');
|
||||
}
|
||||
|
||||
trigger_deprecation('twig/twig', '3.15', 'Using the "deprecated", "deprecating_package", and "alternative" options is deprecated, pass a "deprecation_info" one instead.');
|
||||
|
||||
$this->options['deprecation_info'] = new DeprecatedCallableInfo(
|
||||
$this->options['deprecating_package'],
|
||||
$this->options['deprecated'],
|
||||
null,
|
||||
$this->options['alternative'],
|
||||
);
|
||||
}
|
||||
|
||||
if ($this->options['deprecation_info']) {
|
||||
$this->options['deprecation_info']->setName($name);
|
||||
$this->options['deprecation_info']->setType($this->getType());
|
||||
}
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return \sprintf('%s(%s)', static::class, $this->name);
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function getDynamicName(): string
|
||||
{
|
||||
return $this->dynamicName;
|
||||
}
|
||||
|
||||
public function getCallable()
|
||||
{
|
||||
return $this->callable;
|
||||
}
|
||||
|
||||
public function getNodeClass(): string
|
||||
{
|
||||
return $this->options['node_class'];
|
||||
}
|
||||
|
||||
public function needsCharset(): bool
|
||||
{
|
||||
return $this->options['needs_charset'];
|
||||
}
|
||||
|
||||
public function needsEnvironment(): bool
|
||||
{
|
||||
return $this->options['needs_environment'];
|
||||
}
|
||||
|
||||
public function needsContext(): bool
|
||||
{
|
||||
return $this->options['needs_context'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return static
|
||||
*/
|
||||
public function withDynamicArguments(string $name, string $dynamicName, array $arguments): self
|
||||
{
|
||||
$new = clone $this;
|
||||
$new->name = $name;
|
||||
$new->dynamicName = $dynamicName;
|
||||
$new->arguments = $arguments;
|
||||
|
||||
return $new;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated since Twig 3.12, use withDynamicArguments() instead
|
||||
*/
|
||||
public function setArguments(array $arguments): void
|
||||
{
|
||||
trigger_deprecation('twig/twig', '3.12', 'The "%s::setArguments()" method is deprecated, use "%s::withDynamicArguments()" instead.', static::class, static::class);
|
||||
|
||||
$this->arguments = $arguments;
|
||||
}
|
||||
|
||||
public function getArguments(): array
|
||||
{
|
||||
return $this->arguments;
|
||||
}
|
||||
|
||||
public function isVariadic(): bool
|
||||
{
|
||||
return $this->options['is_variadic'];
|
||||
}
|
||||
|
||||
public function isDeprecated(): bool
|
||||
{
|
||||
return (bool) $this->options['deprecation_info'];
|
||||
}
|
||||
|
||||
public function triggerDeprecation(?string $file = null, ?int $line = null): void
|
||||
{
|
||||
$this->options['deprecation_info']->triggerDeprecation($file, $line);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated since Twig 3.15
|
||||
*/
|
||||
public function getDeprecatingPackage(): string
|
||||
{
|
||||
trigger_deprecation('twig/twig', '3.15', 'The "%s" method is deprecated, use "%s::triggerDeprecation()" instead.', __METHOD__, static::class);
|
||||
|
||||
return $this->options['deprecating_package'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated since Twig 3.15
|
||||
*/
|
||||
public function getDeprecatedVersion(): string
|
||||
{
|
||||
trigger_deprecation('twig/twig', '3.15', 'The "%s" method is deprecated, use "%s::triggerDeprecation()" instead.', __METHOD__, static::class);
|
||||
|
||||
return \is_bool($this->options['deprecated']) ? '' : $this->options['deprecated'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated since Twig 3.15
|
||||
*/
|
||||
public function getAlternative(): ?string
|
||||
{
|
||||
trigger_deprecation('twig/twig', '3.15', 'The "%s" method is deprecated, use "%s::triggerDeprecation()" instead.', __METHOD__, static::class);
|
||||
|
||||
return $this->options['alternative'];
|
||||
}
|
||||
|
||||
public function getMinimalNumberOfRequiredArguments(): int
|
||||
{
|
||||
return ($this->options['needs_charset'] ? 1 : 0) + ($this->options['needs_environment'] ? 1 : 0) + ($this->options['needs_context'] ? 1 : 0) + \count($this->arguments);
|
||||
}
|
||||
}
|
||||
20
lib/twig/twig/src/Attribute/FirstClassTwigCallableReady.php
Normal file
20
lib/twig/twig/src/Attribute/FirstClassTwigCallableReady.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?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\Attribute;
|
||||
|
||||
/**
|
||||
* Marks nodes that are ready to accept a TwigCallable instead of its name.
|
||||
*/
|
||||
#[\Attribute(\Attribute::TARGET_METHOD)]
|
||||
final class FirstClassTwigCallableReady
|
||||
{
|
||||
}
|
||||
20
lib/twig/twig/src/Attribute/YieldReady.php
Normal file
20
lib/twig/twig/src/Attribute/YieldReady.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?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\Attribute;
|
||||
|
||||
/**
|
||||
* Marks nodes that are ready for using "yield" instead of "echo" or "print()" for rendering.
|
||||
*/
|
||||
#[\Attribute(\Attribute::TARGET_CLASS)]
|
||||
final class YieldReady
|
||||
{
|
||||
}
|
||||
88
lib/twig/twig/src/Cache/ChainCache.php
Normal file
88
lib/twig/twig/src/Cache/ChainCache.php
Normal file
@@ -0,0 +1,88 @@
|
||||
<?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\Cache;
|
||||
|
||||
/**
|
||||
* Chains several caches together.
|
||||
*
|
||||
* Cached items are fetched from the first cache having them in its data store.
|
||||
* They are saved and deleted in all adapters at once.
|
||||
*
|
||||
* @author Quentin Devos <quentin@devos.pm>
|
||||
*/
|
||||
final class ChainCache implements CacheInterface, RemovableCacheInterface
|
||||
{
|
||||
/**
|
||||
* @param iterable<CacheInterface> $caches The ordered list of caches used to store and fetch cached items
|
||||
*/
|
||||
public function __construct(
|
||||
private iterable $caches,
|
||||
) {
|
||||
}
|
||||
|
||||
public function generateKey(string $name, string $className): string
|
||||
{
|
||||
return $className.'#'.$name;
|
||||
}
|
||||
|
||||
public function write(string $key, string $content): void
|
||||
{
|
||||
$splitKey = $this->splitKey($key);
|
||||
|
||||
foreach ($this->caches as $cache) {
|
||||
$cache->write($cache->generateKey(...$splitKey), $content);
|
||||
}
|
||||
}
|
||||
|
||||
public function load(string $key): void
|
||||
{
|
||||
[$name, $className] = $this->splitKey($key);
|
||||
|
||||
foreach ($this->caches as $cache) {
|
||||
$cache->load($cache->generateKey($name, $className));
|
||||
|
||||
if (class_exists($className, false)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getTimestamp(string $key): int
|
||||
{
|
||||
$splitKey = $this->splitKey($key);
|
||||
|
||||
foreach ($this->caches as $cache) {
|
||||
if (0 < $timestamp = $cache->getTimestamp($cache->generateKey(...$splitKey))) {
|
||||
return $timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function remove(string $name, string $cls): void
|
||||
{
|
||||
foreach ($this->caches as $cache) {
|
||||
if ($cache instanceof RemovableCacheInterface) {
|
||||
$cache->remove($name, $cls);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
private function splitKey(string $key): array
|
||||
{
|
||||
return array_reverse(explode('#', $key, 2));
|
||||
}
|
||||
}
|
||||
25
lib/twig/twig/src/Cache/ReadOnlyFilesystemCache.php
Normal file
25
lib/twig/twig/src/Cache/ReadOnlyFilesystemCache.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?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\Cache;
|
||||
|
||||
/**
|
||||
* Implements a cache on the filesystem that can only be read, not written to.
|
||||
*
|
||||
* @author Quentin Devos <quentin@devos.pm>
|
||||
*/
|
||||
class ReadOnlyFilesystemCache extends FilesystemCache
|
||||
{
|
||||
public function write(string $key, string $content): void
|
||||
{
|
||||
// Do nothing with the content, it's a read-only filesystem.
|
||||
}
|
||||
}
|
||||
20
lib/twig/twig/src/Cache/RemovableCacheInterface.php
Normal file
20
lib/twig/twig/src/Cache/RemovableCacheInterface.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?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\Cache;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
interface RemovableCacheInterface
|
||||
{
|
||||
public function remove(string $name, string $cls): void;
|
||||
}
|
||||
67
lib/twig/twig/src/DeprecatedCallableInfo.php
Normal file
67
lib/twig/twig/src/DeprecatedCallableInfo.php
Normal file
@@ -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;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
final class DeprecatedCallableInfo
|
||||
{
|
||||
private string $type;
|
||||
private string $name;
|
||||
|
||||
public function __construct(
|
||||
private string $package,
|
||||
private string $version,
|
||||
private ?string $altName = null,
|
||||
private ?string $altPackage = null,
|
||||
private ?string $altVersion = null,
|
||||
) {
|
||||
}
|
||||
|
||||
public function setType(string $type): void
|
||||
{
|
||||
$this->type = $type;
|
||||
}
|
||||
|
||||
public function setName(string $name): void
|
||||
{
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
public function triggerDeprecation(?string $file = null, ?int $line = null): void
|
||||
{
|
||||
$message = \sprintf('Twig %s "%s" is deprecated', ucfirst($this->type), $this->name);
|
||||
|
||||
if ($this->altName) {
|
||||
$message .= \sprintf('; use "%s"', $this->altName);
|
||||
if ($this->altPackage) {
|
||||
$message .= \sprintf(' from the "%s" package', $this->altPackage);
|
||||
}
|
||||
if ($this->altVersion) {
|
||||
$message .= \sprintf(' (available since version %s)', $this->altVersion);
|
||||
}
|
||||
$message .= ' instead';
|
||||
}
|
||||
|
||||
if ($file) {
|
||||
$message .= \sprintf(' in %s', $file);
|
||||
if ($line) {
|
||||
$message .= \sprintf(' at line %d', $line);
|
||||
}
|
||||
}
|
||||
|
||||
$message .= '.';
|
||||
|
||||
trigger_deprecation($this->package, $this->version, $message);
|
||||
}
|
||||
}
|
||||
30
lib/twig/twig/src/Extension/YieldNotReadyExtension.php
Normal file
30
lib/twig/twig/src/Extension/YieldNotReadyExtension.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?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\Extension;
|
||||
|
||||
use Twig\NodeVisitor\YieldNotReadyNodeVisitor;
|
||||
|
||||
/**
|
||||
* @internal to be removed in Twig 4
|
||||
*/
|
||||
final class YieldNotReadyExtension extends AbstractExtension
|
||||
{
|
||||
public function __construct(
|
||||
private bool $useYield,
|
||||
) {
|
||||
}
|
||||
|
||||
public function getNodeVisitors(): array
|
||||
{
|
||||
return [new YieldNotReadyNodeVisitor($this->useYield)];
|
||||
}
|
||||
}
|
||||
57
lib/twig/twig/src/Node/CaptureNode.php
Normal file
57
lib/twig/twig/src/Node/CaptureNode.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?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;
|
||||
use Twig\Compiler;
|
||||
|
||||
/**
|
||||
* Represents a node for which we need to capture the output.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
#[YieldReady]
|
||||
class CaptureNode extends Node
|
||||
{
|
||||
public function __construct(Node $body, int $lineno)
|
||||
{
|
||||
parent::__construct(['body' => $body], ['raw' => false], $lineno);
|
||||
}
|
||||
|
||||
public function compile(Compiler $compiler): void
|
||||
{
|
||||
$useYield = $compiler->getEnvironment()->useYield();
|
||||
|
||||
if (!$this->getAttribute('raw')) {
|
||||
$compiler->raw("('' === \$tmp = ");
|
||||
}
|
||||
$compiler
|
||||
->raw($useYield ? "implode('', iterator_to_array(" : '\\Twig\\Extension\\CoreExtension::captureOutput(')
|
||||
->raw("(function () use (&\$context, \$macros, \$blocks) {\n")
|
||||
->indent()
|
||||
->subcompile($this->getNode('body'))
|
||||
->write("yield from [];\n")
|
||||
->outdent()
|
||||
->write('})()')
|
||||
;
|
||||
if ($useYield) {
|
||||
$compiler->raw(', false))');
|
||||
} else {
|
||||
$compiler->raw(')');
|
||||
}
|
||||
if (!$this->getAttribute('raw')) {
|
||||
$compiler->raw(") ? '' : new Markup(\$tmp, \$this->env->getCharset());");
|
||||
} else {
|
||||
$compiler->raw(';');
|
||||
}
|
||||
}
|
||||
}
|
||||
33
lib/twig/twig/src/Node/EmptyNode.php
Normal file
33
lib/twig/twig/src/Node/EmptyNode.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;
|
||||
|
||||
use Twig\Attribute\YieldReady;
|
||||
|
||||
/**
|
||||
* Represents an empty node.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
#[YieldReady]
|
||||
final class EmptyNode extends Node
|
||||
{
|
||||
public function __construct(int $lineno = 0)
|
||||
{
|
||||
parent::__construct([], [], $lineno);
|
||||
}
|
||||
|
||||
public function setNode(string $name, Node $node): void
|
||||
{
|
||||
throw new \LogicException('EmptyNode cannot have children.');
|
||||
}
|
||||
}
|
||||
23
lib/twig/twig/src/Node/Expression/Binary/XorBinary.php
Normal file
23
lib/twig/twig/src/Node/Expression/Binary/XorBinary.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
* (c) Armin Ronacher
|
||||
*
|
||||
* 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 XorBinary extends AbstractBinary
|
||||
{
|
||||
public function operator(Compiler $compiler): Compiler
|
||||
{
|
||||
return $compiler->raw('xor');
|
||||
}
|
||||
}
|
||||
45
lib/twig/twig/src/Node/Expression/Filter/RawFilter.php
Normal file
45
lib/twig/twig/src/Node/Expression/Filter/RawFilter.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?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\Filter;
|
||||
|
||||
use Twig\Attribute\FirstClassTwigCallableReady;
|
||||
use Twig\Compiler;
|
||||
use Twig\Node\EmptyNode;
|
||||
use Twig\Node\Expression\AbstractExpression;
|
||||
use Twig\Node\Expression\ConstantExpression;
|
||||
use Twig\Node\Expression\FilterExpression;
|
||||
use Twig\Node\Node;
|
||||
use Twig\TwigFilter;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class RawFilter extends FilterExpression
|
||||
{
|
||||
/**
|
||||
* @param AbstractExpression $node
|
||||
*/
|
||||
#[FirstClassTwigCallableReady]
|
||||
public function __construct(Node $node, TwigFilter|ConstantExpression|null $filter = null, ?Node $arguments = null, int $lineno = 0)
|
||||
{
|
||||
if (!$node instanceof AbstractExpression) {
|
||||
trigger_deprecation('twig/twig', '3.15', 'Not passing a "%s" instance to the "node" argument of "%s" is deprecated ("%s" given).', AbstractExpression::class, static::class, get_class($node));
|
||||
}
|
||||
|
||||
parent::__construct($node, $filter ?: new TwigFilter('raw', null, ['is_safe' => ['all']]), $arguments ?: new EmptyNode(), $lineno ?: $node->getTemplateLine());
|
||||
}
|
||||
|
||||
public function compile(Compiler $compiler): void
|
||||
{
|
||||
$compiler->subcompile($this->getNode('node'));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace Twig\Node\Expression\FunctionNode;
|
||||
|
||||
use Twig\Compiler;
|
||||
use Twig\Error\SyntaxError;
|
||||
use Twig\Node\Expression\ConstantExpression;
|
||||
use Twig\Node\Expression\FunctionExpression;
|
||||
|
||||
class EnumCasesFunction extends FunctionExpression
|
||||
{
|
||||
public function compile(Compiler $compiler): void
|
||||
{
|
||||
$arguments = $this->getNode('arguments');
|
||||
if ($arguments->hasNode('enum')) {
|
||||
$firstArgument = $arguments->getNode('enum');
|
||||
} elseif ($arguments->hasNode('0')) {
|
||||
$firstArgument = $arguments->getNode('0');
|
||||
} else {
|
||||
$firstArgument = null;
|
||||
}
|
||||
|
||||
if (!$firstArgument instanceof ConstantExpression || 1 !== \count($arguments)) {
|
||||
parent::compile($compiler);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$value = $firstArgument->getAttribute('value');
|
||||
|
||||
if (!\is_string($value)) {
|
||||
throw new SyntaxError('The first argument of the "enum_cases" function must be a string.', $this->getTemplateLine(), $this->getSourceContext());
|
||||
}
|
||||
|
||||
if (!enum_exists($value)) {
|
||||
throw new SyntaxError(\sprintf('The first argument of the "enum_cases" function must be the name of an enum, "%s" given.', $value), $this->getTemplateLine(), $this->getSourceContext());
|
||||
}
|
||||
|
||||
$compiler->raw(\sprintf('%s::cases()', $value));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace Twig\Node\Expression\FunctionNode;
|
||||
|
||||
use Twig\Compiler;
|
||||
use Twig\Error\SyntaxError;
|
||||
use Twig\Node\Expression\ConstantExpression;
|
||||
use Twig\Node\Expression\FunctionExpression;
|
||||
|
||||
class EnumFunction extends FunctionExpression
|
||||
{
|
||||
public function compile(Compiler $compiler): void
|
||||
{
|
||||
$arguments = $this->getNode('arguments');
|
||||
if ($arguments->hasNode('enum')) {
|
||||
$firstArgument = $arguments->getNode('enum');
|
||||
} elseif ($arguments->hasNode('0')) {
|
||||
$firstArgument = $arguments->getNode('0');
|
||||
} else {
|
||||
$firstArgument = null;
|
||||
}
|
||||
|
||||
if (!$firstArgument instanceof ConstantExpression || 1 !== \count($arguments)) {
|
||||
parent::compile($compiler);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$value = $firstArgument->getAttribute('value');
|
||||
|
||||
if (!\is_string($value)) {
|
||||
throw new SyntaxError('The first argument of the "enum" function must be a string.', $this->getTemplateLine(), $this->getSourceContext());
|
||||
}
|
||||
|
||||
if (!enum_exists($value)) {
|
||||
throw new SyntaxError(\sprintf('The first argument of the "enum" function must be the name of an enum, "%s" given.', $value), $this->getTemplateLine(), $this->getSourceContext());
|
||||
}
|
||||
|
||||
if (!$cases = $value::cases()) {
|
||||
throw new SyntaxError(\sprintf('The first argument of the "enum" function must be a non-empty enum, "%s" given.', $value), $this->getTemplateLine(), $this->getSourceContext());
|
||||
}
|
||||
|
||||
$compiler->raw(\sprintf('%s::%s', $value, $cases[0]->name));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?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;
|
||||
use Twig\Node\Expression\Variable\TemplateVariable;
|
||||
|
||||
/**
|
||||
* Represents a macro call node.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class MacroReferenceExpression extends AbstractExpression
|
||||
{
|
||||
public function __construct(TemplateVariable $template, string $name, AbstractExpression $arguments, int $lineno)
|
||||
{
|
||||
parent::__construct(['template' => $template, 'arguments' => $arguments], ['name' => $name, 'is_defined_test' => false], $lineno);
|
||||
}
|
||||
|
||||
public function compile(Compiler $compiler): void
|
||||
{
|
||||
if ($this->getAttribute('is_defined_test')) {
|
||||
$compiler
|
||||
->subcompile($this->getNode('template'))
|
||||
->raw('->hasMacro(')
|
||||
->repr($this->getAttribute('name'))
|
||||
->raw(', $context')
|
||||
->raw(')')
|
||||
;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$compiler
|
||||
->subcompile($this->getNode('template'))
|
||||
->raw('->getTemplateForMacro(')
|
||||
->repr($this->getAttribute('name'))
|
||||
->raw(', $context, ')
|
||||
->repr($this->getTemplateLine())
|
||||
->raw(', $this->getSourceContext())')
|
||||
->raw(\sprintf('->%s', $this->getAttribute('name')))
|
||||
->raw('(...')
|
||||
->subcompile($this->getNode('arguments'))
|
||||
->raw(')')
|
||||
;
|
||||
}
|
||||
}
|
||||
22
lib/twig/twig/src/Node/Expression/Unary/SpreadUnary.php
Normal file
22
lib/twig/twig/src/Node/Expression/Unary/SpreadUnary.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?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\Unary;
|
||||
|
||||
use Twig\Compiler;
|
||||
|
||||
final class SpreadUnary extends AbstractUnary
|
||||
{
|
||||
public function operator(Compiler $compiler): Compiler
|
||||
{
|
||||
return $compiler->raw('...');
|
||||
}
|
||||
}
|
||||
22
lib/twig/twig/src/Node/Expression/Unary/StringCastUnary.php
Normal file
22
lib/twig/twig/src/Node/Expression/Unary/StringCastUnary.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?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\Unary;
|
||||
|
||||
use Twig\Compiler;
|
||||
|
||||
final class StringCastUnary extends AbstractUnary
|
||||
{
|
||||
public function operator(Compiler $compiler): Compiler
|
||||
{
|
||||
return $compiler->raw('(string)');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?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\Variable;
|
||||
|
||||
use Twig\Node\Expression\AssignNameExpression;
|
||||
|
||||
final class AssignContextVariable extends AssignNameExpression
|
||||
{
|
||||
}
|
||||
@@ -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\Variable;
|
||||
|
||||
use Twig\Compiler;
|
||||
use Twig\Node\Expression\AbstractExpression;
|
||||
|
||||
final class AssignTemplateVariable extends AbstractExpression
|
||||
{
|
||||
public function __construct(TemplateVariable $var, bool $global = true)
|
||||
{
|
||||
parent::__construct(['var' => $var], ['global' => $global], $var->getTemplateLine());
|
||||
}
|
||||
|
||||
public function compile(Compiler $compiler): void
|
||||
{
|
||||
/** @var TemplateVariable $var */
|
||||
$var = $this->nodes['var'];
|
||||
|
||||
$compiler
|
||||
->addDebugInfo($this)
|
||||
->write('$macros[')
|
||||
->string($var->getName($compiler))
|
||||
->raw('] = ')
|
||||
;
|
||||
|
||||
if ($this->getAttribute('global')) {
|
||||
$compiler
|
||||
->raw('$this->macros[')
|
||||
->string($var->getName($compiler))
|
||||
->raw('] = ')
|
||||
;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?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\Variable;
|
||||
|
||||
use Twig\Node\Expression\NameExpression;
|
||||
|
||||
final class ContextVariable extends NameExpression
|
||||
{
|
||||
}
|
||||
18
lib/twig/twig/src/Node/Expression/Variable/LocalVariable.php
Normal file
18
lib/twig/twig/src/Node/Expression/Variable/LocalVariable.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?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\Variable;
|
||||
|
||||
use Twig\Node\Expression\TempNameExpression;
|
||||
|
||||
final class LocalVariable extends TempNameExpression
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?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\Variable;
|
||||
|
||||
use Twig\Compiler;
|
||||
use Twig\Node\Expression\TempNameExpression;
|
||||
|
||||
class TemplateVariable extends TempNameExpression
|
||||
{
|
||||
public function getName(Compiler $compiler): string
|
||||
{
|
||||
if (null === $this->getAttribute('name')) {
|
||||
$this->setAttribute('name', $compiler->getVarName());
|
||||
}
|
||||
|
||||
return $this->getAttribute('name');
|
||||
}
|
||||
|
||||
public function compile(Compiler $compiler): void
|
||||
{
|
||||
$name = $this->getName($compiler);
|
||||
|
||||
if ('_self' === $name) {
|
||||
$compiler->raw('$this');
|
||||
} else {
|
||||
$compiler
|
||||
->raw('$macros[')
|
||||
->string($name)
|
||||
->raw(']')
|
||||
;
|
||||
}
|
||||
}
|
||||
}
|
||||
46
lib/twig/twig/src/Node/NameDeprecation.php
Normal file
46
lib/twig/twig/src/Node/NameDeprecation.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?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;
|
||||
|
||||
/**
|
||||
* Represents a deprecation for a named node or attribute on a Node.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class NameDeprecation
|
||||
{
|
||||
private $package;
|
||||
private $version;
|
||||
private $newName;
|
||||
|
||||
public function __construct(string $package = '', string $version = '', string $newName = '')
|
||||
{
|
||||
$this->package = $package;
|
||||
$this->version = $version;
|
||||
$this->newName = $newName;
|
||||
}
|
||||
|
||||
public function getPackage(): string
|
||||
{
|
||||
return $this->package;
|
||||
}
|
||||
|
||||
public function getVersion(): string
|
||||
{
|
||||
return $this->version;
|
||||
}
|
||||
|
||||
public function getNewName(): string
|
||||
{
|
||||
return $this->newName;
|
||||
}
|
||||
}
|
||||
28
lib/twig/twig/src/Node/Nodes.php
Normal file
28
lib/twig/twig/src/Node/Nodes.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?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;
|
||||
|
||||
/**
|
||||
* Represents a list of nodes.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
#[YieldReady]
|
||||
final class Nodes extends Node
|
||||
{
|
||||
public function __construct(array $nodes = [], int $lineno = 0)
|
||||
{
|
||||
parent::__construct($nodes, [], $lineno);
|
||||
}
|
||||
}
|
||||
28
lib/twig/twig/src/Node/TypesNode.php
Normal file
28
lib/twig/twig/src/Node/TypesNode.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace Twig\Node;
|
||||
|
||||
use Twig\Attribute\YieldReady;
|
||||
use Twig\Compiler;
|
||||
|
||||
/**
|
||||
* Represents a types node.
|
||||
*
|
||||
* @author Jeroen Versteeg <jeroen@alisqi.com>
|
||||
*/
|
||||
#[YieldReady]
|
||||
class TypesNode extends Node
|
||||
{
|
||||
/**
|
||||
* @param array<string, array{type: string, optional: bool}> $types
|
||||
*/
|
||||
public function __construct(array $types, int $lineno)
|
||||
{
|
||||
parent::__construct([], ['mapping' => $types], $lineno);
|
||||
}
|
||||
|
||||
public function compile(Compiler $compiler)
|
||||
{
|
||||
// Don't compile anything.
|
||||
}
|
||||
}
|
||||
59
lib/twig/twig/src/NodeVisitor/YieldNotReadyNodeVisitor.php
Normal file
59
lib/twig/twig/src/NodeVisitor/YieldNotReadyNodeVisitor.php
Normal file
@@ -0,0 +1,59 @@
|
||||
<?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\NodeVisitor;
|
||||
|
||||
use Twig\Attribute\YieldReady;
|
||||
use Twig\Environment;
|
||||
use Twig\Node\Expression\AbstractExpression;
|
||||
use Twig\Node\Node;
|
||||
|
||||
/**
|
||||
* @internal to be removed in Twig 4
|
||||
*/
|
||||
final class YieldNotReadyNodeVisitor implements NodeVisitorInterface
|
||||
{
|
||||
private $yieldReadyNodes = [];
|
||||
|
||||
public function __construct(
|
||||
private bool $useYield,
|
||||
) {
|
||||
}
|
||||
|
||||
public function enterNode(Node $node, Environment $env): Node
|
||||
{
|
||||
$class = \get_class($node);
|
||||
|
||||
if ($node instanceof AbstractExpression || isset($this->yieldReadyNodes[$class])) {
|
||||
return $node;
|
||||
}
|
||||
|
||||
if (!$this->yieldReadyNodes[$class] = (bool) (new \ReflectionClass($class))->getAttributes(YieldReady::class)) {
|
||||
if ($this->useYield) {
|
||||
throw new \LogicException(\sprintf('You cannot enable the "use_yield" option of Twig as node "%s" is not marked as ready for it; please make it ready and then flag it with the #[\Twig\Attribute\YieldReady] attribute.', $class));
|
||||
}
|
||||
|
||||
trigger_deprecation('twig/twig', '3.9', 'Twig node "%s" is not marked as ready for using "yield" instead of "echo"; please make it ready and then flag it with the #[\Twig\Attribute\YieldReady] attribute.', $class);
|
||||
}
|
||||
|
||||
return $node;
|
||||
}
|
||||
|
||||
public function leaveNode(Node $node, Environment $env): ?Node
|
||||
{
|
||||
return $node;
|
||||
}
|
||||
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 255;
|
||||
}
|
||||
}
|
||||
42
lib/twig/twig/src/OperatorPrecedenceChange.php
Normal file
42
lib/twig/twig/src/OperatorPrecedenceChange.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?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;
|
||||
|
||||
/**
|
||||
* Represents a precedence change for an operator.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class OperatorPrecedenceChange
|
||||
{
|
||||
public function __construct(
|
||||
private string $package,
|
||||
private string $version,
|
||||
private int $newPrecedence,
|
||||
) {
|
||||
}
|
||||
|
||||
public function getPackage(): string
|
||||
{
|
||||
return $this->package;
|
||||
}
|
||||
|
||||
public function getVersion(): string
|
||||
{
|
||||
return $this->version;
|
||||
}
|
||||
|
||||
public function getNewPrecedence(): int
|
||||
{
|
||||
return $this->newPrecedence;
|
||||
}
|
||||
}
|
||||
541
lib/twig/twig/src/Resources/core.php
Normal file
541
lib/twig/twig/src/Resources/core.php
Normal file
@@ -0,0 +1,541 @@
|
||||
<?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.
|
||||
*/
|
||||
|
||||
use Twig\Environment;
|
||||
use Twig\Extension\CoreExtension;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @deprecated since Twig 3.9
|
||||
*/
|
||||
function twig_cycle($values, $position)
|
||||
{
|
||||
trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__);
|
||||
|
||||
return CoreExtension::cycle($values, $position);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @deprecated since Twig 3.9
|
||||
*/
|
||||
function twig_random(Environment $env, $values = null, $max = null)
|
||||
{
|
||||
trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__);
|
||||
|
||||
return CoreExtension::random($env->getCharset(), $values, $max);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @deprecated since Twig 3.9
|
||||
*/
|
||||
function twig_date_format_filter(Environment $env, $date, $format = null, $timezone = null)
|
||||
{
|
||||
trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__);
|
||||
|
||||
return $env->getExtension(CoreExtension::class)->formatDate($date, $format, $timezone);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @deprecated since Twig 3.9
|
||||
*/
|
||||
function twig_date_modify_filter(Environment $env, $date, $modifier)
|
||||
{
|
||||
trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__);
|
||||
|
||||
return $env->getExtension(CoreExtension::class)->modifyDate($date, $modifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @deprecated since Twig 3.9
|
||||
*/
|
||||
function twig_sprintf($format, ...$values)
|
||||
{
|
||||
trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__);
|
||||
|
||||
return CoreExtension::sprintf($format, ...$values);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @deprecated since Twig 3.9
|
||||
*/
|
||||
function twig_date_converter(Environment $env, $date = null, $timezone = null)
|
||||
{
|
||||
trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__);
|
||||
|
||||
return $env->getExtension(CoreExtension::class)->convertDate($date, $timezone);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @deprecated since Twig 3.9
|
||||
*/
|
||||
function twig_replace_filter($str, $from)
|
||||
{
|
||||
trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__);
|
||||
|
||||
return CoreExtension::replace($str, $from);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @deprecated since Twig 3.9
|
||||
*/
|
||||
function twig_round($value, $precision = 0, $method = 'common')
|
||||
{
|
||||
trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__);
|
||||
|
||||
return CoreExtension::round($value, $precision, $method);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @deprecated since Twig 3.9
|
||||
*/
|
||||
function twig_number_format_filter(Environment $env, $number, $decimal = null, $decimalPoint = null, $thousandSep = null)
|
||||
{
|
||||
trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__);
|
||||
|
||||
return $env->getExtension(CoreExtension::class)->formatNumber($number, $decimal, $decimalPoint, $thousandSep);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @deprecated since Twig 3.9
|
||||
*/
|
||||
function twig_urlencode_filter($url)
|
||||
{
|
||||
trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__);
|
||||
|
||||
return CoreExtension::urlencode($url);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @deprecated since Twig 3.9
|
||||
*/
|
||||
function twig_array_merge(...$arrays)
|
||||
{
|
||||
trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__);
|
||||
|
||||
return CoreExtension::merge(...$arrays);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @deprecated since Twig 3.9
|
||||
*/
|
||||
function twig_slice(Environment $env, $item, $start, $length = null, $preserveKeys = false)
|
||||
{
|
||||
trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__);
|
||||
|
||||
return CoreExtension::slice($env->getCharset(), $item, $start, $length, $preserveKeys);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @deprecated since Twig 3.9
|
||||
*/
|
||||
function twig_first(Environment $env, $item)
|
||||
{
|
||||
trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__);
|
||||
|
||||
return CoreExtension::first($env->getCharset(), $item);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @deprecated since Twig 3.9
|
||||
*/
|
||||
function twig_last(Environment $env, $item)
|
||||
{
|
||||
trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__);
|
||||
|
||||
return CoreExtension::last($env->getCharset(), $item);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @deprecated since Twig 3.9
|
||||
*/
|
||||
function twig_join_filter($value, $glue = '', $and = null)
|
||||
{
|
||||
trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__);
|
||||
|
||||
return CoreExtension::join($value, $glue, $and);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @deprecated since Twig 3.9
|
||||
*/
|
||||
function twig_split_filter(Environment $env, $value, $delimiter, $limit = null)
|
||||
{
|
||||
trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__);
|
||||
|
||||
return CoreExtension::split($env->getCharset(), $value, $delimiter, $limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @deprecated since Twig 3.9
|
||||
*/
|
||||
function twig_get_array_keys_filter($array)
|
||||
{
|
||||
trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__);
|
||||
|
||||
return CoreExtension::keys($array);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @deprecated since Twig 3.9
|
||||
*/
|
||||
function twig_reverse_filter(Environment $env, $item, $preserveKeys = false)
|
||||
{
|
||||
trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__);
|
||||
|
||||
return CoreExtension::reverse($env->getCharset(), $item, $preserveKeys);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @deprecated since Twig 3.9
|
||||
*/
|
||||
function twig_sort_filter(Environment $env, $array, $arrow = null)
|
||||
{
|
||||
trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__);
|
||||
|
||||
return CoreExtension::sort($env, $array, $arrow);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @deprecated since Twig 3.9
|
||||
*/
|
||||
function twig_matches(string $regexp, ?string $str)
|
||||
{
|
||||
trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__);
|
||||
|
||||
return CoreExtension::matches($regexp, $str);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @deprecated since Twig 3.9
|
||||
*/
|
||||
function twig_trim_filter($string, $characterMask = null, $side = 'both')
|
||||
{
|
||||
trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__);
|
||||
|
||||
return CoreExtension::trim($string, $characterMask, $side);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @deprecated since Twig 3.9
|
||||
*/
|
||||
function twig_nl2br($string)
|
||||
{
|
||||
trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__);
|
||||
|
||||
return CoreExtension::nl2br($string);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @deprecated since Twig 3.9
|
||||
*/
|
||||
function twig_spaceless($content)
|
||||
{
|
||||
trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__);
|
||||
|
||||
return CoreExtension::spaceless($content);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @deprecated since Twig 3.9
|
||||
*/
|
||||
function twig_convert_encoding($string, $to, $from)
|
||||
{
|
||||
trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__);
|
||||
|
||||
return CoreExtension::convertEncoding($string, $to, $from);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @deprecated since Twig 3.9
|
||||
*/
|
||||
function twig_length_filter(Environment $env, $thing)
|
||||
{
|
||||
trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__);
|
||||
|
||||
return CoreExtension::length($env->getCharset(), $thing);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @deprecated since Twig 3.9
|
||||
*/
|
||||
function twig_upper_filter(Environment $env, $string)
|
||||
{
|
||||
trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__);
|
||||
|
||||
return CoreExtension::upper($env->getCharset(), $string);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @deprecated since Twig 3.9
|
||||
*/
|
||||
function twig_lower_filter(Environment $env, $string)
|
||||
{
|
||||
trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__);
|
||||
|
||||
return CoreExtension::lower($env->getCharset(), $string);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @deprecated since Twig 3.9
|
||||
*/
|
||||
function twig_striptags($string, $allowable_tags = null)
|
||||
{
|
||||
trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__);
|
||||
|
||||
return CoreExtension::striptags($string, $allowable_tags);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @deprecated since Twig 3.9
|
||||
*/
|
||||
function twig_title_string_filter(Environment $env, $string)
|
||||
{
|
||||
trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__);
|
||||
|
||||
return CoreExtension::titleCase($env->getCharset(), $string);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @deprecated since Twig 3.9
|
||||
*/
|
||||
function twig_capitalize_string_filter(Environment $env, $string)
|
||||
{
|
||||
trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__);
|
||||
|
||||
return CoreExtension::capitalize($env->getCharset(), $string);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @deprecated since Twig 3.9
|
||||
*/
|
||||
function twig_test_empty($value)
|
||||
{
|
||||
trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__);
|
||||
|
||||
return CoreExtension::testEmpty($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @deprecated since Twig 3.9
|
||||
*/
|
||||
function twig_test_iterable($value)
|
||||
{
|
||||
trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__);
|
||||
|
||||
return is_iterable($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @deprecated since Twig 3.9
|
||||
*/
|
||||
function twig_include(Environment $env, $context, $template, $variables = [], $withContext = true, $ignoreMissing = false, $sandboxed = false)
|
||||
{
|
||||
trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__);
|
||||
|
||||
return CoreExtension::include($env, $context, $template, $variables, $withContext, $ignoreMissing, $sandboxed);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @deprecated since Twig 3.9
|
||||
*/
|
||||
function twig_source(Environment $env, $name, $ignoreMissing = false)
|
||||
{
|
||||
trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__);
|
||||
|
||||
return CoreExtension::source($env, $name, $ignoreMissing);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @deprecated since Twig 3.9
|
||||
*/
|
||||
function twig_constant($constant, $object = null)
|
||||
{
|
||||
trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__);
|
||||
|
||||
return CoreExtension::constant($constant, $object);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @deprecated since Twig 3.9
|
||||
*/
|
||||
function twig_constant_is_defined($constant, $object = null)
|
||||
{
|
||||
trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__);
|
||||
|
||||
return CoreExtension::constant($constant, $object, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @deprecated since Twig 3.9
|
||||
*/
|
||||
function twig_array_batch($items, $size, $fill = null, $preserveKeys = true)
|
||||
{
|
||||
trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__);
|
||||
|
||||
return CoreExtension::batch($items, $size, $fill, $preserveKeys);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @deprecated since Twig 3.9
|
||||
*/
|
||||
function twig_array_column($array, $name, $index = null): array
|
||||
{
|
||||
trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__);
|
||||
|
||||
return CoreExtension::column($array, $name, $index);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @deprecated since Twig 3.9
|
||||
*/
|
||||
function twig_array_filter(Environment $env, $array, $arrow)
|
||||
{
|
||||
trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__);
|
||||
|
||||
return CoreExtension::filter($env, $array, $arrow);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @deprecated since Twig 3.9
|
||||
*/
|
||||
function twig_array_map(Environment $env, $array, $arrow)
|
||||
{
|
||||
trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__);
|
||||
|
||||
return CoreExtension::map($env, $array, $arrow);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @deprecated since Twig 3.9
|
||||
*/
|
||||
function twig_array_reduce(Environment $env, $array, $arrow, $initial = null)
|
||||
{
|
||||
trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__);
|
||||
|
||||
return CoreExtension::reduce($env, $array, $arrow, $initial);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @deprecated since Twig 3.9
|
||||
*/
|
||||
function twig_array_some(Environment $env, $array, $arrow)
|
||||
{
|
||||
trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__);
|
||||
|
||||
return CoreExtension::arraySome($env, $array, $arrow);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @deprecated since Twig 3.9
|
||||
*/
|
||||
function twig_array_every(Environment $env, $array, $arrow)
|
||||
{
|
||||
trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__);
|
||||
|
||||
return CoreExtension::arrayEvery($env, $array, $arrow);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @deprecated since Twig 3.9
|
||||
*/
|
||||
function twig_check_arrow_in_sandbox(Environment $env, $arrow, $thing, $type)
|
||||
{
|
||||
trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__);
|
||||
|
||||
CoreExtension::checkArrow($env, $arrow, $thing, $type);
|
||||
}
|
||||
25
lib/twig/twig/src/Resources/debug.php
Normal file
25
lib/twig/twig/src/Resources/debug.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
use Twig\Environment;
|
||||
use Twig\Extension\DebugExtension;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @deprecated since Twig 3.9
|
||||
*/
|
||||
function twig_var_dump(Environment $env, $context, ...$vars)
|
||||
{
|
||||
trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__);
|
||||
|
||||
DebugExtension::dump($env, $context, ...$vars);
|
||||
}
|
||||
51
lib/twig/twig/src/Resources/escaper.php
Normal file
51
lib/twig/twig/src/Resources/escaper.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?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.
|
||||
*/
|
||||
|
||||
use Twig\Environment;
|
||||
use Twig\Extension\EscaperExtension;
|
||||
use Twig\Node\Node;
|
||||
use Twig\Runtime\EscaperRuntime;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @deprecated since Twig 3.9
|
||||
*/
|
||||
function twig_raw_filter($string)
|
||||
{
|
||||
trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__);
|
||||
|
||||
return $string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @deprecated since Twig 3.9
|
||||
*/
|
||||
function twig_escape_filter(Environment $env, $string, $strategy = 'html', $charset = null, $autoescape = false)
|
||||
{
|
||||
trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__);
|
||||
|
||||
return $env->getRuntime(EscaperRuntime::class)->escape($string, $strategy, $charset, $autoescape);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @deprecated since Twig 3.9
|
||||
*/
|
||||
function twig_escape_filter_is_safe(Node $filterArgs)
|
||||
{
|
||||
trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__);
|
||||
|
||||
return EscaperExtension::escapeFilterIsSafe($filterArgs);
|
||||
}
|
||||
26
lib/twig/twig/src/Resources/string_loader.php
Normal file
26
lib/twig/twig/src/Resources/string_loader.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
use Twig\Environment;
|
||||
use Twig\Extension\StringLoaderExtension;
|
||||
use Twig\TemplateWrapper;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @deprecated since Twig 3.9
|
||||
*/
|
||||
function twig_template_from_string(Environment $env, $template, ?string $name = null): TemplateWrapper
|
||||
{
|
||||
trigger_deprecation('twig/twig', '3.9', 'Using the internal "%s" function is deprecated.', __FUNCTION__);
|
||||
|
||||
return StringLoaderExtension::templateFromString($env, $template, $name);
|
||||
}
|
||||
334
lib/twig/twig/src/Runtime/EscaperRuntime.php
Normal file
334
lib/twig/twig/src/Runtime/EscaperRuntime.php
Normal file
@@ -0,0 +1,334 @@
|
||||
<?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\Runtime;
|
||||
|
||||
use Twig\Error\RuntimeError;
|
||||
use Twig\Extension\RuntimeExtensionInterface;
|
||||
use Twig\Markup;
|
||||
|
||||
final class EscaperRuntime implements RuntimeExtensionInterface
|
||||
{
|
||||
/** @var array<string, callable(string $string, string $charset): string> */
|
||||
private $escapers = [];
|
||||
|
||||
/** @internal */
|
||||
public $safeClasses = [];
|
||||
|
||||
/** @internal */
|
||||
public $safeLookup = [];
|
||||
|
||||
public function __construct(
|
||||
private $charset = 'UTF-8',
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines a new escaper to be used via the escape filter.
|
||||
*
|
||||
* @param string $strategy The strategy name that should be used as a strategy in the escape call
|
||||
* @param callable(string $string, string $charset): string $callable A valid PHP callable
|
||||
*/
|
||||
public function setEscaper($strategy, callable $callable)
|
||||
{
|
||||
$this->escapers[$strategy] = $callable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all defined escapers.
|
||||
*
|
||||
* @return array<string, callable(string $string, string $charset): string> An array of escapers
|
||||
*/
|
||||
public function getEscapers()
|
||||
{
|
||||
return $this->escapers;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<class-string<\Stringable>, string[]> $safeClasses
|
||||
*/
|
||||
public function setSafeClasses(array $safeClasses = [])
|
||||
{
|
||||
$this->safeClasses = [];
|
||||
$this->safeLookup = [];
|
||||
foreach ($safeClasses as $class => $strategies) {
|
||||
$this->addSafeClass($class, $strategies);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param class-string<\Stringable> $class
|
||||
* @param string[] $strategies
|
||||
*/
|
||||
public function addSafeClass(string $class, array $strategies)
|
||||
{
|
||||
$class = ltrim($class, '\\');
|
||||
if (!isset($this->safeClasses[$class])) {
|
||||
$this->safeClasses[$class] = [];
|
||||
}
|
||||
$this->safeClasses[$class] = array_merge($this->safeClasses[$class], $strategies);
|
||||
|
||||
foreach ($strategies as $strategy) {
|
||||
$this->safeLookup[$strategy][$class] = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes a string.
|
||||
*
|
||||
* @param mixed $string The value to be escaped
|
||||
* @param string $strategy The escaping strategy
|
||||
* @param string|null $charset The charset
|
||||
* @param bool $autoescape Whether the function is called by the auto-escaping feature (true) or by the developer (false)
|
||||
*
|
||||
* @throws RuntimeError
|
||||
*/
|
||||
public function escape($string, string $strategy = 'html', ?string $charset = null, bool $autoescape = false)
|
||||
{
|
||||
if ($autoescape && $string instanceof Markup) {
|
||||
return $string;
|
||||
}
|
||||
|
||||
if (!\is_string($string)) {
|
||||
if ($string instanceof \Stringable) {
|
||||
if ($autoescape) {
|
||||
$c = \get_class($string);
|
||||
if (!isset($this->safeClasses[$c])) {
|
||||
$this->safeClasses[$c] = [];
|
||||
foreach (class_parents($string) + class_implements($string) as $class) {
|
||||
if (isset($this->safeClasses[$class])) {
|
||||
$this->safeClasses[$c] = array_unique(array_merge($this->safeClasses[$c], $this->safeClasses[$class]));
|
||||
foreach ($this->safeClasses[$class] as $s) {
|
||||
$this->safeLookup[$s][$c] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isset($this->safeLookup[$strategy][$c]) || isset($this->safeLookup['all'][$c])) {
|
||||
return (string) $string;
|
||||
}
|
||||
}
|
||||
|
||||
$string = (string) $string;
|
||||
} elseif (\in_array($strategy, ['html', 'js', 'css', 'html_attr', 'url'])) {
|
||||
// we return the input as is (which can be of any type)
|
||||
return $string;
|
||||
}
|
||||
}
|
||||
|
||||
if ('' === $string) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$charset = $charset ?: $this->charset;
|
||||
|
||||
switch ($strategy) {
|
||||
case 'html':
|
||||
// see https://www.php.net/htmlspecialchars
|
||||
|
||||
// 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.
|
||||
static $htmlspecialcharsCharsets = [
|
||||
'ISO-8859-1' => true, 'ISO8859-1' => true,
|
||||
'ISO-8859-15' => true, 'ISO8859-15' => true,
|
||||
'utf-8' => true, 'UTF-8' => true,
|
||||
'CP866' => true, 'IBM866' => true, '866' => true,
|
||||
'CP1251' => true, 'WINDOWS-1251' => true, 'WIN-1251' => true,
|
||||
'1251' => true,
|
||||
'CP1252' => true, 'WINDOWS-1252' => true, '1252' => true,
|
||||
'KOI8-R' => true, 'KOI8-RU' => true, 'KOI8R' => true,
|
||||
'BIG5' => true, '950' => true,
|
||||
'GB2312' => true, '936' => true,
|
||||
'BIG5-HKSCS' => true,
|
||||
'SHIFT_JIS' => true, 'SJIS' => true, '932' => true,
|
||||
'EUC-JP' => true, 'EUCJP' => true,
|
||||
'ISO8859-5' => true, 'ISO-8859-5' => true, 'MACROMAN' => true,
|
||||
];
|
||||
|
||||
if (isset($htmlspecialcharsCharsets[$charset])) {
|
||||
return htmlspecialchars($string, \ENT_QUOTES | \ENT_SUBSTITUTE, $charset);
|
||||
}
|
||||
|
||||
if (isset($htmlspecialcharsCharsets[strtoupper($charset)])) {
|
||||
// cache the lowercase variant for future iterations
|
||||
$htmlspecialcharsCharsets[$charset] = true;
|
||||
|
||||
return htmlspecialchars($string, \ENT_QUOTES | \ENT_SUBSTITUTE, $charset);
|
||||
}
|
||||
|
||||
$string = $this->convertEncoding($string, 'UTF-8', $charset);
|
||||
$string = htmlspecialchars($string, \ENT_QUOTES | \ENT_SUBSTITUTE, 'UTF-8');
|
||||
|
||||
return iconv('UTF-8', $charset, $string);
|
||||
|
||||
case 'js':
|
||||
// escape all non-alphanumeric characters
|
||||
// into their \x or \uHHHH representations
|
||||
if ('UTF-8' !== $charset) {
|
||||
$string = $this->convertEncoding($string, 'UTF-8', $charset);
|
||||
}
|
||||
|
||||
if (!preg_match('//u', $string)) {
|
||||
throw new RuntimeError('The string to escape is not a valid UTF-8 string.');
|
||||
}
|
||||
|
||||
$string = preg_replace_callback('#[^a-zA-Z0-9,\._]#Su', function ($matches) {
|
||||
$char = $matches[0];
|
||||
|
||||
/*
|
||||
* A few characters have short escape sequences in JSON and JavaScript.
|
||||
* 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 = [
|
||||
'\\' => '\\\\',
|
||||
'/' => '\\/',
|
||||
"\x08" => '\b',
|
||||
"\x0C" => '\f',
|
||||
"\x0A" => '\n',
|
||||
"\x0D" => '\r',
|
||||
"\x09" => '\t',
|
||||
];
|
||||
|
||||
if (isset($shortMap[$char])) {
|
||||
return $shortMap[$char];
|
||||
}
|
||||
|
||||
$codepoint = mb_ord($char, 'UTF-8');
|
||||
if (0x10000 > $codepoint) {
|
||||
return \sprintf('\u%04X', $codepoint);
|
||||
}
|
||||
|
||||
// Split characters outside the BMP into surrogate pairs
|
||||
// https://tools.ietf.org/html/rfc2781.html#section-2.1
|
||||
$u = $codepoint - 0x10000;
|
||||
$high = 0xD800 | ($u >> 10);
|
||||
$low = 0xDC00 | ($u & 0x3FF);
|
||||
|
||||
return \sprintf('\u%04X\u%04X', $high, $low);
|
||||
}, $string);
|
||||
|
||||
if ('UTF-8' !== $charset) {
|
||||
$string = iconv('UTF-8', $charset, $string);
|
||||
}
|
||||
|
||||
return $string;
|
||||
|
||||
case 'css':
|
||||
if ('UTF-8' !== $charset) {
|
||||
$string = $this->convertEncoding($string, 'UTF-8', $charset);
|
||||
}
|
||||
|
||||
if (!preg_match('//u', $string)) {
|
||||
throw new RuntimeError('The string to escape is not a valid UTF-8 string.');
|
||||
}
|
||||
|
||||
$string = preg_replace_callback('#[^a-zA-Z0-9]#Su', function ($matches) {
|
||||
$char = $matches[0];
|
||||
|
||||
return \sprintf('\\%X ', 1 === \strlen($char) ? \ord($char) : mb_ord($char, 'UTF-8'));
|
||||
}, $string);
|
||||
|
||||
if ('UTF-8' !== $charset) {
|
||||
$string = iconv('UTF-8', $charset, $string);
|
||||
}
|
||||
|
||||
return $string;
|
||||
|
||||
case 'html_attr':
|
||||
if ('UTF-8' !== $charset) {
|
||||
$string = $this->convertEncoding($string, 'UTF-8', $charset);
|
||||
}
|
||||
|
||||
if (!preg_match('//u', $string)) {
|
||||
throw new RuntimeError('The string to escape is not a valid UTF-8 string.');
|
||||
}
|
||||
|
||||
$string = preg_replace_callback('#[^a-zA-Z0-9,\.\-_]#Su', function ($matches) {
|
||||
/**
|
||||
* This function is adapted from code coming from Zend Framework.
|
||||
*
|
||||
* @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (https://www.zend.com)
|
||||
* @license https://framework.zend.com/license/new-bsd New BSD License
|
||||
*/
|
||||
$chr = $matches[0];
|
||||
$ord = \ord($chr);
|
||||
|
||||
/*
|
||||
* 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)) {
|
||||
return '�';
|
||||
}
|
||||
|
||||
/*
|
||||
* Check if the current character to escape has a name entity we should
|
||||
* replace it with while grabbing the hex value of the character.
|
||||
*/
|
||||
if (1 === \strlen($chr)) {
|
||||
/*
|
||||
* While HTML supports far more named entities, the lowest common denominator
|
||||
* has become HTML5's XML Serialisation which is restricted to the those named
|
||||
* entities that XML supports. Using HTML entities would result in this error:
|
||||
* XML Parsing Error: undefined entity
|
||||
*/
|
||||
static $entityMap = [
|
||||
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);
|
||||
}
|
||||
|
||||
/*
|
||||
* Per OWASP recommendations, we'll use hex entities for any other
|
||||
* characters where a named entity does not exist.
|
||||
*/
|
||||
return \sprintf('&#x%04X;', mb_ord($chr, 'UTF-8'));
|
||||
}, $string);
|
||||
|
||||
if ('UTF-8' !== $charset) {
|
||||
$string = iconv('UTF-8', $charset, $string);
|
||||
}
|
||||
|
||||
return $string;
|
||||
|
||||
case 'url':
|
||||
return rawurlencode($string);
|
||||
|
||||
default:
|
||||
if (\array_key_exists($strategy, $this->escapers)) {
|
||||
return $this->escapers[$strategy]($string, $charset);
|
||||
}
|
||||
|
||||
$validStrategies = implode('", "', array_merge(['html', 'js', 'url', 'css', 'html_attr'], array_keys($this->escapers)));
|
||||
|
||||
throw new RuntimeError(\sprintf('Invalid escaping strategy "%s" (valid ones: "%s").', $strategy, $validStrategies));
|
||||
}
|
||||
}
|
||||
|
||||
private function convertEncoding(string $string, string $to, string $from)
|
||||
{
|
||||
if (!\function_exists('iconv')) {
|
||||
throw new RuntimeError('Unable to convert encoding: required function iconv() does not exist. You should install ext-iconv or symfony/polyfill-iconv.');
|
||||
}
|
||||
|
||||
return iconv($from, $to, $string);
|
||||
}
|
||||
}
|
||||
24
lib/twig/twig/src/Sandbox/SourcePolicyInterface.php
Normal file
24
lib/twig/twig/src/Sandbox/SourcePolicyInterface.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?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\Sandbox;
|
||||
|
||||
use Twig\Source;
|
||||
|
||||
/**
|
||||
* Interface for a class that can optionally enable the sandbox mode based on a template's Twig\Source.
|
||||
*
|
||||
* @author Yaakov Saxon
|
||||
*/
|
||||
interface SourcePolicyInterface
|
||||
{
|
||||
public function enableSandbox(Source $source): bool;
|
||||
}
|
||||
69
lib/twig/twig/src/TokenParser/GuardTokenParser.php
Normal file
69
lib/twig/twig/src/TokenParser/GuardTokenParser.php
Normal file
@@ -0,0 +1,69 @@
|
||||
<?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\TokenParser;
|
||||
|
||||
use Twig\Error\SyntaxError;
|
||||
use Twig\Node\EmptyNode;
|
||||
use Twig\Node\Node;
|
||||
use Twig\Node\Nodes;
|
||||
use Twig\Token;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class GuardTokenParser extends AbstractTokenParser
|
||||
{
|
||||
public function parse(Token $token): Node
|
||||
{
|
||||
$stream = $this->parser->getStream();
|
||||
$typeToken = $stream->expect(Token::NAME_TYPE);
|
||||
if (!in_array($typeToken->getValue(), ['function', 'filter', 'test'])) {
|
||||
throw new SyntaxError(\sprintf('Supported guard types are function, filter and test, "%s" given.', $typeToken->getValue()), $typeToken->getLine(), $stream->getSourceContext());
|
||||
}
|
||||
$method = 'get'.$typeToken->getValue();
|
||||
|
||||
$nameToken = $stream->expect(Token::NAME_TYPE);
|
||||
|
||||
$exists = null !== $this->parser->getEnvironment()->$method($nameToken->getValue());
|
||||
|
||||
$stream->expect(Token::BLOCK_END_TYPE);
|
||||
if ($exists) {
|
||||
$body = $this->parser->subparse([$this, 'decideGuardFork']);
|
||||
} else {
|
||||
$body = new EmptyNode();
|
||||
$this->parser->subparseIgnoreUnknownTwigCallables([$this, 'decideGuardFork']);
|
||||
}
|
||||
$else = new EmptyNode();
|
||||
if ('else' === $stream->next()->getValue()) {
|
||||
$stream->expect(Token::BLOCK_END_TYPE);
|
||||
$else = $this->parser->subparse([$this, 'decideGuardEnd'], true);
|
||||
}
|
||||
$stream->expect(Token::BLOCK_END_TYPE);
|
||||
|
||||
return new Nodes([$exists ? $body : $else]);
|
||||
}
|
||||
|
||||
public function decideGuardFork(Token $token): bool
|
||||
{
|
||||
return $token->test(['else', 'endguard']);
|
||||
}
|
||||
|
||||
public function decideGuardEnd(Token $token): bool
|
||||
{
|
||||
return $token->test(['endguard']);
|
||||
}
|
||||
|
||||
public function getTag(): string
|
||||
{
|
||||
return 'guard';
|
||||
}
|
||||
}
|
||||
86
lib/twig/twig/src/TokenParser/TypesTokenParser.php
Normal file
86
lib/twig/twig/src/TokenParser/TypesTokenParser.php
Normal file
@@ -0,0 +1,86 @@
|
||||
<?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\TokenParser;
|
||||
|
||||
use Twig\Error\SyntaxError;
|
||||
use Twig\Node\Node;
|
||||
use Twig\Node\TypesNode;
|
||||
use Twig\Token;
|
||||
use Twig\TokenStream;
|
||||
|
||||
/**
|
||||
* Declare variable types.
|
||||
*
|
||||
* {% types {foo: 'number', bar?: 'string'} %}
|
||||
*
|
||||
* @author Jeroen Versteeg <jeroen@alisqi.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class TypesTokenParser extends AbstractTokenParser
|
||||
{
|
||||
public function parse(Token $token): Node
|
||||
{
|
||||
$stream = $this->parser->getStream();
|
||||
|
||||
$types = $this->parseSimpleMappingExpression($stream);
|
||||
|
||||
$stream->expect(Token::BLOCK_END_TYPE);
|
||||
|
||||
return new TypesNode($types, $token->getLine());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, array{type: string, optional: bool}>
|
||||
*
|
||||
* @throws SyntaxError
|
||||
*/
|
||||
private function parseSimpleMappingExpression(TokenStream $stream): array
|
||||
{
|
||||
$stream->expect(Token::PUNCTUATION_TYPE, '{', 'A mapping element was expected');
|
||||
|
||||
$types = [];
|
||||
|
||||
$first = true;
|
||||
while (!$stream->test(Token::PUNCTUATION_TYPE, '}')) {
|
||||
if (!$first) {
|
||||
$stream->expect(Token::PUNCTUATION_TYPE, ',', 'A type string must be followed by a comma');
|
||||
|
||||
// trailing ,?
|
||||
if ($stream->test(Token::PUNCTUATION_TYPE, '}')) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
$first = false;
|
||||
|
||||
$nameToken = $stream->expect(Token::NAME_TYPE);
|
||||
$isOptional = null !== $stream->nextIf(Token::PUNCTUATION_TYPE, '?');
|
||||
|
||||
$stream->expect(Token::PUNCTUATION_TYPE, ':', 'A type name must be followed by a colon (:)');
|
||||
|
||||
$valueToken = $stream->expect(Token::STRING_TYPE);
|
||||
|
||||
$types[$nameToken->getValue()] = [
|
||||
'type' => $valueToken->getValue(),
|
||||
'optional' => $isOptional,
|
||||
];
|
||||
}
|
||||
$stream->expect(Token::PUNCTUATION_TYPE, '}', 'An opened mapping is not properly closed');
|
||||
|
||||
return $types;
|
||||
}
|
||||
|
||||
public function getTag(): string
|
||||
{
|
||||
return 'types';
|
||||
}
|
||||
}
|
||||
53
lib/twig/twig/src/TwigCallableInterface.php
Normal file
53
lib/twig/twig/src/TwigCallableInterface.php
Normal file
@@ -0,0 +1,53 @@
|
||||
<?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;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
interface TwigCallableInterface extends \Stringable
|
||||
{
|
||||
public function getName(): string;
|
||||
|
||||
public function getType(): string;
|
||||
|
||||
public function getDynamicName(): string;
|
||||
|
||||
/**
|
||||
* @return callable|array{class-string, string}|null
|
||||
*/
|
||||
public function getCallable();
|
||||
|
||||
public function getNodeClass(): string;
|
||||
|
||||
public function needsCharset(): bool;
|
||||
|
||||
public function needsEnvironment(): bool;
|
||||
|
||||
public function needsContext(): bool;
|
||||
|
||||
public function withDynamicArguments(string $name, string $dynamicName, array $arguments): self;
|
||||
|
||||
public function getArguments(): array;
|
||||
|
||||
public function isVariadic(): bool;
|
||||
|
||||
public function isDeprecated(): bool;
|
||||
|
||||
public function getDeprecatingPackage(): string;
|
||||
|
||||
public function getDeprecatedVersion(): string;
|
||||
|
||||
public function getAlternative(): ?string;
|
||||
|
||||
public function getMinimalNumberOfRequiredArguments(): int;
|
||||
}
|
||||
219
lib/twig/twig/src/Util/CallableArgumentsExtractor.php
Normal file
219
lib/twig/twig/src/Util/CallableArgumentsExtractor.php
Normal file
@@ -0,0 +1,219 @@
|
||||
<?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\Util;
|
||||
|
||||
use Twig\Error\SyntaxError;
|
||||
use Twig\Node\Expression\ArrayExpression;
|
||||
use Twig\Node\Expression\ConstantExpression;
|
||||
use Twig\Node\Expression\VariadicExpression;
|
||||
use Twig\Node\Node;
|
||||
use Twig\TwigCallableInterface;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class CallableArgumentsExtractor
|
||||
{
|
||||
private ReflectionCallable $rc;
|
||||
|
||||
public function __construct(
|
||||
private Node $node,
|
||||
private TwigCallableInterface $twigCallable,
|
||||
) {
|
||||
$this->rc = new ReflectionCallable($twigCallable);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<Node>
|
||||
*/
|
||||
public function extractArguments(Node $arguments): array
|
||||
{
|
||||
$extractedArguments = [];
|
||||
$extractedArgumentNameMap = [];
|
||||
$named = false;
|
||||
foreach ($arguments as $name => $node) {
|
||||
if (!\is_int($name)) {
|
||||
$named = true;
|
||||
} elseif ($named) {
|
||||
throw new SyntaxError(\sprintf('Positional arguments cannot be used after named arguments for %s "%s".', $this->twigCallable->getType(), $this->twigCallable->getName()), $this->node->getTemplateLine(), $this->node->getSourceContext());
|
||||
}
|
||||
|
||||
$extractedArguments[$normalizedName = $this->normalizeName($name)] = $node;
|
||||
$extractedArgumentNameMap[$normalizedName] = $name;
|
||||
}
|
||||
|
||||
if (!$named && !$this->twigCallable->isVariadic()) {
|
||||
$min = $this->twigCallable->getMinimalNumberOfRequiredArguments();
|
||||
if (\count($extractedArguments) < $this->rc->getReflector()->getNumberOfRequiredParameters() - $min) {
|
||||
$argName = $this->toSnakeCase($this->rc->getReflector()->getParameters()[$min + \count($extractedArguments)]->getName());
|
||||
|
||||
throw new SyntaxError(\sprintf('Value for argument "%s" is required for %s "%s".', $argName, $this->twigCallable->getType(), $this->twigCallable->getName()), $this->node->getTemplateLine(), $this->node->getSourceContext());
|
||||
}
|
||||
|
||||
return $extractedArguments;
|
||||
}
|
||||
|
||||
if (!$callable = $this->twigCallable->getCallable()) {
|
||||
if ($named) {
|
||||
throw new SyntaxError(\sprintf('Named arguments are not supported for %s "%s".', $this->twigCallable->getType(), $this->twigCallable->getName()));
|
||||
}
|
||||
|
||||
throw new SyntaxError(\sprintf('Arbitrary positional arguments are not supported for %s "%s".', $this->twigCallable->getType(), $this->twigCallable->getName()));
|
||||
}
|
||||
|
||||
[$callableParameters, $isPhpVariadic] = $this->getCallableParameters();
|
||||
$arguments = [];
|
||||
$callableParameterNames = [];
|
||||
$missingArguments = [];
|
||||
$optionalArguments = [];
|
||||
$pos = 0;
|
||||
foreach ($callableParameters as $callableParameter) {
|
||||
$callableParameterName = $callableParameter->name;
|
||||
if (\PHP_VERSION_ID >= 80000 && 'range' === $callable) {
|
||||
if ('start' === $callableParameterName) {
|
||||
$callableParameterName = 'low';
|
||||
} elseif ('end' === $callableParameterName) {
|
||||
$callableParameterName = 'high';
|
||||
}
|
||||
}
|
||||
|
||||
$callableParameterNames[] = $callableParameterName;
|
||||
$normalizedCallableParameterName = $this->normalizeName($callableParameterName);
|
||||
|
||||
if (\array_key_exists($normalizedCallableParameterName, $extractedArguments)) {
|
||||
if (\array_key_exists($pos, $extractedArguments)) {
|
||||
throw new SyntaxError(\sprintf('Argument "%s" is defined twice for %s "%s".', $callableParameterName, $this->twigCallable->getType(), $this->twigCallable->getName()), $this->node->getTemplateLine(), $this->node->getSourceContext());
|
||||
}
|
||||
|
||||
if (\count($missingArguments)) {
|
||||
throw new SyntaxError(\sprintf(
|
||||
'Argument "%s" could not be assigned for %s "%s(%s)" because it is mapped to an internal PHP function which cannot determine default value for optional argument%s "%s".',
|
||||
$callableParameterName, $this->twigCallable->getType(), $this->twigCallable->getName(), implode(', ', array_map([$this, 'toSnakeCase'], $callableParameterNames)), \count($missingArguments) > 1 ? 's' : '', implode('", "', $missingArguments)
|
||||
), $this->node->getTemplateLine(), $this->node->getSourceContext());
|
||||
}
|
||||
|
||||
$arguments = array_merge($arguments, $optionalArguments);
|
||||
$arguments[] = $extractedArguments[$normalizedCallableParameterName];
|
||||
unset($extractedArguments[$normalizedCallableParameterName]);
|
||||
$optionalArguments = [];
|
||||
} elseif (\array_key_exists($pos, $extractedArguments)) {
|
||||
$arguments = array_merge($arguments, $optionalArguments);
|
||||
$arguments[] = $extractedArguments[$pos];
|
||||
unset($extractedArguments[$pos]);
|
||||
$optionalArguments = [];
|
||||
++$pos;
|
||||
} elseif ($callableParameter->isDefaultValueAvailable()) {
|
||||
$optionalArguments[] = new ConstantExpression($callableParameter->getDefaultValue(), $this->node->getTemplateLine());
|
||||
} elseif ($callableParameter->isOptional()) {
|
||||
if (!$extractedArguments) {
|
||||
break;
|
||||
}
|
||||
|
||||
$missingArguments[] = $callableParameterName;
|
||||
} else {
|
||||
throw new SyntaxError(\sprintf('Value for argument "%s" is required for %s "%s".', $this->toSnakeCase($callableParameterName), $this->twigCallable->getType(), $this->twigCallable->getName()), $this->node->getTemplateLine(), $this->node->getSourceContext());
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->twigCallable->isVariadic()) {
|
||||
$arbitraryArguments = $isPhpVariadic ? new VariadicExpression([], $this->node->getTemplateLine()) : new ArrayExpression([], $this->node->getTemplateLine());
|
||||
foreach ($extractedArguments as $key => $value) {
|
||||
if (\is_int($key)) {
|
||||
$arbitraryArguments->addElement($value);
|
||||
} else {
|
||||
$originalKey = $extractedArgumentNameMap[$key];
|
||||
if ($originalKey !== $this->toSnakeCase($originalKey)) {
|
||||
trigger_deprecation('twig/twig', '3.15', \sprintf('Using "snake_case" for variadic arguments is required for a smooth upgrade with Twig 4.0; rename "%s" to "%s" in "%s" at line %d.', $originalKey, $this->toSnakeCase($originalKey), $this->node->getSourceContext()->getName(), $this->node->getTemplateLine()));
|
||||
}
|
||||
$arbitraryArguments->addElement($value, new ConstantExpression($this->toSnakeCase($originalKey), $this->node->getTemplateLine()));
|
||||
// I Twig 4.0, don't convert the key:
|
||||
// $arbitraryArguments->addElement($value, new ConstantExpression($originalKey, $this->node->getTemplateLine()));
|
||||
}
|
||||
unset($extractedArguments[$key]);
|
||||
}
|
||||
|
||||
if ($arbitraryArguments->count()) {
|
||||
$arguments = array_merge($arguments, $optionalArguments);
|
||||
$arguments[] = $arbitraryArguments;
|
||||
}
|
||||
}
|
||||
|
||||
if ($extractedArguments) {
|
||||
$unknownArgument = null;
|
||||
foreach ($extractedArguments as $extractedArgument) {
|
||||
if ($extractedArgument instanceof Node) {
|
||||
$unknownArgument = $extractedArgument;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
throw new SyntaxError(
|
||||
\sprintf(
|
||||
'Unknown argument%s "%s" for %s "%s(%s)".',
|
||||
\count($extractedArguments) > 1 ? 's' : '', implode('", "', array_keys($extractedArguments)), $this->twigCallable->getType(), $this->twigCallable->getName(), implode(', ', array_map([$this, 'toSnakeCase'], $callableParameterNames))
|
||||
),
|
||||
$unknownArgument ? $unknownArgument->getTemplateLine() : $this->node->getTemplateLine(),
|
||||
$unknownArgument ? $unknownArgument->getSourceContext() : $this->node->getSourceContext()
|
||||
);
|
||||
}
|
||||
|
||||
return $arguments;
|
||||
}
|
||||
|
||||
private function normalizeName(string $name): string
|
||||
{
|
||||
return strtolower(str_replace('_', '', $name));
|
||||
}
|
||||
|
||||
private function toSnakeCase(string $name): string
|
||||
{
|
||||
return strtolower(preg_replace(['/([A-Z]+)([A-Z][a-z])/', '/([a-z0-9])([A-Z])/'], '\1_\2', $name));
|
||||
}
|
||||
|
||||
private function getCallableParameters(): array
|
||||
{
|
||||
$parameters = $this->rc->getReflector()->getParameters();
|
||||
if ($this->node->hasNode('node')) {
|
||||
array_shift($parameters);
|
||||
}
|
||||
if ($this->twigCallable->needsCharset()) {
|
||||
array_shift($parameters);
|
||||
}
|
||||
if ($this->twigCallable->needsEnvironment()) {
|
||||
array_shift($parameters);
|
||||
}
|
||||
if ($this->twigCallable->needsContext()) {
|
||||
array_shift($parameters);
|
||||
}
|
||||
foreach ($this->twigCallable->getArguments() as $argument) {
|
||||
array_shift($parameters);
|
||||
}
|
||||
|
||||
$isPhpVariadic = false;
|
||||
if ($this->twigCallable->isVariadic()) {
|
||||
$argument = end($parameters);
|
||||
$isArray = $argument && $argument->hasType() && $argument->getType() instanceof \ReflectionNamedType && 'array' === $argument->getType()->getName();
|
||||
if ($isArray && $argument->isDefaultValueAvailable() && [] === $argument->getDefaultValue()) {
|
||||
array_pop($parameters);
|
||||
} elseif ($argument && $argument->isVariadic()) {
|
||||
array_pop($parameters);
|
||||
$isPhpVariadic = true;
|
||||
} else {
|
||||
throw new SyntaxError(\sprintf('The last parameter of "%s" for %s "%s" must be an array with default value, eg. "array $arg = []".', $this->rc->getName(), $this->twigCallable->getType(), $this->twigCallable->getName()));
|
||||
}
|
||||
}
|
||||
|
||||
return [$parameters, $isPhpVariadic];
|
||||
}
|
||||
}
|
||||
92
lib/twig/twig/src/Util/ReflectionCallable.php
Normal file
92
lib/twig/twig/src/Util/ReflectionCallable.php
Normal file
@@ -0,0 +1,92 @@
|
||||
<?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\Util;
|
||||
|
||||
use Twig\TwigCallableInterface;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class ReflectionCallable
|
||||
{
|
||||
private $reflector;
|
||||
private $callable;
|
||||
private $name;
|
||||
|
||||
public function __construct(
|
||||
TwigCallableInterface $twigCallable,
|
||||
) {
|
||||
$callable = $twigCallable->getCallable();
|
||||
if (\is_string($callable) && false !== $pos = strpos($callable, '::')) {
|
||||
$callable = [substr($callable, 0, $pos), substr($callable, 2 + $pos)];
|
||||
}
|
||||
|
||||
if (\is_array($callable) && method_exists($callable[0], $callable[1])) {
|
||||
$this->reflector = $r = new \ReflectionMethod($callable[0], $callable[1]);
|
||||
$this->callable = $callable;
|
||||
$this->name = $r->class.'::'.$r->name;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$checkVisibility = $callable instanceof \Closure;
|
||||
try {
|
||||
$closure = \Closure::fromCallable($callable);
|
||||
} catch (\TypeError $e) {
|
||||
throw new \LogicException(\sprintf('Callback for %s "%s" is not callable in the current scope.', $twigCallable->getType(), $twigCallable->getName()), 0, $e);
|
||||
}
|
||||
$this->reflector = $r = new \ReflectionFunction($closure);
|
||||
|
||||
if (str_contains($r->name, '{closure')) {
|
||||
$this->callable = $callable;
|
||||
$this->name = 'Closure';
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($object = $r->getClosureThis()) {
|
||||
$callable = [$object, $r->name];
|
||||
$this->name = get_debug_type($object).'::'.$r->name;
|
||||
} elseif (\PHP_VERSION_ID >= 80111 && $class = $r->getClosureCalledClass()) {
|
||||
$callable = [$class->name, $r->name];
|
||||
$this->name = $class->name.'::'.$r->name;
|
||||
} elseif (\PHP_VERSION_ID < 80111 && $class = $r->getClosureScopeClass()) {
|
||||
$callable = [\is_array($callable) ? $callable[0] : $class->name, $r->name];
|
||||
$this->name = (\is_array($callable) ? $callable[0] : $class->name).'::'.$r->name;
|
||||
} else {
|
||||
$callable = $this->name = $r->name;
|
||||
}
|
||||
|
||||
if ($checkVisibility && \is_array($callable) && method_exists(...$callable) && !(new \ReflectionMethod(...$callable))->isPublic()) {
|
||||
$callable = $r->getClosure();
|
||||
}
|
||||
|
||||
$this->callable = $callable;
|
||||
}
|
||||
|
||||
public function getReflector(): \ReflectionFunctionAbstract
|
||||
{
|
||||
return $this->reflector;
|
||||
}
|
||||
|
||||
public function getCallable()
|
||||
{
|
||||
return $this->callable;
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user