Savetheinternet
13 years ago
145 changed files with 11031 additions and 320 deletions
@ -0,0 +1,46 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* 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. |
|||
*/ |
|||
|
|||
/** |
|||
* Autoloads Twig classes. |
|||
* |
|||
* @package twig |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
*/ |
|||
class Twig_Autoloader |
|||
{ |
|||
/** |
|||
* Registers Twig_Autoloader as an SPL autoloader. |
|||
*/ |
|||
static public function register() |
|||
{ |
|||
ini_set('unserialize_callback_func', 'spl_autoload_call'); |
|||
spl_autoload_register(array(new self, 'autoload')); |
|||
} |
|||
|
|||
/** |
|||
* Handles autoloading of classes. |
|||
* |
|||
* @param string $class A class name. |
|||
* |
|||
* @return boolean Returns true if the class has been loaded |
|||
*/ |
|||
static public function autoload($class) |
|||
{ |
|||
if (0 !== strpos($class, 'Twig')) { |
|||
return; |
|||
} |
|||
|
|||
if (is_file($file = dirname(__FILE__).'/../'.str_replace(array('_', "\0"), array('/', ''), $class).'.php')) { |
|||
require $file; |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,219 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of Twig. |
|||
* |
|||
* (c) 2009 Fabien Potencier |
|||
* (c) 2009 Armin Ronacher |
|||
* |
|||
* For the full copyright and license information, please view the LICENSE |
|||
* file that was distributed with this source code. |
|||
*/ |
|||
|
|||
/** |
|||
* Compiles a node to PHP code. |
|||
* |
|||
* @package twig |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
*/ |
|||
class Twig_Compiler implements Twig_CompilerInterface |
|||
{ |
|||
protected $lastLine; |
|||
protected $source; |
|||
protected $indentation; |
|||
protected $env; |
|||
|
|||
/** |
|||
* Constructor. |
|||
* |
|||
* @param Twig_Environment $env The twig environment instance |
|||
*/ |
|||
public function __construct(Twig_Environment $env) |
|||
{ |
|||
$this->env = $env; |
|||
} |
|||
|
|||
/** |
|||
* Returns the environment instance related to this compiler. |
|||
* |
|||
* @return Twig_Environment The environment instance |
|||
*/ |
|||
public function getEnvironment() |
|||
{ |
|||
return $this->env; |
|||
} |
|||
|
|||
/** |
|||
* Gets the current PHP code after compilation. |
|||
* |
|||
* @return string The PHP code |
|||
*/ |
|||
public function getSource() |
|||
{ |
|||
return $this->source; |
|||
} |
|||
|
|||
/** |
|||
* Compiles a node. |
|||
* |
|||
* @param Twig_NodeInterface $node The node to compile |
|||
* @param integer $indent The current indentation |
|||
* |
|||
* @return Twig_Compiler The current compiler instance |
|||
*/ |
|||
public function compile(Twig_NodeInterface $node, $indentation = 0) |
|||
{ |
|||
$this->lastLine = null; |
|||
$this->source = ''; |
|||
$this->indentation = $indentation; |
|||
|
|||
$node->compile($this); |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
public function subcompile(Twig_NodeInterface $node, $raw = true) |
|||
{ |
|||
if (false === $raw) { |
|||
$this->addIndentation(); |
|||
} |
|||
|
|||
$node->compile($this); |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* Adds a raw string to the compiled code. |
|||
* |
|||
* @param string $string The string |
|||
* |
|||
* @return Twig_Compiler The current compiler instance |
|||
*/ |
|||
public function raw($string) |
|||
{ |
|||
$this->source .= $string; |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* Writes a string to the compiled code by adding indentation. |
|||
* |
|||
* @return Twig_Compiler The current compiler instance |
|||
*/ |
|||
public function write() |
|||
{ |
|||
$strings = func_get_args(); |
|||
foreach ($strings as $string) { |
|||
$this->addIndentation(); |
|||
$this->source .= $string; |
|||
} |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
public function addIndentation() |
|||
{ |
|||
$this->source .= str_repeat(' ', $this->indentation * 4); |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* Adds a quoted string to the compiled code. |
|||
* |
|||
* @param string $string The string |
|||
* |
|||
* @return Twig_Compiler The current compiler instance |
|||
*/ |
|||
public function string($value) |
|||
{ |
|||
$this->source .= sprintf('"%s"', addcslashes($value, "\0\t\"\$\\")); |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* Returns a PHP representation of a given value. |
|||
* |
|||
* @param mixed $value The value to convert |
|||
* |
|||
* @return Twig_Compiler The current compiler instance |
|||
*/ |
|||
public function repr($value) |
|||
{ |
|||
if (is_int($value) || is_float($value)) { |
|||
$this->raw($value); |
|||
} else if (null === $value) { |
|||
$this->raw('null'); |
|||
} else if (is_bool($value)) { |
|||
$this->raw($value ? 'true' : 'false'); |
|||
} else if (is_array($value)) { |
|||
$this->raw('array('); |
|||
$i = 0; |
|||
foreach ($value as $key => $value) { |
|||
if ($i++) { |
|||
$this->raw(', '); |
|||
} |
|||
$this->repr($key); |
|||
$this->raw(' => '); |
|||
$this->repr($value); |
|||
} |
|||
$this->raw(')'); |
|||
} else { |
|||
$this->string($value); |
|||
} |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* Adds debugging information. |
|||
* |
|||
* @param Twig_NodeInterface $node The related twig node |
|||
* |
|||
* @return Twig_Compiler The current compiler instance |
|||
*/ |
|||
public function addDebugInfo(Twig_NodeInterface $node) |
|||
{ |
|||
if ($node->getLine() != $this->lastLine) { |
|||
$this->lastLine = $node->getLine(); |
|||
$this->write("// line {$node->getLine()}\n"); |
|||
} |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* Indents the generated code. |
|||
* |
|||
* @param integer $indent The number of indentation to add |
|||
* |
|||
* @return Twig_Compiler The current compiler instance |
|||
*/ |
|||
public function indent($step = 1) |
|||
{ |
|||
$this->indentation += $step; |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* Outdents the generated code. |
|||
* |
|||
* @param integer $indent The number of indentation to remove |
|||
* |
|||
* @return Twig_Compiler The current compiler instance |
|||
*/ |
|||
public function outdent($step = 1) |
|||
{ |
|||
$this->indentation -= $step; |
|||
|
|||
if ($this->indentation < 0) { |
|||
throw new Twig_Error('Unable to call outdent() as the indentation would become negative'); |
|||
} |
|||
|
|||
return $this; |
|||
} |
|||
} |
@ -0,0 +1,35 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* 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. |
|||
*/ |
|||
|
|||
/** |
|||
* Interface implemented by compiler classes. |
|||
* |
|||
* @package twig |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
*/ |
|||
interface Twig_CompilerInterface |
|||
{ |
|||
/** |
|||
* Compiles a node. |
|||
* |
|||
* @param Twig_NodeInterface $node The node to compile |
|||
* |
|||
* @return Twig_CompilerInterface The current compiler instance |
|||
*/ |
|||
function compile(Twig_NodeInterface $node); |
|||
|
|||
/** |
|||
* Gets the current PHP code after compilation. |
|||
* |
|||
* @return string The PHP code |
|||
*/ |
|||
function getSource(); |
|||
} |
@ -0,0 +1,938 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* 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. |
|||
*/ |
|||
|
|||
/** |
|||
* Stores the Twig configuration. |
|||
* |
|||
* @package twig |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
*/ |
|||
class Twig_Environment |
|||
{ |
|||
const VERSION = '1.2.0'; |
|||
|
|||
protected $charset; |
|||
protected $loader; |
|||
protected $debug; |
|||
protected $autoReload; |
|||
protected $cache; |
|||
protected $lexer; |
|||
protected $parser; |
|||
protected $compiler; |
|||
protected $baseTemplateClass; |
|||
protected $extensions; |
|||
protected $parsers; |
|||
protected $visitors; |
|||
protected $filters; |
|||
protected $tests; |
|||
protected $functions; |
|||
protected $globals; |
|||
protected $runtimeInitialized; |
|||
protected $loadedTemplates; |
|||
protected $strictVariables; |
|||
protected $unaryOperators; |
|||
protected $binaryOperators; |
|||
protected $templateClassPrefix = '__TwigTemplate_'; |
|||
protected $functionCallbacks; |
|||
protected $filterCallbacks; |
|||
|
|||
/** |
|||
* Constructor. |
|||
* |
|||
* Available options: |
|||
* |
|||
* * debug: When set to `true`, the generated templates have a __toString() |
|||
* method that you can use to display the generated nodes (default to |
|||
* false). |
|||
* |
|||
* * 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). |
|||
* |
|||
* * 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. |
|||
* If you don't provide the auto_reload option, it will be |
|||
* determined automatically base on the debug value. |
|||
* |
|||
* * strict_variables: Whether to ignore invalid variables in templates |
|||
* (default to false). |
|||
* |
|||
* * autoescape: Whether to enable auto-escaping (default to true); |
|||
* |
|||
* * optimizations: A flag that indicates which optimizations to apply |
|||
* (default to -1 which means that all optimizations are enabled; |
|||
* set it to 0 to disable) |
|||
* |
|||
* @param Twig_LoaderInterface $loader A Twig_LoaderInterface instance |
|||
* @param array $options An array of options |
|||
*/ |
|||
public function __construct(Twig_LoaderInterface $loader = null, $options = array()) |
|||
{ |
|||
if (null !== $loader) { |
|||
$this->setLoader($loader); |
|||
} |
|||
|
|||
$options = array_merge(array( |
|||
'debug' => false, |
|||
'charset' => 'UTF-8', |
|||
'base_template_class' => 'Twig_Template', |
|||
'strict_variables' => false, |
|||
'autoescape' => true, |
|||
'cache' => false, |
|||
'auto_reload' => null, |
|||
'optimizations' => -1, |
|||
), $options); |
|||
|
|||
$this->debug = (bool) $options['debug']; |
|||
$this->charset = $options['charset']; |
|||
$this->baseTemplateClass = $options['base_template_class']; |
|||
$this->autoReload = null === $options['auto_reload'] ? $this->debug : (bool) $options['auto_reload']; |
|||
$this->extensions = array( |
|||
'core' => new Twig_Extension_Core(), |
|||
'escaper' => new Twig_Extension_Escaper((bool) $options['autoescape']), |
|||
'optimizer' => new Twig_Extension_Optimizer($options['optimizations']), |
|||
); |
|||
$this->strictVariables = (bool) $options['strict_variables']; |
|||
$this->runtimeInitialized = false; |
|||
$this->setCache($options['cache']); |
|||
$this->functionCallbacks = array(); |
|||
$this->filterCallbacks = array(); |
|||
} |
|||
|
|||
/** |
|||
* Gets the base template class for compiled templates. |
|||
* |
|||
* @return string The base template class name |
|||
*/ |
|||
public function getBaseTemplateClass() |
|||
{ |
|||
return $this->baseTemplateClass; |
|||
} |
|||
|
|||
/** |
|||
* Sets the base template class for compiled templates. |
|||
* |
|||
* @param string $class The base template class name |
|||
*/ |
|||
public function setBaseTemplateClass($class) |
|||
{ |
|||
$this->baseTemplateClass = $class; |
|||
} |
|||
|
|||
/** |
|||
* Enables debugging mode. |
|||
*/ |
|||
public function enableDebug() |
|||
{ |
|||
$this->debug = true; |
|||
} |
|||
|
|||
/** |
|||
* Disables debugging mode. |
|||
*/ |
|||
public function disableDebug() |
|||
{ |
|||
$this->debug = false; |
|||
} |
|||
|
|||
/** |
|||
* Checks if debug mode is enabled. |
|||
* |
|||
* @return Boolean true if debug mode is enabled, false otherwise |
|||
*/ |
|||
public function isDebug() |
|||
{ |
|||
return $this->debug; |
|||
} |
|||
|
|||
/** |
|||
* Enables the auto_reload option. |
|||
*/ |
|||
public function enableAutoReload() |
|||
{ |
|||
$this->autoReload = true; |
|||
} |
|||
|
|||
/** |
|||
* Disables the auto_reload option. |
|||
*/ |
|||
public function disableAutoReload() |
|||
{ |
|||
$this->autoReload = false; |
|||
} |
|||
|
|||
/** |
|||
* Checks if the auto_reload option is enabled. |
|||
* |
|||
* @return Boolean true if auto_reload is enabled, false otherwise |
|||
*/ |
|||
public function isAutoReload() |
|||
{ |
|||
return $this->autoReload; |
|||
} |
|||
|
|||
/** |
|||
* Enables the strict_variables option. |
|||
*/ |
|||
public function enableStrictVariables() |
|||
{ |
|||
$this->strictVariables = true; |
|||
} |
|||
|
|||
/** |
|||
* Disables the strict_variables option. |
|||
*/ |
|||
public function disableStrictVariables() |
|||
{ |
|||
$this->strictVariables = false; |
|||
} |
|||
|
|||
/** |
|||
* Checks if the strict_variables option is enabled. |
|||
* |
|||
* @return Boolean true if strict_variables is enabled, false otherwise |
|||
*/ |
|||
public function isStrictVariables() |
|||
{ |
|||
return $this->strictVariables; |
|||
} |
|||
|
|||
/** |
|||
* Gets the cache directory or false if cache is disabled. |
|||
* |
|||
* @return string|false |
|||
*/ |
|||
public function getCache() |
|||
{ |
|||
return $this->cache; |
|||
} |
|||
|
|||
/** |
|||
* Sets the cache directory or false if cache is disabled. |
|||
* |
|||
* @param string|false $cache The absolute path to the compiled templates, |
|||
* or false to disable cache |
|||
*/ |
|||
public function setCache($cache) |
|||
{ |
|||
$this->cache = $cache ? $cache : false; |
|||
} |
|||
|
|||
/** |
|||
* Gets the cache filename for a given template. |
|||
* |
|||
* @param string $name The template name |
|||
* |
|||
* @return string The cache file name |
|||
*/ |
|||
public function getCacheFilename($name) |
|||
{ |
|||
if (false === $this->cache) { |
|||
return false; |
|||
} |
|||
|
|||
$class = substr($this->getTemplateClass($name), strlen($this->templateClassPrefix)); |
|||
|
|||
return $this->getCache().'/'.substr($class, 0, 2).'/'.substr($class, 2, 2).'/'.substr($class, 4).'.php'; |
|||
} |
|||
|
|||
/** |
|||
* Gets the template class associated with the given string. |
|||
* |
|||
* @param string $name The name for which to calculate the template class name |
|||
* |
|||
* @return string The template class name |
|||
*/ |
|||
public function getTemplateClass($name) |
|||
{ |
|||
return $this->templateClassPrefix.md5($this->loader->getCacheKey($name)); |
|||
} |
|||
|
|||
/** |
|||
* Gets the template class prefix. |
|||
* |
|||
* @return string The template class prefix |
|||
*/ |
|||
public function getTemplateClassPrefix() |
|||
{ |
|||
return $this->templateClassPrefix; |
|||
} |
|||
|
|||
/** |
|||
* Renders a template. |
|||
* |
|||
* @param string $name The template name |
|||
* @param array $context An array of parameters to pass to the template |
|||
* |
|||
* @return string The rendered template |
|||
*/ |
|||
public function render($name, array $context = array()) |
|||
{ |
|||
return $this->loadTemplate($name)->render($context); |
|||
} |
|||
|
|||
/** |
|||
* Loads a template by name. |
|||
* |
|||
* @param string $name The template name |
|||
* |
|||
* @return Twig_TemplateInterface A template instance representing the given template name |
|||
*/ |
|||
public function loadTemplate($name) |
|||
{ |
|||
$cls = $this->getTemplateClass($name); |
|||
|
|||
if (isset($this->loadedTemplates[$cls])) { |
|||
return $this->loadedTemplates[$cls]; |
|||
} |
|||
|
|||
if (!class_exists($cls, false)) { |
|||
if (false === $cache = $this->getCacheFilename($name)) { |
|||
eval('?>'.$this->compileSource($this->loader->getSource($name), $name)); |
|||
} else { |
|||
if (!is_file($cache) || ($this->isAutoReload() && !$this->loader->isFresh($name, filemtime($cache)))) { |
|||
$this->writeCacheFile($cache, $this->compileSource($this->loader->getSource($name), $name)); |
|||
} |
|||
|
|||
require_once $cache; |
|||
} |
|||
} |
|||
|
|||
if (!$this->runtimeInitialized) { |
|||
$this->initRuntime(); |
|||
} |
|||
|
|||
return $this->loadedTemplates[$cls] = new $cls($this); |
|||
} |
|||
|
|||
public function resolveTemplate($names) |
|||
{ |
|||
if (!is_array($names)) { |
|||
$names = array($names); |
|||
} |
|||
|
|||
foreach ($names as $name) { |
|||
if ($name instanceof Twig_Template) { |
|||
return $name; |
|||
} |
|||
|
|||
try { |
|||
return $this->loadTemplate($name); |
|||
} catch (Twig_Error_Loader $e) { |
|||
} |
|||
} |
|||
|
|||
if (1 === count($names)) { |
|||
throw $e; |
|||
} |
|||
|
|||
throw new Twig_Error_Loader(sprintf('Unable to find one of the following templates: "%s".', implode('", "', $names))); |
|||
} |
|||
|
|||
/** |
|||
* Clears the internal template cache. |
|||
*/ |
|||
public function clearTemplateCache() |
|||
{ |
|||
$this->loadedTemplates = array(); |
|||
} |
|||
|
|||
/** |
|||
* Clears the template cache files on the filesystem. |
|||
*/ |
|||
public function clearCacheFiles() |
|||
{ |
|||
if (false === $this->cache) { |
|||
return; |
|||
} |
|||
|
|||
foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($this->cache), RecursiveIteratorIterator::LEAVES_ONLY) as $file) { |
|||
if ($file->isFile()) { |
|||
@unlink($file->getPathname()); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Gets the Lexer instance. |
|||
* |
|||
* @return Twig_LexerInterface A Twig_LexerInterface instance |
|||
*/ |
|||
public function getLexer() |
|||
{ |
|||
if (null === $this->lexer) { |
|||
$this->lexer = new Twig_Lexer($this); |
|||
} |
|||
|
|||
return $this->lexer; |
|||
} |
|||
|
|||
/** |
|||
* Sets the Lexer instance. |
|||
* |
|||
* @param Twig_LexerInterface A Twig_LexerInterface instance |
|||
*/ |
|||
public function setLexer(Twig_LexerInterface $lexer) |
|||
{ |
|||
$this->lexer = $lexer; |
|||
} |
|||
|
|||
/** |
|||
* Tokenizes a source code. |
|||
* |
|||
* @param string $source The template source code |
|||
* @param string $name The template name |
|||
* |
|||
* @return Twig_TokenStream A Twig_TokenStream instance |
|||
*/ |
|||
public function tokenize($source, $name = null) |
|||
{ |
|||
return $this->getLexer()->tokenize($source, $name); |
|||
} |
|||
|
|||
/** |
|||
* Gets the Parser instance. |
|||
* |
|||
* @return Twig_ParserInterface A Twig_ParserInterface instance |
|||
*/ |
|||
public function getParser() |
|||
{ |
|||
if (null === $this->parser) { |
|||
$this->parser = new Twig_Parser($this); |
|||
} |
|||
|
|||
return $this->parser; |
|||
} |
|||
|
|||
/** |
|||
* Sets the Parser instance. |
|||
* |
|||
* @param Twig_ParserInterface A Twig_ParserInterface instance |
|||
*/ |
|||
public function setParser(Twig_ParserInterface $parser) |
|||
{ |
|||
$this->parser = $parser; |
|||
} |
|||
|
|||
/** |
|||
* Parses a token stream. |
|||
* |
|||
* @param Twig_TokenStream $tokens A Twig_TokenStream instance |
|||
* |
|||
* @return Twig_Node_Module A Node tree |
|||
*/ |
|||
public function parse(Twig_TokenStream $tokens) |
|||
{ |
|||
return $this->getParser()->parse($tokens); |
|||
} |
|||
|
|||
/** |
|||
* Gets the Compiler instance. |
|||
* |
|||
* @return Twig_CompilerInterface A Twig_CompilerInterface instance |
|||
*/ |
|||
public function getCompiler() |
|||
{ |
|||
if (null === $this->compiler) { |
|||
$this->compiler = new Twig_Compiler($this); |
|||
} |
|||
|
|||
return $this->compiler; |
|||
} |
|||
|
|||
/** |
|||
* Sets the Compiler instance. |
|||
* |
|||
* @param Twig_CompilerInterface $compiler A Twig_CompilerInterface instance |
|||
*/ |
|||
public function setCompiler(Twig_CompilerInterface $compiler) |
|||
{ |
|||
$this->compiler = $compiler; |
|||
} |
|||
|
|||
/** |
|||
* Compiles a Node. |
|||
* |
|||
* @param Twig_NodeInterface $node A Twig_NodeInterface instance |
|||
* |
|||
* @return string The compiled PHP source code |
|||
*/ |
|||
public function compile(Twig_NodeInterface $node) |
|||
{ |
|||
return $this->getCompiler()->compile($node)->getSource(); |
|||
} |
|||
|
|||
/** |
|||
* Compiles a template source code. |
|||
* |
|||
* @param string $source The template source code |
|||
* @param string $name The template name |
|||
* |
|||
* @return string The compiled PHP source code |
|||
*/ |
|||
public function compileSource($source, $name = null) |
|||
{ |
|||
try { |
|||
return $this->compile($this->parse($this->tokenize($source, $name))); |
|||
} catch (Twig_Error $e) { |
|||
$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); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Sets the Loader instance. |
|||
* |
|||
* @param Twig_LoaderInterface $loader A Twig_LoaderInterface instance |
|||
*/ |
|||
public function setLoader(Twig_LoaderInterface $loader) |
|||
{ |
|||
$this->loader = $loader; |
|||
} |
|||
|
|||
/** |
|||
* Gets the Loader instance. |
|||
* |
|||
* @return Twig_LoaderInterface A Twig_LoaderInterface instance |
|||
*/ |
|||
public function getLoader() |
|||
{ |
|||
return $this->loader; |
|||
} |
|||
|
|||
/** |
|||
* Sets the default template charset. |
|||
* |
|||
* @param string $charset The default charset |
|||
*/ |
|||
public function setCharset($charset) |
|||
{ |
|||
$this->charset = $charset; |
|||
} |
|||
|
|||
/** |
|||
* Gets the default template charset. |
|||
* |
|||
* @return string The default charset |
|||
*/ |
|||
public function getCharset() |
|||
{ |
|||
return $this->charset; |
|||
} |
|||
|
|||
/** |
|||
* Initializes the runtime environment. |
|||
*/ |
|||
public function initRuntime() |
|||
{ |
|||
$this->runtimeInitialized = true; |
|||
|
|||
foreach ($this->getExtensions() as $extension) { |
|||
$extension->initRuntime($this); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Returns true if the given extension is registered. |
|||
* |
|||
* @param string $name The extension name |
|||
* |
|||
* @return Boolean Whether the extension is registered or not |
|||
*/ |
|||
public function hasExtension($name) |
|||
{ |
|||
return isset($this->extensions[$name]); |
|||
} |
|||
|
|||
/** |
|||
* Gets an extension by name. |
|||
* |
|||
* @param string $name The extension name |
|||
* |
|||
* @return Twig_ExtensionInterface A Twig_ExtensionInterface instance |
|||
*/ |
|||
public function getExtension($name) |
|||
{ |
|||
if (!isset($this->extensions[$name])) { |
|||
throw new Twig_Error_Runtime(sprintf('The "%s" extension is not enabled.', $name)); |
|||
} |
|||
|
|||
return $this->extensions[$name]; |
|||
} |
|||
|
|||
/** |
|||
* Registers an extension. |
|||
* |
|||
* @param Twig_ExtensionInterface $extension A Twig_ExtensionInterface instance |
|||
*/ |
|||
public function addExtension(Twig_ExtensionInterface $extension) |
|||
{ |
|||
$this->extensions[$extension->getName()] = $extension; |
|||
} |
|||
|
|||
/** |
|||
* Removes an extension by name. |
|||
* |
|||
* @param string $name The extension name |
|||
*/ |
|||
public function removeExtension($name) |
|||
{ |
|||
unset($this->extensions[$name]); |
|||
} |
|||
|
|||
/** |
|||
* Registers an array of extensions. |
|||
* |
|||
* @param array $extensions An array of extensions |
|||
*/ |
|||
public function setExtensions(array $extensions) |
|||
{ |
|||
foreach ($extensions as $extension) { |
|||
$this->addExtension($extension); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Returns all registered extensions. |
|||
* |
|||
* @return array An array of extensions |
|||
*/ |
|||
public function getExtensions() |
|||
{ |
|||
return $this->extensions; |
|||
} |
|||
|
|||
/** |
|||
* Registers a Token Parser. |
|||
* |
|||
* @param Twig_TokenParserInterface $parser A Twig_TokenParserInterface instance |
|||
*/ |
|||
public function addTokenParser(Twig_TokenParserInterface $parser) |
|||
{ |
|||
if (null === $this->parsers) { |
|||
$this->getTokenParsers(); |
|||
} |
|||
|
|||
$this->parsers->addTokenParser($parser); |
|||
} |
|||
|
|||
/** |
|||
* Gets the registered Token Parsers. |
|||
* |
|||
* @return Twig_TokenParserInterface[] An array of Twig_TokenParserInterface instances |
|||
*/ |
|||
public function getTokenParsers() |
|||
{ |
|||
if (null === $this->parsers) { |
|||
$this->parsers = new Twig_TokenParserBroker; |
|||
foreach ($this->getExtensions() as $extension) { |
|||
$parsers = $extension->getTokenParsers(); |
|||
foreach($parsers as $parser) { |
|||
if ($parser instanceof Twig_TokenParserInterface) { |
|||
$this->parsers->addTokenParser($parser); |
|||
} else if ($parser instanceof Twig_TokenParserBrokerInterface) { |
|||
$this->parsers->addTokenParserBroker($parser); |
|||
} else { |
|||
throw new Twig_Error_Runtime('getTokenParsers() must return an array of Twig_TokenParserInterface or Twig_TokenParserBrokerInterface instances'); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
return $this->parsers; |
|||
} |
|||
|
|||
/** |
|||
* Registers a Node Visitor. |
|||
* |
|||
* @param Twig_NodeVisitorInterface $visitor A Twig_NodeVisitorInterface instance |
|||
*/ |
|||
public function addNodeVisitor(Twig_NodeVisitorInterface $visitor) |
|||
{ |
|||
if (null === $this->visitors) { |
|||
$this->getNodeVisitors(); |
|||
} |
|||
|
|||
$this->visitors[] = $visitor; |
|||
} |
|||
|
|||
/** |
|||
* Gets the registered Node Visitors. |
|||
* |
|||
* @return Twig_NodeVisitorInterface[] An array of Twig_NodeVisitorInterface instances |
|||
*/ |
|||
public function getNodeVisitors() |
|||
{ |
|||
if (null === $this->visitors) { |
|||
$this->visitors = array(); |
|||
foreach ($this->getExtensions() as $extension) { |
|||
$this->visitors = array_merge($this->visitors, $extension->getNodeVisitors()); |
|||
} |
|||
} |
|||
|
|||
return $this->visitors; |
|||
} |
|||
|
|||
/** |
|||
* Registers a Filter. |
|||
* |
|||
* @param string $name The filter name |
|||
* @param Twig_FilterInterface $visitor A Twig_FilterInterface instance |
|||
*/ |
|||
public function addFilter($name, Twig_FilterInterface $filter) |
|||
{ |
|||
if (null === $this->filters) { |
|||
$this->loadFilters(); |
|||
} |
|||
|
|||
$this->filters[$name] = $filter; |
|||
} |
|||
|
|||
/** |
|||
* Get a filter by name. |
|||
* |
|||
* Subclasses may override this method and load filters differently; |
|||
* so no list of filters is available. |
|||
* |
|||
* @param string $name The filter name |
|||
* |
|||
* @return Twig_Filter|false A Twig_Filter instance or false if the filter does not exists |
|||
*/ |
|||
public function getFilter($name) |
|||
{ |
|||
if (null === $this->filters) { |
|||
$this->loadFilters(); |
|||
} |
|||
|
|||
if (isset($this->filters[$name])) { |
|||
return $this->filters[$name]; |
|||
} |
|||
|
|||
foreach ($this->filterCallbacks as $callback) { |
|||
if (false !== $filter = call_user_func($callback, $name)) { |
|||
return $filter; |
|||
} |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
public function registerUndefinedFilterCallback($callable) |
|||
{ |
|||
$this->filterCallbacks[] = $callable; |
|||
} |
|||
|
|||
/** |
|||
* Gets the registered Filters. |
|||
* |
|||
* @return Twig_FilterInterface[] An array of Twig_FilterInterface instances |
|||
*/ |
|||
protected function loadFilters() |
|||
{ |
|||
$this->filters = array(); |
|||
foreach ($this->getExtensions() as $extension) { |
|||
$this->filters = array_merge($this->filters, $extension->getFilters()); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Registers a Test. |
|||
* |
|||
* @param string $name The test name |
|||
* @param Twig_TestInterface $visitor A Twig_TestInterface instance |
|||
*/ |
|||
public function addTest($name, Twig_TestInterface $test) |
|||
{ |
|||
if (null === $this->tests) { |
|||
$this->getTests(); |
|||
} |
|||
|
|||
$this->tests[$name] = $test; |
|||
} |
|||
|
|||
/** |
|||
* Gets the registered Tests. |
|||
* |
|||
* @return Twig_TestInterface[] An array of Twig_TestInterface instances |
|||
*/ |
|||
public function getTests() |
|||
{ |
|||
if (null === $this->tests) { |
|||
$this->tests = array(); |
|||
foreach ($this->getExtensions() as $extension) { |
|||
$this->tests = array_merge($this->tests, $extension->getTests()); |
|||
} |
|||
} |
|||
|
|||
return $this->tests; |
|||
} |
|||
|
|||
/** |
|||
* Registers a Function. |
|||
* |
|||
* @param string $name The function name |
|||
* @param Twig_FunctionInterface $function A Twig_FunctionInterface instance |
|||
*/ |
|||
public function addFunction($name, Twig_FunctionInterface $function) |
|||
{ |
|||
if (null === $this->functions) { |
|||
$this->loadFunctions(); |
|||
} |
|||
|
|||
$this->functions[$name] = $function; |
|||
} |
|||
|
|||
/** |
|||
* Get a function by name. |
|||
* |
|||
* Subclasses may override this method and load functions differently; |
|||
* so no list of functions is available. |
|||
* |
|||
* @param string $name function name |
|||
* |
|||
* @return Twig_Function|false A Twig_Function instance or false if the function does not exists |
|||
*/ |
|||
public function getFunction($name) |
|||
{ |
|||
if (null === $this->functions) { |
|||
$this->loadFunctions(); |
|||
} |
|||
|
|||
if (isset($this->functions[$name])) { |
|||
return $this->functions[$name]; |
|||
} |
|||
|
|||
foreach ($this->functionCallbacks as $callback) { |
|||
if (false !== $function = call_user_func($callback, $name)) { |
|||
return $function; |
|||
} |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
public function registerUndefinedFunctionCallback($callable) |
|||
{ |
|||
$this->functionCallbacks[] = $callable; |
|||
} |
|||
|
|||
protected function loadFunctions() |
|||
{ |
|||
$this->functions = array(); |
|||
foreach ($this->getExtensions() as $extension) { |
|||
$this->functions = array_merge($this->functions, $extension->getFunctions()); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Registers a Global. |
|||
* |
|||
* @param string $name The global name |
|||
* @param mixed $value The global value |
|||
*/ |
|||
public function addGlobal($name, $value) |
|||
{ |
|||
if (null === $this->globals) { |
|||
$this->getGlobals(); |
|||
} |
|||
|
|||
$this->globals[$name] = $value; |
|||
} |
|||
|
|||
/** |
|||
* Gets the registered Globals. |
|||
* |
|||
* @return array An array of globals |
|||
*/ |
|||
public function getGlobals() |
|||
{ |
|||
if (null === $this->globals) { |
|||
$this->globals = array(); |
|||
foreach ($this->getExtensions() as $extension) { |
|||
$this->globals = array_merge($this->globals, $extension->getGlobals()); |
|||
} |
|||
} |
|||
|
|||
return $this->globals; |
|||
} |
|||
|
|||
/** |
|||
* Gets the registered unary Operators. |
|||
* |
|||
* @return array An array of unary operators |
|||
*/ |
|||
public function getUnaryOperators() |
|||
{ |
|||
if (null === $this->unaryOperators) { |
|||
$this->initOperators(); |
|||
} |
|||
|
|||
return $this->unaryOperators; |
|||
} |
|||
|
|||
/** |
|||
* Gets the registered binary Operators. |
|||
* |
|||
* @return array An array of binary operators |
|||
*/ |
|||
public function getBinaryOperators() |
|||
{ |
|||
if (null === $this->binaryOperators) { |
|||
$this->initOperators(); |
|||
} |
|||
|
|||
return $this->binaryOperators; |
|||
} |
|||
|
|||
protected function initOperators() |
|||
{ |
|||
$this->unaryOperators = array(); |
|||
$this->binaryOperators = array(); |
|||
foreach ($this->getExtensions() as $extension) { |
|||
$operators = $extension->getOperators(); |
|||
|
|||
if (!$operators) { |
|||
continue; |
|||
} |
|||
|
|||
if (2 !== count($operators)) { |
|||
throw new InvalidArgumentException(sprintf('"%s::getOperators()" does not return a valid operators array.', get_class($extension))); |
|||
} |
|||
|
|||
$this->unaryOperators = array_merge($this->unaryOperators, $operators[0]); |
|||
$this->binaryOperators = array_merge($this->binaryOperators, $operators[1]); |
|||
} |
|||
} |
|||
|
|||
protected function writeCacheFile($file, $content) |
|||
{ |
|||
if (!is_dir(dirname($file))) { |
|||
mkdir(dirname($file), 0777, true); |
|||
} |
|||
|
|||
$tmpFile = tempnam(dirname($file), 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))) { |
|||
chmod($file, 0644); |
|||
|
|||
return; |
|||
} |
|||
} |
|||
|
|||
throw new Twig_Error_Runtime(sprintf('Failed to write cache file "%s".', $file)); |
|||
} |
|||
} |
@ -0,0 +1,195 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* 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. |
|||
*/ |
|||
|
|||
/** |
|||
* Twig base exception. |
|||
* |
|||
* @package twig |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
*/ |
|||
class Twig_Error extends Exception |
|||
{ |
|||
protected $lineno; |
|||
protected $filename; |
|||
protected $rawMessage; |
|||
protected $previous; |
|||
|
|||
/** |
|||
* Constructor. |
|||
* |
|||
* @param string $message The error message |
|||
* @param integer $lineno The template line where the error occurred |
|||
* @param string $filename The template file name where the error occurred |
|||
* @param Exception $previous The previous exception |
|||
*/ |
|||
public function __construct($message, $lineno = -1, $filename = null, Exception $previous = null) |
|||
{ |
|||
if (-1 === $lineno || null === $filename) { |
|||
list($lineno, $filename) = $this->findTemplateInfo(null !== $previous ? $previous : $this, $lineno, $filename); |
|||
} |
|||
|
|||
$this->lineno = $lineno; |
|||
$this->filename = $filename; |
|||
$this->rawMessage = $message; |
|||
|
|||
$this->updateRepr(); |
|||
|
|||
if (version_compare(PHP_VERSION, '5.3.0', '<')) { |
|||
$this->previous = $previous; |
|||
parent::__construct($this->message); |
|||
} else { |
|||
parent::__construct($this->message, 0, $previous); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Gets the raw message. |
|||
* |
|||
* @return string The raw message |
|||
*/ |
|||
public function getRawMessage() |
|||
{ |
|||
return $this->rawMessage; |
|||
} |
|||
|
|||
/** |
|||
* Gets the filename where the error occurred. |
|||
* |
|||
* @return string The filename |
|||
*/ |
|||
public function getTemplateFile() |
|||
{ |
|||
return $this->filename; |
|||
} |
|||
|
|||
/** |
|||
* Sets the filename where the error occurred. |
|||
* |
|||
* @param string $filename The filename |
|||
*/ |
|||
public function setTemplateFile($filename) |
|||
{ |
|||
$this->filename = $filename; |
|||
|
|||
$this->updateRepr(); |
|||
} |
|||
|
|||
/** |
|||
* Gets the template line where the error occurred. |
|||
* |
|||
* @return integer The template line |
|||
*/ |
|||
public function getTemplateLine() |
|||
{ |
|||
return $this->lineno; |
|||
} |
|||
|
|||
/** |
|||
* Sets the template line where the error occurred. |
|||
* |
|||
* @param integer $lineno The template line |
|||
*/ |
|||
public function setTemplateLine($lineno) |
|||
{ |
|||
$this->lineno = $lineno; |
|||
|
|||
$this->updateRepr(); |
|||
} |
|||
|
|||
/** |
|||
* For PHP < 5.3.0, provides access to the getPrevious() method. |
|||
* |
|||
* @param string $method The method name |
|||
* @param array $arguments The parameters to be passed to the method |
|||
* |
|||
* @return Exception The previous exception or null |
|||
*/ |
|||
public function __call($method, $arguments) |
|||
{ |
|||
if ('getprevious' == strtolower($method)) { |
|||
return $this->previous; |
|||
} |
|||
|
|||
throw new BadMethodCallException(sprintf('Method "Twig_Error::%s()" does not exist.', $method)); |
|||
} |
|||
|
|||
protected function updateRepr() |
|||
{ |
|||
$this->message = $this->rawMessage; |
|||
|
|||
$dot = false; |
|||
if ('.' === substr($this->message, -1)) { |
|||
$this->message = substr($this->message, 0, -1); |
|||
$dot = true; |
|||
} |
|||
|
|||
if (null !== $this->filename) { |
|||
$this->message .= sprintf(' in %s', json_encode($this->filename)); |
|||
} |
|||
|
|||
if ($this->lineno >= 0) { |
|||
$this->message .= sprintf(' at line %d', $this->lineno); |
|||
} |
|||
|
|||
if ($dot) { |
|||
$this->message .= '.'; |
|||
} |
|||
} |
|||
|
|||
protected function findTemplateInfo(Exception $e, $currentLine, $currentFile) |
|||
{ |
|||
if (!function_exists('token_get_all')) { |
|||
return array($currentLine, $currentFile); |
|||
} |
|||
|
|||
$traces = $e->getTrace(); |
|||
foreach ($traces as $i => $trace) { |
|||
if (!isset($trace['class']) || 'Twig_Template' === $trace['class']) { |
|||
continue; |
|||
} |
|||
|
|||
$r = new ReflectionClass($trace['class']); |
|||
if (!$r->implementsInterface('Twig_TemplateInterface')) { |
|||
continue; |
|||
} |
|||
|
|||
if (!is_file($r->getFilename())) { |
|||
// probably an eval()'d code |
|||
return array($currentLine, $currentFile); |
|||
} |
|||
|
|||
if (0 === $i) { |
|||
$line = $e->getLine(); |
|||
} else { |
|||
$line = isset($traces[$i - 1]['line']) ? $traces[$i - 1]['line'] : -log(0); |
|||
} |
|||
|
|||
$tokens = token_get_all(file_get_contents($r->getFilename())); |
|||
$templateline = -1; |
|||
$template = null; |
|||
foreach ($tokens as $token) { |
|||
if (isset($token[2]) && $token[2] >= $line) { |
|||
return array($templateline, $template); |
|||
} |
|||
|
|||
if (T_COMMENT === $token[0] && null === $template && preg_match('#/\* +(.+) +\*/#', $token[1], $match)) { |
|||
$template = $match[1]; |
|||
} elseif (T_COMMENT === $token[0] && preg_match('#^//\s*line (\d+)\s*$#', $token[1], $match)) { |
|||
$templateline = $match[1]; |
|||
} |
|||
} |
|||
|
|||
return array($currentLine, $template); |
|||
} |
|||
|
|||
return array($currentLine, $currentFile); |
|||
} |
|||
} |
@ -0,0 +1,20 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of Twig. |
|||
* |
|||
* (c) 2010 Fabien Potencier |
|||
* |
|||
* For the full copyright and license information, please view the LICENSE |
|||
* file that was distributed with this source code. |
|||
*/ |
|||
|
|||
/** |
|||
* Exception thrown when an error occurs during template loading. |
|||
* |
|||
* @package twig |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
*/ |
|||
class Twig_Error_Loader extends Twig_Error |
|||
{ |
|||
} |
@ -0,0 +1,21 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of Twig. |
|||
* |
|||
* (c) 2009 Fabien Potencier |
|||
* (c) 2009 Armin Ronacher |
|||
* |
|||
* For the full copyright and license information, please view the LICENSE |
|||
* file that was distributed with this source code. |
|||
*/ |
|||
|
|||
/** |
|||
* Exception thrown when an error occurs at runtime. |
|||
* |
|||
* @package twig |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
*/ |
|||
class Twig_Error_Runtime extends Twig_Error |
|||
{ |
|||
} |
@ -0,0 +1,21 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of Twig. |
|||
* |
|||
* (c) 2009 Fabien Potencier |
|||
* (c) 2009 Armin Ronacher |
|||
* |
|||
* For the full copyright and license information, please view the LICENSE |
|||
* file that was distributed with this source code. |
|||
*/ |
|||
|
|||
/** |
|||
* Exception thrown when a syntax error occurs during lexing or parsing of a template. |
|||
* |
|||
* @package twig |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
*/ |
|||
class Twig_Error_Syntax extends Twig_Error |
|||
{ |
|||
} |
@ -0,0 +1,382 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of Twig. |
|||
* |
|||
* (c) 2009 Fabien Potencier |
|||
* (c) 2009 Armin Ronacher |
|||
* |
|||
* For the full copyright and license information, please view the LICENSE |
|||
* file that was distributed with this source code. |
|||
*/ |
|||
|
|||
/** |
|||
* Parses expressions. |
|||
* |
|||
* This parser implements a "Precedence climbing" algorithm. |
|||
* |
|||
* @see http://www.engr.mun.ca/~theo/Misc/exp_parsing.htm |
|||
* @see http://en.wikipedia.org/wiki/Operator-precedence_parser |
|||
* |
|||
* @package twig |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
*/ |
|||
class Twig_ExpressionParser |
|||
{ |
|||
const OPERATOR_LEFT = 1; |
|||
const OPERATOR_RIGHT = 2; |
|||
|
|||
protected $parser; |
|||
protected $unaryOperators; |
|||
protected $binaryOperators; |
|||
|
|||
public function __construct(Twig_Parser $parser, array $unaryOperators, array $binaryOperators) |
|||
{ |
|||
$this->parser = $parser; |
|||
$this->unaryOperators = $unaryOperators; |
|||
$this->binaryOperators = $binaryOperators; |
|||
} |
|||
|
|||
public function parseExpression($precedence = 0) |
|||
{ |
|||
$expr = $this->getPrimary(); |
|||
$token = $this->parser->getCurrentToken(); |
|||
while ($this->isBinary($token) && $this->binaryOperators[$token->getValue()]['precedence'] >= $precedence) { |
|||
$op = $this->binaryOperators[$token->getValue()]; |
|||
$this->parser->getStream()->next(); |
|||
|
|||
if (isset($op['callable'])) { |
|||
$expr = call_user_func($op['callable'], $this->parser, $expr); |
|||
} else { |
|||
$expr1 = $this->parseExpression(self::OPERATOR_LEFT === $op['associativity'] ? $op['precedence'] + 1 : $op['precedence']); |
|||
$class = $op['class']; |
|||
$expr = new $class($expr, $expr1, $token->getLine()); |
|||
} |
|||
|
|||
$token = $this->parser->getCurrentToken(); |
|||
} |
|||
|
|||
if (0 === $precedence) { |
|||
return $this->parseConditionalExpression($expr); |
|||
} |
|||
|
|||
return $expr; |
|||
} |
|||
|
|||
protected function getPrimary() |
|||
{ |
|||
$token = $this->parser->getCurrentToken(); |
|||
|
|||
if ($this->isUnary($token)) { |
|||
$operator = $this->unaryOperators[$token->getValue()]; |
|||
$this->parser->getStream()->next(); |
|||
$expr = $this->parseExpression($operator['precedence']); |
|||
$class = $operator['class']; |
|||
|
|||
return $this->parsePostfixExpression(new $class($expr, $token->getLine())); |
|||
} elseif ($token->test(Twig_Token::PUNCTUATION_TYPE, '(')) { |
|||
$this->parser->getStream()->next(); |
|||
$expr = $this->parseExpression(); |
|||
$this->parser->getStream()->expect(Twig_Token::PUNCTUATION_TYPE, ')', 'An opened parenthesis is not properly closed'); |
|||
|
|||
return $this->parsePostfixExpression($expr); |
|||
} |
|||
|
|||
return $this->parsePrimaryExpression(); |
|||
} |
|||
|
|||
protected function parseConditionalExpression($expr) |
|||
{ |
|||
while ($this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, '?')) { |
|||
$this->parser->getStream()->next(); |
|||
$expr2 = $this->parseExpression(); |
|||
$this->parser->getStream()->expect(Twig_Token::PUNCTUATION_TYPE, ':', 'The ternary operator must have a default value'); |
|||
$expr3 = $this->parseExpression(); |
|||
|
|||
$expr = new Twig_Node_Expression_Conditional($expr, $expr2, $expr3, $this->parser->getCurrentToken()->getLine()); |
|||
} |
|||
|
|||
return $expr; |
|||
} |
|||
|
|||
protected function isUnary(Twig_Token $token) |
|||
{ |
|||
return $token->test(Twig_Token::OPERATOR_TYPE) && isset($this->unaryOperators[$token->getValue()]); |
|||
} |
|||
|
|||
protected function isBinary(Twig_Token $token) |
|||
{ |
|||
return $token->test(Twig_Token::OPERATOR_TYPE) && isset($this->binaryOperators[$token->getValue()]); |
|||
} |
|||
|
|||
public function parsePrimaryExpression() |
|||
{ |
|||
$token = $this->parser->getCurrentToken(); |
|||
switch ($token->getType()) { |
|||
case Twig_Token::NAME_TYPE: |
|||
$this->parser->getStream()->next(); |
|||
switch ($token->getValue()) { |
|||
case 'true': |
|||
case 'TRUE': |
|||
$node = new Twig_Node_Expression_Constant(true, $token->getLine()); |
|||
break; |
|||
|
|||
case 'false': |
|||
case 'FALSE': |
|||
$node = new Twig_Node_Expression_Constant(false, $token->getLine()); |
|||
break; |
|||
|
|||
case 'none': |
|||
case 'NONE': |
|||
case 'null': |
|||
case 'NULL': |
|||
$node = new Twig_Node_Expression_Constant(null, $token->getLine()); |
|||
break; |
|||
|
|||
default: |
|||
if ('(' === $this->parser->getCurrentToken()->getValue()) { |
|||
$node = $this->getFunctionNode($token->getValue(), $token->getLine()); |
|||
} else { |
|||
$node = new Twig_Node_Expression_Name($token->getValue(), $token->getLine()); |
|||
} |
|||
} |
|||
break; |
|||
|
|||
case Twig_Token::NUMBER_TYPE: |
|||
case Twig_Token::STRING_TYPE: |
|||
$this->parser->getStream()->next(); |
|||
$node = new Twig_Node_Expression_Constant($token->getValue(), $token->getLine()); |
|||
break; |
|||
|
|||
default: |
|||
if ($token->test(Twig_Token::PUNCTUATION_TYPE, '[')) { |
|||
$node = $this->parseArrayExpression(); |
|||
} elseif ($token->test(Twig_Token::PUNCTUATION_TYPE, '{')) { |
|||
$node = $this->parseHashExpression(); |
|||
} else { |
|||
throw new Twig_Error_Syntax(sprintf('Unexpected token "%s" of value "%s"', Twig_Token::typeToEnglish($token->getType(), $token->getLine()), $token->getValue()), $token->getLine()); |
|||
} |
|||
} |
|||
|
|||
return $this->parsePostfixExpression($node); |
|||
} |
|||
|
|||
public function parseArrayExpression() |
|||
{ |
|||
$stream = $this->parser->getStream(); |
|||
$stream->expect(Twig_Token::PUNCTUATION_TYPE, '[', 'An array element was expected'); |
|||
$elements = array(); |
|||
while (!$stream->test(Twig_Token::PUNCTUATION_TYPE, ']')) { |
|||
if (!empty($elements)) { |
|||
$stream->expect(Twig_Token::PUNCTUATION_TYPE, ',', 'An array element must be followed by a comma'); |
|||
|
|||
// trailing ,? |
|||
if ($stream->test(Twig_Token::PUNCTUATION_TYPE, ']')) { |
|||
break; |
|||
} |
|||
} |
|||
|
|||
$elements[] = $this->parseExpression(); |
|||
} |
|||
$stream->expect(Twig_Token::PUNCTUATION_TYPE, ']', 'An opened array is not properly closed'); |
|||
|
|||
return new Twig_Node_Expression_Array($elements, $stream->getCurrent()->getLine()); |
|||
} |
|||
|
|||
public function parseHashExpression() |
|||
{ |
|||
$stream = $this->parser->getStream(); |
|||
$stream->expect(Twig_Token::PUNCTUATION_TYPE, '{', 'A hash element was expected'); |
|||
$elements = array(); |
|||
while (!$stream->test(Twig_Token::PUNCTUATION_TYPE, '}')) { |
|||
if (!empty($elements)) { |
|||
$stream->expect(Twig_Token::PUNCTUATION_TYPE, ',', 'A hash value must be followed by a comma'); |
|||
|
|||
// trailing ,? |
|||
if ($stream->test(Twig_Token::PUNCTUATION_TYPE, '}')) { |
|||
break; |
|||
} |
|||
} |
|||
|
|||
if (!$stream->test(Twig_Token::STRING_TYPE) && !$stream->test(Twig_Token::NUMBER_TYPE)) { |
|||
$current = $stream->getCurrent(); |
|||
throw new Twig_Error_Syntax(sprintf('A hash key must be a quoted string or a number (unexpected token "%s" of value "%s"', Twig_Token::typeToEnglish($current->getType(), $current->getLine()), $current->getValue()), $current->getLine()); |
|||
} |
|||
|
|||
$key = $stream->next()->getValue(); |
|||
$stream->expect(Twig_Token::PUNCTUATION_TYPE, ':', 'A hash key must be followed by a colon (:)'); |
|||
$elements[$key] = $this->parseExpression(); |
|||
} |
|||
$stream->expect(Twig_Token::PUNCTUATION_TYPE, '}', 'An opened hash is not properly closed'); |
|||
|
|||
return new Twig_Node_Expression_Array($elements, $stream->getCurrent()->getLine()); |
|||
} |
|||
|
|||
public function parsePostfixExpression($node) |
|||
{ |
|||
while (true) { |
|||
$token = $this->parser->getCurrentToken(); |
|||
if ($token->getType() == Twig_Token::PUNCTUATION_TYPE) { |
|||
if ('.' == $token->getValue() || '[' == $token->getValue()) { |
|||
$node = $this->parseSubscriptExpression($node); |
|||
} elseif ('|' == $token->getValue()) { |
|||
$node = $this->parseFilterExpression($node); |
|||
} else { |
|||
break; |
|||
} |
|||
} else { |
|||
break; |
|||
} |
|||
} |
|||
|
|||
return $node; |
|||
} |
|||
|
|||
public function getFunctionNode($name, $line) |
|||
{ |
|||
$args = $this->parseArguments(); |
|||
switch ($name) { |
|||
case 'parent': |
|||
if (!count($this->parser->getBlockStack())) { |
|||
throw new Twig_Error_Syntax('Calling "parent" outside a block is forbidden', $line); |
|||
} |
|||
|
|||
if (!$this->parser->getParent()) { |
|||
throw new Twig_Error_Syntax('Calling "parent" on a template that does not extend another one is forbidden', $line); |
|||
} |
|||
|
|||
return new Twig_Node_Expression_Parent($this->parser->peekBlockStack(), $line); |
|||
case 'block': |
|||
return new Twig_Node_Expression_BlockReference($args->getNode(0), false, $line); |
|||
case 'attribute': |
|||
if (count($args) < 2) { |
|||
throw new Twig_Error_Syntax('The "attribute" function takes at least two arguments (the variable and the attribute)', $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_TemplateInterface::ANY_CALL, $line); |
|||
default: |
|||
if (null !== $alias = $this->parser->getImportedFunction($name)) { |
|||
return new Twig_Node_Expression_GetAttr($alias['node'], new Twig_Node_Expression_Constant($alias['name'], $line), $args, Twig_TemplateInterface::METHOD_CALL, $line); |
|||
} |
|||
|
|||
return new Twig_Node_Expression_Function($name, $args, $line); |
|||
} |
|||
} |
|||
|
|||
public function parseSubscriptExpression($node) |
|||
{ |
|||
$token = $this->parser->getStream()->next(); |
|||
$lineno = $token->getLine(); |
|||
$arguments = new Twig_Node(); |
|||
$type = Twig_TemplateInterface::ANY_CALL; |
|||
if ($token->getValue() == '.') { |
|||
$token = $this->parser->getStream()->next(); |
|||
if ( |
|||
$token->getType() == Twig_Token::NAME_TYPE |
|||
|| |
|||
$token->getType() == Twig_Token::NUMBER_TYPE |
|||
|| |
|||
($token->getType() == Twig_Token::OPERATOR_TYPE && preg_match(Twig_Lexer::REGEX_NAME, $token->getValue())) |
|||
) { |
|||
$arg = new Twig_Node_Expression_Constant($token->getValue(), $lineno); |
|||
|
|||
if ($this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, '(')) { |
|||
$type = Twig_TemplateInterface::METHOD_CALL; |
|||
$arguments = $this->parseArguments(); |
|||
} else { |
|||
$arguments = new Twig_Node(); |
|||
} |
|||
} else { |
|||
throw new Twig_Error_Syntax('Expected name or number', $lineno); |
|||
} |
|||
} else { |
|||
$type = Twig_TemplateInterface::ARRAY_CALL; |
|||
|
|||
$arg = $this->parseExpression(); |
|||
$this->parser->getStream()->expect(Twig_Token::PUNCTUATION_TYPE, ']'); |
|||
} |
|||
|
|||
return new Twig_Node_Expression_GetAttr($node, $arg, $arguments, $type, $lineno); |
|||
} |
|||
|
|||
public function parseFilterExpression($node) |
|||
{ |
|||
$this->parser->getStream()->next(); |
|||
|
|||
return $this->parseFilterExpressionRaw($node); |
|||
} |
|||
|
|||
public function parseFilterExpressionRaw($node, $tag = null) |
|||
{ |
|||
while (true) { |
|||
$token = $this->parser->getStream()->expect(Twig_Token::NAME_TYPE); |
|||
|
|||
$name = new Twig_Node_Expression_Constant($token->getValue(), $token->getLine()); |
|||
if (!$this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, '(')) { |
|||
$arguments = new Twig_Node(); |
|||
} else { |
|||
$arguments = $this->parseArguments(); |
|||
} |
|||
|
|||
$node = new Twig_Node_Expression_Filter($node, $name, $arguments, $token->getLine(), $tag); |
|||
|
|||
if (!$this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, '|')) { |
|||
break; |
|||
} |
|||
|
|||
$this->parser->getStream()->next(); |
|||
} |
|||
|
|||
return $node; |
|||
} |
|||
|
|||
public function parseArguments() |
|||
{ |
|||
$args = array(); |
|||
$stream = $this->parser->getStream(); |
|||
|
|||
$stream->expect(Twig_Token::PUNCTUATION_TYPE, '(', 'A list of arguments must be opened by a parenthesis'); |
|||
while (!$stream->test(Twig_Token::PUNCTUATION_TYPE, ')')) { |
|||
if (!empty($args)) { |
|||
$stream->expect(Twig_Token::PUNCTUATION_TYPE, ',', 'Arguments must be separated by a comma'); |
|||
} |
|||
$args[] = $this->parseExpression(); |
|||
} |
|||
$stream->expect(Twig_Token::PUNCTUATION_TYPE, ')', 'A list of arguments must be closed by a parenthesis'); |
|||
|
|||
return new Twig_Node($args); |
|||
} |
|||
|
|||
public function parseAssignmentExpression() |
|||
{ |
|||
$targets = array(); |
|||
while (true) { |
|||
$token = $this->parser->getStream()->expect(Twig_Token::NAME_TYPE, null, 'Only variables can be assigned to'); |
|||
if (in_array($token->getValue(), array('true', 'false', 'none'))) { |
|||
throw new Twig_Error_Syntax(sprintf('You cannot assign a value to "%s"', $token->getValue()), $token->getLine()); |
|||
} |
|||
$targets[] = new Twig_Node_Expression_AssignName($token->getValue(), $token->getLine()); |
|||
|
|||
if (!$this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, ',')) { |
|||
break; |
|||
} |
|||
$this->parser->getStream()->next(); |
|||
} |
|||
|
|||
return new Twig_Node($targets); |
|||
} |
|||
|
|||
public function parseMultitargetExpression() |
|||
{ |
|||
$targets = array(); |
|||
while (true) { |
|||
$targets[] = $this->parseExpression(); |
|||
if (!$this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, ',')) { |
|||
break; |
|||
} |
|||
$this->parser->getStream()->next(); |
|||
} |
|||
|
|||
return new Twig_Node($targets); |
|||
} |
|||
} |
@ -0,0 +1,93 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* 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. |
|||
*/ |
|||
abstract class Twig_Extension implements Twig_ExtensionInterface |
|||
{ |
|||
/** |
|||
* Initializes the runtime environment. |
|||
* |
|||
* This is where you can load some file that contains filter functions for instance. |
|||
* |
|||
* @param Twig_Environment $environment The current Twig_Environment instance |
|||
*/ |
|||
public function initRuntime(Twig_Environment $environment) |
|||
{ |
|||
} |
|||
|
|||
/** |
|||
* Returns the token parser instances to add to the existing list. |
|||
* |
|||
* @return array An array of Twig_TokenParserInterface or Twig_TokenParserBrokerInterface instances |
|||
*/ |
|||
public function getTokenParsers() |
|||
{ |
|||
return array(); |
|||
} |
|||
|
|||
/** |
|||
* Returns the node visitor instances to add to the existing list. |
|||
* |
|||
* @return array An array of Twig_NodeVisitorInterface instances |
|||
*/ |
|||
public function getNodeVisitors() |
|||
{ |
|||
return array(); |
|||
} |
|||
|
|||
/** |
|||
* Returns a list of filters to add to the existing list. |
|||
* |
|||
* @return array An array of filters |
|||
*/ |
|||
public function getFilters() |
|||
{ |
|||
return array(); |
|||
} |
|||
|
|||
/** |
|||
* Returns a list of tests to add to the existing list. |
|||
* |
|||
* @return array An array of tests |
|||
*/ |
|||
public function getTests() |
|||
{ |
|||
return array(); |
|||
} |
|||
|
|||
/** |
|||
* Returns a list of functions to add to the existing list. |
|||
* |
|||
* @return array An array of functions |
|||
*/ |
|||
public function getFunctions() |
|||
{ |
|||
return array(); |
|||
} |
|||
|
|||
/** |
|||
* Returns a list of operators to add to the existing list. |
|||
* |
|||
* @return array An array of operators |
|||
*/ |
|||
public function getOperators() |
|||
{ |
|||
return array(); |
|||
} |
|||
|
|||
/** |
|||
* Returns a list of global functions to add to the existing list. |
|||
* |
|||
* @return array An array of global functions |
|||
*/ |
|||
public function getGlobals() |
|||
{ |
|||
return array(); |
|||
} |
|||
} |
@ -0,0 +1,846 @@ |
|||
<?php |
|||
|
|||
if (!defined('ENT_SUBSTITUTE')) { |
|||
define('ENT_SUBSTITUTE', 8); |
|||
} |
|||
|
|||
/* |
|||
* 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 |
|||
{ |
|||
/** |
|||
* Returns the token parser instance to add to the existing list. |
|||
* |
|||
* @return array 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(), |
|||
); |
|||
} |
|||
|
|||
/** |
|||
* Returns a list of filters to add to the existing list. |
|||
* |
|||
* @return array An array of filters |
|||
*/ |
|||
public function getFilters() |
|||
{ |
|||
$filters = array( |
|||
// formatting filters |
|||
'date' => new Twig_Filter_Function('twig_date_format_filter'), |
|||
'format' => new Twig_Filter_Function('sprintf'), |
|||
'replace' => new Twig_Filter_Function('twig_strtr'), |
|||
|
|||
// encoding |
|||
'url_encode' => new Twig_Filter_Function('twig_urlencode_filter'), |
|||
'json_encode' => new Twig_Filter_Function('twig_jsonencode_filter'), |
|||
|
|||
// string filters |
|||
'title' => new Twig_Filter_Function('twig_title_string_filter', array('needs_environment' => true)), |
|||
'capitalize' => new Twig_Filter_Function('twig_capitalize_string_filter', array('needs_environment' => true)), |
|||
'upper' => new Twig_Filter_Function('strtoupper'), |
|||
'lower' => new Twig_Filter_Function('strtolower'), |
|||
'striptags' => new Twig_Filter_Function('strip_tags'), |
|||
|
|||
// array helpers |
|||
'join' => new Twig_Filter_Function('twig_join_filter'), |
|||
'reverse' => new Twig_Filter_Function('twig_reverse_filter'), |
|||
'length' => new Twig_Filter_Function('twig_length_filter', array('needs_environment' => true)), |
|||
'sort' => new Twig_Filter_Function('twig_sort_filter'), |
|||
'merge' => new Twig_Filter_Function('twig_array_merge'), |
|||
|
|||
// iteration and runtime |
|||
'default' => new Twig_Filter_Function('twig_default_filter'), |
|||
'keys' => new Twig_Filter_Function('twig_get_array_keys_filter'), |
|||
|
|||
// escaping |
|||
'escape' => new Twig_Filter_Function('twig_escape_filter', array('needs_environment' => true, 'is_safe_callback' => 'twig_escape_filter_is_safe')), |
|||
'e' => new Twig_Filter_Function('twig_escape_filter', array('needs_environment' => true, 'is_safe_callback' => 'twig_escape_filter_is_safe')), |
|||
); |
|||
|
|||
if (function_exists('mb_get_info')) { |
|||
$filters['upper'] = new Twig_Filter_Function('twig_upper_filter', array('needs_environment' => true)); |
|||
$filters['lower'] = new Twig_Filter_Function('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( |
|||
'range' => new Twig_Function_Function('range'), |
|||
'constant' => new Twig_Function_Function('constant'), |
|||
'cycle' => new Twig_Function_Function('twig_cycle'), |
|||
); |
|||
} |
|||
|
|||
/** |
|||
* Returns a list of filters to add to the existing list. |
|||
* |
|||
* @return array An array of filters |
|||
*/ |
|||
public function getTests() |
|||
{ |
|||
return array( |
|||
'even' => new Twig_Test_Function('twig_test_even'), |
|||
'odd' => new Twig_Test_Function('twig_test_odd'), |
|||
'defined' => new Twig_Test_Function('twig_test_defined'), |
|||
'sameas' => new Twig_Test_Function('twig_test_sameas'), |
|||
'none' => new Twig_Test_Function('twig_test_none'), |
|||
'null' => new Twig_Test_Function('twig_test_none'), |
|||
'divisibleby' => new Twig_Test_Function('twig_test_divisibleby'), |
|||
'constant' => new Twig_Test_Function('twig_test_constant'), |
|||
'empty' => new Twig_Test_Function('twig_test_empty'), |
|||
); |
|||
} |
|||
|
|||
/** |
|||
* 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' => 50, 'class' => 'Twig_Node_Expression_Unary_Neg'), |
|||
'+' => array('precedence' => 50, 'class' => 'Twig_Node_Expression_Unary_Pos'), |
|||
), |
|||
array( |
|||
'b-and' => array('precedence' => 5, 'class' => 'Twig_Node_Expression_Binary_BitwiseAnd', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), |
|||
'b-xor' => array('precedence' => 5, 'class' => 'Twig_Node_Expression_Binary_BitwiseXor', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), |
|||
'b-or' => array('precedence' => 5, 'class' => 'Twig_Node_Expression_Binary_BitwiseOr', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), |
|||
'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), |
|||
'==' => 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' => 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' => 110, 'class' => 'Twig_Node_Expression_Binary_Range', '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) |
|||
{ |
|||
return new Twig_Node_Expression_Unary_Not($this->parseTestExpression($parser, $node), $parser->getCurrentToken()->getLine()); |
|||
} |
|||
|
|||
public function parseTestExpression(Twig_Parser $parser, $node) |
|||
{ |
|||
$stream = $parser->getStream(); |
|||
$name = $stream->expect(Twig_Token::NAME_TYPE); |
|||
$arguments = null; |
|||
if ($stream->test(Twig_Token::PUNCTUATION_TYPE, '(')) { |
|||
$arguments = $parser->getExpressionParser()->parseArguments(); |
|||
} |
|||
|
|||
return new Twig_Node_Expression_Test($node, $name->getValue(), $arguments, $parser->getCurrentToken()->getLine()); |
|||
} |
|||
|
|||
/** |
|||
* 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 integer $i The cycle value |
|||
* |
|||
* @return string The next value in the cycle |
|||
*/ |
|||
function twig_cycle($values, $i) |
|||
{ |
|||
if (!is_array($values) && !$values instanceof ArrayAccess) { |
|||
return $values; |
|||
} |
|||
|
|||
return $values[$i % count($values)]; |
|||
} |
|||
|
|||
/** |
|||
* Converts a date to the given format. |
|||
* |
|||
* <pre> |
|||
* {{ post.published_at|date("m/d/Y") }} |
|||
* </pre> |
|||
* |
|||
* @param DateTime|string $date A date |
|||
* @param string $format A format |
|||
* @param DateTimeZone|string $timezone A timezone |
|||
* |
|||
* @return string The formatter date |
|||
*/ |
|||
function twig_date_format_filter($date, $format = 'F j, Y H:i', $timezone = null) |
|||
{ |
|||
if (!$date instanceof DateTime) { |
|||
if (ctype_digit((string) $date)) { |
|||
$date = new DateTime('@'.$date); |
|||
$date->setTimezone(new DateTimeZone(date_default_timezone_get())); |
|||
} else { |
|||
$date = new DateTime($date); |
|||
} |
|||
} |
|||
|
|||
if (null !== $timezone) { |
|||
if (!$timezone instanceof DateTimeZone) { |
|||
$timezone = new DateTimeZone($timezone); |
|||
} |
|||
|
|||
$date->setTimezone($timezone); |
|||
} |
|||
|
|||
return $date->format($format); |
|||
} |
|||
|
|||
/** |
|||
* URL encodes a string. |
|||
* |
|||
* @param string $url A URL |
|||
* @param bool $raw true to use rawurlencode() instead of urlencode |
|||
* |
|||
* @return string The URL encoded value |
|||
*/ |
|||
function twig_urlencode_filter($url, $raw = false) |
|||
{ |
|||
if ($raw) { |
|||
return rawurlencode($url); |
|||
} |
|||
|
|||
return urlencode($url); |
|||
} |
|||
|
|||
if (version_compare(PHP_VERSION, '5.3.0', '<')) { |
|||
/** |
|||
* JSON encodes a PHP variable. |
|||
* |
|||
* @param mixed $value The value to encode. |
|||
* @param integer $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 PHP variable. |
|||
* |
|||
* @param mixed $value The value to encode. |
|||
* @param integer $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 $arr1 An array |
|||
* @param array $arr2 An array |
|||
* |
|||
* @return array The merged array |
|||
*/ |
|||
function twig_array_merge($arr1, $arr2) |
|||
{ |
|||
if (!is_array($arr1) || !is_array($arr2)) { |
|||
throw new Twig_Error_Runtime('The merge filter only work with arrays or hashes.'); |
|||
} |
|||
|
|||
return array_merge($arr1, $arr2); |
|||
} |
|||
|
|||
/** |
|||
* 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 = '') |
|||
{ |
|||
return implode($glue, (array) $value); |
|||
} |
|||
|
|||
/** |
|||
* Returns the value or the default value when it is undefined or empty. |
|||
* |
|||
* <pre> |
|||
* |
|||
* {{ var.foo|default('foo item on var is not defined') }} |
|||
* |
|||
* </pre> |
|||
* |
|||
* @param mixed $value A value |
|||
* @param mixed $default The default value |
|||
* |
|||
* @param mixed The value or the default value; |
|||
*/ |
|||
function twig_default_filter($value, $default = '') |
|||
{ |
|||
if (twig_test_empty($value)) { |
|||
return $default; |
|||
} else { |
|||
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 (is_object($array) && $array instanceof Traversable) { |
|||
return array_keys(iterator_to_array($array)); |
|||
} |
|||
|
|||
if (!is_array($array)) { |
|||
return array(); |
|||
} |
|||
|
|||
return array_keys($array); |
|||
} |
|||
|
|||
/** |
|||
* Reverses an array. |
|||
* |
|||
* @param array|Traversable $array An array or a Traversable instance |
|||
* |
|||
* return array The array reversed |
|||
*/ |
|||
function twig_reverse_filter($array) |
|||
{ |
|||
if (is_object($array) && $array instanceof Traversable) { |
|||
return array_reverse(iterator_to_array($array)); |
|||
} |
|||
|
|||
if (!is_array($array)) { |
|||
return array(); |
|||
} |
|||
|
|||
return array_reverse($array); |
|||
} |
|||
|
|||
/** |
|||
* Sorts an array. |
|||
* |
|||
* @param array $array An array |
|||
*/ |
|||
function twig_sort_filter($array) |
|||
{ |
|||
asort($array); |
|||
|
|||
return $array; |
|||
} |
|||
|
|||
/* used internally */ |
|||
function twig_in_filter($value, $compare) |
|||
{ |
|||
if (is_array($compare)) { |
|||
return in_array($value, $compare); |
|||
} elseif (is_string($compare)) { |
|||
return false !== strpos($compare, (string) $value); |
|||
} elseif (is_object($compare) && $compare instanceof Traversable) { |
|||
return in_array($value, iterator_to_array($compare, false)); |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
/** |
|||
* Replaces placeholders in a string. |
|||
* |
|||
* <pre> |
|||
* {{ "I like %this% and %that%."|replace({'%this%': foo, '%that%': "bar"}) }} |
|||
* </pre> |
|||
* |
|||
* @param string $pattern A string |
|||
* @param string $replacements The values for the placeholders |
|||
* |
|||
* @return string The string where the placeholders have been replaced |
|||
*/ |
|||
function twig_strtr($pattern, $replacements) |
|||
{ |
|||
return str_replace(array_keys($replacements), array_values($replacements), $pattern); |
|||
} |
|||
|
|||
/** |
|||
* Escapes a string. |
|||
* |
|||
* @param Twig_Environment $env A Twig_Environment instance |
|||
* @param string $string The value to be escaped |
|||
* @param string $type The escaping strategy |
|||
* @param string $charset The charset |
|||
*/ |
|||
function twig_escape_filter(Twig_Environment $env, $string, $type = 'html', $charset = null) |
|||
{ |
|||
if (is_object($string) && $string instanceof Twig_Markup) { |
|||
return $string; |
|||
} |
|||
|
|||
if (!is_string($string) && !(is_object($string) && method_exists($string, '__toString'))) { |
|||
return $string; |
|||
} |
|||
|
|||
if (null === $charset) { |
|||
$charset = $env->getCharset(); |
|||
} |
|||
|
|||
switch ($type) { |
|||
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 (null === $string = preg_replace_callback('#[^\p{L}\p{N} ]#u', '_twig_escape_js_callback', $string)) { |
|||
throw new Twig_Error_Runtime('The string to escape is not a valid UTF-8 string.'); |
|||
} |
|||
|
|||
if ('UTF-8' != $charset) { |
|||
$string = _twig_convert_encoding($string, $charset, 'UTF-8'); |
|||
} |
|||
|
|||
return $string; |
|||
|
|||
case 'html': |
|||
return htmlspecialchars($string, ENT_QUOTES | ENT_SUBSTITUTE, $charset); |
|||
|
|||
default: |
|||
throw new Twig_Error_Runtime(sprintf('Invalid escape type "%s".', $type)); |
|||
} |
|||
} |
|||
|
|||
/* used internally */ |
|||
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')); |
|||
} else { |
|||
return array(); |
|||
} |
|||
|
|||
break; |
|||
} |
|||
|
|||
return array('html'); |
|||
} |
|||
|
|||
if (function_exists('iconv')) { |
|||
function _twig_convert_encoding($string, $to, $from) |
|||
{ |
|||
return iconv($from, $to, $string); |
|||
} |
|||
} elseif (function_exists('mb_convert_encoding')) { |
|||
function _twig_convert_encoding($string, $to, $from) |
|||
{ |
|||
return mb_convert_encoding($string, $to, $from); |
|||
} |
|||
} 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'.substr('00'.bin2hex($char), -2); |
|||
} |
|||
|
|||
// \uHHHH |
|||
$char = _twig_convert_encoding($char, 'UTF-16BE', 'UTF-8'); |
|||
|
|||
return '\\u'.substr('0000'.bin2hex($char), -4); |
|||
} |
|||
|
|||
// add multibyte extensions if possible |
|||
if (function_exists('mb_get_info')) { |
|||
/** |
|||
* Returns the length of a PHP variable. |
|||
* |
|||
* @param Twig_Environment $env A Twig_Environment instance |
|||
* @param mixed $thing A PHP variable |
|||
* |
|||
* @return integer 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 PHP variable. |
|||
* |
|||
* @param Twig_Environment $env A Twig_Environment instance |
|||
* @param mixed $thing A PHP variable |
|||
* |
|||
* @return integer 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)); |
|||
} |
|||
} |
|||
|
|||
/* used internally */ |
|||
function twig_ensure_traversable($seq) |
|||
{ |
|||
if (is_array($seq) || (is_object($seq) && $seq instanceof Traversable)) { |
|||
return $seq; |
|||
} else { |
|||
return array(); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Checks that a variable points to the same memory address than another one. |
|||
* |
|||
* <pre> |
|||
* {% if foo.attribute is sameas(false) %} |
|||
* the foo attribute really is the ``false`` PHP value |
|||
* {% endif %} |
|||
* </pre> |
|||
* |
|||
* @param mixed $value A PHP variable |
|||
* @param mixed $test The PHP variable to test against |
|||
* |
|||
* @return Boolean true if the values are the same, false otherwise |
|||
*/ |
|||
function twig_test_sameas($value, $test) |
|||
{ |
|||
return $value === $test; |
|||
} |
|||
|
|||
/** |
|||
* Checks that a variable is null. |
|||
* |
|||
* <pre> |
|||
* {{ var is none }} |
|||
* </pre> |
|||
* |
|||
* @param mixed $value a PHP variable. |
|||
* |
|||
* @return Boolean true if the value is null, false otherwise |
|||
*/ |
|||
function twig_test_none($value) |
|||
{ |
|||
return null === $value; |
|||
} |
|||
|
|||
/** |
|||
* Checks if a variable is divisible by a number. |
|||
* |
|||
* <pre> |
|||
* {% if loop.index is divisibleby(3) %} |
|||
* </pre> |
|||
* |
|||
* @param integer $value A PHP value |
|||
* @param integer $num A number |
|||
* |
|||
* @return Boolean true if the value is divisible by the number, false otherwise |
|||
*/ |
|||
function twig_test_divisibleby($value, $num) |
|||
{ |
|||
return 0 == $value % $num; |
|||
} |
|||
|
|||
/** |
|||
* Checks if a number is even. |
|||
* |
|||
* <pre> |
|||
* {{ var is even }} |
|||
* </pre> |
|||
* |
|||
* @param integer $value An integer |
|||
* |
|||
* @return Boolean true if the value is even, false otherwise |
|||
*/ |
|||
function twig_test_even($value) |
|||
{ |
|||
return $value % 2 == 0; |
|||
} |
|||
|
|||
/** |
|||
* Checks if a number is odd. |
|||
* |
|||
* <pre> |
|||
* {{ var is odd }} |
|||
* </pre> |
|||
* |
|||
* @param integer $value An integer |
|||
* |
|||
* @return Boolean true if the value is odd, false otherwise |
|||
*/ |
|||
function twig_test_odd($value) |
|||
{ |
|||
return $value % 2 == 1; |
|||
} |
|||
|
|||
/** |
|||
* Checks if a variable is the exact same value as a constant. |
|||
* |
|||
* <pre> |
|||
* {% if post.status is constant('Post::PUBLISHED') %} |
|||
* the status attribute is exactly the same as Post::PUBLISHED |
|||
* {% endif %} |
|||
* </pre> |
|||
* |
|||
* @param mixed $value A PHP value |
|||
* @param mixed $constant The constant to test against |
|||
* |
|||
* @return Boolean true if the value is the same as the constant, false otherwise |
|||
*/ |
|||
function twig_test_constant($value, $constant) |
|||
{ |
|||
return constant($constant) === $value; |
|||
} |
|||
|
|||
/** |
|||
* Checks if a variable is defined in the current context. |
|||
* |
|||
* <pre> |
|||
* {# defined works with variable names #} |
|||
* {% if foo is defined %} |
|||
* {# ... #} |
|||
* {% endif %} |
|||
* </pre> |
|||
* |
|||
* @param mixed $name A PHP variable |
|||
* @param array $context The current context |
|||
* |
|||
* @return Boolean true if the value is defined, false otherwise |
|||
*/ |
|||
function twig_test_defined($name, $context) |
|||
{ |
|||
return array_key_exists($name, $context); |
|||
} |
|||
|
|||
/** |
|||
* 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 PHP variable |
|||
* |
|||
* @return Boolean true if the value is empty, false otherwise |
|||
*/ |
|||
function twig_test_empty($value) |
|||
{ |
|||
if ($value instanceof Countable) { |
|||
return 0 == count($value); |
|||
} |
|||
return false === $value || (empty($value) && '0' != $value); |
|||
} |
@ -0,0 +1,77 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* 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_Escaper extends Twig_Extension |
|||
{ |
|||
protected $autoescape; |
|||
|
|||
public function __construct($autoescape = true) |
|||
{ |
|||
$this->autoescape = $autoescape; |
|||
} |
|||
|
|||
/** |
|||
* Returns the token parser instances to add to the existing list. |
|||
* |
|||
* @return array An array of Twig_TokenParserInterface or Twig_TokenParserBrokerInterface instances |
|||
*/ |
|||
public function getTokenParsers() |
|||
{ |
|||
return array(new Twig_TokenParser_AutoEscape()); |
|||
} |
|||
|
|||
/** |
|||
* Returns the node visitor instances to add to the existing list. |
|||
* |
|||
* @return array An array of Twig_NodeVisitorInterface instances |
|||
*/ |
|||
public function getNodeVisitors() |
|||
{ |
|||
return array(new Twig_NodeVisitor_Escaper()); |
|||
} |
|||
|
|||
/** |
|||
* Returns a list of filters to add to the existing list. |
|||
* |
|||
* @return array An array of filters |
|||
*/ |
|||
public function getFilters() |
|||
{ |
|||
return array( |
|||
'raw' => new Twig_Filter_Function('twig_raw_filter', array('is_safe' => array('all'))), |
|||
); |
|||
} |
|||
|
|||
public function isGlobal() |
|||
{ |
|||
return $this->autoescape; |
|||
} |
|||
|
|||
/** |
|||
* Returns the name of the extension. |
|||
* |
|||
* @return string The extension name |
|||
*/ |
|||
public function getName() |
|||
{ |
|||
return 'escaper'; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Marks a variable as being safe. |
|||
* |
|||
* @param string $string A PHP variable |
|||
*/ |
|||
function twig_raw_filter($string) |
|||
{ |
|||
return $string; |
|||
} |
|||
|
@ -0,0 +1,35 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of Twig. |
|||
* |
|||
* (c) 2010 Fabien Potencier |
|||
* |
|||
* For the full copyright and license information, please view the LICENSE |
|||
* file that was distributed with this source code. |
|||
*/ |
|||
class Twig_Extension_Optimizer extends Twig_Extension |
|||
{ |
|||
protected $optimizers; |
|||
|
|||
public function __construct($optimizers = -1) |
|||
{ |
|||
$this->optimizers = $optimizers; |
|||
} |
|||
|
|||
/** |
|||
* {@inheritdoc} |
|||
*/ |
|||
public function getNodeVisitors() |
|||
{ |
|||
return array(new Twig_NodeVisitor_Optimizer($this->optimizers)); |
|||
} |
|||
|
|||
/** |
|||
* {@inheritdoc} |
|||
*/ |
|||
public function getName() |
|||
{ |
|||
return 'optimizer'; |
|||
} |
|||
} |
@ -0,0 +1,112 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* 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_Sandbox extends Twig_Extension |
|||
{ |
|||
protected $sandboxedGlobally; |
|||
protected $sandboxed; |
|||
protected $policy; |
|||
|
|||
public function __construct(Twig_Sandbox_SecurityPolicyInterface $policy, $sandboxed = false) |
|||
{ |
|||
$this->policy = $policy; |
|||
$this->sandboxedGlobally = $sandboxed; |
|||
} |
|||
|
|||
/** |
|||
* Returns the token parser instances to add to the existing list. |
|||
* |
|||
* @return array An array of Twig_TokenParserInterface or Twig_TokenParserBrokerInterface instances |
|||
*/ |
|||
public function getTokenParsers() |
|||
{ |
|||
return array(new Twig_TokenParser_Sandbox()); |
|||
} |
|||
|
|||
/** |
|||
* Returns the node visitor instances to add to the existing list. |
|||
* |
|||
* @return array An array of Twig_NodeVisitorInterface instances |
|||
*/ |
|||
public function getNodeVisitors() |
|||
{ |
|||
return array(new Twig_NodeVisitor_Sandbox()); |
|||
} |
|||
|
|||
public function enableSandbox() |
|||
{ |
|||
$this->sandboxed = true; |
|||
} |
|||
|
|||
public function disableSandbox() |
|||
{ |
|||
$this->sandboxed = false; |
|||
} |
|||
|
|||
public function isSandboxed() |
|||
{ |
|||
return $this->sandboxedGlobally || $this->sandboxed; |
|||
} |
|||
|
|||
public function isSandboxedGlobally() |
|||
{ |
|||
return $this->sandboxedGlobally; |
|||
} |
|||
|
|||
public function setSecurityPolicy(Twig_Sandbox_SecurityPolicyInterface $policy) |
|||
{ |
|||
$this->policy = $policy; |
|||
} |
|||
|
|||
public function getSecurityPolicy() |
|||
{ |
|||
return $this->policy; |
|||
} |
|||
|
|||
public function checkSecurity($tags, $filters, $functions) |
|||
{ |
|||
if ($this->isSandboxed()) { |
|||
$this->policy->checkSecurity($tags, $filters, $functions); |
|||
} |
|||
} |
|||
|
|||
public function checkMethodAllowed($obj, $method) |
|||
{ |
|||
if ($this->isSandboxed()) { |
|||
$this->policy->checkMethodAllowed($obj, $method); |
|||
} |
|||
} |
|||
|
|||
public function checkPropertyAllowed($obj, $method) |
|||
{ |
|||
if ($this->isSandboxed()) { |
|||
$this->policy->checkPropertyAllowed($obj, $method); |
|||
} |
|||
} |
|||
|
|||
public function ensureToStringAllowed($obj) |
|||
{ |
|||
if (is_object($obj)) { |
|||
$this->policy->checkMethodAllowed($obj, '__toString'); |
|||
} |
|||
|
|||
return $obj; |
|||
} |
|||
|
|||
/** |
|||
* Returns the name of the extension. |
|||
* |
|||
* @return string The extension name |
|||
*/ |
|||
public function getName() |
|||
{ |
|||
return 'sandbox'; |
|||
} |
|||
} |
@ -0,0 +1,84 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* 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. |
|||
*/ |
|||
|
|||
/** |
|||
* Interface implemented by extension classes. |
|||
* |
|||
* @package twig |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
*/ |
|||
interface Twig_ExtensionInterface |
|||
{ |
|||
/** |
|||
* Initializes the runtime environment. |
|||
* |
|||
* This is where you can load some file that contains filter functions for instance. |
|||
* |
|||
* @param Twig_Environment $environment The current Twig_Environment instance |
|||
*/ |
|||
function initRuntime(Twig_Environment $environment); |
|||
|
|||
/** |
|||
* Returns the token parser instances to add to the existing list. |
|||
* |
|||
* @return array An array of Twig_TokenParserInterface or Twig_TokenParserBrokerInterface instances |
|||
*/ |
|||
function getTokenParsers(); |
|||
|
|||
/** |
|||
* Returns the node visitor instances to add to the existing list. |
|||
* |
|||
* @return array An array of Twig_NodeVisitorInterface instances |
|||
*/ |
|||
function getNodeVisitors(); |
|||
|
|||
/** |
|||
* Returns a list of filters to add to the existing list. |
|||
* |
|||
* @return array An array of filters |
|||
*/ |
|||
function getFilters(); |
|||
|
|||
/** |
|||
* Returns a list of tests to add to the existing list. |
|||
* |
|||
* @return array An array of tests |
|||
*/ |
|||
function getTests(); |
|||
|
|||
/** |
|||
* Returns a list of functions to add to the existing list. |
|||
* |
|||
* @return array An array of functions |
|||
*/ |
|||
function getFunctions(); |
|||
|
|||
/** |
|||
* Returns a list of operators to add to the existing list. |
|||
* |
|||
* @return array An array of operators |
|||
*/ |
|||
function getOperators(); |
|||
|
|||
/** |
|||
* Returns a list of global functions to add to the existing list. |
|||
* |
|||
* @return array An array of global functions |
|||
*/ |
|||
function getGlobals(); |
|||
|
|||
/** |
|||
* Returns the name of the extension. |
|||
* |
|||
* @return string The extension name |
|||
*/ |
|||
function getName(); |
|||
} |
@ -0,0 +1,58 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* 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. |
|||
*/ |
|||
|
|||
/** |
|||
* Represents a template filter. |
|||
* |
|||
* @package twig |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
*/ |
|||
abstract class Twig_Filter implements Twig_FilterInterface |
|||
{ |
|||
protected $options; |
|||
|
|||
public function __construct(array $options = array()) |
|||
{ |
|||
$this->options = array_merge(array( |
|||
'needs_environment' => false, |
|||
'needs_context' => false, |
|||
'pre_escape' => null, |
|||
), $options); |
|||
} |
|||
|
|||
public function needsEnvironment() |
|||
{ |
|||
return $this->options['needs_environment']; |
|||
} |
|||
|
|||
public function needsContext() |
|||
{ |
|||
return $this->options['needs_context']; |
|||
} |
|||
|
|||
public function getSafe(Twig_Node $filterArgs) |
|||
{ |
|||
if (isset($this->options['is_safe'])) { |
|||
return $this->options['is_safe']; |
|||
} |
|||
|
|||
if (isset($this->options['is_safe_callback'])) { |
|||
return call_user_func($this->options['is_safe_callback'], $filterArgs); |
|||
} |
|||
|
|||
return array(); |
|||
} |
|||
|
|||
public function getPreEscape() |
|||
{ |
|||
return $this->options['pre_escape']; |
|||
} |
|||
} |
@ -0,0 +1,33 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* 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. |
|||
*/ |
|||
|
|||
/** |
|||
* Represents a function template filter. |
|||
* |
|||
* @package twig |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
*/ |
|||
class Twig_Filter_Function extends Twig_Filter |
|||
{ |
|||
protected $function; |
|||
|
|||
public function __construct($function, array $options = array()) |
|||
{ |
|||
parent::__construct($options); |
|||
|
|||
$this->function = $function; |
|||
} |
|||
|
|||
public function compile() |
|||
{ |
|||
return $this->function; |
|||
} |
|||
} |
@ -0,0 +1,34 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* 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. |
|||
*/ |
|||
|
|||
/** |
|||
* Represents a method template filter. |
|||
* |
|||
* @package twig |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
*/ |
|||
class Twig_Filter_Method extends Twig_Filter |
|||
{ |
|||
protected $extension, $method; |
|||
|
|||
public function __construct(Twig_ExtensionInterface $extension, $method, array $options = array()) |
|||
{ |
|||
parent::__construct($options); |
|||
|
|||
$this->extension = $extension; |
|||
$this->method = $method; |
|||
} |
|||
|
|||
public function compile() |
|||
{ |
|||
return sprintf('$this->env->getExtension(\'%s\')->%s', $this->extension->getName(), $this->method); |
|||
} |
|||
} |
@ -0,0 +1,34 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of Twig. |
|||
* |
|||
* (c) 2010 Fabien Potencier |
|||
* |
|||
* For the full copyright and license information, please view the LICENSE |
|||
* file that was distributed with this source code. |
|||
*/ |
|||
|
|||
/** |
|||
* Represents a template filter. |
|||
* |
|||
* @package twig |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
*/ |
|||
interface Twig_FilterInterface |
|||
{ |
|||
/** |
|||
* Compiles a filter. |
|||
* |
|||
* @return string The PHP code for the filter |
|||
*/ |
|||
function compile(); |
|||
|
|||
function needsEnvironment(); |
|||
|
|||
function needsContext(); |
|||
|
|||
function getSafe(Twig_Node $filterArgs); |
|||
|
|||
function getPreEscape(); |
|||
} |
@ -0,0 +1,52 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of Twig. |
|||
* |
|||
* (c) 2010 Fabien Potencier |
|||
* |
|||
* For the full copyright and license information, please view the LICENSE |
|||
* file that was distributed with this source code. |
|||
*/ |
|||
|
|||
/** |
|||
* Represents a template function. |
|||
* |
|||
* @package twig |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
*/ |
|||
abstract class Twig_Function implements Twig_FunctionInterface |
|||
{ |
|||
protected $options; |
|||
|
|||
public function __construct(array $options = array()) |
|||
{ |
|||
$this->options = array_merge(array( |
|||
'needs_environment' => false, |
|||
'needs_context' => false, |
|||
), $options); |
|||
} |
|||
|
|||
public function needsEnvironment() |
|||
{ |
|||
return $this->options['needs_environment']; |
|||
} |
|||
|
|||
public function needsContext() |
|||
{ |
|||
return $this->options['needs_context']; |
|||
} |
|||
|
|||
public function getSafe(Twig_Node $functionArgs) |
|||
{ |
|||
if (isset($this->options['is_safe'])) { |
|||
return $this->options['is_safe']; |
|||
} |
|||
|
|||
if (isset($this->options['is_safe_callback'])) { |
|||
return call_user_func($this->options['is_safe_callback'], $functionArgs); |
|||
} |
|||
|
|||
return array(); |
|||
} |
|||
} |
@ -0,0 +1,34 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of Twig. |
|||
* |
|||
* (c) 2009 Fabien Potencier |
|||
* (c) 2010 Arnaud Le Blanc |
|||
* |
|||
* For the full copyright and license information, please view the LICENSE |
|||
* file that was distributed with this source code. |
|||
*/ |
|||
|
|||
/** |
|||
* Represents a function template function. |
|||
* |
|||
* @package twig |
|||
* @author Arnaud Le Blanc <arnaud.lb@gmail.com> |
|||
*/ |
|||
class Twig_Function_Function extends Twig_Function |
|||
{ |
|||
protected $function; |
|||
|
|||
public function __construct($function, array $options = array()) |
|||
{ |
|||
parent::__construct($options); |
|||
|
|||
$this->function = $function; |
|||
} |
|||
|
|||
public function compile() |
|||
{ |
|||
return $this->function; |
|||
} |
|||
} |
@ -0,0 +1,35 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of Twig. |
|||
* |
|||
* (c) 2009 Fabien Potencier |
|||
* (c) 2010 Arnaud Le Blanc |
|||
* |
|||
* For the full copyright and license information, please view the LICENSE |
|||
* file that was distributed with this source code. |
|||
*/ |
|||
|
|||
/** |
|||
* Represents a method template function. |
|||
* |
|||
* @package twig |
|||
* @author Arnaud Le Blanc <arnaud.lb@gmail.com> |
|||
*/ |
|||
class Twig_Function_Method extends Twig_Function |
|||
{ |
|||
protected $extension, $method; |
|||
|
|||
public function __construct(Twig_ExtensionInterface $extension, $method, array $options = array()) |
|||
{ |
|||
parent::__construct($options); |
|||
|
|||
$this->extension = $extension; |
|||
$this->method = $method; |
|||
} |
|||
|
|||
public function compile() |
|||
{ |
|||
return sprintf('$this->env->getExtension(\'%s\')->%s', $this->extension->getName(), $this->method); |
|||
} |
|||
} |
@ -0,0 +1,33 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of Twig. |
|||
* |
|||
* (c) 2010 Fabien Potencier |
|||
* (c) 2010 Arnaud Le Blanc |
|||
* |
|||
* For the full copyright and license information, please view the LICENSE |
|||
* file that was distributed with this source code. |
|||
*/ |
|||
|
|||
/** |
|||
* Represents a template function. |
|||
* |
|||
* @package twig |
|||
* @author Arnaud Le Blanc <arnaud.lb@gmail.com> |
|||
*/ |
|||
interface Twig_FunctionInterface |
|||
{ |
|||
/** |
|||
* Compiles a function. |
|||
* |
|||
* @return string The PHP code for the function |
|||
*/ |
|||
function compile(); |
|||
|
|||
function needsEnvironment(); |
|||
|
|||
function needsContext(); |
|||
|
|||
function getSafe(Twig_Node $filterArgs); |
|||
} |
@ -0,0 +1,328 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of Twig. |
|||
* |
|||
* (c) 2009 Fabien Potencier |
|||
* (c) 2009 Armin Ronacher |
|||
* |
|||
* For the full copyright and license information, please view the LICENSE |
|||
* file that was distributed with this source code. |
|||
*/ |
|||
|
|||
/** |
|||
* Lexes a template string. |
|||
* |
|||
* @package twig |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
*/ |
|||
class Twig_Lexer implements Twig_LexerInterface |
|||
{ |
|||
protected $tokens; |
|||
protected $code; |
|||
protected $cursor; |
|||
protected $lineno; |
|||
protected $end; |
|||
protected $state; |
|||
protected $brackets; |
|||
|
|||
protected $env; |
|||
protected $filename; |
|||
protected $options; |
|||
protected $operatorRegex; |
|||
|
|||
const STATE_DATA = 0; |
|||
const STATE_BLOCK = 1; |
|||
const STATE_VAR = 2; |
|||
|
|||
const REGEX_NAME = '/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/A'; |
|||
const REGEX_NUMBER = '/[0-9]+(?:\.[0-9]+)?/A'; |
|||
const REGEX_STRING = '/"([^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\'/As'; |
|||
const PUNCTUATION = '()[]{}?:.,|'; |
|||
|
|||
public function __construct(Twig_Environment $env, array $options = array()) |
|||
{ |
|||
$this->env = $env; |
|||
|
|||
$this->options = array_merge(array( |
|||
'tag_comment' => array('{#', '#}'), |
|||
'tag_block' => array('{%', '%}'), |
|||
'tag_variable' => array('{{', '}}'), |
|||
'whitespace_trim' => '-', |
|||
), $options); |
|||
} |
|||
|
|||
/** |
|||
* 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 |
|||
*/ |
|||
public function tokenize($code, $filename = null) |
|||
{ |
|||
if (function_exists('mb_internal_encoding') && ((int) ini_get('mbstring.func_overload')) & 2) { |
|||
$mbEncoding = mb_internal_encoding(); |
|||
mb_internal_encoding('ASCII'); |
|||
} |
|||
|
|||
$this->code = str_replace(array("\r\n", "\r"), "\n", $code); |
|||
$this->filename = $filename; |
|||
$this->cursor = 0; |
|||
$this->lineno = 1; |
|||
$this->end = strlen($this->code); |
|||
$this->tokens = array(); |
|||
$this->state = self::STATE_DATA; |
|||
$this->brackets = array(); |
|||
|
|||
while ($this->cursor < $this->end) { |
|||
// dispatch to the lexing functions depending |
|||
// on the current state |
|||
switch ($this->state) { |
|||
case self::STATE_DATA: |
|||
$this->lexData(); |
|||
break; |
|||
|
|||
case self::STATE_BLOCK: |
|||
$this->lexBlock(); |
|||
break; |
|||
|
|||
case self::STATE_VAR: |
|||
$this->lexVar(); |
|||
break; |
|||
} |
|||
} |
|||
|
|||
$this->pushToken(Twig_Token::EOF_TYPE); |
|||
|
|||
if (!empty($this->brackets)) { |
|||
list($expect, $lineno) = array_pop($this->brackets); |
|||
throw new Twig_Error_Syntax(sprintf('Unclosed "%s"', $expect), $lineno, $this->filename); |
|||
} |
|||
|
|||
if (isset($mbEncoding)) { |
|||
mb_internal_encoding($mbEncoding); |
|||
} |
|||
|
|||
return new Twig_TokenStream($this->tokens, $this->filename); |
|||
} |
|||
|
|||
protected function lexData() |
|||
{ |
|||
$pos = $this->end; |
|||
$append = ''; |
|||
|
|||
// Find the first token after the cursor |
|||
foreach (array('tag_comment', 'tag_variable', 'tag_block') as $type) { |
|||
$tmpPos = strpos($this->code, $this->options[$type][0], $this->cursor); |
|||
if (false !== $tmpPos && $tmpPos < $pos) { |
|||
$trimBlock = false; |
|||
$append = ''; |
|||
$pos = $tmpPos; |
|||
$token = $this->options[$type][0]; |
|||
if (strpos($this->code, $this->options['whitespace_trim'], $pos) === ($pos + strlen($token))) { |
|||
$trimBlock = true; |
|||
$append = $this->options['whitespace_trim']; |
|||
} |
|||
} |
|||
} |
|||
|
|||
// if no matches are left we return the rest of the template as simple text token |
|||
if ($pos === $this->end) { |
|||
$this->pushToken(Twig_Token::TEXT_TYPE, substr($this->code, $this->cursor)); |
|||
$this->cursor = $this->end; |
|||
return; |
|||
} |
|||
|
|||
// push the template text first |
|||
$text = $textContent = substr($this->code, $this->cursor, $pos - $this->cursor); |
|||
if (true === $trimBlock) { |
|||
$text = rtrim($text); |
|||
} |
|||
$this->pushToken(Twig_Token::TEXT_TYPE, $text); |
|||
$this->moveCursor($textContent.$token.$append); |
|||
|
|||
switch ($token) { |
|||
case $this->options['tag_comment'][0]: |
|||
$this->lexComment(); |
|||
break; |
|||
|
|||
case $this->options['tag_block'][0]: |
|||
// raw data? |
|||
if (preg_match('/\s*raw\s*'.preg_quote($this->options['tag_block'][1], '/').'/As', $this->code, $match, null, $this->cursor)) { |
|||
$this->moveCursor($match[0]); |
|||
$this->lexRawData(); |
|||
$this->state = self::STATE_DATA; |
|||
// {% line \d+ %} |
|||
} else if (preg_match('/\s*line\s+(\d+)\s*'.preg_quote($this->options['tag_block'][1], '/').'/As', $this->code, $match, null, $this->cursor)) { |
|||
$this->moveCursor($match[0]); |
|||
$this->lineno = (int) $match[1]; |
|||
$this->state = self::STATE_DATA; |
|||
} else { |
|||
$this->pushToken(Twig_Token::BLOCK_START_TYPE); |
|||
$this->state = self::STATE_BLOCK; |
|||
} |
|||
break; |
|||
|
|||
case $this->options['tag_variable'][0]: |
|||
$this->pushToken(Twig_Token::VAR_START_TYPE); |
|||
$this->state = self::STATE_VAR; |
|||
break; |
|||
} |
|||
} |
|||
|
|||
protected function lexBlock() |
|||
{ |
|||
$trimTag = preg_quote($this->options['whitespace_trim'].$this->options['tag_block'][1], '/'); |
|||
$endTag = preg_quote($this->options['tag_block'][1], '/'); |
|||
|
|||
if (empty($this->brackets) && preg_match('/\s*(?:'.$trimTag.'\s*|\s*'.$endTag.')\n?/A', $this->code, $match, null, $this->cursor)) { |
|||
$this->pushToken(Twig_Token::BLOCK_END_TYPE); |
|||
$this->moveCursor($match[0]); |
|||
$this->state = self::STATE_DATA; |
|||
} else { |
|||
$this->lexExpression(); |
|||
} |
|||
} |
|||
|
|||
protected function lexVar() |
|||
{ |
|||
$trimTag = preg_quote($this->options['whitespace_trim'].$this->options['tag_variable'][1], '/'); |
|||
$endTag = preg_quote($this->options['tag_variable'][1], '/'); |
|||
|
|||
if (empty($this->brackets) && preg_match('/\s*'.$trimTag.'\s*|\s*'.$endTag.'/A', $this->code, $match, null, $this->cursor)) { |
|||
$this->pushToken(Twig_Token::VAR_END_TYPE); |
|||
$this->moveCursor($match[0]); |
|||
$this->state = self::STATE_DATA; |
|||
} else { |
|||
$this->lexExpression(); |
|||
} |
|||
} |
|||
|
|||
protected function lexExpression() |
|||
{ |
|||
// whitespace |
|||
if (preg_match('/\s+/A', $this->code, $match, null, $this->cursor)) { |
|||
$this->moveCursor($match[0]); |
|||
|
|||
if ($this->cursor >= $this->end) { |
|||
throw new Twig_Error_Syntax(sprintf('Unexpected end of file: Unclosed "%s"', $this->state === self::STATE_BLOCK ? 'block' : 'variable')); |
|||
} |
|||
} |
|||
|
|||
// operators |
|||
if (preg_match($this->getOperatorRegex(), $this->code, $match, null, $this->cursor)) { |
|||
$this->pushToken(Twig_Token::OPERATOR_TYPE, $match[0]); |
|||
$this->moveCursor($match[0]); |
|||
} |
|||
// names |
|||
elseif (preg_match(self::REGEX_NAME, $this->code, $match, null, $this->cursor)) { |
|||
$this->pushToken(Twig_Token::NAME_TYPE, $match[0]); |
|||
$this->moveCursor($match[0]); |
|||
} |
|||
// numbers |
|||
elseif (preg_match(self::REGEX_NUMBER, $this->code, $match, null, $this->cursor)) { |
|||
$this->pushToken(Twig_Token::NUMBER_TYPE, ctype_digit($match[0]) ? (int) $match[0] : (float) $match[0]); |
|||
$this->moveCursor($match[0]); |
|||
} |
|||
// punctuation |
|||
elseif (false !== strpos(self::PUNCTUATION, $this->code[$this->cursor])) { |
|||
// opening bracket |
|||
if (false !== strpos('([{', $this->code[$this->cursor])) { |
|||
$this->brackets[] = array($this->code[$this->cursor], $this->lineno); |
|||
} |
|||
// closing bracket |
|||
elseif (false !== strpos(')]}', $this->code[$this->cursor])) { |
|||
if (empty($this->brackets)) { |
|||
throw new Twig_Error_Syntax(sprintf('Unexpected "%s"', $this->code[$this->cursor]), $this->lineno, $this->filename); |
|||
} |
|||
|
|||
list($expect, $lineno) = array_pop($this->brackets); |
|||
if ($this->code[$this->cursor] != strtr($expect, '([{', ')]}')) { |
|||
throw new Twig_Error_Syntax(sprintf('Unclosed "%s"', $expect), $lineno, $this->filename); |
|||
} |
|||
} |
|||
|
|||
$this->pushToken(Twig_Token::PUNCTUATION_TYPE, $this->code[$this->cursor]); |
|||
++$this->cursor; |
|||
} |
|||
// strings |
|||
elseif (preg_match(self::REGEX_STRING, $this->code, $match, null, $this->cursor)) { |
|||
$this->pushToken(Twig_Token::STRING_TYPE, stripcslashes(substr($match[0], 1, -1))); |
|||
$this->moveCursor($match[0]); |
|||
} |
|||
// unlexable |
|||
else { |
|||
throw new Twig_Error_Syntax(sprintf('Unexpected character "%s"', $this->code[$this->cursor]), $this->lineno, $this->filename); |
|||
} |
|||
} |
|||
|
|||
protected function lexRawData() |
|||
{ |
|||
if (!preg_match('/'.preg_quote($this->options['tag_block'][0], '/').'\s*endraw\s*'.preg_quote($this->options['tag_block'][1], '/').'/s', $this->code, $match, PREG_OFFSET_CAPTURE, $this->cursor)) { |
|||
throw new Twig_Error_Syntax(sprintf('Unexpected end of file: Unclosed "block"')); |
|||
} |
|||
$text = substr($this->code, $this->cursor, $match[0][1] - $this->cursor); |
|||
$this->pushToken(Twig_Token::TEXT_TYPE, $text); |
|||
$this->moveCursor($text.$match[0][0]); |
|||
} |
|||
|
|||
protected function lexComment() |
|||
{ |
|||
$commentEndRegex = '/(?:'.preg_quote($this->options['whitespace_trim'], '/') |
|||
.preg_quote($this->options['tag_comment'][1], '/').'\s*|' |
|||
.preg_quote($this->options['tag_comment'][1], '/').')\n?/s'; |
|||
|
|||
if (!preg_match($commentEndRegex, $this->code, $match, PREG_OFFSET_CAPTURE, $this->cursor)) { |
|||
throw new Twig_Error_Syntax('Unclosed comment', $this->lineno, $this->filename); |
|||
} |
|||
|
|||
$this->moveCursor(substr($this->code, $this->cursor, $match[0][1] - $this->cursor).$match[0][0]); |
|||
} |
|||
|
|||
protected function pushToken($type, $value = '') |
|||
{ |
|||
// do not push empty text tokens |
|||
if (Twig_Token::TEXT_TYPE === $type && '' === $value) { |
|||
return; |
|||
} |
|||
|
|||
$this->tokens[] = new Twig_Token($type, $value, $this->lineno); |
|||
} |
|||
|
|||
protected function moveCursor($text) |
|||
{ |
|||
$this->cursor += strlen($text); |
|||
$this->lineno += substr_count($text, "\n"); |
|||
} |
|||
|
|||
protected function getOperatorRegex() |
|||
{ |
|||
if (null !== $this->operatorRegex) { |
|||
return $this->operatorRegex; |
|||
} |
|||
|
|||
$operators = array_merge( |
|||
array('='), |
|||
array_keys($this->env->getUnaryOperators()), |
|||
array_keys($this->env->getBinaryOperators()) |
|||
); |
|||
|
|||
$operators = array_combine($operators, array_map('strlen', $operators)); |
|||
arsort($operators); |
|||
|
|||
$regex = array(); |
|||
foreach ($operators as $operator => $length) { |
|||
// 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, '/').'(?=[ ()])'; |
|||
} else { |
|||
$regex[] = preg_quote($operator, '/'); |
|||
} |
|||
} |
|||
|
|||
return $this->operatorRegex = '/'.implode('|', $regex).'/A'; |
|||
} |
|||
} |
@ -0,0 +1,29 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* 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. |
|||
*/ |
|||
|
|||
/** |
|||
* Interface implemented by lexer classes. |
|||
* |
|||
* @package twig |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
*/ |
|||
interface 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 |
|||
*/ |
|||
function tokenize($code, $filename = null); |
|||
} |
@ -0,0 +1,95 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* 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. |
|||
*/ |
|||
|
|||
/** |
|||
* Loads a template from an array. |
|||
* |
|||
* When using this loader with a cache mechanism, you should know that a new cache |
|||
* key is generated each time a template content "changes" (the cache key being the |
|||
* source code of the template). If you don't want to see your cache grows out of |
|||
* control, you need to take care of clearing the old cache file by yourself. |
|||
* |
|||
* @package twig |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
*/ |
|||
class Twig_Loader_Array implements Twig_LoaderInterface |
|||
{ |
|||
protected $templates; |
|||
|
|||
/** |
|||
* Constructor. |
|||
* |
|||
* @param array $templates An array of templates (keys are the names, and values are the source code) |
|||
* |
|||
* @see Twig_Loader |
|||
*/ |
|||
public function __construct(array $templates) |
|||
{ |
|||
$this->templates = array(); |
|||
foreach ($templates as $name => $template) { |
|||
$this->templates[$name] = $template; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Adds or overrides a template. |
|||
* |
|||
* @param string $name The template name |
|||
* @param string $template The template source |
|||
*/ |
|||
public function setTemplate($name, $template) |
|||
{ |
|||
$this->templates[$name] = $template; |
|||
} |
|||
|
|||
/** |
|||
* Gets the source code of a template, given its name. |
|||
* |
|||
* @param string $name The name of the template to load |
|||
* |
|||
* @return string The template source code |
|||
*/ |
|||
public function getSource($name) |
|||
{ |
|||
if (!isset($this->templates[$name])) { |
|||
throw new Twig_Error_Loader(sprintf('Template "%s" is not defined.', $name)); |
|||
} |
|||
|
|||
return $this->templates[$name]; |
|||
} |
|||
|
|||
/** |
|||
* Gets the cache key to use for the cache for a given template name. |
|||
* |
|||
* @param string $name The name of the template to load |
|||
* |
|||
* @return string The cache key |
|||
*/ |
|||
public function getCacheKey($name) |
|||
{ |
|||
if (!isset($this->templates[$name])) { |
|||
throw new Twig_Error_Loader(sprintf('Template "%s" is not defined.', $name)); |
|||
} |
|||
|
|||
return $this->templates[$name]; |
|||
} |
|||
|
|||
/** |
|||
* Returns true if the template is still fresh. |
|||
* |
|||
* @param string $name The template name |
|||
* @param timestamp $time The last modification time of the cached template |
|||
*/ |
|||
public function isFresh($name, $time) |
|||
{ |
|||
return true; |
|||
} |
|||
} |
@ -0,0 +1,100 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of Twig. |
|||
* |
|||
* (c) 2011 Fabien Potencier |
|||
* |
|||
* For the full copyright and license information, please view the LICENSE |
|||
* file that was distributed with this source code. |
|||
*/ |
|||
|
|||
/** |
|||
* Loads templates from other loaders. |
|||
* |
|||
* @package twig |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
*/ |
|||
class Twig_Loader_Chain implements Twig_LoaderInterface |
|||
{ |
|||
protected $loaders; |
|||
|
|||
/** |
|||
* Constructor. |
|||
* |
|||
* @param Twig_LoaderInterface[] $loaders An array of loader instances |
|||
*/ |
|||
public function __construct(array $loaders = array()) |
|||
{ |
|||
$this->loaders = array(); |
|||
foreach ($loaders as $loader) { |
|||
$this->addLoader($loader); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Adds a loader instance. |
|||
* |
|||
* @param Twig_LoaderInterface $loader A Loader instance |
|||
*/ |
|||
public function addLoader(Twig_LoaderInterface $loader) |
|||
{ |
|||
$this->loaders[] = $loader; |
|||
} |
|||
|
|||
/** |
|||
* Gets the source code of a template, given its name. |
|||
* |
|||
* @param string $name The name of the template to load |
|||
* |
|||
* @return string The template source code |
|||
*/ |
|||
public function getSource($name) |
|||
{ |
|||
foreach ($this->loaders as $loader) { |
|||
try { |
|||
return $loader->getSource($name); |
|||
} catch (Twig_Error_Loader $e) { |
|||
} |
|||
} |
|||
|
|||
throw new Twig_Error_Loader(sprintf('Template "%s" is not defined.', $name)); |
|||
} |
|||
|
|||
/** |
|||
* Gets the cache key to use for the cache for a given template name. |
|||
* |
|||
* @param string $name The name of the template to load |
|||
* |
|||
* @return string The cache key |
|||
*/ |
|||
public function getCacheKey($name) |
|||
{ |
|||
foreach ($this->loaders as $loader) { |
|||
try { |
|||
return $loader->getCacheKey($name); |
|||
} catch (Twig_Error_Loader $e) { |
|||
} |
|||
} |
|||
|
|||
throw new Twig_Error_Loader(sprintf('Template "%s" is not defined.', $name)); |
|||
} |
|||
|
|||
/** |
|||
* Returns true if the template is still fresh. |
|||
* |
|||
* @param string $name The template name |
|||
* @param timestamp $time The last modification time of the cached template |
|||
*/ |
|||
public function isFresh($name, $time) |
|||
{ |
|||
foreach ($this->loaders as $loader) { |
|||
try { |
|||
return $loader->isFresh($name, $time); |
|||
} catch (Twig_Error_Loader $e) { |
|||
} |
|||
} |
|||
|
|||
throw new Twig_Error_Loader(sprintf('Template "%s" is not defined.', $name)); |
|||
} |
|||
} |
@ -0,0 +1,152 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* 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. |
|||
*/ |
|||
|
|||
/** |
|||
* Loads template from the filesystem. |
|||
* |
|||
* @package twig |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
*/ |
|||
class Twig_Loader_Filesystem implements Twig_LoaderInterface |
|||
{ |
|||
protected $paths; |
|||
protected $cache; |
|||
|
|||
/** |
|||
* Constructor. |
|||
* |
|||
* @param string|array $paths A path or an array of paths where to look for templates |
|||
*/ |
|||
public function __construct($paths) |
|||
{ |
|||
$this->setPaths($paths); |
|||
} |
|||
|
|||
/** |
|||
* Returns the paths to the templates. |
|||
* |
|||
* @return array The array of paths where to look for templates |
|||
*/ |
|||
public function getPaths() |
|||
{ |
|||
return $this->paths; |
|||
} |
|||
|
|||
/** |
|||
* Sets the paths where templates are stored. |
|||
* |
|||
* @param string|array $paths A path or an array of paths where to look for templates |
|||
*/ |
|||
public function setPaths($paths) |
|||
{ |
|||
if (!is_array($paths)) { |
|||
$paths = array($paths); |
|||
} |
|||
|
|||
$this->paths = array(); |
|||
foreach ($paths as $path) { |
|||
$this->addPath($path); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Adds a path where templates are stored. |
|||
* |
|||
* @param string $path A path where to look for templates |
|||
*/ |
|||
public function addPath($path) |
|||
{ |
|||
// invalidate the cache |
|||
$this->cache = array(); |
|||
|
|||
if (!is_dir($path)) { |
|||
throw new Twig_Error_Loader(sprintf('The "%s" directory does not exist.', $path)); |
|||
} |
|||
|
|||
$this->paths[] = $path; |
|||
} |
|||
|
|||
/** |
|||
* Gets the source code of a template, given its name. |
|||
* |
|||
* @param string $name The name of the template to load |
|||
* |
|||
* @return string The template source code |
|||
*/ |
|||
public function getSource($name) |
|||
{ |
|||
return file_get_contents($this->findTemplate($name)); |
|||
} |
|||
|
|||
/** |
|||
* Gets the cache key to use for the cache for a given template name. |
|||
* |
|||
* @param string $name The name of the template to load |
|||
* |
|||
* @return string The cache key |
|||
*/ |
|||
public function getCacheKey($name) |
|||
{ |
|||
return $this->findTemplate($name); |
|||
} |
|||
|
|||
/** |
|||
* Returns true if the template is still fresh. |
|||
* |
|||
* @param string $name The template name |
|||
* @param timestamp $time The last modification time of the cached template |
|||
*/ |
|||
public function isFresh($name, $time) |
|||
{ |
|||
return filemtime($this->findTemplate($name)) < $time; |
|||
} |
|||
|
|||
protected function findTemplate($name) |
|||
{ |
|||
// normalize name |
|||
$name = preg_replace('#/{2,}#', '/', strtr($name, '\\', '/')); |
|||
|
|||
if (isset($this->cache[$name])) { |
|||
return $this->cache[$name]; |
|||
} |
|||
|
|||
$this->validateName($name); |
|||
|
|||
foreach ($this->paths as $path) { |
|||
if (is_file($path.'/'.$name)) { |
|||
return $this->cache[$name] = $path.'/'.$name; |
|||
} |
|||
} |
|||
|
|||
throw new Twig_Error_Loader(sprintf('Unable to find template "%s" (looked into: %s).', $name, implode(', ', $this->paths))); |
|||
} |
|||
|
|||
protected function validateName($name) |
|||
{ |
|||
if (false !== strpos($name, "\0")) { |
|||
throw new Twig_Error_Loader('A template name cannot contain NUL bytes.'); |
|||
} |
|||
|
|||
$parts = explode('/', $name); |
|||
$level = 0; |
|||
foreach ($parts as $part) { |
|||
if ('..' === $part) { |
|||
--$level; |
|||
} elseif ('.' !== $part) { |
|||
++$level; |
|||
} |
|||
|
|||
if ($level < 0) { |
|||
throw new Twig_Error_Loader(sprintf('Looks like you try to load a template outside configured directories (%s).', $name)); |
|||
} |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,59 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* 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. |
|||
*/ |
|||
|
|||
/** |
|||
* Loads a template from a string. |
|||
* |
|||
* When using this loader with a cache mechanism, you should know that a new cache |
|||
* key is generated each time a template content "changes" (the cache key being the |
|||
* source code of the template). If you don't want to see your cache grows out of |
|||
* control, you need to take care of clearing the old cache file by yourself. |
|||
* |
|||
* @package twig |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
*/ |
|||
class Twig_Loader_String implements Twig_LoaderInterface |
|||
{ |
|||
/** |
|||
* Gets the source code of a template, given its name. |
|||
* |
|||
* @param string $name The name of the template to load |
|||
* |
|||
* @return string The template source code |
|||
*/ |
|||
public function getSource($name) |
|||
{ |
|||
return $name; |
|||
} |
|||
|
|||
/** |
|||
* Gets the cache key to use for the cache for a given template name. |
|||
* |
|||
* @param string $name The name of the template to load |
|||
* |
|||
* @return string The cache key |
|||
*/ |
|||
public function getCacheKey($name) |
|||
{ |
|||
return $name; |
|||
} |
|||
|
|||
/** |
|||
* Returns true if the template is still fresh. |
|||
* |
|||
* @param string $name The template name |
|||
* @param timestamp $time The last modification time of the cached template |
|||
*/ |
|||
public function isFresh($name, $time) |
|||
{ |
|||
return true; |
|||
} |
|||
} |
@ -0,0 +1,45 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* 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. |
|||
*/ |
|||
|
|||
/** |
|||
* Interface all loaders must implement. |
|||
* |
|||
* @package twig |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
*/ |
|||
interface Twig_LoaderInterface |
|||
{ |
|||
/** |
|||
* Gets the source code of a template, given its name. |
|||
* |
|||
* @param string $name The name of the template to load |
|||
* |
|||
* @return string The template source code |
|||
*/ |
|||
function getSource($name); |
|||
|
|||
/** |
|||
* Gets the cache key to use for the cache for a given template name. |
|||
* |
|||
* @param string $name The name of the template to load |
|||
* |
|||
* @return string The cache key |
|||
*/ |
|||
function getCacheKey($name); |
|||
|
|||
/** |
|||
* Returns true if the template is still fresh. |
|||
* |
|||
* @param string $name The template name |
|||
* @param timestamp $time The last modification time of the cached template |
|||
*/ |
|||
function isFresh($name, $time); |
|||
} |
@ -0,0 +1,31 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of Twig. |
|||
* |
|||
* (c) 2010 Fabien Potencier |
|||
* |
|||
* For the full copyright and license information, please view the LICENSE |
|||
* file that was distributed with this source code. |
|||
*/ |
|||
|
|||
/** |
|||
* Marks a content as safe. |
|||
* |
|||
* @package twig |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
*/ |
|||
class Twig_Markup |
|||
{ |
|||
protected $content; |
|||
|
|||
public function __construct($content) |
|||
{ |
|||
$this->content = (string) $content; |
|||
} |
|||
|
|||
public function __toString() |
|||
{ |
|||
return $this->content; |
|||
} |
|||
} |
@ -0,0 +1,227 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of Twig. |
|||
* |
|||
* (c) 2009 Fabien Potencier |
|||
* (c) 2009 Armin Ronacher |
|||
* |
|||
* For the full copyright and license information, please view the LICENSE |
|||
* file that was distributed with this source code. |
|||
*/ |
|||
|
|||
/** |
|||
* Represents a node in the AST. |
|||
* |
|||
* @package twig |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
*/ |
|||
class Twig_Node implements Twig_NodeInterface, Countable, IteratorAggregate |
|||
{ |
|||
protected $nodes; |
|||
protected $attributes; |
|||
protected $lineno; |
|||
protected $tag; |
|||
|
|||
/** |
|||
* Constructor. |
|||
* |
|||
* The nodes are automatically made available as properties ($this->node). |
|||
* The attributes are automatically made available as array items ($this['name']). |
|||
* |
|||
* @param array $nodes An array of named nodes |
|||
* @param array $attributes An array of attributes (should not be nodes) |
|||
* @param integer $lineno The line number |
|||
* @param string $tag The tag name associated with the Node |
|||
*/ |
|||
public function __construct(array $nodes = array(), array $attributes = array(), $lineno = 0, $tag = null) |
|||
{ |
|||
$this->nodes = $nodes; |
|||
$this->attributes = $attributes; |
|||
$this->lineno = $lineno; |
|||
$this->tag = $tag; |
|||
} |
|||
|
|||
public function __toString() |
|||
{ |
|||
$attributes = array(); |
|||
foreach ($this->attributes as $name => $value) { |
|||
$attributes[] = sprintf('%s: %s', $name, str_replace("\n", '', var_export($value, true))); |
|||
} |
|||
|
|||
$repr = array(get_class($this).'('.implode(', ', $attributes)); |
|||
|
|||
if (count($this->nodes)) { |
|||
foreach ($this->nodes as $name => $node) { |
|||
$len = strlen($name) + 4; |
|||
$noderepr = array(); |
|||
foreach (explode("\n", (string) $node) as $line) { |
|||
$noderepr[] = str_repeat(' ', $len).$line; |
|||
} |
|||
|
|||
$repr[] = sprintf(' %s: %s', $name, ltrim(implode("\n", $noderepr))); |
|||
} |
|||
|
|||
$repr[] = ')'; |
|||
} else { |
|||
$repr[0] .= ')'; |
|||
} |
|||
|
|||
return implode("\n", $repr); |
|||
} |
|||
|
|||
public function toXml($asDom = false) |
|||
{ |
|||
$dom = new DOMDocument('1.0', 'UTF-8'); |
|||
$dom->formatOutput = true; |
|||
$dom->appendChild($xml = $dom->createElement('twig')); |
|||
|
|||
$xml->appendChild($node = $dom->createElement('node')); |
|||
$node->setAttribute('class', get_class($this)); |
|||
|
|||
foreach ($this->attributes as $name => $value) { |
|||
$node->appendChild($attribute = $dom->createElement('attribute')); |
|||
$attribute->setAttribute('name', $name); |
|||
$attribute->appendChild($dom->createTextNode($value)); |
|||
} |
|||
|
|||
foreach ($this->nodes as $name => $n) { |
|||
if (null === $n) { |
|||
continue; |
|||
} |
|||
|
|||
$child = $n->toXml(true)->getElementsByTagName('node')->item(0); |
|||
$child = $dom->importNode($child, true); |
|||
$child->setAttribute('name', $name); |
|||
|
|||
$node->appendChild($child); |
|||
} |
|||
|
|||
return $asDom ? $dom : $dom->saveXml(); |
|||
} |
|||
|
|||
public function compile(Twig_Compiler $compiler) |
|||
{ |
|||
foreach ($this->nodes as $node) { |
|||
$node->compile($compiler); |
|||
} |
|||
} |
|||
|
|||
public function getLine() |
|||
{ |
|||
return $this->lineno; |
|||
} |
|||
|
|||
public function getNodeTag() |
|||
{ |
|||
return $this->tag; |
|||
} |
|||
|
|||
/** |
|||
* Returns true if the attribute is defined. |
|||
* |
|||
* @param string The attribute name |
|||
* |
|||
* @return Boolean true if the attribute is defined, false otherwise |
|||
*/ |
|||
public function hasAttribute($name) |
|||
{ |
|||
return array_key_exists($name, $this->attributes); |
|||
} |
|||
|
|||
/** |
|||
* Gets an attribute. |
|||
* |
|||
* @param string The attribute name |
|||
* |
|||
* @return mixed The attribute value |
|||
*/ |
|||
public function getAttribute($name) |
|||
{ |
|||
if (!array_key_exists($name, $this->attributes)) { |
|||
throw new Twig_Error_Runtime(sprintf('Attribute "%s" does not exist for Node "%s".', $name, get_class($this))); |
|||
} |
|||
|
|||
return $this->attributes[$name]; |
|||
} |
|||
|
|||
/** |
|||
* Sets an attribute. |
|||
* |
|||
* @param string The attribute name |
|||
* @param mixed The attribute value |
|||
*/ |
|||
public function setAttribute($name, $value) |
|||
{ |
|||
$this->attributes[$name] = $value; |
|||
} |
|||
|
|||
/** |
|||
* Removes an attribute. |
|||
* |
|||
* @param string The attribute name |
|||
*/ |
|||
public function removeAttribute($name) |
|||
{ |
|||
unset($this->attributes[$name]); |
|||
} |
|||
|
|||
/** |
|||
* Returns true if the node with the given identifier exists. |
|||
* |
|||
* @param string The node name |
|||
* |
|||
* @return Boolean true if the node with the given name exists, false otherwise |
|||
*/ |
|||
public function hasNode($name) |
|||
{ |
|||
return array_key_exists($name, $this->nodes); |
|||
} |
|||
|
|||
/** |
|||
* Gets a node by name. |
|||
* |
|||
* @param string The node name |
|||
* |
|||
* @return Twig_Node A Twig_Node instance |
|||
*/ |
|||
public function getNode($name) |
|||
{ |
|||
if (!array_key_exists($name, $this->nodes)) { |
|||
throw new Twig_Error_Runtime(sprintf('Node "%s" does not exist for Node "%s".', $name, get_class($this))); |
|||
} |
|||
|
|||
return $this->nodes[$name]; |
|||
} |
|||
|
|||
/** |
|||
* Sets a node. |
|||
* |
|||
* @param string The node name |
|||
* @param Twig_Node A Twig_Node instance |
|||
*/ |
|||
public function setNode($name, $node = null) |
|||
{ |
|||
$this->nodes[$name] = $node; |
|||
} |
|||
|
|||
/** |
|||
* Removes a node by name. |
|||
* |
|||
* @param string The node name |
|||
*/ |
|||
public function removeNode($name) |
|||
{ |
|||
unset($this->nodes[$name]); |
|||
} |
|||
|
|||
public function count() |
|||
{ |
|||
return count($this->nodes); |
|||
} |
|||
|
|||
public function getIterator() |
|||
{ |
|||
return new ArrayIterator($this->nodes); |
|||
} |
|||
} |
@ -0,0 +1,40 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* 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. |
|||
*/ |
|||
|
|||
/** |
|||
* Represents an autoescape node. |
|||
* |
|||
* The value is the escaping strategy (can be html, js, ...) |
|||
* |
|||
* The true value is equivalent to html. |
|||
* |
|||
* If autoescaping is disabled, then the value is false. |
|||
* |
|||
* @package twig |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
*/ |
|||
class Twig_Node_AutoEscape extends Twig_Node |
|||
{ |
|||
public function __construct($value, Twig_NodeInterface $body, $lineno, $tag = 'autoescape') |
|||
{ |
|||
parent::__construct(array('body' => $body), array('value' => $value), $lineno, $tag); |
|||
} |
|||
|
|||
/** |
|||
* Compiles the node to PHP. |
|||
* |
|||
* @param Twig_Compiler A Twig_Compiler instance |
|||
*/ |
|||
public function compile(Twig_Compiler $compiler) |
|||
{ |
|||
$compiler->subcompile($this->getNode('body')); |
|||
} |
|||
} |
@ -0,0 +1,45 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of Twig. |
|||
* |
|||
* (c) 2009 Fabien Potencier |
|||
* (c) 2009 Armin Ronacher |
|||
* |
|||
* For the full copyright and license information, please view the LICENSE |
|||
* file that was distributed with this source code. |
|||
*/ |
|||
|
|||
/** |
|||
* Represents a block node. |
|||
* |
|||
* @package twig |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
*/ |
|||
class Twig_Node_Block extends Twig_Node |
|||
{ |
|||
public function __construct($name, Twig_NodeInterface $body, $lineno, $tag = null) |
|||
{ |
|||
parent::__construct(array('body' => $body), array('name' => $name), $lineno, $tag); |
|||
} |
|||
|
|||
/** |
|||
* Compiles the node to PHP. |
|||
* |
|||
* @param Twig_Compiler A Twig_Compiler instance |
|||
*/ |
|||
public function compile(Twig_Compiler $compiler) |
|||
{ |
|||
$compiler |
|||
->addDebugInfo($this) |
|||
->write(sprintf("public function block_%s(\$context, array \$blocks = array())\n", $this->getAttribute('name')), "{\n") |
|||
->indent() |
|||
; |
|||
|
|||
$compiler |
|||
->subcompile($this->getNode('body')) |
|||
->outdent() |
|||
->write("}\n\n") |
|||
; |
|||
} |
|||
} |
@ -0,0 +1,38 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of Twig. |
|||
* |
|||
* (c) 2009 Fabien Potencier |
|||
* (c) 2009 Armin Ronacher |
|||
* |
|||
* For the full copyright and license information, please view the LICENSE |
|||
* file that was distributed with this source code. |
|||
*/ |
|||
|
|||
/** |
|||
* Represents a block call node. |
|||
* |
|||
* @package twig |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
*/ |
|||
class Twig_Node_BlockReference extends Twig_Node implements Twig_NodeOutputInterface |
|||
{ |
|||
public function __construct($name, $lineno, $tag = null) |
|||
{ |
|||
parent::__construct(array(), array('name' => $name), $lineno, $tag); |
|||
} |
|||
|
|||
/** |
|||
* Compiles the node to PHP. |
|||
* |
|||
* @param Twig_Compiler A Twig_Compiler instance |
|||
*/ |
|||
public function compile(Twig_Compiler $compiler) |
|||
{ |
|||
$compiler |
|||
->addDebugInfo($this) |
|||
->write(sprintf("\$this->displayBlock('%s', \$context, \$blocks);\n", $this->getAttribute('name'))) |
|||
; |
|||
} |
|||
} |
@ -0,0 +1,21 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of Twig. |
|||
* |
|||
* (c) 2009 Fabien Potencier |
|||
* (c) 2009 Armin Ronacher |
|||
* |
|||
* For the full copyright and license information, please view the LICENSE |
|||
* file that was distributed with this source code. |
|||
*/ |
|||
|
|||
/** |
|||
* Abstract class for all nodes that represents an expression. |
|||
* |
|||
* @package twig |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
*/ |
|||
abstract class Twig_Node_Expression extends Twig_Node |
|||
{ |
|||
} |
@ -0,0 +1,41 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* 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_Node_Expression_Array extends Twig_Node_Expression |
|||
{ |
|||
public function __construct(array $elements, $lineno) |
|||
{ |
|||
parent::__construct($elements, array(), $lineno); |
|||
} |
|||
|
|||
/** |
|||
* Compiles the node to PHP. |
|||
* |
|||
* @param Twig_Compiler A Twig_Compiler instance |
|||
*/ |
|||
public function compile(Twig_Compiler $compiler) |
|||
{ |
|||
$compiler->raw('array('); |
|||
$first = true; |
|||
foreach ($this->nodes as $name => $node) { |
|||
if (!$first) { |
|||
$compiler->raw(', '); |
|||
} |
|||
$first = false; |
|||
|
|||
$compiler |
|||
->repr($name) |
|||
->raw(' => ') |
|||
->subcompile($node) |
|||
; |
|||
} |
|||
$compiler->raw(')'); |
|||
} |
|||
} |
@ -0,0 +1,24 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of Twig. |
|||
* |
|||
* (c) 2009 Fabien Potencier |
|||
* (c) 2009 Armin Ronacher |
|||
* |
|||
* For the full copyright and license information, please view the LICENSE |
|||
* file that was distributed with this source code. |
|||
*/ |
|||
|
|||
class Twig_Node_Expression_AssignName extends Twig_Node_Expression_Name |
|||
{ |
|||
/** |
|||
* Compiles the node to PHP. |
|||
* |
|||
* @param Twig_Compiler A Twig_Compiler instance |
|||
*/ |
|||
public function compile(Twig_Compiler $compiler) |
|||
{ |
|||
$compiler->raw(sprintf('$context[\'%s\']', $this->getAttribute('name'))); |
|||
} |
|||
} |
@ -0,0 +1,40 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of Twig. |
|||
* |
|||
* (c) 2009 Fabien Potencier |
|||
* (c) 2009 Armin Ronacher |
|||
* |
|||
* For the full copyright and license information, please view the LICENSE |
|||
* file that was distributed with this source code. |
|||
*/ |
|||
abstract class Twig_Node_Expression_Binary extends Twig_Node_Expression |
|||
{ |
|||
public function __construct(Twig_NodeInterface $left, Twig_NodeInterface $right, $lineno) |
|||
{ |
|||
parent::__construct(array('left' => $left, 'right' => $right), array(), $lineno); |
|||
} |
|||
|
|||
/** |
|||
* Compiles the node to PHP. |
|||
* |
|||
* @param Twig_Compiler A Twig_Compiler instance |
|||
*/ |
|||
public function compile(Twig_Compiler $compiler) |
|||
{ |
|||
$compiler |
|||
->raw('(') |
|||
->subcompile($this->getNode('left')) |
|||
->raw(' ') |
|||
; |
|||
$this->operator($compiler); |
|||
$compiler |
|||
->raw(' ') |
|||
->subcompile($this->getNode('right')) |
|||
->raw(')') |
|||
; |
|||
} |
|||
|
|||
abstract public function operator(Twig_Compiler $compiler); |
|||
} |
@ -0,0 +1,18 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of Twig. |
|||
* |
|||
* (c) 2009 Fabien Potencier |
|||
* (c) 2009 Armin Ronacher |
|||
* |
|||
* For the full copyright and license information, please view the LICENSE |
|||
* file that was distributed with this source code. |
|||
*/ |
|||
class Twig_Node_Expression_Binary_Add extends Twig_Node_Expression_Binary |
|||
{ |
|||
public function operator(Twig_Compiler $compiler) |
|||
{ |
|||
return $compiler->raw('+'); |
|||
} |
|||
} |
@ -0,0 +1,18 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of Twig. |
|||
* |
|||
* (c) 2009 Fabien Potencier |
|||
* (c) 2009 Armin Ronacher |
|||
* |
|||
* For the full copyright and license information, please view the LICENSE |
|||
* file that was distributed with this source code. |
|||
*/ |
|||
class Twig_Node_Expression_Binary_And extends Twig_Node_Expression_Binary |
|||
{ |
|||
public function operator(Twig_Compiler $compiler) |
|||
{ |
|||
return $compiler->raw('&&'); |
|||
} |
|||
} |
@ -0,0 +1,18 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of Twig. |
|||
* |
|||
* (c) 2009 Fabien Potencier |
|||
* (c) 2009 Armin Ronacher |
|||
* |
|||
* For the full copyright and license information, please view the LICENSE |
|||
* file that was distributed with this source code. |
|||
*/ |
|||
class Twig_Node_Expression_Binary_BitwiseAnd extends Twig_Node_Expression_Binary |
|||
{ |
|||
public function operator(Twig_Compiler $compiler) |
|||
{ |
|||
return $compiler->raw('&'); |
|||
} |
|||
} |
@ -0,0 +1,18 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of Twig. |
|||
* |
|||
* (c) 2009 Fabien Potencier |
|||
* (c) 2009 Armin Ronacher |
|||
* |
|||
* For the full copyright and license information, please view the LICENSE |
|||
* file that was distributed with this source code. |
|||
*/ |
|||
class Twig_Node_Expression_Binary_BitwiseOr extends Twig_Node_Expression_Binary |
|||
{ |
|||
public function operator(Twig_Compiler $compiler) |
|||
{ |
|||
return $compiler->raw('|'); |
|||
} |
|||
} |
@ -0,0 +1,18 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of Twig. |
|||
* |
|||
* (c) 2009 Fabien Potencier |
|||
* (c) 2009 Armin Ronacher |
|||
* |
|||
* For the full copyright and license information, please view the LICENSE |
|||
* file that was distributed with this source code. |
|||
*/ |
|||
class Twig_Node_Expression_Binary_BitwiseXor extends Twig_Node_Expression_Binary |
|||
{ |
|||
public function operator(Twig_Compiler $compiler) |
|||
{ |
|||
return $compiler->raw('^'); |
|||
} |
|||
} |
@ -0,0 +1,18 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of Twig. |
|||
* |
|||
* (c) 2009 Fabien Potencier |
|||
* (c) 2009 Armin Ronacher |
|||
* |
|||
* For the full copyright and license information, please view the LICENSE |
|||
* file that was distributed with this source code. |
|||
*/ |
|||
class Twig_Node_Expression_Binary_Concat extends Twig_Node_Expression_Binary |
|||
{ |
|||
public function operator(Twig_Compiler $compiler) |
|||
{ |
|||
return $compiler->raw('.'); |
|||
} |
|||
} |
@ -0,0 +1,18 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of Twig. |
|||
* |
|||
* (c) 2009 Fabien Potencier |
|||
* (c) 2009 Armin Ronacher |
|||
* |
|||
* For the full copyright and license information, please view the LICENSE |
|||
* file that was distributed with this source code. |
|||
*/ |
|||
class Twig_Node_Expression_Binary_Div extends Twig_Node_Expression_Binary |
|||
{ |
|||
public function operator(Twig_Compiler $compiler) |
|||
{ |
|||
return $compiler->raw('/'); |
|||
} |
|||
} |
@ -0,0 +1,17 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of Twig. |
|||
* |
|||
* (c) 2010 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_Equal extends Twig_Node_Expression_Binary |
|||
{ |
|||
public function operator(Twig_Compiler $compiler) |
|||
{ |
|||
return $compiler->raw('=='); |
|||
} |
|||
} |
@ -0,0 +1,29 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* 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_Node_Expression_Binary_FloorDiv extends Twig_Node_Expression_Binary |
|||
{ |
|||
/** |
|||
* Compiles the node to PHP. |
|||
* |
|||
* @param Twig_Compiler A Twig_Compiler instance |
|||
*/ |
|||
public function compile(Twig_Compiler $compiler) |
|||
{ |
|||
$compiler->raw('floor('); |
|||
parent::compile($compiler); |
|||
$compiler->raw(')'); |
|||
} |
|||
|
|||
public function operator(Twig_Compiler $compiler) |
|||
{ |
|||
return $compiler->raw('/'); |
|||
} |
|||
} |
@ -0,0 +1,17 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of Twig. |
|||
* |
|||
* (c) 2010 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_Greater extends Twig_Node_Expression_Binary |
|||
{ |
|||
public function operator(Twig_Compiler $compiler) |
|||
{ |
|||
return $compiler->raw('>'); |
|||
} |
|||
} |
@ -0,0 +1,17 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of Twig. |
|||
* |
|||
* (c) 2010 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_GreaterEqual extends Twig_Node_Expression_Binary |
|||
{ |
|||
public function operator(Twig_Compiler $compiler) |
|||
{ |
|||
return $compiler->raw('>='); |
|||
} |
|||
} |
@ -0,0 +1,33 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of Twig. |
|||
* |
|||
* (c) 2010 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_In extends Twig_Node_Expression_Binary |
|||
{ |
|||
/** |
|||
* Compiles the node to PHP. |
|||
* |
|||
* @param Twig_Compiler A Twig_Compiler instance |
|||
*/ |
|||
public function compile(Twig_Compiler $compiler) |
|||
{ |
|||
$compiler |
|||
->raw('twig_in_filter(') |
|||
->subcompile($this->getNode('left')) |
|||
->raw(', ') |
|||
->subcompile($this->getNode('right')) |
|||
->raw(')') |
|||
; |
|||
} |
|||
|
|||
public function operator(Twig_Compiler $compiler) |
|||
{ |
|||
return $compiler->raw('in'); |
|||
} |
|||
} |
@ -0,0 +1,17 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of Twig. |
|||
* |
|||
* (c) 2010 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_Less extends Twig_Node_Expression_Binary |
|||
{ |
|||
public function operator(Twig_Compiler $compiler) |
|||
{ |
|||
return $compiler->raw('<'); |
|||
} |
|||
} |
@ -0,0 +1,17 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of Twig. |
|||
* |
|||
* (c) 2010 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_LessEqual extends Twig_Node_Expression_Binary |
|||
{ |
|||
public function operator(Twig_Compiler $compiler) |
|||
{ |
|||
return $compiler->raw('<='); |
|||
} |
|||
} |
@ -0,0 +1,18 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of Twig. |
|||
* |
|||
* (c) 2009 Fabien Potencier |
|||
* (c) 2009 Armin Ronacher |
|||
* |
|||
* For the full copyright and license information, please view the LICENSE |
|||
* file that was distributed with this source code. |
|||
*/ |
|||
class Twig_Node_Expression_Binary_Mod extends Twig_Node_Expression_Binary |
|||
{ |
|||
public function operator(Twig_Compiler $compiler) |
|||
{ |
|||
return $compiler->raw('%'); |
|||
} |
|||
} |
@ -0,0 +1,18 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of Twig. |
|||
* |
|||
* (c) 2009 Fabien Potencier |
|||
* (c) 2009 Armin Ronacher |
|||
* |
|||
* For the full copyright and license information, please view the LICENSE |
|||
* file that was distributed with this source code. |
|||
*/ |
|||
class Twig_Node_Expression_Binary_Mul extends Twig_Node_Expression_Binary |
|||
{ |
|||
public function operator(Twig_Compiler $compiler) |
|||
{ |
|||
return $compiler->raw('*'); |
|||
} |
|||
} |
@ -0,0 +1,17 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of Twig. |
|||
* |
|||
* (c) 2010 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_NotEqual extends Twig_Node_Expression_Binary |
|||
{ |
|||
public function operator(Twig_Compiler $compiler) |
|||
{ |
|||
return $compiler->raw('!='); |
|||
} |
|||
} |
@ -0,0 +1,33 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of Twig. |
|||
* |
|||
* (c) 2010 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_NotIn extends Twig_Node_Expression_Binary |
|||
{ |
|||
/** |
|||
* Compiles the node to PHP. |
|||
* |
|||
* @param Twig_Compiler A Twig_Compiler instance |
|||
*/ |
|||
public function compile(Twig_Compiler $compiler) |
|||
{ |
|||
$compiler |
|||
->raw('!twig_in_filter(') |
|||
->subcompile($this->getNode('left')) |
|||
->raw(', ') |
|||
->subcompile($this->getNode('right')) |
|||
->raw(')') |
|||
; |
|||
} |
|||
|
|||
public function operator(Twig_Compiler $compiler) |
|||
{ |
|||
return $compiler->raw('not in'); |
|||
} |
|||
} |
@ -0,0 +1,18 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of Twig. |
|||
* |
|||
* (c) 2009 Fabien Potencier |
|||
* (c) 2009 Armin Ronacher |
|||
* |
|||
* For the full copyright and license information, please view the LICENSE |
|||
* file that was distributed with this source code. |
|||
*/ |
|||
class Twig_Node_Expression_Binary_Or extends Twig_Node_Expression_Binary |
|||
{ |
|||
public function operator(Twig_Compiler $compiler) |
|||
{ |
|||
return $compiler->raw('||'); |
|||
} |
|||
} |
@ -0,0 +1,33 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of Twig. |
|||
* |
|||
* (c) 2010 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_Power extends Twig_Node_Expression_Binary |
|||
{ |
|||
/** |
|||
* Compiles the node to PHP. |
|||
* |
|||
* @param Twig_Compiler A Twig_Compiler instance |
|||
*/ |
|||
public function compile(Twig_Compiler $compiler) |
|||
{ |
|||
$compiler |
|||
->raw('pow(') |
|||
->subcompile($this->getNode('left')) |
|||
->raw(', ') |
|||
->subcompile($this->getNode('right')) |
|||
->raw(')') |
|||
; |
|||
} |
|||
|
|||
public function operator(Twig_Compiler $compiler) |
|||
{ |
|||
return $compiler->raw('**'); |
|||
} |
|||
} |
@ -0,0 +1,33 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of Twig. |
|||
* |
|||
* (c) 2010 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_Range extends Twig_Node_Expression_Binary |
|||
{ |
|||
/** |
|||
* Compiles the node to PHP. |
|||
* |
|||
* @param Twig_Compiler A Twig_Compiler instance |
|||
*/ |
|||
public function compile(Twig_Compiler $compiler) |
|||
{ |
|||
$compiler |
|||
->raw('range(') |
|||
->subcompile($this->getNode('left')) |
|||
->raw(', ') |
|||
->subcompile($this->getNode('right')) |
|||
->raw(')') |
|||
; |
|||
} |
|||
|
|||
public function operator(Twig_Compiler $compiler) |
|||
{ |
|||
return $compiler->raw('..'); |
|||
} |
|||
} |
@ -0,0 +1,18 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of Twig. |
|||
* |
|||
* (c) 2009 Fabien Potencier |
|||
* (c) 2009 Armin Ronacher |
|||
* |
|||
* For the full copyright and license information, please view the LICENSE |
|||
* file that was distributed with this source code. |
|||
*/ |
|||
class Twig_Node_Expression_Binary_Sub extends Twig_Node_Expression_Binary |
|||
{ |
|||
public function operator(Twig_Compiler $compiler) |
|||
{ |
|||
return $compiler->raw('-'); |
|||
} |
|||
} |
@ -0,0 +1,52 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of Twig. |
|||
* |
|||
* (c) 2009 Fabien Potencier |
|||
* (c) 2009 Armin Ronacher |
|||
* |
|||
* For the full copyright and license information, please view the LICENSE |
|||
* file that was distributed with this source code. |
|||
*/ |
|||
|
|||
/** |
|||
* Represents a block call node. |
|||
* |
|||
* @package twig |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
*/ |
|||
class Twig_Node_Expression_BlockReference extends Twig_Node_Expression |
|||
{ |
|||
public function __construct(Twig_NodeInterface $name, $asString = false, $lineno, $tag = null) |
|||
{ |
|||
parent::__construct(array('name' => $name), array('as_string' => $asString, 'output' => false), $lineno, $tag); |
|||
} |
|||
|
|||
/** |
|||
* Compiles the node to PHP. |
|||
* |
|||
* @param Twig_Compiler A Twig_Compiler instance |
|||
*/ |
|||
public function compile(Twig_Compiler $compiler) |
|||
{ |
|||
if ($this->getAttribute('as_string')) { |
|||
$compiler->raw('(string) '); |
|||
} |
|||
|
|||
if ($this->getAttribute('output')) { |
|||
$compiler |
|||
->addDebugInfo($this) |
|||
->write("\$this->displayBlock(") |
|||
->subcompile($this->getNode('name')) |
|||
->raw(", \$context, \$blocks);\n") |
|||
; |
|||
} else { |
|||
$compiler |
|||
->raw("\$this->renderBlock(") |
|||
->subcompile($this->getNode('name')) |
|||
->raw(", \$context, \$blocks)") |
|||
; |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,31 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of Twig. |
|||
* |
|||
* (c) 2009 Fabien Potencier |
|||
* (c) 2009 Armin Ronacher |
|||
* |
|||
* For the full copyright and license information, please view the LICENSE |
|||
* file that was distributed with this source code. |
|||
*/ |
|||
class Twig_Node_Expression_Conditional extends Twig_Node_Expression |
|||
{ |
|||
public function __construct(Twig_Node_Expression $expr1, Twig_Node_Expression $expr2, Twig_Node_Expression $expr3, $lineno) |
|||
{ |
|||
parent::__construct(array('expr1' => $expr1, 'expr2' => $expr2, 'expr3' => $expr3), array(), $lineno); |
|||
} |
|||
|
|||
public function compile(Twig_Compiler $compiler) |
|||
{ |
|||
$compiler |
|||
->raw('((') |
|||
->subcompile($this->getNode('expr1')) |
|||
->raw(') ? (') |
|||
->subcompile($this->getNode('expr2')) |
|||
->raw(') : (') |
|||
->subcompile($this->getNode('expr3')) |
|||
->raw('))') |
|||
; |
|||
} |
|||
} |
@ -0,0 +1,23 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of Twig. |
|||
* |
|||
* (c) 2009 Fabien Potencier |
|||
* (c) 2009 Armin Ronacher |
|||
* |
|||
* For the full copyright and license information, please view the LICENSE |
|||
* file that was distributed with this source code. |
|||
*/ |
|||
class Twig_Node_Expression_Constant extends Twig_Node_Expression |
|||
{ |
|||
public function __construct($value, $lineno) |
|||
{ |
|||
parent::__construct(array(), array('value' => $value), $lineno); |
|||
} |
|||
|
|||
public function compile(Twig_Compiler $compiler) |
|||
{ |
|||
$compiler->repr($this->getAttribute('value')); |
|||
} |
|||
} |
@ -0,0 +1,34 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* 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. |
|||
*/ |
|||
|
|||
/** |
|||
* Represents an extension call node. |
|||
* |
|||
* @package twig |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
*/ |
|||
class Twig_Node_Expression_ExtensionReference extends Twig_Node_Expression |
|||
{ |
|||
public function __construct($name, $lineno, $tag = null) |
|||
{ |
|||
parent::__construct(array(), array('name' => $name), $lineno, $tag); |
|||
} |
|||
|
|||
/** |
|||
* Compiles the node to PHP. |
|||
* |
|||
* @param Twig_Compiler A Twig_Compiler instance |
|||
*/ |
|||
public function compile(Twig_Compiler $compiler) |
|||
{ |
|||
$compiler->raw(sprintf("\$this->env->getExtension('%s')", $this->getAttribute('name'))); |
|||
} |
|||
} |
@ -0,0 +1,72 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of Twig. |
|||
* |
|||
* (c) 2009 Fabien Potencier |
|||
* (c) 2009 Armin Ronacher |
|||
* |
|||
* For the full copyright and license information, please view the LICENSE |
|||
* file that was distributed with this source code. |
|||
*/ |
|||
class Twig_Node_Expression_Filter extends Twig_Node_Expression |
|||
{ |
|||
public function __construct(Twig_NodeInterface $node, Twig_Node_Expression_Constant $filterName, Twig_NodeInterface $arguments, $lineno, $tag = null) |
|||
{ |
|||
parent::__construct(array('node' => $node, 'filter' => $filterName, 'arguments' => $arguments), array(), $lineno, $tag); |
|||
} |
|||
|
|||
public function compile(Twig_Compiler $compiler) |
|||
{ |
|||
$name = $this->getNode('filter')->getAttribute('value'); |
|||
if (false === $filter = $compiler->getEnvironment()->getFilter($name)) { |
|||
throw new Twig_Error_Syntax(sprintf('The filter "%s" does not exist', $name), $this->getLine()); |
|||
} |
|||
|
|||
$node = $this->getNode('node'); |
|||
|
|||
// The default filter is intercepted when the filtered value |
|||
// is a name (like obj) or an attribute (like obj.attr) |
|||
// In such a case, it's compiled to {{ obj is defined ? obj|default('bar') : 'bar' }} |
|||
if ('default' === $name && ($node instanceof Twig_Node_Expression_Name || $node instanceof Twig_Node_Expression_GetAttr)) { |
|||
$compiler |
|||
->raw('((') |
|||
->subcompile(new Twig_Node_Expression_Test($node, 'defined', new Twig_Node(), $this->getLine())) |
|||
->raw(') ? (') |
|||
; |
|||
|
|||
$this->compileFilter($compiler, $filter); |
|||
|
|||
$compiler->raw(') : ('); |
|||
|
|||
if ($this->getNode('arguments')->hasNode(0)) { |
|||
$compiler->subcompile($this->getNode('arguments')->getNode(0)); |
|||
} else { |
|||
$compiler->string(''); |
|||
} |
|||
|
|||
$compiler->raw('))'); |
|||
} else { |
|||
$this->compileFilter($compiler, $filter); |
|||
} |
|||
} |
|||
|
|||
protected function compileFilter(Twig_Compiler $compiler, Twig_FilterInterface $filter) |
|||
{ |
|||
$compiler |
|||
->raw($filter->compile().'(') |
|||
->raw($filter->needsEnvironment() ? '$this->env, ' : '') |
|||
->raw($filter->needsContext() ? '$context, ' : '') |
|||
->subcompile($this->getNode('node')) |
|||
; |
|||
|
|||
foreach ($this->getNode('arguments') as $node) { |
|||
$compiler |
|||
->raw(', ') |
|||
->subcompile($node) |
|||
; |
|||
} |
|||
|
|||
$compiler->raw(')'); |
|||
} |
|||
} |
@ -0,0 +1,49 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of Twig. |
|||
* |
|||
* (c) 2010 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_Function extends Twig_Node_Expression |
|||
{ |
|||
public function __construct($name, Twig_NodeInterface $arguments, $lineno) |
|||
{ |
|||
parent::__construct(array('arguments' => $arguments), array('name' => $name), $lineno); |
|||
} |
|||
|
|||
public function compile(Twig_Compiler $compiler) |
|||
{ |
|||
$function = $compiler->getEnvironment()->getFunction($this->getAttribute('name')); |
|||
if (false === $function) { |
|||
throw new Twig_Error_Syntax(sprintf('The function "%s" does not exist', $this->getAttribute('name')), $this->getLine()); |
|||
} |
|||
|
|||
$compiler |
|||
->raw($function->compile().'(') |
|||
->raw($function->needsEnvironment() ? '$this->env' : '') |
|||
; |
|||
|
|||
if ($function->needsContext()) { |
|||
$compiler->raw($function->needsEnvironment() ? ', $context' : '$context'); |
|||
} |
|||
|
|||
$first = true; |
|||
foreach ($this->getNode('arguments') as $node) { |
|||
if (!$first) { |
|||
$compiler->raw(', '); |
|||
} else { |
|||
if ($function->needsEnvironment() || $function->needsContext()) { |
|||
$compiler->raw(', '); |
|||
} |
|||
$first = false; |
|||
} |
|||
$compiler->subcompile($node); |
|||
} |
|||
|
|||
$compiler->raw(')'); |
|||
} |
|||
} |
@ -0,0 +1,53 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of Twig. |
|||
* |
|||
* (c) 2009 Fabien Potencier |
|||
* (c) 2009 Armin Ronacher |
|||
* |
|||
* For the full copyright and license information, please view the LICENSE |
|||
* file that was distributed with this source code. |
|||
*/ |
|||
class Twig_Node_Expression_GetAttr extends Twig_Node_Expression |
|||
{ |
|||
public function __construct(Twig_Node_Expression $node, Twig_Node_Expression $attribute, Twig_NodeInterface $arguments, $type, $lineno) |
|||
{ |
|||
parent::__construct(array('node' => $node, 'attribute' => $attribute, 'arguments' => $arguments), array('type' => $type), $lineno); |
|||
} |
|||
|
|||
public function compile(Twig_Compiler $compiler) |
|||
{ |
|||
$compiler->raw('$this->getAttribute('); |
|||
|
|||
if ($this->hasAttribute('is_defined_test') && $compiler->getEnvironment()->isStrictVariables()) { |
|||
$compiler->subcompile(new Twig_Node_Expression_Filter( |
|||
$this->getNode('node'), |
|||
new Twig_Node_Expression_Constant('default', $this->getLine()), |
|||
new Twig_Node(), |
|||
$this->getLine() |
|||
)); |
|||
} else { |
|||
$compiler->subcompile($this->getNode('node')); |
|||
} |
|||
|
|||
$compiler |
|||
->raw(', ') |
|||
->subcompile($this->getNode('attribute')) |
|||
->raw(', array(') |
|||
; |
|||
|
|||
foreach ($this->getNode('arguments') as $node) { |
|||
$compiler |
|||
->subcompile($node) |
|||
->raw(', ') |
|||
; |
|||
} |
|||
|
|||
$compiler |
|||
->raw('), ') |
|||
->repr($this->getAttribute('type')) |
|||
->raw($this->hasAttribute('is_defined_test') ? ', true' : ', false') |
|||
->raw(')'); |
|||
} |
|||
} |
@ -0,0 +1,41 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of Twig. |
|||
* |
|||
* (c) 2009 Fabien Potencier |
|||
* (c) 2009 Armin Ronacher |
|||
* |
|||
* For the full copyright and license information, please view the LICENSE |
|||
* file that was distributed with this source code. |
|||
*/ |
|||
class Twig_Node_Expression_Name extends Twig_Node_Expression |
|||
{ |
|||
public function __construct($name, $lineno) |
|||
{ |
|||
parent::__construct(array(), array('name' => $name), $lineno); |
|||
} |
|||
|
|||
public function compile(Twig_Compiler $compiler) |
|||
{ |
|||
static $specialVars = array( |
|||
'_self' => '$this', |
|||
'_context' => '$context', |
|||
'_charset' => '$this->env->getCharset()', |
|||
); |
|||
|
|||
$name = $this->getAttribute('name'); |
|||
|
|||
if ($this->hasAttribute('is_defined_test')) { |
|||
if (isset($specialVars[$name])) { |
|||
$compiler->repr(true); |
|||
} else { |
|||
$compiler->raw('array_key_exists(')->repr($name)->raw(', $context)'); |
|||
} |
|||
} elseif (isset($specialVars[$name])) { |
|||
$compiler->raw($specialVars[$name]); |
|||
} else { |
|||
$compiler->raw(sprintf('$this->getContext($context, \'%s\')', $name)); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,39 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of Twig. |
|||
* |
|||
* (c) 2009 Fabien Potencier |
|||
* (c) 2009 Armin Ronacher |
|||
* |
|||
* For the full copyright and license information, please view the LICENSE |
|||
* file that was distributed with this source code. |
|||
*/ |
|||
|
|||
/** |
|||
* Represents a parent node. |
|||
* |
|||
* @package twig |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
*/ |
|||
class Twig_Node_Expression_Parent extends Twig_Node_Expression |
|||
{ |
|||
public function __construct($name, $lineno, $tag = null) |
|||
{ |
|||
parent::__construct(array(), array('name' => $name), $lineno, $tag); |
|||
} |
|||
|
|||
/** |
|||
* Compiles the node to PHP. |
|||
* |
|||
* @param Twig_Compiler A Twig_Compiler instance |
|||
*/ |
|||
public function compile(Twig_Compiler $compiler) |
|||
{ |
|||
$compiler |
|||
->raw("\$this->renderParentBlock(") |
|||
->string($this->getAttribute('name')) |
|||
->raw(", \$context, \$blocks)") |
|||
; |
|||
} |
|||
} |
@ -0,0 +1,60 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of Twig. |
|||
* |
|||
* (c) 2010 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_Test extends Twig_Node_Expression |
|||
{ |
|||
public function __construct(Twig_NodeInterface $node, $name, Twig_NodeInterface $arguments = null, $lineno) |
|||
{ |
|||
parent::__construct(array('node' => $node, 'arguments' => $arguments), array('name' => $name), $lineno); |
|||
} |
|||
|
|||
public function compile(Twig_Compiler $compiler) |
|||
{ |
|||
$testMap = $compiler->getEnvironment()->getTests(); |
|||
if (!isset($testMap[$this->getAttribute('name')])) { |
|||
throw new Twig_Error_Syntax(sprintf('The test "%s" does not exist', $this->getAttribute('name')), $this->getLine()); |
|||
} |
|||
|
|||
$name = $this->getAttribute('name'); |
|||
$node = $this->getNode('node'); |
|||
|
|||
// defined is a special case |
|||
if ('defined' === $name) { |
|||
if ($node instanceof Twig_Node_Expression_Name || $node instanceof Twig_Node_Expression_GetAttr) { |
|||
$node->setAttribute('is_defined_test', true); |
|||
$compiler->subcompile($node); |
|||
$node->removeAttribute('is_defined_test'); |
|||
} else { |
|||
throw new Twig_Error_Syntax('The "defined" test only works with simple variables', $this->getLine()); |
|||
} |
|||
return; |
|||
} |
|||
|
|||
$compiler |
|||
->raw($testMap[$name]->compile().'(') |
|||
->subcompile($node) |
|||
; |
|||
|
|||
if (null !== $this->getNode('arguments')) { |
|||
$compiler->raw(', '); |
|||
|
|||
$max = count($this->getNode('arguments')) - 1; |
|||
foreach ($this->getNode('arguments') as $i => $arg) { |
|||
$compiler->subcompile($arg); |
|||
|
|||
if ($i != $max) { |
|||
$compiler->raw(', '); |
|||
} |
|||
} |
|||
} |
|||
|
|||
$compiler->raw(')'); |
|||
} |
|||
} |
@ -0,0 +1,30 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of Twig. |
|||
* |
|||
* (c) 2009 Fabien Potencier |
|||
* (c) 2009 Armin Ronacher |
|||
* |
|||
* For the full copyright and license information, please view the LICENSE |
|||
* file that was distributed with this source code. |
|||
*/ |
|||
abstract class Twig_Node_Expression_Unary extends Twig_Node_Expression |
|||
{ |
|||
public function __construct(Twig_NodeInterface $node, $lineno) |
|||
{ |
|||
parent::__construct(array('node' => $node), array(), $lineno); |
|||
} |
|||
|
|||
public function compile(Twig_Compiler $compiler) |
|||
{ |
|||
$compiler->raw('('); |
|||
$this->operator($compiler); |
|||
$compiler |
|||
->subcompile($this->getNode('node')) |
|||
->raw(')') |
|||
; |
|||
} |
|||
|
|||
abstract public function operator(Twig_Compiler $compiler); |
|||
} |
@ -0,0 +1,18 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of Twig. |
|||
* |
|||
* (c) 2009 Fabien Potencier |
|||
* (c) 2009 Armin Ronacher |
|||
* |
|||
* For the full copyright and license information, please view the LICENSE |
|||
* file that was distributed with this source code. |
|||
*/ |
|||
class Twig_Node_Expression_Unary_Neg extends Twig_Node_Expression_Unary |
|||
{ |
|||
public function operator(Twig_Compiler $compiler) |
|||
{ |
|||
$compiler->raw('-'); |
|||
} |
|||
} |
@ -0,0 +1,18 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of Twig. |
|||
* |
|||
* (c) 2009 Fabien Potencier |
|||
* (c) 2009 Armin Ronacher |
|||
* |
|||
* For the full copyright and license information, please view the LICENSE |
|||
* file that was distributed with this source code. |
|||
*/ |
|||
class Twig_Node_Expression_Unary_Not extends Twig_Node_Expression_Unary |
|||
{ |
|||
public function operator(Twig_Compiler $compiler) |
|||
{ |
|||
$compiler->raw('!'); |
|||
} |
|||
} |
@ -0,0 +1,18 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of Twig. |
|||
* |
|||
* (c) 2009 Fabien Potencier |
|||
* (c) 2009 Armin Ronacher |
|||
* |
|||
* For the full copyright and license information, please view the LICENSE |
|||
* file that was distributed with this source code. |
|||
*/ |
|||
class Twig_Node_Expression_Unary_Pos extends Twig_Node_Expression_Unary |
|||
{ |
|||
public function operator(Twig_Compiler $compiler) |
|||
{ |
|||
$compiler->raw('+'); |
|||
} |
|||
} |
@ -0,0 +1,141 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of Twig. |
|||
* |
|||
* (c) 2009 Fabien Potencier |
|||
* (c) 2009 Armin Ronacher |
|||
* |
|||
* For the full copyright and license information, please view the LICENSE |
|||
* file that was distributed with this source code. |
|||
*/ |
|||
|
|||
/** |
|||
* Represents a for node. |
|||
* |
|||
* @package twig |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
*/ |
|||
class Twig_Node_For extends Twig_Node |
|||
{ |
|||
public function __construct(Twig_Node_Expression_AssignName $keyTarget, Twig_Node_Expression_AssignName $valueTarget, Twig_Node_Expression $seq, Twig_Node_Expression $ifexpr = null, Twig_NodeInterface $body, Twig_NodeInterface $else = null, $lineno, $tag = null) |
|||
{ |
|||
parent::__construct(array('key_target' => $keyTarget, 'value_target' => $valueTarget, 'seq' => $seq, 'ifexpr' => $ifexpr, 'body' => $body, 'else' => $else), array('with_loop' => true), $lineno, $tag); |
|||
} |
|||
|
|||
/** |
|||
* Compiles the node to PHP. |
|||
* |
|||
* @param Twig_Compiler A Twig_Compiler instance |
|||
*/ |
|||
public function compile(Twig_Compiler $compiler) |
|||
{ |
|||
$compiler |
|||
->addDebugInfo($this) |
|||
// the (array) cast bypasses a PHP 5.2.6 bug |
|||
->write("\$context['_parent'] = (array) \$context;\n") |
|||
->write("\$context['_seq'] = twig_ensure_traversable(") |
|||
->subcompile($this->getNode('seq')) |
|||
->raw(");\n") |
|||
; |
|||
|
|||
if (null !== $this->getNode('else')) { |
|||
$compiler->write("\$context['_iterated'] = false;\n"); |
|||
} |
|||
|
|||
if ($this->getAttribute('with_loop')) { |
|||
$compiler |
|||
->write("\$context['loop'] = array(\n") |
|||
->write(" 'parent' => \$context['_parent'],\n") |
|||
->write(" 'index0' => 0,\n") |
|||
->write(" 'index' => 1,\n") |
|||
->write(" 'first' => true,\n") |
|||
->write(");\n") |
|||
; |
|||
|
|||
if (null === $this->getNode('ifexpr')) { |
|||
$compiler |
|||
->write("if (is_array(\$context['_seq']) || (is_object(\$context['_seq']) && \$context['_seq'] instanceof Countable)) {\n") |
|||
->indent() |
|||
->write("\$length = count(\$context['_seq']);\n") |
|||
->write("\$context['loop']['revindex0'] = \$length - 1;\n") |
|||
->write("\$context['loop']['revindex'] = \$length;\n") |
|||
->write("\$context['loop']['length'] = \$length;\n") |
|||
->write("\$context['loop']['last'] = 1 === \$length;\n") |
|||
->outdent() |
|||
->write("}\n") |
|||
; |
|||
} |
|||
} |
|||
|
|||
$compiler |
|||
->write("foreach (\$context['_seq'] as ") |
|||
->subcompile($this->getNode('key_target')) |
|||
->raw(" => ") |
|||
->subcompile($this->getNode('value_target')) |
|||
->raw(") {\n") |
|||
->indent() |
|||
; |
|||
|
|||
if (null !== $this->getNode('ifexpr')) { |
|||
$compiler |
|||
->write("if (!(") |
|||
->subcompile($this->getNode('ifexpr')) |
|||
->raw(")) {\n") |
|||
->indent() |
|||
->write("continue;\n") |
|||
->outdent() |
|||
->write("}\n\n") |
|||
; |
|||
} |
|||
|
|||
$compiler->subcompile($this->getNode('body')); |
|||
|
|||
if (null !== $this->getNode('else')) { |
|||
$compiler->write("\$context['_iterated'] = true;\n"); |
|||
} |
|||
|
|||
if ($this->getAttribute('with_loop')) { |
|||
$compiler |
|||
->write("++\$context['loop']['index0'];\n") |
|||
->write("++\$context['loop']['index'];\n") |
|||
->write("\$context['loop']['first'] = false;\n") |
|||
; |
|||
|
|||
if (null === $this->getNode('ifexpr')) { |
|||
$compiler |
|||
->write("if (isset(\$context['loop']['length'])) {\n") |
|||
->indent() |
|||
->write("--\$context['loop']['revindex0'];\n") |
|||
->write("--\$context['loop']['revindex'];\n") |
|||
->write("\$context['loop']['last'] = 0 === \$context['loop']['revindex0'];\n") |
|||
->outdent() |
|||
->write("}\n") |
|||
; |
|||
} |
|||
} |
|||
|
|||
$compiler |
|||
->outdent() |
|||
->write("}\n") |
|||
; |
|||
|
|||
if (null !== $this->getNode('else')) { |
|||
$compiler |
|||
->write("if (!\$context['_iterated']) {\n") |
|||
->indent() |
|||
->subcompile($this->getNode('else')) |
|||
->outdent() |
|||
->write("}\n") |
|||
; |
|||
} |
|||
|
|||
$compiler->write("\$_parent = \$context['_parent'];\n"); |
|||
|
|||
// remove some "private" loop variables (needed for nested loops) |
|||
$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"); |
|||
} |
|||
} |
@ -0,0 +1,67 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of Twig. |
|||
* |
|||
* (c) 2009 Fabien Potencier |
|||
* (c) 2009 Armin Ronacher |
|||
* |
|||
* For the full copyright and license information, please view the LICENSE |
|||
* file that was distributed with this source code. |
|||
*/ |
|||
|
|||
/** |
|||
* Represents an if node. |
|||
* |
|||
* @package twig |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
*/ |
|||
class Twig_Node_If extends Twig_Node |
|||
{ |
|||
public function __construct(Twig_NodeInterface $tests, Twig_NodeInterface $else = null, $lineno, $tag = null) |
|||
{ |
|||
parent::__construct(array('tests' => $tests, 'else' => $else), array(), $lineno, $tag); |
|||
} |
|||
|
|||
/** |
|||
* Compiles the node to PHP. |
|||
* |
|||
* @param Twig_Compiler A Twig_Compiler instance |
|||
*/ |
|||
public function compile(Twig_Compiler $compiler) |
|||
{ |
|||
$compiler->addDebugInfo($this); |
|||
for ($i = 0; $i < count($this->getNode('tests')); $i += 2) { |
|||
if ($i > 0) { |
|||
$compiler |
|||
->outdent() |
|||
->write("} elseif (") |
|||
; |
|||
} else { |
|||
$compiler |
|||
->write('if (') |
|||
; |
|||
} |
|||
|
|||
$compiler |
|||
->subcompile($this->getNode('tests')->getNode($i)) |
|||
->raw(") {\n") |
|||
->indent() |
|||
->subcompile($this->getNode('tests')->getNode($i + 1)) |
|||
; |
|||
} |
|||
|
|||
if ($this->hasNode('else') && null !== $this->getNode('else')) { |
|||
$compiler |
|||
->outdent() |
|||
->write("} else {\n") |
|||
->indent() |
|||
->subcompile($this->getNode('else')) |
|||
; |
|||
} |
|||
|
|||
$compiler |
|||
->outdent() |
|||
->write("}\n"); |
|||
} |
|||
} |
@ -0,0 +1,51 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* 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. |
|||
*/ |
|||
|
|||
/** |
|||
* Represents an import node. |
|||
* |
|||
* @package twig |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
*/ |
|||
class Twig_Node_Import extends Twig_Node |
|||
{ |
|||
public function __construct(Twig_Node_Expression $expr, Twig_Node_Expression $var, $lineno, $tag = null) |
|||
{ |
|||
parent::__construct(array('expr' => $expr, 'var' => $var), array(), $lineno, $tag); |
|||
} |
|||
|
|||
/** |
|||
* Compiles the node to PHP. |
|||
* |
|||
* @param Twig_Compiler A Twig_Compiler instance |
|||
*/ |
|||
public function compile(Twig_Compiler $compiler) |
|||
{ |
|||
$compiler |
|||
->addDebugInfo($this) |
|||
->write('') |
|||
->subcompile($this->getNode('var')) |
|||
->raw(' = ') |
|||
; |
|||
|
|||
if ($this->getNode('expr') instanceof Twig_Node_Expression_Name && '_self' === $this->getNode('expr')->getAttribute('name')) { |
|||
$compiler->raw("\$this"); |
|||
} else { |
|||
$compiler |
|||
->raw('$this->env->loadTemplate(') |
|||
->subcompile($this->getNode('expr')) |
|||
->raw(")") |
|||
; |
|||
} |
|||
|
|||
$compiler->raw(";\n"); |
|||
} |
|||
} |
@ -0,0 +1,88 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of Twig. |
|||
* |
|||
* (c) 2009 Fabien Potencier |
|||
* (c) 2009 Armin Ronacher |
|||
* |
|||
* For the full copyright and license information, please view the LICENSE |
|||
* file that was distributed with this source code. |
|||
*/ |
|||
|
|||
/** |
|||
* Represents an include node. |
|||
* |
|||
* @package twig |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
*/ |
|||
class Twig_Node_Include extends Twig_Node implements Twig_NodeOutputInterface |
|||
{ |
|||
public function __construct(Twig_Node_Expression $expr, Twig_Node_Expression $variables = null, $only = false, $ignoreMissing = false, $lineno, $tag = null) |
|||
{ |
|||
parent::__construct(array('expr' => $expr, 'variables' => $variables), array('only' => (Boolean) $only, 'ignore_missing' => (Boolean) $ignoreMissing), $lineno, $tag); |
|||
} |
|||
|
|||
/** |
|||
* Compiles the node to PHP. |
|||
* |
|||
* @param Twig_Compiler A Twig_Compiler instance |
|||
*/ |
|||
public function compile(Twig_Compiler $compiler) |
|||
{ |
|||
$compiler->addDebugInfo($this); |
|||
|
|||
if ($this->getAttribute('ignore_missing')) { |
|||
$compiler |
|||
->write("try {\n") |
|||
->indent() |
|||
; |
|||
} |
|||
|
|||
if ($this->getNode('expr') instanceof Twig_Node_Expression_Constant) { |
|||
$compiler |
|||
->write("\$this->env->loadTemplate(") |
|||
->subcompile($this->getNode('expr')) |
|||
->raw(")->display(") |
|||
; |
|||
} else { |
|||
$compiler |
|||
->write("\$template = \$this->env->resolveTemplate(") |
|||
->subcompile($this->getNode('expr')) |
|||
->raw(");\n") |
|||
->write('$template->display(') |
|||
; |
|||
} |
|||
|
|||
if (false === $this->getAttribute('only')) { |
|||
if (null === $this->getNode('variables')) { |
|||
$compiler->raw('$context'); |
|||
} else { |
|||
$compiler |
|||
->raw('array_merge($context, ') |
|||
->subcompile($this->getNode('variables')) |
|||
->raw(')') |
|||
; |
|||
} |
|||
} else { |
|||
if (null === $this->getNode('variables')) { |
|||
$compiler->raw('array()'); |
|||
} else { |
|||
$compiler->subcompile($this->getNode('variables')); |
|||
} |
|||
} |
|||
|
|||
$compiler->raw(");\n"); |
|||
|
|||
if ($this->getAttribute('ignore_missing')) { |
|||
$compiler |
|||
->outdent() |
|||
->write("} catch (Twig_Error_Loader \$e) {\n") |
|||
->indent() |
|||
->write("// ignore missing template\n") |
|||
->outdent() |
|||
->write("}\n\n") |
|||
; |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,73 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* 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. |
|||
*/ |
|||
|
|||
/** |
|||
* Represents a macro node. |
|||
* |
|||
* @package twig |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
*/ |
|||
class Twig_Node_Macro extends Twig_Node |
|||
{ |
|||
public function __construct($name, Twig_NodeInterface $body, Twig_NodeInterface $arguments, $lineno, $tag = null) |
|||
{ |
|||
parent::__construct(array('body' => $body, 'arguments' => $arguments), array('name' => $name), $lineno, $tag); |
|||
} |
|||
|
|||
/** |
|||
* Compiles the node to PHP. |
|||
* |
|||
* @param Twig_Compiler A Twig_Compiler instance |
|||
*/ |
|||
public function compile(Twig_Compiler $compiler) |
|||
{ |
|||
$arguments = array(); |
|||
foreach ($this->getNode('arguments') as $argument) { |
|||
$arguments[] = '$'.$argument->getAttribute('name').' = null'; |
|||
} |
|||
|
|||
$compiler |
|||
->addDebugInfo($this) |
|||
->write(sprintf("public function get%s(%s)\n", $this->getAttribute('name'), implode(', ', $arguments)), "{\n") |
|||
->indent() |
|||
->write("\$context = array_merge(\$this->env->getGlobals(), array(\n") |
|||
->indent() |
|||
; |
|||
|
|||
foreach ($this->getNode('arguments') as $argument) { |
|||
$compiler |
|||
->write('') |
|||
->string($argument->getAttribute('name')) |
|||
->raw(' => $'.$argument->getAttribute('name')) |
|||
->raw(",\n") |
|||
; |
|||
} |
|||
|
|||
$compiler |
|||
->outdent() |
|||
->write("));\n\n") |
|||
->write("ob_start();\n") |
|||
->write("try {\n") |
|||
->indent() |
|||
->subcompile($this->getNode('body')) |
|||
->outdent() |
|||
->write("} catch(Exception \$e) {\n") |
|||
->indent() |
|||
->write("ob_end_clean();\n\n") |
|||
->write("throw \$e;\n") |
|||
->outdent() |
|||
->write("}\n\n") |
|||
->write("return ob_get_clean();\n") |
|||
->outdent() |
|||
->write("}\n\n") |
|||
; |
|||
} |
|||
} |
@ -0,0 +1,302 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of Twig. |
|||
* |
|||
* (c) 2009 Fabien Potencier |
|||
* (c) 2009 Armin Ronacher |
|||
* |
|||
* For the full copyright and license information, please view the LICENSE |
|||
* file that was distributed with this source code. |
|||
*/ |
|||
|
|||
/** |
|||
* Represents a module node. |
|||
* |
|||
* @package twig |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
*/ |
|||
class Twig_Node_Module extends Twig_Node |
|||
{ |
|||
public function __construct(Twig_NodeInterface $body, Twig_Node_Expression $parent = null, Twig_NodeInterface $blocks, Twig_NodeInterface $macros, Twig_NodeInterface $traits, $filename) |
|||
{ |
|||
parent::__construct(array('parent' => $parent, 'body' => $body, 'blocks' => $blocks, 'macros' => $macros, 'traits' => $traits), array('filename' => $filename), 1); |
|||
} |
|||
|
|||
/** |
|||
* Compiles the node to PHP. |
|||
* |
|||
* @param Twig_Compiler A Twig_Compiler instance |
|||
*/ |
|||
public function compile(Twig_Compiler $compiler) |
|||
{ |
|||
$this->compileTemplate($compiler); |
|||
} |
|||
|
|||
protected function compileTemplate(Twig_Compiler $compiler) |
|||
{ |
|||
$this->compileClassHeader($compiler); |
|||
|
|||
if (count($this->getNode('blocks')) || count($this->getNode('traits'))) { |
|||
$this->compileConstructor($compiler); |
|||
} |
|||
|
|||
$this->compileGetParent($compiler); |
|||
|
|||
$this->compileDisplayHeader($compiler); |
|||
|
|||
$this->compileDisplayBody($compiler); |
|||
|
|||
$this->compileDisplayFooter($compiler); |
|||
|
|||
$compiler->subcompile($this->getNode('blocks')); |
|||
|
|||
$this->compileMacros($compiler); |
|||
|
|||
$this->compileGetTemplateName($compiler); |
|||
|
|||
$this->compileIsTraitable($compiler); |
|||
|
|||
$this->compileClassFooter($compiler); |
|||
} |
|||
|
|||
protected function compileGetParent(Twig_Compiler $compiler) |
|||
{ |
|||
$compiler |
|||
->write("protected function doGetParent(array \$context)\n", "{\n") |
|||
->indent() |
|||
->write("return ") |
|||
; |
|||
|
|||
if (null === $this->getNode('parent')) { |
|||
$compiler->raw("false"); |
|||
} else { |
|||
if ($this->getNode('parent') instanceof Twig_Node_Expression_Constant) { |
|||
$compiler->subcompile($this->getNode('parent')); |
|||
} else { |
|||
$compiler |
|||
->raw("\$this->env->resolveTemplate(") |
|||
->subcompile($this->getNode('parent')) |
|||
->raw(")") |
|||
; |
|||
} |
|||
} |
|||
|
|||
$compiler |
|||
->raw(";\n") |
|||
->outdent() |
|||
->write("}\n\n") |
|||
; |
|||
} |
|||
|
|||
protected function compileDisplayBody(Twig_Compiler $compiler) |
|||
{ |
|||
$compiler->write("\$context = array_merge(\$this->env->getGlobals(), \$context);\n\n"); |
|||
$compiler->subcompile($this->getNode('body')); |
|||
|
|||
if (null !== $this->getNode('parent')) { |
|||
$compiler->write("\$this->getParent(\$context)->display(\$context, array_merge(\$this->blocks, \$blocks));\n"); |
|||
} |
|||
} |
|||
|
|||
protected function compileClassHeader(Twig_Compiler $compiler) |
|||
{ |
|||
$compiler |
|||
->write("<?php\n\n")
|
|||
// if the filename contains */, add a blank to avoid a PHP parse error |
|||
->write("/* ".str_replace('*/', '* /', $this->getAttribute('filename'))." */\n") |
|||
->write('class '.$compiler->getEnvironment()->getTemplateClass($this->getAttribute('filename'))) |
|||
->raw(sprintf(" extends %s\n", $compiler->getEnvironment()->getBaseTemplateClass())) |
|||
->write("{\n") |
|||
->indent() |
|||
; |
|||
} |
|||
|
|||
protected function compileConstructor(Twig_Compiler $compiler) |
|||
{ |
|||
$compiler |
|||
->write("public function __construct(Twig_Environment \$env)\n", "{\n") |
|||
->indent() |
|||
->write("parent::__construct(\$env);\n\n") |
|||
; |
|||
|
|||
$countTraits = count($this->getNode('traits')); |
|||
if ($countTraits) { |
|||
// traits |
|||
foreach ($this->getNode('traits') as $i => $trait) { |
|||
$this->compileLoadTemplate($compiler, $trait->getNode('template'), sprintf('$_trait_%s', $i)); |
|||
|
|||
$compiler |
|||
->addDebugInfo($trait->getNode('template')) |
|||
->write(sprintf("if (!\$_trait_%s->isTraitable()) {\n", $i)) |
|||
->indent() |
|||
->write("throw new Twig_Error_Runtime('Template \"'.") |
|||
->subcompile($trait->getNode('template')) |
|||
->raw(".'\" cannot be used as a trait.');\n") |
|||
->outdent() |
|||
->write("}\n") |
|||
->write(sprintf("\$_trait_%s_blocks = \$_trait_%s->getBlocks();\n\n", $i, $i)) |
|||
; |
|||
|
|||
foreach ($trait->getNode('targets') as $key => $value) { |
|||
$compiler |
|||
->write(sprintf("\$_trait_%s_blocks[", $i)) |
|||
->subcompile($value) |
|||
->raw(sprintf("] = \$_trait_%s_blocks[", $i)) |
|||
->string($key) |
|||
->raw(sprintf("]; unset(\$_trait_%s_blocks[", $i)) |
|||
->string($key) |
|||
->raw("]);\n\n") |
|||
; |
|||
} |
|||
} |
|||
|
|||
$compiler |
|||
->write("\$this->blocks = array_merge(\n") |
|||
->indent() |
|||
; |
|||
|
|||
for ($i = 0; $i < $countTraits; $i++) { |
|||
$compiler |
|||
->write(sprintf("\$_trait_%s_blocks,\n", $i)) |
|||
; |
|||
} |
|||
|
|||
$compiler |
|||
->write("array(\n") |
|||
; |
|||
} else { |
|||
$compiler |
|||
->write("\$this->blocks = array(\n") |
|||
; |
|||
} |
|||
|
|||
// blocks |
|||
$compiler |
|||
->indent() |
|||
; |
|||
|
|||
foreach ($this->getNode('blocks') as $name => $node) { |
|||
$compiler |
|||
->write(sprintf("'%s' => array(\$this, 'block_%s'),\n", $name, $name)) |
|||
; |
|||
} |
|||
|
|||
if ($countTraits) { |
|||
$compiler |
|||
->outdent() |
|||
->write(")\n") |
|||
; |
|||
} |
|||
|
|||
$compiler |
|||
->outdent() |
|||
->write(");\n") |
|||
->outdent() |
|||
->write("}\n\n"); |
|||
; |
|||
} |
|||
|
|||
protected function compileDisplayHeader(Twig_Compiler $compiler) |
|||
{ |
|||
$compiler |
|||
->write("protected function doDisplay(array \$context, array \$blocks = array())\n", "{\n") |
|||
->indent() |
|||
; |
|||
} |
|||
|
|||
protected function compileDisplayFooter(Twig_Compiler $compiler) |
|||
{ |
|||
$compiler |
|||
->outdent() |
|||
->write("}\n\n") |
|||
; |
|||
} |
|||
|
|||
protected function compileClassFooter(Twig_Compiler $compiler) |
|||
{ |
|||
$compiler |
|||
->outdent() |
|||
->write("}\n") |
|||
; |
|||
} |
|||
|
|||
protected function compileMacros(Twig_Compiler $compiler) |
|||
{ |
|||
$compiler->subcompile($this->getNode('macros')); |
|||
} |
|||
|
|||
protected function compileGetTemplateName(Twig_Compiler $compiler) |
|||
{ |
|||
$compiler |
|||
->write("public function getTemplateName()\n", "{\n") |
|||
->indent() |
|||
->write('return ') |
|||
->repr($this->getAttribute('filename')) |
|||
->raw(";\n") |
|||
->outdent() |
|||
->write("}\n\n") |
|||
; |
|||
} |
|||
|
|||
protected function compileIsTraitable(Twig_Compiler $compiler) |
|||
{ |
|||
// A template can be used as a trait if: |
|||
// * it has no parent |
|||
// * it has no macros |
|||
// * it has no body |
|||
// |
|||
// Put another way, a template can be used as a trait if it |
|||
// only contains blocks and use statements. |
|||
$traitable = null === $this->getNode('parent') && 0 === count($this->getNode('macros')); |
|||
if ($traitable) { |
|||
if (!count($nodes = $this->getNode('body'))) { |
|||
$nodes = new Twig_Node(array($this->getNode('body'))); |
|||
} |
|||
|
|||
foreach ($nodes as $node) { |
|||
if ($node instanceof Twig_Node_Text && ctype_space($node->getAttribute('data'))) { |
|||
continue; |
|||
} |
|||
|
|||
if ($node instanceof Twig_Node_BlockReference) { |
|||
continue; |
|||
} |
|||
|
|||
$traitable = false; |
|||
break; |
|||
} |
|||
} |
|||
|
|||
$compiler |
|||
->write("public function isTraitable()\n", "{\n") |
|||
->indent() |
|||
->write(sprintf("return %s;\n", $traitable ? 'true' : 'false')) |
|||
->outdent() |
|||
->write("}\n") |
|||
; |
|||
} |
|||
|
|||
public function compileLoadTemplate(Twig_Compiler $compiler, $node, $var) |
|||
{ |
|||
if ($node instanceof Twig_Node_Expression_Constant) { |
|||
$compiler |
|||
->write(sprintf("%s = \$this->env->loadTemplate(", $var)) |
|||
->subcompile($node) |
|||
->raw(");\n") |
|||
; |
|||
} else { |
|||
$compiler |
|||
->write(sprintf("%s = ", $var)) |
|||
->subcompile($node) |
|||
->raw(";\n") |
|||
->write(sprintf("if (!%s", $var)) |
|||
->raw(" instanceof Twig_Template) {\n") |
|||
->indent() |
|||
->write(sprintf("%s = \$this->env->loadTemplate(%s);\n", $var, $var)) |
|||
->outdent() |
|||
->write("}\n") |
|||
; |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,40 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of Twig. |
|||
* |
|||
* (c) 2009 Fabien Potencier |
|||
* (c) 2009 Armin Ronacher |
|||
* |
|||
* For the full copyright and license information, please view the LICENSE |
|||
* file that was distributed with this source code. |
|||
*/ |
|||
|
|||
/** |
|||
* Represents a node that outputs an expression. |
|||
* |
|||
* @package twig |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
*/ |
|||
class Twig_Node_Print extends Twig_Node implements Twig_NodeOutputInterface |
|||
{ |
|||
public function __construct(Twig_Node_Expression $expr, $lineno, $tag = null) |
|||
{ |
|||
parent::__construct(array('expr' => $expr), array(), $lineno, $tag); |
|||
} |
|||
|
|||
/** |
|||
* Compiles the node to PHP. |
|||
* |
|||
* @param Twig_Compiler A Twig_Compiler instance |
|||
*/ |
|||
public function compile(Twig_Compiler $compiler) |
|||
{ |
|||
$compiler |
|||
->addDebugInfo($this) |
|||
->write('echo ') |
|||
->subcompile($this->getNode('expr')) |
|||
->raw(";\n") |
|||
; |
|||
} |
|||
} |
@ -0,0 +1,48 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of Twig. |
|||
* |
|||
* (c) 2010 Fabien Potencier |
|||
* |
|||
* For the full copyright and license information, please view the LICENSE |
|||
* file that was distributed with this source code. |
|||
*/ |
|||
|
|||
/** |
|||
* Represents a sandbox node. |
|||
* |
|||
* @package twig |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
*/ |
|||
class Twig_Node_Sandbox extends Twig_Node |
|||
{ |
|||
public function __construct(Twig_NodeInterface $body, $lineno, $tag = null) |
|||
{ |
|||
parent::__construct(array('body' => $body), array(), $lineno, $tag); |
|||
} |
|||
|
|||
/** |
|||
* Compiles the node to PHP. |
|||
* |
|||
* @param Twig_Compiler A Twig_Compiler instance |
|||
*/ |
|||
public function compile(Twig_Compiler $compiler) |
|||
{ |
|||
$compiler |
|||
->addDebugInfo($this) |
|||
->write("\$sandbox = \$this->env->getExtension('sandbox');\n") |
|||
->write("if (!\$alreadySandboxed = \$sandbox->isSandboxed()) {\n") |
|||
->indent() |
|||
->write("\$sandbox->enableSandbox();\n") |
|||
->outdent() |
|||
->write("}\n") |
|||
->subcompile($this->getNode('body')) |
|||
->write("if (!\$alreadySandboxed) {\n") |
|||
->indent() |
|||
->write("\$sandbox->disableSandbox();\n") |
|||
->outdent() |
|||
->write("}\n") |
|||
; |
|||
} |
|||
} |
@ -0,0 +1,71 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of Twig. |
|||
* |
|||
* (c) 2009 Fabien Potencier |
|||
* (c) 2009 Armin Ronacher |
|||
* |
|||
* For the full copyright and license information, please view the LICENSE |
|||
* file that was distributed with this source code. |
|||
*/ |
|||
|
|||
/** |
|||
* Represents a module node. |
|||
* |
|||
* @package twig |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
*/ |
|||
class Twig_Node_SandboxedModule extends Twig_Node_Module |
|||
{ |
|||
protected $usedFilters; |
|||
protected $usedTags; |
|||
protected $usedFunctions; |
|||
|
|||
public function __construct(Twig_Node_Module $node, array $usedFilters, array $usedTags, array $usedFunctions) |
|||
{ |
|||
parent::__construct($node->getNode('body'), $node->getNode('parent'), $node->getNode('blocks'), $node->getNode('macros'), $node->getNode('traits'), $node->getAttribute('filename'), $node->getLine(), $node->getNodeTag()); |
|||
|
|||
$this->usedFilters = $usedFilters; |
|||
$this->usedTags = $usedTags; |
|||
$this->usedFunctions = $usedFunctions; |
|||
} |
|||
|
|||
protected function compileDisplayBody(Twig_Compiler $compiler) |
|||
{ |
|||
if (null === $this->getNode('parent')) { |
|||
$compiler->write("\$this->checkSecurity();\n"); |
|||
} |
|||
|
|||
parent::compileDisplayBody($compiler); |
|||
} |
|||
|
|||
protected function compileDisplayFooter(Twig_Compiler $compiler) |
|||
{ |
|||
parent::compileDisplayFooter($compiler); |
|||
|
|||
$compiler |
|||
->write("protected function checkSecurity() {\n") |
|||
->indent() |
|||
->write("\$this->env->getExtension('sandbox')->checkSecurity(\n") |
|||
->indent() |
|||
->write(!$this->usedTags ? "array(),\n" : "array('".implode('\', \'', $this->usedTags)."'),\n") |
|||
->write(!$this->usedFilters ? "array(),\n" : "array('".implode('\', \'', $this->usedFilters)."'),\n") |
|||
->write(!$this->usedFunctions ? "array()\n" : "array('".implode('\', \'', $this->usedFunctions)."')\n") |
|||
->outdent() |
|||
->write(");\n") |
|||
; |
|||
|
|||
if (null !== $this->getNode('parent')) { |
|||
$compiler |
|||
->raw("\n") |
|||
->write("\$this->parent->checkSecurity();\n") |
|||
; |
|||
} |
|||
|
|||
$compiler |
|||
->outdent() |
|||
->write("}\n\n") |
|||
; |
|||
} |
|||
} |
@ -0,0 +1,60 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of Twig. |
|||
* |
|||
* (c) 2010 Fabien Potencier |
|||
* |
|||
* For the full copyright and license information, please view the LICENSE |
|||
* file that was distributed with this source code. |
|||
*/ |
|||
|
|||
/** |
|||
* Twig_Node_SandboxedPrint adds a check for the __toString() method |
|||
* when the variable is an object and the sandbox is activated. |
|||
* |
|||
* When there is a simple Print statement, like {{ article }}, |
|||
* and if the sandbox is enabled, we need to check that the __toString() |
|||
* method is allowed if 'article' is an object. |
|||
* |
|||
* @package twig |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
*/ |
|||
class Twig_Node_SandboxedPrint extends Twig_Node_Print |
|||
{ |
|||
public function __construct(Twig_Node_Expression $expr, $lineno, $tag = null) |
|||
{ |
|||
parent::__construct($expr, $lineno, $tag); |
|||
} |
|||
|
|||
/** |
|||
* Compiles the node to PHP. |
|||
* |
|||
* @param Twig_Compiler A Twig_Compiler instance |
|||
*/ |
|||
public function compile(Twig_Compiler $compiler) |
|||
{ |
|||
$compiler |
|||
->addDebugInfo($this) |
|||
->write('echo $this->env->getExtension(\'sandbox\')->ensureToStringAllowed(') |
|||
->subcompile($this->getNode('expr')) |
|||
->raw(");\n") |
|||
; |
|||
} |
|||
|
|||
/** |
|||
* Removes node filters. |
|||
* |
|||
* This is mostly needed when another visitor adds filters (like the escaper one). |
|||
* |
|||
* @param Twig_Node $node A Node |
|||
*/ |
|||
protected function removeNodeFilter($node) |
|||
{ |
|||
if ($node instanceof Twig_Node_Expression_Filter) { |
|||
return $this->removeNodeFilter($node->getNode('node')); |
|||
} |
|||
|
|||
return $node; |
|||
} |
|||
} |
@ -0,0 +1,102 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of Twig. |
|||
* |
|||
* (c) 2010 Fabien Potencier |
|||
* |
|||
* For the full copyright and license information, please view the LICENSE |
|||
* file that was distributed with this source code. |
|||
*/ |
|||
|
|||
/** |
|||
* Represents a set node. |
|||
* |
|||
* @package twig |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
*/ |
|||
class Twig_Node_Set extends Twig_Node |
|||
{ |
|||
public function __construct($capture, Twig_NodeInterface $names, Twig_NodeInterface $values, $lineno, $tag = null) |
|||
{ |
|||
parent::__construct(array('names' => $names, 'values' => $values), array('capture' => $capture, 'safe' => false), $lineno, $tag); |
|||
|
|||
/* |
|||
* Optimizes the node when capture is used for a large block of text. |
|||
* |
|||
* {% set foo %}foo{% endset %} is compiled to $context['foo'] = new Twig_Markup("foo"); |
|||
*/ |
|||
if ($this->getAttribute('capture')) { |
|||
$this->setAttribute('safe', true); |
|||
|
|||
$values = $this->getNode('values'); |
|||
if ($values instanceof Twig_Node_Text) { |
|||
$this->setNode('values', new Twig_Node_Expression_Constant($values->getAttribute('data'), $values->getLine())); |
|||
$this->setAttribute('capture', false); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Compiles the node to PHP. |
|||
* |
|||
* @param Twig_Compiler A Twig_Compiler instance |
|||
*/ |
|||
public function compile(Twig_Compiler $compiler) |
|||
{ |
|||
$compiler->addDebugInfo($this); |
|||
|
|||
if (count($this->getNode('names')) > 1) { |
|||
$compiler->write('list('); |
|||
foreach ($this->getNode('names') as $idx => $node) { |
|||
if ($idx) { |
|||
$compiler->raw(', '); |
|||
} |
|||
|
|||
$compiler->subcompile($node); |
|||
} |
|||
$compiler->raw(')'); |
|||
} else { |
|||
if ($this->getAttribute('capture')) { |
|||
$compiler |
|||
->write("ob_start();\n") |
|||
->subcompile($this->getNode('values')) |
|||
; |
|||
} |
|||
|
|||
$compiler->subcompile($this->getNode('names'), false); |
|||
|
|||
if ($this->getAttribute('capture')) { |
|||
$compiler->raw(" = new Twig_Markup(ob_get_clean())"); |
|||
} |
|||
} |
|||
|
|||
if (!$this->getAttribute('capture')) { |
|||
$compiler->raw(' = '); |
|||
|
|||
if (count($this->getNode('names')) > 1) { |
|||
$compiler->write('array('); |
|||
foreach ($this->getNode('values') as $idx => $value) { |
|||
if ($idx) { |
|||
$compiler->raw(', '); |
|||
} |
|||
|
|||
$compiler->subcompile($value); |
|||
} |
|||
$compiler->raw(')'); |
|||
} else { |
|||
if ($this->getAttribute('safe')) { |
|||
$compiler |
|||
->raw("new Twig_Markup(") |
|||
->subcompile($this->getNode('values')) |
|||
->raw(")") |
|||
; |
|||
} else { |
|||
$compiler->subcompile($this->getNode('values')); |
|||
} |
|||
} |
|||
} |
|||
|
|||
$compiler->raw(";\n"); |
|||
} |
|||
} |
@ -0,0 +1,41 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of Twig. |
|||
* |
|||
* (c) 2010 Fabien Potencier |
|||
* |
|||
* For the full copyright and license information, please view the LICENSE |
|||
* file that was distributed with this source code. |
|||
*/ |
|||
|
|||
/** |
|||
* Represents a spaceless node. |
|||
* |
|||
* It removes spaces between HTML tags. |
|||
* |
|||
* @package twig |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
*/ |
|||
class Twig_Node_Spaceless extends Twig_Node |
|||
{ |
|||
public function __construct(Twig_NodeInterface $body, $lineno, $tag = 'spaceless') |
|||
{ |
|||
parent::__construct(array('body' => $body), array(), $lineno, $tag); |
|||
} |
|||
|
|||
/** |
|||
* Compiles the node to PHP. |
|||
* |
|||
* @param Twig_Compiler A Twig_Compiler instance |
|||
*/ |
|||
public function compile(Twig_Compiler $compiler) |
|||
{ |
|||
$compiler |
|||
->addDebugInfo($this) |
|||
->write("ob_start();\n") |
|||
->subcompile($this->getNode('body')) |
|||
->write("echo trim(preg_replace('/>\s+</', '><', ob_get_clean()));\n") |
|||
; |
|||
} |
|||
} |
@ -0,0 +1,40 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of Twig. |
|||
* |
|||
* (c) 2009 Fabien Potencier |
|||
* (c) 2009 Armin Ronacher |
|||
* |
|||
* For the full copyright and license information, please view the LICENSE |
|||
* file that was distributed with this source code. |
|||
*/ |
|||
|
|||
/** |
|||
* Represents a text node. |
|||
* |
|||
* @package twig |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
*/ |
|||
class Twig_Node_Text extends Twig_Node implements Twig_NodeOutputInterface |
|||
{ |
|||
public function __construct($data, $lineno) |
|||
{ |
|||
parent::__construct(array(), array('data' => $data), $lineno); |
|||
} |
|||
|
|||
/** |
|||
* Compiles the node to PHP. |
|||
* |
|||
* @param Twig_Compiler A Twig_Compiler instance |
|||
*/ |
|||
public function compile(Twig_Compiler $compiler) |
|||
{ |
|||
$compiler |
|||
->addDebugInfo($this) |
|||
->write('echo ') |
|||
->string($this->getAttribute('data')) |
|||
->raw(";\n") |
|||
; |
|||
} |
|||
} |
@ -0,0 +1,30 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of Twig. |
|||
* |
|||
* (c) 2010 Fabien Potencier |
|||
* |
|||
* For the full copyright and license information, please view the LICENSE |
|||
* file that was distributed with this source code. |
|||
*/ |
|||
|
|||
/** |
|||
* Represents a node in the AST. |
|||
* |
|||
* @package twig |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
*/ |
|||
interface Twig_NodeInterface |
|||
{ |
|||
/** |
|||
* Compiles the node to PHP. |
|||
* |
|||
* @param Twig_Compiler A Twig_Compiler instance |
|||
*/ |
|||
function compile(Twig_Compiler $compiler); |
|||
|
|||
function getLine(); |
|||
|
|||
function getNodeTag(); |
|||
} |
@ -0,0 +1,20 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of Twig. |
|||
* |
|||
* (c) 2010 Fabien Potencier |
|||
* |
|||
* For the full copyright and license information, please view the LICENSE |
|||
* file that was distributed with this source code. |
|||
*/ |
|||
|
|||
/** |
|||
* Represents a displayable node in the AST. |
|||
* |
|||
* @package twig |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
*/ |
|||
interface Twig_NodeOutputInterface |
|||
{ |
|||
} |
@ -0,0 +1,89 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* 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. |
|||
*/ |
|||
|
|||
/** |
|||
* Twig_NodeTraverser is a node traverser. |
|||
* |
|||
* It visits all nodes and their children and call the given visitor for each. |
|||
* |
|||
* @package twig |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
*/ |
|||
class Twig_NodeTraverser |
|||
{ |
|||
protected $env; |
|||
protected $visitors; |
|||
|
|||
/** |
|||
* Constructor. |
|||
* |
|||
* @param Twig_Environment $env A Twig_Environment instance |
|||
* @param array $visitors An array of Twig_NodeVisitorInterface instances |
|||
*/ |
|||
public function __construct(Twig_Environment $env, array $visitors = array()) |
|||
{ |
|||
$this->env = $env; |
|||
$this->visitors = array(); |
|||
foreach ($visitors as $visitor) { |
|||
$this->addVisitor($visitor); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Adds a visitor. |
|||
* |
|||
* @param Twig_NodeVisitorInterface $visitor A Twig_NodeVisitorInterface instance |
|||
*/ |
|||
public function addVisitor(Twig_NodeVisitorInterface $visitor) |
|||
{ |
|||
if (!isset($this->visitors[$visitor->getPriority()])) { |
|||
$this->visitors[$visitor->getPriority()] = array(); |
|||
} |
|||
|
|||
$this->visitors[$visitor->getPriority()][] = $visitor; |
|||
} |
|||
|
|||
/** |
|||
* Traverses a node and calls the registered visitors. |
|||
* |
|||
* @param Twig_NodeInterface $node A Twig_NodeInterface instance |
|||
*/ |
|||
public function traverse(Twig_NodeInterface $node) |
|||
{ |
|||
ksort($this->visitors); |
|||
foreach ($this->visitors as $visitors) { |
|||
foreach ($visitors as $visitor) { |
|||
$node = $this->traverseForVisitor($visitor, $node); |
|||
} |
|||
} |
|||
|
|||
return $node; |
|||
} |
|||
|
|||
protected function traverseForVisitor(Twig_NodeVisitorInterface $visitor, Twig_NodeInterface $node = null) |
|||
{ |
|||
if (null === $node) { |
|||
return null; |
|||
} |
|||
|
|||
$node = $visitor->enterNode($node, $this->env); |
|||
|
|||
foreach ($node as $k => $n) { |
|||
if (false !== $n = $this->traverseForVisitor($visitor, $n)) { |
|||
$node->setNode($k, $n); |
|||
} else { |
|||
$node->removeNode($k); |
|||
} |
|||
} |
|||
|
|||
return $visitor->leaveNode($node, $this->env); |
|||
} |
|||
} |
@ -0,0 +1,161 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* 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. |
|||
*/ |
|||
|
|||
/** |
|||
* Twig_NodeVisitor_Escaper implements output escaping. |
|||
* |
|||
* @package twig |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
*/ |
|||
class Twig_NodeVisitor_Escaper implements Twig_NodeVisitorInterface |
|||
{ |
|||
protected $statusStack = array(); |
|||
protected $blocks = array(); |
|||
|
|||
protected $safeAnalysis; |
|||
protected $traverser; |
|||
|
|||
function __construct() |
|||
{ |
|||
$this->safeAnalysis = new Twig_NodeVisitor_SafeAnalysis(); |
|||
} |
|||
|
|||
/** |
|||
* Called before child nodes are visited. |
|||
* |
|||
* @param Twig_NodeInterface $node The node to visit |
|||
* @param Twig_Environment $env The Twig environment instance |
|||
* |
|||
* @param Twig_NodeInterface The modified node |
|||
*/ |
|||
public function enterNode(Twig_NodeInterface $node, Twig_Environment $env) |
|||
{ |
|||
if ($node instanceof Twig_Node_AutoEscape) { |
|||
$this->statusStack[] = $node->getAttribute('value'); |
|||
} elseif ($node instanceof Twig_Node_Block) { |
|||
$this->statusStack[] = isset($this->blocks[$node->getAttribute('name')]) ? $this->blocks[$node->getAttribute('name')] : $this->needEscaping($env); |
|||
} |
|||
|
|||
return $node; |
|||
} |
|||
|
|||
/** |
|||
* Called after child nodes are visited. |
|||
* |
|||
* @param Twig_NodeInterface $node The node to visit |
|||
* @param Twig_Environment $env The Twig environment instance |
|||
* |
|||
* @param Twig_NodeInterface The modified node |
|||
*/ |
|||
public function leaveNode(Twig_NodeInterface $node, Twig_Environment $env) |
|||
{ |
|||
if ($node instanceof Twig_Node_Expression_Filter) { |
|||
return $this->preEscapeFilterNode($node, $env); |
|||
} elseif ($node instanceof Twig_Node_Print) { |
|||
return $this->escapePrintNode($node, $env, $this->needEscaping($env)); |
|||
} |
|||
|
|||
if ($node instanceof Twig_Node_AutoEscape || $node instanceof Twig_Node_Block) { |
|||
array_pop($this->statusStack); |
|||
} elseif ($node instanceof Twig_Node_BlockReference) { |
|||
$this->blocks[$node->getAttribute('name')] = $this->needEscaping($env); |
|||
} |
|||
|
|||
return $node; |
|||
} |
|||
|
|||
protected function escapePrintNode(Twig_Node_Print $node, Twig_Environment $env, $type) |
|||
{ |
|||
if (false === $type) { |
|||
return $node; |
|||
} |
|||
|
|||
$expression = $node->getNode('expr'); |
|||
|
|||
if ($this->isSafeFor($type, $expression, $env)) { |
|||
return $node; |
|||
} |
|||
|
|||
$class = get_class($node); |
|||
|
|||
return new $class( |
|||
$this->getEscaperFilter($type, $expression), |
|||
$node->getLine() |
|||
); |
|||
} |
|||
|
|||
protected function preEscapeFilterNode(Twig_Node_Expression_Filter $filter, Twig_Environment $env) |
|||
{ |
|||
$name = $filter->getNode('filter')->getAttribute('value'); |
|||
|
|||
if (false !== $f = $env->getFilter($name)) { |
|||
$type = $f->getPreEscape(); |
|||
if (null === $type) { |
|||
return $filter; |
|||
} |
|||
|
|||
$node = $filter->getNode('node'); |
|||
if ($this->isSafeFor($type, $node, $env)) { |
|||
return $filter; |
|||
} |
|||
|
|||
$filter->setNode('node', $this->getEscaperFilter($type, $node)); |
|||
|
|||
return $filter; |
|||
} |
|||
|
|||
return $filter; |
|||
} |
|||
|
|||
protected function isSafeFor($type, Twig_NodeInterface $expression, $env) |
|||
{ |
|||
$safe = $this->safeAnalysis->getSafe($expression); |
|||
|
|||
if (null === $safe) { |
|||
if (null === $this->traverser) { |
|||
$this->traverser = new Twig_NodeTraverser($env, array($this->safeAnalysis)); |
|||
} |
|||
$this->traverser->traverse($expression); |
|||
$safe = $this->safeAnalysis->getSafe($expression); |
|||
} |
|||
|
|||
return in_array($type, $safe) || in_array('all', $safe); |
|||
} |
|||
|
|||
protected function needEscaping(Twig_Environment $env) |
|||
{ |
|||
if (count($this->statusStack)) { |
|||
return $this->statusStack[count($this->statusStack) - 1]; |
|||
} |
|||
|
|||
if ($env->hasExtension('escaper') && $env->getExtension('escaper')->isGlobal()) { |
|||
return 'html'; |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
protected function getEscaperFilter($type, Twig_NodeInterface $node) |
|||
{ |
|||
$line = $node->getLine(); |
|||
$name = new Twig_Node_Expression_Constant('escape', $line); |
|||
$args = new Twig_Node(array(new Twig_Node_Expression_Constant((string) $type, $line))); |
|||
return new Twig_Node_Expression_Filter($node, $name, $args, $line); |
|||
} |
|||
|
|||
/** |
|||
* {@inheritdoc} |
|||
*/ |
|||
public function getPriority() |
|||
{ |
|||
return 0; |
|||
} |
|||
} |
@ -0,0 +1,190 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of Twig. |
|||
* |
|||
* (c) 2010 Fabien Potencier |
|||
* |
|||
* For the full copyright and license information, please view the LICENSE |
|||
* file that was distributed with this source code. |
|||
*/ |
|||
|
|||
/** |
|||
* Twig_NodeVisitor_Optimizer tries to optimizes the AST. |
|||
* |
|||
* This visitor is always the last registered one. |
|||
* |
|||
* You can configure which optimizations you want to activate via the |
|||
* optimizer mode. |
|||
* |
|||
* @package twig |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
*/ |
|||
class Twig_NodeVisitor_Optimizer implements Twig_NodeVisitorInterface |
|||
{ |
|||
const OPTIMIZE_ALL = -1; |
|||
const OPTIMIZE_NONE = 0; |
|||
const OPTIMIZE_FOR = 2; |
|||
const OPTIMIZE_RAW_FILTER = 4; |
|||
|
|||
protected $loops = array(); |
|||
protected $optimizers; |
|||
|
|||
/** |
|||
* Constructor. |
|||
* |
|||
* @param integer $optimizers The optimizer mode |
|||
*/ |
|||
public function __construct($optimizers = -1) |
|||
{ |
|||
if (!is_int($optimizers) || $optimizers > 2) { |
|||
throw new InvalidArgumentException(sprintf('Optimizer mode "%s" is not valid.', $optimizers)); |
|||
} |
|||
|
|||
$this->optimizers = $optimizers; |
|||
} |
|||
|
|||
/** |
|||
* {@inheritdoc} |
|||
*/ |
|||
public function enterNode(Twig_NodeInterface $node, Twig_Environment $env) |
|||
{ |
|||
if (self::OPTIMIZE_FOR === (self::OPTIMIZE_FOR & $this->optimizers)) { |
|||
$this->enterOptimizeFor($node, $env); |
|||
} |
|||
|
|||
return $node; |
|||
} |
|||
|
|||
/** |
|||
* {@inheritdoc} |
|||
*/ |
|||
public function leaveNode(Twig_NodeInterface $node, Twig_Environment $env) |
|||
{ |
|||
if (self::OPTIMIZE_FOR === (self::OPTIMIZE_FOR & $this->optimizers)) { |
|||
$this->leaveOptimizeFor($node, $env); |
|||
} |
|||
|
|||
if (self::OPTIMIZE_RAW_FILTER === (self::OPTIMIZE_RAW_FILTER & $this->optimizers)) { |
|||
$node = $this->optimizeRawFilter($node, $env); |
|||
} |
|||
|
|||
$node = $this->optimizeRenderBlock($node, $env); |
|||
|
|||
return $node; |
|||
} |
|||
|
|||
/** |
|||
* Replaces "echo $this->renderBlock()" with "$this->displayBlock()". |
|||
* |
|||
* @param Twig_NodeInterface $node A Node |
|||
* @param Twig_Environment $env The current Twig environment |
|||
*/ |
|||
protected function optimizeRenderBlock($node, $env) |
|||
{ |
|||
if ($node instanceof Twig_Node_Print && $node->getNode('expr') instanceof Twig_Node_Expression_BlockReference) { |
|||
$node->getNode('expr')->setAttribute('output', true); |
|||
|
|||
return $node->getNode('expr'); |
|||
} |
|||
|
|||
return $node; |
|||
} |
|||
|
|||
/** |
|||
* Removes "raw" filters. |
|||
* |
|||
* @param Twig_NodeInterface $node A Node |
|||
* @param Twig_Environment $env The current Twig environment |
|||
*/ |
|||
protected function optimizeRawFilter($node, $env) |
|||
{ |
|||
if ($node instanceof Twig_Node_Expression_Filter && 'raw' == $node->getNode('filter')->getAttribute('value')) { |
|||
return $node->getNode('node'); |
|||
} |
|||
|
|||
return $node; |
|||
} |
|||
|
|||
/** |
|||
* Optimizes "for" tag by removing the "loop" variable creation whenever possible. |
|||
* |
|||
* @param Twig_NodeInterface $node A Node |
|||
* @param Twig_Environment $env The current Twig environment |
|||
*/ |
|||
protected function enterOptimizeFor($node, $env) |
|||
{ |
|||
if ($node instanceof Twig_Node_For) { |
|||
// disable the loop variable by default |
|||
$node->setAttribute('with_loop', false); |
|||
array_unshift($this->loops, $node); |
|||
} elseif (!$this->loops) { |
|||
// we are outside a loop |
|||
return; |
|||
} |
|||
|
|||
// when do we need to add the loop variable back? |
|||
|
|||
// the loop variable is referenced for the current loop |
|||
elseif ($node instanceof Twig_Node_Expression_Name && 'loop' === $node->getAttribute('name')) { |
|||
$this->addLoopToCurrent(); |
|||
} |
|||
|
|||
// block reference |
|||
elseif ($node instanceof Twig_Node_BlockReference || $node instanceof Twig_Node_Expression_BlockReference) { |
|||
$this->addLoopToCurrent(); |
|||
} |
|||
|
|||
// include without the only attribute |
|||
elseif ($node instanceof Twig_Node_Include && !$node->getAttribute('only')) { |
|||
$this->addLoopToAll(); |
|||
} |
|||
|
|||
// the loop variable is referenced via an attribute |
|||
elseif ($node instanceof Twig_Node_Expression_GetAttr |
|||
&& (!$node->getNode('attribute') instanceof Twig_Node_Expression_Constant |
|||
|| 'parent' === $node->getNode('attribute')->getAttribute('value') |
|||
) |
|||
&& (true === $this->loops[0]->getAttribute('with_loop') |
|||
|| ($node->getNode('node') instanceof Twig_Node_Expression_Name |
|||
&& 'loop' === $node->getNode('node')->getAttribute('name') |
|||
) |
|||
) |
|||
) { |
|||
$this->addLoopToAll(); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Optimizes "for" tag by removing the "loop" variable creation whenever possible. |
|||
* |
|||
* @param Twig_NodeInterface $node A Node |
|||
* @param Twig_Environment $env The current Twig environment |
|||
*/ |
|||
protected function leaveOptimizeFor($node, $env) |
|||
{ |
|||
if ($node instanceof Twig_Node_For) { |
|||
array_shift($this->loops); |
|||
} |
|||
} |
|||
|
|||
protected function addLoopToCurrent() |
|||
{ |
|||
$this->loops[0]->setAttribute('with_loop', true); |
|||
} |
|||
|
|||
protected function addLoopToAll() |
|||
{ |
|||
foreach ($this->loops as $loop) { |
|||
$loop->setAttribute('with_loop', true); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* {@inheritdoc} |
|||
*/ |
|||
public function getPriority() |
|||
{ |
|||
return 255; |
|||
} |
|||
} |
@ -0,0 +1,107 @@ |
|||
<?php |
|||
|
|||
class Twig_NodeVisitor_SafeAnalysis implements Twig_NodeVisitorInterface |
|||
{ |
|||
protected $data = array(); |
|||
|
|||
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']; |
|||
} |
|||
} |
|||
} |
|||
return null; |
|||
} |
|||
|
|||
protected function setSafe(Twig_NodeInterface $node, array $safe) |
|||
{ |
|||
$hash = spl_object_hash($node); |
|||
if (isset($this->data[$hash])) { |
|||
foreach($this->data[$hash] as &$bucket) { |
|||
if ($bucket['key'] === $node) { |
|||
$bucket['value'] = $safe; |
|||
return; |
|||
} |
|||
} |
|||
} |
|||
$this->data[$hash][] = array( |
|||
'key' => $node, |
|||
'value' => $safe, |
|||
); |
|||
} |
|||
|
|||
public function enterNode(Twig_NodeInterface $node, Twig_Environment $env) |
|||
{ |
|||
return $node; |
|||
} |
|||
|
|||
public function leaveNode(Twig_NodeInterface $node, Twig_Environment $env) |
|||
{ |
|||
if ($node instanceof Twig_Node_Expression_Constant) { |
|||
// constants are marked safe for all |
|||
$this->setSafe($node, array('all')); |
|||
} elseif ($node instanceof Twig_Node_Expression_BlockReference) { |
|||
// blocks are safe by definition |
|||
$this->setSafe($node, array('all')); |
|||
} elseif ($node instanceof Twig_Node_Expression_Parent) { |
|||
// parent block is safe by definition |
|||
$this->setSafe($node, array('all')); |
|||
} elseif ($node instanceof Twig_Node_Expression_Conditional) { |
|||
// intersect safeness of both operands |
|||
$safe = $this->intersectSafe($this->getSafe($node->getNode('expr2')), $this->getSafe($node->getNode('expr3'))); |
|||
$this->setSafe($node, $safe); |
|||
} elseif ($node instanceof Twig_Node_Expression_Filter) { |
|||
// filter expression is safe when the filter is safe |
|||
$name = $node->getNode('filter')->getAttribute('value'); |
|||
$args = $node->getNode('arguments'); |
|||
if (false !== $filter = $env->getFilter($name)) { |
|||
$this->setSafe($node, $filter->getSafe($args)); |
|||
} else { |
|||
$this->setSafe($node, array()); |
|||
} |
|||
} elseif ($node instanceof Twig_Node_Expression_Function) { |
|||
// function expression is safe when the function is safe |
|||
$name = $node->getAttribute('name'); |
|||
$args = $node->getNode('arguments'); |
|||
$function = $env->getFunction($name); |
|||
if (false !== $function) { |
|||
$this->setSafe($node, $function->getSafe($args)); |
|||
} else { |
|||
$this->setSafe($node, array()); |
|||
} |
|||
} else { |
|||
$this->setSafe($node, array()); |
|||
} |
|||
|
|||
return $node; |
|||
} |
|||
|
|||
protected function intersectSafe(array $a = null, array $b = null) |
|||
{ |
|||
if (null === $a || null === $b) { |
|||
return array(); |
|||
} |
|||
|
|||
if (in_array('all', $a)) { |
|||
return $b; |
|||
} |
|||
|
|||
if (in_array('all', $b)) { |
|||
return $a; |
|||
} |
|||
|
|||
return array_intersect($a, $b); |
|||
} |
|||
|
|||
/** |
|||
* {@inheritdoc} |
|||
*/ |
|||
public function getPriority() |
|||
{ |
|||
return 0; |
|||
} |
|||
} |
@ -0,0 +1,93 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* 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. |
|||
*/ |
|||
|
|||
/** |
|||
* Twig_NodeVisitor_Sandbox implements sandboxing. |
|||
* |
|||
* @package twig |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
*/ |
|||
class Twig_NodeVisitor_Sandbox implements Twig_NodeVisitorInterface |
|||
{ |
|||
protected $inAModule = false; |
|||
protected $tags; |
|||
protected $filters; |
|||
protected $functions; |
|||
|
|||
/** |
|||
* Called before child nodes are visited. |
|||
* |
|||
* @param Twig_NodeInterface $node The node to visit |
|||
* @param Twig_Environment $env The Twig environment instance |
|||
* |
|||
* @param Twig_NodeInterface The modified node |
|||
*/ |
|||
public function enterNode(Twig_NodeInterface $node, Twig_Environment $env) |
|||
{ |
|||
if ($node instanceof Twig_Node_Module) { |
|||
$this->inAModule = true; |
|||
$this->tags = array(); |
|||
$this->filters = array(); |
|||
$this->functions = array(); |
|||
|
|||
return $node; |
|||
} elseif ($this->inAModule) { |
|||
// look for tags |
|||
if ($node->getNodeTag()) { |
|||
$this->tags[] = $node->getNodeTag(); |
|||
} |
|||
|
|||
// look for filters |
|||
if ($node instanceof Twig_Node_Expression_Filter) { |
|||
$this->filters[] = $node->getNode('filter')->getAttribute('value'); |
|||
} |
|||
|
|||
// look for functions |
|||
if ($node instanceof Twig_Node_Expression_Function) { |
|||
$this->functions[] = $node->getAttribute('name'); |
|||
} |
|||
|
|||
// wrap print to check __toString() calls |
|||
if ($node instanceof Twig_Node_Print) { |
|||
return new Twig_Node_SandboxedPrint($node->getNode('expr'), $node->getLine(), $node->getNodeTag()); |
|||
} |
|||
} |
|||
|
|||
return $node; |
|||
} |
|||
|
|||
/** |
|||
* Called after child nodes are visited. |
|||
* |
|||
* @param Twig_NodeInterface $node The node to visit |
|||
* @param Twig_Environment $env The Twig environment instance |
|||
* |
|||
* @param Twig_NodeInterface The modified node |
|||
*/ |
|||
public function leaveNode(Twig_NodeInterface $node, Twig_Environment $env) |
|||
{ |
|||
if ($node instanceof Twig_Node_Module) { |
|||
$this->inAModule = false; |
|||
|
|||
return new Twig_Node_SandboxedModule($node, array_unique($this->filters), array_unique($this->tags), array_unique($this->functions)); |
|||
} |
|||
|
|||
return $node; |
|||
} |
|||
|
|||
/** |
|||
* {@inheritdoc} |
|||
*/ |
|||
public function getPriority() |
|||
{ |
|||
return 0; |
|||
} |
|||
} |
@ -0,0 +1,48 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* 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. |
|||
*/ |
|||
|
|||
/** |
|||
* Twig_NodeVisitorInterface is the interface the all node visitor classes must implement. |
|||
* |
|||
* @package twig |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
*/ |
|||
interface Twig_NodeVisitorInterface |
|||
{ |
|||
/** |
|||
* Called before child nodes are visited. |
|||
* |
|||
* @param Twig_NodeInterface $node The node to visit |
|||
* @param Twig_Environment $env The Twig environment instance |
|||
* |
|||
* @param Twig_NodeInterface The modified node |
|||
*/ |
|||
function enterNode(Twig_NodeInterface $node, Twig_Environment $env); |
|||
|
|||
/** |
|||
* Called after child nodes are visited. |
|||
* |
|||
* @param Twig_NodeInterface $node The node to visit |
|||
* @param Twig_Environment $env The Twig environment instance |
|||
* |
|||
* @param Twig_NodeInterface The modified node |
|||
*/ |
|||
function leaveNode(Twig_NodeInterface $node, Twig_Environment $env); |
|||
|
|||
/** |
|||
* Returns the priority for this visitor. |
|||
* |
|||
* Priority should be between -10 and 10 (0 is the default). |
|||
* |
|||
* @return integer The priority level |
|||
*/ |
|||
function getPriority(); |
|||
} |
@ -0,0 +1,334 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of Twig. |
|||
* |
|||
* (c) 2009 Fabien Potencier |
|||
* (c) 2009 Armin Ronacher |
|||
* |
|||
* For the full copyright and license information, please view the LICENSE |
|||
* file that was distributed with this source code. |
|||
*/ |
|||
|
|||
/** |
|||
* Default parser implementation. |
|||
* |
|||
* @package twig |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
*/ |
|||
class Twig_Parser implements Twig_ParserInterface |
|||
{ |
|||
protected $stream; |
|||
protected $parent; |
|||
protected $handlers; |
|||
protected $visitors; |
|||
protected $expressionParser; |
|||
protected $blocks; |
|||
protected $blockStack; |
|||
protected $macros; |
|||
protected $env; |
|||
protected $reservedMacroNames; |
|||
protected $importedFunctions; |
|||
protected $tmpVarCount; |
|||
protected $traits; |
|||
|
|||
/** |
|||
* Constructor. |
|||
* |
|||
* @param Twig_Environment $env A Twig_Environment instance |
|||
*/ |
|||
public function __construct(Twig_Environment $env) |
|||
{ |
|||
$this->env = $env; |
|||
} |
|||
|
|||
public function getVarName() |
|||
{ |
|||
return sprintf('__internal_%s_%d', substr($this->env->getTemplateClass($this->stream->getFilename()), strlen($this->env->getTemplateClassPrefix())), ++$this->tmpVarCount); |
|||
} |
|||
|
|||
/** |
|||
* Converts a token stream to a node tree. |
|||
* |
|||
* @param Twig_TokenStream $stream A token stream instance |
|||
* |
|||
* @return Twig_Node_Module A node tree |
|||
*/ |
|||
public function parse(Twig_TokenStream $stream) |
|||
{ |
|||
$this->tmpVarCount = 0; |
|||
|
|||
// tag handlers |
|||
$this->handlers = $this->env->getTokenParsers(); |
|||
$this->handlers->setParser($this); |
|||
|
|||
// node visitors |
|||
$this->visitors = $this->env->getNodeVisitors(); |
|||
|
|||
if (null === $this->expressionParser) { |
|||
$this->expressionParser = new Twig_ExpressionParser($this, $this->env->getUnaryOperators(), $this->env->getBinaryOperators()); |
|||
} |
|||
|
|||
$this->stream = $stream; |
|||
$this->parent = null; |
|||
$this->blocks = array(); |
|||
$this->macros = array(); |
|||
$this->traits = array(); |
|||
$this->blockStack = array(); |
|||
$this->importedFunctions = array(array()); |
|||
|
|||
try { |
|||
$body = $this->subparse(null); |
|||
|
|||
if (null !== $this->parent) { |
|||
if (null === $body = $this->filterBodyNodes($body)) { |
|||
$body = new Twig_Node(); |
|||
} |
|||
} |
|||
} catch (Twig_Error_Syntax $e) { |
|||
if (null === $e->getTemplateFile()) { |
|||
$e->setTemplateFile($this->stream->getFilename()); |
|||
} |
|||
|
|||
throw $e; |
|||
} |
|||
|
|||
$node = new Twig_Node_Module($body, $this->parent, new Twig_Node($this->blocks), new Twig_Node($this->macros), new Twig_Node($this->traits), $this->stream->getFilename()); |
|||
|
|||
$traverser = new Twig_NodeTraverser($this->env, $this->visitors); |
|||
|
|||
return $traverser->traverse($node); |
|||
} |
|||
|
|||
public function subparse($test, $dropNeedle = false) |
|||
{ |
|||
$lineno = $this->getCurrentToken()->getLine(); |
|||
$rv = array(); |
|||
while (!$this->stream->isEOF()) { |
|||
switch ($this->getCurrentToken()->getType()) { |
|||
case Twig_Token::TEXT_TYPE: |
|||
$token = $this->stream->next(); |
|||
$rv[] = new Twig_Node_Text($token->getValue(), $token->getLine()); |
|||
break; |
|||
|
|||
case Twig_Token::VAR_START_TYPE: |
|||
$token = $this->stream->next(); |
|||
$expr = $this->expressionParser->parseExpression(); |
|||
$this->stream->expect(Twig_Token::VAR_END_TYPE); |
|||
$rv[] = new Twig_Node_Print($expr, $token->getLine()); |
|||
break; |
|||
|
|||
case Twig_Token::BLOCK_START_TYPE: |
|||
$this->stream->next(); |
|||
$token = $this->getCurrentToken(); |
|||
|
|||
if ($token->getType() !== Twig_Token::NAME_TYPE) { |
|||
throw new Twig_Error_Syntax('A block must start with a tag name', $token->getLine(), $this->stream->getFilename()); |
|||
} |
|||
|
|||
if (null !== $test && call_user_func($test, $token)) { |
|||
if ($dropNeedle) { |
|||
$this->stream->next(); |
|||
} |
|||
|
|||
if (1 === count($rv)) { |
|||
return $rv[0]; |
|||
} |
|||
|
|||
return new Twig_Node($rv, array(), $lineno); |
|||
} |
|||
|
|||
$subparser = $this->handlers->getTokenParser($token->getValue()); |
|||
if (null === $subparser) { |
|||
if (null !== $test) { |
|||
throw new Twig_Error_Syntax(sprintf('Unexpected tag name "%s" (expecting closing tag for the "%s" tag defined near line %s)', $token->getValue(), $test[0]->getTag(), $lineno), $token->getLine(), $this->stream->getFilename()); |
|||
} |
|||
|
|||
throw new Twig_Error_Syntax(sprintf('Unknown tag name "%s"', $token->getValue()), $token->getLine(), $this->stream->getFilename()); |
|||
} |
|||
|
|||
$this->stream->next(); |
|||
|
|||
$node = $subparser->parse($token); |
|||
if (null !== $node) { |
|||
$rv[] = $node; |
|||
} |
|||
break; |
|||
|
|||
default: |
|||
throw new Twig_Error_Syntax('Lexer or parser ended up in unsupported state.', -1, $this->stream->getFilename()); |
|||
} |
|||
} |
|||
|
|||
if (1 === count($rv)) { |
|||
return $rv[0]; |
|||
} |
|||
|
|||
return new Twig_Node($rv, array(), $lineno); |
|||
} |
|||
|
|||
public function addHandler($name, $class) |
|||
{ |
|||
$this->handlers[$name] = $class; |
|||
} |
|||
|
|||
public function addNodeVisitor(Twig_NodeVisitorInterface $visitor) |
|||
{ |
|||
$this->visitors[] = $visitor; |
|||
} |
|||
|
|||
public function getBlockStack() |
|||
{ |
|||
return $this->blockStack; |
|||
} |
|||
|
|||
public function peekBlockStack() |
|||
{ |
|||
return $this->blockStack[count($this->blockStack) - 1]; |
|||
} |
|||
|
|||
public function popBlockStack() |
|||
{ |
|||
array_pop($this->blockStack); |
|||
} |
|||
|
|||
public function pushBlockStack($name) |
|||
{ |
|||
$this->blockStack[] = $name; |
|||
} |
|||
|
|||
public function hasBlock($name) |
|||
{ |
|||
return isset($this->blocks[$name]); |
|||
} |
|||
|
|||
public function setBlock($name, $value) |
|||
{ |
|||
$this->blocks[$name] = $value; |
|||
} |
|||
|
|||
public function hasMacro($name) |
|||
{ |
|||
return isset($this->macros[$name]); |
|||
} |
|||
|
|||
public function setMacro($name, Twig_Node_Macro $node) |
|||
{ |
|||
if (null === $this->reservedMacroNames) { |
|||
$this->reservedMacroNames = array(); |
|||
$r = new ReflectionClass($this->env->getBaseTemplateClass()); |
|||
foreach ($r->getMethods() as $method) { |
|||
$this->reservedMacroNames[] = $method->getName(); |
|||
} |
|||
} |
|||
|
|||
if (in_array($name, $this->reservedMacroNames)) { |
|||
throw new Twig_Error_Syntax(sprintf('"%s" cannot be used as a macro name as it is a reserved keyword', $name), $node->getLine()); |
|||
} |
|||
|
|||
$this->macros[$name] = $node; |
|||
} |
|||
|
|||
public function addTrait($trait) |
|||
{ |
|||
$this->traits[] = $trait; |
|||
} |
|||
|
|||
public function addImportedFunction($alias, $name, Twig_Node_Expression $node) |
|||
{ |
|||
$this->importedFunctions[0][$alias] = array('name' => $name, 'node' => $node); |
|||
} |
|||
|
|||
public function getImportedFunction($alias) |
|||
{ |
|||
foreach ($this->importedFunctions as $functions) { |
|||
if (isset($functions[$alias])) { |
|||
return $functions[$alias]; |
|||
} |
|||
} |
|||
} |
|||
|
|||
public function isMainScope() |
|||
{ |
|||
return 1 === count($this->importedFunctions); |
|||
} |
|||
|
|||
public function pushLocalScope() |
|||
{ |
|||
array_unshift($this->importedFunctions, array()); |
|||
} |
|||
|
|||
public function popLocalScope() |
|||
{ |
|||
array_shift($this->importedFunctions); |
|||
} |
|||
|
|||
/** |
|||
* Gets the expression parser. |
|||
* |
|||
* @return Twig_ExpressionParser The expression parser |
|||
*/ |
|||
public function getExpressionParser() |
|||
{ |
|||
return $this->expressionParser; |
|||
} |
|||
|
|||
public function getParent() |
|||
{ |
|||
return $this->parent; |
|||
} |
|||
|
|||
public function setParent($parent) |
|||
{ |
|||
$this->parent = $parent; |
|||
} |
|||
|
|||
/** |
|||
* Gets the token stream. |
|||
* |
|||
* @return Twig_TokenStream The token stream |
|||
*/ |
|||
public function getStream() |
|||
{ |
|||
return $this->stream; |
|||
} |
|||
|
|||
/** |
|||
* Gets the current token. |
|||
* |
|||
* @return Twig_Token The current token |
|||
*/ |
|||
public function getCurrentToken() |
|||
{ |
|||
return $this->stream->getCurrent(); |
|||
} |
|||
|
|||
protected function filterBodyNodes(Twig_NodeInterface $node) |
|||
{ |
|||
// check that the body does not contain non-empty output nodes |
|||
if ( |
|||
($node instanceof Twig_Node_Text && !ctype_space($node->getAttribute('data'))) |
|||
|| |
|||
(!$node instanceof Twig_Node_Text && !$node instanceof Twig_Node_BlockReference && $node instanceof Twig_NodeOutputInterface) |
|||
) { |
|||
throw new Twig_Error_Syntax('A template that extends another one cannot have a body.', $node->getLine(), $this->stream->getFilename()); |
|||
} |
|||
|
|||
// bypass "set" nodes as they "capture" the output |
|||
if ($node instanceof Twig_Node_Set) { |
|||
return $node; |
|||
} |
|||
|
|||
if ($node instanceof Twig_NodeOutputInterface) { |
|||
return; |
|||
} |
|||
|
|||
foreach ($node as $k => $n) { |
|||
if (null !== $n && null === $n = $this->filterBodyNodes($n)) { |
|||
$node->removeNode($k); |
|||
} |
|||
} |
|||
|
|||
return $node; |
|||
} |
|||
} |
@ -0,0 +1,28 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* 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. |
|||
*/ |
|||
|
|||
/** |
|||
* Interface implemented by parser classes. |
|||
* |
|||
* @package twig |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
*/ |
|||
interface 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 |
|||
*/ |
|||
function parse(Twig_TokenStream $code); |
|||
} |
@ -0,0 +1,20 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* 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. |
|||
*/ |
|||
|
|||
/** |
|||
* Exception thrown when a security error occurs at runtime. |
|||
* |
|||
* @package twig |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
*/ |
|||
class Twig_Sandbox_SecurityError extends Twig_Error |
|||
{ |
|||
} |
@ -0,0 +1,120 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* 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. |
|||
*/ |
|||
|
|||
/** |
|||
* Represents a security policy which need to be enforced when sandbox mode is enabled. |
|||
* |
|||
* @package twig |
|||
* @author Fabien Potencier <fabien@symfony.com> |
|||
*/ |
|||
class Twig_Sandbox_SecurityPolicy implements Twig_Sandbox_SecurityPolicyInterface |
|||
{ |
|||
protected $allowedTags; |
|||
protected $allowedFilters; |
|||
protected $allowedMethods; |
|||
protected $allowedProperties; |
|||
protected $allowedFunctions; |
|||
|
|||
public function __construct(array $allowedTags = array(), array $allowedFilters = array(), array $allowedMethods = array(), array $allowedProperties = array(), array $allowedFunctions = array()) |
|||
{ |
|||
$this->allowedTags = $allowedTags; |
|||
$this->allowedFilters = $allowedFilters; |
|||
$this->setAllowedMethods($allowedMethods); |
|||
$this->allowedProperties = $allowedProperties; |
|||
$this->allowedFunctions = $allowedFunctions; |
|||
} |
|||
|
|||
public function setAllowedTags(array $tags) |
|||
{ |
|||
$this->allowedTags = $tags; |
|||
} |
|||
|
|||
public function setAllowedFilters(array $filters) |
|||
{ |
|||
$this->allowedFilters = $filters; |
|||
} |
|||
|
|||
public function setAllowedMethods(array $methods) |
|||
{ |
|||
$this->allowedMethods = array(); |
|||
foreach ($methods as $class => $m) { |
|||
$this->allowedMethods[$class] = array_map('strtolower', is_array($m) ? $m : array($m)); |
|||
} |
|||
} |
|||
|
|||
public function setAllowedProperties(array $properties) |
|||
{ |
|||
$this->allowedProperties = $properties; |
|||
} |
|||
|
|||
public function setAllowedFunctions(array $functions) |
|||
{ |
|||
$this->allowedFunctions = $functions; |
|||
} |
|||
|
|||
public function checkSecurity($tags, $filters, $functions) |
|||
{ |
|||
foreach ($tags as $tag) { |
|||
if (!in_array($tag, $this->allowedTags)) { |
|||
throw new Twig_Sandbox_SecurityError(sprintf('Tag "%s" is not allowed.', $tag)); |
|||
} |
|||
} |
|||
|
|||
foreach ($filters as $filter) { |
|||
if (!in_array($filter, $this->allowedFilters)) { |
|||
throw new Twig_Sandbox_SecurityError(sprintf('Filter "%s" is not allowed.', $filter)); |
|||
} |
|||
} |
|||
|
|||
foreach ($functions as $function) { |
|||
if (!in_array($function, $this->allowedFunctions)) { |
|||
throw new Twig_Sandbox_SecurityError(sprintf('Function "%s" is not allowed.', $function)); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public function checkMethodAllowed($obj, $method) |
|||
{ |
|||
if ($obj instanceof Twig_TemplateInterface || $obj instanceof Twig_Markup) { |
|||
return true; |
|||
} |
|||
|
|||
$allowed = false; |
|||
$method = strtolower($method); |
|||
foreach ($this->allowedMethods as $class => $methods) { |
|||
if ($obj instanceof $class) { |
|||
$allowed = in_array($method, $methods); |
|||
|
|||
break; |
|||
} |
|||
} |
|||
|
|||
if (!$allowed) { |
|||
throw new Twig_Sandbox_SecurityError(sprintf('Calling "%s" method on a "%s" object is not allowed.', $method, get_class($obj))); |
|||
} |
|||
} |
|||
|
|||
public function checkPropertyAllowed($obj, $property) |
|||
{ |
|||
$allowed = false; |
|||
foreach ($this->allowedProperties as $class => $properties) { |
|||
if ($obj instanceof $class) { |
|||
$allowed = in_array($property, is_array($properties) ? $properties : array($properties)); |
|||
|
|||
break; |
|||
} |
|||
} |
|||
|
|||
if (!$allowed) { |
|||
throw new Twig_Sandbox_SecurityError(sprintf('Calling "%s" property on a "%s" object is not allowed.', $property, get_class($obj))); |
|||
} |
|||
} |
|||
} |
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue