1<?php
2
3/*
4 * This file is part of Twig.
5 *
6 * (c) Fabien Potencier
7 * (c) Armin Ronacher
8 *
9 * For the full copyright and license information, please view the LICENSE
10 * file that was distributed with this source code.
11 */
12
13namespace Twig\TokenParser;
14
15use Twig\Error\SyntaxError;
16use Twig\Node\Expression\AssignNameExpression;
17use Twig\Node\Expression\ConstantExpression;
18use Twig\Node\Expression\GetAttrExpression;
19use Twig\Node\Expression\NameExpression;
20use Twig\Node\ForNode;
21use Twig\Token;
22use Twig\TokenStream;
23
24/**
25 * Loops over each item of a sequence.
26 *
27 *   <ul>
28 *    {% for user in users %}
29 *      <li>{{ user.username|e }}</li>
30 *    {% endfor %}
31 *   </ul>
32 *
33 * @final
34 */
35class ForTokenParser extends AbstractTokenParser
36{
37    public function parse(Token $token)
38    {
39        $lineno = $token->getLine();
40        $stream = $this->parser->getStream();
41        $targets = $this->parser->getExpressionParser()->parseAssignmentExpression();
42        $stream->expect(Token::OPERATOR_TYPE, 'in');
43        $seq = $this->parser->getExpressionParser()->parseExpression();
44
45        $ifexpr = null;
46        if ($stream->nextIf(Token::NAME_TYPE, 'if')) {
47            $ifexpr = $this->parser->getExpressionParser()->parseExpression();
48        }
49
50        $stream->expect(Token::BLOCK_END_TYPE);
51        $body = $this->parser->subparse([$this, 'decideForFork']);
52        if ('else' == $stream->next()->getValue()) {
53            $stream->expect(Token::BLOCK_END_TYPE);
54            $else = $this->parser->subparse([$this, 'decideForEnd'], true);
55        } else {
56            $else = null;
57        }
58        $stream->expect(Token::BLOCK_END_TYPE);
59
60        if (\count($targets) > 1) {
61            $keyTarget = $targets->getNode(0);
62            $keyTarget = new AssignNameExpression($keyTarget->getAttribute('name'), $keyTarget->getTemplateLine());
63            $valueTarget = $targets->getNode(1);
64            $valueTarget = new AssignNameExpression($valueTarget->getAttribute('name'), $valueTarget->getTemplateLine());
65        } else {
66            $keyTarget = new AssignNameExpression('_key', $lineno);
67            $valueTarget = $targets->getNode(0);
68            $valueTarget = new AssignNameExpression($valueTarget->getAttribute('name'), $valueTarget->getTemplateLine());
69        }
70
71        if ($ifexpr) {
72            $this->checkLoopUsageCondition($stream, $ifexpr);
73            $this->checkLoopUsageBody($stream, $body);
74        }
75
76        return new ForNode($keyTarget, $valueTarget, $seq, $ifexpr, $body, $else, $lineno, $this->getTag());
77    }
78
79    public function decideForFork(Token $token)
80    {
81        return $token->test(['else', 'endfor']);
82    }
83
84    public function decideForEnd(Token $token)
85    {
86        return $token->test('endfor');
87    }
88
89    // the loop variable cannot be used in the condition
90    protected function checkLoopUsageCondition(TokenStream $stream, \Twig_NodeInterface $node)
91    {
92        if ($node instanceof GetAttrExpression && $node->getNode('node') instanceof NameExpression && 'loop' == $node->getNode('node')->getAttribute('name')) {
93            throw new SyntaxError('The "loop" variable cannot be used in a looping condition.', $node->getTemplateLine(), $stream->getSourceContext());
94        }
95
96        foreach ($node as $n) {
97            if (!$n) {
98                continue;
99            }
100
101            $this->checkLoopUsageCondition($stream, $n);
102        }
103    }
104
105    // check usage of non-defined loop-items
106    // it does not catch all problems (for instance when a for is included into another or when the variable is used in an include)
107    protected function checkLoopUsageBody(TokenStream $stream, \Twig_NodeInterface $node)
108    {
109        if ($node instanceof GetAttrExpression && $node->getNode('node') instanceof NameExpression && 'loop' == $node->getNode('node')->getAttribute('name')) {
110            $attribute = $node->getNode('attribute');
111            if ($attribute instanceof ConstantExpression && \in_array($attribute->getAttribute('value'), ['length', 'revindex0', 'revindex', 'last'])) {
112                throw new SyntaxError(sprintf('The "loop.%s" variable is not defined when looping with a condition.', $attribute->getAttribute('value')), $node->getTemplateLine(), $stream->getSourceContext());
113            }
114        }
115
116        // should check for parent.loop.XXX usage
117        if ($node instanceof ForNode) {
118            return;
119        }
120
121        foreach ($node as $n) {
122            if (!$n) {
123                continue;
124            }
125
126            $this->checkLoopUsageBody($stream, $n);
127        }
128    }
129
130    public function getTag()
131    {
132        return 'for';
133    }
134}
135
136class_alias('Twig\TokenParser\ForTokenParser', 'Twig_TokenParser_For');
137