initial import

This commit is contained in:
Alchemy
2011-02-16 16:09:48 +01:00
parent 399a584b6f
commit 339d23c06d
5539 changed files with 2028637 additions and 1 deletions

9
lib/classes/Twig/AUTHORS Normal file
View File

@@ -0,0 +1,9 @@
Twig is written and maintained by the Twig Team:
Lead Developer:
- Fabien Potencier <fabien.potencier@symfony-project.org>
Project Founder:
- Armin Ronacher <armin.ronacher@active-4.com>

46
lib/classes/Twig/Autoloader.php Executable file
View File

@@ -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.potencier@symfony-project.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 (file_exists($file = dirname(__FILE__).'/../'.str_replace('_', '/', $class).'.php')) {
require $file;
}
}
}

208
lib/classes/Twig/CHANGELOG Normal file
View File

@@ -0,0 +1,208 @@
* 0.9.9
Backward incompatibilities:
* the self special variable has been renamed to _self
* the odd and even filters are now tests:
{{ foo|odd }} must now be written {{ foo is odd }}
* the "safe" filter has been renamed to "raw"
* in Node classes,
sub-nodes are now accessed via getNode() (instead of property access)
attributes via getAttribute() (instead of array access)
* the urlencode filter had been renamed to url_encode
* the include tag now merges the passed variables with the current context by default
(the old behavior is still possible by adding the "only" keyword)
* Moved Exceptions to Twig_Error_* (Twig_SyntaxError/Twig_RuntimeError are now Twig_Error_Syntax/Twig_Error_Runtime)
* added the spaceless tag
* removed trim_blocks option
* added support for is*() methods for attributes (foo.bar now looks for foo->getBar() or foo->isBar())
* changed all exceptions to extend Twig_Error
* fixed unary expressions ({{ not(1 or 0) }})
* fixed child templates (with an extend tag) that uses one or more imports
* added support for {{ 1 not in [2, 3] }} (more readable than the current {{ not (1 in [2, 3]) }})
* escaping has been rewritten (from pre-escaping to post-escaping)
* the implementation of template inheritance has been rewritten
(blocks can now be called individually and still work with inheritance)
* fixed error handling for if tag when a syntax error occurs within a subparse process
* added a way to implement custom logic for resolving token parsers given a tag name
* fixed js escaper to be stricter (now uses a whilelist-based js escaper)
* added the following filers: "constant", "trans", "replace", "json_encode"
* added a "constant" test
* fixed objects with __toString() not being autoescaped
* fixed subscript expressions when calling __call() (methods now keep the case)
* added "test" feature (accessible via the "is" operator)
* removed the debug tag (should be done in an extension)
* fixed trans tag when no vars are used in plural form
* fixed race condition when writing template cache
* added the special _charset variable to reference the current charset
* added the special _context variable to reference the current context
* renamed self to _self (to avoid conflict)
* fixed Twig_Template::getAttribute() for protected properties
* 0.9.8 (2010-06-28)
Backward incompatibilities:
* the trans tag plural count is now attached to the plural tag:
old: `{% trans count %}...{% plural %}...{% endtrans %}`
new: `{% trans %}...{% plural count %}...{% endtrans %}`
* added a way to translate strings coming from a variable ({% trans var %})
* fixed trans tag when used with the Escaper extension
* fixed default cache umask
* removed Twig_Template instances from the debug tag output
* fixed objects with __isset() defined
* fixed set tag when used with a capture
* fixed type hinting for Twig_Environment::addFilter() method
* 0.9.7 (2010-06-12)
Backward incompatibilities:
* changed 'as' to '=' for the set tag ({% set title as "Title" %} must now be {% set title = "Title" %})
* removed the sandboxed attribute of the include tag (use the new sandbox tag instead)
* refactored the Node system (if you have custom nodes, you will have to update them to use the new API)
* added self as a special variable that refers to the current template (useful for importing macros from the current template)
* added Twig_Template instance support to the include tag
* added support for dynamic and conditional inheritance ({% extends some_var %} and {% extends standalone ? "minimum" : "base" %})
* added a grammar sub-framework to ease the creation of custom tags
* fixed the for tag for large arrays (some loop variables are now only available for arrays and objects that implement the Countable interface)
* removed the Twig_Resource::resolveMissingFilter() method
* fixed the filter tag which did not apply filtering to included files
* added a bunch of unit tests
* added a bunch of phpdoc
* added a sandbox tag in the sandbox extension
* changed the date filter to support any date format supported by DateTime
* added strict_variable setting to throw an exception when an invalid variable is used in a template (disabled by default)
* added the lexer, parser, and compiler as arguments to the Twig_Environment constructor
* changed the cache option to only accepts an explicit path to a cache directory or false
* added a way to add token parsers, filters, and visitors without creating an extension
* added three interfaces: Twig_NodeInterface, Twig_TokenParserInterface, and Twig_FilterInterface
* changed the generated code to match the new coding standards
* fixed sandbox mode (__toString() method check was not enforced if called implicitly from a simple statement like {{ article }})
* added an exception when a child template has a non-empty body (as it is always ignored when rendering)
* 0.9.6 (2010-05-12)
* fixed variables defined outside a loop and for which the value changes in a for loop
* fixed the test suite for PHP 5.2 and older versions of PHPUnit
* added support for __call() in expression resolution
* fixed node visiting for macros (macros are now visited by visitors as any other node)
* fixed nested block definitions with a parent call (rarely useful but nonetheless supported now)
* added the cycle filter
* fixed the Lexer when mbstring.func_overload is used with an mbstring.internal_encoding different from ASCII
* added a long-syntax for the set tag ({% set foo %}...{% endset %})
* unit tests are now powered by PHPUnit
* added support for gettext via the `i18n` extension
* fixed twig_capitalize_string_filter() and fixed twig_length_filter() when used with UTF-8 values
* added a more useful exception if an if tag is not closed properly
* added support for escaping strategy in the autoescape tag
* fixed lexer when a template has a big chunk of text between/in a block
* 0.9.5 (2010-01-20)
As for any new release, don't forget to remove all cached templates after
upgrading.
If you have defined custom filters, you MUST upgrade them for this release. To
upgrade, replace "array" with "new Twig_Filter_Function", and replace the
environment constant by the "needs_environment" option:
// before
'even' => array('twig_is_even_filter', false),
'escape' => array('twig_escape_filter', true),
// after
'even' => new Twig_Filter_Function('twig_is_even_filter'),
'escape' => new Twig_Filter_Function('twig_escape_filter', array('needs_environment' => true)),
If you have created NodeTransformer classes, you will need to upgrade them to
the new interface (please note that the interface is not yet considered
stable).
* fixed list nodes that did not extend the Twig_NodeListInterface
* added the "without loop" option to the for tag (it disables the generation of the loop variable)
* refactored node transformers to node visitors
* fixed automatic-escaping for blocks
* added a way to specify variables to pass to an included template
* changed the automatic-escaping rules to be more sensible and more configurable in custom filters (the documentation lists all the rules)
* improved the filter system to allow object methods to be used as filters
* changed the Array and String loaders to actually make use of the cache mechanism
* included the default filter function definitions in the extension class files directly (Core, Escaper)
* added the // operator (like the floor() PHP function)
* added the .. operator (as a syntactic sugar for the range filter when the step is 1)
* added the in operator (as a syntactic sugar for the in filter)
* added the following filters in the Core extension: in, range
* added support for arrays (same behavior as in PHP, a mix between lists and dictionaries, arrays and hashes)
* enhanced some error messages to provide better feedback in case of parsing errors
* 0.9.4 (2009-12-02)
If you have custom loaders, you MUST upgrade them for this release: The
Twig_Loader base class has been removed, and the Twig_LoaderInterface has also
been changed (see the source code for more information or the documentation).
* added support for DateTime instances for the date filter
* fixed loop.last when the array only has one item
* made it possible to insert newlines in tag and variable blocks
* fixed a bug when a literal '\n' were present in a template text
* fixed bug when the filename of a template contains */
* refactored loaders
* 0.9.3 (2009-11-11)
This release is NOT backward compatible with the previous releases.
The loaders do not take the cache and autoReload arguments anymore. Instead,
the Twig_Environment class has two new options: cache and auto_reload.
Upgrading your code means changing this kind of code:
$loader = new Twig_Loader_Filesystem('/path/to/templates', '/path/to/compilation_cache', true);
$twig = new Twig_Environment($loader);
to something like this:
$loader = new Twig_Loader_Filesystem('/path/to/templates');
$twig = new Twig_Environment($loader, array(
'cache' => '/path/to/compilation_cache',
'auto_reload' => true,
));
* deprecated the "items" filter as it is not needed anymore
* made cache and auto_reload options of Twig_Environment instead of arguments of Twig_Loader
* optimized template loading speed
* removed output when an error occurs in a template and render() is used
* made major speed improvements for loops (up to 300% on even the smallest loops)
* added properties as part of the sandbox mode
* added public properties support (obj.item can now be the item property on the obj object)
* extended set tag to support expression as value ({% set foo as 'foo' ~ 'bar' %} )
* fixed bug when \ was used in HTML
* 0.9.2 (2009-10-29)
* made some speed optimizations
* changed the cache extension to .php
* added a js escaping strategy
* added support for short block tag
* changed the filter tag to allow chained filters
* made lexer more flexible as you can now change the default delimiters
* added set tag
* changed default directory permission when cache dir does not exist (more secure)
* added macro support
* changed filters first optional argument to be a Twig_Environment instance instead of a Twig_Template instance
* made Twig_Autoloader::autoload() a static method
* avoid writing template file if an error occurs
* added $ escaping when outputting raw strings
* enhanced some error messages to ease debugging
* fixed empty cache files when the template contains an error
* 0.9.1 (2009-10-14)
* fixed a bug in PHP 5.2.6
* fixed numbers with one than one decimal
* added support for method calls with arguments ({{ foo.bar('a', 43) }})
* made small speed optimizations
* made minor tweaks to allow better extensibility and flexibility
* 0.9.0 (2009-10-12)
* Initial release

223
lib/classes/Twig/Compiler.php Executable file
View File

@@ -0,0 +1,223 @@
<?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.potencier@symfony-project.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 = null)
{
if (null !== $env) {
$this->setEnvironment($env);
}
}
public function setEnvironment(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, "\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;
return $this;
}
}

View File

@@ -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.potencier@symfony-project.com>
*/
interface Twig_CompilerInterface
{
/**
* Compiles a node.
*
* @param Twig_NodeInterface $node The node to compile
*
* @return Twig_Compiler The current compiler instance
*/
public function compile(Twig_NodeInterface $node);
/**
* Gets the current PHP code after compilation.
*
* @return string The PHP code
*/
public function getSource();
}

431
lib/classes/Twig/Environment.php Executable file
View File

