Modified some paths to be more generic and not require rewrite. Some instances of phase names have been changed, others are still in process.
1575 lines
52 KiB
PHP
1575 lines
52 KiB
PHP
<?php
|
|
|
|
if (!defined('ENT_SUBSTITUTE')) {
|
|
// use 0 as hhvm does not support several flags yet
|
|
define('ENT_SUBSTITUTE', 0);
|
|
}
|
|
|
|
/*
|
|
* This file is part of Twig.
|
|
*
|
|
* (c) 2009 Fabien Potencier
|
|
*
|
|
* For the full copyright and license information, please view the LICENSE
|
|
* file that was distributed with this source code.
|
|
*/
|
|
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.
|
|
*
|
|
* @param string $format The default date format string
|
|
* @param string $dateIntervalFormat The default date interval format string
|
|
*/
|
|
public function setDateFormat($format = null, $dateIntervalFormat = null)
|
|
{
|
|
if (null !== $format) {
|
|
$this->dateFormats[0] = $format;
|
|
}
|
|
|
|
if (null !== $dateIntervalFormat) {
|
|
$this->dateFormats[1] = $dateIntervalFormat;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets the default format to be used by the date filter.
|
|
*
|
|
* @return array The default date format string and the default date interval format string
|
|
*/
|
|
public function getDateFormat()
|
|
{
|
|
return $this->dateFormats;
|
|
}
|
|
|
|
/**
|
|
* Sets the default timezone to be used by the date filter.
|
|
*
|
|
* @param DateTimeZone|string $timezone The default timezone string or a DateTimeZone object
|
|
*/
|
|
public function setTimezone($timezone)
|
|
{
|
|
$this->timezone = $timezone instanceof DateTimeZone ? $timezone : new DateTimeZone($timezone);
|
|
}
|
|
|
|
/**
|
|
* Gets the default timezone to be used by the date filter.
|
|
*
|
|
* @return DateTimeZone The default timezone currently in use
|
|
*/
|
|
public function getTimezone()
|
|
{
|
|
if (null === $this->timezone) {
|
|
$this->timezone = new DateTimeZone(date_default_timezone_get());
|
|
}
|
|
|
|
return $this->timezone;
|
|
}
|
|
|
|
/**
|
|
* Sets the default format to be used by the number_format filter.
|
|
*
|
|
* @param int $decimal The number of decimal places to use.
|
|
* @param string $decimalPoint The character(s) to use for the decimal point.
|
|
* @param string $thousandSep The character(s) to use for the thousands separator.
|
|
*/
|
|
public function setNumberFormat($decimal, $decimalPoint, $thousandSep)
|
|
{
|
|
$this->numberFormat = array($decimal, $decimalPoint, $thousandSep);
|
|
}
|
|
|
|
/**
|
|
* Get the default format used by the number_format filter.
|
|
*
|
|
* @return array The arguments for number_format()
|
|
*/
|
|
public function getNumberFormat()
|
|
{
|
|
return $this->numberFormat;
|
|
}
|
|
|
|
/**
|
|
* Returns the token parser instance to add to the existing list.
|
|
*
|
|
* @return Twig_TokenParser[] An array of Twig_TokenParser instances
|
|
*/
|
|
public function getTokenParsers()
|
|
{
|
|
return array(
|
|
new Twig_TokenParser_For(),
|
|
new Twig_TokenParser_If(),
|
|
new Twig_TokenParser_Extends(),
|
|
new Twig_TokenParser_Include(),
|
|
new Twig_TokenParser_Block(),
|
|
new Twig_TokenParser_Use(),
|
|
new Twig_TokenParser_Filter(),
|
|
new Twig_TokenParser_Macro(),
|
|
new Twig_TokenParser_Import(),
|
|
new Twig_TokenParser_From(),
|
|
new Twig_TokenParser_Set(),
|
|
new Twig_TokenParser_Spaceless(),
|
|
new Twig_TokenParser_Flush(),
|
|
new Twig_TokenParser_Do(),
|
|
new Twig_TokenParser_Embed(),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Returns a list of filters to add to the existing list.
|
|
*
|
|
* @return array An array of filters
|
|
*/
|
|
public function getFilters()
|
|
{
|
|
$filters = array(
|
|
// formatting filters
|
|
new Twig_SimpleFilter('date', 'twig_date_format_filter', array('needs_environment' => true)),
|
|
new Twig_SimpleFilter('date_modify', 'twig_date_modify_filter', array('needs_environment' => true)),
|
|
new Twig_SimpleFilter('format', 'sprintf'),
|
|
new Twig_SimpleFilter('replace', 'twig_replace_filter'),
|
|
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'),
|
|
new Twig_SimpleFilter('json_encode', 'twig_jsonencode_filter'),
|
|
new Twig_SimpleFilter('convert_encoding', 'twig_convert_encoding'),
|
|
|
|
// string filters
|
|
new Twig_SimpleFilter('title', 'twig_title_string_filter', array('needs_environment' => true)),
|
|
new Twig_SimpleFilter('capitalize', 'twig_capitalize_string_filter', array('needs_environment' => true)),
|
|
new Twig_SimpleFilter('upper', 'strtoupper'),
|
|
new Twig_SimpleFilter('lower', 'strtolower'),
|
|
new Twig_SimpleFilter('striptags', 'strip_tags'),
|
|
new Twig_SimpleFilter('trim', 'trim'),
|
|
new Twig_SimpleFilter('nl2br', 'nl2br', array('pre_escape' => 'html', 'is_safe' => array('html'))),
|
|
|
|
// array helpers
|
|
new Twig_SimpleFilter('join', 'twig_join_filter'),
|
|
new Twig_SimpleFilter('split', 'twig_split_filter', array('needs_environment' => true)),
|
|
new Twig_SimpleFilter('sort', 'twig_sort_filter'),
|
|
new Twig_SimpleFilter('merge', 'twig_array_merge'),
|
|
new Twig_SimpleFilter('batch', 'twig_array_batch'),
|
|
|
|
// string/array filters
|
|
new Twig_SimpleFilter('reverse', 'twig_reverse_filter', array('needs_environment' => true)),
|
|
new Twig_SimpleFilter('length', 'twig_length_filter', array('needs_environment' => true)),
|
|
new Twig_SimpleFilter('slice', 'twig_slice', array('needs_environment' => true)),
|
|
new Twig_SimpleFilter('first', 'twig_first', array('needs_environment' => true)),
|
|
new Twig_SimpleFilter('last', 'twig_last', array('needs_environment' => true)),
|
|
|
|
// iteration and runtime
|
|
new Twig_SimpleFilter('default', '_twig_default_filter', array('node_class' => 'Twig_Node_Expression_Filter_Default')),
|
|
new Twig_SimpleFilter('keys', 'twig_get_array_keys_filter'),
|
|
|
|
// escaping
|
|
new Twig_SimpleFilter('escape', 'twig_escape_filter', array('needs_environment' => true, 'is_safe_callback' => 'twig_escape_filter_is_safe')),
|
|
new Twig_SimpleFilter('e', 'twig_escape_filter', array('needs_environment' => true, 'is_safe_callback' => 'twig_escape_filter_is_safe')),
|
|
);
|
|
|
|
if (function_exists('mb_get_info')) {
|
|
$filters[] = new Twig_SimpleFilter('upper', 'twig_upper_filter', array('needs_environment' => true));
|
|
$filters[] = new Twig_SimpleFilter('lower', 'twig_lower_filter', array('needs_environment' => true));
|
|
}
|
|
|
|
return $filters;
|
|
}
|
|
|
|
/**
|
|
* Returns a list of global functions to add to the existing list.
|
|
*
|
|
* @return array An array of global functions
|
|
*/
|
|
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, 'is_safe' => array('all'))),
|
|
new Twig_SimpleFunction('source', 'twig_source', array('needs_environment' => true, 'is_safe' => array('all'))),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Returns a list of tests to add to the existing list.
|
|
*
|
|
* @return array An array of tests
|
|
*/
|
|
public function getTests()
|
|
{
|
|
return array(
|
|
new Twig_SimpleTest('even', null, array('node_class' => 'Twig_Node_Expression_Test_Even')),
|
|
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', 'deprecated' => true, 'alternative' => 'same as')),
|
|
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', 'deprecated' => true, 'alternative' => 'divisible by')),
|
|
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'),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Returns a list of operators to add to the existing list.
|
|
*
|
|
* @return array An array of operators
|
|
*/
|
|
public function getOperators()
|
|
{
|
|
return array(
|
|
array(
|
|
'not' => array('precedence' => 50, 'class' => 'Twig_Node_Expression_Unary_Not'),
|
|
'-' => array('precedence' => 500, 'class' => 'Twig_Node_Expression_Unary_Neg'),
|
|
'+' => 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),
|
|
'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, Twig_NodeInterface $node)
|
|
{
|
|
return new Twig_Node_Expression_Unary_Not($this->parseTestExpression($parser, $node), $parser->getCurrentToken()->getLine());
|
|
}
|
|
|
|
public function parseTestExpression(Twig_Parser $parser, Twig_NodeInterface $node)
|
|
{
|
|
$stream = $parser->getStream();
|
|
list($name, $test) = $this->getTest($parser, $node->getLine());
|
|
|
|
if ($test instanceof Twig_SimpleTest && $test->isDeprecated()) {
|
|
$message = sprintf('Twig Test "%s" is deprecated', $name);
|
|
if ($test->getAlternative()) {
|
|
$message .= sprintf('. Use "%s" instead', $test->getAlternative());
|
|
}
|
|
$message .= sprintf(' in %s at line %d.', $stream->getFilename(), $stream->getCurrent()->getLine());
|
|
|
|
@trigger_error($message, E_USER_DEPRECATED);
|
|
}
|
|
|
|
$class = $this->getTestNodeClass($parser, $test);
|
|
$arguments = null;
|
|
if ($stream->test(Twig_Token::PUNCTUATION_TYPE, '(')) {
|
|
$arguments = $parser->getExpressionParser()->parseArguments(true);
|
|
}
|
|
|
|
return new $class($node, $name, $arguments, $parser->getCurrentToken()->getLine());
|
|
}
|
|
|
|
protected function getTest(Twig_Parser $parser, $line)
|
|
{
|
|
$stream = $parser->getStream();
|
|
$name = $stream->expect(Twig_Token::NAME_TYPE)->getValue();
|
|
$env = $parser->getEnvironment();
|
|
|
|
if ($test = $env->getTest($name)) {
|
|
return array($name, $test);
|
|
}
|
|
|
|
if ($stream->test(Twig_Token::NAME_TYPE)) {
|
|
// try 2-words tests
|
|
$name = $name.' '.$parser->getCurrentToken()->getValue();
|
|
|
|
if ($test = $env->getTest($name)) {
|
|
$parser->getStream()->next();
|
|
|
|
return array($name, $test);
|
|
}
|
|
}
|
|
|
|
$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));
|
|
}
|
|
|
|
throw new Twig_Error_Syntax($message, $line, $parser->getFilename());
|
|
}
|
|
|
|
protected function getTestNodeClass(Twig_Parser $parser, $test)
|
|
{
|
|
if ($test instanceof Twig_SimpleTest) {
|
|
return $test->getNodeClass();
|
|
}
|
|
|
|
return $test instanceof Twig_Test_Node ? $test->getClass() : 'Twig_Node_Expression_Test';
|
|
}
|
|
|
|
/**
|
|
* Returns the name of the extension.
|
|
*
|
|
* @return string The extension name
|
|
*/
|
|
public function getName()
|
|
{
|
|
return 'core';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Cycles over a value.
|
|
*
|
|
* @param ArrayAccess|array $values An array or an ArrayAccess instance
|
|
* @param int $position The cycle position
|
|
*
|
|
* @return string The next value in the cycle
|
|
*/
|
|
function twig_cycle($values, $position)
|
|
{
|
|
if (!is_array($values) && !$values instanceof ArrayAccess) {
|
|
return $values;
|
|
}
|
|
|
|
return $values[$position % count($values)];
|
|
}
|
|
|
|
/**
|
|
* Returns a random value depending on the supplied parameter type:
|
|
* - a random item from a Traversable or array
|
|
* - a random character from a string
|
|
* - a random integer between 0 and the integer parameter.
|
|
*
|
|
* @param Twig_Environment $env A Twig_Environment instance
|
|
* @param Traversable|array|int|string $values The values to pick a random item from
|
|
*
|
|
* @throws Twig_Error_Runtime When $values is an empty array (does not apply to an empty string which is returned as is).
|
|
*
|
|
* @return mixed A random value from the given sequence
|
|
*/
|
|
function twig_random(Twig_Environment $env, $values = null)
|
|
{
|
|
if (null === $values) {
|
|
return mt_rand();
|
|
}
|
|
|
|
if (is_int($values) || is_float($values)) {
|
|
return $values < 0 ? mt_rand($values, 0) : mt_rand(0, $values);
|
|
}
|
|
|
|
if ($values instanceof Traversable) {
|
|
$values = iterator_to_array($values);
|
|
} elseif (is_string($values)) {
|
|
if ('' === $values) {
|
|
return '';
|
|
}
|
|
if (null !== $charset = $env->getCharset()) {
|
|
if ('UTF-8' != $charset) {
|
|
$values = twig_convert_encoding($values, 'UTF-8', $charset);
|
|
}
|
|
|
|
// unicode version of str_split()
|
|
// split at all positions, but not after the start and not before the end
|
|
$values = preg_split('/(?<!^)(?!$)/u', $values);
|
|
|
|
if ('UTF-8' != $charset) {
|
|
foreach ($values as $i => $value) {
|
|
$values[$i] = twig_convert_encoding($value, $charset, 'UTF-8');
|
|
}
|
|
}
|
|
} else {
|
|
return $values[mt_rand(0, strlen($values) - 1)];
|
|
}
|
|
}
|
|
|
|
if (!is_array($values)) {
|
|
return $values;
|
|
}
|
|
|
|
if (0 === count($values)) {
|
|
throw new Twig_Error_Runtime('The random function cannot pick from an empty array.');
|
|
}
|
|
|
|
return $values[array_rand($values, 1)];
|
|
}
|
|
|
|
/**
|
|
* Converts a date to the given format.
|
|
*
|
|
* <pre>
|
|
* {{ post.published_at|date("m/d/Y") }}
|
|
* </pre>
|
|
*
|
|
* @param Twig_Environment $env A Twig_Environment instance
|
|
* @param DateTime|DateTimeInterface|DateInterval|string $date A date
|
|
* @param string|null $format The target format, null to use the default
|
|
* @param DateTimeZone|string|null|false $timezone The target timezone, null to use the default, false to leave unchanged
|
|
*
|
|
* @return string The formatted date
|
|
*/
|
|
function twig_date_format_filter(Twig_Environment $env, $date, $format = null, $timezone = null)
|
|
{
|
|
if (null === $format) {
|
|
$formats = $env->getExtension('core')->getDateFormat();
|
|
$format = $date instanceof DateInterval ? $formats[1] : $formats[0];
|
|
}
|
|
|
|
if ($date instanceof DateInterval) {
|
|
return $date->format($format);
|
|
}
|
|
|
|
return twig_date_converter($env, $date, $timezone)->format($format);
|
|
}
|
|
|
|
/**
|
|
* Returns a new date object modified.
|
|
*
|
|
* <pre>
|
|
* {{ post.published_at|date_modify("-1day")|date("m/d/Y") }}
|
|
* </pre>
|
|
*
|
|
* @param Twig_Environment $env A Twig_Environment instance
|
|
* @param DateTime|string $date A date
|
|
* @param string $modifier A modifier string
|
|
*
|
|
* @return DateTime A new date object
|
|
*/
|
|
function twig_date_modify_filter(Twig_Environment $env, $date, $modifier)
|
|
{
|
|
$date = twig_date_converter($env, $date, false);
|
|
$resultDate = $date->modify($modifier);
|
|
|
|
// This is a hack to ensure PHP 5.2 support and support for DateTimeImmutable
|
|
// DateTime::modify does not return the modified DateTime object < 5.3.0
|
|
// and DateTimeImmutable does not modify $date.
|
|
return null === $resultDate ? $date : $resultDate;
|
|
}
|
|
|
|
/**
|
|
* Converts an input to a DateTime instance.
|
|
*
|
|
* <pre>
|
|
* {% if date(user.created_at) < date('+2days') %}
|
|
* {# do something #}
|
|
* {% endif %}
|
|
* </pre>
|
|
*
|
|
* @param Twig_Environment $env A Twig_Environment instance
|
|
* @param DateTime|DateTimeInterface|string|null $date A date
|
|
* @param DateTimeZone|string|null|false $timezone The target timezone, null to use the default, false to leave unchanged
|
|
*
|
|
* @return DateTime A DateTime instance
|
|
*/
|
|
function twig_date_converter(Twig_Environment $env, $date = null, $timezone = null)
|
|
{
|
|
// determine the timezone
|
|
if (false !== $timezone) {
|
|
if (null === $timezone) {
|
|
$timezone = $env->getExtension('core')->getTimezone();
|
|
} elseif (!$timezone instanceof DateTimeZone) {
|
|
$timezone = new DateTimeZone($timezone);
|
|
}
|
|
}
|
|
|
|
// immutable dates
|
|
if ($date instanceof DateTimeImmutable) {
|
|
return false !== $timezone ? $date->setTimezone($timezone) : $date;
|
|
}
|
|
|
|
if ($date instanceof DateTime || $date instanceof DateTimeInterface) {
|
|
$date = clone $date;
|
|
if (false !== $timezone) {
|
|
$date->setTimezone($timezone);
|
|
}
|
|
|
|
return $date;
|
|
}
|
|
|
|
if (null === $date || 'now' === $date) {
|
|
return new DateTime($date, false !== $timezone ? $timezone : $env->getExtension('core')->getTimezone());
|
|
}
|
|
|
|
$asString = (string) $date;
|
|
if (ctype_digit($asString) || (!empty($asString) && '-' === $asString[0] && ctype_digit(substr($asString, 1)))) {
|
|
$date = new DateTime('@'.$date);
|
|
} else {
|
|
$date = new DateTime($date, $env->getExtension('core')->getTimezone());
|
|
}
|
|
|
|
if (false !== $timezone) {
|
|
$date->setTimezone($timezone);
|
|
}
|
|
|
|
return $date;
|
|
}
|
|
|
|
/**
|
|
* Replaces strings within a string.
|
|
*
|
|
* @param string $str String to replace in
|
|
* @param array|Traversable $from Replace values
|
|
* @param string|null $to Replace to, deprecated (@see http://php.net/manual/en/function.strtr.php)
|
|
*
|
|
* @return string
|
|
*/
|
|
function twig_replace_filter($str, $from, $to = null)
|
|
{
|
|
if ($from instanceof Traversable) {
|
|
$from = iterator_to_array($from);
|
|
} elseif (is_string($from) && is_string($to)) {
|
|
@trigger_error('Using "replace" with character by character replacement is deprecated and will be removed in Twig 2.0', E_USER_DEPRECATED);
|
|
|
|
return strtr($str, $from, $to);
|
|
} elseif (!is_array($from)) {
|
|
throw new Twig_Error_Runtime(sprintf('The "replace" filter expects an array or "Traversable" as replace values, got "%s".',is_object($from) ? get_class($from) : gettype($from)));
|
|
}
|
|
|
|
return strtr($str, $from);
|
|
}
|
|
|
|
/**
|
|
* Rounds a number.
|
|
*
|
|
* @param int|float $value The value to round
|
|
* @param int|float $precision The rounding precision
|
|
* @param string $method The method to use for rounding
|
|
*
|
|
* @return int|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.
|
|
*
|
|
* All of the formatting options can be left null, in that case the defaults will
|
|
* be used. Supplying any of the parameters will override the defaults set in the
|
|
* environment object.
|
|
*
|
|
* @param Twig_Environment $env A Twig_Environment instance
|
|
* @param mixed $number A float/int/string of the number to format
|
|
* @param int $decimal The number of decimal points to display.
|
|
* @param string $decimalPoint The character(s) to use for the decimal point.
|
|
* @param string $thousandSep The character(s) to use for the thousands separator.
|
|
*
|
|
* @return string The formatted number
|
|
*/
|
|
function twig_number_format_filter(Twig_Environment $env, $number, $decimal = null, $decimalPoint = null, $thousandSep = null)
|
|
{
|
|
$defaults = $env->getExtension('core')->getNumberFormat();
|
|
if (null === $decimal) {
|
|
$decimal = $defaults[0];
|
|
}
|
|
|
|
if (null === $decimalPoint) {
|
|
$decimalPoint = $defaults[1];
|
|
}
|
|
|
|
if (null === $thousandSep) {
|
|
$thousandSep = $defaults[2];
|
|
}
|
|
|
|
return number_format((float) $number, $decimal, $decimalPoint, $thousandSep);
|
|
}
|
|
|
|
/**
|
|
* URL encodes (RFC 3986) 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
|
|
*
|
|
* @return string The URL encoded value
|
|
*/
|
|
function twig_urlencode_filter($url)
|
|
{
|
|
if (is_array($url)) {
|
|
if (defined('PHP_QUERY_RFC3986')) {
|
|
return http_build_query($url, '', '&', PHP_QUERY_RFC3986);
|
|
}
|
|
|
|
return http_build_query($url, '', '&');
|
|
}
|
|
|
|
return rawurlencode($url);
|
|
}
|
|
|
|
if (PHP_VERSION_ID < 50300) {
|
|
/**
|
|
* JSON encodes a variable.
|
|
*
|
|
* @param mixed $value The value to encode.
|
|
* @param int $options Not used on PHP 5.2.x
|
|
*
|
|
* @return mixed The JSON encoded value
|
|
*/
|
|
function twig_jsonencode_filter($value, $options = 0)
|
|
{
|
|
if ($value instanceof Twig_Markup) {
|
|
$value = (string) $value;
|
|
} elseif (is_array($value)) {
|
|
array_walk_recursive($value, '_twig_markup2string');
|
|
}
|
|
|
|
return json_encode($value);
|
|
}
|
|
} else {
|
|
/**
|
|
* JSON encodes a variable.
|
|
*
|
|
* @param mixed $value The value to encode.
|
|
* @param int $options Bitmask consisting of JSON_HEX_QUOT, JSON_HEX_TAG, JSON_HEX_AMP, JSON_HEX_APOS, JSON_NUMERIC_CHECK, JSON_PRETTY_PRINT, JSON_UNESCAPED_SLASHES, JSON_FORCE_OBJECT
|
|
*
|
|
* @return mixed The JSON encoded value
|
|
*/
|
|
function twig_jsonencode_filter($value, $options = 0)
|
|
{
|
|
if ($value instanceof Twig_Markup) {
|
|
$value = (string) $value;
|
|
} elseif (is_array($value)) {
|
|
array_walk_recursive($value, '_twig_markup2string');
|
|
}
|
|
|
|
return json_encode($value, $options);
|
|
}
|
|
}
|
|
|
|
function _twig_markup2string(&$value)
|
|
{
|
|
if ($value instanceof Twig_Markup) {
|
|
$value = (string) $value;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Merges an array with another one.
|
|
*
|
|
* <pre>
|
|
* {% set items = { 'apple': 'fruit', 'orange': 'fruit' } %}
|
|
*
|
|
* {% set items = items|merge({ 'peugeot': 'car' }) %}
|
|
*
|
|
* {# items now contains { 'apple': 'fruit', 'orange': 'fruit', 'peugeot': 'car' } #}
|
|
* </pre>
|
|
*
|
|
* @param array|Traversable $arr1 An array
|
|
* @param array|Traversable $arr2 An array
|
|
*
|
|
* @return array The merged array
|
|
*/
|
|
function twig_array_merge($arr1, $arr2)
|
|
{
|
|
if ($arr1 instanceof Traversable) {
|
|
$arr1 = iterator_to_array($arr1);
|
|
} elseif (!is_array($arr1)) {
|
|
throw new Twig_Error_Runtime(sprintf('The merge filter only works with arrays or "Traversable", got "%s" as first argument.', gettype($arr1)));
|
|
}
|
|
|
|
if ($arr2 instanceof Traversable) {
|
|
$arr2 = iterator_to_array($arr2);
|
|
} elseif (!is_array($arr2)) {
|
|
throw new Twig_Error_Runtime(sprintf('The merge filter only works with arrays or "Traversable", got "%s" as second argument.', gettype($arr2)));
|
|
}
|
|
|
|
return array_merge($arr1, $arr2);
|
|
}
|
|
|
|
/**
|
|
* Slices a variable.
|
|
*
|
|
* @param Twig_Environment $env A Twig_Environment instance
|
|
* @param mixed $item A variable
|
|
* @param int $start Start of the slice
|
|
* @param int $length Size of the slice
|
|
* @param bool $preserveKeys Whether to preserve key or not (when the input is an array)
|
|
*
|
|
* @return mixed The sliced variable
|
|
*/
|
|
function twig_slice(Twig_Environment $env, $item, $start, $length = null, $preserveKeys = false)
|
|
{
|
|
if ($item instanceof Traversable) {
|
|
if ($item instanceof IteratorAggregate) {
|
|
$item = $item->getIterator();
|
|
}
|
|
|
|
if ($start >= 0 && $length >= 0 && $item instanceof Iterator) {
|
|
try {
|
|
return iterator_to_array(new LimitIterator($item, $start, $length === null ? -1 : $length), $preserveKeys);
|
|
} catch (OutOfBoundsException $exception) {
|
|
return array();
|
|
}
|
|
}
|
|
|
|
$item = iterator_to_array($item, $preserveKeys);
|
|
}
|
|
|
|
if (is_array($item)) {
|
|
return array_slice($item, $start, $length, $preserveKeys);
|
|
}
|
|
|
|
$item = (string) $item;
|
|
|
|
if (function_exists('mb_get_info') && null !== $charset = $env->getCharset()) {
|
|
return (string) mb_substr($item, $start, null === $length ? mb_strlen($item, $charset) - $start : $length, $charset);
|
|
}
|
|
|
|
return (string) (null === $length ? substr($item, $start) : substr($item, $start, $length));
|
|
}
|
|
|
|
/**
|
|
* Returns the first element of the item.
|
|
*
|
|
* @param Twig_Environment $env A Twig_Environment instance
|
|
* @param mixed $item A variable
|
|
*
|
|
* @return mixed The first element of the item
|
|
*/
|
|
function twig_first(Twig_Environment $env, $item)
|
|
{
|
|
$elements = twig_slice($env, $item, 0, 1, false);
|
|
|
|
return is_string($elements) ? $elements : current($elements);
|
|
}
|
|
|
|
/**
|
|
* Returns the last element of the item.
|
|
*
|
|
* @param Twig_Environment $env A Twig_Environment instance
|
|
* @param mixed $item A variable
|
|
*
|
|
* @return mixed The last element of the item
|
|
*/
|
|
function twig_last(Twig_Environment $env, $item)
|
|
{
|
|
$elements = twig_slice($env, $item, -1, 1, false);
|
|
|
|
return is_string($elements) ? $elements : current($elements);
|
|
}
|
|
|
|
/**
|
|
* Joins the values to a string.
|
|
*
|
|
* The separator between elements is an empty string per default, you can define it with the optional parameter.
|
|
*
|
|
* <pre>
|
|
* {{ [1, 2, 3]|join('|') }}
|
|
* {# returns 1|2|3 #}
|
|
*
|
|
* {{ [1, 2, 3]|join }}
|
|
* {# returns 123 #}
|
|
* </pre>
|
|
*
|
|
* @param array $value An array
|
|
* @param string $glue The separator
|
|
*
|
|
* @return string The concatenated string
|
|
*/
|
|
function twig_join_filter($value, $glue = '')
|
|
{
|
|
if ($value instanceof Traversable) {
|
|
$value = iterator_to_array($value, false);
|
|
}
|
|
|
|
return implode($glue, (array) $value);
|
|
}
|
|
|
|
/**
|
|
* Splits the string into an array.
|
|
*
|
|
* <pre>
|
|
* {{ "one,two,three"|split(',') }}
|
|
* {# returns [one, two, three] #}
|
|
*
|
|
* {{ "one,two,three,four,five"|split(',', 3) }}
|
|
* {# returns [one, two, "three,four,five"] #}
|
|
*
|
|
* {{ "123"|split('') }}
|
|
* {# returns [1, 2, 3] #}
|
|
*
|
|
* {{ "aabbcc"|split('', 2) }}
|
|
* {# returns [aa, bb, cc] #}
|
|
* </pre>
|
|
*
|
|
* @param string $value A string
|
|
* @param string $delimiter The delimiter
|
|
* @param int $limit The limit
|
|
*
|
|
* @return array The split string as an array
|
|
*/
|
|
function twig_split_filter(Twig_Environment $env, $value, $delimiter, $limit = null)
|
|
{
|
|
if (!empty($delimiter)) {
|
|
return null === $limit ? explode($delimiter, $value) : explode($delimiter, $value, $limit);
|
|
}
|
|
|
|
if (!function_exists('mb_get_info') || null === $charset = $env->getCharset()) {
|
|
return str_split($value, null === $limit ? 1 : $limit);
|
|
}
|
|
|
|
if ($limit <= 1) {
|
|
return preg_split('/(?<!^)(?!$)/u', $value);
|
|
}
|
|
|
|
$length = mb_strlen($value, $charset);
|
|
if ($length < $limit) {
|
|
return array($value);
|
|
}
|
|
|
|
$r = array();
|
|
for ($i = 0; $i < $length; $i += $limit) {
|
|
$r[] = mb_substr($value, $i, $limit, $charset);
|
|
}
|
|
|
|
return $r;
|
|
}
|
|
|
|
// The '_default' filter is used internally to avoid using the ternary operator
|
|
// which costs a lot for big contexts (before PHP 5.4). So, on average,
|
|
// a function call is cheaper.
|
|
/**
|
|
* @internal
|
|
*/
|
|
function _twig_default_filter($value, $default = '')
|
|
{
|
|
if (twig_test_empty($value)) {
|
|
return $default;
|
|
}
|
|
|
|
return $value;
|
|
}
|
|
|
|
/**
|
|
* Returns the keys for the given array.
|
|
*
|
|
* It is useful when you want to iterate over the keys of an array:
|
|
*
|
|
* <pre>
|
|
* {% for key in array|keys %}
|
|
* {# ... #}
|
|
* {% endfor %}
|
|
* </pre>
|
|
*
|
|
* @param array $array An array
|
|
*
|
|
* @return array The keys
|
|
*/
|
|
function twig_get_array_keys_filter($array)
|
|
{
|
|
if ($array instanceof Traversable) {
|
|
return array_keys(iterator_to_array($array));
|
|
}
|
|
|
|
if (!is_array($array)) {
|
|
return array();
|
|
}
|
|
|
|
return array_keys($array);
|
|
}
|
|
|
|
/**
|
|
* Reverses a variable.
|
|
*
|
|
* @param Twig_Environment $env A Twig_Environment instance
|
|
* @param array|Traversable|string $item An array, a Traversable instance, or a string
|
|
* @param bool $preserveKeys Whether to preserve key or not
|
|
*
|
|
* @return mixed The reversed input
|
|
*/
|
|
function twig_reverse_filter(Twig_Environment $env, $item, $preserveKeys = false)
|
|
{
|
|
if ($item instanceof Traversable) {
|
|
return array_reverse(iterator_to_array($item), $preserveKeys);
|
|
}
|
|
|
|
if (is_array($item)) {
|
|
return array_reverse($item, $preserveKeys);
|
|
}
|
|
|
|
if (null !== $charset = $env->getCharset()) {
|
|
$string = (string) $item;
|
|
|
|
if ('UTF-8' != $charset) {
|
|
$item = twig_convert_encoding($string, 'UTF-8', $charset);
|
|
}
|
|
|
|
preg_match_all('/./us', $item, $matches);
|
|
|
|
$string = implode('', array_reverse($matches[0]));
|
|
|
|
if ('UTF-8' != $charset) {
|
|
$string = twig_convert_encoding($string, $charset, 'UTF-8');
|
|
}
|
|
|
|
return $string;
|
|
}
|
|
|
|
return strrev((string) $item);
|
|
}
|
|
|
|
/**
|
|
* Sorts an array.
|
|
*
|
|
* @param array|Traversable $array
|
|
*
|
|
* @return array
|
|
*/
|
|
function twig_sort_filter($array)
|
|
{
|
|
if ($array instanceof Traversable) {
|
|
$array = iterator_to_array($array);
|
|
} elseif (!is_array($array)) {
|
|
throw new Twig_Error_Runtime(sprintf('The sort filter only works with arrays or "Traversable", got "%s".', gettype($array)));
|
|
}
|
|
|
|
asort($array);
|
|
|
|
return $array;
|
|
}
|
|
|
|
/**
|
|
* @internal
|
|
*/
|
|
function twig_in_filter($value, $compare)
|
|
{
|
|
if (is_array($compare)) {
|
|
return in_array($value, $compare, is_object($value) || is_resource($value));
|
|
} elseif (is_string($compare) && (is_string($value) || is_int($value) || is_float($value))) {
|
|
return '' === $value || false !== strpos($compare, (string) $value);
|
|
} elseif ($compare instanceof Traversable) {
|
|
return in_array($value, iterator_to_array($compare, false), is_object($value) || is_resource($value));
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Escapes a string.
|
|
*
|
|
* @param Twig_Environment $env A Twig_Environment instance
|
|
* @param string $string The value to be escaped
|
|
* @param string $strategy The escaping strategy
|
|
* @param string $charset The charset
|
|
* @param bool $autoescape Whether the function is called by the auto-escaping feature (true) or by the developer (false)
|
|
*
|
|
* @return string
|
|
*/
|
|
function twig_escape_filter(Twig_Environment $env, $string, $strategy = 'html', $charset = null, $autoescape = false)
|
|
{
|
|
if ($autoescape && $string instanceof Twig_Markup) {
|
|
return $string;
|
|
}
|
|
|
|
if (!is_string($string)) {
|
|
if (is_object($string) && method_exists($string, '__toString')) {
|
|
$string = (string) $string;
|
|
} elseif (in_array($strategy, array('html', 'js', 'css', 'html_attr', 'url'))) {
|
|
return $string;
|
|
}
|
|
}
|
|
|
|
if (null === $charset) {
|
|
$charset = $env->getCharset();
|
|
}
|
|
|
|
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 (defined('HHVM_VERSION')) {
|
|
$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
|
|
if ('UTF-8' != $charset) {
|
|
$string = twig_convert_encoding($string, 'UTF-8', $charset);
|
|
}
|
|
|
|
if (0 == strlen($string) ? false : (1 == preg_match('/^./su', $string) ? false : true)) {
|
|
throw new Twig_Error_Runtime('The string to escape is not a valid UTF-8 string.');
|
|
}
|
|
|
|
$string = preg_replace_callback('#[^a-zA-Z0-9,\._]#Su', '_twig_escape_js_callback', $string);
|
|
|
|
if ('UTF-8' != $charset) {
|
|
$string = twig_convert_encoding($string, $charset, 'UTF-8');
|
|
}
|
|
|
|
return $string;
|
|
|
|
case 'css':
|
|
if ('UTF-8' != $charset) {
|
|
$string = twig_convert_encoding($string, 'UTF-8', $charset);
|
|
}
|
|
|
|
if (0 == strlen($string) ? false : (1 == preg_match('/^./su', $string) ? false : true)) {
|
|
throw new Twig_Error_Runtime('The string to escape is not a valid UTF-8 string.');
|
|
}
|
|
|
|
$string = preg_replace_callback('#[^a-zA-Z0-9]#Su', '_twig_escape_css_callback', $string);
|
|
|
|
if ('UTF-8' != $charset) {
|
|
$string = twig_convert_encoding($string, $charset, 'UTF-8');
|
|
}
|
|
|
|
return $string;
|
|
|
|
case 'html_attr':
|
|
if ('UTF-8' != $charset) {
|
|
$string = twig_convert_encoding($string, 'UTF-8', $charset);
|
|
}
|
|
|
|
if (0 == strlen($string) ? false : (1 == preg_match('/^./su', $string) ? false : true)) {
|
|
throw new Twig_Error_Runtime('The string to escape is not a valid UTF-8 string.');
|
|
}
|
|
|
|
$string = preg_replace_callback('#[^a-zA-Z0-9,\.\-_]#Su', '_twig_escape_html_attr_callback', $string);
|
|
|
|
if ('UTF-8' != $charset) {
|
|
$string = twig_convert_encoding($string, $charset, 'UTF-8');
|
|
}
|
|
|
|
return $string;
|
|
|
|
case 'url':
|
|
if (PHP_VERSION_ID < 50300) {
|
|
return str_replace('%7E', '~', rawurlencode($string));
|
|
}
|
|
|
|
return rawurlencode($string);
|
|
|
|
default:
|
|
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));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @internal
|
|
*/
|
|
function twig_escape_filter_is_safe(Twig_Node $filterArgs)
|
|
{
|
|
foreach ($filterArgs as $arg) {
|
|
if ($arg instanceof Twig_Node_Expression_Constant) {
|
|
return array($arg->getAttribute('value'));
|
|
}
|
|
|
|
return array();
|
|
}
|
|
|
|
return array('html');
|
|
}
|
|
|
|
if (function_exists('mb_convert_encoding')) {
|
|
function twig_convert_encoding($string, $to, $from)
|
|
{
|
|
return mb_convert_encoding($string, $to, $from);
|
|
}
|
|
} elseif (function_exists('iconv')) {
|
|
function twig_convert_encoding($string, $to, $from)
|
|
{
|
|
return iconv($from, $to, $string);
|
|
}
|
|
} else {
|
|
function twig_convert_encoding($string, $to, $from)
|
|
{
|
|
throw new Twig_Error_Runtime('No suitable convert encoding function (use UTF-8 as your encoding or install the iconv or mbstring extension).');
|
|
}
|
|
}
|
|
|
|
function _twig_escape_js_callback($matches)
|
|
{
|
|
$char = $matches[0];
|
|
|
|
// \xHH
|
|
if (!isset($char[1])) {
|
|
return '\\x'.strtoupper(substr('00'.bin2hex($char), -2));
|
|
}
|
|
|
|
// \uHHHH
|
|
$char = twig_convert_encoding($char, 'UTF-16BE', 'UTF-8');
|
|
|
|
return '\\u'.strtoupper(substr('0000'.bin2hex($char), -4));
|
|
}
|
|
|
|
function _twig_escape_css_callback($matches)
|
|
{
|
|
$char = $matches[0];
|
|
|
|
// \xHH
|
|
if (!isset($char[1])) {
|
|
$hex = ltrim(strtoupper(bin2hex($char)), '0');
|
|
if (0 === strlen($hex)) {
|
|
$hex = '0';
|
|
}
|
|
|
|
return '\\'.$hex.' ';
|
|
}
|
|
|
|
// \uHHHH
|
|
$char = twig_convert_encoding($char, 'UTF-16BE', 'UTF-8');
|
|
|
|
return '\\'.ltrim(strtoupper(bin2hex($char)), '0').' ';
|
|
}
|
|
|
|
/**
|
|
* This function is adapted from code coming from Zend Framework.
|
|
*
|
|
* @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com)
|
|
* @license http://framework.zend.com/license/new-bsd New BSD License
|
|
*/
|
|
function _twig_escape_html_attr_callback($matches)
|
|
{
|
|
/*
|
|
* While HTML supports far more named entities, the lowest common denominator
|
|
* has become HTML5's XML Serialisation which is restricted to the those named
|
|
* entities that XML supports. Using HTML entities would result in this error:
|
|
* XML Parsing Error: undefined entity
|
|
*/
|
|
static $entityMap = array(
|
|
34 => 'quot', /* quotation mark */
|
|
38 => 'amp', /* ampersand */
|
|
60 => 'lt', /* less-than sign */
|
|
62 => 'gt', /* greater-than sign */
|
|
);
|
|
|
|
$chr = $matches[0];
|
|
$ord = ord($chr);
|
|
|
|
/*
|
|
* The following replaces characters undefined in HTML with the
|
|
* hex entity for the Unicode replacement character.
|
|
*/
|
|
if (($ord <= 0x1f && $chr != "\t" && $chr != "\n" && $chr != "\r") || ($ord >= 0x7f && $ord <= 0x9f)) {
|
|
return '�';
|
|
}
|
|
|
|
/*
|
|
* Check if the current character to escape has a name entity we should
|
|
* replace it with while grabbing the hex value of the character.
|
|
*/
|
|
if (strlen($chr) == 1) {
|
|
$hex = strtoupper(substr('00'.bin2hex($chr), -2));
|
|
} else {
|
|
$chr = twig_convert_encoding($chr, 'UTF-16BE', 'UTF-8');
|
|
$hex = strtoupper(substr('0000'.bin2hex($chr), -4));
|
|
}
|
|
|
|
$int = hexdec($hex);
|
|
if (array_key_exists($int, $entityMap)) {
|
|
return sprintf('&%s;', $entityMap[$int]);
|
|
}
|
|
|
|
/*
|
|
* Per OWASP recommendations, we'll use hex entities for any other
|
|
* characters where a named entity does not exist.
|
|
*/
|
|
return sprintf('&#x%s;', $hex);
|
|
}
|
|
|
|
// add multibyte extensions if possible
|
|
if (function_exists('mb_get_info')) {
|
|
/**
|
|
* Returns the length of a variable.
|
|
*
|
|
* @param Twig_Environment $env A Twig_Environment instance
|
|
* @param mixed $thing A variable
|
|
*
|
|
* @return int The length of the value
|
|
*/
|
|
function twig_length_filter(Twig_Environment $env, $thing)
|
|
{
|
|
return is_scalar($thing) ? mb_strlen($thing, $env->getCharset()) : count($thing);
|
|
}
|
|
|
|
/**
|
|
* Converts a string to uppercase.
|
|
*
|
|
* @param Twig_Environment $env A Twig_Environment instance
|
|
* @param string $string A string
|
|
*
|
|
* @return string The uppercased string
|
|
*/
|
|
function twig_upper_filter(Twig_Environment $env, $string)
|
|
{
|
|
if (null !== ($charset = $env->getCharset())) {
|
|
return mb_strtoupper($string, $charset);
|
|
}
|
|
|
|
return strtoupper($string);
|
|
}
|
|
|
|
/**
|
|
* Converts a string to lowercase.
|
|
*
|
|
* @param Twig_Environment $env A Twig_Environment instance
|
|
* @param string $string A string
|
|
*
|
|
* @return string The lowercased string
|
|
*/
|
|
function twig_lower_filter(Twig_Environment $env, $string)
|
|
{
|
|
if (null !== ($charset = $env->getCharset())) {
|
|
return mb_strtolower($string, $charset);
|
|
}
|
|
|
|
return strtolower($string);
|
|
}
|
|
|
|
/**
|
|
* Returns a titlecased string.
|
|
*
|
|
* @param Twig_Environment $env A Twig_Environment instance
|
|
* @param string $string A string
|
|
*
|
|
* @return string The titlecased string
|
|
*/
|
|
function twig_title_string_filter(Twig_Environment $env, $string)
|
|
{
|
|
if (null !== ($charset = $env->getCharset())) {
|
|
return mb_convert_case($string, MB_CASE_TITLE, $charset);
|
|
}
|
|
|
|
return ucwords(strtolower($string));
|
|
}
|
|
|
|
/**
|
|
* Returns a capitalized string.
|
|
*
|
|
* @param Twig_Environment $env A Twig_Environment instance
|
|
* @param string $string A string
|
|
*
|
|
* @return string The capitalized string
|
|
*/
|
|
function twig_capitalize_string_filter(Twig_Environment $env, $string)
|
|
{
|
|
if (null !== $charset = $env->getCharset()) {
|
|
return mb_strtoupper(mb_substr($string, 0, 1, $charset), $charset).mb_strtolower(mb_substr($string, 1, mb_strlen($string, $charset), $charset), $charset);
|
|
}
|
|
|
|
return ucfirst(strtolower($string));
|
|
}
|
|
}
|
|
// and byte fallback
|
|
else {
|
|
/**
|
|
* Returns the length of a variable.
|
|
*
|
|
* @param Twig_Environment $env A Twig_Environment instance
|
|
* @param mixed $thing A variable
|
|
*
|
|
* @return int The length of the value
|
|
*/
|
|
function twig_length_filter(Twig_Environment $env, $thing)
|
|
{
|
|
return is_scalar($thing) ? strlen($thing) : count($thing);
|
|
}
|
|
|
|
/**
|
|
* Returns a titlecased string.
|
|
*
|
|
* @param Twig_Environment $env A Twig_Environment instance
|
|
* @param string $string A string
|
|
*
|
|
* @return string The titlecased string
|
|
*/
|
|
function twig_title_string_filter(Twig_Environment $env, $string)
|
|
{
|
|
return ucwords(strtolower($string));
|
|
}
|
|
|
|
/**
|
|
* Returns a capitalized string.
|
|
*
|
|
* @param Twig_Environment $env A Twig_Environment instance
|
|
* @param string $string A string
|
|
*
|
|
* @return string The capitalized string
|
|
*/
|
|
function twig_capitalize_string_filter(Twig_Environment $env, $string)
|
|
{
|
|
return ucfirst(strtolower($string));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @internal
|
|
*/
|
|
function twig_ensure_traversable($seq)
|
|
{
|
|
if ($seq instanceof Traversable || is_array($seq)) {
|
|
return $seq;
|
|
}
|
|
|
|
return array();
|
|
}
|
|
|
|
/**
|
|
* Checks if a variable is empty.
|
|
*
|
|
* <pre>
|
|
* {# evaluates to true if the foo variable is null, false, or the empty string #}
|
|
* {% if foo is empty %}
|
|
* {# ... #}
|
|
* {% endif %}
|
|
* </pre>
|
|
*
|
|
* @param mixed $value A variable
|
|
*
|
|
* @return bool true if the value is empty, false otherwise
|
|
*/
|
|
function twig_test_empty($value)
|
|
{
|
|
if ($value instanceof Countable) {
|
|
return 0 == count($value);
|
|
}
|
|
|
|
return '' === $value || false === $value || null === $value || array() === $value;
|
|
}
|
|
|
|
/**
|
|
* Checks if a variable is traversable.
|
|
*
|
|
* <pre>
|
|
* {# evaluates to true if the foo variable is an array or a traversable object #}
|
|
* {% if foo is traversable %}
|
|
* {# ... #}
|
|
* {% endif %}
|
|
* </pre>
|
|
*
|
|
* @param mixed $value A variable
|
|
*
|
|
* @return bool true if the value is traversable
|
|
*/
|
|
function twig_test_iterable($value)
|
|
{
|
|
return $value instanceof Traversable || is_array($value);
|
|
}
|
|
|
|
/**
|
|
* Renders a template.
|
|
*
|
|
* @param Twig_Environment $env
|
|
* @param array $context
|
|
* @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 bool $withContext
|
|
* @param bool $ignoreMissing Whether to ignore missing templates or not
|
|
* @param bool $sandboxed Whether to sandbox the template or not
|
|
*
|
|
* @return string The rendered template
|
|
*/
|
|
function twig_include(Twig_Environment $env, $context, $template, $variables = array(), $withContext = true, $ignoreMissing = false, $sandboxed = false)
|
|
{
|
|
$alreadySandboxed = false;
|
|
$sandbox = null;
|
|
if ($withContext) {
|
|
$variables = array_merge($context, $variables);
|
|
}
|
|
|
|
if ($isSandboxed = $sandboxed && $env->hasExtension('sandbox')) {
|
|
$sandbox = $env->getExtension('sandbox');
|
|
if (!$alreadySandboxed = $sandbox->isSandboxed()) {
|
|
$sandbox->enableSandbox();
|
|
}
|
|
}
|
|
|
|
$result = null;
|
|
try {
|
|
$result = $env->resolveTemplate($template)->render($variables);
|
|
} catch (Twig_Error_Loader $e) {
|
|
if (!$ignoreMissing) {
|
|
if ($isSandboxed && !$alreadySandboxed) {
|
|
$sandbox->disableSandbox();
|
|
}
|
|
|
|
throw $e;
|
|
}
|
|
}
|
|
|
|
if ($isSandboxed && !$alreadySandboxed) {
|
|
$sandbox->disableSandbox();
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Returns a template content without rendering it.
|
|
*
|
|
* @param string $name The template name
|
|
* @param bool $ignoreMissing Whether to ignore missing templates or not
|
|
*
|
|
* @return string The template source
|
|
*/
|
|
function twig_source(Twig_Environment $env, $name, $ignoreMissing = false)
|
|
{
|
|
try {
|
|
return $env->getLoader()->getSource($name);
|
|
} catch (Twig_Error_Loader $e) {
|
|
if (!$ignoreMissing) {
|
|
throw $e;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Provides the ability to get constants from instances as well as class/global constants.
|
|
*
|
|
* @param string $constant The name of the constant
|
|
* @param null|object $object The object to get the constant from
|
|
*
|
|
* @return string
|
|
*/
|
|
function twig_constant($constant, $object = null)
|
|
{
|
|
if (null !== $object) {
|
|
$constant = get_class($object).'::'.$constant;
|
|
}
|
|
|
|
return constant($constant);
|
|
}
|
|
|
|
/**
|
|
* Batches item.
|
|
*
|
|
* @param array $items An array of items
|
|
* @param int $size The size of the batch
|
|
* @param mixed $fill A value used to fill missing items
|
|
*
|
|
* @return array
|
|
*/
|
|
function twig_array_batch($items, $size, $fill = null)
|
|
{
|
|
if ($items instanceof Traversable) {
|
|
$items = iterator_to_array($items, false);
|
|
}
|
|
|
|
$size = ceil($size);
|
|
|
|
$result = array_chunk($items, $size, true);
|
|
|
|
if (null !== $fill && !empty($result)) {
|
|
$last = count($result) - 1;
|
|
if ($fillCount = $size - count($result[$last])) {
|
|
$result[$last] = array_merge(
|
|
$result[$last],
|
|
array_fill(0, $fillCount, $fill)
|
|
);
|
|
}
|
|
}
|
|
|
|
return $result;
|
|
}
|