diff --git a/CHANGELOG b/CHANGELOG index 7e6e5b153cb..6a5509cfa95 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,7 @@ # 3.15.0 (2024-XX-XX) - + + * Deprecate the `include` function, use `render` instead + * Add the `render` function to replace the `include` one * Add Spanish inflector support for the `plural` and `singular` filters in the String extension * Deprecate `TempNameExpression` in favor of `LocalVariable` * Deprecate `NameExpression` in favor of `ContextVariable` diff --git a/doc/deprecated.rst b/doc/deprecated.rst index fed5cb38017..1a771665aa1 100644 --- a/doc/deprecated.rst +++ b/doc/deprecated.rst @@ -28,6 +28,11 @@ Functions Note that it won't be removed in 4.0 to allow a smoother upgrade path. +* The ``include`` function is deprecated as of Twig 3.15, use ``render`` + instead. The ``render`` function does the same as ``include``, but the + rendered template never has access to the current context (all variables must + be passed explicitely). + Extensions ---------- diff --git a/doc/functions/include.rst b/doc/functions/include.rst index c5dfc9a9f6d..3d8a0f3ea8d 100644 --- a/doc/functions/include.rst +++ b/doc/functions/include.rst @@ -1,6 +1,11 @@ ``include`` =========== +.. warning:: + + The ``include`` function is deprecated as of Twig 3.15, use the ``render`` + function instead. + The ``include`` function returns the rendered content of a template: .. code-block:: twig diff --git a/doc/functions/index.rst b/doc/functions/index.rst index 557f6938a30..7bb249f5c63 100644 --- a/doc/functions/index.rst +++ b/doc/functions/index.rst @@ -15,6 +15,7 @@ Functions html_classes html_cva include + render max min parent diff --git a/doc/functions/render.rst b/doc/functions/render.rst new file mode 100644 index 00000000000..e39b08ca6de --- /dev/null +++ b/doc/functions/render.rst @@ -0,0 +1,90 @@ +``render`` +========== + +.. versionadded:: 3.15 + + The ``render`` function was added in Twig 3.15. + +The ``render`` function returns the rendered content of a template: + +.. code-block:: twig + + {{ render('template.html') }} + +Templates +--------- + +Templates are loaded via the current Twig loader. + +Templates can be a string or an expression evaluating to a string: + +.. code-block:: twig + + {{ render('template.html') }} + {{ render(template_var) }} + +A template can also be an instance of ``\Twig\Template`` or a +``\Twig\TemplateWrapper``:: + + $template = $twig->load('some_template.twig'); + $twig->display('template.twig', ['template' => $template]); + + // You can render it like this: + // {{ render(template) }} + +When you set the ``ignore_missing`` flag, Twig will return an empty string if +the template does not exist: + +.. code-block:: twig + + {{ render('sidebar.html', ignore_missing = true) }} + +You can also provide a list of templates that are checked for existence. The +first template that exists will be rendered: + +.. code-block:: twig + + {{ render(['page_detailed.html', 'page.html']) }} + +If ``ignore_missing`` is set, it will fall back to rendering nothing if none +of the templates exist, otherwise it will throw an exception. + +Variables +--------- + +Rendered templates **do not** have access to the variables defined in the +context, but you can pass variables explicitely: + +.. code-block:: twig + + {# template.html will have access to the "name" variable #} + + {{ render('template.html', { name: 'Fabien' }) }} + +You can pass existing variables from the current context: + +.. code-block:: twig + + {{ render('template.html', { first_name: first_name, last_name: last_name }) }} + + {# or using the following shortcut #} + + {{ render('template.html', { first_name, last_name }) }} + +Sandboxing +---------- + +When rendering a template created by an end user, you should consider +:doc:`sandboxing<../sandbox>` it: + +.. code-block:: twig + + {{ render('page.html', sandboxed: true) }} + +Arguments +--------- + +* ``template``: The template to render +* ``variables``: The variables to pass to the template +* ``ignore_missing``: Whether to ignore missing templates or not +* ``sandboxed``: Whether to sandbox the template or not diff --git a/src/Extension/CoreExtension.php b/src/Extension/CoreExtension.php index f8f80ac2f0c..b9cc30c42c9 100644 --- a/src/Extension/CoreExtension.php +++ b/src/Extension/CoreExtension.php @@ -265,7 +265,8 @@ public function getFunctions(): array new TwigFunction('cycle', [self::class, 'cycle']), new TwigFunction('random', [self::class, 'random'], ['needs_charset' => true]), new TwigFunction('date', [$this, 'convertDate']), - new TwigFunction('include', [self::class, 'include'], ['needs_environment' => true, 'needs_context' => true, 'is_safe' => ['all']]), + new TwigFunction('include', [self::class, 'include'], ['needs_environment' => true, 'needs_context' => true, 'is_safe' => ['all'], 'deprecation_info' => new DeprecatedCallableInfo('twig/twig', '3.15', 'render')]), + new TwigFunction('render', [self::class, 'render'], ['needs_environment' => true, 'is_safe' => ['all']]), new TwigFunction('source', [self::class, 'source'], ['needs_environment' => true, 'is_safe' => ['all']]), new TwigFunction('enum_cases', [self::class, 'enumCases'], ['node_class' => EnumCasesFunction::class]), new TwigFunction('enum', [self::class, 'enum'], ['node_class' => EnumFunction::class]), @@ -1468,6 +1469,52 @@ public static function include(Environment $env, $context, $template, $variables } } + /** + * Renders a template. + * + * @param string|array|TemplateWrapper $template The template to render or an array of templates to try consecutively + * @param array $variables The variables to pass to the template + * @param bool $ignoreMissing Whether to ignore missing templates or not + * @param bool $sandboxed Whether to sandbox the template or not + * + * @internal + */ + public static function render(Environment $env, $template, $variables = [], $ignoreMissing = false, $sandboxed = false): string + { + $alreadySandboxed = false; + $sandbox = null; + + if ($isSandboxed = $sandboxed && $env->hasExtension(SandboxExtension::class)) { + $sandbox = $env->getExtension(SandboxExtension::class); + if (!$alreadySandboxed = $sandbox->isSandboxed()) { + $sandbox->enableSandbox(); + } + } + + try { + $loaded = null; + try { + $loaded = $env->resolveTemplate($template); + } catch (LoaderError $e) { + if (!$ignoreMissing) { + throw $e; + } + + return ''; + } + + if ($isSandboxed) { + $loaded->unwrap()->checkSecurity(); + } + + return $loaded->render($variables); + } finally { + if ($isSandboxed && !$alreadySandboxed) { + $sandbox->disableSandbox(); + } + } + } + /** * Returns a template content without rendering it. * diff --git a/src/NodeVisitor/OptimizerNodeVisitor.php b/src/NodeVisitor/OptimizerNodeVisitor.php index a943f45c309..84084fc393d 100644 --- a/src/NodeVisitor/OptimizerNodeVisitor.php +++ b/src/NodeVisitor/OptimizerNodeVisitor.php @@ -158,6 +158,7 @@ private function enterOptimizeFor(Node $node): void } // include function without the with_context=false parameter + // to be removed in 4.0 elseif ($node instanceof FunctionExpression && 'include' === $node->getAttribute('name') && (!$node->getNode('arguments')->hasNode('with_context') diff --git a/tests/Extension/CoreTest.php b/tests/Extension/CoreTest.php index 6bb74807f86..33a81931680 100644 --- a/tests/Extension/CoreTest.php +++ b/tests/Extension/CoreTest.php @@ -360,13 +360,13 @@ public static function provideCompareCases() ]; } - public function testSandboxedInclude() + public function testSandboxedRender() { $twig = new Environment(new ArrayLoader([ - 'index' => '{{ include("included", sandboxed: true) }}', + 'index' => '{{ render("included", sandboxed: true) }}', 'included' => '{{ "included"|e }}', ])); - $policy = new SecurityPolicy(allowedFunctions: ['include']); + $policy = new SecurityPolicy(allowedFunctions: ['render']); $sandbox = new SandboxExtension($policy, false); $twig->addExtension($sandbox); @@ -378,10 +378,10 @@ public function testSandboxedInclude() public function testSandboxedIncludeWithPreloadedTemplate() { $twig = new Environment(new ArrayLoader([ - 'index' => '{{ include("included", sandboxed: true) }}', + 'index' => '{{ render("included", sandboxed: true) }}', 'included' => '{{ "included"|e }}', ])); - $policy = new SecurityPolicy(allowedFunctions: ['include']); + $policy = new SecurityPolicy(allowedFunctions: ['render']); $sandbox = new SandboxExtension($policy, false); $twig->addExtension($sandbox); diff --git a/tests/Extension/SandboxTest.php b/tests/Extension/SandboxTest.php index e9115fa498e..35d4626a04f 100644 --- a/tests/Extension/SandboxTest.php +++ b/tests/Extension/SandboxTest.php @@ -50,18 +50,18 @@ protected function setUp(): void '1_basic3' => '{% if name %}foo{% endif %}', '1_basic4' => '{{ obj.bar }}', '1_basic5' => '{{ obj }}', - '1_basic7' => '{{ cycle(["foo","bar"], 1) }}', + '1_basic7' => '{{ cycle(["foo", "bar"], 1) }}', '1_basic8' => '{{ obj.getfoobar }}{{ obj.getFooBar }}', '1_basic9' => '{{ obj.foobar }}{{ obj.fooBar }}', '1_basic' => '{% if obj.foo %}{{ obj.foo|upper }}{% endif %}', '1_layout' => '{% block content %}{% endblock %}', '1_child' => "{% extends \"1_layout\" %}\n{% block content %}\n{{ \"a\"|json_encode }}\n{% endblock %}", - '1_include' => '{{ include("1_basic1", sandboxed=true) }}', - '1_basic2_include_template_from_string_sandboxed' => '{{ include(template_from_string("{{ name|upper }}"), sandboxed=true) }}', - '1_basic2_include_template_from_string' => '{{ include(template_from_string("{{ name|upper }}")) }}', + '1_render' => '{{ render("1_basic1", { obj }, sandboxed: true) }}', + '1_basic2_include_template_from_string_sandboxed' => '{{ render(template_from_string("{{ name|upper }}"), sandboxed=true) }}', + '1_basic2_include_template_from_string' => '{{ render(template_from_string("{{ name|upper }}"), { name }) }}', '1_range_operator' => '{{ (1..2)[0] }}', '1_syntax_error_wrapper_legacy' => '{% sandbox %}{% include "1_syntax_error" %}{% endsandbox %}', - '1_syntax_error_wrapper' => '{{ include("1_syntax_error", sandboxed: true) }}', + '1_syntax_error_wrapper' => '{{ render("1_syntax_error", sandboxed: true) }}', '1_syntax_error' => '{% syntax error }}', '1_childobj_parentmethod' => '{{ child_obj.ParentMethod() }}', '1_childobj_childmethod' => '{{ child_obj.ChildMethod() }}', @@ -193,7 +193,7 @@ public function testSandboxGloballyFalseUnallowedFilterWithIncludeTemplateFromSt public function testSandboxGloballyTrueUnallowedFilterWithIncludeTemplateFromStringSandboxed() { - $twig = $this->getEnvironment(true, [], self::$templates, [], [], [], [], ['include', 'template_from_string']); + $twig = $this->getEnvironment(true, [], self::$templates, [], [], [], [], ['render', 'template_from_string']); $twig->addExtension(new StringLoaderExtension()); try { $twig->load('1_basic2_include_template_from_string_sandboxed')->render(self::$params); @@ -212,7 +212,7 @@ public function testSandboxGloballyFalseUnallowedFilterWithIncludeTemplateFromSt public function testSandboxGloballyTrueUnallowedFilterWithIncludeTemplateFromStringNotSandboxed() { - $twig = $this->getEnvironment(true, [], self::$templates, [], [], [], [], ['include', 'template_from_string']); + $twig = $this->getEnvironment(true, [], self::$templates, [], [], [], [], ['render', 'template_from_string']); $twig->addExtension(new StringLoaderExtension()); try { $twig->load('1_basic2_include_template_from_string')->render(self::$params); @@ -414,11 +414,11 @@ public function testSandboxLocallySetForAnInclude() $this->assertEquals('fooFOOfoo', $twig->load('2_basic')->render(self::$params), 'Sandbox does nothing if disabled globally and sandboxed not used for the include'); self::$templates = [ - '3_basic' => '{{ include("3_included", sandboxed: true) }}', + '3_basic' => '{{ render("3_included", sandboxed: true) }}', '3_included' => '{% if true %}{{ "foo"|upper }}{% endif %}', ]; - $twig = $this->getEnvironment(true, [], self::$templates, functions: ['include']); + $twig = $this->getEnvironment(true, [], self::$templates, functions: ['render']); try { $twig->load('3_basic')->render(self::$params); $this->fail('Sandbox throws a SecurityError exception when the included file is sandboxed'); @@ -441,20 +441,20 @@ public function testMacrosInASandbox() $this->assertEquals('