@@ -0,0 +1,431 @@
<?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_Environment
{
const VERSION = '0.9.9-DEV';
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 $runtimeInitialized;
protected $loadedTemplates;
protected $strictVariables;
/**
* 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).
*
* @param Twig_LoaderInterface $loader A Twig_LoaderInterface instance
* @param array $options An array of options
* @param Twig_LexerInterface $lexer A Twig_LexerInterface instance
* @param Twig_ParserInterface $parser A Twig_ParserInterface instance
* @param Twig_CompilerInterface $compiler A Twig_CompilerInterface instance
*/
public function __construct(Twig_LoaderInterface $loader = null, $options = array(), Twig_LexerInterface $lexer = null, Twig_ParserInterface $parser = null, Twig_CompilerInterface $compiler = null)
{
if (null !== $loader) {
$this->setLoader($loader);
}
$this->setLexer(null !== $lexer ? $lexer : new Twig_Lexer());
$this->setParser(null !== $parser ? $parser : new Twig_Parser());
$this->setCompiler(null !== $compiler ? $compiler : new Twig_Compiler());
$this->debug = isset($options['debug']) ? (bool) $options['debug'] : false;
$this->charset = isset($options['charset']) ? $options['charset'] : 'UTF-8';
$this->baseTemplateClass = isset($options['base_template_class']) ? $options['base_template_class'] : 'Twig_Template';
$this->autoReload = isset($options['auto_reload']) ? (bool) $options['auto_reload'] : $this->debug;
$this->extensions = array('core' => new Twig_Extension_Core());
$this->strictVariables = isset($options['strict_variables']) ? (bool) $options['strict_variables'] : false;
$this->runtimeInitialized = false;
if (isset($options['cache']) && $options['cache']) {
$this->setCache($options['cache']);
}
}
public function getBaseTemplateClass()
{
return $this->baseTemplateClass;
}
public function setBaseTemplateClass($class)
{
$this->baseTemplateClass = $class;
}
public function enableDebug()
{
$this->debug = true;
}
public function disableDebug()
{
$this->debug = false;
}
public function isDebug()
{
return $this->debug;
}
public function isAutoReload()
{
return $this->autoReload;
}
public function setAutoReload($autoReload)
{
$this->autoReload = (Boolean) $autoReload;
}
public function enableStrictVariables()
{
$this->strictVariables = true;
}
public function disableStrictVariables()
{
$this->strictVariables = false;
}
public function isStrictVariables()
{
return $this->strictVariables;
}
public function getCache()
{
return $this->cache;
}
public function setCache($cache)
{
$this->cache = $cache;
if ($this->cache && !is_dir($this->cache)) {
mkdir($this->cache, 0777, true);
}
}
public function getCacheFilename($name)
{
return $this->getCache() ? $this->getCache().'/'.$this->getTemplateClass($name).'.php' : false;
}
/**
* 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 '__TwigTemplate_'.md5($this->loader->getCacheKey($name));
}
/**
* 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 (!file_exists($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 clearTemplateCache()
{
$this->loadedTemplates = array();
}
public function getLexer()
{
return $this->lexer;
}
public function setLexer(Twig_LexerInterface $lexer)
{
$this->lexer = $lexer;
$lexer->setEnvironment($this);
}
public function tokenize($source, $name)
{
return $this->getLexer()->tokenize($source, $name);
}
public function getParser()
{
return $this->parser;
}
public function setParser(Twig_ParserInterface $parser)
{
$this->parser = $parser;
$parser->setEnvironment($this);
}
public function parse(Twig_TokenStream $tokens)
{
return $this->getParser()->parse($tokens);
}
public function getCompiler()
{
return $this->compiler;
}
public function setCompiler(Twig_CompilerInterface $compiler)
{
$this->compiler = $compiler;
$compiler->setEnvironment($this);
}
public function compile(Twig_NodeInterface $node)
{
return $this->getCompiler()->compile($node)->getSource();
}
public function compileSource($source, $name)
{
return $this->compile($this->parse($this->tokenize($source, $name)));
}
public function setLoader(Twig_LoaderInterface $loader)
{
$this->loader = $loader;
}
public function getLoader()
{
return $this->loader;
}
public function setCharset($charset)
{
$this->charset = $charset;
}
public function getCharset()
{
return $this->charset;
}
public function initRuntime()
{
$this->runtimeInitialized = true;
foreach ($this->getExtensions() as $extension) {
$extension->initRuntime($this);
}
}
public function hasExtension($name)
{
return isset($this->extensions[$name]);
}
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];
}
public function addExtension(Twig_ExtensionInterface $extension)
{
$this->extensions[$extension->getName()] = $extension;
}
public function removeExtension($name)
{
unset($this->extensions[$name]);
}
public function setExtensions(array $extensions)
{
foreach ($extensions as $extension) {
$this->addExtension($extension);
}
}
public function getExtensions()
{
return $this->extensions;
}
public function addTokenParser(Twig_TokenParserInterface $parser)
{
if (null === $this->parsers) {
$this->getTokenParsers();
}
$this->parsers->addTokenParser($parser);
}
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;
}
public function addNodeVisitor(Twig_NodeVisitorInterface $visitor)
{
if (null === $this->visitors) {
$this->getNodeVisitors();
}
$this->visitors[] = $visitor;
}
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;
}
public function addFilter($name, Twig_FilterInterface $filter)
{
if (null === $this->filters) {
$this->getFilters();
}
$this->filters[$name] = $filter;
}
public function getFilters()
{
if (null === $this->filters) {
$this->filters = array();
foreach ($this->getExtensions() as $extension) {
$this->filters = array_merge($this->filters, $extension->getFilters());
}
}
return $this->filters;
}
public function addTest($name, Twig_TestInterface $test)
{
if (null === $this->tests) {
$this->getTests();
}
$this->tests[$name] = $test;
}
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;
}
protected function writeCacheFile($file, $content)
{
$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));
}
}

20
lib/classes/Twig/Error.php Executable file
View File

@@ -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.
*/
/**
* Twig base exception.
*
* @package twig
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/
class Twig_Error extends Exception
{
}

View File

@@ -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.potencier@symfony-project.com>
*/
class Twig_Error_Loader extends Twig_Error
{
}

View File

@@ -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.potencier@symfony-project.com>
*/
class Twig_Error_Runtime extends Twig_Error
{
}

View File

@@ -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.
*/
/**
* Exception thrown when a syntax error occurs during lexing or parsing of a template.
*
* @package twig
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/
class Twig_Error_Syntax extends Twig_Error
{
protected $lineno;
protected $filename;
protected $rawMessage;
public function __construct($message, $lineno, $filename = null)
{
$this->lineno = $lineno;
$this->filename = $filename;
$this->rawMessage = $message;
$this->updateRepr();
parent::__construct($this->message, $lineno);
}
public function getFilename()
{
return $this->filename;
}
public function setFilename($filename)
{
$this->filename = $filename;
$this->updateRepr();
}
protected function updateRepr()
{
$this->message = $this->rawMessage.' in '.($this->filename ? $this->filename : 'n/a').' at line '.$this->lineno;
}
}

View File

@@ -0,0 +1,538 @@
<?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.
*
* @package twig
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/
class Twig_ExpressionParser
{
protected $parser;
public function __construct(Twig_Parser $parser)
{
$this->parser = $parser;
}
public function parseExpression()
{
return $this->parseConditionalExpression();
}
public function parseConditionalExpression()
{
$lineno = $this->parser->getCurrentToken()->getLine();
$expr1 = $this->parseOrExpression();
while ($this->parser->getStream()->test(Twig_Token::OPERATOR_TYPE, '?')) {
$this->parser->getStream()->next();
$expr2 = $this->parseOrExpression();
$this->parser->getStream()->expect(Twig_Token::OPERATOR_TYPE, ':');
$expr3 = $this->parseConditionalExpression();
$expr1 = new Twig_Node_Expression_Conditional($expr1, $expr2, $expr3, $lineno);
$lineno = $this->parser->getCurrentToken()->getLine();
}
return $expr1;
}
public function parseOrExpression()
{
$lineno = $this->parser->getCurrentToken()->getLine();
$left = $this->parseAndExpression();
while ($this->parser->getStream()->test('or')) {
$this->parser->getStream()->next();
$right = $this->parseAndExpression();
$left = new Twig_Node_Expression_Binary_Or($left, $right, $lineno);
$lineno = $this->parser->getCurrentToken()->getLine();
}
return $left;
}
public function parseAndExpression()
{
$lineno = $this->parser->getCurrentToken()->getLine();
$left = $this->parseCompareExpression();
while ($this->parser->getStream()->test('and')) {
$this->parser->getStream()->next();
$right = $this->parseCompareExpression();
$left = new Twig_Node_Expression_Binary_And($left, $right, $lineno);
$lineno = $this->parser->getCurrentToken()->getLine();
}
return $left;
}
public function parseCompareExpression()
{
static $operators = array('==', '!=', '<', '>', '>=', '<=');
$lineno = $this->parser->getCurrentToken()->getLine();
$expr = $this->parseAddExpression();
$ops = array();
$negated = false;
while (
$this->parser->getStream()->test(Twig_Token::OPERATOR_TYPE, $operators)
||
($this->parser->getStream()->test(Twig_Token::NAME_TYPE, 'not') && $this->parser->getStream()->look()->test(Twig_Token::NAME_TYPE, 'in'))
||
$this->parser->getStream()->test(Twig_Token::NAME_TYPE, 'in')
) {
$this->parser->getStream()->rewind();
if ($this->parser->getStream()->test(Twig_Token::NAME_TYPE, 'not')) {
$negated = true;
$this->parser->getStream()->next();
}
$ops[] = new Twig_Node_Expression_Constant($this->parser->getStream()->next()->getValue(), $lineno);
$ops[] = $this->parseAddExpression();
}
if (empty($ops)) {
return $expr;
}
$node = new Twig_Node_Expression_Compare($expr, new Twig_Node($ops), $lineno);
if ($negated) {
$node = new Twig_Node_Expression_Unary_Not($node, $lineno);
}
return $node;
}
public function parseAddExpression()
{
$lineno = $this->parser->getCurrentToken()->getLine();
$left = $this->parseSubExpression();
while ($this->parser->getStream()->test(Twig_Token::OPERATOR_TYPE, '+')) {
$this->parser->getStream()->next();
$right = $this->parseSubExpression();
$left = new Twig_Node_Expression_Binary_Add($left, $right, $lineno);
$lineno = $this->parser->getCurrentToken()->getLine();
}
return $left;
}
public function parseSubExpression()
{
$lineno = $this->parser->getCurrentToken()->getLine();
$left = $this->parseConcatExpression();
while ($this->parser->getStream()->test(Twig_Token::OPERATOR_TYPE, '-')) {
$this->parser->getStream()->next();
$right = $this->parseConcatExpression();
$left = new Twig_Node_Expression_Binary_Sub($left, $right, $lineno);
$lineno = $this->parser->getCurrentToken()->getLine();
}
return $left;
}
public function parseConcatExpression()
{
$lineno = $this->parser->getCurrentToken()->getLine();
$left = $this->parseMulExpression();
while ($this->parser->getStream()->test(Twig_Token::OPERATOR_TYPE, '~')) {
$this->parser->getStream()->next();
$right = $this->parseMulExpression();
$left = new Twig_Node_Expression_Binary_Concat($left, $right, $lineno);
$lineno = $this->parser->getCurrentToken()->getLine();
}
return $left;
}
public function parseMulExpression()
{
$lineno = $this->parser->getCurrentToken()->getLine();
$left = $this->parseDivExpression();
while ($this->parser->getStream()->test(Twig_Token::OPERATOR_TYPE, '*')) {
$this->parser->getStream()->next();
$right = $this->parseDivExpression();
$left = new Twig_Node_Expression_Binary_Mul($left, $right, $lineno);
$lineno = $this->parser->getCurrentToken()->getLine();
}
return $left;
}
public function parseDivExpression()
{
$lineno = $this->parser->getCurrentToken()->getLine();
$left = $this->parseFloorDivExpression();
while ($this->parser->getStream()->test(Twig_Token::OPERATOR_TYPE, '/')) {
$this->parser->getStream()->next();
$right = $this->parseModExpression();
$left = new Twig_Node_Expression_Binary_Div($left, $right, $lineno);
$lineno = $this->parser->getCurrentToken()->getLine();
}
return $left;
}
public function parseFloorDivExpression()
{
$lineno = $this->parser->getCurrentToken()->getLine();
$left = $this->parseModExpression();
while ($this->parser->getStream()->test(Twig_Token::OPERATOR_TYPE, '//')) {
$this->parser->getStream()->next();
$right = $this->parseModExpression();
$left = new Twig_Node_Expression_Binary_FloorDiv($left, $right, $lineno);
$lineno = $this->parser->getCurrentToken()->getLine();
}
return $left;
}
public function parseModExpression()
{
$lineno = $this->parser->getCurrentToken()->getLine();
$left = $this->parseUnaryExpression();
while ($this->parser->getStream()->test(Twig_Token::OPERATOR_TYPE, '%')) {
$this->parser->getStream()->next();
$right = $this->parseUnaryExpression();
$left = new Twig_Node_Expression_Binary_Mod($left, $right, $lineno);
$lineno = $this->parser->getCurrentToken()->getLine();
}
return $left;
}
public function parseUnaryExpression()
{
if ($this->parser->getStream()->test('not')) {
return $this->parseNotExpression();
}
if ($this->parser->getCurrentToken()->getType() == Twig_Token::OPERATOR_TYPE) {
switch ($this->parser->getCurrentToken()->getValue()) {
case '-':
return $this->parseNegExpression();
case '+':
return $this->parsePosExpression();
}
}
return $this->parsePrimaryExpression();
}
public function parseNotExpression()
{
$token = $this->parser->getStream()->next();
$node = $this->parseUnaryExpression();
return new Twig_Node_Expression_Unary_Not($node, $token->getLine());
}
public function parseNegExpression()
{
$token = $this->parser->getStream()->next();
$node = $this->parseUnaryExpression();
return new Twig_Node_Expression_Unary_Neg($node, $token->getLine());
}
public function parsePosExpression()
{
$token = $this->parser->getStream()->next();
$node = $this->parseUnaryExpression();
return new Twig_Node_Expression_Unary_Pos($node, $token->getLine());
}
public function parsePrimaryExpression($assignment = false)
{
$token = $this->parser->getCurrentToken();
switch ($token->getType()) {
case Twig_Token::NAME_TYPE:
$this->parser->getStream()->next();
switch ($token->getValue()) {
case 'true':
$node = new Twig_Node_Expression_Constant(true, $token->getLine());
break;
case 'false':
$node = new Twig_Node_Expression_Constant(false, $token->getLine());
break;
case 'none':
$node = new Twig_Node_Expression_Constant(null, $token->getLine());
break;
default:
$cls = $assignment ? 'Twig_Node_Expression_AssignName' : 'Twig_Node_Expression_Name';
$node = new $cls($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::OPERATOR_TYPE, '[')) {
$node = $this->parseArrayExpression();
} elseif ($token->test(Twig_Token::OPERATOR_TYPE, '(')) {
$this->parser->getStream()->next();
$node = $this->parseExpression();
$this->parser->getStream()->expect(Twig_Token::OPERATOR_TYPE, ')');
} else {
throw new Twig_Error_Syntax(sprintf('Unexpected token "%s" of value "%s"', Twig_Token::getTypeAsString($token->getType()), $token->getValue()), $token->getLine());
}
}
if (!$assignment) {
$node = $this->parsePostfixExpression($node);
}
return $node;
}
public function parseArrayExpression()
{
$stream = $this->parser->getStream();
$stream->expect(Twig_Token::OPERATOR_TYPE, '[');
$elements = array();
while (!$stream->test(Twig_Token::OPERATOR_TYPE, ']')) {
if (!empty($elements)) {
$stream->expect(Twig_Token::OPERATOR_TYPE, ',');
// trailing ,?
if ($stream->test(Twig_Token::OPERATOR_TYPE, ']')) {
$stream->expect(Twig_Token::OPERATOR_TYPE, ']');
return new Twig_Node_Expression_Array($elements, $this->parser->getCurrentToken()->getLine());
}
}
// hash or array element?
if (
$stream->test(Twig_Token::STRING_TYPE)
||
$stream->test(Twig_Token::NUMBER_TYPE)
)
{
if ($stream->look()->test(Twig_Token::OPERATOR_TYPE, ':')) {
// hash
$key = $stream->next()->getValue();
$stream->next();
$elements[$key] = $this->parseExpression();
continue;
}
$stream->rewind();
}
$elements[] = $this->parseExpression();
}
$stream->expect(Twig_Token::OPERATOR_TYPE, ']');
return new Twig_Node_Expression_Array($elements, $this->parser->getCurrentToken()->getLine());
}
public function parsePostfixExpression($node)
{
while (1) {
$token = $this->parser->getCurrentToken();
if ($token->getType() == Twig_Token::OPERATOR_TYPE) {
if ('..' == $token->getValue()) {
$node = $this->parseRangeExpression($node);
} elseif ('.' == $token->getValue() || '[' == $token->getValue()) {
$node = $this->parseSubscriptExpression($node);
} elseif ('|' == $token->getValue()) {
$node = $this->parseFilterExpression($node);
} else {
break;
}
} elseif ($token->getType() == Twig_Token::NAME_TYPE && 'is' == $token->getValue()) {
$node = $this->parseTestExpression($node);
break;
} else {
break;
}
}
return $node;
}
public function parseTestExpression($node)
{
$stream = $this->parser->getStream();
$token = $stream->next();
$lineno = $token->getLine();
$negated = false;
if ($stream->test('not')) {
$stream->next();
$negated = true;
}
$name = $stream->expect(Twig_Token::NAME_TYPE);
$arguments = null;
if ($stream->test(Twig_Token::OPERATOR_TYPE, '(')) {
$arguments = $this->parseArguments($node);
}
$test = new Twig_Node_Expression_Test($node, $name->getValue(), $arguments, $lineno);
if ($negated) {
$test = new Twig_Node_Expression_Unary_Not($test, $lineno);
}
return $test;
}
public function parseRangeExpression($node)
{
$token = $this->parser->getStream()->next();
$lineno = $token->getLine();
$end = $this->parseExpression();
$name = new Twig_Node_Expression_Constant('range', $lineno);
$arguments = new Twig_Node(array($end));
return new Twig_Node_Expression_Filter($node, $name, $arguments, $lineno);
}
public function parseSubscriptExpression($node)
{
$token = $this->parser->getStream()->next();
$lineno = $token->getLine();
$arguments = new Twig_Node();
$type = Twig_Node_Expression_GetAttr::TYPE_ANY;
if ($token->getValue() == '.') {
$token = $this->parser->getStream()->next();
if ($token->getType() == Twig_Token::NAME_TYPE || $token->getType() == Twig_Token::NUMBER_TYPE) {
$arg = new Twig_Node_Expression_Constant($token->getValue(), $lineno);
if ($this->parser->getStream()->test(Twig_Token::OPERATOR_TYPE, '(')) {
$type = Twig_Node_Expression_GetAttr::TYPE_METHOD;
$arguments = $this->parseArguments();
} else {
$arguments = new Twig_Node();
}
} else {
throw new Twig_Error_Syntax('Expected name or number', $lineno);
}
} else {
$type = Twig_Node_Expression_GetAttr::TYPE_ARRAY;
$arg = $this->parseExpression();
$this->parser->getStream()->expect(Twig_Token::OPERATOR_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)
{
$lineno = $this->parser->getCurrentToken()->getLine();
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::OPERATOR_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::OPERATOR_TYPE, '|')) {
break;
}
$this->parser->getStream()->next();
}
return $node;
}
public function parseArguments()
{
$parser = $this->parser->getStream();
$parser->expect(Twig_Token::OPERATOR_TYPE, '(');
$args = array();
while (!$parser->test(Twig_Token::OPERATOR_TYPE, ')')) {
if (!empty($args)) {
$parser->expect(Twig_Token::OPERATOR_TYPE, ',');
}
$args[] = $this->parseExpression();
}
$parser->expect(Twig_Token::OPERATOR_TYPE, ')');
return new Twig_Node($args);
}
public function parseAssignmentExpression()
{
$lineno = $this->parser->getCurrentToken()->getLine();
$targets = array();
while (true) {
if (!empty($targets)) {
$this->parser->getStream()->expect(Twig_Token::OPERATOR_TYPE, ',');
}
if ($this->parser->getStream()->test(Twig_Token::OPERATOR_TYPE, ')') ||
$this->parser->getStream()->test(Twig_Token::VAR_END_TYPE) ||
$this->parser->getStream()->test(Twig_Token::BLOCK_END_TYPE) ||
$this->parser->getStream()->test('in'))
{
break;
}
$targets[] = $this->parsePrimaryExpression(true);
if (!$this->parser->getStream()->test(Twig_Token::OPERATOR_TYPE, ',')) {
break;
}
}
return new Twig_Node($targets);
}
public function parseMultitargetExpression()
{
$lineno = $this->parser->getCurrentToken()->getLine();
$targets = array();
$is_multitarget = false;
while (true) {
if (!empty($targets)) {
$this->parser->getStream()->expect(Twig_Token::OPERATOR_TYPE, ',');
}
if ($this->parser->getStream()->test(Twig_Token::OPERATOR_TYPE, ')') ||
$this->parser->getStream()->test(Twig_Token::VAR_END_TYPE) ||
$this->parser->getStream()->test(Twig_Token::BLOCK_END_TYPE))
{
break;
}
$targets[] = $this->parseExpression();
if (!$this->parser->getStream()->test(Twig_Token::OPERATOR_TYPE, ',')) {
break;
}
$is_multitarget = true;
}
return array($is_multitarget, new Twig_Node($targets));
}
}

63
lib/classes/Twig/Extension.php Executable file
View File

@@ -0,0 +1,63 @@
<?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 $environement The current Twig_Environment instance
*/
public function initRuntime(Twig_Environment $environement)
{
}
/**
* 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();
}
}

View File

@@ -0,0 +1,417 @@
<?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_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_Parent(),
new Twig_TokenParser_Display(),
new Twig_TokenParser_Filter(),
new Twig_TokenParser_Macro(),
new Twig_TokenParser_Import(),
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('json_encode'),
// 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'),
'constant' => new Twig_Filter_Function('constant'),
// 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'),
'in' => new Twig_Filter_Function('twig_in_filter'),
'range' => new Twig_Filter_Function('twig_range_filter'),
'cycle' => new Twig_Filter_Function('twig_cycle_filter'),
// iteration and runtime
'default' => new Twig_Filter_Function('twig_default_filter'),
'keys' => new Twig_Filter_Function('twig_get_array_keys_filter'),
'items' => new Twig_Filter_Function('twig_get_array_items_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 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'),
'divisibleby' => new Twig_Test_Function('twig_test_divisibleby'),
'constant' => new Twig_Test_Function('twig_test_constant'),
);
}
/**
* Returns the name of the extension.
*
* @return string The extension name
*/
public function getName()
{
return 'core';
}
}
function twig_date_format_filter($date, $format = 'F j, Y H:i')
{
if (!$date instanceof DateTime) {
$date = new DateTime((ctype_digit($date) ? '@' : '').$date);
}
return $date->format($format);
}
function twig_urlencode_filter($url, $raw = false)
{
if ($raw) {
return rawurlencode($url);
}
return urlencode($url);
}
function twig_join_filter($value, $glue = '')
{
return implode($glue, (array) $value);
}
function twig_default_filter($value, $default = '')
{
return null === $value ? $default : $value;
}
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);
}
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);
}
function twig_sort_filter($array)
{
asort($array);
return $array;
}
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;
}
function twig_range_filter($start, $end, $step = 1)
{
return range($start, $end, $step);
}
function twig_cycle_filter($values, $i)
{
if (!is_array($values) && !$values instanceof ArrayAccess) {
return $values;
}
return $values[$i % count($values)];
}
function twig_strtr($pattern, $replacements)
{
return str_replace(array_keys($replacements), array_values($replacements), $pattern);
}
/*
* Each type specifies a way for applying a transformation to a string
* The purpose is for the string to be "escaped" so it is suitable for
* the format it is being displayed in.
*
* For example, the string: "It's required that you enter a username & password.\n"
* If this were to be displayed as HTML it would be sensible to turn the
* ampersand into '&amp;' and the apostrophe into '&aps;'. However if it were
* going to be used as a string in JavaScript to be displayed in an alert box
* it would be right to leave the string as-is, but c-escape the apostrophe and
* the new line.
*/
function twig_escape_filter(Twig_Environment $env, $string, $type = 'html')
{
if (!is_string($string) && !(is_object($string) && method_exists($string, '__toString'))) {
return $string;
}
switch ($type) {
case 'js':
// escape all non-alphanumeric characters
// into their \xHH or \uHHHH representations
$charset = $env->getCharset();
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, $env->getCharset());
default:
throw new Twig_Error_Runtime(sprintf('Invalid escape type "%s".', $type));
}
}
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')) {
function twig_length_filter(Twig_Environment $env, $thing)
{
return is_string($thing) ? mb_strlen($thing, $env->getCharset()) : count($thing);
}
function twig_upper_filter(Twig_Environment $env, $string)
{
if (null !== ($charset = $env->getCharset())) {
return mb_strtoupper($string, $charset);
}
return strtoupper($string);
}
function twig_lower_filter(Twig_Environment $env, $string)
{
if (null !== ($charset = $env->getCharset())) {
return mb_strtolower($string, $charset);
}
return strtolower($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));
}
function twig_capitalize_string_filter(Twig_Environment $env, $string)
{
if (null !== ($charset = $env->getCharset())) {
return mb_strtoupper(mb_substr($string, 0, 1, $charset)).
mb_strtolower(mb_substr($string, 1, mb_strlen($string), $charset), $charset);
}
return ucfirst(strtolower($string));
}
}
// and byte fallback
else
{
function twig_length_filter(Twig_Environment $env, $thing)
{
return is_string($thing) ? strlen($thing) : count($thing);
}
function twig_title_string_filter(Twig_Environment $env, $string)
{
return ucwords(strtolower($string));
}
function twig_capitalize_string_filter(Twig_Environment $env, $string)
{
return ucfirst(strtolower($string));
}
}
function twig_iterator_to_array($seq, $useKeys = true)
{
if (is_array($seq)) {
return $seq;
} elseif (is_object($seq) && $seq instanceof Traversable) {
return $seq;
} else {
return array();
}
}
// only for backward compatibility
function twig_get_array_items_filter($array)
{
// noop
return $array;
}
function twig_test_sameas($value, $test)
{
return $value === $test;
}
function twig_test_none($value)
{
return null === $value;
}
function twig_test_divisibleby($value, $num)
{
return 0 == $value % $num;
}
function twig_test_even($value)
{
return $value % 2 == 0;
}
function twig_test_odd($value)
{
return $value % 2 == 1;
}
function twig_test_constant($value, $constant)
{
return constant($constant) === $value;
}
function twig_test_defined($name, $context)
{
return array_key_exists($name, $context);
}

