--TEST--
"source" function
--TEMPLATE--
FOO
{{ source("foo.twig") }}
BAR
--TEMPLATE(foo.twig)--
{{ foo }}
--DATA--
return array()
--EXPECT--
FOO
{{ foo }}
BAR
--TEST--
"template_from_string" function
--TEMPLATE--
{% include template_from_string(template) %}
{% include template_from_string("Hello {{ name }}") %}
{% include template_from_string('{% extends "parent.twig" %}{% block content %}Hello {{ name }}{% endblock %}') %}
--TEMPLATE(parent.twig)--
{% block content %}{% endblock %}
--DATA--
return array('name' => 'Fabien', 'template' => "Hello {{ name }}")
--EXPECT--
Hello Fabien
Hello Fabien
Hello Fabien
--TEST--
macro
--TEMPLATE--
{% from _self import test %}
{% macro test(a, b = 'bar') -%}
{{ a }}{{ b }}
{%- endmacro %}
{{ test('foo') }}
{{ test('bar', 'foo') }}
--DATA--
return array();
--EXPECT--
foobar
barfoo
--TEST--
macro
--TEMPLATE--
{% import _self as macros %}
{% macro foo(data) %}
{{ data }}
{% endmacro %}
{% macro bar() %}
{% endmacro %}
{{ macros.foo(macros.bar()) }}
--DATA--
return array();
--EXPECT--
--TEST--
macro
--TEMPLATE--
{% from _self import test %}
{% macro test(this) -%}
{{ this }}
{%- endmacro %}
{{ test(this) }}
--DATA--
return array('this' => 'foo');
--EXPECT--
foo
--TEST--
macro
--TEMPLATE--
{% import _self as test %}
{% from _self import test %}
{% macro test(a, b) -%}
{{ a|default('a') }}
{{- b|default('b') }}
{%- endmacro %}
{{ test.test() }}
{{ test() }}
{{ test.test(1, "c") }}
{{ test(1, "c") }}
--DATA--
return array();
--EXPECT--
a b
a b
1 c
1 c
--TEST--
macro with a filter
--TEMPLATE--
{% import _self as test %}
{% macro test() %}
{% filter escape %}foo {% endfilter %}
{% endmacro %}
{{ test.test() }}
--DATA--
return array();
--EXPECT--
foo<br />
--TEST--
Twig outputs 0 nodes correctly
--TEMPLATE--
{{ foo }}0{{ foo }}
--DATA--
return array('foo' => 'foo')
--EXPECT--
foo0foo
--TEST--
error in twig extension
--TEMPLATE--
{{ object.region is not null ? object.regionChoices[object.region] }}
--EXPECT--
house.region.s
--TEST--
Twig is able to deal with SimpleXMLElement instances as variables
--CONDITION--
version_compare(phpversion(), '5.3.0', '>=')
--TEMPLATE--
Hello '{{ images.image.0.group }}'!
{{ images.image.0.group.attributes.myattr }}
{{ images.children().image.count() }}
{% for image in images %}
- {{ image.group }}
{% endfor %}
--DATA--
return array('images' => new SimpleXMLElement('foobar'))
--EXPECT--
Hello 'foo'!
example
2
- foo
- bar
--TEST--
Twig does not confuse strings with integers in getAttribute()
--TEMPLATE--
{{ hash['2e2'] }}
--DATA--
return array('hash' => array('2e2' => 'works'))
--EXPECT--
works
--TEST--
"autoescape" tag applies escaping on its children
--TEMPLATE--
{% autoescape %}
{{ var }}
{% endautoescape %}
{% autoescape 'html' %}
{{ var }}
{% endautoescape %}
{% autoescape false %}
{{ var }}
{% endautoescape %}
{% autoescape true %}
{{ var }}
{% endautoescape %}
{% autoescape false %}
{{ var }}
{% endautoescape %}
--DATA--
return array('var' => ' ')
--EXPECT--
<br />
<br />
<br />
--TEST--
"autoescape" tag applies escaping on embedded blocks
--TEMPLATE--
{% autoescape 'html' %}
{% block foo %}
{{ var }}
{% endblock %}
{% endautoescape %}
--DATA--
return array('var' => ' ')
--EXPECT--
<br />
--TEST--
"autoescape" tag does not double-escape
--TEMPLATE--
{% autoescape 'html' %}
{{ var|escape }}
{% endautoescape %}
--DATA--
return array('var' => ' ')
--EXPECT--
<br />
--TEST--
"autoescape" tag applies escaping after calling functions
--TEMPLATE--
autoescape false
{% autoescape false %}
safe_br
{{ safe_br() }}
unsafe_br
{{ unsafe_br() }}
{% endautoescape %}
autoescape 'html'
{% autoescape 'html' %}
safe_br
{{ safe_br() }}
unsafe_br
{{ unsafe_br() }}
unsafe_br()|raw
{{ (unsafe_br())|raw }}
safe_br()|escape
{{ (safe_br())|escape }}
safe_br()|raw
{{ (safe_br())|raw }}
unsafe_br()|escape
{{ (unsafe_br())|escape }}
{% endautoescape %}
autoescape js
{% autoescape 'js' %}
safe_br
{{ safe_br() }}
{% endautoescape %}
--DATA--
return array()
--EXPECT--
autoescape false
safe_br
unsafe_br
autoescape 'html'
safe_br
unsafe_br
<br />
unsafe_br()|raw
safe_br()|escape
<br />
safe_br()|raw
unsafe_br()|escape
<br />
autoescape js
safe_br
\x3Cbr\x20\x2F\x3E
--TEST--
"autoescape" tag does not apply escaping on literals
--TEMPLATE--
{% autoescape 'html' %}
1. Simple literal
{{ " " }}
2. Conditional expression with only literals
{{ true ? " " : " " }}
3. Conditional expression with a variable
{{ true ? " " : someVar }}
4. Nested conditionals with only literals
{{ true ? (true ? " " : " ") : "\n" }}
5. Nested conditionals with a variable
{{ true ? (true ? " " : someVar) : "\n" }}
6. Nested conditionals with a variable marked safe
{{ true ? (true ? " " : someVar|raw) : "\n" }}
{% endautoescape %}
--DATA--
return array()
--EXPECT--
1. Simple literal
2. Conditional expression with only literals
3. Conditional expression with a variable
<br />
4. Nested conditionals with only literals
5. Nested conditionals with a variable
<br />
6. Nested conditionals with a variable marked safe
--TEST--
"autoescape" tags can be nested at will
--TEMPLATE--
{{ var }}
{% autoescape 'html' %}
{{ var }}
{% autoescape false %}
{{ var }}
{% autoescape 'html' %}
{{ var }}
{% endautoescape %}
{{ var }}
{% endautoescape %}
{{ var }}
{% endautoescape %}
{{ var }}
--DATA--
return array('var' => ' ')
--EXPECT--
<br />
<br />
<br />
<br />
<br />
--TEST--
"autoescape" tag applies escaping to object method calls
--TEMPLATE--
{% autoescape 'html' %}
{{ user.name }}
{{ user.name|lower }}
{{ user }}
{% endautoescape %}
--EXPECT--
Fabien<br />
fabien<br />
Fabien<br />
--TEST--
"autoescape" tag does not escape when raw is used as a filter
--TEMPLATE--
{% autoescape 'html' %}
{{ var|raw }}
{% endautoescape %}
--DATA--
return array('var' => ' ')
--EXPECT--
--TEST--
"autoescape" tag accepts an escaping strategy
--TEMPLATE--
{% autoescape true js %}{{ var }}{% endautoescape %}
{% autoescape true html %}{{ var }}{% endautoescape %}
{% autoescape 'js' %}{{ var }}{% endautoescape %}
{% autoescape 'html' %}{{ var }}{% endautoescape %}
--DATA--
return array('var' => ' "')
--EXPECT--
\x3Cbr\x20\x2F\x3E\x22
<br />"
\x3Cbr\x20\x2F\x3E\x22
<br />"
--TEST--
escape types
--TEMPLATE--
1. autoescape 'html' |escape('js')
{% autoescape 'html' %}
{% endautoescape %}
2. autoescape 'html' |escape('js')
{% autoescape 'html' %}
{% endautoescape %}
3. autoescape 'js' |escape('js')
{% autoescape 'js' %}
{% endautoescape %}
4. no escape
{% autoescape false %}
{% endautoescape %}
5. |escape('js')|escape('html')
{% autoescape false %}
{% endautoescape %}
6. autoescape 'html' |escape('js')|escape('html')
{% autoescape 'html' %}
{% endautoescape %}
--DATA--
return array('msg' => "<>\n'\"")
--EXPECT--
1. autoescape 'html' |escape('js')
2. autoescape 'html' |escape('js')
3. autoescape 'js' |escape('js')
4. no escape
5. |escape('js')|escape('html')
6. autoescape 'html' |escape('js')|escape('html')
--TEST--
"autoescape" tag do not applies escaping on filter arguments
--TEMPLATE--
{% autoescape 'html' %}
{{ var|nl2br(" ") }}
{{ var|nl2br(" "|escape) }}
{{ var|nl2br(sep) }}
{{ var|nl2br(sep|raw) }}
{{ var|nl2br(sep|escape) }}
{% endautoescape %}
--DATA--
return array('var' => "\nTwig", 'sep' => ' ')
--EXPECT--
<Fabien>
Twig
<Fabien><br />
Twig
<Fabien>
Twig
<Fabien>
Twig
<Fabien><br />
Twig
--TEST--
"autoescape" tag applies escaping after calling filters
--TEMPLATE--
{% autoescape 'html' %}
(escape_and_nl2br is an escaper filter)
1. Don't escape escaper filter output
( var is escaped by |escape_and_nl2br, line-breaks are added,
the output is not escaped )
{{ var|escape_and_nl2br }}
2. Don't escape escaper filter output
( var is escaped by |escape_and_nl2br, line-breaks are added,
the output is not escaped, |raw is redundant )
{{ var|escape_and_nl2br|raw }}
3. Explicit escape
( var is escaped by |escape_and_nl2br, line-breaks are added,
the output is explicitly escaped by |escape )
{{ var|escape_and_nl2br|escape }}
4. Escape non-escaper filter output
( var is upper-cased by |upper,
the output is auto-escaped )
{{ var|upper }}
5. Escape if last filter is not an escaper
( var is escaped by |escape_and_nl2br, line-breaks are added,
the output is upper-cased by |upper,
the output is auto-escaped as |upper is not an escaper )
{{ var|escape_and_nl2br|upper }}
6. Don't escape escaper filter output
( var is upper cased by upper,
the output is escaped by |escape_and_nl2br, line-breaks are added,
the output is not escaped as |escape_and_nl2br is an escaper )
{{ var|upper|escape_and_nl2br }}
7. Escape if last filter is not an escaper
( the output of |format is "" ~ var ~ "",
the output is auto-escaped )
{{ "%s"|format(var) }}
8. Escape if last filter is not an escaper
( the output of |format is "" ~ var ~ "",
|raw is redundant,
the output is auto-escaped )
{{ "%s"|raw|format(var) }}
9. Don't escape escaper filter output
( the output of |format is "" ~ var ~ "",
the output is not escaped due to |raw filter at the end )
{{ "%s"|format(var)|raw }}
10. Don't escape escaper filter output
( the output of |format is "" ~ var ~ "",
the output is not escaped due to |raw filter at the end,
the |raw filter on var is redundant )
{{ "%s"|format(var|raw)|raw }}
{% endautoescape %}
--DATA--
return array('var' => "\nTwig")
--EXPECT--
(escape_and_nl2br is an escaper filter)
1. Don't escape escaper filter output
( var is escaped by |escape_and_nl2br, line-breaks are added,
the output is not escaped )
<Fabien>
Twig
2. Don't escape escaper filter output
( var is escaped by |escape_and_nl2br, line-breaks are added,
the output is not escaped, |raw is redundant )
<Fabien>
Twig
3. Explicit escape
( var is escaped by |escape_and_nl2br, line-breaks are added,
the output is explicitly escaped by |escape )
<Fabien><br />
Twig
4. Escape non-escaper filter output
( var is upper-cased by |upper,
the output is auto-escaped )
<FABIEN>
TWIG
5. Escape if last filter is not an escaper
( var is escaped by |escape_and_nl2br, line-breaks are added,
the output is upper-cased by |upper,
the output is auto-escaped as |upper is not an escaper )
<FABIEN><BR />
TWIG
6. Don't escape escaper filter output
( var is upper cased by upper,
the output is escaped by |escape_and_nl2br, line-breaks are added,
the output is not escaped as |escape_and_nl2br is an escaper )
<FABIEN>
TWIG
7. Escape if last filter is not an escaper
( the output of |format is "" ~ var ~ "",
the output is auto-escaped )
<b><Fabien>
Twig</b>
8. Escape if last filter is not an escaper
( the output of |format is "" ~ var ~ "",
|raw is redundant,
the output is auto-escaped )
<b><Fabien>
Twig</b>
9. Don't escape escaper filter output
( the output of |format is "" ~ var ~ "",
the output is not escaped due to |raw filter at the end )
Twig
10. Don't escape escaper filter output
( the output of |format is "" ~ var ~ "",
the output is not escaped due to |raw filter at the end,
the |raw filter on var is redundant )
Twig
--TEST--
"autoescape" tag applies escaping after calling filters, and before calling pre_escape filters
--TEMPLATE--
{% autoescape 'html' %}
(nl2br is pre_escaped for "html" and declared safe for "html")
1. Pre-escape and don't post-escape
( var|escape|nl2br )
{{ var|nl2br }}
2. Don't double-pre-escape
( var|escape|nl2br )
{{ var|escape|nl2br }}
3. Don't escape safe values
( var|raw|nl2br )
{{ var|raw|nl2br }}
4. Don't escape safe values
( var|escape|nl2br|nl2br )
{{ var|nl2br|nl2br }}
5. Re-escape values that are escaped for an other contexts
( var|escape_something|escape|nl2br )
{{ var|escape_something|nl2br }}
6. Still escape when using filters not declared safe
( var|escape|nl2br|upper|escape )
{{ var|nl2br|upper }}
{% endautoescape %}
--DATA--
return array('var' => "\nTwig")
--EXPECT--
(nl2br is pre_escaped for "html" and declared safe for "html")
1. Pre-escape and don't post-escape
( var|escape|nl2br )
<Fabien>
Twig
2. Don't double-pre-escape
( var|escape|nl2br )
<Fabien>
Twig
3. Don't escape safe values
( var|raw|nl2br )
Twig
4. Don't escape safe values
( var|escape|nl2br|nl2br )
<Fabien>
Twig
5. Re-escape values that are escaped for an other contexts
( var|escape_something|escape|nl2br )
<FABIEN>
TWIG
6. Still escape when using filters not declared safe
( var|escape|nl2br|upper|escape )
<FABIEN><BR />
TWIG
--TEST--
"autoescape" tag handles filters preserving the safety
--TEMPLATE--
{% autoescape 'html' %}
(preserves_safety is preserving safety for "html")
1. Unsafe values are still unsafe
( var|preserves_safety|escape )
{{ var|preserves_safety }}
2. Safe values are still safe
( var|escape|preserves_safety )
{{ var|escape|preserves_safety }}
3. Re-escape values that are escaped for an other contexts
( var|escape_something|preserves_safety|escape )
{{ var|escape_something|preserves_safety }}
4. Still escape when using filters not declared safe
( var|escape|preserves_safety|replace({'FABIEN': 'FABPOT'})|escape )
{{ var|escape|preserves_safety|replace({'FABIEN': 'FABPOT'}) }}
{% endautoescape %}
--DATA--
return array('var' => "\nTwig")
--EXPECT--
(preserves_safety is preserving safety for "html")
1. Unsafe values are still unsafe
( var|preserves_safety|escape )
<FABIEN>
TWIG
2. Safe values are still safe
( var|escape|preserves_safety )
<FABIEN>
TWIG
3. Re-escape values that are escaped for an other contexts
( var|escape_something|preserves_safety|escape )
<FABIEN>
TWIG
4. Still escape when using filters not declared safe
( var|escape|preserves_safety|replace({'FABIEN': 'FABPOT'})|escape )
<FABPOT>
TWIG
--TEST--
"block" tag
--TEMPLATE--
{% block title1 %}FOO{% endblock %}
{% block title2 foo|lower %}
--TEMPLATE(foo.twig)--
{% block content %}{% endblock %}
--DATA--
return array('foo' => 'bar')
--EXPECT--
FOObar
--TEST--
"block" tag
--TEMPLATE--
{% block content %}
{% block content %}
{% endblock %}
{% endblock %}
--DATA--
return array()
--EXCEPTION--
Twig_Error_Syntax: The block 'content' has already been defined line 2 in "index.twig" at line 3
--TEST--
"§" special chars in a block name
--TEMPLATE--
{% block § %}
§
{% endblock § %}
--DATA--
return array()
--EXPECT--
§
--TEST--
"embed" tag
--TEMPLATE--
FOO
{% embed "foo.twig" %}
{% block c1 %}
{{ parent() }}
block1extended
{% endblock %}
{% endembed %}
BAR
--TEMPLATE(foo.twig)--
A
{% block c1 %}
block1
{% endblock %}
B
{% block c2 %}
block2
{% endblock %}
C
--DATA--
return array()
--EXPECT--
FOO
A
block1
block1extended
B
block2
C
BAR
--TEST--
"embed" tag
--TEMPLATE(index.twig)--
FOO
{% embed "foo.twig" %}
{% block c1 %}
{{ nothing }}
{% endblock %}
{% endembed %}
BAR
--TEMPLATE(foo.twig)--
{% block c1 %}{% endblock %}
--DATA--
return array()
--EXCEPTION--
Twig_Error_Runtime: Variable "nothing" does not exist in "index.twig" at line 5
--TEST--
"embed" tag
--TEMPLATE--
FOO
{% embed "foo.twig" %}
{% block c1 %}
{{ parent() }}
block1extended
{% endblock %}
{% endembed %}
{% embed "foo.twig" %}
{% block c1 %}
{{ parent() }}
block1extended
{% endblock %}
{% endembed %}
BAR
--TEMPLATE(foo.twig)--
A
{% block c1 %}
block1
{% endblock %}
B
{% block c2 %}
block2
{% endblock %}
C
--DATA--
return array()
--EXPECT--
FOO
A
block1
block1extended
B
block2
C
A
block1
block1extended
B
block2
C
BAR
--TEST--
"embed" tag
--TEMPLATE--
{% embed "foo.twig" %}
{% block c1 %}
{{ parent() }}
{% embed "foo.twig" %}
{% block c1 %}
{{ parent() }}
block1extended
{% endblock %}
{% endembed %}
{% endblock %}
{% endembed %}
--TEMPLATE(foo.twig)--
A
{% block c1 %}
block1
{% endblock %}
B
{% block c2 %}
block2
{% endblock %}
C
--DATA--
return array()
--EXPECT--
A
block1
A
block1
block1extended
B
block2
C
B
block2
C
--TEST--
"embed" tag
--TEMPLATE--
{% extends "base.twig" %}
{% block c1 %}
{{ parent() }}
blockc1baseextended
{% endblock %}
{% block c2 %}
{{ parent() }}
{% embed "foo.twig" %}
{% block c1 %}
{{ parent() }}
block1extended
{% endblock %}
{% endembed %}
{% endblock %}
--TEMPLATE(base.twig)--
A
{% block c1 %}
blockc1base
{% endblock %}
{% block c2 %}
blockc2base
{% endblock %}
B
--TEMPLATE(foo.twig)--
A
{% block c1 %}
block1
{% endblock %}
B
{% block c2 %}
block2
{% endblock %}
C
--DATA--
return array()
--EXPECT--
A
blockc1base
blockc1baseextended
blockc2base
A
block1
block1extended
B
block2
CB--TEST--
"filter" tag applies a filter on its children
--TEMPLATE--
{% filter upper %}
Some text with a {{ var }}
{% endfilter %}
--DATA--
return array('var' => 'var')
--EXPECT--
SOME TEXT WITH A VAR
--TEST--
"filter" tag applies a filter on its children
--TEMPLATE--
{% filter json_encode|raw %}test{% endfilter %}
--DATA--
return array()
--EXPECT--
"test"
--TEST--
"filter" tags accept multiple chained filters
--TEMPLATE--
{% filter lower|title %}
{{ var }}
{% endfilter %}
--DATA--
return array('var' => 'VAR')
--EXPECT--
Var
--TEST--
"filter" tags can be nested at will
--TEMPLATE--
{% filter lower|title %}
{{ var }}
{% filter upper %}
{{ var }}
{% endfilter %}
{{ var }}
{% endfilter %}
--DATA--
return array('var' => 'var')
--EXPECT--
Var
Var
Var
--TEST--
"filter" tag applies the filter on "for" tags
--TEMPLATE--
{% filter upper %}
{% for item in items %}
{{ item }}
{% endfor %}
{% endfilter %}
--DATA--
return array('items' => array('a', 'b'))
--EXPECT--
A
B
--TEST--
"filter" tag applies the filter on "if" tags
--TEMPLATE--
{% filter upper %}
{% if items %}
{{ items|join(', ') }}
{% endif %}
{% if items.3 is defined %}
FOO
{% else %}
{{ items.1 }}
{% endif %}
{% if items.3 is defined %}
FOO
{% elseif items.1 %}
{{ items.0 }}
{% endif %}
{% endfilter %}
--DATA--
return array('items' => array('a', 'b'))
--EXPECT--
A, B
B
A
--TEST--
"for" tag takes a condition
--TEMPLATE--
{% for i in 1..5 if i is odd -%}
{{ loop.index }}.{{ i }}{{ foo.bar }}
{% endfor %}
--DATA--
return array('foo' => array('bar' => 'X'))
--CONFIG--
return array('strict_variables' => false)
--EXPECT--
1.1X
2.3X
3.5X
--TEST--
"for" tag keeps the context safe
--TEMPLATE--
{% for item in items %}
{% for item in items %}
* {{ item }}
{% endfor %}
* {{ item }}
{% endfor %}
--DATA--
return array('items' => array('a', 'b'))
--EXPECT--
* a
* b
* a
* a
* b
* b
--TEST--
"for" tag can use an "else" clause
--TEMPLATE--
{% for item in items %}
* {{ item }}
{% else %}
no item
{% endfor %}
--DATA--
return array('items' => array('a', 'b'))
--EXPECT--
* a
* b
--DATA--
return array('items' => array())
--EXPECT--
no item
--DATA--
return array()
--CONFIG--
return array('strict_variables' => false)
--EXPECT--
no item
--TEST--
"for" tag does not reset inner variables
--TEMPLATE--
{% for i in 1..2 %}
{% for j in 0..2 %}
{{k}}{% set k = k+1 %} {{ loop.parent.loop.index }}
{% endfor %}
{% endfor %}
--DATA--
return array('k' => 0)
--EXPECT--
0 1
1 1
2 1
3 2
4 2
5 2
--TEST--
"for" tag can iterate over keys and values
--TEMPLATE--
{% for key, item in items %}
* {{ key }}/{{ item }}
{% endfor %}
--DATA--
return array('items' => array('a', 'b'))
--EXPECT--
* 0/a
* 1/b
--TEST--
"for" tag can iterate over keys
--TEMPLATE--
{% for key in items|keys %}
* {{ key }}
{% endfor %}
--DATA--
return array('items' => array('a', 'b'))
--EXPECT--
* 0
* 1
--TEST--
"for" tag adds a loop variable to the context locally
--TEMPLATE--
{% for item in items %}
{% endfor %}
{% if loop is not defined %}WORKS{% endif %}
--DATA--
return array('items' => array())
--EXPECT--
WORKS
--TEST--
"for" tag adds a loop variable to the context
--TEMPLATE--
{% for item in items %}
* {{ loop.index }}/{{ loop.index0 }}
* {{ loop.revindex }}/{{ loop.revindex0 }}
* {{ loop.first }}/{{ loop.last }}/{{ loop.length }}
{% endfor %}
--DATA--
return array('items' => array('a', 'b'))
--EXPECT--
* 1/0
* 2/1
* 1//2
* 2/1
* 1/0
* /1/2
--TEST--
"for" tag
--TEMPLATE--
{% for i, item in items if loop.last > 0 %}
{% endfor %}
--DATA--
return array('items' => array('a', 'b'))
--EXCEPTION--
Twig_Error_Syntax: The "loop" variable cannot be used in a looping condition in "index.twig" at line 2
--TEST--
"for" tag
--TEMPLATE--
{% for i, item in items if i > 0 %}
{{ loop.last }}
{% endfor %}
--DATA--
return array('items' => array('a', 'b'))
--EXCEPTION--
Twig_Error_Syntax: The "loop.last" variable is not defined when looping with a condition in "index.twig" at line 3
--TEST--
"for" tag can use an "else" clause
--TEMPLATE--
{% for item in items %}
{% for item in items1 %}
* {{ item }}
{% else %}
no {{ item }}
{% endfor %}
{% else %}
no item1
{% endfor %}
--DATA--
return array('items' => array('a', 'b'), 'items1' => array())
--EXPECT--
no a
no b
--TEST--
"for" tag iterates over iterable and countable objects
--TEMPLATE--
{% for item in items %}
* {{ item }}
* {{ loop.index }}/{{ loop.index0 }}
* {{ loop.revindex }}/{{ loop.revindex0 }}
* {{ loop.first }}/{{ loop.last }}/{{ loop.length }}
{% endfor %}
{% for key, value in items %}
* {{ key }}/{{ value }}
{% endfor %}
{% for key in items|keys %}
* {{ key }}
{% endfor %}
--DATA--
class ItemsIteratorCountable implements Iterator, Countable
{
protected $values = array('foo' => 'bar', 'bar' => 'foo');
public function current() { return current($this->values); }
public function key() { return key($this->values); }
public function next() { return next($this->values); }
public function rewind() { return reset($this->values); }
public function valid() { return false !== current($this->values); }
public function count() { return count($this->values); }
}
return array('items' => new ItemsIteratorCountable())
--EXPECT--
* bar
* 1/0
* 2/1
* 1//2
* foo
* 2/1
* 1/0
* /1/2
* foo/bar
* bar/foo
* foo
* bar
--TEST--
"for" tag iterates over iterable objects
--TEMPLATE--
{% for item in items %}
* {{ item }}
* {{ loop.index }}/{{ loop.index0 }}
* {{ loop.first }}
{% endfor %}
{% for key, value in items %}
* {{ key }}/{{ value }}
{% endfor %}
{% for key in items|keys %}
* {{ key }}
{% endfor %}
--DATA--
class ItemsIterator implements Iterator
{
protected $values = array('foo' => 'bar', 'bar' => 'foo');
public function current() { return current($this->values); }
public function key() { return key($this->values); }
public function next() { return next($this->values); }
public function rewind() { return reset($this->values); }
public function valid() { return false !== current($this->values); }
}
return array('items' => new ItemsIterator())
--EXPECT--
* bar
* 1/0
* 1
* foo
* 2/1
*
* foo/bar
* bar/foo
* foo
* bar
--TEST--
"for" tags can be nested
--TEMPLATE--
{% for key, item in items %}
* {{ key }} ({{ loop.length }}):
{% for value in item %}
* {{ value }} ({{ loop.length }})
{% endfor %}
{% endfor %}
--DATA--
return array('items' => array('a' => array('a1', 'a2', 'a3'), 'b' => array('b1')))
--EXPECT--
* a (2):
* a1 (3)
* a2 (3)
* a3 (3)
* b (2):
* b1 (1)
--TEST--
"for" tag iterates over item values
--TEMPLATE--
{% for item in items %}
* {{ item }}
{% endfor %}
--DATA--
return array('items' => array('a', 'b'))
--EXPECT--
* a
* b
--TEST--
global variables
--TEMPLATE--
{% include "included.twig" %}
{% from "included.twig" import foobar %}
{{ foobar() }}
--TEMPLATE(included.twig)--
{% macro foobar() %}
called foobar
{% endmacro %}
--DATA--
return array();
--EXPECT--
called foobar
--TEST--
"if" creates a condition
--TEMPLATE--
{% if a is defined %}
{{ a }}
{% elseif b is defined %}
{{ b }}
{% else %}
NOTHING
{% endif %}
--DATA--
return array('a' => 'a')
--EXPECT--
a
--DATA--
return array('b' => 'b')
--EXPECT--
b
--DATA--
return array()
--EXPECT--
NOTHING
--TEST--
"if" takes an expression as a test
--TEMPLATE--
{% if a < 2 %}
A1
{% elseif a > 10 %}
A2
{% else %}
A3
{% endif %}
--DATA--
return array('a' => 1)
--EXPECT--
A1
--DATA--
return array('a' => 12)
--EXPECT--
A2
--DATA--
return array('a' => 7)
--EXPECT--
A3
--TEST--
"include" tag
--TEMPLATE--
FOO
{% include "foo.twig" %}
BAR
--TEMPLATE(foo.twig)--
FOOBAR
--DATA--
return array()
--EXPECT--
FOO
FOOBAR
BAR
--TEST--
"include" tag allows expressions for the template to include
--TEMPLATE--
FOO
{% include foo %}
BAR
--TEMPLATE(foo.twig)--
FOOBAR
--DATA--
return array('foo' => 'foo.twig')
--EXPECT--
FOO
FOOBAR
BAR
--TEST--
"include" tag
--TEMPLATE--
{% include ["foo.twig", "bar.twig"] ignore missing %}
{% include "foo.twig" ignore missing %}
{% include "foo.twig" ignore missing with {} %}
{% include "foo.twig" ignore missing with {} only %}
--DATA--
return array()
--EXPECT--
--TEST--
"include" tag
--TEMPLATE--
{% extends "base.twig" %}
{% block content %}
{{ parent() }}
{% endblock %}
--TEMPLATE(base.twig)--
{% block content %}
{% include "foo.twig" %}
{% endblock %}
--DATA--
return array();
--EXCEPTION--
Twig_Error_Loader: Template "foo.twig" is not defined in "base.twig" at line 3.
--TEST--
"include" tag
--TEMPLATE--
{% include "foo.twig" %}
--DATA--
return array();
--EXCEPTION--
Twig_Error_Loader: Template "foo.twig" is not defined in "index.twig" at line 2.
--TEST--
"include" tag accept variables and only
--TEMPLATE--
{% include "foo.twig" %}
{% include "foo.twig" only %}
{% include "foo.twig" with {'foo1': 'bar'} %}
{% include "foo.twig" with {'foo1': 'bar'} only %}
--TEMPLATE(foo.twig)--
{% for k, v in _context %}{{ k }},{% endfor %}
--DATA--
return array('foo' => 'bar')
--EXPECT--
foo,global,_parent,
global,_parent,
foo,global,foo1,_parent,
foo1,global,_parent,
--TEST--
"include" tag accepts Twig_Template instance
--TEMPLATE--
{% include foo %} FOO
--TEMPLATE(foo.twig)--
BAR
--DATA--
return array('foo' => $twig->loadTemplate('foo.twig'))
--EXPECT--
BAR FOO
--TEST--
"include" tag
--TEMPLATE--
{% include ["foo.twig", "bar.twig"] %}
{% include ["bar.twig", "foo.twig"] %}
--TEMPLATE(foo.twig)--
foo
--DATA--
return array()
--EXPECT--
foo
foo
--TEST--
"include" tag accept variables
--TEMPLATE--
{% include "foo.twig" with {'foo': 'bar'} %}
{% include "foo.twig" with vars %}
--TEMPLATE(foo.twig)--
{{ foo }}
--DATA--
return array('vars' => array('foo' => 'bar'))
--EXPECT--
bar
bar
--TEST--
"extends" tag
--TEMPLATE--
{% extends "foo.twig" %}
{% block content %}
FOO
{% endblock %}
--TEMPLATE(foo.twig)--
{% block content %}{% endblock %}
--DATA--
return array()
--EXPECT--
FOO
--TEST--
block_expr2
--TEMPLATE--
{% extends "base2.twig" %}
{% block element -%}
Element:
{{- parent() -}}
{% endblock %}
--TEMPLATE(base2.twig)--
{% extends "base.twig" %}
--TEMPLATE(base.twig)--
{% spaceless %}
{% block element -%}
{%- if item.children is defined %}
{%- for item in item.children %}
{{- block('element') -}}
{% endfor %}
{%- endif -%}
--TEST--
"§" custom tag
--TEMPLATE--
{% § %}
--DATA--
return array()
--EXPECT--
§
--TEST--
Whitespace trimming on tags.
--TEMPLATE--
{{ 5 * '{#-'|length }}
{{ '{{-'|length * 5 + '{%-'|length }}
Trim on control tag:
{% for i in range(1, 9) -%}
{{ i }}
{%- endfor %}
Trim on output tag:
{% for i in range(1, 9) %}
{{- i -}}
{% endfor %}
Trim comments:
{#- Invisible -#}
After the comment.
Trim leading space:
{% if leading %}
{{- leading }}
{% endif %}
{%- if leading %}
{{- leading }}
{%- endif %}
Trim trailing space:
{% if trailing -%}
{{ trailing -}}
{% endif -%}
Combined:
{%- if both -%}
{{- both -}}
{%- endif -%}
end
--DATA--
return array('leading' => 'leading space', 'trailing' => 'trailing space', 'both' => 'both')
--EXPECT--
15
18
Trim on control tag:
123456789
Trim on output tag:
123456789
Trim comments:After the comment.
Trim leading space:
leading space
leading space
Trim trailing space:
trailing spaceCombined:
both
end
--TEST--
"use" tag
--TEMPLATE--
{% use "blocks.twig" with content as foo %}
{{ block('foo') }}
--TEMPLATE(blocks.twig)--
{% block content 'foo' %}
--DATA--
return array()
--EXPECT--
foo
--TEST--
"use" tag
--TEMPLATE--
{% use "blocks.twig" %}
{{ block('content') }}
--TEMPLATE(blocks.twig)--
{% block content 'foo' %}
--DATA--
return array()
--EXPECT--
foo
--TEST--
"use" tag
--TEMPLATE--
{% use "foo.twig" %}
--TEMPLATE(foo.twig)--
{% use "bar.twig" %}
--TEMPLATE(bar.twig)--
--DATA--
return array()
--EXPECT--
--TEST--
"use" tag
--TEMPLATE--
{% use "foo.twig" %}
{{ block('content') }}
{{ block('foo') }}
{{ block('bar') }}
--TEMPLATE(foo.twig)--
{% use "bar.twig" %}
{% block content 'foo' %}
{% block foo 'foo' %}
--TEMPLATE(bar.twig)--
{% block content 'bar' %}
{% block bar 'bar' %}
--DATA--
return array()
--EXPECT--
foo
foo
bar
--TEST--
"use" tag
--TEMPLATE--
{% use "ancestor.twig" %}
{% use "parent.twig" %}
{{ block('container') }}
--TEMPLATE(parent.twig)--
{% block sub_container %}