diff --git a/CHANGELOG b/CHANGELOG index 59f59f4e0..92e5874e7 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -4,6 +4,9 @@ * Always allow printing a `Markup` object in a sandbox, whatever the security policy is * Remove the `Twig\Sandbox\SourcePolicyInterface` interface and the corresponding argument of `Twig\Extension\SandboxExtension::__construct()` * Enforce the `parent`, `block`, and `attribute` functions against the sandbox `allowedFunctions` allow-list + * Enforce tests against the sandbox `allowedTests` allow-list and add the `$allowedTests` argument to `Twig\Sandbox\SecurityPolicy::__construct()` + * Add a fourth `array $tests` argument to `Twig\Sandbox\SecurityPolicyInterface::checkSecurity()` + * Throw a `SyntaxError` when an `extends`, `use`, or `macro` tag is not at the root of a template # 4.0.0 alpha 1 (2026-05-17) diff --git a/doc/sandbox.rst b/doc/sandbox.rst index 829da175f..3e4bff88a 100644 --- a/doc/sandbox.rst +++ b/doc/sandbox.rst @@ -42,11 +42,10 @@ Everything else won't be allowed and will generate a .. note:: - The ``allowedTests`` argument is available since Twig 3.28 (in earlier - versions all tests were always allowed). Most built-in tests (``empty``, - ``defined``, ``even``, ``same as``, ``iterable``, etc.) are always allowed - and do not need to be listed. Only custom tests and the built-in - ``constant`` test must be allow-listed like filters and functions. + Most built-in tests (``empty``, ``defined``, ``even``, ``same as``, + ``iterable``, etc.) are always allowed and do not need to be listed. Only + custom tests and the built-in ``constant`` test must be allow-listed like + filters and functions. .. note:: @@ -149,12 +148,11 @@ iterated items). That transitive behavior is documented separately under the sandbox model. The criteria above are about what the item itself exposes, not about how its arguments behave. -Built-ins That Will Be Always Allowed in 4.0 -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Built-ins That Are Always Allowed +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The following Twig built-ins meet the criteria above and will have the -``always_allowed_in_sandbox`` flag set in Twig 4.0. They still need to be -explicitly allow-listed in 3.x. +The following Twig built-ins meet the criteria above and have the +``always_allowed_in_sandbox`` flag set, so they never need to be allow-listed. * Tags: ``apply``, ``block``, ``do``, ``for``, ``guard``, ``if``, ``macro``, ``set``, ``types``, ``with``. @@ -165,18 +163,14 @@ explicitly allow-listed in 3.x. ``title``, ``trim``, ``upper``, ``url_encode``. * Functions: ``cycle``, ``max``, ``min``. -When upgrading to 4.0, you can drop these names from your ``SecurityPolicy`` -allow-lists. Leaving them in is harmless: listing a name that is always -allowed has no effect. +Listing one of these names in your ``SecurityPolicy`` is harmless: it has no +effect. The corresponding built-in tests (``defined``, ``divisible by``, ``empty``, ``even``, ``iterable``, ``mapping``, ``none``, ``null``, ``odd``, ``same as``, -``sequence``, ``true``) are **already** flagged as always allowed since Twig -3.28, so they never need to be allow-listed. This is safe because tests were -never enforced by the sandbox before 3.28: flagging them keeps existing -templates working unchanged. The ``constant`` test is the exception: it reaches -into the PHP runtime, so it is not always allowed and must be allow-listed (it -is still implicitly allowed in 3.x with a deprecation, and rejected in 4.0). +``sequence``, ``true``) are also always allowed, so they never need to be +allow-listed. The ``constant`` test is the exception: it reaches into the PHP +runtime, so it is not always allowed and must be allow-listed. Enabling the Sandbox -------------------- diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 348428935..3511d717c 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1,12 +1,5 @@ parameters: ignoreErrors: - - # The "$tests" parameter is documented now and will be part of the signature in 4.0 - message: '#^PHPDoc tag @param references unknown parameter\: \$tests$#' - identifier: parameter.notFound - count: 1 - path: src/Sandbox/SecurityPolicyInterface.php - - - # 2 parameters will be required message: '#^Method Twig\\Node\\IncludeNode\:\:addGetTemplate\(\) invoked with 2 parameters, 1 required\.$#' identifier: arguments.count diff --git a/src/Extension/SandboxExtension.php b/src/Extension/SandboxExtension.php index 3f39aec85..c4da437dc 100644 --- a/src/Extension/SandboxExtension.php +++ b/src/Extension/SandboxExtension.php @@ -65,29 +65,11 @@ final class SandboxExtension extends AbstractExtension return $this->policy; } - public function checkSecurity($tags, $filters, $functions, $tests = [], $source = null): void + public function checkSecurity($tags, $filters, $functions, $tests = [], ?Source $source = null): void { - // BC: previous signature was checkSecurity($tags, $filters, $functions, ?Source $source = null); - // detect a legacy call where the 4th positional argument was the Source. - if ($tests instanceof Source || (null === $tests && \func_num_args() < 5)) { - trigger_deprecation('twig/twig', '3.28', 'Passing a "Twig\Source" as the 4th argument of "%s()" is deprecated; pass an array of tests instead.', __METHOD__); - $source = $tests; - $tests = []; - } - - if (!$this->isSandboxed($source)) { - return; - } - - if ((new \ReflectionMethod($this->policy, 'checkSecurity'))->getNumberOfParameters() >= 4) { + if ($this->isSandboxed($source)) { $this->policy->checkSecurity($tags, $filters, $functions, $tests); - - return; } - - trigger_deprecation('twig/twig', '3.28', 'The "%s::checkSecurity()" method will take a 4th "array $tests" argument in 4.0; not declaring it is deprecated.', $this->policy::class); - - $this->policy->checkSecurity($tags, $filters, $functions); } public function checkMethodAllowed($obj, $method, int $lineno = -1, ?Source $source = null): void diff --git a/src/Node/CheckSecurityNode.php b/src/Node/CheckSecurityNode.php index c3c6d4a51..1ac269826 100644 --- a/src/Node/CheckSecurityNode.php +++ b/src/Node/CheckSecurityNode.php @@ -31,12 +31,8 @@ class CheckSecurityNode extends Node * @param array $usedFunctions * @param array $usedTests */ - public function __construct(array $usedFilters, array $usedTags, array $usedFunctions, array $usedTests = []) + public function __construct(array $usedFilters, array $usedTags, array $usedFunctions, array $usedTests) { - if (\func_num_args() < 4) { - trigger_deprecation('twig/twig', '3.28', 'Not passing the "$usedTests" argument to "%s::__construct()" is deprecated; it will be required in 4.0.', static::class); - } - $this->usedFilters = $usedFilters; $this->usedTags = $usedTags; $this->usedFunctions = $usedFunctions; diff --git a/src/NodeVisitor/CorrectnessNodeVisitor.php b/src/NodeVisitor/CorrectnessNodeVisitor.php index 6fd4e1773..ad6248857 100644 --- a/src/NodeVisitor/CorrectnessNodeVisitor.php +++ b/src/NodeVisitor/CorrectnessNodeVisitor.php @@ -159,7 +159,7 @@ final class CorrectnessNodeVisitor implements NodeVisitorInterface } if (!isset($this->rootNodes[$node])) { - trigger_deprecation('twig/twig', '3.27', 'Using the "%s" tag outside the root of a template is deprecated in %s at line %d.', $node->getNodeTag(), $node->getSourceContext()->getName(), $node->getTemplateLine()); + throw new SyntaxError(\sprintf('The "%s" tag can only be used at the root of a template.', $node->getNodeTag()), $node->getTemplateLine(), $node->getSourceContext()); } } diff --git a/src/Sandbox/SecurityPolicy.php b/src/Sandbox/SecurityPolicy.php index e089450ff..c1095d07b 100644 --- a/src/Sandbox/SecurityPolicy.php +++ b/src/Sandbox/SecurityPolicy.php @@ -42,7 +42,6 @@ final class SecurityPolicy implements SecurityPolicyInterface * @var string[] */ private array $allowedTests; - private bool $strict = false; public function __construct(array $allowedTags = [], array $allowedFilters = [], array $allowedMethods = [], array $allowedProperties = [], array $allowedFunctions = [], array $allowedTests = []) { @@ -100,12 +99,8 @@ final class SecurityPolicy implements SecurityPolicyInterface { } - public function checkSecurity($tags, $filters, $functions, array $tests = []): void + public function checkSecurity($tags, $filters, $functions, array $tests): void { - if (\func_num_args() < 4) { - trigger_deprecation('twig/twig', '3.28', 'Not passing the "$tests" argument to "%s::checkSecurity()" is deprecated; it will be required in 4.0.', static::class); - } - foreach ($tags as $tag) { if (!\in_array($tag, $this->allowedTags, true)) { throw new SecurityNotAllowedTagError(\sprintf('Tag "%s" is not allowed.', $tag), $tag); @@ -126,11 +121,7 @@ final class SecurityPolicy implements SecurityPolicyInterface foreach ($tests as $test) { if (!\in_array($test, $this->allowedTests, true)) { - if (!$this->strict) { - trigger_deprecation('twig/twig', '3.28', 'The "%s" test is always allowed in sandboxes, but won\'t be in 4.0, please enable it explicitly in your sandbox policy if needed (or enable strict mode on the security policy to opt-in to the 4.0 behavior now).', $test); - } else { - throw new SecurityNotAllowedTestError(\sprintf('Test "%s" is not allowed.', $test), $test); - } + throw new SecurityNotAllowedTestError(\sprintf('Test "%s" is not allowed.', $test), $test); } } } diff --git a/src/Sandbox/SecurityPolicyInterface.php b/src/Sandbox/SecurityPolicyInterface.php index cb36ba5be..3e5891d2c 100644 --- a/src/Sandbox/SecurityPolicyInterface.php +++ b/src/Sandbox/SecurityPolicyInterface.php @@ -26,7 +26,7 @@ interface SecurityPolicyInterface * * @throws SecurityError */ - public function checkSecurity($tags, $filters, $functions/* , array $tests */): void; + public function checkSecurity($tags, $filters, $functions, array $tests): void; /** * @param object $obj diff --git a/tests/Extension/SandboxTest.php b/tests/Extension/SandboxTest.php index 89b0c2b00..ad8e64118 100644 --- a/tests/Extension/SandboxTest.php +++ b/tests/Extension/SandboxTest.php @@ -46,7 +46,6 @@ use Twig\Sandbox\SecurityPolicyInterface; use Twig\Source; use Twig\Token; use Twig\TokenParser\AbstractTokenParser; -use Twig\TokenParser\TokenParserInterface; use Twig\TwigFilter; use Twig\TwigFunction; use Twig\TwigTest; @@ -217,7 +216,7 @@ class SandboxTest extends TestCase #[DataProvider('getStrictSandboxRejectsGrandfatheredTagsTests')] public function testStrictSandboxRejectsGrandfatheredTags(string $tag, string $template) { - $twig = $this->getEnvironment(true, [], self::$templates, [], [], [], [], [], true); + $twig = $this->getEnvironment(true, [], self::$templates, [], [], [], [], []); $this->expectException(SecurityNotAllowedTagError::class); $this->expectExceptionMessage(\sprintf('Tag "%s" is not allowed', $tag)); @@ -231,13 +230,10 @@ class SandboxTest extends TestCase yield ['use', '{% use "1_empty" %}']; } - /** - * @dataProvider getStrictSandboxRejectsGrandfatheredFunctionsTests - */ #[DataProvider('getStrictSandboxRejectsGrandfatheredFunctionsTests')] public function testStrictSandboxRejectsGrandfatheredFunctions(string $function, string $templateName, array $extraTemplates, array $allowedTags, array $context) { - $twig = $this->getEnvironment(true, [], $extraTemplates, $allowedTags, [], [], [], [], true); + $twig = $this->getEnvironment(true, [], $extraTemplates, $allowedTags, [], [], [], []); $this->expectException(SecurityNotAllowedFunctionError::class); $this->expectExceptionMessage(\sprintf('Function "%s" is not allowed', $function)); @@ -289,32 +285,17 @@ class SandboxTest extends TestCase [], [], ['parent', 'attribute'], - null, - true, ); $this->assertSame('PARENT CHILD - OK', $twig->load('child')->render(['data' => ['x' => 'OK']])); } - public function testStrictModeCanBeEnabledViaSetterAfterConstruction() - { - $policy = new SecurityPolicy([], [], [], [], []); - $policy->setStrict(true); - - $this->expectException(SecurityNotAllowedTagError::class); - $policy->checkSecurity(['extends'], [], [], []); - } - - /** - * @dataProvider getAlwaysAllowedCoreTests - */ #[DataProvider('getAlwaysAllowedCoreTests')] public function testSandboxAllowsAlwaysAllowedCoreTests(string $template) { // the safe built-in tests are always allowed in a sandbox (they carry - // the `always_allowed_in_sandbox` flag), so they need neither an - // allow-list entry nor strict mode to be opted out of - $twig = $this->getEnvironment(true, [], self::$templates, [], [], [], [], [], true); + // the `always_allowed_in_sandbox` flag), so they need no allow-list entry + $twig = $this->getEnvironment(true, [], self::$templates, [], [], [], [], []); $this->assertSame('y', $twig->createTemplate($template, 'index')->render([])); } @@ -335,43 +316,9 @@ class SandboxTest extends TestCase yield ['{{ {"a": 1} is mapping ? "y" }}']; } - /** - * @group legacy - */ - #[Group('legacy')] - public function testSandboxForConstantTest() - { - // unlike the other built-in tests, "constant" reaches into the PHP - // runtime, so it is not always allowed and is deprecated until 4.0 - $this->expectDeprecation('Since twig/twig 3.28: The "constant" test is always allowed in sandboxes, but won\'t be in 4.0, please enable it explicitly in your sandbox policy if needed (or enable strict mode on the security policy to opt-in to the 4.0 behavior now).'); - - $twig = $this->getEnvironment(true, [], ['index' => '{{ 1 is constant("PHP_INT_MAX") ? "y" }}']); - $twig->load('index')->render([]); - } - - /** - * @group legacy - */ - #[Group('legacy')] - public function testSandboxForUserDefinedTest() - { - $this->expectDeprecation('Since twig/twig 3.28: The "unsafe" test is always allowed in sandboxes, but won\'t be in 4.0, please enable it explicitly in your sandbox policy if needed (or enable strict mode on the security policy to opt-in to the 4.0 behavior now).'); - - $called = 0; - $twig = $this->getEnvironment(true, [], ['index' => '{{ "x" is unsafe ? "y" }}']); - $twig->addTest(new TwigTest('unsafe', static function ($value) use (&$called): bool { - ++$called; - - return true; - })); - - $this->assertSame('y', $twig->load('index')->render([])); - $this->assertSame(1, $called); - } - public function testSandboxAllowsAllowListedTest() { - $twig = $this->getEnvironment(true, [], ['index' => '{{ "x" is unsafe ? "y" }}'], [], [], [], [], [], false, ['unsafe']); + $twig = $this->getEnvironment(true, [], ['index' => '{{ "x" is unsafe ? "y" }}'], [], [], [], [], [], ['unsafe']); $twig->addTest(new TwigTest('unsafe', static fn ($v): bool => true)); $this->assertSame('y', $twig->load('index')->render([])); @@ -380,7 +327,7 @@ class SandboxTest extends TestCase public function testStrictSandboxRejectsConstantTest() { // "constant" is the only built-in test that is not always allowed - $twig = $this->getEnvironment(true, [], ['index' => '{{ 1 is constant("PHP_INT_MAX") ? "y" }}'], [], [], [], [], [], true); + $twig = $this->getEnvironment(true, [], ['index' => '{{ 1 is constant("PHP_INT_MAX") ? "y" }}'], [], [], [], [], []); $this->expectException(SecurityNotAllowedTestError::class); $this->expectExceptionMessage('Test "constant" is not allowed'); @@ -390,14 +337,14 @@ class SandboxTest extends TestCase public function testStrictSandboxStillAllowsAllowListedTest() { - $twig = $this->getEnvironment(true, [], ['index' => '{{ 1 is constant("PHP_INT_MAX") ? "y" : "n" }}'], [], [], [], [], [], true, ['constant']); + $twig = $this->getEnvironment(true, [], ['index' => '{{ 1 is constant("PHP_INT_MAX") ? "y" : "n" }}'], [], [], [], [], [], ['constant']); $this->assertSame('n', $twig->load('index')->render([])); } public function testStrictSandboxRejectsUserDefinedTest() { - $twig = $this->getEnvironment(true, [], ['index' => '{{ "x" is unsafe ? "y" }}'], [], [], [], [], [], true); + $twig = $this->getEnvironment(true, [], ['index' => '{{ "x" is unsafe ? "y" }}'], [], [], [], [], []); $twig->addTest(new TwigTest('unsafe', static fn ($v): bool => true)); $this->expectException(SecurityNotAllowedTestError::class); @@ -411,16 +358,16 @@ class SandboxTest extends TestCase // `{% if %}`, ternary, `?:`, `??`, and `|default` conditions are coerced // to a boolean through `true`/`defined`/`null` tests that the compiler // injects; the template author never wrote them and they are always - // allowed, so they must work even in strict mode with no test allow-listed + // allowed, so they must work with no test allow-listed $template = "{% if x %}a{% endif %}{{ y ? 'b' : 'c' }}{{ z ?: 'd' }}{{ w ?? 'e' }}{{ v|default('f') }}"; - $twig = $this->getEnvironment(true, [], ['index' => $template], ['if'], ['default'], [], [], [], true); + $twig = $this->getEnvironment(true, [], ['index' => $template], ['if'], ['default'], [], [], []); $this->assertSame('acdef', $twig->load('index')->render(['x' => true, 'y' => false])); } public function testStrictSandboxRejectedTestCarriesSourceAndLine() { - $twig = $this->getEnvironment(true, [], ['index' => "{{ 1 }}\n{{ 1 is constant('PHP_INT_MAX') ? 'y' }}"], [], [], [], [], [], true); + $twig = $this->getEnvironment(true, [], ['index' => "{{ 1 }}\n{{ 1 is constant('PHP_INT_MAX') ? 'y' }}"], [], [], [], [], []); try { $twig->load('index')->render([]); @@ -432,10 +379,9 @@ class SandboxTest extends TestCase } } - public function testStrictModeRejectsTestsViaSetter() + public function testSandboxRejectsUnallowedTestViaSecurityPolicy() { $policy = new SecurityPolicy([], [], [], [], []); - $policy->setStrict(true); $this->expectException(SecurityNotAllowedTestError::class); $policy->checkSecurity([], [], [], ['empty']); @@ -444,7 +390,6 @@ class SandboxTest extends TestCase public function testAllowedTestsCanBeUpdatedViaSetter() { $policy = new SecurityPolicy([], [], [], [], []); - $policy->setStrict(true); $policy->setAllowedTests(['empty']); // does not throw @@ -454,58 +399,6 @@ class SandboxTest extends TestCase $policy->checkSecurity([], [], [], ['null']); } - /** - * @group legacy - */ - #[Group('legacy')] - public function testLegacySecurityPolicyWithoutTestsParameterTriggersDeprecation() - { - $this->expectDeprecation('Since twig/twig 3.28: The "Twig\Tests\Extension\LegacySandboxSecurityPolicy::checkSecurity()" method will take a 4th "array $tests" argument in 4.0; not declaring it is deprecated.'); - - $loader = new ArrayLoader(['index' => '{{ "x" is unsafe ? "y" }}']); - $twig = new Environment($loader, ['debug' => true, 'cache' => false, 'autoescape' => false]); - $twig->addExtension(new SandboxExtension(new LegacySandboxSecurityPolicy(), true)); - $twig->addTest(new TwigTest('unsafe', static fn ($v): bool => true)); - - // legacy policies silently allow tests (no security regression vs. today) - $this->assertSame('y', $twig->load('index')->render([])); - } - - /** - * @group legacy - */ - #[Group('legacy')] - public function testLegacyCheckSecurityCallWithSourceAs4thArgumentTriggersDeprecation() - { - $this->expectDeprecation('Since twig/twig 3.28: Passing a "Twig\Source" as the 4th argument of "Twig\Extension\SandboxExtension::checkSecurity()" is deprecated; pass an array of tests instead.'); - - $policy = new SecurityPolicy([], [], [], [], []); - $ext = new SandboxExtension($policy, true); - $ext->checkSecurity([], [], [], new Source('', 'index')); - } - - /** - * @group legacy - */ - #[Group('legacy')] - public function testLegacyCheckSecurityNodeWithoutUsedTestsTriggersDeprecation() - { - $this->expectDeprecation('Since twig/twig 3.28: Not passing the "$usedTests" argument to "Twig\Node\CheckSecurityNode::__construct()" is deprecated; it will be required in 4.0.'); - - new \Twig\Node\CheckSecurityNode([], [], []); - } - - /** - * @group legacy - */ - #[Group('legacy')] - public function testLegacySecurityPolicyCheckSecurityWithoutTestsArgTriggersDeprecation() - { - $this->expectDeprecation('Since twig/twig 3.28: Not passing the "$tests" argument to "Twig\Sandbox\SecurityPolicy::checkSecurity()" is deprecated; it will be required in 4.0.'); - - (new SecurityPolicy([], [], [], [], []))->checkSecurity([], [], []); - } - public function testSandboxWithInheritance() { $twig = $this->getEnvironment(true, [], self::$templates, ['extends', 'block']); @@ -890,7 +783,7 @@ class SandboxTest extends TestCase public function testSandboxBlocksToStringOnIsConstantTestArgument() { - $twig = $this->getEnvironment(true, [], ['index' => '{% if "x" is constant(obj) %}LEAK{% endif %}'], ['if'], [], [], [], [], false, ['constant']); + $twig = $this->getEnvironment(true, [], ['index' => '{% if "x" is constant(obj) %}LEAK{% endif %}'], ['if'], [], [], [], [], ['constant']); try { $twig->load('index')->render(self::$params); $this->fail('Sandbox throws a SecurityError exception if __toString is called on a constant test argument'); @@ -926,7 +819,7 @@ class SandboxTest extends TestCase #[DataProvider('getSandboxAllowedToStringTests')] public function testSandboxAllowedToString($template, $output) { - $twig = $this->getEnvironment(true, [], ['index' => $template], ['set', 'do'], [], [FooObject::class => ['foo', 'getAnotherFooObject']], [], [], false, ['constant']); + $twig = $this->getEnvironment(true, [], ['index' => $template], ['set', 'do'], [], [FooObject::class => ['foo', 'getAnotherFooObject']], [], [], ['constant']); $this->assertEquals($output, $twig->load('index')->render(self::$params)); } @@ -1697,17 +1590,16 @@ EOF $policy->setStrict(true); $policy->setStrict(false); - // 4.0 behavior is the default and unaffected by setStrict() + // sandbox rejection is the default and unaffected by setStrict() $this->expectException(SecurityNotAllowedTagError::class); - $policy->checkSecurity(['extends'], [], []); + $policy->checkSecurity(['extends'], [], [], []); } - protected function getEnvironment($sandboxed, $options, $templates, $tags = [], $filters = [], $methods = [], $properties = [], $functions = [], bool $strict = false, array $tests = []) + protected function getEnvironment($sandboxed, $options, $templates, $tags = [], $filters = [], $methods = [], $properties = [], $functions = [], array $tests = []) { $loader = new ArrayLoader($templates); $twig = new Environment($loader, array_merge(['debug' => true, 'cache' => false, 'autoescape' => false], $options)); $policy = new SecurityPolicy($tags, $filters, $methods, $properties, $functions, $tests); - $policy->setStrict($strict); $twig->addExtension(new SandboxExtension($policy, $sandboxed)); return $twig; @@ -1746,7 +1638,7 @@ EOF public function testNeedsIsSandboxedTestReceivesTrueWhenSandboxed() { - $twig = $this->getEnvironment(true, [], ['index' => '{{ "foo" is sandbox_aware ? "on" : "off" }}'], [], [], [], [], [], false, ['sandbox_aware']); + $twig = $this->getEnvironment(true, [], ['index' => '{{ "foo" is sandbox_aware ? "on" : "off" }}'], [], [], [], [], [], ['sandbox_aware']); $twig->addTest(new TwigTest('sandbox_aware', static function (bool $isSandboxed, string $value) { return $isSandboxed && 'foo' === $value; }, ['needs_is_sandboxed' => true])); @@ -1802,7 +1694,7 @@ EOF public function testAlwaysAllowedInSandboxTestBypassesAllowList() { - $twig = $this->getEnvironment(true, [], ['index' => '{{ 4 is safe_even ? "yes" : "no" }}'], [], [], [], [], [], true); + $twig = $this->getEnvironment(true, [], ['index' => '{{ 4 is safe_even ? "yes" : "no" }}'], [], [], [], [], []); $twig->addTest(new TwigTest('safe_even', static fn ($value) => 0 === $value % 2, ['always_allowed_in_sandbox' => true])); $this->assertSame('yes', $twig->load('index')->render([])); @@ -1810,7 +1702,7 @@ EOF public function testAlwaysAllowedInSandboxTestStillEnforcedWhenFlagNotSet() { - $twig = $this->getEnvironment(true, [], ['index' => '{{ 4 is gated_even ? "yes" : "no" }}'], [], [], [], [], [], true); + $twig = $this->getEnvironment(true, [], ['index' => '{{ 4 is gated_even ? "yes" : "no" }}'], [], [], [], [], []); $twig->addTest(new TwigTest('gated_even', static fn ($value) => 0 === $value % 2)); $this->expectException(SecurityNotAllowedTestError::class); @@ -2100,7 +1992,7 @@ class GatedSandboxTokenParser extends AbstractTokenParser class DenyEverythingSecurityPolicy implements SecurityPolicyInterface { - public function checkSecurity($tags, $filters, $functions): void + public function checkSecurity($tags, $filters, $functions, array $tests): void { } @@ -2114,18 +2006,3 @@ class DenyEverythingSecurityPolicy implements SecurityPolicyInterface throw new SecurityNotAllowedPropertyError(\sprintf('Calling "%s" property on a "%s" object is not allowed.', $property, $obj::class), $obj::class, $property); } } - -class LegacySandboxSecurityPolicy implements SecurityPolicyInterface -{ - public function checkSecurity($tags, $filters, $functions): void - { - } - - public function checkMethodAllowed($obj, $method): void - { - } - - public function checkPropertyAllowed($obj, $property): void - { - } -} diff --git a/tests/Fixtures/tags/inheritance/extends_in_condition.legacy.test b/tests/Fixtures/tags/inheritance/extends_in_condition.test similarity index 51% rename from tests/Fixtures/tags/inheritance/extends_in_condition.legacy.test rename to tests/Fixtures/tags/inheritance/extends_in_condition.test index f3212e944..2ccf756fc 100644 --- a/tests/Fixtures/tags/inheritance/extends_in_condition.legacy.test +++ b/tests/Fixtures/tags/inheritance/extends_in_condition.test @@ -1,7 +1,5 @@ --TEST-- "extends" tag in a condition ---DEPRECATION-- -Since twig/twig 3.27: Using the "extends" tag outside the root of a template is deprecated in index.twig at line 3. --TEMPLATE-- {% if false %} {% extends "base.twig" %} @@ -9,4 +7,5 @@ Since twig/twig 3.27: Using the "extends" tag outside the root of a template is --TEMPLATE(base.twig)-- --DATA-- return [] ---EXPECT-- +--EXCEPTION-- +Twig\Error\SyntaxError: The "extends" tag can only be used at the root of a template in "index.twig" at line 3. diff --git a/tests/Fixtures/tags/inheritance/use_in_condition.legacy.test b/tests/Fixtures/tags/inheritance/use_in_condition.test similarity index 50% rename from tests/Fixtures/tags/inheritance/use_in_condition.legacy.test rename to tests/Fixtures/tags/inheritance/use_in_condition.test index 2551f3f9c..16043c1a7 100644 --- a/tests/Fixtures/tags/inheritance/use_in_condition.legacy.test +++ b/tests/Fixtures/tags/inheritance/use_in_condition.test @@ -1,7 +1,5 @@ --TEST-- "use" tag in a condition ---DEPRECATION-- -Since twig/twig 3.27: Using the "use" tag outside the root of a template is deprecated in index.twig at line 3. --TEMPLATE-- {% if false %} {% use "base.twig" %} @@ -9,4 +7,5 @@ Since twig/twig 3.27: Using the "use" tag outside the root of a template is depr --TEMPLATE(base.twig)-- --DATA-- return [] ---EXPECT-- +--EXCEPTION-- +Twig\Error\SyntaxError: The "use" tag can only be used at the root of a template in "index.twig" at line 3. diff --git a/tests/Fixtures/tags/inheritance/use_in_macro.legacy.test b/tests/Fixtures/tags/inheritance/use_in_macro.test similarity index 68% rename from tests/Fixtures/tags/inheritance/use_in_macro.legacy.test rename to tests/Fixtures/tags/inheritance/use_in_macro.test index a1deeb467..3658b3585 100644 --- a/tests/Fixtures/tags/inheritance/use_in_macro.legacy.test +++ b/tests/Fixtures/tags/inheritance/use_in_macro.test @@ -1,7 +1,5 @@ --TEST-- "use" tag in a macro ---DEPRECATION-- -Since twig/twig 3.27: Using the "use" tag outside the root of a template is deprecated in index.twig at line 3. --TEMPLATE-- {% macro input(name, value, type, size) %} {% use "base.twig" %} @@ -11,4 +9,5 @@ Since twig/twig 3.27: Using the "use" tag outside the root of a template is depr --TEMPLATE(base.twig)-- --DATA-- return [] ---EXPECT-- +--EXCEPTION-- +Twig\Error\SyntaxError: The "use" tag can only be used at the root of a template in "index.twig" at line 3. diff --git a/tests/Fixtures/tags/macro/macro_in_block.legacy.test b/tests/Fixtures/tags/macro/macro_in_block.test similarity index 67% rename from tests/Fixtures/tags/macro/macro_in_block.legacy.test rename to tests/Fixtures/tags/macro/macro_in_block.test index 569e1662b..a846046e2 100644 --- a/tests/Fixtures/tags/macro/macro_in_block.legacy.test +++ b/tests/Fixtures/tags/macro/macro_in_block.test @@ -1,7 +1,5 @@ --TEST-- "macro" tag in a block ---DEPRECATION-- -Since twig/twig 3.27: Using the "macro" tag outside the root of a template is deprecated in index.twig at line 3. --TEMPLATE-- {% block foo %} {% macro input(name, value, type, size) %} @@ -10,4 +8,5 @@ Since twig/twig 3.27: Using the "macro" tag outside the root of a template is de {% endblock %} --DATA-- return [] ---EXPECT-- +--EXCEPTION-- +Twig\Error\SyntaxError: The "macro" tag can only be used at the root of a template in "index.twig" at line 3. diff --git a/tests/Fixtures/tags/macro/macro_in_condition.legacy.test b/tests/Fixtures/tags/macro/macro_in_condition.test similarity index 67% rename from tests/Fixtures/tags/macro/macro_in_condition.legacy.test rename to tests/Fixtures/tags/macro/macro_in_condition.test index 65f5ac0f6..7ff0d745f 100644 --- a/tests/Fixtures/tags/macro/macro_in_condition.legacy.test +++ b/tests/Fixtures/tags/macro/macro_in_condition.test @@ -1,7 +1,5 @@ --TEST-- "macro" tag in a condition ---DEPRECATION-- -Since twig/twig 3.27: Using the "macro" tag outside the root of a template is deprecated in index.twig at line 3. --TEMPLATE-- {% if false %} {% macro input(name, value, type, size) %} @@ -10,4 +8,5 @@ Since twig/twig 3.27: Using the "macro" tag outside the root of a template is de {% endif %} --DATA-- return [] ---EXPECT-- +--EXCEPTION-- +Twig\Error\SyntaxError: The "macro" tag can only be used at the root of a template in "index.twig" at line 3.