View File

@@ -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.
*/
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';
}
}
// tells the escaper node visitor that the string is safe
function twig_raw_filter($string)
{
return $string;
}

View File

@@ -0,0 +1,44 @@
<?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_I18n extends Twig_Extension
{
/**
* 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_Trans());
}
/**
* Returns a list of filters to add to the existing list.
*
* @return array An array of filters
*/
public function getFilters()
{
return array(
'trans' => new Twig_Filter_Function('gettext'),
);
}
/**
* Returns the name of the extension.
*
* @return string The extension name
*/
public function getName()
{
return 'i18n';
}
}

View File

@@ -0,0 +1,103 @@
<?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)
{
if ($this->isSandboxed()) {
$this->policy->checkSecurity($tags, $filters);
}
}
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);
}
}
/**
* Returns the name of the extension.
*
* @return string The extension name
*/
public function getName()
{
return 'sandbox';
}
}

View File

@@ -0,0 +1,63 @@
<?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.potencier@symfony-project.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 $environement The current Twig_Environment instance
*/
public function initRuntime(Twig_Environment $environement);
/**
* 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();
/**
* Returns the node visitor instances to add to the existing list.
*
* @return array An array of Twig_NodeVisitorInterface instances
*/
public function getNodeVisitors();
/**
* Returns a list of filters to add to the existing list.
*
* @return array An array of filters
*/
public function getFilters();
/**
* Returns a list of tests to add to the existing list.
*
* @return array An array of tests
*/
public function getTests();
/**
* Returns the name of the extension.
*
* @return string The extension name
*/
public function getName();
}