username

', $twig->load('index')->render([])); } - public function testSandboxDisabledAfterIncludeFunctionError() + public function testSandboxDisabledAfterRenderFunctionError() { $twig = $this->getEnvironment(false, [], self::$templates); $e = null; try { - $twig->load('1_include')->render(self::$params); + $twig->load('1_render')->render(self::$params); } catch (\Throwable $e) { } if (null === $e) { $this->fail('An exception should be thrown for this test to be valid.'); } - $this->assertFalse($twig->getExtension(SandboxExtension::class)->isSandboxed(), 'Sandboxed include() function call should not leave Sandbox enabled when an error occurs.'); + $this->assertFalse($twig->getExtension(SandboxExtension::class)->isSandboxed(), 'Sandboxed render() function call should not leave Sandbox enabled when an error occurs.'); } public function testSandboxWithNoClosureFilter() diff --git a/tests/Fixtures/autoescape/block.test b/tests/Fixtures/autoescape/block.test index a80b80c376e..f67545595a7 100644 --- a/tests/Fixtures/autoescape/block.test +++ b/tests/Fixtures/autoescape/block.test @@ -1,8 +1,8 @@ --TEST-- blocks and autoescape --TEMPLATE-- -{{ include('unrelated.txt.twig') -}} -{{ include('template.html.twig') -}} +{{ render('unrelated.txt.twig') -}} +{{ render('template.html.twig', { br }) -}} --TEMPLATE(unrelated.txt.twig)-- {% block content %}{% endblock %} --TEMPLATE(template.html.twig)-- diff --git a/tests/Fixtures/autoescape/name.test b/tests/Fixtures/autoescape/name.test index 5ad573cf0aa..4129041ac40 100644 --- a/tests/Fixtures/autoescape/name.test +++ b/tests/Fixtures/autoescape/name.test @@ -2,9 +2,9 @@ "name" autoescape strategy --TEMPLATE-- {{ br -}} -{{ include('index.js.twig') -}} -{{ include('index.html.twig') -}} -{{ include('index.txt.twig') -}} +{{ render('index.js.twig', { br }) -}} +{{ render('index.html.twig', { br }) -}} +{{ render('index.txt.twig', { br }) -}} --TEMPLATE(index.js.twig)-- {{ br -}} --TEMPLATE(index.html.twig)-- diff --git a/tests/Fixtures/exceptions/multiline_function_with_undefined_variable.test b/tests/Fixtures/exceptions/multiline_function_with_undefined_variable.test index 8bc524ef181..ccf56f52651 100644 --- a/tests/Fixtures/exceptions/multiline_function_with_undefined_variable.test +++ b/tests/Fixtures/exceptions/multiline_function_with_undefined_variable.test @@ -1,12 +1,12 @@ --TEST-- -Exception for multile function with undefined variable +Exception for multi-line function with undefined variable --TEMPLATE-- -{{ include('foo', - with_context=with_context +{{ render('foo', + sandboxed=with_sandbox ) }} --TEMPLATE(foo)-- Foo --DATA-- return [] --EXCEPTION-- -Twig\Error\RuntimeError: Variable "with_context" does not exist in "index.twig" at line 3. +Twig\Error\RuntimeError: Variable "with_sandbox" does not exist in "index.twig" at line 3. diff --git a/tests/Fixtures/exceptions/undefined_template_in_child_template.test b/tests/Fixtures/exceptions/undefined_template_in_child_template.test index 904faa50ad4..95e094df971 100644 --- a/tests/Fixtures/exceptions/undefined_template_in_child_template.test +++ b/tests/Fixtures/exceptions/undefined_template_in_child_template.test @@ -4,7 +4,7 @@ Exception for an undefined template in a child template {% extends 'base.twig' %} {% block sidebar %} - {{ include('include.twig') }} + {{ render('include.twig') }} {% endblock %} --TEMPLATE(base.twig)-- {% block sidebar %} diff --git a/tests/Fixtures/functions/include/assignment.legacy.test b/tests/Fixtures/functions/include/assignment.legacy.test new file mode 100644 index 00000000000..2af3cb4413b --- /dev/null +++ b/tests/Fixtures/functions/include/assignment.legacy.test @@ -0,0 +1,15 @@ +--TEST-- +"include" function +--DEPRECATION-- +Since twig/twig 3.15: Twig Function "include" is deprecated; use "render" instead in index.twig at line 2. +--TEMPLATE-- +{% set tmp = include("foo.twig") %} + +FOO{{ tmp }}BAR +--TEMPLATE(foo.twig)-- +FOOBAR +--DATA-- +return [] +--EXPECT-- +FOO +FOOBARBAR diff --git a/tests/Fixtures/functions/include/autoescaping.legacy.test b/tests/Fixtures/functions/include/autoescaping.legacy.test new file mode 100644 index 00000000000..d48af5b26fa --- /dev/null +++ b/tests/Fixtures/functions/include/autoescaping.legacy.test @@ -0,0 +1,12 @@ +--TEST-- +"include" function is safe for auto-escaping +--DEPRECATION-- +Since twig/twig 3.15: Twig Function "include" is deprecated; use "render" instead in index.twig at line 2. +--TEMPLATE-- +{{ include("foo.twig") }} +--TEMPLATE(foo.twig)-- +

