mirror of
https://github.com/twigphp/Twig.git
synced 2026-09-05 06:56:51 +00:00
Merge remote-tracking branch 'origin/3.x' into 4.x
* origin/3.x: Add a `needs_is_sandboxed` option for filters, functions, and tests Bump version Make embeds deterministic [Doc] Document loose comparison in the `in` operator [Doc] Reword whitespace control note about first-newline removal Stop publishing extra package minor versions with no changes Lazy load EscaperRuntime in EscaperExtension Fix typo Replace parent-child analogy in `doc/tags/extends.rst` doc: Add missing toctree entries and fix ordering Fix CHANGELOG Bump version Prepare the 3.24.0 release
This commit is contained in:
@@ -216,6 +216,34 @@ the first argument to the filter call (or the second one if
|
||||
// ...
|
||||
}, ['needs_context' => true, 'needs_environment' => true]);
|
||||
|
||||
Sandbox-aware Filters
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. versionadded:: 3.25
|
||||
|
||||
The ``needs_is_sandboxed`` option was added in Twig 3.25.
|
||||
|
||||
If you want to know whether the current template is sandboxed in your
|
||||
filter, set the ``needs_is_sandboxed`` option to ``true``; Twig will pass the
|
||||
current sandbox state as a boolean to the filter call (as the first
|
||||
argument, or after the charset, the environment, and the context if they
|
||||
are also requested)::
|
||||
|
||||
$filter = new \Twig\TwigFilter('rot13', function (bool $isSandboxed, $string) {
|
||||
if ($isSandboxed) {
|
||||
// adjust behavior when running in a sandboxed template
|
||||
}
|
||||
|
||||
return str_rot13($string);
|
||||
}, ['needs_is_sandboxed' => true]);
|
||||
|
||||
The sandbox state is resolved against the current template source, which
|
||||
means it takes both the global sandbox state and any ``SourcePolicy`` into
|
||||
account.
|
||||
|
||||
The same ``needs_is_sandboxed`` option is also available on functions and
|
||||
tests.
|
||||
|
||||
Automatic Escaping
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
.. _html_attr:
|
||||
|
||||
.. versionadded:: 3.23
|
||||
.. versionadded:: 3.24
|
||||
|
||||
The ``html_attr`` function was added in Twig 3.24.
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ Twig
|
||||
deprecated
|
||||
recipes
|
||||
coding_standards
|
||||
operators_precedence
|
||||
tags/index
|
||||
filters/index
|
||||
functions/index
|
||||
|
||||
@@ -71,8 +71,8 @@ value from the parent template is used instead.
|
||||
|
||||
You can't define multiple ``block`` tags with the same name in the same
|
||||
template. This limitation exists because a block tag works in "both"
|
||||
directions. That is, a block tag doesn't just provide a hole to fill - it also
|
||||
defines the content that fills the hole in the *parent*. If there were two
|
||||
directions. That is, a block tag doesn't just provide a slot to fill - it also
|
||||
defines the content that fills the slot in the *parent*. If there were two
|
||||
similarly-named ``block`` tags in a template, that template's parent wouldn't
|
||||
know which one of the blocks' content to use.
|
||||
|
||||
|
||||
+1
-1
@@ -12,10 +12,10 @@ Tags
|
||||
do
|
||||
embed
|
||||
extends
|
||||
guard
|
||||
flush
|
||||
for
|
||||
from
|
||||
guard
|
||||
if
|
||||
import
|
||||
include
|
||||
|
||||
+16
-1
@@ -780,6 +780,21 @@ operand is contained in the right:
|
||||
You can use this operator to perform a containment test on strings,
|
||||
sequences, mappings, or objects implementing the ``Traversable`` interface.
|
||||
|
||||
.. note::
|
||||
|
||||
For sequences, mappings, and ``Traversable`` objects, ``in`` uses a loose
|
||||
comparison (similar to ``==``); use :doc:`same as <tests/sameas>` for a
|
||||
strict comparison. Like PHP's ``in_array()``, this can yield unexpected
|
||||
results when the left operand is a boolean:
|
||||
|
||||
.. code-block:: twig
|
||||
|
||||
{# returns true because true == 'foo' under PHP loose comparison #}
|
||||
{{ true in ['foo', 'bar'] }}
|
||||
|
||||
Containment on strings only accepts string, integer, and float operands on
|
||||
the left; other types always return ``false``.
|
||||
|
||||
To perform a negative test, use the ``not in`` operator:
|
||||
|
||||
.. code-block:: twig
|
||||
@@ -1181,7 +1196,7 @@ Twig supports two modifiers:
|
||||
|
||||
* *Line whitespace trimming* via the ``~`` modifier: Removes all whitespace
|
||||
(excluding newlines). Using this modifier on the right disables the default
|
||||
removal of the first newline inherited from PHP.
|
||||
removal of the first newline mentioned above.
|
||||
|
||||
The modifiers can be used on either side of the tags like in ``{%-`` or ``-%}``
|
||||
and they consume all whitespace for that side of the tag. It is possible to use
|
||||
|
||||
@@ -10,6 +10,8 @@ Tests
|
||||
empty
|
||||
even
|
||||
iterable
|
||||
mapping
|
||||
null
|
||||
odd
|
||||
sameas
|
||||
sequence
|
||||
|
||||
+2
-1
@@ -10,6 +10,7 @@
|
||||
"string-extra": "extra/string-extra"
|
||||
},
|
||||
"defaults": {
|
||||
"git_constraint": "<1.8.2"
|
||||
"git_constraint": "<1.8.2",
|
||||
"tag_skip": "minor"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ abstract class AbstractTwigCallable implements TwigCallableInterface
|
||||
'needs_environment' => false,
|
||||
'needs_context' => false,
|
||||
'needs_charset' => false,
|
||||
'needs_is_sandboxed' => false,
|
||||
'is_variadic' => false,
|
||||
'deprecation_info' => null,
|
||||
], $options);
|
||||
@@ -95,6 +96,11 @@ abstract class AbstractTwigCallable implements TwigCallableInterface
|
||||
return $this->options['needs_context'];
|
||||
}
|
||||
|
||||
public function needsIsSandboxed(): bool
|
||||
{
|
||||
return $this->options['needs_is_sandboxed'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return static
|
||||
*/
|
||||
@@ -145,6 +151,6 @@ abstract class AbstractTwigCallable implements TwigCallableInterface
|
||||
|
||||
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);
|
||||
return ($this->options['needs_charset'] ? 1 : 0) + ($this->options['needs_environment'] ? 1 : 0) + ($this->options['needs_context'] ? 1 : 0) + ($this->options['needs_is_sandboxed'] ? 1 : 0) + \count($this->arguments);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ final class AsTwigFilter
|
||||
* @param bool|null $needsCharset Whether the filter needs the charset passed as the first argument
|
||||
* @param bool|null $needsEnvironment Whether the filter needs the environment passed as the first argument, or after the charset
|
||||
* @param bool|null $needsContext Whether the filter needs the context array passed as the first argument, or after the charset and the environment
|
||||
* @param bool|null $needsIsSandboxed Whether the filter needs the current sandbox state (a boolean) passed as the first argument, or after the charset, the environment, and the context
|
||||
* @param string[]|null $isSafe List of formats in which you want the raw output to be printed unescaped
|
||||
* @param string|array|null $isSafeCallback Function called at compilation time to determine if the filter is safe
|
||||
* @param string|null $preEscape Some filters may need to work on input that is already escaped or safe
|
||||
@@ -46,6 +47,7 @@ final class AsTwigFilter
|
||||
public ?bool $needsCharset = null,
|
||||
public ?bool $needsEnvironment = null,
|
||||
public ?bool $needsContext = null,
|
||||
public ?bool $needsIsSandboxed = null,
|
||||
public ?array $isSafe = null,
|
||||
public string|array|null $isSafeCallback = null,
|
||||
public ?string $preEscape = null,
|
||||
|
||||
@@ -35,6 +35,7 @@ final class AsTwigFunction
|
||||
* @param bool|null $needsCharset Whether the function needs the charset passed as the first argument
|
||||
* @param bool|null $needsEnvironment Whether the function needs the environment passed as the first argument, or after the charset
|
||||
* @param bool|null $needsContext Whether the function needs the context array passed as the first argument, or after the charset and the environment
|
||||
* @param bool|null $needsIsSandboxed Whether the function needs the current sandbox state (a boolean) passed as the first argument, or after the charset, the environment, and the context
|
||||
* @param string[]|null $isSafe List of formats in which you want the raw output to be printed unescaped
|
||||
* @param string|array|null $isSafeCallback Function called at compilation time to determine if the function is safe
|
||||
* @param DeprecatedCallableInfo|null $deprecationInfo Information about the deprecation
|
||||
@@ -44,6 +45,7 @@ final class AsTwigFunction
|
||||
public ?bool $needsCharset = null,
|
||||
public ?bool $needsEnvironment = null,
|
||||
public ?bool $needsContext = null,
|
||||
public ?bool $needsIsSandboxed = null,
|
||||
public ?array $isSafe = null,
|
||||
public string|array|null $isSafeCallback = null,
|
||||
public ?DeprecatedCallableInfo $deprecationInfo = null,
|
||||
|
||||
@@ -35,6 +35,7 @@ final class AsTwigTest
|
||||
* @param bool|null $needsCharset Whether the test needs the charset passed as the first argument
|
||||
* @param bool|null $needsEnvironment Whether the test needs the environment passed as the first argument, or after the charset
|
||||
* @param bool|null $needsContext Whether the test needs the context array passed as the first argument, or after the charset and the environment
|
||||
* @param bool|null $needsIsSandboxed Whether the test needs the current sandbox state (a boolean) passed as the first argument, or after the charset, the environment, and the context
|
||||
* @param DeprecatedCallableInfo|null $deprecationInfo Information about the deprecation
|
||||
*/
|
||||
public function __construct(
|
||||
@@ -42,6 +43,7 @@ final class AsTwigTest
|
||||
public ?bool $needsCharset = null,
|
||||
public ?bool $needsEnvironment = null,
|
||||
public ?bool $needsContext = null,
|
||||
public ?bool $needsIsSandboxed = null,
|
||||
public ?DeprecatedCallableInfo $deprecationInfo = null,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -95,6 +95,7 @@ final class AttributeExtension extends AbstractExtension
|
||||
'needs_context' => $attribute->needsContext ?? false,
|
||||
'needs_environment' => $attribute->needsEnvironment ?? $this->needsEnvironment($method),
|
||||
'needs_charset' => $attribute->needsCharset ?? false,
|
||||
'needs_is_sandboxed' => $attribute->needsIsSandboxed ?? false,
|
||||
'is_variadic' => $method->isVariadic(),
|
||||
'is_safe' => $attribute->isSafe,
|
||||
'is_safe_callback' => $attribute->isSafeCallback,
|
||||
@@ -118,6 +119,7 @@ final class AttributeExtension extends AbstractExtension
|
||||
'needs_context' => $attribute->needsContext ?? false,
|
||||
'needs_environment' => $attribute->needsEnvironment ?? $this->needsEnvironment($method),
|
||||
'needs_charset' => $attribute->needsCharset ?? false,
|
||||
'needs_is_sandboxed' => $attribute->needsIsSandboxed ?? false,
|
||||
'is_variadic' => $method->isVariadic(),
|
||||
'is_safe' => $attribute->isSafe,
|
||||
'is_safe_callback' => $attribute->isSafeCallback,
|
||||
@@ -139,6 +141,7 @@ final class AttributeExtension extends AbstractExtension
|
||||
'needs_context' => $attribute->needsContext ?? false,
|
||||
'needs_environment' => $attribute->needsEnvironment ?? $this->needsEnvironment($method),
|
||||
'needs_charset' => $attribute->needsCharset ?? false,
|
||||
'needs_is_sandboxed' => $attribute->needsIsSandboxed ?? false,
|
||||
'is_variadic' => $method->isVariadic(),
|
||||
'deprecation_info' => $attribute->deprecationInfo,
|
||||
]);
|
||||
|
||||
@@ -93,6 +93,14 @@ abstract class CallExpression extends AbstractExpression
|
||||
$first = false;
|
||||
}
|
||||
|
||||
if ($twigCallable->needsIsSandboxed()) {
|
||||
if (!$first) {
|
||||
$compiler->raw(', ');
|
||||
}
|
||||
$compiler->raw('$this->env->hasExtension(\Twig\Extension\SandboxExtension::class) && $this->env->getExtension(\Twig\Extension\SandboxExtension::class)->isSandboxed($this->source)');
|
||||
$first = false;
|
||||
}
|
||||
|
||||
foreach ($twigCallable->getArguments() as $argument) {
|
||||
if (!$first) {
|
||||
$compiler->raw(', ');
|
||||
|
||||
+8
-2
@@ -71,6 +71,7 @@ class Parser
|
||||
* @var ModuleNode[]
|
||||
*/
|
||||
private array $embeddedTemplates = [];
|
||||
private int $lastEmbedIndex = 0;
|
||||
private int $varNameSalt = 0;
|
||||
private bool $ignoreUnknownTwigCallables = false;
|
||||
private ExpressionParsers $parsers;
|
||||
@@ -91,8 +92,13 @@ class Parser
|
||||
*/
|
||||
public function parse(TokenStream $stream, $test = null, bool $dropNeedle = false): ModuleNode
|
||||
{
|
||||
// reset on root parse() calls only, so the counter spans nested/reentrant parses
|
||||
if (!$this->stack) {
|
||||
$this->lastEmbedIndex = 0;
|
||||
}
|
||||
|
||||
$vars = get_object_vars($this);
|
||||
unset($vars['stack'], $vars['env'], $vars['handlers'], $vars['visitors'], $vars['reservedMacroNames'], $vars['varNameSalt']);
|
||||
unset($vars['stack'], $vars['env'], $vars['handlers'], $vars['visitors'], $vars['reservedMacroNames'], $vars['lastEmbedIndex'], $vars['varNameSalt']);
|
||||
$this->stack[] = $vars;
|
||||
|
||||
// node visitors
|
||||
@@ -293,7 +299,7 @@ class Parser
|
||||
*/
|
||||
public function embedTemplate(ModuleNode $template)
|
||||
{
|
||||
$template->setIndex(mt_rand());
|
||||
$template->setIndex(++$this->lastEmbedIndex);
|
||||
|
||||
$this->embeddedTemplates[] = $template;
|
||||
}
|
||||
|
||||
@@ -35,6 +35,8 @@ interface TwigCallableInterface extends \Stringable
|
||||
|
||||
public function needsContext(): bool;
|
||||
|
||||
public function needsIsSandboxed(): bool;
|
||||
|
||||
public function withDynamicArguments(string $name, string $dynamicName, array $arguments): self;
|
||||
|
||||
public function getArguments(): array;
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace Twig\Util;
|
||||
use Twig\Error\SyntaxError;
|
||||
use Twig\Node\Expression\AbstractExpression;
|
||||
use Twig\Node\Expression\ArrayExpression;
|
||||
use Twig\Node\Expression\CallExpression;
|
||||
use Twig\Node\Expression\ConstantExpression;
|
||||
use Twig\Node\Expression\VariadicExpression;
|
||||
use Twig\Node\Node;
|
||||
@@ -194,6 +195,9 @@ final class CallableArgumentsExtractor
|
||||
if ($this->twigCallable->needsContext()) {
|
||||
array_shift($parameters);
|
||||
}
|
||||
if ($this->twigCallable->needsIsSandboxed()) {
|
||||
array_shift($parameters);
|
||||
}
|
||||
foreach ($this->twigCallable->getArguments() as $argument) {
|
||||
array_shift($parameters);
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@ class AttributeExtensionTest extends TestCase
|
||||
yield 'with env' => ['with_env_filter', 'withEnvFilter', ['needs_environment' => true]];
|
||||
yield 'with context' => ['with_context_filter', 'withContextFilter', ['needs_context' => true]];
|
||||
yield 'with env and context' => ['with_env_and_context_filter', 'withEnvAndContextFilter', ['needs_environment' => true, 'needs_context' => true]];
|
||||
yield 'with sandbox' => ['with_sandbox_filter', 'withSandboxFilter', ['needs_is_sandboxed' => true]];
|
||||
yield 'variadic' => ['variadic_filter', 'variadicFilter', ['is_variadic' => true]];
|
||||
yield 'deprecated' => ['deprecated_filter', 'deprecatedFilter', ['deprecation_info' => new DeprecatedCallableInfo('foo/bar', '1.2')]];
|
||||
yield 'pattern' => ['pattern_*_filter', 'patternFilter', []];
|
||||
@@ -76,6 +77,7 @@ class AttributeExtensionTest extends TestCase
|
||||
yield 'with env' => ['with_env_function', 'withEnvFunction', ['needs_environment' => true]];
|
||||
yield 'with context' => ['with_context_function', 'withContextFunction', ['needs_context' => true]];
|
||||
yield 'with env and context' => ['with_env_and_context_function', 'withEnvAndContextFunction', ['needs_environment' => true, 'needs_context' => true]];
|
||||
yield 'with sandbox' => ['with_sandbox_function', 'withSandboxFunction', ['needs_is_sandboxed' => true]];
|
||||
yield 'no argument' => ['no_arg_function', 'noArgFunction', []];
|
||||
yield 'variadic' => ['variadic_function', 'variadicFunction', ['is_variadic' => true]];
|
||||
yield 'deprecated' => ['deprecated_function', 'deprecatedFunction', ['deprecation_info' => new DeprecatedCallableInfo('foo/bar', '1.2')]];
|
||||
@@ -104,6 +106,7 @@ class AttributeExtensionTest extends TestCase
|
||||
yield 'with env' => ['with_env_test', 'withEnvTest', ['needs_environment' => true]];
|
||||
yield 'with context' => ['with_context_test', 'withContextTest', ['needs_context' => true]];
|
||||
yield 'with env and context' => ['with_env_and_context_test', 'withEnvAndContextTest', ['needs_environment' => true, 'needs_context' => true]];
|
||||
yield 'with sandbox' => ['with_sandbox_test', 'withSandboxTest', ['needs_is_sandboxed' => true]];
|
||||
yield 'variadic' => ['variadic_test', 'variadicTest', ['is_variadic' => true]];
|
||||
yield 'deprecated' => ['deprecated_test', 'deprecatedTest', ['deprecation_info' => new DeprecatedCallableInfo('foo/bar', '1.2')]];
|
||||
}
|
||||
|
||||
@@ -39,6 +39,11 @@ class ExtensionWithAttributes
|
||||
{
|
||||
}
|
||||
|
||||
#[AsTwigFilter('with_sandbox_filter', needsIsSandboxed: true)]
|
||||
public function withSandboxFilter(bool $isSandboxed, string $string)
|
||||
{
|
||||
}
|
||||
|
||||
#[AsTwigFilter('variadic_filter')]
|
||||
public function variadicFilter(string ...$strings)
|
||||
{
|
||||
@@ -74,6 +79,11 @@ class ExtensionWithAttributes
|
||||
{
|
||||
}
|
||||
|
||||
#[AsTwigFunction('with_sandbox_function', needsIsSandboxed: true)]
|
||||
public function withSandboxFunction(bool $isSandboxed, string $string)
|
||||
{
|
||||
}
|
||||
|
||||
#[AsTwigFunction('no_arg_function')]
|
||||
public function noArgFunction()
|
||||
{
|
||||
@@ -114,6 +124,11 @@ class ExtensionWithAttributes
|
||||
{
|
||||
}
|
||||
|
||||
#[AsTwigTest('with_sandbox_test', needsIsSandboxed: true)]
|
||||
public function withSandboxTest(bool $isSandboxed, $argument)
|
||||
{
|
||||
}
|
||||
|
||||
#[AsTwigTest('deprecated_test', deprecationInfo: new DeprecatedCallableInfo('foo/bar', '1.2'))]
|
||||
public function deprecatedTest($value, $argument)
|
||||
{
|
||||
|
||||
@@ -548,6 +548,76 @@ EOF
|
||||
$this->expectException(SecurityError::class);
|
||||
$twig->load('1_basic')->render([]);
|
||||
}
|
||||
|
||||
public function testNeedsIsSandboxedFilterReceivesTrueWhenSandboxed()
|
||||
{
|
||||
$twig = $this->getEnvironment(true, [], ['index' => '{{ "foo"|sandbox_aware }}'], [], ['sandbox_aware']);
|
||||
$twig->addFilter(new \Twig\TwigFilter('sandbox_aware', static function (bool $isSandboxed, string $value) {
|
||||
return $value.':'.($isSandboxed ? 'on' : 'off');
|
||||
}, ['needs_is_sandboxed' => true]));
|
||||
|
||||
$this->assertSame('foo:on', $twig->load('index')->render([]));
|
||||
}
|
||||
|
||||
public function testNeedsIsSandboxedFilterReceivesFalseWhenNotSandboxed()
|
||||
{
|
||||
$twig = $this->getEnvironment(false, [], ['index' => '{{ "foo"|sandbox_aware }}']);
|
||||
$twig->addFilter(new \Twig\TwigFilter('sandbox_aware', static function (bool $isSandboxed, string $value) {
|
||||
return $value.':'.($isSandboxed ? 'on' : 'off');
|
||||
}, ['needs_is_sandboxed' => true]));
|
||||
|
||||
$this->assertSame('foo:off', $twig->load('index')->render([]));
|
||||
}
|
||||
|
||||
public function testNeedsIsSandboxedFilterFollowsSourcePolicy()
|
||||
{
|
||||
$twig = $this->getEnvironment(false, [], [
|
||||
'in' => '{{ "foo"|sandbox_aware }}',
|
||||
'out' => '{{ "foo"|sandbox_aware }}',
|
||||
], [], ['sandbox_aware'], [], [], [], new class implements \Twig\Sandbox\SourcePolicyInterface {
|
||||
public function enableSandbox(Source $source): bool
|
||||
{
|
||||
return 'in' === $source->getName();
|
||||
}
|
||||
});
|
||||
$twig->addFilter(new \Twig\TwigFilter('sandbox_aware', static function (bool $isSandboxed, string $value) {
|
||||
return $value.':'.($isSandboxed ? 'on' : 'off');
|
||||
}, ['needs_is_sandboxed' => true]));
|
||||
|
||||
$this->assertSame('foo:on', $twig->load('in')->render([]));
|
||||
$this->assertSame('foo:off', $twig->load('out')->render([]));
|
||||
}
|
||||
|
||||
public function testNeedsIsSandboxedFunctionWithoutSandboxExtension()
|
||||
{
|
||||
$loader = new ArrayLoader(['index' => '{{ sandbox_aware("foo") }}']);
|
||||
$twig = new Environment($loader, ['debug' => true, 'cache' => false, 'autoescape' => false]);
|
||||
$twig->addFunction(new \Twig\TwigFunction('sandbox_aware', static function (bool $isSandboxed, string $value) {
|
||||
return $value.':'.($isSandboxed ? 'on' : 'off');
|
||||
}, ['needs_is_sandboxed' => true]));
|
||||
|
||||
$this->assertSame('foo:off', $twig->load('index')->render([]));
|
||||
}
|
||||
|
||||
public function testNeedsIsSandboxedTestReceivesTrueWhenSandboxed()
|
||||
{
|
||||
$twig = $this->getEnvironment(true, [], ['index' => '{{ "foo" is sandbox_aware ? "on" : "off" }}']);
|
||||
$twig->addTest(new \Twig\TwigTest('sandbox_aware', static function (bool $isSandboxed, string $value) {
|
||||
return $isSandboxed && 'foo' === $value;
|
||||
}, ['needs_is_sandboxed' => true]));
|
||||
|
||||
$this->assertSame('on', $twig->load('index')->render([]));
|
||||
}
|
||||
|
||||
public function testNeedsIsSandboxedTestReceivesFalseWhenNotSandboxed()
|
||||
{
|
||||
$twig = $this->getEnvironment(false, [], ['index' => '{{ "foo" is sandbox_aware ? "on" : "off" }}']);
|
||||
$twig->addTest(new \Twig\TwigTest('sandbox_aware', static function (bool $isSandboxed, string $value) {
|
||||
return !$isSandboxed && 'foo' === $value;
|
||||
}, ['needs_is_sandboxed' => true]));
|
||||
|
||||
$this->assertSame('on', $twig->load('index')->render([]));
|
||||
}
|
||||
}
|
||||
|
||||
class ParentClass
|
||||
|
||||
@@ -87,6 +87,14 @@ class FilterTest extends NodeTestCase
|
||||
$node = self::createFilter($environment, new ConstantExpression('foo', 1), 'anonymous');
|
||||
$tests[] = [$node, '$this->env->getFilter(\'anonymous\')->getCallable()("foo")'];
|
||||
|
||||
// needs sandbox
|
||||
$node = self::createFilter($environment, $string, 'bar_sandbox');
|
||||
$tests[] = [$node, 'Twig\Tests\Node\Expression\twig_tests_filter_sandbox($this->env->hasExtension(\Twig\Extension\SandboxExtension::class) && $this->env->getExtension(\Twig\Extension\SandboxExtension::class)->isSandboxed($this->source), "abc")', $environment];
|
||||
|
||||
// needs charset, environment, context, and sandbox
|
||||
$node = self::createFilter($environment, $string, 'bar_all');
|
||||
$tests[] = [$node, 'Twig\Tests\Node\Expression\twig_tests_filter_all($this->env->getCharset(), $this->env, $context, $this->env->hasExtension(\Twig\Extension\SandboxExtension::class) && $this->env->getExtension(\Twig\Extension\SandboxExtension::class)->isSandboxed($this->source), "abc")', $environment];
|
||||
|
||||
// needs environment
|
||||
$node = self::createFilter($environment, $string, 'bar');
|
||||
$tests[] = [$node, 'Twig\Tests\Node\Expression\twig_tests_filter_dummy($this->env, "abc")', $environment];
|
||||
@@ -176,6 +184,8 @@ class FilterTest extends NodeTestCase
|
||||
$env->addFilter(new TwigFilter('bar', 'Twig\Tests\Node\Expression\twig_tests_filter_dummy', ['needs_environment' => true]));
|
||||
$env->addFilter(new TwigFilter('bar_closure', \Closure::fromCallable(twig_tests_filter_dummy::class), ['needs_environment' => true]));
|
||||
$env->addFilter(new TwigFilter('barbar', twig_tests_filter_barbar(...), ['needs_context' => true, 'is_variadic' => true]));
|
||||
$env->addFilter(new TwigFilter('bar_sandbox', twig_tests_filter_sandbox(...), ['needs_is_sandboxed' => true]));
|
||||
$env->addFilter(new TwigFilter('bar_all', twig_tests_filter_all(...), ['needs_charset' => true, 'needs_environment' => true, 'needs_context' => true, 'needs_is_sandboxed' => true]));
|
||||
$env->addFilter(new TwigFilter('magic_static', __NAMESPACE__.'\ChildMagicCallStub::magicStaticCall'));
|
||||
$env->addExtension(new FilterTestExtension());
|
||||
$env->addExtension(self::createExtension());
|
||||
@@ -213,6 +223,14 @@ function twig_tests_filter_barbar($context, $string, $arg1 = null, $arg2 = null,
|
||||
{
|
||||
}
|
||||
|
||||
function twig_tests_filter_sandbox(bool $isSandboxed, $string)
|
||||
{
|
||||
}
|
||||
|
||||
function twig_tests_filter_all(string $charset, Environment $env, array $context, bool $isSandboxed, $string)
|
||||
{
|
||||
}
|
||||
|
||||
class ChildMagicCallStub extends ParentMagicCallStub
|
||||
{
|
||||
public static function identifier()
|
||||
|
||||
@@ -200,6 +200,20 @@ EOF, 'index')));
|
||||
$this->assertTrue($argumentNodes->getNode(3)->getAttribute('value'));
|
||||
}
|
||||
|
||||
public function testEmbeddedTemplatesHaveSequentialIndices(): void
|
||||
{
|
||||
$template = new Source('{% embed "first" %}{% endembed %}{% embed "second" %}{% endembed %}', 'index');
|
||||
$lexer = new Lexer(new Environment(new ArrayLoader()));
|
||||
$stream = $lexer->tokenize($template);
|
||||
|
||||
$embeds = $this->getParser()
|
||||
->parse($stream)
|
||||
->getAttribute('embedded_templates');
|
||||
|
||||
$this->assertSame(1, $embeds->getNode(0)->getAttribute('index'));
|
||||
$this->assertSame(2, $embeds->getNode(1)->getAttribute('index'));
|
||||
}
|
||||
|
||||
protected function getParser()
|
||||
{
|
||||
$parser = new Parser(new Environment(new ArrayLoader()));
|
||||
|
||||
Reference in New Issue
Block a user