51
lib/classes/Twig/Filter.php Executable file
View File

@@ -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 a template filter.
*
* @package twig
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/
abstract class Twig_Filter implements Twig_FilterInterface
{
protected $options;
public function __construct(array $options = array())
{
$this->options = array_merge(array(
'needs_environment' => false,
), $options);
if (isset($this->options['is_escaper'])) {
$this->options['is_safe'] = array('html');
unset($this->options['is_escaper']);
}
}
public function needsEnvironment()
{
return $this->options['needs_environment'];
}
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();
}
}

View File

@@ -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.potencier@symfony-project.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;
}
}

View File

@@ -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.potencier@symfony-project.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->getEnvironment()->getExtension(\'%s\')->%s', $this->extension->getName(), $this->method);
}
}

View File

@@ -0,0 +1,21 @@
<?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.potencier@symfony-project.com>
*/
interface Twig_FilterInterface
{
public function compile();
}

30
lib/classes/Twig/Grammar.php Executable file
View File

@@ -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.
*/
abstract class Twig_Grammar implements Twig_GrammarInterface
{
protected $name;
protected $parser;
public function __construct($name)
{
$this->name = $name;
}
public function setParser(Twig_ParserInterface $parser)
{
$this->parser = $parser;
}
public function getName()
{
return $this->name;
}
}

View File

@@ -0,0 +1,22 @@
<?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_Grammar_Arguments extends Twig_Grammar
{
public function __toString()
{
return sprintf('<%s:arguments>', $this->name);
}
public function parse(Twig_Token $token)
{
return $this->parser->getExpressionParser()->parseArguments();
}
}

View File

@@ -0,0 +1,22 @@
<?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_Grammar_Array extends Twig_Grammar
{
public function __toString()
{
return sprintf('<%s:array>', $this->name);
}
public function parse(Twig_Token $token)
{
return $this->parser->getExpressionParser()->parseArrayExpression();
}
}

View File

@@ -0,0 +1,39 @@
<?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_Grammar_Body extends Twig_Grammar
{
protected $end;
public function __construct($name, $end = null)
{
parent::__construct($name);
$this->end = null === $end ? 'end'.$name : $end;
}
public function __toString()
{
return sprintf('<%s:body>', $this->name);
}
public function parse(Twig_Token $token)
{
$stream = $this->parser->getStream();
$stream->expect(Twig_Token::BLOCK_END_TYPE);
return $this->parser->subparse(array($this, 'decideBlockEnd'), true);
}
public function decideBlockEnd($token)
{
return $token->test($this->end);
}
}

View File

@@ -0,0 +1,24 @@
<?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_Grammar_Boolean extends Twig_Grammar
{
public function __toString()
{
return sprintf('<%s:boolean>', $this->name);
}
public function parse(Twig_Token $token)
{
$this->parser->getStream()->expect(Twig_Token::NAME_TYPE, array('true', 'false'));
return new Twig_Node_Expression_Constant('true' === $token->getValue() ? true : false, $token->getLine());
}
}

View File

@@ -0,0 +1,24 @@
<?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_Grammar_Constant extends Twig_Grammar
{
public function __toString()
{
return $this->name;
}
public function parse(Twig_Token $token)
{
$this->parser->getStream()->expect($this->name);
return $this->name;
}
}

View File

@@ -0,0 +1,22 @@
<?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_Grammar_Expression extends Twig_Grammar
{
public function __toString()
{
return sprintf('<%s>', $this->name);
}
public function parse(Twig_Token $token)
{
return $this->parser->getExpressionParser()->parseExpression();
}
}

View File

@@ -0,0 +1,24 @@
<?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_Grammar_Number extends Twig_Grammar
{
public function __toString()
{
return sprintf('<%s:number>', $this->name);
}
public function parse(Twig_Token $token)
{
$this->parser->getStream()->expect(Twig_Token::NUMBER_TYPE);
return new Twig_Node_Expression_Constant($token->getValue(), $token->getLine());
}
}

View File

@@ -0,0 +1,64 @@
<?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_Grammar_Optional extends Twig_Grammar
{
protected $grammar;
public function __construct()
{
$this->grammar = array();
foreach (func_get_args() as $grammar) {
$this->addGrammar($grammar);
}
}
public function __toString()
{
$repr = array();
foreach ($this->grammar as $grammar) {
$repr[] = (string) $grammar;
}
return sprintf('[%s]', implode(' ', $repr));
}
public function addGrammar(Twig_GrammarInterface $grammar)
{
$this->grammar[] = $grammar;
}
public function parse(Twig_Token $token)
{
// test if we have the optional element before consuming it
if ($this->grammar[0] instanceof Twig_Grammar_Constant) {
if (!$this->parser->getStream()->test($this->grammar[0]->getName())) {
return array();
}
} elseif ($this->grammar[0] instanceof Twig_Grammar_Name) {
if (!$this->parser->getStream()->test(Twig_Token::NAME_TYPE)) {
return array();
}
} elseif ($this->parser->getStream()->test(Twig_Token::BLOCK_END_TYPE)) {
// if this is not a Constant or a Name, it must be the last element of the tag
return array();
}
$elements = array();
foreach ($this->grammar as $grammar) {
$grammar->setParser($this->parser);
$elements[$grammar->getName()] = $grammar->parse($token);
}
return $elements;
}
}

View File

@@ -0,0 +1,24 @@
<?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_Grammar_Switch extends Twig_Grammar
{
public function __toString()
{
return sprintf('<%s:switch>', $this->name);
}
public function parse(Twig_Token $token)
{
$this->parser->getStream()->expect(Twig_Token::NAME_TYPE, $this->name);
return new Twig_Node_Expression_Constant(true, $token->getLine());
}
}

View File

@@ -0,0 +1,56 @@
<?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_Grammar_Tag extends Twig_Grammar
{
protected $grammar;
public function __construct()
{
$this->grammar = array();
foreach (func_get_args() as $grammar) {
$this->addGrammar($grammar);
}
}
public function __toString()
{
$repr = array();
foreach ($this->grammar as $grammar) {
$repr[] = (string) $grammar;
}
return implode(' ', $repr);
}
public function addGrammar(Twig_GrammarInterface $grammar)
{
$this->grammar[] = $grammar;
}
public function parse(Twig_Token $token)
{
$elements = array();
foreach ($this->grammar as $grammar) {
$grammar->setParser($this->parser);
$element = $grammar->parse($token);
if (is_array($element)) {
$elements = array_merge($elements, $element);
} else {
$elements[$grammar->getName()] = $element;
}
}
$this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
return $elements;
}
}

View File

@@ -0,0 +1,18 @@
<?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.
*/
interface Twig_GrammarInterface
{
public function setParser(Twig_ParserInterface $parser);
public function parse(Twig_Token $token);
public function getName();
}

31
lib/classes/Twig/LICENSE Normal file
View File

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

317
lib/classes/Twig/Lexer.php Executable file
View File

