EscaperNodeVisitor.php
Namespace
Twig\NodeVisitorFile
-
vendor/
twig/ twig/ src/ NodeVisitor/ EscaperNodeVisitor.php
View source
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\NodeVisitor;
use Twig\Environment;
use Twig\Extension\EscaperExtension;
use Twig\Node\AutoEscapeNode;
use Twig\Node\BlockNode;
use Twig\Node\BlockReferenceNode;
use Twig\Node\Expression\AbstractExpression;
use Twig\Node\Expression\ConditionalExpression;
use Twig\Node\Expression\ConstantExpression;
use Twig\Node\Expression\FilterExpression;
use Twig\Node\ImportNode;
use Twig\Node\ModuleNode;
use Twig\Node\Node;
use Twig\Node\Nodes;
use Twig\Node\PrintNode;
use Twig\NodeTraverser;
/**
* @author Fabien Potencier <fabien@symfony.com>
*
* @internal
*/
final class EscaperNodeVisitor implements NodeVisitorInterface {
private $statusStack = [];
private $blocks = [];
private $safeAnalysis;
private $traverser;
private $defaultStrategy = false;
private $safeVars = [];
public function __construct() {
$this->safeAnalysis = new SafeAnalysisNodeVisitor();
}
public function enterNode(Node $node, Environment $env) : Node {
if ($node instanceof ModuleNode) {
if ($env->hasExtension(EscaperExtension::class) && ($defaultStrategy = $env->getExtension(EscaperExtension::class)
->getDefaultStrategy($node->getTemplateName()))) {
$this->defaultStrategy = $defaultStrategy;
}
$this->safeVars = [];
$this->blocks = [];
}
elseif ($node instanceof AutoEscapeNode) {
$this->statusStack[] = $node->getAttribute('value');
}
elseif ($node instanceof BlockNode) {
$this->statusStack[] = $this->blocks[$node->getAttribute('name')] ?? $this->needEscaping();
}
elseif ($node instanceof ImportNode) {
$this->safeVars[] = $node->getNode('var')
->getNode('var')
->getAttribute('name');
}
return $node;
}
public function leaveNode(Node $node, Environment $env) : ?Node {
if ($node instanceof ModuleNode) {
$this->defaultStrategy = false;
$this->safeVars = [];
$this->blocks = [];
}
elseif ($node instanceof FilterExpression) {
return $this->preEscapeFilterNode($node, $env);
}
elseif ($node instanceof PrintNode && false !== ($type = $this->needEscaping())) {
$expression = $node->getNode('expr');
if ($expression instanceof ConditionalExpression) {
$this->escapeConditional($expression, $env, $type);
}
else {
$node->setNode('expr', $this->escapeExpression($expression, $env, $type));
}
return $node;
}
if ($node instanceof AutoEscapeNode || $node instanceof BlockNode) {
array_pop($this->statusStack);
}
elseif ($node instanceof BlockReferenceNode) {
$this->blocks[$node->getAttribute('name')] = $this->needEscaping();
}
return $node;
}
private function escapeConditional(ConditionalExpression $expression, Environment $env, string $type) : void {
/** @var AbstractExpression $expr2 */
$expr2 = $expression->getNode('expr2');
if ($expr2 instanceof ConditionalExpression) {
$this->escapeConditional($expr2, $env, $type);
}
else {
$expression->setNode('expr2', $this->escapeExpression($expr2, $env, $type));
}
/** @var AbstractExpression $expr3 */
$expr3 = $expression->getNode('expr3');
if ($expr3 instanceof ConditionalExpression) {
$this->escapeConditional($expr3, $env, $type);
}
else {
$expression->setNode('expr3', $this->escapeExpression($expr3, $env, $type));
}
}
private function escapeExpression(AbstractExpression $expression, Environment $env, string $type) : AbstractExpression {
return $this->isSafeFor($type, $expression, $env) ? $expression : $this->getEscaperFilter($env, $type, $expression);
}
private function preEscapeFilterNode(FilterExpression $filter, Environment $env) : FilterExpression {
if ($filter->hasAttribute('twig_callable')) {
$type = $filter->getAttribute('twig_callable')
->getPreEscape();
}
else {
// legacy
$name = $filter->getNode('filter', false)
->getAttribute('value');
$type = $env->getFilter($name)
->getPreEscape();
}
if (null === $type) {
return $filter;
}
/** @var AbstractExpression $node */
$node = $filter->getNode('node');
if ($this->isSafeFor($type, $node, $env)) {
return $filter;
}
$filter->setNode('node', $this->getEscaperFilter($env, $type, $node));
return $filter;
}
private function isSafeFor(string $type, AbstractExpression $expression, Environment $env) : bool {
$safe = $this->safeAnalysis
->getSafe($expression);
if (!$safe) {
if (null === $this->traverser) {
$this->traverser = new NodeTraverser($env, [
$this->safeAnalysis,
]);
}
$this->safeAnalysis
->setSafeVars($this->safeVars);
$this->traverser
->traverse($expression);
$safe = $this->safeAnalysis
->getSafe($expression);
}
return \in_array($type, $safe) || \in_array('all', $safe);
}
private function needEscaping() {
if (\count($this->statusStack)) {
return $this->statusStack[\count($this->statusStack) - 1];
}
return $this->defaultStrategy ?: false;
}
private function getEscaperFilter(Environment $env, string $type, AbstractExpression $node) : FilterExpression {
$line = $node->getTemplateLine();
$filter = $env->getFilter('escape');
$args = new Nodes([
new ConstantExpression($type, $line),
new ConstantExpression(null, $line),
new ConstantExpression(true, $line),
]);
return new FilterExpression($node, $filter, $args, $line);
}
public function getPriority() : int {
return 0;
}
}
Classes
Title | Deprecated | Summary |
---|---|---|
EscaperNodeVisitor | @author Fabien Potencier <fabien@symfony.com> |