Test

+--DATA-- +return [] +--EXPECT-- +

Test

diff --git a/tests/Fixtures/functions/include/basic.legacy.test b/tests/Fixtures/functions/include/basic.legacy.test new file mode 100644 index 00000000000..b8741e45ad0 --- /dev/null +++ b/tests/Fixtures/functions/include/basic.legacy.test @@ -0,0 +1,19 @@ +--TEST-- +"include" function +--DEPRECATION-- +Since twig/twig 3.15: Twig Function "include" is deprecated; use "render" instead in index.twig at line 3. +--TEMPLATE-- +FOO +{{ include("foo.twig") }} + +BAR +--TEMPLATE(foo.twig)-- +FOOBAR +--DATA-- +return [] +--EXPECT-- +FOO + +FOOBAR + +BAR diff --git a/tests/Fixtures/functions/include/expression.test b/tests/Fixtures/functions/include/expression.legacy.test similarity index 63% rename from tests/Fixtures/functions/include/expression.test rename to tests/Fixtures/functions/include/expression.legacy.test index c6d3d1c5331..860865f5377 100644 --- a/tests/Fixtures/functions/include/expression.test +++ b/tests/Fixtures/functions/include/expression.legacy.test @@ -1,5 +1,7 @@ --TEST-- "include" function allows expressions for the template to include +--DEPRECATION-- +Since twig/twig 3.15: Twig Function "include" is deprecated; use "render" instead in index.twig at line 3. --TEMPLATE-- FOO {{ include(foo) }} diff --git a/tests/Fixtures/functions/include/ignore_missing.legacy.test b/tests/Fixtures/functions/include/ignore_missing.legacy.test new file mode 100644 index 00000000000..69b8a50a525 --- /dev/null +++ b/tests/Fixtures/functions/include/ignore_missing.legacy.test @@ -0,0 +1,15 @@ +--TEST-- +"include" function +--DEPRECATION-- +Since twig/twig 3.15: Twig Function "include" is deprecated; use "render" instead in index.twig at line 2. +Since twig/twig 3.15: Twig Function "include" is deprecated; use "render" instead in index.twig at line 3. +Since twig/twig 3.15: Twig Function "include" is deprecated; use "render" instead in index.twig at line 4. +Since twig/twig 3.15: Twig Function "include" is deprecated; use "render" instead in index.twig at line 5. +--TEMPLATE-- +{{ include(["foo.twig", "bar.twig"], ignore_missing = true) }} +{{ include("foo.twig", ignore_missing = true) }} +{{ include("foo.twig", ignore_missing = true, variables = {}) }} +{{ include("foo.twig", ignore_missing = true, variables = {}, with_context = true) }} +--DATA-- +return [] +--EXPECT-- diff --git a/tests/Fixtures/functions/include/ignore_missing.test b/tests/Fixtures/functions/include/ignore_missing.test deleted file mode 100644 index c05b43e1403..00000000000 --- a/tests/Fixtures/functions/include/ignore_missing.test +++ /dev/null @@ -1,10 +0,0 @@ ---TEST-- -"include" function ---TEMPLATE-- -{{ include(["foo.twig", "bar.twig"], ignore_missing = true) }} -{{ include("foo.twig", ignore_missing = true) }} -{{ include("foo.twig", ignore_missing = true, variables = {}) }} -{{ include("foo.twig", ignore_missing = true, variables = {}, with_context = true) }} ---DATA-- -return [] ---EXPECT-- diff --git a/tests/Fixtures/functions/include/ignore_missing_exists.test b/tests/Fixtures/functions/include/ignore_missing_exists.legacy.test similarity index 70% rename from tests/Fixtures/functions/include/ignore_missing_exists.test rename to tests/Fixtures/functions/include/ignore_missing_exists.legacy.test index fc2d211ad86..b2cdb891331 100644 --- a/tests/Fixtures/functions/include/ignore_missing_exists.test +++ b/tests/Fixtures/functions/include/ignore_missing_exists.legacy.test @@ -1,5 +1,7 @@ --TEST-- "include" function +--DEPRECATION-- +Since twig/twig 3.15: Twig Function "include" is deprecated; use "render" instead in index.twig at line 2. --TEMPLATE-- {{ include("included.twig", ignore_missing = true) }} NOT DISPLAYED diff --git a/tests/Fixtures/functions/include/include_missing_extends.legacy.test b/tests/Fixtures/functions/include/include_missing_extends.legacy.test new file mode 100644 index 00000000000..90b82369230 --- /dev/null +++ b/tests/Fixtures/functions/include/include_missing_extends.legacy.test @@ -0,0 +1,15 @@ +--TEST-- +"include" function +--DEPRECATION-- +Since twig/twig 3.15: Twig Function "include" is deprecated; use "render" instead in index.twig at line 2. +--TEMPLATE-- +{{ include(['bad.twig', 'good.twig'], ignore_missing = true) }} +NOT DISPLAYED +--TEMPLATE(bad.twig)-- +{% extends 'DOES NOT EXIST' %} +--TEMPLATE(good.twig)-- +NOT DISPLAYED +--DATA-- +return [] +--EXCEPTION-- +Twig\Error\LoaderError: Template "DOES NOT EXIST" is not defined in "bad.twig" at line 2. diff --git a/tests/Fixtures/functions/include/missing.legacy.test b/tests/Fixtures/functions/include/missing.legacy.test new file mode 100644 index 00000000000..af4101c4f17 --- /dev/null +++ b/tests/Fixtures/functions/include/missing.legacy.test @@ -0,0 +1,10 @@ +--TEST-- +"include" function +--DEPRECATION-- +Since twig/twig 3.15: Twig Function "include" is deprecated; use "render" instead in index.twig at line 2. +--TEMPLATE-- +{{ include("foo.twig") }} +--DATA-- +return [] +--EXCEPTION-- +Twig\Error\LoaderError: Template "foo.twig" is not defined in "index.twig" at line 2. diff --git a/tests/Fixtures/functions/include/missing_nested.test b/tests/Fixtures/functions/include/missing_nested.legacy.test similarity index 100% rename from tests/Fixtures/functions/include/missing_nested.test rename to tests/Fixtures/functions/include/missing_nested.legacy.test diff --git a/tests/Fixtures/functions/include/sandbox.legacy.test b/tests/Fixtures/functions/include/sandbox.legacy.test new file mode 100644 index 00000000000..aef0b3bc2d4 --- /dev/null +++ b/tests/Fixtures/functions/include/sandbox.legacy.test @@ -0,0 +1,15 @@ +--TEST-- +"include" tag sandboxed +--DEPRECATION-- +Since twig/twig 3.15: Twig Function "include" is deprecated; use "render" instead in index.twig at line 2. +--TEMPLATE-- +{{ include("foo.twig", sandboxed = true) }} +--TEMPLATE(foo.twig)-- + + +{{ 'foo'|e }} +{{ 'foo'|e }} +--DATA-- +return [] +--EXCEPTION-- +Twig\Sandbox\SecurityNotAllowedFilterError: Filter "e" is not allowed in "foo.twig" at line 4. diff --git a/tests/Fixtures/functions/include/sandbox_disabling.legacy.test b/tests/Fixtures/functions/include/sandbox_disabling.legacy.test new file mode 100644 index 00000000000..79cbf227c35 --- /dev/null +++ b/tests/Fixtures/functions/include/sandbox_disabling.legacy.test @@ -0,0 +1,19 @@ +--TEST-- +"include" tag sandboxed +--DEPRECATION-- +Since twig/twig 3.15: Twig Function "include" is deprecated; use "render" instead in index.twig at line 2. +Since twig/twig 3.15: Twig Function "include" is deprecated; use "render" instead in index.twig at line 3. +--TEMPLATE-- +{{ include("foo.twig", sandboxed = true) }} +{{ include("bar.twig") }} +--TEMPLATE(foo.twig)-- +foo +--TEMPLATE(bar.twig)-- +{{ foo|e }} +--DATA-- +return ['foo' => 'bar
'] +--EXPECT-- +foo + + +bar<br /> diff --git a/tests/Fixtures/functions/include/sandbox_disabling_ignore_missing.test b/tests/Fixtures/functions/include/sandbox_disabling_ignore_missing.legacy.test similarity index 51% rename from tests/Fixtures/functions/include/sandbox_disabling_ignore_missing.test rename to tests/Fixtures/functions/include/sandbox_disabling_ignore_missing.legacy.test index c5be0088af6..d1d480f4fee 100644 --- a/tests/Fixtures/functions/include/sandbox_disabling_ignore_missing.test +++ b/tests/Fixtures/functions/include/sandbox_disabling_ignore_missing.legacy.test @@ -1,5 +1,8 @@ --TEST-- "include" tag sandboxed +--DEPRECATION-- +Since twig/twig 3.15: Twig Function "include" is deprecated; use "render" instead in index.twig at line 2. +Since twig/twig 3.15: Twig Function "include" is deprecated; use "render" instead in index.twig at line 3. --TEMPLATE-- {{ include("unknown.twig", sandboxed = true, ignore_missing = true) }} {{ include("bar.twig") }} diff --git a/tests/Fixtures/functions/include/template_instance.legacy.test b/tests/Fixtures/functions/include/template_instance.legacy.test new file mode 100644 index 00000000000..1f26159ccea --- /dev/null +++ b/tests/Fixtures/functions/include/template_instance.legacy.test @@ -0,0 +1,12 @@ +--TEST-- +"include" function accepts Twig\Template instance +--DEPRECATION-- +Since twig/twig 3.15: Twig Function "include" is deprecated; use "render" instead in index.twig at line 2. +--TEMPLATE-- +{{ include(foo) }} FOO +--TEMPLATE(foo.twig)-- +BAR +--DATA-- +return ['foo' => $twig->load('foo.twig')] +--EXPECT-- +BAR FOO diff --git a/tests/Fixtures/functions/include/templates_as_array.legacy.test b/tests/Fixtures/functions/include/templates_as_array.legacy.test new file mode 100644 index 00000000000..abbd8edd33b --- /dev/null +++ b/tests/Fixtures/functions/include/templates_as_array.legacy.test @@ -0,0 +1,15 @@ +--TEST-- +"include" function +--DEPRECATION-- +Since twig/twig 3.15: Twig Function "include" is deprecated; use "render" instead in index.twig at line 2. +Since twig/twig 3.15: Twig Function "include" is deprecated; use "render" instead in index.twig at line 3. +--TEMPLATE-- +{{ include(["foo.twig", "bar.twig"]) }} +{{- include(["bar.twig", "foo.twig"]) }} +--TEMPLATE(foo.twig)-- +foo +--DATA-- +return [] +--EXPECT-- +foo +foo diff --git a/tests/Fixtures/functions/include/templates_as_array.test b/tests/Fixtures/functions/include/templates_as_array.test deleted file mode 100644 index 21e5bb2efd2..00000000000 --- a/tests/Fixtures/functions/include/templates_as_array.test +++ /dev/null @@ -1,12 +0,0 @@ ---TEST-- -"include" function ---TEMPLATE-- -{{ include(["foo.twig", "bar.twig"]) }} -{{- include(["bar.twig", "foo.twig"]) }} ---TEMPLATE(foo.twig)-- -foo ---DATA-- -return [] ---EXPECT-- -foo -foo diff --git a/tests/Fixtures/functions/include/with_context.test b/tests/Fixtures/functions/include/with_context.legacy.test similarity index 50% rename from tests/Fixtures/functions/include/with_context.test rename to tests/Fixtures/functions/include/with_context.legacy.test index 46ac8c79bdc..d41d629a72f 100644 --- a/tests/Fixtures/functions/include/with_context.test +++ b/tests/Fixtures/functions/include/with_context.legacy.test @@ -1,5 +1,10 @@ --TEST-- "include" function accept variables and with_context +--DEPRECATION-- +Since twig/twig 3.15: Twig Function "include" is deprecated; use "render" instead in index.twig at line 2. +Since twig/twig 3.15: Twig Function "include" is deprecated; use "render" instead in index.twig at line 3. +Since twig/twig 3.15: Twig Function "include" is deprecated; use "render" instead in index.twig at line 4. +Since twig/twig 3.15: Twig Function "include" is deprecated; use "render" instead in index.twig at line 5. --TEMPLATE-- {{ include("foo.twig") }} {{- include("foo.twig", with_context = false) }} diff --git a/tests/Fixtures/functions/include/with_variables.legacy.test b/tests/Fixtures/functions/include/with_variables.legacy.test new file mode 100644 index 00000000000..0c3b7cb9c79 --- /dev/null +++ b/tests/Fixtures/functions/include/with_variables.legacy.test @@ -0,0 +1,15 @@ +--TEST-- +"include" function accept variables +--DEPRECATION-- +Since twig/twig 3.15: Twig Function "include" is deprecated; use "render" instead in index.twig at line 2. +Since twig/twig 3.15: Twig Function "include" is deprecated; use "render" instead in index.twig at line 3. +--TEMPLATE-- +{{ include("foo.twig", {'foo': 'bar'}) }} +{{- include("foo.twig", vars) }} +--TEMPLATE(foo.twig)-- +{{ foo }} +--DATA-- +return ['vars' => ['foo' => 'bar']] +--EXPECT-- +bar +bar diff --git a/tests/Fixtures/functions/include_template_from_string.test b/tests/Fixtures/functions/include_template_from_string.test index 8d9ba60ce66..eb97f45296e 100644 --- a/tests/Fixtures/functions/include_template_from_string.test +++ b/tests/Fixtures/functions/include_template_from_string.test @@ -1,8 +1,8 @@ --TEST-- -"template_from_string" function works in an "include" +"template_from_string" function works in a "render" function --TEMPLATE-- {% set embed = '{% embed "embed.twig" %}{% endembed %}' %} -{{ include(template_from_string(embed)) }} +{{ render(template_from_string(embed)) }} --TEMPLATE(embed.twig)-- Cool --DATA-- diff --git a/tests/Fixtures/functions/include/assignment.test b/tests/Fixtures/functions/render/assignment.test similarity index 67% rename from tests/Fixtures/functions/include/assignment.test rename to tests/Fixtures/functions/render/assignment.test index c9ce8123fea..94ed5e34959 100644 --- a/tests/Fixtures/functions/include/assignment.test +++ b/tests/Fixtures/functions/render/assignment.test @@ -1,7 +1,7 @@ --TEST-- -"include" function +"render" function --TEMPLATE-- -{% set tmp = include("foo.twig") %} +{% set tmp = render("foo.twig") %} FOO{{ tmp }}BAR --TEMPLATE(foo.twig)-- diff --git a/tests/Fixtures/functions/include/autoescaping.test b/tests/Fixtures/functions/render/autoescaping.test similarity index 58% rename from tests/Fixtures/functions/include/autoescaping.test rename to tests/Fixtures/functions/render/autoescaping.test index a3666261f39..55d93abd2c5 100644 --- a/tests/Fixtures/functions/include/autoescaping.test +++ b/tests/Fixtures/functions/render/autoescaping.test @@ -1,7 +1,7 @@ --TEST-- -"include" function is safe for auto-escaping +"render" function is safe for auto-escaping --TEMPLATE-- -{{ include("foo.twig") }} +{{ render("foo.twig") }} --TEMPLATE(foo.twig)--

Test

--DATA-- diff --git a/tests/Fixtures/functions/include/basic.test b/tests/Fixtures/functions/render/basic.test similarity index 70% rename from tests/Fixtures/functions/include/basic.test rename to tests/Fixtures/functions/render/basic.test index f90983c02f3..3b390bec1ab 100644 --- a/tests/Fixtures/functions/include/basic.test +++ b/tests/Fixtures/functions/render/basic.test @@ -1,8 +1,8 @@ --TEST-- -"include" function +"render" function --TEMPLATE-- FOO -{{ include("foo.twig") }} +{{ render("foo.twig") }} BAR --TEMPLATE(foo.twig)-- diff --git a/tests/Fixtures/functions/render/expression.test b/tests/Fixtures/functions/render/expression.test new file mode 100644 index 00000000000..6de6b099cf6 --- /dev/null +++ b/tests/Fixtures/functions/render/expression.test @@ -0,0 +1,17 @@ +--TEST-- +"render" function allows expressions for the template to render +--TEMPLATE-- +FOO +{{ render(foo) }} + +BAR +--TEMPLATE(foo.twig)-- +FOOBAR +--DATA-- +return ['foo' => 'foo.twig'] +--EXPECT-- +FOO + +FOOBAR + +BAR diff --git a/tests/Fixtures/functions/render/ignore_missing.test b/tests/Fixtures/functions/render/ignore_missing.test new file mode 100644 index 00000000000..2a73bc5e0f9 --- /dev/null +++ b/tests/Fixtures/functions/render/ignore_missing.test @@ -0,0 +1,9 @@ +--TEST-- +"include" function +--TEMPLATE-- +{{ render(["foo.twig", "bar.twig"], ignore_missing = true) }} +{{ render("foo.twig", ignore_missing = true) }} +{{ render("foo.twig", ignore_missing = true, variables = {}) }} +--DATA-- +return [] +--EXPECT-- diff --git a/tests/Fixtures/functions/render/ignore_missing_exists.test b/tests/Fixtures/functions/render/ignore_missing_exists.test new file mode 100644 index 00000000000..22fa45b99e9 --- /dev/null +++ b/tests/Fixtures/functions/render/ignore_missing_exists.test @@ -0,0 +1,11 @@ +--TEST-- +"render" function +--TEMPLATE-- +{{ render("included.twig", ignore_missing = true) }} +NOT DISPLAYED +--TEMPLATE(included.twig)-- +{{ render("DOES NOT EXIST") }} +--DATA-- +return [] +--EXCEPTION-- +Twig\Error\LoaderError: Template "DOES NOT EXIST" is not defined in "included.twig" at line 2. diff --git a/tests/Fixtures/functions/include/include_missing_extends.test b/tests/Fixtures/functions/render/include_missing_extends.test similarity index 75% rename from tests/Fixtures/functions/include/include_missing_extends.test rename to tests/Fixtures/functions/render/include_missing_extends.test index 810ae824800..6a37b5836b5 100644 --- a/tests/Fixtures/functions/include/include_missing_extends.test +++ b/tests/Fixtures/functions/render/include_missing_extends.test @@ -1,7 +1,7 @@ --TEST-- -"include" function +"render" function --TEMPLATE-- -{{ include(['bad.twig', 'good.twig'], ignore_missing = true) }} +{{ render(['bad.twig', 'good.twig'], ignore_missing = true) }} NOT DISPLAYED --TEMPLATE(bad.twig)-- {% extends 'DOES NOT EXIST' %} diff --git a/tests/Fixtures/functions/include/missing.test b/tests/Fixtures/functions/render/missing.test similarity index 75% rename from tests/Fixtures/functions/include/missing.test rename to tests/Fixtures/functions/render/missing.test index 1d50f7ac2fe..39f632f9f0f 100644 --- a/tests/Fixtures/functions/include/missing.test +++ b/tests/Fixtures/functions/render/missing.test @@ -1,7 +1,7 @@ --TEST-- -"include" function +"render" function --TEMPLATE-- -{{ include("foo.twig") }} +{{ render("foo.twig") }} --DATA-- return [] --EXCEPTION-- diff --git a/tests/Fixtures/functions/render/missing_nested.test b/tests/Fixtures/functions/render/missing_nested.test new file mode 100644 index 00000000000..0e294dee12c --- /dev/null +++ b/tests/Fixtures/functions/render/missing_nested.test @@ -0,0 +1,16 @@ +--TEST-- +"render" function +--TEMPLATE-- +{% extends "base.twig" %} + +{% block content %} + {{ parent() }} +{% endblock %} +--TEMPLATE(base.twig)-- +{% block content %} + {{ render("foo.twig") }} +{% endblock %} +--DATA-- +return [] +--EXCEPTION-- +Twig\Error\LoaderError: Template "foo.twig" is not defined in "base.twig" at line 3. diff --git a/tests/Fixtures/functions/include/sandbox.test b/tests/Fixtures/functions/render/sandbox.test similarity index 74% rename from tests/Fixtures/functions/include/sandbox.test rename to tests/Fixtures/functions/render/sandbox.test index ec7e61e9703..b56f5d51fd8 100644 --- a/tests/Fixtures/functions/include/sandbox.test +++ b/tests/Fixtures/functions/render/sandbox.test @@ -1,7 +1,7 @@ --TEST-- -"include" tag sandboxed +"render" tag sandboxed --TEMPLATE-- -{{ include("foo.twig", sandboxed = true) }} +{{ render("foo.twig", sandboxed = true) }} --TEMPLATE(foo.twig)-- diff --git a/tests/Fixtures/functions/include/sandbox_disabling.test b/tests/Fixtures/functions/render/sandbox_disabling.test similarity index 60% rename from tests/Fixtures/functions/include/sandbox_disabling.test rename to tests/Fixtures/functions/render/sandbox_disabling.test index 1206b67fe31..29fd89561e5 100644 --- a/tests/Fixtures/functions/include/sandbox_disabling.test +++ b/tests/Fixtures/functions/render/sandbox_disabling.test @@ -1,8 +1,8 @@ --TEST-- -"include" tag sandboxed +"render" tag sandboxed --TEMPLATE-- -{{ include("foo.twig", sandboxed = true) }} -{{ include("bar.twig") }} +{{ render("foo.twig", sandboxed = true) }} +{{ render("bar.twig", { foo }) }} --TEMPLATE(foo.twig)-- foo --TEMPLATE(bar.twig)-- diff --git a/tests/Fixtures/functions/render/sandbox_disabling_ignore_missing.test b/tests/Fixtures/functions/render/sandbox_disabling_ignore_missing.test new file mode 100644 index 00000000000..a28662af02f --- /dev/null +++ b/tests/Fixtures/functions/render/sandbox_disabling_ignore_missing.test @@ -0,0 +1,13 @@ +--TEST-- +"render" tag sandboxed +--TEMPLATE-- +{{ render("unknown.twig", sandboxed = true, ignore_missing = true) }} +{{ render("bar.twig", { foo }) }} +--TEMPLATE(bar.twig)-- +{{ foo|e }} +--DATA-- +return ['foo' => 'bar
'] +--EXPECT-- + + +bar<br /> diff --git a/tests/Fixtures/functions/include/template_instance.test b/tests/Fixtures/functions/render/template_instance.test similarity index 61% rename from tests/Fixtures/functions/include/template_instance.test rename to tests/Fixtures/functions/render/template_instance.test index be18d244ac0..d2af8f1bb71 100644 --- a/tests/Fixtures/functions/include/template_instance.test +++ b/tests/Fixtures/functions/render/template_instance.test @@ -1,7 +1,7 @@ --TEST-- -"include" function accepts Twig\Template instance +"render" function accepts Twig\Template instance --TEMPLATE-- -{{ include(foo) }} FOO +{{ render(foo) }} FOO --TEMPLATE(foo.twig)-- BAR --DATA-- diff --git a/tests/Fixtures/functions/render/templates_as_array.test b/tests/Fixtures/functions/render/templates_as_array.test new file mode 100644 index 00000000000..c873e09ed31 --- /dev/null +++ b/tests/Fixtures/functions/render/templates_as_array.test @@ -0,0 +1,12 @@ +--TEST-- +"render" function +--TEMPLATE-- +{{ render(["foo.twig", "bar.twig"]) }} +{{- render(["bar.twig", "foo.twig"]) }} +--TEMPLATE(foo.twig)-- +foo +--DATA-- +return [] +--EXPECT-- +foo +foo diff --git a/tests/Fixtures/functions/include/with_variables.test b/tests/Fixtures/functions/render/with_variables.test similarity index 51% rename from tests/Fixtures/functions/include/with_variables.test rename to tests/Fixtures/functions/render/with_variables.test index 0ed98fed02f..9814d3bf98a 100644 --- a/tests/Fixtures/functions/include/with_variables.test +++ b/tests/Fixtures/functions/render/with_variables.test @@ -1,8 +1,8 @@ --TEST-- -"include" function accept variables +"render" function accept variables --TEMPLATE-- -{{ include("foo.twig", {'foo': 'bar'}) }} -{{- include("foo.twig", vars) }} +{{ render("foo.twig", {'foo': 'bar'}) }} +{{- render("foo.twig", vars) }} --TEMPLATE(foo.twig)-- {{ foo }} --DATA-- diff --git a/tests/Fixtures/regression/combined_debug_info.test b/tests/Fixtures/regression/combined_debug_info.test index 6426d2c1d85..b83fdb659a8 100644 --- a/tests/Fixtures/regression/combined_debug_info.test +++ b/tests/Fixtures/regression/combined_debug_info.test @@ -3,7 +3,7 @@ Exception with bad line number --TEMPLATE-- {% block content %} {{ foo }} - {{ include("foo") }} + {{ render("foo", { foo }) }} {% endblock %} index --TEMPLATE(foo)-- diff --git a/tests/Fixtures/tags/inheritance/dynamic_parent_from_include.test b/tests/Fixtures/tags/inheritance/dynamic_parent_from_include.test index 42a3d60e278..2eeedd65f03 100644 --- a/tests/Fixtures/tags/inheritance/dynamic_parent_from_include.test +++ b/tests/Fixtures/tags/inheritance/dynamic_parent_from_include.test @@ -1,7 +1,7 @@ --TEST-- "extends" tag --TEMPLATE-- -{{ include('included.twig') }} +{{ render('included.twig', { dynamic }) }} --TEMPLATE(included.twig)-- diff --git a/tests/Fixtures/tags/inheritance/multiple_dynamic.test b/tests/Fixtures/tags/inheritance/multiple_dynamic.test index fa887177bd3..e670979687a 100644 --- a/tests/Fixtures/tags/inheritance/multiple_dynamic.test +++ b/tests/Fixtures/tags/inheritance/multiple_dynamic.test @@ -1,11 +1,9 @@ --TEST-- "extends" tag --TEMPLATE-- -{% set foo = 1 %} -{{ include('parent.twig') }} -{{ include('parent.twig') }} -{% set foo = 2 %} -{{ include('parent.twig') }} +{{ render('parent.twig', { foo: 1 }) }} +{{ render('parent.twig', { foo: 1 }) }} +{{ render('parent.twig', { foo: 2 }) }} --TEMPLATE(parent.twig)-- {% extends foo~'_parent.twig' %}{% block content %}{{ parent() }} parent{% endblock %} --TEMPLATE(1_parent.twig)-- diff --git a/tests/NodeVisitor/OptimizerTest.php b/tests/NodeVisitor/OptimizerTest.php index 12bdc12d127..61089d7a119 100644 --- a/tests/NodeVisitor/OptimizerTest.php +++ b/tests/NodeVisitor/OptimizerTest.php @@ -88,7 +88,7 @@ public function testForLoopOptimizer($template, $expected) $stream = $env->parse($env->tokenize(new Source($template, 'index'))); foreach ($expected as $target => $withLoop) { - $this->assertTrue($this->checkForLoopConfiguration($stream, $target, $withLoop), \sprintf('variable %s is %soptimized', $target, $withLoop ? 'not ' : '')); + $this->assertTrue($this->checkForLoopConfiguration($stream, $target, $withLoop), \sprintf('Loop for variable %s is %soptimized', $target, $withLoop ? 'not ' : '')); } } @@ -119,15 +119,7 @@ public static function getTestsForForLoopOptimizer() ['{% for i in foo %}{% for j in foo %}{{ loop["parent"].loop.index }}{% endfor %}{% endfor %}', ['i' => true, 'j' => true]], - ['{% for i in foo %}{{ include("foo") }}{% endfor %}', ['i' => true]], - - ['{% for i in foo %}{{ include("foo", with_context = false) }}{% endfor %}', ['i' => false]], - - ['{% for i in foo %}{{ include("foo", with_context = true) }}{% endfor %}', ['i' => true]], - - ['{% for i in foo %}{{ include("foo", { "foo": "bar" }, with_context = false) }}{% endfor %}', ['i' => false]], - - ['{% for i in foo %}{{ include("foo", { "foo": loop.index }, with_context = false) }}{% endfor %}', ['i' => true]], + ['{% for i in foo %}{{ render("foo") }}{% endfor %}', ['i' => false]], ]; }