@@ -0,0 +1,317 @@
<?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.potencier@symfony-project.com>
*/
class Twig_Lexer implements Twig_LexerInterface
{
protected $cursor;
protected $position;
protected $end;
protected $pushedBack;
protected $code;
protected $lineno;
protected $filename;
protected $env;
protected $options;
const POSITION_DATA = 0;
const POSITION_BLOCK = 1;
const POSITION_VAR = 2;
const REGEX_NAME = '/[A-Za-z_][A-Za-z0-9_]*/A';
const REGEX_NUMBER = '/[0-9]+(?:\.[0-9]+)?/A';
const REGEX_STRING = '/(?:"([^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\')/Asm';
const REGEX_OPERATOR = '/<=? | >=? | [!=]= | = | \/\/ | \.\. | [(){}.,%*\/+~|-] | \[ | \] | \? | \:/Ax';
public function __construct(Twig_Environment $env = null, array $options = array())
{
if (null !== $env) {
$this->setEnvironment($env);
}
$this->options = array_merge(array(
'tag_comment' => array('{#', '#}'),
'tag_block' => array('{%', '%}'),
'tag_variable' => array('{{', '}}'),
), $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 = 'n/a')
{
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->pushedBack = array();
$this->end = strlen($this->code);
$this->position = self::POSITION_DATA;
$tokens = array();
$end = false;
while (!$end) {
$token = $this->nextToken();
$tokens[] = $token;
$end = $token->getType() === Twig_Token::EOF_TYPE;
}
if (isset($mbEncoding)) {
mb_internal_encoding($mbEncoding);
}
return new Twig_TokenStream($tokens, $this->filename);
}
public function setEnvironment(Twig_Environment $env)
{
$this->env = $env;
}
/**
* Parses the next token and returns it.
*/
protected function nextToken()
{
// do we have tokens pushed back? get one
if (!empty($this->pushedBack)) {
return array_shift($this->pushedBack);
}
// have we reached the end of the code?
if ($this->cursor >= $this->end) {
return new Twig_Token(Twig_Token::EOF_TYPE, '', $this->lineno);
}
// otherwise dispatch to the lexing functions depending
// on our current position in the code.
switch ($this->position) {
case self::POSITION_DATA:
$tokens = $this->lexData();
break;
case self::POSITION_BLOCK:
$tokens = $this->lexBlock();
break;
case self::POSITION_VAR:
$tokens = $this->lexVar();
break;
}
// if the return value is not an array it's a token
if (!is_array($tokens)) {
return $tokens;
}
// empty array, call again
else if (empty($tokens)) {
return $this->nextToken();
}
// if we have multiple items we push them to the buffer
else if (count($tokens) > 1) {
$first = array_shift($tokens);
$this->pushedBack = $tokens;
return $first;
}
// otherwise return the first item of the array.
else {
return $tokens[0];
}
}
protected function lexData()
{
$match = null;
$pos1 = strpos($this->code, $this->options['tag_comment'][0], $this->cursor);
$pos2 = strpos($this->code, $this->options['tag_variable'][0], $this->cursor);
$pos3 = strpos($this->code, $this->options['tag_block'][0], $this->cursor);
// if no matches are left we return the rest of the template
// as simple text token
if (false === $pos1 && false === $pos2 && false === $pos3) {
$rv = new Twig_Token(Twig_Token::TEXT_TYPE, substr($this->code, $this->cursor), $this->lineno);
$this->cursor = $this->end;
return $rv;
}
// min
$pos = -log(0);
if (false !== $pos1 && $pos1 < $pos) {
$pos = $pos1;
$token = $this->options['tag_comment'][0];
}
if (false !== $pos2 && $pos2 < $pos) {
$pos = $pos2;
$token = $this->options['tag_variable'][0];
}
if (false !== $pos3 && $pos3 < $pos) {
$pos = $pos3;
$token = $this->options['tag_block'][0];
}
// update the lineno on the instance
$lineno = $this->lineno;
$text = substr($this->code, $this->cursor, $pos - $this->cursor);
$this->moveCursor($text.$token);
$this->moveLineNo($text.$token);
// array of tokens
$result = array();
// push the template text first
if (!empty($text)) {
$result[] = new Twig_Token(Twig_Token::TEXT_TYPE, $text, $lineno);
$lineno += substr_count($text, "\n");
}
switch ($token) {
case $this->options['tag_comment'][0]:
if (!preg_match('/(.*?)'.preg_quote($this->options['tag_comment'][1], '/').'/As', $this->code, $match, null, $this->cursor)) {
throw new Twig_Error_Syntax('unclosed comment', $this->lineno, $this->filename);
}
$this->moveCursor($match[0]);
$this->moveLineNo($match[0]);
break;
case $this->options['tag_block'][0]:
// raw data?
if (preg_match('/\s*raw\s*'.preg_quote($this->options['tag_block'][1], '/').'(.*?)'.preg_quote($this->options['tag_block'][0], '/').'\s*endraw\s*'.preg_quote($this->options['tag_block'][1], '/').'/As', $this->code, $match, null, $this->cursor)) {
$result[] = new Twig_Token(Twig_Token::TEXT_TYPE, $match[1], $lineno);
$this->moveCursor($match[0]);
$this->moveLineNo($match[0]);
$this->position = self::POSITION_DATA;
} else {
$result[] = new Twig_Token(Twig_Token::BLOCK_START_TYPE, '', $lineno);
$this->position = self::POSITION_BLOCK;
}
break;
case $this->options['tag_variable'][0]:
$result[] = new Twig_Token(Twig_Token::VAR_START_TYPE, '', $lineno);
$this->position = self::POSITION_VAR;
break;
}
return $result;
}
protected function lexBlock()
{
if (preg_match('/\s*'.preg_quote($this->options['tag_block'][1], '/').'/As', $this->code, $match, null, $this->cursor)) {
$lineno = $this->lineno;
$this->moveCursor($match[0]);
$this->moveLineNo($match[0]);
$this->position = self::POSITION_DATA;
return new Twig_Token(Twig_Token::BLOCK_END_TYPE, '', $lineno);
}
return $this->lexExpression();
}
protected function lexVar()
{
if (preg_match('/\s*'.preg_quote($this->options['tag_variable'][1], '/').'/As', $this->code, $match, null, $this->cursor)) {
$lineno = $this->lineno;
$this->moveCursor($match[0]);
$this->moveLineNo($match[0]);
$this->position = self::POSITION_DATA;
return new Twig_Token(Twig_Token::VAR_END_TYPE, '', $lineno);
}
return $this->lexExpression();
}
protected function lexExpression()
{
$match = null;
// whitespace
while (preg_match('/\s+/As', $this->code, $match, null, $this->cursor)) {
$this->moveCursor($match[0]);
$this->moveLineNo($match[0]);
}
// sanity check
if ($this->cursor >= $this->end) {
throw new Twig_Error_Syntax('Unexpected end of stream', $this->lineno, $this->filename);
}
// first parse operators
if (preg_match(self::REGEX_OPERATOR, $this->code, $match, null, $this->cursor)) {
$this->moveCursor($match[0]);
return new Twig_Token(Twig_Token::OPERATOR_TYPE, $match[0], $this->lineno);
}
// now names
else if (preg_match(self::REGEX_NAME, $this->code, $match, null, $this->cursor)) {
$this->moveCursor($match[0]);
return new Twig_Token(Twig_Token::NAME_TYPE, $match[0], $this->lineno);
}
// then numbers
else if (preg_match(self::REGEX_NUMBER, $this->code, $match, null, $this->cursor)) {
$this->moveCursor($match[0]);
$value = (float)$match[0];
if ((int)$value === $value) {
$value = (int)$value;
}
return new Twig_Token(Twig_Token::NUMBER_TYPE, $value, $this->lineno);
}
// and finally strings
else if (preg_match(self::REGEX_STRING, $this->code, $match, null, $this->cursor)) {
$this->moveCursor($match[0]);
$this->moveLineNo($match[0]);
$value = stripcslashes(substr($match[0], 1, strlen($match[0]) - 2));
return new Twig_Token(Twig_Token::STRING_TYPE, $value, $this->lineno);
}
// unlexable
throw new Twig_Error_Syntax(sprintf("Unexpected character '%s'", $this->code[$this->cursor]), $this->lineno, $this->filename);
}
protected function moveLineNo($text)
{
$this->lineno += substr_count($text, "\n");
}
protected function moveCursor($text)
{
$this->cursor += strlen($text);
}
}

View File

@@ -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.potencier@symfony-project.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
*/
public function tokenize($code, $filename = 'n/a');
}

View File

@@ -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.
*/
/**
* Loads a template from an array.
*
* When using this loader with a cache mehcanism, 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.potencier@symfony-project.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;
}
}
/**
* Gets the source code of a template, given its name.
*
* @param string $name string 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 string 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;
}
}

View File

@@ -0,0 +1,125 @@
<?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.potencier@symfony-project.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)
{
// invalidate the cache
$this->cache = array();
if (!is_array($paths)) {
$paths = array($paths);
}
$this->paths = array();
foreach ($paths as $path) {
if (!is_dir($path)) {
throw new Twig_Error_Loader(sprintf('The "%s" directory does not exist.', $path));
}
$this->paths[] = realpath($path);
}
}
/**
* Gets the source code of a template, given its name.
*
* @param string $name string 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 string 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)
{
if (isset($this->cache[$name])) {
return $this->cache[$name];
}
foreach ($this->paths as $path) {
if (!file_exists($path.DIRECTORY_SEPARATOR.$name) || is_dir($path.DIRECTORY_SEPARATOR.$name)) {
continue;
}
$file = realpath($path.DIRECTORY_SEPARATOR.$name);
// simple security check
if (0 !== strpos($file, $path)) {
throw new Twig_Error_Loader('Looks like you try to load a template outside configured directories.');
}
return $this->cache[$name] = $file;
}
throw new Twig_Error_Loader(sprintf('Unable to find template "%s" (looked into: %s).', $name, implode(', ', $this->paths)));
}
}

View File

@@ -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.potencier@symfony-project.com>
*/
class Twig_Loader_String implements Twig_LoaderInterface
{
/**
* Gets the source code of a template, given its name.
*
* @param string $name string 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 string 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;
}
}

View File

@@ -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.potencier@symfony-project.com>
*/
interface Twig_LoaderInterface
{
/**
* Gets the source code of a template, given its name.
*
* @param string $name string The name of the template to load
*
* @return string The template source code
*/
public function getSource($name);
/**
* Gets the cache key to use for the cache for a given template name.
*
* @param string $name string The name of the template to load
*
* @return string The cache key
*/
public 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
*/
public function isFresh($name, $time);
}

227
lib/classes/Twig/Node.php Executable file
View File

@@ -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.potencier@symfony-project.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($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);
}
}

View File

@@ -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.potencier@symfony-project.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($compiler)
{
$compiler->subcompile($this->getNode('body'));
}
}

45
lib/classes/Twig/Node/Block.php Executable file
View File

@@ -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.potencier@symfony-project.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($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")
;
}
}

View File

@@ -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.potencier@symfony-project.com>
*/
class Twig_Node_BlockReference extends Twig_Node
{
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($compiler)
{
$compiler
->addDebugInfo($this)
->write(sprintf("\$this->getBlock('%s', \$context, \$blocks);\n", $this->getAttribute('name')))
;
}
}

View File

@@ -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.potencier@symfony-project.com>
*/
abstract class Twig_Node_Expression extends Twig_Node
{
}

View File

@@ -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($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(')');
}
}

View File

@@ -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($compiler)
{
$compiler->raw(sprintf('$context[\'%s\']', $this->getAttribute('name')));
}
}

View File

@@ -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($compiler)
{
$compiler
->raw('(')
->subcompile($this->getNode('left'))
->raw(' ')
;
$this->operator($compiler);
$compiler
->raw(' ')
->subcompile($this->getNode('right'))
->raw(')')
;
}
abstract public function operator($compiler);
}

View File

@@ -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($compiler)
{
return $compiler->raw('+');
}
}

View File

@@ -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($compiler)
{
return $compiler->raw('&&');
}
}

View File

@@ -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($compiler)
{
return $compiler->raw('.');
}
}

View File

@@ -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($compiler)
{
return $compiler->raw('/');
}
}

View File

@@ -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($compiler)
{
$compiler->raw('floor(');
parent::compile($compiler);
$compiler->raw(')');
}
public function operator($compiler)
{
return $compiler->raw('/');
}
}

View File

@@ -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($compiler)
{
return $compiler->raw('%');
}
}

View File

@@ -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($compiler)
{
return $compiler->raw('*');
}
}

View File

@@ -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($compiler)
{
return $compiler->raw('||');
}
}

View File

@@ -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($compiler)
{
return $compiler->raw('-');
}
}

View File

