From 488198cfa3b6f66f126ed73ce678f79f1e1c808d Mon Sep 17 00:00:00 2001 From: Florens Verschelde Date: Tue, 6 Dec 2016 10:02:26 +0100 Subject: [PATCH] Update Twig library to 1.28.2 --- lib/Twig/CHANGELOG | 43 ++++ lib/Twig/lib/Twig/BaseNodeVisitor.php | 6 - lib/Twig/lib/Twig/Compiler.php | 48 ++-- lib/Twig/lib/Twig/CompilerInterface.php | 4 +- lib/Twig/lib/Twig/Environment.php | 228 ++++++++++-------- lib/Twig/lib/Twig/Error.php | 67 +++-- lib/Twig/lib/Twig/ExpressionParser.php | 152 +++++++++--- lib/Twig/lib/Twig/Extension/Core.php | 145 ++++------- lib/Twig/lib/Twig/Extension/Escaper.php | 16 +- lib/Twig/lib/Twig/Extension/StringLoader.php | 2 +- lib/Twig/lib/Twig/ExtensionInterface.php | 4 +- .../Twig/FileExtensionEscapingStrategy.php | 14 +- lib/Twig/lib/Twig/Lexer.php | 16 +- lib/Twig/lib/Twig/LexerInterface.php | 8 +- lib/Twig/lib/Twig/Loader/Array.php | 21 +- lib/Twig/lib/Twig/Loader/Chain.php | 44 +++- lib/Twig/lib/Twig/Loader/Filesystem.php | 83 +++++-- lib/Twig/lib/Twig/Loader/String.php | 12 +- lib/Twig/lib/Twig/LoaderInterface.php | 2 + lib/Twig/lib/Twig/Node.php | 76 +++--- lib/Twig/lib/Twig/Node/CheckSecurity.php | 4 +- lib/Twig/lib/Twig/Node/Embed.php | 12 +- lib/Twig/lib/Twig/Node/Expression/Array.php | 4 +- .../lib/Twig/Node/Expression/Binary/Power.php | 4 + .../Twig/Node/Expression/BlockReference.php | 73 ++++-- lib/Twig/lib/Twig/Node/Expression/Call.php | 21 +- .../Twig/Node/Expression/Filter/Default.php | 8 +- .../lib/Twig/Node/Expression/Function.php | 9 +- lib/Twig/lib/Twig/Node/Expression/Name.php | 18 +- .../lib/Twig/Node/Expression/NullCoalesce.php | 29 ++- .../lib/Twig/Node/Expression/Test/Defined.php | 9 +- lib/Twig/lib/Twig/Node/Import.php | 4 +- lib/Twig/lib/Twig/Node/Include.php | 4 +- lib/Twig/lib/Twig/Node/Macro.php | 6 +- lib/Twig/lib/Twig/Node/Module.php | 66 +++-- lib/Twig/lib/Twig/Node/SandboxedPrint.php | 4 +- lib/Twig/lib/Twig/Node/Set.php | 2 +- lib/Twig/lib/Twig/Node/With.php | 62 +++++ lib/Twig/lib/Twig/NodeInterface.php | 5 +- lib/Twig/lib/Twig/NodeTraverser.php | 13 +- lib/Twig/lib/Twig/NodeVisitor/Escaper.php | 6 +- lib/Twig/lib/Twig/NodeVisitor/Optimizer.php | 27 +-- lib/Twig/lib/Twig/NodeVisitor/Sandbox.php | 2 +- lib/Twig/lib/Twig/NodeVisitorInterface.php | 6 - lib/Twig/lib/Twig/Parser.php | 64 ++--- lib/Twig/lib/Twig/ParserInterface.php | 4 +- .../Twig/Profiler/NodeVisitor/Profiler.php | 2 +- .../Sandbox/SecurityNotAllowedMethodError.php | 38 +++ .../SecurityNotAllowedPropertyError.php | 38 +++ lib/Twig/lib/Twig/Sandbox/SecurityPolicy.php | 6 +- lib/Twig/lib/Twig/Source.php | 49 ++++ .../lib/Twig/SourceContextLoaderInterface.php | 31 +++ lib/Twig/lib/Twig/Template.php | 199 ++++++++++----- lib/Twig/lib/Twig/TemplateInterface.php | 2 +- lib/Twig/lib/Twig/TemplateWrapper.php | 131 ++++++++++ .../lib/Twig/Test/IntegrationTestCase.php | 13 +- lib/Twig/lib/Twig/Test/NodeTestCase.php | 4 + lib/Twig/lib/Twig/Token.php | 27 +-- lib/Twig/lib/Twig/TokenParser.php | 2 - lib/Twig/lib/Twig/TokenParser/AutoEscape.php | 4 +- lib/Twig/lib/Twig/TokenParser/Block.php | 4 +- lib/Twig/lib/Twig/TokenParser/Embed.php | 2 +- lib/Twig/lib/Twig/TokenParser/Extends.php | 8 +- lib/Twig/lib/Twig/TokenParser/Filter.php | 2 +- lib/Twig/lib/Twig/TokenParser/For.php | 10 +- lib/Twig/lib/Twig/TokenParser/From.php | 2 +- lib/Twig/lib/Twig/TokenParser/If.php | 2 +- lib/Twig/lib/Twig/TokenParser/Macro.php | 2 +- lib/Twig/lib/Twig/TokenParser/Sandbox.php | 7 +- lib/Twig/lib/Twig/TokenParser/Set.php | 4 +- lib/Twig/lib/Twig/TokenParser/Use.php | 2 +- lib/Twig/lib/Twig/TokenParser/With.php | 48 ++++ lib/Twig/lib/Twig/TokenParserBroker.php | 22 -- .../lib/Twig/TokenParserBrokerInterface.php | 4 +- lib/Twig/lib/Twig/TokenParserInterface.php | 6 +- lib/Twig/lib/Twig/TokenStream.php | 63 +++-- .../lib/Twig/Util/DeprecationCollector.php | 4 +- package.json | 2 +- 78 files changed, 1504 insertions(+), 693 deletions(-) create mode 100644 lib/Twig/lib/Twig/Node/With.php create mode 100644 lib/Twig/lib/Twig/Sandbox/SecurityNotAllowedMethodError.php create mode 100644 lib/Twig/lib/Twig/Sandbox/SecurityNotAllowedPropertyError.php create mode 100644 lib/Twig/lib/Twig/Source.php create mode 100644 lib/Twig/lib/Twig/SourceContextLoaderInterface.php create mode 100644 lib/Twig/lib/Twig/TemplateWrapper.php create mode 100644 lib/Twig/lib/Twig/TokenParser/With.php diff --git a/lib/Twig/CHANGELOG b/lib/Twig/CHANGELOG index ec13f63..fab1bfb 100644 --- a/lib/Twig/CHANGELOG +++ b/lib/Twig/CHANGELOG @@ -1,3 +1,46 @@ +* 1.28.2 (2016-11-23) + + * fixed precedence between getFoo() and isFoo() in Twig_Template::getAttribute() + * improved a deprecation message + +* 1.28.1 (2016-11-18) + + * fixed block() function when used with a template argument + +* 1.28.0 (2016-11-17) + + * added support for the PHP 7 null coalescing operator for the ?? Twig implementation + * exposed a way to access template data and methods in a portable way + * changed context access to use the PHP 7 null coalescing operator when available + * added the "with" tag + * added support for a custom template on the block() function + * added "is defined" support for block() and constant() + * optimized the way attributes are fetched + +* 1.27.0 (2016-10-25) + + * deprecated Twig_Parser::getEnvironment() + * deprecated Twig_Parser::addHandler() and Twig_Parser::addNodeVisitor() + * deprecated Twig_Compiler::addIndentation() + * fixed regression when registering two extensions having the same class name + * deprecated Twig_LoaderInterface::getSource() (implement Twig_SourceContextLoaderInterface instead) + * fixed the filesystem loader with relative paths + * deprecated Twig_Node::getLine() in favor of Twig_Node::getTemplateLine() + * deprecated Twig_Template::getSource() in favor of Twig_Template::getSourceContext() + * deprecated Twig_Node::getFilename() in favor of Twig_Node::getTemplateName() + * deprecated the "filename" escaping strategy (use "name" instead) + * added Twig_Source to hold information about the original template + * deprecated Twig_Error::getTemplateFile() and Twig_Error::setTemplateFile() in favor of Twig_Error::getTemplateName() and Twig_Error::setTemplateName() + * deprecated Parser::getFilename() + * fixed template paths when a template name contains a protocol like vfs:// + * improved debugging with Twig_Sandbox_SecurityError exceptions for disallowed methods and properties + +* 1.26.1 (2016-10-05) + + * removed template source code from generated template classes when debug is disabled + * fixed default implementation of Twig_Template::getDebugInfo() for better BC + * fixed regression on static calls for functions/filters/tests + * 1.26.0 (2016-10-02) * added template cache invalidation based on more environment options diff --git a/lib/Twig/lib/Twig/BaseNodeVisitor.php b/lib/Twig/lib/Twig/BaseNodeVisitor.php index 3c6ef66..c2e7b7e 100644 --- a/lib/Twig/lib/Twig/BaseNodeVisitor.php +++ b/lib/Twig/lib/Twig/BaseNodeVisitor.php @@ -43,9 +43,6 @@ final public function leaveNode(Twig_NodeInterface $node, Twig_Environment $env) /** * Called before child nodes are visited. * - * @param Twig_Node $node The node to visit - * @param Twig_Environment $env The Twig environment instance - * * @return Twig_Node The modified node */ abstract protected function doEnterNode(Twig_Node $node, Twig_Environment $env); @@ -53,9 +50,6 @@ abstract protected function doEnterNode(Twig_Node $node, Twig_Environment $env); /** * Called after child nodes are visited. * - * @param Twig_Node $node The node to visit - * @param Twig_Environment $env The Twig environment instance - * * @return Twig_Node|false The modified node or false if the node must be removed */ abstract protected function doLeaveNode(Twig_Node $node, Twig_Environment $env); diff --git a/lib/Twig/lib/Twig/Compiler.php b/lib/Twig/lib/Twig/Compiler.php index 16894b5..5e96ac3 100644 --- a/lib/Twig/lib/Twig/Compiler.php +++ b/lib/Twig/lib/Twig/Compiler.php @@ -26,11 +26,6 @@ class Twig_Compiler implements Twig_CompilerInterface protected $sourceLine; protected $filename; - /** - * Constructor. - * - * @param Twig_Environment $env The twig environment instance - */ public function __construct(Twig_Environment $env) { $this->env = $env; @@ -49,7 +44,7 @@ public function getFilename() /** * Returns the environment instance related to this compiler. * - * @return Twig_Environment The environment instance + * @return Twig_Environment */ public function getEnvironment() { @@ -72,7 +67,7 @@ public function getSource() * @param Twig_NodeInterface $node The node to compile * @param int $indentation The current indentation * - * @return Twig_Compiler The current compiler instance + * @return $this */ public function compile(Twig_NodeInterface $node, $indentation = 0) { @@ -85,10 +80,8 @@ public function compile(Twig_NodeInterface $node, $indentation = 0) $this->indentation = $indentation; if ($node instanceof Twig_Node_Module) { - $node->setFilename($node->getAttribute('filename')); - // to be removed in 2.0 - $this->filename = $node->getAttribute('filename'); + $this->filename = $node->getTemplateName(); } $node->compile($this); @@ -99,7 +92,7 @@ public function compile(Twig_NodeInterface $node, $indentation = 0) public function subcompile(Twig_NodeInterface $node, $raw = true) { if (false === $raw) { - $this->addIndentation(); + $this->source .= str_repeat(' ', $this->indentation * 4); } $node->compile($this); @@ -112,7 +105,7 @@ public function subcompile(Twig_NodeInterface $node, $raw = true) * * @param string $string The string * - * @return Twig_Compiler The current compiler instance + * @return $this */ public function raw($string) { @@ -124,14 +117,13 @@ public function raw($string) /** * Writes a string to the compiled code by adding indentation. * - * @return Twig_Compiler The current compiler instance + * @return $this */ public function write() { $strings = func_get_args(); foreach ($strings as $string) { - $this->addIndentation(); - $this->source .= $string; + $this->source .= str_repeat(' ', $this->indentation * 4).$string; } return $this; @@ -140,10 +132,14 @@ public function write() /** * Appends an indentation to the current PHP code after compilation. * - * @return Twig_Compiler The current compiler instance + * @return $this + * + * @deprecated since 1.27 (to be removed in 2.0). */ public function addIndentation() { + @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use write(\'\') instead.', E_USER_DEPRECATED); + $this->source .= str_repeat(' ', $this->indentation * 4); return $this; @@ -154,7 +150,7 @@ public function addIndentation() * * @param string $value The string * - * @return Twig_Compiler The current compiler instance + * @return $this */ public function string($value) { @@ -168,7 +164,7 @@ public function string($value) * * @param mixed $value The value to convert * - * @return Twig_Compiler The current compiler instance + * @return $this */ public function repr($value) { @@ -209,14 +205,12 @@ public function repr($value) /** * Adds debugging information. * - * @param Twig_NodeInterface $node The related twig node - * - * @return Twig_Compiler The current compiler instance + * @return $this */ public function addDebugInfo(Twig_NodeInterface $node) { - if ($node->getLine() != $this->lastLine) { - $this->write(sprintf("// line %d\n", $node->getLine())); + if ($node->getTemplateLine() != $this->lastLine) { + $this->write(sprintf("// line %d\n", $node->getTemplateLine())); // when mbstring.func_overload is set to 2 // mb_substr_count() replaces substr_count() @@ -228,9 +222,9 @@ public function addDebugInfo(Twig_NodeInterface $node) $this->sourceLine += substr_count($this->source, "\n", $this->sourceOffset); } $this->sourceOffset = strlen($this->source); - $this->debugInfo[$this->sourceLine] = $node->getLine(); + $this->debugInfo[$this->sourceLine] = $node->getTemplateLine(); - $this->lastLine = $node->getLine(); + $this->lastLine = $node->getTemplateLine(); } return $this; @@ -248,7 +242,7 @@ public function getDebugInfo() * * @param int $step The number of indentation to add * - * @return Twig_Compiler The current compiler instance + * @return $this */ public function indent($step = 1) { @@ -262,7 +256,7 @@ public function indent($step = 1) * * @param int $step The number of indentation to remove * - * @return Twig_Compiler The current compiler instance + * @return $this * * @throws LogicException When trying to outdent too much so the indentation would become negative */ diff --git a/lib/Twig/lib/Twig/CompilerInterface.php b/lib/Twig/lib/Twig/CompilerInterface.php index 272c767..70350ca 100644 --- a/lib/Twig/lib/Twig/CompilerInterface.php +++ b/lib/Twig/lib/Twig/CompilerInterface.php @@ -21,9 +21,7 @@ interface Twig_CompilerInterface /** * Compiles a node. * - * @param Twig_NodeInterface $node The node to compile - * - * @return Twig_CompilerInterface The current compiler instance + * @return $this */ public function compile(Twig_NodeInterface $node); diff --git a/lib/Twig/lib/Twig/Environment.php b/lib/Twig/lib/Twig/Environment.php index a09e393..7153790 100644 --- a/lib/Twig/lib/Twig/Environment.php +++ b/lib/Twig/lib/Twig/Environment.php @@ -16,7 +16,12 @@ */ class Twig_Environment { - const VERSION = '1.26.0'; + const VERSION = '1.28.2'; + const VERSION_ID = 12802; + const MAJOR_VERSION = 1; + const MINOR_VERSION = 28; + const RELEASE_VERSION = 2; + const EXTRA_VERSION = ''; protected $charset; protected $loader; @@ -49,7 +54,7 @@ class Twig_Environment private $bcWriteCacheFile = false; private $bcGetCacheFilename = false; private $lastModifiedExtension = 0; - private $legacyExtensionNames = array(); + private $extensionsByClass = array(); private $runtimeLoaders = array(); private $runtimes = array(); private $optionsHash; @@ -82,14 +87,14 @@ class Twig_Environment * * false: disable auto-escaping * * true: equivalent to html * * html, js: set the autoescaping to one of the supported strategies - * * filename: set the autoescaping strategy based on the template filename extension - * * PHP callback: a PHP callback that returns an escaping strategy based on the template "filename" + * * name: set the autoescaping strategy based on the template name extension + * * PHP callback: a PHP callback that returns an escaping strategy based on the template "name" * * * optimizations: A flag that indicates which optimizations to apply * (default to -1 which means that all optimizations are enabled; * set it to 0 to disable). * - * @param Twig_LoaderInterface $loader A Twig_LoaderInterface instance + * @param Twig_LoaderInterface $loader * @param array $options An array of options */ public function __construct(Twig_LoaderInterface $loader = null, $options = array()) @@ -373,7 +378,30 @@ public function display($name, array $context = array()) } /** - * Loads a template by name. + * Loads a template. + * + * @param string|Twig_TemplateWrapper|Twig_Template $name The template name + * + * @return Twig_TemplateWrapper + */ + public function load($name) + { + if ($name instanceof Twig_TemplateWrapper) { + return $name; + } + + if ($name instanceof Twig_Template) { + return new Twig_TemplateWrapper($this, $name); + } + + return new Twig_TemplateWrapper($this, $this->loadTemplate($name)); + } + + /** + * Loads a template internal representation. + * + * This method is for internal use only and should never be called + * directly. * * @param string $name The template name * @param int $index The index if it is an embedded template @@ -382,6 +410,8 @@ public function display($name, array $context = array()) * * @throws Twig_Error_Loader When the template cannot be found * @throws Twig_Error_Syntax When an error occurred during compilation + * + * @internal */ public function loadTemplate($name, $index = null) { @@ -403,14 +433,30 @@ public function loadTemplate($name, $index = null) } if (!class_exists($cls, false)) { - $content = $this->compileSource($this->getLoader()->getSource($name), $name); + $loader = $this->getLoader(); + if (!$loader instanceof Twig_SourceContextLoaderInterface) { + $source = new Twig_Source($loader->getSource($name), $name); + } else { + $source = $loader->getSourceContext($name); + } + + $content = $this->compileSource($source); + if ($this->bcWriteCacheFile) { $this->writeCacheFile($key, $content); } else { $this->cache->write($key, $content); + $this->cache->load($key); } - eval('?>'.$content); + if (!class_exists($cls, false)) { + /* Last line of defense if either $this->bcWriteCacheFile was used, + * $this->cache is implemented as a no-op or we have a race condition + * where the cache was cleared between the above calls to write to and load from + * the cache. + */ + eval('?>'.$content); + } } } @@ -555,7 +601,7 @@ public function clearCacheFiles() /** * Gets the Lexer instance. * - * @return Twig_LexerInterface A Twig_LexerInterface instance + * @return Twig_LexerInterface * * @deprecated since 1.25 (to be removed in 2.0) */ @@ -570,11 +616,6 @@ public function getLexer() return $this->lexer; } - /** - * Sets the Lexer instance. - * - * @param Twig_LexerInterface $lexer A Twig_LexerInterface instance - */ public function setLexer(Twig_LexerInterface $lexer) { $this->lexer = $lexer; @@ -583,26 +624,31 @@ public function setLexer(Twig_LexerInterface $lexer) /** * Tokenizes a source code. * - * @param string $source The template source code - * @param string $name The template name + * @param string|Twig_Source $source The template source code + * @param string $name The template name (deprecated) * - * @return Twig_TokenStream A Twig_TokenStream instance + * @return Twig_TokenStream * * @throws Twig_Error_Syntax When the code is syntactically wrong */ public function tokenize($source, $name = null) { + if (!$source instanceof Twig_Source) { + @trigger_error(sprintf('Passing a string as the $source argument of %s() is deprecated since version 1.27. Pass a Twig_Source instance instead.', __METHOD__), E_USER_DEPRECATED); + $source = new Twig_Source($source, $name); + } + if (null === $this->lexer) { $this->lexer = new Twig_Lexer($this); } - return $this->lexer->tokenize($source, $name); + return $this->lexer->tokenize($source); } /** * Gets the Parser instance. * - * @return Twig_ParserInterface A Twig_ParserInterface instance + * @return Twig_ParserInterface * * @deprecated since 1.25 (to be removed in 2.0) */ @@ -617,11 +663,6 @@ public function getParser() return $this->parser; } - /** - * Sets the Parser instance. - * - * @param Twig_ParserInterface $parser A Twig_ParserInterface instance - */ public function setParser(Twig_ParserInterface $parser) { $this->parser = $parser; @@ -630,9 +671,7 @@ public function setParser(Twig_ParserInterface $parser) /** * Converts a token stream to a node tree. * - * @param Twig_TokenStream $stream A token stream instance - * - * @return Twig_Node_Module A node tree + * @return Twig_Node_Module * * @throws Twig_Error_Syntax When the token stream is syntactically or semantically wrong */ @@ -648,7 +687,7 @@ public function parse(Twig_TokenStream $stream) /** * Gets the Compiler instance. * - * @return Twig_CompilerInterface A Twig_CompilerInterface instance + * @return Twig_CompilerInterface * * @deprecated since 1.25 (to be removed in 2.0) */ @@ -663,11 +702,6 @@ public function getCompiler() return $this->compiler; } - /** - * Sets the Compiler instance. - * - * @param Twig_CompilerInterface $compiler A Twig_CompilerInterface instance - */ public function setCompiler(Twig_CompilerInterface $compiler) { $this->compiler = $compiler; @@ -676,8 +710,6 @@ public function setCompiler(Twig_CompilerInterface $compiler) /** * Compiles a node and returns the PHP code. * - * @param Twig_NodeInterface $node A Twig_NodeInterface instance - * * @return string The compiled PHP source code */ public function compile(Twig_NodeInterface $node) @@ -692,8 +724,8 @@ public function compile(Twig_NodeInterface $node) /** * Compiles a template source code. * - * @param string $source The template source code - * @param string $name The template name + * @param string|Twig_Source $source The template source code + * @param string $name The template name (deprecated) * * @return string The compiled PHP source code * @@ -701,30 +733,34 @@ public function compile(Twig_NodeInterface $node) */ public function compileSource($source, $name = null) { + if (!$source instanceof Twig_Source) { + @trigger_error(sprintf('Passing a string as the $source argument of %s() is deprecated since version 1.27. Pass a Twig_Source instance instead.', __METHOD__), E_USER_DEPRECATED); + $source = new Twig_Source($source, $name); + } + try { - return $this->compile($this->parse($this->tokenize($source, $name))); + return $this->compile($this->parse($this->tokenize($source))); } catch (Twig_Error $e) { - $e->setTemplateFile($name); + $e->setTemplateName($source->getName()); throw $e; } catch (Exception $e) { - throw new Twig_Error_Syntax(sprintf('An exception has been thrown during the compilation of a template ("%s").', $e->getMessage()), -1, $name, $e); + throw new Twig_Error_Syntax(sprintf('An exception has been thrown during the compilation of a template ("%s").', $e->getMessage()), -1, $source->getName(), $e); } } - /** - * Sets the Loader instance. - * - * @param Twig_LoaderInterface $loader A Twig_LoaderInterface instance - */ public function setLoader(Twig_LoaderInterface $loader) { + if (!$loader instanceof Twig_SourceContextLoaderInterface && 0 !== strpos(get_class($loader), 'Mock_Twig_LoaderInterface')) { + @trigger_error(sprintf('Twig loader "%s" should implement Twig_SourceContextLoaderInterface since version 1.27.', get_class($loader)), E_USER_DEPRECATED); + } + $this->loader = $loader; } /** * Gets the Loader instance. * - * @return Twig_LoaderInterface A Twig_LoaderInterface instance + * @return Twig_LoaderInterface */ public function getLoader() { @@ -786,12 +822,16 @@ public function initRuntime() */ public function hasExtension($class) { - if (isset($this->legacyExtensionNames[$class])) { - $class = $this->legacyExtensionNames[$class]; - @trigger_error(sprintf('Referencing the "%s" extension by its name (defined by getName()) is deprecated since 1.26 and will be removed in Twig 2.0. Use the Fully Qualified Extension Class Name instead.', $class), E_USER_DEPRECATED); + $class = ltrim($class, '\\'); + if (isset($this->extensions[$class])) { + if ($class !== get_class($this->extensions[$class])) { + @trigger_error(sprintf('Referencing the "%s" extension by its name (defined by getName()) is deprecated since 1.26 and will be removed in Twig 2.0. Use the Fully Qualified Extension Class Name instead.', $class), E_USER_DEPRECATED); + } + + return true; } - return isset($this->extensions[ltrim($class, '\\')]); + return isset($this->extensionsByClass[ltrim($class, '\\')]); } /** @@ -807,22 +847,25 @@ public function addRuntimeLoader(Twig_RuntimeLoaderInterface $loader) * * @param string $class The extension class name * - * @return Twig_ExtensionInterface A Twig_ExtensionInterface instance + * @return Twig_ExtensionInterface */ public function getExtension($class) { - if (isset($this->legacyExtensionNames[$class])) { - $class = $this->legacyExtensionNames[$class]; - @trigger_error(sprintf('Referencing the "%s" extension by its name (defined by getName()) is deprecated since 1.26 and will be removed in Twig 2.0. Use the Fully Qualified Extension Class Name instead.', $class), E_USER_DEPRECATED); - } - $class = ltrim($class, '\\'); - if (!isset($this->extensions[$class])) { + if (isset($this->extensions[$class])) { + if ($class !== get_class($this->extensions[$class])) { + @trigger_error(sprintf('Referencing the "%s" extension by its name (defined by getName()) is deprecated since 1.26 and will be removed in Twig 2.0. Use the Fully Qualified Extension Class Name instead.', $class), E_USER_DEPRECATED); + } + + return $this->extensions[$class]; + } + + if (!isset($this->extensionsByClass[$class])) { throw new Twig_Error_Runtime(sprintf('The "%s" extension is not enabled.', $class)); } - return $this->extensions[$class]; + return $this->extensionsByClass[$class]; } /** @@ -849,32 +892,23 @@ public function getRuntime($class) throw new Twig_Error_Runtime(sprintf('Unable to load the "%s" runtime.', $class)); } - /** - * Registers an extension. - * - * @param Twig_ExtensionInterface $extension A Twig_ExtensionInterface instance - */ public function addExtension(Twig_ExtensionInterface $extension) { - $class = get_class($extension); - if ($this->extensionInitialized) { - throw new LogicException(sprintf('Unable to register extension "%s" as extensions have already been initialized.', $class)); + throw new LogicException(sprintf('Unable to register extension "%s" as extensions have already been initialized.', $extension->getName())); } - $m = new ReflectionMethod($extension, 'getName'); - $legacyName = 'Twig_Extension' !== $m->getDeclaringClass()->getName() ? $extension->getName() : null; - - if (isset($this->extensions[$class]) || (null !== $legacyName && isset($this->legacyExtensionNames[$legacyName]))) { - unset($this->extensions[$this->legacyExtensionNames[$legacyName]], $this->legacyExtensionNames[$legacyName]); - @trigger_error(sprintf('The possibility to register the same extension twice ("%s") is deprecated since version 1.23 and will be removed in Twig 2.0. Use proper PHP inheritance instead.', $class), E_USER_DEPRECATED); + $class = get_class($extension); + if ($class !== $extension->getName()) { + if (isset($this->extensions[$extension->getName()])) { + unset($this->extensions[$extension->getName()], $this->extensionsByClass[$class]); + @trigger_error(sprintf('The possibility to register the same extension twice ("%s") is deprecated since version 1.23 and will be removed in Twig 2.0. Use proper PHP inheritance instead.', $extension->getName()), E_USER_DEPRECATED); + } } $this->lastModifiedExtension = 0; - if ($legacyName !== $class) { - $this->legacyExtensionNames[$legacyName] = $class; - } - $this->extensions[$class] = $extension; + $this->extensionsByClass[$class] = $extension; + $this->extensions[$extension->getName()] = $extension; $this->updateOptionsHash(); } @@ -891,16 +925,20 @@ public function removeExtension($name) { @trigger_error(sprintf('The %s method is deprecated since version 1.12 and will be removed in Twig 2.0.', __METHOD__), E_USER_DEPRECATED); - if (isset($this->legacyExtensionNames[$name])) { - $name = $this->legacyExtensionNames[$name]; - @trigger_error(sprintf('Referencing the "%s" extension by its name (defined by getName()) is deprecated since 1.26 and will be removed in Twig 2.0. Use the Fully Qualified Extension Class Name instead.', $name), E_USER_DEPRECATED); - } - if ($this->extensionInitialized) { throw new LogicException(sprintf('Unable to remove extension "%s" as extensions have already been initialized.', $name)); } - unset($this->extensions[ltrim($name, '\\')]); + $class = ltrim($name, '\\'); + if (isset($this->extensions[$class])) { + if ($class !== get_class($this->extensions[$class])) { + @trigger_error(sprintf('Referencing the "%s" extension by its name (defined by getName()) is deprecated since 1.26 and will be removed in Twig 2.0. Use the Fully Qualified Extension Class Name instead.', $class), E_USER_DEPRECATED); + } + + unset($this->extensions[$class]); + } + + unset($this->extensions[$class]); $this->updateOptionsHash(); } @@ -926,11 +964,6 @@ public function getExtensions() return $this->extensions; } - /** - * Registers a Token Parser. - * - * @param Twig_TokenParserInterface $parser A Twig_TokenParserInterface instance - */ public function addTokenParser(Twig_TokenParserInterface $parser) { if ($this->extensionInitialized) { @@ -943,7 +976,7 @@ public function addTokenParser(Twig_TokenParserInterface $parser) /** * Gets the registered Token Parsers. * - * @return Twig_TokenParserBrokerInterface A broker containing token parsers + * @return Twig_TokenParserBrokerInterface * * @internal */ @@ -961,7 +994,7 @@ public function getTokenParsers() * * Be warned that this method cannot return tags defined by Twig_TokenParserBrokerInterface classes. * - * @return Twig_TokenParserInterface[] An array of Twig_TokenParserInterface instances + * @return Twig_TokenParserInterface[] * * @internal */ @@ -977,11 +1010,6 @@ public function getTags() return $tags; } - /** - * Registers a Node Visitor. - * - * @param Twig_NodeVisitorInterface $visitor A Twig_NodeVisitorInterface instance - */ public function addNodeVisitor(Twig_NodeVisitorInterface $visitor) { if ($this->extensionInitialized) { @@ -994,7 +1022,7 @@ public function addNodeVisitor(Twig_NodeVisitorInterface $visitor) /** * Gets the registered Node Visitors. * - * @return Twig_NodeVisitorInterface[] An array of Twig_NodeVisitorInterface instances + * @return Twig_NodeVisitorInterface[] * * @internal */ @@ -1011,7 +1039,7 @@ public function getNodeVisitors() * Registers a Filter. * * @param string|Twig_SimpleFilter $name The filter name or a Twig_SimpleFilter instance - * @param Twig_FilterInterface|Twig_SimpleFilter $filter A Twig_FilterInterface instance or a Twig_SimpleFilter instance + * @param Twig_FilterInterface|Twig_SimpleFilter $filter */ public function addFilter($name, $filter = null) { @@ -1087,7 +1115,7 @@ public function registerUndefinedFilterCallback($callable) * * Be warned that this method cannot return filters defined with registerUndefinedFilterCallback. * - * @return Twig_FilterInterface[] An array of Twig_FilterInterface instances + * @return Twig_FilterInterface[] * * @see registerUndefinedFilterCallback * @@ -1131,7 +1159,7 @@ public function addTest($name, $test = null) /** * Gets the registered Tests. * - * @return Twig_TestInterface[] An array of Twig_TestInterface instances + * @return Twig_TestInterface[] * * @internal */ @@ -1170,7 +1198,7 @@ public function getTest($name) * Registers a Function. * * @param string|Twig_SimpleFunction $name The function name or a Twig_SimpleFunction instance - * @param Twig_FunctionInterface|Twig_SimpleFunction $function A Twig_FunctionInterface instance or a Twig_SimpleFunction instance + * @param Twig_FunctionInterface|Twig_SimpleFunction $function */ public function addFunction($name, $function = null) { @@ -1246,7 +1274,7 @@ public function registerUndefinedFunctionCallback($callable) * * Be warned that this method cannot return functions defined with registerUndefinedFunctionCallback. * - * @return Twig_FunctionInterface[] An array of Twig_FunctionInterface instances + * @return Twig_FunctionInterface[] * * @see registerUndefinedFunctionCallback * diff --git a/lib/Twig/lib/Twig/Error.php b/lib/Twig/lib/Twig/Error.php index 37c7435..cf373e6 100644 --- a/lib/Twig/lib/Twig/Error.php +++ b/lib/Twig/lib/Twig/Error.php @@ -25,8 +25,8 @@ * and line number) yourself by passing them to the constructor. If some or all * these information are not available from where you throw the exception, then * this class will guess them automatically (when the line number is set to -1 - * and/or the filename is set to null). As this is a costly operation, this - * can be disabled by passing false for both the filename and the line number + * and/or the name is set to null). As this is a costly operation, this + * can be disabled by passing false for both the name and the line number * when creating a new instance of this class. * * @author Fabien Potencier @@ -34,6 +34,7 @@ class Twig_Error extends Exception { protected $lineno; + // to be renamed to name in 2.0 protected $filename; protected $rawMessage; protected $previous; @@ -41,21 +42,21 @@ class Twig_Error extends Exception /** * Constructor. * - * Set both the line number and the filename to false to + * Set both the line number and the name to false to * disable automatic guessing of the original template name * and line number. * * Set the line number to -1 to enable its automatic guessing. - * Set the filename to null to enable its automatic guessing. + * Set the name to null to enable its automatic guessing. * * By default, automatic guessing is enabled. * * @param string $message The error message * @param int $lineno The template line where the error occurred - * @param string $filename The template file name where the error occurred + * @param string $name The template logical name where the error occurred * @param Exception $previous The previous exception */ - public function __construct($message, $lineno = -1, $filename = null, Exception $previous = null) + public function __construct($message, $lineno = -1, $name = null, Exception $previous = null) { if (PHP_VERSION_ID < 50300) { $this->previous = $previous; @@ -65,9 +66,9 @@ public function __construct($message, $lineno = -1, $filename = null, Exception } $this->lineno = $lineno; - $this->filename = $filename; + $this->filename = $name; - if (-1 === $this->lineno || null === $this->filename) { + if (-1 === $lineno || null === $name) { $this->guessTemplateInfo(); } @@ -87,23 +88,53 @@ public function getRawMessage() } /** - * Gets the filename where the error occurred. + * Gets the logical name where the error occurred. * - * @return string The filename + * @return string The name + * + * @deprecated since 1.27 (to be removed in 2.0). Use getTemplateName() instead. */ public function getTemplateFile() + { + @trigger_error(sprintf('The "%s" method is deprecated since version 1.27 and will be removed in 2.0. Use getTemplateName() instead.', __METHOD__), E_USER_DEPRECATED); + + return $this->filename; + } + + /** + * Sets the logical name where the error occurred. + * + * @param string $name The name + * + * @deprecated since 1.27 (to be removed in 2.0). Use setTemplateName() instead. + */ + public function setTemplateFile($name) + { + @trigger_error(sprintf('The "%s" method is deprecated since version 1.27 and will be removed in 2.0. Use setTemplateName() instead.', __METHOD__), E_USER_DEPRECATED); + + $this->filename = $name; + + $this->updateRepr(); + } + + /** + * Gets the logical name where the error occurred. + * + * @return string The name + */ + public function getTemplateName() { return $this->filename; } /** - * Sets the filename where the error occurred. + * Sets the logical name where the error occurred. * - * @param string $filename The filename + * @param string $name The name */ - public function setTemplateFile($filename) + public function setTemplateName($name) { - $this->filename = $filename; + $this->filename = $name; $this->updateRepr(); } @@ -182,11 +213,11 @@ protected function updateRepr() if ($this->filename) { if (is_string($this->filename) || (is_object($this->filename) && method_exists($this->filename, '__toString'))) { - $filename = sprintf('"%s"', $this->filename); + $name = sprintf('"%s"', $this->filename); } else { - $filename = json_encode($this->filename); + $name = json_encode($this->filename); } - $this->message .= sprintf(' in %s', $filename); + $this->message .= sprintf(' in %s', $name); } if ($this->lineno && $this->lineno >= 0) { @@ -227,7 +258,7 @@ protected function guessTemplateInfo() } } - // update template filename + // update template name if (null !== $template && null === $this->filename) { $this->filename = $template->getTemplateName(); } diff --git a/lib/Twig/lib/Twig/ExpressionParser.php b/lib/Twig/lib/Twig/ExpressionParser.php index d128e72..de2e56a 100644 --- a/lib/Twig/lib/Twig/ExpressionParser.php +++ b/lib/Twig/lib/Twig/ExpressionParser.php @@ -19,6 +19,8 @@ * @see http://en.wikipedia.org/wiki/Operator-precedence_parser * * @author Fabien Potencier + * + * @internal */ class Twig_ExpressionParser { @@ -29,11 +31,23 @@ class Twig_ExpressionParser protected $unaryOperators; protected $binaryOperators; - public function __construct(Twig_Parser $parser, array $unaryOperators, array $binaryOperators) + private $env; + + public function __construct(Twig_Parser $parser, Twig_Environment $env = null) { $this->parser = $parser; - $this->unaryOperators = $unaryOperators; - $this->binaryOperators = $binaryOperators; + + if ($env instanceof Twig_Environment) { + $this->env = $env; + $this->unaryOperators = $env->getUnaryOperators(); + $this->binaryOperators = $env->getBinaryOperators(); + } else { + @trigger_error('Passing the operators as constructor arguments to '.__METHOD__.' is deprecated since version 1.27. Pass the environment instead.', E_USER_DEPRECATED); + + $this->env = $parser->getEnvironment(); + $this->unaryOperators = func_get_arg(1); + $this->binaryOperators = func_get_arg(2); + } } public function parseExpression($precedence = 0) @@ -44,7 +58,11 @@ public function parseExpression($precedence = 0) $op = $this->binaryOperators[$token->getValue()]; $this->parser->getStream()->next(); - if (isset($op['callable'])) { + if ('is not' === $token->getValue()) { + $expr = $this->parseNotTestExpression($expr); + } elseif ('is' === $token->getValue()) { + $expr = $this->parseTestExpression($expr); + } elseif (isset($op['callable'])) { $expr = call_user_func($op['callable'], $this->parser, $expr); } else { $expr1 = $this->parseExpression(self::OPERATOR_LEFT === $op['associativity'] ? $op['precedence'] + 1 : $op['precedence']); @@ -171,7 +189,7 @@ public function parsePrimaryExpression() $negClass = 'Twig_Node_Expression_Unary_Neg'; $posClass = 'Twig_Node_Expression_Unary_Pos'; if (!(in_array($ref->getName(), array($negClass, $posClass)) || $ref->isSubclassOf($negClass) || $ref->isSubclassOf($posClass))) { - throw new Twig_Error_Syntax(sprintf('Unexpected unary operator "%s".', $token->getValue()), $token->getLine(), $this->parser->getFilename()); + throw new Twig_Error_Syntax(sprintf('Unexpected unary operator "%s".', $token->getValue()), $token->getLine(), $this->parser->getStream()->getSourceContext()->getName()); } $this->parser->getStream()->next(); @@ -187,7 +205,7 @@ public function parsePrimaryExpression() } elseif ($token->test(Twig_Token::PUNCTUATION_TYPE, '{')) { $node = $this->parseHashExpression(); } else { - throw new Twig_Error_Syntax(sprintf('Unexpected token "%s" of value "%s".', Twig_Token::typeToEnglish($token->getType()), $token->getValue()), $token->getLine(), $this->parser->getFilename()); + throw new Twig_Error_Syntax(sprintf('Unexpected token "%s" of value "%s".', Twig_Token::typeToEnglish($token->getType()), $token->getValue()), $token->getLine(), $this->parser->getStream()->getSourceContext()->getName()); } } @@ -216,7 +234,7 @@ public function parseStringExpression() $expr = array_shift($nodes); foreach ($nodes as $node) { - $expr = new Twig_Node_Expression_Binary_Concat($expr, $node, $node->getLine()); + $expr = new Twig_Node_Expression_Binary_Concat($expr, $node, $node->getTemplateLine()); } return $expr; @@ -278,7 +296,7 @@ public function parseHashExpression() } else { $current = $stream->getCurrent(); - throw new Twig_Error_Syntax(sprintf('A hash key must be a quoted string, a number, a name, or an expression enclosed in parentheses (unexpected token "%s" of value "%s".', Twig_Token::typeToEnglish($current->getType()), $current->getValue()), $current->getLine(), $this->parser->getFilename()); + throw new Twig_Error_Syntax(sprintf('A hash key must be a quoted string, a number, a name, or an expression enclosed in parentheses (unexpected token "%s" of value "%s".', Twig_Token::typeToEnglish($current->getType()), $current->getValue()), $current->getLine(), $stream->getSourceContext()->getName()); } $stream->expect(Twig_Token::PUNCTUATION_TYPE, ':', 'A hash key must be followed by a colon (:)'); @@ -317,20 +335,25 @@ public function getFunctionNode($name, $line) case 'parent': $this->parseArguments(); if (!count($this->parser->getBlockStack())) { - throw new Twig_Error_Syntax('Calling "parent" outside a block is forbidden.', $line, $this->parser->getFilename()); + throw new Twig_Error_Syntax('Calling "parent" outside a block is forbidden.', $line, $this->parser->getStream()->getSourceContext()->getName()); } if (!$this->parser->getParent() && !$this->parser->hasTraits()) { - throw new Twig_Error_Syntax('Calling "parent" on a template that does not extend nor "use" another template is forbidden.', $line, $this->parser->getFilename()); + throw new Twig_Error_Syntax('Calling "parent" on a template that does not extend nor "use" another template is forbidden.', $line, $this->parser->getStream()->getSourceContext()->getName()); } return new Twig_Node_Expression_Parent($this->parser->peekBlockStack(), $line); case 'block': - return new Twig_Node_Expression_BlockReference($this->parseArguments()->getNode(0), false, $line); + $args = $this->parseArguments(); + if (count($args) < 1) { + throw new Twig_Error_Syntax('The "block" function takes one argument (the block name).', $line, $this->parser->getStream()->getSourceContext()->getName()); + } + + return new Twig_Node_Expression_BlockReference($args->getNode(0), count($args) > 1 ? $args->getNode(1) : null, $line); case 'attribute': $args = $this->parseArguments(); if (count($args) < 2) { - throw new Twig_Error_Syntax('The "attribute" function takes at least two arguments (the variable and the attributes).', $line, $this->parser->getFilename()); + throw new Twig_Error_Syntax('The "attribute" function takes at least two arguments (the variable and the attributes).', $line, $this->parser->getStream()->getSourceContext()->getName()); } return new Twig_Node_Expression_GetAttr($args->getNode(0), $args->getNode(1), count($args) > 2 ? $args->getNode(2) : null, Twig_Template::ANY_CALL, $line); @@ -379,18 +402,18 @@ public function parseSubscriptExpression($node) } } } else { - throw new Twig_Error_Syntax('Expected name or number.', $lineno, $this->parser->getFilename()); + throw new Twig_Error_Syntax('Expected name or number.', $lineno, $stream->getSourceContext()->getName()); } if ($node instanceof Twig_Node_Expression_Name && null !== $this->parser->getImportedSymbol('template', $node->getAttribute('name'))) { if (!$arg instanceof Twig_Node_Expression_Constant) { - throw new Twig_Error_Syntax(sprintf('Dynamic macro names are not supported (called on "%s").', $node->getAttribute('name')), $token->getLine(), $this->parser->getFilename()); + throw new Twig_Error_Syntax(sprintf('Dynamic macro names are not supported (called on "%s").', $node->getAttribute('name')), $token->getLine(), $stream->getSourceContext()->getName()); } $name = $arg->getAttribute('value'); if ($this->parser->isReservedMacroName($name)) { - throw new Twig_Error_Syntax(sprintf('"%s" cannot be called as macro as it is a reserved keyword.', $name), $token->getLine(), $this->parser->getFilename()); + throw new Twig_Error_Syntax(sprintf('"%s" cannot be called as macro as it is a reserved keyword.', $name), $token->getLine(), $stream->getSourceContext()->getName()); } $node = new Twig_Node_Expression_MethodCall($node, 'get'.$name, $arguments, $lineno); @@ -500,7 +523,7 @@ public function parseArguments($namedArguments = false, $definition = false) $name = null; if ($namedArguments && $token = $stream->nextIf(Twig_Token::OPERATOR_TYPE, '=')) { if (!$value instanceof Twig_Node_Expression_Name) { - throw new Twig_Error_Syntax(sprintf('A parameter name must be a string, "%s" given.', get_class($value)), $token->getLine(), $this->parser->getFilename()); + throw new Twig_Error_Syntax(sprintf('A parameter name must be a string, "%s" given.', get_class($value)), $token->getLine(), $stream->getSourceContext()->getName()); } $name = $value->getAttribute('name'); @@ -508,7 +531,7 @@ public function parseArguments($namedArguments = false, $definition = false) $value = $this->parsePrimaryExpression(); if (!$this->checkConstantExpression($value)) { - throw new Twig_Error_Syntax(sprintf('A default value for an argument must be a constant (a boolean, a string, a number, or an array).'), $token->getLine(), $this->parser->getFilename()); + throw new Twig_Error_Syntax(sprintf('A default value for an argument must be a constant (a boolean, a string, a number, or an array).'), $token->getLine(), $stream->getSourceContext()->getName()); } } else { $value = $this->parseExpression(); @@ -536,16 +559,17 @@ public function parseArguments($namedArguments = false, $definition = false) public function parseAssignmentExpression() { + $stream = $this->parser->getStream(); $targets = array(); while (true) { - $token = $this->parser->getStream()->expect(Twig_Token::NAME_TYPE, null, 'Only variables can be assigned to'); + $token = $stream->expect(Twig_Token::NAME_TYPE, null, 'Only variables can be assigned to'); $value = $token->getValue(); if (in_array(strtolower($value), array('true', 'false', 'none', 'null'))) { - throw new Twig_Error_Syntax(sprintf('You cannot assign a value to "%s".', $value), $token->getLine(), $this->parser->getFilename()); + throw new Twig_Error_Syntax(sprintf('You cannot assign a value to "%s".', $value), $token->getLine(), $stream->getSourceContext()->getName()); } $targets[] = new Twig_Node_Expression_AssignName($value, $token->getLine()); - if (!$this->parser->getStream()->nextIf(Twig_Token::PUNCTUATION_TYPE, ',')) { + if (!$stream->nextIf(Twig_Token::PUNCTUATION_TYPE, ',')) { break; } } @@ -566,13 +590,79 @@ public function parseMultitargetExpression() return new Twig_Node($targets); } - protected function getFunctionNodeClass($name, $line) + private function parseNotTestExpression(Twig_NodeInterface $node) { - $env = $this->parser->getEnvironment(); + return new Twig_Node_Expression_Unary_Not($this->parseTestExpression($node), $this->parser->getCurrentToken()->getLine()); + } + + private function parseTestExpression(Twig_NodeInterface $node) + { + $stream = $this->parser->getStream(); + list($name, $test) = $this->getTest($node->getTemplateLine()); + + $class = $this->getTestNodeClass($test); + $arguments = null; + if ($stream->test(Twig_Token::PUNCTUATION_TYPE, '(')) { + $arguments = $this->parser->getExpressionParser()->parseArguments(true); + } - if (false === $function = $env->getFunction($name)) { - $e = new Twig_Error_Syntax(sprintf('Unknown "%s" function.', $name), $line, $this->parser->getFilename()); - $e->addSuggestions($name, array_keys($env->getFunctions())); + return new $class($node, $name, $arguments, $this->parser->getCurrentToken()->getLine()); + } + + private function getTest($line) + { + $stream = $this->parser->getStream(); + $name = $stream->expect(Twig_Token::NAME_TYPE)->getValue(); + + if ($test = $this->env->getTest($name)) { + return array($name, $test); + } + + if ($stream->test(Twig_Token::NAME_TYPE)) { + // try 2-words tests + $name = $name.' '.$this->parser->getCurrentToken()->getValue(); + + if ($test = $this->env->getTest($name)) { + $stream->next(); + + return array($name, $test); + } + } + + $e = new Twig_Error_Syntax(sprintf('Unknown "%s" test.', $name), $line, $stream->getSourceContext()->getName()); + $e->addSuggestions($name, array_keys($this->env->getTests())); + + throw $e; + } + + private function getTestNodeClass($test) + { + if ($test instanceof Twig_SimpleTest && $test->isDeprecated()) { + $stream = $this->parser->getStream(); + $message = sprintf('Twig Test "%s" is deprecated', $test->getName()); + if (!is_bool($test->getDeprecatedVersion())) { + $message .= sprintf(' since version %s', $test->getDeprecatedVersion()); + } + if ($test->getAlternative()) { + $message .= sprintf('. Use "%s" instead', $test->getAlternative()); + } + $message .= sprintf(' in %s at line %d.', $stream->getSourceContext()->getName(), $stream->getCurrent()->getLine()); + + @trigger_error($message, E_USER_DEPRECATED); + } + + if ($test instanceof Twig_SimpleTest) { + return $test->getNodeClass(); + } + + return $test instanceof Twig_Test_Node ? $test->getClass() : 'Twig_Node_Expression_Test'; + } + + protected function getFunctionNodeClass($name, $line) + { + if (false === $function = $this->env->getFunction($name)) { + $e = new Twig_Error_Syntax(sprintf('Unknown "%s" function.', $name), $line, $this->parser->getStream()->getSourceContext()->getName()); + $e->addSuggestions($name, array_keys($this->env->getFunctions())); throw $e; } @@ -585,7 +675,7 @@ protected function getFunctionNodeClass($name, $line) if ($function->getAlternative()) { $message .= sprintf('. Use "%s" instead', $function->getAlternative()); } - $message .= sprintf(' in %s at line %d.', $this->parser->getFilename(), $line); + $message .= sprintf(' in %s at line %d.', $this->parser->getStream()->getSourceContext()->getName(), $line); @trigger_error($message, E_USER_DEPRECATED); } @@ -599,11 +689,9 @@ protected function getFunctionNodeClass($name, $line) protected function getFilterNodeClass($name, $line) { - $env = $this->parser->getEnvironment(); - - if (false === $filter = $env->getFilter($name)) { - $e = new Twig_Error_Syntax(sprintf('Unknown "%s" filter.', $name), $line, $this->parser->getFilename()); - $e->addSuggestions($name, array_keys($env->getFilters())); + if (false === $filter = $this->env->getFilter($name)) { + $e = new Twig_Error_Syntax(sprintf('Unknown "%s" filter.', $name), $line, $this->parser->getStream()->getSourceContext()->getName()); + $e->addSuggestions($name, array_keys($this->env->getFilters())); throw $e; } @@ -616,7 +704,7 @@ protected function getFilterNodeClass($name, $line) if ($filter->getAlternative()) { $message .= sprintf('. Use "%s" instead', $filter->getAlternative()); } - $message .= sprintf(' in %s at line %d.', $this->parser->getFilename(), $line); + $message .= sprintf(' in %s at line %d.', $this->parser->getStream()->getSourceContext()->getName(), $line); @trigger_error($message, E_USER_DEPRECATED); } diff --git a/lib/Twig/lib/Twig/Extension/Core.php b/lib/Twig/lib/Twig/Extension/Core.php index ffa8968..16959e4 100644 --- a/lib/Twig/lib/Twig/Extension/Core.php +++ b/lib/Twig/lib/Twig/Extension/Core.php @@ -132,6 +132,7 @@ public function getTokenParsers() new Twig_TokenParser_Flush(), new Twig_TokenParser_Do(), new Twig_TokenParser_Embed(), + new Twig_TokenParser_With(), ); } @@ -258,82 +259,14 @@ public function getOperators() '/' => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_Div', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), '//' => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_FloorDiv', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), '%' => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_Mod', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), - 'is' => array('precedence' => 100, 'callable' => array($this, 'parseTestExpression'), 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), - 'is not' => array('precedence' => 100, 'callable' => array($this, 'parseNotTestExpression'), 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + 'is' => array('precedence' => 100, 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + 'is not' => array('precedence' => 100, 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), '**' => array('precedence' => 200, 'class' => 'Twig_Node_Expression_Binary_Power', 'associativity' => Twig_ExpressionParser::OPERATOR_RIGHT), '??' => array('precedence' => 300, 'class' => 'Twig_Node_Expression_NullCoalesce', 'associativity' => Twig_ExpressionParser::OPERATOR_RIGHT), ), ); } - public function parseNotTestExpression(Twig_Parser $parser, Twig_NodeInterface $node) - { - return new Twig_Node_Expression_Unary_Not($this->parseTestExpression($parser, $node), $parser->getCurrentToken()->getLine()); - } - - public function parseTestExpression(Twig_Parser $parser, Twig_NodeInterface $node) - { - $stream = $parser->getStream(); - list($name, $test) = $this->getTest($parser, $node->getLine()); - - if ($test instanceof Twig_SimpleTest && $test->isDeprecated()) { - $message = sprintf('Twig Test "%s" is deprecated', $name); - if (!is_bool($test->getDeprecatedVersion())) { - $message .= sprintf(' since version %s', $test->getDeprecatedVersion()); - } - if ($test->getAlternative()) { - $message .= sprintf('. Use "%s" instead', $test->getAlternative()); - } - $message .= sprintf(' in %s at line %d.', $stream->getFilename(), $stream->getCurrent()->getLine()); - - @trigger_error($message, E_USER_DEPRECATED); - } - - $class = $this->getTestNodeClass($parser, $test); - $arguments = null; - if ($stream->test(Twig_Token::PUNCTUATION_TYPE, '(')) { - $arguments = $parser->getExpressionParser()->parseArguments(true); - } - - return new $class($node, $name, $arguments, $parser->getCurrentToken()->getLine()); - } - - protected function getTest(Twig_Parser $parser, $line) - { - $stream = $parser->getStream(); - $name = $stream->expect(Twig_Token::NAME_TYPE)->getValue(); - $env = $parser->getEnvironment(); - - if ($test = $env->getTest($name)) { - return array($name, $test); - } - - if ($stream->test(Twig_Token::NAME_TYPE)) { - // try 2-words tests - $name = $name.' '.$parser->getCurrentToken()->getValue(); - - if ($test = $env->getTest($name)) { - $parser->getStream()->next(); - - return array($name, $test); - } - } - - $e = new Twig_Error_Syntax(sprintf('Unknown "%s" test.', $name), $line, $parser->getFilename()); - $e->addSuggestions($name, array_keys($env->getTests())); - - throw $e; - } - - protected function getTestNodeClass(Twig_Parser $parser, $test) - { - if ($test instanceof Twig_SimpleTest) { - return $test->getNodeClass(); - } - - return $test instanceof Twig_Test_Node ? $test->getClass() : 'Twig_Node_Expression_Test'; - } - public function getName() { return 'core'; @@ -343,7 +276,7 @@ public function getName() /** * Cycles over a value. * - * @param ArrayAccess|array $values An array or an ArrayAccess instance + * @param ArrayAccess|array $values * @param int $position The cycle position * * @return string The next value in the cycle @@ -363,8 +296,8 @@ function twig_cycle($values, $position) * - a random character from a string * - a random integer between 0 and the integer parameter. * - * @param Twig_Environment $env A Twig_Environment instance - * @param Traversable|array|int|string $values The values to pick a random item from + * @param Twig_Environment $env + * @param Traversable|array|int|float|string $values The values to pick a random item from * * @throws Twig_Error_Runtime When $values is an empty array (does not apply to an empty string which is returned as is). * @@ -423,7 +356,7 @@ function twig_random(Twig_Environment $env, $values = null) * {{ post.published_at|date("m/d/Y") }} * * - * @param Twig_Environment $env A Twig_Environment instance + * @param Twig_Environment $env * @param DateTime|DateTimeInterface|DateInterval|string $date A date * @param string|null $format The target format, null to use the default * @param DateTimeZone|string|null|false $timezone The target timezone, null to use the default, false to leave unchanged @@ -451,7 +384,7 @@ function twig_date_format_filter(Twig_Environment $env, $date, $format = null, $ * {{ post.published_at|date_modify("-1day")|date("m/d/Y") }} * * - * @param Twig_Environment $env A Twig_Environment instance + * @param Twig_Environment $env * @param DateTime|string $date A date * @param string $modifier A modifier string * @@ -477,7 +410,7 @@ function twig_date_modify_filter(Twig_Environment $env, $date, $modifier) * {% endif %} * * - * @param Twig_Environment $env A Twig_Environment instance + * @param Twig_Environment $env * @param DateTime|DateTimeInterface|string|null $date A date * @param DateTimeZone|string|null|false $timezone The target timezone, null to use the default, false to leave unchanged * @@ -579,7 +512,7 @@ function twig_round($value, $precision = 0, $method = 'common') * be used. Supplying any of the parameters will override the defaults set in the * environment object. * - * @param Twig_Environment $env A Twig_Environment instance + * @param Twig_Environment $env * @param mixed $number A float/int/string of the number to format * @param int $decimal The number of decimal points to display. * @param string $decimalPoint The character(s) to use for the decimal point. @@ -708,7 +641,7 @@ function twig_array_merge($arr1, $arr2) /** * Slices a variable. * - * @param Twig_Environment $env A Twig_Environment instance + * @param Twig_Environment $env * @param mixed $item A variable * @param int $start Start of the slice * @param int $length Size of the slice @@ -750,7 +683,7 @@ function twig_slice(Twig_Environment $env, $item, $start, $length = null, $prese /** * Returns the first element of the item. * - * @param Twig_Environment $env A Twig_Environment instance + * @param Twig_Environment $env * @param mixed $item A variable * * @return mixed The first element of the item @@ -765,7 +698,7 @@ function twig_first(Twig_Environment $env, $item) /** * Returns the last element of the item. * - * @param Twig_Environment $env A Twig_Environment instance + * @param Twig_Environment $env * @param mixed $item A variable * * @return mixed The last element of the item @@ -821,7 +754,7 @@ function twig_join_filter($value, $glue = '') * {# returns [aa, bb, cc] #} * * - * @param Twig_Environment $env A Twig_Environment instance + * @param Twig_Environment $env * @param string $value A string * @param string $delimiter The delimiter * @param int $limit The limit @@ -901,7 +834,7 @@ function twig_get_array_keys_filter($array) /** * Reverses a variable. * - * @param Twig_Environment $env A Twig_Environment instance + * @param Twig_Environment $env * @param array|Traversable|string $item An array, a Traversable instance, or a string * @param bool $preserveKeys Whether to preserve key or not * @@ -977,7 +910,7 @@ function twig_in_filter($value, $compare) /** * Escapes a string. * - * @param Twig_Environment $env A Twig_Environment instance + * @param Twig_Environment $env * @param mixed $string The value to be escaped * @param string $strategy The escaping strategy * @param string $charset The charset @@ -1058,7 +991,7 @@ function twig_escape_filter(Twig_Environment $env, $string, $strategy = 'html', $string = twig_convert_encoding($string, 'UTF-8', $charset); } - if (0 == strlen($string) ? false : (1 == preg_match('/^./su', $string) ? false : true)) { + if (0 == strlen($string) ? false : 1 !== preg_match('/^./su', $string)) { throw new Twig_Error_Runtime('The string to escape is not a valid UTF-8 string.'); } @@ -1075,7 +1008,7 @@ function twig_escape_filter(Twig_Environment $env, $string, $strategy = 'html', $string = twig_convert_encoding($string, 'UTF-8', $charset); } - if (0 == strlen($string) ? false : (1 == preg_match('/^./su', $string) ? false : true)) { + if (0 == strlen($string) ? false : 1 !== preg_match('/^./su', $string)) { throw new Twig_Error_Runtime('The string to escape is not a valid UTF-8 string.'); } @@ -1092,7 +1025,7 @@ function twig_escape_filter(Twig_Environment $env, $string, $strategy = 'html', $string = twig_convert_encoding($string, 'UTF-8', $charset); } - if (0 == strlen($string) ? false : (1 == preg_match('/^./su', $string) ? false : true)) { + if (0 == strlen($string) ? false : 1 !== preg_match('/^./su', $string)) { throw new Twig_Error_Runtime('The string to escape is not a valid UTF-8 string.'); } @@ -1256,7 +1189,7 @@ function _twig_escape_html_attr_callback($matches) /** * Returns the length of a variable. * - * @param Twig_Environment $env A Twig_Environment instance + * @param Twig_Environment $env * @param mixed $thing A variable * * @return int The length of the value @@ -1269,7 +1202,7 @@ function twig_length_filter(Twig_Environment $env, $thing) /** * Converts a string to uppercase. * - * @param Twig_Environment $env A Twig_Environment instance + * @param Twig_Environment $env * @param string $string A string * * @return string The uppercased string @@ -1286,7 +1219,7 @@ function twig_upper_filter(Twig_Environment $env, $string) /** * Converts a string to lowercase. * - * @param Twig_Environment $env A Twig_Environment instance + * @param Twig_Environment $env * @param string $string A string * * @return string The lowercased string @@ -1303,7 +1236,7 @@ function twig_lower_filter(Twig_Environment $env, $string) /** * Returns a titlecased string. * - * @param Twig_Environment $env A Twig_Environment instance + * @param Twig_Environment $env * @param string $string A string * * @return string The titlecased string @@ -1320,7 +1253,7 @@ function twig_title_string_filter(Twig_Environment $env, $string) /** * Returns a capitalized string. * - * @param Twig_Environment $env A Twig_Environment instance + * @param Twig_Environment $env * @param string $string A string * * @return string The capitalized string @@ -1339,7 +1272,7 @@ function twig_capitalize_string_filter(Twig_Environment $env, $string) /** * Returns the length of a variable. * - * @param Twig_Environment $env A Twig_Environment instance + * @param Twig_Environment $env * @param mixed $thing A variable * * @return int The length of the value @@ -1352,7 +1285,7 @@ function twig_length_filter(Twig_Environment $env, $thing) /** * Returns a titlecased string. * - * @param Twig_Environment $env A Twig_Environment instance + * @param Twig_Environment $env * @param string $string A string * * @return string The titlecased string @@ -1365,7 +1298,7 @@ function twig_title_string_filter(Twig_Environment $env, $string) /** * Returns a capitalized string. * - * @param Twig_Environment $env A Twig_Environment instance + * @param Twig_Environment $env * @param string $string A string * * @return string The capitalized string @@ -1489,8 +1422,13 @@ function twig_include(Twig_Environment $env, $context, $template, $variables = a */ function twig_source(Twig_Environment $env, $name, $ignoreMissing = false) { + $loader = $env->getLoader(); try { - return $env->getLoader()->getSource($name); + if (!$loader instanceof Twig_SourceContextLoaderInterface) { + return $loader->getSource($name); + } else { + return $loader->getSourceContext($name)->getCode(); + } } catch (Twig_Error_Loader $e) { if (!$ignoreMissing) { throw $e; @@ -1515,6 +1453,23 @@ function twig_constant($constant, $object = null) return constant($constant); } +/** + * Checks if a constant exists. + * + * @param string $constant The name of the constant + * @param null|object $object The object to get the constant from + * + * @return bool + */ +function twig_constant_is_defined($constant, $object = null) +{ + if (null !== $object) { + $constant = get_class($object).'::'.$constant; + } + + return defined($constant); +} + /** * Batches item. * diff --git a/lib/Twig/lib/Twig/Extension/Escaper.php b/lib/Twig/lib/Twig/Extension/Escaper.php index 0be721d..5efccf2 100644 --- a/lib/Twig/lib/Twig/Extension/Escaper.php +++ b/lib/Twig/lib/Twig/Extension/Escaper.php @@ -13,8 +13,6 @@ class Twig_Extension_Escaper extends Twig_Extension protected $defaultStrategy; /** - * Constructor. - * * @param string|false|callable $defaultStrategy An escaping strategy * * @see setDefaultStrategy() @@ -45,7 +43,7 @@ public function getFilters() * Sets the default strategy to use when not defined by the user. * * The strategy can be a valid PHP callback that takes the template - * "filename" as an argument and returns the strategy to use. + * name as an argument and returns the strategy to use. * * @param string|false|callable $defaultStrategy An escaping strategy */ @@ -59,6 +57,12 @@ public function setDefaultStrategy($defaultStrategy) } if ('filename' === $defaultStrategy) { + @trigger_error('Using "filename" as the default strategy is deprecated since version 1.27. Use "name" instead.', E_USER_DEPRECATED); + + $defaultStrategy = 'name'; + } + + if ('name' === $defaultStrategy) { $defaultStrategy = array('Twig_FileExtensionEscapingStrategy', 'guess'); } @@ -68,16 +72,16 @@ public function setDefaultStrategy($defaultStrategy) /** * Gets the default strategy to use when not defined by the user. * - * @param string $filename The template "filename" + * @param string $name The template name * * @return string|false The default strategy to use for the template */ - public function getDefaultStrategy($filename) + public function getDefaultStrategy($name) { // disable string callables to avoid calling a function named html or js, // or any other upcoming escaping strategy if (!is_string($this->defaultStrategy) && false !== $this->defaultStrategy) { - return call_user_func($this->defaultStrategy, $filename); + return call_user_func($this->defaultStrategy, $name); } return $this->defaultStrategy; diff --git a/lib/Twig/lib/Twig/Extension/StringLoader.php b/lib/Twig/lib/Twig/Extension/StringLoader.php index 2a3ddb6..1b3bd26 100644 --- a/lib/Twig/lib/Twig/Extension/StringLoader.php +++ b/lib/Twig/lib/Twig/Extension/StringLoader.php @@ -33,7 +33,7 @@ public function getName() * @param Twig_Environment $env A Twig_Environment instance * @param string $template A template as a string or object implementing __toString() * - * @return Twig_Template A Twig_Template instance + * @return Twig_Template */ function twig_template_from_string(Twig_Environment $env, $template) { diff --git a/lib/Twig/lib/Twig/ExtensionInterface.php b/lib/Twig/lib/Twig/ExtensionInterface.php index 854d073..980eade 100644 --- a/lib/Twig/lib/Twig/ExtensionInterface.php +++ b/lib/Twig/lib/Twig/ExtensionInterface.php @@ -21,8 +21,6 @@ interface Twig_ExtensionInterface * * This is where you can load some file that contains filter functions for instance. * - * @param Twig_Environment $environment The current Twig_Environment instance - * * @deprecated since 1.23 (to be removed in 2.0), implement Twig_Extension_InitRuntimeInterface instead */ public function initRuntime(Twig_Environment $environment); @@ -37,7 +35,7 @@ public function getTokenParsers(); /** * Returns the node visitor instances to add to the existing list. * - * @return Twig_NodeVisitorInterface[] An array of Twig_NodeVisitorInterface instances + * @return Twig_NodeVisitorInterface[] */ public function getNodeVisitors(); diff --git a/lib/Twig/lib/Twig/FileExtensionEscapingStrategy.php b/lib/Twig/lib/Twig/FileExtensionEscapingStrategy.php index 9bda0b4..772139e 100644 --- a/lib/Twig/lib/Twig/FileExtensionEscapingStrategy.php +++ b/lib/Twig/lib/Twig/FileExtensionEscapingStrategy.php @@ -13,7 +13,7 @@ * Default autoescaping strategy based on file names. * * This strategy sets the HTML as the default autoescaping strategy, - * but changes it based on the filename. + * but changes it based on the template name. * * Note that there is no runtime performance impact as the * default autoescaping strategy is set at compilation time. @@ -25,21 +25,21 @@ class Twig_FileExtensionEscapingStrategy /** * Guesses the best autoescaping strategy based on the file name. * - * @param string $filename The template file name + * @param string $name The template name * * @return string|false The escaping strategy name to use or false to disable */ - public static function guess($filename) + public static function guess($name) { - if (in_array(substr($filename, -1), array('/', '\\'))) { + if (in_array(substr($name, -1), array('/', '\\'))) { return 'html'; // return html for directories } - if ('.twig' === substr($filename, -5)) { - $filename = substr($filename, 0, -5); + if ('.twig' === substr($name, -5)) { + $name = substr($name, 0, -5); } - $extension = pathinfo($filename, PATHINFO_EXTENSION); + $extension = pathinfo($name, PATHINFO_EXTENSION); switch ($extension) { case 'js': diff --git a/lib/Twig/lib/Twig/Lexer.php b/lib/Twig/lib/Twig/Lexer.php index 6040f90..0e7019f 100644 --- a/lib/Twig/lib/Twig/Lexer.php +++ b/lib/Twig/lib/Twig/Lexer.php @@ -26,6 +26,7 @@ class Twig_Lexer implements Twig_LexerInterface protected $states; protected $brackets; protected $env; + // to be renamed to $name in 2.0 (where it is private) protected $filename; protected $options; protected $regexes; @@ -75,8 +76,15 @@ public function __construct(Twig_Environment $env, array $options = array()) /** * {@inheritdoc} */ - public function tokenize($code, $filename = null) + public function tokenize($code, $name = null) { + if (!$code instanceof Twig_Source) { + @trigger_error(sprintf('Passing a string as the $code argument of %s() is deprecated since version 1.27 and will be removed in 2.0. Pass a Twig_Source instance instead.', __METHOD__), E_USER_DEPRECATED); + $source = new Twig_Source($code, $name); + } else { + $source = $code; + } + if (function_exists('mb_internal_encoding') && ((int) ini_get('mbstring.func_overload')) & 2) { $mbEncoding = mb_internal_encoding(); mb_internal_encoding('ASCII'); @@ -84,8 +92,8 @@ public function tokenize($code, $filename = null) $mbEncoding = null; } - $this->code = str_replace(array("\r\n", "\r"), "\n", $code); - $this->filename = $filename; + $this->code = str_replace(array("\r\n", "\r"), "\n", $source->getCode()); + $this->filename = $source->getName(); $this->cursor = 0; $this->lineno = 1; $this->end = strlen($this->code); @@ -136,7 +144,7 @@ public function tokenize($code, $filename = null) mb_internal_encoding($mbEncoding); } - return new Twig_TokenStream($this->tokens, $this->filename, $code); + return new Twig_TokenStream($this->tokens, $source); } protected function lexData() diff --git a/lib/Twig/lib/Twig/LexerInterface.php b/lib/Twig/lib/Twig/LexerInterface.php index 24a9478..afd8633 100644 --- a/lib/Twig/lib/Twig/LexerInterface.php +++ b/lib/Twig/lib/Twig/LexerInterface.php @@ -21,12 +21,12 @@ interface Twig_LexerInterface /** * Tokenizes a source code. * - * @param string $code The source code - * @param string $filename A unique identifier for the source code + * @param string|Twig_Source $code The source code + * @param string $name A unique identifier for the source code * - * @return Twig_TokenStream A token stream instance + * @return Twig_TokenStream * * @throws Twig_Error_Syntax When the code is syntactically wrong */ - public function tokenize($code, $filename = null); + public function tokenize($code, $name = null); } diff --git a/lib/Twig/lib/Twig/Loader/Array.php b/lib/Twig/lib/Twig/Loader/Array.php index 90221d5..2bb2fb8 100644 --- a/lib/Twig/lib/Twig/Loader/Array.php +++ b/lib/Twig/lib/Twig/Loader/Array.php @@ -21,16 +21,14 @@ * * @author Fabien Potencier */ -class Twig_Loader_Array implements Twig_LoaderInterface, Twig_ExistsLoaderInterface +class Twig_Loader_Array implements Twig_LoaderInterface, Twig_ExistsLoaderInterface, Twig_SourceContextLoaderInterface { protected $templates = array(); /** - * Constructor. - * * @param array $templates An array of templates (keys are the names, and values are the source code) */ - public function __construct(array $templates) + public function __construct(array $templates = array()) { $this->templates = $templates; } @@ -51,6 +49,8 @@ public function setTemplate($name, $template) */ public function getSource($name) { + @trigger_error(sprintf('Calling "getSource" on "%s" is deprecated since 1.27. Use getSourceContext() instead.', get_class($this)), E_USER_DEPRECATED); + $name = (string) $name; if (!isset($this->templates[$name])) { throw new Twig_Error_Loader(sprintf('Template "%s" is not defined.', $name)); @@ -59,6 +59,19 @@ public function getSource($name) return $this->templates[$name]; } + /** + * {@inheritdoc} + */ + public function getSourceContext($name) + { + $name = (string) $name; + if (!isset($this->templates[$name])) { + throw new Twig_Error_Loader(sprintf('Template "%s" is not defined.', $name)); + } + + return new Twig_Source($this->templates[$name], $name); + } + /** * {@inheritdoc} */ diff --git a/lib/Twig/lib/Twig/Loader/Chain.php b/lib/Twig/lib/Twig/Loader/Chain.php index 81d57ad..26766d6 100644 --- a/lib/Twig/lib/Twig/Loader/Chain.php +++ b/lib/Twig/lib/Twig/Loader/Chain.php @@ -14,15 +14,13 @@ * * @author Fabien Potencier */ -class Twig_Loader_Chain implements Twig_LoaderInterface, Twig_ExistsLoaderInterface +class Twig_Loader_Chain implements Twig_LoaderInterface, Twig_ExistsLoaderInterface, Twig_SourceContextLoaderInterface { private $hasSourceCache = array(); protected $loaders = array(); /** - * Constructor. - * - * @param Twig_LoaderInterface[] $loaders An array of loader instances + * @param Twig_LoaderInterface[] $loaders */ public function __construct(array $loaders = array()) { @@ -31,11 +29,6 @@ public function __construct(array $loaders = array()) } } - /** - * Adds a loader instance. - * - * @param Twig_LoaderInterface $loader A Loader instance - */ public function addLoader(Twig_LoaderInterface $loader) { $this->loaders[] = $loader; @@ -47,6 +40,8 @@ public function addLoader(Twig_LoaderInterface $loader) */ public function getSource($name) { + @trigger_error(sprintf('Calling "getSource" on "%s" is deprecated since 1.27. Use getSourceContext() instead.', get_class($this)), E_USER_DEPRECATED); + $exceptions = array(); foreach ($this->loaders as $loader) { if ($loader instanceof Twig_ExistsLoaderInterface && !$loader->exists($name)) { @@ -63,6 +58,31 @@ public function getSource($name) throw new Twig_Error_Loader(sprintf('Template "%s" is not defined%s.', $name, $exceptions ? ' ('.implode(', ', $exceptions).')' : '')); } + /** + * {@inheritdoc} + */ + public function getSourceContext($name) + { + $exceptions = array(); + foreach ($this->loaders as $loader) { + if ($loader instanceof Twig_ExistsLoaderInterface && !$loader->exists($name)) { + continue; + } + + try { + if ($loader instanceof Twig_SourceContextLoaderInterface) { + return $loader->getSourceContext($name); + } + + return new Twig_Source($loader->getSource($name), $name); + } catch (Twig_Error_Loader $e) { + $exceptions[] = $e->getMessage(); + } + } + + throw new Twig_Error_Loader(sprintf('Template "%s" is not defined%s.', $name, $exceptions ? ' ('.implode(', ', $exceptions).')' : '')); + } + /** * {@inheritdoc} */ @@ -84,7 +104,11 @@ public function exists($name) } try { - $loader->getSource($name); + if ($loader instanceof Twig_SourceContextLoaderInterface) { + $loader->getSourceContext($name); + } else { + $loader->getSource($name); + } return $this->hasSourceCache[$name] = true; } catch (Twig_Error_Loader $e) { diff --git a/lib/Twig/lib/Twig/Loader/Filesystem.php b/lib/Twig/lib/Twig/Loader/Filesystem.php index 2d2153e..b920478 100644 --- a/lib/Twig/lib/Twig/Loader/Filesystem.php +++ b/lib/Twig/lib/Twig/Loader/Filesystem.php @@ -14,7 +14,7 @@ * * @author Fabien Potencier */ -class Twig_Loader_Filesystem implements Twig_LoaderInterface, Twig_ExistsLoaderInterface +class Twig_Loader_Filesystem implements Twig_LoaderInterface, Twig_ExistsLoaderInterface, Twig_SourceContextLoaderInterface { /** Identifier of the main namespace. */ const MAIN_NAMESPACE = '__main__'; @@ -23,13 +23,19 @@ class Twig_Loader_Filesystem implements Twig_LoaderInterface, Twig_ExistsLoaderI protected $cache = array(); protected $errorCache = array(); + private $rootPath; + /** - * Constructor. - * - * @param string|array $paths A path or an array of paths where to look for templates + * @param string|array $paths A path or an array of paths where to look for templates + * @param string|null $rootPath The root path common to all relative paths (null for getcwd()) */ - public function __construct($paths = array()) + public function __construct($paths = array(), $rootPath = null) { + $this->rootPath = (null === $rootPath ? getcwd() : $rootPath).DIRECTORY_SEPARATOR; + if (false !== $realPath = realpath($rootPath)) { + $this->rootPath = $realPath.DIRECTORY_SEPARATOR; + } + if ($paths) { $this->setPaths($paths); } @@ -81,7 +87,7 @@ public function setPaths($paths, $namespace = self::MAIN_NAMESPACE) * Adds a path where templates are stored. * * @param string $path A path where to look for templates - * @param string $namespace A path name + * @param string $namespace A path namespace * * @throws Twig_Error_Loader */ @@ -90,8 +96,9 @@ public function addPath($path, $namespace = self::MAIN_NAMESPACE) // invalidate the cache $this->cache = $this->errorCache = array(); - if (!is_dir($path)) { - throw new Twig_Error_Loader(sprintf('The "%s" directory does not exist.', $path)); + $checkPath = $this->isAbsolutePath($path) ? $path : $this->rootPath.$path; + if (!is_dir($checkPath)) { + throw new Twig_Error_Loader(sprintf('The "%s" directory does not exist ("%s").', $path, $checkPath)); } $this->paths[$namespace][] = rtrim($path, '/\\'); @@ -101,7 +108,7 @@ public function addPath($path, $namespace = self::MAIN_NAMESPACE) * Prepends a path where templates are stored. * * @param string $path A path where to look for templates - * @param string $namespace A path name + * @param string $namespace A path namespace * * @throws Twig_Error_Loader */ @@ -110,8 +117,9 @@ public function prependPath($path, $namespace = self::MAIN_NAMESPACE) // invalidate the cache $this->cache = $this->errorCache = array(); - if (!is_dir($path)) { - throw new Twig_Error_Loader(sprintf('The "%s" directory does not exist.', $path)); + $checkPath = $this->isAbsolutePath($path) ? $path : $this->rootPath.$path; + if (!is_dir($checkPath)) { + throw new Twig_Error_Loader(sprintf('The "%s" directory does not exist ("%s").', $path, $checkPath)); } $path = rtrim($path, '/\\'); @@ -128,15 +136,33 @@ public function prependPath($path, $namespace = self::MAIN_NAMESPACE) */ public function getSource($name) { + @trigger_error(sprintf('Calling "getSource" on "%s" is deprecated since 1.27. Use getSourceContext() instead.', get_class($this)), E_USER_DEPRECATED); + return file_get_contents($this->findTemplate($name)); } + /** + * {@inheritdoc} + */ + public function getSourceContext($name) + { + $path = $this->findTemplate($name); + + return new Twig_Source(file_get_contents($path), $name, $path); + } + /** * {@inheritdoc} */ public function getCacheKey($name) { - return $this->findTemplate($name); + $path = $this->findTemplate($name); + $len = strlen($this->rootPath); + if (0 === strncmp($this->rootPath, $path, $len)) { + return substr($path, $len); + } + + return $path; } /** @@ -153,6 +179,8 @@ public function exists($name) try { return false !== $this->findTemplate($name, false); } catch (Twig_Error_Loader $exception) { + @trigger_error(sprintf('In %s::findTemplate(), you must accept a second argument that when set to "false" returns "false" instead of throwing an exception. Not supporting this argument is deprecated since version 1.27.', get_class($this)), E_USER_DEPRECATED); + return false; } } @@ -197,8 +225,16 @@ protected function findTemplate($name) } foreach ($this->paths[$namespace] as $path) { + if (!$this->isAbsolutePath($path)) { + $path = $this->rootPath.'/'.$path; + } + if (is_file($path.'/'.$shortname)) { - return $this->cache[$name] = $this->normalizePath($path.'/'.$shortname); + if (false !== $realpath = realpath($path.'/'.$shortname)) { + return $this->cache[$name] = $realpath; + } + + return $this->cache[$name] = $path.'/'.$shortname; } } @@ -254,19 +290,14 @@ protected function validateName($name) } } - private function normalizePath($path) + private function isAbsolutePath($file) { - $parts = explode('/', str_replace('\\', '/', $path)); - $isPhar = strpos($path, 'phar://') === 0; - $new = array(); - foreach ($parts as $i => $part) { - if ('..' === $part) { - array_pop($new); - } elseif ('.' !== $part && ('' !== $part || 0 === $i || $isPhar && $i < 3)) { - $new[] = $part; - } - } - - return implode('/', $new); + return strspn($file, '/\\', 0, 1) + || (strlen($file) > 3 && ctype_alpha($file[0]) + && substr($file, 1, 1) === ':' + && strspn($file, '/\\', 2, 1) + ) + || null !== parse_url($file, PHP_URL_SCHEME) + ; } } diff --git a/lib/Twig/lib/Twig/Loader/String.php b/lib/Twig/lib/Twig/Loader/String.php index 00f507a..46da4a4 100644 --- a/lib/Twig/lib/Twig/Loader/String.php +++ b/lib/Twig/lib/Twig/Loader/String.php @@ -27,16 +27,26 @@ * * @author Fabien Potencier */ -class Twig_Loader_String implements Twig_LoaderInterface, Twig_ExistsLoaderInterface +class Twig_Loader_String implements Twig_LoaderInterface, Twig_ExistsLoaderInterface, Twig_SourceContextLoaderInterface { /** * {@inheritdoc} */ public function getSource($name) { + @trigger_error(sprintf('Calling "getSource" on "%s" is deprecated since 1.27. Use getSourceContext() instead.', get_class($this)), E_USER_DEPRECATED); + return $name; } + /** + * {@inheritdoc} + */ + public function getSourceContext($name) + { + return new Twig_Source($name, $name); + } + /** * {@inheritdoc} */ diff --git a/lib/Twig/lib/Twig/LoaderInterface.php b/lib/Twig/lib/Twig/LoaderInterface.php index 544ea4e..840ab14 100644 --- a/lib/Twig/lib/Twig/LoaderInterface.php +++ b/lib/Twig/lib/Twig/LoaderInterface.php @@ -24,6 +24,8 @@ interface Twig_LoaderInterface * @return string The template source code * * @throws Twig_Error_Loader When $name is not found + * + * @deprecated since 1.27 (to be removed in 2.0), implement Twig_SourceContextLoaderInterface */ public function getSource($name); diff --git a/lib/Twig/lib/Twig/Node.php b/lib/Twig/lib/Twig/Node.php index e1eddd8..ba0596c 100644 --- a/lib/Twig/lib/Twig/Node.php +++ b/lib/Twig/lib/Twig/Node.php @@ -22,7 +22,7 @@ class Twig_Node implements Twig_NodeInterface protected $lineno; protected $tag; - private $filename; + private $name; /** * Constructor. @@ -118,8 +118,18 @@ public function compile(Twig_Compiler $compiler) } } + public function getTemplateLine() + { + return $this->lineno; + } + + /** + * @deprecated since 1.27 (to be removed in 2.0) + */ public function getLine() { + @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use getTemplateLine() instead.', E_USER_DEPRECATED); + return $this->lineno; } @@ -129,11 +139,7 @@ public function getNodeTag() } /** - * Returns true if the attribute is defined. - * - * @param string $name The attribute name - * - * @return bool true if the attribute is defined, false otherwise + * @return bool */ public function hasAttribute($name) { @@ -141,10 +147,6 @@ public function hasAttribute($name) } /** - * Gets an attribute value by name. - * - * @param string $name - * * @return mixed */ public function getAttribute($name) @@ -157,8 +159,6 @@ public function getAttribute($name) } /** - * Sets an attribute by name to a value. - * * @param string $name * @param mixed $value */ @@ -167,21 +167,12 @@ public function setAttribute($name, $value) $this->attributes[$name] = $value; } - /** - * Removes an attribute by name. - * - * @param string $name - */ public function removeAttribute($name) { unset($this->attributes[$name]); } /** - * Returns true if the node with the given name exists. - * - * @param string $name - * * @return bool */ public function hasNode($name) @@ -190,10 +181,6 @@ public function hasNode($name) } /** - * Gets a node by name. - * - * @param string $name - * * @return Twig_Node */ public function getNode($name) @@ -205,12 +192,6 @@ public function getNode($name) return $this->nodes[$name]; } - /** - * Sets a node. - * - * @param string $name - * @param Twig_Node $node - */ public function setNode($name, $node = null) { if (!$node instanceof Twig_NodeInterface) { @@ -220,11 +201,6 @@ public function setNode($name, $node = null) $this->nodes[$name] = $node; } - /** - * Removes a node by name. - * - * @param string $name - */ public function removeNode($name) { unset($this->nodes[$name]); @@ -240,18 +216,38 @@ public function getIterator() return new ArrayIterator($this->nodes); } - public function setFilename($filename) + public function setTemplateName($name) { - $this->filename = $filename; + $this->name = $name; foreach ($this->nodes as $node) { if (null !== $node) { - $node->setFilename($filename); + $node->setTemplateName($name); } } } + public function getTemplateName() + { + return $this->name; + } + + /** + * @deprecated since 1.27 (to be removed in 2.0) + */ + public function setFilename($name) + { + @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use setTemplateName() instead.', E_USER_DEPRECATED); + + $this->setTemplateName($name); + } + + /** + * @deprecated since 1.27 (to be removed in 2.0) + */ public function getFilename() { - return $this->filename; + @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use getTemplateName() instead.', E_USER_DEPRECATED); + + return $this->name; } } diff --git a/lib/Twig/lib/Twig/Node/CheckSecurity.php b/lib/Twig/lib/Twig/Node/CheckSecurity.php index 9a7d2c9..52db7d2 100644 --- a/lib/Twig/lib/Twig/Node/CheckSecurity.php +++ b/lib/Twig/lib/Twig/Node/CheckSecurity.php @@ -33,7 +33,7 @@ public function compile(Twig_Compiler $compiler) foreach (array('tags', 'filters', 'functions') as $type) { foreach ($this->{'used'.ucfirst($type)} as $name => $node) { if ($node instanceof Twig_Node) { - ${$type}[$name] = $node->getLine(); + ${$type}[$name] = $node->getTemplateLine(); } else { ${$type}[$node] = null; } @@ -56,7 +56,7 @@ public function compile(Twig_Compiler $compiler) ->outdent() ->write("} catch (Twig_Sandbox_SecurityError \$e) {\n") ->indent() - ->write("\$e->setTemplateFile(\$this->getTemplateName());\n\n") + ->write("\$e->setTemplateName(\$this->getTemplateName());\n\n") ->write("if (\$e instanceof Twig_Sandbox_SecurityNotAllowedTagError && isset(\$tags[\$e->getTagName()])) {\n") ->indent() ->write("\$e->setTemplateLine(\$tags[\$e->getTagName()]);\n") diff --git a/lib/Twig/lib/Twig/Node/Embed.php b/lib/Twig/lib/Twig/Node/Embed.php index 7ed12fb..d9af37b 100644 --- a/lib/Twig/lib/Twig/Node/Embed.php +++ b/lib/Twig/lib/Twig/Node/Embed.php @@ -17,11 +17,13 @@ class Twig_Node_Embed extends Twig_Node_Include { // we don't inject the module to avoid node visitors to traverse it twice (as it will be already visited in the main module) - public function __construct($filename, $index, Twig_Node_Expression $variables = null, $only = false, $ignoreMissing = false, $lineno, $tag = null) + public function __construct($name, $index, Twig_Node_Expression $variables = null, $only = false, $ignoreMissing = false, $lineno, $tag = null) { parent::__construct(new Twig_Node_Expression_Constant('not_used', $lineno), $variables, $only, $ignoreMissing, $lineno, $tag); - $this->setAttribute('filename', $filename); + $this->setAttribute('name', $name); + // to be removed in 2.0, used name instead + $this->setAttribute('filename', $name); $this->setAttribute('index', $index); } @@ -29,11 +31,11 @@ protected function addGetTemplate(Twig_Compiler $compiler) { $compiler ->write('$this->loadTemplate(') - ->string($this->getAttribute('filename')) + ->string($this->getAttribute('name')) ->raw(', ') - ->repr($this->getFilename()) + ->repr($this->getTemplateName()) ->raw(', ') - ->repr($this->getLine()) + ->repr($this->getTemplateLine()) ->raw(', ') ->string($this->getAttribute('index')) ->raw(')') diff --git a/lib/Twig/lib/Twig/Node/Expression/Array.php b/lib/Twig/lib/Twig/Node/Expression/Array.php index 83e583b..aba1825 100644 --- a/lib/Twig/lib/Twig/Node/Expression/Array.php +++ b/lib/Twig/lib/Twig/Node/Expression/Array.php @@ -43,7 +43,7 @@ public function hasElement(Twig_Node_Expression $key) foreach ($this->getKeyValuePairs() as $pair) { // we compare the string representation of the keys // to avoid comparing the line numbers which are not relevant here. - if ((string) $key == (string) $pair['key']) { + if ((string) $key === (string) $pair['key']) { return true; } } @@ -54,7 +54,7 @@ public function hasElement(Twig_Node_Expression $key) public function addElement(Twig_Node_Expression $value, Twig_Node_Expression $key = null) { if (null === $key) { - $key = new Twig_Node_Expression_Constant(++$this->index, $value->getLine()); + $key = new Twig_Node_Expression_Constant(++$this->index, $value->getTemplateLine()); } array_push($this->nodes, $key, $value); diff --git a/lib/Twig/lib/Twig/Node/Expression/Binary/Power.php b/lib/Twig/lib/Twig/Node/Expression/Binary/Power.php index cd6d046..76f3a8c 100644 --- a/lib/Twig/lib/Twig/Node/Expression/Binary/Power.php +++ b/lib/Twig/lib/Twig/Node/Expression/Binary/Power.php @@ -12,6 +12,10 @@ class Twig_Node_Expression_Binary_Power extends Twig_Node_Expression_Binary { public function compile(Twig_Compiler $compiler) { + if (PHP_VERSION_ID >= 50600) { + return parent::compile($compiler); + } + $compiler ->raw('pow(') ->subcompile($this->getNode('left')) diff --git a/lib/Twig/lib/Twig/Node/Expression/BlockReference.php b/lib/Twig/lib/Twig/Node/Expression/BlockReference.php index f6ed6ff..adc63fc 100644 --- a/lib/Twig/lib/Twig/Node/Expression/BlockReference.php +++ b/lib/Twig/lib/Twig/Node/Expression/BlockReference.php @@ -17,30 +17,75 @@ */ class Twig_Node_Expression_BlockReference extends Twig_Node_Expression { - public function __construct(Twig_NodeInterface $name, $asString = false, $lineno, $tag = null) + /** + * @param Twig_Node|null $template + */ + public function __construct(Twig_NodeInterface $name, $template = null, $lineno, $tag = null) { - parent::__construct(array('name' => $name), array('as_string' => $asString, 'output' => false), $lineno, $tag); + if (is_bool($template)) { + @trigger_error(sprintf('The %s method "$asString" argument is deprecated since version 1.28 and will be removed in 2.0.', __METHOD__), E_USER_DEPRECATED); + + $template = null; + } + + $nodes = array('name' => $name); + if (null !== $template) { + $nodes['template'] = $template; + } + + parent::__construct($nodes, array('is_defined_test' => false, 'output' => false), $lineno, $tag); } public function compile(Twig_Compiler $compiler) { - if ($this->getAttribute('as_string')) { - $compiler->raw('(string) '); + if ($this->getAttribute('is_defined_test')) { + $this->compileTemplateCall($compiler, 'hasBlock'); + } else { + if ($this->getAttribute('output')) { + $compiler->addDebugInfo($this); + + $this + ->compileTemplateCall($compiler, 'displayBlock') + ->raw(";\n"); + } else { + $this->compileTemplateCall($compiler, 'renderBlock'); + } } + } - if ($this->getAttribute('output')) { - $compiler - ->addDebugInfo($this) - ->write('$this->displayBlock(') - ->subcompile($this->getNode('name')) - ->raw(", \$context, \$blocks);\n") - ; + private function compileTemplateCall(Twig_Compiler $compiler, $method) + { + if (!$this->hasNode('template')) { + $compiler->write('$this'); } else { $compiler - ->raw('$this->renderBlock(') - ->subcompile($this->getNode('name')) - ->raw(', $context, $blocks)') + ->write('$this->loadTemplate(') + ->subcompile($this->getNode('template')) + ->raw(', ') + ->repr($this->getTemplateName()) + ->raw(', ') + ->repr($this->getTemplateLine()) + ->raw(')') ; } + + $compiler->raw(sprintf('->%s', $method)); + $this->compileBlockArguments($compiler); + + return $compiler; + } + + private function compileBlockArguments(Twig_Compiler $compiler) + { + $compiler + ->raw('(') + ->subcompile($this->getNode('name')) + ->raw(', $context'); + + if (!$this->hasNode('template')) { + $compiler->raw(', $blocks'); + } + + return $compiler->raw(')'); } } diff --git a/lib/Twig/lib/Twig/Node/Expression/Call.php b/lib/Twig/lib/Twig/Node/Expression/Call.php index 224c22d..a5c88c7 100644 --- a/lib/Twig/lib/Twig/Node/Expression/Call.php +++ b/lib/Twig/lib/Twig/Node/Expression/Call.php @@ -146,7 +146,7 @@ protected function getArguments($callable, $arguments) throw new Twig_Error_Syntax(sprintf('Argument "%s" is defined twice for %s "%s".', $name, $callType, $callName)); } - if (!empty($missingArguments)) { + if (count($missingArguments)) { throw new Twig_Error_Syntax(sprintf( 'Argument "%s" could not be assigned for %s "%s(%s)" because it is mapped to an internal PHP function which cannot determine default value for optional argument%s "%s".', $name, $callType, $callName, implode(', ', $names), count($missingArguments) > 1 ? 's' : '', implode('", "', $missingArguments)) @@ -205,7 +205,7 @@ protected function getArguments($callable, $arguments) throw new Twig_Error_Syntax(sprintf( 'Unknown argument%s "%s" for %s "%s(%s)".', count($parameters) > 1 ? 's' : '', implode('", "', array_keys($parameters)), $callType, $callName, implode(', ', $names) - ), $unknownParameter ? $unknownParameter->getLine() : -1); + ), $unknownParameter ? $unknownParameter->getTemplateLine() : -1); } return $arguments; @@ -218,7 +218,10 @@ protected function normalizeName($name) private function getCallableParameters($callable, $isVariadic) { - list($r, $_) = $this->reflectCallable($callable); + list($r) = $this->reflectCallable($callable); + if (null === $r) { + return array(); + } $parameters = $r->getParameters(); if ($this->hasNode('node')) { @@ -259,14 +262,24 @@ private function reflectCallable($callable) } if (is_array($callable)) { + if (!method_exists($callable[0], $callable[1])) { + // __call() + return array(null, array()); + } $r = new ReflectionMethod($callable[0], $callable[1]); } elseif (is_object($callable) && !$callable instanceof Closure) { $r = new ReflectionObject($callable); $r = $r->getMethod('__invoke'); $callable = array($callable, '__invoke'); } elseif (is_string($callable) && false !== $pos = strpos($callable, '::')) { + $class = substr($callable, 0, $pos); + $method = substr($callable, $pos + 2); + if (!method_exists($class, $method)) { + // __staticCall() + return array(null, array()); + } $r = new ReflectionMethod($callable); - $callable = array(substr($callable, 0, $pos), substr($callable, $pos + 2)); + $callable = array($class, $method); } else { $r = new ReflectionFunction($callable); } diff --git a/lib/Twig/lib/Twig/Node/Expression/Filter/Default.php b/lib/Twig/lib/Twig/Node/Expression/Filter/Default.php index 1827c88..5ce172b 100644 --- a/lib/Twig/lib/Twig/Node/Expression/Filter/Default.php +++ b/lib/Twig/lib/Twig/Node/Expression/Filter/Default.php @@ -22,13 +22,13 @@ class Twig_Node_Expression_Filter_Default extends Twig_Node_Expression_Filter { public function __construct(Twig_NodeInterface $node, Twig_Node_Expression_Constant $filterName, Twig_NodeInterface $arguments, $lineno, $tag = null) { - $default = new Twig_Node_Expression_Filter($node, new Twig_Node_Expression_Constant('default', $node->getLine()), $arguments, $node->getLine()); + $default = new Twig_Node_Expression_Filter($node, new Twig_Node_Expression_Constant('default', $node->getTemplateLine()), $arguments, $node->getTemplateLine()); if ('default' === $filterName->getAttribute('value') && ($node instanceof Twig_Node_Expression_Name || $node instanceof Twig_Node_Expression_GetAttr)) { - $test = new Twig_Node_Expression_Test_Defined(clone $node, 'defined', new Twig_Node(), $node->getLine()); - $false = count($arguments) ? $arguments->getNode(0) : new Twig_Node_Expression_Constant('', $node->getLine()); + $test = new Twig_Node_Expression_Test_Defined(clone $node, 'defined', new Twig_Node(), $node->getTemplateLine()); + $false = count($arguments) ? $arguments->getNode(0) : new Twig_Node_Expression_Constant('', $node->getTemplateLine()); - $node = new Twig_Node_Expression_Conditional($test, $default, $false, $node->getLine()); + $node = new Twig_Node_Expression_Conditional($test, $default, $false, $node->getTemplateLine()); } else { $node = $default; } diff --git a/lib/Twig/lib/Twig/Node/Expression/Function.php b/lib/Twig/lib/Twig/Node/Expression/Function.php index 7326ede..207f655 100644 --- a/lib/Twig/lib/Twig/Node/Expression/Function.php +++ b/lib/Twig/lib/Twig/Node/Expression/Function.php @@ -12,7 +12,7 @@ class Twig_Node_Expression_Function extends Twig_Node_Expression_Call { public function __construct($name, Twig_NodeInterface $arguments, $lineno) { - parent::__construct(array('arguments' => $arguments), array('name' => $name), $lineno); + parent::__construct(array('arguments' => $arguments), array('name' => $name, 'is_defined_test' => false), $lineno); } public function compile(Twig_Compiler $compiler) @@ -27,7 +27,12 @@ public function compile(Twig_Compiler $compiler) $this->setAttribute('needs_context', $function->needsContext()); $this->setAttribute('arguments', $function->getArguments()); if ($function instanceof Twig_FunctionCallableInterface || $function instanceof Twig_SimpleFunction) { - $this->setAttribute('callable', $function->getCallable()); + $callable = $function->getCallable(); + if ('constant' === $name && $this->getAttribute('is_defined_test')) { + $callable = 'twig_constant_is_defined'; + } + + $this->setAttribute('callable', $callable); } if ($function instanceof Twig_SimpleFunction) { $this->setAttribute('is_variadic', $function->isVariadic()); diff --git a/lib/Twig/lib/Twig/Node/Expression/Name.php b/lib/Twig/lib/Twig/Node/Expression/Name.php index a6e0ff4..d18aaca 100644 --- a/lib/Twig/lib/Twig/Node/Expression/Name.php +++ b/lib/Twig/lib/Twig/Node/Expression/Name.php @@ -43,10 +43,20 @@ public function compile(Twig_Compiler $compiler) ->raw(']') ; } else { - // remove the non-PHP 5.4 version when PHP 5.3 support is dropped - // as the non-optimized version is just a workaround for slow ternary operator - // when the context has a lot of variables - if (PHP_VERSION_ID >= 50400) { + if (PHP_VERSION_ID >= 70000) { + // use PHP 7 null coalescing operator + $compiler + ->raw('($context[') + ->string($name) + ->raw('] ?? ') + ; + + if ($this->getAttribute('ignore_strict_check') || !$compiler->getEnvironment()->isStrictVariables()) { + $compiler->raw('null)'); + } else { + $compiler->raw('$this->getContext($context, ')->string($name)->raw('))'); + } + } elseif (PHP_VERSION_ID >= 50400) { // PHP 5.4 ternary operator performance was optimized $compiler ->raw('(isset($context[') diff --git a/lib/Twig/lib/Twig/Node/Expression/NullCoalesce.php b/lib/Twig/lib/Twig/Node/Expression/NullCoalesce.php index 1003913..14f6358 100644 --- a/lib/Twig/lib/Twig/Node/Expression/NullCoalesce.php +++ b/lib/Twig/lib/Twig/Node/Expression/NullCoalesce.php @@ -13,11 +13,34 @@ class Twig_Node_Expression_NullCoalesce extends Twig_Node_Expression_Conditional public function __construct(Twig_NodeInterface $left, Twig_NodeInterface $right, $lineno) { $test = new Twig_Node_Expression_Binary_And( - new Twig_Node_Expression_Test_Defined(clone $left, 'defined', new Twig_Node(), $left->getLine()), - new Twig_Node_Expression_Unary_Not(new Twig_Node_Expression_Test_Null($left, 'null', new Twig_Node(), $left->getLine()), $left->getLine()), - $left->getLine() + new Twig_Node_Expression_Test_Defined(clone $left, 'defined', new Twig_Node(), $left->getTemplateLine()), + new Twig_Node_Expression_Unary_Not(new Twig_Node_Expression_Test_Null($left, 'null', new Twig_Node(), $left->getTemplateLine()), $left->getTemplateLine()), + $left->getTemplateLine() ); parent::__construct($test, $left, $right, $lineno); } + + public function compile(Twig_Compiler $compiler) + { + /* + * This optimizes only one case. PHP 7 also supports more complex expressions + * that can return null. So, for instance, if log is defined, log("foo") ?? "..." works, + * but log($a["foo"]) ?? "..." does not if $a["foo"] is not defined. More advanced + * cases might be implemented as an optimizer node visitor, but has not been done + * as benefits are probably not worth the added complexity. + */ + if (PHP_VERSION_ID >= 70000 && $this->getNode('expr2') instanceof Twig_Node_Expression_Name) { + $this->getNode('expr2')->setAttribute('always_defined', true); + $compiler + ->raw('((') + ->subcompile($this->getNode('expr2')) + ->raw(') ?? (') + ->subcompile($this->getNode('expr3')) + ->raw('))') + ; + } else { + parent::compile($compiler); + } + } } diff --git a/lib/Twig/lib/Twig/Node/Expression/Test/Defined.php b/lib/Twig/lib/Twig/Node/Expression/Test/Defined.php index 3dc6ff5..9d2d7ca 100644 --- a/lib/Twig/lib/Twig/Node/Expression/Test/Defined.php +++ b/lib/Twig/lib/Twig/Node/Expression/Test/Defined.php @@ -29,12 +29,15 @@ public function __construct(Twig_NodeInterface $node, $name, Twig_NodeInterface $node->setAttribute('is_defined_test', true); } elseif ($node instanceof Twig_Node_Expression_GetAttr) { $node->setAttribute('is_defined_test', true); - $this->changeIgnoreStrictCheck($node); + } elseif ($node instanceof Twig_Node_Expression_BlockReference) { + $node->setAttribute('is_defined_test', true); + } elseif ($node instanceof Twig_Node_Expression_Function && 'constant' === $node->getAttribute('name')) { + $node->setAttribute('is_defined_test', true); } elseif ($node instanceof Twig_Node_Expression_Constant || $node instanceof Twig_Node_Expression_Array) { - $node = new Twig_Node_Expression_Constant(true, $node->getLine()); + $node = new Twig_Node_Expression_Constant(true, $node->getTemplateLine()); } else { - throw new Twig_Error_Syntax('The "defined" test only works with simple variables.', $this->getLine()); + throw new Twig_Error_Syntax('The "defined" test only works with simple variables.', $this->getTemplateLine()); } parent::__construct($node, $name, $arguments, $lineno); diff --git a/lib/Twig/lib/Twig/Node/Import.php b/lib/Twig/lib/Twig/Node/Import.php index f69a747..507fb66 100644 --- a/lib/Twig/lib/Twig/Node/Import.php +++ b/lib/Twig/lib/Twig/Node/Import.php @@ -37,9 +37,9 @@ public function compile(Twig_Compiler $compiler) ->raw('$this->loadTemplate(') ->subcompile($this->getNode('expr')) ->raw(', ') - ->repr($this->getFilename()) + ->repr($this->getTemplateName()) ->raw(', ') - ->repr($this->getLine()) + ->repr($this->getTemplateLine()) ->raw(')') ; } diff --git a/lib/Twig/lib/Twig/Node/Include.php b/lib/Twig/lib/Twig/Node/Include.php index bc80dc4..714c991 100644 --- a/lib/Twig/lib/Twig/Node/Include.php +++ b/lib/Twig/lib/Twig/Node/Include.php @@ -64,9 +64,9 @@ protected function addGetTemplate(Twig_Compiler $compiler) ->write('$this->loadTemplate(') ->subcompile($this->getNode('expr')) ->raw(', ') - ->repr($this->getFilename()) + ->repr($this->getTemplateName()) ->raw(', ') - ->repr($this->getLine()) + ->repr($this->getTemplateLine()) ->raw(')') ; } diff --git a/lib/Twig/lib/Twig/Node/Macro.php b/lib/Twig/lib/Twig/Node/Macro.php index c186cd0..2d5226a 100644 --- a/lib/Twig/lib/Twig/Node/Macro.php +++ b/lib/Twig/lib/Twig/Node/Macro.php @@ -22,7 +22,7 @@ public function __construct($name, Twig_NodeInterface $body, Twig_NodeInterface { foreach ($arguments as $argumentName => $argument) { if (self::VARARGS_NAME === $argumentName) { - throw new Twig_Error_Syntax(sprintf('The argument "%s" in macro "%s" cannot be defined because the variable "%s" is reserved for arbitrary arguments.', self::VARARGS_NAME, $name, self::VARARGS_NAME), $argument->getLine()); + throw new Twig_Error_Syntax(sprintf('The argument "%s" in macro "%s" cannot be defined because the variable "%s" is reserved for arbitrary arguments.', self::VARARGS_NAME, $name, self::VARARGS_NAME), $argument->getTemplateLine()); } } @@ -70,7 +70,7 @@ public function compile(Twig_Compiler $compiler) foreach ($this->getNode('arguments') as $name => $default) { $compiler - ->addIndentation() + ->write('') ->string($name) ->raw(' => $__'.$name.'__') ->raw(",\n") @@ -78,7 +78,7 @@ public function compile(Twig_Compiler $compiler) } $compiler - ->addIndentation() + ->write('') ->string(self::VARARGS_NAME) ->raw(' => ') ; diff --git a/lib/Twig/lib/Twig/Node/Module.php b/lib/Twig/lib/Twig/Node/Module.php index 9da9e54..fe7da34 100644 --- a/lib/Twig/lib/Twig/Node/Module.php +++ b/lib/Twig/lib/Twig/Node/Module.php @@ -21,8 +21,17 @@ */ class Twig_Node_Module extends Twig_Node { - public function __construct(Twig_NodeInterface $body, Twig_Node_Expression $parent = null, Twig_NodeInterface $blocks, Twig_NodeInterface $macros, Twig_NodeInterface $traits, $embeddedTemplates, $filename, $source = '') + private $source; + + public function __construct(Twig_NodeInterface $body, Twig_Node_Expression $parent = null, Twig_NodeInterface $blocks, Twig_NodeInterface $macros, Twig_NodeInterface $traits, $embeddedTemplates, $name, $source = '') { + if (!$name instanceof Twig_Source) { + @trigger_error(sprintf('Passing a string as the $name argument of %s() is deprecated since version 1.27. Pass a Twig_Source instance instead.', __METHOD__), E_USER_DEPRECATED); + $this->source = new Twig_Source($source, $name); + } else { + $this->source = $name; + } + $nodes = array( 'body' => $body, 'blocks' => $blocks, @@ -40,11 +49,16 @@ public function __construct(Twig_NodeInterface $body, Twig_Node_Expression $pare // embedded templates are set as attributes so that they are only visited once by the visitors parent::__construct($nodes, array( - 'source' => $source, - 'filename' => $filename, + // source to be remove in 2.0 + 'source' => $this->source->getCode(), + // filename to be remove in 2.0 (use getTemplateName() instead) + 'filename' => $this->source->getName(), 'index' => null, 'embedded_templates' => $embeddedTemplates, ), 1); + + // populate the template name of all node children + $this->setTemplateName($this->source->getName()); } public function setIndex($index) @@ -96,6 +110,8 @@ protected function compileTemplate(Twig_Compiler $compiler) $this->compileGetSource($compiler); + $this->compileGetSourceContext($compiler); + $this->compileClassFooter($compiler); } @@ -120,9 +136,9 @@ protected function compileGetParent(Twig_Compiler $compiler) ->raw('$this->loadTemplate(') ->subcompile($parent) ->raw(', ') - ->repr($this->getAttribute('filename')) + ->repr($this->source->getName()) ->raw(', ') - ->repr($parent->getLine()) + ->repr($parent->getTemplateLine()) ->raw(')') ; } @@ -138,9 +154,9 @@ protected function compileClassHeader(Twig_Compiler $compiler) { $compiler ->write("\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'), $this->getAttribute('index'))) + // if the template name contains */, add a blank to avoid a PHP parse error + ->write('/* '.str_replace('*/', '* /', $this->source->getName())." */\n") + ->write('class '.$compiler->getEnvironment()->getTemplateClass($this->source->getName(), $this->getAttribute('index'))) ->raw(sprintf(" extends %s\n", $compiler->getEnvironment()->getBaseTemplateClass())) ->write("{\n") ->indent() @@ -165,9 +181,9 @@ protected function compileConstructor(Twig_Compiler $compiler) ->write('$this->parent = $this->loadTemplate(') ->subcompile($parent) ->raw(', ') - ->repr($this->getAttribute('filename')) + ->repr($this->source->getName()) ->raw(', ') - ->repr($parent->getLine()) + ->repr($parent->getTemplateLine()) ->raw(");\n") ; } @@ -323,7 +339,7 @@ protected function compileGetTemplateName(Twig_Compiler $compiler) ->write("public function getTemplateName()\n", "{\n") ->indent() ->write('return ') - ->repr($this->getAttribute('filename')) + ->repr($this->source->getName()) ->raw(";\n") ->outdent() ->write("}\n\n") @@ -396,11 +412,29 @@ protected function compileDebugInfo(Twig_Compiler $compiler) protected function compileGetSource(Twig_Compiler $compiler) { $compiler + ->write("/** @deprecated since 1.27 (to be removed in 2.0). Use getSourceContext() instead */\n") ->write("public function getSource()\n", "{\n") ->indent() - ->write('return ') - ->string($this->getAttribute('source')) - ->raw(";\n") + ->write("@trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use getSourceContext() instead.', E_USER_DEPRECATED);\n\n") + ->write('return $this->getSourceContext()->getCode();') + ->raw("\n") + ->outdent() + ->write("}\n\n") + ; + } + + protected function compileGetSourceContext(Twig_Compiler $compiler) + { + $compiler + ->write("public function getSourceContext()\n", "{\n") + ->indent() + ->write('return new Twig_Source(') + ->string($compiler->getEnvironment()->isDebug() ? $this->source->getCode() : '') + ->raw(', ') + ->string($this->source->getName()) + ->raw(', ') + ->string($this->source->getPath()) + ->raw(");\n") ->outdent() ->write("}\n") ; @@ -413,9 +447,9 @@ protected function compileLoadTemplate(Twig_Compiler $compiler, $node, $var) ->write(sprintf('%s = $this->loadTemplate(', $var)) ->subcompile($node) ->raw(', ') - ->repr($this->getAttribute('filename')) + ->repr($node->getTemplateName()) ->raw(', ') - ->repr($node->getLine()) + ->repr($node->getTemplateLine()) ->raw(");\n") ; } else { diff --git a/lib/Twig/lib/Twig/Node/SandboxedPrint.php b/lib/Twig/lib/Twig/Node/SandboxedPrint.php index 3aaafb4..78cc45a 100644 --- a/lib/Twig/lib/Twig/Node/SandboxedPrint.php +++ b/lib/Twig/lib/Twig/Node/SandboxedPrint.php @@ -36,11 +36,9 @@ public function compile(Twig_Compiler $compiler) * * This is mostly needed when another visitor adds filters (like the escaper one). * - * @param Twig_Node $node A Node - * * @return Twig_Node */ - protected function removeNodeFilter($node) + protected function removeNodeFilter(Twig_Node $node) { if ($node instanceof Twig_Node_Expression_Filter) { return $this->removeNodeFilter($node->getNode('node')); diff --git a/lib/Twig/lib/Twig/Node/Set.php b/lib/Twig/lib/Twig/Node/Set.php index e5a6603..b77b8b6 100644 --- a/lib/Twig/lib/Twig/Node/Set.php +++ b/lib/Twig/lib/Twig/Node/Set.php @@ -30,7 +30,7 @@ public function __construct($capture, Twig_NodeInterface $names, Twig_NodeInterf $values = $this->getNode('values'); if ($values instanceof Twig_Node_Text) { - $this->setNode('values', new Twig_Node_Expression_Constant($values->getAttribute('data'), $values->getLine())); + $this->setNode('values', new Twig_Node_Expression_Constant($values->getAttribute('data'), $values->getTemplateLine())); $this->setAttribute('capture', false); } } diff --git a/lib/Twig/lib/Twig/Node/With.php b/lib/Twig/lib/Twig/Node/With.php new file mode 100644 index 0000000..17953e8 --- /dev/null +++ b/lib/Twig/lib/Twig/Node/With.php @@ -0,0 +1,62 @@ + + */ +class Twig_Node_With extends Twig_Node +{ + public function __construct(Twig_Node $body, Twig_Node $variables = null, $only = false, $lineno, $tag = null) + { + $nodes = array('body' => $body); + if (null !== $variables) { + $nodes['variables'] = $variables; + } + + parent::__construct($nodes, array('only' => (bool) $only), $lineno, $tag); + } + + public function compile(Twig_Compiler $compiler) + { + $compiler->addDebugInfo($this); + + if ($this->hasNode('variables')) { + $varsName = $compiler->getVarName(); + $compiler + ->write(sprintf('$%s = ', $varsName)) + ->subcompile($this->getNode('variables')) + ->raw(";\n") + ->write(sprintf("if (!is_array(\$%s)) {\n", $varsName)) + ->indent() + ->write("throw new Twig_Error_Runtime('Variables passed to the \"with\" tag must be a hash.');\n") + ->outdent() + ->write("}\n") + ; + + if ($this->getAttribute('only')) { + $compiler->write("\$context = array('_parent' => \$context);\n"); + } else { + $compiler->write("\$context['_parent'] = \$context;\n"); + } + + $compiler->write(sprintf("\$context = array_merge(\$context, \$%s);\n", $varsName)); + } else { + $compiler->write("\$context['_parent'] = \$context;\n"); + } + + $compiler + ->subcompile($this->getNode('body')) + ->write("\$context = \$context['_parent'];\n") + ; + } +} diff --git a/lib/Twig/lib/Twig/NodeInterface.php b/lib/Twig/lib/Twig/NodeInterface.php index 8077349..80d9993 100644 --- a/lib/Twig/lib/Twig/NodeInterface.php +++ b/lib/Twig/lib/Twig/NodeInterface.php @@ -20,11 +20,12 @@ interface Twig_NodeInterface extends Countable, IteratorAggregate { /** * Compiles the node to PHP. - * - * @param Twig_Compiler $compiler A Twig_Compiler instance */ public function compile(Twig_Compiler $compiler); + /** + * @deprecated since 1.27 (to be removed in 2.0) + */ public function getLine(); public function getNodeTag(); diff --git a/lib/Twig/lib/Twig/NodeTraverser.php b/lib/Twig/lib/Twig/NodeTraverser.php index 00f7b54..85c663b 100644 --- a/lib/Twig/lib/Twig/NodeTraverser.php +++ b/lib/Twig/lib/Twig/NodeTraverser.php @@ -22,10 +22,8 @@ class Twig_NodeTraverser protected $visitors = array(); /** - * Constructor. - * - * @param Twig_Environment $env A Twig_Environment instance - * @param Twig_NodeVisitorInterface[] $visitors An array of Twig_NodeVisitorInterface instances + * @param Twig_Environment $env + * @param Twig_NodeVisitorInterface[] $visitors */ public function __construct(Twig_Environment $env, array $visitors = array()) { @@ -35,11 +33,6 @@ public function __construct(Twig_Environment $env, array $visitors = array()) } } - /** - * Adds a visitor. - * - * @param Twig_NodeVisitorInterface $visitor A Twig_NodeVisitorInterface instance - */ public function addVisitor(Twig_NodeVisitorInterface $visitor) { if (!isset($this->visitors[$visitor->getPriority()])) { @@ -52,8 +45,6 @@ public function addVisitor(Twig_NodeVisitorInterface $visitor) /** * Traverses a node and calls the registered visitors. * - * @param Twig_NodeInterface $node A Twig_NodeInterface instance - * * @return Twig_NodeInterface */ public function traverse(Twig_NodeInterface $node) diff --git a/lib/Twig/lib/Twig/NodeVisitor/Escaper.php b/lib/Twig/lib/Twig/NodeVisitor/Escaper.php index a21ade7..28a9792 100644 --- a/lib/Twig/lib/Twig/NodeVisitor/Escaper.php +++ b/lib/Twig/lib/Twig/NodeVisitor/Escaper.php @@ -34,7 +34,7 @@ public function __construct() protected function doEnterNode(Twig_Node $node, Twig_Environment $env) { if ($node instanceof Twig_Node_Module) { - if ($env->hasExtension('Twig_Extension_Escaper') && $defaultStrategy = $env->getExtension('Twig_Extension_Escaper')->getDefaultStrategy($node->getAttribute('filename'))) { + if ($env->hasExtension('Twig_Extension_Escaper') && $defaultStrategy = $env->getExtension('Twig_Extension_Escaper')->getDefaultStrategy($node->getTemplateName())) { $this->defaultStrategy = $defaultStrategy; } $this->safeVars = array(); @@ -90,7 +90,7 @@ protected function escapePrintNode(Twig_Node_Print $node, Twig_Environment $env, return new $class( $this->getEscaperFilter($type, $expression), - $node->getLine() + $node->getTemplateLine() ); } @@ -142,7 +142,7 @@ protected function needEscaping(Twig_Environment $env) protected function getEscaperFilter($type, Twig_NodeInterface $node) { - $line = $node->getLine(); + $line = $node->getTemplateLine(); $name = new Twig_Node_Expression_Constant('escape', $line); $args = new Twig_Node(array(new Twig_Node_Expression_Constant((string) $type, $line), new Twig_Node_Expression_Constant(null, $line), new Twig_Node_Expression_Constant(true, $line))); diff --git a/lib/Twig/lib/Twig/NodeVisitor/Optimizer.php b/lib/Twig/lib/Twig/NodeVisitor/Optimizer.php index 882d2bb..c0411cb 100644 --- a/lib/Twig/lib/Twig/NodeVisitor/Optimizer.php +++ b/lib/Twig/lib/Twig/NodeVisitor/Optimizer.php @@ -34,8 +34,6 @@ class Twig_NodeVisitor_Optimizer extends Twig_BaseNodeVisitor protected $inABody = false; /** - * Constructor. - * * @param int $optimizers The optimizer mode */ public function __construct($optimizers = -1) @@ -97,7 +95,7 @@ protected function doLeaveNode(Twig_Node $node, Twig_Environment $env) if (!$expression && get_class($node) !== 'Twig_Node' && $prependedNodes = array_shift($this->prependedNodes)) { $nodes = array(); foreach (array_unique($prependedNodes) as $name) { - $nodes[] = new Twig_Node_SetTemp($name, $node->getLine()); + $nodes[] = new Twig_Node_SetTemp($name, $node->getTemplateLine()); } $nodes[] = $node; @@ -114,7 +112,7 @@ protected function optimizeVariables(Twig_NodeInterface $node, Twig_Environment if ('Twig_Node_Expression_Name' === get_class($node) && $node->isSimple()) { $this->prependedNodes[0][] = $node->getAttribute('name'); - return new Twig_Node_Expression_TempName($node->getAttribute('name'), $node->getLine()); + return new Twig_Node_Expression_TempName($node->getAttribute('name'), $node->getTemplateLine()); } return $node; @@ -127,9 +125,6 @@ protected function optimizeVariables(Twig_NodeInterface $node, Twig_Environment * * * "echo $this->render(Parent)Block()" with "$this->display(Parent)Block()" * - * @param Twig_NodeInterface $node A Node - * @param Twig_Environment $env The current Twig environment - * * @return Twig_NodeInterface */ protected function optimizePrintNode(Twig_NodeInterface $node, Twig_Environment $env) @@ -138,13 +133,14 @@ protected function optimizePrintNode(Twig_NodeInterface $node, Twig_Environment return $node; } + $exprNode = $node->getNode('expr'); if ( - $node->getNode('expr') instanceof Twig_Node_Expression_BlockReference || - $node->getNode('expr') instanceof Twig_Node_Expression_Parent + $exprNode instanceof Twig_Node_Expression_BlockReference || + $exprNode instanceof Twig_Node_Expression_Parent ) { - $node->getNode('expr')->setAttribute('output', true); + $exprNode->setAttribute('output', true); - return $node->getNode('expr'); + return $exprNode; } return $node; @@ -153,9 +149,6 @@ protected function optimizePrintNode(Twig_NodeInterface $node, Twig_Environment /** * Removes "raw" filters. * - * @param Twig_NodeInterface $node A Node - * @param Twig_Environment $env The current Twig environment - * * @return Twig_NodeInterface */ protected function optimizeRawFilter(Twig_NodeInterface $node, Twig_Environment $env) @@ -169,9 +162,6 @@ protected function optimizeRawFilter(Twig_NodeInterface $node, Twig_Environment /** * Optimizes "for" tag by removing the "loop" variable creation whenever possible. - * - * @param Twig_NodeInterface $node A Node - * @param Twig_Environment $env The current Twig environment */ protected function enterOptimizeFor(Twig_NodeInterface $node, Twig_Environment $env) { @@ -236,9 +226,6 @@ protected function enterOptimizeFor(Twig_NodeInterface $node, Twig_Environment $ /** * Optimizes "for" tag by removing the "loop" variable creation whenever possible. - * - * @param Twig_NodeInterface $node A Node - * @param Twig_Environment $env The current Twig environment */ protected function leaveOptimizeFor(Twig_NodeInterface $node, Twig_Environment $env) { diff --git a/lib/Twig/lib/Twig/NodeVisitor/Sandbox.php b/lib/Twig/lib/Twig/NodeVisitor/Sandbox.php index 7f1b913..a5e23c2 100644 --- a/lib/Twig/lib/Twig/NodeVisitor/Sandbox.php +++ b/lib/Twig/lib/Twig/NodeVisitor/Sandbox.php @@ -51,7 +51,7 @@ protected function doEnterNode(Twig_Node $node, Twig_Environment $env) // wrap print to check __toString() calls if ($node instanceof Twig_Node_Print) { - return new Twig_Node_SandboxedPrint($node->getNode('expr'), $node->getLine(), $node->getNodeTag()); + return new Twig_Node_SandboxedPrint($node->getNode('expr'), $node->getTemplateLine(), $node->getNodeTag()); } } diff --git a/lib/Twig/lib/Twig/NodeVisitorInterface.php b/lib/Twig/lib/Twig/NodeVisitorInterface.php index f276163..a12de77 100644 --- a/lib/Twig/lib/Twig/NodeVisitorInterface.php +++ b/lib/Twig/lib/Twig/NodeVisitorInterface.php @@ -19,9 +19,6 @@ 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 - * * @return Twig_NodeInterface The modified node */ public function enterNode(Twig_NodeInterface $node, Twig_Environment $env); @@ -29,9 +26,6 @@ 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 - * * @return Twig_NodeInterface|false The modified node or false if the node must be removed */ public function leaveNode(Twig_NodeInterface $node, Twig_Environment $env); diff --git a/lib/Twig/lib/Twig/Parser.php b/lib/Twig/lib/Twig/Parser.php index 1f66cfa..d4e4560 100644 --- a/lib/Twig/lib/Twig/Parser.php +++ b/lib/Twig/lib/Twig/Parser.php @@ -32,18 +32,18 @@ class Twig_Parser implements Twig_ParserInterface protected $traits; protected $embeddedTemplates = array(); - /** - * Constructor. - * - * @param Twig_Environment $env A Twig_Environment instance - */ public function __construct(Twig_Environment $env) { $this->env = $env; } + /** + * @deprecated since 1.27 (to be removed in 2.0) + */ public function getEnvironment() { + @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0.', E_USER_DEPRECATED); + return $this->env; } @@ -52,9 +52,14 @@ public function getVarName() return sprintf('__internal_%s', hash('sha256', uniqid(mt_rand(), true), false)); } + /** + * @deprecated since 1.27 (to be removed in 2.0). Use $parser->getStream()->getSourceContext()->getPath() instead. + */ public function getFilename() { - return $this->stream->getFilename(); + @trigger_error(sprintf('The "%s" method is deprecated since version 1.27 and will be removed in 2.0. Use $parser->getStream()->getSourceContext()->getPath() instead.', __METHOD__), E_USER_DEPRECATED); + + return $this->stream->getSourceContext()->getName(); } /** @@ -64,6 +69,7 @@ public function parse(Twig_TokenStream $stream, $test = null, $dropNeedle = fals { // push all variables into the stack to keep the current state of the parser // using get_object_vars() instead of foreach would lead to https://bugs.php.net/71336 + // This hack can be removed when min version if PHP 7.0 $vars = array(); foreach ($this as $k => $v) { $vars[$k] = $v; @@ -84,7 +90,7 @@ public function parse(Twig_TokenStream $stream, $test = null, $dropNeedle = fals } if (null === $this->expressionParser) { - $this->expressionParser = new Twig_ExpressionParser($this, $this->env->getUnaryOperators(), $this->env->getBinaryOperators()); + $this->expressionParser = new Twig_ExpressionParser($this, $this->env); } $this->stream = $stream; @@ -103,8 +109,8 @@ public function parse(Twig_TokenStream $stream, $test = null, $dropNeedle = fals $body = new Twig_Node(); } } catch (Twig_Error_Syntax $e) { - if (!$e->getTemplateFile()) { - $e->setTemplateFile($this->getFilename()); + if (!$e->getTemplateName()) { + $e->setTemplateName($this->stream->getSourceContext()->getName()); } if (!$e->getTemplateLine()) { @@ -114,7 +120,7 @@ public function parse(Twig_TokenStream $stream, $test = null, $dropNeedle = fals throw $e; } - $node = new Twig_Node_Module(new Twig_Node_Body(array($body)), $this->parent, new Twig_Node($this->blocks), new Twig_Node($this->macros), new Twig_Node($this->traits), $this->embeddedTemplates, $this->getFilename(), $stream->getSource()); + $node = new Twig_Node_Module(new Twig_Node_Body(array($body)), $this->parent, new Twig_Node($this->blocks), new Twig_Node($this->macros), new Twig_Node($this->traits), $this->embeddedTemplates, $stream->getSourceContext()); $traverser = new Twig_NodeTraverser($this->env, $this->visitors); @@ -151,7 +157,7 @@ public function subparse($test, $dropNeedle = false) $token = $this->getCurrentToken(); if ($token->getType() !== Twig_Token::NAME_TYPE) { - throw new Twig_Error_Syntax('A block must start with a tag name.', $token->getLine(), $this->getFilename()); + throw new Twig_Error_Syntax('A block must start with a tag name.', $token->getLine(), $this->stream->getSourceContext()->getName()); } if (null !== $test && call_user_func($test, $token)) { @@ -169,13 +175,13 @@ public function subparse($test, $dropNeedle = false) $subparser = $this->handlers->getTokenParser($token->getValue()); if (null === $subparser) { if (null !== $test) { - $e = new Twig_Error_Syntax(sprintf('Unexpected "%s" tag', $token->getValue()), $token->getLine(), $this->getFilename()); + $e = new Twig_Error_Syntax(sprintf('Unexpected "%s" tag', $token->getValue()), $token->getLine(), $this->stream->getSourceContext()->getName()); if (is_array($test) && isset($test[0]) && $test[0] instanceof Twig_TokenParserInterface) { $e->appendMessage(sprintf(' (expecting closing tag for the "%s" tag defined near line %s).', $test[0]->getTag(), $lineno)); } } else { - $e = new Twig_Error_Syntax(sprintf('Unknown "%s" tag.', $token->getValue()), $token->getLine(), $this->getFilename()); + $e = new Twig_Error_Syntax(sprintf('Unknown "%s" tag.', $token->getValue()), $token->getLine(), $this->stream->getSourceContext()->getName()); $e->addSuggestions($token->getValue(), array_keys($this->env->getTags())); } @@ -191,7 +197,7 @@ public function subparse($test, $dropNeedle = false) break; default: - throw new Twig_Error_Syntax('Lexer or parser ended up in unsupported state.', 0, $this->getFilename()); + throw new Twig_Error_Syntax('Lexer or parser ended up in unsupported state.', 0, $this->stream->getSourceContext()->getName()); } } @@ -202,13 +208,23 @@ public function subparse($test, $dropNeedle = false) return new Twig_Node($rv, array(), $lineno); } + /** + * @deprecated since 1.27 (to be removed in 2.0) + */ public function addHandler($name, $class) { + @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0.', E_USER_DEPRECATED); + $this->handlers[$name] = $class; } + /** + * @deprecated since 1.27 (to be removed in 2.0) + */ public function addNodeVisitor(Twig_NodeVisitorInterface $visitor) { + @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0.', E_USER_DEPRECATED); + $this->visitors[] = $visitor; } @@ -244,7 +260,7 @@ public function getBlock($name) public function setBlock($name, Twig_Node_Block $value) { - $this->blocks[$name] = new Twig_Node_Body(array($value), array(), $value->getLine()); + $this->blocks[$name] = new Twig_Node_Body(array($value), array(), $value->getTemplateLine()); } public function hasMacro($name) @@ -255,7 +271,7 @@ public function hasMacro($name) public function setMacro($name, Twig_Node_Macro $node) { if ($this->isReservedMacroName($name)) { - throw new Twig_Error_Syntax(sprintf('"%s" cannot be used as a macro name as it is a reserved keyword.', $name), $node->getLine(), $this->getFilename()); + throw new Twig_Error_Syntax(sprintf('"%s" cannot be used as a macro name as it is a reserved keyword.', $name), $node->getTemplateLine(), $this->stream->getSourceContext()->getName()); } $this->macros[$name] = $node; @@ -325,9 +341,7 @@ public function popLocalScope() } /** - * Gets the expression parser. - * - * @return Twig_ExpressionParser The expression parser + * @return Twig_ExpressionParser */ public function getExpressionParser() { @@ -345,9 +359,7 @@ public function setParent($parent) } /** - * Gets the token stream. - * - * @return Twig_TokenStream The token stream + * @return Twig_TokenStream */ public function getStream() { @@ -355,9 +367,7 @@ public function getStream() } /** - * Gets the current token. - * - * @return Twig_Token The current token + * @return Twig_Token */ public function getCurrentToken() { @@ -373,10 +383,10 @@ protected function filterBodyNodes(Twig_NodeInterface $node) (!$node instanceof Twig_Node_Text && !$node instanceof Twig_Node_BlockReference && $node instanceof Twig_NodeOutputInterface) ) { if (false !== strpos((string) $node, chr(0xEF).chr(0xBB).chr(0xBF))) { - throw new Twig_Error_Syntax('A template that extends another one cannot start with a byte order mark (BOM); it must be removed.', $node->getLine(), $this->getFilename()); + throw new Twig_Error_Syntax('A template that extends another one cannot start with a byte order mark (BOM); it must be removed.', $node->getTemplateLine(), $this->stream->getSourceContext()->getName()); } - throw new Twig_Error_Syntax('A template that extends another one cannot include contents outside Twig blocks. Did you forget to put the contents inside a {% block %} tag?', $node->getLine(), $this->getFilename()); + throw new Twig_Error_Syntax('A template that extends another one cannot include contents outside Twig blocks. Did you forget to put the contents inside a {% block %} tag?', $node->getTemplateLine(), $this->stream->getSourceContext()->getName()); } // bypass "set" nodes as they "capture" the output diff --git a/lib/Twig/lib/Twig/ParserInterface.php b/lib/Twig/lib/Twig/ParserInterface.php index 8e7cc0a..73da61b 100644 --- a/lib/Twig/lib/Twig/ParserInterface.php +++ b/lib/Twig/lib/Twig/ParserInterface.php @@ -21,9 +21,7 @@ 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 + * @return Twig_Node_Module * * @throws Twig_Error_Syntax When the token stream is syntactically or semantically wrong */ diff --git a/lib/Twig/lib/Twig/Profiler/NodeVisitor/Profiler.php b/lib/Twig/lib/Twig/Profiler/NodeVisitor/Profiler.php index 4b0baa8..099a44f 100644 --- a/lib/Twig/lib/Twig/Profiler/NodeVisitor/Profiler.php +++ b/lib/Twig/lib/Twig/Profiler/NodeVisitor/Profiler.php @@ -36,7 +36,7 @@ protected function doLeaveNode(Twig_Node $node, Twig_Environment $env) { if ($node instanceof Twig_Node_Module) { $varName = $this->getVarName(); - $node->setNode('display_start', new Twig_Node(array(new Twig_Profiler_Node_EnterProfile($this->extensionName, Twig_Profiler_Profile::TEMPLATE, $node->getAttribute('filename'), $varName), $node->getNode('display_start')))); + $node->setNode('display_start', new Twig_Node(array(new Twig_Profiler_Node_EnterProfile($this->extensionName, Twig_Profiler_Profile::TEMPLATE, $node->getTemplateName(), $varName), $node->getNode('display_start')))); $node->setNode('display_end', new Twig_Node(array(new Twig_Profiler_Node_LeaveProfile($varName), $node->getNode('display_end')))); } elseif ($node instanceof Twig_Node_Block) { $varName = $this->getVarName(); diff --git a/lib/Twig/lib/Twig/Sandbox/SecurityNotAllowedMethodError.php b/lib/Twig/lib/Twig/Sandbox/SecurityNotAllowedMethodError.php new file mode 100644 index 0000000..5b352d9 --- /dev/null +++ b/lib/Twig/lib/Twig/Sandbox/SecurityNotAllowedMethodError.php @@ -0,0 +1,38 @@ + + */ +class Twig_Sandbox_SecurityNotAllowedMethodError extends Twig_Sandbox_SecurityError +{ + private $className; + private $methodName; + + public function __construct($message, $className, $methodName, $lineno = -1, $filename = null, Exception $previous = null) + { + parent::__construct($message, $lineno, $filename, $previous); + $this->className = $className; + $this->methodName = $methodName; + } + + public function getClassName() + { + return $this->className; + } + + public function getMethodName() + { + return $this->methodName; + } +} diff --git a/lib/Twig/lib/Twig/Sandbox/SecurityNotAllowedPropertyError.php b/lib/Twig/lib/Twig/Sandbox/SecurityNotAllowedPropertyError.php new file mode 100644 index 0000000..8b4cbc3 --- /dev/null +++ b/lib/Twig/lib/Twig/Sandbox/SecurityNotAllowedPropertyError.php @@ -0,0 +1,38 @@ + + */ +class Twig_Sandbox_SecurityNotAllowedPropertyError extends Twig_Sandbox_SecurityError +{ + private $className; + private $propertyName; + + public function __construct($message, $className, $propertyName, $lineno = -1, $filename = null, Exception $previous = null) + { + parent::__construct($message, $lineno, $filename, $previous); + $this->className = $className; + $this->propertyName = $propertyName; + } + + public function getClassName() + { + return $this->className; + } + + public function getPropertyName() + { + return $this->propertyName; + } +} diff --git a/lib/Twig/lib/Twig/Sandbox/SecurityPolicy.php b/lib/Twig/lib/Twig/Sandbox/SecurityPolicy.php index c4dd03d..ed4ba8e 100644 --- a/lib/Twig/lib/Twig/Sandbox/SecurityPolicy.php +++ b/lib/Twig/lib/Twig/Sandbox/SecurityPolicy.php @@ -97,7 +97,8 @@ public function checkMethodAllowed($obj, $method) } if (!$allowed) { - throw new Twig_Sandbox_SecurityError(sprintf('Calling "%s" method on a "%s" object is not allowed.', $method, get_class($obj))); + $class = get_class($obj); + throw new Twig_Sandbox_SecurityNotAllowedMethodError(sprintf('Calling "%s" method on a "%s" object is not allowed.', $method, $class), $class, $method); } } @@ -113,7 +114,8 @@ public function checkPropertyAllowed($obj, $property) } if (!$allowed) { - throw new Twig_Sandbox_SecurityError(sprintf('Calling "%s" property on a "%s" object is not allowed.', $property, get_class($obj))); + $class = get_class($obj); + throw new Twig_Sandbox_SecurityNotAllowedPropertyError(sprintf('Calling "%s" property on a "%s" object is not allowed.', $property, $class), $class, $property); } } } diff --git a/lib/Twig/lib/Twig/Source.php b/lib/Twig/lib/Twig/Source.php new file mode 100644 index 0000000..b3fb0ce --- /dev/null +++ b/lib/Twig/lib/Twig/Source.php @@ -0,0 +1,49 @@ + + */ +class Twig_Source +{ + private $code; + private $name; + private $path; + + /** + * @param string $code The template source code + * @param string $name The template logical name + * @param string $path The filesystem path of the template if any + */ + public function __construct($code, $name, $path = '') + { + $this->code = $code; + $this->name = $name; + $this->path = $path; + } + + public function getCode() + { + return $this->code; + } + + public function getName() + { + return $this->name; + } + + public function getPath() + { + return $this->path; + } +} diff --git a/lib/Twig/lib/Twig/SourceContextLoaderInterface.php b/lib/Twig/lib/Twig/SourceContextLoaderInterface.php new file mode 100644 index 0000000..bb4115e --- /dev/null +++ b/lib/Twig/lib/Twig/SourceContextLoaderInterface.php @@ -0,0 +1,31 @@ + + * + * @deprecated since 1.27 (to be removed in 3.0) + */ +interface Twig_SourceContextLoaderInterface +{ + /** + * Returns the source context for a given template logical name. + * + * @param string $name The template logical name + * + * @return Twig_Source + * + * @throws Twig_Error_Loader When $name is not found + */ + public function getSourceContext($name); +} diff --git a/lib/Twig/lib/Twig/Template.php b/lib/Twig/lib/Twig/Template.php index 17e78aa..54f6b0d 100644 --- a/lib/Twig/lib/Twig/Template.php +++ b/lib/Twig/lib/Twig/Template.php @@ -13,10 +13,19 @@ /** * Default base class for compiled templates. * + * This class is an implementation detail of how template compilation currently + * works, which might change. It should never be used directly. Use $twig->load() + * instead, which returns an instance of Twig_TemplateWrapper. + * * @author Fabien Potencier + * + * @internal */ abstract class Twig_Template implements Twig_TemplateInterface { + /** + * @internal + */ protected static $cache = array(); protected $parent; @@ -25,11 +34,6 @@ abstract class Twig_Template implements Twig_TemplateInterface protected $blocks = array(); protected $traits = array(); - /** - * Constructor. - * - * @param Twig_Environment $env A Twig_Environment instance - */ public function __construct(Twig_Environment $env) { $this->env = $env; @@ -49,18 +53,35 @@ abstract public function getTemplateName(); * * @internal */ - abstract public function getDebugInfo(); + public function getDebugInfo() + { + return array(); + } /** * Returns the template source code. * * @return string The template source code + * + * @deprecated since 1.27 (to be removed in 2.0). Use getSourceContext() instead */ public function getSource() { + @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use getSourceContext() instead.', E_USER_DEPRECATED); + return ''; } + /** + * Returns information about the original template source code. + * + * @return Twig_Source + */ + public function getSourceContext() + { + return new Twig_Source('', $this->getTemplateName()); + } + /** * @deprecated since 1.20 (to be removed in 2.0) */ @@ -104,7 +125,7 @@ public function getParent(array $context) $this->parents[$parent] = $this->loadTemplate($parent); } } catch (Twig_Error_Loader $e) { - $e->setTemplateFile(null); + $e->setTemplateName(null); $e->guess(); throw $e; @@ -185,8 +206,8 @@ public function displayBlock($name, array $context, array $blocks = array(), $us try { $template->$block($context, $blocks); } catch (Twig_Error $e) { - if (!$e->getTemplateFile()) { - $e->setTemplateFile($template->getTemplateName()); + if (!$e->getTemplateName()) { + $e->setTemplateName($template->getTemplateName()); } // this is mostly useful for Twig_Error_Loader exceptions @@ -251,44 +272,70 @@ public function renderBlock($name, array $context, array $blocks = array(), $use } /** - * Returns whether a block exists or not. - * - * This method is for internal use only and should never be called - * directly. + * Returns whether a block exists or not in the current context of the template. * - * This method does only return blocks defined in the current template - * or defined in "used" traits. + * This method checks blocks defined in the current template + * or defined in "used" traits or defined in parent templates. * - * It does not return blocks from parent templates as the parent - * template name can be dynamic, which is only known based on the - * current context. - * - * @param string $name The block name + * @param string $name The block name + * @param array $context The context + * @param array $blocks The current set of blocks * * @return bool true if the block exists, false otherwise * * @internal */ - public function hasBlock($name) + public function hasBlock($name, array $context = null, array $blocks = array()) { - return isset($this->blocks[(string) $name]); + if (null === $context) { + @trigger_error('The '.__METHOD__.' method is internal and should never be called; calling it directly is deprecated since version 1.28 and won\'t be possible anymore in 2.0.', E_USER_DEPRECATED); + + return isset($this->blocks[(string) $name]); + } + + if (isset($blocks[$name])) { + return $blocks[$name][0] instanceof self; + } + + if (isset($this->blocks[$name])) { + return true; + } + + if (false !== $parent = $this->getParent($context)) { + return $parent->hasBlock($name, $context); + } + + return false; } /** - * Returns all block names. + * Returns all block names in the current context of the template. * - * This method is for internal use only and should never be called - * directly. + * This method checks blocks defined in the current template + * or defined in "used" traits or defined in parent templates. * - * @return array An array of block names + * @param array $context The context + * @param array $blocks The current set of blocks * - * @see hasBlock + * @return array An array of block names * * @internal */ - public function getBlockNames() + public function getBlockNames(array $context = null, array $blocks = array()) { - return array_keys($this->blocks); + if (null === $context) { + @trigger_error('The '.__METHOD__.' method is internal and should never be called; calling it directly is deprecated since version 1.28 and won\'t be possible anymore in 2.0.', E_USER_DEPRECATED); + + return array_keys($this->blocks); + } + + $names = array_merge(array_keys($blocks), array_keys($this->blocks)); + + if (false !== $parent = $this->getParent($context)) { + $names = array_merge($names, $parent->getBlockNames($context)); + } + + return array_unique($names); } protected function loadTemplate($template, $templateName = null, $line = null, $index = null) @@ -302,10 +349,14 @@ protected function loadTemplate($template, $templateName = null, $line = null, $ return $template; } + if ($template instanceof Twig_TemplateWrapper) { + return $template; + } + return $this->env->loadTemplate($template, $index); } catch (Twig_Error $e) { - if (!$e->getTemplateFile()) { - $e->setTemplateFile($templateName ? $templateName : $this->getTemplateName()); + if (!$e->getTemplateName()) { + $e->setTemplateName($templateName ? $templateName : $this->getTemplateName()); } if ($e->getTemplateLine()) { @@ -330,8 +381,6 @@ protected function loadTemplate($template, $templateName = null, $line = null, $ * * @return array An array of blocks * - * @see hasBlock - * * @internal */ public function getBlocks() @@ -378,8 +427,8 @@ protected function displayWithErrorHandling(array $context, array $blocks = arra try { $this->doDisplay($context, $blocks); } catch (Twig_Error $e) { - if (!$e->getTemplateFile()) { - $e->setTemplateFile($this->getTemplateName()); + if (!$e->getTemplateName()) { + $e->setTemplateName($this->getTemplateName()); } // this is mostly useful for Twig_Error_Loader exceptions @@ -450,6 +499,8 @@ final protected function getContext($context, $item, $ignoreStrictCheck = false) * @return mixed The attribute value, or a Boolean when $isDefinedTest is true, or null when the attribute is not set and $ignoreStrictCheck is true * * @throws Twig_Error_Runtime if the attribute does not exist and Twig is running in strict mode and $isDefinedTest is false + * + * @internal */ protected function getAttribute($object, $item, array $arguments = array(), $type = self::ANY_CALL, $isDefinedTest = false, $ignoreStrictCheck = false) { @@ -457,7 +508,7 @@ protected function getAttribute($object, $item, array $arguments = array(), $typ if (self::METHOD_CALL !== $type) { $arrayItem = is_bool($item) || is_float($item) ? (int) $item : $item; - if ((is_array($object) && array_key_exists($arrayItem, $object)) + if ((is_array($object) && (isset($object[$arrayItem]) || array_key_exists($arrayItem, $object))) || ($object instanceof ArrayAccess && isset($object[$arrayItem])) ) { if ($isDefinedTest) { @@ -538,37 +589,57 @@ protected function getAttribute($object, $item, array $arguments = array(), $typ $class = get_class($object); // object method - if (!isset(self::$cache[$class]['methods'])) { + if (!isset(self::$cache[$class])) { // get_class_methods returns all methods accessible in the scope, but we only want public ones to be accessible in templates if ($object instanceof self) { $ref = new ReflectionClass($class); $methods = array(); foreach ($ref->getMethods(ReflectionMethod::IS_PUBLIC) as $refMethod) { - $methodName = strtolower($refMethod->name); - // Accessing the environment from templates is forbidden to prevent untrusted changes to the environment - if ('getenvironment' !== $methodName) { - $methods[$methodName] = true; + if ('getenvironment' !== strtolower($refMethod->name)) { + $methods[] = $refMethod->name; } } - - self::$cache[$class]['methods'] = $methods; } else { - self::$cache[$class]['methods'] = array_change_key_case(array_flip(get_class_methods($object))); + $methods = get_class_methods($object); + } + // sort values to have consistent behavior, so that "get" methods win precedence over "is" methods + sort($methods); + + $cache = array(); + + foreach ($methods as $method) { + $cache[$method] = $method; + $cache[$lcName = strtolower($method)] = $method; + + if ('g' === $lcName[0] && 0 === strpos($lcName, 'get')) { + $name = substr($method, 3); + $lcName = substr($lcName, 3); + } elseif ('i' === $lcName[0] && 0 === strpos($lcName, 'is')) { + $name = substr($method, 2); + $lcName = substr($lcName, 2); + } else { + continue; + } + + if (!isset($cache[$name])) { + $cache[$name] = $method; + } + if (!isset($cache[$lcName])) { + $cache[$lcName] = $method; + } } + self::$cache[$class] = $cache; } $call = false; - $lcItem = strtolower($item); - if (isset(self::$cache[$class]['methods'][$lcItem])) { - $method = (string) $item; - } elseif (isset(self::$cache[$class]['methods']['get'.$lcItem])) { - $method = 'get'.$item; - } elseif (isset(self::$cache[$class]['methods']['is'.$lcItem])) { - $method = 'is'.$item; - } elseif (isset(self::$cache[$class]['methods']['__call'])) { - $method = (string) $item; + if (isset(self::$cache[$class][$item])) { + $method = self::$cache[$class][$item]; + } elseif (isset(self::$cache[$class][$lcItem = strtolower($item)])) { + $method = self::$cache[$class][$lcItem]; + } elseif (isset(self::$cache[$class]['__call'])) { + $method = $item; $call = true; } else { if ($isDefinedTest) { @@ -579,7 +650,7 @@ protected function getAttribute($object, $item, array $arguments = array(), $typ return; } - throw new Twig_Error_Runtime(sprintf('Neither the property "%1$s" nor one of the methods "%1$s()", "get%1$s()"/"is%1$s()" or "__call()" exist and have public access in class "%2$s".', $item, get_class($object)), -1, $this->getTemplateName()); + throw new Twig_Error_Runtime(sprintf('Neither the property "%1$s" nor one of the methods "%1$s()", "get%1$s()"/"is%1$s()" or "__call()" exist and have public access in class "%2$s".', $item, $class), -1, $this->getTemplateName()); } if ($isDefinedTest) { @@ -593,7 +664,11 @@ protected function getAttribute($object, $item, array $arguments = array(), $typ // Some objects throw exceptions when they have __call, and the method we try // to call is not supported. If ignoreStrictCheck is true, we should return null. try { - $ret = call_user_func_array(array($object, $method), $arguments); + if (!$arguments) { + $ret = $object->$method(); + } else { + $ret = call_user_func_array(array($object, $method), $arguments); + } } catch (BadMethodCallException $e) { if ($call && ($ignoreStrictCheck || !$this->env->isStrictVariables())) { return; @@ -601,9 +676,19 @@ protected function getAttribute($object, $item, array $arguments = array(), $typ throw $e; } - // useful when calling a template method from a template - // this is not supported but unfortunately heavily used in the Symfony profiler + // @deprecated in 1.28 if ($object instanceof Twig_TemplateInterface) { + $self = $object->getTemplateName() === $this->getTemplateName(); + $message = sprintf('Calling "%s" on template "%s" from template "%s" is deprecated since version 1.28 and won\'t be supported anymore in 2.0.', $method, $object->getTemplateName(), $this->getTemplateName()); + if ('renderBlock' === $method || 'displayBlock' === $method) { + $message .= sprintf(' Use block("%s"%s) instead).', $arguments[0], $self ? '' : ', template'); + } elseif ('hasBlock' === $method) { + $message .= sprintf(' Use "block("%s"%s) is defined" instead).', $arguments[0], $self ? '' : ', template'); + } elseif ('render' === $method || 'display' === $method) { + $message .= sprintf(' Use include("%s") instead).', $object->getTemplateName()); + } + @trigger_error($message, E_USER_DEPRECATED); + return $ret === '' ? '' : new Twig_Markup($ret, $this->env->getCharset()); } diff --git a/lib/Twig/lib/Twig/TemplateInterface.php b/lib/Twig/lib/Twig/TemplateInterface.php index 3274640..207b54d 100644 --- a/lib/Twig/lib/Twig/TemplateInterface.php +++ b/lib/Twig/lib/Twig/TemplateInterface.php @@ -42,7 +42,7 @@ public function display(array $context, array $blocks = array()); /** * Returns the bound environment for this template. * - * @return Twig_Environment The current environment + * @return Twig_Environment */ public function getEnvironment(); } diff --git a/lib/Twig/lib/Twig/TemplateWrapper.php b/lib/Twig/lib/Twig/TemplateWrapper.php new file mode 100644 index 0000000..2db61cd --- /dev/null +++ b/lib/Twig/lib/Twig/TemplateWrapper.php @@ -0,0 +1,131 @@ + + */ +final class Twig_TemplateWrapper +{ + private $env; + private $template; + + /** + * This method is for internal use only and should never be called + * directly (use Twig_Environment::load() instead). + * + * @internal + */ + public function __construct(Twig_Environment $env, Twig_Template $template) + { + $this->env = $env; + $this->template = $template; + } + + /** + * Renders the template. + * + * @param array $context An array of parameters to pass to the template + * + * @return string The rendered template + */ + public function render($context = array()) + { + return $this->template->render($context); + } + + /** + * Displays the template. + * + * @param array $context An array of parameters to pass to the template + */ + public function display($context = array()) + { + $this->template->display($context); + } + + /** + * Checks if a block is defined. + * + * @param string $name The block name + * @param array $context An array of parameters to pass to the template + * + * @return bool + */ + public function hasBlock($name, $context = array()) + { + return $this->template->hasBlock($name, $context); + } + + /** + * Returns defined block names in the template. + * + * @param array $context An array of parameters to pass to the template + * + * @return string[] An array of defined template block names + */ + public function getBlockNames($context = array()) + { + return $this->template->getBlockNames($context); + } + + /** + * Renders a template block. + * + * @param string $name The block name to render + * @param array $context An array of parameters to pass to the template + * + * @return string The rendered block + */ + public function renderBlock($name, $context = array()) + { + $context = $this->env->mergeGlobals($context); + $level = ob_get_level(); + ob_start(); + try { + $this->template->displayBlock($name, $context); + } catch (Exception $e) { + while (ob_get_level() > $level) { + ob_end_clean(); + } + + throw $e; + } catch (Throwable $e) { + while (ob_get_level() > $level) { + ob_end_clean(); + } + + throw $e; + } + + return ob_get_clean(); + } + + /** + * Displays a template block. + * + * @param string $name The block name to render + * @param array $context An array of parameters to pass to the template + */ + public function displayBlock($name, $context = array()) + { + $this->template->displayBlock($name, $this->env->mergeGlobals($context)); + } + + /** + * @return Twig_Source + */ + public function getSourceContext() + { + return $this->template->getSourceContext(); + } +} diff --git a/lib/Twig/lib/Twig/Test/IntegrationTestCase.php b/lib/Twig/lib/Twig/Test/IntegrationTestCase.php index 22b772c..cffa3ad 100644 --- a/lib/Twig/lib/Twig/Test/IntegrationTestCase.php +++ b/lib/Twig/lib/Twig/Test/IntegrationTestCase.php @@ -174,7 +174,7 @@ protected function doIntegrationTest($file, $message, $condition, $templates, $e } if ($e instanceof Twig_Error_Syntax) { - $e->setTemplateFile($file); + $e->setTemplateName($file); throw $e; } @@ -192,7 +192,7 @@ protected function doIntegrationTest($file, $message, $condition, $templates, $e } if ($e instanceof Twig_Error_Syntax) { - $e->setTemplateFile($file); + $e->setTemplateName($file); } else { $e = new Twig_Error(sprintf('%s: %s', get_class($e), $e->getMessage()), -1, $file, $e); } @@ -212,8 +212,13 @@ protected function doIntegrationTest($file, $message, $condition, $templates, $e foreach (array_keys($templates) as $name) { echo "Template: $name\n"; - $source = $loader->getSource($name); - echo $twig->compile($twig->parse($twig->tokenize($source, $name))); + $loader = $twig->getLoader(); + if (!$loader instanceof Twig_SourceContextLoaderInterface) { + $source = new Twig_Source($loader->getSource($name), $name); + } else { + $source = $loader->getSourceContext($name); + } + echo $twig->compile($twig->parse($twig->tokenize($source))); } } $this->assertEquals($expected, $output, $message.' (in '.$file.')'); diff --git a/lib/Twig/lib/Twig/Test/NodeTestCase.php b/lib/Twig/lib/Twig/Test/NodeTestCase.php index e591c1d..a6b550c 100644 --- a/lib/Twig/lib/Twig/Test/NodeTestCase.php +++ b/lib/Twig/lib/Twig/Test/NodeTestCase.php @@ -46,6 +46,10 @@ protected function getVariableGetter($name, $line = false) { $line = $line > 0 ? "// line {$line}\n" : ''; + if (PHP_VERSION_ID >= 70000) { + return sprintf('%s($context["%s"] ?? null)', $line, $name, $name); + } + if (PHP_VERSION_ID >= 50400) { return sprintf('%s(isset($context["%s"]) ? $context["%s"] : null)', $line, $name, $name); } diff --git a/lib/Twig/lib/Twig/Token.php b/lib/Twig/lib/Twig/Token.php index a0a029b..3937a6f 100644 --- a/lib/Twig/lib/Twig/Token.php +++ b/lib/Twig/lib/Twig/Token.php @@ -36,8 +36,6 @@ class Twig_Token const INTERPOLATION_END_TYPE = 11; /** - * Constructor. - * * @param int $type The type of the token * @param string $value The token value * @param int $lineno The line position in the source @@ -49,11 +47,6 @@ public function __construct($type, $value, $lineno) $this->lineno = $lineno; } - /** - * Returns a string representation of the token. - * - * @return string A string representation of the token - */ public function __toString() { return sprintf('%s(%s)', self::typeToString($this->type, true), $this->value); @@ -63,9 +56,9 @@ public function __toString() * Tests the current token for a type and/or a value. * * Parameters may be: - * * just type - * * type and value (or array of possible values) - * * just value (or array of possible values) (NAME_TYPE is used as type) + * * just type + * * type and value (or array of possible values) + * * just value (or array of possible values) (NAME_TYPE is used as type) * * @param array|int $type The type to test * @param array|string|null $values The token value @@ -87,9 +80,7 @@ public function test($type, $values = null) } /** - * Gets the line. - * - * @return int The source line + * @return int */ public function getLine() { @@ -97,9 +88,7 @@ public function getLine() } /** - * Gets the token type. - * - * @return int The token type + * @return int */ public function getType() { @@ -107,9 +96,7 @@ public function getType() } /** - * Gets the token value. - * - * @return string The token value + * @return string */ public function getValue() { @@ -174,7 +161,7 @@ public static function typeToString($type, $short = false) } /** - * Returns the english representation of a given type. + * Returns the English representation of a given type. * * @param int $type The type as an integer * diff --git a/lib/Twig/lib/Twig/TokenParser.php b/lib/Twig/lib/Twig/TokenParser.php index fa9b6d8..56d074b 100644 --- a/lib/Twig/lib/Twig/TokenParser.php +++ b/lib/Twig/lib/Twig/TokenParser.php @@ -23,8 +23,6 @@ abstract class Twig_TokenParser implements Twig_TokenParserInterface /** * Sets the parser associated with this token parser. - * - * @param Twig_Parser $parser A Twig_Parser instance */ public function setParser(Twig_Parser $parser) { diff --git a/lib/Twig/lib/Twig/TokenParser/AutoEscape.php b/lib/Twig/lib/Twig/TokenParser/AutoEscape.php index fd34caf..044fad7 100644 --- a/lib/Twig/lib/Twig/TokenParser/AutoEscape.php +++ b/lib/Twig/lib/Twig/TokenParser/AutoEscape.php @@ -39,7 +39,7 @@ public function parse(Twig_Token $token) } else { $expr = $this->parser->getExpressionParser()->parseExpression(); if (!$expr instanceof Twig_Node_Expression_Constant) { - throw new Twig_Error_Syntax('An escaping strategy must be a string or a bool.', $stream->getCurrent()->getLine(), $stream->getFilename()); + throw new Twig_Error_Syntax('An escaping strategy must be a string or a bool.', $stream->getCurrent()->getLine(), $stream->getSourceContext()->getName()); } $value = $expr->getAttribute('value'); @@ -53,7 +53,7 @@ public function parse(Twig_Token $token) @trigger_error('Using the autoescape tag with "true" or "false" before the strategy name is deprecated since version 1.21.', E_USER_DEPRECATED); if (false === $value) { - throw new Twig_Error_Syntax('Unexpected escaping strategy as you set autoescaping to false.', $stream->getCurrent()->getLine(), $stream->getFilename()); + throw new Twig_Error_Syntax('Unexpected escaping strategy as you set autoescaping to false.', $stream->getCurrent()->getLine(), $stream->getSourceContext()->getName()); } $value = $stream->next()->getValue(); diff --git a/lib/Twig/lib/Twig/TokenParser/Block.php b/lib/Twig/lib/Twig/TokenParser/Block.php index 4ffafbe..a2df928 100644 --- a/lib/Twig/lib/Twig/TokenParser/Block.php +++ b/lib/Twig/lib/Twig/TokenParser/Block.php @@ -28,7 +28,7 @@ public function parse(Twig_Token $token) $stream = $this->parser->getStream(); $name = $stream->expect(Twig_Token::NAME_TYPE)->getValue(); if ($this->parser->hasBlock($name)) { - throw new Twig_Error_Syntax(sprintf("The block '%s' has already been defined line %d.", $name, $this->parser->getBlock($name)->getLine()), $stream->getCurrent()->getLine(), $stream->getFilename()); + throw new Twig_Error_Syntax(sprintf("The block '%s' has already been defined line %d.", $name, $this->parser->getBlock($name)->getTemplateLine()), $stream->getCurrent()->getLine(), $stream->getSourceContext()->getName()); } $this->parser->setBlock($name, $block = new Twig_Node_Block($name, new Twig_Node(array()), $lineno)); $this->parser->pushLocalScope(); @@ -40,7 +40,7 @@ public function parse(Twig_Token $token) $value = $token->getValue(); if ($value != $name) { - throw new Twig_Error_Syntax(sprintf('Expected endblock for block "%s" (but "%s" given).', $name, $value), $stream->getCurrent()->getLine(), $stream->getFilename()); + throw new Twig_Error_Syntax(sprintf('Expected endblock for block "%s" (but "%s" given).', $name, $value), $stream->getCurrent()->getLine(), $stream->getSourceContext()->getName()); } } } else { diff --git a/lib/Twig/lib/Twig/TokenParser/Embed.php b/lib/Twig/lib/Twig/TokenParser/Embed.php index 150f433..ddc2602 100644 --- a/lib/Twig/lib/Twig/TokenParser/Embed.php +++ b/lib/Twig/lib/Twig/TokenParser/Embed.php @@ -48,7 +48,7 @@ public function parse(Twig_Token $token) $stream->expect(Twig_Token::BLOCK_END_TYPE); - return new Twig_Node_Embed($module->getAttribute('filename'), $module->getAttribute('index'), $variables, $only, $ignoreMissing, $token->getLine(), $this->getTag()); + return new Twig_Node_Embed($module->getTemplateName(), $module->getAttribute('index'), $variables, $only, $ignoreMissing, $token->getLine(), $this->getTag()); } public function decideBlockEnd(Twig_Token $token) diff --git a/lib/Twig/lib/Twig/TokenParser/Extends.php b/lib/Twig/lib/Twig/TokenParser/Extends.php index 510417a..2918cf5 100644 --- a/lib/Twig/lib/Twig/TokenParser/Extends.php +++ b/lib/Twig/lib/Twig/TokenParser/Extends.php @@ -21,16 +21,18 @@ class Twig_TokenParser_Extends extends Twig_TokenParser { public function parse(Twig_Token $token) { + $stream = $this->parser->getStream(); + if (!$this->parser->isMainScope()) { - throw new Twig_Error_Syntax('Cannot extend from a block.', $token->getLine(), $this->parser->getFilename()); + throw new Twig_Error_Syntax('Cannot extend from a block.', $token->getLine(), $stream->getSourceContext()->getName()); } if (null !== $this->parser->getParent()) { - throw new Twig_Error_Syntax('Multiple extends tags are forbidden.', $token->getLine(), $this->parser->getFilename()); + throw new Twig_Error_Syntax('Multiple extends tags are forbidden.', $token->getLine(), $stream->getSourceContext()->getName()); } $this->parser->setParent($this->parser->getExpressionParser()->parseExpression()); - $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE); + $stream->expect(Twig_Token::BLOCK_END_TYPE); } public function getTag() diff --git a/lib/Twig/lib/Twig/TokenParser/Filter.php b/lib/Twig/lib/Twig/TokenParser/Filter.php index b20dd5b..b735ed4 100644 --- a/lib/Twig/lib/Twig/TokenParser/Filter.php +++ b/lib/Twig/lib/Twig/TokenParser/Filter.php @@ -23,7 +23,7 @@ class Twig_TokenParser_Filter extends Twig_TokenParser public function parse(Twig_Token $token) { $name = $this->parser->getVarName(); - $ref = new Twig_Node_Expression_BlockReference(new Twig_Node_Expression_Constant($name, $token->getLine()), true, $token->getLine(), $this->getTag()); + $ref = new Twig_Node_Expression_BlockReference(new Twig_Node_Expression_Constant($name, $token->getLine()), null, $token->getLine(), $this->getTag()); $filter = $this->parser->getExpressionParser()->parseFilterExpressionRaw($ref, $this->getTag()); $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE); diff --git a/lib/Twig/lib/Twig/TokenParser/For.php b/lib/Twig/lib/Twig/TokenParser/For.php index 3fac511..90553a1 100644 --- a/lib/Twig/lib/Twig/TokenParser/For.php +++ b/lib/Twig/lib/Twig/TokenParser/For.php @@ -48,13 +48,13 @@ public function parse(Twig_Token $token) if (count($targets) > 1) { $keyTarget = $targets->getNode(0); - $keyTarget = new Twig_Node_Expression_AssignName($keyTarget->getAttribute('name'), $keyTarget->getLine()); + $keyTarget = new Twig_Node_Expression_AssignName($keyTarget->getAttribute('name'), $keyTarget->getTemplateLine()); $valueTarget = $targets->getNode(1); - $valueTarget = new Twig_Node_Expression_AssignName($valueTarget->getAttribute('name'), $valueTarget->getLine()); + $valueTarget = new Twig_Node_Expression_AssignName($valueTarget->getAttribute('name'), $valueTarget->getTemplateLine()); } else { $keyTarget = new Twig_Node_Expression_AssignName('_key', $lineno); $valueTarget = $targets->getNode(0); - $valueTarget = new Twig_Node_Expression_AssignName($valueTarget->getAttribute('name'), $valueTarget->getLine()); + $valueTarget = new Twig_Node_Expression_AssignName($valueTarget->getAttribute('name'), $valueTarget->getTemplateLine()); } if ($ifexpr) { @@ -79,7 +79,7 @@ public function decideForEnd(Twig_Token $token) protected function checkLoopUsageCondition(Twig_TokenStream $stream, Twig_NodeInterface $node) { if ($node instanceof Twig_Node_Expression_GetAttr && $node->getNode('node') instanceof Twig_Node_Expression_Name && 'loop' == $node->getNode('node')->getAttribute('name')) { - throw new Twig_Error_Syntax('The "loop" variable cannot be used in a looping condition.', $node->getLine(), $stream->getFilename()); + throw new Twig_Error_Syntax('The "loop" variable cannot be used in a looping condition.', $node->getTemplateLine(), $stream->getSourceContext()->getName()); } foreach ($node as $n) { @@ -98,7 +98,7 @@ protected function checkLoopUsageBody(Twig_TokenStream $stream, Twig_NodeInterfa if ($node instanceof Twig_Node_Expression_GetAttr && $node->getNode('node') instanceof Twig_Node_Expression_Name && 'loop' == $node->getNode('node')->getAttribute('name')) { $attribute = $node->getNode('attribute'); if ($attribute instanceof Twig_Node_Expression_Constant && in_array($attribute->getAttribute('value'), array('length', 'revindex0', 'revindex', 'last'))) { - throw new Twig_Error_Syntax(sprintf('The "loop.%s" variable is not defined when looping with a condition.', $attribute->getAttribute('value')), $node->getLine(), $stream->getFilename()); + throw new Twig_Error_Syntax(sprintf('The "loop.%s" variable is not defined when looping with a condition.', $attribute->getAttribute('value')), $node->getTemplateLine(), $stream->getSourceContext()->getName()); } } diff --git a/lib/Twig/lib/Twig/TokenParser/From.php b/lib/Twig/lib/Twig/TokenParser/From.php index f7547eb..77a6eed 100644 --- a/lib/Twig/lib/Twig/TokenParser/From.php +++ b/lib/Twig/lib/Twig/TokenParser/From.php @@ -46,7 +46,7 @@ public function parse(Twig_Token $token) foreach ($targets as $name => $alias) { if ($this->parser->isReservedMacroName($name)) { - throw new Twig_Error_Syntax(sprintf('"%s" cannot be an imported macro as it is a reserved keyword.', $name), $token->getLine(), $stream->getFilename()); + throw new Twig_Error_Syntax(sprintf('"%s" cannot be an imported macro as it is a reserved keyword.', $name), $token->getLine(), $stream->getSourceContext()->getName()); } $this->parser->addImportedSymbol('function', $alias, 'get'.$name, $node->getNode('var')); diff --git a/lib/Twig/lib/Twig/TokenParser/If.php b/lib/Twig/lib/Twig/TokenParser/If.php index 91c0604..8d8328a 100644 --- a/lib/Twig/lib/Twig/TokenParser/If.php +++ b/lib/Twig/lib/Twig/TokenParser/If.php @@ -56,7 +56,7 @@ public function parse(Twig_Token $token) break; default: - throw new Twig_Error_Syntax(sprintf('Unexpected end of template. Twig was looking for the following tags "else", "elseif", or "endif" to close the "if" block started at line %d).', $lineno), $stream->getCurrent()->getLine(), $stream->getFilename()); + throw new Twig_Error_Syntax(sprintf('Unexpected end of template. Twig was looking for the following tags "else", "elseif", or "endif" to close the "if" block started at line %d).', $lineno), $stream->getCurrent()->getLine(), $stream->getSourceContext()->getName()); } } diff --git a/lib/Twig/lib/Twig/TokenParser/Macro.php b/lib/Twig/lib/Twig/TokenParser/Macro.php index 8a7ebd6..bc975ff 100644 --- a/lib/Twig/lib/Twig/TokenParser/Macro.php +++ b/lib/Twig/lib/Twig/TokenParser/Macro.php @@ -35,7 +35,7 @@ public function parse(Twig_Token $token) $value = $token->getValue(); if ($value != $name) { - throw new Twig_Error_Syntax(sprintf('Expected endmacro for macro "%s" (but "%s" given).', $name, $value), $stream->getCurrent()->getLine(), $stream->getFilename()); + throw new Twig_Error_Syntax(sprintf('Expected endmacro for macro "%s" (but "%s" given).', $name, $value), $stream->getCurrent()->getLine(), $stream->getSourceContext()->getName()); } } $this->parser->popLocalScope(); diff --git a/lib/Twig/lib/Twig/TokenParser/Sandbox.php b/lib/Twig/lib/Twig/TokenParser/Sandbox.php index 1feadd0..4809d16 100644 --- a/lib/Twig/lib/Twig/TokenParser/Sandbox.php +++ b/lib/Twig/lib/Twig/TokenParser/Sandbox.php @@ -24,9 +24,10 @@ class Twig_TokenParser_Sandbox extends Twig_TokenParser { public function parse(Twig_Token $token) { - $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE); + $stream = $this->parser->getStream(); + $stream->expect(Twig_Token::BLOCK_END_TYPE); $body = $this->parser->subparse(array($this, 'decideBlockEnd'), true); - $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE); + $stream->expect(Twig_Token::BLOCK_END_TYPE); // in a sandbox tag, only include tags are allowed if (!$body instanceof Twig_Node_Include) { @@ -36,7 +37,7 @@ public function parse(Twig_Token $token) } if (!$node instanceof Twig_Node_Include) { - throw new Twig_Error_Syntax('Only "include" tags are allowed within a "sandbox" section.', $node->getLine(), $this->parser->getFilename()); + throw new Twig_Error_Syntax('Only "include" tags are allowed within a "sandbox" section.', $node->getTemplateLine(), $stream->getSourceContext()->getName()); } } } diff --git a/lib/Twig/lib/Twig/TokenParser/Set.php b/lib/Twig/lib/Twig/TokenParser/Set.php index 5ca614b..b6e966e 100644 --- a/lib/Twig/lib/Twig/TokenParser/Set.php +++ b/lib/Twig/lib/Twig/TokenParser/Set.php @@ -41,13 +41,13 @@ public function parse(Twig_Token $token) $stream->expect(Twig_Token::BLOCK_END_TYPE); if (count($names) !== count($values)) { - throw new Twig_Error_Syntax('When using set, you must have the same number of variables and assignments.', $stream->getCurrent()->getLine(), $stream->getFilename()); + throw new Twig_Error_Syntax('When using set, you must have the same number of variables and assignments.', $stream->getCurrent()->getLine(), $stream->getSourceContext()->getName()); } } else { $capture = true; if (count($names) > 1) { - throw new Twig_Error_Syntax('When using set with a block, you cannot have a multi-target.', $stream->getCurrent()->getLine(), $stream->getFilename()); + throw new Twig_Error_Syntax('When using set with a block, you cannot have a multi-target.', $stream->getCurrent()->getLine(), $stream->getSourceContext()->getName()); } $stream->expect(Twig_Token::BLOCK_END_TYPE); diff --git a/lib/Twig/lib/Twig/TokenParser/Use.php b/lib/Twig/lib/Twig/TokenParser/Use.php index 4945d03..3129e83 100644 --- a/lib/Twig/lib/Twig/TokenParser/Use.php +++ b/lib/Twig/lib/Twig/TokenParser/Use.php @@ -31,7 +31,7 @@ public function parse(Twig_Token $token) $stream = $this->parser->getStream(); if (!$template instanceof Twig_Node_Expression_Constant) { - throw new Twig_Error_Syntax('The template references in a "use" statement must be a string.', $stream->getCurrent()->getLine(), $stream->getFilename()); + throw new Twig_Error_Syntax('The template references in a "use" statement must be a string.', $stream->getCurrent()->getLine(), $stream->getSourceContext()->getName()); } $targets = array(); diff --git a/lib/Twig/lib/Twig/TokenParser/With.php b/lib/Twig/lib/Twig/TokenParser/With.php new file mode 100644 index 0000000..73f820a --- /dev/null +++ b/lib/Twig/lib/Twig/TokenParser/With.php @@ -0,0 +1,48 @@ + + */ +class Twig_TokenParser_With extends Twig_TokenParser +{ + public function parse(Twig_Token $token) + { + $stream = $this->parser->getStream(); + + $variables = null; + $only = false; + if (!$stream->test(Twig_Token::BLOCK_END_TYPE)) { + $variables = $this->parser->getExpressionParser()->parseExpression(); + $only = $stream->nextIf(Twig_Token::NAME_TYPE, 'only'); + } + + $stream->expect(Twig_Token::BLOCK_END_TYPE); + + $body = $this->parser->subparse(array($this, 'decideWithEnd'), true); + + $stream->expect(Twig_Token::BLOCK_END_TYPE); + + return new Twig_Node_With($body, $variables, $only, $token->getLine(), $this->getTag()); + } + + public function decideWithEnd(Twig_Token $token) + { + return $token->test('endwith'); + } + + public function getTag() + { + return 'with'; + } +} diff --git a/lib/Twig/lib/Twig/TokenParserBroker.php b/lib/Twig/lib/Twig/TokenParserBroker.php index d88bb43..0349a1c 100644 --- a/lib/Twig/lib/Twig/TokenParserBroker.php +++ b/lib/Twig/lib/Twig/TokenParserBroker.php @@ -24,8 +24,6 @@ class Twig_TokenParserBroker implements Twig_TokenParserBrokerInterface protected $brokers = array(); /** - * Constructor. - * * @param array|Traversable $parsers A Traversable of Twig_TokenParserInterface instances * @param array|Traversable $brokers A Traversable of Twig_TokenParserBrokerInterface instances * @param bool $triggerDeprecationError @@ -50,21 +48,11 @@ public function __construct($parsers = array(), $brokers = array(), $triggerDepr } } - /** - * Adds a TokenParser. - * - * @param Twig_TokenParserInterface $parser A Twig_TokenParserInterface instance - */ public function addTokenParser(Twig_TokenParserInterface $parser) { $this->parsers[$parser->getTag()] = $parser; } - /** - * Removes a TokenParser. - * - * @param Twig_TokenParserInterface $parser A Twig_TokenParserInterface instance - */ public function removeTokenParser(Twig_TokenParserInterface $parser) { $name = $parser->getTag(); @@ -73,21 +61,11 @@ public function removeTokenParser(Twig_TokenParserInterface $parser) } } - /** - * Adds a TokenParserBroker. - * - * @param Twig_TokenParserBroker $broker A Twig_TokenParserBroker instance - */ public function addTokenParserBroker(Twig_TokenParserBroker $broker) { $this->brokers[] = $broker; } - /** - * Removes a TokenParserBroker. - * - * @param Twig_TokenParserBroker $broker A Twig_TokenParserBroker instance - */ public function removeTokenParserBroker(Twig_TokenParserBroker $broker) { if (false !== $pos = array_search($broker, $this->brokers)) { diff --git a/lib/Twig/lib/Twig/TokenParserBrokerInterface.php b/lib/Twig/lib/Twig/TokenParserBrokerInterface.php index 3ec2a88..b84179c 100644 --- a/lib/Twig/lib/Twig/TokenParserBrokerInterface.php +++ b/lib/Twig/lib/Twig/TokenParserBrokerInterface.php @@ -26,14 +26,12 @@ interface Twig_TokenParserBrokerInterface * * @param string $tag A tag name * - * @return null|Twig_TokenParserInterface A Twig_TokenParserInterface or null if no suitable TokenParser was found + * @return Twig_TokenParserInterface|null A Twig_TokenParserInterface or null if no suitable TokenParser was found */ public function getTokenParser($tag); /** * Calls Twig_TokenParserInterface::setParser on all parsers the implementation knows of. - * - * @param Twig_ParserInterface $parser A Twig_ParserInterface interface */ public function setParser(Twig_ParserInterface $parser); diff --git a/lib/Twig/lib/Twig/TokenParserInterface.php b/lib/Twig/lib/Twig/TokenParserInterface.php index 12ec396..d35f8ff 100644 --- a/lib/Twig/lib/Twig/TokenParserInterface.php +++ b/lib/Twig/lib/Twig/TokenParserInterface.php @@ -18,17 +18,13 @@ interface Twig_TokenParserInterface { /** * Sets the parser associated with this token parser. - * - * @param Twig_Parser $parser A Twig_Parser instance */ public function setParser(Twig_Parser $parser); /** * Parses a token and returns a node. * - * @param Twig_Token $token A Twig_Token instance - * - * @return Twig_NodeInterface A Twig_NodeInterface instance + * @return Twig_NodeInterface * * @throws Twig_Error_Syntax */ diff --git a/lib/Twig/lib/Twig/TokenStream.php b/lib/Twig/lib/Twig/TokenStream.php index cc6bc80..1b6a7b8 100644 --- a/lib/Twig/lib/Twig/TokenStream.php +++ b/lib/Twig/lib/Twig/TokenStream.php @@ -24,24 +24,27 @@ class Twig_TokenStream private $source; /** - * Constructor. - * - * @param array $tokens An array of tokens - * @param string $filename|null The name of the filename which tokens are associated with - * @param string $source|null The source code associated with the tokens + * @param array $tokens An array of tokens + * @param string|null $name The name of the template which tokens are associated with + * @param string|null $source The source code associated with the tokens */ - public function __construct(array $tokens, $filename = null, $source = null) + public function __construct(array $tokens, $name = null, $source = null) { + if (!$name instanceof Twig_Source) { + if (null !== $name || null !== $source) { + @trigger_error(sprintf('Passing a string as the $name argument of %s() is deprecated since version 1.27. Pass a Twig_Source instance instead.', __METHOD__), E_USER_DEPRECATED); + } + $this->source = new Twig_Source($source, $name); + } else { + $this->source = $name; + } + $this->tokens = $tokens; - $this->filename = $filename; - $this->source = $source ? $source : ''; + + // deprecated, not used anymore, to be removed in 2.0 + $this->filename = $this->source->getName(); } - /** - * Returns a string representation of the token stream. - * - * @return string - */ public function __toString() { return implode("\n", $this->tokens); @@ -60,7 +63,7 @@ public function injectTokens(array $tokens) public function next() { if (!isset($this->tokens[++$this->current])) { - throw new Twig_Error_Syntax('Unexpected end of template.', $this->tokens[$this->current - 1]->getLine(), $this->filename); + throw new Twig_Error_Syntax('Unexpected end of template.', $this->tokens[$this->current - 1]->getLine(), $this->source->getName()); } return $this->tokens[$this->current - 1]; @@ -93,7 +96,7 @@ public function expect($type, $value = null, $message = null) Twig_Token::typeToEnglish($token->getType()), $token->getValue(), Twig_Token::typeToEnglish($type), $value ? sprintf(' with value "%s"', $value) : ''), $line, - $this->filename + $this->source->getName() ); } $this->next(); @@ -111,7 +114,7 @@ public function expect($type, $value = null, $message = null) public function look($number = 1) { if (!isset($this->tokens[$this->current + $number])) { - throw new Twig_Error_Syntax('Unexpected end of template.', $this->tokens[$this->current + $number - 1]->getLine(), $this->filename); + throw new Twig_Error_Syntax('Unexpected end of template.', $this->tokens[$this->current + $number - 1]->getLine(), $this->source->getName()); } return $this->tokens[$this->current + $number]; @@ -138,8 +141,6 @@ public function isEOF() } /** - * Gets the current token. - * * @return Twig_Token */ public function getCurrent() @@ -148,21 +149,43 @@ public function getCurrent() } /** - * Gets the filename associated with this stream (null if not defined). + * Gets the name associated with this stream (null if not defined). * * @return string|null + * + * @deprecated since 1.27 (to be removed in 2.0) */ public function getFilename() { - return $this->filename; + @trigger_error(sprintf('The %s() method is deprecated since version 1.27 and will be removed in 2.0. Use getSourceContext() instead.', __METHOD__), E_USER_DEPRECATED); + + return $this->source->getName(); } /** * Gets the source code associated with this stream. * * @return string + * + * @internal Don't use this as it might be empty depending on the environment configuration + * + * @deprecated since 1.27 (to be removed in 2.0) */ public function getSource() + { + @trigger_error(sprintf('The %s() method is deprecated since version 1.27 and will be removed in 2.0. Use getSourceContext() instead.', __METHOD__), E_USER_DEPRECATED); + + return $this->source->getCode(); + } + + /** + * Gets the source associated with this stream. + * + * @return Twig_Source + * + * @internal + */ + public function getSourceContext() { return $this->source; } diff --git a/lib/Twig/lib/Twig/Util/DeprecationCollector.php b/lib/Twig/lib/Twig/Util/DeprecationCollector.php index e406f0a..850b628 100644 --- a/lib/Twig/lib/Twig/Util/DeprecationCollector.php +++ b/lib/Twig/lib/Twig/Util/DeprecationCollector.php @@ -28,7 +28,7 @@ public function __construct(Twig_Environment $twig) * @param string $dir A directory where templates are stored * @param string $ext Limit the loaded templates by extension * - * @return array() An array of deprecations + * @return array An array of deprecations */ public function collectDir($dir, $ext = '.twig') { @@ -46,7 +46,7 @@ public function collectDir($dir, $ext = '.twig') * * @param Iterator $iterator An iterator of templates (where keys are template names and values the contents of the template) * - * @return array() An array of deprecations + * @return array An array of deprecations */ public function collect(Iterator $iterator) { diff --git a/package.json b/package.json index e603433..fd01683 100644 --- a/package.json +++ b/package.json @@ -4,5 +4,5 @@ "author": "Florens Verschelde ", "license": "MIT", "type": "kirby-plugin", - "version": "2.1.1" + "version": "2.1.2" }