mirror of
https://github.com/twigphp/Twig.git
synced 2026-08-17 08:50:50 +00:00
Make the include() function return a Markup object
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
* Stop reporting a skipped test in `IntegrationTestCase` when there is no legacy test to run
|
||||
* Make the `IntegrationTestCase` and `NodeTestCase` test helpers compatible with PHPUnit 11
|
||||
* Cast printed expressions to string so values that cannot be converted to a string (arrays, non-`Stringable` objects, ...) report a usable stack trace at the print location
|
||||
* Make the `include()` function return a `Markup` object so an assigned result is not re-escaped when printed
|
||||
* Skip the sandbox `__toString` check on arguments whose PHP parameter type cannot implicitly coerce to string
|
||||
|
||||
# 3.27.1 (2026-05-30)
|
||||
|
||||
@@ -8,6 +8,18 @@ The ``include`` function returns the rendered content of a template:
|
||||
{{ include('template.html.twig') }}
|
||||
{{ include(some_var) }}
|
||||
|
||||
The returned content is a ``\Twig\Markup`` instance, so it is considered safe
|
||||
and is not escaped again when you store it in a variable and print it later:
|
||||
|
||||
.. code-block:: twig
|
||||
|
||||
{% set body = include('body.html.twig') %}
|
||||
{{ body }} {# rendered as-is, not re-escaped #}
|
||||
|
||||
Beware that, like any safe value, it is not re-escaped for the context it ends
|
||||
up in, so only embed it in the same context it was rendered for (typically
|
||||
HTML).
|
||||
|
||||
Included templates have access to the variables of the active context.
|
||||
|
||||
If you are using the filesystem loader, the templates are looked for in the
|
||||
|
||||
@@ -1490,9 +1490,11 @@ final class CoreExtension extends AbstractExtension
|
||||
* @param bool $ignoreMissing Whether to ignore missing templates or not
|
||||
* @param bool $sandboxed Whether to sandbox the template or not
|
||||
*
|
||||
* @return string|Markup
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public static function include(Environment $env, $context, $template, $variables = [], $withContext = true, $ignoreMissing = false, $sandboxed = false): string
|
||||
public static function include(Environment $env, $context, $template, $variables = [], $withContext = true, $ignoreMissing = false, $sandboxed = false)
|
||||
{
|
||||
$alreadySandboxed = false;
|
||||
$sandbox = null;
|
||||
@@ -1519,7 +1521,9 @@ final class CoreExtension extends AbstractExtension
|
||||
return '';
|
||||
}
|
||||
|
||||
return $loaded->render($variables);
|
||||
$rendered = $loaded->render($variables);
|
||||
|
||||
return '' === $rendered ? '' : new Markup($rendered, $env->getCharset());
|
||||
} finally {
|
||||
if ($isSandboxed && !$alreadySandboxed) {
|
||||
$sandbox->disableSandbox();
|
||||
|
||||
@@ -29,6 +29,7 @@ use Twig\Error\RuntimeError;
|
||||
use Twig\Extension\CoreExtension;
|
||||
use Twig\Extension\SandboxExtension;
|
||||
use Twig\Loader\ArrayLoader;
|
||||
use Twig\Markup;
|
||||
use Twig\Sandbox\SecurityError;
|
||||
use Twig\Sandbox\SecurityPolicy;
|
||||
|
||||
@@ -417,6 +418,41 @@ class CoreTest extends TestCase
|
||||
$twig->render('index');
|
||||
}
|
||||
|
||||
public function testSandboxedIncludeResultStaysEscapedWhenAssigned()
|
||||
{
|
||||
$twig = new Environment(new ArrayLoader([
|
||||
'index' => "{% set body = include('included', sandboxed: true) %}[{{ body }}]",
|
||||
'included' => '{{ evil }}',
|
||||
]), ['autoescape' => 'html']);
|
||||
$twig->addExtension(new SandboxExtension(new SecurityPolicy([], ['escape'], [], [], ['include']), false));
|
||||
|
||||
$this->assertSame('[<script>]', $twig->render('index', ['evil' => '<script>']));
|
||||
}
|
||||
|
||||
public function testIncludeReturnsMarkupForRenderedContent()
|
||||
{
|
||||
$twig = new Environment(new ArrayLoader(['included' => 'content']));
|
||||
|
||||
$result = CoreExtension::include($twig, [], 'included');
|
||||
|
||||
$this->assertInstanceOf(Markup::class, $result);
|
||||
$this->assertSame('content', (string) $result);
|
||||
}
|
||||
|
||||
public function testIncludeReturnsAnEmptyStringForEmptyContent()
|
||||
{
|
||||
$twig = new Environment(new ArrayLoader(['included' => '']));
|
||||
|
||||
$this->assertSame('', CoreExtension::include($twig, [], 'included'));
|
||||
}
|
||||
|
||||
public function testIncludeReturnsAnEmptyStringForIgnoredMissingTemplate()
|
||||
{
|
||||
$twig = new Environment(new ArrayLoader([]));
|
||||
|
||||
$this->assertSame('', CoreExtension::include($twig, [], 'missing', ignoreMissing: true));
|
||||
}
|
||||
|
||||
public function testLastModified()
|
||||
{
|
||||
$this->assertGreaterThan(1000000000, (new CoreExtension())->getLastModified());
|
||||
|
||||
@@ -23,6 +23,6 @@ class StringLoaderExtensionTest extends TestCase
|
||||
{
|
||||
$twig = new Environment(new ArrayLoader());
|
||||
$twig->addExtension(new StringLoaderExtension());
|
||||
$this->assertSame('something', CoreExtension::include($twig, [], StringLoaderExtension::templateFromString($twig, 'something')));
|
||||
$this->assertSame('something', (string) CoreExtension::include($twig, [], StringLoaderExtension::templateFromString($twig, 'something')));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
--TEST--
|
||||
"include" function returns Markup so an assigned result is not re-escaped
|
||||
--TEMPLATE--
|
||||
{% set assigned = include("foo.twig") %}[{{ assigned }}]
|
||||
--TEMPLATE(foo.twig)--
|
||||
{{- "a & b"|escape -}}
|
||||
--DATA--
|
||||
return []
|
||||
--EXPECT--
|
||||
[a & b]
|
||||
Reference in New Issue
Block a user