@@ -0,0 +1,61 @@
<?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_Compare extends Twig_Node_Expression
{
public function __construct(Twig_Node_Expression $expr, Twig_NodeInterface $ops, $lineno)
{
parent::__construct(array('expr' => $expr, 'ops' => $ops), array(), $lineno);
}
public function compile($compiler)
{
if ('in' === $this->getNode('ops')->getNode('0')->getAttribute('value')) {
return $this->compileIn($compiler);
}
$this->getNode('expr')->compile($compiler);
$nbOps = count($this->getNode('ops'));
for ($i = 0; $i < $nbOps; $i += 2) {
if ($i > 0) {
$compiler->raw(' && ($tmp'.($i / 2));
}
$compiler->raw(' '.$this->getNode('ops')->getNode($i)->getAttribute('value').' ');
if ($i != $nbOps - 2) {
$compiler
->raw('($tmp'.(($i / 2) + 1).' = ')
->subcompile($this->getNode('ops')->getNode($i + 1))
->raw(')')
;
} else {
$compiler->subcompile($this->getNode('ops')->getNode($i + 1));
}
}
for ($j = 1; $j < $i / 2; $j++) {
$compiler->raw(')');
}
}
protected function compileIn($compiler)
{
$compiler
->raw('twig_in_filter(')
->subcompile($this->getNode('expr'))
->raw(', ')
->subcompile($this->getNode('ops')->getNode(1))
->raw(')')
;
}
}

View File

@@ -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($compiler)
{
$compiler
->raw('(')
->subcompile($this->getNode('expr1'))
->raw(') ? (')
->subcompile($this->getNode('expr2'))
->raw(') : (')
->subcompile($this->getNode('expr3'))
->raw(')')
;
}
}

View File

@@ -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($compiler)
{
$compiler->repr($this->getAttribute('value'));
}
}

View File

@@ -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.potencier@symfony-project.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($compiler)
{
$compiler->raw(sprintf("\$this->env->getExtension('%s')", $this->getAttribute('name')));
}
}

View File

@@ -0,0 +1,43 @@
<?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 $filter_name, Twig_NodeInterface $arguments, $lineno, $tag = null)
{
parent::__construct(array('node' => $node, 'filter' => $filter_name, 'arguments' => $arguments), array(), $lineno, $tag);
}
public function compile($compiler)
{
$filterMap = $compiler->getEnvironment()->getFilters();
$name = $this->getNode('filter')->getAttribute('value');
$attrs = $this->getNode('arguments');
if (!isset($filterMap[$name])) {
throw new Twig_Error_Syntax(sprintf('The filter "%s" does not exist', $name), $this->getLine());
} else {
$compiler->raw($filterMap[$name]->compile().($filterMap[$name]->needsEnvironment() ? '($this->env, ' : '('));
}
$this->getNode('node')->compile($compiler);
foreach ($attrs as $node) {
$compiler
->raw(', ')
->subcompile($node)
;
}
$compiler->raw(')');
}
}

View File

@@ -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.
*/
class Twig_Node_Expression_GetAttr extends Twig_Node_Expression
{
const TYPE_ANY = 'any';
const TYPE_ARRAY = 'array';
const TYPE_METHOD = 'method';
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($compiler)
{
$compiler
->raw('$this->getAttribute(')
->subcompile($this->getNode('node'))
->raw(', ')
->subcompile($this->getNode('attribute'))
->raw(', array(')
;
foreach ($this->getNode('arguments') as $node) {
$compiler
->subcompile($node)
->raw(', ')
;
}
$compiler
->raw('), ')
->repr($this->getAttribute('type'))
->raw(')');
}
}

View File

@@ -0,0 +1,33 @@
<?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($compiler)
{
if ('_self' === $this->getAttribute('name')) {
$compiler->raw('$this');
} elseif ('_context' === $this->getAttribute('name')) {
$compiler->raw('$context');
} elseif ('_charset' === $this->getAttribute('name')) {
$compiler->raw('$this->getEnvironment()->getCharset()');
} elseif ($compiler->getEnvironment()->isStrictVariables()) {
$compiler->raw(sprintf('$this->getContext($context, \'%s\')', $this->getAttribute('name'), $this->getAttribute('name')));
} else {
$compiler->raw(sprintf('(isset($context[\'%s\']) ? $context[\'%s\'] : null)', $this->getAttribute('name'), $this->getAttribute('name')));
}
}
}

View File

@@ -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($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());
}
// defined is a special case
if ('defined' === $this->getAttribute('name')) {
if (!$this->getNode('node') instanceof Twig_Node_Expression_Name){
throw new Twig_Error_Syntax('The "defined" test only works with simple variables', $this->getLine());
}
$compiler
->raw($testMap[$this->getAttribute('name')]->compile().'(')
->repr($this->getNode('node')->getAttribute('name'))
->raw(', $context)')
;
return;
}
$compiler
->raw($testMap[$this->getAttribute('name')]->compile().'(')
->subcompile($this->getNode('node'))
;
if (null !== $this->getNode('arguments')) {
$compiler->raw(', ');
$max = count($this->getNode('arguments')) - 1;
foreach ($this->getNode('arguments') as $i => $node) {
$compiler->subcompile($node);
if ($i != $max) {
$compiler->raw(', ');
}
}
}
$compiler->raw(')');
}
}

View File

@@ -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($compiler)
{
$compiler->raw('(');
$this->operator($compiler);
$compiler
->subcompile($this->getNode('node'))
->raw(')')
;
}
abstract public function operator($compiler);
}

View File

@@ -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($compiler)
{
$compiler->raw('-');
}
}

View File

@@ -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($compiler)
{
$compiler->raw('!');
}
}

View File

@@ -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($compiler)
{
$compiler->raw('+');
}
}

124
lib/classes/Twig/Node/For.php Executable file
View File

@@ -0,0 +1,124 @@
<?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.potencier@symfony-project.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_NodeInterface $body, Twig_NodeInterface $else = null, $withLoop = false, $lineno, $tag = null)
{
parent::__construct(array('key_target' => $keyTarget, 'value_target' => $valueTarget, 'seq' => $seq, 'body' => $body, 'else' => $else), array('with_loop' => $withLoop), $lineno, $tag);
}
/**
* Compiles the node to PHP.
*
* @param Twig_Compiler A Twig_Compiler instance
*/
public function compile($compiler)
{
$compiler
->addDebugInfo($this)
// the (array) cast bypasses a PHP 5.2.6 bug
->write('$context[\'_parent\'] = (array) $context;'."\n")
;
if (null !== $this->getNode('else')) {
$compiler->write("\$context['_iterated'] = false;\n");
}
$compiler
->write("\$context['_seq'] = twig_iterator_to_array(")
->subcompile($this->getNode('seq'))
->raw(");\n")
;
if ($this->getAttribute('with_loop')) {
$compiler
->write("\$countable = is_array(\$context['_seq']) || (is_object(\$context['_seq']) && \$context['_seq'] instanceof Countable);\n")
->write("\$length = \$countable ? count(\$context['_seq']) : null;\n")
->write("\$context['loop'] = array(\n")
->write(" 'parent' => \$context['_parent'],\n")
->write(" 'index0' => 0,\n")
->write(" 'index' => 1,\n")
->write(" 'first' => true,\n")
->write(");\n")
->write("if (\$countable) {\n")
->indent()
->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('else')) {
$compiler->write("\$context['_iterated'] = true;\n");
}
$compiler->subcompile($this->getNode('body'));
if ($this->getAttribute('with_loop')) {
$compiler
->write("++\$context['loop']['index0'];\n")
->write("++\$context['loop']['index'];\n")
->write("\$context['loop']['first'] = false;\n")
->write("if (\$countable) {\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");
}
}

67
lib/classes/Twig/Node/If.php Executable file
View File

@@ -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.potencier@symfony-project.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($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");
}
}

View File

@@ -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.potencier@symfony-project.com>
*/
class Twig_Node_Import extends Twig_Node
{
public function __construct(Twig_Node_Expression $expr, Twig_Node_Expression_AssignName $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($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(", true)")
;
}
$compiler->raw(";\n");
}
}

View File

@@ -0,0 +1,76 @@
<?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.potencier@symfony-project.com>
*/
class Twig_Node_Include extends Twig_Node
{
public function __construct(Twig_Node_Expression $expr, Twig_Node_Expression $variables = null, $only = false, $lineno, $tag = null)
{
parent::__construct(array('expr' => $expr, 'variables' => $variables), array('only' => (Boolean) $only), $lineno, $tag);
}
/**
* Compiles the node to PHP.
*
* @param Twig_Compiler A Twig_Compiler instance
*/
public function compile($compiler)
{
$compiler->addDebugInfo($this);
if ($this->getNode('expr') instanceof Twig_Node_Expression_Constant) {
$compiler
->write("\$this->env->loadTemplate(")
->subcompile($this->getNode('expr'))
->raw(")->display(")
;
} else {
$compiler
->write("\$template = ")
->subcompile($this->getNode('expr'))
->raw(";\n")
->write("if (!\$template")
->raw(" instanceof Twig_Template) {\n")
->indent()
->write("\$template = \$this->env->loadTemplate(\$template);\n")
->outdent()
->write("}\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");
}
}

62
lib/classes/Twig/Node/Macro.php Executable file
View File

@@ -0,0 +1,62 @@
<?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.potencier@symfony-project.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($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(\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")
->subcompile($this->getNode('body'))
->outdent()
->write("}\n\n")
;
}
}

188
lib/classes/Twig/Node/Module.php Executable file
View File

@@ -0,0 +1,188 @@
<?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.potencier@symfony-project.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, $filename)
{
parent::__construct(array('parent' => $parent, 'body' => $body, 'blocks' => $blocks, 'macros' => $macros), array('filename' => $filename), 1);
}
/**
* Compiles the node to PHP.
*
* @param Twig_Compiler A Twig_Compiler instance
*/
public function compile($compiler)
{
$this->compileTemplate($compiler);
}
protected function compileTemplate($compiler)
{
$this->compileClassHeader($compiler);
if (count($this->getNode('blocks'))) {
$this->compileConstructor($compiler);
}
$this->compileGetParent($compiler);
$this->compileDisplayHeader($compiler);
$this->compileDisplayBody($compiler);
$this->compileDisplayFooter($compiler);
$compiler->subcompile($this->getNode('blocks'));
$this->compileMacros($compiler);
$this->compileClassFooter($compiler);
}
protected function compileGetParent($compiler)
{
if (null === $this->getNode('parent')) {
return;
}
$compiler
->write("public function getParent(array \$context)\n", "{\n")
->indent()
->write("if (null === \$this->parent) {\n")
->indent();
;
if ($this->getNode('parent') instanceof Twig_Node_Expression_Constant) {
$compiler
->write("\$this->parent = \$this->env->loadTemplate(")
->subcompile($this->getNode('parent'))
->raw(");\n")
;
} else {
$compiler
->write("\$this->parent = ")
->subcompile($this->getNode('parent'))
->raw(";\n")
->write("if (!\$this->parent")
->raw(" instanceof Twig_Template) {\n")
->indent()
->write("\$this->parent = \$this->env->loadTemplate(\$this->parent);\n")
->outdent()
->write("}\n")
;
}
$compiler
->outdent()
->write("}\n\n")
->write("return \$this->parent;\n")
->outdent()
->write("}\n\n")
;
}
protected function compileDisplayBody($compiler)
{
if (null !== $this->getNode('parent')) {
// remove all but import nodes
foreach ($this->getNode('body') as $node) {
if ($node instanceof Twig_Node_Import) {
$compiler->subcompile($node);
}
}
$compiler
->write("\$this->getParent(\$context)->display(\$context, array_merge(\$this->blocks, \$blocks));\n")
;
} else {
$compiler->subcompile($this->getNode('body'));
}
}
protected function compileClassHeader($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()
;
if (null !== $this->getNode('parent')) {
$compiler->write("protected \$parent;\n\n");
}
}
protected function compileConstructor($compiler)
{
$compiler
->write("public function __construct(Twig_Environment \$env)\n", "{\n")
->indent()
->write("parent::__construct(\$env);\n\n")
->write("\$this->blocks = array(\n")
->indent()
;
foreach ($this->getNode('blocks') as $name => $node) {
$compiler
->write(sprintf("'%s' => array(\$this, 'block_%s'),\n", $name, $name))
;
}
$compiler
->outdent()
->write(");\n")
->outdent()
->write("}\n\n");
;
}
protected function compileDisplayHeader($compiler)
{
$compiler
->write("public function display(array \$context, array \$blocks = array())\n", "{\n")
->indent()
;
}
protected function compileDisplayFooter($compiler)
{
$compiler
->outdent()
->write("}\n\n")
;
}
protected function compileClassFooter($compiler)
{
$compiler
->outdent()
->write("}\n")
;
}
protected function compileMacros($compiler)
{
$compiler->subcompile($this->getNode('macros'));
}
}

