Issue #2172235 by joelpittet: Upgrade Twig to 1.15.* from 1.12.*.

8.0.x
webchick 2014-01-17 14:04:01 -08:00
parent 56d3a638f4
commit af1ca73aca
162 changed files with 2257 additions and 938 deletions

View File

@ -19,7 +19,7 @@
"symfony/serializer": "2.3.*",
"symfony/validator": "2.3.*",
"symfony/yaml": "2.3.*",
"twig/twig": "1.12.*",
"twig/twig": "1.15.*",
"doctrine/common": "dev-bmaster#99b44f52a1b844f9c4c34e618b160664d5c27daf",
"doctrine/annotations": "dev-master#463d926a8dcc49271cb7db5a08364a70ed6e3cd3",
"guzzle/http": "3.7.*",

16
composer.lock generated
View File

@ -3,7 +3,7 @@
"This file locks the dependencies of your project to a known state",
"Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file"
],
"hash": "06006c1512fb829fcdb498b7e515901d",
"hash": "14b55ea7402f04abe36b677892e442f3",
"packages": [
{
"name": "doctrine/annotations",
@ -1877,16 +1877,16 @@
},
{
"name": "twig/twig",
"version": "v1.12.3",
"version": "v1.15.0",
"source": {
"type": "git",
"url": "https://github.com/fabpot/Twig.git",
"reference": "v1.12.3"
"reference": "85e4ff98000157ff753d934b9f13659a953f5666"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/fabpot/Twig/zipball/v1.12.3",
"reference": "v1.12.3",
"url": "https://api.github.com/repos/fabpot/Twig/zipball/85e4ff98000157ff753d934b9f13659a953f5666",
"reference": "85e4ff98000157ff753d934b9f13659a953f5666",
"shasum": ""
},
"require": {
@ -1895,7 +1895,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.12-dev"
"dev-master": "1.15-dev"
}
},
"autoload": {
@ -1905,7 +1905,7 @@
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3"
"BSD-3-Clause"
],
"authors": [
{
@ -1922,7 +1922,7 @@
"keywords": [
"templating"
],
"time": "2013-04-08 12:40:11"
"time": "2013-12-06 07:47:10"
},
{
"name": "zendframework/zend-escaper",

View File

@ -4,6 +4,7 @@ php:
- 5.2
- 5.3
- 5.4
- 5.5
env:
- TWIG_EXT=no

View File

@ -4,6 +4,10 @@ Lead Developer:
- Fabien Potencier <fabien.potencier@symfony-project.org>
C Extension Developer:
- Derick Rethans <derick@derickrethans.nl>
Project Founder:
- Armin Ronacher <armin.ronacher@active-4.com>

View File

@ -1,3 +1,59 @@
* 1.15.0 (2013-12-06)
* made ignoreStrictCheck in Template::getAttribute() works with __call() methods throwing BadMethodCallException
* added min and max functions
* added the round filter
* fixed a bug that prevented the optimizers to be enabled/disabled selectively
* fixed first and last filters for UTF-8 strings
* added a source function to include the content of a template without rendering it
* fixed the C extension sandbox behavior when get or set is prepend to method name
* 1.14.2 (2013-10-30)
* fixed error filename/line when an error occurs in an included file
* allowed operators that contain whitespaces to have more than one whitespace
* allowed tests to be made of 1 or 2 words (like "same as" or "divisible by")
* 1.14.1 (2013-10-15)
* made it possible to use named operators as variables
* fixed the possibility to have a variable named 'matches'
* added support for PHP 5.5 DateTimeInterface
* 1.14.0 (2013-10-03)
* fixed usage of the html_attr escaping strategy to avoid double-escaping with the html strategy
* added new operators: ends with, starts with, and matches
* fixed some compatibility issues with HHVM
* added a way to add custom escaping strategies
* fixed the C extension compilation on Windows
* fixed the batch filter when using a fill argument with an exact match of elements to batch
* fixed the filesystem loader cache when a template name exists in several namespaces
* fixed template_from_string when the template includes or extends other ones
* fixed a crash of the C extension on an edge case
* 1.13.2 (2013-08-03)
* fixed the error line number for an error occurs in and embedded template
* fixed crashes of the C extension on some edge cases
* 1.13.1 (2013-06-06)
* added the possibility to ignore the filesystem constructor argument in Twig_Loader_Filesystem
* fixed Twig_Loader_Chain::exists() for a loader which implements Twig_ExistsLoaderInterface
* adjusted backtrace call to reduce memory usage when an error occurs
* added support for object instances as the second argument of the constant test
* fixed the include function when used in an assignment
* 1.13.0 (2013-05-10)
* fixed getting a numeric-like item on a variable ('09' for instance)
* fixed getting a boolean or float key on an array, so it is consistent with PHP's array access:
`{{ array[false] }}` behaves the same as `echo $array[false];` (equals `$array[0]`)
* made the escape filter 20% faster for happy path (escaping string for html with UTF-8)
* changed ☃ to § in tests
* enforced usage of named arguments after positional ones
* 1.12.3 (2013-04-08)
* fixed a security issue in the filesystem loader where it was possible to include a template one

View File

@ -1,8 +1,6 @@
Twig, the flexible, fast, and secure template language for PHP
==============================================================
[![Build Status](https://secure.travis-ci.org/fabpot/Twig.png?branch=master)](http://travis-ci.org/fabpot/Twig)
Twig is a template language for PHP, released under the new BSD license (code
and documentation).
@ -12,6 +10,6 @@ inspired the Twig runtime environment.
More Information
----------------
Read the [documentation][1] for more information.
Read the `documentation`_ for more information.
[1]: http://twig.sensiolabs.org/documentation
.. _documentation: http://twig.sensiolabs.org/documentation

View File

@ -4,7 +4,7 @@
"description": "Twig, the flexible, fast, and secure template language for PHP",
"keywords": ["templating"],
"homepage": "http://twig.sensiolabs.org",
"license": "BSD-3",
"license": "BSD-3-Clause",
"authors": [
{
"name": "Fabien Potencier",
@ -25,7 +25,7 @@
},
"extra": {
"branch-alias": {
"dev-master": "1.12-dev"
"dev-master": "1.15-dev"
}
}
}

View File

@ -177,7 +177,7 @@ argument::
$filter = new Twig_SimpleFilter('rot13', 'str_rot13', $options);
Environment aware Filters
Environment-aware Filters
~~~~~~~~~~~~~~~~~~~~~~~~~
If you want to access the current environment instance in your filter, set the
@ -191,7 +191,7 @@ environment as the first argument to the filter call::
return str_rot13($string);
}, array('needs_environment' => true));
Context aware Filters
Context-aware Filters
~~~~~~~~~~~~~~~~~~~~~
If you want to access the current context in your filter, set the
@ -211,14 +211,14 @@ Automatic Escaping
~~~~~~~~~~~~~~~~~~
If automatic escaping is enabled, the output of the filter may be escaped
before printing. If your filter acts as an escaper (or explicitly outputs html
before printing. If your filter acts as an escaper (or explicitly outputs HTML
or JavaScript code), you will want the raw output to be printed. In such a
case, set the ``is_safe`` option::
$filter = new Twig_SimpleFilter('nl2br', 'nl2br', array('is_safe' => array('html')));
Some filters may need to work on input that is already escaped or safe, for
example when adding (safe) html tags to originally unsafe output. In such a
example when adding (safe) HTML tags to originally unsafe output. In such a
case, set the ``pre_escape`` option to escape the input data before it is run
through your filter::
@ -241,11 +241,11 @@ The following filters will be matched by the above defined dynamic filter:
A dynamic filter can define more than one dynamic parts::
$filter = new Twig_SimpleFilter('*_path', function ($name, $suffix, $arguments) {
$filter = new Twig_SimpleFilter('*_path_*', function ($name, $suffix, $arguments) {
// ...
});
The filter will receive all dynamic part values before the normal filters
The filter will receive all dynamic part values before the normal filter
arguments, but after the environment and the context. For instance, a call to
``'foo'|a_path_b()`` will result in the following arguments to be passed to
the filter: ``('a', 'b', 'foo')``.
@ -277,12 +277,64 @@ to create an instance of ``Twig_SimpleTest``::
});
$twig->addTest($test);
Tests do not support any options.
Tests allow you to create custom application specific logic for evaluating
boolean conditions. As a simple example, let's create a Twig test that checks if
objects are 'red'::
$twig = new Twig_Environment($loader)
$test = new Twig_SimpleTest('red', function ($value) {
if (isset($value->color) && $value->color == 'red') {
return true;
}
if (isset($value->paint) && $value->paint == 'red') {
return true;
}
return false;
});
$twig->addTest($test);
Test functions should always return true/false.
When creating tests you can use the ``node_class`` option to provide custom test
compilation. This is useful if your test can be compiled into PHP primitives.
This is used by many of the tests built into Twig::
$twig = new Twig_Environment($loader)
$test = new Twig_SimpleTest(
'odd',
null,
array('node_class' => 'Twig_Node_Expression_Test_Odd'));
$twig->addTest($test);
class Twig_Node_Expression_Test_Odd extends Twig_Node_Expression_Test
{
public function compile(Twig_Compiler $compiler)
{
$compiler
->raw('(')
->subcompile($this->getNode('node'))
->raw(' % 2 == 1')
->raw(')')
;
}
}
The above example shows how you can create tests that use a node class. The
node class has access to one sub-node called 'node'. This sub-node contains the
value that is being tested. When the ``odd`` filter is used in code such as:
.. code-block:: jinja
{% if my_value is odd %}
The ``node`` sub-node will contain an expression of ``my_value``. Node-based
tests also have access to the ``arguments`` node. This node will contain the
various other arguments that have been provided to your test.
Tags
----
One of the most exciting feature of a template engine like Twig is the
One of the most exciting features of a template engine like Twig is the
possibility to define new language constructs. This is also the most complex
feature as you need to understand how Twig's internals work.
@ -330,14 +382,15 @@ Now, let's see the actual code of this class::
{
public function parse(Twig_Token $token)
{
$lineno = $token->getLine();
$name = $this->parser->getStream()->expect(Twig_Token::NAME_TYPE)->getValue();
$this->parser->getStream()->expect(Twig_Token::OPERATOR_TYPE, '=');
$value = $this->parser->getExpressionParser()->parseExpression();
$parser = $this->parser;
$stream = $parser->getStream();
$this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
$name = $stream->expect(Twig_Token::NAME_TYPE)->getValue();
$stream->expect(Twig_Token::OPERATOR_TYPE, '=');
$value = $parser->getExpressionParser()->parseExpression();
$stream->expect(Twig_Token::BLOCK_END_TYPE);
return new Project_Set_Node($name, $value, $lineno, $this->getTag());
return new Project_Set_Node($name, $value, $token->getLine(), $this->getTag());
}
public function getTag()
@ -384,9 +437,9 @@ The ``Project_Set_Node`` class itself is rather simple::
class Project_Set_Node extends Twig_Node
{
public function __construct($name, Twig_Node_Expression $value, $lineno, $tag = null)
public function __construct($name, Twig_Node_Expression $value, $line, $tag = null)
{
parent::__construct(array('value' => $value), array('name' => $name), $lineno, $tag);
parent::__construct(array('value' => $value), array('name' => $name), $line, $tag);
}
public function compile(Twig_Compiler $compiler)
@ -640,7 +693,7 @@ responsible for parsing the tag and compiling it to PHP.
Operators
~~~~~~~~~
The ``getOperators()`` methods allows to add new operators. Here is how to add
The ``getOperators()`` methods lets you add new operators. Here is how to add
``!``, ``||``, and ``&&`` operators::
class Project_Twig_Extension extends Twig_Extension
@ -664,7 +717,7 @@ The ``getOperators()`` methods allows to add new operators. Here is how to add
Tests
~~~~~
The ``getTests()`` methods allows to add new test functions::
The ``getTests()`` method lets you add new test functions::
class Project_Twig_Extension extends Twig_Extension
{
@ -682,16 +735,8 @@ Overloading
-----------
To overload an already defined filter, test, operator, global variable, or
function, define it again **as late as possible**::
$twig = new Twig_Environment($loader);
$twig->addFilter(new Twig_SimpleFilter('date', function ($timestamp, $format = 'F j, Y H:i') {
// do something different from the built-in date filter
}));
Here, we have overloaded the built-in ``date`` filter with a custom one.
That also works with an extension::
function, re-define it in an extension and register it **as late as
possible** (order matters)::
class MyCoreExtension extends Twig_Extension
{
@ -716,6 +761,19 @@ That also works with an extension::
$twig = new Twig_Environment($loader);
$twig->addExtension(new MyCoreExtension());
Here, we have overloaded the built-in ``date`` filter with a custom one.
If you do the same on the Twig_Environment itself, beware that it takes
precedence over any other registered extensions::
$twig = new Twig_Environment($loader);
$twig->addFilter(new Twig_SimpleFilter('date', function ($timestamp, $format = 'F j, Y H:i') {
// do something different from the built-in date filter
}));
// the date filter will come from the above registration, not
// from the registered extension below
$twig->addExtension(new MyCoreExtension());
.. caution::
Note that overloading the built-in Twig elements is not recommended as it

View File

@ -244,14 +244,14 @@ Automatic Escaping
~~~~~~~~~~~~~~~~~~
If automatic escaping is enabled, the output of the filter may be escaped
before printing. If your filter acts as an escaper (or explicitly outputs html
or javascript code), you will want the raw output to be printed. In such a
before printing. If your filter acts as an escaper (or explicitly outputs HTML
or JavaScript code), you will want the raw output to be printed. In such a
case, set the ``is_safe`` option::
$filter = new Twig_Filter_Function('nl2br', array('is_safe' => array('html')));
Some filters may need to work on input that is already escaped or safe, for
example when adding (safe) html tags to originally unsafe output. In such a
example when adding (safe) HTML tags to originally unsafe output. In such a
case, set the ``pre_escape`` option to escape the input data before it is run
through your filter::
@ -266,7 +266,7 @@ Dynamic Filters
A filter name containing the special ``*`` character is a dynamic filter as
the ``*`` can be any string::
$twig->addFilter('*_path', new Twig_Filter_Function('twig_path'));
$twig->addFilter('*_path_*', new Twig_Filter_Function('twig_path'));
function twig_path($name, $arguments)
{

View File

@ -348,10 +348,10 @@ tag, ``autoescape``, and a filter, ``raw``.
When creating the escaper extension, you can switch on or off the global
output escaping strategy::
$escaper = new Twig_Extension_Escaper(true);
$escaper = new Twig_Extension_Escaper('html');
$twig->addExtension($escaper);
If set to ``true``, all variables in templates are escaped (using the ``html``
If set to ``html``, all variables in templates are escaped (using the ``html``
escaping strategy), except those using the ``raw`` filter:
.. code-block:: jinja
@ -417,15 +417,15 @@ The escaping rules are implemented as follows:
{{ var|upper|raw }} {# won't be escaped #}
* Automatic escaping is not applied if the last filter in the chain is marked
safe for the current context (e.g. ``html`` or ``js``). ``escaper`` and
``escaper('html')`` are marked safe for html, ``escaper('js')`` is marked
safe for javascript, ``raw`` is marked safe for everything.
safe for the current context (e.g. ``html`` or ``js``). ``escape`` and
``escape('html')`` are marked safe for HTML, ``escape('js')`` is marked
safe for JavaScript, ``raw`` is marked safe for everything.
.. code-block:: jinja
{% autoescape 'js' %}
{{ var|escape('html') }} {# will be escaped for html and javascript #}
{{ var }} {# will be escaped for javascript #}
{{ var|escape('html') }} {# will be escaped for HTML and JavaScript #}
{{ var }} {# will be escaped for JavaScript #}
{{ var|escape('js') }} {# won't be double-escaped #}
{% endautoescape %}
@ -499,16 +499,16 @@ to enable by passing them to the constructor::
Twig supports the following optimizations:
* ``Twig_NodeVisitor_Optimizer::OPTIMIZE_ALL``, enables all optimizations
(this is the default value).
(this is the default value).
* ``Twig_NodeVisitor_Optimizer::OPTIMIZE_NONE``, disables all optimizations.
This reduces the compilation time, but it can increase the execution time
and the consumed memory.
This reduces the compilation time, but it can increase the execution time
and the consumed memory.
* ``Twig_NodeVisitor_Optimizer::OPTIMIZE_FOR``, optimizes the ``for`` tag by
removing the ``loop`` variable creation whenever possible.
removing the ``loop`` variable creation whenever possible.
* ``Twig_NodeVisitor_Optimizer::OPTIMIZE_RAW_FILTER``, removes the ``raw``
filter whenever possible.
filter whenever possible.
* ``Twig_NodeVisitor_Optimizer::OPTIMIZE_VAR_ACCESS``, simplifies the creation
and access of variables in the compiled templates whenever possible.
and access of variables in the compiled templates whenever possible.
Exceptions
----------

View File

@ -90,7 +90,7 @@ standards:
{% set foo_bar = 'foo' %}
* Indent your code inside tags (use the same indentation as the one used for
the main language of the file):
the target language of the rendered template):
.. code-block:: jinja

View File

@ -77,6 +77,9 @@ Tests
removed in Twig 3.x (use ``Twig_Test`` instead). In Twig 2.x,
``Twig_SimpleTest`` is just an alias for ``Twig_Test``.
* The ``sameas`` and ``divisibleby`` tests are deprecated in favor of ``same
as`` and ``divisible by`` respectively.
Interfaces
----------
@ -88,7 +91,9 @@ Interfaces
* ``Twig_NodeInterface`` (use ``Twig_Node`` instead)
* ``Twig_ParserInterface`` (use ``Twig_Parser`` instead)
* ``Twig_ExistsLoaderInterface`` (merged with ``Twig_LoaderInterface``)
* ``Twig_TemplateInterface`` (use ``Twig_Template`` instead)
* ``Twig_TemplateInterface`` (use ``Twig_Template`` instead, and use
those constants Twig_Template::ANY_CALL, Twig_Template::ARRAY_CALL,
Twig_Template::METHOD_CALL)
Globals
-------

View File

@ -6,9 +6,9 @@ The ``abs`` filter returns the absolute value.
.. code-block:: jinja
{# number = -5 #}
{{ number|abs }}
{# outputs 5 #}
.. note::

View File

@ -14,11 +14,11 @@ missing items:
<table>
{% for row in items|batch(3, 'No item') %}
<tr>
{% for column in row %}
<td>{{ column }}</td>
{% endfor %}
</tr>
<tr>
{% for column in row %}
<td>{{ column }}</td>
{% endfor %}
</tr>
{% endfor %}
</table>
@ -27,19 +27,19 @@ The above example will be rendered as:
.. code-block:: jinja
<table>
<tr>
<td>a</td>
<td>b</td>
<td>c</td>
<tr>
<td>a</td>
<td>b</td>
<td>c</td>
</tr>
<tr>
<td>d</td>
<td>e</td>
<td>f</td>
<tr>
<td>d</td>
<td>e</td>
<td>f</td>
</tr>
<tr>
<td>g</td>
<td>No item</td>
<td>No item</td>
<tr>
<td>g</td>
<td>No item</td>
<td>No item</td>
</tr>
</table>

View File

@ -21,8 +21,8 @@ is the input charset:
Arguments
---------
* ``from``: The input charset
* ``to``: The output charset
* ``from``: The input charset
* ``to``: The output charset
.. _`iconv`: http://php.net/iconv
.. _`mbstring`: http://php.net/mbstring

View File

@ -19,6 +19,10 @@ The ``date`` filter formats a date to a given format:
{{ post.published_at|date("m/d/Y") }}
The format specifier is the same as supported by `date`_,
except when the filtered data is of type `DateInterval`_, when the format must conform to
`DateInterval::format`_ instead.
The ``date`` filter accepts strings (it must be in a format supported by the
`strtotime`_ function), `DateTime`_ instances, or `DateInterval`_ instances. For
instance, to display the current date, filter the word "now":
@ -80,9 +84,11 @@ The default timezone can also be set globally by calling ``setTimezone()``:
Arguments
---------
* ``format``: The date format
* ``timezone``: The date timezone
* ``format``: The date format
* ``timezone``: The date timezone
.. _`strtotime`: http://www.php.net/strtotime
.. _`DateTime`: http://www.php.net/DateTime
.. _`DateInterval`: http://www.php.net/DateInterval
.. _`strtotime`: http://www.php.net/strtotime
.. _`DateTime`: http://www.php.net/DateTime
.. _`DateInterval`: http://www.php.net/DateInterval
.. _`date`: http://www.php.net/date
.. _`DateInterval::format`: http://www.php.net/DateInterval.format

View File

@ -17,7 +17,7 @@ it with the :doc:`date<date>` filter for formatting.
Arguments
---------
* ``modifier``: The modifier
* ``modifier``: The modifier
.. _`strtotime`: http://www.php.net/strtotime
.. _`DateTime`: http://www.php.net/DateTime

View File

@ -30,4 +30,4 @@ undefined:
Arguments
---------
* ``default``: The default value
* ``default``: The default value

View File

@ -5,6 +5,9 @@
The ``css``, ``url``, and ``html_attr`` strategies were added in Twig
1.9.0.
.. versionadded:: 1.14.0
The ability to define custom escapers was added in Twig 1.14.0.
The ``escape`` filter escapes a string for safe insertion into the final
output. It supports different escaping strategies depending on the template
context.
@ -84,10 +87,30 @@ The ``escape`` filter supports the following escaping strategies:
{{ var|escape(strategy)|raw }} {# won't be double-escaped #}
{% endautoescape %}
Custom Escapers
---------------
You can define custom escapers by calling the ``setEscaper()`` method on the
``core`` extension instance. The first argument is the escaper name (to be
used in the ``escape`` call) and the second one must be a valid PHP callable:
.. code-block:: php
$twig = new Twig_Environment($loader);
$twig->getExtension('core')->setEscaper('csv', 'csv_escaper'));
When called by Twig, the callable receives the Twig environment instance, the
string to escape, and the charset.
.. note::
Built-in escapers cannot be overridden mainly they should be considered as
the final implementation and also for better performance.
Arguments
---------
* ``strategy``: The escaping strategy
* ``charset``: The string charset
* ``strategy``: The escaping strategy
* ``charset``: The string charset
.. _`htmlspecialchars`: http://php.net/htmlspecialchars

View File

@ -2,15 +2,15 @@
==========
The ``format`` filter formats a given string by replacing the placeholders
(placeholders follows the `printf`_ notation):
(placeholders follows the `sprintf`_ notation):
.. code-block:: jinja
{{ "I like %s and %s."|format(foo, "bar") }}
{# returns I like foo and bar
{# outputs I like foo and bar
if the foo parameter equals to the foo string. #}
.. _`printf`: http://www.php.net/printf
.. _`sprintf`: http://www.php.net/sprintf
.. seealso:: :doc:`replace<replace>`

View File

@ -27,6 +27,7 @@ Filters
raw
replace
reverse
round
slice
sort
split

View File

@ -15,9 +15,9 @@ define it with the optional first parameter:
.. code-block:: jinja
{{ [1, 2, 3]|join('|') }}
{# returns 1|2|3 #}
{# outputs 1|2|3 #}
Arguments
---------
* ``glue``: The separator
* ``glue``: The separator

View File

@ -14,8 +14,8 @@ The ``json_encode`` filter returns the JSON representation of a string:
Arguments
---------
* ``options``: A bitmask of `json_encode options`_ (``{{
data|json_encode(constant(JSON_PRETTY_PRINT)) }}``)
* ``options``: A bitmask of `json_encode options`_ (``{{
data|json_encode(constant('JSON_PRETTY_PRINT')) }}``)
.. _`json_encode`: http://php.net/json_encode
.. _`json_encode options`: http://www.php.net/manual/en/json.constants.php

View File

@ -9,4 +9,3 @@ the length of a string:
{% if users|length > 10 %}
...
{% endif %}

View File

@ -21,9 +21,9 @@ separator using the additional arguments:
If no formatting options are provided then Twig will use the default formatting
options of:
- 0 decimal places.
- ``.`` as the decimal point.
- ``,`` as the thousands separator.
* 0 decimal places.
* ``.`` as the decimal point.
* ``,`` as the thousands separator.
These defaults can be easily changed through the core extension:
@ -38,8 +38,8 @@ additional parameters.
Arguments
---------
* ``decimal``: The number of decimal points to display
* ``decimal_point``: The character(s) to use for the decimal point
* ``decimal_sep``: The character(s) to use for the thousands separator
* ``decimal``: The number of decimal points to display
* ``decimal_point``: The character(s) to use for the decimal point
* ``thousand_sep``: The character(s) to use for the thousands separator
.. _`number_format`: http://php.net/number_format

View File

@ -7,6 +7,6 @@ if ``raw`` is the last filter applied to it:
.. code-block:: jinja
{% autoescape true %}
{% autoescape %}
{{ var|raw }} {# var won't be escaped #}
{% endautoescape %}

View File

@ -8,12 +8,12 @@ The ``replace`` filter formats a given string by replacing the placeholders
{{ "I like %this% and %that%."|replace({'%this%': foo, '%that%': "bar"}) }}
{# returns I like foo and bar
{# outputs I like foo and bar
if the foo parameter equals to the foo string. #}
Arguments
---------
* ``replace_pairs``: The placeholder values
* ``replace_pairs``: The placeholder values
.. seealso:: :doc:`format<format>`

View File

@ -8,7 +8,7 @@ The ``reverse`` filter reverses a sequence, a mapping, or a string:
.. code-block:: jinja
{% for use in users|reverse %}
{% for user in users|reverse %}
...
{% endfor %}
@ -16,8 +16,32 @@ The ``reverse`` filter reverses a sequence, a mapping, or a string:
{# outputs 4321 #}
.. tip::
For sequences and mappings, numeric keys are not preserved. To reverse
them as well, pass ``true`` as an argument to the ``reverse`` filter:
.. code-block:: jinja
{% for key, value in {1: "a", 2: "b", 3: "c"}|reverse %}
{{ key }}: {{ value }}
{%- endfor %}
{# output: 0: c 1: b 2: a #}
{% for key, value in {1: "a", 2: "b", 3: "c"}|reverse(true) %}
{{ key }}: {{ value }}
{%- endfor %}
{# output: 3: c 2: b 1: a #}
.. note::
It also works with objects implementing the `Traversable`_ interface.
Arguments
---------
* ``preserve_keys``: Preserve keys when reversing a mapping or a sequence.
.. _`Traversable`: http://php.net/Traversable

View File

@ -0,0 +1,37 @@
``round``
=========
.. versionadded:: 1.15.0
The ``round`` filter was added in Twig 1.15.0.
The ``round`` filter rounds a number to a given precision:
.. code-block:: jinja
{{ 42.55|round }}
{# outputs 43 #}
{{ 42.55|round(1, 'floor') }}
{# outputs 42.5 #}
The ``round`` filter takes two optional arguments; the first one specifies the
precision (default is ``0``) and the second the rounding method (default is
``common``):
* ``common`` rounds either up or down (rounds the value up to precision decimal
places away from zero, when it is half way there -- making 1.5 into 2 and
-1.5 into -2);
* ``ceil`` always rounds up;
* ``floor`` always rounds down.
.. note::
The ``//`` operator is equivalent to ``|round(0, 'floor')``.
Arguments
---------
* ``precision``: The rounding precision
* ``method``: The rounding method

View File

@ -8,11 +8,11 @@ The ``slice`` filter extracts a slice of a sequence, a mapping, or a string:
.. code-block:: jinja
{% for i in [1, 2, 3, 4]|slice(1, 2) %}
{% for i in [1, 2, 3, 4, 5]|slice(1, 2) %}
{# will iterate over 2 and 3 #}
{% endfor %}
{{ '1234'|slice(1, 2) }}
{{ '12345'|slice(1, 2) }}
{# outputs 23 #}
@ -20,7 +20,7 @@ You can use any valid expression for both the start and the length:
.. code-block:: jinja
{% for i in [1, 2, 3, 4]|slice(start, length) %}
{% for i in [1, 2, 3, 4, 5]|slice(start, length) %}
{# ... #}
{% endfor %}
@ -28,17 +28,17 @@ As syntactic sugar, you can also use the ``[]`` notation:
.. code-block:: jinja
{% for i in [1, 2, 3, 4][start:length] %}
{% for i in [1, 2, 3, 4, 5][start:length] %}
{# ... #}
{% endfor %}
{{ '1234'[1:2] }}
{{ '12345'[1:2] }}
{# you can omit the first argument -- which is the same as 0 #}
{{ '1234'[:2] }} {# will display "12" #}
{{ '12345'[:2] }} {# will display "12" #}
{# you can omit the last argument -- which will select everything till the end #}
{{ '1234'[2:] }} {# will display "34 #}
{{ '12345'[2:] }} {# will display "345" #}
The ``slice`` filter works as the `array_slice`_ PHP function for arrays and
`substr`_ for strings.
@ -61,9 +61,9 @@ up until the end of the variable.
Arguments
---------
* ``start``: The start of the slice
* ``length``: The size of the slice
* ``preserve_keys``: Whether to preserve key or not (when the input is an array)
* ``start``: The start of the slice
* ``length``: The size of the slice
* ``preserve_keys``: Whether to preserve key or not (when the input is an array)
.. _`Traversable`: http://php.net/manual/en/class.traversable.php
.. _`array_slice`: http://php.net/array_slice

View File

@ -46,8 +46,8 @@ chunks. Length is set by the ``limit`` argument (one character by default).
Arguments
---------
* ``delimiter``: The delimiter
* ``limit``: The limit argument
* ``delimiter``: The delimiter
* ``limit``: The limit argument
.. _`explode`: http://php.net/explode
.. _`str_split`: http://php.net/str_split

View File

@ -24,6 +24,6 @@ and end of a string:
Arguments
---------
* ``character_mask``: The characters to strip
* ``character_mask``: The characters to strip
.. _`trim`: http://php.net/trim

View File

@ -12,12 +12,17 @@ or an array as query string:
{{ "path-seg*ment"|url_encode }}
{# outputs "path-seg%2Ament" #}
{{ "string with spaces"|url_encode(true) }}
{# outputs "string%20with%20spaces" #}
{{ {'param': 'value', 'foo': 'bar'}|url_encode }}
{# outputs "param=value&foo=bar" #}
.. note::
Internally, Twig uses the PHP `urlencode`_ or the `http_build_query`_ function.
Internally, Twig uses the PHP `urlencode`_ (or `rawurlencode`_ if you pass
``true`` as the first parameter) or the `http_build_query`_ function.
.. _`urlencode`: http://php.net/urlencode
.. _`urlencode`: http://php.net/urlencode
.. _`rawurlencode`: http://php.net/rawurlencode
.. _`http_build_query`: http://php.net/http_build_query

View File

@ -22,4 +22,4 @@ The array can contain any number of values:
Arguments
---------
* ``position``: The cycle position
* ``position``: The cycle position

View File

@ -11,7 +11,7 @@ Converts an argument to a date to allow date comparison:
.. code-block:: jinja
{% if date(user.created_at) < date('+2days') %}
{% if date(user.created_at) < date('-2days') %}
{# do something #}
{% endif %}
@ -21,7 +21,7 @@ You can pass a timezone as the second argument:
.. code-block:: jinja
{% if date(user.created_at) < date('+2days', 'Europe/Paris') %}
{% if date(user.created_at) < date('-2days', 'Europe/Paris') %}
{# do something #}
{% endif %}
@ -46,7 +46,7 @@ If no argument is passed, the function returns the current date:
Arguments
---------
* ``date``: The date
* ``timezone``: The timezone
* ``date``: The date
* ``timezone``: The timezone
.. _`date`: http://www.php.net/date

View File

@ -63,7 +63,7 @@ dumped:
Arguments
---------
* ``context``: The context to dump
* ``context``: The context to dump
.. _`XDebug`: http://xdebug.org/docs/display
.. _`var_dump`: http://php.net/var_dump

View File

@ -73,8 +73,8 @@ sandboxing it:
Arguments
---------
* ``template``: The template to render
* ``variables``: The variables to pass to the template
* ``with_context``: Whether to pass the current context variables or not
* ``ignore_missing``: Whether to ignore missing templates or not
* ``sandboxed``: Whether to sandbox the template or not
* ``template``: The template to render
* ``variables``: The variables to pass to the template
* ``with_context``: Whether to pass the current context variables or not
* ``ignore_missing``: Whether to ignore missing templates or not
* ``sandboxed``: Whether to sandbox the template or not

View File

@ -11,7 +11,10 @@ Functions
date
dump
include
max
min
parent
random
range
source
template_from_string

View File

@ -0,0 +1,19 @@
``max``
=======
.. versionadded:: 1.15
The ``max`` function was added in Twig 1.15.
``max`` returns the biggest value of a sequence or a set of values:
.. code-block:: jinja
{{ max(1, 3, 2) }}
{{ max([1, 3, 2]) }}
When called with a mapping, max ignores keys and only compares values:
.. code-block:: jinja
{{ max({2: "two", 1: "one", 3: "three", 5: "five", 4: "for"}) }}
{# return "two" #}

View File

@ -0,0 +1,19 @@
``min``
=======
.. versionadded:: 1.15
The ``min`` function was added in Twig 1.15.
``min`` returns the lowest value of a sequence or a set of values:
.. code-block:: jinja
{{ min(1, 3, 2) }}
{{ min([1, 3, 2]) }}
When called with a mapping, min ignores keys and only compares values:
.. code-block:: jinja
{{ min({2: "two", 1: "one", 3: "three", 5: "five", 4: "for"}) }}
{# return "five" #}

View File

@ -18,12 +18,12 @@ parameter type:
{{ random(['apple', 'orange', 'citrus']) }} {# example output: orange #}
{{ random('ABC') }} {# example output: C #}
{{ random() }} {# example output: 15386094 (works as native PHP `mt_rand`_ function) #}
{{ random() }} {# example output: 15386094 (works as the native PHP mt_rand function) #}
{{ random(5) }} {# example output: 3 #}
Arguments
---------
* ``values``: The values
* ``values``: The values
.. _`mt_rand`: http://php.net/mt_rand

View File

@ -9,7 +9,7 @@ Returns a list containing an arithmetic progression of integers:
{{ i }},
{% endfor %}
{# returns 0, 1, 2, 3 #}
{# outputs 0, 1, 2, 3, #}
When step is given (as the third parameter), it specifies the increment (or
decrement):
@ -20,7 +20,7 @@ decrement):
{{ i }},
{% endfor %}
{# returns 0, 2, 4, 6 #}
{# outputs 0, 2, 4, 6, #}
The Twig built-in ``..`` operator is just syntactic sugar for the ``range``
function (with a step of 1):
@ -38,8 +38,8 @@ function (with a step of 1):
Arguments
---------
* ``low``: The first value of the sequence.
* ``high``: The highest possible value of the sequence.
* ``step``: The increment between elements of the sequence.
* ``low``: The first value of the sequence.
* ``high``: The highest possible value of the sequence.
* ``step``: The increment between elements of the sequence.
.. _`range`: http://php.net/range

View File

@ -0,0 +1,21 @@
``source``
==========
.. versionadded:: 1.15
The source function was added in Twig 1.15.
The ``source`` function returns the content of a template without rendering it:
.. code-block:: jinja
{{ source('template.html') }}
{{ source(some_var) }}
The function uses the same template loaders as the ones used to include
templates. So, if you are using the filesystem loader, the templates are looked
for in the paths defined by it.
Arguments
---------
* ``name``: The name of the template to read

View File

@ -8,7 +8,7 @@ The ``template_from_string`` function loads a template from a string:
.. code-block:: jinja
{{ include(template_from_string("Hello {{ name }}") }}
{{ include(template_from_string("Hello {{ name }}")) }}
{{ include(template_from_string(page.template)) }}
.. note::
@ -29,4 +29,4 @@ The ``template_from_string`` function loads a template from a string:
Arguments
---------
* ``template``: The template
* ``template``: The template

View File

@ -15,4 +15,5 @@ Twig
filters/index
functions/index
tests/index
installation
deprecated

View File

@ -0,0 +1,118 @@
Installation
============
You have multiple ways to install Twig.
Installing the Twig PHP package
-------------------------------
Installing via Composer (recommended)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1. Install Composer in your project:
.. code-block:: bash
curl -s http://getcomposer.org/installer | php
2. Create a ``composer.json`` file in your project root:
.. code-block:: javascript
{
"require": {
"twig/twig": "1.*"
}
}
3. Install via Composer
.. code-block:: bash
php composer.phar install
.. note::
If you want to learn more about Composer, the ``composer.json`` file syntax
and its usage, you can read the `online documentation`_.
Installing from the tarball release
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1. Download the most recent tarball from the `download page`_
2. Unpack the tarball
3. Move the files somewhere in your project
Installing the development version
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1. Install Git
2. ``git clone git://github.com/fabpot/Twig.git``
Installing the PEAR package
~~~~~~~~~~~~~~~~~~~~~~~~~~~
1. Install PEAR
2. ``pear channel-discover pear.twig-project.org``
3. ``pear install twig/Twig`` (or ``pear install twig/Twig-beta``)
Installing the C extension
--------------------------
.. versionadded:: 1.4
The C extension was added in Twig 1.4.
Twig comes with a C extension that enhances the performance of the Twig
runtime engine.
You can install it via PEAR:
1. Install PEAR
2. ``pear channel-discover pear.twig-project.org``
3. ``pear install twig/CTwig`` (or ``pear install twig/CTwig-beta``)
Or manually like any other PHP extension:
.. code-block:: bash
$ cd ext/twig
$ phpize
$ ./configure
$ make
$ make install
For Windows:
1. Setup the build environment following the `PHP documentation`_
2. Put Twig's C extension source code into ``C:\php-sdk\phpdev\vcXX\x86\php-source-directory\ext\twig``
3. Use the ``configure --disable-all --enable-cli --enable-twig=shared`` command instead of step 14
4. ``nmake``
5. Copy the ``C:\php-sdk\phpdev\vcXX\x86\php-source-directory\Release_TS\php_twig.dll`` file to your PHP setup.
.. tip::
For Windows ZendServer, TS is not enabled as mentionned in `Zend Server
FAQ`_.
You have to use `configure --disable-all --disable-zts --enable-cli
--enable-twig=shared` to be able to build the twig C extension for
ZendServer.
The built DLL will be available in
C:\\php-sdk\\phpdev\\vcXX\\x86\\php-source-directory\\Release
Finally, enable the extension in your ``php.ini`` configuration file:
.. code-block:: ini
extension=twig.so #For Unix systems
extension=php_twig.dll #For Windows systems
And from now on, Twig will automatically compile your templates to take
advantage of the C extension. Note that this extension does not replace the
PHP code but only provides an optimized version of the
``Twig_Template::getAttribute()`` method.
.. _`download page`: https://github.com/fabpot/Twig/tags
.. _`online documentation`: http://getcomposer.org/doc
.. _`PHP documentation`: https://wiki.php.net/internals/windows/stepbystepbuild
.. _`Zend Server FAQ`: http://www.zend.com/en/products/server/faq#faqD6

View File

@ -3,11 +3,11 @@ Twig Internals
Twig is very extensible and you can easily hack it. Keep in mind that you
should probably try to create an extension before hacking the core, as most
features and enhancements can be done with extensions. This chapter is also
features and enhancements can be handled with extensions. This chapter is also
useful for people who want to understand how Twig works under the hood.
How Twig works?
---------------
How does Twig work?
-------------------
The rendering of a Twig template can be summarized into four key steps:
@ -18,7 +18,7 @@ The rendering of a Twig template can be summarized into four key steps:
for easier processing;
* Then, the **parser** converts the token stream into a meaningful tree
of nodes (the Abstract Syntax Tree);
* Eventually, the *compiler* transforms the AST into PHP code;
* Eventually, the *compiler* transforms the AST into PHP code.
* **Evaluate** the template: It basically means calling the ``display()``
method of the compiled template and passing it the context.
@ -42,7 +42,7 @@ an instance of ``Twig_Token``, and the stream is an instance of
* ``Twig_Token::EOF_TYPE``: Ends of template.
You can manually convert a source code into a token stream by calling the
``tokenize()`` of an environment::
``tokenize()`` method of an environment::
$stream = $twig->tokenize($source, $identifier);
@ -63,7 +63,7 @@ Here is the output for the ``Hello {{ name }}`` template:
.. note::
You can change the default lexer use by Twig (``Twig_Lexer``) by calling
The default lexer (``Twig_Lexer``) can be changed by calling
the ``setLexer()`` method::
$twig->setLexer($lexer);
@ -97,7 +97,7 @@ Here is the output for the ``Hello {{ name }}`` template:
.. note::
The default parser (``Twig_TokenParser``) can be also changed by calling the
The default parser (``Twig_TokenParser``) can be changed by calling the
``setParser()`` method::
$twig->setParser($parser);
@ -108,13 +108,11 @@ The Compiler
The last step is done by the compiler. It takes a node tree as an input and
generates PHP code usable for runtime execution of the template.
You can call the compiler by hand with the ``compile()`` method of an
environment::
You can manually compile a node tree to PHP code with the ``compile()`` method
of an environment::
$php = $twig->compile($nodes);
The ``compile()`` method returns the PHP source code representing the node.
The generated template for a ``Hello {{ name }}`` template reads as follows
(the actual output can differ depending on the version of Twig you are
using)::
@ -134,7 +132,7 @@ using)::
.. note::
As for the lexer and the parser, the default compiler (``Twig_Compiler``) can
be changed by calling the ``setCompiler()`` method::
The default compiler (``Twig_Compiler``) can be changed by calling the
``setCompiler()`` method::
$twig->setCompiler($compiler);

View File

@ -29,115 +29,27 @@ Twig needs at least **PHP 5.2.4** to run.
Installation
------------
You have multiple ways to install Twig.
Installing via Composer (recommended)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1. Install composer in your project:
The recommended way to install Twig is via Composer:
.. code-block:: bash
curl -s http://getcomposer.org/installer | php
2. Create a ``composer.json`` file in your project root:
.. code-block:: javascript
{
"require": {
"twig/twig": "1.*"
}
}
3. Install via composer
.. code-block:: bash
php composer.phar install
composer require twig/twig:1.*
.. note::
If you want to learn more about Composer, the ``composer.json`` file syntax
and its usage, you can read the `online documentation`_.
Installing from the tarball release
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1. Download the most recent tarball from the `download page`_
2. Unpack the tarball
3. Move the files somewhere in your project
Installing the development version
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1. Install Git
2. ``git clone git://github.com/fabpot/Twig.git``
Installing the PEAR package
~~~~~~~~~~~~~~~~~~~~~~~~~~~
1. Install PEAR
2. ``pear channel-discover pear.twig-project.org``
3. ``pear install twig/Twig`` (or ``pear install twig/Twig-beta``)
Installing the C extension
~~~~~~~~~~~~~~~~~~~~~~~~~~
.. versionadded:: 1.4
The C extension was added in Twig 1.4.
Twig comes with a C extension that enhances the performance of the Twig
runtime engine. You can install it like any other PHP extension:
.. code-block:: bash
$ cd ext/twig
$ phpize
$ ./configure
$ make
$ make install
Finally, enable the extension in your ``php.ini`` configuration file:
.. code-block:: ini
extension=twig.so
And from now on, Twig will automatically compile your templates to take
advantage of the C extension. Note that this extension does not replace the
PHP code but only provides an optimized version of the
``Twig_Template::getAttribute()`` method.
.. tip::
On Windows, you can also simply download and install a `pre-built DLL`_.
To learn more about the other installation methods, read the
:doc:`installation<installation>` chapter; it also explains how to install
the Twig C extension.
Basic API Usage
---------------
This section gives you a brief introduction to the PHP API for Twig.
The first step to use Twig is to register its autoloader::
require_once '/path/to/lib/Twig/Autoloader.php';
Twig_Autoloader::register();
Replace the ``/path/to/lib/`` path with the path you used for Twig
installation.
If you have installed Twig via Composer you can take advantage of Composer's
autoload mechanism by replacing the previous snippet for::
require_once '/path/to/vendor/autoload.php'
.. note::
Twig follows the PEAR convention names for its classes, which means you
can easily integrate Twig classes loading in your own autoloader.
.. code-block:: php
require_once '/path/to/vendor/autoload.php';
$loader = new Twig_Loader_String();
$twig = new Twig_Environment($loader);
@ -159,6 +71,9 @@ filesystem loader::
echo $twig->render('index.html', array('name' => 'Fabien'));
.. _`download page`: https://github.com/fabpot/Twig/tags
.. _`online documentation`: http://getcomposer.org/doc
.. _`pre-build DLL`: https://github.com/stealth35/stealth35.github.com/downloads
.. tip::
If you are not using Composer, use the Twig built-in autoloader::
require_once '/path/to/lib/Twig/Autoloader.php';
Twig_Autoloader::register();

View File

@ -335,7 +335,7 @@ you have some dynamic JavaScript files thanks to the ``autoescape`` tag:
But if you have many HTML and JS files, and if your template names follow some
conventions, you can instead determine the default escaping strategy to use
based on the template name. Let's say that your template names always ends
based on the template name. Let's say that your template names always end
with ``.html`` for HTML files, ``.js`` for JavaScript ones, and ``.css`` for
stylesheets, here is how you can configure Twig::

View File

@ -192,7 +192,7 @@ How blocks work?
A block provides a way to change how a certain part of a template is rendered
but it does not interfere in any way with the logic around it.
Let's take the following example to illustrate how a block work and more
Let's take the following example to illustrate how a block works and more
importantly, how it does not work:
.. code-block:: jinja

View File

@ -1,7 +1,7 @@
``from``
========
The ``from`` tags import :doc:`macro<../tags/macro>` names into the current
The ``from`` tag imports :doc:`macro<../tags/macro>` names into the current
namespace. The tag is documented in detail in the documentation for the
:doc:`import<../tags/import>` tag.

View File

@ -37,7 +37,7 @@ more complex ``expressions`` there too:
{% if kenny.sick %}
Kenny is sick.
{% elseif kenny.dead %}
You killed Kenny! You bastard!!!
You killed Kenny! You bastard!!!
{% else %}
Kenny looks okay --- so far
{% endif %}

View File

@ -15,7 +15,7 @@ Imagine we have a helper module that renders forms (called ``forms.html``):
<input type="{{ type|default('text') }}" name="{{ name }}" value="{{ value|e }}" size="{{ size|default(20) }}" />
{% endmacro %}
{% macro textarea(name, value, rows) %}
{% macro textarea(name, value, rows, cols) %}
<textarea name="{{ name }}" rows="{{ rows|default(10) }}" cols="{{ cols|default(40) }}">{{ value|e }}</textarea>
{% endmacro %}

View File

@ -1,7 +1,7 @@
``include``
===========
The ``include`` statement includes a template and return the rendered content
The ``include`` statement includes a template and returns the rendered content
of that file into the current namespace:
.. code-block:: jinja
@ -63,7 +63,7 @@ directly::
The ``ignore missing`` feature has been added in Twig 1.2.
You can mark an include with ``ignore missing`` in which case Twig will ignore
the statement if the template to be ignored does not exist. It has to be
the statement if the template to be included does not exist. It has to be
placed just after the template name. Here some valid examples:
.. code-block:: jinja

View File

@ -20,7 +20,7 @@ Macros differs from native PHP functions in a few ways:
* Arguments of a macro are always optional.
But as PHP functions, macros don't have access to the current template
But as with PHP functions, macros don't have access to the current template
variables.
.. tip::

View File

@ -2,31 +2,77 @@
=======
Inside code blocks you can also assign values to variables. Assignments use
the ``set`` tag and can have multiple targets:
the ``set`` tag and can have multiple targets.
Here is how you can assign the ``bar`` value to the ``foo`` variable:
.. code-block:: jinja
{% set foo = 'foo' %}
{% set foo = 'bar' %}
After the ``set`` call, the ``foo`` variable is available in the template like
any other ones:
.. code-block:: jinja
{# displays bar #}
{{ foo }}
The assigned value can be any valid :ref:`Twig expressions
<twig-expressions>`:
.. code-block:: jinja
{% set foo = [1, 2] %}
{% set foo = {'foo': 'bar'} %}
{% set foo = 'foo' ~ 'bar' %}
Several variables can be assigned in one block:
.. code-block:: jinja
{% set foo, bar = 'foo', 'bar' %}
{# is equivalent to #}
{% set foo = 'foo' %}
{% set bar = 'bar' %}
The ``set`` tag can also be used to 'capture' chunks of text:
.. code-block:: jinja
{% set foo %}
<div id="pagination">
...
</div>
<div id="pagination">
...
</div>
{% endset %}
.. caution::
If you enable automatic output escaping, Twig will only consider the
content to be safe when capturing chunks of text.
.. note::
Note that loops are scoped in Twig; therefore a variable declared inside a
``for`` loop is not accessible outside the loop itself:
.. code-block:: jinja
{% for item in list %}
{% set foo = item %}
{% endfor %}
{# foo is NOT available #}
If you want to access the variable, just declare it before the loop:
.. code-block:: jinja
{% set foo = "" %}
{% for item in list %}
{% set foo = item %}
{% endfor %}
{# foo is available #}

View File

@ -35,7 +35,7 @@ but without the associated complexity:
{% block content %}{% endblock %}
The ``use`` statement tells Twig to import the blocks defined in
```blocks.html`` into the current template (it's like macros, but for blocks):
``blocks.html`` into the current template (it's like macros, but for blocks):
.. code-block:: jinja

View File

@ -47,7 +47,7 @@ IDEs Integration
Many IDEs support syntax highlighting and auto-completion for Twig:
* *Textmate* via the `Twig bundle`_
* *Vim* via the `Jinja syntax plugin`_
* *Vim* via the `Jinja syntax plugin`_ or the `vim-twig plugin`_
* *Netbeans* via the `Twig syntax plugin`_ (until 7.1, native as of 7.2)
* *PhpStorm* (native as of 2.1)
* *Eclipse* via the `Twig plugin`_
@ -167,7 +167,7 @@ To apply a filter on a section of code, wrap it with the
.. code-block:: jinja
{% filter upper %}
This text becomes uppercase
This text becomes uppercase
{% endfilter %}
Go to the :doc:`filters<filters/index>` page to learn more about the built-in
@ -197,8 +197,6 @@ Named Arguments
.. versionadded:: 1.12
Support for named arguments was added in Twig 1.12.
Arguments for filters and functions can also be passed as *named arguments*:
.. code-block:: jinja
{% for i in range(low=1, high=10, step=2) %}
@ -227,14 +225,12 @@ to change the default value:
{# or skip the format value by using a named argument for the timezone #}
{{ "now"|date(timezone="Europe/Paris") }}
You can also use both positional and named arguments in one call, which is not
recommended as it can be confusing:
You can also use both positional and named arguments in one call, in which
case positional arguments must always come before named arguments:
.. code-block:: jinja
{# both work #}
{{ "now"|date('d/m/Y H:i', timezone="Europe/Paris") }}
{{ "now"|date(timezone="Europe/Paris", 'd/m/Y H:i') }}
.. tip::
@ -544,6 +540,8 @@ macro call:
<input type="{{ type }}" name="{{ name }}" value="{{ value|e }}" size="{{ size }}" />
{% endmacro %}
.. _twig-expressions:
Expressions
-----------
@ -554,8 +552,19 @@ even if you're not working with PHP you should feel comfortable with it.
The operator precedence is as follows, with the lowest-precedence
operators listed first: ``b-and``, ``b-xor``, ``b-or``, ``or``, ``and``,
``==``, ``!=``, ``<``, ``>``, ``>=``, ``<=``, ``in``, ``..``, ``+``,
``-``, ``~``, ``*``, ``/``, ``//``, ``%``, ``is``, and ``**``.
``==``, ``!=``, ``<``, ``>``, ``>=``, ``<=``, ``in``, ``matches``,
``starts with``, ``ends with``, ``..``, ``+``, ``-``, ``~``, ``*``, ``/``,
``//``, ``%``, ``is``, ``**``, ``|``, ``[]``, and ``.``:
.. code-block:: jinja
{% set greeting = 'Hello' %}
{% set name = 'Fabien' %}
{{ greeting ~ name|lower }} {# Hello fabien #}
{# use parenthesis to change precedence #}
{{ (greeting ~ name)|lower }} {# hello fabien #}
Literals
~~~~~~~~
@ -632,8 +641,9 @@ but exists for completeness' sake. The following operators are supported:
* ``%``: Calculates the remainder of an integer division. ``{{ 11 % 7 }}`` is
``4``.
* ``//``: Divides two numbers and returns the truncated integer result. ``{{
20 // 7 }}`` is ``2``.
* ``//``: Divides two numbers and returns the floored integer result. ``{{ 20
// 7 }}`` is ``2``, ``{{ -20 // 7 }}`` is ``-3``(this is just syntactic
sugar for the :doc:`round<filters/round>` filter).
* ``*``: Multiplies the left operand with the right one. ``{{ 2 * 2 }}`` would
return ``4``.
@ -664,6 +674,27 @@ Comparisons
The following comparison operators are supported in any expression: ``==``,
``!=``, ``<``, ``>``, ``>=``, and ``<=``.
You can also check if a string ``starts with`` or ``ends with`` another
string:
.. code-block:: jinja
{% if 'Fabien' starts with 'F' %}
{% endif %}
{% if 'Fabien' ends with 'n' %}
{% endif %}
.. note::
For complex string comparisons, the ``matches`` operator allows you to use
`regular expressions`_:
.. code-block:: jinja
{% if phone matches '{^[\d\.]+$}' %}
{% endif %}
Containment Operator
~~~~~~~~~~~~~~~~~~~~
@ -709,16 +740,16 @@ Tests can accept arguments too:
.. code-block:: jinja
{% if loop.index is divisibleby(3) %}
{% if post.status is constant('Post::PUBLISHED') %}
Tests can be negated by using the ``is not`` operator:
.. code-block:: jinja
{% if loop.index is not divisibleby(3) %}
{% if post.status is not constant('Post::PUBLISHED') %}
{# is equivalent to #}
{% if not (loop.index is divisibleby(3)) %}
{% if not (post.status is constant('Post::PUBLISHED')) %}
Go to the :doc:`tests<tests/index>` page to learn more about the built-in
tests.
@ -751,8 +782,8 @@ categories:
{{ foo ? 'yes' : 'no' }}
{# as of Twig 1.12.0 #}
{{ foo ?: 'no' }} == {{ foo ? foo : 'no' }}
{{ foo ? 'yes' }} == {{ foo ? 'yes' : '' }}
{{ foo ?: 'no' }} is the same as {{ foo ? foo : 'no' }}
{{ foo ? 'yes' }} is the same as {{ foo ? 'yes' : '' }}
String Interpolation
~~~~~~~~~~~~~~~~~~~~
@ -785,11 +816,11 @@ Use the ``spaceless`` tag to remove whitespace *between HTML tags*:
{% spaceless %}
<div>
<strong>foo</strong>
<strong>foo bar</strong>
</div>
{% endspaceless %}
{# output will be <div><strong>foo</strong></div> #}
{# output will be <div><strong>foo bar</strong></div> #}
In addition to the spaceless tag you can also control whitespace on a per tag
level. By using the whitespace control modifier on your tags, you can trim
@ -829,7 +860,8 @@ If you want to create your own, read the :ref:`Creating an
Extension<creating_extensions>` chapter.
.. _`Twig bundle`: https://github.com/Anomareh/PHP-Twig.tmbundle
.. _`Jinja syntax plugin`: http://jinja.pocoo.org/2/documentation/integration
.. _`Jinja syntax plugin`: http://jinja.pocoo.org/docs/integration/#vim
.. _`vim-twig plugin`: https://github.com/evidens/vim-twig
.. _`Twig syntax plugin`: http://plugins.netbeans.org/plugin/37069/php-twig
.. _`Twig plugin`: https://github.com/pulse00/Twig-Eclipse-Plugin
.. _`Twig language definition`: https://github.com/gabrielcorpse/gedit-twig-template-language
@ -838,3 +870,4 @@ Extension<creating_extensions>` chapter.
.. _`other Twig syntax mode`: https://github.com/muxx/Twig-HTML.mode
.. _`Notepad++ Twig Highlighter`: https://github.com/Banane9/notepadplusplus-twig
.. _`web-mode.el`: http://web-mode.org/
.. _`regular expressions`: http://php.net/manual/en/pcre.pattern.php

View File

@ -1,6 +1,9 @@
``constant``
============
.. versionadded: 1.13.1
constant now accepts object instances as the second argument.
``constant`` checks if a variable has the exact same value as a constant. You
can use either global constants or class constants:
@ -9,3 +12,11 @@ can use either global constants or class constants:
{% if post.status is constant('Post::PUBLISHED') %}
the status attribute is exactly the same as Post::PUBLISHED
{% endif %}
You can test constants from object instances as well:
.. code-block:: jinja
{% if post.status is constant('PUBLISHED', post) %}
the status attribute is exactly the same as Post::PUBLISHED
{% endif %}

View File

@ -1,10 +1,14 @@
``divisibleby``
===============
``divisible by``
================
``divisibleby`` checks if a variable is divisible by a number:
.. versionadded:: 1.14.2
The ``divisible by`` test was added in Twig 1.14.2 as an alias for
``divisibleby``.
``divisible by`` checks if a variable is divisible by a number:
.. code-block:: jinja
{% if loop.index is divisibleby(3) %}
{% if loop.index is divisible by(3) %}
...
{% endif %}

View File

@ -1,11 +1,14 @@
``sameas``
==========
``same as``
===========
``sameas`` checks if a variable points to the same memory address than another
variable:
.. versionadded:: 1.14.2
The ``same as`` test was added in Twig 1.14.2 as an alias for ``sameas``.
``same as`` checks if a variable points to the same memory address than
another variable:
.. code-block:: jinja
{% if foo.attribute is sameas(false) %}
the foo attribute really is the ``false`` PHP value
{% if foo.attribute is same as(false) %}
the foo attribute really is the 'false' PHP value
{% endif %}

View File

@ -1,22 +1,31 @@
Copyright (c) 2011, Derick Rethans <derick@derickrethans.nl>
All rights reserved.
Copyright (c) 2009-2013 by the Twig Team, see AUTHORS for more details.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
Some rights reserved.
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* The names of the contributors may not be used to endorse or
promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -6,7 +6,7 @@
+----------------------------------------------------------------------+
| Redistribution and use in source and binary forms, with or without |
| modification, are permitted provided that the conditions mentioned |
| in the accompanying LICENSE file are met (BSD, revised). |
| in the accompanying LICENSE file are met (BSD-3-Clause). |
+----------------------------------------------------------------------+
| Author: Derick Rethans <derick@derickrethans.nl> |
+----------------------------------------------------------------------+
@ -15,7 +15,7 @@
#ifndef PHP_TWIG_H
#define PHP_TWIG_H
#define PHP_TWIG_VERSION "1.12.3"
#define PHP_TWIG_VERSION "1.15.0"
#include "php.h"

View File

@ -6,7 +6,7 @@
+----------------------------------------------------------------------+
| Redistribution and use in source and binary forms, with or without |
| modification, are permitted provided that the conditions mentioned |
| in the accompanying LICENSE file are met (BSD, revised). |
| in the accompanying LICENSE file are met (BSD-3-Clause). |
+----------------------------------------------------------------------+
| Author: Derick Rethans <derick@derickrethans.nl> |
+----------------------------------------------------------------------+
@ -18,8 +18,10 @@
#include "php.h"
#include "php_twig.h"
#include "ext/standard/php_var.h"
#include "ext/standard/php_string.h"
#include "ext/standard/php_smart_str.h"
#include "ext/spl/spl_exceptions.h"
#include "Zend/zend_object_handlers.h"
#include "Zend/zend_interfaces.h"
@ -76,12 +78,26 @@ zend_module_entry twig_module_entry = {
ZEND_GET_MODULE(twig)
#endif
int TWIG_ARRAY_KEY_EXISTS(zval *array, char* key, int key_len)
int TWIG_ARRAY_KEY_EXISTS(zval *array, zval *key)
{
if (Z_TYPE_P(array) != IS_ARRAY) {
return 0;
}
return zend_symtable_exists(Z_ARRVAL_P(array), key, key_len + 1);
switch (Z_TYPE_P(key)) {
case IS_NULL:
return zend_hash_exists(Z_ARRVAL_P(array), "", 1);
case IS_BOOL:
case IS_DOUBLE:
convert_to_long(key);
case IS_LONG:
return zend_hash_index_exists(Z_ARRVAL_P(array), Z_LVAL_P(key));
default:
convert_to_string(key);
return zend_symtable_exists(Z_ARRVAL_P(array), Z_STRVAL_P(key), Z_STRLEN_P(key) + 1);
}
}
int TWIG_INSTANCE_OF(zval *object, zend_class_entry *interface TSRMLS_DC)
@ -106,23 +122,23 @@ int TWIG_INSTANCE_OF_USERLAND(zval *object, char *interface TSRMLS_DC)
zval *TWIG_GET_ARRAYOBJECT_ELEMENT(zval *object, zval *offset TSRMLS_DC)
{
zend_class_entry *ce = Z_OBJCE_P(object);
zval *retval;
zend_class_entry *ce = Z_OBJCE_P(object);
zval *retval;
if (Z_TYPE_P(object) == IS_OBJECT) {
SEPARATE_ARG_IF_REF(offset);
zend_call_method_with_1_params(&object, ce, NULL, "offsetget", &retval, offset);
zval_ptr_dtor(&offset);
zval_ptr_dtor(&offset);
if (!retval) {
if (!EG(exception)) {
zend_error(E_ERROR, "Undefined offset for object of type %s used as array", ce->name);
}
return NULL;
}
if (!retval) {
if (!EG(exception)) {
zend_error(E_ERROR, "Undefined offset for object of type %s used as array", ce->name);
}
return NULL;
}
return retval;
return retval;
}
return NULL;
}
@ -200,8 +216,8 @@ zval *TWIG_CALL_USER_FUNC_ARRAY(zval *object, char *function, zval *arguments TS
fci.no_separation = 0;
if (zend_call_function(&fci, NULL TSRMLS_CC) == FAILURE) {
FREE_DTOR(zfunction)
zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_CC, "Could not execute %s::%s()", zend_get_class_entry(object TSRMLS_CC)->name, function TSRMLS_CC);
ALLOC_INIT_ZVAL(retval_ptr);
ZVAL_BOOL(retval_ptr, 0);
}
if (args) {
@ -243,9 +259,8 @@ zval *TWIG_GET_STATIC_PROPERTY(zval *class, char *prop_name TSRMLS_DC)
zval *TWIG_GET_ARRAY_ELEMENT_ZVAL(zval *class, zval *prop_name TSRMLS_DC)
{
zval **tmp_zval;
char *tmp_name;
if (class == NULL || Z_TYPE_P(class) != IS_ARRAY || Z_TYPE_P(prop_name) != IS_STRING) {
if (class == NULL || Z_TYPE_P(class) != IS_ARRAY) {
if (class != NULL && Z_TYPE_P(class) == IS_OBJECT && TWIG_INSTANCE_OF(class, zend_ce_arrayaccess TSRMLS_CC)) {
// array access object
return TWIG_GET_ARRAYOBJECT_ELEMENT(class, prop_name TSRMLS_CC);
@ -253,11 +268,23 @@ zval *TWIG_GET_ARRAY_ELEMENT_ZVAL(zval *class, zval *prop_name TSRMLS_DC)
return NULL;
}
convert_to_string(prop_name);
tmp_name = Z_STRVAL_P(prop_name);
if (zend_symtable_find(HASH_OF(class), tmp_name, strlen(tmp_name)+1, (void**) &tmp_zval) == SUCCESS) {
return *tmp_zval;
switch(Z_TYPE_P(prop_name)) {
case IS_NULL:
zend_hash_find(HASH_OF(class), "", 1, (void**) &tmp_zval);
return *tmp_zval;
case IS_BOOL:
case IS_DOUBLE:
convert_to_long(prop_name);
case IS_LONG:
zend_hash_index_find(HASH_OF(class), Z_LVAL_P(prop_name), (void **) &tmp_zval);
return *tmp_zval;
case IS_STRING:
zend_symtable_find(HASH_OF(class), Z_STRVAL_P(prop_name), Z_STRLEN_P(prop_name) + 1, (void**) &tmp_zval);
return *tmp_zval;
}
return NULL;
}
@ -669,7 +696,7 @@ static int twig_add_property_to_class(void *pDest APPLY_TSRMLS_DC, int num_args,
zend_property_info *pptr = (zend_property_info *) pDest;
APPLY_TSRMLS_FETCH();
if (!(pptr->flags & ZEND_ACC_PUBLIC)) {
if (!(pptr->flags & ZEND_ACC_PUBLIC) || (pptr->flags & ZEND_ACC_STATIC)) {
return 0;
}
@ -717,7 +744,7 @@ PHP_FUNCTION(twig_template_get_attributes)
zval *object;
char *item;
int item_len;
zval zitem;
zval *zitem, ztmpitem;
zval *arguments = NULL;
zval *ret = NULL;
char *type = NULL;
@ -726,24 +753,21 @@ PHP_FUNCTION(twig_template_get_attributes)
zend_bool ignoreStrictCheck = 0;
int free_ret = 0;
zval *tmp_self_cache;
char *class_name = NULL;
zval *tmp_class;
char *type_name;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ozs|asbb", &template, &object, &item, &item_len, &arguments, &type, &type_len, &isDefinedTest, &ignoreStrictCheck) == FAILURE) {
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ozz|asbb", &template, &object, &zitem, &arguments, &type, &type_len, &isDefinedTest, &ignoreStrictCheck) == FAILURE) {
return;
}
INIT_PZVAL(&zitem);
ZVAL_STRINGL(&zitem, item, item_len, 0);
switch (is_numeric_string(item, item_len, &Z_LVAL(zitem), &Z_DVAL(zitem), 0)) {
case IS_LONG:
Z_TYPE(zitem) = IS_LONG;
break;
case IS_DOUBLE:
Z_TYPE(zitem) = IS_DOUBLE;
convert_to_long(&zitem);
break;
}
// convert the item to a string
ztmpitem = *zitem;
zval_copy_ctor(&ztmpitem);
convert_to_string(&ztmpitem);
item_len = Z_STRLEN(ztmpitem);
item = estrndup(Z_STRVAL(ztmpitem), item_len);
zval_dtor(&ztmpitem);
if (!type) {
type = "any";
@ -751,29 +775,32 @@ PHP_FUNCTION(twig_template_get_attributes)
/*
// array
if (Twig_TemplateInterface::METHOD_CALL !== $type) {
if ((is_array($object) && array_key_exists($item, $object))
|| ($object instanceof ArrayAccess && isset($object[$item]))
if (Twig_Template::METHOD_CALL !== $type) {
$arrayItem = is_bool($item) || is_float($item) ? (int) $item : $item;
if ((is_array($object) && array_key_exists($arrayItem, $object))
|| ($object instanceof ArrayAccess && isset($object[$arrayItem]))
) {
if ($isDefinedTest) {
return true;
}
return $object[$item];
return $object[$arrayItem];
}
*/
if (strcmp("method", type) != 0) {
// printf("XXXmethod: %s\n", type);
if ((TWIG_ARRAY_KEY_EXISTS(object, item, item_len))
|| (TWIG_INSTANCE_OF(object, zend_ce_arrayaccess TSRMLS_CC) && TWIG_ISSET_ARRAYOBJECT_ELEMENT(object, &zitem TSRMLS_CC))
if ((TWIG_ARRAY_KEY_EXISTS(object, zitem))
|| (TWIG_INSTANCE_OF(object, zend_ce_arrayaccess TSRMLS_CC) && TWIG_ISSET_ARRAYOBJECT_ELEMENT(object, zitem TSRMLS_CC))
) {
zval *ret;
if (isDefinedTest) {
RETURN_TRUE;
}
ret = TWIG_GET_ARRAY_ELEMENT(object, item, item_len TSRMLS_CC);
ret = TWIG_GET_ARRAY_ELEMENT_ZVAL(object, zitem TSRMLS_CC);
if (!ret) {
ret = &EG(uninitialized_zval);
}
@ -784,7 +811,7 @@ PHP_FUNCTION(twig_template_get_attributes)
return;
}
/*
if (Twig_TemplateInterface::ARRAY_CALL === $type) {
if (Twig_Template::ARRAY_CALL === $type) {
if ($isDefinedTest) {
return false;
}
@ -792,7 +819,7 @@ PHP_FUNCTION(twig_template_get_attributes)
return null;
}
*/
if (strcmp("array", type) == 0) {
if (strcmp("array", type) == 0 || Z_TYPE_P(object) != IS_OBJECT) {
if (isDefinedTest) {
RETURN_FALSE;
}
@ -801,11 +828,13 @@ PHP_FUNCTION(twig_template_get_attributes)
}
/*
if (is_object($object)) {
throw new Twig_Error_Runtime(sprintf('Key "%s" in object (with ArrayAccess) of type "%s" does not exist', $item, get_class($object)), -1, $this->getTemplateName());
throw new Twig_Error_Runtime(sprintf('Key "%s" in object (with ArrayAccess) of type "%s" does not exist', $arrayItem, get_class($object)), -1, $this->getTemplateName());
} elseif (is_array($object)) {
throw new Twig_Error_Runtime(sprintf('Key "%s" for array with keys "%s" does not exist', $item, implode(', ', array_keys($object))), -1, $this->getTemplateName());
throw new Twig_Error_Runtime(sprintf('Key "%s" for array with keys "%s" does not exist', $arrayItem, implode(', ', array_keys($object))), -1, $this->getTemplateName());
} elseif (Twig_Template::ARRAY_CALL === $type) {
throw new Twig_Error_Runtime(sprintf('Impossible to access a key ("%s") on a %s variable ("%s")', $item, gettype($object), $object), -1, $this->getTemplateName());
} else {
throw new Twig_Error_Runtime(sprintf('Impossible to access a key ("%s") on a "%s" variable', $item, gettype($object)), -1, $this->getTemplateName());
throw new Twig_Error_Runtime(sprintf('Impossible to access an attribute ("%s") on a %s variable ("%s")', $item, gettype($object), $object), -1, $this->getTemplateName());
}
}
}
@ -815,7 +844,15 @@ PHP_FUNCTION(twig_template_get_attributes)
} else if (Z_TYPE_P(object) == IS_ARRAY) {
TWIG_RUNTIME_ERROR(template TSRMLS_CC, "Key \"%s\" for array with keys \"%s\" does not exist", item, TWIG_IMPLODE_ARRAY_KEYS(", ", object TSRMLS_CC));
} else {
TWIG_RUNTIME_ERROR(template TSRMLS_CC, "Impossible to access a key (\"%s\") on a \"%s\" variable", item, zend_zval_type_name(object));
char *type_name = zend_zval_type_name(object);
Z_ADDREF_P(object);
convert_to_string(object);
TWIG_RUNTIME_ERROR(template TSRMLS_CC,
(strcmp("array", type) == 0)
? "Impossible to access a key (\"%s\") on a %s variable (\"%s\")"
: "Impossible to access an attribute (\"%s\") on a %s variable (\"%s\")",
item, type_name, Z_STRVAL_P(object));
zval_ptr_dtor(&object);
}
return;
}
@ -836,58 +873,45 @@ PHP_FUNCTION(twig_template_get_attributes)
if ($ignoreStrictCheck || !$this->env->isStrictVariables()) {
return null;
}
throw new Twig_Error_Runtime(sprintf('Item "%s" for "%s" does not exist', $item, implode(', ', array_keys($object))));
throw new Twig_Error_Runtime(sprintf('Impossible to invoke a method ("%s") on a %s variable ("%s")', $item, gettype($object), $object), -1, $this->getTemplateName());
}
*/
if (ignoreStrictCheck || !TWIG_CALL_BOOLEAN(TWIG_PROPERTY_CHAR(template, "env" TSRMLS_CC), "isStrictVariables" TSRMLS_CC)) {
RETURN_FALSE;
}
if (Z_TYPE_P(object) == IS_ARRAY) {
TWIG_RUNTIME_ERROR(template TSRMLS_CC, "Item \"%s\" for \"Array\" does not exist", item);
} else {
Z_ADDREF_P(object);
convert_to_string_ex(&object);
TWIG_RUNTIME_ERROR(template TSRMLS_CC, "Item \"%s\" for \"%s\" does not exist", item, Z_STRVAL_P(object));
zval_ptr_dtor(&object);
return;
}
type_name = zend_zval_type_name(object);
Z_ADDREF_P(object);
convert_to_string_ex(&object);
TWIG_RUNTIME_ERROR(template TSRMLS_CC, "Impossible to invoke a method (\"%s\") on a %s variable (\"%s\")", item, type_name, Z_STRVAL_P(object));
zval_ptr_dtor(&object);
return;
}
/*
// get some information about the object
$class = get_class($object);
if (!isset(self::$cache[$class])) {
$r = new ReflectionClass($class);
self::$cache[$class] = array('methods' => array(), 'properties' => array());
foreach ($r->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
self::$cache[$class]['methods'][strtolower($method->getName())] = true;
}
foreach ($r->getProperties(ReflectionProperty::IS_PUBLIC) as $property) {
self::$cache[$class]['properties'][$property->getName()] = true;
}
}
*/
if (Z_TYPE_P(object) == IS_OBJECT) {
char *class_name = NULL;
class_name = TWIG_GET_CLASS_NAME(object TSRMLS_CC);
tmp_self_cache = TWIG_GET_STATIC_PROPERTY(template, "cache" TSRMLS_CC);
class_name = TWIG_GET_CLASS_NAME(object TSRMLS_CC);
tmp_self_cache = TWIG_GET_STATIC_PROPERTY(template, "cache" TSRMLS_CC);
tmp_class = TWIG_GET_ARRAY_ELEMENT(tmp_self_cache, class_name, strlen(class_name) TSRMLS_CC);
if (!TWIG_GET_ARRAY_ELEMENT(tmp_self_cache, class_name, strlen(class_name) TSRMLS_CC)) {
twig_add_class_to_cache(tmp_self_cache, object, class_name TSRMLS_CC);
}
efree(class_name);
if (!tmp_class) {
twig_add_class_to_cache(tmp_self_cache, object, class_name TSRMLS_CC);
tmp_class = TWIG_GET_ARRAY_ELEMENT(tmp_self_cache, class_name, strlen(class_name) TSRMLS_CC);
}
efree(class_name);
/*
// object property
if (Twig_TemplateInterface::METHOD_CALL !== $type) {
if (isset(self::$cache[$class]['properties'][$item])
|| isset($object->$item) || array_key_exists($item, $object)
) {
if (Twig_Template::METHOD_CALL !== $type) {
if (isset($object->$item) || array_key_exists((string) $item, $object)) {
if ($isDefinedTest) {
return true;
}
if ($this->env->hasExtension('sandbox')) {
$this->env->getExtension('sandbox')->checkPropertyAllowed($object, $item);
}
@ -897,53 +921,54 @@ PHP_FUNCTION(twig_template_get_attributes)
}
*/
if (strcmp("method", type) != 0) {
zval *tmp_class, *tmp_properties, *tmp_item;
char *class_name = NULL;
zval *tmp_properties, *tmp_item;
class_name = TWIG_GET_CLASS_NAME(object TSRMLS_CC);
tmp_class = TWIG_GET_ARRAY_ELEMENT(tmp_self_cache, class_name, strlen(class_name) TSRMLS_CC);
tmp_properties = TWIG_GET_ARRAY_ELEMENT(tmp_class, "properties", strlen("properties") TSRMLS_CC);
tmp_item = TWIG_GET_ARRAY_ELEMENT(tmp_properties, item, item_len TSRMLS_CC);
efree(class_name);
if (tmp_item || TWIG_HAS_PROPERTY(object, &zitem TSRMLS_CC) || TWIG_HAS_DYNAMIC_PROPERTY(object, item, item_len TSRMLS_CC)) {
if (tmp_item || TWIG_HAS_PROPERTY(object, zitem TSRMLS_CC) || TWIG_HAS_DYNAMIC_PROPERTY(object, item, item_len TSRMLS_CC)) {
if (isDefinedTest) {
RETURN_TRUE;
}
if (TWIG_CALL_SB(TWIG_PROPERTY_CHAR(template, "env" TSRMLS_CC), "hasExtension", "sandbox" TSRMLS_CC)) {
TWIG_CALL_ZZ(TWIG_CALL_S(TWIG_PROPERTY_CHAR(template, "env" TSRMLS_CC), "getExtension", "sandbox" TSRMLS_CC), "checkPropertyAllowed", object, &zitem TSRMLS_CC);
TWIG_CALL_ZZ(TWIG_CALL_S(TWIG_PROPERTY_CHAR(template, "env" TSRMLS_CC), "getExtension", "sandbox" TSRMLS_CC), "checkPropertyAllowed", object, zitem TSRMLS_CC);
}
if (EG(exception)) {
return;
}
ret = TWIG_PROPERTY(object, &zitem TSRMLS_CC);
ret = TWIG_PROPERTY(object, zitem TSRMLS_CC);
RETURN_ZVAL(ret, 1, 0);
}
}
/*
// object method
if (!isset(self::$cache[$class]['methods'])) {
self::$cache[$class]['methods'] = array_change_key_case(array_flip(get_class_methods($object)));
}
$call = false;
$lcItem = strtolower($item);
if (isset(self::$cache[$class]['methods'][$lcItem])) {
$method = $item;
$method = (string) $item;
} elseif (isset(self::$cache[$class]['methods']['get'.$lcItem])) {
$method = 'get'.$item;
} elseif (isset(self::$cache[$class]['methods']['is'.$lcItem])) {
$method = 'is'.$item;
} elseif (isset(self::$cache[$class]['methods']['__call'])) {
$method = $item;
$method = (string) $item;
$call = true;
*/
{
int call = 0;
char *lcItem = TWIG_STRTOLOWER(item, item_len);
int lcItem_length;
char *method = NULL;
char *tmp_method_name_get;
char *tmp_method_name_is;
zval *tmp_class, *tmp_methods;
char *class_name = NULL;
zval *zmethod;
zval *tmp_methods;
class_name = TWIG_GET_CLASS_NAME(object TSRMLS_CC);
lcItem_length = strlen(lcItem);
tmp_method_name_get = emalloc(4 + lcItem_length);
tmp_method_name_is = emalloc(3 + lcItem_length);
@ -951,9 +976,7 @@ PHP_FUNCTION(twig_template_get_attributes)
sprintf(tmp_method_name_get, "get%s", lcItem);
sprintf(tmp_method_name_is, "is%s", lcItem);
tmp_class = TWIG_GET_ARRAY_ELEMENT(tmp_self_cache, class_name, strlen(class_name) TSRMLS_CC);
tmp_methods = TWIG_GET_ARRAY_ELEMENT(tmp_class, "methods", strlen("methods") TSRMLS_CC);
efree(class_name);
if (TWIG_GET_ARRAY_ELEMENT(tmp_methods, lcItem, lcItem_length TSRMLS_CC)) {
method = item;
@ -963,16 +986,20 @@ PHP_FUNCTION(twig_template_get_attributes)
method = tmp_method_name_is;
} else if (TWIG_GET_ARRAY_ELEMENT(tmp_methods, "__call", 6 TSRMLS_CC)) {
method = item;
call = 1;
/*
} else {
if ($isDefinedTest) {
return false;
}
if ($ignoreStrictCheck || !$this->env->isStrictVariables()) {
return null;
}
throw new Twig_Error_Runtime(sprintf('Method "%s" for object "%s" does not exist', $item, get_class($object)));
throw new Twig_Error_Runtime(sprintf('Method "%s" for object "%s" does not exist', $item, get_class($object)), -1, $this->getTemplateName());
}
if ($isDefinedTest) {
return true;
}
@ -1003,27 +1030,46 @@ PHP_FUNCTION(twig_template_get_attributes)
$this->env->getExtension('sandbox')->checkMethodAllowed($object, $method);
}
*/
MAKE_STD_ZVAL(zmethod);
ZVAL_STRING(zmethod, method, 1);
if (TWIG_CALL_SB(TWIG_PROPERTY_CHAR(template, "env" TSRMLS_CC), "hasExtension", "sandbox" TSRMLS_CC)) {
TWIG_CALL_ZZ(TWIG_CALL_S(TWIG_PROPERTY_CHAR(template, "env" TSRMLS_CC), "getExtension", "sandbox" TSRMLS_CC), "checkMethodAllowed", object, &zitem TSRMLS_CC);
TWIG_CALL_ZZ(TWIG_CALL_S(TWIG_PROPERTY_CHAR(template, "env" TSRMLS_CC), "getExtension", "sandbox" TSRMLS_CC), "checkMethodAllowed", object, zmethod TSRMLS_CC);
}
if (EG(exception)) {
efree(tmp_method_name_get);
efree(tmp_method_name_is);
efree(lcItem);
zval_ptr_dtor(&zmethod);
return;
}
/*
$ret = call_user_func_array(array($object, $method), $arguments);
// Some objects throw exceptions when they have __call, and the method we try
// to call is not supported. If ignoreStrictCheck is true, we should return null.
try {
$ret = call_user_func_array(array($object, $method), $arguments);
} catch (BadMethodCallException $e) {
if ($call && ($ignoreStrictCheck || !$this->env->isStrictVariables())) {
return null;
}
throw $e;
}
*/
if (Z_TYPE_P(object) == IS_OBJECT) {
ret = TWIG_CALL_USER_FUNC_ARRAY(object, method, arguments TSRMLS_CC);
free_ret = 1;
ret = TWIG_CALL_USER_FUNC_ARRAY(object, method, arguments TSRMLS_CC);
if (EG(exception) && TWIG_INSTANCE_OF(EG(exception), spl_ce_BadMethodCallException TSRMLS_CC)) {
if (ignoreStrictCheck || !TWIG_CALL_BOOLEAN(TWIG_PROPERTY_CHAR(template, "env" TSRMLS_CC), "isStrictVariables" TSRMLS_CC)) {
zend_clear_exception(TSRMLS_C);
return;
}
}
free_ret = 1;
efree(tmp_method_name_get);
efree(tmp_method_name_is);
efree(lcItem);
zval_ptr_dtor(&zmethod);
}
/*
// useful when calling a template method from a template
// this is not supported but unfortunately heavily used in the Symfony profiler
if ($object instanceof Twig_TemplateInterface) {
return $ret === '' ? '' : new Twig_Markup($ret, $this->env->getCharset());
}
@ -1033,9 +1079,7 @@ PHP_FUNCTION(twig_template_get_attributes)
// ret can be null, if e.g. the called method throws an exception
if (ret) {
if (TWIG_INSTANCE_OF_USERLAND(object, "Twig_TemplateInterface" TSRMLS_CC)) {
if (Z_STRLEN_P(ret) == 0) {
free_ret = 1;
} else {
if (Z_STRLEN_P(ret) != 0) {
zval *charset = TWIG_CALL_USER_FUNC_ARRAY(TWIG_PROPERTY_CHAR(template, "env" TSRMLS_CC), "getCharset", NULL TSRMLS_CC);
TWIG_NEW(return_value, "Twig_Markup", ret, charset TSRMLS_CC);
zval_ptr_dtor(&charset);

View File

@ -18,11 +18,16 @@ class Twig_Autoloader
{
/**
* Registers Twig_Autoloader as an SPL autoloader.
*
* @param Boolean $prepend Whether to prepend the autoloader or not.
*/
public static function register()
public static function register($prepend = false)
{
ini_set('unserialize_callback_func', 'spl_autoload_call');
spl_autoload_register(array(new self, 'autoload'));
if (version_compare(phpversion(), '5.3.0', '>=')) {
spl_autoload_register(array(new self, 'autoload'), true, $prepend);
} else {
spl_autoload_register(array(new self, 'autoload'));
}
}
/**

View File

@ -180,11 +180,12 @@ class Twig_Compiler implements Twig_CompilerInterface
$this->raw($value ? 'true' : 'false');
} elseif (is_array($value)) {
$this->raw('array(');
$i = 0;
$first = true;
foreach ($value as $key => $value) {
if ($i++) {
if (!$first) {
$this->raw(', ');
}
$first = false;
$this->repr($key);
$this->raw(' => ');
$this->repr($value);
@ -252,6 +253,8 @@ class Twig_Compiler implements Twig_CompilerInterface
* @param integer $step The number of indentation to remove
*
* @return Twig_Compiler The current compiler instance
*
* @throws LogicException When trying to outdent too much so the indentation would become negative
*/
public function outdent($step = 1)
{

View File

@ -16,7 +16,7 @@
*/
class Twig_Environment
{
const VERSION = '1.12.3';
const VERSION = '1.15.0';
protected $charset;
protected $loader;
@ -53,7 +53,7 @@ class Twig_Environment
* * debug: When set to true, it automatically set "auto_reload" to true as
* well (default to false).
*
* * charset: The charset used by the templates (default to utf-8).
* * charset: The charset used by the templates (default to UTF-8).
*
* * base_template_class: The base template class to use for generated
* templates (default to Twig_Template).
@ -61,9 +61,9 @@ class Twig_Environment
* * cache: An absolute path where to store the compiled templates, or
* false to disable compilation cache (default).
*
* * auto_reload: Whether to reload the template is the original source changed.
* * auto_reload: Whether to reload the template if the original source changed.
* If you don't provide the auto_reload option, it will be
* determined automatically base on the debug value.
* determined automatically based on the debug value.
*
* * strict_variables: Whether to ignore invalid variables in templates
* (default to false).
@ -99,7 +99,7 @@ class Twig_Environment
), $options);
$this->debug = (bool) $options['debug'];
$this->charset = $options['charset'];
$this->charset = strtoupper($options['charset']);
$this->baseTemplateClass = $options['base_template_class'];
$this->autoReload = null === $options['auto_reload'] ? $this->debug : (bool) $options['auto_reload'];
$this->strictVariables = (bool) $options['strict_variables'];
@ -239,7 +239,7 @@ class Twig_Environment
*
* @param string $name The template name
*
* @return string The cache file name
* @return string|false The cache file name or false when caching is disabled
*/
public function getCacheFilename($name)
{
@ -262,7 +262,7 @@ class Twig_Environment
*/
public function getTemplateClass($name, $index = null)
{
return $this->templateClassPrefix.md5($this->getLoader()->getCacheKey($name)).(null === $index ? '' : '_'.$index);
return $this->templateClassPrefix.hash('sha256', $this->getLoader()->getCacheKey($name)).(null === $index ? '' : '_'.$index);
}
/**
@ -282,6 +282,10 @@ class Twig_Environment
* @param array $context An array of parameters to pass to the template
*
* @return string The rendered template
*
* @throws Twig_Error_Loader When the template cannot be found
* @throws Twig_Error_Syntax When an error occurred during compilation
* @throws Twig_Error_Runtime When an error occurred during rendering
*/
public function render($name, array $context = array())
{
@ -293,6 +297,10 @@ class Twig_Environment
*
* @param string $name The template name
* @param array $context An array of parameters to pass to the template
*
* @throws Twig_Error_Loader When the template cannot be found
* @throws Twig_Error_Syntax When an error occurred during compilation
* @throws Twig_Error_Runtime When an error occurred during rendering
*/
public function display($name, array $context = array())
{
@ -306,6 +314,9 @@ class Twig_Environment
* @param integer $index The index if it is an embedded template
*
* @return Twig_TemplateInterface A template instance representing the given template name
*
* @throws Twig_Error_Loader When the template cannot be found
* @throws Twig_Error_Syntax When an error occurred during compilation
*/
public function loadTemplate($name, $index = null)
{
@ -358,6 +369,19 @@ class Twig_Environment
return $this->getLoader()->isFresh($name, $time);
}
/**
* Tries to load a template consecutively from an array.
*
* Similar to loadTemplate() but it also accepts Twig_TemplateInterface instances and an array
* of templates where each is tried to be loaded.
*
* @param string|Twig_Template|array $names A template or an array of templates to try consecutively
*
* @return Twig_Template
*
* @throws Twig_Error_Loader When none of the templates can be found
* @throws Twig_Error_Syntax When an error occurred during compilation
*/
public function resolveTemplate($names)
{
if (!is_array($names)) {
@ -437,6 +461,8 @@ class Twig_Environment
* @param string $name The template name
*
* @return Twig_TokenStream A Twig_TokenStream instance
*
* @throws Twig_Error_Syntax When the code is syntactically wrong
*/
public function tokenize($source, $name = null)
{
@ -468,15 +494,17 @@ class Twig_Environment
}
/**
* Parses a token stream.
* Converts a token stream to a node tree.
*
* @param Twig_TokenStream $tokens A Twig_TokenStream instance
* @param Twig_TokenStream $stream A token stream instance
*
* @return Twig_Node_Module A Node tree
* @return Twig_Node_Module A node tree
*
* @throws Twig_Error_Syntax When the token stream is syntactically or semantically wrong
*/
public function parse(Twig_TokenStream $tokens)
public function parse(Twig_TokenStream $stream)
{
return $this->getParser()->parse($tokens);
return $this->getParser()->parse($stream);
}
/**
@ -504,7 +532,7 @@ class Twig_Environment
}
/**
* Compiles a Node.
* Compiles a node and returns the PHP code.
*
* @param Twig_NodeInterface $node A Twig_NodeInterface instance
*
@ -522,6 +550,8 @@ class Twig_Environment
* @param string $name The template name
*
* @return string The compiled PHP source code
*
* @throws Twig_Error_Syntax When there was an error during tokenizing, parsing or compiling
*/
public function compileSource($source, $name = null)
{
@ -531,7 +561,7 @@ class Twig_Environment
$e->setTemplateFile($name);
throw $e;
} catch (Exception $e) {
throw new Twig_Error_Runtime(sprintf('An exception has been thrown during the compilation of a template ("%s").', $e->getMessage()), -1, $name, $e);
throw new Twig_Error_Syntax(sprintf('An exception has been thrown during the compilation of a template ("%s").', $e->getMessage()), -1, $name, $e);
}
}
@ -566,7 +596,7 @@ class Twig_Environment
*/
public function setCharset($charset)
{
$this->charset = $charset;
$this->charset = strtoupper($charset);
}
/**
@ -728,7 +758,7 @@ class Twig_Environment
public function addNodeVisitor(Twig_NodeVisitorInterface $visitor)
{
if ($this->extensionInitialized) {
throw new LogicException('Unable to add a node visitor as extensions have already been initialized.', $extension->getName());
throw new LogicException('Unable to add a node visitor as extensions have already been initialized.');
}
$this->staging->addNodeVisitor($visitor);
@ -764,11 +794,11 @@ class Twig_Environment
$filter = $name;
$name = $filter->getName();
}
if ($this->extensionInitialized) {
throw new LogicException(sprintf('Unable to add filter "%s" as extensions have already been initialized.', $name));
}
$this->staging->addFilter($name, $filter);
}
@ -853,7 +883,7 @@ class Twig_Environment
$test = $name;
$name = $test->getName();
}
if ($this->extensionInitialized) {
throw new LogicException(sprintf('Unable to add test "%s" as extensions have already been initialized.', $name));
}
@ -911,11 +941,11 @@ class Twig_Environment
$function = $name;
$name = $function->getName();
}
if ($this->extensionInitialized) {
throw new LogicException(sprintf('Unable to add function "%s" as extensions have already been initialized.', $name));
}
$this->staging->addFunction($name, $function);
}
@ -1099,10 +1129,17 @@ class Twig_Environment
{
$globals = array();
foreach ($this->extensions as $extension) {
$globals = array_merge($globals, $extension->getGlobals());
$extGlob = $extension->getGlobals();
if (!is_array($extGlob)) {
throw new UnexpectedValueException(sprintf('"%s::getGlobals()" must return an array of globals.', get_class($extension)));
}
$globals[] = $extGlob;
}
return array_merge($globals, $this->staging->getGlobals());
$globals[] = $this->staging->getGlobals();
return call_user_func_array('array_merge', $globals);
}
protected function initExtensions()
@ -1202,7 +1239,7 @@ class Twig_Environment
throw new RuntimeException(sprintf("Unable to write in the cache directory (%s).", $dir));
}
$tmpFile = tempnam(dirname($file), basename($file));
$tmpFile = tempnam($dir, basename($file));
if (false !== @file_put_contents($tmpFile, $content)) {
// rename does not work on Win32 before 5.2.6
if (@rename($tmpFile, $file) || (@copy($tmpFile, $file) && unlink($tmpFile))) {

View File

@ -186,10 +186,21 @@ class Twig_Error extends Exception
protected function guessTemplateInfo()
{
$template = null;
foreach (debug_backtrace() as $trace) {
$templateClass = null;
if (version_compare(phpversion(), '5.3.6', '>=')) {
$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS | DEBUG_BACKTRACE_PROVIDE_OBJECT);
} else {
$backtrace = debug_backtrace();
}
foreach ($backtrace as $trace) {
if (isset($trace['object']) && $trace['object'] instanceof Twig_Template && 'Twig_Template' !== get_class($trace['object'])) {
if (null === $this->filename || $this->filename == $trace['object']->getTemplateName()) {
$currentClass = get_class($trace['object']);
$isEmbedContainer = 0 === strpos($templateClass, $currentClass);
if (null === $this->filename || ($this->filename == $trace['object']->getTemplateName() && !$isEmbedContainer)) {
$template = $trace['object'];
$templateClass = get_class($trace['object']);
}
}
}
@ -206,6 +217,11 @@ class Twig_Error extends Exception
$r = new ReflectionObject($template);
$file = $r->getFileName();
// hhvm has a bug where eval'ed files comes out as the current directory
if (is_dir($file)) {
$file = '';
}
$exceptions = array($e = $this);
while (($e instanceof self || method_exists($e, 'getPrevious')) && $e = $e->getPrevious()) {
$exceptions[] = $e;

View File

@ -22,7 +22,7 @@ interface Twig_ExistsLoaderInterface
*
* @param string $name The name of the template to check if we can load
*
* @return boolean If the template source code is handled by this loader or not
* @return Boolean If the template source code is handled by this loader or not
*/
public function exists($name);
}

View File

@ -86,18 +86,15 @@ class Twig_ExpressionParser
protected function parseConditionalExpression($expr)
{
while ($this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, '?')) {
$this->parser->getStream()->next();
if (!$this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, ':')) {
while ($this->parser->getStream()->nextIf(Twig_Token::PUNCTUATION_TYPE, '?')) {
if (!$this->parser->getStream()->nextIf(Twig_Token::PUNCTUATION_TYPE, ':')) {
$expr2 = $this->parseExpression();
if ($this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, ':')) {
$this->parser->getStream()->next();
if ($this->parser->getStream()->nextIf(Twig_Token::PUNCTUATION_TYPE, ':')) {
$expr3 = $this->parseExpression();
} else {
$expr3 = new Twig_Node_Expression_Constant('', $this->parser->getCurrentToken()->getLine());
}
} else {
$this->parser->getStream()->next();
$expr2 = $expr;
$expr3 = $this->parseExpression();
}
@ -161,6 +158,14 @@ class Twig_ExpressionParser
$node = $this->parseStringExpression();
break;
case Twig_Token::OPERATOR_TYPE:
if (preg_match(Twig_Lexer::REGEX_NAME, $token->getValue(), $matches) && $matches[0] == $token->getValue()) {
// in this context, string operators are variable names
$this->parser->getStream()->next();
$node = new Twig_Node_Expression_Name($token->getValue(), $token->getLine());
break;
}
default:
if ($token->test(Twig_Token::PUNCTUATION_TYPE, '[')) {
$node = $this->parseArrayExpression();
@ -182,12 +187,10 @@ class Twig_ExpressionParser
// a string cannot be followed by another string in a single expression
$nextCanBeString = true;
while (true) {
if ($stream->test(Twig_Token::STRING_TYPE) && $nextCanBeString) {
$token = $stream->next();
if ($nextCanBeString && $token = $stream->nextIf(Twig_Token::STRING_TYPE)) {
$nodes[] = new Twig_Node_Expression_Constant($token->getValue(), $token->getLine());
$nextCanBeString = false;
} elseif ($stream->test(Twig_Token::INTERPOLATION_START_TYPE)) {
$stream->next();
} elseif ($stream->nextIf(Twig_Token::INTERPOLATION_START_TYPE)) {
$nodes[] = $this->parseExpression();
$stream->expect(Twig_Token::INTERPOLATION_END_TYPE);
$nextCanBeString = true;
@ -253,8 +256,7 @@ class Twig_ExpressionParser
// * a string -- 'a'
// * a name, which is equivalent to a string -- a
// * an expression, which must be enclosed in parentheses -- (1 + 2)
if ($stream->test(Twig_Token::STRING_TYPE) || $stream->test(Twig_Token::NAME_TYPE) || $stream->test(Twig_Token::NUMBER_TYPE)) {
$token = $stream->next();
if (($token = $stream->nextIf(Twig_Token::STRING_TYPE)) || ($token = $stream->nextIf(Twig_Token::NAME_TYPE)) || $token = $stream->nextIf(Twig_Token::NUMBER_TYPE)) {
$key = new Twig_Node_Expression_Constant($token->getValue(), $token->getLine());
} elseif ($stream->test(Twig_Token::PUNCTUATION_TYPE, '(')) {
$key = $this->parseExpression();
@ -316,7 +318,7 @@ class Twig_ExpressionParser
throw new Twig_Error_Syntax('The "attribute" function takes at least two arguments (the variable and the attributes)', $line, $this->parser->getFilename());
}
return new Twig_Node_Expression_GetAttr($args->getNode(0), $args->getNode(1), count($args) > 2 ? $args->getNode(2) : new Twig_Node_Expression_Array(array(), $line), Twig_TemplateInterface::ANY_CALL, $line);
return new Twig_Node_Expression_GetAttr($args->getNode(0), $args->getNode(1), count($args) > 2 ? $args->getNode(2) : new Twig_Node_Expression_Array(array(), $line), Twig_Template::ANY_CALL, $line);
default:
if (null !== $alias = $this->parser->getImportedSymbol('function', $name)) {
$arguments = new Twig_Node_Expression_Array(array(), $line);
@ -343,7 +345,7 @@ class Twig_ExpressionParser
$token = $stream->next();
$lineno = $token->getLine();
$arguments = new Twig_Node_Expression_Array(array(), $lineno);
$type = Twig_TemplateInterface::ANY_CALL;
$type = Twig_Template::ANY_CALL;
if ($token->getValue() == '.') {
$token = $stream->next();
if (
@ -365,7 +367,7 @@ class Twig_ExpressionParser
throw new Twig_Error_Syntax('Expected name or number', $lineno, $this->parser->getFilename());
}
if ($node instanceof Twig_Node_Expression_Name && null !== $alias = $this->parser->getImportedSymbol('template', $node->getAttribute('name'))) {
if ($node instanceof Twig_Node_Expression_Name && null !== $this->parser->getImportedSymbol('template', $node->getAttribute('name'))) {
if (!$arg instanceof Twig_Node_Expression_Constant) {
throw new Twig_Error_Syntax(sprintf('Dynamic macro names are not supported (called on "%s")', $node->getAttribute('name')), $token->getLine(), $this->parser->getFilename());
}
@ -376,7 +378,7 @@ class Twig_ExpressionParser
return $node;
}
} else {
$type = Twig_TemplateInterface::ARRAY_CALL;
$type = Twig_Template::ARRAY_CALL;
// slice?
$slice = false;
@ -387,9 +389,8 @@ class Twig_ExpressionParser
$arg = $this->parseExpression();
}
if ($stream->test(Twig_Token::PUNCTUATION_TYPE, ':')) {
if ($stream->nextIf(Twig_Token::PUNCTUATION_TYPE, ':')) {
$slice = true;
$stream->next();
}
if ($slice) {
@ -472,8 +473,7 @@ class Twig_ExpressionParser
}
$name = null;
if ($namedArguments && $stream->test(Twig_Token::OPERATOR_TYPE, '=')) {
$token = $stream->next();
if ($namedArguments && $token = $stream->nextIf(Twig_Token::OPERATOR_TYPE, '=')) {
if (!$value instanceof Twig_Node_Expression_Name) {
throw new Twig_Error_Syntax(sprintf('A parameter name must be a string, "%s" given', get_class($value)), $token->getLine(), $this->parser->getFilename());
}
@ -519,10 +519,9 @@ class Twig_ExpressionParser
}
$targets[] = new Twig_Node_Expression_AssignName($token->getValue(), $token->getLine());
if (!$this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, ',')) {
if (!$this->parser->getStream()->nextIf(Twig_Token::PUNCTUATION_TYPE, ',')) {
break;
}
$this->parser->getStream()->next();
}
return new Twig_Node($targets);
@ -533,10 +532,9 @@ class Twig_ExpressionParser
$targets = array();
while (true) {
$targets[] = $this->parseExpression();
if (!$this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, ',')) {
if (!$this->parser->getStream()->nextIf(Twig_Token::PUNCTUATION_TYPE, ',')) {
break;
}
$this->parser->getStream()->next();
}
return new Twig_Node($targets);

View File

@ -34,7 +34,7 @@ abstract class Twig_Extension implements Twig_ExtensionInterface
/**
* Returns the node visitor instances to add to the existing list.
*
* @return array An array of Twig_NodeVisitorInterface instances
* @return Twig_NodeVisitorInterface[] An array of Twig_NodeVisitorInterface instances
*/
public function getNodeVisitors()
{

View File

@ -1,7 +1,8 @@
<?php
if (!defined('ENT_SUBSTITUTE')) {
define('ENT_SUBSTITUTE', 8);
// use 0 as hhvm does not support several flags yet
define('ENT_SUBSTITUTE', 0);
}
/*
@ -17,6 +18,28 @@ class Twig_Extension_Core extends Twig_Extension
protected $dateFormats = array('F j, Y H:i', '%d days');
protected $numberFormat = array(0, '.', ',');
protected $timezone = null;
protected $escapers = array();
/**
* Defines a new escaper to be used via the escape filter.
*
* @param string $strategy The strategy name that should be used as a strategy in the escape call
* @param callable $callable A valid PHP callable
*/
public function setEscaper($strategy, $callable)
{
$this->escapers[$strategy] = $callable;
}
/**
* Gets all defined escapers.
*
* @return array An array of escapers
*/
public function getEscapers()
{
return $this->escapers;
}
/**
* Sets the default format to be used by the date filter.
@ -94,7 +117,7 @@ class Twig_Extension_Core extends Twig_Extension
/**
* Returns the token parser instance to add to the existing list.
*
* @return array An array of Twig_TokenParser instances
* @return Twig_TokenParser[] An array of Twig_TokenParser instances
*/
public function getTokenParsers()
{
@ -132,6 +155,7 @@ class Twig_Extension_Core extends Twig_Extension
new Twig_SimpleFilter('replace', 'strtr'),
new Twig_SimpleFilter('number_format', 'twig_number_format_filter', array('needs_environment' => true)),
new Twig_SimpleFilter('abs', 'abs'),
new Twig_SimpleFilter('round', 'twig_round'),
// encoding
new Twig_SimpleFilter('url_encode', 'twig_urlencode_filter'),
@ -186,12 +210,15 @@ class Twig_Extension_Core extends Twig_Extension
public function getFunctions()
{
return array(
new Twig_SimpleFunction('max', 'max'),
new Twig_SimpleFunction('min', 'min'),
new Twig_SimpleFunction('range', 'range'),
new Twig_SimpleFunction('constant', 'twig_constant'),
new Twig_SimpleFunction('cycle', 'twig_cycle'),
new Twig_SimpleFunction('random', 'twig_random', array('needs_environment' => true)),
new Twig_SimpleFunction('date', 'twig_date_converter', array('needs_environment' => true)),
new Twig_SimpleFunction('include', 'twig_include', array('needs_environment' => true, 'needs_context' => true)),
new Twig_SimpleFunction('include', 'twig_include', array('needs_environment' => true, 'needs_context' => true, 'is_safe' => array('all'))),
new Twig_SimpleFunction('source', 'twig_source', array('needs_environment' => true, 'is_safe' => array('all'))),
);
}
@ -207,9 +234,11 @@ class Twig_Extension_Core extends Twig_Extension
new Twig_SimpleTest('odd', null, array('node_class' => 'Twig_Node_Expression_Test_Odd')),
new Twig_SimpleTest('defined', null, array('node_class' => 'Twig_Node_Expression_Test_Defined')),
new Twig_SimpleTest('sameas', null, array('node_class' => 'Twig_Node_Expression_Test_Sameas')),
new Twig_SimpleTest('same as', null, array('node_class' => 'Twig_Node_Expression_Test_Sameas')),
new Twig_SimpleTest('none', null, array('node_class' => 'Twig_Node_Expression_Test_Null')),
new Twig_SimpleTest('null', null, array('node_class' => 'Twig_Node_Expression_Test_Null')),
new Twig_SimpleTest('divisibleby', null, array('node_class' => 'Twig_Node_Expression_Test_Divisibleby')),
new Twig_SimpleTest('divisible by', null, array('node_class' => 'Twig_Node_Expression_Test_Divisibleby')),
new Twig_SimpleTest('constant', null, array('node_class' => 'Twig_Node_Expression_Test_Constant')),
new Twig_SimpleTest('empty', 'twig_test_empty'),
new Twig_SimpleTest('iterable', 'twig_test_iterable'),
@ -230,50 +259,52 @@ class Twig_Extension_Core extends Twig_Extension
'+' => array('precedence' => 500, 'class' => 'Twig_Node_Expression_Unary_Pos'),
),
array(
'or' => array('precedence' => 10, 'class' => 'Twig_Node_Expression_Binary_Or', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'and' => array('precedence' => 15, 'class' => 'Twig_Node_Expression_Binary_And', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'b-or' => array('precedence' => 16, 'class' => 'Twig_Node_Expression_Binary_BitwiseOr', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'b-xor' => array('precedence' => 17, 'class' => 'Twig_Node_Expression_Binary_BitwiseXor', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'b-and' => array('precedence' => 18, 'class' => 'Twig_Node_Expression_Binary_BitwiseAnd', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'==' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_Equal', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'!=' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_NotEqual', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'<' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_Less', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'>' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_Greater', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'>=' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_GreaterEqual', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'<=' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_LessEqual', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'not in' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_NotIn', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'in' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_In', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'..' => array('precedence' => 25, 'class' => 'Twig_Node_Expression_Binary_Range', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'+' => array('precedence' => 30, 'class' => 'Twig_Node_Expression_Binary_Add', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'-' => array('precedence' => 30, 'class' => 'Twig_Node_Expression_Binary_Sub', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'~' => array('precedence' => 40, 'class' => 'Twig_Node_Expression_Binary_Concat', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'*' => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_Mul', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'/' => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_Div', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'//' => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_FloorDiv', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'%' => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_Mod', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'is' => array('precedence' => 100, 'callable' => array($this, 'parseTestExpression'), 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'is not' => array('precedence' => 100, 'callable' => array($this, 'parseNotTestExpression'), 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'**' => array('precedence' => 200, 'class' => 'Twig_Node_Expression_Binary_Power', 'associativity' => Twig_ExpressionParser::OPERATOR_RIGHT),
'or' => array('precedence' => 10, 'class' => 'Twig_Node_Expression_Binary_Or', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'and' => array('precedence' => 15, 'class' => 'Twig_Node_Expression_Binary_And', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'b-or' => array('precedence' => 16, 'class' => 'Twig_Node_Expression_Binary_BitwiseOr', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'b-xor' => array('precedence' => 17, 'class' => 'Twig_Node_Expression_Binary_BitwiseXor', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'b-and' => array('precedence' => 18, 'class' => 'Twig_Node_Expression_Binary_BitwiseAnd', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'==' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_Equal', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'!=' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_NotEqual', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'<' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_Less', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'>' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_Greater', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'>=' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_GreaterEqual', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'<=' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_LessEqual', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'not in' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_NotIn', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'in' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_In', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'matches' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_Matches', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'starts with' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_StartsWith', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'ends with' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_EndsWith', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'..' => array('precedence' => 25, 'class' => 'Twig_Node_Expression_Binary_Range', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'+' => array('precedence' => 30, 'class' => 'Twig_Node_Expression_Binary_Add', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'-' => array('precedence' => 30, 'class' => 'Twig_Node_Expression_Binary_Sub', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'~' => array('precedence' => 40, 'class' => 'Twig_Node_Expression_Binary_Concat', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'*' => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_Mul', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'/' => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_Div', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'//' => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_FloorDiv', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'%' => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_Mod', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'is' => array('precedence' => 100, 'callable' => array($this, 'parseTestExpression'), 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'is not' => array('precedence' => 100, 'callable' => array($this, 'parseNotTestExpression'), 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
'**' => array('precedence' => 200, 'class' => 'Twig_Node_Expression_Binary_Power', 'associativity' => Twig_ExpressionParser::OPERATOR_RIGHT),
),
);
}
public function parseNotTestExpression(Twig_Parser $parser, $node)
public function parseNotTestExpression(Twig_Parser $parser, Twig_NodeInterface $node)
{
return new Twig_Node_Expression_Unary_Not($this->parseTestExpression($parser, $node), $parser->getCurrentToken()->getLine());
}
public function parseTestExpression(Twig_Parser $parser, $node)
public function parseTestExpression(Twig_Parser $parser, Twig_NodeInterface $node)
{
$stream = $parser->getStream();
$name = $stream->expect(Twig_Token::NAME_TYPE)->getValue();
$class = $this->getTestNodeClass($parser, $name, $node->getLine());
$arguments = null;
if ($stream->test(Twig_Token::PUNCTUATION_TYPE, '(')) {
$arguments = $parser->getExpressionParser()->parseArguments(true);
}
$class = $this->getTestNodeClass($parser, $name, $node->getLine());
return new $class($node, $name, $arguments, $parser->getCurrentToken()->getLine());
}
@ -281,7 +312,21 @@ class Twig_Extension_Core extends Twig_Extension
{
$env = $parser->getEnvironment();
$testMap = $env->getTests();
if (!isset($testMap[$name])) {
$testName = null;
if (isset($testMap[$name])) {
$testName = $name;
} elseif ($parser->getStream()->test(Twig_Token::NAME_TYPE)) {
// try 2-words tests
$name = $name.' '.$parser->getCurrentToken()->getValue();
if (isset($testMap[$name])) {
$parser->getStream()->next();
$testName = $name;
}
}
if (null === $testName) {
$message = sprintf('The test "%s" does not exist', $name);
if ($alternatives = $env->computeAlternatives($name, array_keys($env->getTests()))) {
$message = sprintf('%s. Did you mean "%s"', $message, implode('", "', $alternatives));
@ -348,7 +393,7 @@ function twig_random(Twig_Environment $env, $values = null)
return $values < 0 ? mt_rand($values, 0) : mt_rand(0, $values);
}
if ($values instanceof Traversable) {
if (is_object($values) && $values instanceof Traversable) {
$values = iterator_to_array($values);
} elseif (is_string($values)) {
if ('' === $values) {
@ -459,13 +504,15 @@ function twig_date_converter(Twig_Environment $env, $date = null, $timezone = nu
$defaultTimezone = $timezone;
}
if ($date instanceof DateTime) {
$date = clone $date;
if ($date instanceof DateTime || $date instanceof DateTimeInterface) {
$returningDate = new DateTime($date->format('c'));
if (false !== $timezone) {
$date->setTimezone($defaultTimezone);
$returningDate->setTimezone($defaultTimezone);
} else {
$returningDate->setTimezone($date->getTimezone());
}
return $date;
return $returningDate;
}
$asString = (string) $date;
@ -481,6 +528,28 @@ function twig_date_converter(Twig_Environment $env, $date = null, $timezone = nu
return $date;
}
/**
* Rounds a number.
*
* @param integer|float $value The value to round
* @param integer|float $precision The rounding precision
* @param string $method The method to use for rounding
*
* @return integer|float The rounded number
*/
function twig_round($value, $precision = 0, $method = 'common')
{
if ('common' == $method) {
return round($value, $precision);
}
if ('ceil' != $method && 'floor' != $method) {
throw new Twig_Error_Runtime('The round filter only supports the "common", "ceil", and "floor" methods.');
}
return $method($value * pow(10, $precision)) / pow(10, $precision);
}
/**
* Number format filter.
*
@ -518,7 +587,7 @@ function twig_number_format_filter(Twig_Environment $env, $number, $decimal = nu
* URL encodes a string as a path segment or an array as a query string.
*
* @param string|array $url A URL or an array of query parameters
* @param bool $raw true to use rawurlencode() instead of urlencode
* @param Boolean $raw true to use rawurlencode() instead of urlencode
*
* @return string The URL encoded value
*/
@ -620,7 +689,7 @@ function twig_array_merge($arr1, $arr2)
*/
function twig_slice(Twig_Environment $env, $item, $start, $length = null, $preserveKeys = false)
{
if ($item instanceof Traversable) {
if (is_object($item) && $item instanceof Traversable) {
$item = iterator_to_array($item, false);
}
@ -649,7 +718,7 @@ function twig_first(Twig_Environment $env, $item)
{
$elements = twig_slice($env, $item, 0, 1, false);
return is_string($elements) ? $elements[0] : current($elements);
return is_string($elements) ? $elements : current($elements);
}
/**
@ -664,7 +733,7 @@ function twig_last(Twig_Environment $env, $item)
{
$elements = twig_slice($env, $item, -1, 1, false);
return is_string($elements) ? $elements[0] : current($elements);
return is_string($elements) ? $elements : current($elements);
}
/**
@ -687,7 +756,7 @@ function twig_last(Twig_Environment $env, $item)
*/
function twig_join_filter($value, $glue = '')
{
if ($value instanceof Traversable) {
if (is_object($value) && $value instanceof Traversable) {
$value = iterator_to_array($value, false);
}
@ -829,7 +898,7 @@ function twig_in_filter($value, $compare)
}
return false !== strpos($compare, (string) $value);
} elseif ($compare instanceof Traversable) {
} elseif (is_object($compare) && $compare instanceof Traversable) {
return in_array($value, iterator_to_array($compare, false), is_object($value));
}
@ -847,21 +916,70 @@ function twig_in_filter($value, $compare)
*/
function twig_escape_filter(Twig_Environment $env, $string, $strategy = 'html', $charset = null, $autoescape = false)
{
if ($autoescape && is_object($string) && $string instanceof Twig_Markup) {
if ($autoescape && $string instanceof Twig_Markup) {
return $string;
}
if (!is_string($string) && !(is_object($string) && method_exists($string, '__toString'))) {
return $string;
if (!is_string($string)) {
if (is_object($string) && method_exists($string, '__toString')) {
$string = (string) $string;
} else {
return $string;
}
}
if (null === $charset) {
$charset = $env->getCharset();
}
$string = (string) $string;
switch ($strategy) {
case 'html':
// see http://php.net/htmlspecialchars
// Using a static variable to avoid initializing the array
// each time the function is called. Moving the declaration on the
// top of the function slow downs other escaping strategies.
static $htmlspecialcharsCharsets;
if (null === $htmlspecialcharsCharsets) {
if ('hiphop' === substr(PHP_VERSION, -6)) {
$htmlspecialcharsCharsets = array('utf-8' => true, 'UTF-8' => true);
} else {
$htmlspecialcharsCharsets = array(
'ISO-8859-1' => true, 'ISO8859-1' => true,
'ISO-8859-15' => true, 'ISO8859-15' => true,
'utf-8' => true, 'UTF-8' => true,
'CP866' => true, 'IBM866' => true, '866' => true,
'CP1251' => true, 'WINDOWS-1251' => true, 'WIN-1251' => true,
'1251' => true,
'CP1252' => true, 'WINDOWS-1252' => true, '1252' => true,
'KOI8-R' => true, 'KOI8-RU' => true, 'KOI8R' => true,
'BIG5' => true, '950' => true,
'GB2312' => true, '936' => true,
'BIG5-HKSCS' => true,
'SHIFT_JIS' => true, 'SJIS' => true, '932' => true,
'EUC-JP' => true, 'EUCJP' => true,
'ISO8859-5' => true, 'ISO-8859-5' => true, 'MACROMAN' => true,
);
}
}
if (isset($htmlspecialcharsCharsets[$charset])) {
return htmlspecialchars($string, ENT_QUOTES | ENT_SUBSTITUTE, $charset);
}
if (isset($htmlspecialcharsCharsets[strtoupper($charset)])) {
// cache the lowercase variant for future iterations
$htmlspecialcharsCharsets[$charset] = true;
return htmlspecialchars($string, ENT_QUOTES | ENT_SUBSTITUTE, $charset);
}
$string = twig_convert_encoding($string, 'UTF-8', $charset);
$string = htmlspecialchars($string, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
return twig_convert_encoding($string, $charset, 'UTF-8');
case 'js':
// escape all non-alphanumeric characters
// into their \xHH or \uHHHH representations
@ -915,47 +1033,29 @@ function twig_escape_filter(Twig_Environment $env, $string, $strategy = 'html',
return $string;
case 'html':
// see http://php.net/htmlspecialchars
// Using a static variable to avoid initializing the array
// each time the function is called. Moving the declaration on the
// top of the function slow downs other escaping strategies.
static $htmlspecialcharsCharsets = array(
'iso-8859-1' => true, 'iso8859-1' => true,
'iso-8859-15' => true, 'iso8859-15' => true,
'utf-8' => true,
'cp866' => true, 'ibm866' => true, '866' => true,
'cp1251' => true, 'windows-1251' => true, 'win-1251' => true,
'1251' => true,
'cp1252' => true, 'windows-1252' => true, '1252' => true,
'koi8-r' => true, 'koi8-ru' => true, 'koi8r' => true,
'big5' => true, '950' => true,
'gb2312' => true, '936' => true,
'big5-hkscs' => true,
'shift_jis' => true, 'sjis' => true, '932' => true,
'euc-jp' => true, 'eucjp' => true,
'iso8859-5' => true, 'iso-8859-5' => true, 'macroman' => true,
);
if (isset($htmlspecialcharsCharsets[strtolower($charset)])) {
return htmlspecialchars($string, ENT_QUOTES | ENT_SUBSTITUTE, $charset);
}
$string = twig_convert_encoding($string, 'UTF-8', $charset);
$string = htmlspecialchars($string, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
return twig_convert_encoding($string, $charset, 'UTF-8');
case 'url':
if (version_compare(PHP_VERSION, '5.3.0', '<')) {
// hackish test to avoid version_compare that is much slower, this works unless PHP releases a 5.10.*
// at that point however PHP 5.2.* support can be removed
if (PHP_VERSION < '5.3.0') {
return str_replace('%7E', '~', rawurlencode($string));
}
return rawurlencode($string);
default:
throw new Twig_Error_Runtime(sprintf('Invalid escaping strategy "%s" (valid ones: html, js, url, css, and html_attr).', $strategy));
static $escapers;
if (null === $escapers) {
$escapers = $env->getExtension('core')->getEscapers();
}
if (isset($escapers[$strategy])) {
return call_user_func($escapers[$strategy], $env, $string, $charset);
}
$validStrategies = implode(', ', array_merge(array('html', 'js', 'url', 'css', 'html_attr'), array_keys($escapers)));
throw new Twig_Error_Runtime(sprintf('Invalid escaping strategy "%s" (valid ones: %s).', $strategy, $validStrategies));
}
}
@ -1262,11 +1362,11 @@ function twig_test_iterable($value)
/**
* Renders a template.
*
* @param string template The template to render
* @param array variables The variables to pass to the template
* @param Boolean with_context Whether to pass the current context variables or not
* @param Boolean ignore_missing Whether to ignore missing templates or not
* @param Boolean sandboxed Whether to sandbox the template or not
* @param string|array $template The template to render or an array of templates to try consecutively
* @param array $variables The variables to pass to the template
* @param Boolean $with_context Whether to pass the current context variables or not
* @param Boolean $ignore_missing Whether to ignore missing templates or not
* @param Boolean $sandboxed Whether to sandbox the template or not
*
* @return string The rendered template
*/
@ -1284,7 +1384,7 @@ function twig_include(Twig_Environment $env, $context, $template, $variables = a
}
try {
return $env->resolveTemplate($template)->display($variables);
return $env->resolveTemplate($template)->render($variables);
} catch (Twig_Error_Loader $e) {
if (!$ignoreMissing) {
throw $e;
@ -1296,6 +1396,18 @@ function twig_include(Twig_Environment $env, $context, $template, $variables = a
}
}
/**
* Returns a template content without rendering it.
*
* @param string $name The template name
*
* @return string The template source
*/
function twig_source(Twig_Environment $env, $name)
{
return $env->getLoader()->getSource($name);
}
/**
* Provides the ability to get constants from instances as well as class/global constants.
*
@ -1318,13 +1430,13 @@ function twig_constant($constant, $object = null)
*
* @param array $items An array of items
* @param integer $size The size of the batch
* @param string $fill A string to fill missing items
* @param mixed $fill A value used to fill missing items
*
* @return array
*/
function twig_array_batch($items, $size, $fill = null)
{
if ($items instanceof Traversable) {
if (is_object($items) && $items instanceof Traversable) {
$items = iterator_to_array($items, false);
}
@ -1334,10 +1446,12 @@ function twig_array_batch($items, $size, $fill = null)
if (null !== $fill) {
$last = count($result) - 1;
$result[$last] = array_merge(
$result[$last],
array_fill(0, $size - count($result[$last]), $fill)
);
if ($fillCount = $size - count($result[$last])) {
$result[$last] = array_merge(
$result[$last],
array_fill(0, $fillCount, $fill)
);
}
}
return $result;

View File

@ -24,6 +24,7 @@ class Twig_Extension_Debug extends Twig_Extension
// false means that it was not set (and the default is on) or it explicitly enabled
// xdebug.overload_var_dump produces HTML only when html_errors is also enabled
&& (false === ini_get('html_errors') || ini_get('html_errors'))
|| 'cli' === php_sapi_name()
;
return array(

View File

@ -30,7 +30,7 @@ class Twig_Extension_Escaper extends Twig_Extension
/**
* Returns the node visitor instances to add to the existing list.
*
* @return array An array of Twig_NodeVisitorInterface instances
* @return Twig_NodeVisitorInterface[] An array of Twig_NodeVisitorInterface instances
*/
public function getNodeVisitors()
{

View File

@ -33,7 +33,7 @@ class Twig_Extension_Sandbox extends Twig_Extension
/**
* Returns the node visitor instances to add to the existing list.
*
* @return array An array of Twig_NodeVisitorInterface instances
* @return Twig_NodeVisitorInterface[] An array of Twig_NodeVisitorInterface instances
*/
public function getNodeVisitors()
{

View File

@ -33,7 +33,7 @@ class Twig_Extension_StringLoader extends Twig_Extension
* Loads a template from a string.
*
* <pre>
* {% include template_from_string("Hello {{ name }}") }}
* {{ include(template_from_string("Hello {{ name }}")) }}
* </pre>
*
* @param Twig_Environment $env A Twig_Environment instance
@ -43,16 +43,16 @@ class Twig_Extension_StringLoader extends Twig_Extension
*/
function twig_template_from_string(Twig_Environment $env, $template)
{
static $loader;
$name = sprintf('__string_template__%s', hash('sha256', uniqid(mt_rand(), true), false));
if (null === $loader) {
$loader = new Twig_Loader_String();
}
$loader = new Twig_Loader_Chain(array(
new Twig_Loader_Array(array($name => $template)),
$current = $env->getLoader(),
));
$current = $env->getLoader();
$env->setLoader($loader);
try {
$template = $env->loadTemplate($template);
$template = $env->loadTemplate($name);
} catch (Exception $e) {
$env->setLoader($current);

View File

@ -35,7 +35,7 @@ interface Twig_ExtensionInterface
/**
* Returns the node visitor instances to add to the existing list.
*
* @return array An array of Twig_NodeVisitorInterface instances
* @return Twig_NodeVisitorInterface[] An array of Twig_NodeVisitorInterface instances
*/
public function getNodeVisitors();

View File

@ -62,8 +62,6 @@ abstract class Twig_Filter implements Twig_FilterInterface, Twig_FilterCallableI
if (isset($this->options['is_safe_callback'])) {
return call_user_func($this->options['is_safe_callback'], $filterArgs);
}
return null;
}
public function getPreservesSafety()

View File

@ -73,12 +73,7 @@ class Twig_Lexer implements Twig_LexerInterface
}
/**
* Tokenizes a source code.
*
* @param string $code The source code
* @param string $filename A unique identifier for the source code
*
* @return Twig_TokenStream A token stream instance
* {@inheritdoc}
*/
public function tokenize($code, $filename = null)
{
@ -233,7 +228,7 @@ class Twig_Lexer implements Twig_LexerInterface
// operators
if (preg_match($this->regexes['operator'], $this->code, $match, null, $this->cursor)) {
$this->pushToken(Twig_Token::OPERATOR_TYPE, $match[0]);
$this->pushToken(Twig_Token::OPERATOR_TYPE, preg_replace('/\s+/', ' ', $match[0]));
$this->moveCursor($match[0]);
}
// names
@ -326,7 +321,6 @@ class Twig_Lexer implements Twig_LexerInterface
$this->moveCursor($match[0]);
} elseif (preg_match(self::REGEX_DQ_STRING_DELIM, $this->code, $match, null, $this->cursor)) {
list($expect, $lineno) = array_pop($this->brackets);
if ($this->code[$this->cursor] != '"') {
throw new Twig_Error_Syntax(sprintf('Unclosed "%s"', $expect), $lineno, $this->filename);
@ -382,10 +376,15 @@ class Twig_Lexer implements Twig_LexerInterface
// an operator that ends with a character must be followed by
// a whitespace or a parenthesis
if (ctype_alpha($operator[$length - 1])) {
$regex[] = preg_quote($operator, '/').'(?=[\s()])';
$r = preg_quote($operator, '/').'(?=[\s()])';
} else {
$regex[] = preg_quote($operator, '/');
$r = preg_quote($operator, '/');
}
// an operator with a space can be any amount of whitespaces
$r = preg_replace('/\s+/', '\s+', $r);
$regex[] = $r;
}
return '/'.implode('|', $regex).'/A';

View File

@ -24,6 +24,8 @@ interface Twig_LexerInterface
* @param string $filename A unique identifier for the source code
*
* @return Twig_TokenStream A token stream instance
*
* @throws Twig_Error_Syntax When the code is syntactically wrong
*/
public function tokenize($code, $filename = null);
}

View File

@ -21,7 +21,7 @@
*/
class Twig_Loader_Array implements Twig_LoaderInterface, Twig_ExistsLoaderInterface
{
protected $templates;
protected $templates = array();
/**
* Constructor.
@ -32,10 +32,7 @@ class Twig_Loader_Array implements Twig_LoaderInterface, Twig_ExistsLoaderInterf
*/
public function __construct(array $templates)
{
$this->templates = array();
foreach ($templates as $name => $template) {
$this->templates[$name] = $template;
}
$this->templates = $templates;
}
/**

View File

@ -17,7 +17,7 @@
class Twig_Loader_Chain implements Twig_LoaderInterface, Twig_ExistsLoaderInterface
{
private $hasSourceCache = array();
protected $loaders;
protected $loaders = array();
/**
* Constructor.
@ -26,7 +26,6 @@ class Twig_Loader_Chain implements Twig_LoaderInterface, Twig_ExistsLoaderInterf
*/
public function __construct(array $loaders = array())
{
$this->loaders = array();
foreach ($loaders as $loader) {
$this->addLoader($loader);
}
@ -76,8 +75,12 @@ class Twig_Loader_Chain implements Twig_LoaderInterface, Twig_ExistsLoaderInterf
}
foreach ($this->loaders as $loader) {
if ($loader instanceof Twig_ExistsLoaderInterface && $loader->exists($name)) {
return $this->hasSourceCache[$name] = true;
if ($loader instanceof Twig_ExistsLoaderInterface) {
if ($loader->exists($name)) {
return $this->hasSourceCache[$name] = true;
}
continue;
}
try {

View File

@ -16,17 +16,22 @@
*/
class Twig_Loader_Filesystem implements Twig_LoaderInterface, Twig_ExistsLoaderInterface
{
protected $paths;
protected $cache;
/** Identifier of the main namespace. */
const MAIN_NAMESPACE = '__main__';
protected $paths = array();
protected $cache = array();
/**
* Constructor.
*
* @param string|array $paths A path or an array of paths where to look for templates
*/
public function __construct($paths)
public function __construct($paths = array())
{
$this->setPaths($paths);
if ($paths) {
$this->setPaths($paths);
}
}
/**
@ -36,7 +41,7 @@ class Twig_Loader_Filesystem implements Twig_LoaderInterface, Twig_ExistsLoaderI
*
* @return array The array of paths where to look for templates
*/
public function getPaths($namespace = '__main__')
public function getPaths($namespace = self::MAIN_NAMESPACE)
{
return isset($this->paths[$namespace]) ? $this->paths[$namespace] : array();
}
@ -44,7 +49,7 @@ class Twig_Loader_Filesystem implements Twig_LoaderInterface, Twig_ExistsLoaderI
/**
* Returns the path namespaces.
*
* The "__main__" namespace is always defined.
* The main namespace is always defined.
*
* @return array The array of defined namespaces
*/
@ -59,7 +64,7 @@ class Twig_Loader_Filesystem implements Twig_LoaderInterface, Twig_ExistsLoaderI
* @param string|array $paths A path or an array of paths where to look for templates
* @param string $namespace A path namespace
*/
public function setPaths($paths, $namespace = '__main__')
public function setPaths($paths, $namespace = self::MAIN_NAMESPACE)
{
if (!is_array($paths)) {
$paths = array($paths);
@ -79,7 +84,7 @@ class Twig_Loader_Filesystem implements Twig_LoaderInterface, Twig_ExistsLoaderI
*
* @throws Twig_Error_Loader
*/
public function addPath($path, $namespace = '__main__')
public function addPath($path, $namespace = self::MAIN_NAMESPACE)
{
// invalidate the cache
$this->cache = array();
@ -99,7 +104,7 @@ class Twig_Loader_Filesystem implements Twig_LoaderInterface, Twig_ExistsLoaderI
*
* @throws Twig_Error_Loader
*/
public function prependPath($path, $namespace = '__main__')
public function prependPath($path, $namespace = self::MAIN_NAMESPACE)
{
// invalidate the cache
$this->cache = array();
@ -173,15 +178,15 @@ class Twig_Loader_Filesystem implements Twig_LoaderInterface, Twig_ExistsLoaderI
$this->validateName($name);
$namespace = '__main__';
$namespace = self::MAIN_NAMESPACE;
$shortname = $name;
if (isset($name[0]) && '@' == $name[0]) {
if (false === $pos = strpos($name, '/')) {
throw new Twig_Error_Loader(sprintf('Malformed namespaced template name "%s" (expecting "@namespace/template_name").', $name));
}
$namespace = substr($name, 1, $pos - 1);
$name = substr($name, $pos + 1);
$shortname = substr($name, $pos + 1);
}
if (!isset($this->paths[$namespace])) {
@ -189,8 +194,8 @@ class Twig_Loader_Filesystem implements Twig_LoaderInterface, Twig_ExistsLoaderI
}
foreach ($this->paths[$namespace] as $path) {
if (is_file($path.'/'.$name)) {
return $this->cache[$name] = $path.'/'.$name;
if (is_file($path.'/'.$shortname)) {
return $this->cache[$name] = $path.'/'.$shortname;
}
}

View File

@ -0,0 +1,30 @@
<?php
/*
* This file is part of Twig.
*
* (c) 2013 Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
class Twig_Node_Expression_Binary_EndsWith extends Twig_Node_Expression_Binary
{
public function compile(Twig_Compiler $compiler)
{
$compiler
->raw('(0 === substr_compare(')
->subcompile($this->getNode('left'))
->raw(', ')
->subcompile($this->getNode('right'))
->raw(', -strlen(')
->subcompile($this->getNode('right'))
->raw(')))')
;
}
public function operator(Twig_Compiler $compiler)
{
return $compiler->raw('');
}
}

View File

@ -0,0 +1,28 @@
<?php
/*
* This file is part of Twig.
*
* (c) 2013 Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
class Twig_Node_Expression_Binary_Matches extends Twig_Node_Expression_Binary
{
public function compile(Twig_Compiler $compiler)
{
$compiler
->raw('preg_match(')
->subcompile($this->getNode('right'))
->raw(', ')
->subcompile($this->getNode('left'))
->raw(')')
;
}
public function operator(Twig_Compiler $compiler)
{
return $compiler->raw('');
}
}

View File

@ -0,0 +1,28 @@
<?php
/*
* This file is part of Twig.
*
* (c) 2013 Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
class Twig_Node_Expression_Binary_StartsWith extends Twig_Node_Expression_Binary
{
public function compile(Twig_Compiler $compiler)
{
$compiler
->raw('(0 === strpos(')
->subcompile($this->getNode('left'))
->raw(', ')
->subcompile($this->getNode('right'))
->raw('))')
;
}
public function operator(Twig_Compiler $compiler)
{
return $compiler->raw('');
}
}

View File

@ -98,7 +98,10 @@ abstract class Twig_Node_Expression_Call extends Twig_Node_Expression
if (!is_int($name)) {
$named = true;
$name = $this->normalizeName($name);
} elseif ($named) {
throw new Twig_Error_Syntax(sprintf('Positional arguments cannot be used after named arguments for %s "%s".', $this->getAttribute('type'), $this->getAttribute('name')));
}
$parameters[$name] = $node;
}
@ -142,6 +145,10 @@ abstract class Twig_Node_Expression_Call extends Twig_Node_Expression
$name = $this->normalizeName($param->name);
if (array_key_exists($name, $parameters)) {
if (array_key_exists($pos, $parameters)) {
throw new Twig_Error_Syntax(sprintf('Argument "%s" is defined twice for %s "%s".', $name, $this->getAttribute('type'), $this->getAttribute('name')));
}
$arguments[] = $parameters[$name];
unset($parameters[$name]);
} elseif (array_key_exists($pos, $parameters)) {
@ -157,8 +164,8 @@ abstract class Twig_Node_Expression_Call extends Twig_Node_Expression
}
}
foreach (array_keys($parameters) as $name) {
throw new Twig_Error_Syntax(sprintf('Unknown argument "%s" for %s "%s".', $name, $this->getAttribute('type'), $this->getAttribute('name')));
if (!empty($parameters)) {
throw new Twig_Error_Syntax(sprintf('Unknown argument%s "%s" for %s "%s".', count($parameters) > 1 ? 's' : '' , implode('", "', array_keys($parameters)), $this->getAttribute('type'), $this->getAttribute('name')));
}
return $arguments;

View File

@ -32,10 +32,10 @@ class Twig_Node_Expression_GetAttr extends Twig_Node_Expression
$compiler->raw(', ')->subcompile($this->getNode('attribute'));
if (count($this->getNode('arguments')) || Twig_TemplateInterface::ANY_CALL !== $this->getAttribute('type') || $this->getAttribute('is_defined_test') || $this->getAttribute('ignore_strict_check')) {
if (count($this->getNode('arguments')) || Twig_Template::ANY_CALL !== $this->getAttribute('type') || $this->getAttribute('is_defined_test') || $this->getAttribute('ignore_strict_check')) {
$compiler->raw(', ')->subcompile($this->getNode('arguments'));
if (Twig_TemplateInterface::ANY_CALL !== $this->getAttribute('type') || $this->getAttribute('is_defined_test') || $this->getAttribute('ignore_strict_check')) {
if (Twig_Template::ANY_CALL !== $this->getAttribute('type') || $this->getAttribute('is_defined_test') || $this->getAttribute('ignore_strict_check')) {
$compiler->raw(', ')->repr($this->getAttribute('type'));
}

View File

@ -28,6 +28,17 @@ class Twig_Node_Expression_Test_Constant extends Twig_Node_Expression_Test
->raw('(')
->subcompile($this->getNode('node'))
->raw(' === constant(')
;
if ($this->getNode('arguments')->hasNode(1)) {
$compiler
->raw('get_class(')
->subcompile($this->getNode('arguments')->getNode(1))
->raw(')."::".')
;
}
$compiler
->subcompile($this->getNode('arguments')->getNode(0))
->raw('))')
;

View File

@ -34,7 +34,7 @@ class Twig_Node_Expression_Test_Defined extends Twig_Node_Expression_Test
$this->changeIgnoreStrictCheck($node);
} else {
throw new Twig_Error_Syntax('The "defined" test only works with simple variables', $this->getLine(), $compiler->getFilename());
throw new Twig_Error_Syntax('The "defined" test only works with simple variables', $this->getLine());
}
}

View File

@ -107,6 +107,6 @@ class Twig_Node_For extends Twig_Node
$compiler->write('unset($context[\'_seq\'], $context[\'_iterated\'], $context[\''.$this->getNode('key_target')->getAttribute('name').'\'], $context[\''.$this->getNode('value_target')->getAttribute('name').'\'], $context[\'_parent\'], $context[\'loop\']);'."\n");
// keep the values set in the inner context for variables defined in the outer context
$compiler->write("\$context = array_merge(\$_parent, array_intersect_key(\$context, \$_parent));\n");
$compiler->write("\$context = array_intersect_key(\$context, \$_parent) + \$_parent;\n");
}
}

View File

@ -30,7 +30,7 @@ class Twig_Node_If extends Twig_Node
public function compile(Twig_Compiler $compiler)
{
$compiler->addDebugInfo($this);
for ($i = 0; $i < count($this->getNode('tests')); $i += 2) {
for ($i = 0, $count = count($this->getNode('tests')); $i < $count; $i += 2) {
if ($i > 0) {
$compiler
->outdent()

View File

@ -12,7 +12,7 @@
/**
* Twig_NodeTraverser is a node traverser.
*
* It visits all nodes and their children and call the given visitor for each.
* It visits all nodes and their children and calls the given visitor for each.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
@ -24,8 +24,8 @@ class Twig_NodeTraverser
/**
* Constructor.
*
* @param Twig_Environment $env A Twig_Environment instance
* @param array $visitors An array of Twig_NodeVisitorInterface instances
* @param Twig_Environment $env A Twig_Environment instance
* @param Twig_NodeVisitorInterface[] $visitors An array of Twig_NodeVisitorInterface instances
*/
public function __construct(Twig_Environment $env, array $visitors = array())
{

View File

@ -39,7 +39,7 @@ class Twig_NodeVisitor_Optimizer implements Twig_NodeVisitorInterface
*/
public function __construct($optimizers = -1)
{
if (!is_int($optimizers) || $optimizers > 2) {
if (!is_int($optimizers) || $optimizers > (self::OPTIMIZE_FOR | self::OPTIMIZE_RAW_FILTER | self::OPTIMIZE_VAR_ACCESS)) {
throw new InvalidArgumentException(sprintf('Optimizer mode "%s" is not valid.', $optimizers));
}
@ -108,7 +108,7 @@ class Twig_NodeVisitor_Optimizer implements Twig_NodeVisitorInterface
return $node;
}
protected function optimizeVariables($node, $env)
protected function optimizeVariables(Twig_NodeInterface $node, Twig_Environment $env)
{
if ('Twig_Node_Expression_Name' === get_class($node) && $node->isSimple()) {
$this->prependedNodes[0][] = $node->getAttribute('name');
@ -129,7 +129,7 @@ class Twig_NodeVisitor_Optimizer implements Twig_NodeVisitorInterface
* @param Twig_NodeInterface $node A Node
* @param Twig_Environment $env The current Twig environment
*/
protected function optimizePrintNode($node, $env)
protected function optimizePrintNode(Twig_NodeInterface $node, Twig_Environment $env)
{
if (!$node instanceof Twig_Node_Print) {
return $node;
@ -153,7 +153,7 @@ class Twig_NodeVisitor_Optimizer implements Twig_NodeVisitorInterface
* @param Twig_NodeInterface $node A Node
* @param Twig_Environment $env The current Twig environment
*/
protected function optimizeRawFilter($node, $env)
protected function optimizeRawFilter(Twig_NodeInterface $node, Twig_Environment $env)
{
if ($node instanceof Twig_Node_Expression_Filter && 'raw' == $node->getNode('filter')->getAttribute('value')) {
return $node->getNode('node');
@ -168,7 +168,7 @@ class Twig_NodeVisitor_Optimizer implements Twig_NodeVisitorInterface
* @param Twig_NodeInterface $node A Node
* @param Twig_Environment $env The current Twig environment
*/
protected function enterOptimizeFor($node, $env)
protected function enterOptimizeFor(Twig_NodeInterface $node, Twig_Environment $env)
{
if ($node instanceof Twig_Node_For) {
// disable the loop variable by default
@ -217,7 +217,7 @@ class Twig_NodeVisitor_Optimizer implements Twig_NodeVisitorInterface
* @param Twig_NodeInterface $node A Node
* @param Twig_Environment $env The current Twig environment
*/
protected function leaveOptimizeFor($node, $env)
protected function leaveOptimizeFor(Twig_NodeInterface $node, Twig_Environment $env)
{
if ($node instanceof Twig_Node_For) {
array_shift($this->loops);

View File

@ -13,15 +13,21 @@ class Twig_NodeVisitor_SafeAnalysis implements Twig_NodeVisitorInterface
public function getSafe(Twig_NodeInterface $node)
{
$hash = spl_object_hash($node);
if (isset($this->data[$hash])) {
foreach ($this->data[$hash] as $bucket) {
if ($bucket['key'] === $node) {
return $bucket['value'];
}
}
if (!isset($this->data[$hash])) {
return;
}
return null;
foreach ($this->data[$hash] as $bucket) {
if ($bucket['key'] !== $node) {
continue;
}
if (in_array('html_attr', $bucket['value'])) {
$bucket['value'][] = 'html';
}
return $bucket['value'];
}
}
protected function setSafe(Twig_NodeInterface $node, array $safe)

View File

@ -49,7 +49,7 @@ class Twig_Parser implements Twig_ParserInterface
public function getVarName()
{
return sprintf('__internal_%s', hash('sha1', uniqid(mt_rand(), true), false));
return sprintf('__internal_%s', hash('sha256', uniqid(mt_rand(), true), false));
}
public function getFilename()
@ -58,11 +58,7 @@ class Twig_Parser implements Twig_ParserInterface
}
/**
* Converts a token stream to a node tree.
*
* @param Twig_TokenStream $stream A token stream instance
*
* @return Twig_Node_Module A node tree
* {@inheritdoc}
*/
public function parse(Twig_TokenStream $stream, $test = null, $dropNeedle = false)
{
@ -246,7 +242,7 @@ class Twig_Parser implements Twig_ParserInterface
return $this->blocks[$name];
}
public function setBlock($name, $value)
public function setBlock($name, Twig_Node_Block $value)
{
$this->blocks[$name] = new Twig_Node_Body(array($value), array(), $value->getLine());
}
@ -384,7 +380,7 @@ class Twig_Parser implements Twig_ParserInterface
}
foreach ($node as $k => $n) {
if (null !== $n && null === $n = $this->filterBodyNodes($n)) {
if (null !== $n && null === $this->filterBodyNodes($n)) {
$node->removeNode($k);
}
}

View File

@ -23,6 +23,8 @@ interface Twig_ParserInterface
* @param Twig_TokenStream $stream A token stream instance
*
* @return Twig_Node_Module A node tree
*
* @throws Twig_Error_Syntax When the token stream is syntactically or semantically wrong
*/
public function parse(Twig_TokenStream $stream);
}

View File

@ -80,8 +80,6 @@ class Twig_SimpleFilter
if (null !== $this->options['is_safe_callback']) {
return call_user_func($this->options['is_safe_callback'], $filterArgs);
}
return null;
}
public function getPreservesSafety()

View File

@ -127,12 +127,24 @@ abstract class Twig_Template implements Twig_TemplateInterface
{
$name = (string) $name;
$template = null;
if (isset($blocks[$name])) {
$b = $blocks;
unset($b[$name]);
call_user_func($blocks[$name], $context, $b);
$template = $blocks[$name][0];
$block = $blocks[$name][1];
unset($blocks[$name]);
} elseif (isset($this->blocks[$name])) {
call_user_func($this->blocks[$name], $context, $blocks);
$template = $this->blocks[$name][0];
$block = $this->blocks[$name][1];
}
if (null !== $template) {
try {
$template->$block($context, $blocks);
} catch (Twig_Error $e) {
throw $e;
} catch (Exception $e) {
throw new Twig_Error_Runtime(sprintf('An exception has been thrown during the rendering of a template ("%s").', $e->getMessage()), -1, $template->getTemplateName(), $e);
}
} elseif (false !== $parent = $this->getParent($context)) {
$parent->displayBlock($name, $context, array_merge($this->blocks, $blocks));
}
@ -276,7 +288,7 @@ abstract class Twig_Template implements Twig_TemplateInterface
throw $e;
} catch (Exception $e) {
throw new Twig_Error_Runtime(sprintf('An exception has been thrown during the rendering of a template ("%s").', $e->getMessage()), -1, null, $e);
throw new Twig_Error_Runtime(sprintf('An exception has been thrown during the rendering of a template ("%s").', $e->getMessage()), -1, $this->getTemplateName(), $e);
}
}
@ -326,7 +338,7 @@ abstract class Twig_Template implements Twig_TemplateInterface
* @param mixed $object The object or array from where to get the item
* @param mixed $item The item to get from the array or object
* @param array $arguments An array of arguments to pass if the item is an object method
* @param string $type The type of attribute (@see Twig_TemplateInterface)
* @param string $type The type of attribute (@see Twig_Template constants)
* @param Boolean $isDefinedTest Whether this is only a defined check
* @param Boolean $ignoreStrictCheck Whether to ignore the strict attribute check or not
*
@ -334,23 +346,23 @@ abstract class Twig_Template implements Twig_TemplateInterface
*
* @throws Twig_Error_Runtime if the attribute does not exist and Twig is running in strict mode and $isDefinedTest is false
*/
protected function getAttribute($object, $item, array $arguments = array(), $type = Twig_TemplateInterface::ANY_CALL, $isDefinedTest = false, $ignoreStrictCheck = false)
protected function getAttribute($object, $item, array $arguments = array(), $type = Twig_Template::ANY_CALL, $isDefinedTest = false, $ignoreStrictCheck = false)
{
$item = ctype_digit((string) $item) ? (int) $item : (string) $item;
// array
if (Twig_TemplateInterface::METHOD_CALL !== $type) {
if ((is_array($object) && array_key_exists($item, $object))
|| ($object instanceof ArrayAccess && isset($object[$item]))
if (Twig_Template::METHOD_CALL !== $type) {
$arrayItem = is_bool($item) || is_float($item) ? (int) $item : $item;
if ((is_array($object) && array_key_exists($arrayItem, $object))
|| ($object instanceof ArrayAccess && isset($object[$arrayItem]))
) {
if ($isDefinedTest) {
return true;
}
return $object[$item];
return $object[$arrayItem];
}
if (Twig_TemplateInterface::ARRAY_CALL === $type) {
if (Twig_Template::ARRAY_CALL === $type || !is_object($object)) {
if ($isDefinedTest) {
return false;
}
@ -360,11 +372,13 @@ abstract class Twig_Template implements Twig_TemplateInterface
}
if (is_object($object)) {
throw new Twig_Error_Runtime(sprintf('Key "%s" in object (with ArrayAccess) of type "%s" does not exist', $item, get_class($object)), -1, $this->getTemplateName());
throw new Twig_Error_Runtime(sprintf('Key "%s" in object (with ArrayAccess) of type "%s" does not exist', $arrayItem, get_class($object)), -1, $this->getTemplateName());
} elseif (is_array($object)) {
throw new Twig_Error_Runtime(sprintf('Key "%s" for array with keys "%s" does not exist', $item, implode(', ', array_keys($object))), -1, $this->getTemplateName());
throw new Twig_Error_Runtime(sprintf('Key "%s" for array with keys "%s" does not exist', $arrayItem, implode(', ', array_keys($object))), -1, $this->getTemplateName());
} elseif (Twig_Template::ARRAY_CALL === $type) {
throw new Twig_Error_Runtime(sprintf('Impossible to access a key ("%s") on a %s variable ("%s")', $item, gettype($object), $object), -1, $this->getTemplateName());
} else {
throw new Twig_Error_Runtime(sprintf('Impossible to access a key ("%s") on a "%s" variable', $item, gettype($object)), -1, $this->getTemplateName());
throw new Twig_Error_Runtime(sprintf('Impossible to access an attribute ("%s") on a %s variable ("%s")', $item, gettype($object), $object), -1, $this->getTemplateName());
}
}
}
@ -378,14 +392,14 @@ abstract class Twig_Template implements Twig_TemplateInterface
return null;
}
throw new Twig_Error_Runtime(sprintf('Item "%s" for "%s" does not exist', $item, is_array($object) ? 'Array' : $object), -1, $this->getTemplateName());
throw new Twig_Error_Runtime(sprintf('Impossible to invoke a method ("%s") on a %s variable ("%s")', $item, gettype($object), $object), -1, $this->getTemplateName());
}
$class = get_class($object);
// object property
if (Twig_TemplateInterface::METHOD_CALL !== $type) {
if (isset($object->$item) || array_key_exists($item, $object)) {
if (Twig_Template::METHOD_CALL !== $type) {
if (isset($object->$item) || array_key_exists((string) $item, $object)) {
if ($isDefinedTest) {
return true;
}
@ -403,15 +417,17 @@ abstract class Twig_Template implements Twig_TemplateInterface
self::$cache[$class]['methods'] = array_change_key_case(array_flip(get_class_methods($object)));
}
$call = false;
$lcItem = strtolower($item);
if (isset(self::$cache[$class]['methods'][$lcItem])) {
$method = $item;
$method = (string) $item;
} elseif (isset(self::$cache[$class]['methods']['get'.$lcItem])) {
$method = 'get'.$item;
} elseif (isset(self::$cache[$class]['methods']['is'.$lcItem])) {
$method = 'is'.$item;
} elseif (isset(self::$cache[$class]['methods']['__call'])) {
$method = $item;
$method = (string) $item;
$call = true;
} else {
if ($isDefinedTest) {
return false;
@ -432,7 +448,16 @@ abstract class Twig_Template implements Twig_TemplateInterface
$this->env->getExtension('sandbox')->checkMethodAllowed($object, $method);
}
$ret = call_user_func_array(array($object, $method), $arguments);
// Some objects throw exceptions when they have __call, and the method we try
// to call is not supported. If ignoreStrictCheck is true, we should return null.
try {
$ret = call_user_func_array(array($object, $method), $arguments);
} catch (BadMethodCallException $e) {
if ($call && ($ignoreStrictCheck || !$this->env->isStrictVariables())) {
return null;
}
throw $e;
}
// useful when calling a template method from a template
// this is not supported but unfortunately heavily used in the Symfony profiler

View File

@ -121,11 +121,10 @@ class Twig_Token
*
* @param integer $type The type as an integer
* @param Boolean $short Whether to return a short representation or not
* @param integer $line The code line
*
* @return string The string representation
*/
public static function typeToString($type, $short = false, $line = -1)
public static function typeToString($type, $short = false)
{
switch ($type) {
case self::EOF_TYPE:
@ -178,11 +177,10 @@ class Twig_Token
* Returns the english representation of a given type.
*
* @param integer $type The type as an integer
* @param integer $line The code line
*
* @return string The string representation
*/
public static function typeToEnglish($type, $line = -1)
public static function typeToEnglish($type)
{
switch ($type) {
case self::EOF_TYPE:

Some files were not shown because too many files have changed in this diff Show More