'even' => new Twig_Filter_Function('twig_is_even_filter'),
'escape' => new Twig_Filter_Function('twig_escape_filter', array('needs_environment' => true)),
+ * changed the automatic-escaping rules to be more sensible (the documentation lists all the rules)
* improved the filter system to allow object methods to be used as filters
* changed the Array and String loaders to actually make use of the cache mechanism
* included the default filter function definitions in the extension class files directly (Core, Escaper)
values explicitly marked as safe. Those can be marked in the template by using
the `|safe` filter.
-Functions returning template data (like macros and `parent`) return safe markup always.
+Functions returning template data (like macros and `parent`) always return
+safe markup.
>**NOTE**
>Twig is smart enough to not escape an already escaped value by the `escape`
>filter.
+-
+
+>**NOTE**
+>The chapter for the developers give more information about when and how
+>automatic escaping is applied.
+
List of Control Structures
--------------------------
{% var|escape %} {# var won't be doubled-escaped #}
{% endautoescape %}
+The escaping rules are implemented as follows:
+
+ * Literals (integers, booleans, arrays, ...) used in the template directly as
+ variables or filter arguments are never automatically escaped:
+
+ [twig]
+ {{ "Twig<br />" }} {# won't be escaped #}
+
+ {% set text as "Twig<br />" %}
+ {{ text }} {# will be escaped #}
+
+ * Escaping is applied before any other filter is applied (the reasoning
+ behind this is that filter transformations should be safe, as the filtered
+ value and all its arguments are escaped):
+
+ [twig]
+ {{ var|nl2br }} {# is equivalent to {{ var|escape|nl2br }} #}
+
+ * The `safe` filter can be used anywhere in the filter chain:
+
+ [twig]
+ {{ var|upper|nl2br|safe }} {# is equivalent to {{ var|safe|upper|nl2br }} #}
+
+ * Automatic escaping is applied to filter arguments, except for literals:
+
+ [twig]
+ {{ var|foo("bar") }} {# "bar" won't be escaped #}
+ {{ var|foo(bar) }} {# bar will be escaped #}
+ {{ var|foo(bar|safe) }} {# bar won't be escaped #}
+
### Sandbox Extension
The `sandbox` extension can be used to evaluate untrusted code. Access to
return $this->filters;
}
+ public function setFilters(array $filters)
+ {
+ $this->filters = $filters;
+ }
+
+ public function prependFilter($filter)
+ {
+ $this->filters = array_merge(array($filter), $this->filters);
+ }
+
public function appendFilter($filter)
{
$this->filters[] = $filter;
return $node;
}
- $expression = $node->getExpression();
+ return $this->escapeNode($node);
+ }
+
+ protected function escapeNode($node)
+ {
+ $expression = $node instanceof Twig_Node_Print ? $node->getExpression() : $node;
+
+ if ($expression instanceof Twig_Node_Expression_Filter)
+ {
+ // don't escape if escape has already been called
+ // or if we want the safe string
+ if ($expression->hasFilter('escape') || $expression->hasFilter('safe'))
+ {
+ return $node;
+ }
- // don't escape if escape has already been called
- // or if we want the safe string
- if (
- $expression instanceof Twig_Node_Expression_Filter
- &&
- (
- $expression->hasFilter('escape')
- ||
- $expression->hasFilter('safe')
- )
- )
+ // don't escape if the primary node of the filter is not a variable
+ $nodes = $expression->getNodes();
+ if (!$nodes[0] instanceof Twig_Node_Expression_Name)
+ {
+ return $node;
+ }
+ }
+ elseif (!$expression instanceof Twig_Node_Expression_Name)
{
+ // don't escape if the node is not a variable
return $node;
}
// escape
if ($expression instanceof Twig_Node_Expression_Filter)
{
- $expression->appendFilter(array('escape', array()));
+ // escape all variables in filters arguments
+ $filters = $expression->getFilters();
+ foreach ($filters as $i => $filter)
+ {
+ foreach ($filter[1] as $j => $argument)
+ {
+ $filters[$i][1][$j] = $this->escapeNode($argument);
+ }
+ }
+
+ $expression->setFilters($filters);
+ $expression->prependFilter(array('escape', array()));
return $node;
}
- else
+ elseif ($node instanceof Twig_Node_Print)
{
return new Twig_Node_Print(
new Twig_Node_Expression_Filter($expression, array(array('escape', array())), $node->getLine())
, $node->getLine()
);
}
+ else
+ {
+ return new Twig_Node_Expression_Filter($node, array(array('escape', array())), $node->getLine());
+ }
}
protected function needEscaping()
--- /dev/null
+--TEST--
+"autoescape" tag does not apply escaping on literals
+--TEMPLATE--
+{% autoescape on %}
+{{ "<br />" }}
+{% endautoescape %}
+--DATA--
+return array()
+--EXPECT--
+<br />
--- /dev/null
+--TEST--
+"autoescape" tag applies escaping before calling filters
+--TEMPLATE--
+{% autoescape on %}
+{{ var|nl2br }}
+{{ var|nl2br|escape }}
+{% endautoescape %}
+--DATA--
+return array('var' => "<Fabien>\nTwig")
+--EXPECT--
+<Fabien><br />
+Twig
+<Fabien><br />
+Twig
--- /dev/null
+--TEST--
+"autoescape" tag applies escaping on filter arguments, but not on literals
+--TEMPLATE--
+{% autoescape on %}
+{{ var|nl2br("<br />") }}
+{{ var|nl2br("<br />"|escape) }}
+{{ var|nl2br(sep) }}
+{{ var|nl2br(sep|safe) }}
+{% endautoescape %}
+--DATA--
+return array('var' => "<Fabien>\nTwig", 'sep' => '<br />')
+--EXPECT--
+<Fabien><br />
+Twig
+<Fabien><br />
+Twig
+<Fabien><br />
+Twig
+<Fabien><br />
+Twig
}
}
-$t = new LimeTest(55);
+class TestExtension extends Twig_Extension
+{
+ public function getFilters()
+ {
+ return array('nl2br' => new Twig_Filter_Method($this, 'nl2br'));
+ }
+
+ public function nl2br($value, $sep = '<br />')
+ {
+ return str_replace("\n", $sep."\n", $value);
+ }
+
+ public function getName()
+ {
+ return 'test';
+ }
+}
+
+$t = new LimeTest(58);
$fixturesDir = realpath(dirname(__FILE__).'/../fixtures/');
foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($fixturesDir), RecursiveIteratorIterator::LEAVES_ONLY) as $file)
$loader = new Twig_Loader_Array($templates);
$twig = new Twig_Environment($loader, array('trim_blocks' => true, 'cache' => false));
$twig->addExtension(new Twig_Extension_Escaper());
+ $twig->addExtension(new TestExtension());
$template = $twig->loadTemplate('index.twig');