View File

@@ -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 parent node.
*
* @package twig
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/
class Twig_Node_Parent extends Twig_Node
{
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($compiler)
{
$compiler
->addDebugInfo($this)
->write("\$this->getParentBlock(")
->string($this->getAttribute('name'))
->raw(", \$context, \$blocks);\n")
;
}
}

40
lib/classes/Twig/Node/Print.php Executable file
View File

@@ -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.potencier@symfony-project.com>
*/
class Twig_Node_Print extends Twig_Node
{
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($compiler)
{
$compiler
->addDebugInfo($this)
->write('echo ')
->subcompile($this->getNode('expr'))
->raw(";\n")
;
}
}

View File

@@ -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.potencier@symfony-project.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($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")
;
}
}

View File

@@ -0,0 +1,68 @@
<?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.potencier@symfony-project.com>
*/
class Twig_Node_SandboxedModule extends Twig_Node_Module
{
protected $usedFilters;
protected $usedTags;
public function __construct(Twig_Node_Module $node, array $usedFilters, array $usedTags)
{
parent::__construct($node->getNode('body'), $node->getNode('parent'), $node->getNode('blocks'), $node->getNode('macros'), $node->getAttribute('filename'), $node->getLine(), $node->getNodeTag());
$this->usedFilters = $usedFilters;
$this->usedTags = $usedTags;
}
protected function compileDisplayBody($compiler)
{
if (null === $this->getNode('parent')) {
$compiler->write("\$this->checkSecurity();\n");
}
parent::compileDisplayBody($compiler);
}
protected function compileDisplayFooter($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")
->outdent()
->write(");\n")
;
if (null !== $this->getNode('parent')) {
$compiler
->raw("\n")
->write("\$this->parent->checkSecurity();\n")
;
}
$compiler
->outdent()
->write("}\n\n")
;
}
}

View File

@@ -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.
*/
/**
* 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.potencier@symfony-project.com>
*/
class Twig_Node_SandboxedPrint extends Twig_Node_Print
{
public function __construct(Twig_Node_Print $node)
{
parent::__construct($node->getNode('expr'), $node->getLine(), $node->getNodeTag());
}
/**
* Compiles the node to PHP.
*
* @param Twig_Compiler A Twig_Compiler instance
*/
public function compile($compiler)
{
$compiler
->addDebugInfo($this)
->write('if ($this->env->hasExtension(\'sandbox\') && is_object(')
->subcompile($this->getNode('expr'))
->raw(')) {'."\n")
->indent()
->write('$this->env->getExtension(\'sandbox\')->checkMethodAllowed(')
->subcompile($this->getNode('expr'))
->raw(', \'__toString\');'."\n")
->outdent()
->write('}'."\n")
;
parent::compile($compiler);
}
}

79
lib/classes/Twig/Node/Set.php Executable file
View File

@@ -0,0 +1,79 @@
<?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.potencier@symfony-project.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), $lineno, $tag);
}
/**
* Compiles the node to PHP.
*
* @param Twig_Compiler A Twig_Compiler instance
*/
public function compile($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(" = 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 {
$compiler->subcompile($this->getNode('values'));
}
}
$compiler->raw(";\n");
}
}

View File

@@ -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.potencier@symfony-project.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($compiler)
{
$compiler
->addDebugInfo($this)
->write("ob_start();\n")
->subcompile($this->getNode('body'))
->write("echo trim(preg_replace('/>\s+</', '><', ob_get_clean()));\n")
;
}
}

40
lib/classes/Twig/Node/Text.php Executable file
View File

@@ -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.potencier@symfony-project.com>
*/
class Twig_Node_Text extends Twig_Node
{
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($compiler)
{
$compiler
->addDebugInfo($this)
->write('echo ')
->string($this->getAttribute('data'))
->raw(";\n")
;
}
}

124
lib/classes/Twig/Node/Trans.php Executable file
View File

@@ -0,0 +1,124 @@
<?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 trans node.
*
* @package twig
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/
class Twig_Node_Trans extends Twig_Node
{
public function __construct(Twig_NodeInterface $body, Twig_NodeInterface $plural = null, Twig_Node_Expression $count = null, $lineno, $tag = null)
{
parent::__construct(array('count' => $count, 'body' => $body, 'plural' => $plural), array(), $lineno, $tag);
}
/**
* Compiles the node to PHP.
*
* @param Twig_Compiler A Twig_Compiler instance
*/
public function compile($compiler)
{
$compiler->addDebugInfo($this);
list($msg, $vars) = $this->compileString($this->getNode('body'));
if (null !== $this->getNode('plural')) {
list($msg1, $vars1) = $this->compileString($this->getNode('plural'));
$vars = array_merge($vars, $vars1);
}
$function = null === $this->getNode('plural') ? 'gettext' : 'ngettext';
if ($vars) {
$compiler
->write('echo strtr('.$function.'(')
->subcompile($msg)
;
if (null !== $this->getNode('plural')) {
$compiler
->raw(', ')
->subcompile($msg1)
->raw(', abs(')
->subcompile($this->getNode('count'))
->raw(')')
;
}
$compiler->raw('), array(');
foreach ($vars as $var) {
if ('count' === $var->getAttribute('name')) {
$compiler
->string('%count%')
->raw(' => abs(')
->subcompile($this->getNode('count'))
->raw('), ')
;
} else {
$compiler
->string('%'.$var->getAttribute('name').'%')
->raw(' => ')
->subcompile($var)
->raw(', ')
;
}
}
$compiler->raw("));\n");
} else {
$compiler
->write('echo '.$function.'(')
->subcompile($msg)
;
if (null !== $this->getNode('plural')) {
$compiler
->raw(', ')
->subcompile($msg1)
->raw(', abs(')
->subcompile($this->getNode('count'))
->raw(')')
;
}
$compiler->raw(');');
}
}
protected function compileString(Twig_NodeInterface $body)
{
if ($body instanceof Twig_Node_Expression_Name || $body instanceof Twig_Node_Expression_Constant) {
return array($body, array());
}
$msg = '';
$vars = array();
foreach ($body as $node) {
if ($node instanceof Twig_Node_Print) {
$n = $node->getNode('expr');
while ($n instanceof Twig_Node_Expression_Filter) {
$n = $n->getNode('node');
}
$msg .= sprintf('%%%s%%', $n->getAttribute('name'));
$vars[] = new Twig_Node_Expression_Name($n->getAttribute('name'), $n->getLine());
} else {
$msg .= $node->getAttribute('data');
}
}
return array(new Twig_Node(array(new Twig_Node_Expression_Constant(trim($msg), $node->getLine()))), $vars);
}
}

View File

@@ -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.potencier@symfony-project.com>
*/
interface Twig_NodeInterface
{
/**
* Compiles the node to PHP.
*
* @param Twig_Compiler A Twig_Compiler instance
*/
public function compile($compiler);
public function getLine();
public function getNodeTag();
}

View File

@@ -0,0 +1,79 @@
<?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.potencier@symfony-project.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)
{
$this->visitors[] = $visitor;
}
/**
* Traverses a node and calls the registered visitors.
*
* @param Twig_NodeInterface $node A Twig_NodeInterface instance
*/
public function traverse(Twig_NodeInterface $node = null)
{
if (null === $node) {
return null;
}
foreach ($this->visitors as $visitor) {
$node = $visitor->enterNode($node, $this->env);
}
foreach ($node as $k => $n) {
if (false !== $n = $this->traverse($n)) {
$node->setNode($k, $n);
} else {
$node->removeNode($k);
}
}
foreach ($this->visitors as $visitor) {
$node = $visitor->leaveNode($node, $this->env);
}
return $node;
}
}

View File

@@ -0,0 +1,123 @@
<?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.potencier@symfony-project.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_Print) {
return $this->escapeNode($node, $env, $this->needEscaping($env));
} 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_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 escapeNode(Twig_NodeInterface $node, Twig_Environment $env, $type)
{
if (false === $type) {
return $node;
}
$expression = $node instanceof Twig_Node_Print ? $node->getNode('expr') : $node;
$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);
}
if (false !== in_array($type, $safe) || false !== in_array('all', $safe)) {
return $node;
}
if ($node instanceof Twig_Node_Print) {
return new Twig_Node_Print(
$this->getEscaperFilter($type, $expression),
$node->getLine()
);
}
return $this->getEscaperFilter($type, $node);
}
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);
}
}

View File

@@ -0,0 +1,84 @@
<?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_Conditional) {
// instersect 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
$filterMap = $env->getFilters();
$name = $node->getNode('filter')->getAttribute('value');
$args = $node->getNode('arguments');
if (isset($filterMap[$name])) {
$this->setSafe($node, $filterMap[$name]->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);
}
}

View File

@@ -0,0 +1,78 @@
<?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.potencier@symfony-project.com>
*/
class Twig_NodeVisitor_Sandbox implements Twig_NodeVisitorInterface
{
protected $inAModule = false;
protected $tags;
protected $filters;
/**
* 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();
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 simple print statements ({{ article }})
if ($node instanceof Twig_Node_Print && $node->getNode('expr') instanceof Twig_Node_Expression_Name) {
return new Twig_Node_SandboxedPrint($node);
}
}
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));
}
return $node;
}
}

View File

@@ -0,0 +1,39 @@
<?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.potencier@symfony-project.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
*/
public 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
*/
public function leaveNode(Twig_NodeInterface $node, Twig_Environment $env);
}

227
lib/classes/Twig/Parser.php Executable file
View File

@@ -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.
*/
class Twig_Parser implements Twig_ParserInterface
{
protected $stream;
protected $parent;
protected $handlers;
protected $visitors;
protected $expressionParser;
protected $blocks;
protected $blockStack;
protected $macros;
protected $env;
public function __construct(Twig_Environment $env = null)
{
if (null !== $env) {
$this->setEnvironment($env);
}
}
public function setEnvironment(Twig_Environment $env)
{
$this->env = $env;
}
/**
* 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)
{
// 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->stream = $stream;
$this->parent = null;
$this->blocks = array();
$this->macros = array();
$this->blockStack = array();
try {
$body = $this->subparse(null);
} catch (Twig_Error_Syntax $e) {
if (null === $e->getFilename()) {
$e->setFilename($this->stream->getFilename());
}
throw $e;
}
if (null !== $this->parent) {
$this->checkBodyNodes($body);
}
$node = new Twig_Node_Module($body, $this->parent, new Twig_Node($this->blocks), new Twig_Node($this->macros), $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());
}
if (null !== $test && call_user_func($test, $token)) {
if ($dropNeedle) {
$this->stream->next();
}
return new Twig_Node($rv, array(), $lineno);
}
$subparser = $this->handlers->getTokenParser($token->getValue());
if (null === $subparser) {
throw new Twig_Error_Syntax(sprintf('Unknown tag name "%s"', $token->getValue()), $token->getLine());
}
$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.');
}
}
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, $value)
{
$this->macros[$name] = $value;
}
public function getExpressionParser()
{
return $this->expressionParser;
}
public function getParent()
{
return $this->parent;
}
public function setParent($parent)
{
$this->parent = $parent;
}
public function getStream()
{
return $this->stream;
}
public function getCurrentToken()
{
return $this->stream->getCurrent();
}
protected function checkBodyNodes($body)
{
// check that the body only contains block references and empty text nodes
foreach ($body as $node)
{
if (
($node instanceof Twig_Node_Text && !preg_match('/^\s*$/s', $node->getAttribute('data')))
||
(!$node instanceof Twig_Node_Text && !$node instanceof Twig_Node_BlockReference && !$node instanceof Twig_Node_Import)
) {
throw new Twig_Error_Syntax('A template that extends another one cannot have a body', $node->getLine(), $this->stream->getFilename());
}
}
}
}

View File

@@ -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.potencier@symfony-project.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
*/
public function parse(Twig_TokenStream $code);
}

View File

@@ -0,0 +1,8 @@
Twig, the flexible, fast, and secure template language for PHP
==============================================================
Twig is a template language for PHP, released under the new BSD license (code
and documentation).
Twig uses a syntax similar to the Django and Jinja template languages which
inspired the Twig runtime environment.

View File

@@ -0,0 +1,22 @@
<?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.potencier@symfony-project.com>
* @version SVN: $Id$
*/
class Twig_RuntimeError extends Twig_Error
{
}

View File

@@ -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.potencier@symfony-project.com>
*/
class Twig_Sandbox_SecurityError extends Twig_Error
{
}

View File

@@ -0,0 +1,99 @@
<?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.potencier@symfony-project.com>
*/
class Twig_Sandbox_SecurityPolicy implements Twig_Sandbox_SecurityPolicyInterface
{
protected $allowedTags;
protected $allowedFilters;
protected $allowedMethods;
protected $allowedProperties;
public function __construct(array $allowedTags = array(), array $allowedFilters = array(), array $allowedMethods = array(), array $allowedProperties = array())
{
$this->allowedTags = $allowedTags;
$this->allowedFilters = $allowedFilters;
$this->allowedMethods = $allowedMethods;
$this->allowedProperties = $allowedProperties;
}
public function setAllowedTags(array $tags)
{
$this->allowedTags = $tags;
}
public function setAllowedFilters(array $filters)
{
$this->allowedFilters = $filters;
}
public function setAllowedMethods(array $methods)
{
$this->allowedMethods = $methods;
}
public function setAllowedProperties(array $properties)
{
$this->allowedProperties = $properties;
}
public function checkSecurity($tags, $filters)
{
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));
}
}
}
public function checkMethodAllowed($obj, $method)
{
$allowed = false;
foreach ($this->allowedMethods as $class => $methods) {
if ($obj instanceof $class) {
$allowed = in_array($method, is_array($methods) ? $methods : array($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)));
}
}
}

View File

@@ -0,0 +1,25 @@
<?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.
*/
/**
* Interfaces that all security policy classes must implements.
*
* @package twig
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/
interface Twig_Sandbox_SecurityPolicyInterface
{
public function checkSecurity($tags, $filters);
public function checkMethodAllowed($obj, $method);
public function checkPropertyAllowed($obj, $method);
}

View File

@@ -0,0 +1,129 @@
<?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.
*/
abstract class Twig_SimpleTokenParser extends Twig_TokenParser
{
/**
* Parses a token and returns a node.
*
* @param Twig_Token $token A Twig_Token instance
*
* @return Twig_NodeInterface A Twig_NodeInterface instance
*/
public function parse(Twig_Token $token)
{
$grammar = $this->getGrammar();
if (!is_object($grammar)) {
$grammar = self::parseGrammar($grammar);
}
$grammar->setParser($this->parser);
$values = $grammar->parse($token);
return $this->getNode($values, $token->getLine());
}
/**
* Gets the grammar as an object or as a string.
*
* @return string|Twig_Grammar A Twig_Grammar instance or a string
*/
abstract protected function getGrammar();
/**
* Gets the nodes based on the parsed values.
*
* @param array $values An array of values
* @param integer $line The parser line
*/
abstract protected function getNode(array $values, $line);
protected function getAttribute($node, $attribute, $arguments = array(), $type = Twig_Node_Expression_GetAttr::TYPE_ANY, $line = -1)
{
return new Twig_Node_Expression_GetAttr(
$node instanceof Twig_NodeInterface ? $node : new Twig_Node_Expression_Name($node, $line),
$attribute instanceof Twig_NodeInterface ? $attribute : new Twig_Node_Expression_Constant($attribute, $line),
$arguments instanceof Twig_NodeInterface ? $arguments : new Twig_Node($arguments),
$type,
$line
);
}
protected function call($node, $attribute, $arguments = array(), $line = -1)
{
return $this->getAttribute($node, $attribute, $arguments, Twig_Node_Expression_GetAttr::TYPE_METHOD, $line);
}
protected function markAsSafe(Twig_NodeInterface $node, $line = -1)
{
return new Twig_Node_Expression_Filter(
$node,
new Twig_Node_Expression_Constant('raw', $line),
new Twig_Node(),
$line
);
}
protected function output(Twig_NodeInterface $node, $line = -1)
{
return new Twig_Node_Print($node, $line);
}
protected function getNodeValues(array $values)
{
$nodes = array();
foreach ($values as $value) {
if ($value instanceof Twig_NodeInterface) {
$nodes[] = $value;
}
}
return $nodes;
}
static public function parseGrammar($str, $main = true)
{
static $cursor;
if (true === $main) {
$cursor = 0;
$grammar = new Twig_Grammar_Tag();
} else {
$grammar = new Twig_Grammar_Optional();
}
while ($cursor < strlen($str)) {
if (preg_match('/\s+/A', $str, $match, null, $cursor)) {
$cursor += strlen($match[0]);
} elseif (preg_match('/<(\w+)(?:\:(\w+))?>/A', $str, $match, null, $cursor)) {
$class = sprintf('Twig_Grammar_%s', ucfirst(isset($match[2]) ? $match[2] : 'Expression'));
if (!class_exists($class)) {
throw new Twig_Error_Runtime(sprintf('Unable to understand "%s" in grammar (%s class does not exist)', $match[0], $class));
}
$grammar->addGrammar(new $class($match[1]));
$cursor += strlen($match[0]);
} elseif (preg_match('/(\w+|,)/A', $str, $match, null, $cursor)) {
$grammar->addGrammar(new Twig_Grammar_Constant($match[1]));
$cursor += strlen($match[0]);
} elseif (preg_match('/\[/A', $str, $match, null, $cursor)) {
$cursor += strlen($match[0]);
$grammar->addGrammar(self::parseGrammar($str, false));
} elseif (true !== $main && preg_match('/\]/A', $str, $match, null, $cursor)) {
$cursor += strlen($match[0]);
return $grammar;
} else {
throw new Twig_Error_Runtime(sprintf('Unable to parse grammar "%s" near "...%s..."', $str, substr($str, $cursor, 10)));
}
}
return $grammar;
}
}

View File

@@ -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.
*/
/**
* Exception thrown when a syntax error occurs during lexing or parsing of a template.
*
* @package twig
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
* @version SVN: $Id$
*/
class Twig_SyntaxError extends Twig_Error
{
protected $lineno;
protected $filename;
protected $rawMessage;
public function __construct($message, $lineno, $filename = null)
{
$this->lineno = $lineno;
$this->filename = $filename;
$this->rawMessage = $message;
$this->updateRepr();
parent::__construct($this->message, $lineno);
}
public function getFilename()
{
return $this->filename;
}
public function setFilename($filename)
{
$this->filename = $filename;
$this->updateRepr();
}
protected function updateRepr()
{
$this->message = $this->rawMessage.' in '.($this->filename ? $this->filename : 'n/a').' at line '.$this->lineno;
}
}

173
lib/classes/Twig/Template.php Executable file
View File

@@ -0,0 +1,173 @@
<?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_Template implements Twig_TemplateInterface
{
static protected $cache = array();
protected $env;
protected $blocks;
public function __construct(Twig_Environment $env)
{
$this->env = $env;
$this->blocks = array();
}
public function getEnvironment()
{
return $this->env;
}
public function getParent(array $context)
{
return false;
}
public function getParentBlock($name, array $context, array $blocks = array())
{
if (false !== $parent = $this->getParent($context)) {
return $parent->getBlock($name, $context, $blocks);
} else {
throw new Twig_Error_Runtime('This template has no parent.');
}
}
public function getBlock($name, array $context, array $blocks = array())
{
if (isset($blocks[$name])) {
$b = $blocks;
unset($b[$name]);
return call_user_func($blocks[$name], $context, $b);
} elseif (isset($this->blocks[$name])) {
return call_user_func($this->blocks[$name], $context, $blocks);
}
if (false !== $parent = $this->getParent($context)) {
return $parent->getBlock($name, $context, array_merge($this->blocks, $blocks));
}
}
public function hasBlock($name)
{
return isset($this->blocks[$name]);
}
public function getBlockNames()
{
return array_keys($this->blocks);
}
/**
* Renders the template with the given context and returns it as string.
*
* @param array $context An array of parameters to pass to the template
*
* @return string The rendered template
*/
public function render(array $context)
{
ob_start();
try {
$this->display($context);
} catch (Exception $e) {
ob_end_clean();
throw $e;
}
return ob_get_clean();
}
protected function getContext($context, $item)
{
if (!array_key_exists($item, $context)) {
throw new Twig_Error_Runtime(sprintf('Variable "%s" does not exist.', $item));
}
return $context[$item];
}
protected function getAttribute($object, $item, array $arguments = array(), $type = Twig_Node_Expression_GetAttr::TYPE_ANY)
{
// array
if (Twig_Node_Expression_GetAttr::TYPE_METHOD !== $type) {
if ((is_array($object) || is_object($object) && $object instanceof ArrayAccess) && isset($object[$item])) {
return $object[$item];
}
if (Twig_Node_Expression_GetAttr::TYPE_ARRAY === $type) {
if (!$this->env->isStrictVariables()) {
return null;
}
throw new Twig_Error_Runtime(sprintf('Key "%s" for array "%s" does not exist.', $item, $object));
}
}
if (!is_object($object)) {
if (!$this->env->isStrictVariables()) {
return null;
}
throw new Twig_Error_Runtime(sprintf('Item "%s" for "%s" does not exist.', $item, $object));
}
// get some information about the object
$class = get_class($object);
if (!isset(self::$cache[$class])) {
$r = new ReflectionClass($class);
self::$cache[$class] = array('methods' => array(), 'properties' => array());
foreach ($r->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
self::$cache[$class]['methods'][strtolower($method->getName())] = true;
}
foreach ($r->getProperties(ReflectionProperty::IS_PUBLIC) as $property) {
self::$cache[$class]['properties'][strtolower($property->getName())] = true;
}
}
// object property
if (Twig_Node_Expression_GetAttr::TYPE_METHOD !== $type) {
if (isset(self::$cache[$class]['properties'][strtolower($item)]) || isset($object->$item)) {
if ($this->env->hasExtension('sandbox')) {
$this->env->getExtension('sandbox')->checkPropertyAllowed($object, $item);
}
return $object->$item;
}
}
// object method
$lcItem = strtolower($item);
if (isset(self::$cache[$class]['methods'][$lcItem])) {
$method = $item;
} elseif (isset(self::$cache[$class]['methods']['get'.$lcItem])) {
$method = 'get'.$item;
} elseif (isset(self::$cache[$class]['methods']['is'.$lcItem])) {
$method = 'is'.$item;
} elseif (isset(self::$cache[$class]['methods']['__call'])) {
$method = $item;
} else {
if (!$this->env->isStrictVariables()) {
return null;
}
throw new Twig_Error_Runtime(sprintf('Method "%s" for object "%s" does not exist.', $item, get_class($object)));
}
if ($this->env->hasExtension('sandbox')) {
$this->env->getExtension('sandbox')->checkMethodAllowed($object, $method);
}
return call_user_func_array(array($object, $method), $arguments);
}
}

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